diff --git a/copyparty/sftpd.py b/copyparty/sftpd.py index 2221caf1..85a9275a 100644 --- a/copyparty/sftpd.py +++ b/copyparty/sftpd.py @@ -286,6 +286,8 @@ class SFTP_Srv(paramiko.SFTPServerInterface): self.uname: str = ssh.uname # type: ignore self.args = self.hub.args self.asrv: "AuthSrv" = self.hub.asrv + self.v = self.args.sftpv + self.vv = self.args.sftpvv if self.uname == LEELOO_DALLAS: raise Exception("send her back") @@ -353,15 +355,19 @@ class SFTP_Srv(paramiko.SFTPServerInterface): return SFTP_FAILURE def _list_folder(self, path: str) -> list[SATTR] | int: + if self.v: + self.log("ls(%s):" % (path,)) try: ap, vn, rem = self.v2a(path, r=True) except Pebkac: try: self.v2a(path, w=True) + self.log("ls(%s): [] (write-only)" % (path,)) return [] # display write-only folders as empty except: pass if self.asrv.vfs.realpath or path.strip("/"): + self.log("ls(%s): EPERM" % (path,)) return SFTP_PERMISSION_DENIED # list of accessible volumes ret = [] @@ -376,6 +382,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): except: pass ret.sort(key=lambda x: x.filename) + self.log("ls(%s): vfs-root; |%d|" % (path, len(ret))) return ret _, vfs_ls, vfs_virt = vn.ls( @@ -394,6 +401,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): if self.uname not in vn.axs.udot: ret = [x for x in ret if not x.filename.split("/")[-1].startswith(".")] ret.sort(key=lambda x: x.filename) + self.log("ls(%s): |%d|" % (path, len(ret))) return ret def stat(self, path: str) -> SATTR | int: @@ -457,10 +465,12 @@ class SFTP_Srv(paramiko.SFTPServerInterface): ap = os.path.join(vn.realpath, rem) vf = vn.flags except Pebkac as ex: - t = "denied open file [%s], iflag=%s, attr=%s, read=%s, write=%s: %s" - self.log(t % (vp, iflag, attr, rd, wr, ex)) + t = "denied open file [%s], iflag=%s, read=%s, write=%s: %s" + self.log(t % (vp, iflag, rd, wr, ex)) return SFTP_PERMISSION_DENIED + self.log("open(%s, %x, %s)" % (vp, iflag, smode)) + if wr: try: st = bos.stat(ap) @@ -496,6 +506,8 @@ class SFTP_Srv(paramiko.SFTPServerInterface): self.log(t, 3) return SFTP_PERMISSION_DENIED + self.log("writing to [%s] => [%s]" % (vp, ap)) + if wr and need_unlink: # type: ignore # !rm assert td # type: ignore # !rm if td >= -1 and td <= self.args.ftp_wt: @@ -520,12 +532,15 @@ class SFTP_Srv(paramiko.SFTPServerInterface): chmod = getattr(attr, "st_mode", None) if chmod is None: chmod = vf.get("chmod_f", 644) + self.log("open(%s, %x): client did not chmod" % (vp, iflag)) + else: + self.log("open(%s, %x): client set chmod %s" % (vp, iflag, chmod)) try: fd = os.open(ap, iflag, chmod) except OSError as ex: - t = "failed to os.open [%s] -> [%s] with iflag [%s] and chmod [%s]" - self.log(t % (vp, ap, iflag, chmod), 3) + t = "failed to os.open [%s] -> [%s] with iflag [%s] and chmod [%s]: %r" + self.log(t % (vp, ap, iflag, chmod, ex), 3) return paramiko.SFTPServer.convert_errno(ex.errno) if iflag & os.O_CREAT: @@ -534,8 +549,8 @@ class SFTP_Srv(paramiko.SFTPServerInterface): try: f = os.fdopen(fd, smode) except OSError as ex: - t = "failed to os.fdpen [%s] -> [%s] with smode [%s]" - self.log(t % (vp, ap, smode), 3) + t = "failed to os.fdpen [%s] -> [%s] with smode [%s]: %r" + self.log(t % (vp, ap, smode, ex), 3) return paramiko.SFTPServer.convert_errno(ex.errno) ret = SFTP_FH(iflag) @@ -552,17 +567,20 @@ class SFTP_Srv(paramiko.SFTPServerInterface): return SFTP_FAILURE def _remove(self, vp: str) -> int: + self.log("rm(%s)" % (vp,)) if self.args.no_del: self.log("The delete feature is disabled in server config") return SFTP_PERMISSION_DENIED try: self.hub.up2k.handle_rm(self.uname, self.ip, [vp], [], False, False) + self.log("rm(%s): ok" % (vp,)) return SFTP_OK except Pebkac as ex: t = "denied delete [%s]: %s" self.log(t % (vp, ex)) return SFTP_PERMISSION_DENIED except OSError as ex: + self.log("failed: rm(%s): %r" % (vp, ex)) return paramiko.SFTPServer.convert_errno(ex.errno) def rename(self, oldpath: str, newpath: str) -> int: @@ -573,6 +591,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): return SFTP_FAILURE def _rename(self, svp: str, dvp: str) -> int: + self.log("mv(%s, %s)" % (svp, dvp)) if self.args.no_mv: self.log("The rename/move feature is disabled in server config") svp = svp.strip("/") @@ -585,6 +604,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): self.log(t % (svp, dvp, ex)) return SFTP_PERMISSION_DENIED except OSError as ex: + self.log("mv(%s, %s): %r" % (svp, dvp, ex)) return paramiko.SFTPServer.convert_errno(ex.errno) def mkdir(self, path: str, attr: SATTR) -> int: @@ -595,6 +615,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): return SFTP_FAILURE def _mkdir(self, vp: str, attr: SATTR) -> int: + self.log("mkdir(%s)" % (vp,)) try: vn, rem = self.asrv.vfs.get(vp, self.uname, False, True) ap = os.path.join(vn.realpath, rem) @@ -607,6 +628,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): self.log(t % (vp, ex)) return SFTP_PERMISSION_DENIED except OSError as ex: + self.log("mkdir(%s): %r" % (vp, ex)) return paramiko.SFTPServer.convert_errno(ex.errno) def rmdir(self, path: str) -> int: @@ -617,6 +639,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): return SFTP_FAILURE def _rmdir(self, vp: str) -> int: + self.log("rmdir(%s)" % (vp,)) try: vn, rem = self.asrv.vfs.get(vp, self.uname, False, False, will_del=True) ap = os.path.join(vn.realpath, rem) @@ -627,6 +650,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): self.log(t % (vp, ex)) return SFTP_PERMISSION_DENIED except OSError as ex: + self.log("rmdir(%s): %r" % (vp, ex)) return paramiko.SFTPServer.convert_errno(ex.errno) def chattr(self, path: str, attr: SATTR) -> int: @@ -637,6 +661,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): return SFTP_FAILURE def _chattr(self, vp: str, attr: SATTR) -> int: + self.log("chattr(%s, %s)" % (vp, attr)) try: vn, rem = self.asrv.vfs.get(vp, self.uname, False, True, will_del=True) ap = os.path.join(vn.realpath, rem) @@ -647,6 +672,7 @@ class SFTP_Srv(paramiko.SFTPServerInterface): self.log(t % (vp, ex)) return SFTP_PERMISSION_DENIED except OSError as ex: + self.log("chattr(%s): %r" % (vp, ex)) return paramiko.SFTPServer.convert_errno(ex.errno) def symlink(self, target_path: str, path: str) -> int: @@ -714,7 +740,7 @@ class Sftpd(object): for ip in ips: self._bind(ip) - self.log("listening on %s port %s" % (self.srv, args.sftp)) + self.log("listening on %s port %s" % (self.bound, args.sftp)) def log(self, msg: str, c: Union[int, str] = 0) -> None: self.hub.log("sftp", msg, c) diff --git a/copyparty/up2k.py b/copyparty/up2k.py index 079001ee..06696cf4 100644 --- a/copyparty/up2k.py +++ b/copyparty/up2k.py @@ -4182,6 +4182,7 @@ class Up2k(object): st = bos.lstat(atop) is_dir = stat.S_ISDIR(st.st_mode) except: + # NOTE: "file not found" *sftpd raise Pebkac(400, "file not found on disk (already deleted?)") if "bcasechk" in vn.flags and not vn.casechk(rem, False): diff --git a/copyparty/util.py b/copyparty/util.py index ff5c8c10..75fc0ada 100644 --- a/copyparty/util.py +++ b/copyparty/util.py @@ -377,6 +377,7 @@ IMPLICATIONS = [ ["tftpvv", "tftpv"], ["nodupem", "nodupe"], ["no_dupe_m", "no_dupe"], + ["sftpvv", "sftpv"], ["smbw", "smb"], ["smb1", "smb"], ["smbvvv", "smbvv"],