ftpd: allow implicit overwrite if user has delete perms

the spec doesn't say what you're supposed to do if the target filename of an upload is already taken, but this seems to be the most common behavior on other ftp servers, and is required by wondows 2000 (otherwise it'll freak out and issue a delete and then not actually upload it, nice)

new option `--ftp-no-ow` restores old default behavior of rejecting upload if target filename exists
This commit is contained in:
ed 2024-06-18 12:07:45 +02:00
parent 7219331057
commit 0a9f4c6074
2 changed files with 30 additions and 4 deletions

View file

@ -1034,6 +1034,7 @@ def add_ftp(ap):
ap2.add_argument("--ftpv", action="store_true", help="verbose") ap2.add_argument("--ftpv", action="store_true", help="verbose")
ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4") ap2.add_argument("--ftp4", action="store_true", help="only listen on IPv4")
ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]") ap2.add_argument("--ftp-ipa", metavar="CIDR", type=u, default="", help="only accept connections from IP-addresses inside \033[33mCIDR\033[0m; specify [\033[32many\033[0m] to disable inheriting \033[33m--ipa\033[0m. Examples: [\033[32mlan\033[0m] or [\033[32m10.89.0.0/16, 192.168.33.0/24\033[0m]")
ap2.add_argument("--ftp-no-ow", action="store_true", help="if target file exists, reject upload instead of overwrite")
ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)") ap2.add_argument("--ftp-wt", metavar="SEC", type=int, default=7, help="grace period for resuming interrupted uploads (any client can write to any file last-modified more recently than \033[33mSEC\033[0m seconds ago)")
ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, help="the NAT address to use for passive connections") ap2.add_argument("--ftp-nat", metavar="ADDR", type=u, help="the NAT address to use for passive connections")
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.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")

View file

@ -19,6 +19,7 @@ from .__init__ import PY2, TYPE_CHECKING
from .authsrv import VFS from .authsrv import VFS
from .bos import bos from .bos import bos
from .util import ( from .util import (
VF_CAREFUL,
Daemon, Daemon,
ODict, ODict,
Pebkac, Pebkac,
@ -30,6 +31,7 @@ from .util import (
runhook, runhook,
sanitize_fn, sanitize_fn,
vjoin, vjoin,
wunlink,
) )
if TYPE_CHECKING: if TYPE_CHECKING:
@ -37,7 +39,7 @@ if TYPE_CHECKING:
if True: # pylint: disable=using-constant-test if True: # pylint: disable=using-constant-test
import typing import typing
from typing import Any, Optional from typing import Any, Optional, Union
class FSE(FilesystemError): class FSE(FilesystemError):
@ -139,6 +141,9 @@ class FtpFs(AbstractedFS):
self.listdirinfo = self.listdir self.listdirinfo = self.listdir
self.chdir(".") self.chdir(".")
def log(self, msg: str, c: Union[int, str] = 0) -> None:
self.hub.log("ftpd", msg, c)
def v2a( def v2a(
self, self,
vpath: str, vpath: str,
@ -207,17 +212,37 @@ class FtpFs(AbstractedFS):
w = "w" in mode or "a" in mode or "+" in mode w = "w" in mode or "a" in mode or "+" in mode
ap = self.rv2a(filename, r, w)[0] ap = self.rv2a(filename, r, w)[0]
self.validpath(ap)
if w: if w:
try: try:
st = bos.stat(ap) st = bos.stat(ap)
td = time.time() - st.st_mtime td = time.time() - st.st_mtime
need_unlink = True
except: except:
need_unlink = False
td = 0 td = 0
if td < -1 or td > self.args.ftp_wt: if w and need_unlink:
raise FSE("Cannot open existing file for writing") if td >= -1 and td <= self.args.ftp_wt:
# within permitted timeframe; unlink and accept
do_it = True
elif self.args.no_del or self.args.ftp_no_ow:
# file too old, or overwrite not allowed; reject
do_it = False
else:
# allow overwrite if user has delete permission
# (avoids win2000 freaking out and deleting the server copy without uploading its own)
try:
self.rv2a(filename, False, True, False, True)
do_it = True
except:
do_it = False
if not do_it:
raise FSE("File already exists")
wunlink(self.log, ap, VF_CAREFUL)
self.validpath(ap)
return open(fsenc(ap), mode, self.args.iobuf) return open(fsenc(ap), mode, self.args.iobuf)
def chdir(self, path: str) -> None: def chdir(self, path: str) -> None: