#!/bin/sh
###############################################################################
#                                                                             #
# IPFire.org - A linux based firewall                                         #
# Copyright (C) 2007-2022  IPFire Team  <info@ipfire.org>                     #
#                                                                             #
# This program is free software: you can redistribute it and/or modify        #
# it under the terms of the GNU 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 General Public License for more details.                                #
#                                                                             #
# You should have received a copy of the GNU General Public License           #
# along with this program.  If not, see <http://www.gnu.org/licenses/>.       #
#                                                                             #
###############################################################################

. /etc/sysconfig/rc
. ${rc_functions}

declare -A HT_CAPS=(
	# LDPC Coding Capability
	[0x0001]="[LDPC]"
	# 40 MHz Channel Width
	[0x0002]="[HT40+][HT40-]"
	# Greenfield
	[0x0010]="[GF]"
	# Short Guard Interval (SGI) for 20 MHz
	[0x0020]="[SHORT-GI-20]"
	# Short Guard Interval (SGI) for 40 MHz
	[0x0040]="[SHORT-GI-40]"
	# TX STBC support
	[0x0080]="[TX-STBC]"
	# HT-delayed Block Ack
	[0x0400]="[DELAYED-BA]"
	# Max A-MSDU length (7935 vs. 3839 bytes)
	[0x0800]="[MAX-AMSDU-7935]"
	# DSSS/CCK Mode in 40 MHz
	[0x1000]="[DSSS_CCK-40]"
	# 40 MHz Intolerant
	[0x4000]="[40-INTOLERANT]"
	# L-SIG TXOP protection support
	[0x8000]="[LSIG-TXOP-PROT]"
)

declare -A VHT_CAPS=(
	# RX LDPC
	[0x00000010]="[RXLDPC]"
	# Short GI for 80 MHz
	[0x00000020]="[SHORT-GI-80]"
	# Short GI for 160/80+80 MHz
	[0x00000040]="[SHORT-GI-160]"
	# TX STBC
	[0x00000080]="[TX-STBC-2BY1]"
	# SU Beamformer capable
	[0x00000800]="[SU-BEAMFORMER]"
	# SU Beamformee capable
	[0x00001000]="[SU-BEAMFORMEE]"
	# MU Beamformer capable
	[0x00080000]="[MU-BEAMFORMER]"
	# MU Beamformee capable
	[0x00100000]="[MU-BEAMFORMEE]"
	# VHT TXOP Power Save
	[0x00200000]="[VHT-TXOP-PS]"
	# +HTC-VHT
	[0x00400000]="[HTC-VHT]"
	# RX antenna pattern consistency
	[0x10000000]="[RX-ANTENNA-PATTERN]"
	# TX antenna pattern consistency
	[0x20000000]="[TX-ANTENNA-PATTERN]"
)

declare -A HE_MAC_CAPS=(
	# Nothing, yet
)

declare -A HE_PHY_CAPS=(
	# SU Beamformer
	[0x08000000000]="he_su_beamformer=1"
	# SU Beamformee
	[0x10000000000]="he_su_beamformee=1"
	# MU Beamformer
	[0x20000000000]="he_mu_beamformer=1"
)

declare -A EHT_MAC_CAPS=(
	# Nothing, yet
)

declare -A EHT_PHY_CAPS=(
	# SU Beamformer
	[0x0000000000000010]="eht_su_beamformer=1"
	# SU Beamformee
	[0x0000000000000020]="eht_su_beamformee=1"
)

