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
|
## 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
|
||||||
|
|
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)
|
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", "":
|
case "imaps":
|
||||||
// TODO: auto-discovery for empty scheme
|
|
||||||
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", "":
|
case "smtps":
|
||||||
// TODO: auto-discovery for empty scheme
|
|
||||||
s.smtp.tls = true
|
s.smtp.tls = true
|
||||||
case "smtp+insecure":
|
case "smtp+insecure":
|
||||||
s.smtp.insecure = true
|
s.smtp.insecure = true
|
||||||
|
|
Loading…
Reference in a new issue