Auto-discover upstream SMTP and IMAP servers
Closes: https://todo.sr.ht/~sircmpwn/koushin/49
This commit is contained in:
parent
db328bf7c3
commit
a0800c2436
3 changed files with 90 additions and 4 deletions
|
@ -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
|
||||
|
|
66
discover.go
Normal file
66
discover.go
Normal file
|
@ -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
|
||||
}
|
21
server.go
21
server.go
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue