allow to disable SFTP service

Fixes #228
This commit is contained in:
Nicola Murino 2020-11-24 13:44:57 +01:00
parent 99cd1ccfe5
commit 0609188d3f
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
9 changed files with 112 additions and 48 deletions

View file

@ -206,7 +206,8 @@ func init() {
This can be an absolute path or a path This can be an absolute path or a path
relative to the current directory 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, portableCmd.Flags().IntVar(&portableFTPDPort, "ftpd-port", -1, `0 means a random unprivileged port,
< 0 disabled`) < 0 disabled`)
portableCmd.Flags().IntVar(&portableWebDAVPort, "webdav-port", -1, `0 means a random unprivileged port, portableCmd.Flags().IntVar(&portableWebDAVPort, "webdav-port", -1, `0 means a random unprivileged port,
@ -237,7 +238,7 @@ The format is:
/dir::pattern1,pattern2. /dir::pattern1,pattern2.
For example: "/somedir::*.jpg,a*b?.png"`) For example: "/somedir::*.jpg,a*b?.png"`)
portableCmd.Flags().BoolVarP(&portableAdvertiseService, "advertise-service", "S", false, portableCmd.Flags().BoolVarP(&portableAdvertiseService, "advertise-service", "S", false,
`Advertise SFTP/FTP service using `Advertise configured services using
multicast DNS`) multicast DNS`)
portableCmd.Flags().BoolVarP(&portableAdvertiseCredentials, "advertise-credentials", "C", false, portableCmd.Flags().BoolVarP(&portableAdvertiseCredentials, "advertise-credentials", "C", false,
`If the SFTP/FTP service is `If the SFTP/FTP service is

View file

@ -582,7 +582,7 @@ type ConnectionStatus struct {
Protocol string `json:"protocol"` Protocol string `json:"protocol"`
// active uploads/downloads // active uploads/downloads
Transfers []ConnectionTransfer `json:"active_transfers,omitempty"` Transfers []ConnectionTransfer `json:"active_transfers,omitempty"`
// SSH command or WevDAV method // SSH command or WebDAV method
Command string `json:"command,omitempty"` Command string `json:"command,omitempty"`
} }

View file

@ -240,6 +240,21 @@ func GetHTTPConfig() httpclient.Config {
return globalConf.HTTPConfig 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 { func getRedactedGlobalConf() globalConfig {
conf := globalConf conf := globalConf
conf.ProviderConf.Password = "[redacted]" conf.ProviderConf.Password = "[redacted]"

View file

@ -281,6 +281,33 @@ func TestSetGetConfig(t *testing.T) {
assert.Equal(t, webDavConf.CertificateKeyFile, config.GetWebDAVDConfig().CertificateKeyFile) 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) { func TestConfigFromEnv(t *testing.T) {
os.Setenv("SFTPGO_SFTPD__BIND_ADDRESS", "127.0.0.1") os.Setenv("SFTPGO_SFTPD__BIND_ADDRESS", "127.0.0.1")
os.Setenv("SFTPGO_DATA_PROVIDER__PASSWORD_HASHING__ARGON2_OPTIONS__ITERATIONS", "41") os.Setenv("SFTPGO_DATA_PROVIDER__PASSWORD_HASHING__ARGON2_OPTIONS__ITERATIONS", "41")

View file

@ -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 - 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 - `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 - **"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: "" - `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. - `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. - `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.

View file

@ -19,7 +19,7 @@ Flags:
advertised via multicast DNS, this advertised via multicast DNS, this
flag allows to put username/password flag allows to put username/password
inside the advertised TXT record inside the advertised TXT record
-S, --advertise-service Advertise SFTP/FTP service using -S, --advertise-service Advertise configured services using
multicast DNS multicast DNS
--allowed-patterns stringArray Allowed file patterns case insensitive. --allowed-patterns stringArray Allowed file patterns case insensitive.
The format is: The format is:
@ -88,7 +88,8 @@ Flags:
parallel (default 2) parallel (default 2)
--s3-upload-part-size int The buffer size for multipart uploads --s3-upload-part-size int The buffer size for multipart uploads
(MB) (default 5) (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. -c, --ssh-commands strings SSH commands to enable.
"*" means any supported SSH command "*" means any supported SSH command
including scp including scp

View file

@ -2,6 +2,7 @@
package service package service
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@ -73,6 +74,12 @@ func (s *Service) Start() error {
logger.Error(logSender, "", "error loading configuration: %v", err) 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()) common.Initialize(config.GetCommonConfig())
@ -115,15 +122,19 @@ func (s *Service) startServices() {
httpdConf := config.GetHTTPDConfig() httpdConf := config.GetHTTPDConfig()
webDavDConf := config.GetWebDAVDConfig() webDavDConf := config.GetWebDAVDConfig()
go func() { if sftpdConf.BindPort > 0 {
logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf) go func() {
if err := sftpdConf.Initialize(s.ConfigDir); err != nil { logger.Debug(logSender, "", "initializing SFTP server with config %+v", sftpdConf)
logger.Error(logSender, "", "could not start SFTP server: %v", err) if err := sftpdConf.Initialize(s.ConfigDir); err != nil {
logger.ErrorToConsole("could not start SFTP server: %v", err) logger.Error(logSender, "", "could not start SFTP server: %v", err)
s.Error = err logger.ErrorToConsole("could not start SFTP server: %v", err)
} s.Error = err
s.Shutdown <- true }
}() s.Shutdown <- true
}()
} else {
logger.Debug(logSender, "", "SFTP server not started, disabled in config file")
}
if httpdConf.BindPort > 0 { if httpdConf.BindPort > 0 {
go func() { go func() {
@ -162,7 +173,7 @@ func (s *Service) startServices() {
s.Shutdown <- true s.Shutdown <- true
}() }()
} else { } else {
logger.Debug(logSender, "", "WevDAV server not started, disabled in config file") logger.Debug(logSender, "", "WebDAV server not started, disabled in config file")
} }
} }

View file

@ -32,21 +32,7 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
if err != nil { if err != nil {
fmt.Printf("error loading configuration file: %v using defaults\n", err) fmt.Printf("error loading configuration file: %v using defaults\n", err)
} }
if len(s.PortableUser.Username) == 0 { printablePassword := s.configurePortableUser()
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
}
dataProviderConf := config.GetProviderConf() dataProviderConf := config.GetProviderConf()
dataProviderConf.Driver = dataprovider.MemoryDataProviderName dataProviderConf.Driver = dataprovider.MemoryDataProviderName
dataProviderConf.Name = "" dataProviderConf.Name = ""
@ -57,16 +43,19 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
config.SetHTTPDConfig(httpdConf) config.SetHTTPDConfig(httpdConf)
sftpdConf := config.GetSFTPDConfig() sftpdConf := config.GetSFTPDConfig()
sftpdConf.MaxAuthTries = 12 sftpdConf.MaxAuthTries = 12
if sftpdPort > 0 { sftpdConf.BindPort = sftpdPort
sftpdConf.BindPort = sftpdPort if sftpdPort >= 0 {
} else { if sftpdPort > 0 {
// dynamic ports starts from 49152 sftpdConf.BindPort = sftpdPort
sftpdConf.BindPort = 49152 + rand.Intn(15000) } else {
} // dynamic ports starts from 49152
if utils.IsStringInSlice("*", enabledSSHCommands) { sftpdConf.BindPort = 49152 + rand.Intn(15000)
sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands() }
} else { if utils.IsStringInSlice("*", enabledSSHCommands) {
sftpdConf.EnabledSSHCommands = enabledSSHCommands sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands()
} else {
sftpdConf.EnabledSSHCommands = enabledSSHCommands
}
} }
config.SetSFTPDConfig(sftpdConf) config.SetSFTPDConfig(sftpdConf)
@ -102,8 +91,8 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
s.advertiseServices(advertiseService, advertiseCredentials) s.advertiseServices(advertiseService, advertiseCredentials)
logger.InfoToConsole("Portable mode ready, SFTP port: %v, user: %#v, password: %#v, public keys: %v, directory: %#v, "+ 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", sftpdConf.BindPort, s.PortableUser.Username, "permissions: %+v, enabled ssh commands: %v file patterns filters: %+v %v", s.PortableUser.Username,
printablePassword, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions, printablePassword, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions,
sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FilePatterns, s.getServiceOptionalInfoString()) sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FilePatterns, s.getServiceOptionalInfoString())
return nil return nil
@ -111,15 +100,15 @@ func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledS
func (s *Service) getServiceOptionalInfoString() string { func (s *Service) getServiceOptionalInfoString() string {
var info strings.Builder var info strings.Builder
if config.GetSFTPDConfig().BindPort > 0 {
info.WriteString(fmt.Sprintf("SFTP port: %v ", config.GetSFTPDConfig().BindPort))
}
if config.GetFTPDConfig().BindPort > 0 { if config.GetFTPDConfig().BindPort > 0 {
info.WriteString(fmt.Sprintf("FTP port: %v ", config.GetFTPDConfig().BindPort)) info.WriteString(fmt.Sprintf("FTP port: %v ", config.GetFTPDConfig().BindPort))
} }
if config.GetWebDAVDConfig().BindPort > 0 { if config.GetWebDAVDConfig().BindPort > 0 {
if info.Len() == 0 {
info.WriteString(" ")
}
scheme := "http" scheme := "http"
if len(config.GetWebDAVDConfig().CertificateFile) > 0 && len(config.GetWebDAVDConfig().CertificateKeyFile) > 0 { if config.GetWebDAVDConfig().CertificateFile != "" && config.GetWebDAVDConfig().CertificateKeyFile != "" {
scheme = "https" scheme = "https"
} }
info.WriteString(fmt.Sprintf("WebDAV URL: %v://<your IP>:%v/%v", info.WriteString(fmt.Sprintf("WebDAV URL: %v://<your IP>:%v/%v",
@ -230,3 +219,23 @@ func (s *Service) getPortableDirToServe() string {
} }
return dirToServe 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
}

View file

@ -73,7 +73,7 @@ type Configuration struct {
// Initialize configures and starts the WebDav server // Initialize configures and starts the WebDav server
func (c *Configuration) Initialize(configDir string) error { func (c *Configuration) Initialize(configDir string) error {
var err 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{ mimeTypeCache = mimeCache{
maxSize: c.Cache.MimeTypes.MaxSize, maxSize: c.Cache.MimeTypes.MaxSize,
mimeTypes: make(map[string]string), mimeTypes: make(map[string]string),