|
@@ -89,6 +89,7 @@ SSH_SESSION_CACHE_TTL=10
|
|
|
SSH_SESSION_CACHE_TS=0
|
|
SSH_SESSION_CACHE_TS=0
|
|
|
SSH_SESSION_CACHE_DB_MTIME=0
|
|
SSH_SESSION_CACHE_DB_MTIME=0
|
|
|
SSH_SESSION_TOTAL=0
|
|
SSH_SESSION_TOTAL=0
|
|
|
|
|
+FF_USERS_GROUP="ffusers"
|
|
|
declare -A SSH_SESSION_COUNTS=()
|
|
declare -A SSH_SESSION_COUNTS=()
|
|
|
declare -A SSH_SESSION_PIDS=()
|
|
declare -A SSH_SESSION_PIDS=()
|
|
|
|
|
|
|
@@ -116,6 +117,100 @@ ensure_firewallfalcon_dirs() {
|
|
|
touch "$DB_FILE"
|
|
touch "$DB_FILE"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ensure_firewallfalcon_system_group() {
|
|
|
|
|
+ getent group "$FF_USERS_GROUP" >/dev/null 2>&1 || groupadd "$FF_USERS_GROUP" >/dev/null 2>&1 || true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+db_has_user() {
|
|
|
|
|
+ [[ -f "$DB_FILE" ]] || return 1
|
|
|
|
|
+ awk -F: -v target="$1" '$1 == target { found=1; exit } END { exit(found ? 0 : 1) }' "$DB_FILE"
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+is_firewallfalcon_orphan_user() {
|
|
|
|
|
+ local username="$1"
|
|
|
|
|
+ local passwd_line system_user _ uid _ home shell
|
|
|
|
|
+
|
|
|
|
|
+ passwd_line=$(getent passwd "$username" 2>/dev/null) || return 1
|
|
|
|
|
+ IFS=: read -r system_user _ uid _ _ home shell <<< "$passwd_line"
|
|
|
|
|
+ [[ "$uid" =~ ^[0-9]+$ ]] || return 1
|
|
|
|
|
+ db_has_user "$username" && return 1
|
|
|
|
|
+
|
|
|
|
|
+ if id -nG "$username" 2>/dev/null | tr ' ' '\n' | grep -Fxq "$FF_USERS_GROUP"; then
|
|
|
|
|
+ return 0
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ (( uid >= 1000 )) || return 1
|
|
|
|
|
+ [[ "$home" == "/home/$username" || "$home" == /home/* ]] || return 1
|
|
|
|
|
+
|
|
|
|
|
+ case "$shell" in
|
|
|
|
|
+ /usr/sbin/nologin|/usr/bin/false|/bin/false) return 0 ;;
|
|
|
|
|
+ esac
|
|
|
|
|
+
|
|
|
|
|
+ return 1
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+get_firewallfalcon_orphan_users() {
|
|
|
|
|
+ local username
|
|
|
|
|
+ while IFS=: read -r username _rest; do
|
|
|
|
|
+ [[ -n "$username" ]] || continue
|
|
|
|
|
+ if is_firewallfalcon_orphan_user "$username"; then
|
|
|
|
|
+ echo "$username"
|
|
|
|
|
+ fi
|
|
|
|
|
+ done < /etc/passwd
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+get_firewallfalcon_known_users() {
|
|
|
|
|
+ local username
|
|
|
|
|
+ local -A seen_users=()
|
|
|
|
|
+
|
|
|
|
|
+ if [[ -f "$DB_FILE" ]]; then
|
|
|
|
|
+ while IFS=: read -r username _rest; do
|
|
|
|
|
+ [[ -n "$username" && "$username" != \#* ]] || continue
|
|
|
|
|
+ seen_users["$username"]=1
|
|
|
|
|
+ done < "$DB_FILE"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ while IFS= read -r username; do
|
|
|
|
|
+ [[ -n "$username" ]] && seen_users["$username"]=1
|
|
|
|
|
+ done < <(get_firewallfalcon_orphan_users)
|
|
|
|
|
+
|
|
|
|
|
+ (( ${#seen_users[@]} > 0 )) || return 0
|
|
|
|
|
+ printf "%s\n" "${!seen_users[@]}" | sort
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+delete_firewallfalcon_user_accounts() {
|
|
|
|
|
+ local -a users_to_delete=("$@")
|
|
|
|
|
+ local username
|
|
|
|
|
+
|
|
|
|
|
+ [[ ${#users_to_delete[@]} -gt 0 ]] || return 0
|
|
|
|
|
+
|
|
|
|
|
+ for username in "${users_to_delete[@]}"; do
|
|
|
|
|
+ [[ -n "$username" ]] || continue
|
|
|
|
|
+ killall -u "$username" -9 &>/dev/null
|
|
|
|
|
+ if id "$username" &>/dev/null; then
|
|
|
|
|
+ if userdel -r "$username" &>/dev/null; then
|
|
|
|
|
+ echo -e " ✅ System user '${C_YELLOW}$username${C_RESET}' deleted."
|
|
|
|
|
+ else
|
|
|
|
|
+ echo -e " ❌ Failed to delete system user '${C_YELLOW}$username${C_RESET}'."
|
|
|
|
|
+ fi
|
|
|
|
|
+ else
|
|
|
|
|
+ echo -e " ℹ️ System user '${C_YELLOW}$username${C_RESET}' was already missing. Removing manager data only."
|
|
|
|
|
+ fi
|
|
|
|
|
+ rm -f "$BANDWIDTH_DIR/${username}.usage"
|
|
|
|
|
+ rm -rf "$BANDWIDTH_DIR/pidtrack/${username}"
|
|
|
|
|
+ done
|
|
|
|
|
+
|
|
|
|
|
+ if [[ -f "$DB_FILE" ]]; then
|
|
|
|
|
+ local db_tmp
|
|
|
|
|
+ db_tmp=$(mktemp)
|
|
|
|
|
+ awk -F: 'NR==FNR { drop[$1]=1; next } !($1 in drop)' <(printf "%s\n" "${users_to_delete[@]}") "$DB_FILE" > "$db_tmp" && mv "$db_tmp" "$DB_FILE"
|
|
|
|
|
+ rm -f "$db_tmp" 2>/dev/null
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ invalidate_banner_cache
|
|
|
|
|
+ refresh_dynamic_banner_routing_if_enabled
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
require_interactive_terminal() {
|
|
require_interactive_terminal() {
|
|
|
if [[ ! -t 0 || ! -t 1 ]]; then
|
|
if [[ ! -t 0 || ! -t 1 ]]; then
|
|
|
echo -e "${C_RED}❌ Error: The FirewallFalcon menu must be run from an interactive terminal.${C_RESET}"
|
|
echo -e "${C_RED}❌ Error: The FirewallFalcon menu must be run from an interactive terminal.${C_RESET}"
|
|
@@ -128,6 +223,7 @@ initial_setup() {
|
|
|
check_environment
|
|
check_environment
|
|
|
|
|
|
|
|
ensure_firewallfalcon_dirs
|
|
ensure_firewallfalcon_dirs
|
|
|
|
|
+ ensure_firewallfalcon_system_group
|
|
|
|
|
|
|
|
echo -e "${C_BLUE}🔹 Configuring user limiter service...${C_RESET}"
|
|
echo -e "${C_BLUE}🔹 Configuring user limiter service...${C_RESET}"
|
|
|
setup_limiter_service
|
|
setup_limiter_service
|
|
@@ -251,15 +347,30 @@ setup_limiter_service() {
|
|
|
# Combined limiter + bandwidth monitoring
|
|
# Combined limiter + bandwidth monitoring
|
|
|
cat > "$LIMITER_SCRIPT" << 'EOF'
|
|
cat > "$LIMITER_SCRIPT" << 'EOF'
|
|
|
#!/bin/bash
|
|
#!/bin/bash
|
|
|
-# FirewallFalcon limiter version 2026-03-22.1
|
|
|
|
|
|
|
+# FirewallFalcon limiter version 2026-03-27.1
|
|
|
DB_FILE="/etc/firewallfalcon/users.db"
|
|
DB_FILE="/etc/firewallfalcon/users.db"
|
|
|
BW_DIR="/etc/firewallfalcon/bandwidth"
|
|
BW_DIR="/etc/firewallfalcon/bandwidth"
|
|
|
PID_DIR="$BW_DIR/pidtrack"
|
|
PID_DIR="$BW_DIR/pidtrack"
|
|
|
|
|
+BANNER_DIR="/etc/firewallfalcon/banners"
|
|
|
SCAN_INTERVAL=30
|
|
SCAN_INTERVAL=30
|
|
|
|
|
|
|
|
mkdir -p "$BW_DIR" "$PID_DIR"
|
|
mkdir -p "$BW_DIR" "$PID_DIR"
|
|
|
shopt -s nullglob
|
|
shopt -s nullglob
|
|
|
|
|
|
|
|
|
|
+write_banner_if_changed() {
|
|
|
|
|
+ local user="$1"
|
|
|
|
|
+ local content="$2"
|
|
|
|
|
+ local banner_file="$BANNER_DIR/${user}.txt"
|
|
|
|
|
+ local tmp_file="${banner_file}.tmp"
|
|
|
|
|
+
|
|
|
|
|
+ printf "%s" "$content" > "$tmp_file"
|
|
|
|
|
+ if ! cmp -s "$tmp_file" "$banner_file" 2>/dev/null; then
|
|
|
|
|
+ mv "$tmp_file" "$banner_file"
|
|
|
|
|
+ else
|
|
|
|
|
+ rm -f "$tmp_file"
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
while true; do
|
|
while true; do
|
|
|
if [[ ! -s "$DB_FILE" ]]; then
|
|
if [[ ! -s "$DB_FILE" ]]; then
|
|
|
sleep "$SCAN_INTERVAL"
|
|
sleep "$SCAN_INTERVAL"
|
|
@@ -267,11 +378,11 @@ while true; do
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
current_ts=$(date +%s)
|
|
current_ts=$(date +%s)
|
|
|
- declare -A session_counts=()
|
|
|
|
|
|
|
+ dynamic_banners_enabled=false
|
|
|
declare -A session_pids=()
|
|
declare -A session_pids=()
|
|
|
declare -A locked_users=()
|
|
declare -A locked_users=()
|
|
|
declare -A uid_to_user=()
|
|
declare -A uid_to_user=()
|
|
|
- declare -A seen_sessions=()
|
|
|
|
|
|
|
+ declare -A loginuid_pids=()
|
|
|
|
|
|
|
|
while IFS=: read -r username _ uid _rest; do
|
|
while IFS=: read -r username _ uid _rest; do
|
|
|
[[ -n "$username" && "$uid" =~ ^[0-9]+$ ]] && uid_to_user["$uid"]="$username"
|
|
[[ -n "$username" && "$uid" =~ ^[0-9]+$ ]] && uid_to_user["$uid"]="$username"
|
|
@@ -280,34 +391,56 @@ while true; do
|
|
|
while read -r ssh_pid ssh_owner; do
|
|
while read -r ssh_pid ssh_owner; do
|
|
|
[[ "$ssh_pid" =~ ^[0-9]+$ ]] || continue
|
|
[[ "$ssh_pid" =~ ^[0-9]+$ ]] || continue
|
|
|
|
|
|
|
|
- session_user=""
|
|
|
|
|
if [[ -n "$ssh_owner" && "$ssh_owner" != "root" && "$ssh_owner" != "sshd" ]]; then
|
|
if [[ -n "$ssh_owner" && "$ssh_owner" != "root" && "$ssh_owner" != "sshd" ]]; then
|
|
|
- session_user="$ssh_owner"
|
|
|
|
|
- elif [[ -r "/proc/$ssh_pid/loginuid" ]]; then
|
|
|
|
|
- login_uid=""
|
|
|
|
|
- read -r login_uid < "/proc/$ssh_pid/loginuid" || login_uid=""
|
|
|
|
|
- if [[ "$login_uid" =~ ^[0-9]+$ && "$login_uid" != "4294967295" ]]; then
|
|
|
|
|
- session_user="${uid_to_user[$login_uid]}"
|
|
|
|
|
- fi
|
|
|
|
|
|
|
+ session_pids["$ssh_owner"]+="$ssh_pid "
|
|
|
fi
|
|
fi
|
|
|
|
|
+ done < <(ps -C sshd -o pid=,user= 2>/dev/null)
|
|
|
|
|
+
|
|
|
|
|
+ for p in /proc/[0-9]*/loginuid; do
|
|
|
|
|
+ [[ -f "$p" ]] || continue
|
|
|
|
|
+ login_uid=""
|
|
|
|
|
+ read -r login_uid < "$p" || login_uid=""
|
|
|
|
|
+ [[ "$login_uid" =~ ^[0-9]+$ && "$login_uid" != "4294967295" ]] || continue
|
|
|
|
|
|
|
|
|
|
+ session_user="${uid_to_user[$login_uid]}"
|
|
|
[[ -n "$session_user" ]] || continue
|
|
[[ -n "$session_user" ]] || continue
|
|
|
- session_key="${session_user}:$ssh_pid"
|
|
|
|
|
- [[ -z "${seen_sessions[$session_key]+x}" ]] || continue
|
|
|
|
|
|
|
|
|
|
- seen_sessions["$session_key"]=1
|
|
|
|
|
- ((session_counts["$session_user"]++))
|
|
|
|
|
- session_pids["$session_user"]+="$ssh_pid "
|
|
|
|
|
- done < <(ps -C sshd -o pid=,user= 2>/dev/null)
|
|
|
|
|
|
|
+ pid_dir=$(dirname "$p")
|
|
|
|
|
+ pid_num=$(basename "$pid_dir")
|
|
|
|
|
+ comm=""
|
|
|
|
|
+ read -r comm < "$pid_dir/comm" || comm=""
|
|
|
|
|
+ [[ "$comm" == "sshd" ]] || continue
|
|
|
|
|
+
|
|
|
|
|
+ ppid_val=""
|
|
|
|
|
+ while read -r key value; do
|
|
|
|
|
+ if [[ "$key" == "PPid:" ]]; then
|
|
|
|
|
+ ppid_val="${value:-}"
|
|
|
|
|
+ break
|
|
|
|
|
+ fi
|
|
|
|
|
+ done < "$pid_dir/status"
|
|
|
|
|
+ [[ "$ppid_val" == "1" ]] && continue
|
|
|
|
|
+
|
|
|
|
|
+ loginuid_pids["$session_user"]+="$pid_num "
|
|
|
|
|
+ done
|
|
|
|
|
|
|
|
while read -r passwd_user _ passwd_status _rest; do
|
|
while read -r passwd_user _ passwd_status _rest; do
|
|
|
[[ "$passwd_status" == "L" ]] && locked_users["$passwd_user"]=1
|
|
[[ "$passwd_status" == "L" ]] && locked_users["$passwd_user"]=1
|
|
|
done < <(passwd -Sa 2>/dev/null)
|
|
done < <(passwd -Sa 2>/dev/null)
|
|
|
|
|
|
|
|
|
|
+ if [[ -f "/etc/firewallfalcon/banners_enabled" ]]; then
|
|
|
|
|
+ mkdir -p "$BANNER_DIR"
|
|
|
|
|
+ dynamic_banners_enabled=true
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
|
|
while IFS=: read -r user pass expiry limit bandwidth_gb _extra; do
|
|
|
[[ -z "$user" || "$user" == \#* ]] && continue
|
|
[[ -z "$user" || "$user" == \#* ]] && continue
|
|
|
|
|
|
|
|
- online_count=${session_counts["$user"]:-0}
|
|
|
|
|
|
|
+ declare -A unique_pids=()
|
|
|
|
|
+ for pid in ${session_pids["$user"]} ${loginuid_pids["$user"]}; do
|
|
|
|
|
+ [[ "$pid" =~ ^[0-9]+$ ]] && unique_pids["$pid"]=1
|
|
|
|
|
+ done
|
|
|
|
|
+
|
|
|
|
|
+ online_count=${#unique_pids[@]}
|
|
|
user_locked=false
|
|
user_locked=false
|
|
|
if [[ -n "${locked_users[$user]+x}" ]]; then
|
|
if [[ -n "${locked_users[$user]+x}" ]]; then
|
|
|
user_locked=true
|
|
user_locked=true
|
|
@@ -339,6 +472,44 @@ while true; do
|
|
|
fi
|
|
fi
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
|
|
+ if $dynamic_banners_enabled; then
|
|
|
|
|
+ days_left="N/A"
|
|
|
|
|
+ if [[ "$expiry" != "Never" && -n "$expiry" && "$expiry_ts" =~ ^[0-9]+$ && $expiry_ts -gt 0 ]]; then
|
|
|
|
|
+ diff_secs=$((expiry_ts - current_ts))
|
|
|
|
|
+ if (( diff_secs <= 0 )); then
|
|
|
|
|
+ days_left="EXPIRED"
|
|
|
|
|
+ else
|
|
|
|
|
+ d_l=$(( diff_secs / 86400 ))
|
|
|
|
|
+ h_l=$(( (diff_secs % 86400) / 3600 ))
|
|
|
|
|
+ if (( d_l == 0 )); then
|
|
|
|
|
+ days_left="${h_l}h left"
|
|
|
|
|
+ else
|
|
|
|
|
+ days_left="${d_l}d ${h_l}h"
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ bw_info="Unlimited"
|
|
|
|
|
+ if [[ "$bandwidth_gb" != "0" && -n "$bandwidth_gb" ]]; then
|
|
|
|
|
+ usagefile="$BW_DIR/${user}.usage"
|
|
|
|
|
+ accum_disp=0
|
|
|
|
|
+ if [[ -f "$usagefile" ]]; then
|
|
|
|
|
+ read -r accum_disp < "$usagefile"
|
|
|
|
|
+ [[ "$accum_disp" =~ ^[0-9]+$ ]] || accum_disp=0
|
|
|
|
|
+ fi
|
|
|
|
|
+ used_gb=$(awk "BEGIN {printf \"%.2f\", $accum_disp / 1073741824}")
|
|
|
|
|
+ remain_gb=$(awk "BEGIN {r=$bandwidth_gb - $used_gb; if(r<0) r=0; printf \"%.2f\", r}")
|
|
|
|
|
+ bw_info="${used_gb}/${bandwidth_gb} GB used | ${remain_gb} GB left"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ banner_content="<br><font color=\"yellow\"><b> ✨ ACCOUNT STATUS ✨ </b></font><br><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">👤 <b>Username :</b> $user</font><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">📅 <b>Expiration :</b> $expiry ($days_left)</font><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">📊 <b>Bandwidth :</b> $bw_info</font><br>"
|
|
|
|
|
+ banner_content+="<font color=\"white\">🔌 <b>Sessions :</b> $online_count/$limit</font><br><br>"
|
|
|
|
|
+ write_banner_if_changed "$user" "$banner_content"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
[[ -z "$bandwidth_gb" || "$bandwidth_gb" == "0" ]] && continue
|
|
[[ -z "$bandwidth_gb" || "$bandwidth_gb" == "0" ]] && continue
|
|
|
|
|
|
|
|
usagefile="$BW_DIR/${user}.usage"
|
|
usagefile="$BW_DIR/${user}.usage"
|
|
@@ -348,15 +519,13 @@ while true; do
|
|
|
[[ "$accumulated" =~ ^[0-9]+$ ]] || accumulated=0
|
|
[[ "$accumulated" =~ ^[0-9]+$ ]] || accumulated=0
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
- pids="${session_pids["$user"]}"
|
|
|
|
|
- if [[ -z "$pids" ]]; then
|
|
|
|
|
|
|
+ if (( ${#unique_pids[@]} == 0 )); then
|
|
|
rm -f "$PID_DIR/${user}__"*.last 2>/dev/null
|
|
rm -f "$PID_DIR/${user}__"*.last 2>/dev/null
|
|
|
continue
|
|
continue
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
delta_total=0
|
|
delta_total=0
|
|
|
- for pid in $pids; do
|
|
|
|
|
- [[ "$pid" =~ ^[0-9]+$ ]] || continue
|
|
|
|
|
|
|
+ for pid in "${!unique_pids[@]}"; do
|
|
|
io_file="/proc/$pid/io"
|
|
io_file="/proc/$pid/io"
|
|
|
cur=0
|
|
cur=0
|
|
|
if [[ -r "$io_file" ]]; then
|
|
if [[ -r "$io_file" ]]; then
|
|
@@ -445,11 +614,16 @@ EOF
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
sync_runtime_components_if_needed() {
|
|
sync_runtime_components_if_needed() {
|
|
|
- local limiter_marker="# FirewallFalcon limiter version 2026-03-22.1"
|
|
|
|
|
|
|
+ local limiter_marker="# FirewallFalcon limiter version 2026-03-27.1"
|
|
|
if [[ ! -f "$LIMITER_SCRIPT" ]] || ! grep -Fqx "$limiter_marker" "$LIMITER_SCRIPT" 2>/dev/null; then
|
|
if [[ ! -f "$LIMITER_SCRIPT" ]] || ! grep -Fqx "$limiter_marker" "$LIMITER_SCRIPT" 2>/dev/null; then
|
|
|
setup_limiter_service >/dev/null 2>&1
|
|
setup_limiter_service >/dev/null 2>&1
|
|
|
fi
|
|
fi
|
|
|
- if [[ -f "$SSHD_FF_CONFIG" || -f "/etc/firewallfalcon/banners_enabled" ]]; then
|
|
|
|
|
|
|
+ if [[ -f "$BADVPN_SERVICE_FILE" ]]; then
|
|
|
|
|
+ ensure_badvpn_service_is_quiet
|
|
|
|
|
+ fi
|
|
|
|
|
+ if [[ -f "/etc/firewallfalcon/banners_enabled" ]]; then
|
|
|
|
|
+ update_ssh_banners_config
|
|
|
|
|
+ elif [[ -f "$SSHD_FF_CONFIG" ]]; then
|
|
|
disable_dynamic_ssh_banner_system
|
|
disable_dynamic_ssh_banner_system
|
|
|
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
|
systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null || true
|
|
|
fi
|
|
fi
|
|
@@ -500,14 +674,77 @@ disable_dynamic_ssh_banner_system() {
|
|
|
invalidate_banner_cache
|
|
invalidate_banner_cache
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+disable_static_ssh_banner_in_sshd_config() {
|
|
|
|
|
+ sed -i.bak -E "s|^[[:space:]]*Banner[[:space:]]+$SSH_BANNER_FILE[[:space:]]*$|# Banner $SSH_BANNER_FILE|" /etc/ssh/sshd_config 2>/dev/null
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+is_static_ssh_banner_enabled() {
|
|
|
|
|
+ grep -q -E "^[[:space:]]*Banner[[:space:]]+$SSH_BANNER_FILE[[:space:]]*$" /etc/ssh/sshd_config 2>/dev/null && [ -f "$SSH_BANNER_FILE" ]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+is_dynamic_ssh_banner_enabled() {
|
|
|
|
|
+ [[ -f "/etc/firewallfalcon/banners_enabled" && -f "$SSHD_FF_CONFIG" ]]
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+get_ssh_banner_mode() {
|
|
|
|
|
+ if is_dynamic_ssh_banner_enabled; then
|
|
|
|
|
+ echo "dynamic"
|
|
|
|
|
+ elif is_static_ssh_banner_enabled; then
|
|
|
|
|
+ echo "static"
|
|
|
|
|
+ else
|
|
|
|
|
+ echo "disabled"
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+refresh_dynamic_banner_routing_if_enabled() {
|
|
|
|
|
+ if is_dynamic_ssh_banner_enabled; then
|
|
|
|
|
+ update_ssh_banners_config
|
|
|
|
|
+ fi
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
update_ssh_banners_config() {
|
|
update_ssh_banners_config() {
|
|
|
- disable_dynamic_ssh_banner_system
|
|
|
|
|
|
|
+ local tmp_conf
|
|
|
|
|
+
|
|
|
|
|
+ if [[ ! -f "/etc/firewallfalcon/banners_enabled" ]]; then
|
|
|
|
|
+ if [[ -f "$SSHD_FF_CONFIG" ]]; then
|
|
|
|
|
+ rm -f "$SSHD_FF_CONFIG" 2>/dev/null
|
|
|
|
|
+ systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null
|
|
|
|
|
+ fi
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ ensure_firewallfalcon_dirs
|
|
|
|
|
+ tmp_conf="/tmp/ff_banners_new.conf"
|
|
|
|
|
+ echo "# FirewallFalcon - Dynamic per-user SSH banners" > "$tmp_conf"
|
|
|
|
|
+
|
|
|
|
|
+ if [[ -f "$DB_FILE" ]]; then
|
|
|
|
|
+ while IFS=: read -r u _rest; do
|
|
|
|
|
+ [[ -z "$u" || "$u" == \#* ]] && continue
|
|
|
|
|
+ echo "Match User $u" >> "$tmp_conf"
|
|
|
|
|
+ echo " Banner /etc/firewallfalcon/banners/${u}.txt" >> "$tmp_conf"
|
|
|
|
|
+ done < "$DB_FILE"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ if ! cmp -s "$tmp_conf" "$SSHD_FF_CONFIG" 2>/dev/null; then
|
|
|
|
|
+ mv "$tmp_conf" "$SSHD_FF_CONFIG"
|
|
|
|
|
+ if ! grep -q "^Include /etc/ssh/sshd_config.d/" /etc/ssh/sshd_config 2>/dev/null; then
|
|
|
|
|
+ echo "Include /etc/ssh/sshd_config.d/*.conf" >> /etc/ssh/sshd_config
|
|
|
|
|
+ fi
|
|
|
|
|
+ systemctl reload sshd 2>/dev/null || systemctl reload ssh 2>/dev/null
|
|
|
|
|
+ else
|
|
|
|
|
+ rm -f "$tmp_conf"
|
|
|
|
|
+ fi
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
setup_ssh_login_info() {
|
|
setup_ssh_login_info() {
|
|
|
- echo -e "${C_YELLOW}⚠️ Dynamic per-user SSH banners have been removed.${C_RESET}"
|
|
|
|
|
- echo -e "${C_CYAN}Use the Static SSH Banner menu to paste a custom banner instead.${C_RESET}"
|
|
|
|
|
- return 1
|
|
|
|
|
|
|
+ ensure_firewallfalcon_dirs || return 1
|
|
|
|
|
+ if ! touch "/etc/firewallfalcon/banners_enabled"; then
|
|
|
|
|
+ echo -e "${C_RED}❌ Failed to enable dynamic SSH banners.${C_RESET}"
|
|
|
|
|
+ return 1
|
|
|
|
|
+ fi
|
|
|
|
|
+ disable_static_ssh_banner_in_sshd_config
|
|
|
|
|
+ update_ssh_banners_config
|
|
|
|
|
+ return 0
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@@ -683,17 +920,41 @@ _select_user_interface() {
|
|
|
|
|
|
|
|
_select_multi_user_interface() {
|
|
_select_multi_user_interface() {
|
|
|
local title="$1"
|
|
local title="$1"
|
|
|
|
|
+ local include_orphan_users="${2:-false}"
|
|
|
clear; show_banner
|
|
clear; show_banner
|
|
|
echo -e "${C_BOLD}${C_PURPLE}${title}${C_RESET}\n"
|
|
echo -e "${C_BOLD}${C_PURPLE}${title}${C_RESET}\n"
|
|
|
SELECTED_USERS=()
|
|
SELECTED_USERS=()
|
|
|
- if [[ ! -s $DB_FILE ]]; then
|
|
|
|
|
- echo -e "${C_YELLOW}ℹ️ No users found in the database.${C_RESET}"
|
|
|
|
|
- SELECTED_USERS=("NO_USERS"); return
|
|
|
|
|
- fi
|
|
|
|
|
-
|
|
|
|
|
- mapfile -t all_users < <(cut -d: -f1 "$DB_FILE" | sort)
|
|
|
|
|
|
|
+ local -a all_users=()
|
|
|
|
|
+ local -a orphan_users=()
|
|
|
local -A all_user_lookup=()
|
|
local -A all_user_lookup=()
|
|
|
|
|
+ local -A orphan_user_lookup=()
|
|
|
local username
|
|
local username
|
|
|
|
|
+
|
|
|
|
|
+ if [[ -s $DB_FILE ]]; then
|
|
|
|
|
+ mapfile -t all_users < <(cut -d: -f1 "$DB_FILE" | sort)
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ if [[ "$include_orphan_users" == "true" ]]; then
|
|
|
|
|
+ mapfile -t orphan_users < <(get_firewallfalcon_orphan_users)
|
|
|
|
|
+ for username in "${orphan_users[@]}"; do
|
|
|
|
|
+ orphan_user_lookup["$username"]=1
|
|
|
|
|
+ if ! printf "%s\n" "${all_users[@]}" | grep -Fxq "$username"; then
|
|
|
|
|
+ all_users+=("$username")
|
|
|
|
|
+ fi
|
|
|
|
|
+ done
|
|
|
|
|
+ if [[ ${#all_users[@]} -gt 0 ]]; then
|
|
|
|
|
+ mapfile -t all_users < <(printf "%s\n" "${all_users[@]}" | sort)
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ if [[ ${#all_users[@]} -eq 0 ]]; then
|
|
|
|
|
+ echo -e "${C_YELLOW}ℹ️ No users found in the manager database.${C_RESET}"
|
|
|
|
|
+ if [[ "$include_orphan_users" == "true" ]]; then
|
|
|
|
|
+ echo -e "${C_DIM}No orphan FirewallFalcon system users were found either.${C_RESET}"
|
|
|
|
|
+ fi
|
|
|
|
|
+ SELECTED_USERS=("NO_USERS"); return
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
for username in "${all_users[@]}"; do
|
|
for username in "${all_users[@]}"; do
|
|
|
all_user_lookup["$username"]=1
|
|
all_user_lookup["$username"]=1
|
|
|
done
|
|
done
|
|
@@ -715,12 +976,19 @@ _select_multi_user_interface() {
|
|
|
fi
|
|
fi
|
|
|
echo -e "\nPlease select users:\n"
|
|
echo -e "\nPlease select users:\n"
|
|
|
for i in "${!users[@]}"; do
|
|
for i in "${!users[@]}"; do
|
|
|
- printf " ${C_GREEN}[%2d]${C_RESET} %s\n" "$((i+1))" "${users[$i]}"
|
|
|
|
|
|
|
+ local display_user="${users[$i]}"
|
|
|
|
|
+ if [[ "$include_orphan_users" == "true" && -n "${orphan_user_lookup[${users[$i]}]+x}" ]]; then
|
|
|
|
|
+ display_user="${display_user} ${C_DIM}(system-only)${C_RESET}"
|
|
|
|
|
+ fi
|
|
|
|
|
+ printf " ${C_GREEN}[%2d]${C_RESET} %s\n" "$((i+1))" "$display_user"
|
|
|
done
|
|
done
|
|
|
echo -e "\n ${C_GREEN}[all]${C_RESET} Select ALL listed users"
|
|
echo -e "\n ${C_GREEN}[all]${C_RESET} Select ALL listed users"
|
|
|
echo -e " ${C_RED} [0]${C_RESET} ↩️ Cancel and return to main menu"
|
|
echo -e " ${C_RED} [0]${C_RESET} ↩️ Cancel and return to main menu"
|
|
|
echo -e "\n${C_CYAN}💡 You can select multiple by number, range, or exact username.${C_RESET}"
|
|
echo -e "\n${C_CYAN}💡 You can select multiple by number, range, or exact username.${C_RESET}"
|
|
|
echo -e "${C_CYAN} Examples: '1 3 5' or '1,3' or '1-4' or 'alice bob'${C_RESET}"
|
|
echo -e "${C_CYAN} Examples: '1 3 5' or '1,3' or '1-4' or 'alice bob'${C_RESET}"
|
|
|
|
|
+ if [[ "$include_orphan_users" == "true" ]]; then
|
|
|
|
|
+ echo -e "${C_CYAN} Users marked '(system-only)' are old accounts still on the VPS but missing from users.db${C_RESET}"
|
|
|
|
|
+ fi
|
|
|
echo
|
|
echo
|
|
|
local choice
|
|
local choice
|
|
|
while true; do
|
|
while true; do
|
|
@@ -814,6 +1082,7 @@ create_user() {
|
|
|
clear; show_banner
|
|
clear; show_banner
|
|
|
echo -e "${C_BOLD}${C_PURPLE}--- ✨ Create New SSH User ---${C_RESET}"
|
|
echo -e "${C_BOLD}${C_PURPLE}--- ✨ Create New SSH User ---${C_RESET}"
|
|
|
read -p "👉 Enter username (or '0' to cancel): " username
|
|
read -p "👉 Enter username (or '0' to cancel): " username
|
|
|
|
|
+ local adopt_existing=false
|
|
|
if [[ "$username" == "0" ]]; then
|
|
if [[ "$username" == "0" ]]; then
|
|
|
echo -e "\n${C_YELLOW}❌ User creation cancelled.${C_RESET}"
|
|
echo -e "\n${C_YELLOW}❌ User creation cancelled.${C_RESET}"
|
|
|
return
|
|
return
|
|
@@ -822,8 +1091,25 @@ create_user() {
|
|
|
echo -e "\n${C_RED}❌ Error: Username cannot be empty.${C_RESET}"
|
|
echo -e "\n${C_RED}❌ Error: Username cannot be empty.${C_RESET}"
|
|
|
return
|
|
return
|
|
|
fi
|
|
fi
|
|
|
- if id "$username" &>/dev/null || grep -q "^$username:" "$DB_FILE"; then
|
|
|
|
|
- echo -e "\n${C_RED}❌ Error: User '$username' already exists.${C_RESET}"; return
|
|
|
|
|
|
|
+ if db_has_user "$username"; then
|
|
|
|
|
+ echo -e "\n${C_RED}❌ Error: User '$username' already exists in FirewallFalcon.${C_RESET}"
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+ if id "$username" &>/dev/null; then
|
|
|
|
|
+ if is_firewallfalcon_orphan_user "$username"; then
|
|
|
|
|
+ echo -e "\n${C_YELLOW}⚠️ User '$username' already exists on the system but is missing from users.db.${C_RESET}"
|
|
|
|
|
+ echo -e "${C_DIM}This usually happens after uninstalling the script without deleting the SSH users.${C_RESET}"
|
|
|
|
|
+ read -p "👉 Do you want to take control of this existing user and manage it with FirewallFalcon? (y/n): " adopt_confirm
|
|
|
|
|
+ if [[ "$adopt_confirm" == "y" || "$adopt_confirm" == "Y" ]]; then
|
|
|
|
|
+ adopt_existing=true
|
|
|
|
|
+ else
|
|
|
|
|
+ echo -e "\n${C_YELLOW}❌ User creation cancelled.${C_RESET}"
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+ else
|
|
|
|
|
+ echo -e "\n${C_RED}❌ Error: System user '$username' already exists and does not look like a FirewallFalcon SSH account.${C_RESET}"
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
fi
|
|
fi
|
|
|
local password=""
|
|
local password=""
|
|
|
while true; do
|
|
while true; do
|
|
@@ -847,8 +1133,13 @@ create_user() {
|
|
|
if ! [[ "$bandwidth_gb" =~ ^[0-9]+\.?[0-9]*$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
|
|
if ! [[ "$bandwidth_gb" =~ ^[0-9]+\.?[0-9]*$ ]]; then echo -e "\n${C_RED}❌ Invalid number.${C_RESET}"; return; fi
|
|
|
local expire_date
|
|
local expire_date
|
|
|
expire_date=$(date -d "+$days days" +%Y-%m-%d)
|
|
expire_date=$(date -d "+$days days" +%Y-%m-%d)
|
|
|
- useradd -m -s /usr/sbin/nologin "$username"
|
|
|
|
|
- usermod -aG ffusers "$username" 2>/dev/null
|
|
|
|
|
|
|
+ ensure_firewallfalcon_system_group
|
|
|
|
|
+ if [[ "$adopt_existing" == "true" ]]; then
|
|
|
|
|
+ usermod -s /usr/sbin/nologin "$username" &>/dev/null
|
|
|
|
|
+ else
|
|
|
|
|
+ useradd -m -s /usr/sbin/nologin "$username"
|
|
|
|
|
+ fi
|
|
|
|
|
+ usermod -aG "$FF_USERS_GROUP" "$username" 2>/dev/null
|
|
|
echo "$username:$password" | chpasswd; chage -E "$expire_date" "$username"
|
|
echo "$username:$password" | chpasswd; chage -E "$expire_date" "$username"
|
|
|
echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
|
|
echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
|
|
|
|
|
|
|
@@ -856,7 +1147,11 @@ create_user() {
|
|
|
if [[ "$bandwidth_gb" != "0" ]]; then bw_display="${bandwidth_gb} GB"; fi
|
|
if [[ "$bandwidth_gb" != "0" ]]; then bw_display="${bandwidth_gb} GB"; fi
|
|
|
|
|
|
|
|
clear; show_banner
|
|
clear; show_banner
|
|
|
- echo -e "${C_GREEN}✅ User '$username' created successfully!${C_RESET}\n"
|
|
|
|
|
|
|
+ if [[ "$adopt_existing" == "true" ]]; then
|
|
|
|
|
+ echo -e "${C_GREEN}✅ Existing system user '$username' has been imported into FirewallFalcon!${C_RESET}\n"
|
|
|
|
|
+ else
|
|
|
|
|
+ echo -e "${C_GREEN}✅ User '$username' created successfully!${C_RESET}\n"
|
|
|
|
|
+ fi
|
|
|
echo -e " - 👤 Username: ${C_YELLOW}$username${C_RESET}"
|
|
echo -e " - 👤 Username: ${C_YELLOW}$username${C_RESET}"
|
|
|
echo -e " - 🔑 Password: ${C_YELLOW}$password${C_RESET}"
|
|
echo -e " - 🔑 Password: ${C_YELLOW}$password${C_RESET}"
|
|
|
echo -e " - 🗓️ Expires on: ${C_YELLOW}$expire_date${C_RESET}"
|
|
echo -e " - 🗓️ Expires on: ${C_YELLOW}$expire_date${C_RESET}"
|
|
@@ -872,10 +1167,11 @@ create_user() {
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
invalidate_banner_cache
|
|
invalidate_banner_cache
|
|
|
|
|
+ refresh_dynamic_banner_routing_if_enabled
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
delete_user() {
|
|
delete_user() {
|
|
|
- _select_multi_user_interface "--- 🗑️ Delete Users (from DB) ---"
|
|
|
|
|
|
|
+ _select_multi_user_interface "--- 🗑️ Delete FirewallFalcon Users ---" "true"
|
|
|
if [[ ${#SELECTED_USERS[@]} -eq 0 || "${SELECTED_USERS[0]}" == "NO_USERS" ]]; then return; fi
|
|
if [[ ${#SELECTED_USERS[@]} -eq 0 || "${SELECTED_USERS[0]}" == "NO_USERS" ]]; then return; fi
|
|
|
|
|
|
|
|
echo -e "\n${C_RED}⚠️ You selected ${#SELECTED_USERS[@]} user(s) to delete: ${C_YELLOW}${SELECTED_USERS[*]}${C_RESET}"
|
|
echo -e "\n${C_RED}⚠️ You selected ${#SELECTED_USERS[@]} user(s) to delete: ${C_YELLOW}${SELECTED_USERS[*]}${C_RESET}"
|
|
@@ -883,30 +1179,7 @@ delete_user() {
|
|
|
if [[ "$confirm" != "y" ]]; then echo -e "\n${C_YELLOW}❌ Deletion cancelled.${C_RESET}"; return; fi
|
|
if [[ "$confirm" != "y" ]]; then echo -e "\n${C_YELLOW}❌ Deletion cancelled.${C_RESET}"; return; fi
|
|
|
|
|
|
|
|
echo -e "\n${C_BLUE}🗑️ Deleting selected users...${C_RESET}"
|
|
echo -e "\n${C_BLUE}🗑️ Deleting selected users...${C_RESET}"
|
|
|
- local username
|
|
|
|
|
- for username in "${SELECTED_USERS[@]}"; do
|
|
|
|
|
- killall -u "$username" -9 &>/dev/null
|
|
|
|
|
- if id "$username" &>/dev/null; then
|
|
|
|
|
- if userdel -r "$username" &>/dev/null; then
|
|
|
|
|
- echo -e " ✅ System user '${C_YELLOW}$username${C_RESET}' deleted."
|
|
|
|
|
- else
|
|
|
|
|
- echo -e " ❌ Failed to delete system user '${C_YELLOW}$username${C_RESET}'."
|
|
|
|
|
- fi
|
|
|
|
|
- else
|
|
|
|
|
- echo -e " ℹ️ System user '${C_YELLOW}$username${C_RESET}' was already missing. Removing manager data only."
|
|
|
|
|
- fi
|
|
|
|
|
- rm -f "$BANDWIDTH_DIR/${username}.usage"
|
|
|
|
|
- rm -rf "$BANDWIDTH_DIR/pidtrack/${username}"
|
|
|
|
|
- done
|
|
|
|
|
-
|
|
|
|
|
- if [[ -f "$DB_FILE" ]]; then
|
|
|
|
|
- local db_tmp
|
|
|
|
|
- db_tmp=$(mktemp)
|
|
|
|
|
- awk -F: 'NR==FNR { drop[$1]=1; next } !($1 in drop)' <(printf "%s\n" "${SELECTED_USERS[@]}") "$DB_FILE" > "$db_tmp" && mv "$db_tmp" "$DB_FILE"
|
|
|
|
|
- rm -f "$db_tmp" 2>/dev/null
|
|
|
|
|
- fi
|
|
|
|
|
-
|
|
|
|
|
- invalidate_banner_cache
|
|
|
|
|
|
|
+ delete_firewallfalcon_user_accounts "${SELECTED_USERS[@]}"
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
edit_user() {
|
|
edit_user() {
|
|
@@ -1173,6 +1446,7 @@ cleanup_expired() {
|
|
|
done
|
|
done
|
|
|
echo -e "\n${C_GREEN}✅ Expired users have been cleaned up.${C_RESET}"
|
|
echo -e "\n${C_GREEN}✅ Expired users have been cleaned up.${C_RESET}"
|
|
|
invalidate_banner_cache
|
|
invalidate_banner_cache
|
|
|
|
|
+ refresh_dynamic_banner_routing_if_enabled
|
|
|
else
|
|
else
|
|
|
echo -e "\n${C_YELLOW}❌ Cleanup cancelled.${C_RESET}"
|
|
echo -e "\n${C_YELLOW}❌ Cleanup cancelled.${C_RESET}"
|
|
|
fi
|
|
fi
|
|
@@ -1245,14 +1519,15 @@ restore_user_data() {
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
echo -e "${C_BLUE}⚙️ Re-synchronizing system accounts with the restored database...${C_RESET}"
|
|
echo -e "${C_BLUE}⚙️ Re-synchronizing system accounts with the restored database...${C_RESET}"
|
|
|
|
|
+ ensure_firewallfalcon_system_group
|
|
|
|
|
|
|
|
while IFS=: read -r user pass expiry limit; do
|
|
while IFS=: read -r user pass expiry limit; do
|
|
|
echo "Processing user: ${C_YELLOW}$user${C_RESET}"
|
|
echo "Processing user: ${C_YELLOW}$user${C_RESET}"
|
|
|
if ! id "$user" &>/dev/null; then
|
|
if ! id "$user" &>/dev/null; then
|
|
|
echo " - User does not exist in system. Creating..."
|
|
echo " - User does not exist in system. Creating..."
|
|
|
useradd -m -s /usr/sbin/nologin "$user"
|
|
useradd -m -s /usr/sbin/nologin "$user"
|
|
|
- usermod -aG ffusers "$user" 2>/dev/null
|
|
|
|
|
fi
|
|
fi
|
|
|
|
|
+ usermod -aG "$FF_USERS_GROUP" "$user" 2>/dev/null
|
|
|
echo " - Setting password..."
|
|
echo " - Setting password..."
|
|
|
echo "$user:$pass" | chpasswd
|
|
echo "$user:$pass" | chpasswd
|
|
|
echo " - Setting expiration to $expiry..."
|
|
echo " - Setting expiration to $expiry..."
|
|
@@ -1263,6 +1538,7 @@ restore_user_data() {
|
|
|
echo -e "\n${C_GREEN}✅ SUCCESS: User data restore completed.${C_RESET}"
|
|
echo -e "\n${C_GREEN}✅ SUCCESS: User data restore completed.${C_RESET}"
|
|
|
|
|
|
|
|
invalidate_banner_cache
|
|
invalidate_banner_cache
|
|
|
|
|
+ refresh_dynamic_banner_routing_if_enabled
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
_enable_banner_in_sshd_config() {
|
|
_enable_banner_in_sshd_config() {
|
|
@@ -1326,8 +1602,8 @@ view_ssh_banner() {
|
|
|
|
|
|
|
|
remove_ssh_banner() {
|
|
remove_ssh_banner() {
|
|
|
clear; show_banner
|
|
clear; show_banner
|
|
|
- echo -e "${C_BOLD}${C_PURPLE}--- 🗑️ Remove SSH Banner ---${C_RESET}"
|
|
|
|
|
- read -p "👉 Are you sure you want to disable and remove the SSH banner? (y/n): " confirm
|
|
|
|
|
|
|
+ echo -e "${C_BOLD}${C_PURPLE}--- 🗑️ Disable SSH Banners ---${C_RESET}"
|
|
|
|
|
+ read -p "👉 Are you sure you want to disable all SSH banners? (y/n): " confirm
|
|
|
if [[ "$confirm" != "y" ]]; then
|
|
if [[ "$confirm" != "y" ]]; then
|
|
|
echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
|
|
echo -e "\n${C_YELLOW}❌ Action cancelled.${C_RESET}"
|
|
|
echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
|
|
echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
|
|
@@ -1341,12 +1617,43 @@ remove_ssh_banner() {
|
|
|
fi
|
|
fi
|
|
|
disable_dynamic_ssh_banner_system
|
|
disable_dynamic_ssh_banner_system
|
|
|
echo -e "\n${C_BLUE}⚙️ Disabling banner in sshd_config...${C_RESET}"
|
|
echo -e "\n${C_BLUE}⚙️ Disabling banner in sshd_config...${C_RESET}"
|
|
|
- sed -i.bak -E "s/^( *Banner\s+$SSH_BANNER_FILE)/#\1/" /etc/ssh/sshd_config
|
|
|
|
|
|
|
+ disable_static_ssh_banner_in_sshd_config
|
|
|
echo -e "${C_GREEN}✅ Banner disabled in configuration.${C_RESET}"
|
|
echo -e "${C_GREEN}✅ Banner disabled in configuration.${C_RESET}"
|
|
|
_restart_ssh
|
|
_restart_ssh
|
|
|
echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
|
|
echo -e "\nPress ${C_YELLOW}[Enter]${C_RESET} to return..." && read -r
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+preview_dynamic_ssh_banner() {
|
|
|
|
|
+ if ! is_dynamic_ssh_banner_enabled; then
|
|
|
|
|
+ echo -e "\n${C_RED}❌ Dynamic banners are not enabled right now.${C_RESET}"
|
|
|
|
|
+ press_enter
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ echo -e "${C_DIM}Refreshing dynamic banner worker...${C_RESET}"
|
|
|
|
|
+ setup_limiter_service >/dev/null 2>&1
|
|
|
|
|
+ _select_user_interface "--- 📝 Preview Dynamic Banner ---"
|
|
|
|
|
+ local u=$SELECTED_USER
|
|
|
|
|
+ if [[ -z "$u" || "$u" == "NO_USERS" ]]; then
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ echo -e "\n${C_CYAN}--- Dynamic Banner Preview for user '$u' ---${C_RESET}\n"
|
|
|
|
|
+ if [[ -f "/etc/firewallfalcon/banners/${u}.txt" ]]; then
|
|
|
|
|
+ cat "/etc/firewallfalcon/banners/${u}.txt"
|
|
|
|
|
+ else
|
|
|
|
|
+ echo -e "${C_RED}Banner file not generated yet. Waiting up to 10s for the worker...${C_RESET}"
|
|
|
|
|
+ sleep 5
|
|
|
|
|
+ if ! cat "/etc/firewallfalcon/banners/${u}.txt" 2>/dev/null; then
|
|
|
|
|
+ echo -e "\n${C_RED}Still not generated. Here are the last limiter logs:${C_RESET}"
|
|
|
|
|
+ echo -e "----------------------------------------------------------------------"
|
|
|
|
|
+ journalctl -u firewallfalcon-limiter -n 15 --no-pager
|
|
|
|
|
+ echo -e "----------------------------------------------------------------------"
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
|
|
+ press_enter
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
ssh_banner_menu() {
|
|
ssh_banner_menu() {
|
|
|
while true; do
|
|
while true; do
|
|
|
show_banner
|
|
show_banner
|
|
@@ -1475,6 +1782,27 @@ uninstall_udp_custom() {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+ensure_badvpn_service_is_quiet() {
|
|
|
|
|
+ if [[ ! -f "$BADVPN_SERVICE_FILE" ]] || grep -q "^StandardOutput=null$" "$BADVPN_SERVICE_FILE" 2>/dev/null; then
|
|
|
|
|
+ return
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
|
|
+ local tmp_service
|
|
|
|
|
+ tmp_service=$(mktemp)
|
|
|
|
|
+ awk '
|
|
|
|
|
+ /^\[Service\]$/ {
|
|
|
|
|
+ print
|
|
|
|
|
+ print "StandardOutput=null"
|
|
|
|
|
+ print "StandardError=null"
|
|
|
|
|
+ next
|
|
|
|
|
+ }
|
|
|
|
|
+ { print }
|
|
|
|
|
+ ' "$BADVPN_SERVICE_FILE" > "$tmp_service" && mv "$tmp_service" "$BADVPN_SERVICE_FILE"
|
|
|
|
|
+ rm -f "$tmp_service" 2>/dev/null
|
|
|
|
|
+ systemctl daemon-reload
|
|
|
|
|
+ systemctl restart badvpn.service >/dev/null 2>&1 || true
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
install_badvpn() {
|
|
install_badvpn() {
|
|
|
clear; show_banner
|
|
clear; show_banner
|
|
|
echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Installing badvpn (udpgw) ---${C_RESET}"
|
|
echo -e "${C_BOLD}${C_PURPLE}--- 🚀 Installing badvpn (udpgw) ---${C_RESET}"
|
|
@@ -1513,6 +1841,8 @@ ExecStart=$badvpn_binary --listen-addr 0.0.0.0:7300 --max-clients 1000 --max-con
|
|
|
User=root
|
|
User=root
|
|
|
Restart=always
|
|
Restart=always
|
|
|
RestartSec=3
|
|
RestartSec=3
|
|
|
|
|
+StandardOutput=null
|
|
|
|
|
+StandardError=null
|
|
|
[Install]
|
|
[Install]
|
|
|
WantedBy=multi-user.target
|
|
WantedBy=multi-user.target
|
|
|
EOF
|
|
EOF
|
|
@@ -3245,9 +3575,25 @@ uninstall_script() {
|
|
|
echo -e "\n${C_GREEN}✅ Uninstallation cancelled.${C_RESET}"
|
|
echo -e "\n${C_GREEN}✅ Uninstallation cancelled.${C_RESET}"
|
|
|
return
|
|
return
|
|
|
fi
|
|
fi
|
|
|
|
|
+ local -a removable_users=()
|
|
|
|
|
+ local remove_users_confirm
|
|
|
|
|
+ local remove_users_on_uninstall=false
|
|
|
|
|
+ mapfile -t removable_users < <(get_firewallfalcon_known_users)
|
|
|
|
|
+ if [[ ${#removable_users[@]} -gt 0 ]]; then
|
|
|
|
|
+ echo -e "\n${C_YELLOW}FirewallFalcon SSH users detected on this VPS:${C_RESET} ${removable_users[*]}"
|
|
|
|
|
+ read -p "👉 Do you also want to permanently delete these SSH users before uninstalling? (y/n): " remove_users_confirm
|
|
|
|
|
+ if [[ "$remove_users_confirm" == "y" || "$remove_users_confirm" == "Y" ]]; then
|
|
|
|
|
+ remove_users_on_uninstall=true
|
|
|
|
|
+ fi
|
|
|
|
|
+ fi
|
|
|
export UNINSTALL_MODE="silent"
|
|
export UNINSTALL_MODE="silent"
|
|
|
echo -e "\n${C_BLUE}--- 💥 Starting Uninstallation 💥 ---${C_RESET}"
|
|
echo -e "\n${C_BLUE}--- 💥 Starting Uninstallation 💥 ---${C_RESET}"
|
|
|
|
|
|
|
|
|
|
+ if [[ "$remove_users_on_uninstall" == "true" ]]; then
|
|
|
|
|
+ echo -e "\n${C_BLUE}🗑️ Removing FirewallFalcon SSH users before uninstall...${C_RESET}"
|
|
|
|
|
+ delete_firewallfalcon_user_accounts "${removable_users[@]}"
|
|
|
|
|
+ fi
|
|
|
|
|
+
|
|
|
echo -e "\n${C_BLUE}🗑️ Removing active limiter service...${C_RESET}"
|
|
echo -e "\n${C_BLUE}🗑️ Removing active limiter service...${C_RESET}"
|
|
|
systemctl stop firewallfalcon-limiter &>/dev/null
|
|
systemctl stop firewallfalcon-limiter &>/dev/null
|
|
|
systemctl disable firewallfalcon-limiter &>/dev/null
|
|
systemctl disable firewallfalcon-limiter &>/dev/null
|
|
@@ -3388,8 +3734,9 @@ create_trial_account() {
|
|
|
expiry_timestamp=$(date -d "+${duration_hours} hours" '+%Y-%m-%d %H:%M:%S')
|
|
expiry_timestamp=$(date -d "+${duration_hours} hours" '+%Y-%m-%d %H:%M:%S')
|
|
|
|
|
|
|
|
# Create the system user
|
|
# Create the system user
|
|
|
|
|
+ ensure_firewallfalcon_system_group
|
|
|
useradd -m -s /usr/sbin/nologin "$username"
|
|
useradd -m -s /usr/sbin/nologin "$username"
|
|
|
- usermod -aG ffusers "$username" 2>/dev/null
|
|
|
|
|
|
|
+ usermod -aG "$FF_USERS_GROUP" "$username" 2>/dev/null
|
|
|
echo "$username:$password" | chpasswd
|
|
echo "$username:$password" | chpasswd
|
|
|
chage -E "$expire_date" "$username"
|
|
chage -E "$expire_date" "$username"
|
|
|
echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
|
|
echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
|
|
@@ -3422,6 +3769,7 @@ create_trial_account() {
|
|
|
fi
|
|
fi
|
|
|
|
|
|
|
|
invalidate_banner_cache
|
|
invalidate_banner_cache
|
|
|
|
|
+ refresh_dynamic_banner_routing_if_enabled
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
view_user_bandwidth() {
|
|
view_user_bandwidth() {
|
|
@@ -3508,6 +3856,7 @@ bulk_create_users() {
|
|
|
local expire_date
|
|
local expire_date
|
|
|
expire_date=$(date -d "+$days days" +%Y-%m-%d)
|
|
expire_date=$(date -d "+$days days" +%Y-%m-%d)
|
|
|
local bw_display="Unlimited"; [[ "$bandwidth_gb" != "0" ]] && bw_display="${bandwidth_gb} GB"
|
|
local bw_display="Unlimited"; [[ "$bandwidth_gb" != "0" ]] && bw_display="${bandwidth_gb} GB"
|
|
|
|
|
+ ensure_firewallfalcon_system_group
|
|
|
|
|
|
|
|
echo -e "\n${C_BLUE}⚙️ Creating $count users with prefix '${prefix}'...${C_RESET}\n"
|
|
echo -e "\n${C_BLUE}⚙️ Creating $count users with prefix '${prefix}'...${C_RESET}\n"
|
|
|
echo -e "${C_YELLOW}================================================================${C_RESET}"
|
|
echo -e "${C_YELLOW}================================================================${C_RESET}"
|
|
@@ -3523,7 +3872,7 @@ bulk_create_users() {
|
|
|
fi
|
|
fi
|
|
|
local password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8)
|
|
local password=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 8)
|
|
|
useradd -m -s /usr/sbin/nologin "$username"
|
|
useradd -m -s /usr/sbin/nologin "$username"
|
|
|
- usermod -aG ffusers "$username" 2>/dev/null
|
|
|
|
|
|
|
+ usermod -aG "$FF_USERS_GROUP" "$username" 2>/dev/null
|
|
|
echo "$username:$password" | chpasswd
|
|
echo "$username:$password" | chpasswd
|
|
|
chage -E "$expire_date" "$username"
|
|
chage -E "$expire_date" "$username"
|
|
|
echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
|
|
echo "$username:$password:$expire_date:$limit:$bandwidth_gb" >> "$DB_FILE"
|
|
@@ -3535,6 +3884,7 @@ bulk_create_users() {
|
|
|
echo -e "\n${C_GREEN}✅ Created $created users. Conn Limit: ${limit} | BW: ${bw_display}${C_RESET}"
|
|
echo -e "\n${C_GREEN}✅ Created $created users. Conn Limit: ${limit} | BW: ${bw_display}${C_RESET}"
|
|
|
|
|
|
|
|
invalidate_banner_cache
|
|
invalidate_banner_cache
|
|
|
|
|
+ refresh_dynamic_banner_routing_if_enabled
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
generate_client_config() {
|
|
generate_client_config() {
|
|
@@ -3834,18 +4184,22 @@ _flush_torrent_rules() {
|
|
|
ssh_banner_menu() {
|
|
ssh_banner_menu() {
|
|
|
while true; do
|
|
while true; do
|
|
|
show_banner
|
|
show_banner
|
|
|
|
|
+ local banner_mode
|
|
|
local banner_status
|
|
local banner_status
|
|
|
- if grep -q -E "^\s*Banner\s+$SSH_BANNER_FILE" /etc/ssh/sshd_config && [ -f "$SSH_BANNER_FILE" ]; then
|
|
|
|
|
- banner_status="${C_STATUS_A}(Active)${C_RESET}"
|
|
|
|
|
- else
|
|
|
|
|
- banner_status="${C_STATUS_I}(Inactive)${C_RESET}"
|
|
|
|
|
- fi
|
|
|
|
|
|
|
+ banner_mode=$(get_ssh_banner_mode)
|
|
|
|
|
+ case "$banner_mode" in
|
|
|
|
|
+ dynamic) banner_status="${C_STATUS_A}Dynamic${C_RESET}" ;;
|
|
|
|
|
+ static) banner_status="${C_STATUS_A}Static${C_RESET}" ;;
|
|
|
|
|
+ *) banner_status="${C_STATUS_I}Disabled${C_RESET}" ;;
|
|
|
|
|
+ esac
|
|
|
|
|
|
|
|
- echo -e "\n ${C_TITLE}═════════════════[ ${C_BOLD}🎨 STATIC SSH BANNER ${banner_status} ${C_RESET}${C_TITLE}]═════════════════${C_RESET}"
|
|
|
|
|
- echo -e "${C_DIM}Uses the standard OpenSSH directive: Banner $SSH_BANNER_FILE${C_RESET}"
|
|
|
|
|
- printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "📋 Paste or Replace Static Banner"
|
|
|
|
|
- printf " ${C_CHOICE}[ 2]${C_RESET} %-40s\n" "👁️ View Current Banner"
|
|
|
|
|
- printf " ${C_DANGER}[ 3]${C_RESET} %-40s\n" "🗑️ Disable and Remove Banner"
|
|
|
|
|
|
|
+ echo -e "\n ${C_TITLE}═════════════════[ ${C_BOLD}🎨 SSH BANNER MODE: ${banner_status} ${C_RESET}${C_TITLE}]═════════════════${C_RESET}"
|
|
|
|
|
+ echo -e "${C_DIM}Static mode uses 'Banner $SSH_BANNER_FILE'. Dynamic mode shows per-user account info.${C_RESET}"
|
|
|
|
|
+ printf " ${C_CHOICE}[ 1]${C_RESET} %-40s\n" "✨ Enable Dynamic Account Banner"
|
|
|
|
|
+ printf " ${C_CHOICE}[ 2]${C_RESET} %-40s\n" "📋 Paste or Replace Static Banner"
|
|
|
|
|
+ printf " ${C_CHOICE}[ 3]${C_RESET} %-40s\n" "👁️ View Current Static Banner"
|
|
|
|
|
+ printf " ${C_CHOICE}[ 4]${C_RESET} %-40s\n" "📝 Preview Dynamic Banner"
|
|
|
|
|
+ printf " ${C_DANGER}[ 5]${C_RESET} %-40s\n" "🗑️ Disable All SSH Banners"
|
|
|
echo -e " ${C_DIM}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${C_RESET}"
|
|
echo -e " ${C_DIM}~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~${C_RESET}"
|
|
|
echo -e " ${C_WARN}[ 0]${C_RESET} ↩️ Return to Main Menu"
|
|
echo -e " ${C_WARN}[ 0]${C_RESET} ↩️ Return to Main Menu"
|
|
|
echo
|
|
echo
|
|
@@ -3854,9 +4208,17 @@ ssh_banner_menu() {
|
|
|
return
|
|
return
|
|
|
fi
|
|
fi
|
|
|
case $choice in
|
|
case $choice in
|
|
|
- 1) set_ssh_banner_paste ;;
|
|
|
|
|
- 2) view_ssh_banner ;;
|
|
|
|
|
- 3) remove_ssh_banner ;;
|
|
|
|
|
|
|
+ 1)
|
|
|
|
|
+ if setup_ssh_login_info; then
|
|
|
|
|
+ echo -e "\n${C_GREEN}✅ Dynamic account banner enabled.${C_RESET}"
|
|
|
|
|
+ echo -e "${C_DIM}Users will now see their account info banner instead of the static banner.${C_RESET}"
|
|
|
|
|
+ fi
|
|
|
|
|
+ press_enter
|
|
|
|
|
+ ;;
|
|
|
|
|
+ 2) set_ssh_banner_paste ;;
|
|
|
|
|
+ 3) view_ssh_banner ;;
|
|
|
|
|
+ 4) preview_dynamic_ssh_banner ;;
|
|
|
|
|
+ 5) remove_ssh_banner ;;
|
|
|
0) return ;;
|
|
0) return ;;
|
|
|
*) echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" && sleep 1 ;;
|
|
*) echo -e "\n${C_RED}❌ Invalid option.${C_RESET}" && sleep 1 ;;
|
|
|
esac
|
|
esac
|
|
@@ -3931,7 +4293,7 @@ main_menu() {
|
|
|
echo
|
|
echo
|
|
|
echo -e " ${C_TITLE}══════════════[ ${C_BOLD}⚙️ SYSTEM SETTINGS ${C_RESET}${C_TITLE}]═══════════════${C_RESET}"
|
|
echo -e " ${C_TITLE}══════════════[ ${C_BOLD}⚙️ SYSTEM SETTINGS ${C_RESET}${C_TITLE}]═══════════════${C_RESET}"
|
|
|
printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "15" "☁️ CloudFlare Free Domain" "18" "💾 Backup User Data"
|
|
printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "15" "☁️ CloudFlare Free Domain" "18" "💾 Backup User Data"
|
|
|
- printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "16" "🎨 Static SSH Banner" "19" "📥 Restore User Data"
|
|
|
|
|
|
|
+ printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "16" "🎨 SSH Banner Config" "19" "📥 Restore User Data"
|
|
|
printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "17" "🔄 Auto-Reboot Task" "20" "🧹 Cleanup Expired Users"
|
|
printf " ${C_CHOICE}[%2s]${C_RESET} %-28s ${C_CHOICE}[%2s]${C_RESET} %-28s\n" "17" "🔄 Auto-Reboot Task" "20" "🧹 Cleanup Expired Users"
|
|
|
|
|
|
|
|
echo
|
|
echo
|