From 7d435f7ba831af239d74716b9ad8c7823485f736 Mon Sep 17 00:00:00 2001 From: Aine Date: Mon, 5 Sep 2022 20:38:58 +0300 Subject: [PATCH] move email composing to utils --- bot/email.go | 79 +++--------------------------------------------- utils/email.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 74 deletions(-) diff --git a/bot/email.go b/bot/email.go index 030edc6..9b5639a 100644 --- a/bot/email.go +++ b/bot/email.go @@ -2,15 +2,10 @@ package bot import ( "context" - "crypto" - "crypto/x509" - "encoding/pem" "errors" "fmt" "strings" - "time" - "github.com/emersion/go-msgauth/dkim" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/format" "maunium.net/go/mautrix/id" @@ -206,75 +201,11 @@ func (b *Bot) Send2Email(ctx context.Context, to, subject, body string) error { body = b.getBody(content) } - var msg strings.Builder - msg.WriteString("From: ") - msg.WriteString(from) - msg.WriteString("\r\n") - - msg.WriteString("To: ") - msg.WriteString(to) - msg.WriteString("\r\n") - - msg.WriteString("Message-Id: ") - msg.WriteString(evt.ID.String()[1:] + "@" + b.domain) - msg.WriteString("\r\n") - - msg.WriteString("Date: ") - msg.WriteString(time.Now().UTC().Format(time.RFC1123Z)) - msg.WriteString("\r\n") - - if inReplyTo != "" { - msg.WriteString("In-Reply-To: ") - msg.WriteString(inReplyTo) - msg.WriteString("\r\n") - } - - msg.WriteString("Subject: ") - msg.WriteString(subject) - msg.WriteString("\r\n") - - msg.WriteString("\r\n") - - msg.WriteString(body) - msg.WriteString("\r\n") - - msg = b.signDKIM(msg) - - return b.mta.Send(from, to, msg.String()) -} - -func (b *Bot) signDKIM(body strings.Builder) strings.Builder { - privkey := b.getBotSettings().DKIMPrivateKey() - if privkey == "" { - b.log.Warn("DKIM private key not found, email will be sent unsigned") - return body - } - pemblock, _ := pem.Decode([]byte(privkey)) - if pemblock == nil { - b.log.Error("cannot decode DKIM private key") - return body - } - parsedkey, err := x509.ParsePKCS8PrivateKey(pemblock.Bytes) - if err != nil { - b.log.Error("cannot parse PKCS8 private key: %v", err) - return body - } - signer := parsedkey.(crypto.Signer) - - options := &dkim.SignOptions{ - Domain: b.domain, - Selector: "postmoogle", - Signer: signer, - } - - var msg strings.Builder - err = dkim.Sign(&msg, strings.NewReader(body.String()), options) - if err != nil { - b.log.Error("cannot sign email: %v", err) - return body - } - - return msg + ID := evt.ID.String()[1:] + "@" + b.domain + data := utils. + NewEmail(ID, inReplyTo, subject, from, to, body, "", nil). + Compose(b.getBotSettings().DKIMPrivateKey()) + return b.mta.Send(from, to, data) } func (b *Bot) sendFiles(ctx context.Context, roomID id.RoomID, files []*utils.File, noThreads bool, parentID id.EventID) { diff --git a/utils/email.go b/utils/email.go index bb14e7e..3e60e95 100644 --- a/utils/email.go +++ b/utils/email.go @@ -1,5 +1,15 @@ package utils +import ( + "crypto" + "crypto/x509" + "encoding/pem" + "strings" + "time" + + "github.com/emersion/go-msgauth/dkim" +) + // MTA is mail transfer agent type MTA interface { Send(from, to, data string) error @@ -7,6 +17,9 @@ type MTA interface { // Email object type Email struct { + data strings.Builder + + Date string MessageID string InReplyTo string From string @@ -20,6 +33,7 @@ type Email struct { // NewEmail constructs Email object func NewEmail(messageID, inReplyTo, subject, from, to, text, html string, files []*File) *Email { email := &Email{ + Date: time.Now().UTC().Format(time.RFC1123Z), MessageID: messageID, InReplyTo: inReplyTo, From: from, @@ -40,3 +54,71 @@ func NewEmail(messageID, inReplyTo, subject, from, to, text, html string, files return email } + +// Compose converts email object to string and (optionally) signs it +func (e *Email) Compose(privkey string) string { + domain := strings.SplitN(e.From, "@", 2)[0] + + e.data.WriteString("From: ") + e.data.WriteString(e.From) + e.data.WriteString("\r\n") + + e.data.WriteString("To: ") + e.data.WriteString(e.To) + e.data.WriteString("\r\n") + + e.data.WriteString("Message-Id: ") + e.data.WriteString(e.MessageID) + e.data.WriteString("\r\n") + + e.data.WriteString("Date: ") + e.data.WriteString(e.Date) + e.data.WriteString("\r\n") + + if e.InReplyTo != "" { + e.data.WriteString("In-Reply-To: ") + e.data.WriteString(e.InReplyTo) + e.data.WriteString("\r\n") + } + + e.data.WriteString("Subject: ") + e.data.WriteString(e.Subject) + e.data.WriteString("\r\n") + + e.data.WriteString("\r\n") + + e.data.WriteString(e.Text) + e.data.WriteString("\r\n") + + e.sign(domain, privkey) + return e.data.String() +} + +func (e *Email) sign(domain, privkey string) { + if privkey == "" { + return + } + pemblock, _ := pem.Decode([]byte(privkey)) + if pemblock == nil { + return + } + parsedkey, err := x509.ParsePKCS8PrivateKey(pemblock.Bytes) + if err != nil { + return + } + signer := parsedkey.(crypto.Signer) + + options := &dkim.SignOptions{ + Domain: domain, + Selector: "postmoogle", + Signer: signer, + } + + var msg strings.Builder + err = dkim.Sign(&msg, strings.NewReader(e.data.String()), options) + if err != nil { + return + } + + e.data = msg +}