From a5454454bc200a1bf576d81107ca3780787bea9d Mon Sep 17 00:00:00 2001 From: Ishank Arora Date: Fri, 24 Jul 2020 14:48:36 +0200 Subject: [PATCH] Add headers to SMTP client to prevent being tagged as spam (#970) --- changelog/unreleased/smtp-headers.md | 6 ++ .../docs/config/packages/smtpclient/_index.md | 58 +++++++++++++++ internal/http/services/ocmd/invites.go | 6 +- pkg/smtpclient/smtpclient.go | 72 ++++++++++++++----- 4 files changed, 118 insertions(+), 24 deletions(-) create mode 100644 changelog/unreleased/smtp-headers.md create mode 100644 docs/content/en/docs/config/packages/smtpclient/_index.md diff --git a/changelog/unreleased/smtp-headers.md b/changelog/unreleased/smtp-headers.md new file mode 100644 index 0000000000..af4d568cf4 --- /dev/null +++ b/changelog/unreleased/smtp-headers.md @@ -0,0 +1,6 @@ +Enhancement: Add required headers to SMTP client to prevent being tagged as spam + +Mails being sent through the client, specially through unauthenticated SMTP were +being tagged as spam due to missing headers. + +https://github.com/cs3org/reva/pull/970 diff --git a/docs/content/en/docs/config/packages/smtpclient/_index.md b/docs/content/en/docs/config/packages/smtpclient/_index.md new file mode 100644 index 0000000000..31839cfdbc --- /dev/null +++ b/docs/content/en/docs/config/packages/smtpclient/_index.md @@ -0,0 +1,58 @@ +--- +title: "smtpclient" +linkTitle: "smtpclient" +weight: 10 +description: > + Configuration for the smtpclient service +--- + +# _struct: SMTPCredentials_ + +{{% dir name="sender_mail" type="string" default="" %}} +The email to be used to send mails. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/smtpclient/smtpclient.go#L36) +{{< highlight toml >}} +[smtpclient] +sender_mail = "" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="sender_password" type="string" default="" %}} +The sender's password. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/smtpclient/smtpclient.go#L37) +{{< highlight toml >}} +[smtpclient] +sender_password = "" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="smtp_server" type="string" default="" %}} +The hostname of the SMTP server. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/smtpclient/smtpclient.go#L38) +{{< highlight toml >}} +[smtpclient] +smtp_server = "" +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="smtp_port" type="int" default=587 %}} +The port on which the SMTP daemon is running. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/smtpclient/smtpclient.go#L39) +{{< highlight toml >}} +[smtpclient] +smtp_port = 587 +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="disable_auth" type="bool" default=false %}} +Whether to disable SMTP auth. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/smtpclient/smtpclient.go#L40) +{{< highlight toml >}} +[smtpclient] +disable_auth = false +{{< /highlight >}} +{{% /dir %}} + +{{% dir name="local_name" type="string" default="" %}} +The host name to be used for unauthenticated SMTP. [[Ref]](https://github.com/cs3org/reva/tree/master/pkg/smtpclient/smtpclient.go#L41) +{{< highlight toml >}} +[smtpclient] +local_name = "" +{{< /highlight >}} +{{% /dir %}} + diff --git a/internal/http/services/ocmd/invites.go b/internal/http/services/ocmd/invites.go index 60a6244560..9b890f5785 100644 --- a/internal/http/services/ocmd/invites.go +++ b/internal/http/services/ocmd/invites.go @@ -23,7 +23,6 @@ import ( "errors" "fmt" "net/http" - "os" userpb "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" invitepb "github.com/cs3org/go-cs3apis/cs3/ocm/invite/v1beta1" @@ -44,10 +43,7 @@ type invitesHandler struct { func (h *invitesHandler) init(c *Config) { h.gatewayAddr = c.GatewaySvc - h.smtpCredentials = c.SMTPCredentials - if h.smtpCredentials != nil && !h.smtpCredentials.DisableAuth && h.smtpCredentials.SenderPassword == "" { - h.smtpCredentials.SenderPassword = os.Getenv("REVA_OCMD_SMTP_SENDER_PASSWORD") - } + h.smtpCredentials = smtpclient.NewSMTPCredentials(c.SMTPCredentials) } func (h *invitesHandler) Handler() http.Handler { diff --git a/pkg/smtpclient/smtpclient.go b/pkg/smtpclient/smtpclient.go index 4a3997a516..6ca85170b4 100644 --- a/pkg/smtpclient/smtpclient.go +++ b/pkg/smtpclient/smtpclient.go @@ -20,38 +20,74 @@ package smtpclient import ( "bytes" + "encoding/base64" "fmt" "net/smtp" + "os" + "strings" + "time" + "github.com/google/uuid" "github.com/pkg/errors" ) // SMTPCredentials stores the credentials required to connect to an SMTP server. type SMTPCredentials struct { - SenderMail string `mapstructure:"sender_mail"` - SenderPassword string `mapstructure:"sender_password"` - SMTPServer string `mapstructure:"smtp_server"` - SMTPPort int `mapstructure:"smtp_port"` - DisableAuth bool `mapstructure:"disable_auth"` + SenderMail string `mapstructure:"sender_mail" docs:";The email to be used to send mails."` + SenderPassword string `mapstructure:"sender_password" docs:";The sender's password."` + SMTPServer string `mapstructure:"smtp_server" docs:";The hostname of the SMTP server."` + SMTPPort int `mapstructure:"smtp_port" docs:"587;The port on which the SMTP daemon is running."` + DisableAuth bool `mapstructure:"disable_auth" docs:"false;Whether to disable SMTP auth."` + LocalName string `mapstructure:"local_name" docs:";The host name to be used for unauthenticated SMTP."` +} + +// NewSMTPCredentials creates a new SMTPCredentials object with the details of the passed object with sane defaults. +func NewSMTPCredentials(c *SMTPCredentials) *SMTPCredentials { + creds := c + + if creds.SMTPPort == 0 { + creds.SMTPPort = 587 + } + if !creds.DisableAuth && creds.SenderPassword == "" { + creds.SenderPassword = os.Getenv("REVA_SMTP_SENDER_PASSWORD") + } + if creds.LocalName == "" { + tokens := strings.Split(creds.SenderMail, "@") + creds.LocalName = tokens[len(tokens)-1] + } + return creds } // SendMail allows sending mails using a set of client credentials. func (creds *SMTPCredentials) SendMail(recipient, subject, body string) error { + + headers := map[string]string{ + "From": creds.SenderMail, + "To": recipient, + "Subject": subject, + "Date": time.Now().Format(time.RFC1123Z), + "Message-ID": uuid.New().String(), + "MIME-Version": "1.0", + "Content-Type": "text/plain; charset=\"utf-8\"", + "Content-Transfer-Encoding": "base64", + } + + message := "" + for k, v := range headers { + message += fmt.Sprintf("%s: %s\r\n", k, v) + } + message += "\r\n" + base64.StdEncoding.EncodeToString([]byte(body)) + if creds.DisableAuth { - return creds.sendMailSMTP(recipient, subject, body) + return creds.sendMailSMTP(recipient, subject, message) } - return creds.sendMailAuthSMTP(recipient, subject, body) + return creds.sendMailAuthSMTP(recipient, subject, message) } -func (creds *SMTPCredentials) sendMailAuthSMTP(recipient, subject, body string) error { +func (creds *SMTPCredentials) sendMailAuthSMTP(recipient, subject, message string) error { auth := smtp.PlainAuth("", creds.SenderMail, creds.SenderPassword, creds.SMTPServer) - message := "From: " + creds.SenderMail + "\n" + - "To: " + recipient + "\n" + - "Subject: " + subject + "\n\n" + - body - err := smtp.SendMail( fmt.Sprintf("%s:%d", creds.SMTPServer, creds.SMTPPort), auth, @@ -67,7 +103,7 @@ func (creds *SMTPCredentials) sendMailAuthSMTP(recipient, subject, body string) return nil } -func (creds *SMTPCredentials) sendMailSMTP(recipient, subject, body string) error { +func (creds *SMTPCredentials) sendMailSMTP(recipient, subject, message string) error { c, err := smtp.Dial(fmt.Sprintf("%s:%d", creds.SMTPServer, creds.SMTPPort)) if err != nil { @@ -75,6 +111,9 @@ func (creds *SMTPCredentials) sendMailSMTP(recipient, subject, body string) erro } defer c.Close() + if err = c.Hello(creds.LocalName); err != nil { + return err + } if err = c.Mail(creds.SenderMail); err != nil { return err } @@ -88,12 +127,7 @@ func (creds *SMTPCredentials) sendMailSMTP(recipient, subject, body string) erro } defer wc.Close() - message := "From: " + creds.SenderMail + "\n" + - "To: " + recipient + "\n" + - "Subject: " + subject + "\n\n" + - body buf := bytes.NewBufferString(message) - if _, err = buf.WriteTo(wc); err != nil { return err }