diff --git a/common/common.go b/common/common.go index 0e929675..00d43718 100644 --- a/common/common.go +++ b/common/common.go @@ -291,6 +291,7 @@ type ActiveTransfer interface { SignalClose() Truncate(fsPath string, size int64) (int64, error) GetRealFsPath(fsPath string) string + SetTimes(fsPath string, atime time.Time, mtime time.Time) bool } // ActiveConnection defines the interface for the current active connections diff --git a/common/connection.go b/common/connection.go index 6b8775b6..0cf14a60 100644 --- a/common/connection.go +++ b/common/connection.go @@ -201,6 +201,17 @@ func (c *BaseConnection) getRealFsPath(fsPath string) string { return fsPath } +func (c *BaseConnection) setTimes(fsPath string, atime time.Time, mtime time.Time) { + c.RLock() + defer c.RUnlock() + + for _, t := range c.activeTransfers { + if t.SetTimes(fsPath, atime, mtime) { + return + } + } +} + func (c *BaseConnection) truncateOpenHandle(fsPath string, size int64) (int64, error) { c.RLock() defer c.RUnlock() @@ -558,6 +569,7 @@ func (c *BaseConnection) handleChtimes(fs vfs.Fs, fsPath, pathForPerms string, a fsPath, attributes.Atime, attributes.Mtime, err) return c.GetFsError(fs, err) } + c.setTimes(fsPath, attributes.Atime, attributes.Mtime) accessTimeString := attributes.Atime.Format(chtimesFormat) modificationTimeString := attributes.Mtime.Format(chtimesFormat) logger.CommandLog(chtimesLogSender, fsPath, "", c.User.Username, "", c.ID, c.protocol, -1, -1, diff --git a/common/protocol_test.go b/common/protocol_test.go index 068f4139..f28a9772 100644 --- a/common/protocol_test.go +++ b/common/protocol_test.go @@ -321,6 +321,58 @@ func TestSetStat(t *testing.T) { assert.NoError(t, err) } +func TestChtimesOpenHandle(t *testing.T) { + localUser, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated) + assert.NoError(t, err) + sftpUser, _, err := httpdtest.AddUser(getTestSFTPUser(), http.StatusCreated) + assert.NoError(t, err) + u := getCryptFsUser() + u.Username += "_crypt" + cryptFsUser, _, err := httpdtest.AddUser(u, http.StatusCreated) + assert.NoError(t, err) + + for _, user := range []dataprovider.User{localUser, sftpUser, cryptFsUser} { + conn, client, err := getSftpClient(user) + if assert.NoError(t, err) { + defer conn.Close() + defer client.Close() + + f, err := client.Create(testFileName) + assert.NoError(t, err, "user %v", user.Username) + f1, err := client.Create(testFileName + "1") + assert.NoError(t, err, "user %v", user.Username) + acmodTime := time.Now().Add(36 * time.Hour) + err = client.Chtimes(testFileName, acmodTime, acmodTime) + assert.NoError(t, err, "user %v", user.Username) + _, err = f.Write(testFileContent) + assert.NoError(t, err, "user %v", user.Username) + err = f.Close() + assert.NoError(t, err, "user %v", user.Username) + err = f1.Close() + assert.NoError(t, err, "user %v", user.Username) + info, err := client.Lstat(testFileName) + assert.NoError(t, err, "user %v", user.Username) + diff := math.Abs(info.ModTime().Sub(acmodTime).Seconds()) + assert.LessOrEqual(t, diff, float64(1), "user %v", user.Username) + info1, err := client.Lstat(testFileName + "1") + assert.NoError(t, err, "user %v", user.Username) + diff = math.Abs(info1.ModTime().Sub(acmodTime).Seconds()) + assert.Greater(t, diff, float64(86400), "user %v", user.Username) + } + } + + _, err = httpdtest.RemoveUser(sftpUser, http.StatusOK) + assert.NoError(t, err) + _, err = httpdtest.RemoveUser(localUser, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(localUser.GetHomeDir()) + assert.NoError(t, err) + _, err = httpdtest.RemoveUser(cryptFsUser, http.StatusOK) + assert.NoError(t, err) + err = os.RemoveAll(cryptFsUser.GetHomeDir()) + assert.NoError(t, err) +} + func TestPermissionErrors(t *testing.T) { user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated) assert.NoError(t, err) diff --git a/common/transfer.go b/common/transfer.go index 388c63c0..6dd3703e 100644 --- a/common/transfer.go +++ b/common/transfer.go @@ -38,6 +38,8 @@ type BaseTransfer struct { //nolint:maligned isNewFile bool transferType int AbortTransfer int32 + aTime time.Time + mTime time.Time sync.Mutex ErrTransfer error } @@ -115,6 +117,15 @@ func (t *BaseTransfer) GetFsPath() string { return t.fsPath } +func (t *BaseTransfer) SetTimes(fsPath string, atime time.Time, mtime time.Time) bool { + if fsPath == t.GetFsPath() { + t.aTime = atime + t.mTime = mtime + return true + } + return false +} + // GetRealFsPath returns the real transfer filesystem path. // If atomic uploads are enabled this differ from fsPath func (t *BaseTransfer) GetRealFsPath(fsPath string) string { @@ -252,6 +263,7 @@ func (t *BaseTransfer) Close() error { } t.Connection.Log(logger.LevelDebug, "uploaded file size %v", fileSize) t.updateQuota(numFiles, fileSize) + t.updateTimes() logger.TransferLog(uploadLogSender, t.fsPath, elapsed, atomic.LoadInt64(&t.BytesReceived), t.Connection.User.Username, t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode) ExecuteActionNotification(&t.Connection.User, operationUpload, t.fsPath, t.requestPath, "", "", "", t.Connection.protocol, @@ -266,6 +278,14 @@ func (t *BaseTransfer) Close() error { return err } +func (t *BaseTransfer) updateTimes() { + if !t.aTime.IsZero() && !t.mTime.IsZero() { + err := t.Fs.Chtimes(t.fsPath, t.aTime, t.mTime) + t.Connection.Log(logger.LevelDebug, "set times for file %#v, atime: %v, mtime: %v, err: %v", + t.fsPath, t.aTime, t.mTime, err) + } +} + func (t *BaseTransfer) updateQuota(numFiles int, fileSize int64) bool { // S3 uploads are atomic, if there is an error nothing is uploaded if t.File == nil && t.ErrTransfer != nil { diff --git a/docs/portable-mode.md b/docs/portable-mode.md index 9b9fafb1..007c4135 100644 --- a/docs/portable-mode.md +++ b/docs/portable-mode.md @@ -84,6 +84,7 @@ Flags: --s3-acl string --s3-bucket string --s3-endpoint string + --s3-force-path-style Force path style bucket URL --s3-key-prefix string Allows to restrict access to the virtual folder identified by this prefix and its contents diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go index d223f419..15a41ddd 100644 --- a/sftpd/sftpd_test.go +++ b/sftpd/sftpd_test.go @@ -9762,6 +9762,8 @@ func getScpDownloadCommand(localPath, remotePath string, preserveTime, recursive args = append(args, "2022") args = append(args, "-o") args = append(args, "StrictHostKeyChecking=no") + args = append(args, "-o") + args = append(args, "HostKeyAlgorithms=+ssh-rsa") args = append(args, "-i") args = append(args, privateKeyPath) args = append(args, remotePath) @@ -9787,6 +9789,8 @@ func getScpUploadCommand(localPath, remotePath string, preserveTime, remoteToRem args = append(args, "2022") args = append(args, "-o") args = append(args, "StrictHostKeyChecking=no") + args = append(args, "-o") + args = append(args, "HostKeyAlgorithms=+ssh-rsa") args = append(args, "-i") args = append(args, privateKeyPath) args = append(args, localPath) diff --git a/templates/webadmin/folders.html b/templates/webadmin/folders.html index f91f6301..5bef6844 100644 --- a/templates/webadmin/folders.html +++ b/templates/webadmin/folders.html @@ -232,7 +232,7 @@ function deleteAction() { }, { "targets": [2], - "render": $.fn.dataTable.render.ellipsis(50, true), + "render": $.fn.dataTable.render.ellipsis(60, true), }, { "targets": [3], diff --git a/templates/webadmin/users.html b/templates/webadmin/users.html index 820e71b1..9dd64b0d 100644 --- a/templates/webadmin/users.html +++ b/templates/webadmin/users.html @@ -255,11 +255,11 @@ }, { "targets": [3], - "render": $.fn.dataTable.render.ellipsis(30, true), + "render": $.fn.dataTable.render.ellipsis(40, true), }, { "targets": [4], - "render": $.fn.dataTable.render.ellipsis(40, true), + "render": $.fn.dataTable.render.ellipsis(70, true), } ], "scrollX": false,