diff --git a/README.md b/README.md index 44d3970b..da85912d 100644 --- a/README.md +++ b/README.md @@ -365,8 +365,8 @@ For each account the following properties can be configured: - `rename` rename files or directories is allowed - `create_dirs` create directories is allowed - `create_symlinks` create symbolic links is allowed - - `chmod` changing file or directory permissions is allowed - - `chown` changing file or directory owner and group is allowed + - `chmod` changing file or directory permissions is allowed. On Windows, only the 0200 bit (owner writable) of mode is used; it controls whether the file's read-only attribute is set or cleared. The other bits are currently unused. Use mode 0400 for a read-only file and 0600 for a readable+writable file. + - `chown` changing file or directory owner and group is allowed. Changing owner and group is not supported on Windows. - `upload_bandwidth` maximum upload bandwidth as KB/s, 0 means unlimited. - `download_bandwidth` maximum download bandwidth as KB/s, 0 means unlimited. diff --git a/sftpd/handler.go b/sftpd/handler.go index f380b094..2c57ed16 100644 --- a/sftpd/handler.go +++ b/sftpd/handler.go @@ -229,7 +229,7 @@ func (c Connection) Filelist(request *sftp.Request) (sftp.ListerAt, error) { return nil, sftp.ErrSSHFxPermissionDenied } - c.Log(logger.LevelDebug, logSender, "requested Stat for file: %#v", p) + c.Log(logger.LevelDebug, logSender, "requested stat for file: %#v", p) s, err := os.Stat(p) if err != nil { c.Log(logger.LevelWarn, logSender, "error running Stat on file: %#v", err) diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index 04b1eaf0..fda02625 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -519,6 +519,69 @@ func TestStat(t *testing.T) { if err != nil { t.Errorf("stat error: %v", err) } + // mode 0666 and 0444 works on Windows too + newPerm := os.FileMode(0666) + err = client.Chmod(testFileName, newPerm) + if err != nil { + t.Errorf("chmod error: %v", err) + } + newFi, err := client.Lstat(testFileName) + if err != nil { + t.Errorf("stat error: %v", err) + } + if newPerm != newFi.Mode().Perm() { + t.Errorf("chmod failed expected: %v, actual: %v", newPerm, newFi.Mode().Perm()) + } + newPerm = os.FileMode(0444) + err = client.Chmod(testFileName, newPerm) + if err != nil { + t.Errorf("chmod error: %v", err) + } + newFi, err = client.Lstat(testFileName) + if err != nil { + t.Errorf("stat error: %v", err) + } + if newPerm != newFi.Mode().Perm() { + t.Errorf("chmod failed expected: %v, actual: %v", newPerm, newFi.Mode().Perm()) + } + _, err = client.ReadLink(testFileName) + if err == nil { + t.Errorf("readlink is not supported and must fail") + } + err = client.Chtimes(testFileName, time.Now(), time.Now()) + if err != nil { + t.Errorf("chtime must be silently ignored: %v", err) + } + } + _, err = httpd.RemoveUser(user, http.StatusOK) + if err != nil { + t.Errorf("unable to remove user: %v", err) + } + os.RemoveAll(user.GetHomeDir()) +} + +func TestStatChownChmod(t *testing.T) { + if runtime.GOOS == "windows" { + t.Skip("chown is not supported on Windows, chmod is partially supported") + } + usePubKey := true + user, _, err := httpd.AddUser(getTestUser(usePubKey), http.StatusOK) + if err != nil { + t.Errorf("unable to add user: %v", err) + } + client, err := getSftpClient(user, usePubKey) + if err != nil { + t.Errorf("unable to create sftp client: %v", err) + } else { + defer client.Close() + testFileName := "test_file.dat" + testFilePath := filepath.Join(homeBasePath, testFileName) + testFileSize := int64(65535) + createTestFile(testFilePath, testFileSize) + err = sftpUploadFile(testFilePath, testFileName, testFileSize, client) + if err != nil { + t.Errorf("file upload error: %v", err) + } err = client.Chown(testFileName, os.Getuid(), os.Getgid()) if err != nil { t.Errorf("chown error: %v", err) @@ -535,10 +598,6 @@ func TestStat(t *testing.T) { if newPerm != newFi.Mode().Perm() { t.Errorf("chown failed expected: %v, actual: %v", newPerm, newFi.Mode().Perm()) } - _, err = client.ReadLink(testFileName) - if err == nil { - t.Errorf("readlink is not supported and must fail") - } err = client.Remove(testFileName) if err != nil { t.Errorf("error removing uploaded file: %v", err) @@ -552,10 +611,6 @@ func TestStat(t *testing.T) { if err != os.ErrNotExist { t.Errorf("unexpected chown error: %v expected: %v", err, os.ErrNotExist) } - err = client.Chtimes(testFileName, time.Now(), time.Now()) - if err != nil { - t.Errorf("chtime must be silently ignored: %v", err) - } } _, err = httpd.RemoveUser(user, http.StatusOK) if err != nil {