]> git.ipfire.org Git - thirdparty/hostap.git/blobdiff - hostapd/config_file.c
WPS: Allow AP SAE configuration to be added automatically for PSK
[thirdparty/hostap.git] / hostapd / config_file.c
index ada660132a518f76da78a848081b75342f653c46..5ddfbfb196b5d9f34b469a5dfb63ab9b114a7a0c 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * hostapd / Configuration file parser
- * Copyright (c) 2003-2015, Jouni Malinen <j@w1.fi>
+ * Copyright (c) 2003-2018, Jouni Malinen <j@w1.fi>
  *
  * This software may be distributed under the terms of the BSD license.
  * See README for more details.
@@ -14,6 +14,8 @@
 #include "utils/common.h"
 #include "utils/uuid.h"
 #include "common/ieee802_11_defs.h"
+#include "crypto/sha256.h"
+#include "crypto/tls.h"
 #include "drivers/driver.h"
 #include "eap_server/eap.h"
 #include "radius/radius_client.h"
@@ -35,7 +37,7 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
                                         const char *fname)
 {
        FILE *f;
-       char buf[128], *pos, *pos2;
+       char buf[128], *pos, *pos2, *pos3;
        int line = 0, vlan_id;
        struct hostapd_vlan *vlan;
 
@@ -80,7 +82,10 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
                pos2 = pos;
                while (*pos2 != ' ' && *pos2 != '\t' && *pos2 != '\0')
                        pos2++;
-               *pos2 = '\0';
+
+               if (*pos2 != '\0')
+                       *(pos2++) = '\0';
+
                if (*pos == '\0' || os_strlen(pos) > IFNAMSIZ) {
                        wpa_printf(MSG_ERROR, "Invalid VLAN ifname at line %d "
                                   "in '%s'", line, fname);
@@ -88,6 +93,13 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
                        return -1;
                }
 
+               while (*pos2 == ' ' || *pos2 == '\t')
+                       pos2++;
+               pos3 = pos2;
+               while (*pos3 != ' ' && *pos3 != '\t' && *pos3 != '\0')
+                       pos3++;
+               *pos3 = '\0';
+
                vlan = os_zalloc(sizeof(*vlan));
                if (vlan == NULL) {
                        wpa_printf(MSG_ERROR, "Out of memory while reading "
@@ -100,6 +112,7 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
                vlan->vlan_desc.untagged = vlan_id;
                vlan->vlan_desc.notempty = !!vlan_id;
                os_strlcpy(vlan->ifname, pos, sizeof(vlan->ifname));
+               os_strlcpy(vlan->bridge, pos2, sizeof(vlan->bridge));
                vlan->next = bss->vlan;
                bss->vlan = vlan;
        }
@@ -111,7 +124,7 @@ static int hostapd_config_read_vlan_file(struct hostapd_bss_config *bss,
 #endif /* CONFIG_NO_VLAN */
 
 
-static int hostapd_acl_comp(const void *a, const void *b)
+int hostapd_acl_comp(const void *a, const void *b)
 {
        const struct mac_acl_entry *aa = a;
        const struct mac_acl_entry *bb = b;
@@ -119,6 +132,44 @@ static int hostapd_acl_comp(const void *a, const void *b)
 }
 
 
+int hostapd_add_acl_maclist(struct mac_acl_entry **acl, int *num,
+                           int vlan_id, const u8 *addr)
+{
+       struct mac_acl_entry *newacl;
+
+       newacl = os_realloc_array(*acl, *num + 1, sizeof(**acl));
+       if (!newacl) {
+               wpa_printf(MSG_ERROR, "MAC list reallocation failed");
+               return -1;
+       }
+
+       *acl = newacl;
+       os_memcpy((*acl)[*num].addr, addr, ETH_ALEN);
+       os_memset(&(*acl)[*num].vlan_id, 0, sizeof((*acl)[*num].vlan_id));
+       (*acl)[*num].vlan_id.untagged = vlan_id;
+       (*acl)[*num].vlan_id.notempty = !!vlan_id;
+       (*num)++;
+
+       return 0;
+}
+
+
+void hostapd_remove_acl_mac(struct mac_acl_entry **acl, int *num,
+                           const u8 *addr)
+{
+       int i = 0;
+
+       while (i < *num) {
+               if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) == 0) {
+                       os_remove_in_array(*acl, *num, sizeof(**acl), i);
+                       (*num)--;
+               } else {
+                       i++;
+               }
+       }
+}
+
+
 static int hostapd_config_read_maclist(const char *fname,
                                       struct mac_acl_entry **acl, int *num)
 {
@@ -126,12 +177,8 @@ static int hostapd_config_read_maclist(const char *fname,
        char buf[128], *pos;
        int line = 0;
        u8 addr[ETH_ALEN];
-       struct mac_acl_entry *newacl;
        int vlan_id;
 
-       if (!fname)
-               return 0;
-
        f = fopen(fname, "r");
        if (!f) {
                wpa_printf(MSG_ERROR, "MAC list file '%s' not found.", fname);
@@ -139,7 +186,7 @@ static int hostapd_config_read_maclist(const char *fname,
        }
 
        while (fgets(buf, sizeof(buf), f)) {
-               int i, rem = 0;
+               int rem = 0;
 
                line++;
 
@@ -169,16 +216,7 @@ static int hostapd_config_read_maclist(const char *fname,
                }
 
                if (rem) {
-                       i = 0;
-                       while (i < *num) {
-                               if (os_memcmp((*acl)[i].addr, addr, ETH_ALEN) ==
-                                   0) {
-                                       os_remove_in_array(*acl, *num,
-                                                          sizeof(**acl), i);
-                                       (*num)--;
-                               } else
-                                       i++;
-                       }
+                       hostapd_remove_acl_mac(acl, num, addr);
                        continue;
                }
                vlan_id = 0;
@@ -190,20 +228,10 @@ static int hostapd_config_read_maclist(const char *fname,
                if (*pos != '\0')
                        vlan_id = atoi(pos);
 
-               newacl = os_realloc_array(*acl, *num + 1, sizeof(**acl));
-               if (newacl == NULL) {
-                       wpa_printf(MSG_ERROR, "MAC list reallocation failed");
+               if (hostapd_add_acl_maclist(acl, num, vlan_id, addr) < 0) {
                        fclose(f);
                        return -1;
                }
-
-               *acl = newacl;
-               os_memcpy((*acl)[*num].addr, addr, ETH_ALEN);
-               os_memset(&(*acl)[*num].vlan_id, 0,
-                         sizeof((*acl)[*num].vlan_id));
-               (*acl)[*num].vlan_id.untagged = vlan_id;
-               (*acl)[*num].vlan_id.notempty = !!vlan_id;
-               (*num)++;
        }
 
        fclose(f);
@@ -216,6 +244,62 @@ static int hostapd_config_read_maclist(const char *fname,
 
 
 #ifdef EAP_SERVER
+
+static int hostapd_config_eap_user_salted(struct hostapd_eap_user *user,
+                                         const char *hash, size_t len,
+                                         char **pos, int line,
+                                         const char *fname)
+{
+       char *pos2 = *pos;
+
+       while (*pos2 != '\0' && *pos2 != ' ' && *pos2 != '\t' && *pos2 != '#')
+               pos2++;
+
+       if (pos2 - *pos < (int) (2 * (len + 1))) { /* at least 1 byte of salt */
+               wpa_printf(MSG_ERROR,
+                          "Invalid salted %s hash on line %d in '%s'",
+                          hash, line, fname);
+               return -1;
+       }
+
+       user->password = os_malloc(len);
+       if (!user->password) {
+               wpa_printf(MSG_ERROR,
+                          "Failed to allocate memory for salted %s hash",
+                          hash);
+               return -1;
+       }
+
+       if (hexstr2bin(*pos, user->password, len) < 0) {
+               wpa_printf(MSG_ERROR,
+                          "Invalid salted password on line %d in '%s'",
+                          line, fname);
+               return -1;
+       }
+       user->password_len = len;
+       *pos += 2 * len;
+
+       user->salt_len = (pos2 - *pos) / 2;
+       user->salt = os_malloc(user->salt_len);
+       if (!user->salt) {
+               wpa_printf(MSG_ERROR,
+                          "Failed to allocate memory for salted %s hash",
+                          hash);
+               return -1;
+       }
+
+       if (hexstr2bin(*pos, user->salt, user->salt_len) < 0) {
+               wpa_printf(MSG_ERROR,
+                          "Invalid salt for password on line %d in '%s'",
+                          line, fname);
+               return -1;
+       }
+
+       *pos = pos2;
+       return 0;
+}
+
+
 static int hostapd_config_read_eap_user(const char *fname,
                                        struct hostapd_bss_config *conf)
 {
@@ -224,9 +308,6 @@ static int hostapd_config_read_eap_user(const char *fname,
        int line = 0, ret = 0, num_methods;
        struct hostapd_eap_user *user = NULL, *tail = NULL, *new_user = NULL;
 
-       if (!fname)
-               return 0;
-
        if (os_strncmp(fname, "sqlite:", 7) == 0) {
 #ifdef CONFIG_SQLITE
                os_free(conf->eap_user_sqlite);
@@ -313,13 +394,12 @@ static int hostapd_config_read_eap_user(const char *fname,
                                goto failed;
                        }
 
-                       user->identity = os_malloc(pos - start);
+                       user->identity = os_memdup(start, pos - start);
                        if (user->identity == NULL) {
                                wpa_printf(MSG_ERROR, "Failed to allocate "
                                           "memory for EAP identity");
                                goto failed;
                        }
-                       os_memcpy(user->identity, start, pos - start);
                        user->identity_len = pos - start;
 
                        if (pos[0] == '"' && pos[1] == '*') {
@@ -437,13 +517,12 @@ static int hostapd_config_read_eap_user(const char *fname,
                                goto failed;
                        }
 
-                       user->password = os_malloc(pos - start);
+                       user->password = os_memdup(start, pos - start);
                        if (user->password == NULL) {
                                wpa_printf(MSG_ERROR, "Failed to allocate "
                                           "memory for EAP password");
                                goto failed;
                        }
-                       os_memcpy(user->password, start, pos - start);
                        user->password_len = pos - start;
 
                        pos++;
@@ -472,6 +551,24 @@ static int hostapd_config_read_eap_user(const char *fname,
                        user->password_len = 16;
                        user->password_hash = 1;
                        pos = pos2;
+               } else if (os_strncmp(pos, "ssha1:", 6) == 0) {
+                       pos += 6;
+                       if (hostapd_config_eap_user_salted(user, "sha1", 20,
+                                                          &pos,
+                                                          line, fname) < 0)
+                               goto failed;
+               } else if (os_strncmp(pos, "ssha256:", 8) == 0) {
+                       pos += 8;
+                       if (hostapd_config_eap_user_salted(user, "sha256", 32,
+                                                          &pos,
+                                                          line, fname) < 0)
+                               goto failed;
+               } else if (os_strncmp(pos, "ssha512:", 8) == 0) {
+                       pos += 8;
+                       if (hostapd_config_eap_user_salted(user, "sha512", 64,
+                                                          &pos,
+                                                          line, fname) < 0)
+                               goto failed;
                } else {
                        pos2 = pos;
                        while (*pos2 != '\0' && *pos2 != ' ' &&
@@ -523,19 +620,15 @@ static int hostapd_config_read_eap_user(const char *fname,
        fclose(f);
 
        if (ret == 0) {
-               user = conf->eap_user;
-               while (user) {
-                       struct hostapd_eap_user *prev;
-
-                       prev = user;
-                       user = user->next;
-                       hostapd_config_free_eap_user(prev);
-               }
+               hostapd_config_free_eap_users(conf->eap_user);
                conf->eap_user = new_user;
+       } else {
+               hostapd_config_free_eap_users(new_user);
        }
 
        return ret;
 }
+
 #endif /* EAP_SERVER */
 
 
@@ -690,6 +783,10 @@ static int hostapd_config_parse_key_mgmt(int line, const char *value)
                        val |= WPA_KEY_MGMT_FT_PSK;
                else if (os_strcmp(start, "FT-EAP") == 0)
                        val |= WPA_KEY_MGMT_FT_IEEE8021X;
+#ifdef CONFIG_SHA384
+               else if (os_strcmp(start, "FT-EAP-SHA384") == 0)
+                       val |= WPA_KEY_MGMT_FT_IEEE8021X_SHA384;
+#endif /* CONFIG_SHA384 */
 #endif /* CONFIG_IEEE80211R_AP */
 #ifdef CONFIG_IEEE80211W
                else if (os_strcmp(start, "WPA-PSK-SHA256") == 0)
@@ -723,6 +820,18 @@ static int hostapd_config_parse_key_mgmt(int line, const char *value)
                        val |= WPA_KEY_MGMT_FT_FILS_SHA384;
 #endif /* CONFIG_IEEE80211R_AP */
 #endif /* CONFIG_FILS */
+#ifdef CONFIG_OWE
+               else if (os_strcmp(start, "OWE") == 0)
+                       val |= WPA_KEY_MGMT_OWE;
+#endif /* CONFIG_OWE */
+#ifdef CONFIG_DPP
+               else if (os_strcmp(start, "DPP") == 0)
+                       val |= WPA_KEY_MGMT_DPP;
+#endif /* CONFIG_DPP */
+#ifdef CONFIG_HS20
+               else if (os_strcmp(start, "OSEN") == 0)
+                       val |= WPA_KEY_MGMT_OSEN;
+#endif /* CONFIG_HS20 */
                else {
                        wpa_printf(MSG_ERROR, "Line %d: invalid key_mgmt '%s'",
                                   line, start);
@@ -793,10 +902,9 @@ static int hostapd_config_read_wep(struct hostapd_wep_keys *wep, int keyidx,
                if (len < 2 || val[len - 1] != '"')
                        return -1;
                len -= 2;
-               wep->key[keyidx] = os_malloc(len);
+               wep->key[keyidx] = os_memdup(val + 1, len);
                if (wep->key[keyidx] == NULL)
                        return -1;
-               os_memcpy(wep->key[keyidx], val + 1, len);
                wep->len[keyidx] = len;
        } else {
                if (len & 1)
@@ -1010,6 +1118,26 @@ static int hostapd_config_tx_queue(struct hostapd_config *conf,
 
 
 #ifdef CONFIG_IEEE80211R_AP
+
+static int rkh_derive_key(const char *pos, u8 *key, size_t key_len)
+{
+       u8 oldkey[16];
+       int ret;
+
+       if (!hexstr2bin(pos, key, key_len))
+               return 0;
+
+       /* Try to use old short key for backwards compatibility */
+       if (hexstr2bin(pos, oldkey, sizeof(oldkey)))
+               return -1;
+
+       ret = hmac_sha256_kdf(oldkey, sizeof(oldkey), "FT OLDKEY", NULL, 0,
+                             key, key_len);
+       os_memset(oldkey, 0, sizeof(oldkey));
+       return ret;
+}
+
+
 static int add_r0kh(struct hostapd_bss_config *bss, char *value)
 {
        struct ft_remote_r0kh *r0kh;
@@ -1043,7 +1171,7 @@ static int add_r0kh(struct hostapd_bss_config *bss, char *value)
        os_memcpy(r0kh->id, pos, r0kh->id_len);
 
        pos = next;
-       if (hexstr2bin(pos, r0kh->key, sizeof(r0kh->key))) {
+       if (rkh_derive_key(pos, r0kh->key, sizeof(r0kh->key)) < 0) {
                wpa_printf(MSG_ERROR, "Invalid R0KH key: '%s'", pos);
                os_free(r0kh);
                return -1;
@@ -1088,7 +1216,7 @@ static int add_r1kh(struct hostapd_bss_config *bss, char *value)
        }
 
        pos = next;
-       if (hexstr2bin(pos, r1kh->key, sizeof(r1kh->key))) {
+       if (rkh_derive_key(pos, r1kh->key, sizeof(r1kh->key)) < 0) {
                wpa_printf(MSG_ERROR, "Invalid R1KH key: '%s'", pos);
                os_free(r1kh);
                return -1;
@@ -1116,6 +1244,12 @@ static int hostapd_config_ht_capab(struct hostapd_config *conf,
                conf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
                conf->secondary_channel = 1;
        }
+       if (os_strstr(capab, "[HT40+]") && os_strstr(capab, "[HT40-]")) {
+               conf->ht_capab |= HT_CAP_INFO_SUPP_CHANNEL_WIDTH_SET;
+               conf->ht40_plus_minus_allowed = 1;
+       }
+       if (!os_strstr(capab, "[HT40+]") && !os_strstr(capab, "[HT40-]"))
+               conf->secondary_channel = 0;
        if (os_strstr(capab, "[SMPS-STATIC]")) {
                conf->ht_capab &= ~HT_CAP_INFO_SMPS_MASK;
                conf->ht_capab |= HT_CAP_INFO_SMPS_STATIC;
@@ -1245,6 +1379,30 @@ static int hostapd_config_vht_capab(struct hostapd_config *conf,
 #endif /* CONFIG_IEEE80211AC */
 
 
+#ifdef CONFIG_IEEE80211AX
+
+static u8 find_bit_offset(u8 val)
+{
+       u8 res = 0;
+
+       for (; val; val >>= 1) {
+               if (val & 1)
+                       break;
+               res++;
+       }
+
+       return res;
+}
+
+
+static u8 set_he_cap(int val, u8 mask)
+{
+       return (u8) (mask & (val << find_bit_offset(mask)));
+}
+
+#endif /* CONFIG_IEEE80211AX */
+
+
 #ifdef CONFIG_INTERWORKING
 static int parse_roaming_consortium(struct hostapd_bss_config *bss, char *pos,
                                    int line)
@@ -1338,6 +1496,44 @@ static int parse_venue_name(struct hostapd_bss_config *bss, char *pos,
 }
 
 
+static int parse_venue_url(struct hostapd_bss_config *bss, char *pos,
+                           int line)
+{
+       char *sep;
+       size_t nlen;
+       struct hostapd_venue_url *url;
+       int ret = -1;
+
+       sep = os_strchr(pos, ':');
+       if (!sep)
+               goto fail;
+       *sep++ = '\0';
+
+       nlen = os_strlen(sep);
+       if (nlen > 254)
+               goto fail;
+
+       url = os_realloc_array(bss->venue_url, bss->venue_url_count + 1,
+                              sizeof(struct hostapd_venue_url));
+       if (!url)
+               goto fail;
+
+       bss->venue_url = url;
+       url = &bss->venue_url[bss->venue_url_count++];
+
+       url->venue_number = atoi(pos);
+       url->url_len = nlen;
+       os_memcpy(url->url, sep, nlen);
+
+       ret = 0;
+fail:
+       if (ret)
+               wpa_printf(MSG_ERROR, "Line %d: Invalid venue_url '%s'",
+                          line, pos);
+       return ret;
+}
+
+
 static int parse_3gpp_cell_net(struct hostapd_bss_config *bss, char *buf,
                               int line)
 {
@@ -1888,6 +2084,24 @@ static int hs20_parse_osu_nai(struct hostapd_bss_config *bss,
 }
 
 
+static int hs20_parse_osu_nai2(struct hostapd_bss_config *bss,
+                              char *pos, int line)
+{
+       if (bss->last_osu == NULL) {
+               wpa_printf(MSG_ERROR, "Line %d: Unexpected OSU field", line);
+               return -1;
+       }
+
+       os_free(bss->last_osu->osu_nai2);
+       bss->last_osu->osu_nai2 = os_strdup(pos);
+       if (bss->last_osu->osu_nai2 == NULL)
+               return -1;
+       bss->hs20_osu_providers_nai_count++;
+
+       return 0;
+}
+
+
 static int hs20_parse_osu_method_list(struct hostapd_bss_config *bss, char *pos,
                                      int line)
 {
@@ -1947,6 +2161,25 @@ static int hs20_parse_osu_service_desc(struct hostapd_bss_config *bss,
        return 0;
 }
 
+
+static int hs20_parse_operator_icon(struct hostapd_bss_config *bss, char *pos,
+                                   int line)
+{
+       char **n;
+
+       n = os_realloc_array(bss->hs20_operator_icon,
+                            bss->hs20_operator_icon_count + 1, sizeof(char *));
+       if (!n)
+               return -1;
+       bss->hs20_operator_icon = n;
+       bss->hs20_operator_icon[bss->hs20_operator_icon_count] = os_strdup(pos);
+       if (!bss->hs20_operator_icon[bss->hs20_operator_icon_count])
+               return -1;
+       bss->hs20_operator_icon_count++;
+
+       return 0;
+}
+
 #endif /* CONFIG_HS20 */
 
 
@@ -2040,6 +2273,109 @@ static int parse_fils_realm(struct hostapd_bss_config *bss, const char *val)
 #endif /* CONFIG_FILS */
 
 
+#ifdef EAP_SERVER
+static unsigned int parse_tls_flags(const char *val)
+{
+       unsigned int flags = 0;
+
+       /* Disable TLS v1.3 by default for now to avoid interoperability issue.
+        * This can be enabled by default once the implementation has been fully
+        * completed and tested with other implementations. */
+       flags |= TLS_CONN_DISABLE_TLSv1_3;
+
+       if (os_strstr(val, "[ALLOW-SIGN-RSA-MD5]"))
+               flags |= TLS_CONN_ALLOW_SIGN_RSA_MD5;
+       if (os_strstr(val, "[DISABLE-TIME-CHECKS]"))
+               flags |= TLS_CONN_DISABLE_TIME_CHECKS;
+       if (os_strstr(val, "[DISABLE-TLSv1.0]"))
+               flags |= TLS_CONN_DISABLE_TLSv1_0;
+       if (os_strstr(val, "[ENABLE-TLSv1.0]"))
+               flags |= TLS_CONN_ENABLE_TLSv1_0;
+       if (os_strstr(val, "[DISABLE-TLSv1.1]"))
+               flags |= TLS_CONN_DISABLE_TLSv1_1;
+       if (os_strstr(val, "[ENABLE-TLSv1.1]"))
+               flags |= TLS_CONN_ENABLE_TLSv1_1;
+       if (os_strstr(val, "[DISABLE-TLSv1.2]"))
+               flags |= TLS_CONN_DISABLE_TLSv1_2;
+       if (os_strstr(val, "[ENABLE-TLSv1.2]"))
+               flags |= TLS_CONN_ENABLE_TLSv1_2;
+       if (os_strstr(val, "[DISABLE-TLSv1.3]"))
+               flags |= TLS_CONN_DISABLE_TLSv1_3;
+       if (os_strstr(val, "[ENABLE-TLSv1.3]"))
+               flags &= ~TLS_CONN_DISABLE_TLSv1_3;
+       if (os_strstr(val, "[SUITEB]"))
+               flags |= TLS_CONN_SUITEB;
+       if (os_strstr(val, "[SUITEB-NO-ECDH]"))
+               flags |= TLS_CONN_SUITEB_NO_ECDH | TLS_CONN_SUITEB;
+
+       return flags;
+}
+#endif /* EAP_SERVER */
+
+
+#ifdef CONFIG_SAE
+static int parse_sae_password(struct hostapd_bss_config *bss, const char *val)
+{
+       struct sae_password_entry *pw;
+       const char *pos = val, *pos2, *end = NULL;
+
+       pw = os_zalloc(sizeof(*pw));
+       if (!pw)
+               return -1;
+       os_memset(pw->peer_addr, 0xff, ETH_ALEN); /* default to wildcard */
+
+       pos2 = os_strstr(pos, "|mac=");
+       if (pos2) {
+               end = pos2;
+               pos2 += 5;
+               if (hwaddr_aton(pos2, pw->peer_addr) < 0)
+                       goto fail;
+               pos = pos2 + ETH_ALEN * 3 - 1;
+       }
+
+       pos2 = os_strstr(pos, "|vlanid=");
+       if (pos2) {
+               if (!end)
+                       end = pos2;
+               pos2 += 8;
+               pw->vlan_id = atoi(pos2);
+       }
+
+       pos2 = os_strstr(pos, "|id=");
+       if (pos2) {
+               if (!end)
+                       end = pos2;
+               pos2 += 4;
+               pw->identifier = os_strdup(pos2);
+               if (!pw->identifier)
+                       goto fail;
+       }
+
+       if (!end) {
+               pw->password = os_strdup(val);
+               if (!pw->password)
+                       goto fail;
+       } else {
+               pw->password = os_malloc(end - val + 1);
+               if (!pw->password)
+                       goto fail;
+               os_memcpy(pw->password, val, end - val);
+               pw->password[end - val] = '\0';
+       }
+
+       pw->next = bss->sae_passwords;
+       bss->sae_passwords = pw;
+
+       return 0;
+fail:
+       str_clear_free(pw->password);
+       os_free(pw->identifier);
+       os_free(pw);
+       return -1;
+}
+#endif /* CONFIG_SAE */
+
+
 static int hostapd_config_fill(struct hostapd_config *conf,
                               struct hostapd_bss_config *bss,
                               const char *buf, char *pos, int line)
@@ -2113,13 +2449,16 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "utf8_ssid") == 0) {
                bss->ssid.utf8_ssid = atoi(pos) > 0;
        } else if (os_strcmp(buf, "macaddr_acl") == 0) {
-               bss->macaddr_acl = atoi(pos);
-               if (bss->macaddr_acl != ACCEPT_UNLESS_DENIED &&
-                   bss->macaddr_acl != DENY_UNLESS_ACCEPTED &&
-                   bss->macaddr_acl != USE_EXTERNAL_RADIUS_AUTH) {
+               enum macaddr_acl acl = atoi(pos);
+
+               if (acl != ACCEPT_UNLESS_DENIED &&
+                   acl != DENY_UNLESS_ACCEPTED &&
+                   acl != USE_EXTERNAL_RADIUS_AUTH) {
                        wpa_printf(MSG_ERROR, "Line %d: unknown macaddr_acl %d",
-                                  line, bss->macaddr_acl);
+                                  line, acl);
+                       return 1;
                }
+               bss->macaddr_acl = acl;
        } else if (os_strcmp(buf, "accept_mac_file") == 0) {
                if (hostapd_config_read_maclist(pos, &bss->accept_mac,
                                                &bss->num_accept_mac)) {
@@ -2146,8 +2485,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                bss->skip_inactivity_poll = atoi(pos);
        } else if (os_strcmp(buf, "country_code") == 0) {
                os_memcpy(conf->country, pos, 2);
-               /* FIX: make this configurable */
-               conf->country[2] = ' ';
+       } else if (os_strcmp(buf, "country3") == 0) {
+               conf->country[2] = strtol(pos, NULL, 16);
        } else if (os_strcmp(buf, "ieee80211d") == 0) {
                conf->ieee80211d = atoi(pos);
        } else if (os_strcmp(buf, "ieee80211h") == 0) {
@@ -2155,13 +2494,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "ieee8021x") == 0) {
                bss->ieee802_1x = atoi(pos);
        } else if (os_strcmp(buf, "eapol_version") == 0) {
-               bss->eapol_version = atoi(pos);
-               if (bss->eapol_version < 1 || bss->eapol_version > 2) {
+               int eapol_version = atoi(pos);
+
+               if (eapol_version < 1 || eapol_version > 2) {
                        wpa_printf(MSG_ERROR,
                                   "Line %d: invalid EAPOL version (%d): '%s'.",
-                                  line, bss->eapol_version, pos);
+                                  line, eapol_version, pos);
                        return 1;
                }
+               bss->eapol_version = eapol_version;
                wpa_printf(MSG_DEBUG, "eapol_version=%d", bss->eapol_version);
 #ifdef EAP_SERVER
        } else if (os_strcmp(buf, "eap_authenticator") == 0) {
@@ -2186,8 +2527,14 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                bss->private_key_passwd = os_strdup(pos);
        } else if (os_strcmp(buf, "check_crl") == 0) {
                bss->check_crl = atoi(pos);
+       } else if (os_strcmp(buf, "check_crl_strict") == 0) {
+               bss->check_crl_strict = atoi(pos);
+       } else if (os_strcmp(buf, "crl_reload_interval") == 0) {
+               bss->crl_reload_interval = atoi(pos);
        } else if (os_strcmp(buf, "tls_session_lifetime") == 0) {
                bss->tls_session_lifetime = atoi(pos);
+       } else if (os_strcmp(buf, "tls_flags") == 0) {
+               bss->tls_flags = parse_tls_flags(pos);
        } else if (os_strcmp(buf, "ocsp_stapling_response") == 0) {
                os_free(bss->ocsp_stapling_response);
                bss->ocsp_stapling_response = os_strdup(pos);
@@ -2200,6 +2547,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "openssl_ciphers") == 0) {
                os_free(bss->openssl_ciphers);
                bss->openssl_ciphers = os_strdup(pos);
+       } else if (os_strcmp(buf, "openssl_ecdh_curves") == 0) {
+               os_free(bss->openssl_ecdh_curves);
+               bss->openssl_ecdh_curves = os_strdup(pos);
        } else if (os_strcmp(buf, "fragment_size") == 0) {
                bss->fragment_size = atoi(pos);
 #ifdef EAP_SERVER_FAST
@@ -2262,8 +2612,10 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "pwd_group") == 0) {
                bss->pwd_group = atoi(pos);
 #endif /* EAP_SERVER_PWD */
+#ifdef CONFIG_ERP
        } else if (os_strcmp(buf, "eap_server_erp") == 0) {
                bss->eap_server_erp = atoi(pos);
+#endif /* CONFIG_ERP */
 #endif /* EAP_SERVER */
        } else if (os_strcmp(buf, "eap_message") == 0) {
                char *term;
@@ -2289,24 +2641,25 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                os_free(bss->erp_domain);
                bss->erp_domain = os_strdup(pos);
        } else if (os_strcmp(buf, "wep_key_len_broadcast") == 0) {
-               bss->default_wep_key_len = atoi(pos);
-               if (bss->default_wep_key_len > 13) {
-                       wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %lu (= %lu bits)",
-                                  line,
-                                  (unsigned long) bss->default_wep_key_len,
-                                  (unsigned long)
-                                  bss->default_wep_key_len * 8);
+               int val = atoi(pos);
+
+               if (val < 0 || val > 13) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: invalid WEP key len %d (= %d bits)",
+                                  line, val, val * 8);
                        return 1;
                }
+               bss->default_wep_key_len = val;
        } else if (os_strcmp(buf, "wep_key_len_unicast") == 0) {
-               bss->individual_wep_key_len = atoi(pos);
-               if (bss->individual_wep_key_len < 0 ||
-                   bss->individual_wep_key_len > 13) {
-                       wpa_printf(MSG_ERROR, "Line %d: invalid WEP key len %d (= %d bits)",
-                                  line, bss->individual_wep_key_len,
-                                  bss->individual_wep_key_len * 8);
+               int val = atoi(pos);
+
+               if (val < 0 || val > 13) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: invalid WEP key len %d (= %d bits)",
+                                  line, val, val * 8);
                        return 1;
                }
+               bss->individual_wep_key_len = val;
        } else if (os_strcmp(buf, "wep_rekey_period") == 0) {
                bss->wep_rekeying_period = atoi(pos);
                if (bss->wep_rekeying_period < 0) {
@@ -2488,12 +2841,37 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                bss->wpa = atoi(pos);
        } else if (os_strcmp(buf, "wpa_group_rekey") == 0) {
                bss->wpa_group_rekey = atoi(pos);
+               bss->wpa_group_rekey_set = 1;
        } else if (os_strcmp(buf, "wpa_strict_rekey") == 0) {
                bss->wpa_strict_rekey = atoi(pos);
        } else if (os_strcmp(buf, "wpa_gmk_rekey") == 0) {
                bss->wpa_gmk_rekey = atoi(pos);
        } else if (os_strcmp(buf, "wpa_ptk_rekey") == 0) {
                bss->wpa_ptk_rekey = atoi(pos);
+       } else if (os_strcmp(buf, "wpa_group_update_count") == 0) {
+               char *endp;
+               unsigned long val = strtoul(pos, &endp, 0);
+
+               if (*endp || val < 1 || val > (u32) -1) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: Invalid wpa_group_update_count=%lu; allowed range 1..4294967295",
+                                  line, val);
+                       return 1;
+               }
+               bss->wpa_group_update_count = (u32) val;
+       } else if (os_strcmp(buf, "wpa_pairwise_update_count") == 0) {
+               char *endp;
+               unsigned long val = strtoul(pos, &endp, 0);
+
+               if (*endp || val < 1 || val > (u32) -1) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: Invalid wpa_pairwise_update_count=%lu; allowed range 1..4294967295",
+                                  line, val);
+                       return 1;
+               }
+               bss->wpa_pairwise_update_count = (u32) val;
+       } else if (os_strcmp(buf, "wpa_disable_eapol_key_retries") == 0) {
+               bss->wpa_disable_eapol_key_retries = atoi(pos);
        } else if (os_strcmp(buf, "wpa_passphrase") == 0) {
                int len = os_strlen(pos);
                if (len < 8 || len > 63) {
@@ -2552,7 +2930,7 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                if (bss->wpa_pairwise &
                    (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) {
                        wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'",
-                                  bss->wpa_pairwise, pos);
+                                  line, pos);
                        return 1;
                }
        } else if (os_strcmp(buf, "rsn_pairwise") == 0) {
@@ -2562,7 +2940,21 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                if (bss->rsn_pairwise &
                    (WPA_CIPHER_NONE | WPA_CIPHER_WEP40 | WPA_CIPHER_WEP104)) {
                        wpa_printf(MSG_ERROR, "Line %d: unsupported pairwise cipher suite '%s'",
-                                  bss->rsn_pairwise, pos);
+                                  line, pos);
+                       return 1;
+               }
+       } else if (os_strcmp(buf, "group_cipher") == 0) {
+               bss->group_cipher = hostapd_config_parse_cipher(line, pos);
+               if (bss->group_cipher == -1 || bss->group_cipher == 0)
+                       return 1;
+               if (bss->group_cipher != WPA_CIPHER_TKIP &&
+                   bss->group_cipher != WPA_CIPHER_CCMP &&
+                   bss->group_cipher != WPA_CIPHER_GCMP &&
+                   bss->group_cipher != WPA_CIPHER_GCMP_256 &&
+                   bss->group_cipher != WPA_CIPHER_CCMP_256) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: unsupported group cipher suite '%s'",
+                                  line, pos);
                        return 1;
                }
 #ifdef CONFIG_RSN_PREAUTH
@@ -2572,10 +2964,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                os_free(bss->rsn_preauth_interfaces);
                bss->rsn_preauth_interfaces = os_strdup(pos);
 #endif /* CONFIG_RSN_PREAUTH */
-#ifdef CONFIG_PEERKEY
        } else if (os_strcmp(buf, "peerkey") == 0) {
-               bss->peerkey = atoi(pos);
-#endif /* CONFIG_PEERKEY */
+               wpa_printf(MSG_INFO,
+                          "Line %d: Obsolete peerkey parameter ignored", line);
 #ifdef CONFIG_IEEE80211R_AP
        } else if (os_strcmp(buf, "mobility_domain") == 0) {
                if (os_strlen(pos) != 2 * MOBILITY_DOMAIN_ID_LEN ||
@@ -2595,9 +2986,22 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                        return 1;
                }
        } else if (os_strcmp(buf, "r0_key_lifetime") == 0) {
+               /* DEPRECATED: Use ft_r0_key_lifetime instead. */
+               bss->r0_key_lifetime = atoi(pos) * 60;
+       } else if (os_strcmp(buf, "ft_r0_key_lifetime") == 0) {
                bss->r0_key_lifetime = atoi(pos);
+       } else if (os_strcmp(buf, "r1_max_key_lifetime") == 0) {
+               bss->r1_max_key_lifetime = atoi(pos);
        } else if (os_strcmp(buf, "reassociation_deadline") == 0) {
                bss->reassociation_deadline = atoi(pos);
+       } else if (os_strcmp(buf, "rkh_pos_timeout") == 0) {
+               bss->rkh_pos_timeout = atoi(pos);
+       } else if (os_strcmp(buf, "rkh_neg_timeout") == 0) {
+               bss->rkh_neg_timeout = atoi(pos);
+       } else if (os_strcmp(buf, "rkh_pull_timeout") == 0) {
+               bss->rkh_pull_timeout = atoi(pos);
+       } else if (os_strcmp(buf, "rkh_pull_retries") == 0) {
+               bss->rkh_pull_retries = atoi(pos);
        } else if (os_strcmp(buf, "r0kh") == 0) {
                if (add_r0kh(bss, pos) < 0) {
                        wpa_printf(MSG_DEBUG, "Line %d: Invalid r0kh '%s'",
@@ -2694,6 +3098,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                                   line, pos);
                        return 1;
                }
+       } else if (os_strcmp(buf, "acs_exclude_dfs") == 0) {
+               conf->acs_exclude_dfs = atoi(pos);
        } else if (os_strcmp(buf, "channel") == 0) {
                if (os_strcmp(pos, "acs_survey") == 0) {
 #ifndef CONFIG_ACS
@@ -2744,21 +3150,34 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                }
 #endif /* CONFIG_ACS */
        } else if (os_strcmp(buf, "dtim_period") == 0) {
-               bss->dtim_period = atoi(pos);
-               if (bss->dtim_period < 1 || bss->dtim_period > 255) {
+               int val = atoi(pos);
+
+               if (val < 1 || val > 255) {
                        wpa_printf(MSG_ERROR, "Line %d: invalid dtim_period %d",
-                                  line, bss->dtim_period);
+                                  line, val);
                        return 1;
                }
+               bss->dtim_period = val;
        } else if (os_strcmp(buf, "bss_load_update_period") == 0) {
-               bss->bss_load_update_period = atoi(pos);
-               if (bss->bss_load_update_period < 0 ||
-                   bss->bss_load_update_period > 100) {
+               int val = atoi(pos);
+
+               if (val < 0 || val > 100) {
                        wpa_printf(MSG_ERROR,
                                   "Line %d: invalid bss_load_update_period %d",
-                                  line, bss->bss_load_update_period);
+                                  line, val);
                        return 1;
                }
+               bss->bss_load_update_period = val;
+       } else if (os_strcmp(buf, "chan_util_avg_period") == 0) {
+               int val = atoi(pos);
+
+               if (val < 0) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: invalid chan_util_avg_period",
+                                  line);
+                       return 1;
+               }
+               bss->chan_util_avg_period = val;
        } else if (os_strcmp(buf, "rts_threshold") == 0) {
                conf->rts_threshold = atoi(pos);
                if (conf->rts_threshold < -1 || conf->rts_threshold > 65535) {
@@ -2785,7 +3204,7 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                                   line, val);
                        return 1;
                }
-               conf->send_probe_response = val;
+               bss->send_probe_response = val;
        } else if (os_strcmp(buf, "supported_rates") == 0) {
                if (hostapd_parse_intlist(&conf->supported_rates, pos)) {
                        wpa_printf(MSG_ERROR, "Line %d: invalid rate list",
@@ -2953,6 +3372,12 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                        return 1;
                }
 #endif /* CONFIG_IEEE80211W */
+#ifdef CONFIG_OCV
+       } else if (os_strcmp(buf, "ocv") == 0) {
+               bss->ocv = atoi(pos);
+               if (bss->ocv && !bss->ieee80211w)
+                       bss->ieee80211w = 1;
+#endif /* CONFIG_OCV */
 #ifdef CONFIG_IEEE80211N
        } else if (os_strcmp(buf, "ieee80211n") == 0) {
                conf->ieee80211n = atoi(pos);
@@ -2989,6 +3414,108 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "use_sta_nsts") == 0) {
                bss->use_sta_nsts = atoi(pos);
 #endif /* CONFIG_IEEE80211AC */
+#ifdef CONFIG_IEEE80211AX
+       } else if (os_strcmp(buf, "ieee80211ax") == 0) {
+               conf->ieee80211ax = atoi(pos);
+       } else if (os_strcmp(buf, "he_su_beamformer") == 0) {
+               conf->he_phy_capab.he_su_beamformer = atoi(pos);
+       } else if (os_strcmp(buf, "he_su_beamformee") == 0) {
+               conf->he_phy_capab.he_su_beamformee = atoi(pos);
+       } else if (os_strcmp(buf, "he_mu_beamformer") == 0) {
+               conf->he_phy_capab.he_mu_beamformer = atoi(pos);
+       } else if (os_strcmp(buf, "he_bss_color") == 0) {
+               conf->he_op.he_bss_color = atoi(pos);
+       } else if (os_strcmp(buf, "he_default_pe_duration") == 0) {
+               conf->he_op.he_default_pe_duration = atoi(pos);
+       } else if (os_strcmp(buf, "he_twt_required") == 0) {
+               conf->he_op.he_twt_required = atoi(pos);
+       } else if (os_strcmp(buf, "he_rts_threshold") == 0) {
+               conf->he_op.he_rts_threshold = atoi(pos);
+       } else if (os_strcmp(buf, "he_mu_edca_qos_info_param_count") == 0) {
+               conf->he_mu_edca.he_qos_info |=
+                       set_he_cap(atoi(pos), HE_QOS_INFO_EDCA_PARAM_SET_COUNT);
+       } else if (os_strcmp(buf, "he_mu_edca_qos_info_q_ack") == 0) {
+               conf->he_mu_edca.he_qos_info |=
+                       set_he_cap(atoi(pos), HE_QOS_INFO_Q_ACK);
+       } else if (os_strcmp(buf, "he_mu_edca_qos_info_queue_request") == 0) {
+               conf->he_mu_edca.he_qos_info |=
+                       set_he_cap(atoi(pos), HE_QOS_INFO_QUEUE_REQUEST);
+       } else if (os_strcmp(buf, "he_mu_edca_qos_info_txop_request") == 0) {
+               conf->he_mu_edca.he_qos_info |=
+                       set_he_cap(atoi(pos), HE_QOS_INFO_TXOP_REQUEST);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_be_aifsn") == 0) {
+               conf->he_mu_edca.he_mu_ac_be_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_AIFSN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_be_acm") == 0) {
+               conf->he_mu_edca.he_mu_ac_be_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACM);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_be_aci") == 0) {
+               conf->he_mu_edca.he_mu_ac_be_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACI);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_be_ecwmin") == 0) {
+               conf->he_mu_edca.he_mu_ac_be_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMIN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_be_ecwmax") == 0) {
+               conf->he_mu_edca.he_mu_ac_be_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMAX);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_be_timer") == 0) {
+               conf->he_mu_edca.he_mu_ac_be_param[HE_MU_AC_PARAM_TIMER_IDX] =
+                       atoi(pos) & 0xff;
+       } else if (os_strcmp(buf, "he_mu_edca_ac_bk_aifsn") == 0) {
+               conf->he_mu_edca.he_mu_ac_bk_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_AIFSN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_bk_acm") == 0) {
+               conf->he_mu_edca.he_mu_ac_bk_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACM);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_bk_aci") == 0) {
+               conf->he_mu_edca.he_mu_ac_bk_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACI);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_bk_ecwmin") == 0) {
+               conf->he_mu_edca.he_mu_ac_bk_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMIN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_bk_ecwmax") == 0) {
+               conf->he_mu_edca.he_mu_ac_bk_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMAX);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_bk_timer") == 0) {
+               conf->he_mu_edca.he_mu_ac_bk_param[HE_MU_AC_PARAM_TIMER_IDX] =
+                       atoi(pos) & 0xff;
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vi_aifsn") == 0) {
+               conf->he_mu_edca.he_mu_ac_vi_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_AIFSN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vi_acm") == 0) {
+               conf->he_mu_edca.he_mu_ac_vi_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACM);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vi_aci") == 0) {
+               conf->he_mu_edca.he_mu_ac_vi_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACI);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vi_ecwmin") == 0) {
+               conf->he_mu_edca.he_mu_ac_vi_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMIN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vi_ecwmax") == 0) {
+               conf->he_mu_edca.he_mu_ac_vi_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMAX);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vi_timer") == 0) {
+               conf->he_mu_edca.he_mu_ac_vi_param[HE_MU_AC_PARAM_TIMER_IDX] =
+                       atoi(pos) & 0xff;
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vo_aifsn") == 0) {
+               conf->he_mu_edca.he_mu_ac_vo_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_AIFSN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vo_acm") == 0) {
+               conf->he_mu_edca.he_mu_ac_vo_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACM);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vo_aci") == 0) {
+               conf->he_mu_edca.he_mu_ac_vo_param[HE_MU_AC_PARAM_ACI_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ACI);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vo_ecwmin") == 0) {
+               conf->he_mu_edca.he_mu_ac_vo_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMIN);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vo_ecwmax") == 0) {
+               conf->he_mu_edca.he_mu_ac_vo_param[HE_MU_AC_PARAM_ECW_IDX] |=
+                       set_he_cap(atoi(pos), HE_MU_AC_PARAM_ECWMAX);
+       } else if (os_strcmp(buf, "he_mu_edca_ac_vo_timer") == 0) {
+               conf->he_mu_edca.he_mu_ac_vo_param[HE_MU_AC_PARAM_TIMER_IDX] =
+                       atoi(pos) & 0xff;
+#endif /* CONFIG_IEEE80211AX */
        } else if (os_strcmp(buf, "max_listen_interval") == 0) {
                bss->max_listen_interval = atoi(pos);
        } else if (os_strcmp(buf, "disable_pmksa_caching") == 0) {
@@ -3069,7 +3596,10 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                }
        } else if (os_strcmp(buf, "ap_pin") == 0) {
                os_free(bss->ap_pin);
-               bss->ap_pin = os_strdup(pos);
+               if (*pos == '\0')
+                       bss->ap_pin = NULL;
+               else
+                       bss->ap_pin = os_strdup(pos);
        } else if (os_strcmp(buf, "skip_cred_build") == 0) {
                bss->skip_cred_build = atoi(pos);
        } else if (os_strcmp(buf, "extra_cred") == 0) {
@@ -3082,6 +3612,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                }
        } else if (os_strcmp(buf, "wps_cred_processing") == 0) {
                bss->wps_cred_processing = atoi(pos);
+       } else if (os_strcmp(buf, "wps_cred_add_sae") == 0) {
+               bss->wps_cred_add_sae = atoi(pos);
        } else if (os_strcmp(buf, "ap_settings") == 0) {
                os_free(bss->ap_settings);
                bss->ap_settings =
@@ -3091,6 +3623,56 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                                   line, pos);
                        return 1;
                }
