mirror of
https://github.com/ghostersk/gowebmail.git
synced 2026-04-17 08:36:01 +01:00
added parameters to list bans and unban ip from terminal
This commit is contained in:
@@ -47,6 +47,16 @@ func main() {
|
||||
}
|
||||
runDisableMFA(args[1])
|
||||
return
|
||||
case "--blocklist":
|
||||
runBlockList()
|
||||
return
|
||||
case "--unblock":
|
||||
if len(args) < 2 {
|
||||
fmt.Fprintln(os.Stderr, "Usage: gowebmail --unblock <ip>")
|
||||
os.Exit(1)
|
||||
}
|
||||
runUnblock(args[1])
|
||||
return
|
||||
case "--help", "-h":
|
||||
printHelp()
|
||||
return
|
||||
@@ -335,6 +345,69 @@ func runDisableMFA(username string) {
|
||||
fmt.Printf("MFA disabled for admin '%s'. They can now log in with password only.\n", username)
|
||||
}
|
||||
|
||||
func runBlockList() {
|
||||
database, close := openDB()
|
||||
defer close()
|
||||
|
||||
blocks, err := database.ListIPBlocksWithUsername()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if len(blocks) == 0 {
|
||||
fmt.Println("No blocked IPs.")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Printf("%-18s %-20s %-5s %-22s %-22s %s\n",
|
||||
"IP", "USERNAME USED", "TRIES", "BLOCKED AT", "EXPIRES", "REMAINING")
|
||||
fmt.Printf("%-18s %-20s %-5s %-22s %-22s %s\n",
|
||||
"--", "-------------", "-----", "----------", "-------", "---------")
|
||||
for _, b := range blocks {
|
||||
blockedAt := b.BlockedAt.UTC().Format("2006-01-02 15:04:05")
|
||||
var expires, remaining string
|
||||
if b.IsPermanent || b.ExpiresAt == nil {
|
||||
expires = "permanent"
|
||||
remaining = "∞ (manual unblock)"
|
||||
} else {
|
||||
expires = b.ExpiresAt.UTC().Format("2006-01-02 15:04:05")
|
||||
left := time.Until(*b.ExpiresAt)
|
||||
if left <= 0 {
|
||||
remaining = "expired (purge pending)"
|
||||
} else {
|
||||
h := int(left.Hours())
|
||||
m := int(left.Minutes()) % 60
|
||||
s := int(left.Seconds()) % 60
|
||||
if h > 0 {
|
||||
remaining = fmt.Sprintf("%dh %dm", h, m)
|
||||
} else if m > 0 {
|
||||
remaining = fmt.Sprintf("%dm %ds", m, s)
|
||||
} else {
|
||||
remaining = fmt.Sprintf("%ds", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
username := b.LastUsername
|
||||
if username == "" {
|
||||
username = "(unknown)"
|
||||
}
|
||||
fmt.Printf("%-18s %-20s %-5d %-22s %-22s %s\n",
|
||||
b.IP, username, b.Attempts, blockedAt, expires, remaining)
|
||||
}
|
||||
fmt.Printf("\nTotal: %d blocked IP(s)\n", len(blocks))
|
||||
}
|
||||
|
||||
func runUnblock(ip string) {
|
||||
database, close := openDB()
|
||||
defer close()
|
||||
|
||||
if err := database.UnblockIP(ip); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error unblocking %s: %v\n", ip, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("IP %s has been unblocked.\n", ip)
|
||||
}
|
||||
|
||||
func printHelp() {
|
||||
fmt.Print(`GoWebMail — Admin CLI
|
||||
|
||||
@@ -343,13 +416,17 @@ Usage:
|
||||
gowebmail --list-admin List all admin accounts (username, email, MFA status)
|
||||
gowebmail --pw <username> <pass> Reset password for an admin account
|
||||
gowebmail --mfa-off <username> Disable MFA for an admin account
|
||||
gowebmail --blocklist List all currently blocked IP addresses
|
||||
gowebmail --unblock <ip> Remove block for a specific IP address
|
||||
|
||||
Examples:
|
||||
./gowebmail --list-admin
|
||||
./gowebmail --pw admin "NewSecurePass123"
|
||||
./gowebmail --mfa-off admin
|
||||
./gowebmail --blocklist
|
||||
./gowebmail --unblock 1.2.3.4
|
||||
|
||||
Note: These commands only work on admin accounts.
|
||||
Note: --list-admin, --pw, and --mfa-off only work on admin accounts.
|
||||
Regular user management is done through the web UI.
|
||||
Requires the same environment variables as the server (DB_PATH, ENCRYPTION_KEY, etc).
|
||||
`)
|
||||
|
||||
@@ -1838,11 +1838,11 @@ func (d *DB) ListIPBlocks() ([]IPBlock, error) {
|
||||
var result []IPBlock
|
||||
for rows.Next() {
|
||||
var b IPBlock
|
||||
var expiresStr sql.NullString
|
||||
var expiresAt sql.NullTime
|
||||
rows.Scan(&b.ID, &b.IP, &b.Reason, &b.Country, &b.CountryCode,
|
||||
&b.Attempts, &b.BlockedAt, &expiresStr, &b.IsPermanent)
|
||||
if expiresStr.Valid {
|
||||
t, _ := time.Parse("2006-01-02 15:04:05", expiresStr.String)
|
||||
&b.Attempts, &b.BlockedAt, &expiresAt, &b.IsPermanent)
|
||||
if expiresAt.Valid {
|
||||
t := expiresAt.Time
|
||||
b.ExpiresAt = &t
|
||||
}
|
||||
result = append(result, b)
|
||||
@@ -1988,3 +1988,51 @@ func splitIPs(s string) []string {
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// IPBlockWithUsername extends IPBlock with the last username attempted from that IP.
|
||||
type IPBlockWithUsername struct {
|
||||
IPBlock
|
||||
LastUsername string
|
||||
}
|
||||
|
||||
// ListIPBlocksWithUsername returns active blocks enriched with the most recent
|
||||
// username that was attempted from each IP (from login_attempts history).
|
||||
func (d *DB) ListIPBlocksWithUsername() ([]IPBlockWithUsername, error) {
|
||||
rows, err := d.sql.Query(`
|
||||
SELECT
|
||||
b.id, b.ip, b.reason, b.country, b.country_code,
|
||||
b.attempts, b.blocked_at, b.expires_at, b.is_permanent,
|
||||
COALESCE(
|
||||
(SELECT username FROM login_attempts
|
||||
WHERE ip=b.ip AND username != ''
|
||||
ORDER BY created_at DESC LIMIT 1),
|
||||
''
|
||||
) AS last_username
|
||||
FROM ip_blocks b
|
||||
WHERE b.is_permanent=1 OR b.expires_at IS NULL OR b.expires_at > datetime('now')
|
||||
ORDER BY b.blocked_at DESC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var result []IPBlockWithUsername
|
||||
for rows.Next() {
|
||||
var b IPBlockWithUsername
|
||||
var expiresAt sql.NullTime
|
||||
err := rows.Scan(
|
||||
&b.ID, &b.IP, &b.Reason, &b.Country, &b.CountryCode,
|
||||
&b.Attempts, &b.BlockedAt, &expiresAt, &b.IsPermanent,
|
||||
&b.LastUsername,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if expiresAt.Valid {
|
||||
t := expiresAt.Time
|
||||
b.ExpiresAt = &t
|
||||
}
|
||||
result = append(result, b)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
@@ -39,5 +39,5 @@
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/admin.js?v=21"></script>
|
||||
<script src="/static/js/admin.js?v=23"></script>
|
||||
{{end}}
|
||||
@@ -352,5 +352,5 @@
|
||||
{{end}}
|
||||
|
||||
{{define "scripts"}}
|
||||
<script src="/static/js/app.js?v=21"></script>
|
||||
<script src="/static/js/app.js?v=23"></script>
|
||||
{{end}}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{block "title" .}}GoWebMail{{end}}</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Serif+Display&family=DM+Sans:ital,wght@0,300;0,400;0,500;1,400&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="/static/css/gowebmail.css?v=21">
|
||||
<link rel="stylesheet" href="/static/css/gowebmail.css?v=23">
|
||||
{{block "head_extra" .}}{{end}}
|
||||
</head>
|
||||
<body class="{{block "body_class" .}}{{end}}">
|
||||
{{block "body" .}}{{end}}
|
||||
<script src="/static/js/gowebmail.js?v=21"></script>
|
||||
<script src="/static/js/gowebmail.js?v=23"></script>
|
||||
{{block "scripts" .}}{{end}}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user