copyparty 1.13.8__tar.gz → 1.14.0__tar.gz
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-1.13.8 → copyparty-1.14.0}/PKG-INFO +55 -3
- {copyparty-1.13.8 → copyparty-1.14.0}/README.md +54 -2
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/__main__.py +58 -2
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/__version__.py +3 -3
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/authsrv.py +224 -11
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/httpcli.py +183 -26
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/httpsrv.py +12 -2
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/svchub.py +77 -5
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/tcpsrv.py +19 -1
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/u2idx.py +19 -3
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/up2k.py +67 -13
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/util.py +1 -1
- copyparty-1.14.0/copyparty/web/browser.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/browser.html +4 -4
- copyparty-1.14.0/copyparty/web/browser.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/browser2.html +2 -2
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/md.html +2 -2
- copyparty-1.14.0/copyparty/web/shares.css.gz +0 -0
- copyparty-1.14.0/copyparty/web/shares.html +74 -0
- copyparty-1.14.0/copyparty/web/shares.js.gz +0 -0
- copyparty-1.14.0/copyparty/web/splash.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/splash.html +13 -6
- copyparty-1.14.0/copyparty/web/splash.js.gz +0 -0
- copyparty-1.14.0/copyparty/web/util.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty.egg-info/PKG-INFO +55 -3
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty.egg-info/SOURCES.txt +3 -0
- copyparty-1.13.8/copyparty/web/browser.css.gz +0 -0
- copyparty-1.13.8/copyparty/web/browser.js.gz +0 -0
- copyparty-1.13.8/copyparty/web/splash.css.gz +0 -0
- copyparty-1.13.8/copyparty/web/splash.js.gz +0 -0
- copyparty-1.13.8/copyparty/web/util.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/LICENSE +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/bos/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/bos/bos.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/bos/path.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/broker_mp.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/broker_mpw.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/broker_thr.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/broker_util.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/cert.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/cfg.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/dxml.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/fsutil.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/ftpd.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/httpconn.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/ico.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/mdns.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/metrics.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/mtag.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/multicast.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/pwhash.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/res/COPYING.txt +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/res/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/res/insecure.pem +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/smbd.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/ssdp.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/star.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/bimap.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/bit.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/buffer.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/dns.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/label.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/lex.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/dnslib/ranges.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/ifaddr/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/ifaddr/_posix.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/ifaddr/_shared.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/ifaddr/_win32.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/qrcodegen.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/stolen/surrogateescape.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/sutil.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/szip.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/tftpd.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/th_cli.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/th_srv.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/a/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/a/partyfuse.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/a/u2c.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/a/webdav-cfg.bat +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/baguettebox.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/cf.html +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/dbg-audio.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/dd/2.png +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/dd/3.png +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/dd/4.png +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/dd/5.png +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/dd/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/__init__.py +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/busy.mp3.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/easymde.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/easymde.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/marked.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/mini-fa.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/mini-fa.woff +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/prism.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/prism.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/prismd.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/scp.woff2 +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/sha512.ac.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/deps/sha512.hw.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/md.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/md.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/md2.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/md2.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/mde.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/mde.html +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/mde.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/msg.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/msg.html +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/svcs.html +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/svcs.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/ui.css.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/up2k.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty/web/w.hash.js.gz +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty.egg-info/dependency_links.txt +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty.egg-info/entry_points.txt +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty.egg-info/requires.txt +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/copyparty.egg-info/top_level.txt +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/pyproject.toml +0 -0
- {copyparty-1.13.8 → copyparty-1.14.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: copyparty
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.14.0
|
4
4
|
Summary: Portable file server with accelerated resumable uploads, deduplication, WebDAV, FTP, zeroconf, media indexer, video thumbnails, audio transcoding, and write-only folders
|
5
5
|
Author-email: ed <copyparty@ocv.me>
|
6
6
|
License: MIT
|
@@ -96,6 +96,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
96
96
|
* [self-destruct](#self-destruct) - uploads can be given a lifetime
|
97
97
|
* [race the beam](#race-the-beam) - download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm))
|
98
98
|
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
99
|
+
* [shares](#shares) - share a file or folder by creating a temporary link
|
99
100
|
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
100
101
|
* [media player](#media-player) - plays almost every audio format there is
|
101
102
|
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
@@ -130,6 +131,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
130
131
|
* [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
|
131
132
|
* [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
|
132
133
|
* [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
|
134
|
+
* [user-changeable passwords](#user-changeable-passwords) - if permitted, users can change their own passwords
|
133
135
|
* [using the cloud as storage](#using-the-cloud-as-storage) - connecting to an aws s3 bucket and similar
|
134
136
|
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
135
137
|
* [themes](#themes)
|
@@ -798,6 +800,33 @@ file selection: click somewhere on the line (not the link itsef), then:
|
|
798
800
|
you can move files across browser tabs (cut in one tab, paste in another)
|
799
801
|
|
800
802
|
|
803
|
+
## shares
|
804
|
+
|
805
|
+
share a file or folder by creating a temporary link
|
806
|
+
|
807
|
+
when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or select a file first to share only that file
|
808
|
+
|
809
|
+
this feature was made with [identity providers](#identity-providers) in mind -- configure your reverseproxy to skip the IdP's access-control for a given URL prefix and use that to safely share specific files/folders sans the usual auth checks
|
810
|
+
|
811
|
+
when creating a share, the creator can choose any of the following options:
|
812
|
+
|
813
|
+
* password-protection
|
814
|
+
* expire after a certain time
|
815
|
+
* allow visitors to upload (if the user who creates the share has write-access)
|
816
|
+
|
817
|
+
semi-intentional limitations:
|
818
|
+
|
819
|
+
* cleanup of expired shares only works when global option `e2d` is set, and/or at least one volume on the server has volflag `e2d`
|
820
|
+
* only folders from the same volume are shared; if you are sharing a folder which contains other volumes, then the contents of those volumes will not be available
|
821
|
+
* no option to "delete after first access" because tricky
|
822
|
+
* when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
|
823
|
+
* browsers wouldn't be able to resume a broken download unless the requester's IP gets allowlisted for X minutes (ref. tricky)
|
824
|
+
|
825
|
+
the links are created inside a specific toplevel folder which must be specified with server-config `--shr`, for example `--shr /share/` (this also enables the feature)
|
826
|
+
|
827
|
+
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
|
828
|
+
|
829
|
+
|
801
830
|
## batch rename
|
802
831
|
|
803
832
|
select some files and press `F2` to bring up the rename UI
|
@@ -1409,6 +1438,29 @@ there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik)
|
|
1409
1438
|
|
1410
1439
|
a more complete example of the copyparty configuration options [look like this](./docs/examples/docker/idp/copyparty.conf)
|
1411
1440
|
|
1441
|
+
but if you just want to let users change their own passwords, then you probably want [user-changeable passwords](#user-changeable-passwords) instead
|
1442
|
+
|
1443
|
+
|
1444
|
+
## user-changeable passwords
|
1445
|
+
|
1446
|
+
if permitted, users can change their own passwords in the control-panel
|
1447
|
+
|
1448
|
+
* not compatible with [identity providers](#identity-providers)
|
1449
|
+
|
1450
|
+
* must be enabled with `--chpw` because account-sharing is a popular usecase
|
1451
|
+
|
1452
|
+
* if you want to enable the feature but deny password-changing for a specific list of accounts, you can do that with `--chpw-no name1,name2,name3,...`
|
1453
|
+
|
1454
|
+
* to perform a password reset, edit the server config and give the user another password there, then do a [config reload](#server-config) or server restart
|
1455
|
+
|
1456
|
+
* the custom passwords are kept in a textfile at filesystem-path `--chpw-db`, by default `chpw.json` in the copyparty config folder
|
1457
|
+
|
1458
|
+
* if you run multiple copyparty instances with different users you *almost definitely* want to specify separate DBs for each instance
|
1459
|
+
|
1460
|
+
* if [password hashing](#password-hashing) is enbled, the passwords in the db are also hashed
|
1461
|
+
|
1462
|
+
* ...which means that all user-defined passwords will be forgotten if you change password-hashing settings
|
1463
|
+
|
1412
1464
|
|
1413
1465
|
## using the cloud as storage
|
1414
1466
|
|
@@ -1513,7 +1565,7 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical
|
|
1513
1565
|
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
|
1514
1566
|
* depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
|
1515
1567
|
|
1516
|
-
for improved security (and a
|
1568
|
+
for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/tmp/party.sock` (permission `770` means only members of group `www` can access it)
|
1517
1569
|
|
1518
1570
|
example webserver configs:
|
1519
1571
|
|
@@ -1954,7 +2006,7 @@ some notes on hardening
|
|
1954
2006
|
* cors doesn't work right otherwise
|
1955
2007
|
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
|
1956
2008
|
* this returns html documents as plaintext, and also disables markdown rendering
|
1957
|
-
* when running behind a reverse-proxy, listen on a unix-socket
|
2009
|
+
* when running behind a reverse-proxy, listen on a unix-socket for tighter access control (and more performance); see [reverse-proxy](#reverse-proxy) or `--help-bind`
|
1958
2010
|
|
1959
2011
|
safety profiles:
|
1960
2012
|
|
@@ -42,6 +42,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
42
42
|
* [self-destruct](#self-destruct) - uploads can be given a lifetime
|
43
43
|
* [race the beam](#race-the-beam) - download files while they're still uploading ([demo video](http://a.ocv.me/pub/g/nerd-stuff/cpp/2024-0418-race-the-beam.webm))
|
44
44
|
* [file manager](#file-manager) - cut/paste, rename, and delete files/folders (if you have permission)
|
45
|
+
* [shares](#shares) - share a file or folder by creating a temporary link
|
45
46
|
* [batch rename](#batch-rename) - select some files and press `F2` to bring up the rename UI
|
46
47
|
* [media player](#media-player) - plays almost every audio format there is
|
47
48
|
* [audio equalizer](#audio-equalizer) - and [dynamic range compressor](https://en.wikipedia.org/wiki/Dynamic_range_compression)
|
@@ -76,6 +77,7 @@ turn almost any device into a file server with resumable uploads/downloads using
|
|
76
77
|
* [upload events](#upload-events) - the older, more powerful approach ([examples](./bin/mtag/))
|
77
78
|
* [handlers](#handlers) - redefine behavior with plugins ([examples](./bin/handlers/))
|
78
79
|
* [identity providers](#identity-providers) - replace copyparty passwords with oauth and such
|
80
|
+
* [user-changeable passwords](#user-changeable-passwords) - if permitted, users can change their own passwords
|
79
81
|
* [using the cloud as storage](#using-the-cloud-as-storage) - connecting to an aws s3 bucket and similar
|
80
82
|
* [hiding from google](#hiding-from-google) - tell search engines you dont wanna be indexed
|
81
83
|
* [themes](#themes)
|
@@ -744,6 +746,33 @@ file selection: click somewhere on the line (not the link itsef), then:
|
|
744
746
|
you can move files across browser tabs (cut in one tab, paste in another)
|
745
747
|
|
746
748
|
|
749
|
+
## shares
|
750
|
+
|
751
|
+
share a file or folder by creating a temporary link
|
752
|
+
|
753
|
+
when enabled in the server settings (`--shr`), click the bottom-right `share` button to share the folder you're currently in, or select a file first to share only that file
|
754
|
+
|
755
|
+
this feature was made with [identity providers](#identity-providers) in mind -- configure your reverseproxy to skip the IdP's access-control for a given URL prefix and use that to safely share specific files/folders sans the usual auth checks
|
756
|
+
|
757
|
+
when creating a share, the creator can choose any of the following options:
|
758
|
+
|
759
|
+
* password-protection
|
760
|
+
* expire after a certain time
|
761
|
+
* allow visitors to upload (if the user who creates the share has write-access)
|
762
|
+
|
763
|
+
semi-intentional limitations:
|
764
|
+
|
765
|
+
* cleanup of expired shares only works when global option `e2d` is set, and/or at least one volume on the server has volflag `e2d`
|
766
|
+
* only folders from the same volume are shared; if you are sharing a folder which contains other volumes, then the contents of those volumes will not be available
|
767
|
+
* no option to "delete after first access" because tricky
|
768
|
+
* when linking something to discord (for example) it'll get accessed by their scraper and that would count as a hit
|
769
|
+
* browsers wouldn't be able to resume a broken download unless the requester's IP gets allowlisted for X minutes (ref. tricky)
|
770
|
+
|
771
|
+
the links are created inside a specific toplevel folder which must be specified with server-config `--shr`, for example `--shr /share/` (this also enables the feature)
|
772
|
+
|
773
|
+
users can delete their own shares in the controlpanel, and a list of privileged users (`--shr-adm`) are allowed to see and/or delet any share on the server
|
774
|
+
|
775
|
+
|
747
776
|
## batch rename
|
748
777
|
|
749
778
|
select some files and press `F2` to bring up the rename UI
|
@@ -1355,6 +1384,29 @@ there is a [docker-compose example](./docs/examples/docker/idp-authelia-traefik)
|
|
1355
1384
|
|
1356
1385
|
a more complete example of the copyparty configuration options [look like this](./docs/examples/docker/idp/copyparty.conf)
|
1357
1386
|
|
1387
|
+
but if you just want to let users change their own passwords, then you probably want [user-changeable passwords](#user-changeable-passwords) instead
|
1388
|
+
|
1389
|
+
|
1390
|
+
## user-changeable passwords
|
1391
|
+
|
1392
|
+
if permitted, users can change their own passwords in the control-panel
|
1393
|
+
|
1394
|
+
* not compatible with [identity providers](#identity-providers)
|
1395
|
+
|
1396
|
+
* must be enabled with `--chpw` because account-sharing is a popular usecase
|
1397
|
+
|
1398
|
+
* if you want to enable the feature but deny password-changing for a specific list of accounts, you can do that with `--chpw-no name1,name2,name3,...`
|
1399
|
+
|
1400
|
+
* to perform a password reset, edit the server config and give the user another password there, then do a [config reload](#server-config) or server restart
|
1401
|
+
|
1402
|
+
* the custom passwords are kept in a textfile at filesystem-path `--chpw-db`, by default `chpw.json` in the copyparty config folder
|
1403
|
+
|
1404
|
+
* if you run multiple copyparty instances with different users you *almost definitely* want to specify separate DBs for each instance
|
1405
|
+
|
1406
|
+
* if [password hashing](#password-hashing) is enbled, the passwords in the db are also hashed
|
1407
|
+
|
1408
|
+
* ...which means that all user-defined passwords will be forgotten if you change password-hashing settings
|
1409
|
+
|
1358
1410
|
|
1359
1411
|
## using the cloud as storage
|
1360
1412
|
|
@@ -1459,7 +1511,7 @@ some reverse proxies (such as [Caddy](https://caddyserver.com/)) can automatical
|
|
1459
1511
|
* **warning:** nginx-QUIC (HTTP/3) is still experimental and can make uploads much slower, so HTTP/1.1 is recommended for now
|
1460
1512
|
* depending on server/client, HTTP/1.1 can also be 5x faster than HTTP/2
|
1461
1513
|
|
1462
|
-
for improved security (and a
|
1514
|
+
for improved security (and a 10% performance boost) consider listening on a unix-socket with `-i unix:770:www:/tmp/party.sock` (permission `770` means only members of group `www` can access it)
|
1463
1515
|
|
1464
1516
|
example webserver configs:
|
1465
1517
|
|
@@ -1900,7 +1952,7 @@ some notes on hardening
|
|
1900
1952
|
* cors doesn't work right otherwise
|
1901
1953
|
* if you allow anonymous uploads or otherwise don't trust the contents of a volume, you can prevent XSS with volflag `nohtml`
|
1902
1954
|
* this returns html documents as plaintext, and also disables markdown rendering
|
1903
|
-
* when running behind a reverse-proxy, listen on a unix-socket
|
1955
|
+
* when running behind a reverse-proxy, listen on a unix-socket for tighter access control (and more performance); see [reverse-proxy](#reverse-proxy) or `--help-bind`
|
1904
1956
|
|
1905
1957
|
safety profiles:
|
1906
1958
|
|
@@ -521,6 +521,41 @@ def showlic() :
|
|
521
521
|
|
522
522
|
def get_sects():
|
523
523
|
return [
|
524
|
+
[
|
525
|
+
"bind",
|
526
|
+
"configure listening",
|
527
|
+
dedent(
|
528
|
+
"""
|
529
|
+
\033[33m-i\033[0m takes a comma-separated list of interfaces to listen on;
|
530
|
+
IP-addresses and/or unix-sockets (Unix Domain Sockets)
|
531
|
+
|
532
|
+
the default (\033[32m-i ::\033[0m) means all IPv4 and IPv6 addresses
|
533
|
+
|
534
|
+
\033[32m-i 0.0.0.0\033[0m listens on all IPv4 NICs/subnets
|
535
|
+
\033[32m-i 127.0.0.1\033[0m listens on IPv4 localhost only
|
536
|
+
\033[32m-i 127.1\033[0m listens on IPv4 localhost only
|
537
|
+
\033[32m-i 127.1,192.168.123.1\033[0m = IPv4 localhost and 192.168.123.1
|
538
|
+
|
539
|
+
\033[33m-p\033[0m takes a comma-separated list of tcp ports to listen on;
|
540
|
+
the default is \033[32m-p 3923\033[0m but as root you can \033[32m-p 80,443,3923\033[0m
|
541
|
+
|
542
|
+
when running behind a reverse-proxy, it's recommended to
|
543
|
+
use unix-sockets for improved performance and security;
|
544
|
+
|
545
|
+
\033[32m-i unix:770:www:\033[33m/tmp/a.sock\033[0m listens on \033[33m/tmp/a.sock\033[0m with
|
546
|
+
permissions \033[33m0770\033[0m; only accessible to members of the \033[33mwww\033[0m
|
547
|
+
group. This is the best approach. Alternatively,
|
548
|
+
|
549
|
+
\033[32m-i unix:777:\033[33m/tmp/a.sock\033[0m sets perms \033[33m0777\033[0m so anyone can
|
550
|
+
access it; bad unless it's inside a restricted folder
|
551
|
+
|
552
|
+
\033[32m-i unix:\033[33m/tmp/a.sock\033[0m keeps umask-defined permissions
|
553
|
+
(usually \033[33m0600\033[0m) and the same user/group as copyparty
|
554
|
+
|
555
|
+
\033[33m-p\033[0m (tcp ports) is ignored for unix sockets
|
556
|
+
"""
|
557
|
+
),
|
558
|
+
],
|
524
559
|
[
|
525
560
|
"accounts",
|
526
561
|
"accounts and volumes",
|
@@ -931,6 +966,15 @@ def add_fs(ap):
|
|
931
966
|
ap2.add_argument("--mtab-age", metavar="SEC", type=int, default=60, help="rebuild mountpoint cache every \033[33mSEC\033[0m to keep track of sparse-files support; keep low on servers with removable media")
|
932
967
|
|
933
968
|
|
969
|
+
def add_share(ap):
|
970
|
+
db_path = os.path.join(E.cfg, "shares.db")
|
971
|
+
ap2 = ap.add_argument_group('share-url options')
|
972
|
+
ap2.add_argument("--shr", metavar="URL", default="", help="base url for shared files, for example [\033[32m/share\033[0m] (must be a toplevel subfolder)")
|
973
|
+
ap2.add_argument("--shr-db", metavar="PATH", default=db_path, help="database to store shares in")
|
974
|
+
ap2.add_argument("--shr-adm", metavar="U,U", default="", help="comma-separated list of users allowed to view/delete any share")
|
975
|
+
ap2.add_argument("--shr-v", action="store_true", help="debug")
|
976
|
+
|
977
|
+
|
934
978
|
def add_upload(ap):
|
935
979
|
ap2 = ap.add_argument_group('upload options')
|
936
980
|
ap2.add_argument("--dotpart", action="store_true", help="dotfile incomplete uploads, hiding them from clients unless \033[33m-ed\033[0m")
|
@@ -963,8 +1007,8 @@ def add_upload(ap):
|
|
963
1007
|
|
964
1008
|
def add_network(ap):
|
965
1009
|
ap2 = ap.add_argument_group('network options')
|
966
|
-
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="
|
967
|
-
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to
|
1010
|
+
ap2.add_argument("-i", metavar="IP", type=u, default="::", help="IPs and/or unix-sockets to listen on (see \033[33m--help-bind\033[0m). Default: all IPv4 and IPv6")
|
1011
|
+
ap2.add_argument("-p", metavar="PORT", type=u, default="3923", help="ports to listen on (comma/range); ignored for unix-sockets")
|
968
1012
|
ap2.add_argument("--ll", action="store_true", help="include link-local IPv4/IPv6 in mDNS replies, even if the NIC has routable IPs (breaks some mDNS clients)")
|
969
1013
|
ap2.add_argument("--rproxy", metavar="DEPTH", type=int, default=1, help="which ip to associate clients with; [\033[32m0\033[0m]=tcp, [\033[32m1\033[0m]=origin (first x-fwd, unsafe), [\033[32m2\033[0m]=outermost-proxy, [\033[32m3\033[0m]=second-proxy, [\033[32m-1\033[0m]=closest-proxy")
|
970
1014
|
ap2.add_argument("--xff-hdr", metavar="NAME", type=u, default="x-forwarded-for", help="if reverse-proxied, which http header to read the client's real ip from")
|
@@ -1024,6 +1068,16 @@ def add_auth(ap):
|
|
1024
1068
|
ap2.add_argument("--bauth-last", action="store_true", help="keeps basic-authentication enabled, but only as a last-resort; if a cookie is also provided then the cookie wins")
|
1025
1069
|
|
1026
1070
|
|
1071
|
+
def add_chpw(ap):
|
1072
|
+
db_path = os.path.join(E.cfg, "chpw.json")
|
1073
|
+
ap2 = ap.add_argument_group('user-changeable passwords options')
|
1074
|
+
ap2.add_argument("--chpw", action="store_true", help="allow users to change their own passwords")
|
1075
|
+
ap2.add_argument("--chpw-no", metavar="U,U,U", type=u, action="append", help="do not allow password-changes for this comma-separated list of usernames")
|
1076
|
+
ap2.add_argument("--chpw-db", metavar="PATH", type=u, default=db_path, help="where to store the passwords database (if you run multiple copyparty instances, make sure they use different DBs)")
|
1077
|
+
ap2.add_argument("--chpw-len", metavar="N", type=int, default=8, help="minimum password length")
|
1078
|
+
ap2.add_argument("--chpw-v", metavar="LVL", type=int, default=2, help="verbosity of summary on config load [\033[32m0\033[0m] = nothing at all, [\033[32m1\033[0m] = number of users, [\033[32m2\033[0m] = list users with default-pw, [\033[32m3\033[0m] = list all users")
|
1079
|
+
|
1080
|
+
|
1027
1081
|
def add_zeroconf(ap):
|
1028
1082
|
ap2 = ap.add_argument_group("Zeroconf options")
|
1029
1083
|
ap2.add_argument("-z", action="store_true", help="enable all zeroconf backends (mdns, ssdp)")
|
@@ -1432,11 +1486,13 @@ def run_argparse(
|
|
1432
1486
|
add_tls(ap, cert_path)
|
1433
1487
|
add_cert(ap, cert_path)
|
1434
1488
|
add_auth(ap)
|
1489
|
+
add_chpw(ap)
|
1435
1490
|
add_qr(ap, tty)
|
1436
1491
|
add_zeroconf(ap)
|
1437
1492
|
add_zc_mdns(ap)
|
1438
1493
|
add_zc_ssdp(ap)
|
1439
1494
|
add_fs(ap)
|
1495
|
+
add_share(ap)
|
1440
1496
|
add_upload(ap)
|
1441
1497
|
add_db_general(ap, hcores)
|
1442
1498
|
add_db_metadata(ap)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
VERSION = (1,
|
4
|
-
CODENAME = "
|
5
|
-
BUILD_DT = (2024, 8,
|
3
|
+
VERSION = (1, 14, 0)
|
4
|
+
CODENAME = "one step forward"
|
5
|
+
BUILD_DT = (2024, 8, 18)
|
6
6
|
|
7
7
|
S_VERSION = ".".join(map(str, VERSION))
|
8
8
|
S_BUILD_DT = "{0:04d}-{1:02d}-{2:02d}".format(*BUILD_DT)
|
@@ -4,6 +4,7 @@ from __future__ import print_function, unicode_literals
|
|
4
4
|
import argparse
|
5
5
|
import base64
|
6
6
|
import hashlib
|
7
|
+
import json
|
7
8
|
import os
|
8
9
|
import re
|
9
10
|
import stat
|
@@ -37,6 +38,7 @@ from .util import (
|
|
37
38
|
uncyg,
|
38
39
|
undot,
|
39
40
|
unhumanize,
|
41
|
+
vjoin,
|
40
42
|
vsplit,
|
41
43
|
)
|
42
44
|
|
@@ -334,6 +336,7 @@ class VFS(object):
|
|
334
336
|
self.histtab = {} # all realpath->histpath
|
335
337
|
self.dbv = None # closest full/non-jump parent
|
336
338
|
self.lim = None # upload limits; only set for dbv
|
339
|
+
self.shr_src = None # source vfs+rem of a share
|
337
340
|
self.aread = {}
|
338
341
|
self.awrite = {}
|
339
342
|
self.amove = {}
|
@@ -358,6 +361,8 @@ class VFS(object):
|
|
358
361
|
self.all_aps = []
|
359
362
|
self.all_vps = []
|
360
363
|
|
364
|
+
self.get_dbv = self._get_dbv
|
365
|
+
|
361
366
|
def __repr__(self) :
|
362
367
|
return "VFS(%s)" % (
|
363
368
|
", ".join(
|
@@ -519,7 +524,15 @@ class VFS(object):
|
|
519
524
|
|
520
525
|
return vn, rem
|
521
526
|
|
522
|
-
def
|
527
|
+
def _get_share_src(self, vrem ) :
|
528
|
+
src = self.shr_src
|
529
|
+
if not src:
|
530
|
+
return self._get_dbv(vrem)
|
531
|
+
|
532
|
+
shv, srem = src
|
533
|
+
return shv, vjoin(srem, vrem)
|
534
|
+
|
535
|
+
def _get_dbv(self, vrem ) :
|
523
536
|
dbv = self.dbv
|
524
537
|
if not dbv:
|
525
538
|
return self, vrem
|
@@ -800,6 +813,7 @@ class AuthSrv(object):
|
|
800
813
|
self.vfs = VFS(log_func, "", "", AXS(), {})
|
801
814
|
self.acct = {}
|
802
815
|
self.iacct = {}
|
816
|
+
self.defpw = {}
|
803
817
|
self.grps = {}
|
804
818
|
self.re_pwd = None
|
805
819
|
|
@@ -1345,7 +1359,7 @@ class AuthSrv(object):
|
|
1345
1359
|
flags[name] = vals
|
1346
1360
|
self._e("volflag [{}] += {} ({})".format(name, vals, desc))
|
1347
1361
|
|
1348
|
-
def reload(self) :
|
1362
|
+
def reload(self, verbosity = 9) :
|
1349
1363
|
"""
|
1350
1364
|
construct a flat list of mountpoints and usernames
|
1351
1365
|
first from the commandline arguments
|
@@ -1353,9 +1367,9 @@ class AuthSrv(object):
|
|
1353
1367
|
before finally building the VFS
|
1354
1368
|
"""
|
1355
1369
|
with self.mutex:
|
1356
|
-
self._reload()
|
1370
|
+
self._reload(verbosity)
|
1357
1371
|
|
1358
|
-
def _reload(self) :
|
1372
|
+
def _reload(self, verbosity = 9) :
|
1359
1373
|
acct = {} # username:password
|
1360
1374
|
grps = {} # groupname:usernames
|
1361
1375
|
daxs = {}
|
@@ -1433,6 +1447,8 @@ class AuthSrv(object):
|
|
1433
1447
|
raise
|
1434
1448
|
|
1435
1449
|
self.setup_pwhash(acct)
|
1450
|
+
defpw = acct.copy()
|
1451
|
+
self.setup_chpw(acct)
|
1436
1452
|
|
1437
1453
|
# case-insensitive; normalize
|
1438
1454
|
if WINDOWS:
|
@@ -1448,9 +1464,8 @@ class AuthSrv(object):
|
|
1448
1464
|
vfs = VFS(self.log_func, absreal("."), "", axs, {})
|
1449
1465
|
elif "" not in mount:
|
1450
1466
|
# there's volumes but no root; make root inaccessible
|
1451
|
-
|
1452
|
-
vfs.
|
1453
|
-
vfs.flags["d2d"] = True
|
1467
|
+
zsd = {"d2d": True, "tcolor": self.args.tcolor}
|
1468
|
+
vfs = VFS(self.log_func, "", "", AXS(), zsd)
|
1454
1469
|
|
1455
1470
|
maxdepth = 0
|
1456
1471
|
for dst in sorted(mount.keys(), key=lambda x: (x.count("/"), len(x))):
|
@@ -1479,6 +1494,52 @@ class AuthSrv(object):
|
|
1479
1494
|
vol.all_vps.sort(key=lambda x: len(x[0]), reverse=True)
|
1480
1495
|
vol.root = vfs
|
1481
1496
|
|
1497
|
+
enshare = self.args.shr
|
1498
|
+
shr = enshare[1:-1]
|
1499
|
+
shrs = enshare[1:]
|
1500
|
+
if enshare:
|
1501
|
+
import sqlite3
|
1502
|
+
|
1503
|
+
shv = VFS(self.log_func, "", shr, AXS(), {"d2d": True})
|
1504
|
+
par = vfs.all_vols[""]
|
1505
|
+
|
1506
|
+
db_path = self.args.shr_db
|
1507
|
+
db = sqlite3.connect(db_path)
|
1508
|
+
cur = db.cursor()
|
1509
|
+
now = time.time()
|
1510
|
+
for row in cur.execute("select * from sh"):
|
1511
|
+
s_k, s_pw, s_vp, s_pr, s_st, s_un, s_t0, s_t1 = row
|
1512
|
+
if s_t1 and s_t1 < now:
|
1513
|
+
continue
|
1514
|
+
|
1515
|
+
if self.args.shr_v:
|
1516
|
+
t = "loading %s share [%s] by [%s] => [%s]"
|
1517
|
+
self.log(t % (s_pr, s_k, s_un, s_vp))
|
1518
|
+
|
1519
|
+
if s_pw:
|
1520
|
+
sun = "s_%s" % (s_k,)
|
1521
|
+
acct[sun] = s_pw
|
1522
|
+
else:
|
1523
|
+
sun = "*"
|
1524
|
+
|
1525
|
+
s_axs = AXS(
|
1526
|
+
[sun] if "r" in s_pr else [],
|
1527
|
+
[sun] if "w" in s_pr else [],
|
1528
|
+
[sun] if "m" in s_pr else [],
|
1529
|
+
[sun] if "d" in s_pr else [],
|
1530
|
+
)
|
1531
|
+
|
1532
|
+
# don't know the abspath yet + wanna ensure the user
|
1533
|
+
# still has the privs they granted, so nullmap it
|
1534
|
+
shv.nodes[s_k] = VFS(
|
1535
|
+
self.log_func, "", "%s/%s" % (shr, s_k), s_axs, par.flags.copy()
|
1536
|
+
)
|
1537
|
+
|
1538
|
+
vfs.nodes[shr] = vfs.all_vols[shr] = shv
|
1539
|
+
for vol in shv.nodes.values():
|
1540
|
+
vfs.all_vols[vol.vpath] = vol
|
1541
|
+
vol.get_dbv = vol._get_share_src
|
1542
|
+
|
1482
1543
|
zss = set(acct)
|
1483
1544
|
zss.update(self.idp_accs)
|
1484
1545
|
zss.discard("*")
|
@@ -1497,7 +1558,7 @@ class AuthSrv(object):
|
|
1497
1558
|
for usr in unames:
|
1498
1559
|
for vp, vol in vfs.all_vols.items():
|
1499
1560
|
zx = getattr(vol.axs, axs_key)
|
1500
|
-
if usr in zx:
|
1561
|
+
if usr in zx and (not enshare or not vp.startswith(shrs)):
|
1501
1562
|
umap[usr].append(vp)
|
1502
1563
|
umap[usr].sort()
|
1503
1564
|
setattr(vfs, "a" + perm, umap)
|
@@ -1547,6 +1608,8 @@ class AuthSrv(object):
|
|
1547
1608
|
|
1548
1609
|
for usr in acct:
|
1549
1610
|
if usr not in associated_users:
|
1611
|
+
if enshare and usr.startswith("s_"):
|
1612
|
+
continue
|
1550
1613
|
if len(vfs.all_vols) > 1:
|
1551
1614
|
# user probably familiar enough that the verbose message is not necessary
|
1552
1615
|
t = "account [%s] is not mentioned in any volume definitions; see --help-accounts"
|
@@ -1982,7 +2045,7 @@ class AuthSrv(object):
|
|
1982
2045
|
have_e2t = False
|
1983
2046
|
t = "volumes and permissions:\n"
|
1984
2047
|
for zv in vfs.all_vols.values():
|
1985
|
-
if not self.warn_anonwrite:
|
2048
|
+
if not self.warn_anonwrite or verbosity < 5:
|
1986
2049
|
break
|
1987
2050
|
|
1988
2051
|
t += '\n\033[36m"/{}" \033[33m{}\033[0m'.format(zv.vpath, zv.realpath)
|
@@ -2011,7 +2074,7 @@ class AuthSrv(object):
|
|
2011
2074
|
|
2012
2075
|
t += "\n"
|
2013
2076
|
|
2014
|
-
if self.warn_anonwrite:
|
2077
|
+
if self.warn_anonwrite and verbosity > 4:
|
2015
2078
|
if not self.args.no_voldump:
|
2016
2079
|
self.log(t)
|
2017
2080
|
|
@@ -2035,7 +2098,7 @@ class AuthSrv(object):
|
|
2035
2098
|
|
2036
2099
|
try:
|
2037
2100
|
zv, _ = vfs.get("", "*", False, True, err=999)
|
2038
|
-
if self.warn_anonwrite and os.getcwd() == zv.realpath:
|
2101
|
+
if self.warn_anonwrite and verbosity > 4 and os.getcwd() == zv.realpath:
|
2039
2102
|
t = "anyone can write to the current directory: {}\n"
|
2040
2103
|
self.log(t.format(zv.realpath), c=1)
|
2041
2104
|
|
@@ -2062,6 +2125,7 @@ class AuthSrv(object):
|
|
2062
2125
|
|
2063
2126
|
self.vfs = vfs
|
2064
2127
|
self.acct = acct
|
2128
|
+
self.defpw = defpw
|
2065
2129
|
self.grps = grps
|
2066
2130
|
self.iacct = {v: k for k, v in acct.items()}
|
2067
2131
|
|
@@ -2082,6 +2146,155 @@ class AuthSrv(object):
|
|
2082
2146
|
MIMES[ext] = mime
|
2083
2147
|
EXTS.update({v: k for k, v in MIMES.items()})
|
2084
2148
|
|
2149
|
+
if enshare:
|
2150
|
+
# hide shares from controlpanel
|
2151
|
+
vfs.all_vols = {
|
2152
|
+
x: y
|
2153
|
+
for x, y in vfs.all_vols.items()
|
2154
|
+
if x != shr and not x.startswith(shrs)
|
2155
|
+
}
|
2156
|
+
|
2157
|
+
assert cur # type: ignore
|
2158
|
+
assert shv # type: ignore
|
2159
|
+
for row in cur.execute("select * from sh"):
|
2160
|
+
s_k, s_pw, s_vp, s_pr, s_st, s_un, s_t0, s_t1 = row
|
2161
|
+
shn = shv.nodes.get(s_k, None)
|
2162
|
+
if not shn:
|
2163
|
+
continue
|
2164
|
+
|
2165
|
+
try:
|
2166
|
+
s_vfs, s_rem = vfs.get(
|
2167
|
+
s_vp, s_un, "r" in s_pr, "w" in s_pr, "m" in s_pr, "d" in s_pr
|
2168
|
+
)
|
2169
|
+
except Exception as ex:
|
2170
|
+
t = "removing share [%s] by [%s] to [%s] due to %r"
|
2171
|
+
self.log(t % (s_k, s_un, s_vp, ex), 3)
|
2172
|
+
shv.nodes.pop(s_k)
|
2173
|
+
continue
|
2174
|
+
|
2175
|
+
shn.shr_src = (s_vfs, s_rem)
|
2176
|
+
shn.realpath = s_vfs.canonical(s_rem)
|
2177
|
+
|
2178
|
+
if self.args.shr_v:
|
2179
|
+
t = "mapped %s share [%s] by [%s] => [%s] => [%s]"
|
2180
|
+
self.log(t % (s_pr, s_k, s_un, s_vp, shn.realpath))
|
2181
|
+
|
2182
|
+
# transplant shadowing into shares
|
2183
|
+
for vn in shv.nodes.values():
|
2184
|
+
svn, srem = vn.shr_src # type: ignore
|
2185
|
+
if srem:
|
2186
|
+
continue # free branch, safe
|
2187
|
+
ap = svn.canonical(srem)
|
2188
|
+
if bos.path.isfile(ap):
|
2189
|
+
continue # also fine
|
2190
|
+
for zs in svn.nodes.keys():
|
2191
|
+
# hide subvolume
|
2192
|
+
vn.nodes[zs] = VFS(self.log_func, "", "", AXS(), {})
|
2193
|
+
|
2194
|
+
def chpw(self, broker , uname, pw) :
|
2195
|
+
if not self.args.chpw:
|
2196
|
+
return False, "feature disabled in server config"
|
2197
|
+
|
2198
|
+
if uname == "*" or uname not in self.defpw:
|
2199
|
+
return False, "not logged in"
|
2200
|
+
|
2201
|
+
if uname in self.args.chpw_no:
|
2202
|
+
return False, "not allowed for this account"
|
2203
|
+
|
2204
|
+
if len(pw) < self.args.chpw_len:
|
2205
|
+
t = "minimum password length: %d characters"
|
2206
|
+
return False, t % (self.args.chpw_len,)
|
2207
|
+
|
2208
|
+
hpw = self.ah.hash(pw) if self.ah.on else pw
|
2209
|
+
|
2210
|
+
if hpw == self.acct[uname]:
|
2211
|
+
return False, "that's already your password my dude"
|
2212
|
+
|
2213
|
+
if hpw in self.iacct:
|
2214
|
+
return False, "password is taken"
|
2215
|
+
|
2216
|
+
with self.mutex:
|
2217
|
+
ap = self.args.chpw_db
|
2218
|
+
if not bos.path.exists(ap):
|
2219
|
+
pwdb = {}
|
2220
|
+
else:
|
2221
|
+
with open(ap, "r", encoding="utf-8") as f:
|
2222
|
+
pwdb = json.load(f)
|
2223
|
+
|
2224
|
+
pwdb = [x for x in pwdb if x[0] != uname]
|
2225
|
+
pwdb.append((uname, self.defpw[uname], hpw))
|
2226
|
+
|
2227
|
+
with open(ap, "w", encoding="utf-8") as f:
|
2228
|
+
json.dump(pwdb, f, separators=(",\n", ": "))
|
2229
|
+
|
2230
|
+
self.log("reinitializing due to password-change for user [%s]" % (uname,))
|
2231
|
+
|
2232
|
+
if not broker:
|
2233
|
+
# only true for tests
|
2234
|
+
self._reload()
|
2235
|
+
return True, "new password OK"
|
2236
|
+
|
2237
|
+
broker.ask("_reload_blocking", False, False).get()
|
2238
|
+
return True, "new password OK"
|
2239
|
+
|
2240
|
+
def setup_chpw(self, acct ) :
|
2241
|
+
ap = self.args.chpw_db
|
2242
|
+
if not self.args.chpw or not bos.path.exists(ap):
|
2243
|
+
return
|
2244
|
+
|
2245
|
+
with open(ap, "r", encoding="utf-8") as f:
|
2246
|
+
pwdb = json.load(f)
|
2247
|
+
|
2248
|
+
useen = set()
|
2249
|
+
urst = set()
|
2250
|
+
uok = set()
|
2251
|
+
for usr, orig, mod in pwdb:
|
2252
|
+
useen.add(usr)
|
2253
|
+
if usr not in acct:
|
2254
|
+
# previous user, no longer known
|
2255
|
+
continue
|
2256
|
+
if acct[usr] != orig:
|
2257
|
+
urst.add(usr)
|
2258
|
+
continue
|
2259
|
+
uok.add(usr)
|
2260
|
+
acct[usr] = mod
|
2261
|
+
|
2262
|
+
if not self.args.chpw_v:
|
2263
|
+
return
|
2264
|
+
|
2265
|
+
for usr in acct:
|
2266
|
+
if usr not in useen:
|
2267
|
+
urst.add(usr)
|
2268
|
+
|
2269
|
+
for zs in uok:
|
2270
|
+
urst.discard(zs)
|
2271
|
+
|
2272
|
+
if self.args.chpw_v == 1 or (self.args.chpw_v == 2 and not urst):
|
2273
|
+
t = "chpw: %d changed, %d unchanged"
|
2274
|
+
self.log(t % (len(uok), len(urst)))
|
2275
|
+
return
|
2276
|
+
|
2277
|
+
elif self.args.chpw_v == 2:
|
2278
|
+
t = "chpw: %d changed" % (len(uok))
|
2279
|
+
if urst:
|
2280
|
+
t += ", \033[0munchanged:\033[35m %s" % (", ".join(list(urst)))
|
2281
|
+
|
2282
|
+
self.log(t, 6)
|
2283
|
+
return
|
2284
|
+
|
2285
|
+
msg = ""
|
2286
|
+
if uok:
|
2287
|
+
t = "\033[0mchanged: \033[32m%s"
|
2288
|
+
msg += t % (", ".join(list(uok)),)
|
2289
|
+
if urst:
|
2290
|
+
t = "%s\033[0munchanged: \033[35m%s"
|
2291
|
+
msg += t % (
|
2292
|
+
", " if msg else "",
|
2293
|
+
", ".join(list(urst)),
|
2294
|
+
)
|
2295
|
+
|
2296
|
+
self.log("chpw: " + msg, 6)
|
2297
|
+
|
2085
2298
|
def setup_pwhash(self, acct ) :
|
2086
2299
|
self.ah = PWHash(self.args)
|
2087
2300
|
if not self.ah.on:
|