From 91a8dd8ba933b589630a5bf8a7bcc8b8c8594868 Mon Sep 17 00:00:00 2001 From: Drew Smirnoff Date: Sat, 28 Feb 2026 01:01:08 +0400 Subject: [PATCH] fix: add support for STARTTLS (#219) Signed-off-by: drew --- config/config.go | 3 +++ fetcher/fetcher.go | 28 +++++++++++++++++++--- sender/sender.go | 59 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 4 deletions(-) diff --git a/config/config.go b/config/config.go index 0cb3e501b7e6cddceb3998e20cf62a2e6986aff5..e62b3a45b5c801f1ce9457084126ad84bb21a316 100644 --- a/config/config.go +++ b/config/config.go @@ -27,6 +27,7 @@ type Account struct { IMAPPort int `json:"imap_port,omitempty"` SMTPServer string `json:"smtp_server,omitempty"` SMTPPort int `json:"smtp_port,omitempty"` + Insecure bool `json:"insecure,omitempty"` } // MailingList represents a named group of email addresses. @@ -170,6 +171,7 @@ func LoadConfig() (*Config, error) { IMAPPort int `json:"imap_port,omitempty"` SMTPServer string `json:"smtp_server,omitempty"` SMTPPort int `json:"smtp_port,omitempty"` + Insecure bool `json:"insecure,omitempty"` } type diskConfig struct { Accounts []rawAccount `json:"accounts"` @@ -215,6 +217,7 @@ func LoadConfig() (*Config, error) { IMAPPort: rawAcc.IMAPPort, SMTPServer: rawAcc.SMTPServer, SMTPPort: rawAcc.SMTPPort, + Insecure: rawAcc.Insecure, } if rawAcc.Password != "" { diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index 1023b87284bae840d5ae02eda4c848ff7150aae3..c3d134ef03623e1315eb2bd59976557d13f125ef 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -2,6 +2,7 @@ package fetcher import ( "bytes" + "crypto/tls" "encoding/base64" "fmt" "io" @@ -111,9 +112,30 @@ func connect(account *config.Account) (*client.Client, error) { } addr := fmt.Sprintf("%s:%d", imapServer, imapPort) - c, err := client.DialTLS(addr, nil) - if err != nil { - return nil, err + + tlsConfig := &tls.Config{ + ServerName: imapServer, + InsecureSkipVerify: account.Insecure, + } + + var c *client.Client + var err error + + // If using standard non-implicit ports (1143 or 143), use Dial + STARTTLS + if imapPort == 1143 || imapPort == 143 { + c, err = client.Dial(addr) + if err != nil { + return nil, err + } + if err := c.StartTLS(tlsConfig); err != nil { + return nil, err + } + } else { + // Otherwise default to implicit TLS (port 993) + c, err = client.DialTLS(addr, tlsConfig) + if err != nil { + return nil, err + } } if err := c.Login(account.Email, account.Password); err != nil { diff --git a/sender/sender.go b/sender/sender.go index e21cb60860cf9da85f952fc2d8a0de2781e4623d..3e3125d3364299b83a9fba1a5e2e21b963466de6 100644 --- a/sender/sender.go +++ b/sender/sender.go @@ -3,6 +3,7 @@ package sender import ( "bytes" "crypto/rand" + "crypto/tls" "encoding/base64" "fmt" "mime" @@ -166,7 +167,63 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody allRecipients = append(allRecipients, bcc...) addr := fmt.Sprintf("%s:%d", smtpServer, smtpPort) - return smtp.SendMail(addr, auth, account.Email, allRecipients, msg.Bytes()) + + // Custom SMTP dialer to support skipping TLS verification for Proton Bridge + c, err := smtp.Dial(addr) + if err != nil { + return err + } + defer c.Close() + + if err = c.Hello("localhost"); err != nil { + return err + } + + // Trigger STARTTLS if supported + if ok, _ := c.Extension("STARTTLS"); ok { + tlsConfig := &tls.Config{ + ServerName: smtpServer, + InsecureSkipVerify: account.Insecure, + } + if err = c.StartTLS(tlsConfig); err != nil { + return err + } + } + + // Authenticate + if auth != nil { + if ok, _ := c.Extension("AUTH"); ok { + if err = c.Auth(auth); err != nil { + return err + } + } + } + + // Send Envelope + if err = c.Mail(account.Email); err != nil { + return err + } + for _, r := range allRecipients { + if err = c.Rcpt(r); err != nil { + return err + } + } + + // Write Data + w, err := c.Data() + if err != nil { + return err + } + _, err = w.Write(msg.Bytes()) + if err != nil { + return err + } + err = w.Close() + if err != nil { + return err + } + + return c.Quit() } // wrapBase64 wraps base64-encoded data at 76 characters per line as required by MIME.