find_interface() {
	local address="${1}"

	local path
	for path in /sys/class/net/*; do
		if [ -s "${path}/address" ] && [ "$(<${path}/address)" = "${address}" ]; then
			basename "${path}"
			return 0
		fi
	done

	return 1;
}

write_config() {
	local interface="${1}"

	# Fetch the PHY
	local phy="$(</sys/class/net/${interface}/phy80211/name)"

	local flag
	local ht_flags=0
	local vht_flags=0
	local he_mac_flags=0
	local he_phy_flags=0
	local eht_mac_flags=0
	local eht_phy_flags=0

	# Set some default BAND if none is set
	if [ -z "${CONFIG["BAND"]}" ]; then
		# Use 2.4 GHz for 802.11g/n and assume 5 GHz for anything else
		case "${CONFIG["HW_MODE"]}" in
			gn)
				BAND="2g"
				;;
			*)
				BAND="5g"
				;;
		esac
	fi

	# Fetch PHY information
	local line
	while read -r line; do
		case "${line}" in
			"EHT MAC Capabilities"*)
				eht_mac_flags="${line:22:6}"
				;;
			"EHT PHY Capabilities"*)
				eht_phy_flags="${line:23:18}"
				;;
			"HE MAC Capabilities"*)
				he_mac_flags="${line:21:14}"
				;;
			"HE PHY Capabilities"*)
				he_phy_flags="${line:22:24}"
				;;
			"VHT Capabilities"*)
				vht_flags="${line:18:10}"
				;;
			"Capabilities: "*)
				ht_flags="${line:14}"
				;;

			# Check if we are in the right band
			"* 2412.0 MHz"*)
				if [ "${CONFIG["BAND"]}" = "2g" ]; then
					break
				fi
				;;

			"* 5180.0 MHz"*)
				if [ "${CONFIG["BAND"]}" = "5g" ]; then
					break
				fi
				;;
		esac
	done <<<"$(iw phy "${phy}" info)"

	local ht_caps=()
	local vht_caps=()
	local he_caps=()
	local eht_caps=()

	# HT Capabilities
	for flag in ${!HT_CAPS[@]}; do
		if (( ${ht_flags} & ${flag} )); then
			ht_caps+=( "${HT_CAPS[${flag}]}" )
		fi
	done

	# RX STBC
	case "$(( (${ht_flags} >> 8) & 0x03 ))" in
		0)
			# No spacial stream support
			;;
		1)
			ht_caps+=( "[RX-STBC1]" )
			;;
		2)
			ht_caps+=( "[RX-STBC12]" )
			;;
		3)
			ht_caps+=( "[RX-STBC123]" )
			;;
	esac

	# VHT Capabilities
	for flag in ${!VHT_CAPS[@]}; do
		if (( ${vht_flags} & ${flag} )); then
			vht_caps+=( "${VHT_CAPS[${flag}]}" )
		fi
	done

	# Supported channel width
	case "$(( (${vht_flags} >> 2) & 0x03 ))" in
		0)
			# Neither 160, nor 80+80 MHz
			;;
		1)
			vht_caps+=( "[VHT160]" )
			;;
		2)
			vht_caps+=( "[VHT160-80PLUS80]" )
			;;
	esac

	# VHT Max MPDU Length
	case "$(( ${vht_flags} & 0x03 ))" in
		0)
			# Default, 3895
			;;
		1)
			vht_caps+=( "[MAX-MPDU-7991]" )
			;;
		2)
			vht_caps+=( "[MAX-MPDU-11454]" )
			;;
	esac

	# RX Spacial Streams
	case "$(( (${vht_flags} >> 8) & 0x03 ))" in
		1)
			vht_caps+=( "[RX-STBC-1]" )
			;;
		2)
			vht_caps+=( "[RX-STBC-12]" )
			;;
		3)
			vht_caps+=( "[RX-STBC-123]" )
			;;
		4)
			vht_caps+=( "[RX-STBC-1234]" )
			;;
	esac

	# Compressed Steering
	case "$(( ((${vht_flags} >> 13) & 0x03) + 1 ))" in
		2)
			vht_caps+=( "[BF-ANTENNA-2]" )
			;;
		3)
			vht_caps+=( "[BF-ANTENNA-3]" )
			;;
		4)
			vht_caps+=( "[BF-ANTENNA-4]" )
			;;
	esac

	# Sounding Dimension
	case "$(( ((${vht_flags} >> 16) & 0x03) + 1 ))" in
		2)
			vht_caps+=( "[SOUNDING-DIMENSION-2]" )
			;;
		3)
			vht_caps+=( "[SOUNDING-DIMENSION-3]" )
			;;
		4)
			vht_caps+=( "[SOUNDING-DIMENSION-4]" )
			;;
	esac

	local exponent="$(( (${vht_flags} >> 23) & 0x03 ))"
	if [ "${exponent}" -ge 0 ] && [ "${exponent}" -le 7 ]; then
		vht_caps+=( "[MAX-A-MPDU-LEN-EXP${exponent}]" )
	fi

	# VHT Link Adaptation
	case "$(( (${vht_flags} >> 26) & 0x03 ))" in
		2)
			vht_caps+=( "[VHT-LINK-ADAPT2]" )
			;;
		3)
			vht_caps+=( "[VHT-LINK-ADAPT3]" )
			;;
	esac

	# HE PHY Capabilities
	for flag in ${!HE_PHY_CAPS[@]}; do
		if (( ${he_phy_flags} & ${flag} )); then
			he_caps+=( "${HE_PHY_CAPS[${flag}]}" )
		fi
	done

	# EHT PHY Capabilities
	for flag in ${!EHT_PHY_CAPS[@]}; do
		if (( ${eht_phy_flags} & ${flag} )); then
			eht_caps+=( "${EHT_PHY_CAPS[${flag}]}" )
		fi
	done

	# Set the channel to zero if not set
	if [ -z "${CONFIG["CHANNEL"]}" ]; then
		CONFIG["CHANNEL"]=0
	fi

	# Translate the old HW_MODE to the newer MODE setting
	if [ -z "${CONFIG["MODE"]}" ]; then
		case "${CONFIG["HW_MODE"]}" in
			ac)
				CONFIG["MODE"]="VHT20"
				;;
			an|gn)
				CONFIG["MODE"]="HT20";
				;;
		esac
	fi

	# Header
	echo "# Automatically generated configuration"
	echo "# DO NOT EDIT"

	# Enable logging
	echo "logger_syslog=-1"
	echo "logger_syslog_level=4"

	# Use Netlink
	echo "driver=nl80211"

	# Set the country code
	echo "country_code=${CONFIG["COUNTRY"]}"
	echo "country3=0x49"

	# Enable 802.11d and 802.11h
	echo "ieee80211d=1"
	echo "ieee80211h=1"

	# Set the channel (if chosen)
	if [ -n "${CONFIG["CHANNEL"]}" ]; then
		echo "channel=${CONFIG["CHANNEL"]}"
	fi

	# Always advertise TPC
	echo "local_pwr_constraint=3"
	echo "spectrum_mgmt_required=1"

	# Try to perform radar detetection in the background (if supported by the driver)
	echo "enable_background_radar=1"

	# Always enable WMM
	echo "wmm_enabled=1"

	# 802.11n
	local enable_n=0

	case "${CONFIG["MODE"]}" in
		HT*|VHT*|HE*|EHT*)
			enable_n=1
			;;
	esac

	# 802.11ac
	local enable_ac=0
	local vht_oper_chwidth=0
	local vht_oper_centr_freq_seg0_idx=""

	case "${CONFIG["MODE"]}" in
		VHT20|HE20|EHT20)
			enable_ac=1
			;;

		# 40 MHz Channel Width
		VHT40|HE40|EHT40)
			enable_ac=1

			# Compute the channel segment index
			if [ "${CONFIG["CHANNEL"]}" -gt 0 ]; then
				case "$(( (${CONFIG["CHANNEL"]} / 4) % 2 ))" in
					0)
						vht_oper_centr_freq_seg0_idx="$(( ${CONFIG["CHANNEL"]} - 2 ))"
						;;
					1)
						vht_oper_centr_freq_seg0_idx="$(( ${CONFIG["CHANNEL"]} + 2 ))"
						;;
				esac
			fi
			;;

		# 80 MHz Channel Width
		VHT80|HE80|EHT80)
			enable_ac=1
			vht_oper_chwidth=1

			# Compute the channel segment index
			if [ "${CONFIG["CHANNEL"]}" -gt 0 ]; then
				case "$(( (${CONFIG["CHANNEL"]} / 4) % 4 ))" in
					0)
						vht_oper_centr_freq_seg0_idx="$(( ${CONFIG["CHANNEL"]} - 6 ))"
						;;
					1)
						vht_oper_centr_freq_seg0_idx="$(( ${CONFIG["CHANNEL"]} + 6 ))"
						;;
					2)
						vht_oper_centr_freq_seg0_idx="$(( ${CONFIG["CHANNEL"]} + 2 ))"
						;;
					3)
						vht_oper_centr_freq_seg0_idx="$(( ${CONFIG["CHANNEL"]} - 2 ))"
						;;
				esac
			fi
			;;

		# 160/320 MHz Channel Width
		VHT160|HE160|EHT160|EHT320)
			enable_ac=1
			vht_oper_chwidth=2

			# Compute the channel segment index
			if [ "${CONFIG["CHANNEL"]}" -gt 0 ]; then
				case "${CONFIG["CHANNEL"]}" in
					36|40|44|48|52|56|60|64)
						vht_oper_centr_freq_seg0_idx=50
						;;
					100|104|108|112|116|120|124|128)
						vht_oper_centr_freq_seg0_idx=114
						;;
					149|153|157|161|165|169|173|177)
						vht_oper_centr_freq_seg0_idx=163
						;;
				esac
			fi
			;;
	esac

	# 802.11ax
	local enable_ax=0
	local he_oper_chwidth="${vht_oper_chwidth}"
	local he_oper_centr_freq_seg0_idx="${vht_oper_centr_freq_seg0_idx}"

	case "${CONFIG["MODE"]}" in
		HE*|EHT*)
			enable_ax=1
			;;
	esac

	# 802.11be
	local enable_be=0
	local eht_oper_chwidth="${he_oper_chwidth}"
	local eht_oper_centr_freq_seg0_idx="${he_oper_centr_freq_seg0_idx}"

	case "${CONFIG["MODE"]}" in
		EHT*)
			enable_be=1
			;;
	esac

	# Set hardware mode
	case "${CONFIG["BAND"]}" in
		5g)
			echo "hw_mode=a"
			;;
		2g)
			echo "hw_mode=g"
			;;
	esac

	# Enable 802.11be?
	if [ "${enable_be}" -eq 1 ]; then
		echo "ieee80211be=1"

		# Configure wider channels
		echo "eht_oper_chwidth=${eht_oper_chwidth}"
		echo "eht_oper_centr_freq_seg0_idx=${eht_oper_centr_freq_seg0_idx}"

		# Set EHT capabilities
		if [ ${#eht_caps[@]} -gt 0 ]; then
			printf "%s\n" "${eht_caps[@]}"
		fi
	fi

	# Enable 802.11ax?
	if [ "${enable_ax}" -eq 1 ]; then
		echo "ieee80211ax=1"

		# Configure wider channels
		echo "he_oper_chwidth=${he_oper_chwidth}"
		echo "he_oper_centr_freq_seg0_idx=${he_oper_centr_freq_seg0_idx}"

		# Set HE capabilities
		if [ ${#he_caps[@]} -gt 0 ]; then
			printf "%s\n" "${he_caps[@]}"
		fi
	fi

	# Enable 802.11ac?
	if [ "${enable_ac}" -eq 1 ]; then
		echo "ieee80211ac=1"

		# Configure wider channels
		echo "vht_oper_chwidth=${vht_oper_chwidth}"
		echo "vht_oper_centr_freq_seg0_idx=${vht_oper_centr_freq_seg0_idx}"

		# Set VHT capabilities
		if [ ${#vht_caps[@]} -gt 0 ]; then
			echo "vht_capab=${vht_caps[@]}"
		fi
	fi

	# Enable 802.11n?
	if [ "${enable_n}" -eq 1 ]; then
		echo "ieee80211n=1"
	fi

	# Set HT capabilities
	if [ ${#ht_caps[@]} -gt 0 ]; then
		echo "ht_capab=${ht_caps[@]}"
	fi

	# Configure antennas
	if [ -z "${CONFIG["RX_ANTENNAS"]}" ]; then
		CONFIG["RX_ANTENNAS"]="0xffffffff"
	fi
	if [ -z "${CONFIG["TX_ANTENNAS"]}" ]; then
		CONFIG["TX_ANTENNAS"]="0xffffffff"
	fi

	# Set the antennas to use
	iw phy "${phy}" set antenna \
		"${CONFIG["TX_ANTENNAS"]}" "${CONFIG["RX_ANTENNAS"]}" &>/dev/null

	# Enable authentication
	echo "auth_algs=1"

	# Enable the control interface
	echo "ctrl_interface=/var/run/hostapd"
	echo "ctrl_interface_group=0"

	# Disconnect clients that are too far away
	echo "disassoc_low_ack=1"

	# SSID
	echo "ssid2=\"${CONFIG["SSID"]}\""
	echo "utf8_ssid=1"

	# Hide the SSID?
	if [ "${CONFIG["HIDESSID"]}" = "on" ]; then
		echo "ignore_broadcast_ssid=2"
	fi

	# Isolate clients?
	if [ "${CONFIG["CLIENTISOLATION"]}" = "on" ]; then
		echo "ap_isolate=1"
	fi

	# Disable neighbour scan?
	if [ "${CONFIG["NOSCAN"]}" = "on" ]; then
		echo "noscan=1"
	else
		echo "noscan=0"
	fi

	# Management Frame Protection (802.11w)
	case "${CONFIG["IEEE80211W"]}" in
		on)
			echo "ieee80211w=2"

			# Enable beacon protection
			echo "beacon_prot=1"

			# Enable Operating Channel Validation
			echo "ocv=1"
			;;

		optional)
			echo "ieee80211w=1"

			# Enable beacon protection
			echo "beacon_prot=1"

			# Enable OCV in compatibility mode for broken stations
			echo "ocv=2"
			;;
		*)
			echo "ieee80211w=0"
			;;
	esac

	# Encryption
	case "${CONFIG["ENC"]}" in
		wpa3)
			echo "wpa=2"
			echo "wpa_passphrase=${CONFIG["PWD"]}"
			echo "wpa_key_mgmt=SAE"
			echo "rsn_pairwise=CCMP"
			;;
		wpa2+3)
			echo "wpa=2"
			echo "wpa_passphrase=${CONFIG["PWD"]}"
			echo "wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256 SAE"
			echo "rsn_pairwise=CCMP"
			;;
		wpa2)
			echo "wpa=2"
			echo "wpa_passphrase=${CONFIG["PWD"]}"
			echo "wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256"
			echo "rsn_pairwise=CCMP"
			;;
		wpa1+2)
			echo "wpa=3"
			echo "wpa_passphrase=${CONFIG["PWD"]}"
			echo "wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256"
			echo "wpa_pairwise=TKIP"
			echo "rsn_pairwise=CCMP"
			;;
		wpa1)
			echo "wpa=1"
			echo "wpa_passphrase=${CONFIG["PWD"]}"
			echo "wpa_key_mgmt=WPA-PSK WPA-PSK-SHA256"
			echo "wpa_pairwise=TKIP"
			;;
	esac

	# Multi-Band Operation - prefer WiFi over mobile networks
	# This feature requires Management Frame Protection
	case "${CONFIG["IEEE80211W"]}" in
		on|optional)
			echo "mbo=1"
			echo "mbo_cell_data_conn_pref=1"
			;;
	esac

	# Always enable SSID protection
	echo "ssid_protection=1"

	# Extended Key ID support for Individually Addressed frames
	echo "extended_key_id=1"

	# Fully enable Optimized Connectivity Experience
	echo "oce=7"

	# Enable 802.11u Interworking Support
	echo "interworking=1"

	# 802.11u: We are a private network
	echo "access_network_type=0"

	# 802.11u: We have internet access
	echo "internet=1"

	# 802.11v: Advertise the time
	echo "time_advertisement=2"

	# Convert multicast to unicast packets
	echo "multicast_to_unicast=1"

	return 0
}

# Read the configuration
readhash CONFIG "/var/ipfire/wlanap/settings"

case "${1}" in
	start)
		interface="$(find_interface "${CONFIG["INTERFACE"]}")"
		if [ -z "${interface}" ]; then
			boot_mesg "Could not find interface with address ${CONFIG["INTERFACE"]} for wireless access point"
			echo_failure
			exit 1
		fi

		# Write the configuration
		if ! write_config "${interface}" > /etc/hostapd.conf; then
			boot_mesg "Failed to generate configuration"
			echo_failure
			exit 1
		fi

		# Compose the command line
		args=(
			/usr/bin/hostapd
			-s
			-B
			/etc/hostapd.conf
			-i "${interface}"
		)

		# Enable debugging?
		if [ -n "${CONFIG["DEBUG"]}" ] && [[ "${CONFIG["DEBUG"]}" =~ ^[0-9]+$ ]]; then
			for (( i = 0; i < CONFIG["DEBUG"]; i++ )); do
				args+=( "-d" )
			done
		fi

		boot_mesg "Starting hostapd... "
		loadproc "${args[@]}"
		;;

	stop)
		boot_mesg "Stopping hostapd..."
		killproc /usr/bin/hostapd
		evaluate_retval
		;;

	restart)
		${0} stop
		sleep 1
		${0} start
		;;

	status)
		statusproc /usr/bin/hostapd
		;;

	show-config)
		interface="$(find_interface "${CONFIG["INTERFACE"]}")"
		if [ -z "${interface}" ]; then
			boot_mesg "Could not find interface with address ${CONFIG["INTERFACE"]} for wireless access point"
			echo_failure
			exit 1
		fi

		write_config "${interface}"
		;;

	*)
		echo "Usage: ${0} {start|stop|restart|status}"
		exit 1
		;;
esac
