aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--discover.go66
-rw-r--r--server.go21
3 files changed, 90 insertions, 4 deletions
diff --git a/README.md b/README.md
index ec90d82..2e3f193 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/discover.go b/discover.go
new file mode 100644
index 0000000..08d4aa1
--- /dev/null
+++ b/discover.go
@@ -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
+}
diff --git a/server.go b/server.go
index 71f40bf..11b55f9 100644
--- a/server.go
+++ b/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