#!/usr/bin/env python3
#
# Copyright (C) 2019 VyOS maintainers and contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 2 or later as
# published by the Free Software Foundation.
#
# 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/>.

import os

from jinja2 import Template
from copy import deepcopy
from sys import exit
from stat import S_IRWXU,S_IRGRP,S_IXGRP,S_IROTH,S_IXOTH
from pwd import getpwnam
from grp import getgrnam
from re import findall

from subprocess import Popen, PIPE
from psutil import pid_exists
from netifaces import interfaces
from netaddr import *

from vyos.ifconfig import EthernetIf
from vyos.ifconfig_vlan import apply_vlan_config, verify_vlan_config
from vyos.configdict import list_diff, vlan_to_dict
from vyos.config import Config
from vyos import ConfigError

user = 'root'
group = 'vyattacfg'

# Please be careful if you edit the template.
config_hostapd_tmpl = """
### Autogenerated by interfaces-wireless.py ###
{% if description %}
# Description: {{ description }}
# User-friendly description of device; up to 32 octets encoded in UTF-8
device_name={{ description | truncate(32, True) }}
{% endif %}

# AP netdevice name (without 'ap' postfix, i.e., wlan0 uses wlan0ap for
# management frames with the Host AP driver); wlan0 with many nl80211 drivers
# Note: This attribute can be overridden by the values supplied with the '-i'
# command line parameter.
interface={{ intf }}

# Driver interface type (hostap/wired/none/nl80211/bsd);
# default: hostap). nl80211 is used with all Linux mac80211 drivers.
# Use driver=none if building hostapd as a standalone RADIUS server that does
# not control any wireless/wired driver.
driver=nl80211

#
# What about bridge?
#   bridge=br0
#   wds_sta=1
#

# Levels (minimum value for logged events):
#  0 = verbose debugging
#  1 = debugging
#  2 = informational messages
#  3 = notification
#  4 = warning
logger_syslog=-1
logger_syslog_level=0
logger_stdout=-1
logger_stdout_level=0

{%- if country_code %}

# Country code (ISO/IEC 3166-1). Used to set regulatory domain.
# Set as needed to indicate country in which device is operating.
# This can limit available channels and transmit power.
country_code={{ country_code }}

# Enable IEEE 802.11d. This advertises the country_code and the set of allowed
# channels and transmit power levels based on the regulatory limits. The
# country_code setting must be configured with the correct country for
# IEEE 802.11d functions.
ieee80211d=1
{% endif %}

{%- if ssid %}

# SSID to be used in IEEE 802.11 management frames
ssid={{ ssid }}
{% endif %}

{%- if channel %}

# Channel number (IEEE 802.11)
# (default: 0, i.e., not set)
# Please note that some drivers do not use this value from hostapd and the
# channel will need to be configured separately with iwconfig.
#
# If CONFIG_ACS build option is enabled, the channel can be selected
# automatically at run time by setting channel=acs_survey or channel=0, both of
# which will enable the ACS survey based algorithm.
channel={{ channel }}
{% endif %}

{%- if mode %}

# Operation mode (a = IEEE 802.11a (5 GHz), b = IEEE 802.11b (2.4 GHz),
# g = IEEE 802.11g (2.4 GHz), ad = IEEE 802.11ad (60 GHz); a/g options are used
# with IEEE 802.11n (HT), too, to specify band). For IEEE 802.11ac (VHT), this
# needs to be set to hw_mode=a. For IEEE 802.11ax (HE) on 6 GHz this needs
# to be set to hw_mode=a. When using ACS (see channel parameter), a
# special value "any" can be used to indicate that any support band can be used.
# This special case is currently supported only with drivers with which
# offloaded ACS is used.
{% if 'n' in mode -%}
hw_mode=g
ieee80211n=1
{% elif 'ac' in mode -%}
hw_mode=a
ieee80211h=1
ieee80211ac=1
{% else -%}
hw_mode={{ mode }}
{% endif %}
{% endif %}

# ieee80211w: Whether management frame protection (MFP) is enabled
# 0 = disabled (default)
# 1 = optional
# 2 = required
{% if 'disabled' in mgmt_frame_protection -%}
ieee80211w=0
{% elif 'optional' in mgmt_frame_protection -%}
ieee80211w=1
{% elif 'required' in mgmt_frame_protection -%}
ieee80211w=2
{% endif %}

# ht_capab: HT capabilities (list of flags)
# LDPC coding capability: [LDPC] = supported
# Supported channel width set: [HT40-] = both 20 MHz and 40 MHz with secondary
#	channel below the primary channel; [HT40+] = both 20 MHz and 40 MHz
#	with secondary channel above the primary channel
#	(20 MHz only if neither is set)
#	Note: There are limits on which channels can be used with HT40- and
#	HT40+. Following table shows the channels that may be available for
#	HT40- and HT40+ use per IEEE 802.11n Annex J:
#	freq		HT40-		HT40+
#	2.4 GHz		5-13		1-7 (1-9 in Europe/Japan)
#	5 GHz		40,48,56,64	36,44,52,60
#	(depending on the location, not all of these channels may be available
#	for use)
#	Please note that 40 MHz channels may switch their primary and secondary
#	channels if needed or creation of 40 MHz channel maybe rejected based
#	on overlapping BSSes. These changes are done automatically when hostapd
#	is setting up the 40 MHz channel.
# Spatial Multiplexing (SM) Power Save: [SMPS-STATIC] or [SMPS-DYNAMIC]
#	(SMPS disabled if neither is set)
# HT-greenfield: [GF] (disabled if not set)
# Short GI for 20 MHz: [SHORT-GI-20] (disabled if not set)
# Short GI for 40 MHz: [SHORT-GI-40] (disabled if not set)
# Tx STBC: [TX-STBC] (disabled if not set)
# Rx STBC: [RX-STBC1] (one spatial stream), [RX-STBC12] (one or two spatial
#	streams), or [RX-STBC123] (one, two, or three spatial streams); Rx STBC
#	disabled if none of these set
# HT-delayed Block Ack: [DELAYED-BA] (disabled if not set)
# Maximum A-MSDU length: [MAX-AMSDU-7935] for 7935 octets (3839 octets if not
#	set)
# DSSS/CCK Mode in 40 MHz: [DSSS_CCK-40] = allowed (not allowed if not set)
# 40 MHz intolerant [40-INTOLERANT] (not advertised if not set)
# L-SIG TXOP protection support: [LSIG-TXOP-PROT] (disabled if not set)
{% if cap_ht %}
ht_capab=
{%- endif -%}

{%- if cap_ht_40mhz_incapable -%}
[40-INTOLERANT]
{%- endif -%}

{%- if cap_ht_delayed_block_ack -%}
[DELAYED-BA]
{%- endif -%}

{%- if cap_ht_dsss_cck_40 -%}
[DSSS_CCK-40]
{%- endif -%}

{%- if cap_ht_greenfield -%}
[GF]
{%- endif -%}

{%- if cap_ht_ldpc -%}
[LDPC]
{%- endif -%}

{%- if cap_ht_lsig_protection -%}
[LSIG-TXOP-PROT]
{%- endif -%}

{%- if cap_ht_max_amsdu -%}
[MAX-AMSDU-{{ cap_ht_max_amsdu }}]
{%- endif -%}

{%- if cap_ht_smps -%}
[SMPS-{{ cap_ht_smps | upper }}]
{%- endif -%}

{%- if cap_ht_chan_set_width -%}
{%- for csw in cap_ht_chan_set_width -%}
[{{ csw | upper }}]
{%- endfor -%}
{%- endif -%}

{%- if cap_ht_short_gi -%}
{%- for gi in cap_ht_short_gi -%}
[SHORT-GI-{{ gi }}]
{%- endfor -%}
{%- endif -%}

{%- if cap_ht_stbc_tx -%}
[TX-STBC]
{%- endif -%}
{%- if cap_ht_stbc_rx -%}
[RX-STBC{{ cap_ht_stbc_rx }}]
{%- endif %}

# Required for full HT and VHT functionality
wme_enabled=1

{% if cap_ht_powersave -%}
# WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD]
# Enable this flag if U-APSD supported outside hostapd (eg., Firmware/driver)
uapsd_advertisement_enabled=1
{%- endif %}

{% if cap_req_ht -%}
# Require stations to support HT PHY (reject association if they do not)
require_ht=1
{% endif %}

# vht_capab: VHT capabilities (list of flags)
#
# vht_max_mpdu_len: [MAX-MPDU-7991] [MAX-MPDU-11454]
# Indicates maximum MPDU length
# 0 = 3895 octets (default)
# 1 = 7991 octets
# 2 = 11454 octets
# 3 = reserved
#
# supported_chan_width: [VHT160] [VHT160-80PLUS80]
# Indicates supported Channel widths
# 0 = 160 MHz & 80+80 channel widths are not supported (default)
# 1 = 160 MHz channel width is supported
# 2 = 160 MHz & 80+80 channel widths are supported
# 3 = reserved
#
# Rx LDPC coding capability: [RXLDPC]
# Indicates support for receiving LDPC coded pkts
# 0 = Not supported (default)
# 1 = Supported
#
# Short GI for 80 MHz: [SHORT-GI-80]
# Indicates short GI support for reception of packets transmitted with TXVECTOR
# params format equal to VHT and CBW = 80Mhz
# 0 = Not supported (default)
# 1 = Supported
#
# Short GI for 160 MHz: [SHORT-GI-160]
# Indicates short GI support for reception of packets transmitted with TXVECTOR
# params format equal to VHT and CBW = 160Mhz
# 0 = Not supported (default)
# 1 = Supported
#
# Tx STBC: [TX-STBC-2BY1]
# Indicates support for the transmission of at least 2x1 STBC
# 0 = Not supported (default)
# 1 = Supported
#
# Rx STBC: [RX-STBC-1] [RX-STBC-12] [RX-STBC-123] [RX-STBC-1234]
# Indicates support for the reception of PPDUs using STBC
# 0 = Not supported (default)
# 1 = support of one spatial stream
# 2 = support of one and two spatial streams
# 3 = support of one, two and three spatial streams
# 4 = support of one, two, three and four spatial streams
# 5,6,7 = reserved
#
# SU Beamformer Capable: [SU-BEAMFORMER]
# Indicates support for operation as a single user beamformer
# 0 = Not supported (default)
# 1 = Supported
#
# SU Beamformee Capable: [SU-BEAMFORMEE]
# Indicates support for operation as a single user beamformee
# 0 = Not supported (default)
# 1 = Supported
#
# Compressed Steering Number of Beamformer Antennas Supported:
# [BF-ANTENNA-2] [BF-ANTENNA-3] [BF-ANTENNA-4]
#   Beamformee's capability indicating the maximum number of beamformer
#   antennas the beamformee can support when sending compressed beamforming
#   feedback
# If SU beamformer capable, set to maximum value minus 1
# else reserved (default)
#
# Number of Sounding Dimensions:
# [SOUNDING-DIMENSION-2] [SOUNDING-DIMENSION-3] [SOUNDING-DIMENSION-4]
# Beamformer's capability indicating the maximum value of the NUM_STS parameter
# in the TXVECTOR of a VHT NDP
# If SU beamformer capable, set to maximum value minus 1
# else reserved (default)
#
# MU Beamformer Capable: [MU-BEAMFORMER]
# Indicates support for operation as an MU beamformer
# 0 = Not supported or sent by Non-AP STA (default)
# 1 = Supported
#
# VHT TXOP PS: [VHT-TXOP-PS]
# Indicates whether or not the AP supports VHT TXOP Power Save Mode
#  or whether or not the STA is in VHT TXOP Power Save mode
# 0 = VHT AP doesn't support VHT TXOP PS mode (OR) VHT STA not in VHT TXOP PS
#  mode
# 1 = VHT AP supports VHT TXOP PS mode (OR) VHT STA is in VHT TXOP power save
#  mode
#
# +HTC-VHT Capable: [HTC-VHT]
# Indicates whether or not the STA supports receiving a VHT variant HT Control
# field.
# 0 = Not supported (default)
# 1 = supported
#
# Maximum A-MPDU Length Exponent: [MAX-A-MPDU-LEN-EXP0]..[MAX-A-MPDU-LEN-EXP7]
# Indicates the maximum length of A-MPDU pre-EOF padding that the STA can recv
# This field is an integer in the range of 0 to 7.
# The length defined by this field is equal to
# 2 pow(13 + Maximum A-MPDU Length Exponent) -1 octets
#
# VHT Link Adaptation Capable: [VHT-LINK-ADAPT2] [VHT-LINK-ADAPT3]
# Indicates whether or not the STA supports link adaptation using VHT variant
# HT Control field
# If +HTC-VHTcapable is 1
#  0 = (no feedback) if the STA does not provide VHT MFB (default)
#  1 = reserved
#  2 = (Unsolicited) if the STA provides only unsolicited VHT MFB
#  3 = (Both) if the STA can provide VHT MFB in response to VHT MRQ and if the
#      STA provides unsolicited VHT MFB
# Reserved if +HTC-VHTcapable is 0
#
# Rx Antenna Pattern Consistency: [RX-ANTENNA-PATTERN]
# Indicates the possibility of Rx antenna pattern change
# 0 = Rx antenna pattern might change during the lifetime of an association
# 1 = Rx antenna pattern does not change during the lifetime of an association
#
# Tx Antenna Pattern Consistency: [TX-ANTENNA-PATTERN]
# Indicates the possibility of Tx antenna pattern change
# 0 = Tx antenna pattern might change during the lifetime of an association
# 1 = Tx antenna pattern does not change during the lifetime of an association
{% if cap_vht %}
vht_capab=
{%- endif -%}

{%- if cap_vht_max_mpdu -%}
[MAX-MPDU-{{ cap_vht_max_mpdu }}]
{%- endif -%}

{%- if cap_vht_max_mpdu_exp -%}
[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}]
{%- endif -%}

{%- if cap_vht_chan_set_width -%}
[MAX-A-MPDU-LEN-EXP{{ cap_vht_max_mpdu_exp }}]
{%- endif -%}

{%- if cap_vht_chan_set_width -%}
{%- if '2' in cap_vht_chan_set_width -%}
[VHT160]
{%- elif '3' in cap_vht_chan_set_width -%}
[VHT160-80PLUS80]
{%- endif -%}
{%- endif -%}

{%- if cap_vht_stbc_tx -%}
[TX-STBC-2BY1]
{%- endif -%}

{%- if cap_vht_stbc_rx -%}
[RX-STBC-{{ cap_vht_stbc_rx }}]
{%- endif -%}

{%- if cap_vht_link_adaptation -%}
{%- if 'unsolicited' in cap_vht_link_adaptation -%}
[VHT-LINK-ADAPT2]
{%- elif 'both' in cap_vht_link_adaptation -%}
[VHT-LINK-ADAPT3]
{%- endif -%}
{%- endif -%}

{%- if cap_vht_short_gi -%}
{%- for gi in cap_vht_short_gi -%}
[SHORT-GI-{{ gi }}]
{%- endfor -%}
{%- endif -%}

{%- if cap_vht_ldpc -%}
[RXLDPC]
{%- endif -%}

{%- if cap_vht_tx_powersave -%}
[VHT-TXOP-PS]
{%- endif -%}

{%- if cap_vht_vht_cf -%}
[HTC-VHT]
{%- endif -%}

{%- if cap_vht_beamform -%}
{%- for beamform in cap_vht_beamform -%}
{%- if 'single-user-beamformer' in beamform -%}
[SU-BEAMFORMER]
{%- elif 'single-user-beamformee' in beamform -%}
[SU-BEAMFORMEE]
{%- elif 'multi-user-beamformer' in beamform -%}
[MU-BEAMFORMER]
{%- elif 'multi-user-beamformee' in beamform -%}
[MU-BEAMFORMEE]
{%- endif -%}
{%- endfor -%}
{%- endif -%}

{%- if cap_vht_antenna_fixed -%}
[RX-ANTENNA-PATTERN][TX-ANTENNA-PATTERN]
{%- endif -%}

{%- if cap_vht_antenna_cnt -%}
{%- for beamform in cap_vht_beamform -%}
{%- if 'single-user-beamformer' in beamform -%}
[BF-ANTENNA-{{ cap_vht_antenna_cnt|int -1 }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt|int -1}}]
{%- else -%}
[BF-ANTENNA-{{ cap_vht_antenna_cnt }}][SOUNDING-DIMENSION-{{ cap_vht_antenna_cnt }}]
{%- endif -%}
{%- endfor -%}
{%- endif %}

# ieee80211n: Whether IEEE 802.11n (HT) is enabled
# 0 = disabled (default)
# 1 = enabled
# Note: You will also need to enable WMM for full HT functionality.
# Note: hw_mode=g (2.4 GHz) and hw_mode=a (5 GHz) is used to specify the band.
{% if cap_req_vht -%}
ieee80211n=0
# Require stations to support VHT PHY (reject association if they do not)
require_vht=1
{% endif %}

{% if cap_vht_center_freq_1 -%}
# center freq = 5 GHz + (5 * index)
# So index 42 gives center freq 5.210 GHz
# which is channel 42 in 5G band
vht_oper_centr_freq_seg0_idx={{ cap_vht_center_freq_1 }}
{% endif %}

{% if cap_vht_center_freq_2 -%}
# center freq = 5 GHz + (5 * index)
# So index 159 gives center freq 5.795 GHz
# which is channel 159 in 5G band
vht_oper_centr_freq_seg1_idx={{ cap_vht_center_freq_2 }}
{% endif %}

{% if disable_broadcast_ssid -%}
# Send empty SSID in beacons and ignore probe request frames that do not
# specify full SSID, i.e., require stations to know SSID.
# default: disabled (0)
# 1 = send empty (length=0) SSID in beacon and ignore probe request for
#     broadcast SSID
# 2 = clear SSID (ASCII 0), but keep the original length (this may be required
#     with some clients that do not support empty SSID) and ignore probe
#     requests for broadcast SSID
ignore_broadcast_ssid=1
{% endif %}

# Station MAC address -based authentication
# Please note that this kind of access control requires a driver that uses
# hostapd to take care of management frame processing and as such, this can be
# used with driver=hostap or driver=nl80211, but not with driver=atheros.
# 0 = accept unless in deny list
# 1 = deny unless in accept list
# 2 = use external RADIUS server (accept/deny lists are searched first)
macaddr_acl=0

{% if max_stations -%}
# Maximum number of stations allowed in station table. New stations will be
# rejected after the station table is full. IEEE 802.11 has a limit of 2007
# different association IDs, so this number should not be larger than that.
# (default: 2007)
max_num_sta={{ max_stations }}
{% endif %}

{% if isolate_stations -%}
# Client isolation can be used to prevent low-level bridging of frames between
# associated stations in the BSS. By default, this bridging is allowed.
ap_isolate=1
{% endif %}

{% if reduce_transmit_power -%}
# Add Power Constraint element to Beacon and Probe Response frames
# This config option adds Power Constraint element when applicable and Country
# element is added. Power Constraint element is required by Transmit Power
# Control. This can be used only with ieee80211d=1.
# Valid values are 0..255.
local_pwr_constraint={{ reduce_transmit_power }}
{% endif %}

{% if expunge_failing_stations -%}
# Disassociate stations based on excessive transmission failures or other
# indications of connection loss. This depends on the driver capabilities and
# may not be available with all drivers.
disassoc_low_ack=1
{% endif %}

{% if sec_wep -%}
# IEEE 802.11 specifies two authentication algorithms. hostapd can be
# configured to allow both of these or only one. Open system authentication
# should be used with IEEE 802.1X.
# Bit fields of allowed authentication algorithms:
# bit 0 = Open System Authentication
# bit 1 = Shared Key Authentication (requires WEP)
auth_algs=2

# WEP rekeying (disabled if key lengths are not set or are set to 0)
# Key lengths for default/broadcast and individual/unicast keys:
# 5 = 40-bit WEP (also known as 64-bit WEP with 40 secret bits)
# 13 = 104-bit WEP (also known as 128-bit WEP with 104 secret bits)
wep_key_len_broadcast=5
wep_key_len_unicast=5

# Static WEP key configuration
#
# The key number to use when transmitting.
# It must be between 0 and 3, and the corresponding key must be set.
# default: not set
wep_default_key=0

# The WEP keys to use.
# A key may be a quoted string or unquoted hexadecimal digits.
# The key length should be 5, 13, or 16 characters, or 10, 26, or 32
# digits, depending on whether 40-bit (64-bit), 104-bit (128-bit), or
# 128-bit (152-bit) WEP is used.
# Only the default key must be supplied; the others are optional.
{% if sec_wep_key -%}
{% for key in sec_wep_key -%}
wep_key{{ loop.index -1 }}={{ key}}
{% endfor %}
{%- endif %}

{% elif sec_wpa -%}
##### WPA/IEEE 802.11i configuration ##########################################

# Enable WPA. Setting this variable configures the AP to require WPA (either
# WPA-PSK or WPA-RADIUS/EAP based on other configuration). For WPA-PSK, either
# wpa_psk or wpa_passphrase must be set and wpa_key_mgmt must include WPA-PSK.
# Instead of wpa_psk / wpa_passphrase, wpa_psk_radius might suffice.
# For WPA-RADIUS/EAP, ieee8021x must be set (but without dynamic WEP keys),
# RADIUS authentication server must be configured, and WPA-EAP must be included
# in wpa_key_mgmt.
# This field is a bit field that can be used to enable WPA (IEEE 802.11i/D3.0)
# and/or WPA2 (full IEEE 802.11i/RSN):
# bit0 = WPA
# bit1 = IEEE 802.11i/RSN (WPA2) (dot11RSNAEnabled)
{% if 'both' in sec_wpa_mode -%}
wpa=3
{%- elif 'wpa2' in sec_wpa_mode -%}
wpa=2
{%- elif 'wpa' in sec_wpa_mode -%}
wpa=1
{%- endif %}

{% if sec_wpa_cipher -%}
# Set of accepted cipher suites (encryption algorithms) for pairwise keys
# (unicast packets). This is a space separated list of algorithms:
# CCMP = AES in Counter mode with CBC-MAC (CCMP-128)
# TKIP = Temporal Key Integrity Protocol
# CCMP-256 = AES in Counter mode with CBC-MAC with 256-bit key
# GCMP = Galois/counter mode protocol (GCMP-128)
# GCMP-256 = Galois/counter mode protocol with 256-bit key
# Group cipher suite (encryption algorithm for broadcast and multicast frames)
# is automatically selected based on this configuration. If only CCMP is
# allowed as the pairwise cipher, group cipher will also be CCMP. Otherwise,
# TKIP will be used as the group cipher. The optional group_cipher parameter can
# be used to override this automatic selection.
{% if 'wpa2' in sec_wpa_mode -%}
# Pairwise cipher for RSN/WPA2 (default: use wpa_pairwise value)
rsn_pairwise={{ sec_wpa_cipher | join(" ") }}
{% else -%}
# Pairwise cipher for WPA (v1) (default: TKIP)
wpa_pairwise={{ sec_wpa_cipher | join(" ") }}
{%- endif -%}
{% endif %}

{% if sec_wpa_passphrase -%}
# IEEE 802.11 specifies two authentication algorithms. hostapd can be
# configured to allow both of these or only one. Open system authentication
# should be used with IEEE 802.1X.
# Bit fields of allowed authentication algorithms:
# bit 0 = Open System Authentication
# bit 1 = Shared Key Authentication (requires WEP)
auth_algs=1

# WPA pre-shared keys for WPA-PSK. This can be either entered as a 256-bit
# secret in hex format (64 hex digits), wpa_psk, or as an ASCII passphrase
# (8..63 characters) that will be converted to PSK. This conversion uses SSID
# so the PSK changes when ASCII passphrase is used and the SSID is changed.
wpa_passphrase={{ sec_wpa_passphrase }}

# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The
# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be
# added to enable SHA256-based stronger algorithms.
# WPA-PSK = WPA-Personal / WPA2-Personal
# WPA-PSK-SHA256 = WPA2-Personal using SHA256
wpa_key_mgmt=WPA-PSK

{% elif sec_wpa_radius -%}
##### IEEE 802.1X-2004 related configuration ##################################
# Require IEEE 802.1X authorization
ieee8021x=1

# Set of accepted key management algorithms (WPA-PSK, WPA-EAP, or both). The
# entries are separated with a space. WPA-PSK-SHA256 and WPA-EAP-SHA256 can be
# added to enable SHA256-based stronger algorithms.
# WPA-EAP = WPA-Enterprise / WPA2-Enterprise
# WPA-EAP-SHA256 = WPA2-Enterprise using SHA256
wpa_key_mgmt=WPA-EAP

{% if sec_wpa_radius_source -%}
# RADIUS client forced local IP address for the access point
# Normally the local IP address is determined automatically based on configured
# IP addresses, but this field can be used to force a specific address to be
# used, e.g., when the device has multiple IP addresses.
radius_client_addr={{ sec_wpa_radius_source }}

# The own IP address of the access point (used as NAS-IP-Address)
own_ip_addr={{ sec_wpa_radius_source }}
{% else %}
# The own IP address of the access point (used as NAS-IP-Address)
own_ip_addr=127.0.0.1
{% endif %}

{% for radius in sec_wpa_radius -%}
{%- if not radius.disabled -%}
# RADIUS authentication server
auth_server_addr={{ radius.server }}
auth_server_port={{ radius.port }}
auth_server_shared_secret={{ radius.key }}
{% if radius.acc_port -%}
# RADIUS accounting server
acct_server_addr={{ radius.server }}
acct_server_port={{ radius.acc_port }}
acct_server_shared_secret={{ radius.key }}
{% endif %}
{% endif %}
{% endfor %}

{% endif %}

{% else %}
# Open system
auth_algs=1
{% endif %}

# TX queue parameters (EDCF / bursting)
# tx_queue_<queue name>_<param>
# queues: data0, data1, data2, data3
#		(data0 is the highest priority queue)
# parameters:
#   aifs: AIFS (default 2)
#   cwmin: cwMin (1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191,
#	   16383, 32767)
#   cwmax: cwMax (same values as cwMin, cwMax >= cwMin)
#   burst: maximum length (in milliseconds with precision of up to 0.1 ms) for
#          bursting
#
# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e):
# These parameters are used by the access point when transmitting frames
# to the clients.
#
# Low priority / AC_BK = background
tx_queue_data3_aifs=7
tx_queue_data3_cwmin=15
tx_queue_data3_cwmax=1023
tx_queue_data3_burst=0
# Note: for IEEE 802.11b mode: cWmin=31 cWmax=1023 burst=0
#
# Normal priority / AC_BE = best effort
tx_queue_data2_aifs=3
tx_queue_data2_cwmin=15
tx_queue_data2_cwmax=63
tx_queue_data2_burst=0
# Note: for IEEE 802.11b mode: cWmin=31 cWmax=127 burst=0
#
# High priority / AC_VI = video
tx_queue_data1_aifs=1
tx_queue_data1_cwmin=7
tx_queue_data1_cwmax=15
tx_queue_data1_burst=3.0
# Note: for IEEE 802.11b mode: cWmin=15 cWmax=31 burst=6.0
#
# Highest priority / AC_VO = voice
tx_queue_data0_aifs=1
tx_queue_data0_cwmin=3
tx_queue_data0_cwmax=7
tx_queue_data0_burst=1.5

# Default WMM parameters (IEEE 802.11 draft; 11-03-0504-03-000e):
# for 802.11a or 802.11g networks
# These parameters are sent to WMM clients when they associate.
# The parameters will be used by WMM clients for frames transmitted to the
# access point.
#
# note - txop_limit is in units of 32microseconds
# note - acm is admission control mandatory flag. 0 = admission control not
# required, 1 = mandatory
# note - Here cwMin and cmMax are in exponent form. The actual cw value used
# will be (2^n)-1 where n is the value given here. The allowed range for these
# wmm_ac_??_{cwmin,cwmax} is 0..15 with cwmax >= cwmin.
#
wmm_enabled=1

# Low priority / AC_BK = background
wmm_ac_bk_cwmin=4
wmm_ac_bk_cwmax=10
wmm_ac_bk_aifs=7
wmm_ac_bk_txop_limit=0
wmm_ac_bk_acm=0
# Note: for IEEE 802.11b mode: cWmin=5 cWmax=10
#
# Normal priority / AC_BE = best effort
wmm_ac_be_aifs=3
wmm_ac_be_cwmin=4
wmm_ac_be_cwmax=10
wmm_ac_be_txop_limit=0
wmm_ac_be_acm=0
# Note: for IEEE 802.11b mode: cWmin=5 cWmax=7
#
# High priority / AC_VI = video
wmm_ac_vi_aifs=2
wmm_ac_vi_cwmin=3
wmm_ac_vi_cwmax=4
wmm_ac_vi_txop_limit=94
wmm_ac_vi_acm=0
# Note: for IEEE 802.11b mode: cWmin=4 cWmax=5 txop_limit=188
#
# Highest priority / AC_VO = voice
wmm_ac_vo_aifs=2
wmm_ac_vo_cwmin=2
wmm_ac_vo_cwmax=3
wmm_ac_vo_txop_limit=47
wmm_ac_vo_acm=0

"""

# Please be careful if you edit the template.
config_wpa_suppl_tmpl = """
# WPA supplicant config
network={
    ssid="{{ ssid }}"
{%- if sec_wpa_passphrase %}
    psk="{{ sec_wpa_passphrase }}"
{% else %}
    key_mgmt=NONE
{% endif %}
}

"""

default_config_data = {
    'address': [],
    'address_remove': [],
    'cap_ht' : False,
    'cap_ht_40mhz_incapable' : False,
    'cap_ht_powersave' : False,
    'cap_ht_chan_set_width' : '',
    'cap_ht_delayed_block_ack' : False,
    'cap_ht_dsss_cck_40' : False,
    'cap_ht_greenfield' : False,
    'cap_ht_ldpc' : False,
    'cap_ht_lsig_protection' : False,
    'cap_ht_max_amsdu' : '',
    'cap_ht_short_gi' :  [],
    'cap_ht_smps' : '',
    'cap_ht_stbc_rx' : '',
    'cap_ht_stbc_tx' : False,
    'cap_req_ht' : False,
    'cap_req_vht' : False,
    'cap_vht' : False,
    'cap_vht_antenna_cnt' : '',
    'cap_vht_antenna_fixed' : False,
    'cap_vht_beamform' : '',
    'cap_vht_center_freq_1' : '',
    'cap_vht_center_freq_2' : '',
    'cap_vht_chan_set_width' : '',
    'cap_vht_ldpc' : False,
    'cap_vht_link_adaptation' : False,
    'cap_vht_max_mpdu_exp' : '',
    'cap_vht_max_mpdu' : '',
    'cap_vht_short_gi' : [],
    'cap_vht_stbc_rx' : '',
    'cap_vht_stbc_tx' : False,
    'cap_vht_tx_powersave' : False,
    'cap_vht_vht_cf' : False,
    'channel': '',
    'country_code': '',
    'description': '',
    'deleted': False,
    'dhcp_client_id': '',
    'dhcp_hostname': '',
    'dhcp_vendor_class_id': '',
    'dhcpv6_prm_only': False,
    'dhcpv6_temporary': False,
    'disable': False,
    'disable_broadcast_ssid' : False,
    'disable_link_detect' : 1,
    'expunge_failing_stations' : False,
    'hw_id' : '',
    'intf': '',
    'isolate_stations' : False,
    'ip_disable_arp_filter': 1,
    'ip_enable_arp_accept': 0,
    'ip_enable_arp_announce': 0,
    'ip_enable_arp_ignore': 0,
    'mac' : '',
    'max_stations' : '',
    'mgmt_frame_protection' : 'disabled',
    'mode' : 'g',
    'phy' : '',
    'reduce_transmit_power' : '',
    'sec_wep' : False,
    'sec_wep_key' : [],
    'sec_wpa' : False,
    'sec_wpa_cipher' : [],
    'sec_wpa_mode' : 'both',
    'sec_wpa_passphrase' : '',
    'sec_wpa_radius' : [],
    'ssid' : '',
    'op_mode' : 'monitor',
    'vif': [],
    'vif_remove': [],
    'vrf': ''
}

