Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server: Remove authentication-related session creation #146

Merged
merged 1 commit into from
Jul 20, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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