#!/usr/bin/env bash

# general dependencies:
#    bash (to run this script)
#    util-linux (for getopt)
#    procps or procps-ng
#    hostapd
#    iproute2
#    iw
#    iwconfig (you only need this if 'iw' can not recognize your adapter)
#    haveged (optional)

# dependencies for 'nat' or 'none' Internet sharing method
#    dnsmasq
#    iptables

VERSION=0.4.9
PROGNAME="$(basename $0)"

# make sure that all command outputs are in english
# so we can parse them correctly
export LC_ALL=C

# all new files and directories must be readable only by root.
# in special cases we must use chmod to give any other permissions.
SCRIPT_UMASK=0077
umask $SCRIPT_UMASK

usage() {
    echo "Usage: "$PROGNAME" [options] <wifi-interface> [<interface-with-internet>] [<access-point-name> [<passphrase>]]"
    echo
    echo "Options:"
    echo "  -h, --help              Show this help"
    echo "  --version               Print version number"
    echo "  -c <channel>            Channel number (default: 1 or fallback to currently connected channel)"
    echo "  -w <WPA version>        Use 1 for WPA, use 2 for WPA2, use 1+2 for both (default: 2)"
    echo "  -n                      Disable Internet sharing (if you use this, don't pass"
    echo "                          the <interface-with-internet> argument)"
    echo "  -m <method>             Method for Internet sharing."
    echo "                          Use: 'nat' for NAT (default)"
    echo "                               'bridge' for bridging"
    echo "                               'none' for no Internet sharing (equivalent to -n)"
    echo "  --psk                   Use 64 hex digits pre-shared-key instead of passphrase"
    echo "  --hidden                Make the Access Point hidden (do not broadcast the SSID)"
    echo "  --mac-filter            Enable MAC address filtering"
    echo "  --mac-filter-accept     Location of MAC address filter list (defaults to /etc/hostapd/hostapd.accept)"
    echo "  --redirect-to-localhost If -n is set, redirect every web request to localhost (useful for public information networks)"
    echo "  --hostapd-debug <level> With level between 1 and 2, passes arguments -d or -dd to hostapd for debugging."
    echo "  --hostapd-timestamps    Include timestamps in hostapd debug messages."
    echo "  --isolate-clients       Disable communication between clients"
    echo "  --ieee80211n            Enable IEEE 802.11n (HT)"
    echo "  --ieee80211ac           Enable IEEE 802.11ac (VHT)"
    echo "  --ieee80211ax           Enable IEEE 802.11ax (VHT)"
    echo "  --ht_capab <HT>         HT capabilities (default: [HT40+])"
    echo "  --vht_capab <VHT>       VHT capabilities"
    echo "  --vht-chwidth <MHz>     Channel width to aim for on 5GHz with --ieee80211ac."
    echo "                          Valid inputs: 20, 40, 80, 160 (default: 80). Capped to"
    echo "                          what the regulatory domain allows."
    echo "  --country <code>        Set two-letter country code for regularity (example: US)"
    echo "  --freq-band <GHz>       Set frequency band. Valid inputs: 2.4, 5 (default: Use 5GHz if the interface supports it)"
    echo "  --driver                Choose your WiFi adapter driver (default: nl80211)"
    echo "  --no-virt               Do not create virtual interface"
    echo "  --no-haveged            Do not run 'haveged' automatically when needed"
    echo "  --fix-unmanaged         If NetworkManager shows your interface as unmanaged after you"
    echo "                          close create_ap, then use this option to switch your interface"
    echo "                          back to managed"
    echo "  --mac <MAC>             Set MAC address"
    echo "  --dhcp-dns <IP1[,IP2]>  Set DNS returned by DHCP"
    echo "  --dhcp-hosts <H1[,H2]>  Add list of dnsmasq.conf 'dhcp-host=' values"
    echo "                          If ETC_HOSTS=1, it will use the ip addresses for the named hosts in that /etc/hosts."
    echo "                          Othwise, the following syntax would work --dhcp-hosts \"host1,192.168.12.2 host2,192.168.12.3\""
    echo "                          See https://github.com/imp/dnsmasq/blob/770bce967cfc9967273d0acfb3ea018fb7b17522/dnsmasq.conf.example#L238"
    echo "                          for other valid dnsmasq dhcp-host parameters."
    echo "  --daemon                Run create_ap in the background"
    echo "  --pidfile <pidfile>     Save daemon PID to file"
    echo "  --logfile <logfile>     Save daemon messages to file"
    echo "  --dns-logfile <logfile> Log DNS queries to file"
    echo "  --stop <id>             Send stop command to an already running create_ap. For an <id>"
    echo "                          you can put the PID of create_ap or the WiFi interface. You can"
    echo "                          get them with --list-running"
    echo "  --list-running          Show the create_ap processes that are already running"
    echo "  --list-clients <id>     List the clients connected to create_ap instance associated with <id>."
    echo "                          For an <id> you can put the PID of create_ap or the WiFi interface."
    echo "                          If virtual WiFi interface was created, then use that one."
    echo "                          You can get them with --list-running"
    echo "  --mkconfig <conf_file>  Store configs in conf_file"
    echo "  --config <conf_file>    Load configs from conf_file"
    echo
    echo "Non-Bridging Options:"
    echo "  --no-dns                Disable dnsmasq DNS server"
    echo "  --no-dnsmasq            Disable dnsmasq server completely"
    echo "  -g <gateway>            IPv4 Gateway for the Access Point (default: 192.168.12.1)"
    echo "  -d                      DNS server will take into account /etc/hosts"
    echo "  -e <hosts_file>         DNS server will take into account additional hosts file"
    echo
    echo "Useful informations:"
    echo "  * If you're not using the --no-virt option, then you can create an AP with the same"
    echo "    interface you are getting your Internet connection."
    echo "  * You can pass your SSID and password through pipe or through arguments (see examples)."
    echo "  * On bridge method if the <interface-with-internet> is not a bridge interface, then"
    echo "    a bridge interface is created automatically."
    echo
    echo "Examples:"
    echo "  "$PROGNAME" wlan0 eth0 MyAccessPoint MyPassPhrase"
    echo "  echo -e 'MyAccessPoint\nMyPassPhrase' | "$PROGNAME" wlan0 eth0"
    echo "  "$PROGNAME" wlan0 eth0 MyAccessPoint"
    echo "  echo 'MyAccessPoint' | "$PROGNAME" wlan0 eth0"
    echo "  "$PROGNAME" wlan0 wlan0 MyAccessPoint MyPassPhrase"
    echo "  "$PROGNAME" -n wlan0 MyAccessPoint MyPassPhrase"
    echo "  "$PROGNAME" -m bridge wlan0 eth0 MyAccessPoint MyPassPhrase"
    echo "  "$PROGNAME" -m bridge wlan0 br0 MyAccessPoint MyPassPhrase"
    echo "  "$PROGNAME" --driver rtl871xdrv wlan0 eth0 MyAccessPoint MyPassPhrase"
    echo "  "$PROGNAME" --daemon wlan0 eth0 MyAccessPoint MyPassPhrase"
    echo "  "$PROGNAME" --stop wlan0"
}

# Busybox polyfills
if cp --help 2>&1 | grep -q -- --no-clobber; then
    cp_n() {
        cp -n "$@"
    }
else
    cp_n() {
        yes n | cp -i "$@"
    }
fi

# on success it echos a non-zero unused FD
# on error it echos 0
get_avail_fd() {
    local x
    for x in $(seq 1 $(ulimit -n)); do
        if [[ ! -a "/proc/$BASHPID/fd/$x" ]]; then
            echo $x
            return
        fi
    done
    echo 0
}

# lock file for the mutex counter
COUNTER_LOCK_FILE=/tmp/create_ap.$$.lock

cleanup_lock() {
    rm -f $COUNTER_LOCK_FILE
}

init_lock() {
    local LOCK_FILE=/tmp/create_ap.all.lock

    # we initialize only once
    [[ $LOCK_FD -ne 0 ]] && return 0

    LOCK_FD=$(get_avail_fd)
    [[ $LOCK_FD -eq 0 ]] && return 1

    # open/create lock file with write access for all users
    # otherwise normal users will not be able to use it.
    # to avoid race conditions on creation, we need to
    # use umask to set the permissions.
    umask 0555
    eval "exec $LOCK_FD>$LOCK_FILE" > /dev/null 2>&1 || return 1
    umask $SCRIPT_UMASK

    # there is a case where lock file was created from a normal
    # user. change the owner to root as soon as we can.
    [[ $(id -u) -eq 0 ]] && chown 0:0 $LOCK_FILE

    # create mutex counter lock file
    echo 0 > $COUNTER_LOCK_FILE

    return $?
}

# recursive mutex lock for all create_ap processes
mutex_lock() {
    local counter_mutex_fd
    local counter

    # lock local mutex and read counter
    counter_mutex_fd=$(get_avail_fd)
    if [[ $counter_mutex_fd -ne 0 ]]; then
        eval "exec $counter_mutex_fd<>$COUNTER_LOCK_FILE"
        flock $counter_mutex_fd
        read -u $counter_mutex_fd counter
    else
        echo "Failed to lock mutex counter" >&2
        return 1
    fi

    # lock global mutex and increase counter
    [[ $counter -eq 0 ]] && flock $LOCK_FD
    counter=$(( $counter + 1 ))

    # write counter and unlock local mutex
    echo $counter > /proc/$BASHPID/fd/$counter_mutex_fd
    eval "exec ${counter_mutex_fd}<&-"
    return 0
}

# recursive mutex unlock for all create_ap processes
mutex_unlock() {
    local counter_mutex_fd
    local counter

    # lock local mutex and read counter
    counter_mutex_fd=$(get_avail_fd)
    if [[ $counter_mutex_fd -ne 0 ]]; then
        eval "exec $counter_mutex_fd<>$COUNTER_LOCK_FILE"
        flock $counter_mutex_fd
        read -u $counter_mutex_fd counter
    else
        echo "Failed to lock mutex counter" >&2
        return 1
    fi

    # decrease counter and unlock global mutex
    if [[ $counter -gt 0 ]]; then
        counter=$(( $counter - 1 ))
        [[ $counter -eq 0 ]] && flock -u $LOCK_FD
    fi

    # write counter and unlock local mutex
    echo $counter > /proc/$BASHPID/fd/$counter_mutex_fd
    eval "exec ${counter_mutex_fd}<&-"
    return 0
}

# it takes 2 arguments
# returns:
#  0 if v1 (1st argument) and v2 (2nd argument) are the same
#  1 if v1 is less than v2
#  2 if v1 is greater than v2
version_cmp() {
    local V1 V2 VN x
    [[ ! $1 =~ ^[0-9]+(\.[0-9]+)*$ ]] && die "Wrong version format!"
    [[ ! $2 =~ ^[0-9]+(\.[0-9]+)*$ ]] && die "Wrong version format!"

    V1=( $(echo $1 | tr '.' ' ') )
    V2=( $(echo $2 | tr '.' ' ') )
    VN=${#V1[@]}
    [[ $VN -lt ${#V2[@]} ]] && VN=${#V2[@]}

    for ((x = 0; x < $VN; x++)); do
        [[ ${V1[x]} -lt ${V2[x]} ]] && return 1
        [[ ${V1[x]} -gt ${V2[x]} ]] && return 2
    done

    return 0
}

USE_IWCONFIG=0

is_interface() {
    [[ -z "$1" ]] && return 1
    [[ -d "/sys/class/net/${1}" ]]
}

is_wifi_interface() {
    which iw > /dev/null 2>&1 && iw dev $1 info > /dev/null 2>&1 && return 0
    if which iwconfig > /dev/null 2>&1 && iwconfig $1 > /dev/null 2>&1; then
        USE_IWCONFIG=1
        return 0
    fi
    return 1
}

is_bridge_interface() {
    [[ -z "$1" ]] && return 1
    [[ -d "/sys/class/net/${1}/bridge" ]]
}

get_phy_device() {
    local x
    for x in /sys/class/ieee80211/*; do
        [[ ! -e "$x" ]] && continue
        if [[ "${x##*/}" = "$1" ]]; then
            echo $1
            return 0
        elif [[ -e "$x/device/net/$1" ]]; then
            echo ${x##*/}
            return 0
        elif [[ -e "$x/device/net:$1" ]]; then
            echo ${x##*/}
            return 0
        fi
    done
    echo "Failed to get phy interface" >&2
    return 1
}

get_adapter_info() {
    local PHY
    PHY=$(get_phy_device "$1")
    [[ $? -ne 0 ]] && return 1
    iw phy $PHY info
}

get_adapter_kernel_module() {
    local MODULE
    MODULE=$(readlink -f "/sys/class/net/$1/device/driver/module")
    echo ${MODULE##*/}
}

# Bus the adapter is attached to (pci, sdio, usb, ...). Empty if unknown.
get_adapter_bus() {
    local SUBSYSTEM
    SUBSYSTEM=$(readlink -f "/sys/class/net/$1/device/subsystem" 2>/dev/null)
    echo ${SUBSYSTEM##*/}
}

can_be_sta_and_ap() {
    # iwconfig does not provide this information, assume false
    [[ $USE_IWCONFIG -eq 1 ]] && return 1
    if [[ "$(get_adapter_kernel_module "$1")" == "brcmfmac" ]]; then
        # Adding a virtual interface panics the kernel on SDIO-attached
        # brcmfmac chips, so those stay disallowed unconditionally:
        # https://github.com/oblique/create_ap/issues/203
        # That report is specific to SDIO. PCIe and USB parts are not
        # affected, so for those we trust the interface combinations the
        # driver advertises, exactly as we do for every other driver.
        if [[ "$(get_adapter_bus "$1")" == "sdio" ]]; then
            echo "WARN: brcmfmac driver doesn't work properly with virtual interfaces and" >&2
            echo "      it can cause kernel panic. For this reason we disallow virtual" >&2
            echo "      interfaces for your SDIO adapter." >&2
            echo "      For more info: https://github.com/oblique/create_ap/issues/203" >&2
            return 1
        fi
        echo "WARN: virtual interface support varies between brcmfmac chips. If the" >&2
        echo "      AP fails to start or the adapter stops working, try --no-virt." >&2
    fi
    get_adapter_info "$1" | grep -E '{.* managed.* AP.*}' > /dev/null 2>&1 && return 0
    get_adapter_info "$1" | grep -E '{.* AP.* managed.*}' > /dev/null 2>&1 && return 0
    return 1
}

# Largest number of distinct channels the adapter allows in an interface
# combination that actually contains an AP. Drivers commonly advertise several
# combinations and only some of them permit AP mode, so the AP ones have to be
# picked out before reading "#channels" - a plain grep over the whole block
# happily matches a multi-channel combination that can not hold an AP at all.
# Prints nothing when the information is unavailable.
get_ap_max_channels() {
    [[ $USE_IWCONFIG -eq 1 ]] && return 0
    get_adapter_info "$1" 2>/dev/null | awk '
        /valid interface combinations:/ { comb = 1; next }
        !comb { next }
        # a line indented with a single tab ends the combination list
        /^\t[^\t]/ { comb = 0; next }
        {
            if ($0 ~ /^[[:space:]]*\*/) {
                if (rec != "") print rec
                rec = $0
            } else if (rec != "") {
                rec = rec " " $0
            }
        }
        END { if (rec != "") print rec }
    ' | grep -E '#\{[^}]*\bAP\b[^}]*\}' \
      | sed -n 's/.*#channels <= \([0-9]\+\).*/\1/p' \
      | sort -n | tail -1
}

# Every 5GHz channel is reported as "no IR" while the adapter uses a
# self-managed (LAR) regulatory domain that is still on the world domain 00.
# Nothing in userspace can lift that, so explain it instead of only reporting
# that the channel is unusable.
regdomain_hint() {
    local PHY
    [[ $USE_IWCONFIG -eq 1 ]] && return 0
    PHY=$(get_phy_device "$1" 2>/dev/null) || return 0
    iw reg get 2>/dev/null | grep -A1 "^phy#${PHY#phy} (self-managed)" |
        grep -q "^country 00:" || return 0
    echo "
       ${PHY} uses a self-managed regulatory domain and is still on the world
       domain (country 00), where every 5GHz channel is flagged 'no IR' and can
       not carry an access point. 'iw reg set' does not change this, the value
       comes from the adapter's own firmware. 2.4GHz is unaffected, so run the
       hotspot with --freq-band 2.4."
}

# Widest channel (MHz) the active regulatory domain permits at a frequency.
# iw reg get can print both a global and a per-phy section, so take the most
# restrictive rule that covers the frequency. Prints nothing when unknown.
get_max_regulatory_bw() {
    local FREQ=$1
    iw reg get 2>/dev/null | tr -d '\t' |
        sed -n 's/^(\([0-9]\+\) - \([0-9]\+\) @ \([0-9]\+\)).*/\1 \2 \3/p' |
        while read -r LO HI BW; do
            [[ $FREQ -ge $LO && $FREQ -le $HI ]] && echo "$BW"
        done | sort -n | head -1
}

# VHT centre frequency index for the 80 or 160MHz block holding a 5GHz channel.
get_vht_seg0_idx() {
    local CH=$1 WIDTH=$2
    if [[ $WIDTH -eq 80 ]]; then
        case $CH in
            36|40|44|48)        echo 42  ;;
            52|56|60|64)        echo 58  ;;
            100|104|108|112)    echo 106 ;;
            116|120|124|128)    echo 122 ;;
            132|136|140|144)    echo 138 ;;
            149|153|157|161)    echo 155 ;;
            165|169|173|177)    echo 171 ;;
        esac
    elif [[ $WIDTH -eq 160 ]]; then
        case $CH in
            36|40|44|48|52|56|60|64)             echo 50  ;;
            100|104|108|112|116|120|124|128)     echo 114 ;;
            149|153|157|161|165|169|173|177)     echo 163 ;;
        esac
    fi
}

# Each 5GHz 40MHz pair has a fixed layout: the lower channel takes its
# secondary above (HT40+), the upper one below (HT40-). Getting this backwards
# leaves hostapd on a 20MHz channel.
get_ht40_direction() {
    # printf, not echo: a lone "-" is swallowed as an end-of-options marker by
    # some shells' echo builtins
    case $1 in
        36|44|52|60|100|108|116|124|132|140|149|157|165|173) printf '+' ;;
        40|48|56|64|104|112|120|128|136|144|153|161|169|177) printf '-' ;;
    esac
}

can_be_ap() {
    # iwconfig does not provide this information, assume true
    [[ $USE_IWCONFIG -eq 1 ]] && return 0
    get_adapter_info "$1" | grep -E '\* AP$' > /dev/null 2>&1 && return 0
    return 1
}

can_transmit_to_channel() {
    local IFACE CHANNEL_NUM CHANNEL_INFO
    IFACE=$1
    CHANNEL_NUM=$2

    if [[ $USE_IWCONFIG -eq 0 ]]; then
        if [[ $FREQ_BAND == 2.4 ]]; then
            CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " 24[0-9][0-9]\(\.0\+\)\? MHz \[${CHANNEL_NUM}\]")
        else
            CHANNEL_INFO=$(get_adapter_info ${IFACE} | grep " \(49[0-9][0-9]\|5[0-9]\{3\}\)\(\.0\+\)\? MHz \[${CHANNEL_NUM}\]")
        fi
        [[ -z "${CHANNEL_INFO}" ]] && return 1
        [[ "${CHANNEL_INFO}" == *no\ IR* ]] && return 1
        [[ "${CHANNEL_INFO}" == *disabled* ]] && return 1
        return 0
    else
        CHANNEL_NUM=$(printf '%02d' ${CHANNEL_NUM})
        CHANNEL_INFO=$(iwlist ${IFACE} channel | grep -E "Channel[[:blank:]]${CHANNEL_NUM}[[:blank:]]?:")
        [[ -z "${CHANNEL_INFO}" ]] && return 1
        return 0
    fi
}

# taken from iw/util.c
ieee80211_frequency_to_channel() {
    local FREQ_MAYBE_FRACTIONAL=$1
    local FREQ=${FREQ_MAYBE_FRACTIONAL%.*}

    if [[ $FREQ -lt 1000 ]]; then
        echo 0
    elif [[ $FREQ -eq 2484 ]]; then
        echo 14
    elif [[ $FREQ -eq 5935 ]]; then
        echo 2
    elif [[ $FREQ -lt 2484 ]]; then
        echo $(( ($FREQ - 2407) / 5 ))
    elif [[ $FREQ -ge 4910 && $FREQ -le 4980 ]]; then
        echo $(( ($FREQ - 4000) / 5 ))
    elif [[ $FREQ -lt 5950 ]]; then
        echo $(( ($FREQ - 5000) / 5 ))
    elif [[ $FREQ -le 45000 ]]; then
        echo $(( ($FREQ - 5950) / 5 ))
    elif [[ $FREQ -ge 58320 && $FREQ -le 70200 ]]; then
        echo $(( ($FREQ - 56160) / 2160 ))
    else
        echo 0
    fi
}

is_5ghz_frequency() {
    [[ $1 =~ ^(49[0-9]{2})|(5[0-9]{3})(\.0+)?$ ]]
}

is_wifi_connected() {
    if [[ $USE_IWCONFIG -eq 0 ]]; then
        iw dev "$1" link 2>&1 | grep -E '^Connected to' > /dev/null 2>&1 && return 0
    else
        iwconfig "$1" 2>&1 | grep -E 'Access Point: [0-9a-fA-F]{2}:' > /dev/null 2>&1 && return 0
    fi
    return 1
}

is_macaddr() {
    echo "$1" | grep -E "^([0-9a-fA-F]{2}:){5}[0-9a-fA-F]{2}$" > /dev/null 2>&1
}

is_unicast_macaddr() {
    local x
    is_macaddr "$1" || return 1
    x=$(echo "$1" | cut -d: -f1)
    x=$(printf '%d' "0x${x}")
    [[ $(expr $x % 2) -eq 0 ]]
}

get_macaddr() {
    is_interface "$1" || return
    cat "/sys/class/net/${1}/address"
}

get_mtu() {
    is_interface "$1" || return
    cat "/sys/class/net/${1}/mtu"
}

