From af62be378f067f9b648fdf20d249b549b7cd5874 Mon Sep 17 00:00:00 2001
From: Tagada <36127788+Tagadda@users.noreply.github.com>
Date: Sat, 29 Jan 2022 09:01:37 +0100
Subject: [PATCH] SMTP support (#146)

---
 README.md       | 16 +++++++++-
 emailer/smtp.go | 81 +++++++++++++++++++++++++++++++++++++++++++++++++
 go.mod          |  1 +
 go.sum          |  2 ++
 main.go         | 25 ++++++++++++++-
 util/config.go  |  6 ++++
 6 files changed, 129 insertions(+), 2 deletions(-)
 create mode 100644 emailer/smtp.go

diff --git a/README.md b/README.md
index dec0978..b3f213c 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,9 @@ wireguard interface stats. See the `cap_add` and `network_mode` options on the d
 
 Set the `SESSION_SECRET` environment variable to a random value.
 
-In order to sent the wireguard configuration to clients via email (using sendgrid api) set the following environment variables
+In order to sent the wireguard configuration to clients via email, set the following environment variables:
+
+- using SendGrid API
 
 ```
 SENDGRID_API_KEY: Your sendgrid api key
@@ -41,6 +43,18 @@ EMAIL_FROM_ADDRESS: the email address you registered on sendgrid
 EMAIL_FROM_NAME: the sender's email address
 ```
 
+- using SMTP
+
+```
+SMTP_HOSTNAME
+SMTP_PORT
+SMTP_USERNAME
+SMTP_PASSWORD
+SMTP_AUTH_TYPE
+EMAIL_FROM_ADDRESS: the sender's email address
+EMAIL_FROM_NAME: the sender's name
+```
+
 ### Using binary file
 
 Download the binary file from the release and run it with command:
diff --git a/emailer/smtp.go b/emailer/smtp.go
new file mode 100644
index 0000000..326fa11
--- /dev/null
+++ b/emailer/smtp.go
@@ -0,0 +1,81 @@
+package emailer
+
+import (
+	"crypto/tls"
+	"fmt"
+	"time"
+
+	mail "github.com/xhit/go-simple-mail/v2"
+)
+
+type SmtpMail struct {
+	hostname   string
+	port       int
+	username   string
+	password   string
+	authType   mail.AuthType
+	noTLSCheck bool
+	fromName   string
+	from       string
+}
+
+func authType(authType string) mail.AuthType {
+	switch authType {
+	case "PLAIN":
+		return mail.AuthPlain
+	case "LOGIN":
+		return mail.AuthLogin
+	default:
+		return mail.AuthNone
+	}
+}
+
+func NewSmtpMail(hostname string, port int, username string, password string, noTLSCheck bool, auth string, fromName, from string) *SmtpMail {
+	ans := SmtpMail{hostname: hostname, port: port, username: username, password: password, noTLSCheck: noTLSCheck, fromName: fromName, from: from, authType: authType(auth)}
+	return &ans
+}
+
+func addressField(address string, name string) string {
+	if name == "" {
+		return address
+	}
+	return fmt.Sprintf("%s <%s>", name, address)
+}
+
+func (o *SmtpMail) Send(toName string, to string, subject string, content string, attachments []Attachment) error {
+	server := mail.NewSMTPClient()
+
+	server.Host = o.hostname
+	server.Port = o.port
+	server.Authentication = o.authType
+	server.Username = o.username
+	server.Password = o.password
+	server.Encryption = mail.EncryptionSTARTTLS
+	server.KeepAlive = false
+	server.ConnectTimeout = 10 * time.Second
+	server.SendTimeout = 10 * time.Second
+
+	if o.noTLSCheck {
+		server.TLSConfig = &tls.Config{InsecureSkipVerify: true}
+	}
+
+	smtpClient, err := server.Connect()
+
+	if err != nil {
+		return err
+	}
+
+	email := mail.NewMSG()
+	email.SetFrom("From "+addressField(o.from, o.fromName)).
+		AddTo(addressField(to, toName)).
+		SetSubject(subject).
+		SetBody(mail.TextHTML, content)
+
+	for _, v := range attachments {
+		email.Attach(&mail.File{Name: v.Name, Data: v.Data})
+	}
+
+	err = email.Send(smtpClient)
+
+	return err
+}
diff --git a/go.mod b/go.mod
index fb535fe..918d704 100644
--- a/go.mod
+++ b/go.mod
@@ -17,6 +17,7 @@ require (
 	github.com/sendgrid/rest v2.6.4+incompatible // indirect
 	github.com/sendgrid/sendgrid-go v3.10.0+incompatible
 	github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086
+	github.com/xhit/go-simple-mail/v2 v2.10.0
 	golang.zx2c4.com/wireguard v0.0.20200121 // indirect
 	golang.zx2c4.com/wireguard/wgctrl v0.0.0-20210803171230-4253848d036c
 	gopkg.in/go-playground/assert.v1 v1.2.1 // indirect
diff --git a/go.sum b/go.sum
index b31de99..206090f 100644
--- a/go.sum
+++ b/go.sum
@@ -147,6 +147,8 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
 github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
 github.com/valyala/fasttemplate v1.1.0 h1:RZqt0yGBsps8NGvLSGW804QQqCUYYLsaOjTVHy1Ocw4=
 github.com/valyala/fasttemplate v1.1.0/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
+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=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
diff --git a/main.go b/main.go
index 8cf5dc9..99e0460 100644
--- a/main.go
+++ b/main.go
@@ -25,6 +25,12 @@ var (
 	// configuration variables
 	flagDisableLogin   bool   = false
 	flagBindAddress    string = "0.0.0.0:5000"
+	flagSmtpHostname   string = "127.0.0.1"
+	flagSmtpPort       int    = 25
+	flagSmtpUsername   string
+	flagSmtpPassword   string
+	flagSmtpAuthType   string = "None"
+	flagSmtpNoTLSCheck bool   = false
 	flagSendgridApiKey string
 	flagEmailFrom      string
 	flagEmailFromName  string = "WireGuard UI"
@@ -45,6 +51,12 @@ func init() {
 	// command-line flags and env variables
 	flag.BoolVar(&flagDisableLogin, "disable-login", util.LookupEnvOrBool("DISABLE_LOGIN", flagDisableLogin), "Disable authentication on the app. This is potentially dangerous.")
 	flag.StringVar(&flagBindAddress, "bind-address", util.LookupEnvOrString("BIND_ADDRESS", flagBindAddress), "Address:Port to which the app will be bound.")
+	flag.StringVar(&flagSmtpHostname, "smtp-hostname", util.LookupEnvOrString("SMTP_HOSTNAME", flagSmtpHostname), "SMTP Hostname")
+	flag.IntVar(&flagSmtpPort, "smtp-port", util.LookupEnvOrInt("SMTP_PORT", flagSmtpPort), "SMTP Port")
+	flag.StringVar(&flagSmtpUsername, "smtp-username", util.LookupEnvOrString("SMTP_USERNAME", flagSmtpUsername), "SMTP Password")
+	flag.StringVar(&flagSmtpPassword, "smtp-password", util.LookupEnvOrString("SMTP_PASSWORD", flagSmtpPassword), "SMTP Password")
+	flag.BoolVar(&flagSmtpNoTLSCheck, "smtp-no-tls-check", util.LookupEnvOrBool("SMTP_NO_TLS_CHECK", flagSmtpNoTLSCheck), "Disable TLS verification for SMTP. This is potentially dangerous.")
+	flag.StringVar(&flagSmtpAuthType, "smtp-auth-type", util.LookupEnvOrString("SMTP_AUTH_TYPE", flagSmtpAuthType), "SMTP Auth Type : Plain or None.")
 	flag.StringVar(&flagSendgridApiKey, "sendgrid-api-key", util.LookupEnvOrString("SENDGRID_API_KEY", flagSendgridApiKey), "Your sendgrid api key.")
 	flag.StringVar(&flagEmailFrom, "email-from", util.LookupEnvOrString("EMAIL_FROM_ADDRESS", flagEmailFrom), "'From' email address.")
 	flag.StringVar(&flagEmailFromName, "email-from-name", util.LookupEnvOrString("EMAIL_FROM_NAME", flagEmailFromName), "'From' email name.")
@@ -54,6 +66,12 @@ func init() {
 	// update runtime config
 	util.DisableLogin = flagDisableLogin
 	util.BindAddress = flagBindAddress
+	util.SmtpHostname = flagSmtpHostname
+	util.SmtpPort = flagSmtpPort
+	util.SmtpUsername = flagSmtpUsername
+	util.SmtpPassword = flagSmtpPassword
+	util.SmtpAuthType = flagSmtpAuthType
+	util.SmtpNoTLSCheck = flagSmtpNoTLSCheck
 	util.SendgridApiKey = flagSendgridApiKey
 	util.EmailFrom = flagEmailFrom
 	util.EmailFromName = flagEmailFromName
@@ -103,7 +121,12 @@ func main() {
 		app.POST("/login", handler.Login(db))
 	}
 
-	sendmail := emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
+	var sendmail emailer.Emailer
+	if util.SendgridApiKey != "" {
+		sendmail = emailer.NewSendgridApiMail(util.SendgridApiKey, util.EmailFromName, util.EmailFrom)
+	} else {
+		sendmail = emailer.NewSmtpMail(util.SmtpHostname, util.SmtpPort, util.SmtpUsername, util.SmtpPassword, util.SmtpNoTLSCheck, util.SmtpAuthType, util.EmailFromName, util.EmailFrom)
+	}
 
 	app.GET("/_health", handler.Health())
 	app.GET("/logout", handler.Logout(), handler.ValidSession)
diff --git a/util/config.go b/util/config.go
index acdb720..80cbc9c 100644
--- a/util/config.go
+++ b/util/config.go
@@ -4,6 +4,12 @@ package util
 var (
 	DisableLogin   bool
 	BindAddress    string
+	SmtpHostname   string
+	SmtpPort       int
+	SmtpUsername   string
+	SmtpPassword   string
+	SmtpNoTLSCheck bool
+	SmtpAuthType   string
 	SendgridApiKey string
 	EmailFrom      string
 	EmailFromName  string