package smtp import ( "crypto/tls" "fmt" "net" "net/smtp" "net/url" "time" ) // Client represents an SMTP client for sending emails type Client struct { host string port string username string password string sender string tls bool startTLS bool } // Config holds SMTP configuration type Config struct { Address string Username string Password string Sender string } // NewClient creates a new SMTP client from Authelia configuration func NewClient(cfg Config) (*Client, error) { if cfg.Address == "" { return nil, fmt.Errorf("SMTP address is required") } // Parse the address (format: "submission://host:port") u, err := url.Parse(cfg.Address) if err != nil { return nil, fmt.Errorf("invalid SMTP address format: %w", err) } host := u.Hostname() port := u.Port() if port == "" { // Default ports based on scheme switch u.Scheme { case "smtp": port = "25" case "smtps": port = "465" case "submission": port = "587" default: port = "587" } } // Determine TLS mode based on scheme var useTLS, useStartTLS bool switch u.Scheme { case "smtp": // Plain SMTP, no TLS useTLS = false useStartTLS = false case "smtps": // Implicit TLS (SMTPS) useTLS = true useStartTLS = false case "submission": // STARTTLS on port 587 useTLS = false useStartTLS = true default: // Default to STARTTLS for unknown schemes useTLS = false useStartTLS = true } return &Client{ host: host, port: port, username: cfg.Username, password: cfg.Password, sender: cfg.Sender, tls: useTLS, startTLS: useStartTLS, }, nil } // SendWelcomeEmail sends a welcome email to a new user func (c *Client) SendWelcomeEmail(toEmail, username, placeholderPassword string) error { subject := "Welcome to Authelia - Set Your Password" body := c.generateWelcomeEmail(username, placeholderPassword) // Prepare message message := fmt.Sprintf(`From: %s To: %s Subject: %s Content-Type: text/html; charset=UTF-8 %s`, c.sender, toEmail, subject, body) // Connect to SMTP server var auth smtp.Auth if c.username != "" && c.password != "" { auth = smtp.PlainAuth("", c.username, c.password, c.host) } addr := fmt.Sprintf("%s:%s", c.host, c.port) // Send email based on TLS configuration if c.tls { // Implicit TLS (SMTPS) tlsConfig := &tls.Config{ ServerName: c.host, MinVersion: tls.VersionTLS12, } conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { return fmt.Errorf("TLS dial failed: %w", err) } defer conn.Close() client, err := smtp.NewClient(conn, c.host) if err != nil { return fmt.Errorf("SMTP client creation failed: %w", err) } defer client.Close() // Authenticate if needed if auth != nil { if err := client.Auth(auth); err != nil { return fmt.Errorf("SMTP authentication failed: %w", err) } } // Send mail if err := client.Mail(c.sender); err != nil { return fmt.Errorf("SMTP MAIL failed: %w", err) } if err := client.Rcpt(toEmail); err != nil { return fmt.Errorf("SMTP RCPT failed: %w", err) } w, err := client.Data() if err != nil { return fmt.Errorf("SMTP DATA failed: %w", err) } _, err = w.Write([]byte(message)) if err != nil { return fmt.Errorf("SMTP write failed: %w", err) } err = w.Close() if err != nil { return fmt.Errorf("SMTP close failed: %w", err) } client.Quit() } else if c.startTLS { // STARTTLS (plain connection then upgrade) client, err := smtp.Dial(addr) if err != nil { return fmt.Errorf("SMTP dial failed: %w", err) } defer client.Close() // Start TLS tlsConfig := &tls.Config{ ServerName: c.host, MinVersion: tls.VersionTLS12, } if err := client.StartTLS(tlsConfig); err != nil { return fmt.Errorf("STARTTLS failed: %w", err) } // Authenticate if needed if auth != nil { if err := client.Auth(auth); err != nil { return fmt.Errorf("SMTP authentication failed: %w", err) } } // Send mail if err := client.Mail(c.sender); err != nil { return fmt.Errorf("SMTP MAIL failed: %w", err) } if err := client.Rcpt(toEmail); err != nil { return fmt.Errorf("SMTP RCPT failed: %w", err) } w, err := client.Data() if err != nil { return fmt.Errorf("SMTP DATA failed: %w", err) } _, err = w.Write([]byte(message)) if err != nil { return fmt.Errorf("SMTP write failed: %w", err) } err = w.Close() if err != nil { return fmt.Errorf("SMTP close failed: %w", err) } client.Quit() } else { // Plain SMTP (not recommended) err := smtp.SendMail(addr, auth, c.sender, []string{toEmail}, []byte(message)) if err != nil { return fmt.Errorf("SMTP send failed: %w", err) } } fmt.Printf("✓ Welcome email sent to %s\n", toEmail) return nil } // generateWelcomeEmail generates the HTML email content func (c *Client) generateWelcomeEmail(username, placeholderPassword string) string { // Simple HTML email template return fmt.Sprintf(`

Welcome to Authelia

Hello %s,

Your account has been created in our authentication system. To get started, you need to set your password.

Next Steps:

  1. Visit the Authelia portal at: https://auth.sechpoint.app
  2. Click on "Reset Password"
  3. Enter your username: %s
  4. You will receive a password reset email
  5. Follow the instructions in that email to set your password
Important: Do not use the placeholder password below for login.
It is only shown for verification purposes.

If you have any issues, please contact your system administrator.

Best regards,
The Authelia Team

`, username, username) } // TestConnection tests the SMTP connection with current credentials func (c *Client) TestConnection() error { addr := fmt.Sprintf("%s:%s", c.host, c.port) // Test with timeout conn, err := net.DialTimeout("tcp", addr, 10*time.Second) if err != nil { return fmt.Errorf("connection failed: %w", err) } conn.Close() // Don't actually send, just test credentials if c.tls { // Implicit TLS test tlsConfig := &tls.Config{ ServerName: c.host, MinVersion: tls.VersionTLS12, } conn, err := tls.Dial("tcp", addr, tlsConfig) if err != nil { return fmt.Errorf("TLS test failed: %w", err) } conn.Close() } else if c.startTLS { // STARTTLS test client, err := smtp.Dial(addr) if err != nil { return fmt.Errorf("SMTP dial failed: %w", err) } defer client.Close() tlsConfig := &tls.Config{ ServerName: c.host, MinVersion: tls.VersionTLS12, } if err := client.StartTLS(tlsConfig); err != nil { return fmt.Errorf("STARTTLS test failed: %w", err) } } fmt.Printf("✓ SMTP connection test passed: %s:%s\n", c.host, c.port) return nil }