mirror of
https://github.com/drakkan/sftpgo.git
synced 2024-11-25 00:50:31 +00:00
add SMTP support
it will be used in future update to add email sending capabilities
This commit is contained in:
parent
0661876e99
commit
da0ccc6426
11 changed files with 302 additions and 12 deletions
|
@ -71,6 +71,7 @@ var (
|
|||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||
rootCmd.Flags().BoolP("version", "v", false, "")
|
||||
rootCmd.Version = version.GetAsString()
|
||||
rootCmd.SetVersionTemplate(`{{printf "SFTPGo "}}{{printf "%s" .Version}}
|
||||
|
|
54
cmd/smtptest.go
Normal file
54
cmd/smtptest.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/config"
|
||||
"github.com/drakkan/sftpgo/v2/logger"
|
||||
"github.com/drakkan/sftpgo/v2/smtp"
|
||||
"github.com/drakkan/sftpgo/v2/util"
|
||||
)
|
||||
|
||||
var (
|
||||
smtpTestRecipient string
|
||||
smtpTestCmd = &cobra.Command{
|
||||
Use: "smtptest",
|
||||
Short: "Test the SMTP configuration",
|
||||
Long: `SFTPGo will try to send a test email to the specified recipient.
|
||||
If the SMTP configuration is correct you should receive this email.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
logger.DisableLogger()
|
||||
logger.EnableConsoleLogger(zerolog.DebugLevel)
|
||||
configDir = util.CleanDirInput(configDir)
|
||||
err := config.LoadConfig(configDir, configFile)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("Unable to initialize data provider, config load error: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
smtpConfig := config.GetSMTPConfig()
|
||||
err = smtpConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.ErrorToConsole("unable to initialize SMTP configuration: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
err = smtp.SendEmail(smtpTestRecipient, "SFTPGo - Testing Email Settings", "It appears your SFTPGo email is setup correctly!",
|
||||
smtp.EmailContentTypeTextPlain)
|
||||
if err != nil {
|
||||
logger.WarnToConsole("Error sending email: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
logger.InfoToConsole("No errors were reported while sending an email. Please check your inbox to make sure.")
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
addConfigFlags(smtpTestCmd)
|
||||
smtpTestCmd.Flags().StringVar(&smtpTestRecipient, "recipient", "", `email address to send the test e-mail to`)
|
||||
smtpTestCmd.MarkFlagRequired("recipient") //nolint:errcheck
|
||||
|
||||
rootCmd.AddCommand(smtpTestCmd)
|
||||
}
|
|
@ -86,6 +86,12 @@ Command-line flags should be specified in the Subsystem declaration.
|
|||
logger.Error(logSender, connectionID, "unable to initialize plugin system: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
smtpConfig := config.GetSMTPConfig()
|
||||
err = smtpConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.Error(logSender, connectionID, "unable to initialize SMTP configuration: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
dataProviderConf := config.GetProviderConf()
|
||||
if dataProviderConf.Driver == dataprovider.SQLiteDataProviderName || dataProviderConf.Driver == dataprovider.BoltDataProviderName {
|
||||
logger.Debug(logSender, connectionID, "data provider %#v not supported in subsystem mode, using %#v provider",
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
"github.com/drakkan/sftpgo/v2/mfa"
|
||||
"github.com/drakkan/sftpgo/v2/sdk/plugin"
|
||||
"github.com/drakkan/sftpgo/v2/sftpd"
|
||||
"github.com/drakkan/sftpgo/v2/smtp"
|
||||
"github.com/drakkan/sftpgo/v2/telemetry"
|
||||
"github.com/drakkan/sftpgo/v2/util"
|
||||
"github.com/drakkan/sftpgo/v2/version"
|
||||
|
@ -107,6 +108,7 @@ type globalConfig struct {
|
|||
MFAConfig mfa.Config `json:"mfa" mapstructure:"mfa"`
|
||||
TelemetryConfig telemetry.Conf `json:"telemetry" mapstructure:"telemetry"`
|
||||
PluginsConfig []plugin.Config `json:"plugins" mapstructure:"plugins"`
|
||||
SMTPConfig smtp.Config `json:"smtp" mapstructure:"smtp"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -305,6 +307,16 @@ func Init() {
|
|||
TLSCipherSuites: nil,
|
||||
},
|
||||
PluginsConfig: nil,
|
||||
SMTPConfig: smtp.Config{
|
||||
Host: "",
|
||||
Port: 25,
|
||||
From: "",
|
||||
User: "",
|
||||
Password: "",
|
||||
AuthType: 0,
|
||||
Encryption: 0,
|
||||
Domain: "",
|
||||
},
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix(configEnvPrefix)
|
||||
|
@ -411,6 +423,11 @@ func GetMFAConfig() mfa.Config {
|
|||
return globalConf.MFAConfig
|
||||
}
|
||||
|
||||
// GetSMTPConfig returns the SMTP configuration
|
||||
func GetSMTPConfig() smtp.Config {
|
||||
return globalConf.SMTPConfig
|
||||
}
|
||||
|
||||
// HasServicesToStart returns true if the config defines at least a service to start.
|
||||
// Supported services are SFTP, FTP and WebDAV
|
||||
func HasServicesToStart() bool {
|
||||
|
@ -426,19 +443,24 @@ func HasServicesToStart() bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func getRedactedPassword() string {
|
||||
return "[redacted]"
|
||||
}
|
||||
|
||||
func getRedactedGlobalConf() globalConfig {
|
||||
conf := globalConf
|
||||
conf.Common.Actions.Hook = util.GetRedactedURL(conf.Common.Actions.Hook)
|
||||
conf.Common.StartupHook = util.GetRedactedURL(conf.Common.StartupHook)
|
||||
conf.Common.PostConnectHook = util.GetRedactedURL(conf.Common.PostConnectHook)
|
||||
conf.SFTPD.KeyboardInteractiveHook = util.GetRedactedURL(conf.SFTPD.KeyboardInteractiveHook)
|
||||
conf.HTTPDConfig.SigningPassphrase = "[redacted]"
|
||||
conf.ProviderConf.Password = "[redacted]"
|
||||
conf.HTTPDConfig.SigningPassphrase = getRedactedPassword()
|
||||
conf.ProviderConf.Password = getRedactedPassword()
|
||||
conf.ProviderConf.Actions.Hook = util.GetRedactedURL(conf.ProviderConf.Actions.Hook)
|
||||
conf.ProviderConf.ExternalAuthHook = util.GetRedactedURL(conf.ProviderConf.ExternalAuthHook)
|
||||
conf.ProviderConf.PreLoginHook = util.GetRedactedURL(conf.ProviderConf.PreLoginHook)
|
||||
conf.ProviderConf.PostLoginHook = util.GetRedactedURL(conf.ProviderConf.PostLoginHook)
|
||||
conf.ProviderConf.CheckPasswordHook = util.GetRedactedURL(conf.ProviderConf.CheckPasswordHook)
|
||||
conf.SMTPConfig.Password = getRedactedPassword()
|
||||
return conf
|
||||
}
|
||||
|
||||
|
@ -1146,6 +1168,14 @@ func setViperDefaults() {
|
|||
viper.SetDefault("telemetry.certificate_file", globalConf.TelemetryConfig.CertificateFile)
|
||||
viper.SetDefault("telemetry.certificate_key_file", globalConf.TelemetryConfig.CertificateKeyFile)
|
||||
viper.SetDefault("telemetry.tls_cipher_suites", globalConf.TelemetryConfig.TLSCipherSuites)
|
||||
viper.SetDefault("smtp.host", globalConf.SMTPConfig.Host)
|
||||
viper.SetDefault("smtp.port", globalConf.SMTPConfig.Port)
|
||||
viper.SetDefault("smtp.from", globalConf.SMTPConfig.From)
|
||||
viper.SetDefault("smtp.user", globalConf.SMTPConfig.User)
|
||||
viper.SetDefault("smtp.password", globalConf.SMTPConfig.Password)
|
||||
viper.SetDefault("smtp.auth_type", globalConf.SMTPConfig.AuthType)
|
||||
viper.SetDefault("smtp.encryption", globalConf.SMTPConfig.Encryption)
|
||||
viper.SetDefault("smtp.domain", globalConf.SMTPConfig.Domain)
|
||||
}
|
||||
|
||||
func lookupBoolFromEnv(envName string) (bool, bool) {
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/drakkan/sftpgo/v2/kms"
|
||||
"github.com/drakkan/sftpgo/v2/mfa"
|
||||
"github.com/drakkan/sftpgo/v2/sftpd"
|
||||
"github.com/drakkan/sftpgo/v2/smtp"
|
||||
"github.com/drakkan/sftpgo/v2/util"
|
||||
)
|
||||
|
||||
|
@ -42,6 +43,7 @@ func TestLoadConfigTest(t *testing.T) {
|
|||
assert.NotEqual(t, dataprovider.Config{}, config.GetProviderConf())
|
||||
assert.NotEqual(t, sftpd.Configuration{}, config.GetSFTPDConfig())
|
||||
assert.NotEqual(t, httpclient.Config{}, config.GetHTTPConfig())
|
||||
assert.NotEqual(t, smtp.Config{}, config.GetSMTPConfig())
|
||||
confName := tempConfigName + ".json"
|
||||
configFilePath := filepath.Join(configDir, confName)
|
||||
err = config.LoadConfig(configDir, confName)
|
||||
|
@ -340,6 +342,24 @@ func TestSSHCommandsFromEnv(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSMTPFromEnv(t *testing.T) {
|
||||
reset()
|
||||
|
||||
os.Setenv("SFTPGO_SMTP__HOST", "smtp.example.com")
|
||||
os.Setenv("SFTPGO_SMTP__PORT", "587")
|
||||
t.Cleanup(func() {
|
||||
os.Unsetenv("SFTPGO_SMTP__HOST")
|
||||
os.Unsetenv("SFTPGO_SMTP__PORT")
|
||||
})
|
||||
|
||||
configDir := ".."
|
||||
err := config.LoadConfig(configDir, "")
|
||||
assert.NoError(t, err)
|
||||
smtpConfig := config.GetSMTPConfig()
|
||||
assert.Equal(t, "smtp.example.com", smtpConfig.Host)
|
||||
assert.Equal(t, 587, smtpConfig.Port)
|
||||
}
|
||||
|
||||
func TestMFAFromEnv(t *testing.T) {
|
||||
reset()
|
||||
|
||||
|
|
|
@ -9,11 +9,14 @@ Usage:
|
|||
sftpgo [command]
|
||||
|
||||
Available Commands:
|
||||
gen A collection of useful generators
|
||||
help Help about any command
|
||||
initprovider Initializes and/or updates the configured data provider
|
||||
portable Serve a single directory
|
||||
serve Start the SFTP Server
|
||||
gen A collection of useful generators
|
||||
help Help about any command
|
||||
initprovider Initializes and/or updates the configured data provider
|
||||
portable Serve a single directory
|
||||
revertprovider Revert the configured data provider to a previous version
|
||||
serve Start the SFTPGo service
|
||||
smtptest Test the SMTP configuration
|
||||
startsubsys Use SFTPGo as SFTP file transfer subsystem
|
||||
|
||||
Flags:
|
||||
-h, --help help for sftpgo
|
||||
|
@ -257,6 +260,15 @@ The configuration file contains the following sections:
|
|||
- `name`, string. Unique configuration name. This name should not be changed if there are users or admins using the configuration. The name is not exposed to the authentication apps. Default: `Default`.
|
||||
- `issuer`, string. Name of the issuing Organization/Company. Default: `SFTPGo`.
|
||||
- `algo`, string. Algorithm to use for HMAC. The supported algorithms are: `sha1`, `sha256`, `sha512`. Currently Google Authenticator app on iPhone seems to only support `sha1`, please check the compatibility with your target apps/device before setting a different algorithm. You can also define multiple configurations, for example one that uses `sha256` or `sha512` and another one that uses `sha1` and instruct your users to use the appropriate configuration for their devices/apps. The algorithm should not be changed if there are users or admins using the configuration. Default: `sha1`.
|
||||
- **smtp**, SMTP configuration enables SFTPGo email sending capabilities
|
||||
- `host`, string. Location of SMTP email server. Leavy empty to disable email sending capabilities. Default: empty.
|
||||
- `port`, integer. Port of SMTP email server.
|
||||
- `from`, string. From address, for example `SFTPGo <sftpgo@example.com>`. Default: empty
|
||||
- `user`, string. SMTP username. Default: empty
|
||||
- `password`, string. SMTP password. Leaving both username and password empty the SMTP authentication will be disabled. Default: empty
|
||||
- `auth_type`, integer. 0 means `Plain`, 1 means `Login`, 2 means `CRAM-MD5`. Default: `0`.
|
||||
- `encryption`, integer. 0 means no encryption, 1 means `TLS`, 2 means `STARTTLS`. Default: `0`.
|
||||
- `domain`, string. Domain to use for `HELO` command, if empty `localhost` will be used. Default: empty.
|
||||
- **plugins**, list of external plugins. Each plugin is configured using a struct with the following fields:
|
||||
- `type`, string. Defines the plugin type. Supported types: `notifier`, `kms`, `auth`.
|
||||
- `notifier_options`, struct. Defines the options for notifier plugins.
|
||||
|
|
5
go.mod
5
go.mod
|
@ -29,7 +29,7 @@ require (
|
|||
github.com/klauspost/compress v1.13.6
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||
github.com/lestrrat-go/jwx v1.2.6
|
||||
github.com/lestrrat-go/jwx v1.2.7
|
||||
github.com/lib/pq v1.10.3
|
||||
github.com/lithammer/shortuuid/v3 v3.0.7
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
|
@ -43,7 +43,7 @@ require (
|
|||
github.com/pkg/sftp v1.13.3
|
||||
github.com/pquerna/otp v1.3.0
|
||||
github.com/prometheus/client_golang v1.11.0
|
||||
github.com/prometheus/common v0.30.0 // indirect
|
||||
github.com/prometheus/common v0.31.0 // indirect
|
||||
github.com/rs/cors v1.8.0
|
||||
github.com/rs/xid v1.3.0
|
||||
github.com/rs/zerolog v1.25.0
|
||||
|
@ -55,6 +55,7 @@ require (
|
|||
github.com/stretchr/testify v1.7.0
|
||||
github.com/studio-b12/gowebdav v0.0.0-20210917133250-a3a86976a1df
|
||||
github.com/wagslane/go-password-validator v0.3.0
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0
|
||||
github.com/yl2chen/cidranger v1.0.2
|
||||
go.etcd.io/bbolt v1.3.6
|
||||
go.uber.org/automaxprocs v1.4.0
|
||||
|
|
10
go.sum
10
go.sum
|
@ -544,12 +544,14 @@ github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBB
|
|||
github.com/lestrrat-go/blackmagic v1.0.0 h1:XzdxDbuQTz0RZZEmdU7cnQxUtFUzgCSPq8RCz4BxIi4=
|
||||
github.com/lestrrat-go/blackmagic v1.0.0/go.mod h1:TNgH//0vYSs8VXDCfkZLgIrVTTXQELZffUV0tz3MtdQ=
|
||||
github.com/lestrrat-go/codegen v1.0.1/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
|
||||
github.com/lestrrat-go/codegen v1.0.2/go.mod h1:JhJw6OQAuPEfVKUCLItpaVLumDGWQznd1VaXrBk9TdM=
|
||||
github.com/lestrrat-go/httpcc v1.0.0 h1:FszVC6cKfDvBKcJv646+lkh4GydQg2Z29scgUfkOpYc=
|
||||
github.com/lestrrat-go/httpcc v1.0.0/go.mod h1:tGS/u00Vh5N6FHNkExqGGNId8e0Big+++0Gf8MBnAvE=
|
||||
github.com/lestrrat-go/iter v1.0.1 h1:q8faalr2dY6o8bV45uwrxq12bRa1ezKrB6oM9FUgN4A=
|
||||
github.com/lestrrat-go/iter v1.0.1/go.mod h1:zIdgO1mRKhn8l9vrZJZz9TUMMFbQbLeTsbqPDrJ/OJc=
|
||||
github.com/lestrrat-go/jwx v1.2.6 h1:XAgfuHaOB7fDZ/6WhVgl8K89af768dU+3Nx4DlTbLIk=
|
||||
github.com/lestrrat-go/jwx v1.2.6/go.mod h1:tJuGuAI3LC71IicTx82Mz1n3w9woAs2bYJZpkjJQ5aU=
|
||||
github.com/lestrrat-go/jwx v1.2.7 h1:wO7fEc3PW56wpQBMU5CyRkrk4DVsXxCoJg7oIm5HHE4=
|
||||
github.com/lestrrat-go/jwx v1.2.7/go.mod h1:bw24IXWbavc0R2RsOtpXL7RtMyP589yZ1+L7kd09ZGA=
|
||||
github.com/lestrrat-go/option v1.0.0 h1:WqAWL8kh8VcSoD6xjSH34/1m8yxluXQbDeKNfvFeEO4=
|
||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
|
@ -679,8 +681,8 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
|
|||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/common v0.30.0 h1:JEkYlQnpzrzQFxi6gnukFPdQ+ac82oRhzMcIduJu/Ug=
|
||||
github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/common v0.31.0 h1:FTJdLTjtrh4dXlCjpzdZJXMnejSTL5F/nVQm5sNwD34=
|
||||
github.com/prometheus/common v0.31.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
|
@ -762,6 +764,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
|||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/wagslane/go-password-validator v0.3.0 h1:vfxOPzGHkz5S146HDpavl0cw1DSVP061Ry2PX0/ON6I=
|
||||
github.com/wagslane/go-password-validator v0.3.0/go.mod h1:TI1XJ6T5fRdRnHqHt14pvy1tNVnrwe7m3/f1f2fDphQ=
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0 h1:nib6RaJ4qVh5HD9UE9QJqnUZyWp3upv+Z6CFxaMj0V8=
|
||||
github.com/xhit/go-simple-mail/v2 v2.10.0/go.mod h1:kA1XbQfCI4JxQ9ccSN6VFyIEkkugOm7YiPkA5hKiQn4=
|
||||
github.com/yl2chen/cidranger v1.0.2 h1:lbOWZVCG1tCRX4u24kuM1Tb4nHqWkDxwLdoS+SevawU=
|
||||
github.com/yl2chen/cidranger v1.0.2/go.mod h1:9U1yz7WPYDwf0vpNWFaeRh0bjwz5RVgRy/9UEQfHl0g=
|
||||
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
|
|
|
@ -110,6 +110,13 @@ func (s *Service) Start() error {
|
|||
logger.ErrorToConsole("unable to initialize plugin system: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
smtpConfig := config.GetSMTPConfig()
|
||||
err = smtpConfig.Initialize()
|
||||
if err != nil {
|
||||
logger.Error(logSender, "", "unable to initialize SMTP configuration: %v", err)
|
||||
logger.ErrorToConsole("unable to initialize SMTP configuration: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
providerConf := config.GetProviderConf()
|
||||
|
||||
|
|
10
sftpgo.json
10
sftpgo.json
|
@ -254,5 +254,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"smtp": {
|
||||
"host": "",
|
||||
"port": 25,
|
||||
"from": "",
|
||||
"user": "",
|
||||
"password": "",
|
||||
"auth_type": 0,
|
||||
"encryption": 0,
|
||||
"domain": ""
|
||||
},
|
||||
"plugins": []
|
||||
}
|
145
smtp/smtp.go
Normal file
145
smtp/smtp.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
// Package smtp provides supports for sending emails
|
||||
package smtp
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
mail "github.com/xhit/go-simple-mail/v2"
|
||||
|
||||
"github.com/drakkan/sftpgo/v2/logger"
|
||||
)
|
||||
|
||||
const (
|
||||
logSender = "smtp"
|
||||
)
|
||||
|
||||
// EmailContentType defines the support content types for email body
|
||||
type EmailContentType int
|
||||
|
||||
// Supporte email body content type
|
||||
const (
|
||||
EmailContentTypeTextPlain EmailContentType = iota
|
||||
EmailContentTypeTextHTML
|
||||
)
|
||||
|
||||
var (
|
||||
smtpServer *mail.SMTPServer
|
||||
from string
|
||||
)
|
||||
|
||||
// Config defines the SMTP configuration to use to send emails
|
||||
type Config struct {
|
||||
// Location of SMTP email server. Leavy empty to disable email sending capabilities
|
||||
Host string `json:"host" mapstructure:"host"`
|
||||
// Port of SMTP email server
|
||||
Port int `json:"port" mapstructure:"port"`
|
||||
// From address, for example "SFTPGo <sftpgo@example.com>"
|
||||
From string `json:"from" mapstructure:"from"`
|
||||
// SMTP username
|
||||
User string `json:"user" mapstructure:"user"`
|
||||
// SMTP password. Leaving both username and password empty the SMTP authentication
|
||||
// will be disabled
|
||||
Password string `json:"password" mapstructure:"password"`
|
||||
// 0 Plain
|
||||
// 1 Login
|
||||
// 2 CRAM-MD5
|
||||
AuthType int `json:"auth_type" mapstructure:"auth_type"`
|
||||
// 0 no encryption
|
||||
// 1 TLS
|
||||
// 2 start TLS
|
||||
Encryption int `json:"encryption" mapstructure:"encryption"`
|
||||
// Domain to use for HELO command, if empty localhost will be used
|
||||
Domain string `json:"domain" mapstructure:"domain"`
|
||||
}
|
||||
|
||||
// Initialize initialized and validates the SMTP configuration
|
||||
func (c *Config) Initialize() error {
|
||||
smtpServer = nil
|
||||
if c.Host == "" {
|
||||
logger.Debug(logSender, "", "configuration disabled, email capabilities will not be available")
|
||||
return nil
|
||||
}
|
||||
if c.Port <= 0 || c.Port > 65535 {
|
||||
return fmt.Errorf("smtp: invalid port %v", c.Port)
|
||||
}
|
||||
if c.AuthType < 0 || c.AuthType > 2 {
|
||||
return fmt.Errorf("smtp: invalid auth type %v", c.AuthType)
|
||||
}
|
||||
if c.Encryption < 0 || c.Encryption > 2 {
|
||||
return fmt.Errorf("smtp: invalid encryption %v", c.Encryption)
|
||||
}
|
||||
from = c.From
|
||||
smtpServer = mail.NewSMTPClient()
|
||||
smtpServer.Host = c.Host
|
||||
smtpServer.Port = c.Port
|
||||
smtpServer.Username = c.User
|
||||
smtpServer.Password = c.Password
|
||||
smtpServer.Authentication = c.getAuthType()
|
||||
smtpServer.Encryption = c.getEncryption()
|
||||
smtpServer.KeepAlive = false
|
||||
smtpServer.ConnectTimeout = 10 * time.Second
|
||||
smtpServer.SendTimeout = 30 * time.Second
|
||||
if c.Domain != "" {
|
||||
smtpServer.Helo = c.Domain
|
||||
}
|
||||
logger.Debug(logSender, "", "configuration successfully initialized, host: %#v, port: %v, username: %#v, auth: %v, encryption: %v, helo: %#v",
|
||||
smtpServer.Host, smtpServer.Port, smtpServer.Username, smtpServer.Authentication, smtpServer.Encryption, smtpServer.Helo)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Config) getEncryption() mail.Encryption {
|
||||
switch c.Encryption {
|
||||
case 1:
|
||||
return mail.EncryptionSSLTLS
|
||||
case 2:
|
||||
return mail.EncryptionSTARTTLS
|
||||
default:
|
||||
return mail.EncryptionNone
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) getAuthType() mail.AuthType {
|
||||
if c.User == "" && c.Password == "" {
|
||||
return mail.AuthNone
|
||||
}
|
||||
switch c.AuthType {
|
||||
case 1:
|
||||
return mail.AuthLogin
|
||||
case 2:
|
||||
return mail.AuthCRAMMD5
|
||||
default:
|
||||
return mail.AuthPlain
|
||||
}
|
||||
}
|
||||
|
||||
// SendEmail tries to send an email using the specified parameters.
|
||||
// If the contentType is 0 text/plain is assumed, otherwise text/html
|
||||
func SendEmail(to, subject, body string, contentType EmailContentType) error {
|
||||
if smtpServer == nil {
|
||||
return errors.New("smtp: not configured")
|
||||
}
|
||||
smtpClient, err := smtpServer.Connect()
|
||||
if err != nil {
|
||||
return fmt.Errorf("smtp: unable to connect: %w", err)
|
||||
}
|
||||
|
||||
email := mail.NewMSG()
|
||||
if from != "" {
|
||||
email.SetFrom(from)
|
||||
}
|
||||
email.AddTo(to).SetSubject(subject)
|
||||
switch contentType {
|
||||
case EmailContentTypeTextPlain:
|
||||
email.SetBody(mail.TextPlain, body)
|
||||
case EmailContentTypeTextHTML:
|
||||
email.SetBody(mail.TextHTML, body)
|
||||
default:
|
||||
return fmt.Errorf("smtp: unsupported body content type %v", contentType)
|
||||
}
|
||||
if email.Error != nil {
|
||||
return fmt.Errorf("smtp: email error: %w", email.Error)
|
||||
}
|
||||
return email.Send(smtpClient)
|
||||
}
|
Loading…
Reference in a new issue