improve smoketests, warnings and error-messages:

* docker: warn if there are config-files in ~/.config/copyparty
   because somebody copied their config into
   /cfg/copyparty instead of /cfg as intended

* docker: warn if there are no config-files in an included directory

* make misconfigured reverse-proxies more obvious
  * explain cors rejections in server log
  * indicate cors rejection in error toast
This commit is contained in:
ed 2024-03-07 19:47:38 +00:00
parent 8ca996e2f7
commit d744f3ff8f
5 changed files with 60 additions and 19 deletions

View file

@ -395,7 +395,7 @@ def configure_ssl_ciphers(al: argparse.Namespace) -> None:
def args_from_cfg(cfg_path: str) -> list[str]: def args_from_cfg(cfg_path: str) -> list[str]:
lines: list[str] = [] lines: list[str] = []
expand_config_file(lines, cfg_path, "") expand_config_file(None, lines, cfg_path, "")
lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "") lines = upgrade_cfg_fmt(None, argparse.Namespace(vc=False), lines, "")
ret: list[str] = [] ret: list[str] = []

View file

@ -863,7 +863,7 @@ class AuthSrv(object):
) -> None: ) -> None:
self.line_ctr = 0 self.line_ctr = 0
expand_config_file(cfg_lines, fp, "") expand_config_file(self.log, cfg_lines, fp, "")
if self.args.vc: if self.args.vc:
lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)] lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)]
self.log("expanded config file (unprocessed):\n" + "\n".join(lns)) self.log("expanded config file (unprocessed):\n" + "\n".join(lns))
@ -2101,27 +2101,47 @@ def split_cfg_ln(ln: str) -> dict[str, Any]:
return ret return ret
def expand_config_file(ret: list[str], fp: str, ipath: str) -> None: def expand_config_file(log: Optional["NamedLogger"], ret: list[str], fp: str, ipath: str) -> None:
"""expand all % file includes""" """expand all % file includes"""
fp = absreal(fp) fp = absreal(fp)
if len(ipath.split(" -> ")) > 64: if len(ipath.split(" -> ")) > 64:
raise Exception("hit max depth of 64 includes") raise Exception("hit max depth of 64 includes")
if os.path.isdir(fp): if os.path.isdir(fp):
names = os.listdir(fp) names = list(sorted(os.listdir(fp)))
crumb = "#\033[36m cfg files in {} => {}\033[0m".format(fp, names) cnames = [x for x in names if x.lower().endswith(".conf")]
ret.append(crumb) if not cnames:
for fn in sorted(names): t = "warning: tried to read config-files from folder '%s' but it does not contain any "
if names:
t += ".conf files; the following files were ignored: %s"
t = t % (fp, ", ".join(names[:8]))
else:
t += "files at all"
t = t % (fp,)
if log:
log(t, 3)
ret.append("#\033[33m %s\033[0m" % (t,))
else:
zs = "#\033[36m cfg files in %s => %s\033[0m" % (fp, cnames)
ret.append(zs)
for fn in cnames:
fp2 = os.path.join(fp, fn) fp2 = os.path.join(fp, fn)
if not fp2.endswith(".conf") or fp2 in ipath: if fp2 in ipath:
continue continue
expand_config_file(ret, fp2, ipath) expand_config_file(log, ret, fp2, ipath)
if ret[-1] == crumb: return
# no config files below; remove breadcrumb
ret.pop()
if not os.path.exists(fp):
t = "warning: tried to read config from '%s' but the file/folder does not exist" % (fp,)
if log:
log(t, 3)
ret.append("#\033[31m %s\033[0m" % (t,))
return return
ipath += " -> " + fp ipath += " -> " + fp
@ -2135,7 +2155,7 @@ def expand_config_file(ret: list[str], fp: str, ipath: str) -> None:
fp2 = ln[1:].strip() fp2 = ln[1:].strip()
fp2 = os.path.join(os.path.dirname(fp), fp2) fp2 = os.path.join(os.path.dirname(fp), fp2)
ofs = len(ret) ofs = len(ret)
expand_config_file(ret, fp2, ipath) expand_config_file(log, ret, fp2, ipath)
for n in range(ofs, len(ret)): for n in range(ofs, len(ret)):
ret[n] = pad + ret[n] ret[n] = pad + ret[n]
continue continue

View file

