Closes: https://todo.sr.ht/~sircmpwn/koushin/49master
@@ -4,6 +4,12 @@ | |||
## Usage | |||
Assuming SRV DNS records are properly set up (see [RFC 6186]): | |||
go run example.org | |||
To manually specify upstream servers: | |||
go run ./cmd/koushin imaps://mail.example.org:993 smtps://mail.example.org:465 | |||
See `-h` for more information. | |||
@@ -55,6 +61,7 @@ Send patches on the [mailing list], report bugs on the [issue tracker]. | |||
MIT | |||
[RFC 6186]: https://tools.ietf.org/html/rfc6186 | |||
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/koushin#GoPlugin | |||
[mailing list]: https://lists.sr.ht/~sircmpwn/koushin | |||
[issue tracker]: https://todo.sr.ht/~sircmpwn/koushin |
@@ -0,0 +1,66 @@ | |||
package koushin | |||
import ( | |||
"fmt" | |||
"net" | |||
"net/url" | |||
"strings" | |||
) | |||
func discoverTCP(service, name string) (string, error) { | |||
_, addrs, err := net.LookupSRV(service, "tcp", name) | |||
if dnsErr, ok := err.(*net.DNSError); ok { | |||
if dnsErr.IsTemporary { | |||
return "", err | |||
} | |||
} else if err != nil { | |||
return "", err | |||
} | |||
if len(addrs) == 0 { | |||
return "", nil | |||
} | |||
addr := addrs[0] | |||
target := strings.TrimSuffix(addr.Target, ".") | |||
if target == "" { | |||
return "", nil | |||
} | |||
return fmt.Sprintf("%v:%v", target, addr.Port), nil | |||
} | |||
// discoverIMAP performs a DNS-based IMAP service discovery, as defined in | |||
// RFC 6186 section 3.2. | |||
func discoverIMAP(domain string) (*url.URL, error) { | |||
imapsHost, err := discoverTCP("imaps", domain) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if imapsHost != "" { | |||
return &url.URL{Scheme: "imaps", Host: imapsHost}, nil | |||
} | |||
imapHost, err := discoverTCP("imap", domain) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if imapHost != "" { | |||
return &url.URL{Scheme: "imap", Host: imapHost}, nil | |||
} | |||
return nil, fmt.Errorf("IMAP service discovery not configured for domain %q", domain) | |||
} | |||
// discoverSMTP performs a DNS-based SMTP submission service discovery, as | |||
// defined in RFC 6186 section 3.1. | |||
func discoverSMTP(domain string) (*url.URL, error) { | |||
host, err := discoverTCP("submission", domain) | |||
if err != nil { | |||
return nil, err | |||
} | |||
if host == "" { | |||
return nil, fmt.Errorf("SMTP service discovery not configured for domain %q", domain) | |||
} | |||
return &url.URL{Scheme: "smtp", Host: host}, nil | |||
} |
@@ -108,12 +108,18 @@ func (s *Server) parseIMAPUpstream() error { | |||
return fmt.Errorf("failed to parse upstream IMAP server: %v", err) | |||
} | |||
if u.Scheme == "" { | |||
u, err = discoverIMAP(u.Host) | |||
if err != nil { | |||
return fmt.Errorf("failed to discover IMAP server: %v", err) | |||
} | |||
} | |||
s.imap.host = u.Host | |||
switch u.Scheme { | |||
case "imap": | |||
// This space is intentionally left blank | |||
case "imaps", "": | |||
// TODO: auto-discovery for empty scheme | |||
case "imaps": | |||
s.imap.tls = true | |||
case "imap+insecure": | |||
s.imap.insecure = true | |||
@@ -133,12 +139,19 @@ func (s *Server) parseSMTPUpstream() error { | |||
return fmt.Errorf("failed to parse upstream SMTP server: %v", err) | |||
} | |||
if u.Scheme == "" { | |||
u, err = discoverSMTP(u.Host) | |||
if err != nil { | |||
s.e.Logger.Printf("Failed to discover SMTP server: %v", err) | |||
return nil | |||
} | |||
} | |||
s.smtp.host = u.Host | |||
switch u.Scheme { | |||
case "smtp": | |||
// This space is intentionally left blank | |||
case "smtps", "": | |||
// TODO: auto-discovery for empty scheme | |||
case "smtps": | |||
s.smtp.tls = true | |||
case "smtp+insecure": | |||
s.smtp.insecure = true | |||