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
This commit is contained in:
parent
d5124c9645
commit
db328bf7c3
2 changed files with 89 additions and 36 deletions
|
@ -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 {
|
||||
|
|
117
server.go
117
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.
|
||||
|
|
Loading…
Reference in a new issue