postmoogle/smtp/authserver.go

70 lines
2 KiB
Go

package smtp
import (
"bytes"
"context"
"errors"
"github.com/emersion/go-sasl"
"github.com/emersion/go-smtp"
)
// ensure that PlainAuthServer implements sasl.Server
var _ sasl.Server = (*PlainAuthServer)(nil)
// 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)
// ref: https://github.com/emersion/go-sasl/blob/e73c9f7bad438a9bf3f5b28e661b74d752ecafdd/plain.go
// The reason for modification is to extend automatic banning mechanism of Postmoogle, as the original implementation
// doesn't provide a way to return an error to the caller before the actual authentication process.
type PlainAuthServer struct {
done bool
ctx context.Context //nolint:containedctx // that's per-request structure
bot matrixbot
conn *smtp.Conn
authenticate sasl.PlainAuthenticator
}
// NewPlainAuthServer creates a new PLAIN authentication server.
func NewPlainAuthServer(ctx context.Context, bot matrixbot, conn *smtp.Conn, auth sasl.PlainAuthenticator) *PlainAuthServer {
return &PlainAuthServer{
ctx: ctx,
bot: bot,
conn: conn,
authenticate: auth,
}
}
func (a *PlainAuthServer) Next(response []byte) (challenge []byte, done bool, err error) {
if a.done {
err = sasl.ErrUnexpectedClientResponse
return
}
addr := a.conn.Conn().RemoteAddr()
if a.bot.IsBanned(a.ctx, addr) {
err = ErrBanned
return
}
// No initial response, send an empty challenge
if response == nil {
return []byte{}, false, nil
}
a.done = true
parts := bytes.Split(response, []byte("\x00"))
if len(parts) != 3 {
a.bot.BanAuth(a.ctx, addr)
err = errors.New("sasl: invalid response. Don't bother me anymore, kupo")
return
}
identity := string(parts[0])
username := string(parts[1])
password := string(parts[2])
err = a.authenticate(identity, username, password)
done = true
return
}