package autoconfig import ( "context" "encoding/xml" "fmt" "io" "net/http" "strings" "time" ) const autoconfigTimeout = 4 * time.Second type mozillaConfig struct { XMLName xml.Name `xml:"clientConfig"` EmailProvider struct { ID string `xml:"id,attr"` Domain []string `xml:"domain"` DisplayName string `xml:"displayName"` IncomingServers []mozillaServer `xml:"incomingServer"` OutgoingServers []mozillaServer `xml:"outgoingServer"` } `xml:"emailProvider"` } type mozillaServer struct { Type string `xml:"type,attr"` Hostname string `xml:"hostname"` Port int `xml:"port"` SocketType string `xml:"socketType"` Username string `xml:"username"` } func fetchMozillaAutoconfig(ctx context.Context, domain string) (Result, bool) { urls := []string{ fmt.Sprintf("https://autoconfig.%s/mail/config-v1.1.xml", domain), fmt.Sprintf("https://%s/.well-known/autoconfig/mail/config-v1.1.xml", domain), fmt.Sprintf("https://autoconfig.thunderbird.net/v1.1/%s", domain), } client := &http.Client{Timeout: autoconfigTimeout} for _, u := range urls { req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil) if err != nil { continue } req.Header.Set("User-Agent", "Ultimail-Autoconfig/1.0") resp, err := client.Do(req) if err != nil { continue } body, readErr := io.ReadAll(io.LimitReader(resp.Body, 256<<10)) _ = resp.Body.Close() if readErr != nil || resp.StatusCode != http.StatusOK { continue } if r, ok := parseMozillaConfig(body, domain); ok { return r, true } } return Result{}, false } func parseMozillaConfig(data []byte, domain string) (Result, bool) { var cfg mozillaConfig if err := xml.Unmarshal(data, &cfg); err != nil { return Result{}, false } var imapHost string var imapPort int var imapTLS bool var smtpHost string var smtpPort int var smtpTLS bool for _, s := range cfg.EmailProvider.IncomingServers { if strings.EqualFold(s.Type, "imap") && imapHost == "" { imapHost = strings.TrimSpace(s.Hostname) imapPort = s.Port imapTLS = socketUsesTLS(s.SocketType) if imapPort == 0 { imapPort = 993 } } } for _, s := range cfg.EmailProvider.OutgoingServers { if strings.EqualFold(s.Type, "smtp") && smtpHost == "" { smtpHost = strings.TrimSpace(s.Hostname) smtpPort = s.Port smtpTLS = socketUsesTLS(s.SocketType) if smtpPort == 0 { smtpPort = 587 } } } if imapHost == "" && smtpHost == "" { return Result{}, false } name := strings.TrimSpace(cfg.EmailProvider.DisplayName) if name == "" { name = cfg.EmailProvider.ID } if name == "" { name = domain } return Result{ Domain: domain, ProviderID: "autoconfig", ProviderName: name, Source: "autoconfig", Confidence: "medium", IMAPHost: imapHost, IMAPPort: imapPort, IMAPTLS: imapTLS, SMTPHost: smtpHost, SMTPPort: smtpPort, SMTPTLS: smtpTLS, UsernameHint: mozillaUsernameHint(cfg.EmailProvider.IncomingServers), AuthMethods: []string{"password"}, Notes: []string{"Configuration issue de l'autoconfiguration Mozilla/Thunderbird."}, }, true } func socketUsesTLS(socketType string) bool { switch strings.ToUpper(strings.TrimSpace(socketType)) { case "SSL", "TLS", "STARTTLS": return true default: return false } } func mozillaUsernameHint(servers []mozillaServer) string { for _, s := range servers { u := strings.ToLower(strings.TrimSpace(s.Username)) if strings.Contains(u, "%emailaddress%") { return "email" } } return "email" }