aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Ser <contact@emersion.fr>2020-01-20 12:00:04 +0100
committerSimon Ser <contact@emersion.fr>2020-01-20 12:00:04 +0100
commitdb328bf7c374ed7b079d9d8984cb25165605c28e (patch)
tree8e5605d354abff16a86f9928a4461acc31c9327e
parentd5124c964533feff4490873607b1ef77b1e549c7 (diff)
downloadalps-db328bf7c374ed7b079d9d8984cb25165605c28e.tar.gz
alps-db328bf7c374ed7b079d9d8984cb25165605c28e.zip
Generalize upstream server URLs
koushin now takes a list of upstream URLs instead of an IMAP and SMTP URL. This allows to specify upstream server URLs for plugins. In the future, this will allow for auto-discovering upstream servers based on a single domain name. References: https://todo.sr.ht/~sircmpwn/koushin/49
-rw-r--r--cmd/koushin/main.go8
-rw-r--r--server.go117
2 files changed, 89 insertions, 36 deletions
diff --git a/cmd/koushin/main.go b/cmd/koushin/main.go
index 472cd4a..d22b404 100644
--- a/cmd/koushin/main.go
+++ b/cmd/koushin/main.go
@@ -22,20 +22,18 @@ func main() {
flag.StringVar(&addr, "addr", ":1323", "listening address")
flag.Usage = func() {
- fmt.Fprintf(flag.CommandLine.Output(), "usage: koushin [options...] <IMAP URL> [SMTP URL]\n")
+ fmt.Fprintf(flag.CommandLine.Output(), "usage: koushin [options...] <upstream server...>\n")
flag.PrintDefaults()
}
flag.Parse()
- if flag.NArg() < 1 || flag.NArg() > 2 {
+ options.Upstreams = flag.Args()
+ if len(options.Upstreams) == 0 {
flag.Usage()
return
}
- options.IMAPURL = flag.Arg(0)
- options.SMTPURL = flag.Arg(1)
-
e := echo.New()
e.HideBanner = true
if l, ok := e.Logger.(*log.Logger); ok {
diff --git a/server.go b/server.go
index 8e2e7d6..71f40bf 100644
--- a/server.go
+++ b/server.go
@@ -22,6 +22,9 @@ type Server struct {
plugins []Plugin
luaPlugins []Plugin
+ // maps protocols to URLs (protocol can be empty for auto-discovery)
+ upstreams map[string]*url.URL
+
imap struct {
host string
tls bool
@@ -35,45 +38,115 @@ type Server struct {
defaultTheme string
}
-func (s *Server) parseIMAPURL(imapURL string) error {
- u, err := url.Parse(imapURL)
+func newServer(e *echo.Echo, options *Options) (*Server, error) {
+ s := &Server{e: e, defaultTheme: options.Theme}
+
+ s.upstreams = make(map[string]*url.URL, len(options.Upstreams))
+ for _, upstream := range options.Upstreams {
+ u, err := parseUpstream(upstream)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse upstream %q: %v", upstream, err)
+ }
+ if _, ok := s.upstreams[u.Scheme]; ok {
+ return nil, fmt.Errorf("found two upstream servers for scheme %q", u.Scheme)
+ }
+ s.upstreams[u.Scheme] = u
+ }
+
+ if err := s.parseIMAPUpstream(); err != nil {
+ return nil, err
+ }
+ if err := s.parseSMTPUpstream(); err != nil {
+ return nil, err
+ }
+
+ s.Sessions = newSessionManager(s.dialIMAP, s.dialSMTP)
+
+ return s, nil
+}
+
+func parseUpstream(s string) (*url.URL, error) {
+ if !strings.ContainsAny(s, ":/") {
+ // This is a raw domain name, make it an URL with an empty scheme
+ s = "//" + s
+ }
+ return url.Parse(s)
+}
+
+type NoUpstreamError struct {
+ schemes []string
+}
+
+func (err *NoUpstreamError) Error() string {
+ return fmt.Sprintf("no upstream server configured for schemes %v", err.schemes)
+}
+
+// Upstream retrieves the configured upstream server URL for the provided
+// schemes. If no configured upstream server matches, a *NoUpstreamError is
+// returned. An empty URL.Scheme means that the caller needs to perform
+// auto-discovery with URL.Host.
+func (s *Server) Upstream(schemes... string) (*url.URL, error) {
+ var urls []*url.URL
+ for _, scheme := range append(schemes, "") {
+ u, ok := s.upstreams[scheme]
+ if ok {
+ urls = append(urls, u)
+ }
+ }
+ if len(urls) == 0 {
+ return nil, &NoUpstreamError{schemes}
+ }
+ if len(urls) > 1 {
+ return nil, fmt.Errorf("multiple upstream servers are configured for schemes %v", schemes)
+ }
+ return urls[0], nil
+}
+
+func (s *Server) parseIMAPUpstream() error {
+ u, err := s.Upstream("imap", "imaps", "imap+insecure")
if err != nil {
- return fmt.Errorf("failed to parse IMAP server URL: %v", err)
+ return fmt.Errorf("failed to parse upstream IMAP server: %v", err)
}
s.imap.host = u.Host
switch u.Scheme {
case "imap":
// This space is intentionally left blank
- case "imaps":
+ case "imaps", "":
+ // TODO: auto-discovery for empty scheme
s.imap.tls = true
case "imap+insecure":
s.imap.insecure = true
default:
- return fmt.Errorf("unrecognized IMAP URL scheme: %s", u.Scheme)
+ panic("unreachable")
}
+ s.e.Logger.Printf("Configured upstream IMAP server: %v", u)
return nil
}
-func (s *Server) parseSMTPURL(smtpURL string) error {
- u, err := url.Parse(smtpURL)
- if err != nil {
- return fmt.Errorf("failed to parse SMTP server URL: %v", err)
+func (s *Server) parseSMTPUpstream() error {
+ u, err := s.Upstream("smtp", "smtps", "smtp+insecure")
+ if _, ok := err.(*NoUpstreamError); ok {
+ return nil
+ } else if err != nil {
+ return fmt.Errorf("failed to parse upstream SMTP server: %v", err)
}
s.smtp.host = u.Host
switch u.Scheme {
case "smtp":
// This space is intentionally left blank
- case "smtps":
+ case "smtps", "":
+ // TODO: auto-discovery for empty scheme
s.smtp.tls = true
case "smtp+insecure":
s.smtp.insecure = true
default:
- return fmt.Errorf("unrecognized SMTP URL scheme: %s", u.Scheme)
+ panic("unreachable")
}
+ s.e.Logger.Printf("Configured upstream SMTP server: %v", u)
return nil
}
@@ -123,24 +196,6 @@ func (s *Server) Reload() error {
return s.load()
}
-func newServer(e *echo.Echo, options *Options) (*Server, error) {
- s := &Server{e: e, defaultTheme: options.Theme}
-
- if err := s.parseIMAPURL(options.IMAPURL); err != nil {
- return nil, err
- }
-
- if options.SMTPURL != "" {
- if err := s.parseSMTPURL(options.SMTPURL); err != nil {
- return nil, err
- }
- }
-
- s.Sessions = newSessionManager(s.dialIMAP, s.dialSMTP)
-
- return s, nil
-}
-
// Context is the context used by HTTP handlers.
//
// Use a type assertion to get it from a echo.Context:
@@ -197,8 +252,8 @@ func handleUnauthenticated(next echo.HandlerFunc, ctx *Context) error {
}
type Options struct {
- IMAPURL, SMTPURL string
- Theme string
+ Upstreams []string
+ Theme string
}
// New creates a new server.