From a4fd43e03d81334a36ab701b2c21c8a73e4bd5f0 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Mon, 16 Jun 2025 17:00:58 +0200 Subject: [PATCH 1/2] Allow safe debugging of mail transfer by adding debug_mail_transfer build flag --- mail.go => mail_common.go | 44 ------------------------------- mail_transfer.go | 54 +++++++++++++++++++++++++++++++++++++++ mail_transfer_debug.go | 23 +++++++++++++++++ 3 files changed, 77 insertions(+), 44 deletions(-) rename mail.go => mail_common.go (63%) create mode 100644 mail_transfer.go create mode 100644 mail_transfer_debug.go diff --git a/mail.go b/mail_common.go similarity index 63% rename from mail.go rename to mail_common.go index a37c933..fee72f9 100644 --- a/mail.go +++ b/mail_common.go @@ -3,14 +3,12 @@ package main import ( - "crypto/tls" "fmt" "mime" "mime/quotedprintable" "net/mail" "net/smtp" "strings" - "time" ) type MailContent struct { @@ -86,48 +84,6 @@ func sendNotices(recipient string, notices []*WidNotice, template MailTemplate, return nil } -func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte) error { - addr := fmt.Sprintf("%v:%v", smtpConf.ServerHost, smtpConf.ServerPort) - logger.debug("Connecting to mail server at " + addr + " ...") - connection, err := smtp.Dial(addr) - if err != nil { return err } - defer connection.Close() - // can leave out connection.Hello - hasTlsExt, _ := connection.Extension("starttls") - if hasTlsExt { - err = connection.StartTLS(&tls.Config{ServerName: smtpConf.ServerHost}) - if err != nil { return err } - logger.debug("Mail Server supports TLS") - } else { - logger.debug("Mail Server doesn't support TLS") - } - logger.debug("Authenticating to mail server ...") - err = connection.Auth(auth) - if err != nil { return err } - if logger.LogLevel >= 3 { - fmt.Printf("DEBUG %v Sending mails to server ", time.Now().Format("2006/01/02 15:04:05.000000")) - } - for _, d := range data { - err = connection.Mail(smtpConf.From) - if err != nil { return err } - err = connection.Rcpt(to) - if err != nil { return err } - writer, err := connection.Data() - if err != nil { return err } - _, err = writer.Write(d) - if err != nil { return err } - err = writer.Close() - if err != nil { return err } - if logger.LogLevel >= 3 { - print(".") - } - } - if logger.LogLevel >= 3 { - print("\n") - } - return connection.Quit() -} - func mailAddressIsValid(address string) bool { _, err := mail.ParseAddress(address); return err == nil diff --git a/mail_transfer.go b/mail_transfer.go new file mode 100644 index 0000000..1013f0b --- /dev/null +++ b/mail_transfer.go @@ -0,0 +1,54 @@ +// +build !debug_mail_transfer +// Copyright (c) 2023 Julian Müller (ChaoticByte) + +package main + +import ( + "crypto/tls" + "fmt" + "net/smtp" + "time" +) + + +func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte) error { + addr := fmt.Sprintf("%v:%v", smtpConf.ServerHost, smtpConf.ServerPort) + logger.debug("Connecting to mail server at " + addr + " ...") + connection, err := smtp.Dial(addr) + if err != nil { return err } + defer connection.Close() + // can leave out connection.Hello + hasTlsExt, _ := connection.Extension("starttls") + if hasTlsExt { + err = connection.StartTLS(&tls.Config{ServerName: smtpConf.ServerHost}) + if err != nil { return err } + logger.debug("Mail Server supports StartTLS") + } else { + logger.debug("Mail Server doesn't support StartTLS") + } + logger.debug("Authenticating to mail server ...") + err = connection.Auth(auth) + if err != nil { return err } + if logger.LogLevel >= 3 { + fmt.Printf("DEBUG %v Sending mails to server ", time.Now().Format("2006/01/02 15:04:05.000000")) + } + for _, d := range data { + err = connection.Mail(smtpConf.From) + if err != nil { return err } + err = connection.Rcpt(to) + if err != nil { return err } + writer, err := connection.Data() + if err != nil { return err } + _, err = writer.Write(d) + if err != nil { return err } + err = writer.Close() + if err != nil { return err } + if logger.LogLevel >= 3 { + print(".") + } + } + if logger.LogLevel >= 3 { + print("\n") + } + return connection.Quit() +} diff --git a/mail_transfer_debug.go b/mail_transfer_debug.go new file mode 100644 index 0000000..d3846a3 --- /dev/null +++ b/mail_transfer_debug.go @@ -0,0 +1,23 @@ +// +build debug_mail_transfer +// Copyright (c) 2023 Julian Müller (ChaoticByte) + +package main + +import ( + "fmt" + "net/smtp" +) + +func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte) error { + logger.warn("Mail Transfer Debugging is active. Not connecting.") + logger.info("MAIL TRANSFER: \n\n") + for _, d := range data { + fmt.Println("MAIL FROM:" + smtpConf.From) + fmt.Println("RCPT TO:" + to) + fmt.Println("DATA") + fmt.Println(string(d)) + fmt.Println(".") + } + fmt.Print("\n\n") + return nil +} From c1b3d106a0005c62ed40c6d9454cf6484a4975a8 Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Mon, 16 Jun 2025 17:29:38 +0200 Subject: [PATCH 2/2] Fix leak of first recipient to other recipients because mail headers are also cached and re-used --- mail_common.go | 24 ++++++++++++------------ mail_transfer.go | 7 +++++-- mail_transfer_debug.go | 7 +++++-- main.go | 2 +- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/mail_common.go b/mail_common.go index fee72f9..71e29bf 100644 --- a/mail_common.go +++ b/mail_common.go @@ -47,30 +47,30 @@ type SmtpSettings struct { Password string `json:"password"` } -func sendNotices(recipient string, notices []*WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, cache *map[string][]byte) error { +func sendNotices(recipient string, notices []*WidNotice, template MailTemplate, auth smtp.Auth, smtpConfig SmtpSettings, mailContentCache *map[string]*MailContent) error { logger.debug("Generating and sending mails for recipient " + recipient + " ...") cacheHits := 0 cacheMisses := 0 - mails := [][]byte{} + mails := []*MailContent{} for _, n := range notices { - var data []byte - cacheResult := (*cache)[n.Uuid] - if len(cacheResult) > 0 { + var mc *MailContent + cacheResult := (*mailContentCache)[n.Uuid] + if cacheResult != nil { cacheHits++ - data = cacheResult + mc = cacheResult } else { cacheMisses++ - mailContent, err := template.generate(TemplateData{n, Version}) + mc_, err := template.generate(TemplateData{n, Version}) if err != nil { logger.error("Could not create mail from template") logger.error(err) + } else { + mc = &mc_ + // add to cache + (*mailContentCache)[n.Uuid] = mc } - // serialize mail - data = mailContent.serializeValidMail(smtpConfig.From, recipient) - // add to cache - (*cache)[n.Uuid] = data } - mails = append(mails, data) + mails = append(mails, mc) } logger.debug(fmt.Sprintf("%v mail cache hits, %v misses", cacheHits, cacheMisses)) err := sendMails( diff --git a/mail_transfer.go b/mail_transfer.go index 1013f0b..45675d3 100644 --- a/mail_transfer.go +++ b/mail_transfer.go @@ -11,7 +11,7 @@ import ( ) -func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte) error { +func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, mails []*MailContent) error { addr := fmt.Sprintf("%v:%v", smtpConf.ServerHost, smtpConf.ServerPort) logger.debug("Connecting to mail server at " + addr + " ...") connection, err := smtp.Dial(addr) @@ -32,7 +32,10 @@ func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte) if logger.LogLevel >= 3 { fmt.Printf("DEBUG %v Sending mails to server ", time.Now().Format("2006/01/02 15:04:05.000000")) } - for _, d := range data { + for _, mc := range mails { + // serialize mail + d := mc.serializeValidMail(smtpConf.From, to) + // send mail err = connection.Mail(smtpConf.From) if err != nil { return err } err = connection.Rcpt(to) diff --git a/mail_transfer_debug.go b/mail_transfer_debug.go index d3846a3..60fb2bd 100644 --- a/mail_transfer_debug.go +++ b/mail_transfer_debug.go @@ -8,10 +8,13 @@ import ( "net/smtp" ) -func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, data [][]byte) error { +func sendMails(smtpConf SmtpSettings, auth smtp.Auth, to string, mails []*MailContent) error { logger.warn("Mail Transfer Debugging is active. Not connecting.") logger.info("MAIL TRANSFER: \n\n") - for _, d := range data { + for _, mc := range mails { + // serialize mail + d := mc.serializeValidMail(smtpConf.From, to) + // output mail fmt.Println("MAIL FROM:" + smtpConf.From) fmt.Println("RCPT TO:" + to) fmt.Println("DATA") diff --git a/main.go b/main.go index 8fc4ab0..d239bf0 100644 --- a/main.go +++ b/main.go @@ -98,7 +98,7 @@ func main() { t1 := time.Now().UnixMilli() newNotices := []WidNotice{} lastPublished := map[string]time.Time{} // endpoint id : last published timestamp - cache := map[string][]byte{} // cache generated emails for reuse + cache := map[string]*MailContent{} // cache generated emails for reuse for _, a := range enabledApiEndpoints { logger.info("Querying endpoint '" + a.Id + "' for new notices ...") n, t, err := a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])