feat(imap): postkasser kan læses, og det er bevist mod en rigtig server

Handlere til at forbinde, koble fra og hente nu, plus baggrundssynk hvert tiende
minut ved siden af Gmails. Forbindelsen prøves FØR den gemmes: en postkasse, der
blev taget imod og så fejler stille hvert tiende minut, er værre end en, der blev
afvist ved døren, mens man stadig sidder med kodeordet i hånden.

Ingen "set"-liste til deduplikering som Gmail har. Uid'er er monotone, så
last_uid er hele svaret — og den skrives også, når budgettet løb ud, for en
kørsel, der lavede opgaver og glemte hvor langt den nåede, laver dem alle igen
næste gang.

Rundturen køres nu mod go-imap's egen server i processen: flaget post kommer med,
uflaget gør ikke, og fortsættelsen fra et uid henter kun det nyere. De hidtidige
prøver dækkede de rene dele og ville alle have bestået med selve hentningen i
stykker.

Testen kræver et selvsigneret certifikat, og klienten nægter dem. Account.RootCAs
lader en test TILFØJE tillid; der er stadig ingen måde at fjerne den på. Forskellen
er en søm og ikke et hul — en prøve viser, at samme server afvises uden.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kristian Wind
2026-08-18 22:15:50 +02:00
co-authored by Claude Opus 5
parent 1e59886d86
commit 6b4ef5b2f2
7 changed files with 466 additions and 1 deletions
+13 -1
View File
@@ -17,6 +17,7 @@ package imap
import (
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"strings"
@@ -41,6 +42,13 @@ type Account struct {
Username string
Password string // an app-specific password on every host worth naming
Folder string // "INBOX" when empty
// RootCAs adds a certificate authority to trust, on top of the system's. It
// exists so a test can put a real IMAP server behind a self-signed certificate
// and still be refused by everything else — the field can only widen trust,
// never switch verification off, which is the difference between a seam and a
// hole. Nil in production, where the system's roots are the answer.
RootCAs *x509.CertPool
}
// Dial opens a TLS connection and logs in. The caller closes it.
@@ -55,7 +63,11 @@ func Dial(a Account) (*Client, error) {
}
c, err := imapclient.DialTLS(a.Host, &imapclient.Options{
TLSConfig: &tls.Config{ServerName: host, MinVersion: tls.VersionTLS12},
TLSConfig: &tls.Config{
ServerName: host,
MinVersion: tls.VersionTLS12,
RootCAs: a.RootCAs,
},
})
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", a.Host, err)