mirror of
				https://github.com/9001/copyparty.git
				synced 2025-11-04 05:43:17 +00:00 
			
		
		
		
	restrict runtime-state in $TMP; closes #747
the preferred locations (XDG_CONFIG_HOME and ~/.config) are trusted and will behave as before, because they are only writable by the current unix-user but when an emergency fallback location ($TMPDIR or /tmp) is used because none of the preferred locations are writable, then this will now force-disable sessions-db, idp-db, chpw, and shares this security safeguard can be overridden with --unsafe-state will now also create the config folder with chmod 700 (rwx------)
This commit is contained in:
		@@ -2318,6 +2318,7 @@ buggy feature? rip it out  by setting any of the following environment variables
 | 
			
		||||
| `PRTY_NO_SQLITE`     | disable all database-related functionality (file indexing, metadata indexing, most file deduplication logic) |
 | 
			
		||||
| `PRTY_NO_TLS`        | disable native HTTPS support; if you still want to accept HTTPS connections then TLS must now be terminated by a reverse-proxy |
 | 
			
		||||
| `PRTY_NO_TPOKE`      | disable systemd-tmpfilesd avoider |
 | 
			
		||||
| `PRTY_UNSAFE_STATE`  | allow storing secrets into emergency-fallback locations |
 | 
			
		||||
 | 
			
		||||
example: `PRTY_NO_IFADDR=1 python3 copyparty-sfx.py`
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -112,6 +112,7 @@ class EnvParams(object):
 | 
			
		||||
        self.t0 = time.time()
 | 
			
		||||
        self.mod = ""
 | 
			
		||||
        self.cfg = ""
 | 
			
		||||
        self.scfg = True
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
E = EnvParams()
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ from .__init__ import (
 | 
			
		||||
)
 | 
			
		||||
from .__version__ import CODENAME, S_BUILD_DT, S_VERSION
 | 
			
		||||
from .authsrv import expand_config_file, split_cfg_ln, upgrade_cfg_fmt
 | 
			
		||||
from .bos import bos
 | 
			
		||||
from .cfg import flagcats, onedash
 | 
			
		||||
from .svchub import SvcHub
 | 
			
		||||
from .util import (
 | 
			
		||||
@@ -186,7 +187,7 @@ def init_E(EE: EnvParams) -> None:
 | 
			
		||||
 | 
			
		||||
    E = EE  # pylint: disable=redefined-outer-name
 | 
			
		||||
 | 
			
		||||
    def get_unixdir() -> str:
 | 
			
		||||
    def get_unixdir() -> tuple[str, bool]:
 | 
			
		||||
        paths: list[tuple[Callable[..., Any], str]] = [
 | 
			
		||||
            (os.environ.get, "XDG_CONFIG_HOME"),
 | 
			
		||||
            (os.path.expanduser, "~/.config"),
 | 
			
		||||
@@ -197,6 +198,8 @@ def init_E(EE: EnvParams) -> None:
 | 
			
		||||
        ]
 | 
			
		||||
        errs = []
 | 
			
		||||
        for npath, (pf, pa) in enumerate(paths):
 | 
			
		||||
            priv = npath < 2  # private/trusted location
 | 
			
		||||
            ram = npath > 1  # "nonvolatile"; not semantically same as `not priv`
 | 
			
		||||
            p = ""
 | 
			
		||||
            try:
 | 
			
		||||
                p = pf(pa)
 | 
			
		||||
@@ -206,15 +209,21 @@ def init_E(EE: EnvParams) -> None:
 | 
			
		||||
                p = os.path.normpath(p)
 | 
			
		||||
                mkdir = not os.path.isdir(p)
 | 
			
		||||
                if mkdir:
 | 
			
		||||
                    os.mkdir(p)
 | 
			
		||||
                    os.mkdir(p, 0o700)
 | 
			
		||||
 | 
			
		||||
                p = os.path.join(p, "copyparty")
 | 
			
		||||
                if not priv and os.path.isdir(p):
 | 
			
		||||
                    uid = os.geteuid()
 | 
			
		||||
                    if os.stat(p).st_uid != uid:
 | 
			
		||||
                        p += ".%s" % (uid,)
 | 
			
		||||
                        if os.path.isdir(p) and os.stat(p).st_uid != uid:
 | 
			
		||||
                            raise Exception("filesystem has broken unix permissions")
 | 
			
		||||
                try:
 | 
			
		||||
                    os.listdir(p)
 | 
			
		||||
                except:
 | 
			
		||||
                    os.mkdir(p)
 | 
			
		||||
                    os.mkdir(p, 0o700)
 | 
			
		||||
 | 
			
		||||
                if npath > 1:
 | 
			
		||||
                if ram:
 | 
			
		||||
                    t = "Using %s/copyparty [%s] for config; filekeys/dirkeys will change on every restart. Consider setting XDG_CONFIG_HOME or giving the unix-user a ~/.config/"
 | 
			
		||||
                    errs.append(t % (pa, p))
 | 
			
		||||
                elif mkdir:
 | 
			
		||||
@@ -226,13 +235,14 @@ def init_E(EE: EnvParams) -> None:
 | 
			
		||||
                if errs:
 | 
			
		||||
                    warn(". ".join(errs))
 | 
			
		||||
 | 
			
		||||
                return p  # type: ignore
 | 
			
		||||
                return p, priv
 | 
			
		||||
            except Exception as ex:
 | 
			
		||||
                if p and npath < 2:
 | 
			
		||||
                if p:
 | 
			
		||||
                    t = "Unable to store config in %s [%s] due to %r"
 | 
			
		||||
                    errs.append(t % (pa, p, ex))
 | 
			
		||||
 | 
			
		||||
        raise Exception("could not find a writable path for config")
 | 
			
		||||
        t = "could not find a writable path for runtime state:\n> %s"
 | 
			
		||||
        raise Exception(t % ("\n> ".join(errs)))
 | 
			
		||||
 | 
			
		||||
    E.mod = os.path.dirname(os.path.realpath(__file__))
 | 
			
		||||
    if E.mod.endswith("__init__"):
 | 
			
		||||
@@ -247,7 +257,7 @@ def init_E(EE: EnvParams) -> None:
 | 
			
		||||
        p = os.path.abspath(os.path.realpath(p))
 | 
			
		||||
        p = os.path.join(p, "copyparty")
 | 
			
		||||
        if not os.path.isdir(p):
 | 
			
		||||
            os.mkdir(p)
 | 
			
		||||
            os.mkdir(p, 0o700)
 | 
			
		||||
        os.listdir(p)
 | 
			
		||||
    except:
 | 
			
		||||
        p = ""
 | 
			
		||||
@@ -260,11 +270,11 @@ def init_E(EE: EnvParams) -> None:
 | 
			
		||||
    elif sys.platform == "darwin":
 | 
			
		||||
        E.cfg = os.path.expanduser("~/Library/Preferences/copyparty")
 | 
			
		||||
    else:
 | 
			
		||||
        E.cfg = get_unixdir()
 | 
			
		||||
        E.cfg, E.scfg = get_unixdir()
 | 
			
		||||
 | 
			
		||||
    E.cfg = E.cfg.replace("\\", "/")
 | 
			
		||||
    try:
 | 
			
		||||
        os.makedirs(E.cfg)
 | 
			
		||||
        bos.makedirs(E.cfg, bos.MKD_700)
 | 
			
		||||
    except:
 | 
			
		||||
        if not os.path.isdir(E.cfg):
 | 
			
		||||
            raise
 | 
			
		||||
@@ -1453,6 +1463,7 @@ def add_yolo(ap):
 | 
			
		||||
    ap2.add_argument("--no-fnugg", action="store_true", help="disable the smoketest for caching-related issues in the web-UI")
 | 
			
		||||
    ap2.add_argument("--getmod", action="store_true", help="permit ?move=[...] and ?delete as GET")
 | 
			
		||||
    ap2.add_argument("--wo-up-readme", action="store_true", help="allow users with write-only access to upload logues and readmes without adding the _wo_ filename prefix (volflag=wo_up_readme)")
 | 
			
		||||
    ap2.add_argument("--unsafe-state", action="store_true", help="when one of the emergency fallback locations are used for runtime state ($TMPDIR, /tmp), certain features will be force-disabled for security reasons by default. This option overrides that safeguard and allows unsafe storage of secrets")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def add_optouts(ap):
 | 
			
		||||
 
 | 
			
		||||
@@ -976,6 +976,24 @@ class SvcHub(object):
 | 
			
		||||
                    t = "WARNING:\nDisabling WebDAV support because dxml selftest failed. Please report this bug;\n%s\n...and include the following information in the bug-report:\n%s | expat %s\n"
 | 
			
		||||
                    self.log("root", t % (URL_BUG, VERSIONS, expat_ver()), 1)
 | 
			
		||||
 | 
			
		||||
        if not E.scfg and not al.unsafe_state and not os.getenv("PRTY_UNSAFE_STATE"):
 | 
			
		||||
            t = "because runtime config is currently being stored in an untrusted emergency-fallback location. Please fix your environment so either XDG_CONFIG_HOME or ~/.config can be used instead, or disable this safeguard with --unsafe-state or env-var PRTY_UNSAFE_STATE=1."
 | 
			
		||||
            if not al.no_ses:
 | 
			
		||||
                al.no_ses = True
 | 
			
		||||
                t2 = "A consequence of this misconfiguration is that passwords will now be sent in the HTTP-header of every request!"
 | 
			
		||||
                self.log("root", "WARNING:\nWill disable sessions %s %s" % (t, t2), 1)
 | 
			
		||||
            if al.idp_store == 1:
 | 
			
		||||
                al.idp_store = 0
 | 
			
		||||
                self.log("root", "WARNING:\nDisabling --idp-store %s" % (t,), 3)
 | 
			
		||||
            if al.idp_store:
 | 
			
		||||
                t2 = "ERROR: Cannot enable --idp-store %s" % (t,)
 | 
			
		||||
                self.log("root", t2, 1)
 | 
			
		||||
                raise Exception(t2)
 | 
			
		||||
            if al.shr:
 | 
			
		||||
                t2 = "ERROR: Cannot enable shares %s" % (t,)
 | 
			
		||||
                self.log("root", t2, 1)
 | 
			
		||||
                raise Exception(t2)
 | 
			
		||||
 | 
			
		||||
    def _process_config(self) -> bool:
 | 
			
		||||
        al = self.args
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user