#!/bin/bash # # Wifi Hotspot app for YunoHost # Copyright (C) 2015 Julien Vaubourg # Contribute at https://github.com/jvaubourg/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 -nvt nat -L POSTROUTING | grep MASQUERADE | grep -q "${internet_device}" } is_ip4nataddr_set() { i=${1} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi ip address show dev "${dev}" 2> /dev/null | grep -q "${ynh_ip4_nat_prefix[${i}]}.1/24" } is_ip6addr_set() { i=${1} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi ip address show dev "${dev}" 2> /dev/null | grep -q "${ynh_ip6_addr[${i}]}/64" } 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} && 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 && is_nat_set "${new_internet_device}" } ## Setters set_nat() { internet_device=${1} iptables -t nat -A POSTROUTING -o "${internet_device}" -j MASQUERADE } set_ip4nataddr() { i=${1} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi ip address add "${ynh_ip4_nat_prefix[${i}]}.1/24" dev "${dev}" } set_ip6addr() { i=${1} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi ip address delete "${ynh_ip6_addr[${i}]}/64" dev tun0 &> /dev/null ip address add "${ynh_ip6_addr[${i}]}/64" dev "${dev}" } 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} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi 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} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi 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 -t nat -D POSTROUTING -o "${internet_device}" -j MASQUERADE } unset_ip4nataddr() { i=${1} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi ip address delete "${ynh_ip4_nat_prefix[${i}]}.1/24" dev "${dev}" } unset_ip6addr() { i=${1} if [ "${i}" -eq 0 ]; then dev=${ynh_wifi_device} else dev="hotspot${i}" fi ip address delete "${ynh_ip6_addr[${i}]}/64" dev "${dev}" } 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 moulinette_get() { var=${1} gotcha=0 while [ "${gotcha}" -eq 0 ]; do value=$(yunohost app setting hotspot "${var}") if [[ "${value}" =~ "An instance is already running" ]]; then sleep $(($((RANDOM%5)) + 1)) else gotcha=1 fi done echo "${value}" } moulinette_vpnclient_get() { var=${1} gotcha=0 while [ "${gotcha}" -eq 0 ]; do value=$(yunohost app setting vpnclient "${var}") if [[ "${value}" =~ "An instance is already running" ]]; then sleep $(($((RANDOM%5)) + 1)) else gotcha=1 fi done echo "${value}" } moulinette_set() { var=${1} value=${2} msg=$(yunohost app setting hotspot "${var}" -v "${value}") if [ ! $? -eq 0 ]; then echo "${msg}" >&2 exit 1 fi } if [ "$1" != restart ]; then # Restart php5-fpm at the first start (it needs to be restarted after the slapd start) if [ ! -e /tmp/.ynh-hotspot-boot ]; then touch /tmp/.ynh-hotspot-boot systemctl restart php5-fpm fi # Variables echo -n "Retrieving Yunohost settings... " ynh_service_enabled=$(moulinette_get service_enabled) ynh_wifi_device=$(moulinette_get wifi_device) ynh_wifi_channel=$(moulinette_get wifi_channel) ynh_multissid=$(moulinette_get multissid) IFS='|' read -a ynh_wifi_ssid <<< "$(moulinette_get wifi_ssid)" IFS='|' read -a ynh_wifi_secure <<< "$(moulinette_get wifi_secure)" IFS='|' read -a ynh_wifi_passphrase <<< "$(moulinette_get wifi_passphrase)" IFS='|' read -a ynh_ip6_addr <<< "$(moulinette_get ip6_addr)" IFS='|' read -a ynh_ip6_net <<< "$(moulinette_get ip6_net)" IFS='|' read -a ynh_ip6_dns0 <<< "$(moulinette_get ip6_dns0)" IFS='|' read -a ynh_ip6_dns1 <<< "$(moulinette_get ip6_dns1)" IFS='|' read -a ynh_ip4_dns0 <<< "$(moulinette_get ip4_dns0)" IFS='|' read -a ynh_ip4_dns1 <<< "$(moulinette_get ip4_dns1)" IFS='|' read -a ynh_ip4_nat_prefix <<< "$(moulinette_get ip4_nat_prefix)" old_internet_device=$(moulinette_get internet_device) new_internet_device=$(ip route | awk '/default via/ { print $NF; }') # Switch the NAT interface if there is a VPN ip link show dev tun0 &> /dev/null if [ "$?" -eq 0 ]; then new_internet_device=tun0 fi echo "OK" # Check IPv6 delegated prefix from vpnclient # vpnclient_ip6_net=$(moulinette_vpnclient_get ip6_net) # # if [ ! -z "${vpnclient_ip6_addr}" ]; then # if [ "${ynh_ip6_net}" == none ]; then # ynh_ip6_net=$vpnclient_ip6_net # ynh_ip6_addr=$(moulinette_vpnclient_get ip6_addr) # # moulinette_set ip6_net "${ynh_ip6_net}" # moulinette_set ip6_addr "${ynh_ip6_addr}" # else # if [ "${ynh_ip6_net}" != "${vpnclient_ip6_net}" ]; then # echo "[WARN] The IPv6 delegated prefix is different from the vpnclient one" # fi # fi # fi fi # Script case "$1" in start) if is_running; then echo "Already started" elif [ "${ynh_service_enabled}" -eq 0 ]; then echo "Disabled service" else echo "[hotspot] Starting..." touch /tmp/.ynh-hotspot-started if [ "${new_internet_device}" == tun0 ]; then moulinette_set vpnclient yes else moulinette_set 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 ! 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 # 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 moulinette_set internet_device "${new_internet_device}" fi ;; stop) echo "[hotspot] Stopping..." rm -f /tmp/.ynh-hotspot-started if 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 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}" -eq 0 ]; then echo "[ERR] Hotspot Service disabled" 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 echo "[ERR] No IPv4 NAT set" 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_dhcpd6_running ${i}; then echo "[OK] hotspot${i}: NDP and DHCPv6 server (dnsmasq) is running" else echo "[ERR] hotspot${i}: NDP and DHCPv6 server (dnsmasq) is 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}: NDP and 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