Browse Source

Interworking: Support username/password based network selection

Add support for network selection for username/password credentials with
EAP-TTLS and EAP-PEAP. The new global configuration parameters
home_username, home_password, and home_ca_cert can be used to specify
credentials for network selection.
Jouni Malinen 13 years ago
parent
commit
67e1b98463
4 changed files with 237 additions and 2 deletions
  1. 27 0
      wpa_supplicant/config.c
  2. 17 0
      wpa_supplicant/config.h
  3. 8 0
      wpa_supplicant/config_file.c
  4. 185 2
      wpa_supplicant/interworking.c

+ 27 - 0
wpa_supplicant/config.c

@@ -1727,6 +1727,9 @@ void wpa_config_free(struct wpa_config *config)
 	os_free(config->p2p_ssid_postfix);
 	os_free(config->pssid);
 	os_free(config->home_realm);
+	os_free(config->home_username);
+	os_free(config->home_password);
+	os_free(config->home_ca_cert);
 	os_free(config);
 }
 
@@ -1910,6 +1913,27 @@ int wpa_config_set(struct wpa_ssid *ssid, const char *var, const char *value,
 }
 
 
+int wpa_config_set_quoted(struct wpa_ssid *ssid, const char *var,
+			  const char *value)
+{
+	size_t len;
+	char *buf;
+	int ret;
+
+	len = os_strlen(value);
+	buf = os_malloc(len + 3);
+	if (buf == NULL)
+		return -1;
+	buf[0] = '"';
+	os_memcpy(buf + 1, value, len);
+	buf[len + 1] = '"';
+	buf[len + 2] = '\0';
+	ret = wpa_config_set(ssid, var, buf, 0);
+	os_free(buf);
+	return ret;
+}
+
+
 /**
  * wpa_config_get_all - Get all options from network configuration
  * @ssid: Pointer to network configuration data
@@ -2462,6 +2486,9 @@ static const struct global_parse_data global_fields[] = {
 	{ INT(max_num_sta), 0 },
 	{ INT_RANGE(disassoc_low_ack, 0, 1), 0 },
 	{ STR(home_realm), 0 },
+	{ STR(home_username), 0 },
+	{ STR(home_password), 0 },
+	{ STR(home_ca_cert), 0 },
 	{ INT_RANGE(interworking, 0, 1), 0 },
 	{ FUNC(hessid), 0 }
 };

+ 17 - 0
wpa_supplicant/config.h

@@ -445,6 +445,21 @@ struct wpa_config {
 	 * home_realm - Home Realm for Interworking
 	 */
 	char *home_realm;
+
+	/**
+	 * home_username - Username for Interworking network selection
+	 */
+	char *home_username;
+
+	/**
+	 * home_password - Password for Interworking network selection
+	 */
+	char *home_password;
+
+	/**
+	 * home_ca_cert - CA certificate for Interworking network selection
+	 */
+	char *home_ca_cert;
 };
 
 
@@ -461,6 +476,8 @@ int wpa_config_remove_network(struct wpa_config *config, int id);
 void wpa_config_set_network_defaults(struct wpa_ssid *ssid);
 int wpa_config_set(struct wpa_ssid *ssid, const char *var, const char *value,
 		   int line);
+int wpa_config_set_quoted(struct wpa_ssid *ssid, const char *var,
+			  const char *value);
 char ** wpa_config_get_all(struct wpa_ssid *ssid, int get_keys);
 char * wpa_config_get(struct wpa_ssid *ssid, const char *var);
 char * wpa_config_get_no_key(struct wpa_ssid *ssid, const char *var);

+ 8 - 0
wpa_supplicant/config_file.c

@@ -703,12 +703,20 @@ static void wpa_config_write_global(FILE *f, struct wpa_config *config)
 		fprintf(f, "max_num_sta=%u\n", config->max_num_sta);
 	if (config->disassoc_low_ack)
 		fprintf(f, "disassoc_low_ack=%u\n", config->disassoc_low_ack);
+#ifdef CONFIG_INTERWORKING
 	if (config->home_realm)
 		fprintf(f, "home_realm=%s\n", config->home_realm);
+	if (config->home_username)
+		fprintf(f, "home_username=%s\n", config->home_username);
+	if (config->home_password)
+		fprintf(f, "home_password=%s\n", config->home_password);
+	if (config->home_ca_cert)
+		fprintf(f, "home_ca_cert=%s\n", config->home_ca_cert);
 	if (config->interworking)
 		fprintf(f, "interworking=%u\n", config->interworking);
 	if (!is_zero_ether_addr(config->hessid))
 		fprintf(f, "hessid=" MACSTR "\n", MAC2STR(config->hessid));
+#endif /* CONFIG_INTERWORKING */
 }
 
 #endif /* CONFIG_NO_CONFIG_WRITE */

+ 185 - 2
wpa_supplicant/interworking.c

@@ -20,10 +20,12 @@
 #include "common/wpa_ctrl.h"
 #include "drivers/driver.h"
 #include "eap_common/eap_defs.h"
+#include "eap_peer/eap_methods.h"
 #include "wpa_supplicant_i.h"
 #include "config.h"
 #include "bss.h"
 #include "scan.h"
+#include "notify.h"
 #include "gas_query.h"
 #include "interworking.h"
 
@@ -371,15 +373,194 @@ static int nai_realm_match(struct nai_realm *realm, const char *home_realm)
 }
 
 
+static int nai_realm_cred_username(struct nai_realm_eap *eap)
+{
+	if (eap_get_name(EAP_VENDOR_IETF, eap->method) == NULL)
+		return 0; /* method not supported */
+
+	if (eap->method != EAP_TYPE_TTLS && eap->method != EAP_TYPE_PEAP) {
+		/* Only tunneled methods with username/password supported */
+		return 0;
+	}
+
+	if (eap->method == EAP_TYPE_PEAP &&
+	    eap_get_name(EAP_VENDOR_IETF, eap->inner_method) == NULL)
+		return 0;
+
+	if (eap->method == EAP_TYPE_TTLS) {
+		if (eap->inner_method == 0 && eap->inner_non_eap == 0)
+			return 0;
+		if (eap->inner_method &&
+		    eap_get_name(EAP_VENDOR_IETF, eap->inner_method) == NULL)
+			return 0;
+		if (eap->inner_non_eap &&
+		    eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_PAP &&
+		    eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_CHAP &&
+		    eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_MSCHAP &&
+		    eap->inner_non_eap != NAI_REALM_INNER_NON_EAP_MSCHAPV2)
+			return 0;
+	}
+
+	if (eap->inner_method &&
+	    eap->inner_method != EAP_TYPE_GTC &&
+	    eap->inner_method != EAP_TYPE_MSCHAPV2)
+		return 0;
+
+	return 1;
+}
+
+
+struct nai_realm_eap * nai_realm_find_eap(struct wpa_supplicant *wpa_s,
+					  struct nai_realm *realm)
+{
+	u8 e;
+
+	if (wpa_s->conf->home_username == NULL ||
+	    wpa_s->conf->home_username[0] == '\0' ||
+	    wpa_s->conf->home_password == NULL ||
+	    wpa_s->conf->home_password[0] == '\0')
+		return NULL;
+
+	for (e = 0; e < realm->eap_count; e++) {
+		struct nai_realm_eap *eap = &realm->eap[e];
+		if (nai_realm_cred_username(eap))
+			return eap;
+	}
+
+	return NULL;
+}
+
+
 int interworking_connect(struct wpa_supplicant *wpa_s, struct wpa_bss *bss)
 {
+	struct wpa_ssid *ssid;
+	struct nai_realm *realm;
+	struct nai_realm_eap *eap = NULL;
+	u16 count, i;
+	char buf[100];
+	const u8 *ie;
+
 	if (bss == NULL)
 		return -1;
+	ie = wpa_bss_get_ie(bss, WLAN_EID_SSID);
+	if (ie == NULL || ie[1] == 0) {
+		wpa_printf(MSG_DEBUG, "Interworking: No SSID known for "
+			   MACSTR, MAC2STR(bss->bssid));
+		return -1;
+	}
+
+	realm = nai_realm_parse(bss->anqp_nai_realm, &count);
+	if (realm == NULL) {
+		wpa_printf(MSG_DEBUG, "Interworking: Could not parse NAI "
+			   "Realm list from " MACSTR, MAC2STR(bss->bssid));
+		nai_realm_free(realm, count);
+		return -1;
+	}
+
+	for (i = 0; i < count; i++) {
+		if (!nai_realm_match(&realm[i], wpa_s->conf->home_realm))
+			continue;
+		eap = nai_realm_find_eap(wpa_s, &realm[i]);
+		if (eap)
+			break;
+	}
+
+	if (!eap) {
+		wpa_printf(MSG_DEBUG, "Interworking: No matching credentials "
+			   "and EAP method found for " MACSTR,
+			   MAC2STR(bss->bssid));
+		nai_realm_free(realm, count);
+		return -1;
+	}
 
 	wpa_printf(MSG_DEBUG, "Interworking: Connect with " MACSTR,
 		   MAC2STR(bss->bssid));
-	/* TODO: create network block and connect */
+
+	ssid = wpa_config_add_network(wpa_s->conf);
+	if (ssid == NULL) {
+		nai_realm_free(realm, count);
+		return -1;
+	}
+	wpas_notify_network_added(wpa_s, ssid);
+	wpa_config_set_network_defaults(ssid);
+	ssid->temporary = 1;
+	ssid->ssid = os_zalloc(ie[1] + 1);
+	if (ssid->ssid == NULL)
+		goto fail;
+	os_memcpy(ssid->ssid, ie + 2, ie[1]);
+	ssid->ssid_len = ie[1];
+
+	if (wpa_config_set(ssid, "eap", eap_get_name(EAP_VENDOR_IETF,
+						     eap->method), 0) < 0)
+		goto fail;
+
+	if (wpa_s->conf->home_username && wpa_s->conf->home_username[0] &&
+	    wpa_config_set_quoted(ssid, "identity",
+				  wpa_s->conf->home_username) < 0)
+		goto fail;
+
+	if (wpa_s->conf->home_password && wpa_s->conf->home_password[0] &&
+	    wpa_config_set_quoted(ssid, "password", wpa_s->conf->home_password)
+	    < 0)
+		goto fail;
+
+	switch (eap->method) {
+	case EAP_TYPE_TTLS:
+		if (eap->inner_method) {
+			os_snprintf(buf, sizeof(buf), "\"autheap=%s\"",
+				    eap_get_name(EAP_VENDOR_IETF,
+						 eap->inner_method));
+			if (wpa_config_set(ssid, "phase2", buf, 0) < 0)
+				goto fail;
+			break;
+		}
+		switch (eap->inner_non_eap) {
+		case NAI_REALM_INNER_NON_EAP_PAP:
+			if (wpa_config_set(ssid, "phase2", "\"auth=PAP\"", 0) <
+			    0)
+				goto fail;
+			break;
+		case NAI_REALM_INNER_NON_EAP_CHAP:
+			if (wpa_config_set(ssid, "phase2", "\"auth=CHAP\"", 0)
+			    < 0)
+				goto fail;
+			break;
+		case NAI_REALM_INNER_NON_EAP_MSCHAP:
+			if (wpa_config_set(ssid, "phase2", "\"auth=CHAP\"", 0)
+			    < 0)
+				goto fail;
+			break;
+		case NAI_REALM_INNER_NON_EAP_MSCHAPV2:
+			if (wpa_config_set(ssid, "phase2", "\"auth=MSCHAPV2\"",
+					   0) < 0)
+				goto fail;
+			break;
+		}
+		break;
+	case EAP_TYPE_PEAP:
+		os_snprintf(buf, sizeof(buf), "\"auth=%s\"",
+			    eap_get_name(EAP_VENDOR_IETF, eap->inner_method));
+		if (wpa_config_set(ssid, "phase2", buf, 0) < 0)
+			goto fail;
+		break;
+	}
+
+	if (wpa_s->conf->home_ca_cert && wpa_s->conf->home_ca_cert[0] &&
+	    wpa_config_set_quoted(ssid, "ca_cert", wpa_s->conf->home_ca_cert) <
+	    0)
+		goto fail;
+
+	nai_realm_free(realm, count);
+
+	wpa_supplicant_select_network(wpa_s, ssid);
+
 	return 0;
+
+fail:
+	wpas_notify_network_removed(wpa_s, ssid);
+	wpa_config_remove_network(wpa_s->conf, ssid->id);
+	nai_realm_free(realm, count);
+	return -1;
 }
 
 
@@ -406,7 +587,9 @@ static int interworking_credentials_available(struct wpa_supplicant *wpa_s,
 	}
 
 	for (i = 0; i < count; i++) {
-		if (nai_realm_match(&realm[i], wpa_s->conf->home_realm)) {
+		if (!nai_realm_match(&realm[i], wpa_s->conf->home_realm))
+			continue;
+		if (nai_realm_find_eap(wpa_s, &realm[i])) {
 			found++;
 			break;
 		}