diff --git a/copyparty/__main__.py b/copyparty/__main__.py index e8c95884..1c1e664f 100644 --- a/copyparty/__main__.py +++ b/copyparty/__main__.py @@ -1727,6 +1727,8 @@ def add_safety(ap): ap2.add_argument("--loris", metavar="B", type=int, default=60, help="if a client maxes out the server connection limit without sending headers, ban it for \033[33mB\033[0m minutes; disable with [\033[32m0\033[0m]") ap2.add_argument("--acao", metavar="V[,V]", type=u, default="*", help="Access-Control-Allow-Origin; list of origins (domains/IPs without port) to accept requests from; [\033[32mhttps://1.2.3.4\033[0m]. Default [\033[32m*\033[0m] allows requests from all sites but removes cookies and http-auth; only ?pw=hunter2 survives") ap2.add_argument("--acam", metavar="V[,V]", type=u, default="GET,HEAD", help="Access-Control-Allow-Methods; list of methods to accept from offsite ('*' behaves like \033[33m--acao\033[0m's description)") + if FULL_HELP or ANYWIN: + ap2.add_argument("--unsafe-tools", action="store_true", help="windows-only: allow running programs (ffmpeg, dcraw_emu, ...) when their location is inside a folder that can be uploaded to (dangerous due to DLL-hijacking)") if FULL_HELP or (not ANYWIN and not UNIX): ap2.add_argument("--th-bwrap", metavar="CMD", type=u, default=th_bwrap, help="optional bwrap sandbox command for FFmpeg and dcraw (Linux-only)") ap2.add_argument("--use-bwrap", metavar="TXT", type=u, default="n", help="a/n/f; [\033[32ma\033[0m]=auto (yes if the program 'bwrap' exists (assumes it works)), [\033[32mn\033[0m]=no (assumes bwrap is broken), [\033[32mf\033[0m]=force (disables FFmpeg if bwrap unavailable)") diff --git a/copyparty/mtag.py b/copyparty/mtag.py index 5fb8fd97..2b1f09fd 100644 --- a/copyparty/mtag.py +++ b/copyparty/mtag.py @@ -17,7 +17,9 @@ from .util import ( FFMPEG_URL, LOG, REKOBO_LKEY, + SCWD, VF_CAREFUL, + absreal, expand_osenv_c, fsenc, gzip, @@ -47,10 +49,10 @@ except: HAVE_MUTAGEN = False -def have_ff(name: str) -> bytes: +def have_ff(name: str) -> list[bytes]: uname = name.upper() if os.environ.get("PRTY_NO_" + uname): - return b"" + return [] ebin = os.environ.get("PRTY_%s_BIN" % (uname,)) try: @@ -66,11 +68,12 @@ def have_ff(name: str) -> bytes: bcmd = scmd.encode("utf-8") try: sp.Popen([bcmd, b"-version"], stdout=sp.PIPE, stderr=sp.PIPE).communicate() - return bcmd + return [bcmd] except: - return b"" + return [] else: - return (shutil.which(scmd) or "").encode("utf-8") + ret = shutil.which(scmd) + return [absreal(ret).encode("utf-8")] if ret else [] HAVE_FFMPEG = have_ff("ffmpeg") @@ -247,14 +250,14 @@ def ffprobe( ) -> tuple[dict[str, tuple[int, Any]], dict[str, list[Any]], list[Any], dict[str, Any]]: # ffprobe -hide_banner -show_streams -show_format -- bap = fsenc(abspath) - cmd = bwrap(HAVE_FFPROBE, bap, b"") + [ + cmd = bwrap(HAVE_FFPROBE[0], bap, b"") + [ b"-hide_banner", b"-show_streams", b"-show_format", b"--", bap, ] - rc, so, se = runcmd(cmd, timeout=timeout, nice=True, oom=200) + rc, so, se = runcmd(cmd, cwd=SCWD, timeout=timeout, nice=True, oom=200) if rc and TH_BWRAP: bwrap_fail(se) retchk(rc, cmd, se) diff --git a/copyparty/svchub.py b/copyparty/svchub.py index d670c6e8..092309d0 100644 --- a/copyparty/svchub.py +++ b/copyparty/svchub.py @@ -81,6 +81,7 @@ from .util import ( alltrace, build_netmap, expat_ver, + fsenc, gzip, html_escape, load_ipr, @@ -400,6 +401,9 @@ class SvcHub(object): self.asrv = AuthSrv(self.args, self.log, dargs=self.dargs) ramdisk_chk(self.asrv) + if ANYWIN and not self.args.unsafe_tools: + self._check_toolpaths() + if args.cgen: self.asrv.cgen() @@ -1025,6 +1029,18 @@ class SvcHub(object): if zb: Daemon(self.s6_notify, "s6-notify", (zb,)) + def _check_toolpaths(self) -> None: + for vol in self.asrv.vfs.all_vols.values(): + if not vol.realpath or not vol.axs.uwrite: + continue + ap_vol = (vol.realpath + os.sep).encode("utf-8") + for zsl in (HAVE_FFMPEG, HAVE_FFPROBE, HAVE_DCRAW): + if zsl and zsl[0].startswith(ap_vol): + zs = zsl[0].decode("utf-8", "replace") + t = "will not use [%s] because it is inside a writable volume [/%s] => [%s] and --unsafe-tools is not enabled" + self.log("root", t % (zs, vol.vpath, vol.realpath), 3) + del zsl[0] + def _feature_test(self) -> None: fok = [] fng = [] diff --git a/copyparty/th_srv.py b/copyparty/th_srv.py index 8dc59f33..6e51a70b 100644 --- a/copyparty/th_srv.py +++ b/copyparty/th_srv.py @@ -31,6 +31,7 @@ from .mtag import ( from .util import BytesIO # type: ignore from .util import ( FFMPEG_URL, + SCWD, VF_CAREFUL, Cooldown, Daemon, @@ -774,7 +775,7 @@ class ThumbSrv(object): self.wait4ram(0.6, tpath) bap = fsenc(abspath) # fmt: off - cmd = bwrap(HAVE_DCRAW, bap, b"") + [ + cmd = bwrap(HAVE_DCRAW[0], bap, b"") + [ b"-h", # halfsize b"-o", b"1", # srgb b"-s", b"0", # first frame @@ -782,7 +783,7 @@ class ThumbSrv(object): bap, ] # fmt: on - p = sp.Popen(cmd, stdout=sp.PIPE) + p = sp.Popen(cmd, stdout=sp.PIPE, cwd=SCWD) try: if HAVE_PIL: self.conv_image_pil(Image.open(p.stdout), tpath, fmt, vn) @@ -877,7 +878,7 @@ class ThumbSrv(object): bap_in = fsenc(abspath) bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner" @@ -918,7 +919,7 @@ class ThumbSrv(object): def _run_ff(self, cmd: list[bytes], vn: VFS, kto: str, oom: int = 400) -> None: # self.log((b" ".join(cmd)).decode("utf-8")) - ret, _, serr = runcmd(cmd, timeout=vn.flags[kto], nice=True, oom=oom) + ret, _, serr = runcmd(cmd, cwd=SCWD, timeout=vn.flags[kto], nice=True, oom=oom) if not ret: return @@ -1020,7 +1021,7 @@ class ThumbSrv(object): bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1101,7 +1102,7 @@ class ThumbSrv(object): bap_out = fsenc(infile) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1135,7 +1136,7 @@ class ThumbSrv(object): bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1173,7 +1174,7 @@ class ThumbSrv(object): # to not support opus then it's probably also super picky # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1204,7 +1205,7 @@ class ThumbSrv(object): bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1241,7 +1242,7 @@ class ThumbSrv(object): bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1304,7 +1305,7 @@ class ThumbSrv(object): bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1347,7 +1348,7 @@ class ThumbSrv(object): bap_out = fsenc(tmp_opus) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1374,7 +1375,7 @@ class ThumbSrv(object): bap_in = fsenc(abspath) bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", @@ -1396,7 +1397,7 @@ class ThumbSrv(object): bap_in = fsenc(tmp_opus) bap_out = fsenc(tpath) # fmt: off - cmd = bwrap(HAVE_FFMPEG, bap_in, bap_out) + [ + cmd = bwrap(HAVE_FFMPEG[0], bap_in, bap_out) + [ b"-nostdin", b"-v", b"error", b"-hide_banner", diff --git a/copyparty/util.py b/copyparty/util.py index ebd69ffc..8cd45e4a 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -678,6 +678,12 @@ except: HAVE_BWRAP = "" +if ANYWIN: + SCWD = os.environ["systemroot"] +else: + SCWD = None + + def py_desc() -> str: interp = platform.python_implementation() py_ver = ".".join([str(x) for x in sys.version_info])