#!/bin/bash # # Wifi Hotspot app for YunoHost # Copyright (C) 2015 Julien Vaubourg # Contribute at https://github.com/labriqueinternet/hotspot_ynh # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # Functions ## State functions has_vpnclient_app() { [ -e /tmp/.ynh-vpnclient-started ] } has_ip6delegatedprefix() { i=${1} [ "${ynh_ip6_net[${i}]}" != none ] } is_nat_set() { internet_device=${1} iptables -w -nvt nat -L POSTROUTING | grep MASQUERADE | grep -q "${internet_device}" } is_ip4nataddr_set() { i=${1} dev=$(devfromid "${i}") ip address show dev "${dev}" 2> /dev/null | grep -q "${ynh_ip4_nat_prefix[${i}]}.1/24" } is_ip6addr_set() { i=${1} dev=$(devfromid "${i}") ip address show dev "${dev}" 2> /dev/null | grep -q "${ynh_ip6_addr[${i}]}/64" } is_ip6firewall_set() { i=${1} dev=$(devfromid "${i}") ip6tables -w -nvL FORWARD | grep DROP | grep -q "${dev}" } is_forwarding_set() { ip6=$(sysctl net.ipv6.conf.all.forwarding | awk '{ print $NF; }') ip4=$(sysctl net.ipv4.conf.all.forwarding | awk '{ print $NF; }') [ "${ip6}" -eq 1 -a "${ip4}" -eq 1 ] } is_dhcpd6_running() { i=${1} $(ps aux | grep "dhcpdv6-ssid${i}" | grep -qv grep) } is_dhcpd4_running() { i=${1} $(ps aux | grep "dhcpdv4-ssid${i}" | grep -qv grep) } is_hostapd_running() { systemctl is-active hostapd &> /dev/null } is_running() { for i in $(seq 0 $((${ynh_multissid} - 1))); do ( has_ip6delegatedprefix ${i} && is_ip6addr_set ${i}\ && ( [ "${ynh_ip6_firewall[${i}]}" -eq 1 ] && is_ip6firewall_set ${i} || [ "${ynh_ip6_firewall[${i}]}" -eq 0 ] )\ && is_dhcpd6_running ${i} || ! has_ip6delegatedprefix ${i} )\ && is_ip4nataddr_set ${i} && is_dhcpd4_running ${i} if [ ! $? -eq 0 ]; then return 1 fi done is_hostapd_running && is_forwarding_set && ( [ -z "${new_internet_device}" ] || is_nat_set "${new_internet_device}" ) } ## Setters set_nat() { internet_device=${1} iptables -w -t nat -A POSTROUTING -o "${internet_device}" -j MASQUERADE } set_ip4nataddr() { i=${1} dev=$(devfromid "${i}") ip address add "${ynh_ip4_nat_prefix[${i}]}.1/24" dev "${dev}" } set_ip6addr() { i=${1} dev=$(devfromid "${i}") ip address delete "${ynh_ip6_addr[${i}]}/64" dev tun0 &> /dev/null ip address add "${ynh_ip6_addr[${i}]}/64" dev "${dev}" } set_ip6firewall() { i=${1} dev=$(devfromid "${i}") ip6tables -w -A FORWARD -i "${dev}" -j ACCEPT ip6tables -w -A FORWARD -o "${dev}" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -w -A FORWARD -o "${dev}" -j DROP } set_forwarding() { sysctl -w net.ipv6.conf.all.forwarding=1 > /dev/null sysctl -w net.ipv4.conf.all.forwarding=1 > /dev/null } start_dhcpd6() { i=${1} dev=$(devfromid "${i}") cp /etc/dnsmasq.dhcpd/dhcpdv6{.conf.tpl,-ssid${i}.conf} sed "s||${dev}|g" -i /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf sed "s||${ynh_ip6_net[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf sed "s||${ynh_ip6_dns0[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf sed "s||${ynh_ip6_dns1[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf dnsmasq -C /etc/dnsmasq.dhcpd/dhcpdv6-ssid${i}.conf -p0 } start_dhcpd4() { i=${1} dev=$(devfromid "${i}") cp /etc/dnsmasq.dhcpd/dhcpdv4{.conf.tpl,-ssid${i}.conf} sed "s||${ynh_ip4_dns0[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf sed "s||${ynh_ip4_dns1[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf sed "s||${dev}|g" -i /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf sed "s||${ynh_ip4_nat_prefix[${i}]}|g" -i /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf dnsmasq -C /etc/dnsmasq.dhcpd/dhcpdv4-ssid${i}.conf -p0 } start_hostapd() { cp /etc/hostapd/hostapd.conf{.tpl1,} ethaddr=$(ip link show dev "${ynh_wifi_device}" | grep link/ether | awk -F: '{ printf "02:%s:%s:%s:%s:00", $2, $3, $4, $5 }') ip link set addr "${ethaddr}" dev "${ynh_wifi_device}" sed "s||${ynh_wifi_device}|g" -i /etc/hostapd/hostapd.conf sed "s||${ynh_wifi_channel}|g" -i /etc/hostapd/hostapd.conf sed "s|||g" -i /etc/hostapd/hostapd.conf for i in $(seq 0 $((${ynh_multissid} - 1))); do cp /etc/hostapd/hostapd.conf{.tpl2,.tmp} sed "s||hotspot${i}|g" -i /etc/hostapd/hostapd.conf.tmp sed "s||${ynh_wifi_ssid[${i}]}|g" -i /etc/hostapd/hostapd.conf.tmp sed "s||${ynh_wifi_passphrase[${i}]}|g" -i /etc/hostapd/hostapd.conf.tmp if [ "${ynh_wifi_secure[${i}]}" -eq 1 ]; then sed "s|||g" -i /etc/hostapd/hostapd.conf.tmp else sed "s||#|g" -i /etc/hostapd/hostapd.conf.tmp fi if [ "${i}" -eq 0 ]; then sed "s||#|g" -i /etc/hostapd/hostapd.conf.tmp else sed "s|||g" -i /etc/hostapd/hostapd.conf.tmp fi cat /etc/hostapd/hostapd.conf.tmp >> /etc/hostapd/hostapd.conf rm /etc/hostapd/hostapd.conf.tmp done systemctl start hostapd } ## Unsetters unset_nat() { internet_device=${1} iptables -w -t nat -D POSTROUTING -o "${internet_device}" -j MASQUERADE } unset_ip4nataddr() { i=${1} dev=$(devfromid "${i}") ip address delete "${ynh_ip4_nat_prefix[${i}]}.1/24" dev "${dev}" } unset_ip6addr() { i=${1} dev=$(devfromid "${i}") ip address delete "${ynh_ip6_addr[${i}]}/64" dev "${dev}" } unset_ip6firewall() { i=${1} dev=$(devfromid "${i}") ip6tables -w -D FORWARD -i "${dev}" -j ACCEPT ip6tables -w -D FORWARD -o "${dev}" -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT ip6tables -w -D FORWARD -o "${dev}" -j DROP } unset_forwarding() { sysctl -w net.ipv6.conf.all.forwarding=0 > /dev/null sysctl -w net.ipv4.conf.all.forwarding=0 > /dev/null } stop_dhcpd6() { kill $(ps aux | grep 'dhcpdv6-ssid' | grep -v grep | awk '{ print $2 }') rm -f /etc/dnsmasq.d/dhcpdv6-ssid*.conf } stop_dhcpd4() { kill $(ps aux | grep 'dhcpdv4-ssid' | grep -v grep | awk '{ print $2 }') rm -f /etc/dnsmasq.d/dhcpdv4-ssid*.conf } stop_hostapd() { systemctl stop hostapd } ## Tools ynh_setting_get() { app=${1} setting=${2} grep "^${setting}:" "/etc/yunohost/apps/${app}/settings.yml" | sed s/^[^:]\\+:\\s*[\"\']\\?// | sed s/\\s*[\"\']\$// } ynh_setting_set() { # This is a partial copypasta of the official ynh_app_setting internal helper # In particular, we do this instead of relying on 'yunohost app setting' for # performance reasons (it takes a few second to run every yunohost commands) # and to remove the need for the infamous '--need-lock' option/issue. app="$1" key="$2" value="${3:-}" python2.7 - < /dev/null if [ "$?" -eq 0 ]; then new_internet_device=tun0 fi echo "OK" fi # Script case "$1" in start) if is_running; then echo "Already started" elif [ "${ynh_service_enabled}" != "enabled" ]; then echo "Disabled service" else if [ -z "${ynh_wifi_device}" ]; then echo "[ERR] No wifi device selected. Make sure your wifi antenna is plugged-in / available and select it in the Hotspot admin" exitcode=1 fi echo "[hotspot] Starting..." touch /tmp/.ynh-hotspot-started if [ "${new_internet_device}" == tun0 ]; then ynh_setting_set hotspot vpnclient yes else ynh_setting_set hotspot vpnclient no fi # Check old state of the ipv4 NAT settings if [ ! -z "${old_internet_device}" -a "${new_internet_device}" != "${old_internet_device}" ]\ && is_nat_set "${old_internet_device}"; then unset_nat "${old_internet_device}" fi # Set ipv4 NAT if [ ! -z "${new_internet_device}" ] && ! is_nat_set "${new_internet_device}"; then echo "Set NAT" set_nat "${new_internet_device}" fi # Set forwarding for ipv6 and ipv4 if ! is_forwarding_set; then echo "Set forwarding" set_forwarding fi # Run hostapd if ! is_hostapd_running; then echo "Run hostapd" start_hostapd ${i} if [ ! $? -eq 0 ]; then exit 1 fi if [ "${ynh_multissid}" -gt 1 ]; then i=0; false || while [ $? -ne 0 ]; do sleep 1 && (( i++ )) [ ${i} -gt 20 ] && stop_hostapd [ ${i} -gt 20 ] && exit 1 ip link show dev hotspot1 &> /dev/null done else sleep 1 fi fi # For each registred ssid for i in $(seq 0 $((${ynh_multissid} - 1))); do # Set ipv4 NAT address if ! is_ip4nataddr_set ${i}; then echo "hotspot${i}: Set IPv4 NAT address" set_ip4nataddr ${i} fi # Set the ipv6 address if has_ip6delegatedprefix ${i} && ! is_ip6addr_set ${i}; then echo "hotspot${i}: Set IPv6 address" set_ip6addr ${i} fi # Set ipv6 firewalling if has_ip6delegatedprefix ${i} && [ "${ynh_ip6_firewall[${i}]}" -eq 1 ] && ! is_ip6firewall_set ${i}; then echo "hotspot${i}: Set IPv6 firewalling" set_ip6firewall ${i} fi # Run DHCPv6 server if has_ip6delegatedprefix ${i} && ! is_dhcpd6_running ${i}; then echo "hotspot${i}: Start the NDP and DHCPv6 server (dnsmasq)" start_dhcpd6 ${i} fi # Run DHCPv4 server if ! is_dhcpd4_running ${i}; then echo "hotspot${i}: Start the DHCPv4 server (dnsmasq)" start_dhcpd4 ${i} fi done # Update dynamic settings ynh_setting_set hotspot internet_device "${new_internet_device}" fi ;; stop) echo "[hotspot] Stopping..." rm -f /tmp/.ynh-hotspot-started if [ ! -z "${old_internet_device}" ] && is_nat_set "${old_internet_device}"; then echo "Unset NAT" unset_nat "${old_internet_device}" fi if is_forwarding_set; then echo "Unset forwarding" unset_forwarding fi for i in $(seq 0 $((${ynh_multissid} - 1))); do if is_ip4nataddr_set ${i}; then echo "hotspot${i}: Unset IPv4 NAT address" unset_ip4nataddr ${i} fi if has_ip6delegatedprefix ${i} && is_ip6addr_set ${i}; then echo "hotspot${i}: Unset IPv6 address" unset_ip6addr ${i} fi if has_ip6delegatedprefix ${i} && [ "${ynh_ip6_firewall[${i}]}" -eq 1 ] && is_ip6firewall_set ${i}; then echo "hotspot${i}: Unset IPv6 firewalling" unset_ip6firewall ${i} fi if is_dhcpd6_running ${i}; then echo "hotspot${i}: Stop the NDP and DHCPv6 server (dnsmasq)" stop_dhcpd6 ${i} fi if is_dhcpd4_running ${i}; then echo "hotspot${i}: Stop the DHCPv4 server (dnsmasq)" stop_dhcpd4 ${i} fi done if is_hostapd_running; then echo "Stop hostapd" stop_hostapd fi # Fix configuration if has_vpnclient_app; then ynh-vpnclient start fi ;; restart) $0 stop $0 start ;; status) exitcode=0 if [ "${ynh_service_enabled}" != "enabled" ]; then echo "[ERR] Hotspot Service disabled" exitcode=1 fi if [ -z "${ynh_wifi_device}" ]; then echo "[ERR] No wifi device selected. Make sure your wifi antenna is plugged-in / available and select it in the Hotspot admin" exitcode=1 fi echo "[INFO] Autodetected internet interface: ${new_internet_device} (last start: ${old_internet_device})" if is_nat_set "${new_internet_device}"; then echo "[OK] IPv4 NAT set" else if [ -z "${new_internet_device}" ]; then echo "[INFO] No IPv4 NAT set (no internet interface)" else echo "[ERR] No IPv4 NAT set" fi exitcode=1 fi if is_forwarding_set; then echo "[OK] IPv6/IPv4 forwarding set" else echo "[ERR] No IPv6/IPv4 forwarding set" exitcode=1 fi if is_hostapd_running; then echo "[OK] Hostapd is running" else echo "[ERR] Hostapd is not running" exitcode=1 fi for i in $(seq 0 $((${ynh_multissid} - 1))); do if has_ip6delegatedprefix ${i}; then echo "[INFO] hotspot${i}: IPv6 delegated prefix found" echo "[INFO] hotspot${i}: IPv6 address computed from the delegated prefix: ${ynh_ip6_addr}" if is_ip6addr_set ${i}; then echo "[OK] hotspot${i}: IPv6 address set" else echo "[ERR] hotspot${i}: No IPv6 address set" exitcode=1 fi if is_ip6firewall_set ${i}; then echo "[OK] hotspot${i}: IPv6 firewalling set" else if [ "${ynh_ip6_firewall[${i}]}" -eq 1 ]; then echo "[ERR] hotspot${i}: No IPv6 firewalling set" else echo "[INFO] hotspot${i}: No IPv6 firewalling set" fi exitcode=1 fi if is_dhcpd6_running ${i}; then echo "[OK] hotspot${i}: NDP and DHCPv6 server (dnsmasq) are running" else echo "[ERR] hotspot${i}: NDP and DHCPv6 server (dnsmasq) are not running" exitcode=1 fi else echo "[INFO] hotspot${i}: No IPv6 delegated prefix found" fi if is_dhcpd4_running ${i}; then echo "[OK] hotspot${i}: DHCPv4 server (dnsmasq) is running" else echo "[ERR] hotspot${i}: DHCPv4 (dnsmasq) is not running" exitcode=1 fi if is_ip4nataddr_set ${i}; then echo "[OK] hotspot${i}: IPv4 NAT address set" else echo "[ERR] hotspot${i}: No IPv4 NAT address set" exitcode=1 fi done exit ${exitcode} ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0