diff --git a/internal/smtp/receiver.go b/internal/smtp/receiver.go new file mode 100644 index 0000000..429ca48 --- /dev/null +++ b/internal/smtp/receiver.go @@ -0,0 +1,20 @@ +package smtp + +import ( + "fmt" + "io" +) + +type Receiver struct{} + +func (r *Receiver) Name() string { + return "smtp-starttls" +} + +// It seems odd to have a Receiver called Send, but what we're looking for is an +// attempt from an arbitrary source to send an email to known accounts. +// +// We might want to clean this interface +func (r *Receiver) Send(from string, to []string, reader io.Reader) error { + return fmt.Errorf("Not yet implemented") +} diff --git a/internal/smtp/sender.go b/internal/smtp/sender.go new file mode 100644 index 0000000..c22a5e3 --- /dev/null +++ b/internal/smtp/sender.go @@ -0,0 +1,21 @@ +package smtp + +import ( + "fmt" + "io" +) + +type Sender struct{} + +func (s *Sender) Name() string { + return "submission-starttls" +} + +// We're looking for the email to be from a logged-in account, to anywhere +// on the internet - including locally! +// +// Should we handle local delivery differently to remote delivery, or connect +// to ourselves, i.e., from submission, to smtp? +func (s *Sender) Send(from string, to []string, reader io.Reader) error { + return fmt.Errorf("Not yet implemented") +} diff --git a/internal/smtp/server.go b/internal/smtp/server.go index fdbc744..2754bc8 100644 --- a/internal/smtp/server.go +++ b/internal/smtp/server.go @@ -29,10 +29,11 @@ func NewServer(cancel context.CancelFunc, datastore store.Interface, submission out.server.TLSConfig = datastore.TLSConfig() if submission { - out.name = "submission" + out.handler = &Sender{} out.server.Addr = ":587" + out.allowLogin = true // Only allow login on submission ports } else { - out.name = "SMTP" + out.handler = &Receiver{} out.server.Addr = ":25" } @@ -40,10 +41,12 @@ func NewServer(cancel context.CancelFunc, datastore store.Interface, submission } type concrete struct { - name string - cancel context.CancelFunc - store store.Interface - server *smtp.Server + name string + cancel context.CancelFunc + store store.Interface + server *smtp.Server + handler Handler + allowLogin bool // Session IDs sid uint64 @@ -61,6 +64,10 @@ func (c *concrete) Run() { // backend implementation for go-smtp func (c *concrete) Login(user, pass string) (smtp.User, error) { + if !c.allowLogin { + 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 @@ -69,9 +76,9 @@ func (c *concrete) Login(user, pass string) (smtp.User, error) { } session := &Session{ - ID: atomic.AddUint64(&c.sid, uint64(1)), - Account: account, - ServiceName: c.name, + 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) diff --git a/internal/smtp/session.go b/internal/smtp/session.go index 8af9349..0b3da11 100644 --- a/internal/smtp/session.go +++ b/internal/smtp/session.go @@ -1,7 +1,7 @@ package smtp import ( - "fmt" + "bytes" "io" "io/ioutil" "log" @@ -9,26 +9,33 @@ import ( "ur.gs/crockery/internal/store" ) +type Handler interface { + Name() string + Send(from string, to []string, r io.Reader) error +} + // type Session implements the User interface for emersion/go-smtp type Session struct { - ID uint64 - Account *store.Account - ServiceName string + ID uint64 + Account *store.Account + Handler Handler } func (s *Session) Send(from string, to []string, r io.Reader) error { + // FIXME: TODO: don't keep this lot forever, it's for debugging purposes data, err := ioutil.ReadAll(r) if err != nil { return err } - log.Printf("%s session %d for %s: from=%s, to=%s, r=<