2472 lines
109 KiB
Bash
Executable file
2472 lines
109 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# BastionGuard WebUI privileged helper.
|
|
# Installare root:root 0755 in /usr/local/sbin e consentire via sudoers solo agli utenti web.
|
|
|
|
usage() {
|
|
cat >&2 <<EOF
|
|
Uso:
|
|
$0 ensure-configs <utente>
|
|
$0 first-run-config <utente> <base64-json>
|
|
$0 list-configs <utente>
|
|
$0 read-config-b64 <chiave> [utente]
|
|
$0 write-config <chiave> <base64-content> [utente]
|
|
$0 scan-path-b64 <base64-path>
|
|
$0 samba-scan-b64 <base64-path> <utente>
|
|
$0 quarantine-path-b64 <base64-path> <utente|->
|
|
$0 restore-quarantine-b64 <base64-name> <base64-restore-dir> <utente|->
|
|
$0 delete-quarantine-b64 <base64-name> <utente|->
|
|
$0 inotify-start <utente> <base64-paths-newline-separated>
|
|
$0 inotify-save <utente> <base64-paths-newline-separated>
|
|
$0 inotify-service-start <utente> <base64-paths-newline-separated>
|
|
$0 inotify-service-stop <utente>
|
|
$0 inotify-service-status <utente>
|
|
$0 inotify-start-saved <utente>
|
|
$0 inotify-stop <utente>
|
|
$0 inotify-status <utente>
|
|
$0 inotify-events <utente> <lines>
|
|
$0 ransomware-realtime-start <utente>
|
|
$0 import-ransomware-events
|
|
$0 journal <system|user> <utente|-> <lines> <unit...>
|
|
$0 service-action <system|user> <utente|-> <start|stop|restart|enable|disable> <unit>
|
|
$0 update-yara <utente>
|
|
$0 update-sanesecurity <utente>
|
|
$0 update-phishing <utente>
|
|
$0 update-banks <utente> [source-url]
|
|
$0 phish-auto-update <enable|disable>
|
|
$0 read-version
|
|
$0 read-data-file-b64 <banks> <utente>
|
|
$0 write-system-file-b64 <key> <base64-content>
|
|
$0 desktop-mode <utente>
|
|
$0 apply-web-config <http_port> <https_port> [auto|distro]
|
|
$0 wizard <utente> <http_port> <https_port> [resolv dnsmasq firewall nftables certs webconf native useragent banks services]
|
|
EOF
|
|
exit 64
|
|
}
|
|
|
|
valid_user() { [[ "${1:-}" =~ ^[A-Za-z_][A-Za-z0-9_-]*[$]?$ ]] && id "$1" >/dev/null 2>&1; }
|
|
valid_port() { [[ "${1:-}" =~ ^[0-9]+$ ]] && (( $1 >= 1 && $1 <= 65535 )); }
|
|
user_home() { getent passwd "$1" | cut -d: -f6; }
|
|
user_uid() { id -u "$1"; }
|
|
user_gid() { id -g "$1"; }
|
|
q() { printf '%q' "$1"; }
|
|
|
|
can_write_dir() {
|
|
local dir="$1" tmp
|
|
mkdir -p "$dir" 2>/dev/null || return 1
|
|
tmp="$(mktemp "$dir/.bastionguard-webui.XXXXXX" 2>/dev/null)" || return 1
|
|
rm -f "$tmp"
|
|
return 0
|
|
}
|
|
|
|
|
|
# Same detection semantics used by src/SettingsPage.cpp for Settings -> Web configuration.
|
|
detect_distro_src() {
|
|
local id="" result="" line
|
|
if command -v lsb_release >/dev/null 2>&1; then
|
|
result="$(lsb_release -si 2>/dev/null | tr -d '\r\n' || true)"
|
|
if [[ -n "$result" && "$result" != *n/a* && "$result" != *N/A* ]]; then
|
|
id="$result"
|
|
fi
|
|
fi
|
|
if [[ -z "$id" && -r /etc/os-release ]]; then
|
|
while IFS= read -r line; do
|
|
if [[ "$line" == ID=* ]]; then
|
|
id="${line#ID=}"
|
|
id="${id%\"}"; id="${id#\"}"
|
|
break
|
|
fi
|
|
done < /etc/os-release
|
|
fi
|
|
if [[ -z "$id" ]]; then
|
|
[[ -e /etc/arch-release ]] && id=arch
|
|
[[ -z "$id" && -e /etc/debian_version ]] && id=debian
|
|
[[ -z "$id" && -e /etc/redhat-release ]] && id=fedora
|
|
[[ -z "$id" && -e /etc/gentoo-release ]] && id=gentoo
|
|
[[ -z "$id" && -e /etc/slackware-version ]] && id=slackware
|
|
[[ -z "$id" && -e /usr/local/etc/rc.conf ]] && id=bsd
|
|
fi
|
|
id="${id,,}"
|
|
case "$id" in
|
|
*debian*|*ubuntu*|*mint*) echo debian ;;
|
|
*fedora*|*rhel*|*centos*|*alma*|*rocky*) echo fedora ;;
|
|
*arch*|*manjaro*|*endeavour*) echo arch ;;
|
|
*suse*) echo opensuse ;;
|
|
*gentoo*) echo gentoo ;;
|
|
*slackware*) echo slackware ;;
|
|
*bsd*) echo bsd ;;
|
|
*) echo auto ;;
|
|
esac
|
|
}
|
|
|
|
detect_webserver_src() {
|
|
if systemctl is-active --quiet apache2 2>/dev/null || systemctl is-active --quiet httpd 2>/dev/null || command -v apache2 >/dev/null 2>&1 || command -v httpd >/dev/null 2>&1; then
|
|
echo apache; return 0
|
|
fi
|
|
if systemctl is-active --quiet nginx 2>/dev/null || command -v nginx >/dev/null 2>&1; then
|
|
echo nginx; return 0
|
|
fi
|
|
if systemctl is-active --quiet lshttpd 2>/dev/null || systemctl is-active --quiet lsws 2>/dev/null || command -v lsws >/dev/null 2>&1 || command -v lshttpd >/dev/null 2>&1; then
|
|
echo litespeed; return 0
|
|
fi
|
|
local out
|
|
out="$(ps -eo comm 2>/dev/null | grep -E 'apache2|httpd|nginx|lshttpd|lsws' | grep -v grep || true)"
|
|
[[ "$out" == *apache2* || "$out" == *httpd* ]] && { echo apache; return 0; }
|
|
[[ "$out" == *nginx* ]] && { echo nginx; return 0; }
|
|
[[ "$out" == *lshttpd* || "$out" == *lsws* ]] && { echo litespeed; return 0; }
|
|
echo unknown
|
|
}
|
|
|
|
# Desktop arbitration for proxy-owning components.
|
|
# CEF and PAC are never owned by the WebUI: they are GTK/desktop proxy/browser
|
|
# components and can rewrite system/browser proxy state.
|
|
webui_disabled_unit() {
|
|
case "${1,,}" in
|
|
bastionguard-cef.service|bastionguard-cef|bastionguard-pacd.service|bastionguard-pacd|bastionguard-privacyd.service|bastionguard-privacyd|bastionguard-usbd.service|bastionguard-usbd) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
proxy_sensitive_unit() {
|
|
return 1
|
|
}
|
|
desktop_session_summary() {
|
|
local user="${1:-}" uid="" sid name active state type class remote desktop leader comm details=""
|
|
valid_user "$user" || { echo "invalid-user"; return 1; }
|
|
uid="$(user_uid "$user")"
|
|
|
|
if command -v loginctl >/dev/null 2>&1; then
|
|
while read -r sid _rest; do
|
|
[[ -n "$sid" ]] || continue
|
|
name="$(loginctl show-session "$sid" -p Name --value 2>/dev/null || true)"
|
|
[[ "$name" == "$user" ]] || continue
|
|
active="$(loginctl show-session "$sid" -p Active --value 2>/dev/null || true)"
|
|
state="$(loginctl show-session "$sid" -p State --value 2>/dev/null || true)"
|
|
type="$(loginctl show-session "$sid" -p Type --value 2>/dev/null || true)"
|
|
class="$(loginctl show-session "$sid" -p Class --value 2>/dev/null || true)"
|
|
remote="$(loginctl show-session "$sid" -p Remote --value 2>/dev/null || true)"
|
|
desktop="$(loginctl show-session "$sid" -p Desktop --value 2>/dev/null || true)"
|
|
leader="$(loginctl show-session "$sid" -p Leader --value 2>/dev/null || true)"
|
|
comm=""
|
|
[[ "$leader" =~ ^[0-9]+$ ]] && comm="$(ps -p "$leader" -o comm= 2>/dev/null || true)"
|
|
details="session=$sid active=${active:-?} state=${state:-?} type=${type:-?} class=${class:-?} remote=${remote:-?} desktop=${desktop:-?} leader=${comm:-$leader}"
|
|
case "$type" in
|
|
x11|wayland|mir) echo "gui $details"; return 0 ;;
|
|
esac
|
|
if [[ "$active" == yes && "$class" == user && "$remote" != yes ]]; then
|
|
case "$desktop $comm" in
|
|
*plasma*|*gnome*|*xfce*|*cinnamon*|*mate*|*lxqt*|*budgie*) echo "gui $details"; return 0 ;;
|
|
esac
|
|
fi
|
|
done < <(loginctl list-sessions --no-legend 2>/dev/null || true)
|
|
fi
|
|
|
|
# Fallbacks for systems without complete logind metadata.
|
|
if [[ -S "/run/user/$uid/bus" ]]; then
|
|
if pgrep -u "$user" -x plasmashell >/dev/null 2>&1 || pgrep -u "$user" -x gnome-shell >/dev/null 2>&1 || pgrep -u "$user" -x xfce4-session >/dev/null 2>&1 || pgrep -u "$user" -x cinnamon-session >/dev/null 2>&1 || pgrep -u "$user" -x mate-session >/dev/null 2>&1 || pgrep -u "$user" -x lxqt-session >/dev/null 2>&1; then
|
|
echo "gui process-detected user=$user uid=$uid"
|
|
return 0
|
|
fi
|
|
if compgen -G "/run/user/$uid/wayland-*" >/dev/null 2>&1; then
|
|
echo "gui wayland-socket user=$user uid=$uid"
|
|
return 0
|
|
fi
|
|
fi
|
|
|
|
echo "headless no-active-graphical-session user=$user uid=$uid"
|
|
return 1
|
|
}
|
|
|
|
is_desktop_gui_active() {
|
|
local summary
|
|
summary="$(desktop_session_summary "$1" 2>/dev/null || true)"
|
|
[[ "$summary" == gui* ]]
|
|
}
|
|
|
|
guard_proxy_desktop_conflict() {
|
|
local user="$1" unit="$2" action="${3:-manage}"
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
proxy_sensitive_unit "$unit" || return 0
|
|
case "$action" in start|restart|enable|enable--now|install|manage) ;; stop|disable|disable--now) return 0 ;; esac
|
|
local summary
|
|
summary="$(desktop_session_summary "$user" 2>/dev/null || true)"
|
|
if [[ "$summary" == gui* ]]; then
|
|
cat >&2 <<EOF
|
|
BastionGuard WebUI safe-desktop guard: blocked $action for $unit.
|
|
Reason: graphical desktop detected for user '$user' ($summary).
|
|
The GTK desktop UI must own proxy/browser configuration.
|
|
CEF/PAC, Webcam/Privacy and USB are always disabled in the server WebUI and must be managed by the GTK/desktop UI or locally on the host.
|
|
EOF
|
|
exit 75
|
|
fi
|
|
echo "BastionGuard WebUI safe-desktop guard: no GUI detected for '$user'; WebUI may manage $unit. ($summary)"
|
|
}
|
|
|
|
make_bastionguard_nginx_conf() {
|
|
local http="$1" https="$2" root_dir="${3:-/srv/http/webui}"
|
|
cat <<EOFNGINX
|
|
# BastionGuard Local Web Server
|
|
server {
|
|
listen ${http};
|
|
listen ${https} ssl;
|
|
server_name localhost 127.0.0.1;
|
|
root ${root_dir};
|
|
index index.php index.html;
|
|
|
|
location / {
|
|
try_files \$uri \$uri/ /index.php?\$query_string;
|
|
}
|
|
|
|
location ~ \.php$ {
|
|
include fastcgi_params;
|
|
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
|
|
fastcgi_pass unix:/run/php-fpm/php-fpm.sock;
|
|
}
|
|
}
|
|
EOFNGINX
|
|
}
|
|
|
|
config_path_for_key() {
|
|
local key="$1" user="${2:-}" home rel path scope
|
|
[[ "$key" =~ ^[a-z_]+$ ]] || { echo "Chiave non valida" >&2; exit 64; }
|
|
case "$key" in
|
|
webports) path=/etc/BastionGuard/webports.conf; scope=system ;;
|
|
dnsmasq) path=/etc/dnsmasq.d/BastionGuard.conf; scope=system ;;
|
|
app_config) rel=.config/BastionGuard/config.json; scope=user ;;
|
|
scan_config) rel=.local/share/BastionGuard/config.json; scope=user ;;
|
|
cloud) rel=.config/BastionGuard/cloud.conf; scope=user ;;
|
|
cred) rel=.config/BastionGuard/cred.conf; scope=user ;;
|
|
firewall) rel=.config/BastionGuard/firewall.conf; scope=user ;;
|
|
identity_leak) rel=.config/BastionGuard/identity_leak.json; scope=user ;;
|
|
scanner) rel=.config/BastionGuard/scanner.conf; scope=user ;;
|
|
googlesafe) rel=.config/BastionGuard/googlesafe.conf; scope=user ;;
|
|
mail) rel=.config/BastionGuard/mail.json; scope=user ;;
|
|
payments) rel=.config/BastionGuard/payments.json; scope=user ;;
|
|
proxy_bypass) rel=.config/BastionGuard/proxy_bypass.json; scope=user ;;
|
|
samba) rel=.config/BastionGuard/samba.conf; scope=user ;;
|
|
settings) rel=.config/BastionGuard/settings.json; scope=user ;;
|
|
theme) rel=.config/BastionGuard/theme.conf; scope=user ;;
|
|
whitelist) rel=.config/BastionGuard/whitelist.json; scope=user ;;
|
|
lang) rel=.config/BastionGuard/lang.conf; scope=user ;;
|
|
sources) rel=.config/BastionGuard/sources.conf; scope=user ;;
|
|
first_run_services_done) rel=.config/BastionGuard/first-run-services-done; scope=user ;;
|
|
*) echo "Chiave non consentita: $key" >&2; exit 64 ;;
|
|
esac
|
|
if [[ "$scope" == user ]]; then
|
|
[[ -n "$user" ]] || { echo "Missing user" >&2; exit 64; }
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
home="$(user_home "$user")"
|
|
path="$home/$rel"
|
|
fi
|
|
printf '%s\t%s\n' "$scope" "$path"
|
|
}
|
|
|
|
list_configs() {
|
|
local user="$1" home dir uid gid name path type readable size mtime
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
home="$(user_home "$user")"
|
|
dir="$home/.config/BastionGuard"
|
|
printf 'DIR\t%s\t%s\n' "$dir" "$( [[ -d "$dir" ]] && echo 1 || echo 0 )"
|
|
[[ -d "$dir" ]] || exit 0
|
|
shopt -s nullglob dotglob
|
|
for path in "$dir"/*; do
|
|
name="$(basename "$path")"
|
|
[[ "$name" == "." || "$name" == ".." ]] && continue
|
|
if [[ -d "$path" ]]; then type=directory; size=0; else type=file; size="$(stat -c '%s' "$path" 2>/dev/null || echo 0)"; fi
|
|
[[ -r "$path" ]] && readable=1 || readable=0
|
|
mtime="$(stat -c '%Y' "$path" 2>/dev/null || echo 0)"
|
|
printf 'ITEM\t%s\t%s\t%s\t%s\t%s\n' "$(printf '%s' "$name" | base64 -w0)" "$type" "$readable" "$size" "$mtime"
|
|
done
|
|
}
|
|
|
|
read_config_b64() {
|
|
local key="$1" user="${2:-}" meta scope path
|
|
meta="$(config_path_for_key "$key" "$user")"
|
|
scope="${meta%%$'\t'*}"; path="${meta#*$'\t'}"
|
|
[[ -e "$path" ]] || { echo "MISSING $path" >&2; exit 66; }
|
|
[[ ! -d "$path" ]] || { echo "DIRECTORY $path" >&2; exit 68; }
|
|
[[ -f "$path" ]] || { echo "NOTFILE $path" >&2; exit 65; }
|
|
[[ -r "$path" ]] || { echo "UNREADABLE $path" >&2; exit 13; }
|
|
base64 -w0 "$path"
|
|
}
|
|
|
|
write_user_file() {
|
|
local user="$1" rel="$2" mode="$3" content="$4"
|
|
local home uid gid path dir tmp
|
|
home="$(user_home "$user")"; uid="$(user_uid "$user")"; gid="$(user_gid "$user")"
|
|
path="$home/$rel"; dir="$(dirname "$path")"
|
|
mkdir -p "$dir"
|
|
tmp="$(mktemp)"
|
|
printf '%s' "$content" > "$tmp"
|
|
install -o "$uid" -g "$gid" -m "$mode" "$tmp" "$path"
|
|
rm -f "$tmp"
|
|
echo "created: $path"
|
|
}
|
|
|
|
write_system_file() {
|
|
local path="$1" mode="$2" content="$3"
|
|
local dir tmp err rc
|
|
dir="$(dirname "$path")"
|
|
if ! mkdir -p "$dir" 2>/tmp/bastionguard-webui-mkdir.err; then
|
|
err="$(cat /tmp/bastionguard-webui-mkdir.err 2>/dev/null || true)"
|
|
echo "skipped: $path (${err:-cannot create directory})"
|
|
return 1
|
|
fi
|
|
tmp="$(mktemp)"
|
|
printf '%s' "$content" > "$tmp"
|
|
err="$(install -o root -g root -m "$mode" "$tmp" "$path" 2>&1)" || {
|
|
rc=$?
|
|
rm -f "$tmp"
|
|
case "$err" in
|
|
*'Read-only file system'*) echo "skipped: $path (read-only file system)" ;;
|
|
*'Permission denied'*) echo "skipped: $path (permission denied)" ;;
|
|
*) echo "skipped: $path (${err:-install failed})" ;;
|
|
esac
|
|
return "$rc"
|
|
}
|
|
rm -f "$tmp"
|
|
echo "created: $path"
|
|
}
|
|
|
|
ensure_user_dirs() {
|
|
local user="$1" home uid gid
|
|
home="$(user_home "$user")"; uid="$(user_uid "$user")"; gid="$(user_gid "$user")"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$home/.config/BastionGuard"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$home/.local/share/BastionGuard"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$home/.local/share/BastionGuard/logs"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$home/.local/share/BastionGuard/quarantine"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$home/.local/share/BastionGuard/phishing"
|
|
}
|
|
|
|
ensure_configs() {
|
|
local user="$1" home
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
home="$(user_home "$user")"
|
|
ensure_user_dirs "$user"
|
|
|
|
[[ -f /etc/BastionGuard/webports.conf ]] || write_system_file /etc/BastionGuard/webports.conf 0644 $'81 444\n'
|
|
[[ -f /etc/dnsmasq.d/BastionGuard.conf ]] || write_system_file /etc/dnsmasq.d/BastionGuard.conf 0644 $'# Interfacce di ascolto per BastionGuard DNS Protection\nlisten-address=127.0.0.1,127.0.0.2\n\n# Facoltativo: riduce warning di bind\nbind-interfaces\n'
|
|
|
|
# Non marchiamo il wizard come completato qui: config.json completato viene scritto solo dal first-run wizard.
|
|
[[ -f "$home/.config/BastionGuard/config.json" ]] || true
|
|
[[ -f "$home/.local/share/BastionGuard/config.json" ]] || write_user_file "$user" .local/share/BastionGuard/config.json 0640 $'{\n "auto_scan_enabled": true\n}\n'
|
|
[[ -f "$home/.config/BastionGuard/cloud.conf" ]] || write_user_file "$user" .config/BastionGuard/cloud.conf 0640 $'malware_bazaar_api_key=\n'
|
|
[[ -f "$home/.config/BastionGuard/scanner.conf" ]] || write_user_file "$user" .config/BastionGuard/scanner.conf 0640 $'# --- BastionGuard Anti-Ransomware Config ---\nsuspicious_only=0\nignore_paths=/proc;/sys;/dev;/run;/tmp\nignore_ext=.tmp;.log;.cache\nsuspicious_ext=.locked;.encrypted;.crypt;.enc;.encrypted\n\n# --- Ransomware Scanner ---\nenable_yara=1\nenable_sanesecurity=1\nscan_interval=60\nscan_path=/home\n'
|
|
[[ -f "$home/.config/BastionGuard/mail.json" ]] || write_user_file "$user" .config/BastionGuard/mail.json 0640 $'{\n "version": 2,\n "enabled": false,\n "scan_outgoing": true,\n "inject_signature": false,\n "local_smtp_host": "127.0.0.1",\n "local_smtp_port": 2525,\n "local_smtp_tls_port": 2465,\n "local_submission_port": 2587,\n "advertise_starttls": true,\n "enable_implicit_tls_listener": true,\n "default_profile_id": "default",\n "profiles": [\n {\n "id": "default",\n "label": "Default",\n "match_from": [],\n "match_from_domain": [],\n "smtp_host": "",\n "smtp_port": 587,\n "starttls": true,\n "implicit_tls": false,\n "username": "",\n "password": "",\n "signature": {\n "display_name": "",\n "job_title": "",\n "company": "",\n "phone": "",\n "website": "",\n "logo_path": "/usr/share/BastionGuard/data/logo.png"\n }\n }\n ]\n}\n'
|
|
[[ -f "$home/.config/BastionGuard/payments.json" ]] || write_user_file "$user" .config/BastionGuard/payments.json 0640 $'{\n "version": 1,\n "enabled": false,\n "list": [\n "paypal.com",\n "stripe.com",\n "adyen.com",\n "klarna.com",\n "checkout.com",\n "worldpay.com",\n "braintreepayments.com",\n "braintreegateway.com",\n "amazonpay.com",\n "pay.google.com",\n "pay.apple.com",\n "amazon.com"\n ]\n}\n'
|
|
[[ -f "$home/.config/BastionGuard/whitelist.json" ]] || write_user_file "$user" .config/BastionGuard/whitelist.json 0640 $'{\n "domains": []\n}\n'
|
|
[[ -f "$home/.config/BastionGuard/lang.conf" ]] || write_user_file "$user" .config/BastionGuard/lang.conf 0640 $'language=it\n'
|
|
[[ -f "$home/.config/BastionGuard/googlesafe.conf" ]] || write_user_file "$user" .config/BastionGuard/googlesafe.conf 0640 $'# Google Safe Browsing configuration\ngoogle_safe_enabled=false\ngoogle_safe_key=\n'
|
|
[[ -f "$home/.config/BastionGuard/sources.conf" ]] || write_user_file "$user" .config/BastionGuard/sources.conf 0640 $'clamav=1\nsanesecurity=1\n'
|
|
[[ -f "$home/.config/BastionGuard/cred.conf" ]] || write_user_file "$user" .config/BastionGuard/cred.conf 0640 $'# Credenziali locali BastionGuard\n'
|
|
[[ -f "$home/.config/BastionGuard/firewall.conf" ]] || write_user_file "$user" .config/BastionGuard/firewall.conf 0640 $'enabled=1\nmode=auto\n'
|
|
[[ -f "$home/.config/BastionGuard/identity_leak.json" ]] || write_user_file "$user" .config/BastionGuard/identity_leak.json 0640 $'{\n "emails": [],\n "providers": []\n}\n'
|
|
[[ -f "$home/.config/BastionGuard/proxy_bypass.json" ]] || write_user_file "$user" .config/BastionGuard/proxy_bypass.json 0640 $'{\n "domains": ["localhost", "127.0.0.1"]\n}\n'
|
|
[[ -f "$home/.config/BastionGuard/samba.conf" ]] || write_user_file "$user" .config/BastionGuard/samba.conf 0640 $'scan_enabled=1\nquarantine_enabled=1\n'
|
|
[[ -f "$home/.config/BastionGuard/settings.json" ]] || write_user_file "$user" .config/BastionGuard/settings.json 0640 $'{\n "version": 1\n}\n'
|
|
[[ -f "$home/.config/BastionGuard/theme.conf" ]] || write_user_file "$user" .config/BastionGuard/theme.conf 0640 $'theme=system\n'
|
|
}
|
|
|
|
first_run_config() {
|
|
local user="$1" b64="$2" home cfg content uid gid
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
content="$(printf '%s' "$b64" | base64 -d)" || { echo "Base64 non valido" >&2; exit 64; }
|
|
if ! printf '%s' "$content" | grep -q '"wizard_completed"[[:space:]]*:[[:space:]]*true'; then
|
|
echo "config.json rifiutato: wizard_completed deve essere true" >&2
|
|
exit 64
|
|
fi
|
|
home="$(user_home "$user")"; uid="$(user_uid "$user")"; gid="$(user_gid "$user")"
|
|
cfg="$home/.config/BastionGuard/config.json"
|
|
mkdir -p "$(dirname "$cfg")"
|
|
chown "$uid:$gid" "$(dirname "$cfg")" 2>/dev/null || true
|
|
if [[ -f "$cfg" ]] && grep -q '"wizard_completed"[[:space:]]*:[[:space:]]*true' "$cfg" && grep -q '"services"' "$cfg"; then
|
|
echo "config.json already completed: no changes."
|
|
exit 0
|
|
fi
|
|
write_user_file "$user" .config/BastionGuard/config.json 0640 "$content
|
|
"
|
|
: > "$home/.config/BastionGuard/first-run-services-done"
|
|
chown "$uid:$gid" "$home/.config/BastionGuard/first-run-services-done" 2>/dev/null || true
|
|
chmod 0640 "$home/.config/BastionGuard/first-run-services-done" 2>/dev/null || true
|
|
}
|
|
|
|
write_config() {
|
|
local key="$1" b64="$2" user="${3:-}" path scope mode content home uid gid
|
|
[[ "$key" =~ ^[a-z_]+$ ]] || { echo "Chiave non valida" >&2; exit 64; }
|
|
content="$(printf '%s' "$b64" | base64 -d)" || { echo "Base64 non valido" >&2; exit 64; }
|
|
case "$key" in
|
|
webports) path=/etc/BastionGuard/webports.conf; scope=system; mode=0644 ;;
|
|
dnsmasq) path=/etc/dnsmasq.d/BastionGuard.conf; scope=system; mode=0644 ;;
|
|
app_config) rel=.config/BastionGuard/config.json; scope=user; mode=0640 ;;
|
|
config_ini) rel=.config/BastionGuard/config.ini; scope=user; mode=0640 ;;
|
|
scan_config) rel=.local/share/BastionGuard/config.json; scope=user; mode=0640 ;;
|
|
cloud) rel=.config/BastionGuard/cloud.conf; scope=user; mode=0640 ;;
|
|
cred) rel=.config/BastionGuard/cred.conf; scope=user; mode=0640 ;;
|
|
firewall) rel=.config/BastionGuard/firewall.conf; scope=user; mode=0640 ;;
|
|
identity_leak) rel=.config/BastionGuard/identity_leak.json; scope=user; mode=0640 ;;
|
|
scanner) rel=.config/BastionGuard/scanner.conf; scope=user; mode=0640 ;;
|
|
googlesafe) rel=.config/BastionGuard/googlesafe.conf; scope=user; mode=0640 ;;
|
|
allowlist) rel=.config/BastionGuard/allowlist.txt; scope=user; mode=0640 ;;
|
|
mail) rel=.config/BastionGuard/mail.json; scope=user; mode=0640 ;;
|
|
payments) rel=.config/BastionGuard/payments.json; scope=user; mode=0640 ;;
|
|
proxy_bypass) rel=.config/BastionGuard/proxy_bypass.json; scope=user; mode=0640 ;;
|
|
samba) rel=.config/BastionGuard/samba.conf; scope=user; mode=0640 ;;
|
|
settings) rel=.config/BastionGuard/settings.json; scope=user; mode=0640 ;;
|
|
theme) rel=.config/BastionGuard/theme.conf; scope=user; mode=0640 ;;
|
|
whitelist) rel=.config/BastionGuard/whitelist.json; scope=user; mode=0640 ;;
|
|
lang) rel=.config/BastionGuard/lang.conf; scope=user; mode=0640 ;;
|
|
sources) rel=.config/BastionGuard/sources.conf; scope=user; mode=0640 ;;
|
|
first_run_services_done) rel=.config/BastionGuard/first-run-services-done; scope=user; mode=0640 ;;
|
|
*) echo "Chiave non consentita: $key" >&2; exit 64 ;;
|
|
esac
|
|
if [[ "$scope" == system ]]; then
|
|
write_system_file "$path" "$mode" "$content"
|
|
else
|
|
[[ -n "$user" ]] || { echo "Missing user" >&2; exit 64; }
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
write_user_file "$user" "$rel" "$mode" "$content"
|
|
fi
|
|
}
|
|
|
|
run_as_user() {
|
|
local user="$1"; shift
|
|
local uid runtime bus
|
|
uid="$(user_uid "$user")"; runtime="/run/user/$uid"; bus="unix:path=$runtime/bus"
|
|
if command -v runuser >/dev/null 2>&1; then
|
|
runuser -u "$user" -- env XDG_RUNTIME_DIR="$runtime" DBUS_SESSION_BUS_ADDRESS="$bus" "$@"
|
|
else
|
|
sudo -n -u "$user" env XDG_RUNTIME_DIR="$runtime" DBUS_SESSION_BUS_ADDRESS="$bus" "$@"
|
|
fi
|
|
}
|
|
|
|
setup_net() {
|
|
local script=/usr/share/BastionGuard/data/scripts/BastionGuard-net-setup.sh
|
|
if [[ -f "$script" ]]; then
|
|
echo "running: $script"
|
|
/usr/bin/env bash "$script" || echo "warning: BastionGuard-net-setup.sh failed or incomplete"
|
|
else
|
|
echo "skipped: $script (not found)"
|
|
fi
|
|
}
|
|
|
|
setup_dnsmasq() {
|
|
if ! can_write_dir /etc; then
|
|
echo "skipped: /etc/dnsmasq.conf (read-only or not writable)"
|
|
write_system_file /etc/dnsmasq.d/BastionGuard.conf 0644 $'# Interfacce di ascolto per BastionGuard DNS Protection\nlisten-address=127.0.0.1,127.0.0.2\n\n# Facoltativo: riduce warning di bind\nbind-interfaces\n' || true
|
|
return 0
|
|
fi
|
|
mkdir -p /etc/dnsmasq.d || { echo "skipped: /etc/dnsmasq.d (cannot create directory)"; return 0; }
|
|
if [[ -f /etc/dnsmasq.conf ]]; then
|
|
[[ -f /etc/dnsmasq.conf.bastionguard.bak ]] || cp -a /etc/dnsmasq.conf /etc/dnsmasq.conf.bastionguard.bak || true
|
|
if grep -Eq '^[[:space:]]*#?[[:space:]]*conf-dir=/etc/dnsmasq\.d/.*,\*\.conf[[:space:]]*$' /etc/dnsmasq.conf; then
|
|
sed -i -E 's|^[[:space:]]*#?[[:space:]]*(conf-dir=/etc/dnsmasq\.d/.*,\*\.conf)[[:space:]]*$|\1|' /etc/dnsmasq.conf || echo "skipped: /etc/dnsmasq.conf sed update failed"
|
|
else
|
|
printf '\nconf-dir=/etc/dnsmasq.d/,*.conf\n' >> /etc/dnsmasq.conf || echo "skipped: /etc/dnsmasq.conf append failed"
|
|
fi
|
|
else
|
|
printf 'conf-dir=/etc/dnsmasq.d/,*.conf\n' > /etc/dnsmasq.conf || echo "skipped: /etc/dnsmasq.conf create failed"
|
|
fi
|
|
write_system_file /etc/dnsmasq.d/BastionGuard.conf 0644 $'# Interfacce di ascolto per BastionGuard DNS Protection\nlisten-address=127.0.0.1,127.0.0.2\n\n# Facoltativo: riduce warning di bind\nbind-interfaces\n' || true
|
|
systemctl try-restart dnsmasq.service 2>/dev/null || true
|
|
}
|
|
|
|
setup_browser_policies() {
|
|
local ff='{
|
|
"policies": {
|
|
"DNSOverHTTPS": {
|
|
"Enabled": true,
|
|
"ProviderURL": "https://127.0.0.1:4443/dns-query",
|
|
"Locked": true
|
|
}
|
|
}
|
|
}
|
|
'
|
|
local chrom='{
|
|
"DnsOverHttpsMode": "secure",
|
|
"DnsOverHttpsTemplates": ["https://127.0.0.1:4443/dns-query"]
|
|
}
|
|
'
|
|
write_system_file /usr/lib/firefox/distribution/policies.json 0644 "$ff" || true
|
|
write_system_file /etc/chromium/policies/managed/BastionGuard-dns.json 0644 "$chrom" || true
|
|
write_system_file /etc/opt/chrome/policies/managed/BastionGuard-dns.json 0644 "$chrom" || true
|
|
write_system_file /etc/opt/edge/policies/managed/BastionGuard-dns.json 0644 "$chrom" || true
|
|
}
|
|
|
|
setup_certs() {
|
|
if ! can_write_dir /etc/BastionGuard; then echo "skipped: /etc/BastionGuard/certs (read-only or not writable)"; return 0; fi
|
|
mkdir -p /etc/BastionGuard/certs && chmod 755 /etc/BastionGuard/certs
|
|
[[ -f /etc/BastionGuard/certs/BastionGuard-ca.key.pem ]] || openssl genrsa -out /etc/BastionGuard/certs/BastionGuard-ca.key.pem 4096
|
|
[[ -f /etc/BastionGuard/certs/BastionGuard-ca.crt.pem ]] || openssl req -x509 -new -nodes -key /etc/BastionGuard/certs/BastionGuard-ca.key.pem -sha256 -days 3650 -subj '/CN=BastionGuard Local CA' -out /etc/BastionGuard/certs/BastionGuard-ca.crt.pem
|
|
[[ -f /etc/BastionGuard/certs/local-warning.key.pem ]] || openssl genrsa -out /etc/BastionGuard/certs/local-warning.key.pem 2048
|
|
openssl req -new -key /etc/BastionGuard/certs/local-warning.key.pem -subj '/CN=localhost' -out /etc/BastionGuard/certs/local-warning.csr.pem
|
|
printf 'subjectAltName = DNS:localhost,IP:127.0.0.2\n' > /etc/BastionGuard/certs/v3ext.cnf
|
|
openssl x509 -req -in /etc/BastionGuard/certs/local-warning.csr.pem -CA /etc/BastionGuard/certs/BastionGuard-ca.crt.pem -CAkey /etc/BastionGuard/certs/BastionGuard-ca.key.pem -CAcreateserial -out /etc/BastionGuard/certs/local-warning.crt.pem -days 825 -sha256 -extfile /etc/BastionGuard/certs/v3ext.cnf
|
|
rm -f /etc/BastionGuard/certs/local-warning.csr.pem /etc/BastionGuard/certs/BastionGuard-ca.srl /etc/BastionGuard/certs/v3ext.cnf || true
|
|
chmod 600 /etc/BastionGuard/certs/*key.pem || true
|
|
chmod 644 /etc/BastionGuard/certs/*.crt.pem || true
|
|
if command -v update-ca-certificates >/dev/null 2>&1; then install -m 0644 /etc/BastionGuard/certs/BastionGuard-ca.crt.pem /usr/local/share/ca-certificates/BastionGuard-ca.crt && update-ca-certificates || true
|
|
elif command -v trust >/dev/null 2>&1; then trust anchor --store /etc/BastionGuard/certs/BastionGuard-ca.crt.pem || true
|
|
elif command -v update-ca-trust >/dev/null 2>&1; then install -m 0644 /etc/BastionGuard/certs/BastionGuard-ca.crt.pem /etc/pki/ca-trust/source/anchors/BastionGuard-ca.crt && update-ca-trust extract || true
|
|
fi
|
|
}
|
|
|
|
|
|
setup_source_presteps() {
|
|
local clamav_script=/usr/share/BastionGuard/data/scripts/BastionGuard-setup-clamav-daemon.sh
|
|
local locale_script=/usr/share/BastionGuard/data/scripts/BastionGuard-locale.sh
|
|
if [[ -f "$clamav_script" ]]; then
|
|
echo "[BastionGuard] Setup ClamAV daemon pre-step"
|
|
/usr/bin/env bash "$clamav_script" || echo "warning: BastionGuard-setup-clamav-daemon.sh failed or incomplete"
|
|
else
|
|
echo "skipped: $clamav_script (not found)"
|
|
fi
|
|
if [[ -f "$locale_script" ]]; then
|
|
echo "[BastionGuard] Locale pre-step"
|
|
/bin/sh "$locale_script" || echo "warning: BastionGuard-locale.sh failed or incomplete"
|
|
else
|
|
echo "skipped: $locale_script (not found)"
|
|
fi
|
|
}
|
|
|
|
restart_phishing_scanner() {
|
|
echo "[BastionGuard] Restarting BastionGuard-phishing-scanner.service"
|
|
if command -v systemctl >/dev/null 2>&1 && systemctl list-unit-files BastionGuard-phishing-scanner.service >/dev/null 2>&1; then
|
|
systemctl restart BastionGuard-phishing-scanner.service || true
|
|
else
|
|
echo "skipped: BastionGuard-phishing-scanner.service unit not found"
|
|
fi
|
|
}
|
|
|
|
setup_firewall() {
|
|
local user="${1:-}"
|
|
local ports=(53/udp 81/tcp 444/tcp 4443/tcp)
|
|
echo "[BastionGuard] Opening required firewall ports: 53/udp, 81/tcp, 444/tcp, 4443/tcp"
|
|
if command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld.service; then
|
|
firewall-cmd --permanent --add-port=53/udp || true
|
|
firewall-cmd --permanent --add-port=81/tcp || true
|
|
firewall-cmd --permanent --add-port=444/tcp || true
|
|
firewall-cmd --permanent --add-port=4443/tcp || true
|
|
firewall-cmd --reload || true
|
|
echo "firewalld: rules applied"
|
|
elif command -v ufw >/dev/null 2>&1; then
|
|
for p in "${ports[@]}"; do ufw allow "$p" || true; done
|
|
echo "ufw: rules applied"
|
|
elif command -v nft >/dev/null 2>&1; then
|
|
modprobe nf_nat 2>/dev/null || true
|
|
mkdir -p /etc/modules-load.d 2>/dev/null || true
|
|
printf 'nf_nat\n' > /etc/modules-load.d/BastionGuard.conf 2>/dev/null || true
|
|
nft add table inet BastionGuard_filter 2>/dev/null || true
|
|
nft 'add chain inet BastionGuard_filter input { type filter hook input priority 0; policy accept; }' 2>/dev/null || true
|
|
nft add rule inet BastionGuard_filter input udp dport 53 accept 2>/dev/null || true
|
|
nft add rule inet BastionGuard_filter input tcp dport 81 accept 2>/dev/null || true
|
|
nft add rule inet BastionGuard_filter input tcp dport 444 accept 2>/dev/null || true
|
|
nft add rule inet BastionGuard_filter input tcp dport 4443 accept 2>/dev/null || true
|
|
echo "nftables: best-effort rules applied"
|
|
elif command -v iptables >/dev/null 2>&1; then
|
|
iptables -I INPUT -p udp --dport 53 -j ACCEPT || true
|
|
iptables -I INPUT -p tcp --dport 81 -j ACCEPT || true
|
|
iptables -I INPUT -p tcp --dport 444 -j ACCEPT || true
|
|
iptables -I INPUT -p tcp --dport 4443 -j ACCEPT || true
|
|
echo "iptables: best-effort rules applied"
|
|
else
|
|
echo "warning: no supported firewall tool detected; open ports manually if needed"
|
|
fi
|
|
|
|
# Coerente con src/wizard/wizard_setup.cpp: aggiunge l'utente ai gruppi utili per log/journal.
|
|
if [[ -n "$user" ]] && valid_user "$user"; then
|
|
if getent group adm >/dev/null 2>&1; then usermod -aG adm "$user" || true; fi
|
|
if getent group system-journal >/dev/null 2>&1; then usermod -aG system-journal "$user" || true; fi
|
|
echo "groups: user $user added to adm/system-journal when available"
|
|
fi
|
|
}
|
|
|
|
setup_nftables() {
|
|
command -v nft >/dev/null 2>&1 || { echo "skipped: nft command not found"; return 0; }
|
|
local src_conf=/usr/share/BastionGuard/data/config/nftables.conf
|
|
local dst_conf=/etc/nftables.conf
|
|
local net_script=/usr/share/BastionGuard/data/scripts/BastionGuard-net-setup.sh
|
|
|
|
echo "[BastionGuard] Configuring nftables using source wizard logic"
|
|
if [[ -f "$src_conf" ]]; then
|
|
if can_write_dir /etc; then
|
|
install -m 0644 "$src_conf" "$dst_conf" || echo "warning: cannot install $dst_conf"
|
|
nft -f "$dst_conf" 2>/dev/null || true
|
|
if ! systemctl is-active --quiet ufw 2>/dev/null && ! systemctl is-active --quiet firewalld 2>/dev/null; then
|
|
systemctl enable --now nftables.service 2>/dev/null || true
|
|
echo "nftables.service enabled when no UFW/firewalld was active"
|
|
else
|
|
echo "UFW or firewalld detected: existing firewall kept"
|
|
fi
|
|
else
|
|
echo "skipped: $dst_conf (read-only or not writable)"
|
|
fi
|
|
else
|
|
echo "skipped: $src_conf (not found)"
|
|
fi
|
|
|
|
if [[ -f "$net_script" ]]; then
|
|
echo "running: $net_script"
|
|
/usr/bin/env bash "$net_script" || echo "warning: BastionGuard-net-setup.sh failed or incomplete"
|
|
else
|
|
echo "skipped: $net_script (not found)"
|
|
fi
|
|
}
|
|
|
|
setup_webconf() {
|
|
local http="$1" https="$2" distro
|
|
distro="$(detect_distro_src)"
|
|
echo "[detect_distro] Distribuzione rilevata: $distro"
|
|
apply_web_config "$http" "$https" "$distro"
|
|
}
|
|
|
|
setup_native() {
|
|
local user="${1:-}" extension_id="${2:-}"
|
|
local bin_path=/usr/bin/BastionGuard-native-host
|
|
local chrome_json firefox_json
|
|
[[ -x "$bin_path" || -f "$bin_path" ]] || { echo "warning: native host binary not found: $bin_path"; }
|
|
chmod a+rx "$bin_path" 2>/dev/null || true
|
|
|
|
if [[ -n "$extension_id" ]]; then
|
|
chrome_json=$(cat <<EOF
|
|
{
|
|
"name": "com.BastionGuard.native_host",
|
|
"description": "BastionGuard native messaging host",
|
|
"path": "$bin_path",
|
|
"type": "stdio",
|
|
"allowed_origins": ["chrome-extension://$extension_id/"]
|
|
}
|
|
EOF
|
|
)
|
|
else
|
|
chrome_json=$(cat <<EOF
|
|
{
|
|
"name": "com.BastionGuard.native_host",
|
|
"description": "BastionGuard native messaging host",
|
|
"path": "$bin_path",
|
|
"type": "stdio",
|
|
"allowed_origins": []
|
|
}
|
|
EOF
|
|
)
|
|
fi
|
|
firefox_json=$(cat <<EOF
|
|
{
|
|
"name": "com.BastionGuard.native_host",
|
|
"description": "BastionGuard native messaging host",
|
|
"path": "$bin_path",
|
|
"type": "stdio",
|
|
"allowed_extensions": ["BastionGuard@local"]
|
|
}
|
|
EOF
|
|
)
|
|
|
|
echo "[BastionGuard] Installing native messaging host manifests"
|
|
if [[ -n "$user" ]] && valid_user "$user"; then
|
|
write_user_file "$user" .config/google-chrome/NativeMessagingHosts/com.BastionGuard.native_host.json 0644 "$chrome_json" || true
|
|
write_user_file "$user" .config/chromium/NativeMessagingHosts/com.BastionGuard.native_host.json 0644 "$chrome_json" || true
|
|
write_user_file "$user" .config/BraveSoftware/Brave-Browser/NativeMessagingHosts/com.BastionGuard.native_host.json 0644 "$chrome_json" || true
|
|
write_user_file "$user" .config/microsoft-edge/NativeMessagingHosts/com.BastionGuard.native_host.json 0644 "$chrome_json" || true
|
|
write_user_file "$user" .mozilla/native-messaging-hosts/com.BastionGuard.native_host.json 0644 "$firefox_json" || true
|
|
write_user_file "$user" .librewolf/native-messaging-hosts/com.BastionGuard.native_host.json 0644 "$firefox_json" || true
|
|
fi
|
|
|
|
if can_write_dir /etc; then
|
|
write_system_file /etc/opt/chrome/native-messaging-hosts/com.BastionGuard.native_host.json 0644 "$chrome_json" || true
|
|
write_system_file /etc/chromium/native-messaging-hosts/com.BastionGuard.native_host.json 0644 "$chrome_json" || true
|
|
else
|
|
echo "skipped: system native host manifests under /etc (read-only or not writable)"
|
|
fi
|
|
if can_write_dir /usr/lib; then
|
|
write_system_file /usr/lib/mozilla/native-messaging-hosts/com.BastionGuard.native_host.json 0644 "$firefox_json" || true
|
|
else
|
|
echo "skipped: Firefox system native host manifest under /usr/lib (read-only or not writable)"
|
|
fi
|
|
}
|
|
|
|
setup_phishing_blacklist() {
|
|
local user="$1" src=/usr/share/BastionGuard/data/phishing-blacklist.txt
|
|
if [[ -f "$src" ]]; then
|
|
write_user_file "$user" .local/share/BastionGuard/phishing/blacklist.txt 0644 "$(cat "$src")" || true
|
|
echo "phishing blacklist copied to ~/.local/share/BastionGuard/phishing/blacklist.txt"
|
|
else
|
|
echo "skipped: $src (not found)"
|
|
fi
|
|
}
|
|
|
|
setup_useragent() {
|
|
local script=/usr/share/BastionGuard/data/scripts/enable-user-agents.sh
|
|
[[ -x "$script" || -f "$script" ]] && /bin/sh "$script" || echo "Script enable-user-agents.sh non presente, salto."
|
|
}
|
|
|
|
setup_banks() {
|
|
local user="$1"
|
|
update_banks "$user" || true
|
|
}
|
|
|
|
setup_services() {
|
|
local user="$1" u gui_summary
|
|
local system_units=(BastionGuard-phishing-scanner.service BastionGuard-phishing-updater.service BastionGuard-phishing-updater.timer Bastionguard-privhelper.service bastionguard-sanesecurity.service bastionguard-sanesecurity.timer BastionGuard-ransomware-realtime.service clamav-daemon.service clamav-freshclam.service clamav-clamonacc.service)
|
|
local user_units=(BastionGuard-ransomware-alert.service BastionGuard-ransomware-realtime-alert.service BastionGuard-ransomware-scanner.service BastionGuard-useragent.service BastionGuard-mailproxy.service)
|
|
for u in "${system_units[@]}"; do systemctl list-unit-files "$u" >/dev/null 2>&1 && systemctl enable --now "$u" || true; done
|
|
gui_summary="$(desktop_session_summary "$user" 2>/dev/null || true)"
|
|
for u in "${user_units[@]}"; do
|
|
if webui_disabled_unit "$u"; then
|
|
echo "webui-default: skipped $u enable/start because proxy/browser daemons are GTK/desktop-only"
|
|
continue
|
|
fi
|
|
if proxy_sensitive_unit "$u" && [[ "$gui_summary" == gui* ]]; then
|
|
echo "safe-desktop: skipped $u enable/start because GTK owns proxy while desktop is active ($gui_summary)"
|
|
continue
|
|
fi
|
|
run_as_user "$user" systemctl --user enable --now "$u" 2>/dev/null || true
|
|
done
|
|
}
|
|
|
|
wizard() {
|
|
local user="$1" http="$2" https="$3"; shift 3
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
valid_port "$http" || { echo "Porta HTTP non valida" >&2; exit 64; }
|
|
valid_port "$https" || { echo "Porta HTTPS non valida" >&2; exit 64; }
|
|
ensure_configs "$user"
|
|
# src/wizard on_finish(): run_net_setup() is executed before the selected privileged actions.
|
|
setup_net
|
|
# src/wizard worker_run(): ClamAV daemon and locale scripts are pre-steps before certs/web/blacklist.
|
|
setup_source_presteps
|
|
local flags=" $* "
|
|
[[ "$flags" == *' resolv '* ]] && setup_browser_policies
|
|
[[ "$flags" == *' dnsmasq '* ]] && setup_dnsmasq
|
|
[[ "$flags" == *' firewall '* ]] && setup_firewall "$user"
|
|
[[ "$flags" == *' nftables '* ]] && setup_nftables
|
|
[[ "$flags" == *' certs '* ]] && setup_certs
|
|
setup_phishing_blacklist "$user"
|
|
[[ "$flags" == *' webconf '* ]] && setup_webconf "$http" "$https"
|
|
[[ "$flags" == *' native '* ]] && setup_native "$user"
|
|
[[ "$flags" == *' useragent '* ]] && setup_useragent
|
|
[[ "$flags" == *' banks '* ]] && setup_banks "$user"
|
|
[[ "$flags" == *' services '* ]] && setup_services "$user"
|
|
# src/wizard worker_run(): always restart phishing scanner after blacklist/pre-step commands.
|
|
restart_phishing_scanner
|
|
echo "BastionGuard system wizard actions completed. config.json is managed by the WebPanel first-run wizard."
|
|
}
|
|
|
|
|
|
|
|
download_to_owned_file() {
|
|
local user="$1" url="$2" dest="$3" min_size="${4:-10}"
|
|
local uid gid dir tmp size
|
|
uid="$(user_uid "$user")"; gid="$(user_gid "$user")"; dir="$(dirname "$dest")"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$dir"
|
|
tmp="$(mktemp)"
|
|
if ! curl -fsSL --max-time 30 "$url" -o "$tmp"; then rm -f "$tmp"; echo "failed: $url"; return 1; fi
|
|
size="$(stat -c '%s' "$tmp" 2>/dev/null || echo 0)"
|
|
if (( size < min_size )); then rm -f "$tmp"; echo "failed-small: $url"; return 1; fi
|
|
install -o "$uid" -g "$gid" -m 0640 "$tmp" "$dest"
|
|
rm -f "$tmp"
|
|
echo "updated: $dest"
|
|
}
|
|
|
|
install_copy_system_data() {
|
|
local src="$1" dest="$2" dir
|
|
dir="$(dirname "$dest")"
|
|
if ! mkdir -p "$dir" 2>/dev/null; then echo "skipped: $dest (cannot create directory)"; return 0; fi
|
|
if install -o root -g root -m 0644 "$src" "$dest" 2>/tmp/bastionguard-webui-install.err; then
|
|
echo "installed: $dest"
|
|
else
|
|
local err; err="$(cat /tmp/bastionguard-webui-install.err 2>/dev/null || true)"
|
|
case "$err" in
|
|
*'Read-only file system'*) echo "skipped: $dest (read-only file system)" ;;
|
|
*'Permission denied'*) echo "skipped: $dest (permission denied)" ;;
|
|
*) echo "skipped: $dest (${err:-install failed})" ;;
|
|
esac
|
|
fi
|
|
}
|
|
|
|
update_yara() {
|
|
local user="$1" home base sysdir success=0 fail=0 f url dest
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
command -v curl >/dev/null 2>&1 || { echo "curl not found" >&2; exit 127; }
|
|
home="$(user_home "$user")"; base="$home/.local/share/BastionGuard/data/yara"; sysdir="/usr/share/BastionGuard/data/yara"
|
|
local files=(
|
|
ByteCode.MSIL.Ransomware.Apis.yara ByteCode.MSIL.Ransomware.Thanos.yara Linux.Ransomware.KillDisk.yara
|
|
Win32.Ransomware.WannaCry.yara Win32.Ransomware.LockBit.yara Win64.Ransomware.BlackBasta.yara
|
|
ByteCode.MSIL.Ransomware.ChupaCabra.yara ByteCode.MSIL.Ransomware.Cring.yara ByteCode.MSIL.Ransomware.Dusk.yara
|
|
ByteCode.MSIL.Ransomware.EAF.yara ByteCode.MSIL.Ransomware.Eternity.yara ByteCode.MSIL.Ransomware.Fantom.yara
|
|
ByteCode.MSIL.Ransomware.GhosTEncryptor.yara ByteCode.MSIL.Ransomware.Ghostbin.yara ByteCode.MSIL.Ransomware.GoodWill.yara
|
|
ByteCode.MSIL.Ransomware.HarpoonLocker.yara ByteCode.MSIL.Ransomware.Hog.yara ByteCode.MSIL.Ransomware.Invert.yara
|
|
ByteCode.MSIL.Ransomware.Janelle.yara ByteCode.MSIL.Ransomware.Khonsari.yara ByteCode.MSIL.Ransomware.McBurglar.yara
|
|
ByteCode.MSIL.Ransomware.Moisha.yara ByteCode.MSIL.Ransomware.Namaste.yara ByteCode.MSIL.Ransomware.Oct.yara
|
|
ByteCode.MSIL.Ransomware.Pacman.yara ByteCode.MSIL.Ransomware.PoliceRecords.yara ByteCode.MSIL.Ransomware.Povlsomware.yara
|
|
ByteCode.MSIL.Ransomware.Retis.yara ByteCode.MSIL.Ransomware.TaRRaK.yara ByteCode.MSIL.Ransomware.TimeCrypt.yara
|
|
ByteCode.MSIL.Ransomware.TimeTime.yara ByteCode.MSIL.Ransomware.Venom.yara ByteCode.MSIL.Ransomware.WildFire.yara
|
|
ByteCode.MSIL.Ransomware.WormLocker.yara ByteCode.MSIL.Ransomware.ZeroLocker.yara Bytecode.MSIL.Ransomware.CobraLocker.yara
|
|
Linux.Ransomware.GwisinLocker.yara Linux.Ransomware.Helldown.yara Linux.Ransomware.LuckyJoe.yara Linux.Ransomware.RedAlert.yara
|
|
Win32.Ransomware.Conti.yara Win32.Ransomware.BlackCat.yara Win32.Ransomware.Clop.yara Win32.Ransomware.Ryuk.yara
|
|
Win64.Ransomware.Cactus.yara Win64.Ransomware.Pandora.yara Win64.Ransomware.HermeticRansom.yara
|
|
)
|
|
echo "Updating YARA rules..."
|
|
for f in "${files[@]}"; do
|
|
url="https://raw.githubusercontent.com/reversinglabs/reversinglabs-yara-rules/develop/yara/ransomware/$f"
|
|
dest="$base/$f"
|
|
if download_to_owned_file "$user" "$url" "$dest" 100; then ((success++)); else ((fail++)); fi
|
|
done
|
|
if (( success > 0 )); then
|
|
for f in "$base"/*.yara; do [[ -f "$f" ]] && install_copy_system_data "$f" "$sysdir/$(basename "$f")"; done
|
|
fi
|
|
echo "YARA updated: success=$success fail=$fail user_dir=$base"
|
|
(( success > 0 )) || exit 1
|
|
}
|
|
|
|
update_sanesecurity() {
|
|
local user="$1" home base sysdir success=0 fail=0 db url dest
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
command -v curl >/dev/null 2>&1 || { echo "curl not found" >&2; exit 127; }
|
|
home="$(user_home "$user")"; base="$home/.local/share/BastionGuard/data/sanesecurity"; sysdir="/usr/share/BastionGuard/data/sanesecurity"
|
|
local dbs=(scamsigs.hdb scam.ndb phish.ndb rogue.hdb ransomware.ndb)
|
|
echo "Updating Sanesecurity DB..."
|
|
for db in "${dbs[@]}"; do
|
|
url="https://sanesecurity.com/clamav/$db"; dest="$base/$db"
|
|
if download_to_owned_file "$user" "$url" "$dest" 100; then ((success++)); install_copy_system_data "$dest" "$sysdir/$db"; else ((fail++)); fi
|
|
done
|
|
echo "Sanesecurity updated: success=$success fail=$fail user_dir=$base"
|
|
(( success > 0 )) || exit 1
|
|
}
|
|
|
|
update_phishing() {
|
|
local user="$1" home uid gid base tmp final reduce sysdir src count=0
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
command -v curl >/dev/null 2>&1 || { echo "curl not found" >&2; exit 127; }
|
|
home="$(user_home "$user")"; uid="$(user_uid "$user")"; gid="$(user_gid "$user")"
|
|
base="$home/.local/share/BastionGuard/data/phishing"; sysdir="/usr/share/BastionGuard/data/phishing"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$base"
|
|
tmp="$(mktemp)"; final="$(mktemp)"; reduce="$(mktemp)"
|
|
trap 'rm -f "$tmp" "$final" "$reduce"' RETURN
|
|
local sources=(
|
|
https://phishing.army/download/phishing_army_blocklist.txt
|
|
https://openphish.com/feed.txt
|
|
https://urlhaus.abuse.ch/downloads/text/
|
|
)
|
|
echo "Updating phishing blacklist..."
|
|
: > "$tmp"
|
|
for src in "${sources[@]}"; do
|
|
echo "source: $src"
|
|
curl -fsSL --max-time 45 "$src" >> "$tmp" || echo "warning: failed $src"
|
|
printf '\n' >> "$tmp"
|
|
done
|
|
sed -E 's/#.*$//; s/[[:space:]]+$//; s/^[[:space:]]+//' "$tmp" | awk 'length($0)>0' | sort -u > "$final"
|
|
awk 'length($0)>0 && $0 !~ /^[0-9.]+$/ && $0 !~ /:/ {print}' "$final" | sort -u > "$reduce"
|
|
count="$(wc -l < "$final" | tr -d ' ')"
|
|
if (( count < 10 )); then echo "Downloaded blacklist is empty or too small" >&2; exit 1; fi
|
|
install -o "$uid" -g "$gid" -m 0640 "$final" "$base/blacklist.txt"
|
|
install -o "$uid" -g "$gid" -m 0640 "$reduce" "$base/blacklist-reduce.txt"
|
|
install_copy_system_data "$final" "$sysdir/blacklist.txt"
|
|
install_copy_system_data "$reduce" "$sysdir/blacklist-reduce.txt"
|
|
echo "Phishing blacklist updated: $count entries user_dir=$base"
|
|
}
|
|
|
|
|
|
read_version() {
|
|
local path=/usr/share/BastionGuard/data/version.bs
|
|
if [[ -r "$path" && -f "$path" ]]; then
|
|
cat "$path"
|
|
else
|
|
echo "version=?.?"
|
|
echo "build=?"
|
|
fi
|
|
}
|
|
|
|
valid_data_key() {
|
|
case "${1:-}" in
|
|
banks) return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
data_file_path() {
|
|
local key="$1" user="$2" home
|
|
valid_data_key "$key" || { echo "Invalid data file key: $key" >&2; exit 64; }
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
home="$(user_home "$user")"
|
|
case "$key" in
|
|
banks) printf '%s\n' "$home/.local/share/BastionGuard/banks.json" ;;
|
|
esac
|
|
}
|
|
|
|
read_data_file_b64() {
|
|
local key="$1" user="$2" path
|
|
path="$(data_file_path "$key" "$user")"
|
|
[[ -e "$path" ]] || { echo "MISSING $path" >&2; exit 66; }
|
|
[[ -f "$path" ]] || { echo "NOTFILE $path" >&2; exit 65; }
|
|
[[ -r "$path" ]] || { echo "UNREADABLE $path" >&2; exit 13; }
|
|
base64 -w0 "$path"
|
|
}
|
|
|
|
update_banks() {
|
|
local user="$1" url="${2:-https://raw.githubusercontent.com/MISP/misp-warninglists/main/lists/bank-website/list.json}"
|
|
local home uid gid out tmp size
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
[[ "$url" =~ ^https:// ]] || { echo "Invalid bank-list URL. HTTPS only." >&2; exit 64; }
|
|
command -v curl >/dev/null 2>&1 || { echo "curl not found" >&2; exit 127; }
|
|
home="$(user_home "$user")"; uid="$(user_uid "$user")"; gid="$(user_gid "$user")"
|
|
out="$home/.local/share/BastionGuard/banks.json"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$(dirname "$out")"
|
|
tmp="$(mktemp)"
|
|
if ! curl -fsSL --max-time 60 "$url" -o "$tmp"; then rm -f "$tmp"; echo "failed: $url" >&2; exit 1; fi
|
|
size="$(stat -c '%s' "$tmp" 2>/dev/null || echo 0)"
|
|
if (( size < 100 )); then rm -f "$tmp"; echo "downloaded bank list is empty or too small" >&2; exit 1; fi
|
|
install -o "$uid" -g "$gid" -m 0640 "$tmp" "$out"
|
|
rm -f "$tmp"
|
|
echo "updated: $out"
|
|
}
|
|
|
|
system_file_path_for_key() {
|
|
local key="$1"
|
|
case "$key" in
|
|
webports) printf '%s\n' /etc/BastionGuard/webports.conf ;;
|
|
dnsmasq_bastionguard) printf '%s\n' /etc/dnsmasq.d/BastionGuard.conf ;;
|
|
firefox_policy) printf '%s\n' /usr/lib/firefox/distribution/policies.json ;;
|
|
chromium_policy) printf '%s\n' /etc/chromium/policies/managed/BastionGuard-dns.json ;;
|
|
chrome_policy) printf '%s\n' /etc/opt/chrome/policies/managed/BastionGuard-dns.json ;;
|
|
edge_policy) printf '%s\n' /etc/opt/edge/policies/managed/BastionGuard-dns.json ;;
|
|
phishing_custom) printf '%s\n' /usr/share/BastionGuard/data/phishing/blacklist_custom.txt ;;
|
|
dnsmasq_custom_blacklist) printf '%s\n' /etc/dnsmasq.d/BastionGuard-custom-blacklist.conf ;;
|
|
nginx_webui) printf '%s\n' /etc/nginx/conf.d/BastionGuard-webui.conf ;;
|
|
nftables_bastionguard) printf '%s\n' /etc/nftables.d/BastionGuard.nft ;;
|
|
*) echo "System file key not allowed: $key" >&2; exit 64 ;;
|
|
esac
|
|
}
|
|
|
|
inotify_start_saved() {
|
|
local user="$1" uid gid saved tmp
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
uid="$(id -u "$user")"; gid="$(id -g "$user")"
|
|
saved="$(inotify_saved_paths_file "$user")"
|
|
tmp="$(mktemp)"
|
|
if [[ -s "$saved" ]]; then cat "$saved" > "$tmp"; else inotify_default_paths "$user" > "$tmp"; fi
|
|
[[ -s "$tmp" ]] || { echo "No valid realtime paths configured. Add paths from the WebPanel Scan page." >&2; rm -f "$tmp"; exit 64; }
|
|
chown "$uid:$gid" "$tmp" 2>/dev/null || true
|
|
inotify_start "$user" "$(base64 -w0 < "$tmp")"
|
|
rm -f "$tmp"
|
|
}
|
|
|
|
write_system_file_b64() {
|
|
local key="$1" b64="$2" path content
|
|
path="$(system_file_path_for_key "$key")"
|
|
content="$(printf '%s' "$b64" | base64 -d)" || { echo "Invalid base64" >&2; exit 64; }
|
|
write_system_file "$path" 0644 "$content"
|
|
}
|
|
|
|
phish_auto_update() {
|
|
local action="$1"
|
|
case "$action" in
|
|
enable) systemctl enable --now BastionGuard-phishing-updater.timer ;;
|
|
disable) systemctl disable --now BastionGuard-phishing-updater.timer ;;
|
|
*) echo "Invalid phish-auto-update action" >&2; exit 64 ;;
|
|
esac
|
|
}
|
|
|
|
|
|
|
|
|
|
backup_fmt_bytes() {
|
|
local bytes="$1"
|
|
if [[ ! "$bytes" =~ ^[0-9]+$ ]]; then echo "?"; return 0; fi
|
|
if (( bytes >= 1073741824 )); then awk -v b="$bytes" 'BEGIN{printf "%.1f GB", b/1073741824}';
|
|
elif (( bytes >= 1048576 )); then awk -v b="$bytes" 'BEGIN{printf "%.0f MB", b/1048576}';
|
|
else awk -v b="$bytes" 'BEGIN{printf "%.0f KB", b/1024}'; fi
|
|
}
|
|
|
|
backup_bin_path() {
|
|
local bin=""
|
|
bin="$(command -v bastionguard-backup 2>/dev/null || true)"
|
|
[[ -n "$bin" ]] || bin="/usr/bin/bastionguard-backup"
|
|
printf '%s' "$bin"
|
|
}
|
|
|
|
backup_prepare_root_runtime() {
|
|
# The WebUI helper is expected to run through sudo as root. Prepare the same
|
|
# runtime shape expected by the native backup tool, but do not fail if a
|
|
# container/chroot has a read-only /dev or no device-mapper support.
|
|
export HOME=/root
|
|
export USER=root
|
|
export LOGNAME=root
|
|
export PATH=/usr/sbin:/usr/bin:/sbin:/bin:${PATH:-}
|
|
unset DBUS_SESSION_BUS_ADDRESS XDG_RUNTIME_DIR WAYLAND_DISPLAY DISPLAY
|
|
mkdir -p /run/bastionguard-backup 2>/dev/null || true
|
|
mkdir -p /dev/mapper 2>/dev/null || true
|
|
}
|
|
|
|
backup_cli() {
|
|
local bin
|
|
backup_prepare_root_runtime
|
|
bin="$(backup_bin_path)"
|
|
[[ -x "$bin" ]] || { echo "Missing BastionGuard Backup CLI: $bin" >&2; exit 66; }
|
|
"$bin" "$@"
|
|
}
|
|
|
|
|
|
backup_read_json_key() {
|
|
local file="$1" key="$2"
|
|
[[ -r "$file" ]] || return 1
|
|
python3 - "$file" "$key" <<'PYJSONKEY' 2>/dev/null || true
|
|
import json, sys
|
|
try:
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as fh:
|
|
data = json.load(fh)
|
|
value = data.get(sys.argv[2], '')
|
|
if isinstance(value, bool):
|
|
print('true' if value else 'false')
|
|
elif value is None:
|
|
print('')
|
|
else:
|
|
print(str(value))
|
|
except Exception:
|
|
pass
|
|
PYJSONKEY
|
|
}
|
|
|
|
backup_resolve_device() {
|
|
local value="${1:-}" dev=""
|
|
value="${value//[$'\r\n\t ']/}"
|
|
[[ -n "$value" ]] || return 1
|
|
if [[ "$value" == /dev/* && -b "$value" ]]; then printf '%s\n' "$value"; return 0; fi
|
|
if [[ "$value" == /dev/disk/by-uuid/* && -e "$value" ]]; then readlink -f -- "$value"; return 0; fi
|
|
if [[ -e "/dev/disk/by-uuid/$value" ]]; then readlink -f -- "/dev/disk/by-uuid/$value"; return 0; fi
|
|
if command -v blkid >/dev/null 2>&1; then
|
|
dev="$(blkid -U "$value" 2>/dev/null || true)"
|
|
[[ -n "$dev" && -b "$dev" ]] && { printf '%s\n' "$dev"; return 0; }
|
|
fi
|
|
if command -v lsblk >/dev/null 2>&1; then
|
|
dev="$(lsblk -nrpo NAME,UUID 2>/dev/null | awk -v u="$value" '$2==u{print $1; exit}')"
|
|
[[ -n "$dev" && -b "$dev" ]] && { printf '%s\n' "$dev"; return 0; }
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
backup_device_arg_if_block() {
|
|
local value="${1:-}" dev=""
|
|
[[ -n "$value" ]] || return 1
|
|
dev="$(backup_resolve_device "$value" 2>/dev/null || true)"
|
|
[[ -n "$dev" && -b "$dev" ]] || return 1
|
|
printf '%s\n' "$dev"
|
|
}
|
|
|
|
backup_configured_device_value() {
|
|
local cfg
|
|
cfg="$(backup_config_path)"
|
|
backup_read_json_key "$cfg" backup_device_uuid
|
|
}
|
|
|
|
backup_configured_mode() {
|
|
local cfg v
|
|
cfg="$(backup_config_path)"
|
|
v="$(backup_read_json_key "$cfg" btrfs_mode)"
|
|
case "${v,,}" in true|1|yes|on) echo btrfs ;; *) echo rsync ;; esac
|
|
}
|
|
|
|
backup_find_mount_for_source() {
|
|
local source="${1:-}" target=""
|
|
[[ -n "$source" ]] || return 1
|
|
if command -v findmnt >/dev/null 2>&1; then
|
|
target="$(findmnt -rn -S "$source" -o TARGET 2>/dev/null | head -n1 || true)"
|
|
[[ -n "$target" ]] && { printf '%s\n' "$target"; return 0; }
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
backup_mount_ro_device() {
|
|
local dev="$1" mp=""
|
|
[[ -b "$dev" ]] || return 1
|
|
mp="$(backup_find_mount_for_source "$dev" 2>/dev/null || true)"
|
|
if [[ -n "$mp" && -d "$mp" ]]; then printf '%s\n' "$mp"; return 0; fi
|
|
mp="/run/bastionguard-backup/webui-list-$$-$(basename "$dev")"
|
|
mkdir -p "$mp" 2>/dev/null || return 1
|
|
if mount -o ro "$dev" "$mp" 2>/dev/null; then
|
|
printf '%s\n' "$mp"
|
|
return 0
|
|
fi
|
|
rmdir "$mp" 2>/dev/null || true
|
|
return 1
|
|
}
|
|
|
|
backup_snapshot_emit_dir() {
|
|
local snap="$1" repo="$2" mode="$3" name path_b64 repo_b64 created tags comments comments_b64 type
|
|
[[ -d "$snap" ]] || return 0
|
|
name="$(basename -- "$snap")"
|
|
created=""; tags=""; comments=""; type="$mode"
|
|
if [[ -r "$snap/info.json" ]]; then
|
|
# Output: created<TAB>tags<TAB>comments<TAB>type. Comments are base64 encoded after this block.
|
|
IFS=$'\t' read -r created tags comments type < <(python3 - "$snap/info.json" <<'PYSNAP' 2>/dev/null || true
|
|
import json, sys
|
|
try:
|
|
with open(sys.argv[1], 'r', encoding='utf-8') as fh:
|
|
data = json.load(fh)
|
|
print('\t'.join(str(data.get(k, '')) for k in ('created','tags','comments','type')))
|
|
except Exception:
|
|
pass
|
|
PYSNAP
|
|
)
|
|
fi
|
|
[[ -n "$created" ]] || created="$(stat -c '%Y' "$snap" 2>/dev/null || echo 0)"
|
|
[[ -n "$type" ]] || type="$mode"
|
|
path_b64="$(printf '%s' "$snap" | base64 -w0)"
|
|
repo_b64="$(printf '%s' "$repo" | base64 -w0)"
|
|
comments_b64="$(printf '%s' "$comments" | base64 -w0)"
|
|
printf 'SNAP\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' "$name" "$type" "$path_b64" "${created:-0}" "$tags" "$comments_b64" "$repo_b64"
|
|
}
|
|
|
|
backup_snapshot_scan_root() {
|
|
local root="$1" repo mode snapdir snap
|
|
[[ -d "$root" ]] || return 0
|
|
for repo in \
|
|
"$root/bastionguard-backup/snapshots" \
|
|
"$root/timeshift/snapshots" \
|
|
"$root/bastionguard-backup-btrfs/snapshots" \
|
|
"$root/timeshift-btrfs/snapshots"; do
|
|
[[ -d "$repo" ]] || continue
|
|
mode="rsync"
|
|
case "$repo" in *btrfs*) mode="btrfs" ;; esac
|
|
find "$repo" -mindepth 1 -maxdepth 1 -type d -print0 2>/dev/null | sort -z | while IFS= read -r -d '' snap; do
|
|
[[ "$(basename -- "$snap")" == ".sync" ]] && continue
|
|
backup_snapshot_emit_dir "$snap" "$repo" "$mode"
|
|
done
|
|
done
|
|
}
|
|
|
|
backup_snapshot_index() {
|
|
backup_prepare_root_runtime
|
|
local cfg_value dev mp tmp_mounted="" root
|
|
declare -A seen=()
|
|
cfg_value="$(backup_configured_device_value 2>/dev/null || true)"
|
|
if [[ -n "$cfg_value" ]]; then
|
|
dev="$(backup_resolve_device "$cfg_value" 2>/dev/null || true)"
|
|
if [[ -n "$dev" ]]; then
|
|
mp="$(backup_mount_ro_device "$dev" 2>/dev/null || true)"
|
|
if [[ -n "$mp" ]]; then
|
|
backup_snapshot_scan_root "$mp"
|
|
case "$mp" in /run/bastionguard-backup/webui-list-*) tmp_mounted="$mp" ;; esac
|
|
else
|
|
printf 'WARN\t%s\n' "Configured backup device is present but could not be mounted read-only: $dev"
|
|
fi
|
|
else
|
|
printf 'WARN\t%s\n' "Configured backup device was not found: $cfg_value"
|
|
fi
|
|
fi
|
|
|
|
# Scan already-mounted filesystems and common removable/media roots. This is
|
|
# deliberately independent from the native CLI so the WebUI can show existing
|
|
# snapshots even when the configured device is full or cannot be mounted by
|
|
# the CLI for creation/restore.
|
|
{
|
|
command -v findmnt >/dev/null 2>&1 && findmnt -rn -o TARGET 2>/dev/null || true
|
|
printf '%s\n' / /mnt /media /run/media /run/bastionguard-backup
|
|
find /mnt /media /run/media /run/bastionguard-backup -mindepth 1 -maxdepth 3 -type d 2>/dev/null || true
|
|
} | while IFS= read -r root; do
|
|
[[ -n "$root" && -d "$root" ]] || continue
|
|
case "$root" in /proc*|/sys*|/dev*|/run/user*|/tmp*) continue ;; esac
|
|
# avoid scanning the temporary mount twice through /run/bastionguard-backup
|
|
if [[ -n "$tmp_mounted" && "$root" == "$tmp_mounted" ]]; then continue; fi
|
|
backup_snapshot_scan_root "$root"
|
|
done | awk '!seen[$0]++'
|
|
|
|
if [[ -n "$tmp_mounted" ]]; then
|
|
umount "$tmp_mounted" 2>/dev/null || true
|
|
rmdir "$tmp_mounted" 2>/dev/null || true
|
|
fi
|
|
}
|
|
|
|
backup_config_path() { printf '%s' "/etc/bastionguard-backup/bastionguard-backup.json"; }
|
|
|
|
backup_default_config() {
|
|
cat <<'JSONDEFAULT'
|
|
{
|
|
"backup_device_uuid": "",
|
|
"parent_device_uuid": "",
|
|
"do_first_run": "false",
|
|
"btrfs_mode": "false",
|
|
"include_btrfs_home_for_backup": "false",
|
|
"include_btrfs_home_for_restore": "false",
|
|
"stop_cron_emails": "true",
|
|
"schedule_monthly": "false",
|
|
"schedule_weekly": "false",
|
|
"schedule_daily": "true",
|
|
"schedule_hourly": "false",
|
|
"schedule_boot": "false",
|
|
"count_monthly": "2",
|
|
"count_weekly": "3",
|
|
"count_daily": "5",
|
|
"count_hourly": "6",
|
|
"count_boot": "5",
|
|
"snapshot_size": "0",
|
|
"snapshot_count": "0",
|
|
"date_format": "%Y-%m-%d %H:%M:%S",
|
|
"exclude": [],
|
|
"exclude-apps": []
|
|
}
|
|
JSONDEFAULT
|
|
}
|
|
|
|
backup_config_read() {
|
|
local cfg
|
|
cfg="$(backup_config_path)"
|
|
if [[ -f "$cfg" ]]; then cat "$cfg"; else backup_default_config; fi
|
|
}
|
|
|
|
backup_config_save_b64() {
|
|
local b64="$1" cfg tmp dir
|
|
cfg="$(backup_config_path)"
|
|
dir="$(dirname "$cfg")"
|
|
install -d -o root -g root -m 0755 "$dir"
|
|
tmp="$(mktemp)"
|
|
printf '%s' "$b64" | base64 -d > "$tmp" || { rm -f "$tmp"; echo "Invalid base64 config" >&2; exit 64; }
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
python3 - "$tmp" <<'PYCFG' || { rm -f "$tmp"; echo "Invalid JSON config" >&2; exit 64; }
|
|
import json, sys
|
|
path = sys.argv[1]
|
|
with open(path, 'r', encoding='utf-8') as fh:
|
|
data = json.load(fh)
|
|
if not isinstance(data, dict):
|
|
raise SystemExit(1)
|
|
allowed = {
|
|
'backup_device_uuid','parent_device_uuid','do_first_run','btrfs_mode','include_btrfs_home','include_btrfs_home_for_backup','include_btrfs_home_for_restore','stop_cron_emails',
|
|
'schedule_monthly','schedule_weekly','schedule_daily','schedule_hourly','schedule_boot','count_monthly','count_weekly','count_daily','count_hourly','count_boot',
|
|
'snapshot_size','snapshot_count','date_format','exclude','exclude-apps'
|
|
}
|
|
for key in list(data.keys()):
|
|
if key not in allowed:
|
|
data.pop(key, None)
|
|
if not isinstance(data.get('exclude', []), list):
|
|
data['exclude'] = []
|
|
if not isinstance(data.get('exclude-apps', []), list):
|
|
data['exclude-apps'] = []
|
|
with open(path, 'w', encoding='utf-8') as fh:
|
|
json.dump(data, fh, ensure_ascii=False, indent=2)
|
|
fh.write('
|
|
')
|
|
PYCFG
|
|
fi
|
|
install -o root -g root -m 0644 "$tmp" "$cfg"
|
|
rm -f "$tmp"
|
|
echo "BastionGuard Backup config saved: $cfg"
|
|
}
|
|
|
|
backup_status() {
|
|
local squash="/boot/bastionguard-recovery/live/filesystem.squashfs"
|
|
local kernel="/boot/bastionguard-recovery/vmlinuz"
|
|
local grub="/etc/grub.d/42_bastionguard-recovery"
|
|
local check_path="/boot" free="0" size="0" bin cfg
|
|
[[ -d "$check_path" ]] || check_path="/"
|
|
free="$(df -PB1 "$check_path" 2>/dev/null | awk 'NR==2{print $4}' || echo 0)"
|
|
[[ -f "$squash" ]] && size="$(stat -c '%s' "$squash" 2>/dev/null || echo 0)"
|
|
bin="$(backup_bin_path)"
|
|
cfg="$(backup_config_path)"
|
|
printf 'RECOVERY_DIR %s
|
|
' "/boot/bastionguard-recovery"
|
|
printf 'SQUASHFS %s %s %s
|
|
' "$squash" "$( [[ -f "$squash" ]] && echo 1 || echo 0 )" "$(backup_fmt_bytes "$size")"
|
|
printf 'KERNEL %s %s
|
|
' "$kernel" "$( [[ -f "$kernel" ]] && echo 1 || echo 0 )"
|
|
printf 'GRUB %s %s
|
|
' "$grub" "$( [[ -f "$grub" ]] && echo 1 || echo 0 )"
|
|
printf 'SPACE %s %s %s
|
|
' "$check_path" "${free:-0}" "$(backup_fmt_bytes "${free:-0}")"
|
|
printf 'CLI %s %s
|
|
' "$bin" "$( [[ -x "$bin" ]] && echo 1 || echo 0 )"
|
|
printf 'CONFIG %s %s
|
|
' "$cfg" "$( [[ -f "$cfg" ]] && echo 1 || echo 0 )"
|
|
}
|
|
|
|
backup_devices_tsv() {
|
|
backup_prepare_root_runtime
|
|
if command -v lsblk >/dev/null 2>&1 && command -v python3 >/dev/null 2>&1; then
|
|
python3 - <<'PYDEVS'
|
|
import json, os, subprocess, sys
|
|
|
|
COLUMN_SETS = [
|
|
'NAME,KNAME,PATH,TYPE,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT,TRAN,RM,RO,PKNAME,MODEL,VENDOR,HOTPLUG',
|
|
'NAME,KNAME,PATH,TYPE,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT,TRAN,RM,RO,PKNAME,MODEL,VENDOR',
|
|
'NAME,KNAME,PATH,TYPE,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT,RM,RO,PKNAME',
|
|
'NAME,TYPE,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT',
|
|
]
|
|
|
|
def run_lsblk():
|
|
for cols in COLUMN_SETS:
|
|
try:
|
|
out = subprocess.check_output(['lsblk', '-J', '-o', cols], stderr=subprocess.DEVNULL, text=True)
|
|
data = json.loads(out)
|
|
return data.get('blockdevices', []) or []
|
|
except Exception:
|
|
continue
|
|
return []
|
|
|
|
def read_file(path):
|
|
try:
|
|
with open(path, 'r', encoding='utf-8', errors='ignore') as fh:
|
|
return fh.read().strip()
|
|
except Exception:
|
|
return ''
|
|
|
|
def boolish(value):
|
|
if isinstance(value, bool):
|
|
return value
|
|
if value is None:
|
|
return False
|
|
return str(value).strip().lower() in ('1', 'true', 'yes', 'on')
|
|
|
|
def kname_of(dev):
|
|
return (dev.get('kname') or dev.get('name') or '').replace('/dev/', '').strip()
|
|
|
|
def dev_path(dev):
|
|
path = (dev.get('path') or '').strip()
|
|
if path:
|
|
return path
|
|
name = (dev.get('name') or '').strip()
|
|
if name.startswith('/dev/'):
|
|
return name
|
|
if name:
|
|
return '/dev/' + name
|
|
return ''
|
|
|
|
def sys_parent_disk(kname):
|
|
if not kname:
|
|
return ''
|
|
base = os.path.basename(kname)
|
|
# /sys/class/block/sde/sde1/partition exists for partitions; PKNAME is better,
|
|
# but this fallback keeps ancient lsblk output usable.
|
|
parent = os.path.realpath(f'/sys/class/block/{base}')
|
|
cur = parent
|
|
for _ in range(8):
|
|
if os.path.exists(os.path.join(cur, 'removable')):
|
|
return os.path.basename(cur)
|
|
nxt = os.path.dirname(cur)
|
|
if nxt == cur:
|
|
break
|
|
cur = nxt
|
|
return base
|
|
|
|
def sys_is_usb(kname, pkname=''):
|
|
names = [x for x in (kname, pkname, sys_parent_disk(kname)) if x]
|
|
for name in names:
|
|
rp = os.path.realpath(f'/sys/class/block/{os.path.basename(name)}')
|
|
if '/usb' in rp.lower():
|
|
return True
|
|
cur = rp
|
|
for _ in range(12):
|
|
modalias = read_file(os.path.join(cur, 'modalias')).lower()
|
|
if modalias.startswith('usb:') or 'usb' in modalias:
|
|
return True
|
|
nxt = os.path.dirname(cur)
|
|
if nxt == cur:
|
|
break
|
|
cur = nxt
|
|
return False
|
|
|
|
def sys_removable(kname, pkname=''):
|
|
for name in (pkname, sys_parent_disk(kname), kname):
|
|
if not name:
|
|
continue
|
|
v = read_file(f'/sys/class/block/{os.path.basename(name)}/removable')
|
|
if v == '1':
|
|
return True
|
|
return False
|
|
|
|
def flatten(devs, parent=None, inherited=None):
|
|
inherited = dict(inherited or {})
|
|
rows = []
|
|
for dev in devs:
|
|
kname = kname_of(dev)
|
|
path = dev_path(dev)
|
|
typ = (dev.get('type') or '').strip()
|
|
pkname = (dev.get('pkname') or inherited.get('kname') or '').replace('/dev/', '').strip()
|
|
tran = (dev.get('tran') or inherited.get('tran') or '').strip()
|
|
removable = boolish(dev.get('rm')) or boolish(dev.get('hotplug')) or boolish(inherited.get('rm')) or sys_removable(kname, pkname)
|
|
usb = (tran.lower() == 'usb') or boolish(dev.get('hotplug')) or boolish(inherited.get('usb')) or sys_is_usb(kname, pkname)
|
|
row = {
|
|
'path': path,
|
|
'type': typ,
|
|
'fstype': (dev.get('fstype') or '').strip(),
|
|
'size': str(dev.get('size') or '').strip(),
|
|
'label': str(dev.get('label') or '').strip(),
|
|
'uuid': str(dev.get('uuid') or '').strip(),
|
|
'mountpoint': str(dev.get('mountpoint') or '').strip(),
|
|
'tran': tran,
|
|
'rm': '1' if removable else '0',
|
|
'ro': '1' if boolish(dev.get('ro')) else '0',
|
|
'model': str(dev.get('model') or inherited.get('model') or '').strip(),
|
|
'vendor': str(dev.get('vendor') or inherited.get('vendor') or '').strip(),
|
|
'hotplug': '1' if boolish(dev.get('hotplug')) else '0',
|
|
'parent': ('/dev/' + pkname) if pkname and not pkname.startswith('/dev/') else pkname,
|
|
'usb': '1' if usb else '0',
|
|
}
|
|
rows.append(row)
|
|
child_inherited = {
|
|
'kname': kname,
|
|
'tran': tran,
|
|
'rm': '1' if removable else '0',
|
|
'usb': '1' if usb else '0',
|
|
'model': row['model'],
|
|
'vendor': row['vendor'],
|
|
}
|
|
rows.extend(flatten(dev.get('children') or [], dev, child_inherited))
|
|
return rows
|
|
|
|
linux_fs = {
|
|
'ext2','ext3','ext4','xfs','btrfs','f2fs','jfs','reiserfs','nilfs2','bcachefs',
|
|
'crypto_luks','LVM2_member','linux_raid_member'
|
|
}
|
|
flat_devices = flatten(run_lsblk())
|
|
if not flat_devices:
|
|
# Last-resort fallback: blkid can still report partitions in restricted
|
|
# environments where lsblk cannot read sysfs. Metadata is limited, but the
|
|
# WebUI can still offer a selectable device instead of an empty list.
|
|
try:
|
|
out = subprocess.check_output(['blkid', '-o', 'export'], stderr=subprocess.DEVNULL, text=True)
|
|
item = {}
|
|
for line in out.splitlines() + ['']:
|
|
if not line.strip():
|
|
if item.get('DEVNAME'):
|
|
path = item.get('DEVNAME', '')
|
|
kname = os.path.basename(path)
|
|
flat_devices.append({
|
|
'path': path, 'type': 'part', 'fstype': item.get('TYPE', ''), 'size': '',
|
|
'label': item.get('LABEL', ''), 'uuid': item.get('UUID', ''), 'mountpoint': '',
|
|
'tran': '', 'rm': '1' if sys_removable(kname) else '0', 'ro': '0',
|
|
'model': '', 'vendor': '', 'hotplug': '0', 'parent': '',
|
|
'usb': '1' if sys_is_usb(kname) else '0',
|
|
})
|
|
item = {}
|
|
continue
|
|
if '=' in line:
|
|
k, v = line.split('=', 1)
|
|
item[k] = v
|
|
except Exception:
|
|
pass
|
|
|
|
rows = []
|
|
seen = set()
|
|
for r in flat_devices:
|
|
path = r['path']
|
|
if not path or path in seen or not os.path.exists(path):
|
|
continue
|
|
seen.add(path)
|
|
typ = r['type']
|
|
if typ in ('loop','rom') or path.startswith('/dev/loop') or path.startswith('/dev/sr'):
|
|
continue
|
|
fstype = r['fstype']
|
|
# Keep disks, partitions and mapper/lvm/crypt nodes. The UI can select USB
|
|
# partitions for backup and disk nodes for GRUB/restore targets.
|
|
if typ not in ('disk','part','crypt','lvm','raid','md'):
|
|
continue
|
|
eligible = '1' if (fstype in linux_fs or fstype.startswith('ext') or fstype == 'btrfs' or typ == 'disk') else '0'
|
|
if r['ro'] == '1':
|
|
reason = 'read-only'
|
|
elif eligible == '1':
|
|
reason = 'ok'
|
|
elif fstype:
|
|
reason = 'non-linux-fs'
|
|
else:
|
|
reason = 'no-filesystem'
|
|
print('\t'.join([
|
|
r['path'], r['type'], r['fstype'], r['size'], r['label'], r['uuid'], r['mountpoint'],
|
|
r['tran'], r['rm'], r['ro'], r['model'], r['vendor'], r['hotplug'], r['parent'], r['usb'], eligible, reason
|
|
]))
|
|
PYDEVS
|
|
return 0
|
|
fi
|
|
|
|
if ! command -v lsblk >/dev/null 2>&1; then return 0; fi
|
|
# Fallback for very small systems without python3/lsblk JSON.
|
|
lsblk -nrPpo NAME,TYPE,FSTYPE,SIZE,LABEL,UUID,MOUNTPOINT 2>/dev/null | while IFS= read -r line; do
|
|
local NAME="" TYPE="" FSTYPE="" SIZE="" LABEL="" UUID="" MOUNTPOINT=""
|
|
eval "$line" 2>/dev/null || true
|
|
[[ -n "${NAME:-}" ]] || continue
|
|
case "${TYPE:-}" in loop|rom) continue ;; esac
|
|
[[ -e "$NAME" ]] || continue
|
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
"$NAME" "${TYPE:-}" "${FSTYPE:-}" "${SIZE:-}" "${LABEL:-}" "${UUID:-}" "${MOUNTPOINT:-}" \
|
|
"" "0" "0" "" "" "0" "" "0" "1" "ok"
|
|
done
|
|
}
|
|
backup_list_snapshots() {
|
|
local mode="${1:-config}" device_b64="${2:-}" raw_device="" device=""; local -a args
|
|
[[ -n "$device_b64" ]] && raw_device="$(printf '%s' "$device_b64" | base64 -d 2>/dev/null || true)"
|
|
args=(--list-snapshots)
|
|
case "$mode" in rsync) args+=(--rsync);; btrfs) args+=(--btrfs);; esac
|
|
# The native CLI expects /dev/xxx for --snapshot-device, not a UUID. If the
|
|
# WebUI/config contains a UUID, resolve it before passing it. If it cannot be
|
|
# resolved, omit the override and let the native config path handle/report it.
|
|
device="$(backup_device_arg_if_block "$raw_device" 2>/dev/null || true)"
|
|
[[ -n "$device" ]] && args+=(--snapshot-device "$device")
|
|
backup_cli "${args[@]}"
|
|
}
|
|
|
|
backup_create_snapshot() {
|
|
local comment_b64="$1" tags="${2:-O}" mode="${3:-config}" device_b64="${4:-}" comment raw_device device; local -a args
|
|
comment="$(printf '%s' "$comment_b64" | base64 -d 2>/dev/null || true)"
|
|
raw_device="$(printf '%s' "$device_b64" | base64 -d 2>/dev/null || true)"
|
|
device="$(backup_device_arg_if_block "$raw_device" 2>/dev/null || true)"
|
|
[[ "$tags" =~ ^[OBHDWM,]+$ ]] || tags="O"
|
|
args=(--create --scripted --yes --comments "$comment" --tags "$tags")
|
|
case "$mode" in rsync) args+=(--rsync);; btrfs) args+=(--btrfs);; esac
|
|
[[ -n "$device" ]] && args+=(--snapshot-device "$device")
|
|
backup_cli "${args[@]}"
|
|
}
|
|
|
|
backup_delete_snapshot() {
|
|
local snap_b64="$1" device_b64="${2:-}" snap raw_device device; local -a args
|
|
snap="$(printf '%s' "$snap_b64" | base64 -d 2>/dev/null || true)"
|
|
raw_device="$(printf '%s' "$device_b64" | base64 -d 2>/dev/null || true)"
|
|
device="$(backup_device_arg_if_block "$raw_device" 2>/dev/null || true)"
|
|
[[ -n "$snap" ]] || { echo "Snapshot name is required" >&2; exit 64; }
|
|
args=(--delete --scripted --yes --snapshot "$snap")
|
|
[[ -n "$device" ]] && args+=(--snapshot-device "$device")
|
|
backup_cli "${args[@]}"
|
|
}
|
|
|
|
backup_delete_all() {
|
|
local device_b64="${1:-}" raw_device device; local -a args
|
|
raw_device="$(printf '%s' "$device_b64" | base64 -d 2>/dev/null || true)"
|
|
device="$(backup_device_arg_if_block "$raw_device" 2>/dev/null || true)"
|
|
args=(--delete-all --scripted --yes)
|
|
[[ -n "$device" ]] && args+=(--snapshot-device "$device")
|
|
backup_cli "${args[@]}"
|
|
}
|
|
|
|
backup_restore_snapshot() {
|
|
local snap_b64="$1" target_b64="$2" grub_b64="${3:-}" skip_grub="${4:-0}" device_b64="${5:-}"
|
|
local snap raw_target target raw_grub grub raw_device device; local -a args
|
|
snap="$(printf '%s' "$snap_b64" | base64 -d 2>/dev/null || true)"
|
|
raw_target="$(printf '%s' "$target_b64" | base64 -d 2>/dev/null || true)"
|
|
raw_grub="$(printf '%s' "$grub_b64" | base64 -d 2>/dev/null || true)"
|
|
raw_device="$(printf '%s' "$device_b64" | base64 -d 2>/dev/null || true)"
|
|
target="$(backup_device_arg_if_block "$raw_target" 2>/dev/null || true)"
|
|
grub="$(backup_device_arg_if_block "$raw_grub" 2>/dev/null || true)"
|
|
device="$(backup_device_arg_if_block "$raw_device" 2>/dev/null || true)"
|
|
[[ -n "$snap" ]] || { echo "Snapshot name is required" >&2; exit 64; }
|
|
[[ -n "$target" ]] || { echo "Target block device is required or could not be resolved: $raw_target" >&2; exit 64; }
|
|
args=(--restore --scripted --yes --snapshot "$snap" --target-device "$target")
|
|
if [[ "$skip_grub" == "1" ]]; then args+=(--skip-grub); elif [[ -n "$grub" ]]; then args+=(--grub-device "$grub"); fi
|
|
[[ -n "$device" ]] && args+=(--snapshot-device "$device")
|
|
backup_cli "${args[@]}"
|
|
}
|
|
|
|
backup_action() {
|
|
local action="${1:-}"; shift || true
|
|
local build_script="/usr/share/bastionguard-backup/scripts/build-recovery-squashfs.sh"
|
|
local grub_script="/usr/share/bastionguard-backup/scripts/install-grub-entry.sh"
|
|
case "$action" in
|
|
status) backup_status ;;
|
|
config-read) backup_config_read ;;
|
|
config-save-b64) [[ $# -eq 1 ]] || { echo "config-save-b64 requires one argument" >&2; exit 64; }; backup_config_save_b64 "$1" ;;
|
|
devices-tsv) backup_devices_tsv ;;
|
|
list-devices) backup_devices_tsv ;;
|
|
snapshot-index) backup_snapshot_index ;;
|
|
list-snapshots) backup_list_snapshots "${1:-config}" "${2:-}" ;;
|
|
create-snapshot-b64) [[ $# -ge 4 ]] || { echo "create-snapshot-b64 requires comment, tags, mode, device" >&2; exit 64; }; backup_create_snapshot "$1" "$2" "$3" "$4" ;;
|
|
delete-snapshot-b64) [[ $# -ge 1 ]] || { echo "delete-snapshot-b64 requires snapshot" >&2; exit 64; }; backup_delete_snapshot "$1" "${2:-}" ;;
|
|
delete-all-b64) backup_delete_all "${1:-}" ;;
|
|
restore-snapshot-b64) [[ $# -ge 5 ]] || { echo "restore-snapshot-b64 requires snapshot, target, grub, skip, device" >&2; exit 64; }; backup_restore_snapshot "$1" "$2" "$3" "$4" "$5" ;;
|
|
launch-manager)
|
|
echo "BastionGuard WebUI: graphical backup manager launch is disabled. The WebUI backup manager uses the headless CLI and scripts." >&2
|
|
exit 76
|
|
;;
|
|
build-recovery)
|
|
[[ -f "$build_script" ]] || { echo "Missing script: $build_script" >&2; exit 66; }
|
|
echo "==> Running recovery build script: $build_script"
|
|
bash "$build_script"
|
|
;;
|
|
install-grub)
|
|
[[ -f "$grub_script" ]] || { echo "Missing script: $grub_script" >&2; exit 66; }
|
|
echo "==> Running GRUB install script: $grub_script"
|
|
bash "$grub_script"
|
|
;;
|
|
remove-grub)
|
|
echo "==> Removing BastionGuard recovery GRUB entry"
|
|
rm -f /etc/grub.d/42_bastionguard-recovery
|
|
if command -v update-grub >/dev/null 2>&1; then update-grub
|
|
elif command -v grub-mkconfig >/dev/null 2>&1; then grub-mkconfig -o /boot/grub/grub.cfg
|
|
else echo "Warning: no update-grub or grub-mkconfig found" >&2
|
|
fi
|
|
;;
|
|
*) echo "Invalid backup action: $action" >&2; exit 64 ;;
|
|
esac
|
|
}
|
|
|
|
|
|
scan_path_b64() {
|
|
local b64="$1" target real scanner
|
|
target="$(printf '%s' "$b64" | base64 -d)" || { echo "Base64 non valido" >&2; exit 64; }
|
|
[[ -n "$target" ]] || { echo "Percorso vuoto" >&2; exit 64; }
|
|
[[ -e "$target" ]] || { echo "Percorso inesistente: $target" >&2; exit 66; }
|
|
if command -v realpath >/dev/null 2>&1; then real="$(realpath -m -- "$target")"; else real="$target"; fi
|
|
case "$real" in
|
|
/home|/home/*|/srv|/srv/*|/mnt|/mnt/*|/media|/media/*|/run/media|/run/media/*|/var/www|/var/www/*|/var/lib/bastionguard-webui/*|/tmp/bastionguard-webui-data/*|/tmp|/tmp/*) ;;
|
|
*) echo "Percorso non consentito dal profilo scan WebUI: $real" >&2; exit 64 ;;
|
|
esac
|
|
run_clam_scan_and_alert "$real" manual
|
|
}
|
|
|
|
valid_unit_name() {
|
|
[[ "${1:-}" =~ ^[A-Za-z0-9@_.:-]+\.service$ || "${1:-}" =~ ^[A-Za-z0-9@_.:-]+\.timer$ ]]
|
|
}
|
|
|
|
service_action() {
|
|
local mode="$1" user="$2" action="$3" unit="$4" verb guard_action
|
|
[[ "$mode" == "system" || "$mode" == "user" ]] || { echo "Invalid service mode" >&2; exit 64; }
|
|
case "$action" in start|stop|restart|enable|disable) ;; *) echo "Invalid service action" >&2; exit 64 ;; esac
|
|
valid_unit_name "$unit" || { echo "Invalid unit: $unit" >&2; exit 64; }
|
|
verb="$action"
|
|
guard_action="$action"
|
|
if [[ "$action" == "enable" ]]; then verb="enable --now"; guard_action="enable--now"; fi
|
|
if [[ "$action" == "disable" ]]; then verb="disable --now"; guard_action="disable--now"; fi
|
|
if webui_disabled_unit "$unit"; then
|
|
case "$guard_action" in stop|disable|disable--now) ;; *) echo "BastionGuard WebUI: blocked $guard_action for $unit. CEF/PAC, Webcam/Privacy and USB are disabled in the server WebUI." >&2; exit 76 ;; esac
|
|
fi
|
|
if [[ "$mode" == "user" ]]; then
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
guard_proxy_desktop_conflict "$user" "$unit" "$guard_action"
|
|
# shellcheck disable=SC2086
|
|
run_as_user "$user" systemctl --user $verb "$unit"
|
|
else
|
|
# shellcheck disable=SC2086
|
|
systemctl $verb "$unit"
|
|
fi
|
|
}
|
|
|
|
append_alert_tsv() {
|
|
local type="$1" family="$2" path="$3" source="$4" dir=/var/lib/bastionguard-webui file ts id bpath bfamily owner
|
|
mkdir -p "$dir" 2>/dev/null || return 0
|
|
owner="$(stat -c '%u:%g' "$dir" 2>/dev/null || true)"
|
|
chmod 0750 "$dir" 2>/dev/null || true
|
|
file="$dir/alerts.tsv"
|
|
ts="$(date +%s)"
|
|
id="$(printf '%s|%s|%s|%s' "$ts" "$type" "$family" "$path" | sha256sum | awk '{print $1}')"
|
|
bpath="$(printf '%s' "$path" | base64 -w0)"
|
|
bfamily="$(printf '%s' "$family" | base64 -w0)"
|
|
printf '%s\t%s\t%s\t%s\t%s\t%s\n' "$ts" "$id" "$type" "$bfamily" "$bpath" "$source" >> "$file" || true
|
|
[[ -n "$owner" ]] && chown "$owner" "$file" 2>/dev/null || true
|
|
chmod 0640 "$file" 2>/dev/null || true
|
|
}
|
|
|
|
|
|
ransomware_scanner_bin() {
|
|
local c
|
|
for c in /usr/bin/BastionGuard-ransomware-scanner /usr/local/bin/BastionGuard-ransomware-scanner /bin/BastionGuard-ransomware-scanner; do
|
|
[[ -x "$c" ]] && { printf '%s\n' "$c"; return 0; }
|
|
done
|
|
command -v BastionGuard-ransomware-scanner 2>/dev/null || true
|
|
}
|
|
|
|
append_ransomware_alert_for_file() {
|
|
local path="$1" family="${2:-BastionGuard.Ransomware.Signal}" source="${3:-ransomware}"
|
|
[[ -n "$path" ]] || return 0
|
|
append_alert_tsv ransomware "$family" "$path" "$source"
|
|
}
|
|
|
|
scan_ransomware_path_and_alert() {
|
|
local target="$1" source="${2:-ransomware}" scanner out rc=0 found=0 event_count_before=0 event_count_after=0 line path fam
|
|
[[ -e "$target" ]] || return 0
|
|
|
|
|
|
scanner="$(ransomware_scanner_bin)"
|
|
if [[ -n "$scanner" ]]; then
|
|
if [[ -d "$target" ]]; then
|
|
event_count_before=$(wc -l < /tmp/BastionGuard-ransomware-events.log 2>/dev/null || echo 0)
|
|
set +e
|
|
out="$($scanner --scan-dir "$target" 2>&1)"
|
|
rc=$?
|
|
set -e
|
|
else
|
|
event_count_before=$(wc -l < /tmp/BastionGuard-ransomware-events.log 2>/dev/null || echo 0)
|
|
set +e
|
|
out="$($scanner --scan "$target" 2>&1)"
|
|
rc=$?
|
|
set -e
|
|
fi
|
|
printf '%s\n' "$out"
|
|
if printf '%s\n' "$out" | grep -Eiq '(Ransomware rilevato|Ransomware detected|\[YARA\]|YARA_SAMBA|YARA_FILE_MATCH|RILEVATO|MATCH:)'; then
|
|
if ! printf '%s\n' "$out" | grep -Eiq '(Nessuna|No rule|No YARA|nessuna regola|No match)'; then
|
|
fam="BastionGuard.YARA.Ransomware"
|
|
[[ -f "$target" ]] && append_ransomware_alert_for_file "$target" "$fam" "$source" && found=1
|
|
fi
|
|
fi
|
|
|
|
# Import events written by the native scanner, if any were appended during this run.
|
|
event_count_after=$(wc -l < /tmp/BastionGuard-ransomware-events.log 2>/dev/null || echo 0)
|
|
if [[ "$event_count_after" =~ ^[0-9]+$ && "$event_count_before" =~ ^[0-9]+$ && $event_count_after -gt $event_count_before ]]; then
|
|
tail -n $((event_count_after-event_count_before)) /tmp/BastionGuard-ransomware-events.log 2>/dev/null | while IFS='|' read -r ts path fam rest; do
|
|
[[ -n "$path" ]] || continue
|
|
case "$fam" in SUPPRESSED_ALLOWLIST*) continue ;; esac
|
|
if [[ "$fam" == YARA* || "$fam" == *RANSOM* || "$fam" == *ransom* ]]; then
|
|
append_ransomware_alert_for_file "$path" "${fam:-BastionGuard.Ransomware}" "$source"
|
|
fi
|
|
done
|
|
found=1
|
|
fi
|
|
return "$rc"
|
|
fi
|
|
|
|
return "$found"
|
|
}
|
|
|
|
import_ransomware_events() {
|
|
local f=/tmp/BastionGuard-ransomware-events.log native=/var/log/BastionGuard/antiransom_inotify.log line ts path fam rest payload imported=0
|
|
if [[ -r "$f" ]]; then
|
|
tail -n 200 "$f" | while IFS='|' read -r ts path fam rest; do
|
|
[[ -n "$path" && -n "$fam" ]] || continue
|
|
case "$fam" in SUPPRESSED_ALLOWLIST*) continue ;; esac
|
|
if [[ "$fam" == YARA* || "$fam" == *RANSOM* || "$fam" == *ransom* ]]; then
|
|
append_ransomware_alert_for_file "$path" "${fam:-BastionGuard.Ransomware}" "ransomware-realtime"
|
|
fi
|
|
done
|
|
imported=1
|
|
fi
|
|
if [[ -r "$native" ]]; then
|
|
tail -n 300 "$native" | while IFS= read -r line; do
|
|
[[ -n "$line" ]] || continue
|
|
path=""; fam=""
|
|
case "$line" in
|
|
*"Alert inviato:"*" | "*)
|
|
payload="${line#*Alert inviato: }"
|
|
path="${payload%% | *}"
|
|
fam="${payload##* | }"
|
|
;;
|
|
*"ARCHIVE THREAT SIGNAL:"*)
|
|
rest="${line#*ARCHIVE THREAT SIGNAL: }"
|
|
path="${rest%% risk=*}"
|
|
fam="BastionGuard.ARCHIVE_THREAT"
|
|
;;
|
|
*) continue ;;
|
|
esac
|
|
[[ -n "$path" ]] || continue
|
|
append_ransomware_alert_for_file "$path" "${fam:-BastionGuard.RansomwareRealtime}" "ransomware-realtime"
|
|
done
|
|
imported=1
|
|
fi
|
|
if [[ "$imported" == 0 ]]; then
|
|
echo "No ransomware event log found: $f or $native"
|
|
else
|
|
echo "ransomware events imported"
|
|
fi
|
|
}
|
|
|
|
parse_scan_alerts() {
|
|
local source="$1" line f fam
|
|
while IFS= read -r line; do
|
|
[[ "$line" == *" FOUND"* ]] || continue
|
|
f="${line%%:*}"
|
|
fam="${line#*: }"; fam="${fam% FOUND*}"
|
|
if [[ -n "$f" ]]; then
|
|
local atype="malware"; [[ "${fam,,}" == *ransom* || "${f,,}" == *ransom* ]] && atype="ransomware"
|
|
append_alert_tsv "$atype" "${fam:-Malware}" "$f" "$source"
|
|
fi
|
|
done
|
|
}
|
|
|
|
|
|
webui_trusted_script_path() {
|
|
local p="${1:-}" base
|
|
base="$(basename -- "$p" 2>/dev/null || true)"
|
|
case "$p" in
|
|
/usr/local/sbin/bastionguard-webui-*|/usr/local/libexec/bastionguard-webui-*|/usr/share/bastionguard-webui/scripts/*|/srv/http/webui/scripts/*|/srv/www/webui/scripts/*|/var/www/*/webui/scripts/*|*/webui/scripts/bastionguard-webui-*|*/webui/scripts/install-webui-helpers.sh|*/webui/scripts/install-user-service-helper.sh|*/webui/scripts/vendor-cantarell-font.sh)
|
|
case "$base" in bastionguard-webui-*|install-webui-helpers.sh|install-user-service-helper.sh|vendor-cantarell-font.sh) return 0 ;; esac
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
run_clam_scan_and_alert() {
|
|
local real="$1" source="$2" out rc scanner=()
|
|
if [[ -f "$real" ]] && webui_trusted_script_path "$real"; then
|
|
echo "SKIP trusted BastionGuard WebUI helper script: $real"
|
|
return 0
|
|
fi
|
|
if command -v clamdscan >/dev/null 2>&1; then
|
|
scanner=(clamdscan --fdpass --multiscan --no-summary "$real")
|
|
elif command -v clamscan >/dev/null 2>&1; then
|
|
scanner=(clamscan -r --infected "$real")
|
|
else
|
|
echo "WARN: clamdscan/clamscan non trovati; eseguo comunque i controlli anti-ransomware" >&2
|
|
scanner=()
|
|
fi
|
|
if [[ ${#scanner[@]} -gt 0 ]]; then
|
|
echo "Comando: ${scanner[*]}"
|
|
set +e
|
|
out="$("${scanner[@]}" 2>&1)"
|
|
rc=$?
|
|
set -e
|
|
printf '%s\n' "$out"
|
|
printf '%s\n' "$out" | parse_scan_alerts "$source"
|
|
else
|
|
rc=0
|
|
fi
|
|
# The monitored paths must also run the BastionGuard anti-ransomware checks,
|
|
# not only ClamAV. This keeps manual scans, Samba scans and inotify events aligned.
|
|
scan_ransomware_path_and_alert "$real" "$source" || true
|
|
return "$rc"
|
|
}
|
|
|
|
smb_cred_for() {
|
|
local key="$1" user="$2" home cred cur line u p enc
|
|
home="$(user_home "$user")"; cred="$home/.config/BastionGuard/cred.conf"
|
|
[[ -r "$cred" ]] || return 0
|
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
|
[[ -z "$line" ]] && continue
|
|
if [[ "$line" == \[*\] ]]; then cur="${line#[}"; cur="${cur%]}"; continue; fi
|
|
[[ "$cur" == "$key" ]] || continue
|
|
if [[ "$line" == user=* ]]; then u="$(printf '%s' "${line#user=}" | base64 -d 2>/dev/null || true)"; fi
|
|
if [[ "$line" == pass=* ]]; then
|
|
enc="${line#pass=}"
|
|
if command -v python3 >/dev/null 2>&1; then
|
|
p="$(python3 - "$enc" <<'PYDEC' 2>/dev/null || true
|
|
import base64,sys
|
|
key=b'BastionGuard0101SecretKey'
|
|
try:
|
|
data=base64.b64decode(sys.argv[1])
|
|
print(bytes([b ^ key[i % len(key)] for i,b in enumerate(data)]).decode('utf-8','ignore'))
|
|
except Exception:
|
|
pass
|
|
PYDEC
|
|
)"
|
|
else
|
|
p="$(printf '%s' "$enc" | base64 -d 2>/dev/null || true)"
|
|
fi
|
|
fi
|
|
done < "$cred"
|
|
printf '%s\t%s\n' "$u" "$p"
|
|
}
|
|
|
|
|
|
run_samba_clam_scan_and_alert() {
|
|
local tmp="$1" remote_base="$2" out rc scanner=() line f fam rel remote atype
|
|
if command -v clamdscan >/dev/null 2>&1; then
|
|
scanner=(clamdscan --fdpass --multiscan --no-summary "$tmp")
|
|
elif command -v clamscan >/dev/null 2>&1; then
|
|
scanner=(clamscan -r --infected "$tmp")
|
|
else
|
|
echo "clamdscan/clamscan non trovati" >&2
|
|
return 127
|
|
fi
|
|
echo "Comando: ${scanner[*]}"
|
|
set +e
|
|
out="$("${scanner[@]}" 2>&1)"
|
|
rc=$?
|
|
set -e
|
|
printf '%s\n' "$out"
|
|
while IFS= read -r line; do
|
|
[[ "$line" == *" FOUND"* ]] || continue
|
|
f="${line%%:*}"
|
|
fam="${line#*: }"; fam="${fam% FOUND*}"
|
|
rel="${f#$tmp/}"
|
|
if [[ "$rel" == "$f" ]]; then remote="$remote_base"; else remote="${remote_base%/}/$rel"; fi
|
|
atype="malware"; [[ "${fam,,}" == *ransom* || "${remote,,}" == *ransom* ]] && atype="ransomware"
|
|
append_alert_tsv "$atype" "${fam:-Malware}" "$remote" samba
|
|
done <<< "$out"
|
|
return "$rc"
|
|
}
|
|
|
|
samba_scan_b64() {
|
|
local b64="$1" user="$2" target server share rpath key cred smb_user smb_pass tmp cmd rc
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
target="$(printf '%s' "$b64" | base64 -d)" || { echo "Base64 non valido" >&2; exit 64; }
|
|
[[ -n "$target" ]] || { echo "Percorso vuoto" >&2; exit 64; }
|
|
if [[ "$target" != smb://* ]]; then
|
|
scan_path_b64 "$b64"
|
|
return $?
|
|
fi
|
|
command -v smbclient >/dev/null 2>&1 || { echo "smbclient non trovato: installa smbclient per scansionare URL smb://" >&2; exit 127; }
|
|
local rest="${target#smb://}"
|
|
server="${rest%%/*}"
|
|
rest="${rest#*/}"
|
|
share="${rest%%/*}"
|
|
if [[ "$rest" == "$share" ]]; then rpath=""; else rpath="${rest#*/}"; fi
|
|
[[ -n "$server" && -n "$share" ]] || { echo "URL SMB non valido: $target" >&2; exit 64; }
|
|
key="smb://$server/$share"
|
|
cred="$(smb_cred_for "$key" "$user")"
|
|
smb_user="${cred%%$'\t'*}"; smb_pass="${cred#*$'\t'}"
|
|
tmp="$(mktemp -d /tmp/BastionGuard_samba_XXXXXX)"
|
|
chmod 0755 "$tmp" || true
|
|
cmd="$(mktemp)"
|
|
{
|
|
echo 'prompt OFF'
|
|
echo 'recurse ON'
|
|
echo "lcd $tmp"
|
|
if [[ -n "$rpath" ]]; then printf 'cd %q\n' "$rpath"; fi
|
|
echo 'mget *'
|
|
} > "$cmd"
|
|
echo "=== Scansione Samba: $target ==="
|
|
echo "Temp dir: $tmp"
|
|
echo "Share: //$server/$share"
|
|
set +e
|
|
if [[ -n "$smb_user" ]]; then
|
|
smbclient "//$server/$share" -U "$smb_user%$smb_pass" -m SMB3 -E < "$cmd" 2>&1
|
|
else
|
|
smbclient "//$server/$share" -N -m SMB3 -E < "$cmd" 2>&1
|
|
fi
|
|
rc=$?
|
|
set -e
|
|
rm -f "$cmd"
|
|
if [[ $rc -ne 0 ]]; then
|
|
echo "ERRORE: impossibile copiare la share SMB. Verifica URL, credenziali in cred.conf e permessi. [code $rc]"
|
|
rm -rf "$tmp"
|
|
exit "$rc"
|
|
fi
|
|
echo "File copiati temporaneamente: $(find "$tmp" -type f 2>/dev/null | wc -l)"
|
|
rc=0
|
|
run_samba_clam_scan_and_alert "$tmp" "$target" || rc=$?
|
|
rm -rf "$tmp"
|
|
echo "=== Scansione Samba completata ==="
|
|
return "$rc"
|
|
}
|
|
|
|
allowed_restore_path() {
|
|
local p="$1"
|
|
case "$p" in /home|/home/*|/srv|/srv/*|/mnt|/mnt/*|/media|/media/*|/run/media|/run/media/*|/var/www|/var/www/*|/var/lib/bastionguard-webui/*|/tmp|/tmp/*) return 0 ;; *) return 1 ;; esac
|
|
}
|
|
|
|
quarantine_owner() {
|
|
stat -c '%u:%g' /var/lib/bastionguard-webui 2>/dev/null || true
|
|
}
|
|
|
|
safe_quarantine_name() {
|
|
local name="$1"
|
|
[[ -n "$name" && "$name" != "." && "$name" != ".." && "$name" != */* ]] || { echo "Invalid quarantine item name" >&2; exit 64; }
|
|
}
|
|
|
|
quarantine_metadata_dir() {
|
|
printf '%s\n' /var/lib/bastionguard-webui/quarantine/.metadata
|
|
}
|
|
|
|
write_quarantine_metadata() {
|
|
local dest="$1" original="$2" user="$3" meta_dir meta name owner uid gid mode mtime
|
|
meta_dir="$(quarantine_metadata_dir)"
|
|
mkdir -p "$meta_dir"
|
|
name="$(basename "$dest")"
|
|
uid="$(stat -c '%u' "$dest" 2>/dev/null || echo 0)"
|
|
gid="$(stat -c '%g' "$dest" 2>/dev/null || echo 0)"
|
|
mode="$(stat -c '%a' "$dest" 2>/dev/null || echo 0640)"
|
|
mtime="$(stat -c '%Y' "$dest" 2>/dev/null || date +%s)"
|
|
meta="$meta_dir/$name.json"
|
|
python3 - "$meta" "$original" "$dest" "$user" "$uid" "$gid" "$mode" "$mtime" <<'PYMETA'
|
|
import json, sys, time
|
|
meta, original, dest, user, uid, gid, mode, mtime = sys.argv[1:]
|
|
data = {
|
|
"original_path": original,
|
|
"quarantine_path": dest,
|
|
"quarantined_at": time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()),
|
|
"desktop_user": user if user != '-' else '',
|
|
"uid": int(uid) if uid.isdigit() else 0,
|
|
"gid": int(gid) if gid.isdigit() else 0,
|
|
"mode": mode,
|
|
"mtime": int(mtime) if str(mtime).isdigit() else int(time.time()),
|
|
}
|
|
with open(meta, 'w', encoding='utf-8') as fh:
|
|
json.dump(data, fh, indent=2, ensure_ascii=False)
|
|
fh.write('\n')
|
|
PYMETA
|
|
owner="$(quarantine_owner)"
|
|
[[ -n "$owner" ]] && chown "$owner" "$meta_dir" "$meta" 2>/dev/null || true
|
|
chmod 0750 "$meta_dir" 2>/dev/null || true
|
|
chmod 0640 "$meta" 2>/dev/null || true
|
|
}
|
|
|
|
quarantine_path_b64() {
|
|
local b64="$1" user="${2:-}" target real qdir dest base n owner uid gid mode
|
|
target="$(printf '%s' "$b64" | base64 -d)" || { echo "Base64 non valido" >&2; exit 64; }
|
|
[[ "$target" != smb://* ]] || { echo "Remote SMB path cannot be quarantined from WebPanel; remove or isolate it on the SMB server." >&2; exit 65; }
|
|
[[ -e "$target" && -f "$target" ]] || { echo "File non trovato: $target" >&2; exit 66; }
|
|
if command -v realpath >/dev/null 2>&1; then real="$(realpath -m -- "$target")"; else real="$target"; fi
|
|
allowed_restore_path "$real" || { echo "Path non consentito: $real" >&2; exit 64; }
|
|
uid="$(stat -c '%u' "$real" 2>/dev/null || echo 0)"
|
|
gid="$(stat -c '%g' "$real" 2>/dev/null || echo 0)"
|
|
mode="$(stat -c '%a' "$real" 2>/dev/null || echo 0640)"
|
|
qdir=/var/lib/bastionguard-webui/quarantine
|
|
mkdir -p "$qdir"
|
|
owner="$(quarantine_owner)"
|
|
[[ -n "$owner" ]] && chown "$owner" "$qdir" 2>/dev/null || true
|
|
chmod 0750 "$qdir" || true
|
|
base="$(basename "$real")"; dest="$qdir/$base"; n=0
|
|
while [[ -e "$dest" ]]; do n=$((n+1)); dest="$qdir/${base}.$n"; done
|
|
mv -- "$real" "$dest"
|
|
chown "$uid:$gid" "$dest" 2>/dev/null || true
|
|
chmod "$mode" "$dest" 2>/dev/null || chmod 0640 "$dest" || true
|
|
write_quarantine_metadata "$dest" "$real" "${user:-}"
|
|
[[ -n "$owner" ]] && chown "$owner" "$dest" 2>/dev/null || true
|
|
chmod 0640 "$dest" 2>/dev/null || true
|
|
echo "quarantined: $dest"
|
|
}
|
|
|
|
restore_quarantine_b64() {
|
|
local name_b64="$1" restore_b64="$2" user="${3:-}" name restore_dir qdir src meta dest original owner uid gid mode n base dir
|
|
name="$(printf '%s' "$name_b64" | base64 -d)" || { echo "Invalid quarantine file encoding" >&2; exit 64; }
|
|
restore_dir="$(printf '%s' "$restore_b64" | base64 -d)" || { echo "Invalid restore directory encoding" >&2; exit 64; }
|
|
safe_quarantine_name "$name"
|
|
qdir=/var/lib/bastionguard-webui/quarantine
|
|
src="$qdir/$name"
|
|
[[ -f "$src" ]] || { echo "Quarantine item not found: $name" >&2; exit 66; }
|
|
meta="$(quarantine_metadata_dir)/$name.json"
|
|
original=""
|
|
uid=""; gid=""; mode=""
|
|
if [[ -f "$meta" ]]; then
|
|
original="$(python3 - <<'PYMETA' "$meta"
|
|
import json,sys
|
|
try:
|
|
d=json.load(open(sys.argv[1], encoding='utf-8'))
|
|
print(d.get('original_path',''))
|
|
except Exception:
|
|
print('')
|
|
PYMETA
|
|
)"
|
|
uid="$(python3 - <<'PYMETA' "$meta"
|
|
import json,sys
|
|
try: print(json.load(open(sys.argv[1], encoding='utf-8')).get('uid',''))
|
|
except Exception: print('')
|
|
PYMETA
|
|
)"
|
|
gid="$(python3 - <<'PYMETA' "$meta"
|
|
import json,sys
|
|
try: print(json.load(open(sys.argv[1], encoding='utf-8')).get('gid',''))
|
|
except Exception: print('')
|
|
PYMETA
|
|
)"
|
|
mode="$(python3 - <<'PYMETA' "$meta"
|
|
import json,sys
|
|
try: print(json.load(open(sys.argv[1], encoding='utf-8')).get('mode',''))
|
|
except Exception: print('')
|
|
PYMETA
|
|
)"
|
|
fi
|
|
if [[ -n "$original" ]]; then
|
|
if command -v realpath >/dev/null 2>&1; then dest="$(realpath -m -- "$original")"; else dest="$original"; fi
|
|
else
|
|
[[ -n "$restore_dir" ]] || restore_dir=/tmp/bastionguard-restore
|
|
if command -v realpath >/dev/null 2>&1; then restore_dir="$(realpath -m -- "$restore_dir")"; fi
|
|
allowed_restore_path "$restore_dir" || { echo "Restore directory not allowed: $restore_dir" >&2; exit 64; }
|
|
mkdir -p "$restore_dir"
|
|
dest="$restore_dir/$name"
|
|
fi
|
|
allowed_restore_path "$dest" || { echo "Restore path not allowed: $dest" >&2; exit 64; }
|
|
dir="$(dirname "$dest")"
|
|
mkdir -p "$dir"
|
|
base="$(basename "$dest")"
|
|
if [[ -e "$dest" ]]; then
|
|
n=0
|
|
while [[ -e "$dir/$base.restored.$n" ]]; do n=$((n+1)); done
|
|
dest="$dir/$base.restored.$n"
|
|
fi
|
|
mv -- "$src" "$dest"
|
|
if [[ "$uid" =~ ^[0-9]+$ && "$gid" =~ ^[0-9]+$ ]]; then chown "$uid:$gid" "$dest" 2>/dev/null || true
|
|
elif [[ -n "$user" && "$user" != "-" ]] && valid_user "$user"; then chown "$(user_uid "$user"):$(user_gid "$user")" "$dest" 2>/dev/null || true
|
|
fi
|
|
[[ "$mode" =~ ^[0-7]{3,4}$ ]] && chmod "$mode" "$dest" 2>/dev/null || chmod 0640 "$dest" 2>/dev/null || true
|
|
rm -f -- "$meta" 2>/dev/null || true
|
|
owner="$(quarantine_owner)"; [[ -n "$owner" ]] && chown "$owner" "$qdir" "$(quarantine_metadata_dir)" 2>/dev/null || true
|
|
echo "restored: $dest"
|
|
}
|
|
|
|
delete_quarantine_b64() {
|
|
local name_b64="$1" user="${2:-}" name qdir src meta
|
|
name="$(printf '%s' "$name_b64" | base64 -d)" || { echo "Invalid quarantine file encoding" >&2; exit 64; }
|
|
safe_quarantine_name "$name"
|
|
qdir=/var/lib/bastionguard-webui/quarantine
|
|
src="$qdir/$name"
|
|
meta="$(quarantine_metadata_dir)/$name.json"
|
|
[[ -f "$src" ]] || { echo "Quarantine item not found: $name" >&2; exit 66; }
|
|
rm -f -- "$src" "$meta"
|
|
echo "deleted: $name"
|
|
}
|
|
|
|
inotify_pid_file() { printf '/run/bastionguard-webui/inotify-%s.pid\n' "$1"; }
|
|
inotify_log_file() { local home; home="$(user_home "$1")"; printf '%s/.local/share/BastionGuard/logs/webui-inotify.log\n' "$home"; }
|
|
inotify_saved_paths_file() { local home; home="$(user_home "$1")"; printf '%s/.local/share/BastionGuard/webui-inotify.paths\n' "$home"; }
|
|
inotify_conf_file() { local home; home="$(user_home "$1")"; printf '%s/.config/BastionGuard/webui-inotify.conf\n' "$home"; }
|
|
inotify_default_paths() {
|
|
local user="$1" home p
|
|
home="$(user_home "$user")"
|
|
for p in "$home/Downloads" "$home/public_html" "$home/www" /home /var/www /var/www/html /srv/http /srv/www /tmp; do
|
|
[[ -d "$p" ]] && printf '%s\n' "$p"
|
|
done
|
|
}
|
|
inotify_path_allowed() {
|
|
local p="$1"
|
|
case "$p" in /home|/home/*|/srv|/srv/*|/mnt|/mnt/*|/media|/media/*|/run/media|/run/media/*|/var/www|/var/www/*|/tmp|/tmp/*|/root|/root/*|/etc|/etc/*|/var/lib|/var/lib/*|/var/log|/var/log/*) return 0 ;; *) return 1 ;; esac
|
|
}
|
|
|
|
ransomware_realtime_start() {
|
|
local user="${1:-}" unit bin pidfile log pid
|
|
[[ -z "$user" || "$user" == "-" ]] || valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
for unit in BastionGuard-ransomware-realtime.service bastionguard-ransomware-realtime.service; do
|
|
if systemctl list-unit-files "$unit" >/dev/null 2>&1 || systemctl status "$unit" >/dev/null 2>&1; then
|
|
systemctl enable --now "$unit" 2>/dev/null || systemctl start "$unit" 2>/dev/null || true
|
|
echo "ransomware realtime: systemd unit requested: $unit"
|
|
return 0
|
|
fi
|
|
done
|
|
for bin in /usr/bin/BastionGuard-ransomware-realtime /usr/local/bin/BastionGuard-ransomware-realtime /bin/BastionGuard-ransomware-realtime; do
|
|
[[ -x "$bin" ]] && break || true
|
|
done
|
|
if [[ ! -x "${bin:-}" ]]; then
|
|
bin="$(command -v BastionGuard-ransomware-realtime 2>/dev/null || true)"
|
|
fi
|
|
if [[ -z "${bin:-}" || ! -x "$bin" ]]; then
|
|
echo "ransomware realtime: BastionGuard-ransomware-realtime not installed; WebUI inotify will still run clamdscan and BastionGuard-ransomware-scanner when available"
|
|
return 0
|
|
fi
|
|
pidfile=/run/BastionGuard-ransomware-realtime-webui.pid
|
|
log=/var/log/BastionGuard/antiransom_inotify.log
|
|
mkdir -p /var/log/BastionGuard /run 2>/dev/null || true
|
|
if [[ -f "$pidfile" ]]; then
|
|
pid="$(cat "$pidfile" 2>/dev/null || true)"
|
|
[[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null && { echo "ransomware realtime: already running pid=$pid"; return 0; }
|
|
fi
|
|
nohup "$bin" >> "$log" 2>&1 &
|
|
echo "$!" > "$pidfile"
|
|
chmod 0644 "$log" 2>/dev/null || true
|
|
echo "ransomware realtime: started $bin pid=$(cat "$pidfile") log=$log"
|
|
}
|
|
|
|
inotify_system_unit_exists() {
|
|
[[ -f /etc/systemd/system/bastionguard-webui-inotify.service ]] || systemctl list-unit-files bastionguard-webui-inotify.service >/dev/null 2>&1
|
|
}
|
|
|
|
inotify_service_start() {
|
|
local user="$1" b64="$2"
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
inotify_save_paths "$user" "$b64"
|
|
ransomware_realtime_start "$user" || true
|
|
if inotify_system_unit_exists; then
|
|
systemctl daemon-reload 2>/dev/null || true
|
|
systemctl enable bastionguard-webui-inotify.service 2>/dev/null || true
|
|
systemctl restart bastionguard-webui-inotify.service
|
|
systemctl is-active --quiet bastionguard-webui-inotify.service && echo "root service active: bastionguard-webui-inotify.service" || true
|
|
else
|
|
echo "warning: root systemd unit not installed; falling back to direct root monitor. Re-run scripts/install-webui-helpers.sh."
|
|
inotify_start_saved "$user"
|
|
fi
|
|
}
|
|
|
|
inotify_service_stop() {
|
|
local user="$1"
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
if inotify_system_unit_exists; then
|
|
systemctl stop bastionguard-webui-inotify.service 2>/dev/null || true
|
|
fi
|
|
inotify_stop "$user"
|
|
}
|
|
|
|
inotify_service_status() {
|
|
local user="$1" active enabled
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
if inotify_system_unit_exists; then
|
|
active="$(systemctl is-active bastionguard-webui-inotify.service 2>/dev/null || true)"
|
|
enabled="$(systemctl is-enabled bastionguard-webui-inotify.service 2>/dev/null || true)"
|
|
printf 'systemd=%s enabled=%s ' "${active:-unknown}" "${enabled:-unknown}"
|
|
else
|
|
printf 'systemd=missing '
|
|
fi
|
|
inotify_status "$user"
|
|
}
|
|
|
|
inotify_status() {
|
|
local user="$1" pidfile oldpidfile pid log
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
pidfile="$(inotify_pid_file "$user")"; oldpidfile="/run/bastionguard-webui-inotify-$user.pid"; log="$(inotify_log_file "$user")"
|
|
if [[ -f "$pidfile" ]]; then
|
|
pid="$(cat "$pidfile" 2>/dev/null || true)"
|
|
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then echo "running pid=$pid log=$log runner=/usr/local/libexec/bastionguard-webui-inotify-runner"; exit 0; fi
|
|
fi
|
|
if [[ -f "$oldpidfile" ]]; then
|
|
pid="$(cat "$oldpidfile" 2>/dev/null || true)"
|
|
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then echo "legacy-runtime-script-running pid=$pid log=$log; stop/start to migrate"; exit 0; fi
|
|
fi
|
|
echo "stopped log=$log"
|
|
}
|
|
|
|
inotify_stop() {
|
|
local user="$1" pidfile oldpidfile pid stopped=0
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
pidfile="$(inotify_pid_file "$user")"
|
|
oldpidfile="/run/bastionguard-webui-inotify-$user.pid"
|
|
for pf in "$pidfile" "$oldpidfile"; do
|
|
[[ -f "$pf" ]] || continue
|
|
pid="$(cat "$pf" 2>/dev/null || true)"
|
|
[[ -n "$pid" ]] && kill "$pid" 2>/dev/null || true
|
|
rm -f "$pf"
|
|
stopped=1
|
|
done
|
|
rm -f -- "/run/bastionguard-webui-inotify-$user.sh" "/run/bastionguard-webui-inotify-$user.paths" 2>/dev/null || true
|
|
if [[ "$stopped" == "1" ]]; then echo "stopped"; else echo "not running"; fi
|
|
}
|
|
|
|
inotify_events() {
|
|
local user="$1" lines="${2:-120}" log
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
[[ "$lines" =~ ^[0-9]+$ ]] || lines=120
|
|
(( lines > 500 )) && lines=500
|
|
log="$(inotify_log_file "$user")"
|
|
[[ -f "$log" ]] || { echo "No realtime inotify log yet: $log"; exit 0; }
|
|
tail -n "$lines" "$log"
|
|
}
|
|
|
|
inotify_save_paths() {
|
|
local user="$1" b64="$2" home uid gid pathsfile p real saved conf log
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
home="$(user_home "$user")"; uid="$(user_uid "$user")"; gid="$(user_gid "$user")"
|
|
pathsfile="$(mktemp)"
|
|
printf '%s' "$b64" | base64 -d > "$pathsfile" || { echo "Base64 paths invalid" >&2; rm -f "$pathsfile"; exit 64; }
|
|
while IFS= read -r p || [[ -n "$p" ]]; do
|
|
[[ -z "$p" ]] && continue
|
|
[[ -e "$p" ]] || { echo "Path not found: $p" >&2; rm -f "$pathsfile"; exit 66; }
|
|
if command -v realpath >/dev/null 2>&1; then real="$(realpath -m -- "$p")"; else real="$p"; fi
|
|
if ! inotify_path_allowed "$real"; then echo "Path not allowed for inotify monitor: $real" >&2; rm -f "$pathsfile"; exit 64; fi
|
|
done < "$pathsfile"
|
|
saved="$(inotify_saved_paths_file "$user")"; conf="$(inotify_conf_file "$user")"; log="$(inotify_log_file "$user")"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$(dirname "$saved")" "$(dirname "$conf")" "$(dirname "$log")" 2>/dev/null || true
|
|
install -o "$uid" -g "$gid" -m 0640 "$pathsfile" "$saved" 2>/dev/null || cp "$pathsfile" "$saved" 2>/dev/null || true
|
|
printf 'enabled=1\npaths_file=%s\nlog_file=%s\n' "$saved" "$log" > "$conf.tmp" 2>/dev/null || true
|
|
if [[ -f "$conf.tmp" ]]; then install -o "$uid" -g "$gid" -m 0640 "$conf.tmp" "$conf" 2>/dev/null || cp "$conf.tmp" "$conf" 2>/dev/null || true; rm -f "$conf.tmp"; fi
|
|
rm -f "$pathsfile"
|
|
echo "saved paths=$saved"
|
|
}
|
|
|
|
inotify_start() {
|
|
local user="$1" b64="$2" home uid gid pidfile log pathsfile runner pid p real saved conf runtime_dir old_script
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
command -v inotifywait >/dev/null 2>&1 || { echo "inotifywait non trovato: installa inotify-tools." >&2; exit 127; }
|
|
runner=/usr/local/libexec/bastionguard-webui-inotify-runner
|
|
[[ -x "$runner" ]] || { echo "Missing static realtime runner: $runner. Re-run scripts/install-webui-helpers.sh." >&2; exit 127; }
|
|
home="$(user_home "$user")"; uid="$(user_uid "$user")"; gid="$(user_gid "$user")"
|
|
pidfile="$(inotify_pid_file "$user")"; log="$(inotify_log_file "$user")"
|
|
if [[ -f "$pidfile" ]]; then pid="$(cat "$pidfile" 2>/dev/null || true)"; [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null && { echo "already running pid=$pid"; exit 0; }; fi
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$(dirname "$log")"
|
|
runtime_dir=/run/bastionguard-webui
|
|
install -d -o root -g root -m 0755 "$runtime_dir"
|
|
pathsfile="$runtime_dir/inotify-$user.paths"
|
|
old_script="/run/bastionguard-webui-inotify-$user.sh"
|
|
rm -f -- "$old_script" 2>/dev/null || true
|
|
printf '%s' "$b64" | base64 -d > "$pathsfile" || { echo "Base64 paths invalid" >&2; rm -f "$pathsfile"; exit 64; }
|
|
chmod 0640 "$pathsfile" 2>/dev/null || true
|
|
chown root:root "$pathsfile" 2>/dev/null || true
|
|
while IFS= read -r p || [[ -n "$p" ]]; do
|
|
[[ -z "$p" ]] && continue
|
|
[[ -e "$p" ]] || { echo "Path not found: $p" >&2; rm -f "$pathsfile"; exit 66; }
|
|
if command -v realpath >/dev/null 2>&1; then real="$(realpath -m -- "$p")"; else real="$p"; fi
|
|
if ! inotify_path_allowed "$real"; then echo "Path not allowed for inotify monitor: $real" >&2; rm -f "$pathsfile"; exit 64; fi
|
|
done < "$pathsfile"
|
|
saved="$(inotify_saved_paths_file "$user")"; conf="$(inotify_conf_file "$user")"
|
|
install -d -o "$uid" -g "$gid" -m 0750 "$(dirname "$saved")" "$(dirname "$conf")" 2>/dev/null || true
|
|
install -o "$uid" -g "$gid" -m 0640 "$pathsfile" "$saved" 2>/dev/null || cp "$pathsfile" "$saved" 2>/dev/null || true
|
|
printf 'enabled=1\npaths_file=%s\nlog_file=%s\nrunner=%s\n' "$saved" "$log" "$runner" > "$conf.tmp" 2>/dev/null || true
|
|
if [[ -f "$conf.tmp" ]]; then install -o "$uid" -g "$gid" -m 0640 "$conf.tmp" "$conf" 2>/dev/null || cp "$conf.tmp" "$conf" 2>/dev/null || true; rm -f "$conf.tmp"; fi
|
|
nohup "$runner" "$user" "$pathsfile" "$log" >> "$log" 2>&1 &
|
|
pid=$!
|
|
echo "$pid" > "$pidfile"
|
|
chmod 0644 "$pidfile" 2>/dev/null || true
|
|
chown "$uid:$gid" "$log" 2>/dev/null || true
|
|
echo "started pid=$pid log=$log runner=$runner"
|
|
}
|
|
|
|
|
|
clamd_conf_path() {
|
|
local candidates=(/etc/clamd/clamd.conf /etc/clamav/clamd.conf /etc/clamd.d/clamd.conf /etc/clamd.conf)
|
|
local p d
|
|
for p in "${candidates[@]}"; do [[ -f "$p" ]] && { printf '%s\n' "$p"; return 0; }; done
|
|
for p in "${candidates[@]}"; do d="$(dirname "$p")"; [[ -d "$d" ]] && { printf '%s\n' "$p"; return 0; }; done
|
|
printf '%s\n' /etc/clamav/clamd.conf
|
|
}
|
|
|
|
read_clamd_b64() {
|
|
local p; p="$(clamd_conf_path)"
|
|
[[ -e "$p" ]] || { echo "MISSING $p" >&2; exit 66; }
|
|
[[ -f "$p" ]] || { echo "NOTFILE $p" >&2; exit 65; }
|
|
[[ -r "$p" ]] || { echo "UNREADABLE $p" >&2; exit 13; }
|
|
base64 -w0 "$p"
|
|
}
|
|
|
|
write_clamd_b64() {
|
|
local b64="$1" p tmp content
|
|
p="$(clamd_conf_path)"
|
|
content="$(printf '%s' "$b64" | base64 -d)" || { echo "Invalid base64" >&2; exit 64; }
|
|
tmp="$(mktemp)"
|
|
printf '%s' "$content" > "$tmp"
|
|
if [[ -f "$p" ]]; then cp -a "$p" "$p.bak.$(date +%s)" 2>/dev/null || true; fi
|
|
write_system_file "$p" 0644 "$(cat "$tmp")"
|
|
rm -f "$tmp"
|
|
systemctl try-restart clamav-daemon.service 2>/dev/null || systemctl try-restart clamd.service 2>/dev/null || true
|
|
systemctl try-restart clamav-clamonacc.service 2>/dev/null || systemctl try-restart clamonacc.service 2>/dev/null || true
|
|
echo "written: $p"
|
|
}
|
|
|
|
install_secure_ca() {
|
|
local helper=/usr/share/BastionGuard/data/scripts/install-ca-system.sh
|
|
local user_or_ca="${1:-}" ca="/etc/BastionGuard/certs/BastionGuard-ca.crt.pem"
|
|
if [[ -n "$user_or_ca" && "$user_or_ca" != /* ]] && valid_user "$user_or_ca"; then
|
|
echo "BastionGuard WebUI: secure browsing / CEF bootstrap is disabled. Use the GTK desktop UI for CEF/browser proxy setup." >&2
|
|
exit 76
|
|
elif [[ -n "$user_or_ca" ]]; then
|
|
ca="$user_or_ca"
|
|
fi
|
|
if [[ -x "$helper" || -f "$helper" ]]; then
|
|
/usr/bin/env bash "$helper" "$ca"
|
|
else
|
|
echo "skipped: $helper not found"
|
|
fi
|
|
}
|
|
|
|
install_thunderbird_extension() {
|
|
local user="$1" script=/usr/share/BastionGuard/data/extension/bastionguard-tb-extension/install-tb-extension.sh
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
if [[ -f "$script" ]]; then
|
|
run_as_user "$user" /usr/bin/env bash "$script"
|
|
else
|
|
echo "skipped: $script not found"
|
|
fi
|
|
}
|
|
|
|
apply_web_config() {
|
|
local http="$1" https="$2" os="${3:-auto}" distro websrv tmpfile target_conf site_avail site_enable
|
|
valid_port "$http" || { echo "Invalid HTTP port" >&2; exit 64; }
|
|
valid_port "$https" || { echo "Invalid HTTPS port" >&2; exit 64; }
|
|
|
|
if [[ -z "$os" || "$os" == auto ]]; then
|
|
distro="$(detect_distro_src)"
|
|
else
|
|
distro="${os,,}"
|
|
fi
|
|
websrv="$(detect_webserver_src)"
|
|
echo "[detect_distro] Distribuzione rilevata: $distro"
|
|
echo "[detect_webserver] Server rilevato: $websrv"
|
|
|
|
if [[ "$websrv" != nginx && ! -x /usr/sbin/nginx && ! -x /usr/bin/nginx ]] && ! command -v nginx >/dev/null 2>&1; then
|
|
echo "Nessun server NGINX rilevato sul sistema." >&2
|
|
exit 69
|
|
fi
|
|
|
|
echo "[BastionGuard] Applicazione configurazione NGINX..."
|
|
write_system_file /etc/BastionGuard/webports.conf 0644 "$http $https
|
|
"
|
|
|
|
tmpfile="$(mktemp /tmp/BastionGuard_nginx.XXXXXX.conf)"
|
|
trap 'rm -f "$tmpfile"' RETURN
|
|
make_bastionguard_nginx_conf "$http" "$https" "/srv/http/webui" > "$tmpfile"
|
|
|
|
if [[ "$distro" == arch || "$distro" == manjaro || "$distro" == endeavour ]]; then
|
|
site_avail=/etc/nginx/sites-available/default
|
|
site_enable=/etc/nginx/sites-enabled/default
|
|
echo "[BastionGuard] Installo configurazione Arch-based..."
|
|
mkdir -p /etc/nginx/sites-available /etc/nginx/sites-enabled
|
|
install -m 0644 "$tmpfile" "$site_avail"
|
|
ln -sf "$site_avail" "$site_enable"
|
|
target_conf="$site_avail"
|
|
else
|
|
echo "[BastionGuard] Verifica configurazione esistente..."
|
|
if [[ -f /etc/nginx/nginx.conf ]] && ! grep -q 'BastionGuard Local Web Server' /etc/nginx/nginx.conf 2>/dev/null; then
|
|
cp -a /etc/nginx/nginx.conf "/etc/nginx/nginx.conf.bak.$(date +%s)" 2>/dev/null || true
|
|
echo "[BastionGuard] Backup nginx.conf creato; installo il vhost BastionGuard separato."
|
|
fi
|
|
if [[ -d /etc/nginx/conf.d ]]; then
|
|
target_conf=/etc/nginx/conf.d/BastionGuard.conf
|
|
else
|
|
mkdir -p /etc/nginx/sites-enabled
|
|
target_conf=/etc/nginx/sites-enabled/BastionGuard.conf
|
|
fi
|
|
install -m 0644 "$tmpfile" "$target_conf"
|
|
fi
|
|
|
|
if nginx -t; then
|
|
systemctl restart nginx
|
|
echo "Configurazione NGINX aggiornata e servizio riavviato: $target_conf"
|
|
else
|
|
echo "Errore: nginx -t non riuscito; configurazione scritta in $target_conf" >&2
|
|
exit 70
|
|
fi
|
|
}
|
|
|
|
restart_user_service() {
|
|
local user="$1" unit="$2"
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
valid_unit_name "$unit" || { echo "Invalid unit: $unit" >&2; exit 64; }
|
|
if webui_disabled_unit "$unit"; then
|
|
echo "BastionGuard WebUI: blocked restart for $unit. the unit is disabled in the server WebUI." >&2
|
|
exit 76
|
|
fi
|
|
guard_proxy_desktop_conflict "$user" "$unit" restart
|
|
run_as_user "$user" systemctl --user restart "$unit" || true
|
|
}
|
|
|
|
|
|
journal_logs() {
|
|
local mode="$1" user="$2" lines="$3" unit out
|
|
shift 3
|
|
[[ "$mode" == "system" || "$mode" == "user" ]] || { echo "Invalid journal mode" >&2; exit 64; }
|
|
[[ "$lines" =~ ^[0-9]+$ ]] || { echo "Invalid line count" >&2; exit 64; }
|
|
(( lines >= 1 )) || lines=1
|
|
(( lines <= 2000 )) || lines=2000
|
|
if [[ "$mode" == "user" ]]; then
|
|
valid_user "$user" || { echo "Invalid user: $user" >&2; exit 67; }
|
|
fi
|
|
[[ $# -ge 1 ]] || { echo "No journal unit specified" >&2; exit 64; }
|
|
command -v journalctl >/dev/null 2>&1 || { echo "journalctl not found" >&2; exit 127; }
|
|
for unit in "$@"; do
|
|
valid_unit_name "$unit" || { echo "Invalid unit: $unit" >&2; continue; }
|
|
printf '### %s\n' "$unit"
|
|
if [[ "$mode" == "user" ]]; then
|
|
out="$(run_as_user "$user" journalctl --user -u "$unit" -n "$lines" --no-pager --output=short-iso 2>&1 || true)"
|
|
else
|
|
out="$(journalctl -u "$unit" -n "$lines" --no-pager --output=short-iso 2>&1 || true)"
|
|
fi
|
|
if [[ -n "$out" ]]; then
|
|
printf '%s\n' "$out"
|
|
else
|
|
printf '(no log entries)\n'
|
|
fi
|
|
printf '\n'
|
|
done
|
|
}
|
|
|
|
[[ $# -ge 1 ]] || usage
|
|
cmd="$1"; shift
|
|
case "$cmd" in
|
|
ensure-configs) [[ $# -eq 1 ]] || usage; ensure_configs "$1" ;;
|
|
first-run-config) [[ $# -eq 2 ]] || usage; first_run_config "$1" "$2" ;;
|
|
list-configs) [[ $# -eq 1 ]] || usage; list_configs "$1" ;;
|
|
read-config-b64) [[ $# -ge 1 && $# -le 2 ]] || usage; read_config_b64 "$@" ;;
|
|
write-config) [[ $# -ge 2 && $# -le 3 ]] || usage; write_config "$@" ;;
|
|
scan-path-b64) [[ $# -eq 1 ]] || usage; scan_path_b64 "$1" ;;
|
|
samba-scan-b64) [[ $# -eq 2 ]] || usage; samba_scan_b64 "$1" "$2" ;;
|
|
quarantine-path-b64) [[ $# -eq 2 ]] || usage; quarantine_path_b64 "$1" "$2" ;;
|
|
restore-quarantine-b64) [[ $# -eq 3 ]] || usage; restore_quarantine_b64 "$1" "$2" "$3" ;;
|
|
delete-quarantine-b64) [[ $# -eq 2 ]] || usage; delete_quarantine_b64 "$1" "$2" ;;
|
|
inotify-start) [[ $# -eq 2 ]] || usage; inotify_start "$1" "$2" ;;
|
|
inotify-save) [[ $# -eq 2 ]] || usage; inotify_save_paths "$1" "$2" ;;
|
|
inotify-service-start) [[ $# -eq 2 ]] || usage; inotify_service_start "$1" "$2" ;;
|
|
inotify-service-stop) [[ $# -eq 1 ]] || usage; inotify_service_stop "$1" ;;
|
|
inotify-service-status) [[ $# -eq 1 ]] || usage; inotify_service_status "$1" ;;
|
|
inotify-start-saved) [[ $# -eq 1 ]] || usage; inotify_start_saved "$1" ;;
|
|
inotify-stop) [[ $# -eq 1 ]] || usage; inotify_stop "$1" ;;
|
|
inotify-status) [[ $# -eq 1 ]] || usage; inotify_status "$1" ;;
|
|
inotify-events) [[ $# -eq 2 ]] || usage; inotify_events "$1" "$2" ;;
|
|
ransomware-realtime-start) [[ $# -eq 1 ]] || usage; ransomware_realtime_start "$1" ;;
|
|
import-ransomware-events) [[ $# -eq 0 ]] || usage; import_ransomware_events ;;
|
|
backup-action) echo "BastionGuard WebUI: backup is disabled on the server WebUI. Use the GTK desktop backup client." >&2; exit 76 ;;
|
|
journal) [[ $# -ge 4 ]] || usage; journal_logs "$@" ;;
|
|
service-action) [[ $# -eq 4 ]] || usage; service_action "$@" ;;
|
|
update-yara) [[ $# -eq 1 ]] || usage; update_yara "$1" ;;
|
|
update-sanesecurity) [[ $# -eq 1 ]] || usage; update_sanesecurity "$1" ;;
|
|
update-phishing) [[ $# -eq 1 ]] || usage; update_phishing "$1" ;;
|
|
update-banks) [[ $# -ge 1 && $# -le 2 ]] || usage; update_banks "$@" ;;
|
|
phish-auto-update) [[ $# -eq 1 ]] || usage; phish_auto_update "$1" ;;
|
|
read-version) [[ $# -eq 0 ]] || usage; read_version ;;
|
|
desktop-mode) [[ $# -eq 1 ]] || usage; desktop_session_summary "$1" ;;
|
|
read-data-file-b64) [[ $# -eq 2 ]] || usage; read_data_file_b64 "$@" ;;
|
|
write-system-file-b64) [[ $# -eq 2 ]] || usage; write_system_file_b64 "$@" ;;
|
|
|
|
read-clamd-b64) [[ $# -eq 0 ]] || usage; read_clamd_b64 ;;
|
|
write-clamd-b64) [[ $# -eq 1 ]] || usage; write_clamd_b64 "$1" ;;
|
|
install-secure-ca) [[ $# -ge 0 && $# -le 1 ]] || usage; install_secure_ca "${1:-}" ;;
|
|
install-thunderbird-extension) [[ $# -eq 1 ]] || usage; install_thunderbird_extension "$1" ;;
|
|
apply-web-config) [[ $# -ge 2 && $# -le 3 ]] || usage; apply_web_config "$@" ;;
|
|
restart-user-service) [[ $# -eq 2 ]] || usage; restart_user_service "$1" "$2" ;;
|
|
wizard) [[ $# -ge 3 ]] || usage; wizard "$@" ;;
|
|
*) usage ;;
|
|
esac
|