Closes: https://todo.sr.ht/~sircmpwn/koushin/49master
@@ -4,6 +4,12 @@ | |||||
## Usage | ## 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 | go run ./cmd/koushin imaps://mail.example.org:993 smtps://mail.example.org:465 | ||||
See `-h` for more information. | See `-h` for more information. | ||||
@@ -55,6 +61,7 @@ Send patches on the [mailing list], report bugs on the [issue tracker]. | |||||
MIT | MIT | ||||
[RFC 6186]: https://tools.ietf.org/html/rfc6186 | |||||
[Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/koushin#GoPlugin | [Go plugin helpers]: https://godoc.org/git.sr.ht/~emersion/koushin#GoPlugin | ||||
[mailing list]: https://lists.sr.ht/~sircmpwn/koushin | [mailing list]: https://lists.sr.ht/~sircmpwn/koushin | ||||
[issue tracker]: https://todo.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) | 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 | s.imap.host = u.Host | ||||
switch u.Scheme { | switch u.Scheme { | ||||
case "imap": | case "imap": | ||||
// This space is intentionally left blank | // This space is intentionally left blank | ||||
case "imaps", "": | |||||
// TODO: auto-discovery for empty scheme | |||||
case "imaps": | |||||
s.imap.tls = true | s.imap.tls = true | ||||
case "imap+insecure": | case "imap+insecure": | ||||
s.imap.insecure = true | s.imap.insecure = true | ||||
@@ -133,12 +139,19 @@ func (s *Server) parseSMTPUpstream() error { | |||||
return fmt.Errorf("failed to parse upstream SMTP server: %v", err) | 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 | s.smtp.host = u.Host | ||||
switch u.Scheme { | switch u.Scheme { | ||||
case "smtp": | case "smtp": | ||||
// This space is intentionally left blank | // This space is intentionally left blank | ||||
case "smtps", "": | |||||
// TODO: auto-discovery for empty scheme | |||||
case "smtps": | |||||
s.smtp.tls = true | s.smtp.tls = true | ||||
case "smtp+insecure": | case "smtp+insecure": | ||||
s.smtp.insecure = true | s.smtp.insecure = true | ||||