diff --git a/cmd/portable.go b/cmd/portable.go index 27b5559c..c9e69e16 100644 --- a/cmd/portable.go +++ b/cmd/portable.go @@ -206,7 +206,8 @@ func init() { This can be an absolute path or a path relative to the current directory `) - portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, "0 means a random unprivileged port") + portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, `0 means a random unprivileged port, +< 0 disabled`) 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, @@ -237,7 +238,7 @@ The format is: /dir::pattern1,pattern2. For example: "/somedir::*.jpg,a*b?.png"`) portableCmd.Flags().BoolVarP(&portableAdvertiseService, "advertise-service", "S", false, - `Advertise SFTP/FTP service using + `Advertise configured services using multicast DNS`) portableCmd.Flags().BoolVarP(&portableAdvertiseCredentials, "advertise-credentials", "C", false, `If the SFTP/FTP service is diff --git a/common/common.go b/common/common.go index 6cd96e75..1b5f5101 100644 --- a/common/common.go +++ b/common/common.go @@ -582,7 +582,7 @@ type ConnectionStatus struct { Protocol string `json:"protocol"` // active uploads/downloads Transfers []ConnectionTransfer `json:"active_transfers,omitempty"` - // SSH command or WevDAV method + // SSH command or WebDAV method Command string `json:"command,omitempty"` } diff --git a/config/config.go b/config/config.go index c3727eb2..2a73aaaa 100644 --- a/config/config.go +++ b/config/config.go @@ -240,6 +240,21 @@ func GetHTTPConfig() httpclient.Config { return globalConf.HTTPConfig } +// HasServicesToStart returns true if the config defines at least a service to start. +// Supported services are SFTP, FTP and WebDAV +func HasServicesToStart() bool { + if globalConf.SFTPD.BindPort > 0 { + return true + } + if globalConf.FTPD.BindPort > 0 { + return true + } + if globalConf.WebDAVD.BindPort > 0 { + return true + } + return false +} + func getRedactedGlobalConf() globalConfig { conf := globalConf conf.ProviderConf.Password = "[redacted]" diff --git a/config/config_test.go b/config/config_test.go index c8624d7b..a991ae89 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -281,6 +281,33 @@ func TestSetGetConfig(t *testing.T) { assert.Equal(t, webDavConf.CertificateKeyFile, config.GetWebDAVDConfig().CertificateKeyFile) } +func TestServiceToStart(t *testing.T) { + configDir := ".." + err := config.LoadConfig(configDir, configName) + assert.NoError(t, err) + assert.True(t, config.HasServicesToStart()) + sftpdConf := config.GetSFTPDConfig() + sftpdConf.BindPort = 0 + config.SetSFTPDConfig(sftpdConf) + assert.False(t, config.HasServicesToStart()) + ftpdConf := config.GetFTPDConfig() + ftpdConf.BindPort = 2121 + config.SetFTPDConfig(ftpdConf) + assert.True(t, config.HasServicesToStart()) + ftpdConf.BindPort = 0 + config.SetFTPDConfig(ftpdConf) + webdavdConf := config.GetWebDAVDConfig() + webdavdConf.BindPort = 9000 + config.SetWebDAVDConfig(webdavdConf) + assert.True(t, config.HasServicesToStart()) + webdavdConf.BindPort = 0 + config.SetWebDAVDConfig(webdavdConf) + assert.False(t, config.HasServicesToStart()) + sftpdConf.BindPort = 2022 + config.SetSFTPDConfig(sftpdConf) + assert.True(t, config.HasServicesToStart()) +} + func TestConfigFromEnv(t *testing.T) { os.Setenv("SFTPGO_SFTPD__BIND_ADDRESS", "127.0.0.1") os.Setenv("SFTPGO_DATA_PROVIDER__PASSWORD_HASHING__ARGON2_OPTIONS__ITERATIONS", "41") diff --git a/docs/full-configuration.md b/docs/full-configuration.md index 91de40ee..9bf176e9 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -64,7 +64,7 @@ The configuration file contains the following sections: - If `proxy_protocol` is set to 2 and we receive a proxy header from an IP that is not in the list then the connection will be rejected - `post_connect_hook`, string. Absolute path to the command to execute or HTTP URL to notify. See [Post connect hook](./post-connect-hook.md) for more details. Leave empty to disable - **"sftpd"**, the configuration for the SFTP server - - `bind_port`, integer. The port used for serving SFTP requests. Default: 2022 + - `bind_port`, integer. The port used for serving SFTP requests. 0 means disabled. Default: 2022 - `bind_address`, string. Leave blank to listen on all available network interfaces. Default: "" - `idle_timeout`, integer. Deprecated, please use the same key in `common` section. - `max_auth_tries` integer. Maximum number of authentication attempts permitted per connection. If set to a negative number, the number of attempts is unlimited. If set to zero, the number of attempts is limited to 6. diff --git a/docs/portable-mode.md b/docs/portable-mode.md index 259037a0..fe4a9d48 100644 --- a/docs/portable-mode.md +++ b/docs/portable-mode.md @@ -19,7 +19,7 @@ Flags: advertised via multicast DNS, this flag allows to put username/password inside the advertised TXT record - -S, --advertise-service Advertise SFTP/FTP service using + -S, --advertise-service Advertise configured services using multicast DNS --allowed-patterns stringArray Allowed file patterns case insensitive. The format is: @@ -88,7 +88,8 @@ Flags: parallel (default 2) --s3-upload-part-size int The buffer size for multipart uploads (MB) (default 5) - -s, --sftpd-port int 0 means a random unprivileged port + -s, --sftpd-port int 0 means a random unprivileged port, + < 0 disabled -c, --ssh-commands strings SSH commands to enable. "*" means any supported SSH command including scp diff --git a/service/service.go b/service/service.go index 61580903..69b17cf8 100644 --- a/service/service.go +++ b/service/service.go @@ -2,6 +2,7 @@ package service import ( + "errors" "fmt" "io/ioutil" "os" @@ -73,6 +74,12 @@ func (s *Service) Start() error { logger.Error(logSender, "", "error loading configuration: %v", err) } } + if !config.HasServicesToStart() { + infoString := "No service configured, nothing to do" + logger.Info(logSender, "", infoString) + logger.InfoToConsole(infoString) + return errors.New(infoString) + } common.Initialize(config.GetCommonConfig()) @@ -115,15 +122,19 @@ func (s *Service) startServices() { httpdConf := config.GetHTTPDConfig() webDavDConf := config.GetWebDAVDConfig() - go func() { - logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf) - if err := sftpdConf.Initialize(s.ConfigDir); err != nil { - logger.Error(logSender, "", "could not start SFTP server: %v", err) - logger.ErrorToConsole("could not start SFTP server: %v", err) - s.Error = err - } - s.Shutdown <- true - }() + if sftpdConf.BindPort > 0 { + go func() { + logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf) + if err := sftpdConf.Initialize(s.ConfigDir); err != nil { + logger.Error(logSender, "", "could not start SFTP server: %v", err) + logger.ErrorToConsole("could not start SFTP server: %v", err) + s.Error = err + } + s.Shutdown <- true + }() + } else { + logger.Debug(logSender, "", "SFTP server not started, disabled in config file") + } if httpdConf.BindPort > 0 { go func() { @@ -162,7 +173,7 @@ func (s *Service) startServices() { s.Shutdown <- true }() } else { - logger.Debug(logSender, "", "WevDAV server not started, disabled in config file") + logger.Debug(logSender, "", "WebDAV server not started, disabled in config file") } } diff --git a/service/service_portable.go b/service/service_portable.go index da2c718c..e419838b 100644 --- a/service/service_portable.go +++ b/service/service_portable.go @@ -32,21 +32,7 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS if err != nil { fmt.Printf("error loading configuration file: %v using defaults\n", err) } - if len(s.PortableUser.Username) == 0 { - s.PortableUser.Username = "user" - } - printablePassword := "" - if len(s.PortableUser.Password) > 0 { - printablePassword = "[redacted]" - } - if len(s.PortableUser.PublicKeys) == 0 && len(s.PortableUser.Password) == 0 { - var b strings.Builder - for i := 0; i < 8; i++ { - b.WriteRune(chars[rand.Intn(len(chars))]) - } - s.PortableUser.Password = b.String() - printablePassword = s.PortableUser.Password - } + printablePassword := s.configurePortableUser() dataProviderConf := config.GetProviderConf() dataProviderConf.Driver = dataprovider.MemoryDataProviderName dataProviderConf.Name = "" @@ -57,16 +43,19 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS config.SetHTTPDConfig(httpdConf) sftpdConf := config.GetSFTPDConfig() sftpdConf.MaxAuthTries = 12 - if sftpdPort > 0 { - sftpdConf.BindPort = sftpdPort - } else { - // dynamic ports starts from 49152 - sftpdConf.BindPort = 49152 + rand.Intn(15000) - } - if utils.IsStringInSlice("*", enabledSSHCommands) { - sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands() - } else { - sftpdConf.EnabledSSHCommands = enabledSSHCommands + sftpdConf.BindPort = sftpdPort + if sftpdPort >= 0 { + if sftpdPort > 0 { + sftpdConf.BindPort = sftpdPort + } else { + // dynamic ports starts from 49152 + sftpdConf.BindPort = 49152 + rand.Intn(15000) + } + if utils.IsStringInSlice("*", enabledSSHCommands) { + sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands() + } else { + sftpdConf.EnabledSSHCommands = enabledSSHCommands + } } config.SetSFTPDConfig(sftpdConf) @@ -102,8 +91,8 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS s.advertiseServices(advertiseService, advertiseCredentials) - logger.InfoToConsole("Portable mode ready, SFTP port: %v, user: %#v, password: %#v, public keys: %v, directory: %#v, "+ - "permissions: %+v, enabled ssh commands: %v file patterns filters: %+v %v", sftpdConf.BindPort, s.PortableUser.Username, + logger.InfoToConsole("Portable mode ready, user: %#v, password: %#v, public keys: %v, directory: %#v, "+ + "permissions: %+v, enabled ssh commands: %v file patterns filters: %+v %v", s.PortableUser.Username, printablePassword, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions, sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FilePatterns, s.getServiceOptionalInfoString()) return nil @@ -111,15 +100,15 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS func (s *Service) getServiceOptionalInfoString() string { var info strings.Builder + if config.GetSFTPDConfig().BindPort > 0 { + info.WriteString(fmt.Sprintf("SFTP port: %v ", config.GetSFTPDConfig().BindPort)) + } if config.GetFTPDConfig().BindPort > 0 { info.WriteString(fmt.Sprintf("FTP port: %v ", config.GetFTPDConfig().BindPort)) } if config.GetWebDAVDConfig().BindPort > 0 { - if info.Len() == 0 { - info.WriteString(" ") - } scheme := "http" - if len(config.GetWebDAVDConfig().CertificateFile) > 0 && len(config.GetWebDAVDConfig().CertificateKeyFile) > 0 { + if config.GetWebDAVDConfig().CertificateFile != "" && config.GetWebDAVDConfig().CertificateKeyFile != "" { scheme = "https" } info.WriteString(fmt.Sprintf("WebDAV URL: %v://:%v/%v", @@ -230,3 +219,23 @@ func (s *Service) getPortableDirToServe() string { } return dirToServe } + +// configures the portable user and return the printable password if any +func (s *Service) configurePortableUser() string { + if s.PortableUser.Username == "" { + s.PortableUser.Username = "user" + } + printablePassword := "" + if len(s.PortableUser.Password) > 0 { + printablePassword = "[redacted]" + } + if len(s.PortableUser.PublicKeys) == 0 && len(s.PortableUser.Password) == 0 { + var b strings.Builder + for i := 0; i < 8; i++ { + b.WriteRune(chars[rand.Intn(len(chars))]) + } + s.PortableUser.Password = b.String() + printablePassword = s.PortableUser.Password + } + return printablePassword +} diff --git a/webdavd/webdavd.go b/webdavd/webdavd.go index 93867137..4eb71baf 100644 --- a/webdavd/webdavd.go +++ b/webdavd/webdavd.go @@ -73,7 +73,7 @@ type Configuration struct { // Initialize configures and starts the WebDav server func (c *Configuration) Initialize(configDir string) error { var err error - logger.Debug(logSender, "", "initializing WevDav server with config %+v", *c) + logger.Debug(logSender, "", "initializing WebDAV server with config %+v", *c) mimeTypeCache = mimeCache{ maxSize: c.Cache.MimeTypes.MaxSize, mimeTypes: make(map[string]string),