diff --git a/common/transfer.go b/common/transfer.go index ed4e3ef1..1a8df966 100644 --- a/common/transfer.go +++ b/common/transfer.go @@ -30,6 +30,7 @@ type BaseTransfer struct { //nolint:maligned fsPath string effectiveFsPath string requestPath string + ftpMode string start time.Time MaxWriteSize int64 MinWriteOffset int64 @@ -68,6 +69,11 @@ func NewBaseTransfer(file vfs.File, conn *BaseConnection, cancelFn func(), fsPat return t } +// SetFtpMode sets the FTP mode for the current transfer +func (t *BaseTransfer) SetFtpMode(mode string) { + t.ftpMode = mode +} + // GetID returns the transfer ID func (t *BaseTransfer) GetID() uint64 { return t.ID @@ -236,7 +242,7 @@ func (t *BaseTransfer) Close() error { elapsed := time.Since(t.start).Nanoseconds() / 1000000 if t.transferType == TransferDownload { logger.TransferLog(downloadLogSender, t.fsPath, elapsed, atomic.LoadInt64(&t.BytesSent), t.Connection.User.Username, - t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr) + t.Connection.ID, t.Connection.protocol, t.Connection.localAddr, t.Connection.remoteAddr, t.ftpMode) ExecuteActionNotification(&t.Connection.User, operationDownload, t.fsPath, t.requestPath, "", "", t.Connection.protocol, atomic.LoadInt64(&t.BytesSent), t.ErrTransfer) } else { @@ -247,7 +253,7 @@ func (t *BaseTransfer) Close() error { t.Connection.Log(logger.LevelDebug, "uploaded file size %v", fileSize) t.updateQuota(numFiles, fileSize) 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.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, fileSize, t.ErrTransfer) } diff --git a/common/transfer_test.go b/common/transfer_test.go index 1c76b6d7..765ee250 100644 --- a/common/transfer_test.go +++ b/common/transfer_test.go @@ -284,3 +284,16 @@ func TestRemovePartialCryptoFile(t *testing.T) { assert.Equal(t, int64(9), size) assert.NoFileExists(t, testFile) } + +func TestFTPMode(t *testing.T) { + conn := NewBaseConnection("", ProtocolFTP, "", "", dataprovider.User{}) + transfer := BaseTransfer{ + Connection: conn, + transferType: TransferUpload, + BytesReceived: 123, + Fs: vfs.NewOsFs("", os.TempDir(), ""), + } + assert.Empty(t, transfer.ftpMode) + transfer.SetFtpMode("active") + assert.Equal(t, "active", transfer.ftpMode) +} diff --git a/docs/logs.md b/docs/logs.md index b0614530..4e08b302 100644 --- a/docs/logs.md +++ b/docs/logs.md @@ -20,7 +20,8 @@ The logs can be divided into the following categories: - `username`, string - `file_path` string - `connection_id` string. Unique connection identifier - - `protocol` string. `SFTP` or `SCP` + - `protocol` string. `SFTP`, `SCP`, `SSH`, `FTP`, `HTTP`, `DAV` + - `ftp_mode`, string. `active` or `passive`. Included only for `FTP` protocol - **"command logs"**, SFTP/SCP command logs: - `sender` string. `Rename`, `Rmdir`, `Mkdir`, `Symlink`, `Remove`, `Chmod`, `Chown`, `Chtimes`, `Truncate`, `SSHCommand` - `level` string diff --git a/ftpd/handler.go b/ftpd/handler.go index 29c683b1..ba663497 100644 --- a/ftpd/handler.go +++ b/ftpd/handler.go @@ -30,6 +30,19 @@ type Connection struct { clientContext ftpserver.ClientContext } +func (c *Connection) getFTPMode() string { + if c.clientContext == nil { + return "" + } + switch c.clientContext.GetLastDataChannel() { + case ftpserver.DataChannelActive: + return "active" + case ftpserver.DataChannelPassive: + return "passive" + } + return "" +} + // GetClientVersion returns the connected client's version. // It returns "Unknown" if the client does not advertise its // version @@ -325,6 +338,7 @@ func (c *Connection) downloadFile(fs vfs.Fs, fsPath, ftpPath string, offset int6 baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, fsPath, fsPath, ftpPath, common.TransferDownload, 0, 0, 0, false, fs) + baseTransfer.SetFtpMode(c.getFTPMode()) t := newTransfer(baseTransfer, nil, r, offset) return t, nil @@ -390,6 +404,7 @@ func (c *Connection) handleFTPUploadToNewFile(fs vfs.Fs, resolvedPath, filePath, baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, filePath, requestPath, common.TransferUpload, 0, 0, maxWriteSize, true, fs) + baseTransfer.SetFtpMode(c.getFTPMode()) t := newTransfer(baseTransfer, w, nil, 0) return t, nil @@ -466,6 +481,7 @@ func (c *Connection) handleFTPUploadToExistingFile(fs vfs.Fs, flags int, resolve baseTransfer := common.NewBaseTransfer(file, c.BaseConnection, cancelFn, resolvedPath, filePath, requestPath, common.TransferUpload, minWriteOffset, initialSize, maxWriteSize, false, fs) + baseTransfer.SetFtpMode(c.getFTPMode()) t := newTransfer(baseTransfer, w, nil, 0) return t, nil diff --git a/ftpd/internal_test.go b/ftpd/internal_test.go index d3fc51b3..014c6afe 100644 --- a/ftpd/internal_test.go +++ b/ftpd/internal_test.go @@ -253,6 +253,7 @@ xr5cb9VBRBtB9aOKVfuRhpatAfS2Pzm2Htae9lFn7slGPUmu2hkjDw== ) type mockFTPClientContext struct { + lastDataChannel ftpserver.DataChannel } func (cc mockFTPClientContext) Path() string { @@ -298,7 +299,7 @@ func (cc mockFTPClientContext) GetLastCommand() string { } func (cc mockFTPClientContext) GetLastDataChannel() ftpserver.DataChannel { - return ftpserver.DataChannelPassive + return cc.lastDataChannel } // MockOsFs mockable OsFs @@ -559,6 +560,19 @@ func TestUserInvalidParams(t *testing.T) { assert.Error(t, err) } +func TestFTPMode(t *testing.T) { + connection := &Connection{ + BaseConnection: common.NewBaseConnection("", common.ProtocolFTP, "", "", dataprovider.User{}), + } + assert.Empty(t, connection.getFTPMode()) + connection.clientContext = mockFTPClientContext{lastDataChannel: ftpserver.DataChannelActive} + assert.Equal(t, "active", connection.getFTPMode()) + connection.clientContext = mockFTPClientContext{lastDataChannel: ftpserver.DataChannelPassive} + assert.Equal(t, "passive", connection.getFTPMode()) + connection.clientContext = mockFTPClientContext{lastDataChannel: 0} + assert.Empty(t, connection.getFTPMode()) +} + func TestClientVersion(t *testing.T) { mockCC := mockFTPClientContext{} connID := fmt.Sprintf("2_%v", mockCC.ID()) diff --git a/logger/logger.go b/logger/logger.go index 3e2a6620..eee983ff 100644 --- a/logger/logger.go +++ b/logger/logger.go @@ -259,8 +259,10 @@ func ErrorToConsole(format string, v ...interface{}) { } // TransferLog logs uploads or downloads -func TransferLog(operation, path string, elapsed int64, size int64, user, connectionID, protocol, localAddr, remoteAddr string) { - logger.Info(). +func TransferLog(operation, path string, elapsed int64, size int64, user, connectionID, protocol, localAddr, + remoteAddr, ftpMode string, +) { + ev := logger.Info(). Timestamp(). Str("sender", operation). Str("local_addr", localAddr). @@ -270,8 +272,11 @@ func TransferLog(operation, path string, elapsed int64, size int64, user, connec Str("username", user). Str("file_path", path). Str("connection_id", connectionID). - Str("protocol", protocol). - Send() + Str("protocol", protocol) + if ftpMode != "" { + ev.Str("ftp_mode", ftpMode) + } + ev.Send() } // CommandLog logs an SFTP/SCP/SSH command