+       } else if (os_strcmp(buf, "multi_ap_backhaul_ssid") == 0) {
+               size_t slen;
+               char *str = wpa_config_parse_string(pos, &slen);
+
+               if (!str || slen < 1 || slen > SSID_MAX_LEN) {
+                       wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'",
+                                  line, pos);
+                       os_free(str);
+                       return 1;
+               }
+               os_memcpy(bss->multi_ap_backhaul_ssid.ssid, str, slen);
+               bss->multi_ap_backhaul_ssid.ssid_len = slen;
+               bss->multi_ap_backhaul_ssid.ssid_set = 1;
+               os_free(str);
+       } else if (os_strcmp(buf, "multi_ap_backhaul_wpa_passphrase") == 0) {
+               int len = os_strlen(pos);
+
+               if (len < 8 || len > 63) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: invalid WPA passphrase length %d (expected 8..63)",
+                                  line, len);
+                       return 1;
+               }
+               os_free(bss->multi_ap_backhaul_ssid.wpa_passphrase);
+               bss->multi_ap_backhaul_ssid.wpa_passphrase = os_strdup(pos);
+               if (bss->multi_ap_backhaul_ssid.wpa_passphrase) {
+                       hostapd_config_clear_wpa_psk(
+                               &bss->multi_ap_backhaul_ssid.wpa_psk);
+                       bss->multi_ap_backhaul_ssid.wpa_passphrase_set = 1;
+               }
+       } else if (os_strcmp(buf, "multi_ap_backhaul_wpa_psk") == 0) {
+               hostapd_config_clear_wpa_psk(
+                       &bss->multi_ap_backhaul_ssid.wpa_psk);
+               bss->multi_ap_backhaul_ssid.wpa_psk =
+                       os_zalloc(sizeof(struct hostapd_wpa_psk));
+               if (!bss->multi_ap_backhaul_ssid.wpa_psk)
+                       return 1;
+               if (hexstr2bin(pos, bss->multi_ap_backhaul_ssid.wpa_psk->psk,
+                              PMK_LEN) ||
+                   pos[PMK_LEN * 2] != '\0') {
+                       wpa_printf(MSG_ERROR, "Line %d: Invalid PSK '%s'.",
+                                  line, pos);
+                       hostapd_config_clear_wpa_psk(
+                               &bss->multi_ap_backhaul_ssid.wpa_psk);
+                       return 1;
+               }
+               bss->multi_ap_backhaul_ssid.wpa_psk->group = 1;
+               os_free(bss->multi_ap_backhaul_ssid.wpa_passphrase);
+               bss->multi_ap_backhaul_ssid.wpa_passphrase = NULL;
+               bss->multi_ap_backhaul_ssid.wpa_psk_set = 1;
        } else if (os_strcmp(buf, "upnp_iface") == 0) {
                os_free(bss->upnp_iface);
                bss->upnp_iface = os_strdup(pos);
@@ -3180,12 +3762,14 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                bss->time_zone = os_strdup(pos);
                if (bss->time_zone == NULL)
                        return 1;
-#ifdef CONFIG_WNM
+#ifdef CONFIG_WNM_AP
        } else if (os_strcmp(buf, "wnm_sleep_mode") == 0) {
                bss->wnm_sleep_mode = atoi(pos);
+       } else if (os_strcmp(buf, "wnm_sleep_mode_no_keys") == 0) {
+               bss->wnm_sleep_mode_no_keys = atoi(pos);
        } else if (os_strcmp(buf, "bss_transition") == 0) {
                bss->bss_transition = atoi(pos);
-#endif /* CONFIG_WNM */
+#endif /* CONFIG_WNM_AP */
 #ifdef CONFIG_INTERWORKING
        } else if (os_strcmp(buf, "interworking") == 0) {
                bss->interworking = atoi(pos);
@@ -3223,6 +3807,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "venue_name") == 0) {
                if (parse_venue_name(bss, pos, line) < 0)
                        return 1;
+       } else if (os_strcmp(buf, "venue_url") == 0) {
+               if (parse_venue_url(bss, pos, line) < 0)
+                       return 1;
        } else if (os_strcmp(buf, "network_auth_type") == 0) {
                u8 auth_type;
                u16 redirect_url_len;
@@ -3301,7 +3888,15 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                if (parse_anqp_elem(bss, pos, line) < 0)
                        return 1;
        } else if (os_strcmp(buf, "gas_frag_limit") == 0) {
-               bss->gas_frag_limit = atoi(pos);
+               int val = atoi(pos);
+
+               if (val <= 0) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: Invalid gas_frag_limit '%s'",
+                                  line, pos);
+                       return 1;
+               }
+               bss->gas_frag_limit = val;
        } else if (os_strcmp(buf, "gas_comeback_delay") == 0) {
                bss->gas_comeback_delay = atoi(pos);
        } else if (os_strcmp(buf, "qos_map_set") == 0) {
@@ -3320,6 +3915,16 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 #ifdef CONFIG_HS20
        } else if (os_strcmp(buf, "hs20") == 0) {
                bss->hs20 = atoi(pos);
+       } else if (os_strcmp(buf, "hs20_release") == 0) {
+               int val = atoi(pos);
+
+               if (val < 1 || val > (HS20_VERSION >> 4) + 1) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: Unsupported hs20_release: %s",
+                                  line, pos);
+                       return 1;
+               }
+               bss->hs20_release = val;
        } else if (os_strcmp(buf, "disable_dgaf") == 0) {
                bss->disable_dgaf = atoi(pos);
        } else if (os_strcmp(buf, "na_mcast_to_ucast") == 0) {
@@ -3382,6 +3987,9 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "osu_nai") == 0) {
                if (hs20_parse_osu_nai(bss, pos, line) < 0)
                        return 1;
+       } else if (os_strcmp(buf, "osu_nai2") == 0) {
+               if (hs20_parse_osu_nai2(bss, pos, line) < 0)
+                       return 1;
        } else if (os_strcmp(buf, "osu_method_list") == 0) {
                if (hs20_parse_osu_method_list(bss, pos, line) < 0)
                        return 1;
@@ -3391,15 +3999,33 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "osu_service_desc") == 0) {
                if (hs20_parse_osu_service_desc(bss, pos, line) < 0)
                        return 1;
+       } else if (os_strcmp(buf, "operator_icon") == 0) {
+               if (hs20_parse_operator_icon(bss, pos, line) < 0)
+                       return 1;
        } else if (os_strcmp(buf, "subscr_remediation_url") == 0) {
                os_free(bss->subscr_remediation_url);
                bss->subscr_remediation_url = os_strdup(pos);
        } else if (os_strcmp(buf, "subscr_remediation_method") == 0) {
                bss->subscr_remediation_method = atoi(pos);
+       } else if (os_strcmp(buf, "hs20_t_c_filename") == 0) {
+               os_free(bss->t_c_filename);
+               bss->t_c_filename = os_strdup(pos);
+       } else if (os_strcmp(buf, "hs20_t_c_timestamp") == 0) {
+               bss->t_c_timestamp = strtol(pos, NULL, 0);
+       } else if (os_strcmp(buf, "hs20_t_c_server_url") == 0) {
+               os_free(bss->t_c_server_url);
+               bss->t_c_server_url = os_strdup(pos);
+       } else if (os_strcmp(buf, "hs20_sim_provisioning_url") == 0) {
+               os_free(bss->hs20_sim_provisioning_url);
+               bss->hs20_sim_provisioning_url = os_strdup(pos);
 #endif /* CONFIG_HS20 */
 #ifdef CONFIG_MBO
        } else if (os_strcmp(buf, "mbo") == 0) {
                bss->mbo_enabled = atoi(pos);
+       } else if (os_strcmp(buf, "mbo_cell_data_conn_pref") == 0) {
+               bss->mbo_cell_data_conn_pref = atoi(pos);
+       } else if (os_strcmp(buf, "oce") == 0) {
+               bss->oce = atoi(pos);
 #endif /* CONFIG_MBO */
 #ifdef CONFIG_TESTING_OPTIONS
 #define PARSE_TEST_PROBABILITY(_val)                           \
@@ -3468,7 +4094,20 @@ static int hostapd_config_fill(struct hostapd_config *conf,
 
                wpabuf_free(bss->own_ie_override);
                bss->own_ie_override = tmp;
+       } else if (os_strcmp(buf, "sae_reflection_attack") == 0) {
+               bss->sae_reflection_attack = atoi(pos);
+       } else if (os_strcmp(buf, "sae_commit_override") == 0) {
+               wpabuf_free(bss->sae_commit_override);
+               bss->sae_commit_override = wpabuf_parse_bin(pos);
 #endif /* CONFIG_TESTING_OPTIONS */
+#ifdef CONFIG_SAE
+       } else if (os_strcmp(buf, "sae_password") == 0) {
+               if (parse_sae_password(bss, pos) < 0) {
+                       wpa_printf(MSG_ERROR, "Line %d: Invalid sae_password",
+                                  line);
+                       return 1;
+               }
+#endif /* CONFIG_SAE */
        } else if (os_strcmp(buf, "vendor_elements") == 0) {
                if (parse_wpabuf_hex(line, buf, &bss->vendor_elements, pos))
                        return 1;
@@ -3477,6 +4116,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                        return 1;
        } else if (os_strcmp(buf, "sae_anti_clogging_threshold") == 0) {
                bss->sae_anti_clogging_threshold = atoi(pos);
+       } else if (os_strcmp(buf, "sae_sync") == 0) {
+               bss->sae_sync = atoi(pos);
        } else if (os_strcmp(buf, "sae_groups") == 0) {
                if (hostapd_parse_intlist(&bss->sae_groups, pos)) {
                        wpa_printf(MSG_ERROR,
@@ -3484,6 +4125,8 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                                   line, pos);
                        return 1;
                }
+       } else if (os_strcmp(buf, "sae_require_mfp") == 0) {
+               bss->sae_require_mfp = atoi(pos);
        } else if (os_strcmp(buf, "local_pwr_constraint") == 0) {
                int val = atoi(pos);
                if (val < 0 || val > 255) {
@@ -3584,6 +4227,12 @@ static int hostapd_config_fill(struct hostapd_config *conf,
                if (atoi(pos))
                        bss->radio_measurements[0] |=
                                WLAN_RRM_CAPS_NEIGHBOR_REPORT;
+       } else if (os_strcmp(buf, "rrm_beacon_report") == 0) {
+               if (atoi(pos))
+                       bss->radio_measurements[0] |=
+                               WLAN_RRM_CAPS_BEACON_REPORT_PASSIVE |
+                               WLAN_RRM_CAPS_BEACON_REPORT_ACTIVE |
+                               WLAN_RRM_CAPS_BEACON_REPORT_TABLE;
        } else if (os_strcmp(buf, "gas_address3") == 0) {
                bss->gas_address3 = atoi(pos);
        } else if (os_strcmp(buf, "stationary_ap") == 0) {
@@ -3604,9 +4253,91 @@ static int hostapd_config_fill(struct hostapd_config *conf,
        } else if (os_strcmp(buf, "fils_realm") == 0) {
                if (parse_fils_realm(bss, pos) < 0)
                        return 1;
+       } else if (os_strcmp(buf, "fils_dh_group") == 0) {
+               bss->fils_dh_group = atoi(pos);
+       } else if (os_strcmp(buf, "dhcp_server") == 0) {
+               if (hostapd_parse_ip_addr(pos, &bss->dhcp_server)) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: invalid IP address '%s'",
+                                  line, pos);
+                       return 1;
+               }
+       } else if (os_strcmp(buf, "dhcp_rapid_commit_proxy") == 0) {
+               bss->dhcp_rapid_commit_proxy = atoi(pos);
+       } else if (os_strcmp(buf, "fils_hlp_wait_time") == 0) {
+               bss->fils_hlp_wait_time = atoi(pos);
+       } else if (os_strcmp(buf, "dhcp_server_port") == 0) {
+               bss->dhcp_server_port = atoi(pos);
+       } else if (os_strcmp(buf, "dhcp_relay_port") == 0) {
+               bss->dhcp_relay_port = atoi(pos);
 #endif /* CONFIG_FILS */
        } else if (os_strcmp(buf, "multicast_to_unicast") == 0) {
                bss->multicast_to_unicast = atoi(pos);
+       } else if (os_strcmp(buf, "broadcast_deauth") == 0) {
+               bss->broadcast_deauth = atoi(pos);
+#ifdef CONFIG_DPP
+       } else if (os_strcmp(buf, "dpp_connector") == 0) {
+               os_free(bss->dpp_connector);
+               bss->dpp_connector = os_strdup(pos);
+       } else if (os_strcmp(buf, "dpp_netaccesskey") == 0) {
+               if (parse_wpabuf_hex(line, buf, &bss->dpp_netaccesskey, pos))
+                       return 1;
+       } else if (os_strcmp(buf, "dpp_netaccesskey_expiry") == 0) {
+               bss->dpp_netaccesskey_expiry = strtol(pos, NULL, 0);
+       } else if (os_strcmp(buf, "dpp_csign") == 0) {
+               if (parse_wpabuf_hex(line, buf, &bss->dpp_csign, pos))
+                       return 1;
+#endif /* CONFIG_DPP */
+#ifdef CONFIG_OWE
+       } else if (os_strcmp(buf, "owe_transition_bssid") == 0) {
+               if (hwaddr_aton(pos, bss->owe_transition_bssid)) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: invalid owe_transition_bssid",
+                                  line);
+                       return 1;
+               }
+       } else if (os_strcmp(buf, "owe_transition_ssid") == 0) {
+               size_t slen;
+               char *str = wpa_config_parse_string(pos, &slen);
+
+               if (!str || slen < 1 || slen > SSID_MAX_LEN) {
+                       wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'",
+                                  line, pos);
+                       os_free(str);
+                       return 1;
+               }
+               os_memcpy(bss->owe_transition_ssid, str, slen);
+               bss->owe_transition_ssid_len = slen;
+               os_free(str);
+       } else if (os_strcmp(buf, "owe_transition_ifname") == 0) {
+               os_strlcpy(bss->owe_transition_ifname, pos,
+                          sizeof(bss->owe_transition_ifname));
+       } else if (os_strcmp(buf, "owe_groups") == 0) {
+               if (hostapd_parse_intlist(&bss->owe_groups, pos)) {
+                       wpa_printf(MSG_ERROR,
+                                  "Line %d: Invalid owe_groups value '%s'",
+                                  line, pos);
+                       return 1;
+               }
+       } else if (os_strcmp(buf, "coloc_intf_reporting") == 0) {
+               bss->coloc_intf_reporting = atoi(pos);
+#endif /* CONFIG_OWE */
+       } else if (os_strcmp(buf, "multi_ap") == 0) {
+               int val = atoi(pos);
+
+               if (val < 0 || val > 3) {
+                       wpa_printf(MSG_ERROR, "Line %d: Invalid multi_ap '%s'",
+                                  line, buf);
+                       return -1;
+               }
+
+               bss->multi_ap = val;
+       } else if (os_strcmp(buf, "rssi_reject_assoc_rssi") == 0) {
+               conf->rssi_reject_assoc_rssi = atoi(pos);
+       } else if (os_strcmp(buf, "rssi_reject_assoc_timeout") == 0) {
+               conf->rssi_reject_assoc_timeout = atoi(pos);
+       } else if (os_strcmp(buf, "pbss") == 0) {
+               bss->pbss = atoi(pos);
        } else {
                wpa_printf(MSG_ERROR,
                           "Line %d: unknown configuration item '%s'",