Skip to content

Commit 58da15f

Browse files
authored
fix: support SMTP LOGIN auth and implicit TLS for port 465 (#243)
1 parent e0121bf commit 58da15f

1 file changed

Lines changed: 72 additions & 19 deletions

File tree

sender/sender.go

Lines changed: 72 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,31 @@ import (
2323
"go.mozilla.org/pkcs7"
2424
)
2525

26+
// loginAuth implements the SMTP LOGIN authentication mechanism.
27+
// Some SMTP servers (e.g. Mailo) only support LOGIN and not PLAIN.
28+
type loginAuth struct {
29+
username, password string
30+
}
31+
32+
func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) {
33+
return "LOGIN", nil, nil
34+
}
35+
36+
func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) {
37+
if !more {
38+
return nil, nil
39+
}
40+
prompt := strings.TrimSpace(string(fromServer))
41+
switch strings.ToLower(prompt) {
42+
case "username:":
43+
return []byte(a.username), nil
44+
case "password:":
45+
return []byte(a.password), nil
46+
default:
47+
return nil, fmt.Errorf("unexpected LOGIN prompt: %s", prompt)
48+
}
49+
}
50+
2651
// generateMessageID creates a unique Message-ID header.
2752
func generateMessageID(from string) string {
2853
buf := make([]byte, 16)
@@ -42,7 +67,8 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody
4267
return fmt.Errorf("unsupported or missing service_provider: %s", account.ServiceProvider)
4368
}
4469

45-
auth := smtp.PlainAuth("", account.Email, account.Password, smtpServer)
70+
plainAuth := smtp.PlainAuth("", account.Email, account.Password, smtpServer)
71+
loginAuthFallback := &loginAuth{username: account.Email, password: account.Password}
4672

4773
fromHeader := account.FetchEmail
4874
if account.Name != "" {
@@ -325,37 +351,64 @@ func SendEmail(account *config.Account, to, cc, bcc []string, subject, plainBody
325351

326352
addr := fmt.Sprintf("%s:%d", smtpServer, smtpPort)
327353

328-
// Custom SMTP dialer to support skipping TLS verification for Proton Bridge
329-
c, err := smtp.Dial(addr)
330-
if err != nil {
331-
return err
354+
tlsConfig := &tls.Config{
355+
ServerName: smtpServer,
356+
InsecureSkipVerify: account.Insecure,
332357
}
333-
defer c.Close()
334358

335-
if err = c.Hello("localhost"); err != nil {
336-
return err
337-
}
359+
var c *smtp.Client
338360

339-
// Trigger STARTTLS if supported
340-
if ok, _ := c.Extension("STARTTLS"); ok {
341-
tlsConfig := &tls.Config{
342-
ServerName: smtpServer,
343-
InsecureSkipVerify: account.Insecure,
361+
// Port 465 uses implicit TLS (the connection starts with TLS).
362+
// All other ports use plain TCP with optional STARTTLS upgrade.
363+
if smtpPort == 465 {
364+
conn, err := tls.Dial("tcp", addr, tlsConfig)
365+
if err != nil {
366+
return err
344367
}
345-
if err = c.StartTLS(tlsConfig); err != nil {
368+
c, err = smtp.NewClient(conn, smtpServer)
369+
if err != nil {
370+
conn.Close()
346371
return err
347372
}
373+
} else {
374+
var err error
375+
c, err = smtp.Dial(addr)
376+
if err != nil {
377+
return err
378+
}
379+
}
380+
defer c.Close()
381+
382+
if err = c.Hello("localhost"); err != nil {
383+
return err
348384
}
349385

350-
// Authenticate
351-
if auth != nil {
352-
if ok, _ := c.Extension("AUTH"); ok {
353-
if err = c.Auth(auth); err != nil {
386+
// Trigger STARTTLS if supported (not needed for implicit TLS on port 465)
387+
if smtpPort != 465 {
388+
if ok, _ := c.Extension("STARTTLS"); ok {
389+
if err = c.StartTLS(tlsConfig); err != nil {
354390
return err
355391
}
356392
}
357393
}
358394

395+
// Authenticate using the best available mechanism.
396+
// c.Extension("AUTH") returns the list of supported mechanisms.
397+
if ok, mechs := c.Extension("AUTH"); ok {
398+
mechList := strings.ToUpper(mechs)
399+
if strings.Contains(mechList, "PLAIN") {
400+
err = c.Auth(plainAuth)
401+
} else if strings.Contains(mechList, "LOGIN") {
402+
err = c.Auth(loginAuthFallback)
403+
} else {
404+
// Fall back to PLAIN and let the server decide
405+
err = c.Auth(plainAuth)
406+
}
407+
if err != nil {
408+
return err
409+
}
410+
}
411+
359412
// Send Envelope
360413
if err = c.Mail(account.Email); err != nil {
361414
return err

0 commit comments

Comments
 (0)