mirror of
https://github.com/mjl-/mox.git
synced 2025-06-28 21:38:15 +03:00

logging of login attempts happens in the background, because we don't want to block regular operation with disk since for such logging. however, when a line is logged, we evaluate some attributes of a connection, notably the username. but about when we do authentication, we change the username on a connection. so we were reading and writing at the same time. this is now fixed by evaluating the attributes before we pass off the logger to the goroutine. found by the go race detector.
85 lines
1.6 KiB
Go
85 lines
1.6 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime/debug"
|
|
"time"
|
|
|
|
"github.com/mjl-/bstore"
|
|
|
|
"github.com/mjl-/mox/metrics"
|
|
"github.com/mjl-/mox/mlog"
|
|
"github.com/mjl-/mox/mox-"
|
|
"github.com/mjl-/mox/moxvar"
|
|
)
|
|
|
|
// AuthDB and AuthDBTypes are exported for ../backup.go.
|
|
var AuthDB *bstore.DB
|
|
var AuthDBTypes = []any{TLSPublicKey{}, LoginAttempt{}, LoginAttemptState{}}
|
|
|
|
// Init opens auth.db and starts the login writer.
|
|
func Init(ctx context.Context) error {
|
|
if AuthDB != nil {
|
|
return fmt.Errorf("already initialized")
|
|
}
|
|
pkglog := mlog.New("store", nil)
|
|
p := mox.DataDirPath("auth.db")
|
|
os.MkdirAll(filepath.Dir(p), 0770)
|
|
opts := bstore.Options{Timeout: 5 * time.Second, Perm: 0660, RegisterLogger: moxvar.RegisterLogger(p, pkglog.Logger)}
|
|
var err error
|
|
AuthDB, err = bstore.Open(ctx, p, &opts, AuthDBTypes...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
startLoginAttemptWriter()
|
|
|
|
go func() {
|
|
defer func() {
|
|
x := recover()
|
|
if x == nil {
|
|
return
|
|
}
|
|
|
|
mlog.New("store", nil).Error("unhandled panic in LoginAttemptCleanup", slog.Any("err", x))
|
|
debug.PrintStack()
|
|
metrics.PanicInc(metrics.Store)
|
|
|
|
}()
|
|
|
|
t := time.NewTicker(24 * time.Hour)
|
|
for {
|
|
err := LoginAttemptCleanup(ctx)
|
|
pkglog.Check(err, "cleaning up old historic login attempts")
|
|
|
|
select {
|
|
case <-t.C:
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Close closes auth.db and stops the login writer.
|
|
func Close() error {
|
|
if AuthDB == nil {
|
|
return fmt.Errorf("not open")
|
|
}
|
|
|
|
stopc := make(chan struct{})
|
|
writeLoginAttemptStop <- stopc
|
|
<-stopc
|
|
|
|
err := AuthDB.Close()
|
|
AuthDB = nil
|
|
|
|
return err
|
|
}
|