Files
latchd/legacy/go/auth/faillock.go

78 lines
1.5 KiB
Go

package auth
import (
"bytes"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
)
type FaillockStatus struct {
Enabled bool
Failures int
MaxTries int
Remaining int
LockedOut bool
}
func GetFaillockStatus(user string) (FaillockStatus, error) {
s := FaillockStatus{}
data, err := os.ReadFile("/etc/security/faillock.conf")
if err != nil {
return s, nil
}
s.Enabled = true
s.MaxTries = 3
for _, line := range strings.Split(string(data), "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "deny") {
parts := strings.Split(line, "=")
if len(parts) == 2 {
if n, err := strconv.Atoi(strings.TrimSpace(parts[1])); err == nil {
s.MaxTries = n
}
}
}
}
var out bytes.Buffer
cmd := exec.Command("faillock", "--user", user)
cmd.Stdout = &out
cmd.Stderr = &out
if err := cmd.Run(); err != nil {
return s, fmt.Errorf("faillock: %w", err)
}
s.Failures = countFaillockLines(out.String())
s.Remaining = s.MaxTries - s.Failures
if s.Remaining < 0 {
s.Remaining = 0
}
s.LockedOut = s.Remaining <= 0
return s, nil
}
func ResetFaillock(user string) error {
return exec.Command("faillock", "--user", user, "--reset").Run()
}
func countFaillockLines(out string) int {
n := 0
for _, line := range strings.Split(out, "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "When") ||
strings.HasPrefix(line, "Source") || strings.HasPrefix(line, "Date") {
continue
}
if strings.Contains(line, ":") {
continue
}
n++
}
return n
}