diff --git a/README.md b/README.md index 5d97eb7e..52c14c6e 100644 --- a/README.md +++ b/README.md @@ -425,6 +425,7 @@ The `command` can also read the following environment variables: - `SFTPGO_ACTION_TARGET`, non empty for `rename` `SFTPGO_ACTION` - `SFTPGO_ACTION_SSH_CMD`, non empty for `ssh_cmd` `SFTPGO_ACTION` - `SFTPGO_ACTION_FILE_SIZE`, non empty for `upload`, `download` and `delete` `SFTPGO_ACTION` +- `SFTPGO_ACTION_LOCAL_FILE`, `true` if the affected file is stored on the local filesystem, otherwise `false` Previous global environment variables aren't cleared when the script is called. The `command` must finish within 30 seconds. @@ -434,6 +435,7 @@ The `http_notification_url`, if defined, will contain the following, percent enc - `action` - `username` - `path` +- `local_file`, `true` if the affected file is stored on the local filesystem, otherwise `false` - `target_path`, added for `rename` action - `ssh_cmd`, added for `ssh_cmd` action - `file_size`, added for `upload`, `download`, `delete` actions diff --git a/sftpd/handler.go b/sftpd/handler.go index adb29e9d..4df7ba85 100644 --- a/sftpd/handler.go +++ b/sftpd/handler.go @@ -314,7 +314,7 @@ func (c Connection) handleSFTPRename(sourcePath string, targetPath string, reque return vfs.GetSFTPError(c.fs, err) } logger.CommandLog(renameLogSender, sourcePath, targetPath, c.User.Username, "", c.ID, c.protocol, -1, -1, "", "", "") - go executeAction(operationRename, c.User.Username, sourcePath, targetPath, "", 0) + go executeAction(operationRename, c.User.Username, sourcePath, targetPath, "", 0, vfs.IsLocalOsFs(c.fs)) return nil } @@ -404,7 +404,7 @@ func (c Connection) handleSFTPRemove(filePath string, request *sftp.Request) err if fi.Mode()&os.ModeSymlink != os.ModeSymlink { dataprovider.UpdateUserQuota(dataProvider, c.User, -1, -size, false) } - go executeAction(operationDelete, c.User.Username, filePath, "", "", fi.Size()) + go executeAction(operationDelete, c.User.Username, filePath, "", "", fi.Size(), vfs.IsLocalOsFs(c.fs)) return sftp.ErrSSHFxOk } diff --git a/sftpd/internal_test.go b/sftpd/internal_test.go index 86a44913..8ef2b2f0 100644 --- a/sftpd/internal_test.go +++ b/sftpd/internal_test.go @@ -130,17 +130,17 @@ func TestWrongActions(t *testing.T) { Command: badCommand, HTTPNotificationURL: "", } - err := executeAction(operationDownload, "username", "path", "", "", 0) + err := executeAction(operationDownload, "username", "path", "", "", 0, true) if err == nil { t.Errorf("action with bad command must fail") } - err = executeAction(operationDelete, "username", "path", "", "", 0) + err = executeAction(operationDelete, "username", "path", "", "", 0, true) if err != nil { t.Errorf("action not configured must silently fail") } actions.Command = "" actions.HTTPNotificationURL = "http://foo\x7f.com/" - err = executeAction(operationDownload, "username", "path", "", "", 0) + err = executeAction(operationDownload, "username", "path", "", "", 0, true) if err == nil { t.Errorf("action with bad url must fail") } diff --git a/sftpd/sftpd.go b/sftpd/sftpd.go index ab2d3b2b..b7e93cc5 100644 --- a/sftpd/sftpd.go +++ b/sftpd/sftpd.go @@ -415,7 +415,7 @@ func isAtomicUploadEnabled() bool { return uploadMode == uploadModeAtomic || uploadMode == uploadModeAtomicWithResume } -func executeNotificationCommand(operation, username, path, target, sshCmd, fileSize string) error { +func executeNotificationCommand(operation, username, path, target, sshCmd, fileSize, isLocalFile string) error { ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() cmd := exec.CommandContext(ctx, actions.Command, operation, username, path, target, sshCmd) @@ -426,6 +426,7 @@ func executeNotificationCommand(operation, username, path, target, sshCmd, fileS fmt.Sprintf("SFTPGO_ACTION_TARGET=%v", target), fmt.Sprintf("SFTPGO_ACTION_SSH_CMD=%v", sshCmd), fmt.Sprintf("SFTPGO_ACTION_FILE_SIZE=%v", fileSize), + fmt.Sprintf("SFTPGO_ACTION_LOCAL_FILE=%v", isLocalFile), ) startTime := time.Now() err := cmd.Run() @@ -435,7 +436,7 @@ func executeNotificationCommand(operation, username, path, target, sshCmd, fileS } // executed in a goroutine -func executeAction(operation, username, path, target, sshCmd string, fileSize int64) error { +func executeAction(operation, username, path, target, sshCmd string, fileSize int64, isLocalFile bool) error { if !utils.IsStringInSlice(operation, actions.ExecuteOn) { return nil } @@ -448,9 +449,9 @@ func executeAction(operation, username, path, target, sshCmd string, fileSize in // we are in a goroutine but if we have to send an HTTP notification we don't want to wait for the // end of the command if len(actions.HTTPNotificationURL) > 0 { - go executeNotificationCommand(operation, username, path, target, sshCmd, size) + go executeNotificationCommand(operation, username, path, target, sshCmd, size, fmt.Sprintf("%t", isLocalFile)) } else { - err = executeNotificationCommand(operation, username, path, target, sshCmd, size) + err = executeNotificationCommand(operation, username, path, target, sshCmd, size, fmt.Sprintf("%t", isLocalFile)) } } if len(actions.HTTPNotificationURL) > 0 { @@ -470,6 +471,7 @@ func executeAction(operation, username, path, target, sshCmd string, fileSize in if len(size) > 0 { q.Add("file_size", size) } + q.Add("local_file", fmt.Sprintf("%t", isLocalFile)) url.RawQuery = q.Encode() startTime := time.Now() httpClient := &http.Client{ diff --git a/sftpd/ssh_cmd.go b/sftpd/ssh_cmd.go index 91e09e83..1cdc05a1 100644 --- a/sftpd/ssh_cmd.go +++ b/sftpd/ssh_cmd.go @@ -402,7 +402,7 @@ func (c *sshCommand) sendExitStatus(err error) { realPath = p } } - go executeAction(operationSSHCmd, c.connection.User.Username, realPath, "", c.command, 0) + go executeAction(operationSSHCmd, c.connection.User.Username, realPath, "", c.command, 0, vfs.IsLocalOsFs(c.connection.fs)) } } diff --git a/sftpd/transfer.go b/sftpd/transfer.go index 8cf46cfa..242319e1 100644 --- a/sftpd/transfer.go +++ b/sftpd/transfer.go @@ -152,10 +152,10 @@ func (t *Transfer) Close() error { elapsed := time.Since(t.start).Nanoseconds() / 1000000 if t.transferType == transferDownload { logger.TransferLog(downloadLogSender, t.path, elapsed, t.bytesSent, t.user.Username, t.connectionID, t.protocol) - go executeAction(operationDownload, t.user.Username, t.path, "", "", t.bytesSent) + go executeAction(operationDownload, t.user.Username, t.path, "", "", t.bytesSent, (t.file != nil)) } else { logger.TransferLog(uploadLogSender, t.path, elapsed, t.bytesReceived, t.user.Username, t.connectionID, t.protocol) - go executeAction(operationUpload, t.user.Username, t.path, "", "", t.bytesReceived+t.minWriteOffset) + go executeAction(operationUpload, t.user.Username, t.path, "", "", t.bytesReceived+t.minWriteOffset, (t.file != nil)) } } else { logger.Warn(logSender, t.connectionID, "transfer error: %v, path: %#v", t.transferError, t.path)