diff --git a/README.md b/README.md index 23fb3178..28fd9500 100644 --- a/README.md +++ b/README.md @@ -156,8 +156,8 @@ The `sftpgo` configuration file contains the following sections: - `scp`, SCP is an experimental feature, we have our own SCP implementation since we can't rely on "scp" system command to proper handle quotas and user's home dir restrictions. The SCP protocol is quite simple but there is no official docs about it, so we need more testing and feedbacks before enabling it by default. We may not handle some borderline cases or have sneaky bugs. Please do accurate tests yourself before enabling SCP and let us known if something does not work as expected for your use cases. SCP between two remote hosts is supported using the `-3` scp option. - `md5sum`, `sha1sum`, `sha256sum`, `sha384sum`, `sha512sum`. Useful to check message digests for uploaded files. These commands are implemented inside SFTPGo so they work even if the matching system commands are not available, for example on Windows. - `cd`, `pwd`. Some SFTP clients does not support the SFTP SSH_FXP_REALPATH packet type and so they use `cd` and `pwd` SSH commands to get the initial directory. Currently `cd` do nothing and `pwd` always returns the `/` path. - - `git-receive-pack`, `git-upload-pack`, `git-upload-archive`. These commands enable support for Git repositories over SSH, they need to be installed and in your system's `PATH`. - - `rsync`. The `rsync` command need to be installed and in your system's `PATH`. We cannot avoid that rsync create symlinks so if the user has the permission to create symlinks we add the option `--safe-links` to the received rsync command if it is not already set. This should prevent to create symlinks that point outside the home dir. If the user cannot create symlinks we add the option `--munge-links`, if it is not already set. This should make symlinks unusable (but manually recoverable) + - `git-receive-pack`, `git-upload-pack`, `git-upload-archive`. These commands enable support for Git repositories over SSH, they need to be installed and in your system's `PATH`. Git commands are not allowed inside virtual folders. + - `rsync`. The `rsync` command need to be installed and in your system's `PATH`. We cannot avoid that rsync create symlinks so if the user has the permission to create symlinks we add the option `--safe-links` to the received rsync command if it is not already set. This should prevent to create symlinks that point outside the home dir. If the user cannot create symlinks we add the option `--munge-links`, if it is not already set. This should make symlinks unusable (but manually recoverable). The `rsync` command interacts with the filesystem directly and it is not aware about virtual folders, so it will be automatically disabled for users with virtual folders. - `keyboard_interactive_auth_program`, string. Absolute path to an external program to use for keyboard interactive authentication. See the "Keyboard Interactive Authentication" paragraph for more details. - **"data_provider"**, the configuration for the data provider - `driver`, string. Supported drivers are `sqlite`, `mysql`, `postgresql`, `bolt`, `memory` @@ -410,7 +410,7 @@ The external program can read the following environment variables to get info ab - `SFTPGO_LOGIND_USER`, it contains the user trying to login serialized as JSON - `SFTPGO_LOGIND_METHOD`, possibile values are: `password`, `publickey` and `keyboard-interactive` -The program must write, on its the standard output, an empty string (or no response at all) if no user update is needed or with a valid SFTPGo user serialized as JSON. +The program must write, on its the standard output, an empty string (or no response at all) if no user update is needed or a valid SFTPGo user serialized as JSON. The JSON response can include only the fields that need to the updated instead of the full user, for example if you want to disable the user you can return a response like this: ```json diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index b1009496..f6f7a63f 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -799,6 +799,48 @@ func TestSSHCommandQuotaScan(t *testing.T) { } } +func TestGitVirtualFolders(t *testing.T) { + permissions := make(map[string][]string) + permissions["/"] = []string{dataprovider.PermAny} + user := dataprovider.User{ + Permissions: permissions, + HomeDir: os.TempDir(), + } + fs, _ := user.GetFilesystem("123") + conn := Connection{ + User: user, + fs: fs, + } + cmd := sshCommand{ + command: "git-receive-pack", + connection: conn, + args: []string{"/vdir"}, + } + cmd.connection.User.VirtualFolders = append(cmd.connection.User.VirtualFolders, vfs.VirtualFolder{ + VirtualPath: "/vdir", + MappedPath: os.TempDir(), + }) + _, err := cmd.getSystemCommand() + if err != errUnsupportedConfig { + t.Errorf("unexpected error: %v", err) + } + cmd.connection.User.VirtualFolders = nil + cmd.connection.User.VirtualFolders = append(cmd.connection.User.VirtualFolders, vfs.VirtualFolder{ + VirtualPath: "/vdir", + MappedPath: os.TempDir(), + }) + cmd.args = []string{"/vdir/subdir"} + _, err = cmd.getSystemCommand() + if err != errUnsupportedConfig { + t.Errorf("unexpected error: %v", err) + } + cmd.args = []string{"/adir/subdir"} + _, err = cmd.getSystemCommand() + if err != nil { + t.Errorf("unexpected error: %v", err) + } +} + func TestRsyncOptions(t *testing.T) { permissions := make(map[string][]string) permissions["/"] = []string{dataprovider.PermAny} @@ -843,6 +885,14 @@ func TestRsyncOptions(t *testing.T) { if !utils.IsStringInSlice("--munge-links", cmd.cmd.Args) { t.Errorf("--munge-links must be added if the user has the create symlinks permission") } + sshCmd.connection.User.VirtualFolders = append(sshCmd.connection.User.VirtualFolders, vfs.VirtualFolder{ + VirtualPath: "/vdir", + MappedPath: os.TempDir(), + }) + _, err = sshCmd.getSystemCommand() + if err != errUnsupportedConfig { + t.Errorf("unexpected error: %v", err) + } } func TestSystemCommandErrors(t *testing.T) { diff --git a/sftpd/scp.go b/sftpd/scp.go index bce81390..ef9b7f34 100644 --- a/sftpd/scp.go +++ b/sftpd/scp.go @@ -312,7 +312,15 @@ func (c *scpCommand) sendDownloadProtocolMessages(dirPath string, stat os.FileIn } } - fileMode := fmt.Sprintf("D%v 0 %v\n", getFileModeAsString(stat.Mode(), stat.IsDir()), filepath.Base(dirPath)) + dirName := filepath.Base(dirPath) + for _, v := range c.connection.User.VirtualFolders { + if v.MappedPath == dirPath { + dirName = path.Base(v.VirtualPath) + break + } + } + + fileMode := fmt.Sprintf("D%v 0 %v\n", getFileModeAsString(stat.Mode(), stat.IsDir()), dirName) err = c.sendProtocolMessage(fileMode) if err != nil { return err @@ -332,6 +340,7 @@ func (c *scpCommand) handleRecursiveDownload(dirPath string, stat os.FileInfo) e return err } files, err := c.connection.fs.ReadDir(dirPath) + files = c.connection.User.AddVirtualDirs(files, c.connection.fs.GetRelativePath(dirPath)) if err != nil { c.sendErrorMessage(err.Error()) return err @@ -627,8 +636,14 @@ func (c *scpCommand) getNextUploadProtocolMessage() (string, error) { func (c *scpCommand) createDir(dirPath string) error { var err error + var isDir bool + isDir, err = vfs.IsDirectory(c.connection.fs, dirPath) + if err == nil && isDir { + c.connection.Log(logger.LevelDebug, logSenderSCP, "directory %#v already exists", dirPath) + return nil + } if err = c.connection.fs.Mkdir(dirPath); err != nil { - c.connection.Log(logger.LevelError, logSenderSCP, "error creating dir: %v", dirPath) + c.connection.Log(logger.LevelError, logSenderSCP, "error creating dir %#v", dirPath) c.sendErrorMessage(err.Error()) return err } diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 629adb85..e081900d 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -3952,6 +3952,11 @@ func TestSCPRecursive(t *testing.T) { if err != nil { t.Errorf("error uploading dir via scp: %v", err) } + // overwrite existing dir + err = scpUpload(testBaseDirPath, remoteUpPath, true, false) + if err != nil { + t.Errorf("error uploading dir via scp: %v", err) + } err = scpDownload(testBaseDirDownPath, remoteDownPath, true, true) if err != nil { t.Errorf("error downloading dir via scp: %v", err) diff --git a/sftpd/ssh_cmd.go b/sftpd/ssh_cmd.go index fe83908e..4d1f147b 100644 --- a/sftpd/ssh_cmd.go +++ b/sftpd/ssh_cmd.go @@ -302,7 +302,32 @@ func (c *sshCommand) getSystemCommand() (systemCommand, error) { args = args[:len(args)-1] args = append(args, path) } + if strings.HasPrefix(c.command, "git-") { + // we don't allow git inside virtual folders + gitPath := c.getDestPath() + for _, v := range c.connection.User.VirtualFolders { + if v.VirtualPath == gitPath { + c.connection.Log(logger.LevelDebug, logSenderSSH, "git is not supported inside virtual folder %#v user %#v", + gitPath, c.connection.User.Username) + return command, errUnsupportedConfig + } + if len(gitPath) > len(v.VirtualPath) { + if strings.HasPrefix(gitPath, v.VirtualPath+"/") { + c.connection.Log(logger.LevelDebug, logSenderSSH, "git is not supported inside virtual folder %#v user %#v", + gitPath, c.connection.User.Username) + return command, errUnsupportedConfig + } + } + } + } if c.command == "rsync" { + // if the user has virtual folders we don't allow rsync since the rsync command interacts with the + // filesystem directly and it is not aware about virtual folders + if len(c.connection.User.VirtualFolders) > 0 { + c.connection.Log(logger.LevelDebug, logSenderSSH, "user %#v has virtual folders, rsync is not supported", + c.connection.User.Username) + return command, errUnsupportedConfig + } // we cannot avoid that rsync create symlinks so if the user has the permission // to create symlinks we add the option --safe-links to the received rsync command if // it is not already set. This should prevent to create symlinks that point outside