diff options
author | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-07-16 16:05:18 +0200 |
---|---|---|
committer | HimbeerserverDE <himbeerserverde@gmail.com> | 2023-07-16 16:05:18 +0200 |
commit | 36bcdbd3fd50dd9d44d753d0bb3b7f89b28a74cd (patch) | |
tree | 893f3a6b8284a01bd23d0117d29e67cef7963f5c /auth_mtsqlite3.go | |
parent | 1a1ce068eea1d8200a748bc0b9e8a02e6d12c242 (diff) |
add rudimentary support for minetest's sqlite3 auth format
Diffstat (limited to 'auth_mtsqlite3.go')
-rw-r--r-- | auth_mtsqlite3.go | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/auth_mtsqlite3.go b/auth_mtsqlite3.go new file mode 100644 index 0000000..a267bc2 --- /dev/null +++ b/auth_mtsqlite3.go @@ -0,0 +1,195 @@ +package proxy + +import ( + "database/sql" + "errors" + "net" + "time" +) + +// A handle to a SQLite3 authentication database. +// The upstream Minetest schema is used. +// +// Table info: +// +// 0|id|INTEGER|0||1 +// 1|name|VARCHAR(32)|0||0 +// 2|password|VARCHAR(512)|0||0 +// 3|last_login|INTEGER|0||0 +type AuthMTSQLite3 struct { + db *sql.DB +} + +// NewAuthMTSQLite3 opens the SQLite3 authentication database at auth.sqlite. +func NewAuthMTSQLite3() (*AuthMTSQLite3, error) { + db, err := sql.Open("sqlite3", "auth.sqlite") + if err != nil { + return nil, err + } + + // Initialize the database if necessary. + if _, err := db.Exec("CREATE TABLE IF NOT EXISTS auth (id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(32) UNIQUE, password VARCHAR(512), last_login INTEGER);"); err != nil { + return nil, err + } + + return &AuthMTSQLite3{db}, nil +} + +// Close closes the underlying SQLite3 database handle. +func (a *AuthMTSQLite3) Close() error { + return a.db.Close() +} + +// Exists reports whether a user is registered. +// Error cases count as inexistent. +func (a *AuthMTSQLite3) Exists(name string) bool { + result := a.db.QueryRow("SELECT COUNT(1) FROM auth WHERE name = ?;", name) + + var count int + if err := result.Scan(&count); err != nil { + return false + } + + return count == 1 +} + +// Passwd returns the SRP salt and verifier of a user or an error. +func (a *AuthMTSQLite3) Passwd(name string) (salt, verifier []byte, err error) { + result := a.db.QueryRow("SELECT password FROM auth WHERE name = ?;", name) + + var encodedPasswd string + if err = result.Scan(&encodedPasswd); err != nil { + return + } + + salt, verifier, err = decodeVerifierAndSalt(encodedPasswd) + + a.updateTimestamp(name) + return +} + +// SetPasswd creates a password entry if necessary +// and sets the password of a user. +func (a *AuthMTSQLite3) SetPasswd(name string, salt, verifier []byte) error { + encodedPasswd := encodeVerifierAndSalt(salt, verifier) + + _, err := a.db.Exec("REPLACE INTO auth (name, password, last_login) VALUES (?, ?, unixepoch());", name, encodedPasswd) + return err +} + +// LastSrv always returns an error +// since the Minetest database schema cannot store this information. +func (a *AuthMTSQLite3) LastSrv(_ string) (string, error) { + return "", ErrLastSrvNotSupported +} + +// SetLastSrv is a no-op +// since the Minetest database schema cannot store this information. +func (a *AuthMTSQLite3) SetLastSrv(_, _ string) error { + return nil +} + +// Timestamp returns the last time an authentication entry was accessed +// or an error. +func (a *AuthMTSQLite3) Timestamp(name string) (time.Time, error) { + result := a.db.QueryRow("SELECT last_login FROM auth WHERE name = ?;", name) + + var timestamp int64 + if err := result.Scan(×tamp); err != nil { + return time.Time{}, err + } + + return time.Unix(timestamp, 0), nil +} + +// Import adds the passed users. +func (a *AuthMTSQLite3) Import(in []user) error { + for _, u := range in { + if err := a.SetPasswd(u.name, u.salt, u.verifier); err != nil { + return err + } + + a.setTimestamp(u.name, u.timestamp) + } + + return nil +} + +// Export returns data that can be processed by Import +// or an error. +func (a *AuthMTSQLite3) Export() ([]user, error) { + var names []string + result := a.db.QueryRow("SELECT name FROM auth;") + + for { + var name string + if err := result.Scan(&name); err != nil { + if errors.Is(err, sql.ErrNoRows) { + break + } + + return nil, err + } + + names = append(names, name) + } + + var out []user + for _, name := range names { + u := user{name: name} + + var err error + u.timestamp, err = a.Timestamp(u.name) + if err != nil { + return nil, err + } + + u.salt, u.verifier, err = a.Passwd(u.name) + if err != nil { + return nil, err + } + + out = append(out, u) + } + + return out, nil +} + +// Ban always returns an error +// since the Minetest database schema cannot store this information. +func (a *AuthMTSQLite3) Ban(_, _ string) error { + return ErrBanNotSupported +} + +// Unban always returns an error +// since the Minetest database schema cannot store this information. +func (a *AuthMTSQLite3) Unban(_ string) error { + return ErrBanNotSupported +} + +// Banned always reports that the user is not banned +// since the Minetest database schema cannot store this information. +func (a *AuthMTSQLite3) Banned(_ *net.UDPAddr) bool { + return false +} + +// ImportBans always returns an error +// since the Minetest database schema cannot store this information. +func (a *AuthMTSQLite3) ImportBans(in []ban) error { + return ErrBanNotSupported +} + +// ExportBans always returns an empty list of ban entries +// since the Minetest database schema cannot store this information. +func (a *AuthMTSQLite3) ExportBans() ([]ban, error) { + return []ban{}, nil +} + +func (a *AuthMTSQLite3) setTimestamp(name string, t time.Time) { + timestamp := t.Unix() + a.db.Exec("UPDATE auth SET last_login = ? WHERE name = ?;", timestamp, name) +} + +func (a *AuthMTSQLite3) updateTimestamp(name string) { + a.setTimestamp(name, time.Now().Local()) +} |