Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] add optional sftpd idle timeout (portable mode) #1539

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/portable-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ Flags:
operations to a given path within the
remote SFTP server
--sftp-username string SFTP user for SFTP provider
-I, --sftpd-idle-timeout int shutdown sftpd server if there are no active connections for the given amount of seconds. A value of "-1" disables the automatic shutdown. (default -1)
-s, --sftpd-port int 0 means a random unprivileged port,
< 0 disabled
--ssh-commands strings SSH commands to enable.
Expand Down
49 changes: 49 additions & 0 deletions internal/cmd/portable.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"path"
"path/filepath"
"strings"
"time"

"github.com/sftpgo/sdk"
"github.com/spf13/cobra"
Expand All @@ -40,6 +41,7 @@ import (
var (
directoryToServe string
portableSFTPDPort int
portableSFTPDIdleTimeout int
portableUsername string
portablePassword string
portablePasswordFile string
Expand Down Expand Up @@ -292,6 +294,9 @@ Please take a look at the usage below to customize the serving parameters`,
portableSSHCommands, portableFTPSCert, portableFTPSKey, portableWebDAVCert, portableWebDAVKey,
portableHTTPSCert, portableHTTPSKey)
if err == nil {
if portableSFTPDIdleTimeout >= 0 {
go shutdownSFTPDOnInactivity(&service, portableSFTPDPort, portableSFTPDIdleTimeout)
}
service.Wait()
if service.Error == nil {
os.Exit(0)
Expand All @@ -314,6 +319,7 @@ This is a virtual path not a filesystem
path`)
portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, `0 means a random unprivileged port,
< 0 disabled`)
portableCmd.Flags().IntVarP(&portableSFTPDIdleTimeout, "sftpd-idle-timeout", "I", -1, `shutdown sftpd server if there are no active connections for the given amount of seconds. A value of "-1" disables the automatic shutdown.`)
portableCmd.Flags().IntVar(&portableFTPDPort, "ftpd-port", -1, `0 means a random unprivileged port,
< 0 disabled`)
portableCmd.Flags().IntVar(&portableWebDAVPort, "webdav-port", -1, `0 means a random unprivileged port,
Expand Down Expand Up @@ -524,3 +530,46 @@ func getFileContents(name string) (string, error) {
}
return string(contents), nil
}

func shutdownSFTPDOnInactivity(svc *service.Service, sftpPort, inactiveShutdownSeconds int) {
username := svc.PortableUser.Username
checkDuration := 3 * time.Second
shutdownAfter := time.Now().
Add(checkDuration).
Add(time.Duration(inactiveShutdownSeconds) * time.Second)

ticker := time.NewTicker(checkDuration)
warningLogged := false
current := 0
fmt.Println("automatic inactivity shutdown enabled")
for {
select {
case <-ticker.C:
connections := common.Connections.GetActiveSessions(username)
if current > connections {
fmt.Println(fmt.Sprintf("[%d] connection for user %q closed", sftpPort, username))
} else if current < connections {
fmt.Println(fmt.Sprintf("[%d] new connection for user %q", sftpPort, username))
}
current = connections
if connections > 0 {
shutdownAfter = time.Now().
Add(checkDuration).
Add(time.Duration(inactiveShutdownSeconds) * time.Second)
warningLogged = false
continue
}

if time.Now().After(shutdownAfter) {
fmt.Println("shutdown due to inactivity")
svc.Stop()
return
}

if !warningLogged {
fmt.Println(fmt.Sprintf("there are no active connections. Server will be shutdown after %s if no new connection is established", shutdownAfter))
warningLogged = true
}
}
}
}