Skip to content

Commit

Permalink
add SMB/CIFS server
Browse files Browse the repository at this point in the history
  • Loading branch information
9001 committed Oct 23, 2022
1 parent 4bcd30d commit f3a501d
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 20 deletions.
41 changes: 40 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ try the **[read-only demo server](https://a.ocv.me/pub/demo/)** 👀 running fro
* [qr-code](#qr-code) - print a qr-code [(screenshot)](https://user-images.githubusercontent.com/241032/194728533-6f00849b-c6ac-43c6-9359-83e454d11e00.png) for quick access
* [ftp server](#ftp-server) - an FTP server can be started using `--ftp 3921`
* [webdav server](#webdav-server) - enable with `--dav`
* [smb server](#smb-server) - unsafe, not recommended for wan
* [file indexing](#file-indexing) - enables dedup and music search ++
* [exclude-patterns](#exclude-patterns) - to save some time
* [filesystem guards](#filesystem-guards) - avoid traversing into other filesystems
Expand Down Expand Up @@ -168,7 +169,9 @@ feature summary
* ☑ multiprocessing (actual multithreading)
* ☑ volumes (mountpoints)
*[accounts](#accounts-and-volumes)
*[ftp-server](#ftp-server)
*[ftp server](#ftp-server)
*[webdav server](#webdav-server)
*[smb/cifs server](#smb-server)
*[qr-code](#qr-code) for quick access
* upload
* ☑ basic: plain multipart, ie6 support
Expand Down Expand Up @@ -735,6 +738,39 @@ known client bugs:
* latin-1 is fine, hiragana is not (not even as shift-jis on japanese xp)


## smb server

unsafe, not recommended for wan, enable with `--smb` for read-only or `--smbw` for read-write

dependencies: `python3 -m pip install --user -U impacket==0.10.0`
* newer versions of impacket will hopefully work just fine but there is monkeypatching so maybe not

some big warnings specific to SMB/CIFS, in decreasing importance:
* not entirely confident that read-only is read-only
* the smb backend is not fully integrated with vfs, meaning there could be security issues (path traversal). Please use `--smb-port` (see below) and [./bin#prisonpartysh](prisonparty)
* account passwords work per-volume as expected, but account permissions are ignored; all accounts have access to all volumes, and `--smbw` gives all accounts write-access everywhere
* shadowing (hiding the contents in subfolders by creating overlapping volumes) probably works as expected but no guarantees

and some minor issues,
* files are not [indexed](#file-indexing) when uploaded through smb; please [schedule rescans](#periodic-rescan) as a workaround
* hot-reload of server config (`/?reload=cfg`) only works for volumes, not account passwords
* listens on the first `-i` interface only (default = 0.0.0.0 = all)
* login doesn't work on winxp, but anonymous access is ok -- remove all accounts from copyparty config for that to work
* python3 only
* slow

known client bugs:
* on win7 only, `--smb1` is much faster than smb2 (default) because it keeps rescanning folders on smb2, however win10 onwards does not have smb1
* windows cannot access folders which contain filenames with invalid unicode or forbidden characters (`<>:"/\|?*`), or names ending with `.`

the smb protocol listens on TCP port 445, which is a privileged port on linux and macos, which would require running copyparty as root. However, this can be avoided by listening on another port using `--smb-port 3945` and then using NAT to forward the traffic from 445 to there;
* on linux: `iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 445 -j REDIRECT --to-port 3945`

authenticate with one of the following:
* username `$username`, password `$password`
* username `$password`, password blank


## file indexing

enables dedup and music search ++
Expand Down Expand Up @@ -1317,6 +1353,9 @@ enable [thumbnails](#thumbnails) of...
* **AVIF pictures:** `pyvips` or `ffmpeg` or `pillow-avif-plugin`
* **JPEG XL pictures:** `pyvips` or `ffmpeg`

enable [smb](#smb-server) support:
* `impacket==0.10.0`

`pyvips` gives higher quality thumbnails than `Pillow` and is 320% faster, using 270% more ram: `sudo apt install libvips42 && python3 -m pip install --user -U pyvips`


Expand Down
13 changes: 12 additions & 1 deletion copyparty/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -633,11 +633,18 @@ def run_argparse(argv: list[str], formatter: Any, retry: bool) -> argparse.Names
ap2.add_argument("--ftp-pr", metavar="P-P", type=u, help="the range of TCP ports to use for passive connections, for example \033[32m12000-13000")

ap2 = ap.add_argument_group('WebDAV options')
ap2.add_argument("--dav", action="store_true", help="enable webdav; read-only even if user has write-access")
ap2.add_argument("--dav", action="store_true", help="enable webdav with limited write-support (most clients will fail to overwrite files)")
ap2.add_argument("--daw", action="store_true", help="enable full write support. \033[1;31mNB!\033[0m This has side-effects -- PUT-operations will now \033[1;31mOVERWRITE\033[0m existing files, rather than inventing new filenames to avoid loss of data. You might want to instead set this as a volflag where needed. By not setting this flag, uploaded files can get written to a filename which the client does not expect (which might be okay, depending on client)")
ap2.add_argument("--dav-nr", action="store_true", help="reject depth:infinite requests (recursive file listing); breaks spec compliance and some clients, which might be a good thing since depth:infinite is extremely server-heavy")
ap2.add_argument("--dav-mac", action="store_true", help="disable apple-garbage filter -- allow macos to create junk files (._* and .DS_Store, .Spotlight-*, .fseventsd, .Trashes, .AppleDouble, __MACOS)")

ap2 = ap.add_argument_group('SMB/CIFS options')
ap2.add_argument("--smb", action="store_true", help="enable smb (read-only) -- this requires running copyparty as root on linux and macos unless --smb-port")
ap2.add_argument("--smbw", action="store_true", help="enable write support (please dont)")
ap2.add_argument("--smb1", action="store_true", help="disable SMBv2, only enable SMBv1 (CIFS)")
ap2.add_argument("--smb-port", metavar="PORT", type=int, default=445, help="port to listen on -- if you change this value, you must NAT from TCP:445 to this port using iptables or similar")
ap2.add_argument("--smb-dbg", action="store_true", help="show debug messages")

ap2 = ap.add_argument_group('opt-outs')
ap2.add_argument("-nw", action="store_true", help="never write anything to disk (debug/benchmark)")
ap2.add_argument("--keep-qem", action="store_true", help="do not disable quick-edit-mode on windows (it is disabled to avoid accidental text selection which will deadlock copyparty)")
Expand Down Expand Up @@ -949,6 +956,10 @@ def main(argv: Optional[list[str]] = None) -> None:
+ " (if you crash with codec errors then that is why)"
)

if PY2 and al.smb:
print("error: python2 cannot --smb")
return

if sys.version_info < (3, 6):
al.no_scandir = True

Expand Down
4 changes: 2 additions & 2 deletions copyparty/authsrv.py
Original file line number Diff line number Diff line change
Expand Up @@ -592,7 +592,7 @@ def zipgen(
# if single folder: the folder itself is the top-level item
folder = "" if flt or not wrap else (vrem.split("/")[-1] or "top")

g = self.walk(folder, vrem, [], uname, [[True]], dots, scandir, False)
g = self.walk(folder, vrem, [], uname, [[True, False]], dots, scandir, False)
for _, _, vpath, apath, files, rd, vd in g:
if flt:
files = [x for x in files if x[0] in flt]
Expand Down Expand Up @@ -1370,7 +1370,7 @@ def dbg_ls(self) -> None:
"",
[],
u,
[[True]],
[[True, False]],
True,
not self.args.no_scandir,
False,
Expand Down
6 changes: 5 additions & 1 deletion copyparty/bos/bos.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from . import path

try:
from typing import Optional
from typing import Any, Optional
except:
pass

Expand Down Expand Up @@ -38,6 +38,10 @@ def mkdir(p: str, mode: int = 0o755) -> None:
return os.mkdir(fsenc(p), mode)


def open(p: str, *a, **ka) -> Any:
return os.open(fsenc(p), *a, **ka)


def rename(src: str, dst: str) -> None:
return os.rename(fsenc(src), fsenc(dst))

Expand Down
3 changes: 1 addition & 2 deletions copyparty/dxml.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import sys
import importlib
import sys
import xml.etree.ElementTree as ET

from .__init__ import PY2


try:
from typing import Any, Optional
except:
Expand Down
5 changes: 4 additions & 1 deletion copyparty/ftpd.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ def listdir(self, path: str) -> list[str]:
vfs, rem = self.hub.asrv.vfs.get(vpath, self.uname, True, False)

fsroot, vfs_ls1, vfs_virt = vfs.ls(
rem, self.uname, not self.args.no_scandir, [[True], [False, True]]
rem,
self.uname,
not self.args.no_scandir,
[[True, False], [False, True]],
)
vfs_ls = [x[0] for x in vfs_ls1]
vfs_ls.extend(vfs_virt.keys())
Expand Down
13 changes: 8 additions & 5 deletions copyparty/httpcli.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,7 +754,7 @@ def handle_propfind(self) -> bool:

elif depth == "1":
_, vfs_ls, vfs_virt = vn.ls(
rem, self.uname, not self.args.no_scandir, [[True]]
rem, self.uname, not self.args.no_scandir, [[True, False]]
)
zi = int(time.time())
zsr = os.stat_result((16877, -1, -1, 1, 1000, 1000, 8, zi, zi, zi))
Expand Down Expand Up @@ -844,8 +844,8 @@ def handle_proppatch(self) -> bool:
self.log("{} tried to proppatch [{}]".format(self.uname, self.vpath))
raise Pebkac(401, "authenticate")

from .dxml import parse_xml, mkenod, mktnod
from xml.etree import ElementTree as ET
from .dxml import mkenod, mktnod, parse_xml

vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
# abspath = vn.dcanonical(rem)
Expand Down Expand Up @@ -901,8 +901,8 @@ def handle_lock(self) -> bool:
self.log("{} tried to lock [{}]".format(self.uname, self.vpath))
raise Pebkac(401, "authenticate")

from .dxml import parse_xml, mkenod, mktnod
from xml.etree import ElementTree as ET
from .dxml import mkenod, mktnod, parse_xml

vn, rem = self.asrv.vfs.get(self.vpath, self.uname, False, False)
abspath = vn.dcanonical(rem)
Expand Down Expand Up @@ -2694,7 +2694,10 @@ def gen_tree(self, top: str, target: str) -> dict[str, Any]:
try:
vn, rem = self.asrv.vfs.get(top, self.uname, True, False)
fsroot, vfs_ls, vfs_virt = vn.ls(
rem, self.uname, not self.args.no_scandir, [[True], [False, True]]
rem,
self.uname,
not self.args.no_scandir,
[[True, False], [False, True]],
)
except:
vfs_ls = []
Expand Down Expand Up @@ -3103,7 +3106,7 @@ def tx_browser(self) -> bool:
return self.tx_zip(k, v, vn, rem, [], self.args.ed)

fsroot, vfs_ls, vfs_virt = vn.ls(
rem, self.uname, not self.args.no_scandir, [[True], [False, True]]
rem, self.uname, not self.args.no_scandir, [[True, False], [False, True]]
)
stats = {k: v for k, v in vfs_ls}
ls_names = [x[0] for x in vfs_ls]
Expand Down
2 changes: 1 addition & 1 deletion copyparty/httpsrv.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,11 @@
from .bos import bos
from .httpconn import HttpConn
from .util import (
E_SCK,
FHC,
Daemon,
Garda,
Magician,
E_SCK,
min_ex,
shut_socket,
spack,
Expand Down
Loading

0 comments on commit f3a501d

Please sign in to comment.