diff --git a/go.mod b/go.mod index b40a9910..90cceb9e 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/rs/xid v1.5.0 github.com/rs/zerolog v1.32.0 github.com/sftpgo/sdk v0.1.6-0.20240426175227-52f492b8b83b - github.com/shirou/gopsutil/v3 v3.24.3 + github.com/shirou/gopsutil/v3 v3.24.4 github.com/spf13/afero v1.11.0 github.com/spf13/cobra v1.8.0 github.com/spf13/viper v1.18.2 @@ -73,7 +73,7 @@ require ( golang.org/x/sys v0.19.0 golang.org/x/term v0.19.0 golang.org/x/time v0.5.0 - google.golang.org/api v0.176.1 + google.golang.org/api v0.177.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) diff --git a/go.sum b/go.sum index 717f6067..4e566bb2 100644 --- a/go.sum +++ b/go.sum @@ -353,8 +353,8 @@ github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= github.com/sftpgo/sdk v0.1.6-0.20240426175227-52f492b8b83b h1:BazWPub9GBUKvfM2O6MHhAwd9JbPD1i3UudhmHfGc2w= github.com/sftpgo/sdk v0.1.6-0.20240426175227-52f492b8b83b/go.mod h1:AWoY2YYe/P1ymfTlRER/meERQjCcZZTbgVPGcPQgaqc= -github.com/shirou/gopsutil/v3 v3.24.3 h1:eoUGJSmdfLzJ3mxIhmOAhgKEKgQkeOwKpz1NbhVnuPE= -github.com/shirou/gopsutil/v3 v3.24.3/go.mod h1:JpND7O217xa72ewWz9zN2eIIkPWsDN/3pl0H8Qt0uwg= +github.com/shirou/gopsutil/v3 v3.24.4 h1:dEHgzZXt4LMNm+oYELpzl9YCqV65Yr/6SfrvgRBtXeU= +github.com/shirou/gopsutil/v3 v3.24.4/go.mod h1:lTd2mdiOspcqLgAnr9/nGi71NkeMpWKdmhuxm9GusH8= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= @@ -482,7 +482,6 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -512,8 +511,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= -google.golang.org/api v0.176.1 h1:DJSXnV6An+NhJ1J+GWtoF2nHEuqB1VNoTfnIbjNvwD4= -google.golang.org/api v0.176.1/go.mod h1:j2MaSDYcvYV1lkZ1+SMW4IeF90SrEyFA+tluDYWRrFg= +google.golang.org/api v0.177.0 h1:8a0p/BbPa65GlqGWtUKxot4p0TV8OGOfyTjtmkXNXmk= +google.golang.org/api v0.177.0/go.mod h1:srbhue4MLjkjbkux5p3dw/ocYOSZTaIEvf7bCOnFQDw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= diff --git a/internal/acme/acme.go b/internal/acme/acme.go index 315e2b43..261c7140 100644 --- a/internal/acme/acme.go +++ b/internal/acme/acme.go @@ -489,7 +489,7 @@ func (c *Configuration) setup() (*account, *lego.Client, error) { config := lego.NewConfig(&account) config.CADirURL = c.CAEndpoint config.Certificate.KeyType = certcrypto.KeyType(c.KeyType) - config.UserAgent = fmt.Sprintf("SFTPGo/%v", version.Get().Version) + config.UserAgent = version.GetServerVersion("/", false) client, err := lego.NewClient(config) if err != nil { acmeLog(logger.LevelError, "unable to get ACME client: %v", err) @@ -555,7 +555,7 @@ func (c *Configuration) register(client *lego.Client) (*registration.Resource, e func (c *Configuration) tryRecoverRegistration(privateKey crypto.PrivateKey) (*registration.Resource, error) { config := lego.NewConfig(&account{key: privateKey}) config.CADirURL = c.CAEndpoint - config.UserAgent = fmt.Sprintf("SFTPGo/%v", version.Get().Version) + config.UserAgent = version.GetServerVersion("/", false) client, err := lego.NewClient(config) if err != nil { diff --git a/internal/common/common.go b/internal/common/common.go index b7fb5473..e35316bc 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -42,6 +42,7 @@ import ( "github.com/drakkan/sftpgo/v2/internal/plugin" "github.com/drakkan/sftpgo/v2/internal/smtp" "github.com/drakkan/sftpgo/v2/internal/util" + "github.com/drakkan/sftpgo/v2/internal/version" "github.com/drakkan/sftpgo/v2/internal/vfs" ) @@ -168,6 +169,7 @@ var ( func Initialize(c Configuration, isShared int) error { isShuttingDown.Store(false) util.SetUmask(c.Umask) + version.SetConfig(c.ServerVersion) Config = c Config.Actions.ExecuteOn = util.RemoveDuplicates(Config.Actions.ExecuteOn, true) Config.Actions.ExecuteSync = util.RemoveDuplicates(Config.Actions.ExecuteSync, true) @@ -577,6 +579,8 @@ type Configuration struct { RateLimitersConfig []RateLimiterConfig `json:"rate_limiters" mapstructure:"rate_limiters"` // Umask for new uploads. Leave blank to use the system default. Umask string `json:"umask" mapstructure:"umask"` + // Defines the server version + ServerVersion string `json:"server_version" mapstructure:"server_version"` // Metadata configuration Metadata MetadataConfig `json:"metadata" mapstructure:"metadata"` idleTimeoutAsDuration time.Duration diff --git a/internal/common/common_test.go b/internal/common/common_test.go index d57b0a66..c7d4ba51 100644 --- a/internal/common/common_test.go +++ b/internal/common/common_test.go @@ -38,6 +38,7 @@ import ( "github.com/drakkan/sftpgo/v2/internal/kms" "github.com/drakkan/sftpgo/v2/internal/plugin" "github.com/drakkan/sftpgo/v2/internal/util" + "github.com/drakkan/sftpgo/v2/internal/version" "github.com/drakkan/sftpgo/v2/internal/vfs" ) @@ -1768,6 +1769,21 @@ func TestALPNProtocols(t *testing.T) { assert.Equal(t, []string{"h2", "http/1.1"}, protocols) } +func TestServerVersion(t *testing.T) { + appName := "SFTPGo" + version.SetConfig("") + v := version.GetServerVersion("_", false) + assert.Equal(t, fmt.Sprintf("%s_%s", appName, version.Get().Version), v) + v = version.GetServerVersion("-", true) + assert.Equal(t, fmt.Sprintf("%s-%s-", appName, version.Get().Version), v) + version.SetConfig("short") + v = version.GetServerVersion("_", false) + assert.Equal(t, appName, v) + v = version.GetServerVersion("_", true) + assert.Equal(t, appName+"_", v) + version.SetConfig("") +} + func BenchmarkBcryptHashing(b *testing.B) { bcryptPassword := "bcryptpassword" for i := 0; i < b.N; i++ { diff --git a/internal/config/config.go b/internal/config/config.go index e92bf13a..6207bdd5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -41,7 +41,6 @@ import ( "github.com/drakkan/sftpgo/v2/internal/smtp" "github.com/drakkan/sftpgo/v2/internal/telemetry" "github.com/drakkan/sftpgo/v2/internal/util" - "github.com/drakkan/sftpgo/v2/internal/version" "github.com/drakkan/sftpgo/v2/internal/webdavd" ) @@ -58,7 +57,6 @@ const ( var ( globalConf globalConfig - defaultFTPDBanner = fmt.Sprintf("SFTPGo %v ready", version.Get().Version) defaultInstallCodeHint = "Installation code" defaultSFTPDBinding = sftpd.Binding{ Address: "", @@ -231,6 +229,7 @@ func Init() { }, RateLimitersConfig: []common.RateLimiterConfig{defaultRateLimiter}, Umask: "", + ServerVersion: "", Metadata: common.MetadataConfig{ Read: 0, }, @@ -254,7 +253,6 @@ func Init() { SFTPD: sftpd.Configuration{ Bindings: []sftpd.Binding{defaultSFTPDBinding}, MaxAuthTries: 0, - Banner: "", HostKeys: []string{}, HostCertificates: []string{}, HostKeyAlgorithms: []string{}, @@ -272,7 +270,6 @@ func Init() { }, FTPD: ftpd.Configuration{ Bindings: []ftpd.Binding{defaultFTPDBinding}, - Banner: defaultFTPDBanner, BannerFile: "", ActiveTransfersPortNon20: true, PassivePortRange: ftpd.PortRange{ @@ -757,9 +754,6 @@ func isExternalAuthScopeValid() bool { } func resetInvalidConfigs() { - if strings.TrimSpace(globalConf.FTPD.Banner) == "" { - globalConf.FTPD.Banner = defaultFTPDBanner - } if strings.TrimSpace(globalConf.HTTPDConfig.Setup.InstallationCodeHint) == "" { globalConf.HTTPDConfig.Setup.InstallationCodeHint = defaultInstallCodeHint } @@ -1996,6 +1990,7 @@ func setViperDefaults() { viper.SetDefault("common.defender.entries_soft_limit", globalConf.Common.DefenderConfig.EntriesSoftLimit) viper.SetDefault("common.defender.entries_hard_limit", globalConf.Common.DefenderConfig.EntriesHardLimit) viper.SetDefault("common.umask", globalConf.Common.Umask) + viper.SetDefault("common.server_version", globalConf.Common.ServerVersion) viper.SetDefault("common.metadata.read", globalConf.Common.Metadata.Read) viper.SetDefault("acme.email", globalConf.ACME.Email) viper.SetDefault("acme.key_type", globalConf.ACME.KeyType) @@ -2008,7 +2003,6 @@ func setViperDefaults() { viper.SetDefault("acme.http01_challenge.proxy_header", globalConf.ACME.HTTP01Challenge.ProxyHeader) viper.SetDefault("acme.tls_alpn01_challenge.port", globalConf.ACME.TLSALPN01Challenge.Port) viper.SetDefault("sftpd.max_auth_tries", globalConf.SFTPD.MaxAuthTries) - viper.SetDefault("sftpd.banner", globalConf.SFTPD.Banner) viper.SetDefault("sftpd.host_keys", globalConf.SFTPD.HostKeys) viper.SetDefault("sftpd.host_certificates", globalConf.SFTPD.HostCertificates) viper.SetDefault("sftpd.host_key_algorithms", globalConf.SFTPD.HostKeyAlgorithms) @@ -2023,7 +2017,6 @@ func setViperDefaults() { viper.SetDefault("sftpd.keyboard_interactive_authentication", globalConf.SFTPD.KeyboardInteractiveAuthentication) viper.SetDefault("sftpd.keyboard_interactive_auth_hook", globalConf.SFTPD.KeyboardInteractiveHook) viper.SetDefault("sftpd.password_authentication", globalConf.SFTPD.PasswordAuthentication) - viper.SetDefault("ftpd.banner", globalConf.FTPD.Banner) viper.SetDefault("ftpd.banner_file", globalConf.FTPD.BannerFile) viper.SetDefault("ftpd.active_transfers_port_non_20", globalConf.FTPD.ActiveTransfersPortNon20) viper.SetDefault("ftpd.passive_port_range.start", globalConf.FTPD.PassivePortRange.Start) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 03fd4c1b..b5a29d79 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -19,7 +19,6 @@ import ( "encoding/json" "os" "path/filepath" - "strings" "testing" "github.com/sftpgo/sdk/kms" @@ -31,7 +30,6 @@ import ( "github.com/drakkan/sftpgo/v2/internal/common" "github.com/drakkan/sftpgo/v2/internal/config" "github.com/drakkan/sftpgo/v2/internal/dataprovider" - "github.com/drakkan/sftpgo/v2/internal/ftpd" "github.com/drakkan/sftpgo/v2/internal/httpclient" "github.com/drakkan/sftpgo/v2/internal/httpd" "github.com/drakkan/sftpgo/v2/internal/mfa" @@ -124,42 +122,6 @@ func TestReadEnvFiles(t *testing.T) { os.RemoveAll(envd) } -func TestEmptyBanner(t *testing.T) { - reset() - - confName := tempConfigName + ".json" - configFilePath := filepath.Join(configDir, confName) - err := config.LoadConfig(configDir, "") - assert.NoError(t, err) - sftpdConf := config.GetSFTPDConfig() - sftpdConf.Banner = " " - c := make(map[string]sftpd.Configuration) - c["sftpd"] = sftpdConf - jsonConf, _ := json.Marshal(c) - err = os.WriteFile(configFilePath, jsonConf, os.ModePerm) - assert.NoError(t, err) - err = config.LoadConfig(configDir, confName) - assert.NoError(t, err) - sftpdConf = config.GetSFTPDConfig() - assert.Empty(t, strings.TrimSpace(sftpdConf.Banner)) - err = os.Remove(configFilePath) - assert.NoError(t, err) - - ftpdConf := config.GetFTPDConfig() - ftpdConf.Banner = " " - c1 := make(map[string]ftpd.Configuration) - c1["ftpd"] = ftpdConf - jsonConf, _ = json.Marshal(c1) - err = os.WriteFile(configFilePath, jsonConf, os.ModePerm) - assert.NoError(t, err) - err = config.LoadConfig(configDir, confName) - assert.NoError(t, err) - ftpdConf = config.GetFTPDConfig() - assert.NotEmpty(t, strings.TrimSpace(ftpdConf.Banner)) - err = os.Remove(configFilePath) - assert.NoError(t, err) -} - func TestEnabledSSHCommands(t *testing.T) { reset() diff --git a/internal/ftpd/ftpd.go b/internal/ftpd/ftpd.go index 185ac6aa..3a8cc80b 100644 --- a/internal/ftpd/ftpd.go +++ b/internal/ftpd/ftpd.go @@ -262,10 +262,7 @@ type ServiceStatus struct { type Configuration struct { // Addresses and ports to bind to Bindings []Binding `json:"bindings" mapstructure:"bindings"` - // Greeting banner displayed when a connection first comes in - Banner string `json:"banner" mapstructure:"banner"` - // the contents of the specified file, if any, are diplayed when someone connects to the server. - // If set, it overrides the banner string provided by the banner option + // The contents of the specified file, if any, are diplayed when someone connects to the server. BannerFile string `json:"banner_file" mapstructure:"banner_file"` // If files containing a certificate and matching private key for the server are provided the server will accept // both plain FTP an explicit FTP over TLS. diff --git a/internal/ftpd/internal_test.go b/internal/ftpd/internal_test.go index 027aee1f..646968cc 100644 --- a/internal/ftpd/internal_test.go +++ b/internal/ftpd/internal_test.go @@ -37,6 +37,7 @@ import ( "github.com/drakkan/sftpgo/v2/internal/common" "github.com/drakkan/sftpgo/v2/internal/dataprovider" "github.com/drakkan/sftpgo/v2/internal/util" + "github.com/drakkan/sftpgo/v2/internal/version" "github.com/drakkan/sftpgo/v2/internal/vfs" ) @@ -440,7 +441,7 @@ func TestInitialization(t *testing.T) { c.CertificateKeyFile = "" c.BannerFile = "afile" server := NewServer(c, configDir, binding, 0) - assert.Equal(t, "", server.initialMsg) + assert.Equal(t, version.GetServerVersion("_", false), server.initialMsg) _, err = server.GetTLSConfig() assert.Error(t, err) diff --git a/internal/ftpd/server.go b/internal/ftpd/server.go index adff2631..122dfc33 100644 --- a/internal/ftpd/server.go +++ b/internal/ftpd/server.go @@ -48,10 +48,11 @@ type Server struct { // NewServer returns a new FTP server driver func NewServer(config *Configuration, configDir string, binding Binding, id int) *Server { binding.setCiphers() + vers := version.GetServerVersion("_", false) server := &Server{ config: config, - initialMsg: config.Banner, - statusBanner: fmt.Sprintf("SFTPGo %v FTP Server", version.Get().Version), + initialMsg: vers, + statusBanner: fmt.Sprintf("%s FTP Server", vers), binding: binding, ID: id, } diff --git a/internal/httpd/server.go b/internal/httpd/server.go index d1875dd9..0584344f 100644 --- a/internal/httpd/server.go +++ b/internal/httpd/server.go @@ -1087,6 +1087,7 @@ func (s *httpdServer) updateContextFromCookie(r *http.Request) *http.Request { func (s *httpdServer) parseHeaders(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Server", version.GetServerVersion("/", false)) ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr) var ip net.IP isUnixSocket := filepath.IsAbs(s.binding.Address) diff --git a/internal/httpd/web.go b/internal/httpd/web.go index 9d21e253..d4bc68f5 100644 --- a/internal/httpd/web.go +++ b/internal/httpd/web.go @@ -16,7 +16,6 @@ package httpd import ( "errors" - "fmt" "net/http" "strings" @@ -118,11 +117,10 @@ func hasPrefixAndSuffix(key, prefix, suffix string) bool { } func getCommonBasePage(r *http.Request) commonBasePage { - v := version.Get() return commonBasePage{ CSPNonce: secure.CSPNonce(r.Context()), StaticURL: webStaticFilesPath, - Version: fmt.Sprintf("v%v-%v", v.Version, v.CommitHash), + Version: version.GetServerVersion(" ", true), } } diff --git a/internal/service/service_portable.go b/internal/service/service_portable.go index a59f6edc..06b05f7a 100644 --- a/internal/service/service_portable.go +++ b/internal/service/service_portable.go @@ -32,7 +32,6 @@ import ( "github.com/drakkan/sftpgo/v2/internal/logger" "github.com/drakkan/sftpgo/v2/internal/sftpd" "github.com/drakkan/sftpgo/v2/internal/util" - "github.com/drakkan/sftpgo/v2/internal/version" "github.com/drakkan/sftpgo/v2/internal/webdavd" ) @@ -232,9 +231,6 @@ func configurePortableFTPService(port int, cert, key string) { } else { ftpConf.Bindings[0].Port = 0 } - if ftpConf.Banner == "" { - ftpConf.Banner = fmt.Sprintf("SFTPGo portable %v ready", version.Get().Version) - } ftpConf.Bindings[0].CertificateFile = cert ftpConf.Bindings[0].CertificateKeyFile = key config.SetFTPDConfig(ftpConf) diff --git a/internal/sftpd/server.go b/internal/sftpd/server.go index 2f16dd52..8be4fede 100644 --- a/internal/sftpd/server.go +++ b/internal/sftpd/server.go @@ -107,8 +107,6 @@ func (b *Binding) HasProxy() bool { // Configuration for the SFTP server type Configuration struct { - // Identification string used by the server - Banner string `json:"banner" mapstructure:"banner"` // Addresses and ports to bind to Bindings []Binding `json:"bindings" mapstructure:"bindings"` // Maximum number of authentication attempts permitted per connection. @@ -227,13 +225,6 @@ func (c *Configuration) ShouldBind() bool { return false } -func (c *Configuration) getServerVersion() string { - if c.Banner == "short" { - return "SSH-2.0-SFTPGo" - } - return fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version) -} - func (c *Configuration) getServerConfig() *ssh.ServerConfig { serverConfig := &ssh.ServerConfig{ NoClientAuth: false, @@ -251,7 +242,7 @@ func (c *Configuration) getServerConfig() *ssh.ServerConfig { return sp, nil }, - ServerVersion: c.getServerVersion(), + ServerVersion: fmt.Sprintf("SSH-2.0-%s", version.GetServerVersion("_", false)), } if c.PasswordAuthentication { diff --git a/internal/smtp/smtp.go b/internal/smtp/smtp.go index dbe9a963..308721bb 100644 --- a/internal/smtp/smtp.go +++ b/internal/smtp/smtp.go @@ -323,9 +323,8 @@ func (c *Config) getMailClientOptions() []mail.Option { func (c *Config) getSMTPClientAndMsg(to, bcc []string, subject, body string, contentType EmailContentType, attachments ...*mail.File) (*mail.Client, *mail.Msg, error) { - version := version.Get() msg := mail.NewMsg() - msg.SetUserAgent(fmt.Sprintf("SFTPGo-%s-%s", version.Version, version.CommitHash)) + msg.SetUserAgent(version.GetServerVersion(" ", true)) var from string if c.From != "" { diff --git a/internal/version/version.go b/internal/version/version.go index 5b591e0d..719206cc 100644 --- a/internal/version/version.go +++ b/internal/version/version.go @@ -17,7 +17,10 @@ package version import "strings" -const version = "2.5.99-dev" +const ( + version = "2.5.99-dev" + appName = "SFTPGo" +) var ( commit = "" @@ -25,6 +28,10 @@ var ( info Info ) +var ( + config string +) + // Info defines version details type Info struct { Version string `json:"version"` @@ -69,3 +76,33 @@ func AddFeature(feature string) { func Get() Info { return info } + +// SetConfig sets the version configuration +func SetConfig(val string) { + config = val +} + +// GetServerVersion returns the server version according to the configuration +// and the provided parameters. +func GetServerVersion(separator string, addHash bool) string { + var sb strings.Builder + sb.WriteString(appName) + if config != "short" { + sb.WriteString(separator) + sb.WriteString(info.Version) + } + if addHash { + sb.WriteString(separator) + sb.WriteString(info.CommitHash) + } + return sb.String() +} + +// GetVersionHash returns the server identification string with the commit hash. +func GetVersionHash() string { + var sb strings.Builder + sb.WriteString(appName) + sb.WriteString("-") + sb.WriteString(info.CommitHash) + return sb.String() +} diff --git a/internal/vfs/azblobfs.go b/internal/vfs/azblobfs.go index 85d7597b..067d612c 100644 --- a/internal/vfs/azblobfs.go +++ b/internal/vfs/azblobfs.go @@ -1133,11 +1133,10 @@ func checkDirectoryMarkers(contentType string, metadata map[string]*string) bool } func getAzContainerClientOptions() *container.ClientOptions { - version := version.Get() return &container.ClientOptions{ ClientOptions: azcore.ClientOptions{ Telemetry: policy.TelemetryOptions{ - ApplicationID: fmt.Sprintf("SFTPGo-%s", version.CommitHash), + ApplicationID: version.GetVersionHash(), }, }, } diff --git a/internal/vfs/s3fs.go b/internal/vfs/s3fs.go index c93a8186..97a05272 100644 --- a/internal/vfs/s3fs.go +++ b/internal/vfs/s3fs.go @@ -125,7 +125,7 @@ func NewS3Fs(connectionID, localTempDir, mountPath string, s3Config S3FsConfig) awsConfig.Credentials = creds } fs.svc = s3.NewFromConfig(awsConfig, func(o *s3.Options) { - o.AppID = fmt.Sprintf("SFTPGo-%s", version.Get().CommitHash) + o.AppID = version.GetVersionHash() o.UsePathStyle = fs.config.ForcePathStyle if fs.config.Endpoint != "" { o.BaseEndpoint = aws.String(fs.config.Endpoint) diff --git a/internal/vfs/sftpfs.go b/internal/vfs/sftpfs.go index 2dcbda9e..869c315e 100644 --- a/internal/vfs/sftpfs.go +++ b/internal/vfs/sftpfs.go @@ -977,7 +977,7 @@ func (c *sftpConnection) openConnNoLock() error { return nil }, Timeout: 15 * time.Second, - ClientVersion: fmt.Sprintf("SSH-2.0-SFTPGo_%v", version.Get().Version), + ClientVersion: fmt.Sprintf("SSH-2.0-%s", version.GetServerVersion("_", false)), } signer, err := c.getKeySigner() if err != nil { diff --git a/internal/webdavd/server.go b/internal/webdavd/server.go index 318b5d86..88ba6a08 100644 --- a/internal/webdavd/server.go +++ b/internal/webdavd/server.go @@ -41,6 +41,7 @@ import ( "github.com/drakkan/sftpgo/v2/internal/metric" "github.com/drakkan/sftpgo/v2/internal/plugin" "github.com/drakkan/sftpgo/v2/internal/util" + "github.com/drakkan/sftpgo/v2/internal/version" ) type webDavServer struct { @@ -165,6 +166,7 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { } }() + w.Header().Set("Server", version.GetServerVersion("/", false)) ipAddr := s.checkRemoteAddress(r) common.Connections.AddClientConnection(ipAddr) @@ -194,7 +196,7 @@ func (s *webDavServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { user, isCached, lockSystem, loginMethod, err := s.authenticate(r, ipAddr) if err != nil { if !s.binding.DisableWWWAuthHeader { - w.Header().Set("WWW-Authenticate", "Basic realm=\"SFTPGo WebDAV\"") + w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=\"%s WebDAV\"", version.GetServerVersion("_", false))) } http.Error(w, fmt.Sprintf("Authentication error: %v", err), http.StatusUnauthorized) return diff --git a/sftpgo.json b/sftpgo.json index 41aeb77b..7748c3d3 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -23,6 +23,7 @@ "allowlist_status": 0, "allow_self_connections": 0, "umask": "", + "server_version": "", "metadata": { "read": 0 }, @@ -83,7 +84,6 @@ } ], "max_auth_tries": 0, - "banner": "", "host_keys": [], "host_certificates": [], "host_key_algorithms": [], @@ -129,7 +129,6 @@ "debug": false } ], - "banner": "", "banner_file": "", "active_transfers_port_non_20": true, "passive_port_range": { diff --git a/templates/common/base.html b/templates/common/base.html index 75e88fb0..5ec3a54a 100644 --- a/templates/common/base.html +++ b/templates/common/base.html @@ -881,7 +881,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).