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 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, fk_salt):
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\.(js|css)$\033[0m] (volflag=unlist)")
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, fk_salt)
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
- try:
1138
- idx = argv.index(dk)
1139
- except:
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, 7, 6)
4
- CODENAME = "unlinked"
5
- BUILD_DT = (2023, 6, 11)
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
- pass
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, "ingress saturated")
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 ["mtp"] + hooks:
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.re_pwd = re.compile("=(" + "|".join(pwds) + ")([]&; ]|$)")
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
- if username == "anonymous":
78
- uname = "*"
79
- else:
80
- uname = asrv.iacct.get(password, "") or asrv.iacct.get(username, "") or "*"
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