diff --git a/README.md b/README.md index aec0475..dfd2999 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,7 @@ The following configuration options are available: | EMAIL_CERT_VALIDATION | certcheck | email | false | Validate the email server certificate. | | EMAIL_USERNAME | user | email | | An optional username for SMTP authentication. | | EMAIL_PASSWORD | pass | email | | An optional password for SMTP authentication. | +| EMAIL_AUTHTYPE | auth | email | plain | Either plain, login or crammd5. If username and password are empty, this value is ignored. | | WG_DEVICES | devices | wg | wg0 | A comma separated list of WireGuard devices. | | WG_DEFAULT_DEVICE | defaultDevice | wg | wg0 | This device is used for auto-created peers (if CREATE_DEFAULT_PEER is enabled). | | WG_CONFIG_PATH | configDirectory | wg | /etc/wireguard | If set, interface configuration updates will be written to this path, filename: .conf. | diff --git a/go.mod b/go.mod index 84611b0..4489866 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,6 @@ require ( github.com/go-openapi/swag v0.19.15 // indirect github.com/go-playground/validator/v10 v10.4.1 github.com/gorilla/sessions v1.2.1 // indirect - github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible github.com/kelseyhightower/envconfig v1.4.0 github.com/mailru/easyjson v0.7.7 // indirect github.com/milosgajdos/tenus v0.0.3 @@ -25,6 +24,7 @@ require ( github.com/tatsushid/go-fastping v0.0.0-20160109021039-d7bb493dee3e github.com/toorop/gin-logrus v0.0.0-20210225092905-2c785434f26f github.com/utrack/gin-csrf v0.0.0-20190424104817-40fb8d2c8fca + github.com/xhit/go-simple-mail/v2 v2.8.1 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 // indirect diff --git a/internal/common/email.go b/internal/common/email.go index af65188..5233701 100644 --- a/internal/common/email.go +++ b/internal/common/email.go @@ -3,13 +3,10 @@ package common import ( "crypto/tls" "io" - "net/smtp" - "strconv" - "strings" + "io/ioutil" "github.com/pkg/errors" - - "github.com/jordan-wright/email" + mail "github.com/xhit/go-simple-mail/v2" ) type MailEncryption string @@ -20,6 +17,14 @@ const ( MailEncryptionStartTLS MailEncryption = "starttls" ) +type MailAuthType string + +const ( + MailAuthPlain MailAuthType = "plain" + MailAuthLogin MailAuthType = "login" + MailAuthCramMD5 MailAuthType = "crammd5" +) + type MailConfig struct { Host string `yaml:"host" envconfig:"EMAIL_HOST"` Port int `yaml:"port" envconfig:"EMAIL_PORT"` @@ -28,6 +33,7 @@ type MailConfig struct { CertValidation bool `yaml:"certcheck" envconfig:"EMAIL_CERT_VALIDATION"` Username string `yaml:"user" envconfig:"EMAIL_USERNAME"` Password string `yaml:"pass" envconfig:"EMAIL_PASSWORD"` + AuthType MailAuthType `yaml:"auth" envconfig:"EMAIL_AUTHTYPE"` } type MailAttachment struct { @@ -39,60 +45,72 @@ type MailAttachment struct { // SendEmailWithAttachments sends a mail with optional attachments. func SendEmailWithAttachments(cfg MailConfig, sender, replyTo, subject, body string, htmlBody string, receivers []string, attachments []MailAttachment) error { - e := email.NewEmail() + srv := mail.NewSMTPClient() - hostname := cfg.Host + ":" + strconv.Itoa(cfg.Port) - subject = strings.Trim(subject, "\n\r\t") - sender = strings.Trim(sender, "\n\r\t") - replyTo = strings.Trim(replyTo, "\n\r\t") - if replyTo == "" { - replyTo = sender - } - - var auth smtp.Auth - if cfg.Username == "" { - auth = nil - } else { - // Set up authentication information. - auth = smtp.PlainAuth( - "", - cfg.Username, - cfg.Password, - cfg.Host, - ) - } - - // Set email data. - e.From = sender - e.To = receivers - e.ReplyTo = []string{replyTo} - e.Subject = subject - e.Text = []byte(body) - if htmlBody != "" { - e.HTML = []byte(htmlBody) - } - - for _, attachment := range attachments { - a, err := e.Attach(attachment.Data, attachment.Name, attachment.ContentType) - if err != nil { - return errors.Wrapf(err, "failed to attach %s to mailbody", attachment.Name) - } - if attachment.Embedded { - a.HTMLRelated = true - } - } + srv.Host = cfg.Host + srv.Port = cfg.Port + srv.Username = cfg.Username + srv.Password = cfg.Password // TODO: remove this once the deprecated MailConfig.TLS config option has been removed if cfg.TLS { cfg.Encryption = MailEncryptionStartTLS } - switch cfg.Encryption { case MailEncryptionTLS: - return e.SendWithTLS(hostname, auth, &tls.Config{ServerName: cfg.Host, InsecureSkipVerify: !cfg.CertValidation}) + srv.Encryption = mail.EncryptionTLS case MailEncryptionStartTLS: - return e.SendWithStartTLS(hostname, auth, &tls.Config{ServerName: cfg.Host, InsecureSkipVerify: !cfg.CertValidation}) + srv.Encryption = mail.EncryptionSTARTTLS default: // MailEncryptionNone - return e.Send(hostname, auth) + srv.Encryption = mail.EncryptionNone } + srv.TLSConfig = &tls.Config{InsecureSkipVerify: !cfg.CertValidation} + switch cfg.AuthType { + case MailAuthPlain: + srv.Authentication = mail.AuthPlain + case MailAuthLogin: + srv.Authentication = mail.AuthLogin + case MailAuthCramMD5: + srv.Authentication = mail.AuthCRAMMD5 + } + + client, err := srv.Connect() + if err != nil { + return errors.Wrap(err, "failed to connect via SMTP") + } + + if replyTo == "" { + replyTo = sender + } + + email := mail.NewMSG() + email.SetFrom(sender). + AddTo(receivers...). + SetReplyTo(replyTo). + SetSubject(subject) + + email.SetBody(mail.TextPlain, body) + if htmlBody != "" { + email.SetBody(mail.TextHTML, htmlBody) + } + + for _, attachment := range attachments { + attachmentData, err := ioutil.ReadAll(attachment.Data) + if err != nil { + return errors.Wrapf(err, "failed to read attachment data for %s", attachment.Name) + } + + if attachment.Embedded { + email.AddInlineData(attachmentData, attachment.Name, attachment.ContentType) + } else { + email.AddAttachmentData(attachmentData, attachment.Name, attachment.ContentType) + } + } + + // Call Send and pass the client + err = email.Send(client) + if err != nil { + return errors.Wrapf(err, "failed to send email") + } + return nil } diff --git a/internal/server/configuration.go b/internal/server/configuration.go index 6b8f2e6..730aa43 100644 --- a/internal/server/configuration.go +++ b/internal/server/configuration.go @@ -114,6 +114,7 @@ func NewConfig() *Config { cfg.Email.Host = "127.0.0.1" cfg.Email.Port = 25 cfg.Email.Encryption = common.MailEncryptionNone + cfg.Email.AuthType = common.MailAuthPlain // Load config from file and environment cfgFile, ok := os.LookupEnv("CONFIG_FILE") diff --git a/internal/server/handlers_peer.go b/internal/server/handlers_peer.go index 3933519..0112100 100644 --- a/internal/server/handlers_peer.go +++ b/internal/server/handlers_peer.go @@ -265,6 +265,7 @@ func (s *Server) GetPeerConfigMail(c *gin.Context) { return } // Apply mail template + qrcodeFileName := "wireguard-qrcode.png" var tplBuff bytes.Buffer if err := s.mailTpl.Execute(&tplBuff, struct { Peer wireguard.Peer @@ -274,7 +275,7 @@ func (s *Server) GetPeerConfigMail(c *gin.Context) { }{ Peer: peer, User: user, - QrcodePngName: "wireguard-config.png", + QrcodePngName: qrcodeFileName, PortalUrl: s.config.Core.ExternalUrl, }); err != nil { s.GetHandleError(c, http.StatusInternalServerError, "Template error", err.Error()) @@ -289,7 +290,13 @@ func (s *Server) GetPeerConfigMail(c *gin.Context) { Data: bytes.NewReader(cfg), }, { - Name: "wireguard-config.png", + Name: qrcodeFileName, + ContentType: "image/png", + Data: bytes.NewReader(png), + Embedded: true, + }, + { + Name: qrcodeFileName, ContentType: "image/png", Data: bytes.NewReader(png), },