mirror of
https://gitlab.com/hacklab01/pivilion.git
synced 2026-08-06 16:08:22 +00:00
Fix captive portal detection and restore MOTD
This commit is contained in:
parent
3b7a16564b
commit
34b4c4b036
9 changed files with 153 additions and 59 deletions
|
|
@ -42,3 +42,7 @@ Use `pikey` to save upstream Wi-Fi credentials, then `onion` and reboot to host
|
|||
through Tor. Use `hotspot` and reboot to run the local captive gallery at
|
||||
`http://10.1.1.1/`. HTTPS interception is intentionally not attempted because a
|
||||
local captive portal cannot present valid certificates for arbitrary sites.
|
||||
|
||||
If a phone joins but does not open its captive-login window, run
|
||||
`pivilion-diagnose` on the Pi to verify DHCP/DNS, Apache rewrite handling,
|
||||
nftables, and the common Android, Apple, and Microsoft HTTP probes.
|
||||
|
|
|
|||
12
etc/profile.d/pivilion-motd.sh
Normal file
12
etc/profile.d/pivilion-motd.sh
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Show the Pivilion status banner for the configured owner on interactive Bash
|
||||
# login shells. Other users and non-interactive sessions remain unchanged.
|
||||
[ -n "${BASH_VERSION:-}" ] || return 0
|
||||
case $- in *i*) ;; *) return 0 ;; esac
|
||||
[ -r /usr/local/lib/pivilion/common.sh ] || return 0
|
||||
[ "${PIVILION_MOTD_SHOWN:-0}" != 1 ] || return 0
|
||||
|
||||
. /usr/local/lib/pivilion/common.sh
|
||||
pivilion_load_config >/dev/null 2>&1 || return 0
|
||||
[ "$(id -un)" = "$PIVILION_USER" ] || return 0
|
||||
pivilion_print_motd
|
||||
export PIVILION_MOTD_SHOWN=1
|
||||
|
|
@ -112,27 +112,13 @@ if ! shopt -oq posix; then
|
|||
fi
|
||||
fi
|
||||
|
||||
# info on login
|
||||
|
||||
let upSeconds="$(/usr/bin/cut -d. -f1 /proc/uptime)"
|
||||
let secs=$((${upSeconds}%60))
|
||||
let mins=$((${upSeconds}/60%60))
|
||||
let hours=$((${upSeconds}/3600%24))
|
||||
let days=$((${upSeconds}/86400))
|
||||
UPTIME=`printf "%d days, %02dh%02dm%02ds" "$days" "$hours" "$mins" "$secs"`
|
||||
|
||||
# get the load averages
|
||||
read one five fifteen rest < /proc/loadavg
|
||||
echo "
|
||||
@@@@@@@@@@@@@@@@@@@ `date +"%A, %e %B %Y, %r"`
|
||||
@@@@@@, &@& *@@@@@@ `uname -srnmo`
|
||||
@@@@@@@. .@@@@@@@ Uptime.............: ${UPTIME}
|
||||
@@@@ .@@@. @@@@ Memory.............: `cat /proc/meminfo | grep MemFree | awk {'print $2'}`kB (Free) / `cat /proc/meminfo | grep MemTotal | awk {'print $2'}`kB (Total)
|
||||
@ &@@@@.@@@@# @ Load Averages......: ${one}, ${five}, ${fifteen} (1, 5, 15 min)
|
||||
@ @ . #@@@# . @ @ Running Processes..: `ps ax | wc -l | tr -d " "`
|
||||
@ @@@, # ,@@@ @ Manual / more info.: pivilion.net
|
||||
@@, @ ,@@ Onion Address......: `cat /home/pi/pivilion/torname`
|
||||
@@@@@#@@@@@@@#@@@@@ Run "pivilion" for inital setup! / Running in `cat /home/pi/pivilion/mode` mode
|
||||
"
|
||||
# Pivilion login banner. The modern installer deploys the same hook through
|
||||
# /etc/profile.d; this fallback keeps the legacy filesystem overlay usable.
|
||||
if [ "${PIVILION_MOTD_SHOWN:-0}" != 1 ] && [ -r /usr/local/lib/pivilion/common.sh ]; then
|
||||
. /usr/local/lib/pivilion/common.sh
|
||||
if pivilion_load_config >/dev/null 2>&1 && [ "$(id -un)" = "$PIVILION_USER" ]; then
|
||||
pivilion_print_motd
|
||||
export PIVILION_MOTD_SHOWN=1
|
||||
fi
|
||||
fi
|
||||
export LC_ALL=C
|
||||
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
RewriteEngine on
|
||||
RewriteCond %{REQUEST_URI} !(\/index\.php)$
|
||||
RewriteCond %{REQUEST_URI} !(\/pi-logo_128\.png)$
|
||||
RewriteCond %{REQUEST_URI} !(\/content\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/index\.php)$
|
||||
RewriteCond %{REQUEST_URI} !(\/pi-logo_128\.png)$
|
||||
RewriteCond %{REQUEST_URI} !(\/content\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/css\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/doc\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/docker\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/img\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/js\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/modules\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/tests\/.*)$
|
||||
RewriteCond %{REQUEST_URI} !(\/*.php)$
|
||||
RewriteCond %{REQUEST_URI} !(\/*.head.*)$
|
||||
RewriteRule ^(.*)$ http://10.1.1.1/index.php [L,R=301]
|
||||
|
||||
# Captive-network probes use otherwise unknown paths such as /generate_204,
|
||||
# /hotspot-detect.html, and /connecttest.txt. Redirect those temporarily so
|
||||
# clients open their login assistant, but let the gallery serve its own PHP and
|
||||
# asset paths without a loop.
|
||||
RewriteCond %{REQUEST_URI} !\.php$ [NC]
|
||||
RewriteCond %{REQUEST_URI} !\.head(?:\.|$) [NC]
|
||||
RewriteCond %{REQUEST_URI} !^/(?:content|css|doc|docker|img|images|js|modules|tests|upload)(?:/|$) [NC]
|
||||
RewriteCond %{REQUEST_URI} !^/pi-logo_128\.png$ [NC]
|
||||
RewriteRule ^ http://10.1.1.1/index.php [L,R=302,NE]
|
||||
|
|
|
|||
16
install.sh
16
install.sh
|
|
@ -1,15 +1,16 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
SOURCE_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
source "$SOURCE_DIR/usr/local/lib/pivilion/common.sh"
|
||||
pivilion_load_config
|
||||
pivilion_print_motd "Pivilion installer"
|
||||
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
echo "Run this installer with sudo: sudo ./install.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_DIR=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
source "$SOURCE_DIR/usr/local/lib/pivilion/common.sh"
|
||||
pivilion_load_config
|
||||
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
is_networkmanager_system() {
|
||||
|
|
@ -20,7 +21,7 @@ is_networkmanager_system() {
|
|||
}
|
||||
|
||||
install_packages() {
|
||||
local packages=(apache2 libapache2-mod-php php php-cli tor dnsmasq nftables iproute2 iw rfkill wget unzip wpasupplicant)
|
||||
local packages=(apache2 libapache2-mod-php php php-cli tor dnsmasq dnsutils nftables iproute2 iw rfkill curl wget unzip wpasupplicant)
|
||||
apt-get update
|
||||
apt-get install -y "${packages[@]}"
|
||||
if is_networkmanager_system; then
|
||||
|
|
@ -37,13 +38,14 @@ install_packages() {
|
|||
|
||||
deploy_files() {
|
||||
local php_version
|
||||
install -d -m 0755 /usr/local/lib/pivilion /usr/local/bin /etc/systemd/system /etc/tor/torrc.d
|
||||
install -d -m 0755 /usr/local/lib/pivilion /usr/local/bin /etc/systemd/system /etc/tor/torrc.d /etc/profile.d
|
||||
install -m 0644 "$SOURCE_DIR/usr/local/lib/pivilion/common.sh" /usr/local/lib/pivilion/common.sh
|
||||
install -m 0755 "$SOURCE_DIR/usr/local/lib/pivilion/mode.sh" /usr/local/lib/pivilion/mode.sh
|
||||
install -m 0755 "$SOURCE_DIR"/usr/local/bin/* /usr/local/bin/
|
||||
install -m 0644 "$SOURCE_DIR/etc/systemd/system/pivilion-mode.service" /etc/systemd/system/pivilion-mode.service
|
||||
install -m 0644 "$SOURCE_DIR/etc/systemd/system/pivilion-dnsmasq.service" /etc/systemd/system/pivilion-dnsmasq.service
|
||||
install -m 0644 "$SOURCE_DIR/etc/tor/torrc.d/pivilion.conf" /etc/tor/torrc.d/pivilion.conf
|
||||
install -m 0644 "$SOURCE_DIR/etc/profile.d/pivilion-motd.sh" /etc/profile.d/pivilion-motd.sh
|
||||
|
||||
cat > /etc/pivilion.defaults <<EOF
|
||||
PIVILION_USER=$PIVILION_USER
|
||||
|
|
@ -122,7 +124,7 @@ validate_install() {
|
|||
dnsmasq --test
|
||||
bash -n /usr/local/lib/pivilion/common.sh /usr/local/lib/pivilion/mode.sh /usr/local/bin/pivilion \
|
||||
/usr/local/bin/hotspot /usr/local/bin/onion /usr/local/bin/pikey /usr/local/bin/generator \
|
||||
/usr/local/bin/static /usr/local/bin/hotglue /usr/local/bin/htaccess
|
||||
/usr/local/bin/static /usr/local/bin/hotglue /usr/local/bin/htaccess /usr/local/bin/pivilion-diagnose
|
||||
}
|
||||
|
||||
install_packages
|
||||
|
|
|
|||
|
|
@ -3,21 +3,11 @@ set -euo pipefail
|
|||
source /usr/local/lib/pivilion/common.sh
|
||||
pivilion_load_config
|
||||
|
||||
mode=$(pivilion_current_mode)
|
||||
torname="not generated yet"
|
||||
[ ! -s "$PIVILION_DIR/torname" ] || torname=$(tr -d '\r\n' < "$PIVILION_DIR/torname")
|
||||
uptime_text=$(uptime -p 2>/dev/null || true)
|
||||
pivilion_print_motd
|
||||
|
||||
cat <<EOF
|
||||
|
||||
Welcome to Pivilion!
|
||||
|
||||
Host...............: $(uname -n)
|
||||
System.............: $(uname -srmo)
|
||||
Uptime.............: ${uptime_text:-unknown}
|
||||
Mode after reboot..: $mode
|
||||
Onion address......: $torname
|
||||
Web root...........: $PIVILION_WEBROOT
|
||||
Web root: $PIVILION_WEBROOT
|
||||
|
||||
Commands:
|
||||
pivilion show this status
|
||||
|
|
@ -28,4 +18,5 @@ Commands:
|
|||
static convert the current gallery to static files
|
||||
hotglue install or restore Hotglue
|
||||
htaccess reset or remove captive redirect rules
|
||||
pivilion-diagnose check captive DNS, HTTP redirects, and services
|
||||
EOF
|
||||
|
|
|
|||
4
usr/local/bin/pivilion-diagnose
Executable file
4
usr/local/bin/pivilion-diagnose
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
[ "$(id -u)" -eq 0 ] || exec sudo "$0" "$@"
|
||||
exec /usr/local/lib/pivilion/mode.sh diagnose
|
||||
|
|
@ -111,3 +111,30 @@ pivilion_current_mode() {
|
|||
pivilion_require_root() {
|
||||
[ "$(id -u)" -eq 0 ] || { echo "This operation must run as root (use sudo)." >&2; return 1; }
|
||||
}
|
||||
|
||||
pivilion_print_motd() {
|
||||
local heading=${1:-Welcome to Pivilion!}
|
||||
local uptime_text memory_free memory_total load_one load_five load_fifteen processes torname mode
|
||||
|
||||
uptime_text=$(uptime -p 2>/dev/null || printf 'unknown')
|
||||
memory_free=$(awk '/^MemAvailable:/ {print $2; exit}' /proc/meminfo 2>/dev/null)
|
||||
memory_total=$(awk '/^MemTotal:/ {print $2; exit}' /proc/meminfo 2>/dev/null)
|
||||
read -r load_one load_five load_fifteen _ < /proc/loadavg
|
||||
processes=$(ps ax --no-headers 2>/dev/null | wc -l)
|
||||
torname=not-generated-yet
|
||||
[ ! -s "$PIVILION_DIR/torname" ] || torname=$(tr -d '\r\n' < "$PIVILION_DIR/torname")
|
||||
mode=$(pivilion_current_mode)
|
||||
|
||||
printf '\n%s\n' "$heading"
|
||||
cat <<EOF
|
||||
@@@@@@@@@@@@@@@@@@@ $(date '+%A, %e %B %Y, %r')
|
||||
@@@@@@, &@& *@@@@@@ $(uname -srnmo)
|
||||
@@@@@@@. .@@@@@@@ Uptime.............: $uptime_text
|
||||
@@@@ .@@@. @@@@ Memory.............: ${memory_free:-unknown}kB (Available) / ${memory_total:-unknown}kB (Total)
|
||||
@ &@@@@.@@@@# @ Load Averages......: $load_one, $load_five, $load_fifteen (1, 5, 15 min)
|
||||
@ @ . #@@@# . @ @ Running Processes..: $processes
|
||||
@ @@@, # ,@@@ @ Manual / more info.: pivilion.net
|
||||
@@, @ ,@@ Onion Address......: $torname
|
||||
@@@@@#@@@@@@@#@@@@@ Running in $mode mode
|
||||
EOF
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,77 @@ IFACE=$(pivilion_wifi_interface)
|
|||
RUN_DIR=/run/pivilion
|
||||
DNSMASQ_CONF=/etc/dnsmasq.d/pivilion-runtime.conf
|
||||
NM_HOTSPOT=pivilion-hotspot
|
||||
CAPTIVE_TEMPLATE=$PIVILION_DIR/config/html/.htaccess
|
||||
CAPTIVE_LIVE=$PIVILION_WEBROOT/gen/.htaccess
|
||||
|
||||
install_captive_redirect() {
|
||||
local tmp
|
||||
[ -r "$CAPTIVE_TEMPLATE" ] || {
|
||||
echo "Captive redirect template is missing: $CAPTIVE_TEMPLATE" >&2
|
||||
return 1
|
||||
}
|
||||
[ -d "$PIVILION_WEBROOT/gen" ] || {
|
||||
echo "Gallery webroot is missing: $PIVILION_WEBROOT/gen" >&2
|
||||
return 1
|
||||
}
|
||||
tmp=$(mktemp "$PIVILION_WEBROOT/gen/.htaccess.XXXXXX")
|
||||
if ! install -m 0644 -o root -g www-data "$CAPTIVE_TEMPLATE" "$tmp"; then
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
fi
|
||||
mv -f "$tmp" "$CAPTIVE_LIVE"
|
||||
}
|
||||
|
||||
diagnostic_result() {
|
||||
local label=$1
|
||||
shift
|
||||
if "$@"; then
|
||||
printf ' [ok] %s\n' "$label"
|
||||
return 0
|
||||
fi
|
||||
printf ' [fail] %s\n' "$label" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
probe_redirect() {
|
||||
local host=$1 path=$2 headers
|
||||
headers=$(curl --silent --show-error --max-time 5 --dump-header - --output /dev/null \
|
||||
--header "Host: $host" "http://10.1.1.1$path") || return 1
|
||||
grep -Eq '^HTTP/[0-9.]+ 302([[:space:]]|$)' <<<"$headers" &&
|
||||
grep -Eiq '^Location:[[:space:]]*http://10\.1\.1\.1/index\.php\r?$' <<<"$headers"
|
||||
}
|
||||
|
||||
dns_points_to_portal() {
|
||||
local host=$1 answer
|
||||
answer=$(dig +short A @10.1.1.1 "$host" 2>/dev/null | tail -n1)
|
||||
[ "$answer" = 10.1.1.1 ]
|
||||
}
|
||||
|
||||
apache_rewrite_enabled() {
|
||||
apache2ctl -M 2>/dev/null | grep -q 'rewrite_module'
|
||||
}
|
||||
|
||||
nft_portal_table_active() {
|
||||
nft list table inet pivilion >/dev/null 2>&1
|
||||
}
|
||||
|
||||
hotspot_diagnostics() {
|
||||
local failures=0
|
||||
echo "Pivilion hotspot diagnostics:"
|
||||
diagnostic_result "Apache is active" systemctl is-active --quiet apache2 || failures=$((failures + 1))
|
||||
diagnostic_result "Apache rewrite module is enabled" apache_rewrite_enabled || failures=$((failures + 1))
|
||||
diagnostic_result "live captive redirect matches its template" cmp -s "$CAPTIVE_TEMPLATE" "$CAPTIVE_LIVE" || failures=$((failures + 1))
|
||||
diagnostic_result "Pivilion dnsmasq is active" systemctl is-active --quiet pivilion-dnsmasq.service || failures=$((failures + 1))
|
||||
diagnostic_result "captive DNS resolves Android probe" dns_points_to_portal connectivitycheck.gstatic.com || failures=$((failures + 1))
|
||||
diagnostic_result "Pivilion nftables table is active" nft_portal_table_active || failures=$((failures + 1))
|
||||
diagnostic_result "Android probe receives portal redirect" probe_redirect connectivitycheck.gstatic.com /generate_204 || failures=$((failures + 1))
|
||||
diagnostic_result "Apple probe receives portal redirect" probe_redirect captive.apple.com /hotspot-detect.html || failures=$((failures + 1))
|
||||
diagnostic_result "Microsoft probe receives portal redirect" probe_redirect www.msftconnecttest.com /connecttest.txt || failures=$((failures + 1))
|
||||
[ "$failures" -eq 0 ] || {
|
||||
echo "$failures hotspot diagnostic check(s) failed." >&2
|
||||
return 1
|
||||
}
|
||||
}
|
||||
|
||||
stop_hotspot() {
|
||||
systemctl stop pivilion-dnsmasq.service 2>/dev/null || true
|
||||
|
|
@ -56,6 +127,7 @@ EOF
|
|||
|
||||
start_hotspot() {
|
||||
stop_hotspot
|
||||
install_captive_redirect
|
||||
rfkill unblock wifi 2>/dev/null || true
|
||||
systemctl start apache2
|
||||
if pivilion_uses_networkmanager; then
|
||||
|
|
@ -87,6 +159,7 @@ EOF
|
|||
dnsmasq --test --conf-file="$DNSMASQ_CONF"
|
||||
systemctl restart pivilion-dnsmasq.service
|
||||
start_firewall
|
||||
hotspot_diagnostics
|
||||
echo "Pivilion hotspot '$PIVILION_HOTSPOT_SSID' is active on $IFACE (10.1.1.1)."
|
||||
}
|
||||
|
||||
|
|
@ -120,7 +193,7 @@ start_onion() {
|
|||
systemctl restart dhcpcd 2>/dev/null || true
|
||||
systemctl restart "wpa_supplicant@$IFACE.service" 2>/dev/null || systemctl restart wpa_supplicant 2>/dev/null || true
|
||||
fi
|
||||
rm -f "$PIVILION_WEBROOT/gen/.htaccess"
|
||||
rm -f "$CAPTIVE_LIVE"
|
||||
systemctl start apache2
|
||||
if systemctl cat tor@default.service >/dev/null 2>&1; then
|
||||
systemctl restart tor@default.service
|
||||
|
|
@ -134,5 +207,6 @@ start_onion() {
|
|||
case "$MODE" in
|
||||
hotspot) start_hotspot ;;
|
||||
onion) start_onion ;;
|
||||
diagnose) hotspot_diagnostics ;;
|
||||
*) echo "Unknown Pivilion mode: $MODE" >&2; exit 2 ;;
|
||||
esac
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue