diff --git a/ftpd/ftpd.go b/ftpd/ftpd.go
index 108ed3ff..f61c92e1 100644
--- a/ftpd/ftpd.go
+++ b/ftpd/ftpd.go
@@ -32,6 +32,11 @@ type PassiveIPOverride struct {
parsedNetworks []func(net.IP) bool
}
+// GetNetworksAsString returns the configured networks as string
+func (p *PassiveIPOverride) GetNetworksAsString() string {
+ return strings.Join(p.Networks, ", ")
+}
+
// Binding defines the configuration for a network listener
type Binding struct {
// The address to listen on. A blank value means listen on all available network interfaces.
diff --git a/ftpd/internal_test.go b/ftpd/internal_test.go
index 52923b2f..12b84bc1 100644
--- a/ftpd/internal_test.go
+++ b/ftpd/internal_test.go
@@ -975,6 +975,7 @@ func TestPassiveIPResolver(t *testing.T) {
}
err = b.checkPassiveIP()
assert.NoError(t, err)
+ assert.NotEmpty(t, b.PassiveIPOverrides[0].GetNetworksAsString())
assert.Equal(t, "192.168.1.1", b.PassiveIPOverrides[0].IP)
require.Len(t, b.PassiveIPOverrides[0].parsedNetworks, 1)
ip := net.ParseIP("192.168.1.2")
diff --git a/httpd/httpd.go b/httpd/httpd.go
index bc85a67c..c889b548 100644
--- a/httpd/httpd.go
+++ b/httpd/httpd.go
@@ -634,8 +634,8 @@ func getConfigPath(name, configDir string) string {
return name
}
-func getServicesStatus() ServicesStatus {
- status := ServicesStatus{
+func getServicesStatus() *ServicesStatus {
+ status := &ServicesStatus{
SSH: sftpd.GetStatus(),
FTP: ftpd.GetStatus(),
WebDAV: webdavd.GetStatus(),
diff --git a/httpd/webadmin.go b/httpd/webadmin.go
index 69345f71..6d775afd 100644
--- a/httpd/webadmin.go
+++ b/httpd/webadmin.go
@@ -141,7 +141,7 @@ type connectionsPage struct {
type statusPage struct {
basePage
- Status ServicesStatus
+ Status *ServicesStatus
}
type fsWrapper struct {
diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml
index 43b875b1..e2dc1c9a 100644
--- a/openapi/openapi.yaml
+++ b/openapi/openapi.yaml
@@ -4467,6 +4467,23 @@ components:
- admin
- api_key
- share
+ SSHAuthentications:
+ type: string
+ enum:
+ - publickey
+ - password
+ - keyboard-interactive
+ - publickey+password
+ - publickey+keyboard-interactive
+ TLSVersions:
+ type: integer
+ enum:
+ - 12
+ - 13
+ description: >
+ TLS version:
+ * `12` - TLS 1.2
+ * `13` - TLS 1.3
TOTPConfig:
type: object
properties:
@@ -5373,9 +5390,33 @@ components:
description: the port used for serving requests
enable_https:
type: boolean
+ min_tls_version:
+ $ref: '#/components/schemas/TLSVersions'
client_auth_type:
type: integer
description: 1 means that client certificate authentication is required in addition to HTTP basic authentication
+ tls_cipher_suites:
+ type: array
+ items:
+ type: string
+ description: 'List of supported cipher suites for TLS version 1.2. If empty a default list of secure cipher suites is used, with a preference order based on hardware performance'
+ prefix:
+ type: string
+ description: 'Prefix for WebDAV resources, if empty WebDAV resources will be available at the `/` URI'
+ proxy_allowed:
+ type: array
+ items:
+ type: string
+ description: 'List of IP addresses and IP ranges allowed to set proxy headers'
+ PassiveIPOverride:
+ type: object
+ properties:
+ networks:
+ type: array
+ items:
+ type: string
+ ip:
+ type: string
FTPDBinding:
type: object
properties:
@@ -5399,12 +5440,44 @@ components:
* `0` - clear or explicit TLS
* `1` - explicit TLS required
* `2` - implicit TLS
+ min_tls_version:
+ $ref: '#/components/schemas/TLSVersions'
force_passive_ip:
type: string
description: External IP address to expose for passive connections
+ passive_ip_overrides:
+ type: array
+ items:
+ $ref: '#/components/schemas/PassiveIPOverride'
client_auth_type:
type: integer
description: 1 means that client certificate authentication is required in addition to FTP authentication
+ tls_cipher_suites:
+ type: array
+ items:
+ type: string
+ description: 'List of supported cipher suites for TLS version 1.2. If empty a default list of secure cipher suites is used, with a preference order based on hardware performance'
+ passive_connections_security:
+ type: integer
+ enum:
+ - 0
+ - 1
+ description: |
+ Active connections security:
+ * `0` - require matching peer IP addresses of control and data connection
+ * `1` - disable any checks
+ active_connections_security:
+ type: integer
+ enum:
+ - 0
+ - 1
+ description: |
+ Active connections security:
+ * `0` - require matching peer IP addresses of control and data connection
+ * `1` - disable any checks
+ debug:
+ type: boolean
+ description: 'If enabled any FTP command will be logged'
SSHServiceStatus:
type: object
properties:
@@ -5424,6 +5497,10 @@ components:
type: array
items:
type: string
+ authentications:
+ type: array
+ items:
+ $ref: '#/components/schemas/SSHAuthentications'
FTPPassivePortRange:
type: object
properties:
diff --git a/service/service.go b/service/service.go
index 816514ce..7c0a5f7f 100644
--- a/service/service.go
+++ b/service/service.go
@@ -263,6 +263,11 @@ func (s *Service) loadInitialData() error {
}
info, err := os.Stat(s.LoadDataFrom)
if err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ logger.Warn(logSender, "", "unable to load initial data, the file %#v does not exist", s.LoadDataFrom)
+ logger.WarnToConsole("unable to load initial data, the file %#v does not exist", s.LoadDataFrom)
+ return nil
+ }
return err
}
if info.Size() > httpd.MaxRestoreSize {
diff --git a/sftpd/server.go b/sftpd/server.go
index 621d1074..3323b573 100644
--- a/sftpd/server.go
+++ b/sftpd/server.go
@@ -203,13 +203,30 @@ func (c *Configuration) getServerConfig() *ssh.ServerConfig {
return sp, nil
}
+ serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.LoginMethodPassword)
}
+ serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodPublicKey)
return serverConfig
}
+func (c *Configuration) updateSupportedAuthentications() {
+ serviceStatus.Authentications = util.RemoveDuplicates(serviceStatus.Authentications)
+
+ if util.IsStringInSlice(dataprovider.LoginMethodPassword, serviceStatus.Authentications) &&
+ util.IsStringInSlice(dataprovider.SSHLoginMethodPublicKey, serviceStatus.Authentications) {
+ serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyAndPassword)
+ }
+
+ if util.IsStringInSlice(dataprovider.SSHLoginMethodKeyboardInteractive, serviceStatus.Authentications) &&
+ util.IsStringInSlice(dataprovider.SSHLoginMethodPublicKey, serviceStatus.Authentications) {
+ serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyAndKeyboardInt)
+ }
+}
+
// Initialize the SFTP server and add a persistent listener to handle inbound SFTP connections.
func (c *Configuration) Initialize(configDir string) error {
+ serviceStatus.Authentications = nil
serverConfig := c.getServerConfig()
if !c.ShouldBind() {
@@ -270,6 +287,7 @@ func (c *Configuration) Initialize(configDir string) error {
serviceStatus.IsActive = true
serviceStatus.SSHCommands = c.EnabledSSHCommands
+ c.updateSupportedAuthentications()
return <-exitChannel
}
@@ -381,6 +399,8 @@ func (c *Configuration) configureKeyboardInteractiveAuth(serverConfig *ssh.Serve
return sp, nil
}
+
+ serviceStatus.Authentications = append(serviceStatus.Authentications, dataprovider.SSHLoginMethodKeyboardInteractive)
}
func canAcceptConnection(ip string) bool {
diff --git a/sftpd/sftpd.go b/sftpd/sftpd.go
index ad45f26e..9de41df6 100644
--- a/sftpd/sftpd.go
+++ b/sftpd/sftpd.go
@@ -38,17 +38,23 @@ type HostKey struct {
// ServiceStatus defines the service status
type ServiceStatus struct {
- IsActive bool `json:"is_active"`
- Bindings []Binding `json:"bindings"`
- SSHCommands []string `json:"ssh_commands"`
- HostKeys []HostKey `json:"host_keys"`
+ IsActive bool `json:"is_active"`
+ Bindings []Binding `json:"bindings"`
+ SSHCommands []string `json:"ssh_commands"`
+ HostKeys []HostKey `json:"host_keys"`
+ Authentications []string `json:"authentications"`
}
// GetSSHCommandsAsString returns enabled SSH commands as comma separated string
-func (s ServiceStatus) GetSSHCommandsAsString() string {
+func (s *ServiceStatus) GetSSHCommandsAsString() string {
return strings.Join(s.SSHCommands, ", ")
}
+// GetSupportedAuthsAsString returns the supported authentications as comma separated string
+func (s *ServiceStatus) GetSupportedAuthsAsString() string {
+ return strings.Join(s.Authentications, ", ")
+}
+
// GetStatus returns the server status
func GetStatus() ServiceStatus {
return serviceStatus
diff --git a/sftpd/sftpd_test.go b/sftpd/sftpd_test.go
index 47365bf0..4b61748b 100644
--- a/sftpd/sftpd_test.go
+++ b/sftpd/sftpd_test.go
@@ -445,6 +445,8 @@ func TestBasicSFTPHandling(t *testing.T) {
assert.True(t, status.IsActive)
sshCommands := status.GetSSHCommandsAsString()
assert.NotEmpty(t, sshCommands)
+ sshAuths := status.GetSupportedAuthsAsString()
+ assert.NotEmpty(t, sshAuths)
}
func TestBasicSFTPFsHandling(t *testing.T) {
diff --git a/templates/webadmin/status.html b/templates/webadmin/status.html
index 8667a50e..3b3cdf6a 100644
--- a/templates/webadmin/status.html
+++ b/templates/webadmin/status.html
@@ -21,6 +21,8 @@
Address: "{{.GetAddress}}" {{if .HasProxy}}Proxy: ON{{end}}
{{end}}
+ Accepted authentications: "{{.Status.SSH.GetSupportedAuthsAsString}}"
+
Accepted commands: "{{.Status.SSH.GetSSHCommandsAsString}}"
{{range .Status.SSH.HostKeys}}
@@ -49,9 +51,13 @@
TLS: "{{.GetTLSDescription}}"
{{if .ForcePassiveIP}}
- PassiveIP: {{.ForcePassiveIP}}
+ Passive IP: {{.ForcePassiveIP}}
{{end}}
+ {{range .PassiveIPOverrides}}
+ Passive IP: {{.IP}} for networks: {{.GetNetworksAsString}}
+
+ {{end}}
{{end}}
Passive port range: "{{.Status.FTP.PassivePortRange.Start}}-{{.Status.FTP.PassivePortRange.End}}"
diff --git a/webdavd/webdavd.go b/webdavd/webdavd.go
index 2f16f3e5..4cff64ef 100644
--- a/webdavd/webdavd.go
+++ b/webdavd/webdavd.go
@@ -26,7 +26,6 @@ const (
)
var (
- //server *webDavServer
certMgr *common.CertManager
serviceStatus ServiceStatus
)