@@ -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.
2752func 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