#18: implement SMTP Submission (587/465), SASL PLAIN/LOGIN auth and preflight diagnostics
This commit is contained in:
1 parent
7249419efc
commit
d56f29d084
70 files changed
+5163
-659
No files matched your search
@@ -0,0 +1,67 @@
|
||||
package smtppresentation
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/emersion/go-sasl"
|
||||
)
|
||||
|
||||
// loginMechanism is the SASL mechanism name for the legacy LOGIN exchange.
|
||||
// PLAN.md section 4 requires it alongside PLAIN because several desktop and
|
||||
// mobile clients still offer nothing else. Neither go-sasl nor go-smtp ships a
|
||||
// server side for it, so it is implemented here.
|
||||
const loginMechanism = "LOGIN"
|
||||
|
||||
// LOGIN is not specified by an RFC; it is a de facto mechanism where the
|
||||
// server prompts for the username, then for the password, each as a bare
|
||||
// challenge string.
|
||||
var (
|
||||
loginUsernameChallenge = []byte("Username:")
|
||||
loginPasswordChallenge = []byte("Password:")
|
||||
)
|
||||
|
||||
// loginAuthenticator verifies a username and password pair.
|
||||
type loginAuthenticator func(username, password string) error
|
||||
|
||||
// loginServer implements sasl.Server for the LOGIN mechanism.
|
||||
type loginServer struct {
|
||||
authenticate loginAuthenticator
|
||||
username string
|
||||
// state advances from awaiting the username, to awaiting the password,
|
||||
// to finished.
|
||||
state int
|
||||
}
|
||||
|
||||
const (
|
||||
loginStateAwaitingUsername = iota
|
||||
loginStateAwaitingPassword
|
||||
loginStateDone
|
||||
)
|
||||
|
||||
func newLoginServer(authenticate loginAuthenticator) sasl.Server {
|
||||
return &loginServer{authenticate: authenticate}
|
||||
}
|
||||
|
||||
func (s *loginServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||
switch s.state {
|
||||
case loginStateAwaitingUsername:
|
||||
// A client may send the username as the initial response, in which
|
||||
// case there is nothing to prompt for yet.
|
||||
if response == nil {
|
||||
return loginUsernameChallenge, false, nil
|
||||
}
|
||||
s.username = string(response)
|
||||
s.state = loginStateAwaitingPassword
|
||||
return loginPasswordChallenge, false, nil
|
||||
|
||||
case loginStateAwaitingPassword:
|
||||
s.state = loginStateDone
|
||||
if err := s.authenticate(s.username, string(response)); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil, true, nil
|
||||
|
||||
default:
|
||||
return nil, false, errors.New("sasl: LOGIN exchange already finished")
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user