add LOGIN SMTP auth method (do not use it, unless you have to!)
This commit is contained in:
parent
83d839e561
commit
5cdde922f6
2 changed files with 84 additions and 6 deletions
|
|
@ -9,8 +9,73 @@ import (
|
||||||
"github.com/emersion/go-smtp"
|
"github.com/emersion/go-smtp"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
loginStateNotStarted = iota
|
||||||
|
loginStateUsername
|
||||||
|
loginStatePassword
|
||||||
|
)
|
||||||
|
|
||||||
// ensure that PlainAuthServer implements sasl.Server
|
// ensure that PlainAuthServer implements sasl.Server
|
||||||
var _ sasl.Server = (*PlainAuthServer)(nil)
|
var (
|
||||||
|
_ sasl.Server = (*PlainAuthServer)(nil)
|
||||||
|
_ sasl.Server = (*LoginAuthServer)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
// LoginAuthServer is a server implementation of the LOGIN authentication mechanism.
|
||||||
|
// It's a modified implementation of the go-sasl's original loginServer (MIT License),
|
||||||
|
// removed from go-sasl in https://github.com/emersion/go-sasl/commit/b788ff22d5a6b3970cde181998f52658a475bffc
|
||||||
|
// DO NOT USE IT, unless you have no other choice.
|
||||||
|
type LoginAuthServer struct {
|
||||||
|
done bool
|
||||||
|
state int
|
||||||
|
ctx context.Context //nolint:containedctx // that's per-request structure
|
||||||
|
bot matrixbot
|
||||||
|
conn *smtp.Conn
|
||||||
|
authenticate sasl.PlainAuthenticator // we use the same interface as PLAIN auth for simplicity
|
||||||
|
username string
|
||||||
|
password string
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLoginAuthServer creates a new LOGIN authentication server.
|
||||||
|
func NewLoginAuthServer(ctx context.Context, bot matrixbot, conn *smtp.Conn, auth sasl.PlainAuthenticator) *LoginAuthServer {
|
||||||
|
return &LoginAuthServer{
|
||||||
|
ctx: ctx,
|
||||||
|
bot: bot,
|
||||||
|
conn: conn,
|
||||||
|
authenticate: auth,
|
||||||
|
state: loginStateNotStarted,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next processes the next step of the authentication.
|
||||||
|
func (a *LoginAuthServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||||
|
if a.done {
|
||||||
|
err = sasl.ErrUnexpectedClientResponse
|
||||||
|
return challenge, done, err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch a.state {
|
||||||
|
case loginStateNotStarted:
|
||||||
|
a.state = loginStateUsername
|
||||||
|
return []byte("Username:"), false, nil
|
||||||
|
|
||||||
|
case loginStateUsername:
|
||||||
|
a.username = string(response)
|
||||||
|
a.state = loginStatePassword
|
||||||
|
return []byte("Password:"), false, nil
|
||||||
|
|
||||||
|
case loginStatePassword:
|
||||||
|
a.password = string(response)
|
||||||
|
a.done = true
|
||||||
|
err = a.authenticate("", a.username, a.password)
|
||||||
|
done = true
|
||||||
|
return challenge, done, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
err = sasl.ErrUnexpectedClientResponse
|
||||||
|
return challenge, done, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PlainAuthServer is a server implementation of the PLAIN authentication mechanism.
|
// PlainAuthServer is a server implementation of the PLAIN authentication mechanism.
|
||||||
// It's a modified version of the original plainServer from https://github.com/emersion/go-sasl package (MIT License)
|
// It's a modified version of the original plainServer from https://github.com/emersion/go-sasl package (MIT License)
|
||||||
|
|
@ -35,6 +100,7 @@ func NewPlainAuthServer(ctx context.Context, bot matrixbot, conn *smtp.Conn, aut
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Next processes the next step of the authentication.
|
||||||
func (a *PlainAuthServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
func (a *PlainAuthServer) Next(response []byte) (challenge []byte, done bool, err error) {
|
||||||
if a.done {
|
if a.done {
|
||||||
err = sasl.ErrUnexpectedClientResponse
|
err = sasl.ErrUnexpectedClientResponse
|
||||||
|
|
|
||||||
|
|
@ -48,20 +48,32 @@ type session struct {
|
||||||
|
|
||||||
// AuthMechanisms returns the list of supported authentication mechanisms
|
// AuthMechanisms returns the list of supported authentication mechanisms
|
||||||
func (s *session) AuthMechanisms() []string {
|
func (s *session) AuthMechanisms() []string {
|
||||||
return []string{sasl.Plain}
|
return []string{sasl.Plain, sasl.Login}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) Auth(mech string) (sasl.Server, error) {
|
func (s *session) Auth(mech string) (sasl.Server, error) {
|
||||||
|
addr := s.conn.Conn().RemoteAddr()
|
||||||
if !slices.Contains(s.AuthMechanisms(), mech) {
|
if !slices.Contains(s.AuthMechanisms(), mech) {
|
||||||
addr := s.conn.Conn().RemoteAddr()
|
|
||||||
s.log.Info().Str("addr", addr.String()).Msg("banning due to invalid auth mechanism")
|
s.log.Info().Str("addr", addr.String()).Msg("banning due to invalid auth mechanism")
|
||||||
s.bot.BanAuth(s.ctx, addr)
|
s.bot.BanAuth(s.ctx, addr)
|
||||||
return nil, ErrBanned
|
return nil, ErrBanned
|
||||||
}
|
}
|
||||||
|
|
||||||
return NewPlainAuthServer(s.ctx, s.bot, s.conn, func(identity, username, password string) error {
|
switch mech {
|
||||||
return s.authPlain(identity, username, password)
|
case sasl.Login:
|
||||||
}), nil
|
return NewLoginAuthServer(s.ctx, s.bot, s.conn, func(identity, username, password string) error {
|
||||||
|
return s.authPlain(identity, username, password)
|
||||||
|
}), nil
|
||||||
|
|
||||||
|
case sasl.Plain:
|
||||||
|
return NewPlainAuthServer(s.ctx, s.bot, s.conn, func(identity, username, password string) error {
|
||||||
|
return s.authPlain(identity, username, password)
|
||||||
|
}), nil
|
||||||
|
default:
|
||||||
|
s.log.Warn().Str("addr", addr.String()).Str("mech", mech).Msg("unsupported auth mechanism")
|
||||||
|
s.bot.BanAuth(s.ctx, addr)
|
||||||
|
return nil, ErrBanned
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *session) authPlain(_, username, password string) error {
|
func (s *session) authPlain(_, username, password string) error {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue