log a consistent log line for failed authentication attempts, with the remote ip

so external tools (like fail2ban) can monitor the logs and block ip's of bots.

for issue #30 by inigoserna, though i'm not sure i interpreted the suggestion correctly.
This commit is contained in:
Mechiel Lukkien 2023-05-31 20:39:00 +02:00
parent 70d07c5459
commit 713d781bad
No known key found for this signature in database
4 changed files with 28 additions and 4 deletions

View file

@ -70,11 +70,14 @@ func checkAccountAuth(ctx context.Context, log *mlog.Log, w http.ResponseWriter,
}() }()
var err error var err error
var remoteIP net.IP
addr, err = net.ResolveTCPAddr("tcp", r.RemoteAddr) addr, err = net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err != nil { if err != nil {
log.Errorx("parsing remote address", err, mlog.Field("addr", r.RemoteAddr)) log.Errorx("parsing remote address", err, mlog.Field("addr", r.RemoteAddr))
} else if addr != nil {
remoteIP = addr.IP
} }
if addr != nil && !mox.LimiterFailedAuth.Add(addr.IP, start, 1) { if remoteIP != nil && !mox.LimiterFailedAuth.Add(remoteIP, start, 1) {
metrics.AuthenticationRatelimitedInc("httpaccount") metrics.AuthenticationRatelimitedInc("httpaccount")
http.Error(w, "429 - too many auth attempts", http.StatusTooManyRequests) http.Error(w, "429 - too many auth attempts", http.StatusTooManyRequests)
return "" return ""
@ -89,6 +92,7 @@ func checkAccountAuth(ctx context.Context, log *mlog.Log, w http.ResponseWriter,
} else if acc, err := store.OpenEmailAuth(t[0], t[1]); err != nil { } else if acc, err := store.OpenEmailAuth(t[0], t[1]); err != nil {
if errors.Is(err, store.ErrUnknownCredentials) { if errors.Is(err, store.ErrUnknownCredentials) {
authResult = "badcreds" authResult = "badcreds"
log.Info("failed authentication attempt", mlog.Field("username", t[0]), mlog.Field("remote", remoteIP))
} }
log.Errorx("open account", err) log.Errorx("open account", err)
} else { } else {

View file

@ -132,11 +132,14 @@ func checkAdminAuth(ctx context.Context, passwordfile string, w http.ResponseWri
}() }()
var err error var err error
var remoteIP net.IP
addr, err = net.ResolveTCPAddr("tcp", r.RemoteAddr) addr, err = net.ResolveTCPAddr("tcp", r.RemoteAddr)
if err != nil { if err != nil {
log.Errorx("parsing remote address", err, mlog.Field("addr", r.RemoteAddr)) log.Errorx("parsing remote address", err, mlog.Field("addr", r.RemoteAddr))
} else if addr != nil {
remoteIP = addr.IP
} }
if addr != nil && !mox.LimiterFailedAuth.Add(addr.IP, start, 1) { if remoteIP != nil && !mox.LimiterFailedAuth.Add(remoteIP, start, 1) {
metrics.AuthenticationRatelimitedInc("httpadmin") metrics.AuthenticationRatelimitedInc("httpadmin")
http.Error(w, "429 - too many auth attempts", http.StatusTooManyRequests) http.Error(w, "429 - too many auth attempts", http.StatusTooManyRequests)
return false return false
@ -164,10 +167,12 @@ func checkAdminAuth(ctx context.Context, passwordfile string, w http.ResponseWri
} }
t := strings.SplitN(string(auth), ":", 2) t := strings.SplitN(string(auth), ":", 2)
if len(t) != 2 || len(t[1]) < 8 { if len(t) != 2 || len(t[1]) < 8 {
log.Info("failed authentication attempt", mlog.Field("username", "admin"), mlog.Field("remote", remoteIP))
return respondAuthFail() return respondAuthFail()
} }
if err := bcrypt.CompareHashAndPassword([]byte(passwordhash), []byte(t[1])); err != nil { if err := bcrypt.CompareHashAndPassword([]byte(passwordhash), []byte(t[1])); err != nil {
authResult = "badcreds" authResult = "badcreds"
log.Info("failed authentication attempt", mlog.Field("username", "admin"), mlog.Field("remote", remoteIP))
return respondAuthFail() return respondAuthFail()
} }
authCache.lastSuccessHash = passwordhash authCache.lastSuccessHash = passwordhash

View file

@ -1538,6 +1538,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
if err != nil { if err != nil {
if errors.Is(err, store.ErrUnknownCredentials) { if errors.Is(err, store.ErrUnknownCredentials) {
authResult = "badcreds" authResult = "badcreds"
c.log.Info("authentication failed", mlog.Field("username", authc))
xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials") xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials")
} }
xusercodeErrorf("", "error") xusercodeErrorf("", "error")
@ -1565,6 +1566,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
acc, _, err := store.OpenEmail(addr) acc, _, err := store.OpenEmail(addr)
if err != nil { if err != nil {
if errors.Is(err, store.ErrUnknownCredentials) { if errors.Is(err, store.ErrUnknownCredentials) {
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials") xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials")
} }
xserverErrorf("looking up address: %v", err) xserverErrorf("looking up address: %v", err)
@ -1580,6 +1582,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
err := acc.DB.Read(context.TODO(), func(tx *bstore.Tx) error { err := acc.DB.Read(context.TODO(), func(tx *bstore.Tx) error {
password, err := bstore.QueryTx[store.Password](tx).Get() password, err := bstore.QueryTx[store.Password](tx).Get()
if err == bstore.ErrAbsent { if err == bstore.ErrAbsent {
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials") xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials")
} }
if err != nil { if err != nil {
@ -1593,7 +1596,8 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
xcheckf(err, "tx read") xcheckf(err, "tx read")
}) })
if ipadhash == nil || opadhash == nil { if ipadhash == nil || opadhash == nil {
c.log.Info("cram-md5 auth attempt without derived secrets set, save password again to store secrets", mlog.Field("address", addr)) c.log.Info("cram-md5 auth attempt without derived secrets set, save password again to store secrets", mlog.Field("username", addr))
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials") xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials")
} }
@ -1602,6 +1606,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
opadhash.Write(ipadhash.Sum(nil)) opadhash.Write(ipadhash.Sum(nil))
digest := fmt.Sprintf("%x", opadhash.Sum(nil)) digest := fmt.Sprintf("%x", opadhash.Sum(nil))
if digest != t[1] { if digest != t[1] {
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials") xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials")
} }
@ -1675,6 +1680,7 @@ func (c *conn) cmdAuthenticate(tag, cmd string, p *parser) {
c.readline(false) // Should be "*" for cancellation. c.readline(false) // Should be "*" for cancellation.
if errors.Is(err, scram.ErrInvalidProof) { if errors.Is(err, scram.ErrInvalidProof) {
authResult = "badcreds" authResult = "badcreds"
c.log.Info("failed authentication attempt", mlog.Field("username", ss.Authentication), mlog.Field("remote", c.remoteIP))
xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials") xusercodeErrorf("AUTHENTICATIONFAILED", "bad credentials")
} }
xuserErrorf("server final: %w", err) xuserErrorf("server final: %w", err)
@ -1744,6 +1750,7 @@ func (c *conn) cmdLogin(tag, cmd string, p *parser) {
var code string var code string
if errors.Is(err, store.ErrUnknownCredentials) { if errors.Is(err, store.ErrUnknownCredentials) {
code = "AUTHENTICATIONFAILED" code = "AUTHENTICATIONFAILED"
c.log.Info("failed authentication attempt", mlog.Field("username", userid), mlog.Field("remote", c.remoteIP))
} }
xusercodeErrorf(code, "login failed") xusercodeErrorf(code, "login failed")
} }

View file

@ -985,6 +985,7 @@ func (c *conn) cmdAuth(p *parser) {
if err != nil && errors.Is(err, store.ErrUnknownCredentials) { if err != nil && errors.Is(err, store.ErrUnknownCredentials) {
// ../rfc/4954:274 // ../rfc/4954:274
authResult = "badcreds" authResult = "badcreds"
c.log.Info("failed authentication attempt", mlog.Field("username", authc), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass") xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass")
} }
xcheckf(err, "verifying credentials") xcheckf(err, "verifying credentials")
@ -1016,6 +1017,7 @@ func (c *conn) cmdAuth(p *parser) {
acc, _, err := store.OpenEmail(addr) acc, _, err := store.OpenEmail(addr)
if err != nil { if err != nil {
if errors.Is(err, store.ErrUnknownCredentials) { if errors.Is(err, store.ErrUnknownCredentials) {
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass") xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass")
} }
} }
@ -1031,6 +1033,7 @@ func (c *conn) cmdAuth(p *parser) {
err := acc.DB.Read(context.TODO(), func(tx *bstore.Tx) error { err := acc.DB.Read(context.TODO(), func(tx *bstore.Tx) error {
password, err := bstore.QueryTx[store.Password](tx).Get() password, err := bstore.QueryTx[store.Password](tx).Get()
if err == bstore.ErrAbsent { if err == bstore.ErrAbsent {
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass") xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass")
} }
if err != nil { if err != nil {
@ -1044,7 +1047,8 @@ func (c *conn) cmdAuth(p *parser) {
xcheckf(err, "tx read") xcheckf(err, "tx read")
}) })
if ipadhash == nil || opadhash == nil { if ipadhash == nil || opadhash == nil {
c.log.Info("cram-md5 auth attempt without derived secrets set, save password again to store secrets", mlog.Field("address", addr)) c.log.Info("cram-md5 auth attempt without derived secrets set, save password again to store secrets", mlog.Field("username", addr))
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass") xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass")
} }
@ -1053,6 +1057,7 @@ func (c *conn) cmdAuth(p *parser) {
opadhash.Write(ipadhash.Sum(nil)) opadhash.Write(ipadhash.Sum(nil))
digest := fmt.Sprintf("%x", opadhash.Sum(nil)) digest := fmt.Sprintf("%x", opadhash.Sum(nil))
if digest != t[1] { if digest != t[1] {
c.log.Info("failed authentication attempt", mlog.Field("username", addr), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass") xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad user/pass")
} }
@ -1088,6 +1093,7 @@ func (c *conn) cmdAuth(p *parser) {
// todo: we could continue scram with a generated salt, deterministically generated // todo: we could continue scram with a generated salt, deterministically generated
// from the username. that way we don't have to store anything but attackers cannot // from the username. that way we don't have to store anything but attackers cannot
// learn if an account exists. same for absent scram saltedpassword below. // learn if an account exists. same for absent scram saltedpassword below.
c.log.Info("failed authentication attempt", mlog.Field("username", ss.Authentication), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C454TempAuthFail, smtp.SeSys3Other0, "scram not possible") xsmtpUserErrorf(smtp.C454TempAuthFail, smtp.SeSys3Other0, "scram not possible")
} }
defer func() { defer func() {
@ -1110,6 +1116,7 @@ func (c *conn) cmdAuth(p *parser) {
} }
if err == bstore.ErrAbsent || err == nil && (len(xscram.Salt) == 0 || xscram.Iterations == 0 || len(xscram.SaltedPassword) == 0) { if err == bstore.ErrAbsent || err == nil && (len(xscram.Salt) == 0 || xscram.Iterations == 0 || len(xscram.SaltedPassword) == 0) {
c.log.Info("scram auth attempt without derived secrets set, save password again to store secrets", mlog.Field("address", ss.Authentication)) c.log.Info("scram auth attempt without derived secrets set, save password again to store secrets", mlog.Field("address", ss.Authentication))
c.log.Info("failed authentication attempt", mlog.Field("username", ss.Authentication), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C454TempAuthFail, smtp.SeSys3Other0, "scram not possible") xsmtpUserErrorf(smtp.C454TempAuthFail, smtp.SeSys3Other0, "scram not possible")
} }
xcheckf(err, "fetching credentials") xcheckf(err, "fetching credentials")
@ -1129,6 +1136,7 @@ func (c *conn) cmdAuth(p *parser) {
c.readline() // Should be "*" for cancellation. c.readline() // Should be "*" for cancellation.
if errors.Is(err, scram.ErrInvalidProof) { if errors.Is(err, scram.ErrInvalidProof) {
authResult = "badcreds" authResult = "badcreds"
c.log.Info("failed authentication attempt", mlog.Field("username", ss.Authentication), mlog.Field("remote", c.remoteIP))
xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad credentials") xsmtpUserErrorf(smtp.C535AuthBadCreds, smtp.SePol7AuthBadCreds8, "bad credentials")
} }
xcheckf(err, "server final") xcheckf(err, "server final")