package hammerhead import ( "crypto/tls" "errors" "fmt" "net" "net/http" "net/smtp" "strconv" "strings" "time" ) func (s *Server) getSettings(w http.ResponseWriter, r *http.Request, p *principal) { s.mu.Lock() defer s.mu.Unlock() st := s.st.Settings st.SMTP.Pass = "" // write-only writeJSON(w, 200, map[string]any{"settings": st, "smtpPassSet": s.st.Settings.SMTP.Pass != ""}) } func (s *Server) putSettings(w http.ResponseWriter, r *http.Request, p *principal) { var in Settings if !decode(w, r, &in) { return } in.DashboardURL = strings.TrimRight(strings.TrimSpace(in.DashboardURL), "/") if in.DashboardURL != "" && !strings.HasPrefix(in.DashboardURL, "http://") && !strings.HasPrefix(in.DashboardURL, "https://") { fail(w, 400, errors.New("dashboard URL must start with http:// or https://")) return } if m := in.SMTP.Mode; m != "" && m != "starttls" && m != "tls" && m != "none" { fail(w, 400, errors.New("smtp mode must be starttls, tls or none")) return } s.mu.Lock() if in.SMTP.Pass == "" { in.SMTP.Pass = s.st.Settings.SMTP.Pass // blank keeps the saved password } if s.opts.DashboardURL != "" { in.DashboardURL = s.st.Settings.DashboardURL // pinned by flag/env } in.SelfEdgeID = s.st.Settings.SelfEdgeID // not editable here; losing it would make the next start create a second self edge s.st.Settings = in s.bump() // edges receive the dashboard URL and ACME settings s.mu.Unlock() s.audit("", p.User.Email, "settings.update", "") ok(w) } func (s *Server) testMail(w http.ResponseWriter, r *http.Request, p *principal) { if err := s.Mailer(p.User.Email, "Hammerhead test email", "If you can read this, outgoing email works."); err != nil { fail(w, 400, err) return } ok(w) } func (s *Server) mailConfigured() bool { s.mu.Lock() defer s.mu.Unlock() return s.st.Settings.SMTP.Host != "" } // smtpSend is the default Mailer. func (s *Server) smtpSend(to, subject, body string) error { s.mu.Lock() c := s.st.Settings.SMTP s.mu.Unlock() if c.Host == "" { return errors.New("Email is not set up yet. Set it up under Server, Settings.") } if c.Port == 0 { c.Port = 587 if c.Mode == "tls" { c.Port = 465 } } if c.From == "" { c.From = c.User } addr := net.JoinHostPort(c.Host, strconv.Itoa(c.Port)) msg := fmt.Sprintf("From: %s\r\nTo: %s\r\nSubject: %s\r\nMIME-Version: 1.0\r\nContent-Type: text/plain; charset=utf-8\r\n\r\n%s\r\n", c.From, to, strings.NewReplacer("\r", "", "\n", " ").Replace(subject), body) d := net.Dialer{Timeout: 10 * time.Second} conn, err := d.Dial("tcp", addr) if err != nil { return err } if c.Mode == "tls" { conn = tls.Client(conn, &tls.Config{ServerName: c.Host}) } conn.SetDeadline(time.Now().Add(30 * time.Second)) cl, err := smtp.NewClient(conn, c.Host) if err != nil { conn.Close() return err } defer cl.Close() if c.Mode == "" || c.Mode == "starttls" { if ok, _ := cl.Extension("STARTTLS"); ok { if err := cl.StartTLS(&tls.Config{ServerName: c.Host}); err != nil { return err } } } if c.User != "" { if err := cl.Auth(smtp.PlainAuth("", c.User, c.Pass, c.Host)); err != nil { return err } } if err := cl.Mail(c.From); err != nil { return err } if err := cl.Rcpt(to); err != nil { return err } wc, err := cl.Data() if err != nil { return err } if _, err := wc.Write([]byte(msg)); err != nil { return err } if err := wc.Close(); err != nil { return err } return cl.Quit() }