mirror of
https://github.com/hanzoskill/sogcli.git
synced 2026-10-30 21:03:29 +00:00
154 lines
3.6 KiB
Go
154 lines
3.6 KiB
Go
// Package discover provides email server auto-discovery.
|
|
package discover
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
)
|
|
|
|
// ServerConfig holds discovered server settings.
|
|
type ServerConfig struct {
|
|
Host string
|
|
Port int
|
|
}
|
|
|
|
// Result holds the discovered IMAP and SMTP settings.
|
|
type Result struct {
|
|
IMAP *ServerConfig
|
|
SMTP *ServerConfig
|
|
}
|
|
|
|
// Discover attempts to find IMAP and SMTP servers for an email domain.
|
|
// It tries SRV records first, then falls back to common hostnames.
|
|
func Discover(email string) (*Result, error) {
|
|
parts := strings.Split(email, "@")
|
|
if len(parts) != 2 {
|
|
return nil, fmt.Errorf("invalid email address: %s", email)
|
|
}
|
|
domain := parts[1]
|
|
|
|
result := &Result{}
|
|
|
|
// Try IMAP SRV records
|
|
if srv := lookupSRV("imaps", "tcp", domain); srv != nil {
|
|
result.IMAP = srv
|
|
} else if srv := lookupSRV("imap", "tcp", domain); srv != nil {
|
|
result.IMAP = srv
|
|
}
|
|
|
|
// Try SMTP SRV records
|
|
if srv := lookupSRV("submission", "tcp", domain); srv != nil {
|
|
result.SMTP = srv
|
|
} else if srv := lookupSRV("smtp", "tcp", domain); srv != nil {
|
|
result.SMTP = srv
|
|
}
|
|
|
|
// Fall back to common hostnames
|
|
if result.IMAP == nil {
|
|
result.IMAP = tryCommonHosts(domain, []string{
|
|
"imap.%s",
|
|
"mail.%s",
|
|
"imap.mail.%s",
|
|
}, 993)
|
|
}
|
|
|
|
if result.SMTP == nil {
|
|
result.SMTP = tryCommonHosts(domain, []string{
|
|
"smtp.%s",
|
|
"mail.%s",
|
|
"smtp.mail.%s",
|
|
}, 587)
|
|
}
|
|
|
|
// Check for well-known providers
|
|
result = applyWellKnownProviders(domain, result)
|
|
|
|
if result.IMAP == nil && result.SMTP == nil {
|
|
return nil, fmt.Errorf("could not discover servers for %s", domain)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
func lookupSRV(service, proto, domain string) *ServerConfig {
|
|
_, addrs, err := net.LookupSRV(service, proto, domain)
|
|
if err != nil || len(addrs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// Use the highest priority (lowest number) server
|
|
srv := addrs[0]
|
|
return &ServerConfig{
|
|
Host: strings.TrimSuffix(srv.Target, "."),
|
|
Port: int(srv.Port),
|
|
}
|
|
}
|
|
|
|
func tryCommonHosts(domain string, patterns []string, defaultPort int) *ServerConfig {
|
|
for _, pattern := range patterns {
|
|
host := fmt.Sprintf(pattern, domain)
|
|
if _, err := net.LookupHost(host); err == nil {
|
|
return &ServerConfig{
|
|
Host: host,
|
|
Port: defaultPort,
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func applyWellKnownProviders(domain string, result *Result) *Result {
|
|
// Google Workspace / Gmail
|
|
if isGoogleDomain(domain) {
|
|
if result.IMAP == nil {
|
|
result.IMAP = &ServerConfig{Host: "imap.gmail.com", Port: 993}
|
|
}
|
|
if result.SMTP == nil {
|
|
result.SMTP = &ServerConfig{Host: "smtp.gmail.com", Port: 587}
|
|
}
|
|
}
|
|
|
|
// Microsoft 365 / Outlook
|
|
if isMicrosoftDomain(domain) {
|
|
if result.IMAP == nil {
|
|
result.IMAP = &ServerConfig{Host: "outlook.office365.com", Port: 993}
|
|
}
|
|
if result.SMTP == nil {
|
|
result.SMTP = &ServerConfig{Host: "smtp.office365.com", Port: 587}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func isGoogleDomain(domain string) bool {
|
|
// Check MX records for Google
|
|
mxs, err := net.LookupMX(domain)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, mx := range mxs {
|
|
if strings.Contains(strings.ToLower(mx.Host), "google") ||
|
|
strings.Contains(strings.ToLower(mx.Host), "googlemail") {
|
|
return true
|
|
}
|
|
}
|
|
return domain == "gmail.com" || domain == "googlemail.com"
|
|
}
|
|
|
|
func isMicrosoftDomain(domain string) bool {
|
|
// Check MX records for Microsoft
|
|
mxs, err := net.LookupMX(domain)
|
|
if err != nil {
|
|
return false
|
|
}
|
|
for _, mx := range mxs {
|
|
if strings.Contains(strings.ToLower(mx.Host), "outlook") ||
|
|
strings.Contains(strings.ToLower(mx.Host), "microsoft") {
|
|
return true
|
|
}
|
|
}
|
|
return domain == "outlook.com" || domain == "hotmail.com" || domain == "live.com"
|
|
}
|