Add LOGIN/PLAIN/CRAM-MD5 authentication hooks

This commit is contained in:
Ian Kent 2014-11-23 00:36:32 +00:00
parent fc25fce4d2
commit 1a5fd826ec
4 changed files with 54 additions and 22 deletions

View file

@ -1603,8 +1603,8 @@ var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
"strutil.js": &_bintree_t{assets_js_strutil_js, map[string]*_bintree_t{}}, "strutil.js": &_bintree_t{assets_js_strutil_js, map[string]*_bintree_t{}},
}}, }},
"templates": &_bintree_t{nil, map[string]*_bintree_t{ "templates": &_bintree_t{nil, map[string]*_bintree_t{
"index.html": &_bintree_t{assets_templates_index_html, map[string]*_bintree_t{}},
"layout.html": &_bintree_t{assets_templates_layout_html, map[string]*_bintree_t{}}, "layout.html": &_bintree_t{assets_templates_layout_html, map[string]*_bintree_t{}},
"index.html": &_bintree_t{assets_templates_index_html, map[string]*_bintree_t{}},
}}, }},
}}, }},
}} }}

View file

@ -24,7 +24,7 @@ type Protocol struct {
hostname string hostname string
// LogHandler is called for each log message. If nil, log messages will // LogHandler is called for each log message. If nil, log messages will
// be output using fmt.Printf instead. // be output using log.Printf instead.
LogHandler func(message string, args ...interface{}) LogHandler func(message string, args ...interface{})
// MessageReceivedHandler is called for each message accepted by the // MessageReceivedHandler is called for each message accepted by the
// SMTP protocol. It must return a MessageID or error. If nil, messages // SMTP protocol. It must return a MessageID or error. If nil, messages
@ -39,7 +39,7 @@ type Protocol struct {
// ValidateAuthenticationhandler should return true if the authentication // ValidateAuthenticationhandler should return true if the authentication
// parameters are valid, otherwise false. If nil, all authentication // parameters are valid, otherwise false. If nil, all authentication
// attempts will be accepted. // attempts will be accepted.
ValidateAuthenticationHandler func(mechanism string, args ...string) bool ValidateAuthenticationHandler func(mechanism string, args ...string) (errorReply *Reply, ok bool)
} }
// NewProtocol returns a new SMTP state machine in INVALID state // NewProtocol returns a new SMTP state machine in INVALID state
@ -167,14 +167,37 @@ func (proto *Protocol) Command(command *Command) (reply *Reply) {
proto.logf("Got unknown command for ESTABLISH state: '%s'", command.verb) proto.logf("Got unknown command for ESTABLISH state: '%s'", command.verb)
return ReplyUnrecognisedCommand() return ReplyUnrecognisedCommand()
} }
case AUTH == proto.state: case AUTHPLAIN == proto.state:
proto.logf("Got authentication response: '%s', switching to MAIL state", command.args) proto.logf("Got PLAIN authentication response: '%s', switching to MAIL state", command.args)
proto.state = MAIL proto.state = MAIL
if proto.ValidateAuthenticationHandler != nil {
if reply, ok := proto.ValidateAuthenticationHandler("CRAM-MD5", command.args); !ok {
return reply
}
}
return ReplyAuthOk() return ReplyAuthOk()
case AUTHLOGIN == proto.state: case AUTHLOGIN == proto.state:
proto.logf("Got LOGIN authentication response: '%s', switching to AUTH state", command.args) proto.logf("Got LOGIN authentication response: '%s', switching to AUTHLOGIN2 state", command.args)
proto.state = AUTH proto.state = AUTHLOGIN2
return ReplyAuthResponse("UGFzc3dvcmQ6") return ReplyAuthResponse("UGFzc3dvcmQ6")
case AUTHLOGIN2 == proto.state:
proto.logf("Got LOGIN authentication response: '%s', switching to MAIL state", command.args)
proto.state = MAIL
if proto.ValidateAuthenticationHandler != nil {
if reply, ok := proto.ValidateAuthenticationHandler("CRAM-MD5", command.args); !ok {
return reply
}
}
return ReplyAuthOk()
case AUTHCRAMMD5 == proto.state:
proto.logf("Got CRAM-MD5 authentication response: '%s', switching to MAIL state", command.args)
proto.state = MAIL
if proto.ValidateAuthenticationHandler != nil {
if reply, ok := proto.ValidateAuthenticationHandler("CRAM-MD5", command.args); !ok {
return reply
}
}
return ReplyAuthOk()
case MAIL == proto.state: case MAIL == proto.state:
switch command.verb { switch command.verb {
case "AUTH": case "AUTH":
@ -182,6 +205,11 @@ func (proto *Protocol) Command(command *Command) (reply *Reply) {
switch { switch {
case strings.HasPrefix(command.args, "PLAIN "): case strings.HasPrefix(command.args, "PLAIN "):
proto.logf("Got PLAIN authentication: %s", strings.TrimPrefix(command.args, "PLAIN ")) proto.logf("Got PLAIN authentication: %s", strings.TrimPrefix(command.args, "PLAIN "))
if proto.ValidateAuthenticationHandler != nil {
if reply, ok := proto.ValidateAuthenticationHandler("PLAIN", strings.TrimPrefix(command.args, "PLAIN ")); !ok {
return reply
}
}
return ReplyAuthOk() return ReplyAuthOk()
case "LOGIN" == command.args: case "LOGIN" == command.args:
proto.logf("Got LOGIN authentication, switching to AUTH state") proto.logf("Got LOGIN authentication, switching to AUTH state")
@ -189,17 +217,17 @@ func (proto *Protocol) Command(command *Command) (reply *Reply) {
return ReplyAuthResponse("VXNlcm5hbWU6") return ReplyAuthResponse("VXNlcm5hbWU6")
case "PLAIN" == command.args: case "PLAIN" == command.args:
proto.logf("Got PLAIN authentication (no args), switching to AUTH2 state") proto.logf("Got PLAIN authentication (no args), switching to AUTH2 state")
proto.state = AUTH proto.state = AUTHPLAIN
return ReplyAuthResponse("") return ReplyAuthResponse("")
case "CRAM-MD5" == command.args: case "CRAM-MD5" == command.args:
proto.logf("Got CRAM-MD5 authentication, switching to AUTH state") proto.logf("Got CRAM-MD5 authentication, switching to AUTH state")
proto.state = AUTH proto.state = AUTHCRAMMD5
return ReplyAuthResponse("PDQxOTI5NDIzNDEuMTI4Mjg0NzJAc291cmNlZm91ci5hbmRyZXcuY211LmVkdT4=") return ReplyAuthResponse("PDQxOTI5NDIzNDEuMTI4Mjg0NzJAc291cmNlZm91ci5hbmRyZXcuY211LmVkdT4=")
case strings.HasPrefix(command.args, "EXTERNAL "): case strings.HasPrefix(command.args, "EXTERNAL "):
proto.logf("Got EXTERNAL authentication: %s", strings.TrimPrefix(command.args, "EXTERNAL ")) proto.logf("Got EXTERNAL authentication: %s", strings.TrimPrefix(command.args, "EXTERNAL "))
if proto.ValidateAuthenticationHandler != nil { if proto.ValidateAuthenticationHandler != nil {
if !proto.ValidateAuthenticationHandler("EXTERNAL", command.args) { if reply, ok := proto.ValidateAuthenticationHandler("EXTERNAL", strings.TrimPrefix(command.args, "EXTERNAL ")); !ok {
// TODO error reply return reply
} }
} }
return ReplyAuthOk() return ReplyAuthOk()

View file

@ -7,8 +7,10 @@ type State int
const ( const (
INVALID = State(-1) INVALID = State(-1)
ESTABLISH = State(iota) ESTABLISH = State(iota)
AUTH AUTHPLAIN
AUTHLOGIN AUTHLOGIN
AUTHLOGIN2
AUTHCRAMMD5
MAIL MAIL
RCPT RCPT
DATA DATA
@ -17,12 +19,14 @@ const (
// StateMap provides string representations of SMTP conversation states // StateMap provides string representations of SMTP conversation states
var StateMap = map[State]string{ var StateMap = map[State]string{
INVALID: "INVALID", INVALID: "INVALID",
ESTABLISH: "ESTABLISH", ESTABLISH: "ESTABLISH",
AUTH: "AUTH", AUTHPLAIN: "AUTHPLAIN",
AUTHLOGIN: "AUTHLOGIN", AUTHLOGIN: "AUTHLOGIN",
MAIL: "MAIL", AUTHLOGIN2: "AUTHLOGIN2",
RCPT: "RCPT", AUTHCRAMMD5: "AUTHCRAMMD5",
DATA: "DATA", MAIL: "MAIL",
DONE: "DONE", RCPT: "RCPT",
DATA: "DATA",
DONE: "DONE",
} }

View file

@ -38,8 +38,8 @@ func Accept(conn *net.TCPConn, conf *config.Config) {
session.logf("Session ended") session.logf("Session ended")
} }
func (c *Session) validateAuthentication(mechanism string, args ...string) bool { func (c *Session) validateAuthentication(mechanism string, args ...string) (errorReply *protocol.Reply, ok bool) {
return true return nil, true
} }
func (c *Session) validateRecipient(to string) bool { func (c *Session) validateRecipient(to string) bool {