copyparty 1.7.6__py3-none-any.whl → 1.8.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 +125 -11
- copyparty/__version__.py +3 -3
- copyparty/authsrv.py +96 -6
- copyparty/broker_mp.py +14 -1
- copyparty/cert.py +3 -1
- copyparty/cfg.py +7 -1
- copyparty/ftpd.py +7 -4
- copyparty/httpcli.py +122 -14
- copyparty/ico.py +17 -4
- copyparty/pwhash.py +145 -0
- copyparty/u2idx.py +1 -1
- copyparty/up2k.py +82 -15
- copyparty/util.py +34 -1
- copyparty/web/browser.css.gz +0 -0
- copyparty/web/browser.js.gz +0 -0
- copyparty/web/ui.css.gz +0 -0
- copyparty/web/util.js.gz +0 -0
- {copyparty-1.7.6.dist-info → copyparty-1.8.1.dist-info}/METADATA +41 -1
- {copyparty-1.7.6.dist-info → copyparty-1.8.1.dist-info}/RECORD +23 -22
- {copyparty-1.7.6.dist-info → copyparty-1.8.1.dist-info}/LICENSE +0 -0
- {copyparty-1.7.6.dist-info → copyparty-1.8.1.dist-info}/WHEEL +0 -0
- {copyparty-1.7.6.dist-info → copyparty-1.8.1.dist-info}/entry_points.txt +0 -0
- {copyparty-1.7.6.dist-info → copyparty-1.8.1.dist-info}/top_level.txt +0 -0
copyparty/__main__.py
CHANGED
@@ -252,6 +252,19 @@ def get_fk_salt(cert_path) :
|
|
252
252
|
return ret.decode("utf-8")
|
253
253
|
|
254
254
|
|
255
|
+
def get_ah_salt() :
|
256
|
+
fp = os.path.join(E.cfg, "ah-salt.txt")
|
257
|
+
try:
|
258
|
+
with open(fp, "rb") as f:
|
259
|
+
ret = f.read().strip()
|
260
|
+
except:
|
261
|
+
ret = base64.b64encode(os.urandom(18))
|
262
|
+
with open(fp, "wb") as f:
|
263
|
+
f.write(ret + b"\n")
|
264
|
+
|
265
|
+
return ret.decode("utf-8")
|
266
|
+
|
267
|
+
|
255
268
|
def ensure_locale() :
|
256
269
|
safe = "en_US.UTF-8"
|
257
270
|
for x in [
|
@@ -509,6 +522,50 @@ def get_sects():
|
|
509
522
|
).rstrip()
|
510
523
|
+ build_flags_desc(),
|
511
524
|
],
|
525
|
+
[
|
526
|
+
"handlers",
|
527
|
+
"use plugins to handle certain events",
|
528
|
+
dedent(
|
529
|
+
"""
|
530
|
+
usually copyparty returns a \033[33m404\033[0m if a file does not exist, and
|
531
|
+
\033[33m403\033[0m if a user tries to access a file they don't have access to
|
532
|
+
|
533
|
+
you can load a plugin which will be invoked right before this
|
534
|
+
happens, and the plugin can choose to override this behavior
|
535
|
+
|
536
|
+
load the plugin using --args or volflags; for example \033[36m
|
537
|
+
--on404 ~/partyhandlers/not404.py
|
538
|
+
-v .::r:c,on404=~/partyhandlers/not404.py
|
539
|
+
\033[0m
|
540
|
+
the file must define the function \033[35mmain(cli,vn,rem)\033[0m:
|
541
|
+
\033[35mcli\033[0m: the copyparty HttpCli instance
|
542
|
+
\033[35mvn\033[0m: the VFS which overlaps with the requested URL
|
543
|
+
\033[35mrem\033[0m: the remainder of the URL below the VFS mountpoint
|
544
|
+
|
545
|
+
`main` must return a string; one of the following:
|
546
|
+
|
547
|
+
> \033[32m"true"\033[0m: the plugin has responded to the request,
|
548
|
+
and the TCP connection should be kept open
|
549
|
+
|
550
|
+
> \033[32m"false"\033[0m: the plugin has responded to the request,
|
551
|
+
and the TCP connection should be terminated
|
552
|
+
|
553
|
+
> \033[32m"retry"\033[0m: the plugin has done something to resolve the 404
|
554
|
+
situation, and copyparty should reattempt reading the file.
|
555
|
+
if it still fails, a regular 404 will be returned
|
556
|
+
|
557
|
+
> \033[32m"allow"\033[0m: should ignore the insufficient permissions
|
558
|
+
and let the client continue anyways
|
559
|
+
|
560
|
+
> \033[32m""\033[0m: the plugin has not handled the request;
|
561
|
+
try the next plugin or return the usual 404 or 403
|
562
|
+
|
563
|
+
\033[1;35mPS!\033[0m the folder that contains the python file should ideally
|
564
|
+
not contain many other python files, and especially nothing
|
565
|
+
with filenames that overlap with modules used by copyparty
|
566
|
+
"""
|
567
|
+
),
|
568
|
+
],
|
512
569
|
[
|
513
570
|
"hooks",
|
514
571
|
"execute commands before/after various events",
|
@@ -616,6 +673,38 @@ def get_sects():
|
|
616
673
|
"""
|
617
674
|
),
|
618
675
|
],
|
676
|
+
[
|
677
|
+
"pwhash",
|
678
|
+
"password hashing",
|
679
|
+
dedent(
|
680
|
+
"""
|
681
|
+
when \033[36m--ah-alg\033[0m is not the default [\033[32mnone\033[0m], all account passwords must be hashed
|
682
|
+
|
683
|
+
passwords can be hashed on the commandline with \033[36m--ah-gen\033[0m, but copyparty will also hash and print any passwords that are non-hashed (password which do not start with '+') and then terminate afterwards
|
684
|
+
|
685
|
+
\033[36m--ah-alg\033[0m specifies the hashing algorithm and a list of optional comma-separated arguments:
|
686
|
+
|
687
|
+
\033[36m--ah-alg argon2\033[0m # which is the same as:
|
688
|
+
\033[36m--ah-alg argon2,3,256,4,19\033[0m
|
689
|
+
use argon2id with timecost 3, 256 MiB, 4 threads, version 19 (0x13/v1.3)
|
690
|
+
|
691
|
+
\033[36m--ah-alg scrypt\033[0m # which is the same as:
|
692
|
+
\033[36m--ah-alg scrypt,13,2,8,4\033[0m
|
693
|
+
use scrypt with cost 2**13, 2 iterations, blocksize 8, 4 threads
|
694
|
+
|
695
|
+
\033[36m--ah-alg sha2\033[0m # which is the same as:
|
696
|
+
\033[36m--ah-alg sha2,424242\033[0m
|
697
|
+
use sha2-512 with 424242 iterations
|
698
|
+
|
699
|
+
recommended: \033[32m--ah-alg argon2\033[0m
|
700
|
+
(takes about 0.4 sec and 256M RAM to process a new password)
|
701
|
+
|
702
|
+
argon2 needs python-package argon2-cffi,
|
703
|
+
scrypt needs openssl,
|
704
|
+
sha2 is always available
|
705
|
+
"""
|
706
|
+
),
|
707
|
+
],
|
619
708
|
]
|
620
709
|
|
621
710
|
|
@@ -724,6 +813,7 @@ def add_cert(ap, cert_path):
|
|
724
813
|
ap2.add_argument("--crt-exact", action="store_true", help="do not add wildcard entries for each --crt-ns")
|
725
814
|
ap2.add_argument("--crt-noip", action="store_true", help="do not add autodetected IP addresses into cert")
|
726
815
|
ap2.add_argument("--crt-nolo", action="store_true", help="do not add 127.0.0.1 / localhost into cert")
|
816
|
+
ap2.add_argument("--crt-nohn", action="store_true", help="do not add mDNS names / hostname into cert")
|
727
817
|
ap2.add_argument("--crt-dir", metavar="PATH", default=cert_dir, help="where to save the CA cert")
|
728
818
|
ap2.add_argument("--crt-cdays", metavar="D", type=float, default=3650, help="ca-certificate expiration time in days")
|
729
819
|
ap2.add_argument("--crt-sdays", metavar="D", type=float, default=365, help="server-cert expiration time in days")
|
@@ -806,6 +896,13 @@ def add_smb(ap):
|
|
806
896
|
ap2.add_argument("--smbvvv", action="store_true", help="verbosest")
|
807
897
|
|
808
898
|
|
899
|
+
def add_handlers(ap):
|
900
|
+
ap2 = ap.add_argument_group('handlers (see --help-handlers)')
|
901
|
+
ap2.add_argument("--on404", metavar="PY", type=u, action="append", help="handle 404s by executing PY file")
|
902
|
+
ap2.add_argument("--on403", metavar="PY", type=u, action="append", help="handle 403s by executing PY file")
|
903
|
+
ap2.add_argument("--hot-handlers", action="store_true", help="reload handlers on each request -- expensive but convenient when hacking on stuff")
|
904
|
+
|
905
|
+
|
809
906
|
def add_hooks(ap):
|
810
907
|
ap2 = ap.add_argument_group('event hooks (see --help-hooks)')
|
811
908
|
ap2.add_argument("--xbu", metavar="CMD", type=u, action="append", help="execute CMD before a file upload starts")
|
@@ -838,7 +935,7 @@ def add_optouts(ap):
|
|
838
935
|
ap2.add_argument("--no-lifetime", action="store_true", help="disable automatic deletion of uploads after a certain time (as specified by the 'lifetime' volflag)")
|
839
936
|
|
840
937
|
|
841
|
-
def add_safety(ap
|
938
|
+
def add_safety(ap):
|
842
939
|
ap2 = ap.add_argument_group('safety options')
|
843
940
|
ap2.add_argument("-s", action="count", default=0, help="increase safety: Disable thumbnails / potentially dangerous software (ffmpeg/pillow/vips), hide partial uploads, avoid crawlers.\n └─Alias of\033[32m --dotpart --no-thumb --no-mtag-ff --no-robots --force-js")
|
844
941
|
ap2.add_argument("-ss", action="store_true", help="further increase safety: Prevent js-injection, accidental move/delete, broken symlinks, webdav, 404 on 403, ban on excessive 404s.\n └─Alias of\033[32m -s --unpost=0 --no-del --no-mv --hardlink --vague-403 --ban-404=50,60,1440 -nih")
|
@@ -846,8 +943,6 @@ def add_safety(ap, fk_salt):
|
|
846
943
|
ap2.add_argument("--ls", metavar="U[,V[,F]]", type=u, help="do a sanity/safety check of all volumes on startup; arguments \033[33mUSER\033[0m,\033[33mVOL\033[0m,\033[33mFLAGS\033[0m; example [\033[32m**,*,ln,p,r\033[0m]")
|
847
944
|
ap2.add_argument("--xvol", action="store_true", help="never follow symlinks leaving the volume root, unless the link is into another volume where the user has similar access (volflag=xvol)")
|
848
945
|
ap2.add_argument("--xdev", action="store_true", help="stay within the filesystem of the volume root; do not descend into other devices (symlink or bind-mount to another HDD, ...) (volflag=xdev)")
|
849
|
-
ap2.add_argument("--salt", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
|
850
|
-
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files -- this one DOES matter")
|
851
946
|
ap2.add_argument("--no-dot-mv", action="store_true", help="disallow moving dotfiles; makes it impossible to move folders containing dotfiles")
|
852
947
|
ap2.add_argument("--no-dot-ren", action="store_true", help="disallow renaming dotfiles; makes it impossible to make something a dotfile")
|
853
948
|
ap2.add_argument("--no-logues", action="store_true", help="disable rendering .prologue/.epilogue.html into directory listings")
|
@@ -864,6 +959,16 @@ def add_safety(ap, fk_salt):
|
|
864
959
|
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 described in --acao)")
|
865
960
|
|
866
961
|
|
962
|
+
def add_salt(ap, fk_salt, ah_salt):
|
963
|
+
ap2 = ap.add_argument_group('salting options')
|
964
|
+
ap2.add_argument("--ah-alg", metavar="ALG", type=u, default="none", help="account-pw hashing algorithm; one of these, best to worst: argon2 scrypt sha2 none (each optionally followed by alg-specific comma-sep. config)")
|
965
|
+
ap2.add_argument("--ah-salt", metavar="SALT", type=u, default=ah_salt, help="account-pw salt; ignored if --ah-alg is none (default)")
|
966
|
+
ap2.add_argument("--ah-gen", metavar="PW", type=u, default="", help="generate hashed password for \033[33mPW\033[0m, or read passwords from STDIN if \033[33mPW\033[0m is [\033[32m-\033[0m]")
|
967
|
+
ap2.add_argument("--ah-cli", action="store_true", help="interactive shell which hashes passwords without ever storing or displaying the original passwords")
|
968
|
+
ap2.add_argument("--fk-salt", metavar="SALT", type=u, default=fk_salt, help="per-file accesskey salt; used to generate unpredictable URLs for hidden files")
|
969
|
+
ap2.add_argument("--warksalt", metavar="SALT", type=u, default="hunter2", help="up2k file-hash salt; serves no purpose, no reason to change this (but delete all databases if you do)")
|
970
|
+
|
971
|
+
|
867
972
|
def add_shutdown(ap):
|
868
973
|
ap2 = ap.add_argument_group('shutdown options')
|
869
974
|
ap2.add_argument("--ign-ebind", action="store_true", help="continue running even if it's impossible to listen on some of the requested endpoints")
|
@@ -974,7 +1079,7 @@ def add_ui(ap, retry):
|
|
974
1079
|
ap2.add_argument("--lang", metavar="LANG", type=u, default="eng", help="language")
|
975
1080
|
ap2.add_argument("--theme", metavar="NUM", type=int, default=0, help="default theme to use")
|
976
1081
|
ap2.add_argument("--themes", metavar="NUM", type=int, default=8, help="number of themes installed")
|
977
|
-
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching REGEX in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m
|
1082
|
+
ap2.add_argument("--unlist", metavar="REGEX", type=u, default="", help="don't show files matching REGEX in file list. Purely cosmetic! Does not affect API calls, just the browser. Example: [\033[32m\\.(js|css)$\033[0m] (volflag=unlist)")
|
978
1083
|
ap2.add_argument("--favico", metavar="TXT", type=u, default="c 000 none" if retry else "🎉 000 none", help="\033[33mfavicon-text\033[0m [ \033[33mforeground\033[0m [ \033[33mbackground\033[0m ] ], set blank to disable")
|
979
1084
|
ap2.add_argument("--mpmc", metavar="URL", type=u, default="", help="change the mediaplayer-toggle mouse cursor; URL to a folder with {2..5}.png inside (or disable with [\033[32m.\033[0m])")
|
980
1085
|
ap2.add_argument("--js-browser", metavar="L", type=u, help="URL to additional JS to include")
|
@@ -1024,6 +1129,7 @@ def run_argparse(
|
|
1024
1129
|
cert_path = os.path.join(E.cfg, "cert.pem")
|
1025
1130
|
|
1026
1131
|
fk_salt = get_fk_salt(cert_path)
|
1132
|
+
ah_salt = get_ah_salt()
|
1027
1133
|
|
1028
1134
|
hcores = min(CORES, 4) # optimal on py3.11 @ r5-4500U
|
1029
1135
|
|
@@ -1047,10 +1153,12 @@ def run_argparse(
|
|
1047
1153
|
add_ftp(ap)
|
1048
1154
|
add_webdav(ap)
|
1049
1155
|
add_smb(ap)
|
1050
|
-
add_safety(ap
|
1156
|
+
add_safety(ap)
|
1157
|
+
add_salt(ap, fk_salt, ah_salt)
|
1051
1158
|
add_optouts(ap)
|
1052
1159
|
add_shutdown(ap)
|
1053
1160
|
add_yolo(ap)
|
1161
|
+
add_handlers(ap)
|
1054
1162
|
add_hooks(ap)
|
1055
1163
|
add_ui(ap, retry)
|
1056
1164
|
add_admin(ap)
|
@@ -1132,16 +1240,22 @@ def main(argv = None) :
|
|
1132
1240
|
supp = args_from_cfg(v)
|
1133
1241
|
argv.extend(supp)
|
1134
1242
|
|
1135
|
-
deprecated = []
|
1243
|
+
deprecated = [("--salt", "--warksalt")]
|
1136
1244
|
for dk, nk in deprecated:
|
1137
|
-
|
1138
|
-
|
1139
|
-
|
1245
|
+
idx = -1
|
1246
|
+
ov = ""
|
1247
|
+
for n, k in enumerate(argv):
|
1248
|
+
if k == dk or k.startswith(dk + "="):
|
1249
|
+
idx = n
|
1250
|
+
if "=" in k:
|
1251
|
+
ov = "=" + k.split("=", 1)[1]
|
1252
|
+
|
1253
|
+
if idx < 0:
|
1140
1254
|
continue
|
1141
1255
|
|
1142
1256
|
msg = "\033[1;31mWARNING:\033[0;1m\n {} \033[0;33mwas replaced with\033[0;1m {} \033[0;33mand will be removed\n\033[0m"
|
1143
1257
|
lprint(msg.format(dk, nk))
|
1144
|
-
argv[idx] = nk
|
1258
|
+
argv[idx] = nk + ov
|
1145
1259
|
time.sleep(2)
|
1146
1260
|
|
1147
1261
|
da = len(argv) == 1
|
@@ -1192,7 +1306,7 @@ def main(argv = None) :
|
|
1192
1306
|
elif not al.no_ansi:
|
1193
1307
|
al.ansi = VT100
|
1194
1308
|
|
1195
|
-
if WINDOWS and not al.keep_qem:
|
1309
|
+
if WINDOWS and not al.keep_qem and not al.ah_cli:
|
1196
1310
|
try:
|
1197
1311
|
disable_quickedit()
|
1198
1312
|
except:
|
copyparty/__version__.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
VERSION = (1,
|
4
|
-
CODENAME = "
|
5
|
-
BUILD_DT = (2023,
|
3
|
+
VERSION = (1, 8, 1)
|
4
|
+
CODENAME = "argon"
|
5
|
+
BUILD_DT = (2023, 7, 7)
|
6
6
|
|
7
7
|
S_VERSION = ".".join(map(str, VERSION))
|
8
8
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
copyparty/authsrv.py
CHANGED
@@ -15,6 +15,7 @@ from datetime import datetime
|
|
15
15
|
from .__init__ import ANYWIN, TYPE_CHECKING, WINDOWS
|
16
16
|
from .bos import bos
|
17
17
|
from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
|
18
|
+
from .pwhash import PWHash
|
18
19
|
from .util import (
|
19
20
|
IMPLICATIONS,
|
20
21
|
META_NOBOTS,
|
@@ -33,7 +34,10 @@ from .util import (
|
|
33
34
|
)
|
34
35
|
|
35
36
|
if TYPE_CHECKING:
|
36
|
-
|
37
|
+
from .broker_mp import BrokerMp
|
38
|
+
from .broker_thr import BrokerThr
|
39
|
+
from .broker_util import BrokerCli
|
40
|
+
|
37
41
|
# Vflags: TypeAlias = dict[str, str | bool | float | list[str]]
|
38
42
|
# Vflags: TypeAlias = dict[str, Any]
|
39
43
|
# Mflags: TypeAlias = dict[str, Vflags]
|
@@ -83,6 +87,8 @@ class Lim(object):
|
|
83
87
|
self.dfl = 0 # free disk space limit
|
84
88
|
self.dft = 0 # last-measured time
|
85
89
|
self.dfv = 0 # currently free
|
90
|
+
self.vbmax = 0 # volume bytes max
|
91
|
+
self.vnmax = 0 # volume max num files
|
86
92
|
|
87
93
|
self.smin = 0 # filesize min
|
88
94
|
self.smax = 0 # filesize max
|
@@ -112,8 +118,11 @@ class Lim(object):
|
|
112
118
|
ip ,
|
113
119
|
rem ,
|
114
120
|
sz ,
|
121
|
+
ptop ,
|
115
122
|
abspath ,
|
123
|
+
broker = None,
|
116
124
|
reg = None,
|
125
|
+
volgetter = "up2k.get_volsize",
|
117
126
|
) :
|
118
127
|
if reg is not None and self.reg is None:
|
119
128
|
self.reg = reg
|
@@ -124,6 +133,7 @@ class Lim(object):
|
|
124
133
|
self.chk_rem(rem)
|
125
134
|
if sz != -1:
|
126
135
|
self.chk_sz(sz)
|
136
|
+
self.chk_vsz(broker, ptop, sz, volgetter)
|
127
137
|
self.chk_df(abspath, sz) # side effects; keep last-ish
|
128
138
|
|
129
139
|
ap2, vp2 = self.rot(abspath)
|
@@ -139,6 +149,25 @@ class Lim(object):
|
|
139
149
|
if self.smax and sz > self.smax:
|
140
150
|
raise Pebkac(400, "file too big")
|
141
151
|
|
152
|
+
def chk_vsz(
|
153
|
+
self,
|
154
|
+
broker ,
|
155
|
+
ptop ,
|
156
|
+
sz ,
|
157
|
+
volgetter = "up2k.get_volsize",
|
158
|
+
) :
|
159
|
+
if not broker or not self.vbmax + self.vnmax:
|
160
|
+
return
|
161
|
+
|
162
|
+
x = broker.ask(volgetter, ptop)
|
163
|
+
nbytes, nfiles = x.get()
|
164
|
+
|
165
|
+
if self.vbmax and self.vbmax < nbytes + sz:
|
166
|
+
raise Pebkac(400, "volume has exceeded max size")
|
167
|
+
|
168
|
+
if self.vnmax and self.vnmax < nfiles + 1:
|
169
|
+
raise Pebkac(400, "volume has exceeded max num.files")
|
170
|
+
|
142
171
|
def chk_df(self, abspath , sz , already_written = False) :
|
143
172
|
if not self.dfl:
|
144
173
|
return
|
@@ -259,7 +288,7 @@ class Lim(object):
|
|
259
288
|
|
260
289
|
self.bupc[ip] = mark
|
261
290
|
if mark >= self.bmax:
|
262
|
-
raise Pebkac(429, "
|
291
|
+
raise Pebkac(429, "upload size limit exceeded")
|
263
292
|
|
264
293
|
|
265
294
|
class VFS(object):
|
@@ -722,6 +751,7 @@ class AuthSrv(object):
|
|
722
751
|
warn_anonwrite = True,
|
723
752
|
dargs = None,
|
724
753
|
) :
|
754
|
+
self.ah = PWHash(args)
|
725
755
|
self.args = args
|
726
756
|
self.dargs = dargs or args
|
727
757
|
self.log_func = log_func
|
@@ -1010,7 +1040,7 @@ class AuthSrv(object):
|
|
1010
1040
|
flags[name] = True
|
1011
1041
|
return
|
1012
1042
|
|
1013
|
-
if name not in "mtp xbu xau xiu xbr xar xbd xad xm".split():
|
1043
|
+
if name not in "mtp xbu xau xiu xbr xar xbd xad xm on404 on403".split():
|
1014
1044
|
if value is True:
|
1015
1045
|
t = "└─add volflag [{}] = {} ({})"
|
1016
1046
|
else:
|
@@ -1099,6 +1129,8 @@ class AuthSrv(object):
|
|
1099
1129
|
self.log("\n{0}\n{1}{0}".format(t, "\n".join(slns)))
|
1100
1130
|
raise
|
1101
1131
|
|
1132
|
+
self.setup_pwhash(acct)
|
1133
|
+
|
1102
1134
|
# case-insensitive; normalize
|
1103
1135
|
if WINDOWS:
|
1104
1136
|
cased = {}
|
@@ -1283,6 +1315,16 @@ class AuthSrv(object):
|
|
1283
1315
|
use = True
|
1284
1316
|
lim.bmax, lim.bwin = [unhumanize(x) for x in zs.split(",")]
|
1285
1317
|
|
1318
|
+
zs = vol.flags.get("vmaxb")
|
1319
|
+
if zs:
|
1320
|
+
use = True
|
1321
|
+
lim.vbmax = unhumanize(zs)
|
1322
|
+
|
1323
|
+
zs = vol.flags.get("vmaxn")
|
1324
|
+
if zs:
|
1325
|
+
use = True
|
1326
|
+
lim.vnmax = unhumanize(zs)
|
1327
|
+
|
1286
1328
|
if use:
|
1287
1329
|
vol.lim = lim
|
1288
1330
|
|
@@ -1397,7 +1439,7 @@ class AuthSrv(object):
|
|
1397
1439
|
|
1398
1440
|
# append additive args from argv to volflags
|
1399
1441
|
hooks = "xbu xau xiu xbr xar xbd xad xm".split()
|
1400
|
-
for name in
|
1442
|
+
for name in "mtp on404 on403".split() + hooks:
|
1401
1443
|
self._read_volflag(vol.flags, name, getattr(self.args, name), True)
|
1402
1444
|
|
1403
1445
|
for hn in hooks:
|
@@ -1529,6 +1571,10 @@ class AuthSrv(object):
|
|
1529
1571
|
self.log(t, 1)
|
1530
1572
|
errors = True
|
1531
1573
|
|
1574
|
+
if self.args.smb and self.ah.on and acct:
|
1575
|
+
self.log("--smb can only be used when --ah-alg is none", 1)
|
1576
|
+
errors = True
|
1577
|
+
|
1532
1578
|
for vol in vfs.all_vols.values():
|
1533
1579
|
for k in list(vol.flags.keys()):
|
1534
1580
|
if re.match("^-[^-]+$", k):
|
@@ -1598,7 +1644,51 @@ class AuthSrv(object):
|
|
1598
1644
|
self.re_pwd = None
|
1599
1645
|
pwds = [re.escape(x) for x in self.iacct.keys()]
|
1600
1646
|
if pwds:
|
1601
|
-
self.
|
1647
|
+
if self.ah.on:
|
1648
|
+
zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)"
|
1649
|
+
else:
|
1650
|
+
zs = r"(\[H\] pw:.*|=)(" + "|".join(pwds) + r")([]&; ]|$)"
|
1651
|
+
|
1652
|
+
self.re_pwd = re.compile(zs)
|
1653
|
+
|
1654
|
+
def setup_pwhash(self, acct ) :
|
1655
|
+
self.ah = PWHash(self.args)
|
1656
|
+
if not self.ah.on:
|
1657
|
+
return
|
1658
|
+
|
1659
|
+
if self.args.ah_cli:
|
1660
|
+
self.ah.cli()
|
1661
|
+
sys.exit()
|
1662
|
+
elif self.args.ah_gen == "-":
|
1663
|
+
self.ah.stdin()
|
1664
|
+
sys.exit()
|
1665
|
+
elif self.args.ah_gen:
|
1666
|
+
print(self.ah.hash(self.args.ah_gen))
|
1667
|
+
sys.exit()
|
1668
|
+
|
1669
|
+
if not acct:
|
1670
|
+
return
|
1671
|
+
|
1672
|
+
changed = False
|
1673
|
+
for uname, pw in list(acct.items())[:]:
|
1674
|
+
if pw.startswith("+") and len(pw) == 33:
|
1675
|
+
continue
|
1676
|
+
|
1677
|
+
changed = True
|
1678
|
+
hpw = self.ah.hash(pw)
|
1679
|
+
acct[uname] = hpw
|
1680
|
+
t = "hashed password for account {}: {}"
|
1681
|
+
self.log(t.format(uname, hpw), 3)
|
1682
|
+
|
1683
|
+
if not changed:
|
1684
|
+
return
|
1685
|
+
|
1686
|
+
lns = []
|
1687
|
+
for uname, pw in acct.items():
|
1688
|
+
lns.append(" {}: {}".format(uname, pw))
|
1689
|
+
|
1690
|
+
t = "please use the following hashed passwords in your config:\n{}"
|
1691
|
+
self.log(t.format("\n".join(lns)), 3)
|
1602
1692
|
|
1603
1693
|
def chk_sqlite_threadsafe(self) :
|
1604
1694
|
v = SQLITE_VER[-1:]
|
@@ -1738,7 +1828,7 @@ class AuthSrv(object):
|
|
1738
1828
|
]
|
1739
1829
|
|
1740
1830
|
csv = set("i p".split())
|
1741
|
-
lst = set("c ihead mtm mtp xad xar xau xiu xbd xbr xbu xm".split())
|
1831
|
+
lst = set("c ihead mtm mtp xad xar xau xiu xbd xbr xbu xm on404 on403".split())
|
1742
1832
|
askip = set("a v c vc cgen theme".split())
|
1743
1833
|
|
1744
1834
|
# keymap from argv to vflag
|
copyparty/broker_mp.py
CHANGED
@@ -9,7 +9,7 @@ import queue
|
|
9
9
|
|
10
10
|
from .__init__ import CORES, TYPE_CHECKING
|
11
11
|
from .broker_mpw import MpWorker
|
12
|
-
from .broker_util import try_exec
|
12
|
+
from .broker_util import ExceptionalQueue, try_exec
|
13
13
|
from .util import Daemon, mp
|
14
14
|
|
15
15
|
if TYPE_CHECKING:
|
@@ -103,6 +103,19 @@ class BrokerMp(object):
|
|
103
103
|
if retq_id:
|
104
104
|
proc.q_pend.put((retq_id, "retq", rv))
|
105
105
|
|
106
|
+
def ask(self, dest , *args ) :
|
107
|
+
|
108
|
+
# new non-ipc invoking managed service in hub
|
109
|
+
obj = self.hub
|
110
|
+
for node in dest.split("."):
|
111
|
+
obj = getattr(obj, node)
|
112
|
+
|
113
|
+
rv = try_exec(True, obj, *args)
|
114
|
+
|
115
|
+
retq = ExceptionalQueue(1)
|
116
|
+
retq.put(rv)
|
117
|
+
return retq
|
118
|
+
|
106
119
|
def say(self, dest , *args ) :
|
107
120
|
"""
|
108
121
|
send message to non-hub component in other process,
|
copyparty/cert.py
CHANGED
@@ -10,7 +10,6 @@ from .util import Netdev, runcmd
|
|
10
10
|
|
11
11
|
HAVE_CFSSL = True
|
12
12
|
|
13
|
-
|
14
13
|
def ensure_cert(log , args) :
|
15
14
|
"""
|
16
15
|
the default cert (and the entire TLS support) is only here to enable the
|
@@ -118,6 +117,9 @@ def _gen_srv(log , args, netdevs ):
|
|
118
117
|
names.append(ip.split("/")[0])
|
119
118
|
if args.crt_nolo:
|
120
119
|
names = [x for x in names if x not in ("localhost", "127.0.0.1", "::1")]
|
120
|
+
if not args.crt_nohn:
|
121
|
+
names.append(args.name)
|
122
|
+
names.append(args.name + ".local")
|
121
123
|
if not names:
|
122
124
|
names = ["127.0.0.1"]
|
123
125
|
if "127.0.0.1" in names or "::1" in names:
|
copyparty/cfg.py
CHANGED
@@ -78,7 +78,9 @@ flagcats = {
|
|
78
78
|
},
|
79
79
|
"upload rules": {
|
80
80
|
"maxn=250,600": "max 250 uploads over 15min",
|
81
|
-
"maxb=1g,300": "max 1 GiB over 5min (suffixes: b, k, m, g)",
|
81
|
+
"maxb=1g,300": "max 1 GiB over 5min (suffixes: b, k, m, g, t)",
|
82
|
+
"vmaxb=1g": "total volume size max 1 GiB (suffixes: b, k, m, g, t)",
|
83
|
+
"vmaxn=4k": "max 4096 files in volume (suffixes: b, k, m, g, t)",
|
82
84
|
"rand": "force randomized filenames, 9 chars long by default",
|
83
85
|
"nrand=N": "randomized filenames are N chars long",
|
84
86
|
"sz=1k-3m": "allow filesizes between 1 KiB and 3MiB",
|
@@ -123,6 +125,10 @@ flagcats = {
|
|
123
125
|
"dathumb": "disables audio thumbnails (spectrograms)",
|
124
126
|
"dithumb": "disables image thumbnails",
|
125
127
|
},
|
128
|
+
"handlers\n(better explained in --help-handlers)": {
|
129
|
+
"on404=PY": "handle 404s by executing PY file",
|
130
|
+
"on403=PY": "handle 403s by executing PY file",
|
131
|
+
},
|
126
132
|
"event hooks\n(better explained in --help-hooks)": {
|
127
133
|
"xbu=CMD": "execute CMD before a file upload starts",
|
128
134
|
"xau=CMD": "execute CMD after a file upload finishes",
|
copyparty/ftpd.py
CHANGED
@@ -74,10 +74,13 @@ class FtpAuth(DummyAuthorizer):
|
|
74
74
|
raise AuthenticationFailed("banned")
|
75
75
|
|
76
76
|
asrv = self.hub.asrv
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
77
|
+
uname = "*"
|
78
|
+
if username != "anonymous":
|
79
|
+
for zs in (password, username):
|
80
|
+
zs = asrv.iacct.get(asrv.ah.hash(zs), "")
|
81
|
+
if zs:
|
82
|
+
uname = zs
|
83
|
+
break
|
81
84
|
|
82
85
|
if not uname or not (asrv.vfs.aread.get(uname) or asrv.vfs.awrite.get(uname)):
|
83
86
|
g = self.hub.gpwd
|