checker-tls/checker/starttls_imap.go

68 lines
1.7 KiB
Go

package checker
import (
"bufio"
"fmt"
"net"
"strings"
)
func init() {
registerStartTLS("imap", starttlsIMAP)
}
// starttlsIMAP implements RFC 3501 STARTTLS.
func starttlsIMAP(conn net.Conn, sni string) error {
rw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn))
if _, err := readLineLimited(rw.Reader); err != nil {
return fmt.Errorf("read greeting: %w", err)
}
if _, err := rw.WriteString("A001 CAPABILITY\r\n"); err != nil {
return fmt.Errorf("write CAPABILITY: %w", err)
}
if err := rw.Flush(); err != nil {
return fmt.Errorf("flush CAPABILITY: %w", err)
}
supportsSTARTTLS := false
for {
line, err := readLineLimited(rw.Reader)
if err != nil {
return fmt.Errorf("read CAPABILITY: %w", err)
}
if strings.Contains(strings.ToUpper(line), "STARTTLS") {
supportsSTARTTLS = true
}
if strings.HasPrefix(line, "A001 ") {
rest := strings.TrimSpace(line[len("A001 "):])
if !strings.HasPrefix(strings.ToUpper(rest), "OK") {
return fmt.Errorf("CAPABILITY rejected by server: %s", rest)
}
break
}
}
if !supportsSTARTTLS {
return fmt.Errorf("%w: IMAP CAPABILITY did not advertise STARTTLS", errStartTLSNotOffered)
}
if _, err := rw.WriteString("A002 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)
}
for {
line, err := readLineLimited(rw.Reader)
if err != nil {
return fmt.Errorf("read STARTTLS response: %w", err)
}
if strings.HasPrefix(line, "A002 OK") {
return nil
}
if strings.HasPrefix(line, "A002 ") {
return fmt.Errorf("server refused STARTTLS: %s", strings.TrimSpace(line))
}
}
}