From 26c702b11c14c0bcedfb67ad7459987524b13f7e Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 7 Mar 2018 21:58:50 +0000 Subject: [PATCH] Get receiving email a step closer with a modified go-smtp --- Gopkg.lock | 9 +-- Gopkg.toml | 3 +- internal/smtp/server.go | 23 ++++++- vendor/github.com/emersion/go-smtp/backend.go | 4 ++ vendor/github.com/emersion/go-smtp/conn.go | 60 +++++++++++++------ vendor/github.com/emersion/go-smtp/server.go | 4 ++ 6 files changed, 77 insertions(+), 26 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 30fc516..9a7a453 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -40,10 +40,11 @@ revision = "7e096a0a6197b89989e8cc31016daa67c8c62051" [[projects]] - branch = "master" + branch = "8-unauthed-mail" name = "github.com/emersion/go-smtp" packages = ["."] - revision = "a63104657743890cb7c2fd54f15a2725291f6a9f" + revision = "b858ceea28e02e5fab171c5f877860ad9080f8b6" + source = "https://github.com/lupine/go-smtp" [[projects]] branch = "master" @@ -52,7 +53,7 @@ "bcrypt", "blowfish" ] - revision = "91a49db82a88618983a78a06c1cbd4e00ab749ab" + revision = "85f98707c97e11569271e4d9b3d397e079c4f4d0" [[projects]] branch = "master" @@ -81,6 +82,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "9d6a4c3f6c1568091ba837d3207861ceae7cb7b2d4b5e094d1892ed935bd72cd" + inputs-digest = "4f9c6f3cf2ad42694856bc039ddb3a7e78af676f095fc24ef633305fe499c3bf" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 37d27c0..793cfc7 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -26,8 +26,9 @@ [[constraint]] - branch = "master" + branch = "8-unauthed-mail" name = "github.com/emersion/go-smtp" + source = "https://github.com/lupine/go-smtp" [prune] go-tests = true diff --git a/internal/smtp/server.go b/internal/smtp/server.go index 2754bc8..6bf24f7 100644 --- a/internal/smtp/server.go +++ b/internal/smtp/server.go @@ -31,7 +31,7 @@ func NewServer(cancel context.CancelFunc, datastore store.Interface, submission if submission { out.handler = &Sender{} out.server.Addr = ":587" - out.allowLogin = true // Only allow login on submission ports + out.submission = true // Only allow login on submission ports } else { out.handler = &Receiver{} out.server.Addr = ":25" @@ -46,7 +46,7 @@ type concrete struct { store store.Interface server *smtp.Server handler Handler - allowLogin bool + submission bool // Session IDs sid uint64 @@ -64,7 +64,7 @@ func (c *concrete) Run() { // backend implementation for go-smtp func (c *concrete) Login(user, pass string) (smtp.User, error) { - if !c.allowLogin { + if !c.submission { return nil, fmt.Errorf("Login is disabled") } @@ -87,6 +87,23 @@ func (c *concrete) Login(user, pass string) (smtp.User, error) { 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 diff --git a/vendor/github.com/emersion/go-smtp/backend.go b/vendor/github.com/emersion/go-smtp/backend.go index d813880..820c988 100644 --- a/vendor/github.com/emersion/go-smtp/backend.go +++ b/vendor/github.com/emersion/go-smtp/backend.go @@ -8,6 +8,10 @@ import ( type Backend interface { // Authenticate a user. Login(username, password string) (User, error) + + // Called if the client attempts to send mail without logging in first. + // Respond with smtp.AuthRequiredErr if you don't want to support this. + AnonymousLogin() (User, error) } // An authenticated user. diff --git a/vendor/github.com/emersion/go-smtp/conn.go b/vendor/github.com/emersion/go-smtp/conn.go index e6225e0..bd465db 100644 --- a/vendor/github.com/emersion/go-smtp/conn.go +++ b/vendor/github.com/emersion/go-smtp/conn.go @@ -37,6 +37,10 @@ type Conn struct { locker sync.Mutex } +var ( + AuthRequiredErr = fmt.Errorf("Please authenticate first.") +) + func newConn(c net.Conn, s *Server) *Conn { sc := &Conn{ server: s, @@ -64,6 +68,16 @@ func (c *Conn) init() { c.text = textproto.NewConn(rwc) } +func (c *Conn) unrecognizedCommand(cmd string) { + c.WriteResponse(500, fmt.Sprintf("Syntax error, %v command unrecognized", cmd)) + + c.nbrErrors++ + if c.nbrErrors > 3 { + c.WriteResponse(500, "Too many unrecognized commands") + c.Close() + } +} + // Commands are dispatched to the appropriate handler functions. func (c *Conn) handle(cmd string, arg string) { if cmd == "" { @@ -94,17 +108,15 @@ func (c *Conn) handle(cmd string, arg string) { c.WriteResponse(221, "Goodnight and good luck") c.Close() case "AUTH": - c.handleAuth(arg) + if c.server.AuthDisabled { + c.unrecognizedCommand(cmd) + } else { + c.handleAuth(arg) + } case "STARTTLS": c.handleStartTLS() default: - c.WriteResponse(500, fmt.Sprintf("Syntax error, %v command unrecognized", cmd)) - - c.nbrErrors++ - if c.nbrErrors > 3 { - c.WriteResponse(500, "Too many unrecognized commands") - c.Close() - } + c.unrecognizedCommand(cmd) } } @@ -118,10 +130,12 @@ func (c *Conn) User() User { return c.user } +// Setting the user resets any message beng generated func (c *Conn) SetUser(user User) { c.locker.Lock() defer c.locker.Unlock() c.user = user + c.msg = &message{} } func (c *Conn) Close() error { @@ -138,6 +152,11 @@ func (c *Conn) IsTLS() bool { return ok } +func (c *Conn) authAllowed() bool { + return !c.server.AuthDisabled && + (c.IsTLS() || c.server.AllowInsecureAuth) +} + // GREET state -> waiting for HELO func (c *Conn) handleGreet(enhanced bool, arg string) { if !enhanced { @@ -163,7 +182,7 @@ func (c *Conn) handleGreet(enhanced bool, arg string) { if c.server.TLSConfig != nil && !c.IsTLS() { caps = append(caps, "STARTTLS") } - if c.IsTLS() || c.server.AllowInsecureAuth { + if c.authAllowed() { authCap := "AUTH" for name, _ := range c.server.auths { authCap += " " + name @@ -187,9 +206,15 @@ func (c *Conn) handleMail(arg string) { c.WriteResponse(502, "Please introduce yourself first.") return } - if c.msg == nil { - c.WriteResponse(502, "Please authenticate first.") - return + + if c.User() == nil { + user, err := c.server.Backend.AnonymousLogin() + if err != nil { + c.WriteResponse(502, err.Error()) + return + } + + c.SetUser(user) } // Match FROM, while accepting '>' as quoted pair and in double quoted strings @@ -318,8 +343,6 @@ func (c *Conn) handleAuth(arg string) { if c.User() != nil { c.WriteResponse(235, "Authentication succeeded") - - c.msg = &message{} } } @@ -421,12 +444,13 @@ func (c *Conn) ReadLine() (string, error) { } func (c *Conn) reset() { - if user := c.User(); user != nil { - user.Logout() + c.locker.Lock() + defer c.locker.Unlock() + + if c.user != nil { + c.user.Logout() } - c.locker.Lock() c.user = nil c.msg = nil - c.locker.Unlock() } diff --git a/vendor/github.com/emersion/go-smtp/server.go b/vendor/github.com/emersion/go-smtp/server.go index 6e20e9e..19bdbfc 100755 --- a/vendor/github.com/emersion/go-smtp/server.go +++ b/vendor/github.com/emersion/go-smtp/server.go @@ -27,6 +27,10 @@ type Server struct { AllowInsecureAuth bool Debug io.Writer + // If set, the AUTH command will not be advertised and authentication + // attempts will be rejected. This setting overrides AllowInsecureAuth. + AuthDisabled bool + // The server backend. Backend Backend