alloc_new_iface() {
    local prefix=$1
    local i=0

    mutex_lock
    while :; do
        if ! is_interface $prefix$i && [[ ! -f $COMMON_CONFDIR/ifaces/$prefix$i ]]; then
            mkdir -p $COMMON_CONFDIR/ifaces
            touch $COMMON_CONFDIR/ifaces/$prefix$i
            echo $prefix$i
            mutex_unlock
            return
        fi
        i=$((i + 1))
    done
    mutex_unlock
}

dealloc_iface() {
    rm -f $COMMON_CONFDIR/ifaces/$1
}

get_all_macaddrs() {
    cat /sys/class/net/*/address
}

get_new_macaddr() {
    local OLDMAC NEWMAC LAST_BYTE i
    OLDMAC=$(get_macaddr "$1")
    LAST_BYTE=$(printf %d 0x${OLDMAC##*:})
    mutex_lock
    for i in {1..255}; do
        NEWMAC="${OLDMAC%:*}:$(printf %02x $(( ($LAST_BYTE + $i) % 256 )))"
        (get_all_macaddrs | grep "$NEWMAC" > /dev/null 2>&1) || break
    done
    mutex_unlock
    echo $NEWMAC
}

# start haveged when needed
haveged_watchdog() {
    local show_warn=1
    while :; do
        if [[ $(cat /proc/sys/kernel/random/entropy_avail) -lt 1000 ]]; then
            if ! which haveged > /dev/null 2>&1; then
                if [[ $show_warn -eq 1 ]]; then
                    echo "WARN: Low entropy detected. We recommend you to install \`haveged'"
                    show_warn=0
                fi
            elif ! pidof haveged > /dev/null 2>&1; then
                echo "Low entropy detected, starting haveged"
                # boost low-entropy
                mutex_lock
                haveged -w 1024 -p $COMMON_CONFDIR/haveged.pid
                mutex_unlock
            fi
        fi
        sleep 2
    done
}

NETWORKMANAGER_CONF=/etc/NetworkManager/NetworkManager.conf
NM_OLDER_VERSION=1

networkmanager_exists() {
    local NM_VER
    which nmcli > /dev/null 2>&1 || return 1
    NM_VER=$(nmcli -v | grep -m1 -oE '[0-9]+(\.[0-9]+)*\.[0-9]+')
    version_cmp $NM_VER 0.9.9
    if [[ $? -eq 1 ]]; then
        NM_OLDER_VERSION=1
    else
        NM_OLDER_VERSION=0
    fi
    return 0
}

networkmanager_is_running() {
    local NMCLI_OUT
    networkmanager_exists || return 1
    if [[ $NM_OLDER_VERSION -eq 1 ]]; then
        NMCLI_OUT=$(nmcli -t -f RUNNING nm 2>&1 | grep -E '^running$')
    else
        NMCLI_OUT=$(nmcli -t -f RUNNING g 2>&1 | grep -E '^running$')
    fi
    [[ -n "$NMCLI_OUT" ]]
}

networkmanager_knows_iface() {
    # check if the interface $1 is known to NetworkManager
    # an interface may exist but may not be known to NetworkManager if it is in a different network namespace than NetworkManager
    nmcli -t -f DEVICE d 2>&1 | grep -Fxq "$1"
}

networkmanager_iface_is_unmanaged() {
    is_interface "$1" || return 2
    networkmanager_knows_iface "$1" || return 0
    (nmcli -t -f DEVICE,STATE d 2>&1 | grep -E "^$1:unmanaged$" > /dev/null 2>&1) || return 1
}

ADDED_UNMANAGED=

networkmanager_add_unmanaged() {
    local MAC UNMANAGED WAS_EMPTY x
    networkmanager_exists || return 1

    [[ -d ${NETWORKMANAGER_CONF%/*} ]] || mkdir -p ${NETWORKMANAGER_CONF%/*}
    [[ -f ${NETWORKMANAGER_CONF} ]] || touch ${NETWORKMANAGER_CONF}

    if [[ $NM_OLDER_VERSION -eq 1 ]]; then
        if [[ -z "$2" ]]; then
            MAC=$(get_macaddr "$1")
        else
            MAC="$2"
        fi
        [[ -z "$MAC" ]] && return 1
    fi

    mutex_lock
    UNMANAGED=$(grep -m1 -Eo '^unmanaged-devices=[[:alnum:]:;,?*~=-]*' /etc/NetworkManager/NetworkManager.conf)

    WAS_EMPTY=0
    [[ -z "$UNMANAGED" ]] && WAS_EMPTY=1
    UNMANAGED=$(echo "$UNMANAGED" | sed 's/unmanaged-devices=//' | tr ';,' ' ')

    # if it exists, do nothing
    for x in $UNMANAGED; do
        if [[ $x == "mac:${MAC}" ]] ||
               [[ $NM_OLDER_VERSION -eq 0 && $x == "interface-name:${1}" ]]; then
            mutex_unlock
            return 2
        fi
    done

    if [[ $NM_OLDER_VERSION -eq 1 ]]; then
        UNMANAGED="${UNMANAGED} mac:${MAC}"
    else
        UNMANAGED="${UNMANAGED} interface-name:${1}"
    fi

    UNMANAGED=$(echo $UNMANAGED | sed -e 's/^ //')
    UNMANAGED="${UNMANAGED// /;}"
    UNMANAGED="unmanaged-devices=${UNMANAGED}"

    if ! grep -E '^\[keyfile\]' ${NETWORKMANAGER_CONF} > /dev/null 2>&1; then
        echo -e "\n\n[keyfile]\n${UNMANAGED}" >> ${NETWORKMANAGER_CONF}
    elif [[ $WAS_EMPTY -eq 1 ]]; then
        sed -e "s/^\(\[keyfile\].*\)$/\1\n${UNMANAGED}/" -i ${NETWORKMANAGER_CONF}
    else
        sed -e "s/^unmanaged-devices=.*/${UNMANAGED}/" -i ${NETWORKMANAGER_CONF}
    fi

    ADDED_UNMANAGED="${ADDED_UNMANAGED} ${1} "
    mutex_unlock

    local nm_pid=$(pidof NetworkManager)
    [[ -n "$nm_pid" ]] && kill -HUP $nm_pid

    return 0
}

networkmanager_rm_unmanaged() {
    local MAC UNMANAGED
    networkmanager_exists || return 1
    [[ ! -f ${NETWORKMANAGER_CONF} ]] && return 1

    if [[ $NM_OLDER_VERSION -eq 1 ]]; then
        if [[ -z "$2" ]]; then
            MAC=$(get_macaddr "$1")
        else
            MAC="$2"
        fi
        [[ -z "$MAC" ]] && return 1
    fi

    mutex_lock
    UNMANAGED=$(grep -m1 -Eo '^unmanaged-devices=[[:alnum:]:;,?*~=-]*' /etc/NetworkManager/NetworkManager.conf | sed 's/unmanaged-devices=//' | tr ';,' ' ')

    if [[ -z "$UNMANAGED" ]]; then
        mutex_unlock
        return 1
    fi

    [[ -n "$MAC" ]] && UNMANAGED=$(echo $UNMANAGED | sed -e "s/mac:${MAC}\( \|$\)//g")
    UNMANAGED=$(echo $UNMANAGED | sed -e "s/interface-name:${1}\( \|$\)//g")
    UNMANAGED=$(echo $UNMANAGED | sed -e 's/ $//')

    if [[ -z "$UNMANAGED" ]]; then
        sed -e "/^unmanaged-devices=.*/d" -i ${NETWORKMANAGER_CONF}
    else
        UNMANAGED="${UNMANAGED// /;}"
        UNMANAGED="unmanaged-devices=${UNMANAGED}"
        sed -e "s/^unmanaged-devices=.*/${UNMANAGED}/" -i ${NETWORKMANAGER_CONF}
    fi

    ADDED_UNMANAGED="${ADDED_UNMANAGED/ ${1} /}"
    mutex_unlock

    local nm_pid=$(pidof NetworkManager)
    [[ -n "$nm_pid" ]] && kill -HUP $nm_pid

    return 0
}

networkmanager_fix_unmanaged() {
    [[ -f ${NETWORKMANAGER_CONF} ]] || return

    mutex_lock
    sed -e "/^unmanaged-devices=.*/d" -i ${NETWORKMANAGER_CONF}
    mutex_unlock

    local nm_pid=$(pidof NetworkManager)
    [[ -n "$nm_pid" ]] && kill -HUP $nm_pid
}

networkmanager_rm_unmanaged_if_needed() {
    [[ $ADDED_UNMANAGED =~ .*\ ${1}\ .* ]] && networkmanager_rm_unmanaged $1 $2
}

networkmanager_wait_until_unmanaged() {
    local RES n=0
    networkmanager_is_running || return 1
    while :; do
        networkmanager_iface_is_unmanaged "$1"
        RES=$?
        [[ $RES -eq 0 ]] && break
        [[ $RES -eq 2 ]] && die "Interface '${1}' does not exist.
       It's probably renamed by a udev rule."
        # Some NetworkManager versions ignore 'unmanaged-devices' from the
        # config for interfaces created at runtime; the device then never
        # reports state 'unmanaged' and this loop would spin forever.
        # Give NM a couple of polls to honor the config, then force it
        # at device level (idempotent, supported by all nmcli versions).
        if [[ $n -ge 2 ]]; then
            nmcli device set "$1" managed no 2>/dev/null
        fi
        ((n++))
        sleep 1
    done
    sleep 2
    return 0
}


CHANNEL=default
GATEWAY=192.168.12.1
WPA_VERSION=2
ETC_HOSTS=0
ADDN_HOSTS=
DHCP_HOSTS=
DHCP_DNS=gateway
NO_DNS=0
NO_DNSMASQ=0
DNS_PORT=
HIDDEN=0
MAC_FILTER=0
MAC_FILTER_ACCEPT=/etc/hostapd/hostapd.accept
ISOLATE_CLIENTS=0
SHARE_METHOD=nat
IEEE80211N=0
IEEE80211AC=0
IEEE80211AX=0
HT_CAPAB='[HT40+]'
VHT_CAPAB=
VHT_CHWIDTH=80
DRIVER=nl80211
NO_VIRT=0
COUNTRY=
FREQ_BAND=2.4
NEW_MACADDR=
DAEMONIZE=0
DAEMON_PIDFILE=
DAEMON_LOGFILE=/dev/null
DNS_LOGFILE=
NO_HAVEGED=0
USE_PSK=0

HOSTAPD_DEBUG_ARGS=
REDIRECT_TO_LOCALHOST=0

CONFIG_OPTS=(CHANNEL GATEWAY WPA_VERSION ETC_HOSTS DHCP_DNS NO_DNS NO_DNSMASQ HIDDEN MAC_FILTER MAC_FILTER_ACCEPT ISOLATE_CLIENTS
             SHARE_METHOD IEEE80211N IEEE80211AC IEEE80211AX HT_CAPAB VHT_CAPAB VHT_CHWIDTH DRIVER NO_VIRT COUNTRY FREQ_BAND
             NEW_MACADDR DAEMONIZE DAEMON_PIDFILE DAEMON_LOGFILE DNS_LOGFILE NO_HAVEGED WIFI_IFACE INTERNET_IFACE
             SSID PASSPHRASE USE_PSK ADDN_HOSTS DHCP_HOSTS)

FIX_UNMANAGED=0
LIST_RUNNING=0
STOP_ID=
LIST_CLIENTS_ID=

STORE_CONFIG=
LOAD_CONFIG=

CONFDIR=
WIFI_IFACE=
VWIFI_IFACE=
INTERNET_IFACE=
BRIDGE_IFACE=
OLD_MACADDR=
IP_ADDRS=
ROUTE_ADDRS=

HAVEGED_WATCHDOG_PID=

_cleanup() {
    local PID x

    trap "" SIGINT SIGUSR1 SIGUSR2 EXIT
    mutex_lock
    disown -a

    # kill haveged_watchdog
    [[ -n "$HAVEGED_WATCHDOG_PID" ]] && kill $HAVEGED_WATCHDOG_PID

    # kill processes
    for x in $CONFDIR/*.pid; do
        # even if the $CONFDIR is empty, the for loop will assign
        # a value in $x. so we need to check if the value is a file
        [[ -f $x ]] && kill -9 $(cat $x)
    done

    rm -rf $CONFDIR

    local found=0
    for x in $(list_running_conf); do
        if [[ -f $x/nat_internet_iface && $(cat $x/nat_internet_iface) == $INTERNET_IFACE ]]; then
            found=1
            break
        fi
    done

    if [[ $found -eq 0 ]]; then
        cp -f $COMMON_CONFDIR/${INTERNET_IFACE}_forwarding \
           /proc/sys/net/ipv4/conf/$INTERNET_IFACE/forwarding
        rm -f $COMMON_CONFDIR/${INTERNET_IFACE}_forwarding
    fi

    # if we are the last create_ap instance then set back the common values
    if ! has_running_instance; then
        # kill common processes
        for x in $COMMON_CONFDIR/*.pid; do
            [[ -f $x ]] && kill -9 $(cat $x)
        done

        # set old ip_forward
        if [[ -f $COMMON_CONFDIR/ip_forward ]]; then
            cp -f $COMMON_CONFDIR/ip_forward /proc/sys/net/ipv4
            rm -f $COMMON_CONFDIR/ip_forward
        fi

        # set old bridge-nf-call-iptables
        if [[ -f $COMMON_CONFDIR/bridge-nf-call-iptables ]]; then
            if [[ -e /proc/sys/net/bridge/bridge-nf-call-iptables ]]; then
                cp -f $COMMON_CONFDIR/bridge-nf-call-iptables /proc/sys/net/bridge
            fi
            rm -f $COMMON_CONFDIR/bridge-nf-call-iptables
        fi

        rm -rf $COMMON_CONFDIR
    fi

    if [[ "$SHARE_METHOD" != "none" ]]; then
        if [[ "$SHARE_METHOD" == "nat" ]]; then
            iptables -w -t nat -D POSTROUTING -s ${GATEWAY%.*}.0/24 ! -o ${WIFI_IFACE} -j MASQUERADE
            iptables -w -D FORWARD -i ${WIFI_IFACE} -s ${GATEWAY%.*}.0/24 -j ACCEPT
            iptables -w -D FORWARD -i ${INTERNET_IFACE} -d ${GATEWAY%.*}.0/24 -j ACCEPT
        elif [[ "$SHARE_METHOD" == "bridge" ]]; then
            if ! is_bridge_interface $INTERNET_IFACE; then
                ip link set dev $BRIDGE_IFACE down
                ip link set dev $INTERNET_IFACE down
                ip link set dev $INTERNET_IFACE promisc off
                ip link set dev $INTERNET_IFACE nomaster
                ip link delete $BRIDGE_IFACE type bridge
                ip addr flush $INTERNET_IFACE
                ip link set dev $INTERNET_IFACE up
                dealloc_iface $BRIDGE_IFACE

                for x in "${IP_ADDRS[@]}"; do
                    x="${x/inet/}"
                    x="${x/secondary/}"
                    x="${x/dynamic/}"
                    x=$(echo $x | sed 's/\([0-9]\)sec/\1/g')
                    x="${x/${INTERNET_IFACE}/}"
                    ip addr add $x dev $INTERNET_IFACE
                done

                ip route flush dev $INTERNET_IFACE

                for x in "${ROUTE_ADDRS[@]}"; do
                    [[ -z "$x" ]] && continue
                    [[ "$x" == default* ]] && continue
                    ip route add $x dev $INTERNET_IFACE
                done

                for x in "${ROUTE_ADDRS[@]}"; do
                    [[ -z "$x" ]] && continue
                    [[ "$x" != default* ]] && continue
                    ip route add $x dev $INTERNET_IFACE
                done

                networkmanager_rm_unmanaged_if_needed $INTERNET_IFACE
            fi
        fi
    fi

    if [[ "$SHARE_METHOD" != "bridge" ]]; then
        if [[ $NO_DNS -eq 0 ]]; then
            iptables -w -D INPUT -p tcp -m tcp --dport $DNS_PORT -j ACCEPT
            iptables -w -D INPUT -p udp -m udp --dport $DNS_PORT -j ACCEPT
            iptables -w -t nat -D PREROUTING -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} \
                -p tcp -m tcp --dport 53 -j REDIRECT --to-ports $DNS_PORT
            iptables -w -t nat -D PREROUTING -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} \
                -p udp -m udp --dport 53 -j REDIRECT --to-ports $DNS_PORT
        fi
        iptables -w -D INPUT -p udp -m udp --dport 67 -j ACCEPT
    fi

    if [[ $NO_VIRT -eq 0 ]]; then
        if [[ -n "$VWIFI_IFACE" ]]; then
            ip link set down dev ${VWIFI_IFACE}
            ip addr flush ${VWIFI_IFACE}
            networkmanager_rm_unmanaged_if_needed ${VWIFI_IFACE} ${OLD_MACADDR}
            iw dev ${VWIFI_IFACE} del
            dealloc_iface $VWIFI_IFACE
        fi
    else
        ip link set down dev ${WIFI_IFACE}
        ip addr flush ${WIFI_IFACE}
        if [[ -n "$NEW_MACADDR" ]]; then
            ip link set dev ${WIFI_IFACE} address ${OLD_MACADDR}
        fi
        networkmanager_rm_unmanaged_if_needed ${WIFI_IFACE} ${OLD_MACADDR}
    fi

    mutex_unlock
    cleanup_lock

    if [[ $RUNNING_AS_DAEMON -eq 1 && -n "$DAEMON_PIDFILE" && -f "$DAEMON_PIDFILE" ]]; then
        rm $DAEMON_PIDFILE
    fi
}

cleanup() {
    echo
    echo -n "Doing cleanup.. "
    _cleanup > /dev/null 2>&1
    echo "done"
}

die() {
    [[ -n "$1" ]] && echo -e "\nERROR: $1\n" >&2
    # send die signal to the main process
    [[ $BASHPID -ne $$ ]] && kill -USR2 $$
    # we don't need to call cleanup because it's traped on EXIT
    exit 1
}

clean_exit() {
    # send clean_exit signal to the main process
    [[ $BASHPID -ne $$ ]] && kill -USR1 $$
    # we don't need to call cleanup because it's traped on EXIT
    exit 0
}

list_running_conf() {
    local x
    mutex_lock
    for x in /tmp/create_ap.*; do
        if [[ -f $x/pid && -f $x/wifi_iface && -d /proc/$(cat $x/pid) ]]; then
            echo $x
        fi
    done
    mutex_unlock
}

list_running() {
    local IFACE wifi_iface x
    mutex_lock
    for x in $(list_running_conf); do
        IFACE=${x#*.}
        IFACE=${IFACE%%.*}
        wifi_iface=$(cat $x/wifi_iface)

        if [[ $IFACE == $wifi_iface ]]; then
            echo $(cat $x/pid) $IFACE
        else
            echo $(cat $x/pid) $IFACE '('$(cat $x/wifi_iface)')'
        fi
    done
    mutex_unlock
}

get_wifi_iface_from_pid() {
    list_running | awk '{print $1 " " $NF}' | tr -d '\(\)' | grep -E "^${1} " | cut -d' ' -f2
}

get_pid_from_wifi_iface() {
    list_running | awk '{print $1 " " $NF}' | tr -d '\(\)' | grep -E " ${1}$" | cut -d' ' -f1
}

get_confdir_from_pid() {
    local IFACE x
    mutex_lock
    for x in $(list_running_conf); do
        if [[ $(cat $x/pid) == "$1" ]]; then
            echo $x
            break
        fi
    done
    mutex_unlock
}

print_client() {
    local line ipaddr hostname
    local mac="$1"

    if [[ -f $CONFDIR/dnsmasq.leases ]]; then
        line=$(grep " $mac " $CONFDIR/dnsmasq.leases | tail -n 1)
        ipaddr=$(echo $line | cut -d' ' -f3)
        hostname=$(echo "$line" | cut -d' ' -f4)
    fi

    [[ -z "$ipaddr" ]] && ipaddr="*"
    [[ -z "$hostname" ]] && hostname="*"

    printf "%-20s %-18s %s\n" "$mac" "$ipaddr" "$hostname"
}

list_clients() {
    local wifi_iface pid

    # If PID is given, get the associated wifi iface
    if [[ "$1" =~ ^[1-9][0-9]*$ ]]; then
        pid="$1"
        wifi_iface=$(get_wifi_iface_from_pid "$pid")
        [[ -z "$wifi_iface" ]] && die "'$pid' is not the pid of a running $PROGNAME instance."
    fi

    [[ -z "$wifi_iface" ]] && wifi_iface="$1"
    is_wifi_interface "$wifi_iface" || die "'$wifi_iface' is not a WiFi interface."

    [[ -z "$pid" ]] && pid=$(get_pid_from_wifi_iface "$wifi_iface")
    [[ -z "$pid" ]] && die "'$wifi_iface' is not used from $PROGNAME instance.\n\
       Maybe you need to pass the virtual interface instead.\n\
       Use --list-running to find it out."
    [[ -z "$CONFDIR" ]] && CONFDIR=$(get_confdir_from_pid "$pid")

    if [[ $USE_IWCONFIG -eq 0 ]]; then
        local awk_cmd='($1 ~ /Station$/) {print $2}'
        local client_list=$(iw dev "$wifi_iface" station dump | awk "$awk_cmd")

        if [[ -z "$client_list" ]]; then
            echo "No clients connected"
            return
        fi

        printf "%-20s %-18s %s\n" "MAC" "IP" "Hostname"

        local mac
        for mac in $client_list; do
            print_client $mac
        done
    else
        die "This option is not supported for the current driver."
    fi
}

has_running_instance() {
    local PID x

    mutex_lock
    for x in /tmp/create_ap.*; do
        if [[ -f $x/pid ]]; then
            PID=$(cat $x/pid)
            if [[ -d /proc/$PID ]]; then
                mutex_unlock
                return 0
            fi
        fi
    done
    mutex_lock

    return 1
}

is_running_pid() {
    list_running | grep -E "^${1} " > /dev/null 2>&1
}

send_stop() {
    local x

    mutex_lock
    # send stop signal to specific pid
    if is_running_pid $1; then
        kill -USR1 $1
        mutex_unlock
        return
    fi

    # send stop signal to specific interface
    for x in $(list_running | grep -E " \(?${1}( |\)?\$)" | cut -f1 -d' '); do
        kill -USR1 $x
    done
    mutex_unlock
}

# Storing configs
write_config() {
    local i=1

    # If using pkexec, evaluate permissions before writing.
    #   However, the /etc/create_ap.conf
    #   location is excepted.
    if [[ "$STORE_CONFIG" != "/etc/create_ap.conf" && $PKEXEC_UID ]]; then
        if [ -e "$STORE_CONFIG" ]; then
            if ! pkexec --user "$(id -nu $PKEXEC_UID)" test -w "$STORE_CONFIG"; then
                echo "ERROR: 1 $(id -nu $PKEXEC_UID) has insufficient permissions to write to config file $STORE_CONFIG"
                exit 1
            fi
        elif ! pkexec --user "$(id -nu $PKEXEC_UID)" test -w "$(dirname "$STORE_CONFIG")"; then
            echo "ERROR: 2 $(id -nu $PKEXEC_UID) has insufficient permissions to write to config file $STORE_CONFIG"
            exit 1
        fi
        # Assume that the user is making a conf file in a directory they normally
        # have control over, and keep permissions strictly private. (i.e. they will
        # need to run create_ap directly with sudo in order to write to, say, /etc/create_ap2.conf)
        touch "$STORE_CONFIG"
        chown "$(id -nu $PKEXEC_UID):$(id -ng $PKEXEC_GID)" "$STORE_CONFIG"
        chmod 600 "$STORE_CONFIG"
    elif ! eval 'echo -n > "$STORE_CONFIG"' > /dev/null 2>&1; then
        echo "ERROR: Unable to create config file $STORE_CONFIG" >&2
        exit 1
    fi

    WIFI_IFACE=$1
    if [[ "$SHARE_METHOD" == "none" ]]; then
        SSID="$2"
        PASSPHRASE="$3"
    else
        INTERNET_IFACE="$2"
        SSID="$3"
        PASSPHRASE="$4"
    fi

    if [[ $FREQ_BAND_SET -eq 0 ]]; then
        FREQ_BAND="default"
    fi

    for config_opt in "${CONFIG_OPTS[@]}"; do
        eval echo $config_opt=\$$config_opt
    done >> "$STORE_CONFIG"

    echo -e "Config options written to '$STORE_CONFIG'"
    exit 0
}

is_config_opt() {
    local elem opt="$1"

    for elem in "${CONFIG_OPTS[@]}"; do
        if [[ "$elem" == "$opt" ]]; then
            return 0
        fi
    done
    return 1
}

# Load options from config file
read_config() {
    local opt_name opt_val line

    while read line; do
        # Read switches and their values
        opt_name="${line%%=*}"
        opt_val="${line#*=}"

        if [[ $opt_name == "FREQ_BAND" && $opt_val != "default" ]] ; then
            FREQ_BAND_SET=1
        fi

        if is_config_opt "$opt_name" ; then
            eval $opt_name="\$opt_val"
        else
            echo "WARN: Unrecognized configuration entry. Please check your config file." >&2
        fi
    done < "$LOAD_CONFIG"
}


ARGS=( "$@" )

FREQ_BAND_SET=0

# Preprocessing for --config before option-parsing starts
for ((i=0; i<$#; i++)); do
    if [[ "${ARGS[i]}" = "--config" ]]; then
        if [[ -f "${ARGS[i+1]}" ]]; then
            LOAD_CONFIG="${ARGS[i+1]}"
            read_config
        else
            echo "ERROR: No config file found at given location" >&2
            exit 1
        fi
        break
    fi
done

GETOPT_ARGS=$(getopt -o hc:w:g:de:nm: -l "help","hidden","hostapd-debug:","hostapd-timestamps","redirect-to-localhost","mac-filter","mac-filter-accept:","isolate-clients","ieee80211n","ieee80211ac","ieee80211ax","ht_capab:","vht_capab:","vht-chwidth:","driver:","no-virt","fix-unmanaged","country:","freq-band:","mac:","dhcp-dns:","daemon","pidfile:","logfile:","dns-logfile:","stop:","list","list-running","list-clients:","version","psk","no-haveged","no-dns","no-dnsmasq","mkconfig:","config:","dhcp-hosts:" -n "$PROGNAME" -- "$@")
[[ $? -ne 0 ]] && exit 1
eval set -- "$GETOPT_ARGS"

while :; do
    case "$1" in
        -h|--help)
            usage
            exit 0
            ;;
        --version)
            echo $VERSION
            exit 0
            ;;
        --hidden)
            shift
            HIDDEN=1
            ;;
        --mac-filter)
            shift
            MAC_FILTER=1
            ;;
        --mac-filter-accept)
            shift
            MAC_FILTER_ACCEPT="$1"
            shift
            ;;
        --isolate-clients)
            shift
            ISOLATE_CLIENTS=1
            ;;
        -c)
            shift
            CHANNEL="$1"
            shift
            ;;
        -w)
            shift
            WPA_VERSION="$1"
            [[ "$WPA_VERSION" == "2+1" ]] && WPA_VERSION=1+2
            shift
            ;;
        -g)
            shift
            GATEWAY="$1"
            shift
            ;;
        -d)
            shift
            ETC_HOSTS=1
            ;;
        -e)
            shift
            ADDN_HOSTS="$1"
            shift
            ;;
        --dhcp-hosts)
            shift
            DHCP_HOSTS="$1"
            shift
            ;;
        -n)
            shift
            SHARE_METHOD=none
            ;;
        -m)
            shift
            SHARE_METHOD="$1"
            shift
            ;;
        --ieee80211n)
            shift
            IEEE80211N=1
            ;;
        --ieee80211ac)
            shift
            IEEE80211AC=1
            ;;
	--ieee80211ax)
	    shift
	    IEEE80211AX=1
	    ;;
        --ht_capab)
            shift
            HT_CAPAB="$1"
            shift
            ;;
        --vht_capab)
            shift
            VHT_CAPAB="$1"
            shift
            ;;
        --vht-chwidth)
            shift
            VHT_CHWIDTH="$1"
            shift
            ;;
        --driver)
            shift
            DRIVER="$1"
            shift
            ;;
        --no-virt)
            shift
            NO_VIRT=1
            ;;
        --fix-unmanaged)
            shift
            FIX_UNMANAGED=1
            ;;
        --country)
            shift
            COUNTRY="$1"
            shift
            ;;
        --freq-band)
            shift
            FREQ_BAND="$1"
            FREQ_BAND_SET=1
            shift
            ;;
        --mac)
            shift
            NEW_MACADDR="$1"
            shift
            ;;
        --dhcp-dns)
            shift
            DHCP_DNS="$1"
            shift
            ;;
        --daemon)
            shift
            DAEMONIZE=1
            ;;
        --pidfile)
            shift
            DAEMON_PIDFILE="$1"
            shift
            ;;
        --logfile)
            shift
            DAEMON_LOGFILE="$1"
            shift
            ;;
        --dns-logfile)
            shift
            DNS_LOGFILE="$1"
            shift
            ;;
        --stop)
            shift
            STOP_ID="$1"
            shift
            ;;
        --list)
            shift
            LIST_RUNNING=1
            echo -e "WARN: --list is deprecated, use --list-running instead.\n" >&2
            ;;
        --list-running)
            shift
            LIST_RUNNING=1
            ;;
        --list-clients)
            shift
            LIST_CLIENTS_ID="$1"
            shift
            ;;
        --no-haveged)
            shift
            NO_HAVEGED=1
            ;;
        --psk)
            shift
            USE_PSK=1
            ;;
        --no-dns)
            shift
            NO_DNS=1
            ;;
        --no-dnsmasq)
            shift
            NO_DNSMASQ=1
            ;;
        --redirect-to-localhost)
            shift
            REDIRECT_TO_LOCALHOST=1
            ;;
        --hostapd-debug)
            shift
            if [ "x$1" = "x1" ]; then
                HOSTAPD_DEBUG_ARGS+="-d "
            elif [ "x$1" = "x2" ]; then
                HOSTAPD_DEBUG_ARGS+="-dd "
            else
                printf "Error: argument for --hostapd-debug expected 1 or 2, got %s\n" "$1"
                exit 1
            fi
            shift
            ;;
        --hostapd-timestamps)
            shift
            HOSTAPD_DEBUG_ARGS+="-t "
            ;;
        --mkconfig)
            shift
            STORE_CONFIG="$1"
            shift
            ;;
        --config)
            shift
            shift
            ;;
        --)
            shift
            break
            ;;
    esac
done

# Load positional args from config file, if needed
if [[ -n "$LOAD_CONFIG" && $# -eq 0 ]]; then
    i=0
    # set arguments in order
    for x in WIFI_IFACE INTERNET_IFACE SSID PASSPHRASE; do
        if eval "[[ -n \"\$${x}\" ]]"; then
            eval "set -- \"\${@:1:$i}\" \"\$${x}\""
            ((i++))
        fi
        # we unset the variable to avoid any problems later
        eval "unset $x"
    done
fi

# Check if required number of positional args are present
if [[ $# -lt 1 && $FIX_UNMANAGED -eq 0  && -z "$STOP_ID" &&
      $LIST_RUNNING -eq 0 && -z "$LIST_CLIENTS_ID" ]]; then
    usage >&2
    exit 1
fi

# Set NO_DNS, if dnsmasq is disabled
if [[ $NO_DNSMASQ -eq 1 ]]; then
  NO_DNS=1
fi

trap "cleanup_lock" EXIT

if [[ $(id -u) -ne 0 ]]; then
    echo "create_ap must be run as root." >&2
    exit 1
fi

if ! init_lock; then
    echo "ERROR: Failed to initialize lock" >&2
    exit 1
fi

# if the user press ctrl+c or we get USR1 signal
# then run clean_exit()
trap "clean_exit" SIGINT SIGUSR1
# if we get USR2 signal then run die().
trap "die" SIGUSR2

[[ -n "$STORE_CONFIG" ]] && write_config "$@"

if [[ $LIST_RUNNING -eq 1 ]]; then
    #echo -e "List of running $PROGNAME instances:\n"
    list_running
    exit 0
fi

if [[ -n "$LIST_CLIENTS_ID" ]]; then
    list_clients "$LIST_CLIENTS_ID"
    exit 0
fi

if [[ -n "$STOP_ID" ]]; then
    echo "Trying to kill $PROGNAME instance associated with $STOP_ID..."
    send_stop "$STOP_ID"
    exit 0
fi

if [[ $FIX_UNMANAGED -eq 1 ]]; then
    echo "Trying to fix unmanaged status in NetworkManager..."
    networkmanager_fix_unmanaged
    exit 0
fi

if [[ $DAEMONIZE -eq 1 && $RUNNING_AS_DAEMON -eq 0 ]]; then
    # Assume we're running underneath a service manager if PIDFILE is set
    # and don't clobber it's output with a useless message
    if [ -z "$DAEMON_PIDFILE" ]; then
        echo "Running as Daemon..."
    fi
    # run a detached create_ap
    RUNNING_AS_DAEMON=1 setsid "$0" "${ARGS[@]}" >>$DAEMON_LOGFILE 2>&1 &
    exit 0
elif [[ $RUNNING_AS_DAEMON -eq 1 && -n "$DAEMON_PIDFILE" ]]; then
    echo $$ >$DAEMON_PIDFILE
fi

# --mkconfig (and the GUI) store FREQ_BAND=default when no band was requested.
# Normalise it back to the built-in default, otherwise the literal string
# "default" falls through every 2.4GHz comparison below and the hotspot ends up
# on 5GHz channel 36 without anyone asking for it.
if [[ $FREQ_BAND == default ]]; then
    FREQ_BAND=2.4
fi

if [[ $FREQ_BAND_SET != 0 ]]; then
    if [[ $FREQ_BAND != 2.4 && $FREQ_BAND != 5 ]]; then
        echo "ERROR: Invalid frequency band" >&2
        exit 1
    fi
fi

if [[ $CHANNEL == default ]]; then
    USING_DEFAULT_CHANNEL=1
    if [[ $FREQ_BAND == 2.4 ]]; then
        CHANNEL=1
    else
        CHANNEL=36
    fi
else
    USING_DEFAULT_CHANNEL=0
fi


if [[ $FREQ_BAND != 5 && $CHANNEL -gt 14 ]]; then
    echo "Channel number is greater than 14, assuming 5GHz frequency band"
    FREQ_BAND=5
fi

WIFI_IFACE=$1

if ! is_wifi_interface ${WIFI_IFACE}; then
    echo "ERROR: '${WIFI_IFACE}' is not a WiFi interface" >&2
    exit 1
fi

if ! can_be_ap ${WIFI_IFACE}; then
    echo "ERROR: Your adapter does not support AP (master) mode" >&2
    exit 1
fi

if ! can_be_sta_and_ap ${WIFI_IFACE}; then
    if is_wifi_connected ${WIFI_IFACE}; then
        echo "ERROR: Your adapter can not be a station (i.e. be connected) and an AP at the same time" >&2
        exit 1
    elif [[ $NO_VIRT -eq 0 ]]; then
        echo "WARN: Your adapter does not fully support AP virtual interface, enabling --no-virt" >&2
        NO_VIRT=1
    fi
fi

HOSTAPD=$(which hostapd)

if [[ ! -x "$HOSTAPD" ]]; then
    echo "ERROR: hostapd not found." >&2
    exit 1
fi

if [[ $(get_adapter_kernel_module ${WIFI_IFACE}) =~ ^(8192[cd][ue]|8723a[sue])$ ]]; then
    if ! strings "$HOSTAPD" | grep -m1 rtl871xdrv > /dev/null 2>&1; then
        echo "ERROR: You need to patch your hostapd with rtl871xdrv patches." >&2
        exit 1
    fi

    if [[ $DRIVER != "rtl871xdrv" ]]; then
        echo "WARN: Your adapter needs rtl871xdrv, enabling --driver=rtl871xdrv" >&2
        DRIVER=rtl871xdrv
    fi
fi

if [[ "$SHARE_METHOD" != "nat" && "$SHARE_METHOD" != "bridge" && "$SHARE_METHOD" != "none" ]]; then
    echo "ERROR: Wrong Internet sharing method" >&2
    echo
    usage >&2
    exit 1
fi

if [[ -n "$NEW_MACADDR" ]]; then
    if ! is_macaddr "$NEW_MACADDR"; then
        echo "ERROR: '${NEW_MACADDR}' is not a valid MAC address" >&2
        exit 1
    fi

    if ! is_unicast_macaddr "$NEW_MACADDR"; then
        echo "ERROR: The first byte of MAC address (${NEW_MACADDR}) must be even" >&2
        exit 1
    fi

    if [[ $(get_all_macaddrs | grep -c ${NEW_MACADDR}) -ne 0 ]]; then
        echo "WARN: MAC address '${NEW_MACADDR}' already exists. Because of this, you may encounter some problems" >&2
    fi
fi

if [[ "$SHARE_METHOD" != "none" ]]; then
    MIN_REQUIRED_ARGS=2
else
    MIN_REQUIRED_ARGS=1
fi

if [[ $# -gt $MIN_REQUIRED_ARGS ]]; then
    if [[ "$SHARE_METHOD" != "none" ]]; then
        if [[ $# -ne 3 && $# -ne 4 ]]; then
            usage >&2
            exit 1
        fi
        INTERNET_IFACE="$2"
        SSID="$3"
        PASSPHRASE="$4"
    else
        if [[ $# -ne 2 && $# -ne 3 ]]; then
            usage >&2
            exit 1
        fi
        SSID="$2"
        PASSPHRASE="$3"
    fi
else
    if [[ "$SHARE_METHOD" != "none" ]]; then
        if [[ $# -ne 2 ]]; then
            usage >&2
            exit 1
        fi
        INTERNET_IFACE="$2"
    fi
    if tty -s; then
        while :; do
            read -p "SSID: " SSID
            if [[ ${#SSID} -lt 1 || ${#SSID} -gt 32 ]]; then
                echo "ERROR: Invalid SSID length ${#SSID} (expected 1..32)" >&2
                continue
            fi
            break
        done
        while :; do
            if [[ $USE_PSK -eq 0 ]]; then
                read -p "Passphrase: " -s PASSPHRASE
                echo
                if [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -lt 8 ]] || [[ ${#PASSPHRASE} -gt 63 ]]; then
                    echo "ERROR: Invalid passphrase length ${#PASSPHRASE} (expected 8..63)" >&2
                    continue
                fi
                read -p "Retype passphrase: " -s PASSPHRASE2
                echo
                if [[ "$PASSPHRASE" != "$PASSPHRASE2" ]]; then
                    echo "Passphrases do not match."
                else
                    break
                fi
            else
                read -p "PSK: " PASSPHRASE
                echo
                if [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -ne 64 ]]; then
                    echo "ERROR: Invalid pre-shared-key length ${#PASSPHRASE} (expected 64)" >&2
                    continue
                fi
            fi
        done
    else
        read SSID
        read PASSPHRASE
    fi
fi

if [[ "$SHARE_METHOD" != "none" ]] && ! is_interface $INTERNET_IFACE; then
    echo "ERROR: '${INTERNET_IFACE}' is not an interface" >&2
    exit 1
fi

if [[ ${#SSID} -lt 1 || ${#SSID} -gt 32 ]]; then
    echo "ERROR: Invalid SSID length ${#SSID} (expected 1..32)" >&2
    exit 1
fi

if [[ $USE_PSK -eq 0 ]]; then
    if [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -lt 8 ]] || [[ ${#PASSPHRASE} -gt 63 ]]; then
        echo "ERROR: Invalid passphrase length ${#PASSPHRASE} (expected 8..63)" >&2
        exit 1
    fi
elif [[ ${#PASSPHRASE} -gt 0 && ${#PASSPHRASE} -ne 64 ]]; then
    echo "ERROR: Invalid pre-shared-key length ${#PASSPHRASE} (expected 64)" >&2
    exit 1
fi

if [[ $(get_adapter_kernel_module ${WIFI_IFACE}) =~ ^rtl[0-9].*$ ]]; then
    if [[ -n "$PASSPHRASE" ]]; then
        echo "WARN: Realtek drivers usually have problems with WPA1, enabling -w 2" >&2
        WPA_VERSION=2
    fi
    echo "WARN: If AP doesn't work, please read: howto/realtek.md" >&2
fi

if [[ $NO_VIRT -eq 1 && "$WIFI_IFACE" == "$INTERNET_IFACE" ]]; then
    echo -n "ERROR: You can not share your connection from the same" >&2
    echo " interface if you are using --no-virt option." >&2
    exit 1
fi

mutex_lock
trap "cleanup" EXIT
CONFDIR=$(mktemp -d /tmp/create_ap.${WIFI_IFACE}.conf.XXXXXXXX)
echo "Config dir: $CONFDIR"
echo "PID: $$"
echo $$ > $CONFDIR/pid

# to make --list-running work from any user, we must give read
# permissions to $CONFDIR and $CONFDIR/pid
chmod 755 $CONFDIR
chmod 444 $CONFDIR/pid

COMMON_CONFDIR=/tmp/create_ap.common.conf
mkdir -p $COMMON_CONFDIR

if [[ "$SHARE_METHOD" == "nat" ]]; then
    echo $INTERNET_IFACE > $CONFDIR/nat_internet_iface
    cp_n /proc/sys/net/ipv4/conf/$INTERNET_IFACE/forwarding \
       $COMMON_CONFDIR/${INTERNET_IFACE}_forwarding
fi
cp_n /proc/sys/net/ipv4/ip_forward $COMMON_CONFDIR
if [[ -e /proc/sys/net/bridge/bridge-nf-call-iptables ]]; then
    cp_n /proc/sys/net/bridge/bridge-nf-call-iptables $COMMON_CONFDIR
fi
mutex_unlock

if [[ "$SHARE_METHOD" == "bridge" ]]; then
    if is_bridge_interface $INTERNET_IFACE; then
        BRIDGE_IFACE=$INTERNET_IFACE
    else
        BRIDGE_IFACE=$(alloc_new_iface br)
    fi
fi

if [[ $USE_IWCONFIG -eq 0 ]]; then
    iw dev ${WIFI_IFACE} set power_save off
fi

if [[ $NO_VIRT -eq 0 ]]; then
    VWIFI_IFACE=$(alloc_new_iface ap)

    # in NetworkManager 0.9.9 and above we can set the interface as unmanaged without
    # the need of MAC address, so we set it before we create the virtual interface.
    if networkmanager_is_running && [[ $NM_OLDER_VERSION -eq 0 ]]; then
        echo -n "Network Manager found, set ${VWIFI_IFACE} as unmanaged device... "
        networkmanager_add_unmanaged ${VWIFI_IFACE}
        # do not call networkmanager_wait_until_unmanaged because interface does not
        # exist yet
        echo "DONE"
    fi


    if is_wifi_connected ${WIFI_IFACE}; then
        WIFI_IFACE_FREQ=$(iw dev ${WIFI_IFACE} link | grep -i freq | awk '{print $2}')
        WIFI_IFACE_CHANNEL=$(ieee80211_frequency_to_channel ${WIFI_IFACE_FREQ})
        if is_5ghz_frequency $WIFI_IFACE_FREQ; then
            WIFI_IFACE_BAND=5
        else
            WIFI_IFACE_BAND=2.4
        fi
        echo "${WIFI_IFACE} is already associated with channel ${WIFI_IFACE_CHANNEL} (${WIFI_IFACE_FREQ} MHz)"

        AP_MAX_CHANNELS=$(get_ap_max_channels ${WIFI_IFACE})

        if [[ "$AP_MAX_CHANNELS" == 1 && $WIFI_IFACE_CHANNEL -ne $CHANNEL ]]; then
            # The adapter only offers AP mode in single-channel interface
            # combinations, so the AP has to sit on the channel the station half
            # already uses. iwlwifi behaves this way since Linux 6.11, see
            # https://github.com/lakinduakash/linux-wifi-hotspot/issues/435
            # Without this hostapd only reports "Failed to set beacon parameters".
            if [[ $USING_DEFAULT_CHANNEL -eq 0 ]] ||
               [[ $FREQ_BAND_SET -eq 1 && $FREQ_BAND != $WIFI_IFACE_BAND ]]; then
                die "Your adapter can only run an access point on the channel it is already
       connected to, but a different channel was requested.

           requested: channel ${CHANNEL} (${FREQ_BAND}GHz)
           connected: channel ${WIFI_IFACE_CHANNEL} (${WIFI_IFACE_BAND}GHz) on ${WIFI_IFACE}

       Use one of these instead:
         * run the hotspot on the channel already in use:
               --freq-band ${WIFI_IFACE_BAND} -c ${WIFI_IFACE_CHANNEL}
         * connect ${WIFI_IFACE} to a network on channel ${CHANNEL} first
         * disconnect ${WIFI_IFACE} and share a different uplink (e.g. ethernet)"
            fi
            echo "Adapter supports the AP on one channel only, following ${WIFI_IFACE} to channel ${WIFI_IFACE_CHANNEL}"
            CHANNEL=$WIFI_IFACE_CHANNEL
            FREQ_BAND=$WIFI_IFACE_BAND
        elif [[ $FREQ_BAND_SET -eq 0 ]]; then
            # Multi-channel capable adapter: keep following the station's band
            # when no specific band was requested.
            FREQ_BAND=$WIFI_IFACE_BAND
        fi
    fi


    VIRTDIEMSG="Maybe your WiFi adapter does not fully support virtual interfaces.
       Try again with --no-virt."
    echo -n "Creating a virtual WiFi interface... "

    if iw dev ${WIFI_IFACE} interface add ${VWIFI_IFACE} type __ap; then
        # now we can call networkmanager_wait_until_unmanaged
        networkmanager_is_running && [[ $NM_OLDER_VERSION -eq 0 ]] && networkmanager_wait_until_unmanaged ${VWIFI_IFACE}
        echo "${VWIFI_IFACE} created."
    else
        VWIFI_IFACE=
        die "$VIRTDIEMSG"
    fi
    OLD_MACADDR=$(get_macaddr ${VWIFI_IFACE})
    if [[ -z "$NEW_MACADDR" && $(get_all_macaddrs | grep -c ${OLD_MACADDR}) -ne 1 ]]; then
        NEW_MACADDR=$(get_new_macaddr ${VWIFI_IFACE})
    fi
    WIFI_IFACE=${VWIFI_IFACE}
else
    OLD_MACADDR=$(get_macaddr ${WIFI_IFACE})
fi

mutex_lock
echo $WIFI_IFACE > $CONFDIR/wifi_iface
chmod 444 $CONFDIR/wifi_iface
mutex_unlock

if [[ -n "$COUNTRY" && $USE_IWCONFIG -eq 0 ]]; then
    iw reg set "$COUNTRY"
fi

# Fallback to currently connected channel if the adapter can not transmit to the default channel (1)
if can_transmit_to_channel "${WIFI_IFACE}" "${CHANNEL}"; then
    echo "Transmitting to channel ${CHANNEL}..."
else
    # WIFI_IFACE_CHANNEL is only set when the adapter is associated. Without the
    # emptiness test the comparison below evaluates it as 0, so a disconnected
    # adapter "falls back" to an empty channel:
    # https://github.com/lakinduakash/linux-wifi-hotspot/issues/206
    if [[ $USING_DEFAULT_CHANNEL -eq 1 && -n "$WIFI_IFACE_CHANNEL" && $WIFI_IFACE_CHANNEL -ne $CHANNEL ]]; then
        echo -e "Your adapter can not transmit to channel ${CHANNEL}" >&2
        CHANNEL=$WIFI_IFACE_CHANNEL
        echo -e "Falling back to channel ${CHANNEL}"
        can_transmit_to_channel "${WIFI_IFACE}" "${CHANNEL}" || die "Your adapter can not transmit to channel ${CHANNEL}, frequency band ${FREQ_BAND}GHz.$(regdomain_hint ${WIFI_IFACE})"
    else
        die "Your adapter can not transmit to channel ${CHANNEL}, frequency band ${FREQ_BAND}GHz.$(regdomain_hint ${WIFI_IFACE})"
    fi
fi

if networkmanager_exists && ! networkmanager_iface_is_unmanaged ${WIFI_IFACE}; then
    echo -n "Network Manager found, set ${WIFI_IFACE} as unmanaged device... "
    networkmanager_add_unmanaged ${WIFI_IFACE}

    if networkmanager_is_running; then
        networkmanager_wait_until_unmanaged ${WIFI_IFACE}
    fi

    echo "DONE"
fi

[[ $HIDDEN -eq 1 ]] && echo "Access Point's SSID is hidden!"

[[ $MAC_FILTER -eq 1 ]] && echo "MAC address filtering is enabled!"

[[ $ISOLATE_CLIENTS -eq 1 ]] && echo "Access Point's clients will be isolated!"

# hostapd config
cat << EOF > $CONFDIR/hostapd.conf
beacon_int=100
ssid=${SSID}
interface=${WIFI_IFACE}
driver=${DRIVER}
channel=${CHANNEL}
ctrl_interface=$CONFDIR/hostapd_ctrl
ctrl_interface_group=0
ignore_broadcast_ssid=$HIDDEN
ap_isolate=$ISOLATE_CLIENTS
EOF

if [[ -n "$COUNTRY" ]]; then
    cat << EOF >> $CONFDIR/hostapd.conf
country_code=${COUNTRY}
ieee80211d=1
EOF
fi

if [[ $FREQ_BAND == 2.4 ]]; then
    echo "hw_mode=g" >> $CONFDIR/hostapd.conf
else
    echo "hw_mode=a" >> $CONFDIR/hostapd.conf
fi

if [[ $MAC_FILTER -eq 1 ]]; then
    cat << EOF >> $CONFDIR/hostapd.conf
macaddr_acl=${MAC_FILTER}
accept_mac_file=${MAC_FILTER_ACCEPT}
EOF
fi

# Work out how wide the channel can actually be. hostapd stays on a 20/40MHz
# channel unless it is told the VHT operating width and centre frequency index,
# so "ieee80211ac=1" on its own never gets past 40MHz. Whatever is asked for is
# capped to what the regulatory domain allows, so an over-wide request degrades
# instead of leaving hostapd unable to start.
EFFECTIVE_BW=20
if [[ $FREQ_BAND == 5 ]] && [[ $IEEE80211N -eq 1 || $IEEE80211AC -eq 1 ]]; then
    case $VHT_CHWIDTH in
        20|40|80|160) EFFECTIVE_BW=$VHT_CHWIDTH ;;
        *) echo "WARN: invalid --vht-chwidth '${VHT_CHWIDTH}', using 80" >&2
           EFFECTIVE_BW=80 ;;
    esac

    # 802.11n tops out at 40MHz; 80 and 160 need VHT
    [[ $IEEE80211AC -eq 0 && $EFFECTIVE_BW -gt 40 ]] && EFFECTIVE_BW=40

    REG_BW=$(get_max_regulatory_bw $((5000 + CHANNEL * 5)))
    if [[ -n "$REG_BW" && $REG_BW -lt $EFFECTIVE_BW ]]; then
        echo "Regulatory domain limits channel ${CHANNEL} to ${REG_BW} MHz wide, using that instead of ${EFFECTIVE_BW} MHz"
        EFFECTIVE_BW=$REG_BW
    fi

    HT40_DIR=$(get_ht40_direction ${CHANNEL})
    if [[ $EFFECTIVE_BW -ge 40 && -n "$HT40_DIR" ]]; then
        HT_CAPAB=${HT_CAPAB//\[HT40+\]/[HT40${HT40_DIR}]}
        HT_CAPAB=${HT_CAPAB//\[HT40-\]/[HT40${HT40_DIR}]}
    else
        # a 40MHz capability on a 20MHz channel makes hostapd refuse to start
        HT_CAPAB=${HT_CAPAB//\[HT40+\]/}
        HT_CAPAB=${HT_CAPAB//\[HT40-\]/}
    fi
fi

if [[ $IEEE80211N -eq 1 ]]; then
    cat << EOF >> $CONFDIR/hostapd.conf
ieee80211n=1
ht_capab=${HT_CAPAB}
EOF
fi

if [[ $IEEE80211AC -eq 1 ]]; then
    echo "ieee80211ac=1" >> $CONFDIR/hostapd.conf

    if [[ $FREQ_BAND == 5 ]]; then
        case $EFFECTIVE_BW in
            160) VHT_OPER_CHWIDTH=2 ;;
            80)  VHT_OPER_CHWIDTH=1 ;;
            *)   VHT_OPER_CHWIDTH=0 ;;
        esac
        echo "vht_oper_chwidth=${VHT_OPER_CHWIDTH}" >> $CONFDIR/hostapd.conf

        if [[ $VHT_OPER_CHWIDTH -ne 0 ]]; then
            VHT_SEG0=$(get_vht_seg0_idx ${CHANNEL} ${EFFECTIVE_BW})
            if [[ -n "$VHT_SEG0" ]]; then
                echo "vht_oper_centr_freq_seg0_idx=${VHT_SEG0}" >> $CONFDIR/hostapd.conf
            else
                # no known centre index for this channel, stay narrow rather
                # than emit a width hostapd can not place
                sed -i 's/^vht_oper_chwidth=.*/vht_oper_chwidth=0/' $CONFDIR/hostapd.conf
            fi
        fi
    fi
fi

if [[ $IEEE80211AX -eq 1 ]]; then
    echo "ieee80211ax=1" >> $CONFDIR/hostapd.conf
fi

if [[ -n "$VHT_CAPAB" ]]; then
    echo "vht_capab=${VHT_CAPAB}" >> $CONFDIR/hostapd.conf
fi

if [[ $IEEE80211N -eq 1 ]] || [[ $IEEE80211AC -eq 1 ]]; then
    echo "wmm_enabled=1" >> $CONFDIR/hostapd.conf
fi

if [[ -n "$PASSPHRASE" ]]; then
    if [[ "$WPA_VERSION" == "1+2" ]]; then
        WPA_VERSION=2 # Assuming you want to default to WPA2 for the "1+2" setting
    fi
    if [[ $USE_PSK -eq 0 ]]; then
        WPA_KEY_TYPE=passphrase
    else
        WPA_KEY_TYPE=psk
    fi

    if [[ "$WPA_VERSION" == "3" ]]; then
        # Configuring for WPA3 Transition Mode
        # 80211w must be 1 or Apple Devices will not connect. 
        # 1 is the only valid value for WPA3 Transition Mode
        cat << EOF >> $CONFDIR/hostapd.conf
wpa=2
wpa_${WPA_KEY_TYPE}=${PASSPHRASE}
wpa_key_mgmt=WPA-PSK SAE
wpa_pairwise=CCMP
rsn_pairwise=CCMP
ieee80211w=1
EOF
    else
        # Original configuration for WPA_VERSION other than 3
        cat << EOF >> $CONFDIR/hostapd.conf
wpa=${WPA_VERSION}
wpa_${WPA_KEY_TYPE}=${PASSPHRASE}
wpa_key_mgmt=WPA-PSK
wpa_pairwise=CCMP
rsn_pairwise=CCMP
EOF
    fi
fi


if [[ "$SHARE_METHOD" == "bridge" ]]; then
    echo "bridge=${BRIDGE_IFACE}" >> $CONFDIR/hostapd.conf
elif [[ $NO_DNSMASQ -eq 0 ]]; then
    # dnsmasq config (dhcp + dns)
    DNSMASQ_VER=$(dnsmasq -v | grep -m1 -oE '[0-9]+(\.[0-9]+)*\.[0-9]+')
    version_cmp $DNSMASQ_VER 2.63
    if [[ $? -eq 1 ]]; then
        DNSMASQ_BIND=bind-interfaces
    else
        DNSMASQ_BIND=bind-dynamic
    fi
    if [[ "$DHCP_DNS" == "gateway" ]]; then
        DHCP_DNS="$GATEWAY"
    fi
    cat << EOF > $CONFDIR/dnsmasq.conf
listen-address=${GATEWAY}
${DNSMASQ_BIND}
dhcp-range=${GATEWAY%.*}.1,${GATEWAY%.*}.254,255.255.255.0,24h
dhcp-option-force=option:router,${GATEWAY}
dhcp-option-force=option:dns-server,${DHCP_DNS}
EOF
    MTU=$(get_mtu $INTERNET_IFACE)
    [[ -n "$MTU" ]] && echo "dhcp-option-force=option:mtu,${MTU}" >> $CONFDIR/dnsmasq.conf
    [[ $ETC_HOSTS -eq 0 ]] && echo no-hosts >> $CONFDIR/dnsmasq.conf
    [[ -n "$ADDN_HOSTS" ]] && echo "addn-hosts=${ADDN_HOSTS}" >> $CONFDIR/dnsmasq.conf
    if [[ -n "$DHCP_HOSTS" ]]; then
        for HOST in $DHCP_HOSTS; do
            echo "dhcp-host=${HOST}" >> $CONFDIR/dnsmasq.conf
        done
    fi


    if [[ -n "$DNS_LOGFILE" ]]; then
        cat << EOF >> $CONFDIR/dnsmasq.conf
log-queries
log-facility=${DNS_LOGFILE}
EOF
    fi
    if [[ "$SHARE_METHOD" == "none" && "$REDIRECT_TO_LOCALHOST" == "1" ]]; then
        cat << EOF >> $CONFDIR/dnsmasq.conf
address=/#/$GATEWAY
EOF
    fi
fi

# initialize WiFi interface
if [[ $NO_VIRT -eq 0 && -n "$NEW_MACADDR" ]]; then
    ip link set dev ${WIFI_IFACE} address ${NEW_MACADDR} || die "$VIRTDIEMSG"
fi

ip link set down dev ${WIFI_IFACE} || die "$VIRTDIEMSG"
ip addr flush ${WIFI_IFACE} || die "$VIRTDIEMSG"

if [[ $NO_VIRT -eq 1 && -n "$NEW_MACADDR" ]]; then
    ip link set dev ${WIFI_IFACE} address ${NEW_MACADDR} || die
fi

if [[ "$SHARE_METHOD" != "bridge" ]]; then
    ip link set up dev ${WIFI_IFACE} || die "$VIRTDIEMSG"
    ip addr add ${GATEWAY}/24 broadcast ${GATEWAY%.*}.255 dev ${WIFI_IFACE} || die "$VIRTDIEMSG"
fi

# enable Internet sharing
if [[ "$SHARE_METHOD" != "none" ]]; then
    echo "Sharing Internet using method: $SHARE_METHOD"
    if [[ "$SHARE_METHOD" == "nat" ]]; then
        iptables -w -t nat -I POSTROUTING -s ${GATEWAY%.*}.0/24 ! -o ${WIFI_IFACE} -j MASQUERADE || die
        iptables -w -I FORWARD -i ${WIFI_IFACE} -s ${GATEWAY%.*}.0/24 -j ACCEPT || die
        iptables -w -I FORWARD -i ${INTERNET_IFACE} -d ${GATEWAY%.*}.0/24 -j ACCEPT || die
        echo 1 > /proc/sys/net/ipv4/conf/$INTERNET_IFACE/forwarding || die
        echo 1 > /proc/sys/net/ipv4/ip_forward || die
        # to enable clients to establish PPTP connections we must
        # load nf_nat_pptp module
        modprobe nf_nat_pptp > /dev/null 2>&1
    elif [[ "$SHARE_METHOD" == "bridge" ]]; then
        # disable iptables rules for bridged interfaces
        if [[ -e /proc/sys/net/bridge/bridge-nf-call-iptables ]]; then
            echo 0 > /proc/sys/net/bridge/bridge-nf-call-iptables
        fi

        # to initialize the bridge interface correctly we need to do the following:
        #
        # 1) save the IPs and route table of INTERNET_IFACE
        # 2) if NetworkManager is running set INTERNET_IFACE as unmanaged
        # 3) create BRIDGE_IFACE and attach INTERNET_IFACE to it
        # 4) set the previously saved IPs and route table to BRIDGE_IFACE
        #
        # we need the above because BRIDGE_IFACE is the master interface from now on
        # and it must know where is connected, otherwise connection is lost.
        if ! is_bridge_interface $INTERNET_IFACE; then
            echo -n "Create a bridge interface... "
            OLD_IFS="$IFS"
            IFS=$'\n'

            IP_ADDRS=( $(ip addr show $INTERNET_IFACE | grep -A 1 -E 'inet[[:blank:]]' | paste - -) )
            ROUTE_ADDRS=( $(ip route show dev $INTERNET_IFACE) )

            IFS="$OLD_IFS"

            if networkmanager_is_running; then
                networkmanager_add_unmanaged $INTERNET_IFACE
                networkmanager_wait_until_unmanaged $INTERNET_IFACE
            fi

            # create bridge interface
            ip link add name $BRIDGE_IFACE type bridge || die
            ip link set dev $BRIDGE_IFACE up || die
            # set 0ms forward delay
            echo -n 0 > /sys/class/net/$BRIDGE_IFACE/bridge/forward_delay

            # attach internet interface to bridge interface
            ip link set dev $INTERNET_IFACE promisc on || die
            ip link set dev $INTERNET_IFACE up || die
            ip link set dev $INTERNET_IFACE master $BRIDGE_IFACE || die

            ip addr flush $INTERNET_IFACE
            for x in "${IP_ADDRS[@]}"; do
                x="${x/inet/}"
                x="${x/secondary/}"
                x="${x/dynamic/}"
                x=$(echo $x | sed 's/\([0-9]\)sec/\1/g')
                x="${x/${INTERNET_IFACE}/}"
                ip addr add $x dev $BRIDGE_IFACE || die
            done

            # remove any existing entries that were added from 'ip addr add'
            ip route flush dev $INTERNET_IFACE
            ip route flush dev $BRIDGE_IFACE

            # we must first add the entries that specify the subnets and then the
            # gateway entry, otherwise 'ip addr add' will return an error
            for x in "${ROUTE_ADDRS[@]}"; do
                [[ "$x" == default* ]] && continue
                ip route add $x dev $BRIDGE_IFACE || die
            done

            for x in "${ROUTE_ADDRS[@]}"; do
                [[ "$x" != default* ]] && continue
                ip route add $x dev $BRIDGE_IFACE || die
            done

            echo "$BRIDGE_IFACE created."
        fi
    fi
else
    echo "No Internet sharing"
fi

# start dhcp + dns (optional)
if [[ "$SHARE_METHOD" != "bridge" ]]; then
    if [[ $NO_DNS -eq 0 ]]; then
        DNS_PORT=5353
        iptables -w -I INPUT -p tcp -m tcp --dport $DNS_PORT -j ACCEPT || die
        iptables -w -I INPUT -p udp -m udp --dport $DNS_PORT -j ACCEPT || die
        iptables -w -t nat -I PREROUTING -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} \
            -p tcp -m tcp --dport 53 -j REDIRECT --to-ports $DNS_PORT || die
        iptables -w -t nat -I PREROUTING -s ${GATEWAY%.*}.0/24 -d ${GATEWAY} \
            -p udp -m udp --dport 53 -j REDIRECT --to-ports $DNS_PORT || die
    else
        DNS_PORT=0
    fi

    if [[ $NO_DNSMASQ -eq 0 ]]; then
      iptables -w -I INPUT -p udp -m udp --dport 67 -j ACCEPT || die


      # apparmor does not allow dnsmasq to read files.
      # remove restriction.

      if COMPLAIN_CMD=$(command -v complain || command -v aa-complain); then
        $COMPLAIN_CMD dnsmasq
      fi

      umask 0033
      dnsmasq -C $CONFDIR/dnsmasq.conf -x $CONFDIR/dnsmasq.pid -l $CONFDIR/dnsmasq.leases -p $DNS_PORT || die
      umask $SCRIPT_UMASK
    fi
