diff --git a/internal/services/services.go b/internal/services/services.go index 7d7e8a4..244f85d 100644 --- a/internal/services/services.go +++ b/internal/services/services.go @@ -40,16 +40,16 @@ type concrete struct { } // For now, bind unconditionally to these ports, on IPv4 + IPv6: -// 25 - incoming SMTP (STARTTLS) -// 587 - outgoing SMTP (STARTTLS) +// 25 - SMTP+MTA (STARTTLS) +// 587 - SMTP+MSA (STARTTLS) // 143 - IMAP (STARTTLS) // 993 - IMAP (TLS) // // Each listener gets incoming connections serviced against the passed-in store. func (c *concrete) Run() { c.servers = []genServer{ - smtp.NewServer(c.cancel, c.store, false), - smtp.NewServer(c.cancel, c.store, true), + smtp.NewMTA(c.cancel, c.store), + smtp.NewMSA(c.cancel, c.store), imap.NewServer(c.cancel, c.store, false), imap.NewServer(c.cancel, c.store, true), diff --git a/internal/smtp/msa.go b/internal/smtp/msa.go new file mode 100644 index 0000000..8afd8e2 --- /dev/null +++ b/internal/smtp/msa.go @@ -0,0 +1,77 @@ +package smtp + +import ( + "context" + "fmt" + "log" + "sync/atomic" + + "github.com/emersion/go-smtp" + + "ur.gs/crockery/internal/store" +) + +type msa struct { + cancel context.CancelFunc + store store.Interface + server *smtp.Server + + // Session IDs + sid uint64 +} + +func NewMSA(cancel context.CancelFunc, datastore store.Interface) Server { + out := &msa{ + cancel: cancel, + store: datastore, + } + + out.server = smtp.NewServer(out) + out.server.Domain = datastore.Domain() + out.server.TLSConfig = datastore.TLSConfig() + out.server.Addr = ":587" + + return out +} + +func (m *msa) Run() { + if err := m.server.ListenAndServe(); err != nil { + log.Printf("Error serving submission %s: %v", m.server.Addr, err) + } else { + log.Printf("Stopped listening on submission %s", m.server.Addr) + } + + m.cancel() +} + +func (m *msa) Close() error { + m.cancel() // FIXME: this doesn't touch the server + + return nil +} + +// Backend implementation for go-smtp after this point + +func (m *msa) Login(user, pass string) (smtp.User, error) { + account, err := m.store.FindAccountWithPassword(user, pass) + if err != nil { + // Log the real error, but don't show it to the end user + log.Printf("Beginning submission session for %s failed: %s", user, err) + return nil, fmt.Errorf("Login failed") + } + + session := &Session{ + ID: atomic.AddUint64(&m.sid, uint64(1)), + Account: account, + Handler: &Sender{}, + } + + log.Printf("Beginning submission session %d for %s", session.ID, user) + // FIXME: TODO: Track ongoing sessions for termination or notifications + + return session, nil +} + +func (m *msa) AnonymousLogin() (smtp.User, error) { + return nil, smtp.AuthRequiredErr +} diff --git a/internal/smtp/mta.go b/internal/smtp/mta.go new file mode 100644 index 0000000..451b468 --- /dev/null +++ b/internal/smtp/mta.go @@ -0,0 +1,71 @@ +package smtp + +import ( + "context" + "fmt" + "log" + "sync/atomic" + + "github.com/emersion/go-smtp" + + "ur.gs/crockery/internal/store" +) + +type mta struct { + cancel context.CancelFunc + store store.Interface + server *smtp.Server + + // Session IDs + sid uint64 +} + +func NewMTA(cancel context.CancelFunc, datastore store.Interface) Server { + out := &mta{ + cancel: cancel, + store: datastore, + } + + out.server = smtp.NewServer(out) + out.server.Domain = datastore.Domain() + out.server.TLSConfig = datastore.TLSConfig() + out.server.Addr = ":25" + out.server.AuthDisabled = true // Don't allow login on the MTA port + + return out +} + +func (m *mta) Run() { + if err := m.server.ListenAndServe(); err != nil { + log.Printf("Error serving SMTP %s: %v", m.server.Addr, err) + } else { + log.Printf("Stopped listening on SMTP %s", m.server.Addr) + } + + m.cancel() +} + +func (m *mta) Close() error { + m.cancel() // FIXME: this doesn't touch the server + + return nil +} + +// Backend implementation for go-smtp after this point + +func (m *mta) Login(user, pass string) (smtp.User, error) { + return nil, fmt.Errorf("Submission to this MTA is not permitted.") +} + +func (m *mta) AnonymousLogin() (smtp.User, error) { + session := &Session{ + ID: atomic.AddUint64(&m.sid, uint64(1)), + Account: nil, + Handler: &Receiver{}, + } + + log.Printf("Beginning SMTP session %d", session.ID) + // FIXME: TODO: Track ongoing sessions for termination or notifications + + return session, nil +} diff --git a/internal/smtp/server.go b/internal/smtp/server.go index 26a3ec6..ee05ea0 100644 --- a/internal/smtp/server.go +++ b/internal/smtp/server.go @@ -1,15 +1,7 @@ package smtp import ( - "context" - "fmt" "io" - "log" - "sync/atomic" - - "github.com/emersion/go-smtp" - - "ur.gs/crockery/internal/store" ) type Server interface { @@ -17,96 +9,3 @@ type Server interface { io.Closer } - -func NewServer(cancel context.CancelFunc, datastore store.Interface, submission bool) Server { - out := &concrete{ - cancel: cancel, - store: datastore, - } - - out.server = smtp.NewServer(out) - out.server.Domain = datastore.Domain() - out.server.TLSConfig = datastore.TLSConfig() - - if submission { - out.handler = &Sender{} - out.server.Addr = ":587" - out.submission = true // Only allow login on the MSA Port - } else { - out.handler = &Receiver{} - out.server.Addr = ":25" - out.server.AuthDisabled = true // Don't allow login on the MTA port - } - - return out -} - -type concrete struct { - name string - cancel context.CancelFunc - store store.Interface - server *smtp.Server - handler Handler - submission bool - - // Session IDs - sid uint64 -} - -func (c *concrete) Run() { - if err := c.server.ListenAndServe(); err != nil { - log.Printf("Error serving SMTP %s: %v", c.server.Addr, err) - } else { - log.Printf("Stopped listening on SMTP %s", c.server.Addr) - } - - c.cancel() -} - -// backend implementation for go-smtp -func (c *concrete) Login(user, pass string) (smtp.User, error) { - if !c.submission { - return nil, fmt.Errorf("Login is disabled") - } - - account, err := c.store.FindAccountWithPassword(user, pass) - if err != nil { - // Lo the real error, but don't show it to the end user - log.Printf("Opening %s session for %s failed: %s", c.name, user, err) - return nil, fmt.Errorf("Login failed") - } - - session := &Session{ - ID: atomic.AddUint64(&c.sid, uint64(1)), - Account: account, - Handler: c.handler, - } - - log.Printf("Beginning %s session %d for %s", c.name, session.ID, user) - // FIXME: TODO: Track ongoing sessions for termination or notifications - - return session, nil -} - -func (c *concrete) AnonymousLogin() (smtp.User, error) { - if c.submission { - return nil, smtp.AuthRequiredErr - } - - session := &Session{ - ID: atomic.AddUint64(&c.sid, uint64(1)), - Account: nil, - Handler: c.handler, - } - - log.Printf("Beginning anonymous %s session %d", c.name, session.ID) - // FIXME: TODO: Track ongoing sessions for termination or notifications - - return session, nil -} - -func (c *concrete) Close() error { - c.cancel() // FIXME: this doesn't touch the server - - return nil -}