Skip to content

Commit

Permalink
server: Remove authentication-related session creation
Browse files Browse the repository at this point in the history
Closes #136.
Closes #130.
  • Loading branch information
foxcpp authored and emersion committed Jul 20, 2021
1 parent 26eb481 commit ff30d70
Show file tree
Hide file tree
Showing 9 changed files with 119 additions and 150 deletions.
20 changes: 9 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,22 +65,20 @@ import (
// The Backend implements SMTP server methods.
type Backend struct{}

// Login handles a login command with username and password.
func (bkd *Backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
if username != "username" || password != "password" {
return nil, errors.New("Invalid username or password")
}
func (bkd *Backend) NewSession(_ smtp.ConnectionState, _ string) (smtp.Session, error) {
return &Session{}, nil
}

// AnonymousLogin requires clients to authenticate using SMTP AUTH before sending emails
func (bkd *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
return nil, smtp.ErrAuthRequired
}

// A Session is returned after successful login.
// A Session is returned after EHLO.
type Session struct{}

func (s *Session) AuthPlain(username, password string) error {
if username != "username" || password != "password" {
return errors.New("Invalid username or password")
}
return nil
}

func (s *Session) Mail(from string, opts smtp.MailOptions) error {
log.Println("Mail from:", from)
return nil
Expand Down
25 changes: 14 additions & 11 deletions backend.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
package smtp

import (
"errors"
"io"
)

var (
ErrAuthRequired = errors.New("Please authenticate first")
ErrAuthUnsupported = errors.New("Authentication not supported")
)
ErrAuthRequired = &SMTPError{
Code: 502,
EnhancedCode: EnhancedCode{5, 7, 0},
Message: "Please authenticate first",
}
ErrAuthUnsupported = &SMTPError{
Code: 502,
EnhancedCode: EnhancedCode{5, 7, 0},
Message: "Authentication not supported",
})

// A SMTP server backend.
type Backend interface {
// Authenticate a user. Return smtp.ErrAuthUnsupported if you don't want to
// support this.
Login(state *ConnectionState, username, password string) (Session, error)

// Called if the client attempts to send mail without logging in first.
// Return smtp.ErrAuthRequired if you don't want to support this.
AnonymousLogin(state *ConnectionState) (Session, error)
NewSession(c ConnectionState, hostname string) (Session, error)
}

type BodyType string
Expand Down Expand Up @@ -68,6 +68,9 @@ type Session interface {
// Free all resources associated with session.
Logout() error

// Authenticate the user using SASL PLAIN.
AuthPlain(username, password string) error

// Set return path for currently processed message.
Mail(from string, opts MailOptions) error
// Add recipient for currently processed message.
Expand Down
20 changes: 7 additions & 13 deletions backendutil/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,12 @@ type TransformBackend struct {
TransformData func(r io.Reader) (io.Reader, error)
}

// Login implements the smtp.Backend interface.
func (be *TransformBackend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
s, err := be.Backend.Login(state, username, password)
func (be *TransformBackend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
sess, err := be.Backend.NewSession(c, hostname)
if err != nil {
return nil, err
}
return &transformSession{s, be}, nil
}

// AnonymousLogin implements the smtp.Backend interface.
func (be *TransformBackend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
s, err := be.Backend.AnonymousLogin(state)
if err != nil {
return nil, err
}
return &transformSession{s, be}, nil
return &transformSession{Session: sess, be: be}, nil
}

type transformSession struct {
Expand All @@ -43,6 +33,10 @@ func (s *transformSession) Reset() {
s.Session.Reset()
}

func (s *transformSession) AuthPlain(username, password string) error {
return s.Session.AuthPlain(username, password)
}

func (s *transformSession) Mail(from string, opts smtp.MailOptions) error {
if s.be.TransformMail != nil {
var err error
Expand Down
28 changes: 12 additions & 16 deletions backendutil/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,7 @@ type backend struct {
userErr error
}

func (be *backend) Login(_ *smtp.ConnectionState, username, password string) (smtp.Session, error) {
if be.userErr != nil {
return &session{}, be.userErr
}

if username != "username" || password != "password" {
return nil, errors.New("Invalid username or password")
}
return &session{backend: be}, nil
}

func (be *backend) AnonymousLogin(_ *smtp.ConnectionState) (smtp.Session, error) {
if be.userErr != nil {
return &session{}, be.userErr
}

func (be *backend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
return &session{backend: be, anonymous: true}, nil
}

Expand All @@ -63,7 +48,18 @@ func (s *session) Logout() error {
return nil
}

func (s *session) AuthPlain(username, password string) error {
if username != "username" || password != "password" {
return errors.New("Invalid username or password")
}
s.anonymous = false
return nil
}

func (s *session) Mail(from string, opts smtp.MailOptions) error {
if s.backend.userErr != nil {
return s.backend.userErr
}
s.Reset()
s.msg.From = from
return nil
Expand Down
10 changes: 5 additions & 5 deletions cmd/smtp-debug-server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,16 @@ func init() {

type backend struct{}

func (bkd *backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
return &session{}, nil
}

func (bkd *backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
func (bkd *backend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
return &session{}, nil
}

type session struct{}

func (s *session) AuthPlain(username, password string) error {
return nil
}

func (s *session) Mail(from string, opts smtp.MailOptions) error {
return nil
}
Expand Down
103 changes: 46 additions & 57 deletions conn.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,56 +235,60 @@ func (c *Conn) protocolError(code int, ec EnhancedCode, msg string) {

// GREET state -> waiting for HELO
func (c *Conn) handleGreet(enhanced bool, arg string) {
if !enhanced {
domain, err := parseHelloArgument(arg)
if err != nil {
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Domain/address argument required for HELO")
return
}
c.helo = domain
domain, err := parseHelloArgument(arg)
if err != nil {
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Domain/address argument required for HELO")
return
}
c.helo = domain

c.WriteResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Hello %s", domain))
} else {
domain, err := parseHelloArgument(arg)
if err != nil {
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Domain/address argument required for EHLO")
sess, err := c.server.Backend.NewSession(c.State(), domain)
if err != nil {
if smtpErr, ok := err.(*SMTPError); ok {
c.WriteResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
return
}
c.WriteResponse(451, EnhancedCode{4, 0, 0}, err.Error())
return
}
c.SetSession(sess)

c.helo = domain

caps := []string{}
caps = append(caps, c.server.caps...)
if _, isTLS := c.TLSConnectionState(); c.server.TLSConfig != nil && !isTLS {
caps = append(caps, "STARTTLS")
}
if c.authAllowed() {
authCap := "AUTH"
for name := range c.server.auths {
authCap += " " + name
}
if !enhanced {
c.WriteResponse(250, EnhancedCode{2, 0, 0}, fmt.Sprintf("Hello %s", domain))
return
}

caps = append(caps, authCap)
}
if c.server.EnableSMTPUTF8 {
caps = append(caps, "SMTPUTF8")
}
if _, isTLS := c.TLSConnectionState(); isTLS && c.server.EnableREQUIRETLS {
caps = append(caps, "REQUIRETLS")
}
if c.server.EnableBINARYMIME {
caps = append(caps, "BINARYMIME")
}
if c.server.MaxMessageBytes > 0 {
caps = append(caps, fmt.Sprintf("SIZE %v", c.server.MaxMessageBytes))
} else {
caps = append(caps, "SIZE")
caps := []string{}
caps = append(caps, c.server.caps...)
if _, isTLS := c.TLSConnectionState(); c.server.TLSConfig != nil && !isTLS {
caps = append(caps, "STARTTLS")
}
if c.authAllowed() {
authCap := "AUTH"
for name := range c.server.auths {
authCap += " " + name
}

args := []string{"Hello " + domain}
args = append(args, caps...)
c.WriteResponse(250, NoEnhancedCode, args...)
caps = append(caps, authCap)
}
if c.server.EnableSMTPUTF8 {
caps = append(caps, "SMTPUTF8")
}
if _, isTLS := c.TLSConnectionState(); isTLS && c.server.EnableREQUIRETLS {
caps = append(caps, "REQUIRETLS")
}
if c.server.EnableBINARYMIME {
caps = append(caps, "BINARYMIME")
}
if c.server.MaxMessageBytes > 0 {
caps = append(caps, fmt.Sprintf("SIZE %v", c.server.MaxMessageBytes))
} else {
caps = append(caps, "SIZE")
}

args := []string{"Hello " + domain}
args = append(args, caps...)
c.WriteResponse(250, NoEnhancedCode, args...)
}

// READY state -> waiting for MAIL
Expand All @@ -298,21 +302,6 @@ func (c *Conn) handleMail(arg string) {
return
}

if c.Session() == nil {
state := c.State()
session, err := c.server.Backend.AnonymousLogin(&state)
if err != nil {
if smtpErr, ok := err.(*SMTPError); ok {
c.WriteResponse(smtpErr.Code, smtpErr.EnhancedCode, smtpErr.Message)
} else {
c.WriteResponse(502, EnhancedCode{5, 7, 0}, err.Error())
}
return
}

c.SetSession(session)
}

if len(arg) < 6 || strings.ToUpper(arg[0:5]) != "FROM:" {
c.WriteResponse(501, EnhancedCode{5, 5, 2}, "Was expecting MAIL arg syntax of FROM:<address>")
return
Expand Down
20 changes: 10 additions & 10 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,22 +92,22 @@ func ExampleSendMail() {
// The Backend implements SMTP server methods.
type Backend struct{}

// Login handles a login command with username and password.
func (bkd *Backend) Login(state *smtp.ConnectionState, username, password string) (smtp.Session, error) {
if username != "username" || password != "password" {
return nil, errors.New("Invalid username or password")
}
// NewSession is called after client greeting (EHLO, HELO).
func (bkd *Backend) NewSession(c smtp.ConnectionState, hostname string) (smtp.Session, error) {
return &Session{}, nil
}

// AnonymousLogin requires clients to authenticate using SMTP AUTH before sending emails
func (bkd *Backend) AnonymousLogin(state *smtp.ConnectionState) (smtp.Session, error) {
return nil, smtp.ErrAuthRequired
}

// A Session is returned after successful login.
type Session struct{}

// AuthPlain implements authentication using SASL PLAIN.
func (s *Session) AuthPlain(username, password string) error {
if username != "username" || password != "password" {
return errors.New("Invalid username or password")
}
return nil
}

func (s *Session) Mail(from string, opts smtp.MailOptions) error {
log.Println("Mail from:", from)
return nil
Expand Down
10 changes: 4 additions & 6 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,12 @@ func NewServer(be Backend) *Server {
return errors.New("Identities not supported")
}

state := conn.State()
session, err := be.Login(&state, username, password)
if err != nil {
return err
sess := conn.Session()
if sess == nil {
panic("No session when AUTH is called")
}

conn.SetSession(session)
return nil
return sess.AuthPlain(username, password)
})
},
},
Expand Down
Loading

0 comments on commit ff30d70

Please sign in to comment.