fi

# start access point
echo "hostapd command-line interface: hostapd_cli -p $CONFDIR/hostapd_ctrl"

if [[ $NO_HAVEGED -eq 0 ]]; then
    haveged_watchdog &
    HAVEGED_WATCHDOG_PID=$!
fi

# Pass hostapd's output through unchanged, but watch it for clients that
# associate and are dropped again without ever completing the WPA handshake.
# Some adapters beacon happily while their firmware rejects AP-mode key
# setup, so the AP looks up and every client silently fails to connect.
handshake_watchdog() {
    local line assoc=0 checked=0

    while IFS= read -r line; do
        echo "$line"

        [[ $checked -eq 1 ]] && continue

        case "$line" in
            *AP-STA-CONNECTED*|*"pairwise key handshake completed"*)
                # a client got through, nothing to warn about
                checked=1
                ;;
            *"IEEE 802.11: associated"*)
                assoc=$((assoc + 1))
                if [[ $assoc -ge 3 ]]; then
                    checked=1
                    echo "WARN: clients keep associating and disconnecting without completing the" >&2
                    echo "      WPA handshake. Your adapter's firmware may not support AP mode with" >&2
                    echo "      encryption. Check 'dmesg' for driver errors, and try an open network" >&2
                    echo "      (empty passphrase) to see whether encryption is the problem." >&2
                fi
                ;;
        esac
    done
}

# start hostapd (use stdbuf when available for no delayed output in programs that redirect stdout)
STDBUF_PATH=`which stdbuf`
if [ $? -eq 0 ]; then
    STDBUF_PATH=$STDBUF_PATH" -oL"
fi
$STDBUF_PATH $HOSTAPD $HOSTAPD_DEBUG_ARGS $CONFDIR/hostapd.conf > >(handshake_watchdog) &
HOSTAPD_PID=$!
echo $HOSTAPD_PID > $CONFDIR/hostapd.pid

if ! wait $HOSTAPD_PID; then
    echo -e "\nError: Failed to run hostapd, maybe a program is interfering." >&2
    if networkmanager_is_running; then
        echo "If an error like 'n80211: Could not configure driver mode' was thrown" >&2
        echo "try running the following before starting create_ap:" >&2
        if [[ $NM_OLDER_VERSION -eq 1 ]]; then
            echo "    nmcli nm wifi off" >&2
        else
            echo "    nmcli r wifi off" >&2
        fi
        echo "    rfkill unblock wlan" >&2
    fi
    die
fi

clean_exit

# Local Variables:
# tab-width: 4
# indent-tabs-mode: nil
# End:

# vim: et sts=4 sw=4
