Browse Source

Auto-discover upstream SMTP and IMAP servers

Closes: https://todo.sr.ht/~sircmpwn/koushin/49
master
Simon Ser 4 years ago
parent
commit
a0800c2436
No known key found for this signature in database GPG Key ID: FDE7BE0E88F5E48
3 changed files with 90 additions and 4 deletions
  1. +7
    -0
      README.md
  2. +66
    -0
      discover.go
  3. +17
    -4
      server.go

+ 7
- 0
README.md View File

@@ -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
- 0
discover.go View 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
}

+ 17
- 4
server.go View File

@@ -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…
Cancel
Save