From da0ccc6426a2b1e4a8d509d91c22d0dfc08e0f3a Mon Sep 17 00:00:00 2001 From: Nicola Murino Date: Sun, 26 Sep 2021 20:25:37 +0200 Subject: [PATCH] add SMTP support it will be used in future update to add email sending capabilities --- cmd/root.go | 1 + cmd/smtptest.go | 54 ++++++++++++++ cmd/startsubsys.go | 6 ++ config/config.go | 34 ++++++++- config/config_test.go | 20 +++++ docs/full-configuration.md | 22 ++++-- go.mod | 5 +- go.sum | 10 ++- service/service.go | 7 ++ sftpgo.json | 10 +++ smtp/smtp.go | 145 +++++++++++++++++++++++++++++++++++++ 11 files changed, 302 insertions(+), 12 deletions(-) create mode 100644 cmd/smtptest.go create mode 100644 smtp/smtp.go diff --git a/cmd/root.go b/cmd/root.go index 7129c3e0..63dac3ce 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -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}} diff --git a/cmd/smtptest.go b/cmd/smtptest.go new file mode 100644 index 00000000..54fd0838 --- /dev/null +++ b/cmd/smtptest.go @@ -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) +} diff --git a/cmd/startsubsys.go b/cmd/startsubsys.go index d1d9f05b..31679f4d 100644 --- a/cmd/startsubsys.go +++ b/cmd/startsubsys.go @@ -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", diff --git a/config/config.go b/config/config.go index 3f9fb37c..7137f5a0 100644 --- a/config/config.go +++ b/config/config.go @@ -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) { diff --git a/config/config_test.go b/config/config_test.go index 46d39e1c..1df51158 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -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() diff --git a/docs/full-configuration.md b/docs/full-configuration.md index c5ac431c..522ec1f3 100644 --- a/docs/full-configuration.md +++ b/docs/full-configuration.md @@ -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 `. 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. diff --git a/go.mod b/go.mod index 3dbd1897..628aa6fd 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 49306100..0e8b2c9e 100644 --- a/go.sum +++ b/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= diff --git a/service/service.go b/service/service.go index df2d7eee..d31c229e 100644 --- a/service/service.go +++ b/service/service.go @@ -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() diff --git a/sftpgo.json b/sftpgo.json index 0e52c08b..21a5aea1 100644 --- a/sftpgo.json +++ b/sftpgo.json @@ -254,5 +254,15 @@ } ] }, + "smtp": { + "host": "", + "port": 25, + "from": "", + "user": "", + "password": "", + "auth_type": 0, + "encryption": 0, + "domain": "" + }, "plugins": [] } \ No newline at end of file diff --git a/smtp/smtp.go b/smtp/smtp.go new file mode 100644 index 00000000..c71291ea --- /dev/null +++ b/smtp/smtp.go @@ -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 " + 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) +}