mirror of
https://github.com/ghostersk/gowebmail.git
synced 2026-09-15 00:00:36 +01:00
added parameters to list bans and unban ip from terminal
This commit is contained in:
+78
-1
@@ -47,6 +47,16 @@ func main() {
|
|||||||
}
|
}
|
||||||
runDisableMFA(args[1])
|
runDisableMFA(args[1])
|
||||||
return
|
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":
|
case "--help", "-h":
|
||||||
printHelp()
|
printHelp()
|
||||||
return
|
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)
|
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() {
|
func printHelp() {
|
||||||
fmt.Print(`GoWebMail — Admin CLI
|
fmt.Print(`GoWebMail — Admin CLI
|
||||||
|
|
||||||
@@ -343,13 +416,17 @@ Usage:
|
|||||||
gowebmail --list-admin List all admin accounts (username, email, MFA status)
|
gowebmail --list-admin List all admin accounts (username, email, MFA status)
|
||||||
gowebmail --pw <username> <pass> Reset password for an admin account
|
gowebmail --pw <username> <pass> Reset password for an admin account
|
||||||
gowebmail --mfa-off <username> Disable MFA 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:
|
Examples:
|
||||||
./gowebmail --list-admin
|
./gowebmail --list-admin
|
||||||
./gowebmail --pw admin "NewSecurePass123"
|
./gowebmail --pw admin "NewSecurePass123"
|
||||||
./gowebmail --mfa-off admin
|
./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.
|
Regular user management is done through the web UI.
|
||||||
Requires the same environment variables as the server (DB_PATH, ENCRYPTION_KEY, etc).
|
Requires the same environment variables as the server (DB_PATH, ENCRYPTION_KEY, etc).
|
||||||
`)
|
`)
|
||||||
|
|||||||
+52
-4
@@ -1838,11 +1838,11 @@ func (d *DB) ListIPBlocks() ([]IPBlock, error) {
|
|||||||
var result []IPBlock
|
var result []IPBlock
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var b IPBlock
|
var b IPBlock
|
||||||
var expiresStr sql.NullString
|
var expiresAt sql.NullTime
|
||||||
rows.Scan(&b.ID, &b.IP, &b.Reason, &b.Country, &b.CountryCode,
|
rows.Scan(&b.ID, &b.IP, &b.Reason, &b.Country, &b.CountryCode,
|
||||||
&b.Attempts, &b.BlockedAt, &expiresStr, &b.IsPermanent)
|
&b.Attempts, &b.BlockedAt, &expiresAt, &b.IsPermanent)
|
||||||
if expiresStr.Valid {
|
if expiresAt.Valid {
|
||||||
t, _ := time.Parse("2006-01-02 15:04:05", expiresStr.String)
|
t := expiresAt.Time
|
||||||
b.ExpiresAt = &t
|
b.ExpiresAt = &t
|
||||||
}
|
}
|
||||||
result = append(result, b)
|
result = append(result, b)
|
||||||
@@ -1988,3 +1988,51 @@ func splitIPs(s string) []string {
|
|||||||
}
|
}
|
||||||
return result
|
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}}
|
{{end}}
|
||||||
|
|
||||||
{{define "scripts"}}
|
{{define "scripts"}}
|
||||||
<script src="/static/js/admin.js?v=21"></script>
|
<script src="/static/js/admin.js?v=23"></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
@@ -352,5 +352,5 @@
|
|||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "scripts"}}
|
{{define "scripts"}}
|
||||||
<script src="/static/js/app.js?v=21"></script>
|
<script src="/static/js/app.js?v=23"></script>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -5,12 +5,12 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>{{block "title" .}}GoWebMail{{end}}</title>
|
<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 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}}
|
{{block "head_extra" .}}{{end}}
|
||||||
</head>
|
</head>
|
||||||
<body class="{{block "body_class" .}}{{end}}">
|
<body class="{{block "body_class" .}}{{end}}">
|
||||||
{{block "body" .}}{{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}}
|
{{block "scripts" .}}{{end}}
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Reference in New Issue
Block a user