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/authsrv.py
CHANGED
@@ -18,7 +18,6 @@ from .cfg import flagdescs, permdescs, vf_bmap, vf_cmap, vf_vmap
|
|
18
18
|
from .pwhash import PWHash
|
19
19
|
from .util import (
|
20
20
|
IMPLICATIONS,
|
21
|
-
META_NOBOTS,
|
22
21
|
SQLITE_VER,
|
23
22
|
UNPLICATIONS,
|
24
23
|
UTC,
|
@@ -34,6 +33,7 @@ from .util import (
|
|
34
33
|
uncyg,
|
35
34
|
undot,
|
36
35
|
unhumanize,
|
36
|
+
vsplit,
|
37
37
|
)
|
38
38
|
|
39
39
|
if TYPE_CHECKING:
|
@@ -54,6 +54,10 @@ BAD_CFG = "invalid config; {}".format(SEE_LOG)
|
|
54
54
|
SBADCFG = " ({})".format(BAD_CFG)
|
55
55
|
|
56
56
|
|
57
|
+
class CfgEx(Exception):
|
58
|
+
pass
|
59
|
+
|
60
|
+
|
57
61
|
class AXS(object):
|
58
62
|
def __init__(
|
59
63
|
self,
|
@@ -773,6 +777,20 @@ class AuthSrv(object):
|
|
773
777
|
self.line_ctr = 0
|
774
778
|
self.indent = ""
|
775
779
|
|
780
|
+
# fwd-decl
|
781
|
+
self.vfs = VFS(log_func, "", "", AXS(), {})
|
782
|
+
self.acct = {}
|
783
|
+
self.iacct = {}
|
784
|
+
self.grps = {}
|
785
|
+
self.re_pwd = None
|
786
|
+
|
787
|
+
# all volumes observed since last restart
|
788
|
+
self.idp_vols = {} # vpath->abspath
|
789
|
+
|
790
|
+
# all users/groups observed since last restart
|
791
|
+
self.idp_accs = {} # username->groupnames
|
792
|
+
self.idp_usr_gh = {} # username->group-header-value (cache)
|
793
|
+
|
776
794
|
self.mutex = threading.Lock()
|
777
795
|
self.reload()
|
778
796
|
|
@@ -790,6 +808,86 @@ class AuthSrv(object):
|
|
790
808
|
|
791
809
|
yield prev, True
|
792
810
|
|
811
|
+
def idp_checkin(
|
812
|
+
self, broker , uname , gname
|
813
|
+
) :
|
814
|
+
if uname in self.acct:
|
815
|
+
return False
|
816
|
+
|
817
|
+
if self.idp_usr_gh.get(uname) == gname:
|
818
|
+
return False
|
819
|
+
|
820
|
+
gnames = [x.strip() for x in self.args.idp_gsep.split(gname)]
|
821
|
+
gnames.sort()
|
822
|
+
|
823
|
+
with self.mutex:
|
824
|
+
self.idp_usr_gh[uname] = gname
|
825
|
+
if self.idp_accs.get(uname) == gnames:
|
826
|
+
return False
|
827
|
+
|
828
|
+
self.idp_accs[uname] = gnames
|
829
|
+
|
830
|
+
t = "reinitializing due to new user from IdP: [%s:%s]"
|
831
|
+
self.log(t % (uname, gnames), 3)
|
832
|
+
|
833
|
+
if not broker:
|
834
|
+
# only true for tests
|
835
|
+
self._reload()
|
836
|
+
return True
|
837
|
+
|
838
|
+
broker.ask("_reload_blocking", False).get()
|
839
|
+
return True
|
840
|
+
|
841
|
+
def _map_volume_idp(
|
842
|
+
self,
|
843
|
+
src ,
|
844
|
+
dst ,
|
845
|
+
mount ,
|
846
|
+
daxs ,
|
847
|
+
mflags ,
|
848
|
+
un_gns ,
|
849
|
+
) :
|
850
|
+
ret = []
|
851
|
+
visited = set()
|
852
|
+
src0 = src # abspath
|
853
|
+
dst0 = dst # vpath
|
854
|
+
|
855
|
+
un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
|
856
|
+
if not un_gn:
|
857
|
+
# ensure volume creation if there's no users
|
858
|
+
un_gn = [("", "")]
|
859
|
+
|
860
|
+
for un, gn in un_gn:
|
861
|
+
# if ap/vp has a user/group placeholder, make sure to keep
|
862
|
+
# track so the same user/gruop is mapped when setting perms;
|
863
|
+
# otherwise clear un/gn to indicate it's a regular volume
|
864
|
+
|
865
|
+
src1 = src0.replace("${u}", un or "\n")
|
866
|
+
dst1 = dst0.replace("${u}", un or "\n")
|
867
|
+
if src0 == src1 and dst0 == dst1:
|
868
|
+
un = ""
|
869
|
+
|
870
|
+
src = src1.replace("${g}", gn or "\n")
|
871
|
+
dst = dst1.replace("${g}", gn or "\n")
|
872
|
+
if src == src1 and dst == dst1:
|
873
|
+
gn = ""
|
874
|
+
|
875
|
+
if "\n" in (src + dst):
|
876
|
+
continue
|
877
|
+
|
878
|
+
label = "%s\n%s" % (src, dst)
|
879
|
+
if label in visited:
|
880
|
+
continue
|
881
|
+
visited.add(label)
|
882
|
+
|
883
|
+
src, dst = self._map_volume(src, dst, mount, daxs, mflags)
|
884
|
+
if src:
|
885
|
+
ret.append((src, dst, un, gn))
|
886
|
+
if un or gn:
|
887
|
+
self.idp_vols[dst] = src
|
888
|
+
|
889
|
+
return ret
|
890
|
+
|
793
891
|
def _map_volume(
|
794
892
|
self,
|
795
893
|
src ,
|
@@ -797,7 +895,11 @@ class AuthSrv(object):
|
|
797
895
|
mount ,
|
798
896
|
daxs ,
|
799
897
|
mflags ,
|
800
|
-
)
|
898
|
+
) :
|
899
|
+
src = os.path.expandvars(os.path.expanduser(src))
|
900
|
+
src = absreal(src)
|
901
|
+
dst = dst.strip("/")
|
902
|
+
|
801
903
|
if dst in mount:
|
802
904
|
t = "multiple filesystem-paths mounted at [/{}]:\n [{}]\n [{}]"
|
803
905
|
self.log(t.format(dst, mount[dst], src), c=1)
|
@@ -818,6 +920,7 @@ class AuthSrv(object):
|
|
818
920
|
mount[dst] = src
|
819
921
|
daxs[dst] = AXS()
|
820
922
|
mflags[dst] = {}
|
923
|
+
return (src, dst)
|
821
924
|
|
822
925
|
def _e(self, desc = None) :
|
823
926
|
if not self.args.vc or not self.line_ctr:
|
@@ -845,31 +948,76 @@ class AuthSrv(object):
|
|
845
948
|
|
846
949
|
self.log(t.format(self.line_ctr, c, self.indent, ln, desc))
|
847
950
|
|
951
|
+
def _all_un_gn(
|
952
|
+
self,
|
953
|
+
acct ,
|
954
|
+
grps ,
|
955
|
+
) :
|
956
|
+
"""
|
957
|
+
generate list of all confirmed pairs of username/groupname seen since last restart;
|
958
|
+
in case of conflicting group memberships then it is selected as follows:
|
959
|
+
* any non-zero value from IdP group header
|
960
|
+
* otherwise take --grps / [groups]
|
961
|
+
"""
|
962
|
+
ret = {un: gns[:] for un, gns in self.idp_accs.items()}
|
963
|
+
ret.update({zs: [""] for zs in acct if zs not in ret})
|
964
|
+
for gn, uns in grps.items():
|
965
|
+
for un in uns:
|
966
|
+
try:
|
967
|
+
ret[un].append(gn)
|
968
|
+
except:
|
969
|
+
ret[un] = [gn]
|
970
|
+
|
971
|
+
return ret
|
972
|
+
|
848
973
|
def _parse_config_file(
|
849
974
|
self,
|
850
975
|
fp ,
|
851
976
|
cfg_lines ,
|
852
977
|
acct ,
|
978
|
+
grps ,
|
853
979
|
daxs ,
|
854
980
|
mflags ,
|
855
981
|
mount ,
|
856
982
|
) :
|
857
983
|
self.line_ctr = 0
|
858
984
|
|
859
|
-
expand_config_file(cfg_lines, fp, "")
|
985
|
+
expand_config_file(self.log, cfg_lines, fp, "")
|
860
986
|
if self.args.vc:
|
861
987
|
lns = ["{:4}: {}".format(n, s) for n, s in enumerate(cfg_lines, 1)]
|
862
988
|
self.log("expanded config file (unprocessed):\n" + "\n".join(lns))
|
863
989
|
|
864
990
|
cfg_lines = upgrade_cfg_fmt(self.log, self.args, cfg_lines, fp)
|
865
991
|
|
992
|
+
# due to IdP, volumes must be parsed after users and groups;
|
993
|
+
# do volumes in a 2nd pass to allow arbitrary order in config files
|
994
|
+
for npass in range(1, 3):
|
995
|
+
if self.args.vc:
|
996
|
+
self.log("parsing config files; pass %d/%d" % (npass, 2))
|
997
|
+
self._parse_config_file_2(cfg_lines, acct, grps, daxs, mflags, mount, npass)
|
998
|
+
|
999
|
+
def _parse_config_file_2(
|
1000
|
+
self,
|
1001
|
+
cfg_lines ,
|
1002
|
+
acct ,
|
1003
|
+
grps ,
|
1004
|
+
daxs ,
|
1005
|
+
mflags ,
|
1006
|
+
mount ,
|
1007
|
+
npass ,
|
1008
|
+
) :
|
1009
|
+
self.line_ctr = 0
|
1010
|
+
all_un_gn = self._all_un_gn(acct, grps)
|
1011
|
+
|
866
1012
|
cat = ""
|
867
1013
|
catg = "[global]"
|
868
1014
|
cata = "[accounts]"
|
1015
|
+
catgrp = "[groups]"
|
869
1016
|
catx = "accs:"
|
870
1017
|
catf = "flags:"
|
871
1018
|
ap = None
|
872
1019
|
vp = None
|
1020
|
+
vols = []
|
873
1021
|
for ln in cfg_lines:
|
874
1022
|
self.line_ctr += 1
|
875
1023
|
ln = ln.split(" #")[0].strip()
|
@@ -882,7 +1030,7 @@ class AuthSrv(object):
|
|
882
1030
|
subsection = ln in (catx, catf)
|
883
1031
|
if ln.startswith("[") or subsection:
|
884
1032
|
self._e()
|
885
|
-
if ap is None and vp is not None:
|
1033
|
+
if npass > 1 and ap is None and vp is not None:
|
886
1034
|
t = "the first line after [/{}] must be a filesystem path to share on that volume"
|
887
1035
|
raise Exception(t.format(vp))
|
888
1036
|
|
@@ -898,6 +1046,8 @@ class AuthSrv(object):
|
|
898
1046
|
self._l(ln, 6, t)
|
899
1047
|
elif ln == cata:
|
900
1048
|
self._l(ln, 5, "begin user-accounts section")
|
1049
|
+
elif ln == catgrp:
|
1050
|
+
self._l(ln, 5, "begin user-groups section")
|
901
1051
|
elif ln.startswith("[/"):
|
902
1052
|
vp = ln[1:-1].strip("/")
|
903
1053
|
self._l(ln, 2, "define volume at URL [/{}]".format(vp))
|
@@ -934,15 +1084,39 @@ class AuthSrv(object):
|
|
934
1084
|
raise Exception(t + SBADCFG)
|
935
1085
|
continue
|
936
1086
|
|
1087
|
+
if cat == catgrp:
|
1088
|
+
try:
|
1089
|
+
gn, zs1 = [zs.strip() for zs in ln.split(":", 1)]
|
1090
|
+
uns = [zs.strip() for zs in zs1.split(",")]
|
1091
|
+
t = "group [%s] = " % (gn,)
|
1092
|
+
t += ", ".join("user [%s]" % (x,) for x in uns)
|
1093
|
+
self._l(ln, 5, t)
|
1094
|
+
grps[gn] = uns
|
1095
|
+
except:
|
1096
|
+
t = 'lines inside the [groups] section must be "groupname: user1, user2, user..."'
|
1097
|
+
raise Exception(t + SBADCFG)
|
1098
|
+
continue
|
1099
|
+
|
937
1100
|
if vp is not None and ap is None:
|
1101
|
+
if npass != 2:
|
1102
|
+
continue
|
1103
|
+
|
938
1104
|
ap = ln
|
939
|
-
ap = os.path.expandvars(os.path.expanduser(ap))
|
940
|
-
ap = absreal(ap)
|
941
1105
|
self._l(ln, 2, "bound to filesystem-path [{}]".format(ap))
|
942
|
-
self.
|
1106
|
+
vols = self._map_volume_idp(ap, vp, mount, daxs, mflags, all_un_gn)
|
1107
|
+
if not vols:
|
1108
|
+
ap = vp = None
|
1109
|
+
self._l(ln, 2, "└─no users/groups known; was not mapped")
|
1110
|
+
elif len(vols) > 1:
|
1111
|
+
for vol in vols:
|
1112
|
+
self._l(ln, 2, "└─mapping: [%s] => [%s]" % (vol[1], vol[0]))
|
943
1113
|
continue
|
944
1114
|
|
945
1115
|
if cat == catx:
|
1116
|
+
if npass != 2 or not ap:
|
1117
|
+
# not stage2, or unmapped ${u}/${g}
|
1118
|
+
continue
|
1119
|
+
|
946
1120
|
err = ""
|
947
1121
|
try:
|
948
1122
|
self._l(ln, 5, "volume access config:")
|
@@ -953,14 +1127,20 @@ class AuthSrv(object):
|
|
953
1127
|
if " " in re.sub(", *", "", sv).strip():
|
954
1128
|
err = "list of users is not comma-separated; "
|
955
1129
|
raise Exception(err)
|
956
|
-
|
957
|
-
self.
|
1130
|
+
sv = sv.replace(" ", "")
|
1131
|
+
self._read_vol_str_idp(sk, sv, vols, all_un_gn, daxs, mflags)
|
958
1132
|
continue
|
1133
|
+
except CfgEx:
|
1134
|
+
raise
|
959
1135
|
except:
|
960
1136
|
err += "accs entries must be 'rwmdgGhaA.: user1, user2, ...'"
|
961
|
-
raise
|
1137
|
+
raise CfgEx(err + SBADCFG)
|
962
1138
|
|
963
1139
|
if cat == catf:
|
1140
|
+
if npass != 2 or not ap:
|
1141
|
+
# not stage2, or unmapped ${u}/${g}
|
1142
|
+
continue
|
1143
|
+
|
964
1144
|
err = ""
|
965
1145
|
try:
|
966
1146
|
self._l(ln, 6, "volume-specific config:")
|
@@ -977,11 +1157,14 @@ class AuthSrv(object):
|
|
977
1157
|
else:
|
978
1158
|
fstr += ",{}={}".format(sk, sv)
|
979
1159
|
assert vp is not None
|
980
|
-
self.
|
1160
|
+
self._read_vol_str_idp(
|
1161
|
+
"c", fstr[1:], vols, all_un_gn, daxs, mflags
|
1162
|
+
)
|
981
1163
|
fstr = ""
|
982
1164
|
if fstr:
|
983
|
-
|
984
|
-
|
1165
|
+
self._read_vol_str_idp(
|
1166
|
+
"c", fstr[1:], vols, all_un_gn, daxs, mflags
|
1167
|
+
)
|
985
1168
|
continue
|
986
1169
|
except:
|
987
1170
|
err += "flags entries (volflags) must be one of the following:\n 'flag1, flag2, ...'\n 'key: value'\n 'flag1, flag2, key: value'"
|
@@ -992,12 +1175,18 @@ class AuthSrv(object):
|
|
992
1175
|
self._e()
|
993
1176
|
self.line_ctr = 0
|
994
1177
|
|
995
|
-
def
|
996
|
-
self,
|
1178
|
+
def _read_vol_str_idp(
|
1179
|
+
self,
|
1180
|
+
lvl ,
|
1181
|
+
uname ,
|
1182
|
+
vols ,
|
1183
|
+
un_gns ,
|
1184
|
+
axs ,
|
1185
|
+
flags ,
|
997
1186
|
) :
|
998
1187
|
if lvl.strip("crwmdgGhaA."):
|
999
1188
|
t = "%s,%s" % (lvl, uname) if uname else lvl
|
1000
|
-
raise
|
1189
|
+
raise CfgEx("invalid config value (volume or volflag): %s" % (t,))
|
1001
1190
|
|
1002
1191
|
if lvl == "c":
|
1003
1192
|
# here, 'uname' is not a username; it is a volflag name... sorry
|
@@ -1012,16 +1201,63 @@ class AuthSrv(object):
|
|
1012
1201
|
while "," in uname:
|
1013
1202
|
# one or more bools before the final flag; eat them
|
1014
1203
|
n1, uname = uname.split(",", 1)
|
1015
|
-
|
1204
|
+
for _, vp, _, _ in vols:
|
1205
|
+
self._read_volflag(flags[vp], n1, True, False)
|
1206
|
+
|
1207
|
+
for _, vp, _, _ in vols:
|
1208
|
+
self._read_volflag(flags[vp], uname, cval, False)
|
1016
1209
|
|
1017
|
-
self._read_volflag(flags, uname, cval, False)
|
1018
1210
|
return
|
1019
1211
|
|
1020
1212
|
if uname == "":
|
1021
1213
|
uname = "*"
|
1022
1214
|
|
1023
|
-
|
1215
|
+
unames = []
|
1024
1216
|
for un in uname.replace(",", " ").strip().split():
|
1217
|
+
if un.startswith("@"):
|
1218
|
+
grp = un[1:]
|
1219
|
+
uns = [x[0] for x in un_gns.items() if grp in x[1]]
|
1220
|
+
if not uns and grp != "${g}" and not self.args.idp_h_grp:
|
1221
|
+
t = "group [%s] must be defined with --grp argument (or in a [groups] config section)"
|
1222
|
+
raise CfgEx(t % (grp,))
|
1223
|
+
|
1224
|
+
unames.extend(uns)
|
1225
|
+
else:
|
1226
|
+
unames.append(un)
|
1227
|
+
|
1228
|
+
# unames may still contain ${u} and ${g} so now expand those;
|
1229
|
+
un_gn = [(un, gn) for un, gns in un_gns.items() for gn in gns]
|
1230
|
+
if "*" not in un_gns:
|
1231
|
+
# need ("*","") to match "*" in unames
|
1232
|
+
un_gn.append(("*", ""))
|
1233
|
+
|
1234
|
+
for _, dst, vu, vg in vols:
|
1235
|
+
unames2 = set()
|
1236
|
+
for un, gn in un_gn:
|
1237
|
+
# if vu/vg (volume user/group) is non-null,
|
1238
|
+
# then each non-null value corresponds to
|
1239
|
+
# ${u}/${g}; consider this a filter to
|
1240
|
+
# apply to unames, as well as un_gn
|
1241
|
+
if (vu and vu != un) or (vg and vg != gn):
|
1242
|
+
continue
|
1243
|
+
|
1244
|
+
for uname in unames + ([un] if vu or vg else []):
|
1245
|
+
if uname == "${u}":
|
1246
|
+
uname = vu or un
|
1247
|
+
elif uname in ("${g}", "@${g}"):
|
1248
|
+
uname = vg or gn
|
1249
|
+
|
1250
|
+
if vu and vu != uname:
|
1251
|
+
continue
|
1252
|
+
|
1253
|
+
if uname:
|
1254
|
+
unames2.add(uname)
|
1255
|
+
|
1256
|
+
self._read_vol_str(lvl, list(unames2), axs[dst])
|
1257
|
+
|
1258
|
+
def _read_vol_str(self, lvl , unames , axs ) :
|
1259
|
+
junkset = set()
|
1260
|
+
for un in unames:
|
1025
1261
|
for alias, mapping in [
|
1026
1262
|
("h", "gh"),
|
1027
1263
|
("G", "gG"),
|
@@ -1098,12 +1334,18 @@ class AuthSrv(object):
|
|
1098
1334
|
then supplementing with config files
|
1099
1335
|
before finally building the VFS
|
1100
1336
|
"""
|
1337
|
+
with self.mutex:
|
1338
|
+
self._reload()
|
1101
1339
|
|
1340
|
+
def _reload(self) :
|
1102
1341
|
acct = {} # username:password
|
1342
|
+
grps = {} # groupname:usernames
|
1103
1343
|
daxs = {}
|
1104
1344
|
mflags = {} # moutpoint:flags
|
1105
1345
|
mount = {} # dst:src (mountpoint:realpath)
|
1106
1346
|
|
1347
|
+
self.idp_vols = {} # yolo
|
1348
|
+
|
1107
1349
|
if self.args.a:
|
1108
1350
|
# list of username:password
|
1109
1351
|
for x in self.args.a:
|
@@ -1114,9 +1356,22 @@ class AuthSrv(object):
|
|
1114
1356
|
t = '\n invalid value "{}" for argument -a, must be username:password'
|
1115
1357
|
raise Exception(t.format(x))
|
1116
1358
|
|
1359
|
+
if self.args.grp:
|
1360
|
+
# list of groupname:username,username,...
|
1361
|
+
for x in self.args.grp:
|
1362
|
+
try:
|
1363
|
+
# accept both = and : as separator between groupname and usernames,
|
1364
|
+
# accept both , and : as separators between usernames
|
1365
|
+
zs1, zs2 = x.replace("=", ":").split(":", 1)
|
1366
|
+
grps[zs1] = zs2.replace(":", ",").split(",")
|
1367
|
+
except:
|
1368
|
+
t = '\n invalid value "{}" for argument --grp, must be groupname:username1,username2,...'
|
1369
|
+
raise Exception(t.format(x))
|
1370
|
+
|
1117
1371
|
if self.args.v:
|
1118
1372
|
# list of src:dst:permset:permset:...
|
1119
1373
|
# permset is <rwmdgGhaA.>[,username][,username] or <c>,<flag>[=args]
|
1374
|
+
all_un_gn = self._all_un_gn(acct, grps)
|
1120
1375
|
for v_str in self.args.v:
|
1121
1376
|
m = re_vol.match(v_str)
|
1122
1377
|
if not m:
|
@@ -1126,20 +1381,19 @@ class AuthSrv(object):
|
|
1126
1381
|
if WINDOWS:
|
1127
1382
|
src = uncyg(src)
|
1128
1383
|
|
1129
|
-
|
1130
|
-
src = absreal(src)
|
1131
|
-
dst = dst.strip("/")
|
1132
|
-
self._map_volume(src, dst, mount, daxs, mflags)
|
1384
|
+
vols = self._map_volume_idp(src, dst, mount, daxs, mflags, all_un_gn)
|
1133
1385
|
|
1134
1386
|
for x in perms.split(":"):
|
1135
1387
|
lvl, uname = x.split(",", 1) if "," in x else [x, ""]
|
1136
|
-
self.
|
1388
|
+
self._read_vol_str_idp(lvl, uname, vols, all_un_gn, daxs, mflags)
|
1137
1389
|
|
1138
1390
|
if self.args.c:
|
1139
1391
|
for cfg_fn in self.args.c:
|
1140
1392
|
lns = []
|
1141
1393
|
try:
|
1142
|
-
self._parse_config_file(
|
1394
|
+
self._parse_config_file(
|
1395
|
+
cfg_fn, lns, acct, grps, daxs, mflags, mount
|
1396
|
+
)
|
1143
1397
|
|
1144
1398
|
zs = "#\033[36m cfg files in "
|
1145
1399
|
zst = [x[len(zs) :] for x in lns if x.startswith(zs)]
|
@@ -1170,7 +1424,7 @@ class AuthSrv(object):
|
|
1170
1424
|
|
1171
1425
|
mount = cased
|
1172
1426
|
|
1173
|
-
if not mount:
|
1427
|
+
if not mount and not self.args.idp_h_usr:
|
1174
1428
|
# -h says our defaults are CWD at root and read/write for everyone
|
1175
1429
|
axs = AXS(["*"], ["*"], None, None)
|
1176
1430
|
vfs = VFS(self.log_func, absreal("."), "", axs, {})
|
@@ -1206,9 +1460,13 @@ class AuthSrv(object):
|
|
1206
1460
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
1207
1461
|
vol.root = vfs
|
1208
1462
|
|
1463
|
+
zss = set(acct)
|
1464
|
+
zss.update(self.idp_accs)
|
1465
|
+
zss.discard("*")
|
1466
|
+
unames = ["*"] + list(sorted(zss))
|
1467
|
+
|
1209
1468
|
for perm in "read write move del get pget html admin dot".split():
|
1210
1469
|
axs_key = "u" + perm
|
1211
|
-
unames = ["*"] + list(acct.keys())
|
1212
1470
|
for vp, vol in vfs.all_vols.items():
|
1213
1471
|
zx = getattr(vol.axs, axs_key)
|
1214
1472
|
if "*" in zx:
|
@@ -1242,18 +1500,20 @@ class AuthSrv(object):
|
|
1242
1500
|
]:
|
1243
1501
|
for usr in d:
|
1244
1502
|
all_users[usr] = 1
|
1245
|
-
if usr != "*" and usr not in acct:
|
1503
|
+
if usr != "*" and usr not in acct and usr not in self.idp_accs:
|
1246
1504
|
missing_users[usr] = 1
|
1247
1505
|
if "*" not in d:
|
1248
1506
|
associated_users[usr] = 1
|
1249
1507
|
|
1250
1508
|
if missing_users:
|
1251
|
-
|
1252
|
-
|
1253
|
-
|
1254
|
-
c=
|
1255
|
-
|
1256
|
-
|
1509
|
+
zs = ", ".join(k for k in sorted(missing_users))
|
1510
|
+
if self.args.idp_h_usr:
|
1511
|
+
t = "the following users are unknown, and assumed to come from IdP: "
|
1512
|
+
self.log(t + zs, c=6)
|
1513
|
+
else:
|
1514
|
+
t = "you must -a the following users: "
|
1515
|
+
self.log(t + zs, c=1)
|
1516
|
+
raise Exception(BAD_CFG)
|
1257
1517
|
|
1258
1518
|
if LEELOO_DALLAS in all_users:
|
1259
1519
|
raise Exception("sorry, reserved username: " + LEELOO_DALLAS)
|
@@ -1394,13 +1654,6 @@ class AuthSrv(object):
|
|
1394
1654
|
if not vol.flags.get("robots"):
|
1395
1655
|
vol.flags["norobots"] = True
|
1396
1656
|
|
1397
|
-
for vol in vfs.all_vols.values():
|
1398
|
-
h = [vol.flags.get("html_head", self.args.html_head)]
|
1399
|
-
if vol.flags.get("norobots"):
|
1400
|
-
h.insert(0, META_NOBOTS)
|
1401
|
-
|
1402
|
-
vol.flags["html_head"] = "\n".join([x for x in h if x])
|
1403
|
-
|
1404
1657
|
for vol in vfs.all_vols.values():
|
1405
1658
|
if self.args.no_vthumb:
|
1406
1659
|
vol.flags["dvthumb"] = True
|
@@ -1478,7 +1731,7 @@ class AuthSrv(object):
|
|
1478
1731
|
if k not in vol.flags:
|
1479
1732
|
vol.flags[k] = getattr(self.args, k)
|
1480
1733
|
|
1481
|
-
for k in ("nrand",):
|
1734
|
+
for k in ("nrand", "u2abort"):
|
1482
1735
|
if k in vol.flags:
|
1483
1736
|
vol.flags[k] = int(vol.flags[k])
|
1484
1737
|
|
@@ -1742,20 +1995,37 @@ class AuthSrv(object):
|
|
1742
1995
|
except Pebkac:
|
1743
1996
|
self.warn_anonwrite = True
|
1744
1997
|
|
1745
|
-
with
|
1746
|
-
|
1747
|
-
|
1748
|
-
|
1749
|
-
|
1750
|
-
|
1751
|
-
|
1752
|
-
|
1753
|
-
if
|
1754
|
-
|
1755
|
-
|
1756
|
-
|
1998
|
+
idp_err = "WARNING! The following IdP volumes are mounted directly below another volume where anonymous users can read and/or write files. This is a SECURITY HAZARD!! When copyparty is restarted, it will not know about these IdP volumes yet. These volumes will then be accessible by anonymous users UNTIL one of the users associated with their volume sends a request to the server. RECOMMENDATION: You should create a restricted volume where nobody can read/write files, and make sure that all IdP volumes are configured to appear somewhere below that volume."
|
1999
|
+
for idp_vp in self.idp_vols:
|
2000
|
+
parent_vp = vsplit(idp_vp)[0]
|
2001
|
+
vn, _ = vfs.get(parent_vp, "*", False, False)
|
2002
|
+
zs = (
|
2003
|
+
"READABLE"
|
2004
|
+
if "*" in vn.axs.uread
|
2005
|
+
else "WRITABLE"
|
2006
|
+
if "*" in vn.axs.uwrite
|
2007
|
+
else ""
|
2008
|
+
)
|
2009
|
+
if zs:
|
2010
|
+
t = '\nWARNING: Volume "/%s" appears below "/%s" and would be WORLD-%s'
|
2011
|
+
idp_err += t % (idp_vp, vn.vpath, zs)
|
2012
|
+
if "\n" in idp_err:
|
2013
|
+
self.log(idp_err, 1)
|
2014
|
+
|
2015
|
+
self.vfs = vfs
|
2016
|
+
self.acct = acct
|
2017
|
+
self.grps = grps
|
2018
|
+
self.iacct = {v: k for k, v in acct.items()}
|
2019
|
+
|
2020
|
+
self.re_pwd = None
|
2021
|
+
pwds = [re.escape(x) for x in self.iacct.keys()]
|
2022
|
+
if pwds:
|
2023
|
+
if self.ah.on:
|
2024
|
+
zs = r"(\[H\] pw:.*|[?&]pw=)([^&]+)"
|
2025
|
+
else:
|
2026
|
+
zs = r"(\[H\] pw:.*|=)(" + "|".join(pwds) + r")([]&; ]|$)"
|
1757
2027
|
|
1758
|
-
|
2028
|
+
self.re_pwd = re.compile(zs)
|
1759
2029
|
|
1760
2030
|
def setup_pwhash(self, acct ) :
|
1761
2031
|
self.ah = PWHash(self.args)
|
@@ -1997,6 +2267,12 @@ class AuthSrv(object):
|
|
1997
2267
|
ret.append(" {}: {}".format(u, p))
|
1998
2268
|
ret.append("")
|
1999
2269
|
|
2270
|
+
if self.grps:
|
2271
|
+
ret.append("[groups]")
|
2272
|
+
for gn, uns in self.grps.items():
|
2273
|
+
ret.append(" %s: %s" % (gn, ", ".join(uns)))
|
2274
|
+
ret.append("")
|
2275
|
+
|
2000
2276
|
for vol in self.vfs.all_vols.values():
|
2001
2277
|
ret.append("[/{}]".format(vol.vpath))
|
2002
2278
|
ret.append(" " + vol.realpath)
|
@@ -2094,27 +2370,50 @@ def split_cfg_ln(ln ) :
|
|
2094
2370
|
return ret
|
2095
2371
|
|
2096
2372
|
|
2097
|
-
def expand_config_file(
|
2373
|
+
def expand_config_file(
|
2374
|
+
log , ret , fp , ipath
|
2375
|
+
) :
|
2098
2376
|
"""expand all % file includes"""
|
2099
2377
|
fp = absreal(fp)
|
2100
2378
|
if len(ipath.split(" -> ")) > 64:
|
2101
2379
|
raise Exception("hit max depth of 64 includes")
|
2102
2380
|
|
2103
2381
|
if os.path.isdir(fp):
|
2104
|
-
names = os.listdir(fp)
|
2105
|
-
|
2106
|
-
|
2107
|
-
|
2382
|
+
names = list(sorted(os.listdir(fp)))
|
2383
|
+
cnames = [x for x in names if x.lower().endswith(".conf")]
|
2384
|
+
if not cnames:
|
2385
|
+
t = "warning: tried to read config-files from folder '%s' but it does not contain any "
|
2386
|
+
if names:
|
2387
|
+
t += ".conf files; the following files were ignored: %s"
|
2388
|
+
t = t % (fp, ", ".join(names[:8]))
|
2389
|
+
else:
|
2390
|
+
t += "files at all"
|
2391
|
+
t = t % (fp,)
|
2392
|
+
|
2393
|
+
if log:
|
2394
|
+
log(t, 3)
|
2395
|
+
|
2396
|
+
ret.append("#\033[33m %s\033[0m" % (t,))
|
2397
|
+
else:
|
2398
|
+
zs = "#\033[36m cfg files in %s => %s\033[0m" % (fp, cnames)
|
2399
|
+
ret.append(zs)
|
2400
|
+
|
2401
|
+
for fn in cnames:
|
2108
2402
|
fp2 = os.path.join(fp, fn)
|
2109
|
-
if
|
2403
|
+
if fp2 in ipath:
|
2110
2404
|
continue
|
2111
2405
|
|
2112
|
-
expand_config_file(ret, fp2, ipath)
|
2406
|
+
expand_config_file(log, ret, fp2, ipath)
|
2407
|
+
|
2408
|
+
return
|
2113
2409
|
|
2114
|
-
|
2115
|
-
|
2116
|
-
|
2410
|
+
if not os.path.exists(fp):
|
2411
|
+
t = "warning: tried to read config from '%s' but the file/folder does not exist"
|
2412
|
+
t = t % (fp,)
|
2413
|
+
if log:
|
2414
|
+
log(t, 3)
|
2117
2415
|
|
2416
|
+
ret.append("#\033[31m %s\033[0m" % (t,))
|
2118
2417
|
return
|
2119
2418
|
|
2120
2419
|
ipath += " -> " + fp
|
@@ -2128,7 +2427,7 @@ def expand_config_file(ret , fp , ipath ) :
|
|
2128
2427
|
fp2 = ln[1:].strip()
|
2129
2428
|
fp2 = os.path.join(os.path.dirname(fp), fp2)
|
2130
2429
|
ofs = len(ret)
|
2131
|
-
expand_config_file(ret, fp2, ipath)
|
2430
|
+
expand_config_file(log, ret, fp2, ipath)
|
2132
2431
|
for n in range(ofs, len(ret)):
|
2133
2432
|
ret[n] = pad + ret[n]
|
2134
2433
|
continue
|