2019-09-06 21:41:43 +03:00
|
|
|
import os
|
|
|
|
|
import pwd
|
|
|
|
|
import subprocess
|
|
|
|
|
|
2023-09-16 01:27:46 -07:00
|
|
|
from dsc.const import DEFAULT_USERNAME
|
2019-09-06 21:41:43 +03:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def setup_uid(uid: int, gid: int):
|
2023-09-16 01:27:46 -07:00
|
|
|
"""
|
|
|
|
|
Set GID and UID of default user so that seafile client creates files with
|
|
|
|
|
correct permissions.
|
|
|
|
|
If GID does not match, create a new group with the given GID.
|
|
|
|
|
Then update UID and GID of default user to match the given ones.
|
|
|
|
|
"""
|
2019-09-06 21:41:43 +03:00
|
|
|
user_pwinfo = pwd.getpwnam(DEFAULT_USERNAME)
|
2023-09-16 01:27:46 -07:00
|
|
|
create_group(gid)
|
2019-09-06 21:41:43 +03:00
|
|
|
if user_pwinfo.pw_uid != uid or user_pwinfo.pw_gid != gid:
|
|
|
|
|
subprocess.call(["usermod", "-o", "-u", str(uid), "-g", str(gid), DEFAULT_USERNAME])
|
|
|
|
|
|
|
|
|
|
|
2023-09-16 01:27:46 -07:00
|
|
|
def create_group(gid: int):
|
|
|
|
|
"""Search for a group with the given GID. If not found, create a new one."""
|
|
|
|
|
if not os.path.exists(f"/etc/group"):
|
|
|
|
|
raise RuntimeError(f"File /etc/group does not exist")
|
|
|
|
|
with open("/etc/group", "r") as f:
|
|
|
|
|
for line in f.readlines():
|
|
|
|
|
cur_gid = line.split(sep=":", maxsplit=3)[2]
|
|
|
|
|
if int(cur_gid) == gid:
|
|
|
|
|
return
|
|
|
|
|
subprocess.call(["groupadd", "-g", str(gid), DEFAULT_USERNAME + "-data"])
|
|
|
|
|
|
|
|
|
|
|
2019-09-06 21:41:43 +03:00
|
|
|
def create_dir(dir_path: str):
|
|
|
|
|
if not os.path.exists(dir_path):
|
|
|
|
|
os.mkdir(dir_path)
|
|
|
|
|
user_pwinfo = pwd.getpwnam(DEFAULT_USERNAME)
|
|
|
|
|
os.chown(dir_path, user_pwinfo.pw_uid, user_pwinfo.pw_gid)
|
|
|
|
|
else:
|
|
|
|
|
if not os.path.isdir(dir_path):
|
|
|
|
|
raise RuntimeError(f"Data dir {dir_path} is not a directory")
|
2023-09-14 16:22:02 -07:00
|
|
|
|
|
|
|
|
|
2026-08-02 16:47:48 -07:00
|
|
|
def user_cmd(argv: list) -> list:
|
|
|
|
|
"""
|
2026-08-03 17:44:34 +00:00
|
|
|
Return a command as-is, having already validated it is a plain argument
|
|
|
|
|
list and never a shell string.
|
2026-08-02 16:47:48 -07:00
|
|
|
|
2026-08-03 17:44:34 +00:00
|
|
|
The process itself runs as the seafile user for its whole life after
|
|
|
|
|
drop_privileges() (see start.py), so subprocess calls no longer need to
|
|
|
|
|
be individually re-targeted with ``runuser``.
|
2026-08-02 16:47:48 -07:00
|
|
|
"""
|
|
|
|
|
if not isinstance(argv, list):
|
|
|
|
|
raise TypeError(f"Command must be a list of arguments, got {type(argv).__name__}")
|
2026-08-03 17:44:34 +00:00
|
|
|
return argv
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def drop_privileges(uid: int, gid: int):
|
|
|
|
|
"""
|
|
|
|
|
Permanently switch the current process to the given UID/GID.
|
|
|
|
|
|
|
|
|
|
Must run after setup_uid()/create_dir(), which need root, and before any
|
|
|
|
|
other code, since everything from here on (the daemon, the RPC socket,
|
|
|
|
|
the sync loop) runs as this user for the rest of the process's life.
|
|
|
|
|
Order matters: supplementary groups and the GID must be set before the
|
|
|
|
|
UID is dropped, since giving up root removes the permission to change
|
|
|
|
|
them.
|
|
|
|
|
"""
|
|
|
|
|
user_pwinfo = pwd.getpwnam(DEFAULT_USERNAME)
|
|
|
|
|
os.initgroups(DEFAULT_USERNAME, gid)
|
|
|
|
|
os.setgid(gid)
|
|
|
|
|
os.setuid(uid)
|
|
|
|
|
os.environ["HOME"] = user_pwinfo.pw_dir
|
|
|
|
|
os.environ["USER"] = DEFAULT_USERNAME
|
|
|
|
|
os.environ["LOGNAME"] = DEFAULT_USERNAME
|
2026-08-02 16:47:48 -07:00
|
|
|
|
|
|
|
|
|
2026-08-02 17:21:46 -07:00
|
|
|
def hide_secrets(cmd: list, *secrets: str) -> list:
|
|
|
|
|
"""Mask every given secret in a command before it is logged."""
|
2023-09-14 16:22:02 -07:00
|
|
|
cmd = cmd.copy()
|
2026-08-02 17:21:46 -07:00
|
|
|
for secret in secrets:
|
|
|
|
|
if not secret:
|
|
|
|
|
continue
|
|
|
|
|
for i, arg in enumerate(cmd):
|
|
|
|
|
if secret in arg:
|
|
|
|
|
cmd[i] = arg.replace(secret, "********")
|
2023-09-14 16:22:02 -07:00
|
|
|
return cmd
|
2026-08-02 17:44:57 -07:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def config_value(value) -> str:
|
|
|
|
|
"""
|
|
|
|
|
Render a setting the way seaf-cli stores it.
|
|
|
|
|
|
|
|
|
|
Booleans are written as "true" and "false": the daemon reads this setting
|
|
|
|
|
with its boolean accessor, and Python's str() would produce "True".
|
|
|
|
|
"""
|
|
|
|
|
if isinstance(value, bool):
|
|
|
|
|
return "true" if value else "false"
|
|
|
|
|
return str(value)
|