package checker import ( "bufio" "errors" "io" "net" "strings" "testing" "time" ) // runStartTLS drives upgrader against a fake server. The server callback runs // on the peer end of an in-memory pipe and may read/write the plaintext // dialect transcript. The test deadline guards both ends from hanging. func runStartTLS(t *testing.T, upgrader func(net.Conn, string) error, sni string, server func(net.Conn) error) error { t.Helper() clientConn, serverConn := net.Pipe() deadline := time.Now().Add(2 * time.Second) _ = clientConn.SetDeadline(deadline) _ = serverConn.SetDeadline(deadline) srvErr := make(chan error, 1) go func() { defer serverConn.Close() srvErr <- server(serverConn) }() clientErr := upgrader(clientConn, sni) clientConn.Close() if err := <-srvErr; err != nil && !errors.Is(err, io.EOF) && !errors.Is(err, io.ErrClosedPipe) { t.Logf("server side returned: %v", err) } return clientErr } // readLineCRLF reads one CRLF-terminated line. func readLineCRLF(r *bufio.Reader) (string, error) { line, err := r.ReadString('\n') return strings.TrimRight(line, "\r\n"), err } func TestStartTLS_SMTP_OK(t *testing.T) { err := runStartTLS(t, starttlsSMTP, "mail.example.com", func(c net.Conn) error { br := bufio.NewReader(c) if _, err := io.WriteString(c, "220 mail.example.com ESMTP\r\n"); err != nil { return err } ehlo, err := readLineCRLF(br) if err != nil { return err } if !strings.HasPrefix(ehlo, "EHLO ") { return errors.New("expected EHLO") } if _, err := io.WriteString(c, "250-mail.example.com\r\n250-SIZE 10485760\r\n250 STARTTLS\r\n"); err != nil { return err } stls, err := readLineCRLF(br) if err != nil { return err } if stls != "STARTTLS" { return errors.New("expected STARTTLS") } _, err = io.WriteString(c, "220 ready\r\n") return err }) if err != nil { t.Fatalf("expected success, got: %v", err) } } func TestStartTLS_SMTP_NotAdvertised(t *testing.T) { err := runStartTLS(t, starttlsSMTP, "mail.example.com", func(c net.Conn) error { br := bufio.NewReader(c) _, _ = io.WriteString(c, "220 mail.example.com ESMTP\r\n") if _, err := readLineCRLF(br); err != nil { return err } _, err := io.WriteString(c, "250-mail.example.com\r\n250 SIZE 10485760\r\n") return err }) if !errors.Is(err, errStartTLSNotOffered) { t.Fatalf("expected errStartTLSNotOffered, got: %v", err) } } func TestStartTLS_SMTP_Refused(t *testing.T) { err := runStartTLS(t, starttlsSMTP, "mail.example.com", func(c net.Conn) error { br := bufio.NewReader(c) _, _ = io.WriteString(c, "220 mail.example.com ESMTP\r\n") _, _ = readLineCRLF(br) _, _ = io.WriteString(c, "250-mail.example.com\r\n250 STARTTLS\r\n") _, _ = readLineCRLF(br) _, err := io.WriteString(c, "454 TLS not available\r\n") return err }) if err == nil { t.Fatal("expected refusal error") } if errors.Is(err, errStartTLSNotOffered) { t.Fatalf("refusal should not be classified as not-offered: %v", err) } } func TestStartTLS_IMAP_OK(t *testing.T) { err := runStartTLS(t, starttlsIMAP, "imap.example.com", func(c net.Conn) error { br := bufio.NewReader(c) _, _ = io.WriteString(c, "* OK IMAP4rev1 ready\r\n") cap1, err := readLineCRLF(br) if err != nil { return err } if !strings.HasSuffix(cap1, "CAPABILITY") { return errors.New("expected CAPABILITY") } _, _ = io.WriteString(c, "* CAPABILITY IMAP4rev1 STARTTLS LOGINDISABLED\r\nA001 OK CAPABILITY completed\r\n") stls, err := readLineCRLF(br) if err != nil { return err } if !strings.HasSuffix(stls, "STARTTLS") { return errors.New("expected STARTTLS") } _, err = io.WriteString(c, "A002 OK Begin TLS\r\n") return err }) if err != nil { t.Fatalf("expected success, got: %v", err) } } func TestStartTLS_IMAP_NotAdvertised(t *testing.T) { err := runStartTLS(t, starttlsIMAP, "imap.example.com", func(c net.Conn) error { br := bufio.NewReader(c) _, _ = io.WriteString(c, "* OK IMAP4rev1 ready\r\n") _, _ = readLineCRLF(br) _, err := io.WriteString(c, "* CAPABILITY IMAP4rev1 LOGINDISABLED\r\nA001 OK CAPABILITY completed\r\n") return err }) if !errors.Is(err, errStartTLSNotOffered) { t.Fatalf("expected errStartTLSNotOffered, got: %v", err) } } func TestStartTLS_POP3_OK(t *testing.T) { err := runStartTLS(t, starttlsPOP3, "pop.example.com", func(c net.Conn) error { br := bufio.NewReader(c) _, _ = io.WriteString(c, "+OK POP3 ready\r\n") capa, err := readLineCRLF(br) if err != nil { return err } if capa != "CAPA" { return errors.New("expected CAPA") } _, _ = io.WriteString(c, "+OK capa list\r\nUSER\r\nSTLS\r\n.\r\n") stls, err := readLineCRLF(br) if err != nil { return err } if stls != "STLS" { return errors.New("expected STLS") } _, err = io.WriteString(c, "+OK begin TLS\r\n") return err }) if err != nil { t.Fatalf("expected success, got: %v", err) } } func TestStartTLS_POP3_NotAdvertised(t *testing.T) { err := runStartTLS(t, starttlsPOP3, "pop.example.com", func(c net.Conn) error { br := bufio.NewReader(c) _, _ = io.WriteString(c, "+OK POP3 ready\r\n") _, _ = readLineCRLF(br) _, err := io.WriteString(c, "+OK capa list\r\nUSER\r\n.\r\n") return err }) if !errors.Is(err, errStartTLSNotOffered) { t.Fatalf("expected errStartTLSNotOffered, got: %v", err) } } func TestStartTLS_XMPP_OK(t *testing.T) { err := runStartTLS(t, starttlsXMPPClient, "xmpp.example.com", func(c net.Conn) error { br := bufio.NewReader(c) // Read the client's stream header (one line is enough for our writer). buf := make([]byte, 1024) if _, err := br.Read(buf); err != nil { return err } _, _ = io.WriteString(c, ``+ ``) // Read the request from the client. if _, err := br.Read(buf); err != nil { return err } _, err := io.WriteString(c, ``) return err }) if err != nil { t.Fatalf("expected success, got: %v", err) } } func TestStartTLS_XMPP_NotAdvertised(t *testing.T) { err := runStartTLS(t, starttlsXMPPClient, "xmpp.example.com", func(c net.Conn) error { br := bufio.NewReader(c) buf := make([]byte, 1024) if _, err := br.Read(buf); err != nil { return err } _, err := io.WriteString(c, ``+ `PLAIN`) return err }) if !errors.Is(err, errStartTLSNotOffered) { t.Fatalf("expected errStartTLSNotOffered, got: %v", err) } } func TestStartTLS_LDAP_OK(t *testing.T) { err := runStartTLS(t, starttlsLDAP, "ldap.example.com", func(c net.Conn) error { // Drain the StartTLS request (fixed 31 bytes: 0x30 0x1d + 29 bytes). req := make([]byte, 31) if _, err := io.ReadFull(c, req); err != nil { return err } // Build a minimal ExtendedResponse with resultCode=0. // LDAPMessage SEQUENCE { messageID INTEGER 1, [APPLICATION 24] SEQUENCE { resultCode ENUMERATED 0, matchedDN "", diagnosticMessage "" } } resp := []byte{ 0x30, 0x0c, // SEQUENCE, length 12 0x02, 0x01, 0x01, // messageID = 1 0x78, 0x07, // [APPLICATION 24], length 7 0x0a, 0x01, 0x00, // resultCode ENUMERATED 0 0x04, 0x00, // matchedDN "" 0x04, 0x00, // diagnosticMessage "" } _, err := c.Write(resp) return err }) if err != nil { t.Fatalf("expected success, got: %v", err) } } func TestStartTLS_LDAP_Refused(t *testing.T) { err := runStartTLS(t, starttlsLDAP, "ldap.example.com", func(c net.Conn) error { req := make([]byte, 31) if _, err := io.ReadFull(c, req); err != nil { return err } // resultCode = 53 (unwillingToPerform) -> classified as not-offered. resp := []byte{ 0x30, 0x0c, 0x02, 0x01, 0x01, 0x78, 0x07, 0x0a, 0x01, 0x35, 0x04, 0x00, 0x04, 0x00, } _, err := c.Write(resp) return err }) if !errors.Is(err, errStartTLSNotOffered) { t.Fatalf("expected errStartTLSNotOffered for resultCode 53, got: %v", err) } }