@ -518,9 +518,13 @@ class HttpCli(object):
return self.handle_options() and self.keepalive return self.handle_options() and self.keepalive
if not cors_k: if not cors_k:
host = self.headers.get("host", "<?>")
origin = self.headers.get("origin", "<?>") origin = self.headers.get("origin", "<?>")
self.log("cors-reject {} from {}".format(self.mode, origin), 3) proto = "https://" if self.is_https else "http://"
raise Pebkac(403, "no surfing") guess = "modifying" if (origin and host) else "stripping"
t = "cors-reject %s because request-header Origin='%s' does not match request-protocol '%s' and host '%s' based on request-header Host='%s' (note: if this request is not malicious, check if your reverse-proxy is accidentally %s request headers, in particular 'Origin', for example by running copyparty with --ihead='*' to show all request headers)"
self.log(t % (self.mode, origin, proto, self.host, host, guess), 3)
raise Pebkac(403, "rejected by cors-check")
# getattr(self.mode) is not yet faster than this # getattr(self.mode) is not yet faster than this
if self.mode == "POST": if self.mode == "POST":

View file

@ -28,7 +28,7 @@ if True: # pylint: disable=using-constant-test
import typing import typing
from typing import Any, Optional, Union from typing import Any, Optional, Union
from .__init__ import ANYWIN, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode from .__init__ import ANYWIN, E, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode
from .authsrv import BAD_CFG, AuthSrv from .authsrv import BAD_CFG, AuthSrv
from .cert import ensure_cert from .cert import ensure_cert
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
@ -154,6 +154,8 @@ class SvcHub(object):
lg.handlers = [lh] lg.handlers = [lh]
lg.setLevel(logging.DEBUG) lg.setLevel(logging.DEBUG)
self._check_env()
if args.stackmon: if args.stackmon:
start_stackmon(args.stackmon, 0) start_stackmon(args.stackmon, 0)
@ -385,6 +387,17 @@ class SvcHub(object):
Daemon(self.sd_notify, "sd-notify") Daemon(self.sd_notify, "sd-notify")
def _check_env(self) -> None:
try:
files = os.listdir(E.cfg)
except:
files = []
hits = [x for x in files if x.lower().endswith(".conf")]
if hits:
t = "WARNING: found config files in [%s]: %s\n config files are not expected here, and will NOT be loaded (unless your setup is intentionally hella funky)"
self.log("root", t % (E.cfg, ", ".join(hits)), 3)
def _process_config(self) -> bool: def _process_config(self) -> bool:
al = self.args al = self.args

View file

@ -1995,15 +1995,19 @@ function xhrchk(xhr, prefix, e404, lvl, tag) {
if (tag === undefined) if (tag === undefined)
tag = prefix; tag = prefix;
var errtxt = (xhr.response && xhr.response.err) || xhr.responseText, var errtxt = ((xhr.response && xhr.response.err) || xhr.responseText) || '',
suf = '',
fun = toast[lvl || 'err'], fun = toast[lvl || 'err'],
is_cf = /[Cc]loud[f]lare|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser|\/chall[e]nge-platform|"chall[e]nge-error|nable Ja[v]aScript and cook/.test(errtxt); is_cf = /[Cc]loud[f]lare|>Just a mo[m]ent|#cf-b[u]bbles|Chec[k]ing your br[o]wser|\/chall[e]nge-platform|"chall[e]nge-error|nable Ja[v]aScript and cook/.test(errtxt);
if (errtxt.startsWith('<pre>'))
suf = '\n\nerror-details: «' + errtxt.slice(5).split('\n')[0].trim() + '»';
if (xhr.status == 403 && !is_cf) if (xhr.status == 403 && !is_cf)
return toast.err(0, prefix + (L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out"), tag); return toast.err(0, prefix + (L && L.xhr403 || "403: access denied\n\ntry pressing F5, maybe you got logged out") + suf, tag);
if (xhr.status == 404) if (xhr.status == 404)
return toast.err(0, prefix + e404, tag); return toast.err(0, prefix + e404 + suf, tag);
if (is_cf && (xhr.status == 403 || xhr.status == 503)) { if (is_cf && (xhr.status == 403 || xhr.status == 503)) {
var now = Date.now(), td = now - cf_cha_t; var now = Date.now(), td = now - cf_cha_t;