copyparty 1.10.2__py3-none-any.whl → 1.11.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- copyparty/__main__.py +16 -6
- copyparty/__version__.py +3 -3
- copyparty/authsrv.py +365 -66
- copyparty/cfg.py +3 -0
- copyparty/ftpd.py +2 -2
- copyparty/httpcli.py +113 -48
- copyparty/httpconn.py +4 -1
- copyparty/httpsrv.py +8 -3
- copyparty/metrics.py +3 -0
- copyparty/multicast.py +1 -1
- copyparty/smbd.py +1 -1
- copyparty/svchub.py +55 -14
- copyparty/tftpd.py +11 -9
- copyparty/up2k.py +143 -49
- copyparty/util.py +52 -8
- copyparty/web/baguettebox.js.gz +0 -0
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.html +1 -1
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/browser2.html +1 -1
- copyparty/web/deps/easymde.css.gz +0 -0
- copyparty/web/deps/easymde.js.gz +0 -0
- copyparty/web/md.css.gz +0 -0
- copyparty/web/md.html +1 -1
- copyparty/web/md.js.gz +0 -0
- copyparty/web/md2.css.gz +0 -0
- copyparty/web/md2.js.gz +0 -0
- copyparty/web/mde.css.gz +0 -0
- copyparty/web/mde.html +1 -1
- copyparty/web/mde.js.gz +0 -0
- copyparty/web/msg.css.gz +0 -0
- copyparty/web/msg.html +1 -1
- copyparty/web/splash.css.gz +0 -0
- copyparty/web/splash.html +4 -2
- copyparty/web/splash.js.gz +0 -0
- copyparty/web/svcs.html +1 -1
- copyparty/web/ui.css.gz +0 -0
- copyparty/web/up2k.js.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.10.2.dist-info → copyparty-1.11.1.dist-info}/METADATA +62 -20
- {copyparty-1.10.2.dist-info → copyparty-1.11.1.dist-info}/RECORD +45 -43
- {copyparty-1.10.2.dist-info → copyparty-1.11.1.dist-info}/WHEEL +1 -1
- {copyparty-1.10.2.dist-info → copyparty-1.11.1.dist-info}/LICENSE +0 -0
- {copyparty-1.10.2.dist-info → copyparty-1.11.1.dist-info}/entry_points.txt +0 -0
- {copyparty-1.10.2.dist-info → copyparty-1.11.1.dist-info}/top_level.txt +0 -0
copyparty/cfg.py
CHANGED
@@ -66,6 +66,7 @@ def vf_vmap() :
|
|
66
66
|
"rm_retry",
|
67
67
|
"sort",
|
68
68
|
"unlist",
|
69
|
+
"u2abort",
|
69
70
|
"u2ts",
|
70
71
|
):
|
71
72
|
ret[k] = k
|
@@ -116,6 +117,7 @@ flagcats = {
|
|
116
117
|
"hardlink": "does dedup with hardlinks instead of symlinks",
|
117
118
|
"neversymlink": "disables symlink fallback; full copy instead",
|
118
119
|
"copydupes": "disables dedup, always saves full copies of dupes",
|
120
|
+
"sparse": "force use of sparse files, mainly for s3-backed storage",
|
119
121
|
"daw": "enable full WebDAV write support (dangerous);\nPUT-operations will now \033[1;31mOVERWRITE\033[0;35m existing files",
|
120
122
|
"nosub": "forces all uploads into the top folder of the vfs",
|
121
123
|
"magic": "enables filetype detection for nameless uploads",
|
@@ -130,6 +132,7 @@ flagcats = {
|
|
130
132
|
"rand": "force randomized filenames, 9 chars long by default",
|
131
133
|
"nrand=N": "randomized filenames are N chars long",
|
132
134
|
"u2ts=fc": "[f]orce [c]lient-last-modified or [u]pload-time",
|
135
|
+
"u2abort=1": "allow aborting unfinished uploads? 0=no 1=strict 2=ip-chk 3=acct-chk",
|
133
136
|
"sz=1k-3m": "allow filesizes between 1 KiB and 3MiB",
|
134
137
|
"df=1g": "ensure 1 GiB free disk space",
|
135
138
|
},
|
copyparty/ftpd.py
CHANGED
@@ -295,7 +295,7 @@ class FtpFs(AbstractedFS):
|
|
295
295
|
|
296
296
|
vp = join(self.cwd, path).lstrip("/")
|
297
297
|
try:
|
298
|
-
self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], [], False)
|
298
|
+
self.hub.up2k.handle_rm(self.uname, self.h.cli_ip, [vp], [], False, False)
|
299
299
|
except Exception as ex:
|
300
300
|
raise FSE(str(ex))
|
301
301
|
|
@@ -405,7 +405,7 @@ class FtpHandler(FTPHandler):
|
|
405
405
|
if cip.startswith("::ffff:"):
|
406
406
|
cip = cip[7:]
|
407
407
|
|
408
|
-
if self.args.
|
408
|
+
if self.args.ftp_ipa_nm and not self.args.ftp_ipa_nm.map(cip):
|
409
409
|
logging.warning("client rejected (--ftp-ipa): %s", cip)
|
410
410
|
self.connected = False
|
411
411
|
conn.close()
|
copyparty/httpcli.py
CHANGED
@@ -166,16 +166,12 @@ class HttpCli(object):
|
|
166
166
|
self.can_dot = False
|
167
167
|
self.out_headerlist = []
|
168
168
|
self.out_headers = {}
|
169
|
-
self.html_head = " "
|
170
169
|
# post
|
171
170
|
self.parser = None
|
172
171
|
# end placeholders
|
173
172
|
|
174
173
|
self.bufsz = 1024 * 32
|
175
|
-
|
176
|
-
if self.args.no_robots:
|
177
|
-
h = META_NOBOTS + (("\n" + h) if h else "")
|
178
|
-
self.html_head = h
|
174
|
+
self.html_head = ""
|
179
175
|
|
180
176
|
def log(self, msg , c = 0) :
|
181
177
|
ptn = self.asrv.re_pwd
|
@@ -227,13 +223,11 @@ class HttpCli(object):
|
|
227
223
|
"Vary": "Origin, PW, Cookie",
|
228
224
|
"Cache-Control": "no-store, max-age=0",
|
229
225
|
}
|
230
|
-
if self.args.no_robots:
|
231
|
-
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
232
226
|
|
233
|
-
if self.is_banned():
|
227
|
+
if self.args.early_ban and self.is_banned():
|
234
228
|
return False
|
235
229
|
|
236
|
-
if self.
|
230
|
+
if self.conn.ipa_nm and not self.conn.ipa_nm.map(self.conn.addr[0]):
|
237
231
|
self.log("client rejected (--ipa)", 3)
|
238
232
|
self.terse_reply(b"", 500)
|
239
233
|
return False
|
@@ -296,6 +290,7 @@ class HttpCli(object):
|
|
296
290
|
zs = "%s:%s" % self.s.getsockname()[:2]
|
297
291
|
self.host = zs[7:] if zs.startswith("::ffff:") else zs
|
298
292
|
|
293
|
+
trusted_xff = False
|
299
294
|
n = self.args.rproxy
|
300
295
|
if n:
|
301
296
|
zso = self.headers.get(self.args.xff_hdr)
|
@@ -312,21 +307,26 @@ class HttpCli(object):
|
|
312
307
|
self.log(t.format(self.args.rproxy, zso), c=3)
|
313
308
|
|
314
309
|
pip = self.conn.addr[0]
|
315
|
-
|
316
|
-
|
310
|
+
xffs = self.conn.xff_nm
|
311
|
+
if xffs and not xffs.map(pip):
|
312
|
+
t = 'got header "%s" from untrusted source "%s" claiming the true client ip is "%s" (raw value: "%s"); if you trust this, you must allowlist this proxy with "--xff-src=%s"%s'
|
317
313
|
if self.headers.get("cf-connecting-ip"):
|
318
|
-
t +=
|
314
|
+
t += ' Note: if you are behind cloudflare, then this default header is not a good choice; please first make sure your local reverse-proxy (if any) does not allow non-cloudflare IPs from providing cf-* headers, and then add this additional global setting: "--xff-hdr=cf-connecting-ip"'
|
315
|
+
else:
|
316
|
+
t += ' Note: depending on your reverse-proxy, and/or WAF, and/or other intermediates, you may want to read the true client IP from another header by also specifying "--xff-hdr=SomeOtherHeader"'
|
319
317
|
zs = (
|
320
318
|
".".join(pip.split(".")[:2]) + "."
|
321
319
|
if "." in pip
|
322
320
|
else ":".join(pip.split(":")[:4]) + ":"
|
323
|
-
)
|
324
|
-
|
321
|
+
) + "0.0/16"
|
322
|
+
zs2 = ' or "--xff-src=lan"' if self.conn.xff_lan.map(pip) else ""
|
323
|
+
self.log(t % (self.args.xff_hdr, pip, cli_ip, zso, zs, zs2), 3)
|
325
324
|
else:
|
326
325
|
self.ip = cli_ip
|
327
326
|
self.is_vproxied = bool(self.args.R)
|
328
327
|
self.log_src = self.conn.set_rproxy(self.ip)
|
329
328
|
self.host = self.headers.get("x-forwarded-host") or self.host
|
329
|
+
trusted_xff = True
|
330
330
|
|
331
331
|
if self.is_banned():
|
332
332
|
return False
|
@@ -454,9 +454,56 @@ class HttpCli(object):
|
|
454
454
|
|
455
455
|
if self.args.idp_h_usr:
|
456
456
|
self.pw = ""
|
457
|
-
|
458
|
-
if
|
459
|
-
|
457
|
+
idp_usr = self.headers.get(self.args.idp_h_usr) or ""
|
458
|
+
if idp_usr:
|
459
|
+
idp_grp = (
|
460
|
+
self.headers.get(self.args.idp_h_grp) or ""
|
461
|
+
if self.args.idp_h_grp
|
462
|
+
else ""
|
463
|
+
)
|
464
|
+
|
465
|
+
if not trusted_xff:
|
466
|
+
pip = self.conn.addr[0]
|
467
|
+
xffs = self.conn.xff_nm
|
468
|
+
trusted_xff = xffs and xffs.map(pip)
|
469
|
+
|
470
|
+
trusted_key = (
|
471
|
+
not self.args.idp_h_key
|
472
|
+
) or self.args.idp_h_key in self.headers
|
473
|
+
|
474
|
+
if trusted_key and trusted_xff:
|
475
|
+
self.asrv.idp_checkin(self.conn.hsrv.broker, idp_usr, idp_grp)
|
476
|
+
else:
|
477
|
+
if not trusted_key:
|
478
|
+
t = 'the idp-h-key header ("%s") is not present in the request; will NOT trust the other headers saying that the client\'s username is "%s" and group is "%s"'
|
479
|
+
self.log(t % (self.args.idp_h_key, idp_usr, idp_grp), 3)
|
480
|
+
|
481
|
+
if not trusted_xff:
|
482
|
+
t = 'got IdP headers from untrusted source "%s" claiming the client\'s username is "%s" and group is "%s"; if you trust this, you must allowlist this proxy with "--xff-src=%s"%s'
|
483
|
+
if not self.args.idp_h_key:
|
484
|
+
t += " Note: you probably also want to specify --idp-h-key <SECRET-HEADER-NAME> for additional security"
|
485
|
+
|
486
|
+
pip = self.conn.addr[0]
|
487
|
+
zs = (
|
488
|
+
".".join(pip.split(".")[:2]) + "."
|
489
|
+
if "." in pip
|
490
|
+
else ":".join(pip.split(":")[:4]) + ":"
|
491
|
+
) + "0.0/16"
|
492
|
+
zs2 = (
|
493
|
+
' or "--xff-src=lan"' if self.conn.xff_lan.map(pip) else ""
|
494
|
+
)
|
495
|
+
self.log(t % (pip, idp_usr, idp_grp, zs, zs2), 3)
|
496
|
+
|
497
|
+
idp_usr = "*"
|
498
|
+
idp_grp = ""
|
499
|
+
|
500
|
+
if idp_usr in self.asrv.vfs.aread:
|
501
|
+
self.uname = idp_usr
|
502
|
+
self.html_head += "<script>var is_idp=1</script>\n"
|
503
|
+
else:
|
504
|
+
self.log("unknown username: [%s]" % (idp_usr), 1)
|
505
|
+
self.uname = "*"
|
506
|
+
else:
|
460
507
|
self.uname = "*"
|
461
508
|
else:
|
462
509
|
self.pw = uparam.get("pw") or self.headers.get("pw") or bauth or cookie_pw
|
@@ -506,6 +553,10 @@ class HttpCli(object):
|
|
506
553
|
|
507
554
|
self.s.settimeout(self.args.s_tbody or None)
|
508
555
|
|
556
|
+
if "norobots" in vn.flags:
|
557
|
+
self.html_head += META_NOBOTS
|
558
|
+
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
559
|
+
|
509
560
|
try:
|
510
561
|
cors_k = self._cors()
|
511
562
|
if self.mode in ("GET", "HEAD"):
|
@@ -514,9 +565,13 @@ class HttpCli(object):
|
|
514
565
|
return self.handle_options() and self.keepalive
|
515
566
|
|
516
567
|
if not cors_k:
|
568
|
+
host = self.headers.get("host", "<?>")
|
517
569
|
origin = self.headers.get("origin", "<?>")
|
518
|
-
|
519
|
-
|
570
|
+
proto = "https://" if self.is_https else "http://"
|
571
|
+
guess = "modifying" if (origin and host) else "stripping"
|
572
|
+
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)"
|
573
|
+
self.log(t % (self.mode, origin, proto, self.host, host, guess), 3)
|
574
|
+
raise Pebkac(403, "rejected by cors-check")
|
520
575
|
|
521
576
|
# getattr(self.mode) is not yet faster than this
|
522
577
|
if self.mode == "POST":
|
@@ -647,7 +702,11 @@ class HttpCli(object):
|
|
647
702
|
|
648
703
|
def k304(self) :
|
649
704
|
k304 = self.cookies.get("k304")
|
650
|
-
return
|
705
|
+
return (
|
706
|
+
k304 == "y"
|
707
|
+
or (self.args.k304 == 2 and k304 != "n")
|
708
|
+
or ("; Trident/" in self.ua and not k304)
|
709
|
+
)
|
651
710
|
|
652
711
|
def send_headers(
|
653
712
|
self,
|
@@ -2823,11 +2882,11 @@ class HttpCli(object):
|
|
2823
2882
|
logtail = ""
|
2824
2883
|
|
2825
2884
|
#
|
2826
|
-
# if request is for foo.js, check if we have foo.js.
|
2885
|
+
# if request is for foo.js, check if we have foo.js.gz
|
2827
2886
|
|
2828
2887
|
file_ts = 0.0
|
2829
2888
|
editions = {}
|
2830
|
-
for ext in
|
2889
|
+
for ext in ("", ".gz"):
|
2831
2890
|
try:
|
2832
2891
|
fs_path = req_path + ext
|
2833
2892
|
st = bos.stat(fs_path)
|
@@ -2872,12 +2931,7 @@ class HttpCli(object):
|
|
2872
2931
|
x.strip()
|
2873
2932
|
for x in self.headers.get("accept-encoding", "").lower().split(",")
|
2874
2933
|
]
|
2875
|
-
if ".
|
2876
|
-
is_compressed = True
|
2877
|
-
selected_edition = ".br"
|
2878
|
-
fs_path, file_sz = editions[".br"]
|
2879
|
-
self.out_headers["Content-Encoding"] = "br"
|
2880
|
-
elif ".gz" in editions:
|
2934
|
+
if ".gz" in editions:
|
2881
2935
|
is_compressed = True
|
2882
2936
|
selected_edition = ".gz"
|
2883
2937
|
fs_path, file_sz = editions[".gz"]
|
@@ -2893,13 +2947,8 @@ class HttpCli(object):
|
|
2893
2947
|
is_compressed = False
|
2894
2948
|
selected_edition = "plain"
|
2895
2949
|
|
2896
|
-
|
2897
|
-
|
2898
|
-
logmsg += "{} ".format(selected_edition.lstrip("."))
|
2899
|
-
except:
|
2900
|
-
# client is old and we only have .br
|
2901
|
-
# (could make brotli a dep to fix this but it's not worth)
|
2902
|
-
raise Pebkac(404)
|
2950
|
+
fs_path, file_sz = editions[selected_edition]
|
2951
|
+
logmsg += "{} ".format(selected_edition.lstrip("."))
|
2903
2952
|
|
2904
2953
|
#
|
2905
2954
|
# partial
|
@@ -3339,6 +3388,8 @@ class HttpCli(object):
|
|
3339
3388
|
self.reply(zb, mime="text/plain; charset=utf-8")
|
3340
3389
|
return True
|
3341
3390
|
|
3391
|
+
self.html_head += self.vn.flags.get("html_head", "")
|
3392
|
+
|
3342
3393
|
html = self.j2s(
|
3343
3394
|
"splash",
|
3344
3395
|
this=self,
|
@@ -3354,6 +3405,7 @@ class HttpCli(object):
|
|
3354
3405
|
dbwt=vs["dbwt"],
|
3355
3406
|
url_suf=suf,
|
3356
3407
|
k304=self.k304(),
|
3408
|
+
k304vis=self.args.k304 > 0,
|
3357
3409
|
ver=S_VERSION if self.args.ver else "",
|
3358
3410
|
ahttps="" if self.is_https else "https://" + self.host + self.req,
|
3359
3411
|
)
|
@@ -3362,7 +3414,7 @@ class HttpCli(object):
|
|
3362
3414
|
|
3363
3415
|
def set_k304(self) :
|
3364
3416
|
v = self.uparam["k304"].lower()
|
3365
|
-
if v
|
3417
|
+
if v in "yn":
|
3366
3418
|
dur = 86400 * 299
|
3367
3419
|
else:
|
3368
3420
|
dur = 0
|
@@ -3545,9 +3597,6 @@ class HttpCli(object):
|
|
3545
3597
|
return ret
|
3546
3598
|
|
3547
3599
|
def tx_ups(self) :
|
3548
|
-
if not self.args.unpost:
|
3549
|
-
raise Pebkac(403, "the unpost feature is disabled in server config")
|
3550
|
-
|
3551
3600
|
idx = self.conn.get_u2idx()
|
3552
3601
|
if not idx or not hasattr(idx, "p_end"):
|
3553
3602
|
raise Pebkac(500, "sqlite3 is not available on the server; cannot unpost")
|
@@ -3565,7 +3614,20 @@ class HttpCli(object):
|
|
3565
3614
|
if "fk" in vol.flags
|
3566
3615
|
and (self.uname in vol.axs.uread or self.uname in vol.axs.upget)
|
3567
3616
|
}
|
3568
|
-
|
3617
|
+
|
3618
|
+
x = self.conn.hsrv.broker.ask(
|
3619
|
+
"up2k.get_unfinished_by_user", self.uname, self.ip
|
3620
|
+
)
|
3621
|
+
uret = x.get()
|
3622
|
+
|
3623
|
+
if not self.args.unpost:
|
3624
|
+
allvols = []
|
3625
|
+
else:
|
3626
|
+
allvols = list(self.asrv.vfs.all_vols.values())
|
3627
|
+
|
3628
|
+
allvols = [x for x in allvols if "e2d" in x.flags]
|
3629
|
+
|
3630
|
+
for vol in allvols:
|
3569
3631
|
cur = idx.get_cur(vol.realpath)
|
3570
3632
|
if not cur:
|
3571
3633
|
continue
|
@@ -3617,9 +3679,13 @@ class HttpCli(object):
|
|
3617
3679
|
for v in ret:
|
3618
3680
|
v["vp"] = self.args.SR + v["vp"]
|
3619
3681
|
|
3620
|
-
|
3621
|
-
|
3622
|
-
|
3682
|
+
if not allvols:
|
3683
|
+
ret = [{"kinshi": 1}]
|
3684
|
+
|
3685
|
+
jtxt = '{"u":%s,"c":%s}' % (uret, json.dumps(ret, indent=0))
|
3686
|
+
zi = len(uret.split('\n"pd":')) - 1
|
3687
|
+
self.log("%s #%d+%d %.2fsec" % (lm, zi, len(ret), time.time() - t0))
|
3688
|
+
self.reply(jtxt.encode("utf-8", "replace"), mime="application/json")
|
3623
3689
|
return True
|
3624
3690
|
|
3625
3691
|
def handle_rm(self, req ) :
|
@@ -3634,11 +3700,12 @@ class HttpCli(object):
|
|
3634
3700
|
elif self.is_vproxied:
|
3635
3701
|
req = [x[len(self.args.SR) :] for x in req]
|
3636
3702
|
|
3703
|
+
unpost = "unpost" in self.uparam
|
3637
3704
|
nlim = int(self.uparam.get("lim") or 0)
|
3638
3705
|
lim = [nlim, nlim] if nlim else []
|
3639
3706
|
|
3640
3707
|
x = self.conn.hsrv.broker.ask(
|
3641
|
-
"up2k.handle_rm", self.uname, self.ip, req, lim, False
|
3708
|
+
"up2k.handle_rm", self.uname, self.ip, req, lim, False, unpost
|
3642
3709
|
)
|
3643
3710
|
self.loud_reply(x.get())
|
3644
3711
|
return True
|
@@ -3776,11 +3843,9 @@ class HttpCli(object):
|
|
3776
3843
|
e2d = "e2d" in vn.flags
|
3777
3844
|
e2t = "e2t" in vn.flags
|
3778
3845
|
|
3779
|
-
self.html_head
|
3780
|
-
if
|
3846
|
+
self.html_head += vn.flags.get("html_head", "")
|
3847
|
+
if "b" in self.uparam:
|
3781
3848
|
self.out_headers["X-Robots-Tag"] = "noindex, nofollow"
|
3782
|
-
else:
|
3783
|
-
self.out_headers.pop("X-Robots-Tag", None)
|
3784
3849
|
|
3785
3850
|
is_dir = stat.S_ISDIR(st.st_mode)
|
3786
3851
|
fk_pass = False
|
copyparty/httpconn.py
CHANGED
@@ -23,7 +23,7 @@ from .mtag import HAVE_FFMPEG
|
|
23
23
|
from .th_cli import ThumbCli
|
24
24
|
from .th_srv import HAVE_PIL, HAVE_VIPS
|
25
25
|
from .u2idx import U2idx
|
26
|
-
from .util import HMaccas, shut_socket
|
26
|
+
from .util import HMaccas, NetMap, shut_socket
|
27
27
|
|
28
28
|
if TYPE_CHECKING:
|
29
29
|
from .httpsrv import HttpSrv
|
@@ -52,6 +52,9 @@ class HttpConn(object):
|
|
52
52
|
self.E = self.args.E
|
53
53
|
self.asrv = hsrv.asrv # mypy404
|
54
54
|
self.u2fh = hsrv.u2fh # mypy404
|
55
|
+
self.ipa_nm = hsrv.ipa_nm
|
56
|
+
self.xff_nm = hsrv.xff_nm
|
57
|
+
self.xff_lan = hsrv.xff_lan # type: ignore
|
55
58
|
self.iphash = hsrv.broker.iphash
|
56
59
|
self.bans = hsrv.bans
|
57
60
|
self.aclose = hsrv.aclose
|
copyparty/httpsrv.py
CHANGED
@@ -67,6 +67,7 @@ from .util import (
|
|
67
67
|
Netdev,
|
68
68
|
NetMap,
|
69
69
|
absreal,
|
70
|
+
build_netmap,
|
70
71
|
ipnorm,
|
71
72
|
min_ex,
|
72
73
|
shut_socket,
|
@@ -99,7 +100,7 @@ class HttpSrv(object):
|
|
99
100
|
self.t0 = time.time()
|
100
101
|
nsuf = "-n{}-i{:x}".format(nid, os.getpid()) if nid else ""
|
101
102
|
self.magician = Magician()
|
102
|
-
self.nm = NetMap([],
|
103
|
+
self.nm = NetMap([], [])
|
103
104
|
self.ssdp = None
|
104
105
|
self.gpwd = Garda(self.args.ban_pw)
|
105
106
|
self.g404 = Garda(self.args.ban_404)
|
@@ -146,6 +147,10 @@ class HttpSrv(object):
|
|
146
147
|
zs = os.path.join(self.E.mod, "web", "deps", "prism.js.gz")
|
147
148
|
self.prism = os.path.exists(zs)
|
148
149
|
|
150
|
+
self.ipa_nm = build_netmap(self.args.ipa)
|
151
|
+
self.xff_nm = build_netmap(self.args.xff_src)
|
152
|
+
self.xff_lan = build_netmap("lan")
|
153
|
+
|
149
154
|
self.statics = set()
|
150
155
|
self._build_statics()
|
151
156
|
|
@@ -187,7 +192,7 @@ class HttpSrv(object):
|
|
187
192
|
for fn in df:
|
188
193
|
ap = absreal(os.path.join(dp, fn))
|
189
194
|
self.statics.add(ap)
|
190
|
-
if ap.endswith(".gz")
|
195
|
+
if ap.endswith(".gz"):
|
191
196
|
self.statics.add(ap[:-3])
|
192
197
|
|
193
198
|
def set_netdevs(self, netdevs ) :
|
@@ -195,7 +200,7 @@ class HttpSrv(object):
|
|
195
200
|
for ip, _ in self.bound:
|
196
201
|
ips.add(ip)
|
197
202
|
|
198
|
-
self.nm = NetMap(list(ips), netdevs)
|
203
|
+
self.nm = NetMap(list(ips), list(netdevs))
|
199
204
|
|
200
205
|
def start_threads(self, n ) :
|
201
206
|
self.tp_nthr += n
|
copyparty/metrics.py
CHANGED
@@ -206,6 +206,9 @@ class Metrics(object):
|
|
206
206
|
try:
|
207
207
|
x = self.hsrv.broker.ask("up2k.get_unfinished")
|
208
208
|
xs = x.get()
|
209
|
+
if not xs:
|
210
|
+
raise Exception("up2k mutex acquisition timed out")
|
211
|
+
|
209
212
|
xj = json.loads(xs)
|
210
213
|
for ptop, (nbytes, nfiles) in xj.items():
|
211
214
|
tnbytes += nbytes
|
copyparty/multicast.py
CHANGED
copyparty/smbd.py
CHANGED
@@ -337,7 +337,7 @@ class SMB(object):
|
|
337
337
|
yeet("blocked delete (no-del-acc): " + vpath)
|
338
338
|
|
339
339
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
340
|
-
self.hub.up2k.handle_rm(uname, "1.7.6.2", [vpath], [], False)
|
340
|
+
self.hub.up2k.handle_rm(uname, "1.7.6.2", [vpath], [], False, False)
|
341
341
|
|
342
342
|
def _utime(self, vpath , times ) :
|
343
343
|
if not self.args.smbw:
|
copyparty/svchub.py
CHANGED
@@ -22,7 +22,7 @@ from datetime import datetime, timedelta
|
|
22
22
|
# print(currentframe().f_lineno)
|
23
23
|
|
24
24
|
|
25
|
-
from .__init__ import ANYWIN, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode
|
25
|
+
from .__init__ import ANYWIN, E, EXE, MACOS, TYPE_CHECKING, EnvParams, unicode
|
26
26
|
from .authsrv import BAD_CFG, AuthSrv
|
27
27
|
from .cert import ensure_cert
|
28
28
|
from .mtag import HAVE_FFMPEG, HAVE_FFPROBE
|
@@ -43,6 +43,7 @@ from .util import (
|
|
43
43
|
ODict,
|
44
44
|
alltrace,
|
45
45
|
ansi_re,
|
46
|
+
build_netmap,
|
46
47
|
min_ex,
|
47
48
|
mp,
|
48
49
|
odfusion,
|
@@ -88,7 +89,7 @@ class SvcHub(object):
|
|
88
89
|
self.stopping = False
|
89
90
|
self.stopped = False
|
90
91
|
self.reload_req = False
|
91
|
-
self.reloading =
|
92
|
+
self.reloading = 0
|
92
93
|
self.stop_cond = threading.Condition()
|
93
94
|
self.nsigs = 3
|
94
95
|
self.retcode = 0
|
@@ -148,6 +149,8 @@ class SvcHub(object):
|
|
148
149
|
lg.handlers = [lh]
|
149
150
|
lg.setLevel(logging.DEBUG)
|
150
151
|
|
152
|
+
self._check_env()
|
153
|
+
|
151
154
|
if args.stackmon:
|
152
155
|
start_stackmon(args.stackmon, 0)
|
153
156
|
|
@@ -379,6 +382,17 @@ class SvcHub(object):
|
|
379
382
|
|
380
383
|
Daemon(self.sd_notify, "sd-notify")
|
381
384
|
|
385
|
+
def _check_env(self) :
|
386
|
+
try:
|
387
|
+
files = os.listdir(E.cfg)
|
388
|
+
except:
|
389
|
+
files = []
|
390
|
+
|
391
|
+
hits = [x for x in files if x.lower().endswith(".conf")]
|
392
|
+
if hits:
|
393
|
+
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)"
|
394
|
+
self.log("root", t % (E.cfg, ", ".join(hits)), 3)
|
395
|
+
|
382
396
|
def _process_config(self) :
|
383
397
|
al = self.args
|
384
398
|
|
@@ -459,12 +473,11 @@ class SvcHub(object):
|
|
459
473
|
|
460
474
|
al.xff_hdr = al.xff_hdr.lower()
|
461
475
|
al.idp_h_usr = al.idp_h_usr.lower()
|
462
|
-
|
476
|
+
al.idp_h_grp = al.idp_h_grp.lower()
|
477
|
+
al.idp_h_key = al.idp_h_key.lower()
|
463
478
|
|
464
|
-
al.
|
465
|
-
al.
|
466
|
-
al.ftp_ipa_re = self._ipa2re(al.ftp_ipa or al.ipa)
|
467
|
-
al.tftp_ipa_re = self._ipa2re(al.tftp_ipa or al.ipa)
|
479
|
+
al.ftp_ipa_nm = build_netmap(al.ftp_ipa or al.ipa)
|
480
|
+
al.tftp_ipa_nm = build_netmap(al.tftp_ipa or al.ipa)
|
468
481
|
|
469
482
|
mte = ODict.fromkeys(DEF_MTE.split(","), True)
|
470
483
|
al.mte = odfusion(mte, al.mte)
|
@@ -481,6 +494,18 @@ class SvcHub(object):
|
|
481
494
|
if ptn:
|
482
495
|
setattr(self.args, k, re.compile(ptn))
|
483
496
|
|
497
|
+
for k in ["idp_gsep"]:
|
498
|
+
ptn = getattr(self.args, k)
|
499
|
+
if "]" in ptn:
|
500
|
+
ptn = "]" + ptn.replace("]", "")
|
501
|
+
if "[" in ptn:
|
502
|
+
ptn = ptn.replace("[", "") + "["
|
503
|
+
if "-" in ptn:
|
504
|
+
ptn = ptn.replace("-", "") + "-"
|
505
|
+
|
506
|
+
ptn = ptn.replace("\\", "\\\\").replace("^", "\\^")
|
507
|
+
setattr(self.args, k, re.compile("[%s]" % (ptn,)))
|
508
|
+
|
484
509
|
try:
|
485
510
|
zf1, zf2 = self.args.rm_retry.split("/")
|
486
511
|
self.args.rm_re_t = float(zf1)
|
@@ -656,21 +681,37 @@ class SvcHub(object):
|
|
656
681
|
self.log("root", "ssdp startup failed;\n" + min_ex(), 3)
|
657
682
|
|
658
683
|
def reload(self) :
|
659
|
-
|
660
|
-
|
684
|
+
with self.up2k.mutex:
|
685
|
+
if self.reloading:
|
686
|
+
return "cannot reload; already in progress"
|
687
|
+
self.reloading = 1
|
661
688
|
|
662
|
-
self.reloading = True
|
663
689
|
Daemon(self._reload, "reloading")
|
664
690
|
return "reload initiated"
|
665
691
|
|
666
|
-
def _reload(self) :
|
667
|
-
self.log("root", "reload scheduled")
|
692
|
+
def _reload(self, rescan_all_vols = True) :
|
668
693
|
with self.up2k.mutex:
|
694
|
+
if self.reloading != 1:
|
695
|
+
return
|
696
|
+
self.reloading = 2
|
697
|
+
self.log("root", "reloading config")
|
669
698
|
self.asrv.reload()
|
670
|
-
self.up2k.reload()
|
699
|
+
self.up2k.reload(rescan_all_vols)
|
671
700
|
self.broker.reload()
|
701
|
+
self.reloading = 0
|
702
|
+
|
703
|
+
def _reload_blocking(self, rescan_all_vols = True) :
|
704
|
+
while True:
|
705
|
+
with self.up2k.mutex:
|
706
|
+
if self.reloading < 2:
|
707
|
+
self.reloading = 1
|
708
|
+
break
|
709
|
+
time.sleep(0.05)
|
710
|
+
|
711
|
+
# try to handle multiple pending IdP reloads at once:
|
712
|
+
time.sleep(0.2)
|
672
713
|
|
673
|
-
self.
|
714
|
+
self._reload(rescan_all_vols=rescan_all_vols)
|
674
715
|
|
675
716
|
def stop_thr(self) :
|
676
717
|
while not self.stop_req:
|
copyparty/tftpd.py
CHANGED
@@ -52,17 +52,16 @@ def noop(*a, **ka) :
|
|
52
52
|
|
53
53
|
def _serverInitial(self, pkt , raddress , rport ) :
|
54
54
|
info("connection from %s:%s", raddress, rport)
|
55
|
-
ret =
|
56
|
-
|
57
|
-
if
|
55
|
+
ret = _sinitial[0](self, pkt, raddress, rport)
|
56
|
+
nm = _hub[0].args.tftp_ipa_nm
|
57
|
+
if nm and not nm.map(raddress):
|
58
58
|
yeet("client rejected (--tftp-ipa): %s" % (raddress,))
|
59
59
|
return ret
|
60
60
|
|
61
61
|
|
62
|
-
# patch ipa-check into partftpd
|
62
|
+
# patch ipa-check into partftpd (part 1/2)
|
63
63
|
_hub = []
|
64
|
-
|
65
|
-
TftpStates.TftpServerState.serverInitial = _serverInitial
|
64
|
+
_sinitial = []
|
66
65
|
|
67
66
|
|
68
67
|
class Tftpd(object):
|
@@ -95,8 +94,6 @@ class Tftpd(object):
|
|
95
94
|
cbak = []
|
96
95
|
if not self.args.tftp_no_fast and not EXE:
|
97
96
|
try:
|
98
|
-
import inspect
|
99
|
-
|
100
97
|
ptn = re.compile(r"(^\s*)log\.debug\(.*\)$")
|
101
98
|
for C in Cs:
|
102
99
|
cbak.append(C.__dict__)
|
@@ -113,6 +110,11 @@ class Tftpd(object):
|
|
113
110
|
for C in Cs:
|
114
111
|
C.log.debug = noop
|
115
112
|
|
113
|
+
# patch ipa-check into partftpd (part 2/2)
|
114
|
+
_sinitial[:] = []
|
115
|
+
_sinitial.append(TftpStates.TftpServerState.serverInitial)
|
116
|
+
TftpStates.TftpServerState.serverInitial = _serverInitial
|
117
|
+
|
116
118
|
# patch vfs into partftpy
|
117
119
|
TftpContexts.open = self._open
|
118
120
|
TftpStates.open = self._open
|
@@ -357,7 +359,7 @@ class Tftpd(object):
|
|
357
359
|
yeet("attempted delete of non-empty file")
|
358
360
|
|
359
361
|
vpath = vpath.replace("\\", "/").lstrip("/")
|
360
|
-
self.hub.up2k.handle_rm("*", "8.3.8.7", [vpath], [], False)
|
362
|
+
self.hub.up2k.handle_rm("*", "8.3.8.7", [vpath], [], False, False)
|
361
363
|
|
362
364
|
def _access(self, *a ) :
|
363
365
|
return True
|