package checker import ( "bufio" "fmt" "net" "strings" ) // EHLOHostname is the hostname sent in the SMTP EHLO command during STARTTLS // negotiation. Override it at startup (e.g. via -ldflags or programmatically) // to match the identity of the host running the checker. var EHLOHostname = "checker.localhost" func init() { registerStartTLS("smtp", starttlsSMTP) registerStartTLS("submission", starttlsSMTP) } // starttlsSMTP implements ESMTP EHLO + STARTTLS (RFC 3207). func starttlsSMTP(conn net.Conn, sni string) error { rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) if err := readSMTPGreeting(rw.Reader); err != nil { return fmt.Errorf("read greeting: %w", err) } if _, err := fmt.Fprintf(rw, "EHLO %s\r\n", EHLOHostname); err != nil { return fmt.Errorf("write ehlo: %w", err) } if err := rw.Flush(); err != nil { return fmt.Errorf("flush ehlo: %w", err) } lines, err := readSMTPResponse(rw.Reader) if err != nil { return fmt.Errorf("read ehlo: %w", err) } if !hasSTARTTLSExt(lines) { return fmt.Errorf("%w: EHLO did not advertise STARTTLS", errStartTLSNotOffered) } if _, err := rw.WriteString("STARTTLS\r\n"); err != nil { return fmt.Errorf("write starttls: %w", err) } if err := rw.Flush(); err != nil { return fmt.Errorf("flush starttls: %w", err) } resp, err := readSMTPResponse(rw.Reader) if err != nil { return fmt.Errorf("read starttls: %w", err) } if len(resp) == 0 || !strings.HasPrefix(resp[0], "220") { return fmt.Errorf("server refused STARTTLS: %s", strings.Join(resp, " / ")) } return nil } func readSMTPGreeting(r *bufio.Reader) error { _, err := readSMTPResponse(r) return err } // readSMTPResponse reads one multi-line SMTP response (lines with "NNN-" are // continuation, "NNN " terminates). func readSMTPResponse(r *bufio.Reader) ([]string, error) { var out []string for { line, err := readLineLimited(r) if err != nil { return out, err } line = strings.TrimRight(line, "\r\n") out = append(out, line) if len(line) < 4 || line[3] == ' ' { return out, nil } } } func hasSTARTTLSExt(lines []string) bool { for _, l := range lines { if len(l) < 4 { continue } rest := strings.ToUpper(strings.TrimSpace(l[4:])) if rest == "STARTTLS" || strings.HasPrefix(rest, "STARTTLS ") { return true } } return false }