package main import ( "crypto/rand" "crypto/sha256" "crypto/tls" "encoding/hex" "fmt" "log" "net" "net/smtp" "time" ) func (s *Server) smtpGet(key string) string { var val string s.db.QueryRow("SELECT value FROM server_smtp_config WHERE key=?", key).Scan(&val) return val } func (s *Server) smtpSet(key, val string) error { _, err := s.db.Exec("INSERT OR REPLACE INTO server_smtp_config (key, value) VALUES (?, ?)", key, val) return err } func sha256Hex(s string) string { h := sha256.Sum256([]byte(s)) return hex.EncodeToString(h[:]) } func genDeviceToken() (token, prefix, suffix string) { b := make([]byte, 32) rand.Read(b) token = "vs_dev_" + hex.EncodeToString(b) prefix = token[:16] suffix = token[len(token)-8:] return } func sel(v, want string) string { if v == want { return " selected" } return "" } func (s *Server) smtpConnect(host, port, user, pass, security string) (*smtp.Client, error) { addr := net.JoinHostPort(host, port) switch security { case "tls": tlsCfg := &tls.Config{ServerName: host} conn, err := tls.Dial("tcp", addr, tlsCfg) if err != nil { return nil, fmt.Errorf("tls dial: %w", err) } cl, err := smtp.NewClient(conn, host) if err != nil { conn.Close() return nil, fmt.Errorf("smtp client: %w", err) } return cl, nil default: conn, err := net.DialTimeout("tcp", addr, 10*time.Second) if err != nil { return nil, fmt.Errorf("connect: %w", err) } cl, err := smtp.NewClient(conn, host) if err != nil { conn.Close() return nil, fmt.Errorf("smtp client: %w", err) } if security != "none" { if ok, _ := cl.Extension("STARTTLS"); ok { tlsCfg := &tls.Config{ServerName: host} if err := cl.StartTLS(tlsCfg); err != nil { cl.Close() return nil, fmt.Errorf("starttls: %w", err) } } } return cl, nil } } func (s *Server) smtpSendMsg(cl *smtp.Client, user, pass, host, from, to string, msg []byte) error { if user != "" { auth := smtp.PlainAuth("", user, pass, host) if err := cl.Auth(auth); err != nil { return fmt.Errorf("auth: %w", err) } } if err := cl.Mail(from); err != nil { return fmt.Errorf("mail from: %w", err) } if err := cl.Rcpt(to); err != nil { return fmt.Errorf("rcpt: %w", err) } w, err := cl.Data() if err != nil { return fmt.Errorf("data: %w", err) } if _, err := w.Write(msg); err != nil { w.Close() return fmt.Errorf("write: %w", err) } if err := w.Close(); err != nil { return fmt.Errorf("send: %w", err) } return nil } func (s *Server) smtpSend(to, subject, body string) error { host := s.smtpGet("smtp_host") port := s.smtpGet("smtp_port") user := s.smtpGet("smtp_user") pass := s.smtpGet("smtp_pass") from := s.smtpGet("smtp_from") security := s.smtpGet("smtp_security") if host == "" || port == "" || from == "" { err := fmt.Errorf("SMTP not configured") log.Printf("smtp: %v (to=%s)", err, to) return err } log.Printf("smtp: sending to %s via %s:%s (security=%s)", to, host, port, security) msg := []byte("From: " + from + "\r\n" + "To: " + to + "\r\n" + "Subject: " + subject + "\r\n" + "MIME-Version: 1.0\r\n" + "Content-Type: text/plain; charset=UTF-8\r\n" + "\r\n" + body + "\r\n") cl, err := s.smtpConnect(host, port, user, pass, security) if err != nil { log.Printf("smtp: connect error: %v", err) return err } defer cl.Close() if err := s.smtpSendMsg(cl, user, pass, host, from, to, msg); err != nil { log.Printf("smtp: send error: %v", err) return err } log.Printf("smtp: sent OK to %s", to) return nil } func (s *Server) smtpTest(host, port, user, pass, security, from, to string) error { if host == "" || port == "" || from == "" { return fmt.Errorf("SMTP not configured") } msg := []byte("From: " + from + "\r\nTo: " + to + "\r\nSubject: Test from Verstak Sync\r\n\r\nThis is a test email from Verstak Sync Server.\r\n") cl, err := s.smtpConnect(host, port, user, pass, security) if err != nil { return err } defer cl.Close() return s.smtpSendMsg(cl, user, pass, host, from, to, msg) }