def get_conf_file(conf_type, intf):
    cfg_dir = '/var/run/' + conf_type

    # create directory on demand
    if not os.path.exists(cfg_dir):
        os.mkdir(cfg_dir)
        # fix permissions - corresponds to mode 755
        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
        uid = getpwnam(user).pw_uid
        gid = getgrnam(group).gr_gid
        os.chown(cfg_dir, uid, gid)

    cfg_file = cfg_dir + r'/{}.cfg'.format(intf)
    return cfg_file

def get_pid(conf_type, intf):
    cfg_dir = '/var/run/' + conf_type

    # create directory on demand
    if not os.path.exists(cfg_dir):
        os.mkdir(cfg_dir)
        # fix permissions - corresponds to mode 755
        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
        uid = getpwnam(user).pw_uid
        gid = getgrnam(group).gr_gid
        os.chown(cfg_dir, uid, gid)

    cfg_file = cfg_dir + r'/{}.pid'.format(intf)
    return cfg_file


def get_wpa_suppl_config_name(intf):
    cfg_dir = '/var/run/wpa_supplicant'

    # create directory on demand
    if not os.path.exists(cfg_dir):
        os.mkdir(cfg_dir)
        # fix permissions - corresponds to mode 755
        os.chmod(cfg_dir, S_IRWXU|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
        uid = getpwnam(user).pw_uid
        gid = getgrnam(group).gr_gid
        os.chown(cfg_dir, uid, gid)

    cfg_file = cfg_dir + r'/{}.cfg'.format(intf)
    return cfg_file

def subprocess_cmd(command):
    p = Popen(command, stdout=PIPE, shell=True)
    p.communicate()

def get_config():
    wifi = deepcopy(default_config_data)
    conf = Config()

    # determine tagNode instance
    if 'VYOS_TAGNODE_VALUE' not in os.environ:
        raise ConfigError('Interface (VYOS_TAGNODE_VALUE) not specified')

    wifi['intf'] = os.environ['VYOS_TAGNODE_VALUE']

    # check if wireless interface has been removed
    cfg_base = 'interfaces wireless ' + wifi['intf']
    if not conf.exists(cfg_base):
        wifi['deleted'] = True
        # we can not bail out early as wireless interface can not be removed
        # Kernel will complain with: RTNETLINK answers: Operation not supported.
        # Thus we need to remove individual settings
        return wifi

    # set new configuration level
    conf.set_level(cfg_base)

    # retrieve configured interface addresses
    if conf.exists('address'):
        wifi['address'] = conf.return_values('address')

    # get interface addresses (currently effective) - to determine which
    # address is no longer valid and needs to be removed
    eff_addr = conf.return_effective_values('address')
    wifi['address_remove'] = list_diff(eff_addr, wifi['address'])

    # 40MHz intolerance, use 20MHz only
    if conf.exists('capabilities ht 40mhz-incapable'):
        wifi['cap_ht'] = True
        wifi['cap_ht_40mhz_incapable'] = True

    # WMM-PS Unscheduled Automatic Power Save Delivery [U-APSD]
    if conf.exists('capabilities ht auto-powersave'):
        wifi['cap_ht'] = True
        wifi['cap_ht_powersave'] = True

    # Supported channel set width
    if conf.exists('capabilities ht channel-set-width'):
        wifi['cap_ht'] = True
        wifi['cap_ht_chan_set_width'] = conf.return_values('capabilities ht channel-set-width')

    # HT-delayed Block Ack
    if conf.exists('capabilities ht delayed-block-ack'):
        wifi['cap_ht'] = True
        wifi['cap_ht_delayed_block_ack'] = True

    # DSSS/CCK Mode in 40 MHz
    if conf.exists('capabilities ht dsss-cck-40'):
        wifi['cap_ht'] = True
        wifi['cap_ht_dsss_cck_40'] = True

    # HT-greenfield capability
    if conf.exists('capabilities ht greenfield'):
        wifi['cap_ht'] = True
        wifi['cap_ht_greenfield'] = True

    # LDPC coding capability
    if conf.exists('capabilities ht ldpc'):
        wifi['cap_ht'] = True
        wifi['cap_ht_ldpc'] = True

    # L-SIG TXOP protection capability
    if conf.exists('capabilities ht lsig-protection'):
        wifi['cap_ht'] = True
        wifi['cap_ht_lsig_protection'] = True

    # Set Maximum A-MSDU length
    if conf.exists('capabilities ht max-amsdu'):
        wifi['cap_ht'] = True
        wifi['cap_ht_max_amsdu'] = conf.return_value('capabilities ht max-amsdu')

    # Short GI capabilities
    if conf.exists('capabilities ht short-gi'):
        wifi['cap_ht'] = True
        wifi['cap_ht_short_gi'] = conf.return_values('capabilities ht short-gi')

    # Spatial Multiplexing Power Save (SMPS) settings
    if conf.exists('capabilities ht smps'):
        wifi['cap_ht'] = True
        wifi['cap_ht_smps'] = conf.return_value('capabilities ht smps')

    # Support for receiving PPDU using STBC (Space Time Block Coding)
    if conf.exists('capabilities ht stbc rx'):
        wifi['cap_ht'] = True
        wifi['cap_ht_stbc_rx'] = conf.return_value('capabilities ht stbc rx')

    # Support for sending PPDU using STBC (Space Time Block Coding)
    if conf.exists('capabilities ht stbc tx'):
        wifi['cap_ht'] = True
        wifi['cap_ht_stbc_tx'] = True

    # Require stations to support HT PHY (reject association if they do not)
    if conf.exists('capabilities require-ht'):
        wifi['cap_req_ht'] = True

    # Require stations to support VHT PHY (reject association if they do not)
    if conf.exists('capabilities require-vht'):
        wifi['cap_req_vht'] = True

    # Number of antennas on this card
    if conf.exists('capabilities vht antenna-count'):
        wifi['cap_vht'] = True
        wifi['cap_vht_antenna_cnt'] = conf.return_value('capabilities vht antenna-count')

    # set if antenna pattern does not change during the lifetime of an association
    if conf.exists('capabilities vht antenna-pattern-fixed'):
        wifi['cap_vht'] = True
        wifi['cap_vht_antenna_fixed'] = True

    # Beamforming capabilities
    if conf.exists('capabilities vht beamform'):
        wifi['cap_vht'] = True
        wifi['cap_vht_beamform'] = conf.return_values('capabilities vht beamform')

    # VHT operating channel center frequency - center freq 1 (for use with 80, 80+80 and 160 modes)
    if conf.exists('capabilities vht center-channel-freq freq-1'):
        wifi['cap_vht'] = True
        wifi['cap_vht_center_freq_1'] = conf.return_value('capabilities vht center-channel-freq freq-1')

    # VHT operating channel center frequency - center freq 2 (for use with the 80+80 mode)
    if conf.exists('capabilities vht center-channel-freq freq-2'):
        wifi['cap_vht'] = True
        wifi['cap_vht_center_freq_2'] = conf.return_value('capabilities vht center-channel-freq freq-2')

    # VHT operating Channel width
    if conf.exists('capabilities vht channel-set-width'):
        wifi['cap_vht'] = True
        wifi['cap_vht_chan_set_width'] = conf.return_value('capabilities vht channel-set-width')

    # LDPC coding capability
    if conf.exists('capabilities vht ldpc'):
        wifi['cap_vht'] = True
        wifi['cap_vht_ldpc'] = True

    # VHT link adaptation capabilities
    if conf.exists('capabilities vht link-adaptation'):
        wifi['cap_vht'] = True
        wifi['cap_vht_link_adaptation'] = True

    # Set the maximum length of A-MPDU pre-EOF padding that the station can receive
    if conf.exists('capabilities vht max-mpdu-exp'):
        wifi['cap_vht'] = True
        wifi['cap_vht_max_mpdu_exp'] = conf.return_value('capabilities vht max-mpdu-exp')

    # Increase Maximum MPDU length
    if conf.exists('capabilities vht max-mpdu'):
        wifi['cap_vht'] = True
        wifi['cap_vht_max_mpdu'] = conf.return_value('capabilities vht max-mpdu')

    # Increase Maximum MPDU length
    if conf.exists('capabilities vht short-gi'):
        wifi['cap_vht'] = True
        wifi['cap_vht_short_gi'] = conf.return_values('capabilities vht short-gi')

    # Support for receiving PPDU using STBC (Space Time Block Coding)
    if conf.exists('capabilities vht stbc rx'):
        wifi['cap_vht'] = True
        wifi['cap_vht_stbc_rx'] = conf.return_value('capabilities vht stbc rx')

    # Support for the transmission of at least 2x1 STBC (Space Time Block Coding)
    if conf.exists('capabilities vht stbc tx'):
        wifi['cap_vht'] = True
        wifi['cap_vht_stbc_tx'] = True

    # Support for VHT TXOP Power Save Mode
    if conf.exists('capabilities vht tx-powersave'):
        wifi['cap_vht'] = True
        wifi['cap_vht_tx_powersave'] = True

    # STA supports receiving a VHT variant HT Control field
    if conf.exists('capabilities vht vht-cf'):
        wifi['cap_vht'] = True
        wifi['cap_vht_vht_cf'] = True

    # Wireless radio channel
    if conf.exists('channel'):
        wifi['channel'] = conf.return_value('channel')

    # retrieve interface description
    if conf.exists('description'):
        wifi['description'] = conf.return_value('description')

    # get DHCP client identifier
    if conf.exists('dhcp-options client-id'):
        wifi['dhcp_client_id'] = conf.return_value('dhcp-options client-id')

    # DHCP client host name (overrides the system host name)
    if conf.exists('dhcp-options host-name'):
        wifi['dhcp_hostname'] = conf.return_value('dhcp-options host-name')

    # DHCP client vendor identifier
    if conf.exists('dhcp-options vendor-class-id'):
        wifi['dhcp_vendor_class_id'] = conf.return_value('dhcp-options vendor-class-id')

    # DHCPv6 only acquire config parameters, no address
    if conf.exists('dhcpv6-options parameters-only'):
        wifi['dhcpv6_prm_only'] = conf.return_value('dhcpv6-options parameters-only')

    # DHCPv6 temporary IPv6 address
    if conf.exists('dhcpv6-options temporary'):
        wifi['dhcpv6_temporary'] = conf.return_value('dhcpv6-options temporary')

    # Disable broadcast of SSID from access-point
    if conf.exists('disable-broadcast-ssid'):
        wifi['disable_broadcast_ssid'] = True

    # ignore link state changes on this interface
    if conf.exists('disable-link-detect'):
        wifi['disable_link_detect'] = 2

    # Disassociate stations based on excessive transmission failures
    if conf.exists('expunge-failing-stations'):
        wifi['expunge_failing_stations'] = True

    # retrieve real hardware address
    if conf.exists('hw-id'):
        wifi['hw_id'] = conf.return_value('hw-id')

    # Isolate stations on the AP so they cannot see each other
    if conf.exists('isolate-stations'):
        wifi['isolate_stations'] = True

    # ARP filter configuration
    if conf.exists('ip disable-arp-filter'):
        wifi['ip_disable_arp_filter'] = 0

    # ARP enable accept
    if conf.exists('ip enable-arp-accept'):
        wifi['ip_enable_arp_accept'] = 1

    # ARP enable announce
    if conf.exists('ip enable-arp-announce'):
        wifi['ip_enable_arp_announce'] = 1

    # ARP enable ignore
    if conf.exists('ip enable-arp-ignore'):
        wifi['ip_enable_arp_ignore'] = 1

    # Wireless physical device
    if conf.exists('physical-device'):
        wifi['phy'] = conf.return_value('physical-device')

    # Media Access Control (MAC) address
    if conf.exists('mac'):
        wifi['mac'] = conf.return_value('mac')

    # Maximum number of wireless radio stations
    if conf.exists('max-stations'):
        wifi['max_stations'] = conf.return_value('max-stations')

    # Management Frame Protection (MFP) according to IEEE 802.11w
    if conf.exists('mgmt-frame-protection'):
        wifi['mgmt_frame_protection'] = conf.return_value('mgmt-frame-protection')

    # Wireless radio mode
    if conf.exists('mode'):
        wifi['mode'] = conf.return_value('mode')

    # retrieve VRF instance
    if conf.exists('vrf'):
        wifi['vrf'] = conf.return_value('vrf')

    # Transmission power reduction in dBm
    if conf.exists('reduce-transmit-power'):
        wifi['reduce_transmit_power'] = conf.return_value('reduce-transmit-power')

    # WEP enabled?
    if conf.exists('security wep'):
        wifi['sec_wep'] = True

    # WEP encryption key(s)
    if conf.exists('security wep key'):
        wifi['sec_wep_key'] = conf.return_values('security wep key')

    # WPA enabled?
    if conf.exists('security wpa'):
        wifi['sec_wpa'] = True

    # WPA Cipher suite
    if conf.exists('security wpa cipher'):
        wifi['sec_wpa_cipher'] = conf.return_values('security wpa cipher')

    # WPA mode
    if conf.exists('security wpa mode'):
        wifi['sec_wpa_mode'] = conf.return_value('security wpa mode')

    # WPA default ciphers depend on WPA mode
    if not wifi['sec_wpa_cipher']:
        if wifi['sec_wpa_mode'] == 'wpa':
            wifi['sec_wpa_cipher'].append('TKIP')
            wifi['sec_wpa_cipher'].append('CCMP')

        elif wifi['sec_wpa_mode'] == 'wpa2':
            wifi['sec_wpa_cipher'].append('CCMP')

        elif wifi['sec_wpa_mode'] == 'both':
            wifi['sec_wpa_cipher'].append('CCMP')
            wifi['sec_wpa_cipher'].append('TKIP')

    # WPA personal shared pass phrase
    if conf.exists('security wpa passphrase'):
        wifi['sec_wpa_passphrase'] = conf.return_value('security wpa passphrase')

    # WPA RADIUS source address
    if conf.exists('security wpa radius source-address'):
        wifi['sec_wpa_radius_source'] = conf.return_value('security wpa radius source-address')

    # WPA RADIUS server
    for server in conf.list_nodes('security wpa radius server'):
        # set new configuration level
        conf.set_level(cfg_base + ' security wpa radius server ' + server)
        radius = {
            'server' : server,
            'acc_port' : '',
            'disabled': False,
            'port' : 1812,
            'key' : ''
        }

        # RADIUS server port
        if conf.exists('port'):
            radius['port'] = int(conf.return_value('port'))

        # receive RADIUS accounting info
        if conf.exists('accounting'):
            radius['acc_port'] = radius['port'] + 1

        # Check if RADIUS server was temporary disabled
        if conf.exists(['disable']):
            radius['disabled'] = True

        # RADIUS server shared-secret
        if conf.exists('key'):
            radius['key'] = conf.return_value('key')

        # append RADIUS server to list of servers
        wifi['sec_wpa_radius'].append(radius)

    # re-set configuration level to parse new nodes
    conf.set_level(cfg_base)

    # Wireless access-point service set identifier (SSID)
    if conf.exists('ssid'):
        wifi['ssid'] = conf.return_value('ssid')

    # Wireless device type for this interface
    if conf.exists('type'):
        tmp = conf.return_value('type')
        if tmp == 'access-point':
            tmp = 'ap'

        wifi['op_mode'] = tmp

    # re-set configuration level to parse new nodes
    conf.set_level(cfg_base)
    # Determine vif interfaces (currently effective) - to determine which
    # vif interface is no longer present and needs to be removed
    eff_intf = conf.list_effective_nodes('vif')
    act_intf = conf.list_nodes('vif')
    wifi['vif_remove'] = list_diff(eff_intf, act_intf)

    if conf.exists('vif'):
        for vif in conf.list_nodes('vif'):
            # set config level to vif interface
            conf.set_level(cfg_base + ' vif ' + vif)
            wifi['vif'].append(vlan_to_dict(conf))

    # disable interface
    if conf.exists('disable'):
        wifi['disable'] = True

    # retrieve configured regulatory domain
    conf.set_level('system')
    if conf.exists('wifi-regulatory-domain'):
        wifi['country_code'] = conf.return_value('wifi-regulatory-domain')

    return wifi


def verify(wifi):
    if wifi['deleted']:
        return None

    if wifi['op_mode'] != 'monitor' and not wifi['ssid']:
        raise ConfigError('SSID must be set for {}'.format(wifi['intf']))

    if not wifi['phy']:
        raise ConfigError('You must specify physical-device')

    if wifi['op_mode'] == 'ap':
        c = Config()
        if not c.exists('system wifi-regulatory-domain'):
            raise ConfigError('Wireless regulatory domain is mandatory,\n' \
                              'use "set system wifi-regulatory-domain".')

        if not wifi['channel']:
            raise ConfigError('Channel must be set for {}'.format(wifi['intf']))

    if len(wifi['sec_wep_key']) > 4:
        raise ConfigError('No more then 4 WEP keys configurable')

    if wifi['cap_vht'] and not wifi['cap_ht']:
        raise ConfigError('Specify HT flags if you want to use VHT!')

    if wifi['cap_vht_beamform'] and wifi['cap_vht_antenna_cnt'] == 1:
        raise ConfigError('Cannot use beam forming with just one antenna!')

    if wifi['sec_wep'] and (len(wifi['sec_wep_key']) == 0):
        raise ConfigError('Missing WEP keys')

    if wifi['sec_wpa'] and not (wifi['sec_wpa_passphrase'] or wifi['sec_wpa_radius']):
        raise ConfigError('Misssing WPA key or RADIUS server')

    for radius in wifi['sec_wpa_radius']:
        if not radius['key']:
            raise ConfigError('Misssing RADIUS shared secret key for server: {}'.format(radius['server']))

    vrf_name = wifi['vrf']
    if vrf_name and vrf_name not in interfaces():
        raise ConfigError(f'VRF "{vrf_name}" does not exist')

    # use common function to verify VLAN configuration
    verify_vlan_config(wifi)

    conf = Config()
    # Only one wireless interface per phy can be in station mode
    base = ['interfaces', 'wireless']
    for phy in os.listdir('/sys/class/ieee80211'):
        stations = []
        for wlan in conf.list_nodes(base):
            # the following node is mandatory
            if conf.exists(base + [wlan, 'physical-device', phy]):
                tmp = conf.return_value(base + [wlan, 'type'])
                if tmp == 'station':
                    stations.append(wlan)

        if len(stations) > 1:
            raise ConfigError('Only one station per wireless physical interface possible!')

    return None

def generate(wifi):
    pid = 0
    # always stop hostapd service first before reconfiguring it
    pidfile = get_pid('hostapd', wifi['intf'])
    if os.path.isfile(pidfile):
        pid = 0
        with open(pidfile, 'r') as f:
            pid = int(f.read())

    if pid_exists(pid):
        cmd  = 'start-stop-daemon --stop --quiet'
        cmd += ' --pidfile ' + pidfile
        subprocess_cmd(cmd)

    # always stop wpa_supplicant service first before reconfiguring it
    pidfile = get_pid('wpa_supplicant', wifi['intf'])
    if os.path.isfile(pidfile):
        pid = 0
        with open(pidfile, 'r') as f:
            pid = int(f.read())

    if pid_exists(pid):
        cmd  = 'start-stop-daemon --stop --quiet'
        cmd += ' --pidfile ' + pidfile
        subprocess_cmd(cmd)

    # Delete config files if interface is removed
    if wifi['deleted']:
        if os.path.isfile(get_conf_file('hostapd', wifi['intf'])):
            os.unlink(get_conf_file('hostapd', wifi['intf']))

        if os.path.isfile(get_conf_file('wpa_supplicant', wifi['intf'])):
            os.unlink(get_conf_file('wpa_supplicant', wifi['intf']))

        return None

    if not wifi['mac']:
        # http://wiki.stocksy.co.uk/wiki/Multiple_SSIDs_with_hostapd
        # generate locally administered MAC address from used phy interface
        with open('/sys/class/ieee80211/{}/addresses'.format(wifi['phy']), 'r') as f:
            tmp = EUI(f.read().rstrip()).value
            # mask last nibble from the MAC address
            tmp &= 0xfffffffffff0
            # set locally administered bit in MAC address
            tmp |= 0x020000000000
            # we now need to add an offset to our MAC address indicating this
            # subinterfaces index
            tmp += int(findall(r'\d+', wifi['intf'])[0])

            # convert integer to "real" MAC address representation
            mac = EUI(hex(tmp).split('x')[-1])
            # change dialect to use : as delimiter instead of -
            mac.dialect = mac_unix_expanded
            wifi['mac'] = str(mac)

    # render appropriate new config files depending on access-point or station mode
    if wifi['op_mode'] == 'ap':
        tmpl = Template(config_hostapd_tmpl)
        config_text = tmpl.render(wifi)
        with open(get_conf_file('hostapd', wifi['intf']), 'w') as f:
            f.write(config_text)

    elif wifi['op_mode'] == 'station':
        tmpl = Template(config_wpa_suppl_tmpl)
        config_text = tmpl.render(wifi)
        with open(get_conf_file('wpa_supplicant', wifi['intf']), 'w') as f:
            f.write(config_text)

    return None

def apply(wifi):
    w = EthernetIf(wifi['intf'])
    if wifi['deleted']:
        # delete interface
        w.remove()
    else:
        # Some parts e.g. MAC address can't be changed when interface is up
        w.set_state('down')

        # update interface description used e.g. within SNMP
        w.set_alias(wifi['description'])

        # get DHCP config dictionary and update values
        opt = w.get_dhcp_options()

        if wifi['dhcp_client_id']:
            opt['client_id'] = wifi['dhcp_client_id']

        if wifi['dhcp_hostname']:
            opt['hostname'] = wifi['dhcp_hostname']

        if wifi['dhcp_vendor_class_id']:
            opt['vendor_class_id'] = wifi['dhcp_vendor_class_id']

        # store DHCP config dictionary - used later on when addresses are aquired
        w.set_dhcp_options(opt)

        # get DHCPv6 config dictionary and update values
        opt = w.get_dhcpv6_options()

        if wifi['dhcpv6_prm_only']:
            opt['dhcpv6_prm_only'] = True

        if wifi['dhcpv6_temporary']:
            opt['dhcpv6_temporary'] = True

        # store DHCPv6 config dictionary - used later on when addresses are aquired
        w.set_dhcpv6_options(opt)

        # ignore link state changes
        w.set_link_detect(wifi['disable_link_detect'])

        # assign/remove VRF
        w.set_vrf(wifi['vrf'])

        # Change interface MAC address - re-set to real hardware address (hw-id)
        # if custom mac is removed
        if wifi['mac']:
            w.set_mac(wifi['mac'])
        else:
            w.set_mac(wifi['hw_id'])

        # configure ARP filter configuration
        w.set_arp_filter(wifi['ip_disable_arp_filter'])
        # configure ARP accept
        w.set_arp_accept(wifi['ip_enable_arp_accept'])
        # configure ARP announce
        w.set_arp_announce(wifi['ip_enable_arp_announce'])
        # configure ARP ignore
        w.set_arp_ignore(wifi['ip_enable_arp_ignore'])

        # Enable/Disable interface
        if wifi['disable']:
            w.set_state('down')
        else:
            w.set_state('up')

        # Configure interface address(es)
        # - not longer required addresses get removed first
        # - newly addresses will be added second
        for addr in wifi['address_remove']:
            w.del_addr(addr)
        for addr in wifi['address']:
            w.add_addr(addr)

        # remove no longer required VLAN interfaces (vif)
        for vif in wifi['vif_remove']:
            e.del_vlan(vif)

        # create VLAN interfaces (vif)
        for vif in wifi['vif']:
            # QoS priority mapping can only be set during interface creation
            # so we delete the interface first if required.
            if vif['egress_qos_changed'] or vif['ingress_qos_changed']:
                try:
                    # on system bootup the above condition is true but the interface
                    # does not exists, which throws an exception, but that's legal
                    e.del_vlan(vif['id'])
                except:
                    pass

            vlan = e.add_vlan(vif['id'])
            apply_vlan_config(vlan, vif)

    # Physical interface is now configured. Proceed by starting hostapd or
    # wpa_supplicant daemon. When type is monitor we can just skip this.
    if wifi['op_mode'] == 'ap':
        cmd  = 'start-stop-daemon --start --quiet'
        cmd += ' --exec /usr/sbin/hostapd'
        # now pass arguments to hostapd binary
        cmd += ' -- -B'
        cmd += ' -P {}'.format(get_pid('hostapd', wifi['intf']))
        cmd += ' {}'.format(get_conf_file('hostapd', wifi['intf']))

        # execute assembled command
        subprocess_cmd(cmd)

    elif wifi['op_mode'] == 'station':
        cmd  = 'start-stop-daemon --start --quiet'
        cmd += ' --exec /sbin/wpa_supplicant'
        # now pass arguments to hostapd binary
        cmd += ' -- -s -B -D nl80211'
        cmd += ' -P {}'.format(get_pid('wpa_supplicant', wifi['intf']))
        cmd += ' -i {}'.format(wifi['intf'])
        cmd += ' -c {}'.format(get_conf_file('wpa_supplicant', wifi['intf']))

        # execute assembled command
        subprocess_cmd(cmd)

    return None

if __name__ == '__main__':
    try:
        c = get_config()
        verify(c)
        generate(c)
        apply(c)
    except ConfigError as e:
        print(e)
        exit(1)