mirror of
https://github.com/andsyrovatko/s4k-ip-manager.git
synced 2026-04-21 22:18:53 +02:00
Initial release: Billing infrastructure automation (IP manager).
This commit is contained in:
+29
@@ -0,0 +1,29 @@
|
|||||||
|
# Build and Release Folders
|
||||||
|
bin-debug/
|
||||||
|
bin-release/
|
||||||
|
[Oo]bj/
|
||||||
|
[Bb]in/
|
||||||
|
|
||||||
|
# Other files and folders
|
||||||
|
.settings/
|
||||||
|
logs/
|
||||||
|
.logs/
|
||||||
|
sps_logs/*
|
||||||
|
.sps_logs/*
|
||||||
|
|
||||||
|
# Executables
|
||||||
|
*.conf
|
||||||
|
*.swf
|
||||||
|
*.air
|
||||||
|
*.ipa
|
||||||
|
*.apk
|
||||||
|
*.log
|
||||||
|
*.tmp
|
||||||
|
*.log*
|
||||||
|
*.html*
|
||||||
|
*tmp_*
|
||||||
|
*variables*
|
||||||
|
|
||||||
|
# Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties`
|
||||||
|
# should NOT be excluded as they contain compiler settings and other important
|
||||||
|
# information for Eclipse / Flash Builder.
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Andrii Syrovatko
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
# IP Manager for ISPs (Billing ↔ IPSET)
|
||||||
|
|
||||||
|
### 📋 Overview
|
||||||
|
This script acts as a robust bridge between the **BOSS (Billing System)** (or any other billing/CRM) and the **Linux Netfilter (ipset)**. It automates customer access control by dynamically moving IP addresses between different firewall sets based on their current account status.
|
||||||
|
|
||||||
|
### ⚙️ How It Works
|
||||||
|
The system logic relies on two primary IPSET groups:
|
||||||
|
* **Allowed (`allowed_customers_nets`)**: IPs in this set are granted full Internet access.
|
||||||
|
* **Restricted (`restricted_customers_nets`)**: IPs in this set are redirected to a captive portal (e.g., for billing reminders or payment pages).
|
||||||
|
|
||||||
|
### 🚀 Commands & Logic Flow
|
||||||
|
The billing system invokes this script with specific commands to reflect customer state changes:
|
||||||
|
|
||||||
|
| Command | Action | Customer Status / Use Case |
|
||||||
|
| :--- | :--- | :--- |
|
||||||
|
| **`NEW`** | Add IP to *Allowed* | New connection or service activation. |
|
||||||
|
| **`RESTRICT`** | Add to *Allowed* + *Restricted* | Balance is zero; redirecting to portal. |
|
||||||
|
| **`RESUME`** | Remove from *Restricted* | Payment received; restoring access. |
|
||||||
|
| **`SUSPEND`** | Remove from both sets | Manual temporary service suspension. |
|
||||||
|
| **`DELETE`** | Remove from both sets | Contract terminated or account closed. |
|
||||||
|
| **`UPDATE`** | Swap `OLD_IP` with `NEW_IP` | Change of equipment or static IP address. |
|
||||||
|
|
||||||
|
### 🛠 Installation & Setup
|
||||||
|
1. **Clone the repository:**
|
||||||
|
```bash
|
||||||
|
git clone [https://github.com/your-username/s4k-ip-manager.git](https://github.com/your-username/s4k-ip-manager.git)
|
||||||
|
cd s4k-ip-manager
|
||||||
|
```
|
||||||
|
2. **Configure the environment:**
|
||||||
|
Create your local configuration file from the provided example:
|
||||||
|
```bash
|
||||||
|
cp ip_manager.conf.example ip_manager.conf
|
||||||
|
```
|
||||||
|
Edit `ip_manager.conf` to set your specific IPSET names, log paths, and email for alerts.
|
||||||
|
3. **Prepare log directories:**
|
||||||
|
```bash
|
||||||
|
sudo mkdir -p /var/log/ip-manager
|
||||||
|
sudo chown $USER:$USER /var/log/ip-manager
|
||||||
|
```
|
||||||
|
4. **Testing:**
|
||||||
|
Before running in production, ensure `DRY_RUN=1` is set in your .conf file to simulate actions without modifying live firewall rules.
|
||||||
|
|
||||||
|
### 🛡 Reliability & Safety Features
|
||||||
|
* **Atomic-like Locking:** Utilizes flock to manage a wait queue, preventing race conditions when the billing system sends hundreds of concurrent updates.
|
||||||
|
* **Strict Validation:** Uses regex to validate IPv4 formats and automatically cleans input (e.g., stripping trailing /32 masks).
|
||||||
|
* **State Persistence:** Automatically executes ipset save to ensure changes survive a system reboot.
|
||||||
|
* **Linter-Friendly:** Fully compliant with ShellCheck (SC1090 handled) for high-quality, predictable execution.
|
||||||
|
|
||||||
|
### ⚖️ License
|
||||||
|
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
||||||
|
|
||||||
|
|
||||||
|
### Use at your own risk! The author is not responsible for any data loss!
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# ip_manager.conf - Configuration for IPSET Management Script
|
||||||
|
|
||||||
|
# --- IPSET Sets ---
|
||||||
|
# Set for active customers with Internet access
|
||||||
|
ALLOW_SET="allowed_customers_nets"
|
||||||
|
# Set for customers redirected to the captive portal (no payment)
|
||||||
|
RESTRICT_SET="restricted_customers_nets"
|
||||||
|
|
||||||
|
# --- Paths & Binaries ---
|
||||||
|
LOG_DIR="/var/log/ip-manager"
|
||||||
|
LOG_FILE="${LOG_DIR}/ip-manager.log"
|
||||||
|
IPSET_SAVE_FILE="/etc/iptables/ipsets"
|
||||||
|
LOCK_FILE="/tmp/ip-manager.lock"
|
||||||
|
|
||||||
|
# --- Notifications ---
|
||||||
|
# Email to receive lock timeout warnings
|
||||||
|
MAIL_RECEIVER="support@your-isp.net"
|
||||||
|
|
||||||
|
# --- Safety Settings ---
|
||||||
|
LOCK_TIMEOUT=300
|
||||||
|
# Set to 0 for production, 1 for testing
|
||||||
|
DRY_RUN=1
|
||||||
Executable
+386
@@ -0,0 +1,386 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# =============================================================================
|
||||||
|
# Script Name : ip_manager.sh
|
||||||
|
# Description : Manage customer IPs in IPSET sets (allow / restrict)
|
||||||
|
# Called by BOSS4 billing system on customer state changes.
|
||||||
|
#
|
||||||
|
# Commands (also PARAMETERS):
|
||||||
|
# NEW IP — new customer: add to ALLOW set
|
||||||
|
# DELETE IP — delete/prototype: remove from BOTH sets
|
||||||
|
# RESTRICT IP — no payment: add to BOTH sets (allow + restrict)
|
||||||
|
# SUSPEND IP — manual suspend: remove from BOTH sets
|
||||||
|
# RESUME IP — resume: remove from restrict, add to allow (if not there)
|
||||||
|
# UPDATE IP_OLD IP_NEW — IP/tariff change: replace OLD with NEW in each set
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# ./ip_manager.sh COMMAND IP # for NEW/DELETE/RESTRICT/SUSPEND/RESUME
|
||||||
|
# ./ip_manager.sh UPDATE IP_OLD IP_NEW # for UPDATE
|
||||||
|
#
|
||||||
|
# Author : syr4ok (Andrii Syrovatko)
|
||||||
|
# Version : 2.0.1r
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# --- STRICT MODE ---
|
||||||
|
set -uo pipefail
|
||||||
|
IFS=$'\n\t'
|
||||||
|
|
||||||
|
# --- Configuration Loader ---
|
||||||
|
CONFIG_FILE="$(dirname "$0")/ip_manager.conf"
|
||||||
|
|
||||||
|
if [[ -f "$CONFIG_FILE" ]]; then
|
||||||
|
# shellcheck source=/dev/null
|
||||||
|
source "$CONFIG_FILE"
|
||||||
|
else
|
||||||
|
echo "Error: Configuration file not found. Create ip_manager.conf from example."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Environment & Tools ---
|
||||||
|
IPSET_BIN=$(which ipset 2>/dev/null || true)
|
||||||
|
MAIL_BIN=$(which mail 2>/dev/null || true)
|
||||||
|
|
||||||
|
if [[ -z "$IPSET_BIN" ]]; then
|
||||||
|
echo "Error: 'ipset' utility not found. Please install it."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ -z "$MAIL_BIN" ]]; then
|
||||||
|
echo "Warning: 'mail' utility not found. Notifications will be disabled."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- LOG COLORS ---
|
||||||
|
RED='\033[0;31m'
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
YELLOW='\033[1;33m'
|
||||||
|
BLUE='\033[0;34m'
|
||||||
|
CYAN='\033[0;36m'
|
||||||
|
BOLD='\033[1m'
|
||||||
|
NC='\033[0m'
|
||||||
|
|
||||||
|
# INIT — ensure log directory and file exist
|
||||||
|
mkdir -p "${LOG_DIR}" 2>/dev/null || true
|
||||||
|
CAN_LOG_TO_FILE=0
|
||||||
|
if touch "${LOG_FILE}" 2>/dev/null; then
|
||||||
|
CAN_LOG_TO_FILE=1
|
||||||
|
echo -e "${BLUE}Info: Log file (${LOG_FILE}) created or exists. Logging to file enabled.${NC}"
|
||||||
|
else
|
||||||
|
echo -e "${YELLOW}Warning: Cannot write to log file (${LOG_FILE}). Logging to file disabled.${NC}"
|
||||||
|
fi
|
||||||
|
# --- LOGGING ---
|
||||||
|
log() {
|
||||||
|
local level="$1"; shift
|
||||||
|
local msg="$*"
|
||||||
|
local ts
|
||||||
|
ts=$(date '+%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
|
# Clear colors for file logging (keep colors in console, but remove them from file logs)
|
||||||
|
local plain_msg
|
||||||
|
plain_msg=$(echo -e "${msg}" | sed 's/\x1B\[[0-9;]*[mK]//g')
|
||||||
|
|
||||||
|
# Write logs to file if possible
|
||||||
|
if [[ "${CAN_LOG_TO_FILE}" -eq 1 ]]; then
|
||||||
|
echo "${ts} [${level}] ${plain_msg}" >> "${LOG_FILE}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Type logs tto console forever
|
||||||
|
echo -e "${ts} [${level}] ${msg}"
|
||||||
|
}
|
||||||
|
|
||||||
|
log_info() { log "INFO " "${GREEN}${*}${NC}"; }
|
||||||
|
log_warn() { log "WARN " "${YELLOW}${*}${NC}"; }
|
||||||
|
log_error() { log "ERROR" "${RED}${*}${NC}"; }
|
||||||
|
log_debug() { log "DEBUG" "${CYAN}${*}${NC}"; }
|
||||||
|
log_sep() {
|
||||||
|
log "INFO " "${BLUE}------------------------------------------------------------------------------${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# LOCK — single instance with wait queue (flock-based)
|
||||||
|
acquire_lock() {
|
||||||
|
local call_args
|
||||||
|
call_args="$(IFS=" "; echo "$*")"
|
||||||
|
exec 9>"${LOCK_FILE}"
|
||||||
|
log_debug "Waiting for lock (timeout: ${LOCK_TIMEOUT}s) PID=$$"
|
||||||
|
if ! flock -w "${LOCK_TIMEOUT}" 9; then
|
||||||
|
log_error "Could not acquire lock after ${LOCK_TIMEOUT}s — another instance hung? Aborting!"
|
||||||
|
|
||||||
|
if [[ -n "${MAIL_BIN}" ]]; then
|
||||||
|
echo -e "WARN: $0\nProcess for [${call_args}] timed out by lockfile (${LOCK_FILE})\nafter allowed ${LOCK_TIMEOUT}s waiting.\nexit 9" \
|
||||||
|
| "${MAIL_BIN}" -s "WARN: Process timed out by lockfile after ${LOCK_TIMEOUT}s" "$MAIL_RECEIVER"
|
||||||
|
fi
|
||||||
|
exit 9
|
||||||
|
fi
|
||||||
|
echo $$ >&9
|
||||||
|
log_debug "Lock acquired by PID=$$"
|
||||||
|
}
|
||||||
|
|
||||||
|
release_lock() {
|
||||||
|
flock -u 9
|
||||||
|
exec 9>&-
|
||||||
|
log_debug "Lock released by PID=$$"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- HELPERS ---
|
||||||
|
usage() {
|
||||||
|
echo -e "${BOLD}Usage:${NC}"
|
||||||
|
echo -e " $0 ${CYAN}NEW${NC} IP"
|
||||||
|
echo -e " $0 ${CYAN}DELETE${NC} IP"
|
||||||
|
echo -e " $0 ${CYAN}RESTRICT${NC} IP"
|
||||||
|
echo -e " $0 ${CYAN}SUSPEND${NC} IP"
|
||||||
|
echo -e " $0 ${CYAN}RESUME${NC} IP"
|
||||||
|
echo -e " $0 ${CYAN}UPDATE${NC} IP_OLD IP_NEW"
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
strip_mask() {
|
||||||
|
local val="${1}"
|
||||||
|
val="${val// /}" # trim all spaces
|
||||||
|
echo "${val%%/32}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Validate IPv4 address (without mask)
|
||||||
|
is_valid_ip() {
|
||||||
|
local ip="$1"
|
||||||
|
local octet='(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
|
||||||
|
[[ "${ip}" =~ ^${octet}\.${octet}\.${octet}\.${octet}$ ]]
|
||||||
|
}
|
||||||
|
|
||||||
|
ipset_exists() {
|
||||||
|
local set="$1"
|
||||||
|
local ip="$2"
|
||||||
|
# grep -q closes pipe early after first match → ipset gets SIGPIPE → pipefail returns 141
|
||||||
|
# fix: read full ipset output first, then grep from variable
|
||||||
|
local dump
|
||||||
|
dump=$("${IPSET_BIN}" -L "${set}" 2>/dev/null)
|
||||||
|
grep -qw "^${ip}$" <<< "${dump}"
|
||||||
|
}
|
||||||
|
|
||||||
|
ipset_add() {
|
||||||
|
local set="$1"
|
||||||
|
local ip="$2"
|
||||||
|
|
||||||
|
if ipset_exists "${set}" "${ip}"; then
|
||||||
|
log_warn "IP ${ip} already in set [${set}] — skipping add"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_debug "ipset add ${set} ${ip}"
|
||||||
|
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
||||||
|
log_warn "[DRY-RUN] Would ADD ${ip} to [${set}]"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! "${IPSET_BIN}" add "${set}" "${ip}" 2>&1 | tee -a "${LOG_FILE}"; then
|
||||||
|
log_error "Failed to ADD ${ip} to [${set}]"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log_info "Added ${ip} → [${set}]"
|
||||||
|
}
|
||||||
|
|
||||||
|
ipset_del() {
|
||||||
|
local set="$1"
|
||||||
|
local ip="$2"
|
||||||
|
|
||||||
|
if ! ipset_exists "${set}" "${ip}"; then
|
||||||
|
log_warn "IP ${ip} not found in set [${set}] — skipping del"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
log_debug "ipset del ${set} ${ip}"
|
||||||
|
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
||||||
|
log_warn "[DRY-RUN] Would DEL ${ip} from [${set}]"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! "${IPSET_BIN}" del "${set}" "${ip}" 2>&1 | tee -a "${LOG_FILE}"; then
|
||||||
|
log_error "Failed to DEL ${ip} from [${set}]"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log_info "Removed ${ip} ← [${set}]"
|
||||||
|
}
|
||||||
|
|
||||||
|
ipset_save() {
|
||||||
|
log_debug "Saving ipset state to ${IPSET_SAVE_FILE}"
|
||||||
|
if [[ "${DRY_RUN}" -eq 1 ]]; then
|
||||||
|
log_warn "[DRY-RUN] Would save ipset state to ${IPSET_SAVE_FILE}"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
if ! "${IPSET_BIN}" save > "${IPSET_SAVE_FILE}" 2>&1; then
|
||||||
|
log_error "Failed to save ipset state to ${IPSET_SAVE_FILE}"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
log_info "ipset state saved → ${IPSET_SAVE_FILE}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- COMMAND HANDLERS ---
|
||||||
|
cmd_new() {
|
||||||
|
local ip="$1"
|
||||||
|
log_info "[NEW] New customer IP: ${ip} — adding to allow set"
|
||||||
|
ipset_add "${ALLOW_SET}" "${ip}"
|
||||||
|
ipset_save
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_delete() {
|
||||||
|
local ip="$1"
|
||||||
|
log_info "[DELETE] Removing ${ip} from BOTH sets"
|
||||||
|
ipset_del "${ALLOW_SET}" "${ip}"
|
||||||
|
ipset_del "${RESTRICT_SET}" "${ip}"
|
||||||
|
ipset_save
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_restrict() {
|
||||||
|
local ip="$1"
|
||||||
|
log_info "[RESTRICT] Adding ${ip} to ALLOW + RESTRICT sets (no payment)"
|
||||||
|
ipset_add "${ALLOW_SET}" "${ip}"
|
||||||
|
ipset_add "${RESTRICT_SET}" "${ip}"
|
||||||
|
ipset_save
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_suspend() {
|
||||||
|
local ip="$1"
|
||||||
|
log_info "[SUSPEND] Removing ${ip} from BOTH sets (manual suspend)"
|
||||||
|
ipset_del "${ALLOW_SET}" "${ip}"
|
||||||
|
ipset_del "${RESTRICT_SET}" "${ip}"
|
||||||
|
ipset_save
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_resume() {
|
||||||
|
local ip="$1"
|
||||||
|
log_info "[RESUME] Resuming ${ip}: remove from restrict, ensure in allow"
|
||||||
|
ipset_del "${RESTRICT_SET}" "${ip}"
|
||||||
|
ipset_add "${ALLOW_SET}" "${ip}"
|
||||||
|
ipset_save
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd_update() {
|
||||||
|
local ip_old="$1"
|
||||||
|
local ip_new="$2"
|
||||||
|
log_info "[UPDATE] Replacing ${ip_old} → ${ip_new}"
|
||||||
|
|
||||||
|
local changed=0
|
||||||
|
|
||||||
|
if ipset_exists "${ALLOW_SET}" "${ip_old}"; then
|
||||||
|
log_debug "${ip_old} found in [${ALLOW_SET}] — swapping"
|
||||||
|
ipset_del "${ALLOW_SET}" "${ip_old}"
|
||||||
|
ipset_add "${ALLOW_SET}" "${ip_new}"
|
||||||
|
changed=1
|
||||||
|
else
|
||||||
|
log_warn "${ip_old} not found in [${ALLOW_SET}] — no change in this set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ipset_exists "${RESTRICT_SET}" "${ip_old}"; then
|
||||||
|
log_debug "${ip_old} found in [${RESTRICT_SET}] — swapping"
|
||||||
|
ipset_del "${RESTRICT_SET}" "${ip_old}"
|
||||||
|
ipset_add "${RESTRICT_SET}" "${ip_new}"
|
||||||
|
changed=1
|
||||||
|
else
|
||||||
|
log_warn "${ip_old} not found in [${RESTRICT_SET}] — no change in this set"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${changed}" -eq 0 ]]; then
|
||||||
|
log_warn "[UPDATE] ${ip_old} was not found in ANY set — nothing was changed"
|
||||||
|
fi
|
||||||
|
|
||||||
|
ipset_save
|
||||||
|
}
|
||||||
|
|
||||||
|
# MAIN EXIT HELPER — always logs final status + releases lock before return
|
||||||
|
main_exit() {
|
||||||
|
local code="$1"
|
||||||
|
if [[ "${code}" -eq 0 ]]; then
|
||||||
|
log_info "Done. Exit 0 (OK)"
|
||||||
|
else
|
||||||
|
log_error "Finished with errors (code: ${code})"
|
||||||
|
fi
|
||||||
|
# log_sep
|
||||||
|
release_lock
|
||||||
|
# sleep 45 # DEBUG: keep lock released for a while to allow testing concurrent runs and lock timeout
|
||||||
|
return "${code}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# MAIN
|
||||||
|
main() {
|
||||||
|
local error_code=0
|
||||||
|
|
||||||
|
log_sep
|
||||||
|
log_info "Invoked: $0"
|
||||||
|
log_info "Arguments ($#): $(IFS=" "; echo "$*")"
|
||||||
|
acquire_lock "$@"
|
||||||
|
[[ "${DRY_RUN}" -eq 1 ]] && log_warn "=== DRY-RUN MODE — no real ipset actions will be performed ==="
|
||||||
|
|
||||||
|
if [[ $# -lt 2 ]]; then
|
||||||
|
log_error "Not enough arguments! At least 2 required, got $#"
|
||||||
|
usage
|
||||||
|
main_exit 1; return
|
||||||
|
fi
|
||||||
|
|
||||||
|
local raw_command="${1^^}"
|
||||||
|
local ip_arg1
|
||||||
|
local ip_arg2
|
||||||
|
|
||||||
|
ip_arg1=$(strip_mask "${2:-}")
|
||||||
|
ip_arg2=$(strip_mask "${3:-}")
|
||||||
|
|
||||||
|
log_info "COMMAND: ${raw_command} | IP1: ${ip_arg1:-—} | IP2: ${ip_arg2:-—}"
|
||||||
|
|
||||||
|
# Validate IP1
|
||||||
|
if [[ -z "${ip_arg1}" ]]; then
|
||||||
|
log_error "IP1 is empty or blank!"
|
||||||
|
main_exit 1; return
|
||||||
|
fi
|
||||||
|
if ! is_valid_ip "${ip_arg1}"; then
|
||||||
|
log_error "IP1 is not a valid IPv4 address: '${ip_arg1}'"
|
||||||
|
main_exit 1; return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Validate IP2 only for UPDATE
|
||||||
|
if [[ "${raw_command}" == "UPDATE" ]]; then
|
||||||
|
if [[ $# -ne 3 ]]; then
|
||||||
|
log_error "UPDATE requires exactly 2 IPs: IP_OLD IP_NEW, got $(( $# - 1 )) IP(s)"
|
||||||
|
usage
|
||||||
|
main_exit 1; return
|
||||||
|
fi
|
||||||
|
if [[ -z "${ip_arg2}" ]]; then
|
||||||
|
log_warn "[UPDATE] IP_NEW is empty or blank — nothing to do"
|
||||||
|
main_exit 0; return
|
||||||
|
fi
|
||||||
|
if ! is_valid_ip "${ip_arg2}"; then
|
||||||
|
log_error "IP2 (IP_NEW) is not a valid IPv4 address: '${ip_arg2}'"
|
||||||
|
main_exit 1; return
|
||||||
|
fi
|
||||||
|
if [[ "${ip_arg1}" == "${ip_arg2}" ]]; then
|
||||||
|
log_warn "[UPDATE] IP_OLD == IP_NEW (${ip_arg1}) — nothing to do"
|
||||||
|
main_exit 0; return
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "${raw_command}" in
|
||||||
|
|
||||||
|
NEW | DELETE | RESTRICT | SUSPEND | RESUME)
|
||||||
|
if [[ $# -gt 2 ]]; then
|
||||||
|
log_error "Command ${raw_command} accepts exactly 1 IP, but got $(( $# - 1 )): ${*:2}"
|
||||||
|
usage
|
||||||
|
main_exit 1; return
|
||||||
|
fi
|
||||||
|
case "${raw_command}" in
|
||||||
|
NEW) cmd_new "${ip_arg1}" ;;
|
||||||
|
DELETE) cmd_delete "${ip_arg1}" ;;
|
||||||
|
RESTRICT) cmd_restrict "${ip_arg1}" ;;
|
||||||
|
SUSPEND) cmd_suspend "${ip_arg1}" ;;
|
||||||
|
RESUME) cmd_resume "${ip_arg1}" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
|
||||||
|
UPDATE)
|
||||||
|
cmd_update "${ip_arg1}" "${ip_arg2}"
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
log_error "Unknown command: '${raw_command}'"
|
||||||
|
usage
|
||||||
|
main_exit 5; return
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
main_exit "${error_code}"
|
||||||
|
}
|
||||||
|
|
||||||
|
main "$@"; _ec=$?
|
||||||
|
[[ "${DRY_RUN}" -eq 1 ]] && exit 0 || exit "${_ec}"
|
||||||
Reference in New Issue
Block a user