Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions fetcher/fetcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -416,17 +416,24 @@ func connectWithOptions(account *config.Account, extraOpts *imapclient.Options)
var c *imapclient.Client
var err error

// If using standard non-implicit ports (1143 or 143), use DialStartTLS
if imapPort == 1143 || imapPort == 143 {
c, err = imapclient.DialStartTLS(addr, options)
// Port 993 always uses implicit TLS. For all other ports, try STARTTLS
// first to support local bridges (e.g. Proton Mail Bridge) that serve
// IMAP on non-standard ports. If STARTTLS fails, fall back to implicit
// TLS for servers that use non-standard ports for implicit TLS
// (e.g. GreenMail on port 3993).
if imapPort == 993 {
c, err = imapclient.DialTLS(addr, options)
if err != nil {
return nil, err
}
} else {
// Otherwise default to implicit TLS (port 993)
c, err = imapclient.DialTLS(addr, options)
c, err = imapclient.DialStartTLS(addr, options)
if err != nil {
return nil, err
// STARTTLS failed; try implicit TLS.
c, err = imapclient.DialTLS(addr, options)
if err != nil {
return nil, err
}
}
}

Expand Down
26 changes: 20 additions & 6 deletions fetcher/fetcher_chunk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,14 @@ func TestFetchMailboxEmailsUsesRequestedLimitForSmallFetchChunks(t *testing.T) {
func startFetchRecorderIMAPServer(t *testing.T, messages uint32, fetchCommands chan<- string) (string, func()) {
t.Helper()

listener, err := tls.Listen("tcp", "127.0.0.1:0", &tls.Config{
Certificates: []tls.Certificate{newTestTLSCertificate(t)},
})
// Use plain TCP with STARTTLS support so the test matches the real-world
// connection flow used by connectWithOptions for non-993 ports.
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("starting test IMAP server: %v", err)
}

cert := newTestTLSCertificate(t)
var closeOnce sync.Once
var connMu sync.Mutex
var conn net.Conn
Expand All @@ -100,13 +101,13 @@ func startFetchRecorderIMAPServer(t *testing.T, messages uint32, fetchCommands c
connMu.Lock()
conn = accepted
connMu.Unlock()
serveFetchRecorderIMAPConn(accepted, messages, fetchCommands)
serveFetchRecorderIMAPConn(accepted, messages, fetchCommands, cert)
}()

return listener.Addr().String(), closeServer
}

func serveFetchRecorderIMAPConn(conn net.Conn, messages uint32, fetchCommands chan<- string) {
func serveFetchRecorderIMAPConn(conn net.Conn, messages uint32, fetchCommands chan<- string, cert tls.Certificate) {
defer conn.Close()

reader := bufio.NewReader(conn)
Expand Down Expand Up @@ -136,12 +137,25 @@ func serveFetchRecorderIMAPConn(conn net.Conn, messages uint32, fetchCommands ch
tag := fields[0]
switch strings.ToUpper(fields[1]) {
case "CAPABILITY":
if !writeIMAPLine("* CAPABILITY IMAP4rev1 AUTH=PLAIN") {
if !writeIMAPLine("* CAPABILITY IMAP4rev1 AUTH=PLAIN STARTTLS") {
return
}
if !writeIMAPLine("%s OK CAPABILITY completed", tag) {
return
}
case "STARTTLS":
if !writeIMAPLine("%s OK BEGIN TLS negotiation", tag) {
return
}
writer.Flush()
tlsConn := tls.Server(conn, &tls.Config{
Certificates: []tls.Certificate{cert},
})
if err := tlsConn.Handshake(); err != nil {
return
}
reader = bufio.NewReader(tlsConn)
writer = bufio.NewWriter(tlsConn)
case "LOGIN":
if !writeIMAPLine("%s OK LOGIN completed", tag) {
return
Expand Down