[wpa_supplicant] Cumulative patch from c4e90da6d

Bug: 124017368
Test: Device boots up and connects to WPA3/OWE wifi networks, run traffic.
Test: Able to turn on/off softap, associate wifi STA, run traffic.
Test: DPP functional test.
Test: Regression test passed (Bug: 124052942)

c4e90da6d MBO: Move the WNM-Notification subtype definitions to common location
105b14f54 HS 2.0: Update the T&C Acceptance subtype value
65b487ae5 HS 2.0: Add QUIET=1 support for building hs20-osu-client
73f285dad Add FT-PSK to GET_CAPABILITY key_mgmt
6110753b1 nl80211: Clear PMKID add command message buffer
0fa33e05b nl80211: Clear connect command message buffer
b14e8ea1d nl80211: Request kernel to trim off payload of netlink requests from acks
789b48bb4 EAP peer: Clear temporary message buffers before freeing
8f99a3c26 Clear config item writing buffer before freeing it
a68e9b698 D-Bus: Fix P2P DeleteService dict iteration
0607346f1 D-Bus: Fix a memory leak in DeleteService handler
d05dda61d PEAP: Explicitly clear temporary keys from memory when using CMK
4e1cd3468 EAP-PEAP: Derive EMSK and use 128-octet derivation for MSK
d8c20ec59 DPP: Clear dpp_listen_freq on remain-on-channel failure
59fa20538 P2P: Allow the avoid channels for P2P discovery/negotiation
e34cd9f06 WNM: Fix WNM-Sleep Mode Request bounds checking
159a7fbde crl_reload_interval: Add CRL reloading support
83c860813 AP: Add wpa_psk_file reloading in runtime
ec5c39a55 AP: Allow identifying which passphrase station used with wpa_psk_file
b08c9ad0c AP: Expose PMK outside of wpa_auth module
89896c000 tests: Use python3 compatible print statement
bab493b90 tests: Use python3 compatible "except" statement
0dab47733 Write multi_ap_backhaul_sta to wpa_supplicant config
98251c6f2 dbus: Document more possible BSS/RSA/KeyMgmt values
1e591df06 Check supported types in wpas_mac_addr_rand_scan_set()
c85249aa1 Fix test compilation error related to sme_event_unprot_disconnect()
42d308635 SAE: Advertise Password Identifier use
59c693064 HS 2.0 server: Command line option to fetch the version information
2d1762fa4 HS 2.0 server: Alternative subrem updateNode for certificate credentials
d97cf2a11 HS 2.0 server: Use noMOUpdate in client certificate subrem
13a200a92 FILS: Remove notes about experimental implementation
86d4e0537 dbus: Expose support of SAE key management in BSS properties

Change-Id: I83ffca34ff5349c226db6215ff1ae35c3b7ab335
diff --git a/hs20/server/spp_server.c b/hs20/server/spp_server.c
index e5af4c2..4bef0ff 100644
--- a/hs20/server/spp_server.c
+++ b/hs20/server/spp_server.c
@@ -41,6 +41,8 @@
 	POLICY_REMEDIATION,
 	POLICY_UPDATE,
 	FREE_REMEDIATION,
+	CLEAR_REMEDIATION,
+	CERT_REENROLL,
 };
 
 
@@ -51,6 +53,11 @@
 				    const char *field);
 static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user,
 				 const char *realm, int use_dmacc);
+static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx,
+					     const char *session_id,
+					     const char *user,
+					     const char *realm,
+					     int add_est_user);
 
 
 static int db_add_session(struct hs20_svc *ctx,
@@ -204,6 +211,61 @@
 }
 
 
+static void db_add_session_dmacc(struct hs20_svc *ctx, const char *sessionid,
+				 const char *username, const char *password)
+{
+	char *sql;
+
+	sql = sqlite3_mprintf("UPDATE sessions SET osu_user=%Q, osu_password=%Q WHERE id=%Q",
+			      username, password, sessionid);
+	if (!sql)
+		return;
+	debug_print(ctx, 1, "DB: %s", sql);
+	if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
+		debug_print(ctx, 1, "Failed to add session DMAcc: %s",
+			    sqlite3_errmsg(ctx->db));
+	}
+	sqlite3_free(sql);
+}
+
+
+static void db_add_session_eap_method(struct hs20_svc *ctx,
+				      const char *sessionid,
+				      const char *method)
+{
+	char *sql;
+
+	sql = sqlite3_mprintf("UPDATE sessions SET eap_method=%Q WHERE id=%Q",
+			      method, sessionid);
+	if (!sql)
+		return;
+	debug_print(ctx, 1, "DB: %s", sql);
+	if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
+		debug_print(ctx, 1, "Failed to add session EAP method: %s",
+			    sqlite3_errmsg(ctx->db));
+	}
+	sqlite3_free(sql);
+}
+
+
+static void db_add_session_id_hash(struct hs20_svc *ctx, const char *sessionid,
+				   const char *id_hash)
+{
+	char *sql;
+
+	sql = sqlite3_mprintf("UPDATE sessions SET mobile_identifier_hash=%Q WHERE id=%Q",
+			      id_hash, sessionid);
+	if (!sql)
+		return;
+	debug_print(ctx, 1, "DB: %s", sql);
+	if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) != SQLITE_OK) {
+		debug_print(ctx, 1, "Failed to add session ID hash: %s",
+			    sqlite3_errmsg(ctx->db));
+	}
+	sqlite3_free(sql);
+}
+
+
 static void db_remove_session(struct hs20_svc *ctx,
 			      const char *user, const char *realm,
 			      const char *sessionid)
@@ -290,8 +352,7 @@
 	char *sql;
 	if (user == NULL || realm == NULL || name == NULL)
 		return;
-	sql = sqlite3_mprintf("UPDATE users SET %s=%Q "
-		 "WHERE identity=%Q AND realm=%Q AND phase2=1",
+	sql = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE identity=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')",
 			      name, str, user, realm);
 	if (sql == NULL)
 		return;
@@ -413,8 +474,7 @@
 	char *cmd;
 	struct get_db_field_data data;
 
-	cmd = sqlite3_mprintf("SELECT %s FROM users WHERE "
-			      "%s=%Q AND realm=%Q AND phase2=1",
+	cmd = sqlite3_mprintf("SELECT %s FROM users WHERE %s=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')",
 			      field, dmacc ? "osu_user" : "identity",
 			      user, realm);
 	if (cmd == NULL)
@@ -443,8 +503,7 @@
 	char *cmd;
 	int ret;
 
-	cmd = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE "
-			      "%s=%Q AND realm=%Q AND phase2=1",
+	cmd = sqlite3_mprintf("UPDATE users SET %s=%Q WHERE %s=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')",
 			      field, val, dmacc ? "osu_user" : "identity", user,
 			      realm);
 	if (cmd == NULL)
@@ -524,6 +583,27 @@
 }
 
 
+static int clear_remediation(struct hs20_svc *ctx, const char *user,
+			     const char *realm, int dmacc)
+{
+	char *cmd;
+
+	cmd = sqlite3_mprintf("UPDATE users SET remediation='' WHERE %s=%Q",
+			      dmacc ? "osu_user" : "identity",
+			      user);
+	if (cmd == NULL)
+		return -1;
+	debug_print(ctx, 1, "DB: %s", cmd);
+	if (sqlite3_exec(ctx->db, cmd, NULL, NULL, NULL) != SQLITE_OK) {
+		debug_print(ctx, 1, "Failed to update database for user '%s'",
+			    user);
+	}
+	sqlite3_free(cmd);
+
+	return 0;
+}
+
+
 static int add_eap_ttls(struct hs20_svc *ctx, xml_node_t *parent)
 {
 	xml_node_t *node;
@@ -545,6 +625,7 @@
 {
 	xml_node_t *node;
 	char *b64;
+	size_t len;
 
 	node = xml_node_create(ctx->xml, parent, NULL, "UsernamePassword");
 	if (node == NULL)
@@ -555,6 +636,9 @@
 	b64 = (char *) base64_encode((unsigned char *) pw, strlen(pw), NULL);
 	if (b64 == NULL)
 		return NULL;
+	len = os_strlen(b64);
+	if (len > 0 && b64[len - 1] == '\n')
+		b64[len - 1] = '\0';
 	add_text_node(ctx, node, "Password", b64);
 	free(b64);
 
@@ -706,6 +790,45 @@
 }
 
 
+static xml_node_t * read_subrem_file(struct hs20_svc *ctx,
+				     const char *subrem_id,
+				     char *uri, size_t uri_size)
+{
+	char fname[200];
+	char *buf, *buf2, *pos;
+	size_t len;
+	xml_node_t *node;
+
+	os_snprintf(fname, sizeof(fname), "%s/spp/subrem/%s",
+		    ctx->root_dir, subrem_id);
+	debug_print(ctx, 1, "Use subrem file %s", fname);
+
+	buf = os_readfile(fname, &len);
+	if (!buf)
+		return NULL;
+	buf2 = os_realloc(buf, len + 1);
+	if (!buf2) {
+		os_free(buf);
+		return NULL;
+	}
+	buf = buf2;
+	buf[len] = '\0';
+
+	pos = os_strchr(buf, '\n');
+	if (!pos) {
+		os_free(buf);
+		return NULL;
+	}
+	*pos++ = '\0';
+	os_strlcpy(uri, buf, uri_size);
+
+	node = xml_node_from_buf(ctx->xml, pos);
+	os_free(buf);
+
+	return node;
+}
+
+
 static xml_node_t * build_sub_rem_resp(struct hs20_svc *ctx,
 				       const char *user, const char *realm,
 				       const char *session_id,
@@ -715,28 +838,48 @@
 	xml_node_t *spp_node, *cred;
 	char buf[400];
 	char new_pw[33];
-	char *real_user = NULL;
 	char *status;
 	char *cert;
 
-	if (dmacc) {
-		real_user = db_get_val(ctx, user, realm, "identity", dmacc);
-		if (real_user == NULL) {
-			debug_print(ctx, 1, "Could not find user identity for "
-				    "dmacc user '%s'", user);
-			return NULL;
-		}
-	}
-
 	cert = db_get_val(ctx, user, realm, "cert", dmacc);
-	if (cert && cert[0] == '\0')
+	if (cert && cert[0] == '\0') {
+		os_free(cert);
 		cert = NULL;
+	}
 	if (cert) {
-		cred = build_credential_cert(ctx, real_user ? real_user : user,
-					     realm, cert);
+		char *subrem;
+
+		/* No change needed in PPS MO unless specifically asked to */
+		cred = NULL;
+		buf[0] = '\0';
+
+		subrem = db_get_val(ctx, user, realm, "subrem", dmacc);
+		if (subrem && subrem[0]) {
+			cred = read_subrem_file(ctx, subrem, buf, sizeof(buf));
+			if (!cred) {
+				debug_print(ctx, 1,
+					    "Could not create updateNode from subrem file");
+				os_free(subrem);
+				os_free(cert);
+				return NULL;
+			}
+		}
+		os_free(subrem);
 	} else {
+		char *real_user = NULL;
 		char *pw;
 
+		if (dmacc) {
+			real_user = db_get_val(ctx, user, realm, "identity",
+					       dmacc);
+			if (!real_user) {
+				debug_print(ctx, 1,
+					    "Could not find user identity for dmacc user '%s'",
+					    user);
+				return NULL;
+			}
+		}
+
 		pw = db_get_session_val(ctx, user, realm, session_id,
 					"password");
 		if (pw && pw[0]) {
@@ -752,11 +895,17 @@
 						real_user ? real_user : user,
 						realm, new_pw, sizeof(new_pw));
 		}
-	}
-	free(real_user);
-	if (!cred) {
-		debug_print(ctx, 1, "Could not build credential");
-		return NULL;
+
+		free(real_user);
+		if (!cred) {
+			debug_print(ctx, 1, "Could not build credential");
+			os_free(cert);
+			return NULL;
+		}
+
+		snprintf(buf, sizeof(buf),
+			 "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential",
+			 realm);
 	}
 
 	status = "Remediation complete, request sppUpdateResponse";
@@ -764,16 +913,15 @@
 						NULL);
 	if (spp_node == NULL) {
 		debug_print(ctx, 1, "Could not build sppPostDevDataResponse");
+		os_free(cert);
 		return NULL;
 	}
 
-	snprintf(buf, sizeof(buf),
-		 "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential",
-		 realm);
-
-	if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) {
+	if ((cred && add_update_node(ctx, spp_node, ns, buf, cred) < 0) ||
+	    (!cred && !xml_node_create(ctx->xml, spp_node, ns, "noMOUpdate"))) {
 		debug_print(ctx, 1, "Could not add update node");
 		xml_node_free(ctx->xml, spp_node);
+		os_free(cert);
 		return NULL;
 	}
 
@@ -783,14 +931,16 @@
 	xml_node_free(ctx->xml, cred);
 
 	if (cert) {
-		debug_print(ctx, 1, "Certificate credential - no need for DB "
-			    "password update on success notification");
+		debug_print(ctx, 1, "Request DB remediation clearing on success notification (certificate credential)");
+		db_add_session(ctx, user, realm, session_id, NULL, NULL,
+			       CLEAR_REMEDIATION, NULL);
 	} else {
 		debug_print(ctx, 1, "Request DB password update on success "
 			    "notification");
 		db_add_session(ctx, user, realm, session_id, new_pw, NULL,
 			       UPDATE_PASSWORD, NULL);
 	}
+	os_free(cert);
 
 	return spp_node;
 }
@@ -805,6 +955,17 @@
 }
 
 
+static xml_node_t * cert_reenroll(struct hs20_svc *ctx,
+				  const char *user,
+				  const char *realm,
+				  const char *session_id)
+{
+	db_add_session(ctx, user, realm, session_id, NULL, NULL,
+		       CERT_REENROLL, NULL);
+	return spp_exec_get_certificate(ctx, session_id, user, realm, 0);
+}
+
+
 static xml_node_t * policy_remediation(struct hs20_svc *ctx,
 				       const char *user, const char *realm,
 				       const char *session_id, int dmacc)
@@ -989,6 +1150,8 @@
 		ret = policy_remediation(ctx, user, realm, session_id, dmacc);
 	else if (type && strcmp(type, "machine") == 0)
 		ret = machine_remediation(ctx, user, realm, session_id, dmacc);
+	else if (type && strcmp(type, "reenroll") == 0)
+		ret = cert_reenroll(ctx, user, realm, session_id);
 	else
 		ret = no_sub_rem(ctx, user, realm, session_id);
 	free(type);
@@ -997,11 +1160,41 @@
 }
 
 
+static xml_node_t * read_policy_file(struct hs20_svc *ctx,
+				     const char *policy_id)
+{
+	char fname[200];
+
+	snprintf(fname, sizeof(fname), "%s/spp/policy/%s.xml",
+		 ctx->root_dir, policy_id);
+	debug_print(ctx, 1, "Use policy file %s", fname);
+
+	return node_from_file(ctx->xml, fname);
+}
+
+
+static void update_policy_update_uri(struct hs20_svc *ctx, const char *realm,
+				     xml_node_t *policy)
+{
+	xml_node_t *node;
+	char *url;
+
+	node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate/URI");
+	if (!node)
+		return;
+
+	url = db_get_osu_config_val(ctx, realm, "policy_url");
+	if (!url)
+		return;
+	xml_node_set_text(ctx->xml, node, url);
+	free(url);
+}
+
+
 static xml_node_t * build_policy(struct hs20_svc *ctx, const char *user,
 				 const char *realm, int use_dmacc)
 {
 	char *policy_id;
-	char fname[200];
 	xml_node_t *policy, *node;
 
 	policy_id = db_get_val(ctx, user, realm, "policy", use_dmacc);
@@ -1011,27 +1204,12 @@
 		if (policy_id == NULL)
 			return NULL;
 	}
-
-	snprintf(fname, sizeof(fname), "%s/spp/policy/%s.xml",
-		 ctx->root_dir, policy_id);
+	policy = read_policy_file(ctx, policy_id);
 	free(policy_id);
-	debug_print(ctx, 1, "Use policy file %s", fname);
-
-	policy = node_from_file(ctx->xml, fname);
 	if (policy == NULL)
 		return NULL;
 
-	node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate/URI");
-	if (node) {
-		char *url;
-		url = db_get_osu_config_val(ctx, realm, "policy_url");
-		if (url == NULL) {
-			xml_node_free(ctx->xml, policy);
-			return NULL;
-		}
-		xml_node_set_text(ctx->xml, node, url);
-		free(url);
-	}
+	update_policy_update_uri(ctx, realm, policy);
 
 	node = get_node_uri(ctx->xml, policy, "Policy/PolicyUpdate");
 	if (node && use_dmacc) {
@@ -1264,15 +1442,20 @@
 static xml_node_t * build_pps(struct hs20_svc *ctx,
 			      const char *user, const char *realm,
 			      const char *pw, const char *cert,
-			      int machine_managed, const char *test)
+			      int machine_managed, const char *test,
+			      const char *imsi, const char *dmacc_username,
+			      const char *dmacc_password,
+			      xml_node_t *policy_node)
 {
 	xml_node_t *pps, *c, *trust, *aaa, *aaa1, *upd, *homesp, *p;
 	xml_node_t *cred, *eap, *userpw;
 
 	pps = xml_node_create_root(ctx->xml, NULL, NULL, NULL,
 				   "PerProviderSubscription");
-	if (pps == NULL)
+	if (!pps) {
+		xml_node_free(ctx->xml, policy_node);
 		return NULL;
+	}
 
 	add_text_node(ctx, pps, "UpdateIdentifier", "1");
 
@@ -1280,6 +1463,8 @@
 
 	add_text_node(ctx, c, "CredentialPriority", "1");
 
+	if (imsi)
+		goto skip_aaa_trust_root;
 	aaa = xml_node_create(ctx->xml, c, NULL, "AAAServerTrustRoot");
 	aaa1 = xml_node_create(ctx->xml, aaa, NULL, "AAA1");
 	add_text_node_conf(ctx, realm, aaa1, "CertURL",
@@ -1311,6 +1496,7 @@
 					   "CertSHA256Fingerprint",
 					   "policy_trust_root_cert_fingerprint");
 	}
+skip_aaa_trust_root:
 
 	upd = xml_node_create(ctx->xml, c, NULL, "SubscriptionUpdate");
 	add_text_node(ctx, upd, "UpdateInterval", "4294967295");
@@ -1330,6 +1516,17 @@
 				   "trust_root_cert_fingerprint");
 	}
 
+	if (dmacc_username &&
+	    !build_username_password(ctx, upd, dmacc_username,
+				     dmacc_password)) {
+		xml_node_free(ctx->xml, pps);
+		xml_node_free(ctx->xml, policy_node);
+		return NULL;
+	}
+
+	if (policy_node)
+		xml_node_add_child(ctx->xml, c, policy_node);
+
 	homesp = xml_node_create(ctx->xml, c, NULL, "HomeSP");
 	add_text_node_conf(ctx, realm, homesp, "FriendlyName", "friendly_name");
 	add_text_node_conf(ctx, realm, homesp, "FQDN", "fqdn");
@@ -1338,7 +1535,19 @@
 
 	cred = xml_node_create(ctx->xml, c, NULL, "Credential");
 	add_creation_date(ctx, cred);
-	if (cert) {
+	if (imsi) {
+		xml_node_t *sim;
+		const char *type = "18"; /* default to EAP-SIM */
+
+		sim = xml_node_create(ctx->xml, cred, NULL, "SIM");
+		add_text_node(ctx, sim, "IMSI", imsi);
+		if (ctx->eap_method && os_strcmp(ctx->eap_method, "AKA") == 0)
+			type = "23";
+		else if (ctx->eap_method &&
+			 os_strcmp(ctx->eap_method, "AKA'") == 0)
+			type = "50";
+		add_text_node(ctx, sim, "EAPType", type);
+	} else if (cert) {
 		xml_node_t *dc;
 		dc = xml_node_create(ctx->xml, cred, NULL,
 				     "DigitalCertificate");
@@ -1361,7 +1570,8 @@
 static xml_node_t * spp_exec_get_certificate(struct hs20_svc *ctx,
 					     const char *session_id,
 					     const char *user,
-					     const char *realm)
+					     const char *realm,
+					     int add_est_user)
 {
 	xml_namespace_t *ns;
 	xml_node_t *spp_node, *enroll, *exec_node;
@@ -1369,7 +1579,7 @@
 	char password[11];
 	char *b64;
 
-	if (new_password(password, sizeof(password)) < 0)
+	if (add_est_user && new_password(password, sizeof(password)) < 0)
 		return NULL;
 
 	spp_node = build_post_dev_data_response(ctx, &ns, session_id, "OK",
@@ -1386,6 +1596,10 @@
 	xml_node_create_text(ctx->xml, enroll, ns, "enrollmentServerURI",
 			     val ? val : "");
 	os_free(val);
+
+	if (!add_est_user)
+		return spp_node;
+
 	xml_node_create_text(ctx->xml, enroll, ns, "estUserID", user);
 
 	b64 = (char *) base64_encode((unsigned char *) password,
@@ -1445,7 +1659,7 @@
 		xml_node_t *ret;
 		hs20_eventlog(ctx, user, realm, session_id,
 			      "request client certificate enrollment", NULL);
-		ret = spp_exec_get_certificate(ctx, session_id, user, realm);
+		ret = spp_exec_get_certificate(ctx, session_id, user, realm, 1);
 		free(user);
 		free(realm);
 		free(pw);
@@ -1477,7 +1691,7 @@
 			    test);
 	pps = build_pps(ctx, user, realm, pw,
 			fingerprint ? fingerprint : NULL, machine_managed,
-			test);
+			test, NULL, NULL, NULL, NULL);
 	free(fingerprint);
 	free(test);
 	if (!pps) {
@@ -1618,6 +1832,72 @@
 }
 
 
+static xml_node_t * hs20_cert_reenroll_complete(struct hs20_svc *ctx,
+						 const char *session_id)
+{
+	char *user, *realm, *cert;
+	char *status;
+	xml_namespace_t *ns;
+	xml_node_t *spp_node, *cred;
+	char buf[400];
+
+	user = db_get_session_val(ctx, NULL, NULL, session_id, "user");
+	realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm");
+	cert = db_get_session_val(ctx, NULL, NULL, session_id, "cert");
+	if (!user || !realm || !cert) {
+		debug_print(ctx, 1,
+			    "Could not find session info from DB for certificate reenrollment");
+		free(user);
+		free(realm);
+		free(cert);
+		return NULL;
+	}
+
+	cred = build_credential_cert(ctx, user, realm, cert);
+	if (!cred) {
+		debug_print(ctx, 1, "Could not build credential");
+		free(user);
+		free(realm);
+		free(cert);
+		return NULL;
+	}
+
+	status = "Remediation complete, request sppUpdateResponse";
+	spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
+						NULL);
+	if (spp_node == NULL) {
+		debug_print(ctx, 1, "Could not build sppPostDevDataResponse");
+		free(user);
+		free(realm);
+		free(cert);
+		xml_node_free(ctx->xml, cred);
+		return NULL;
+	}
+
+	snprintf(buf, sizeof(buf),
+		 "./Wi-Fi/%s/PerProviderSubscription/Cred01/Credential",
+		 realm);
+
+	if (add_update_node(ctx, spp_node, ns, buf, cred) < 0) {
+		debug_print(ctx, 1, "Could not add update node");
+		xml_node_free(ctx->xml, spp_node);
+		free(user);
+		free(realm);
+		free(cert);
+		return NULL;
+	}
+
+	hs20_eventlog_node(ctx, user, realm, session_id,
+			   "certificate reenrollment", cred);
+	xml_node_free(ctx->xml, cred);
+
+	free(user);
+	free(realm);
+	free(cert);
+	return spp_node;
+}
+
+
 static xml_node_t * hs20_cert_enroll_completed(struct hs20_svc *ctx,
 					       const char *user,
 					       const char *realm, int dmacc,
@@ -1626,7 +1906,7 @@
 	char *val;
 	enum hs20_session_operation oper;
 
-	val = db_get_session_val(ctx, user, realm, session_id, "operation");
+	val = db_get_session_val(ctx, NULL, NULL, session_id, "operation");
 	if (val == NULL) {
 		debug_print(ctx, 1, "No session %s found to continue",
 			    session_id);
@@ -1637,6 +1917,8 @@
 
 	if (oper == SUBSCRIPTION_REGISTRATION)
 		return hs20_user_input_registration(ctx, session_id, 1);
+	if (oper == CERT_REENROLL)
+		return hs20_cert_reenroll_complete(ctx, session_id);
 
 	debug_print(ctx, 1, "User session %s not in state for certificate "
 		    "enrollment completion", session_id);
@@ -1684,6 +1966,103 @@
 }
 
 
+static xml_node_t * hs20_sim_provisioning(struct hs20_svc *ctx,
+					  const char *user,
+					  const char *realm, int dmacc,
+					  const char *session_id)
+{
+	xml_namespace_t *ns;
+	xml_node_t *spp_node, *node = NULL;
+	xml_node_t *pps, *tnds;
+	char buf[400];
+	char *str;
+	const char *status;
+	char dmacc_username[32];
+	char dmacc_password[32];
+	char *policy;
+	xml_node_t *policy_node = NULL;
+
+	if (!ctx->imsi) {
+		debug_print(ctx, 1, "IMSI not available for SIM provisioning");
+		return NULL;
+	}
+
+	if (new_password(dmacc_username, sizeof(dmacc_username)) < 0 ||
+	    new_password(dmacc_password, sizeof(dmacc_password)) < 0) {
+		debug_print(ctx, 1,
+			    "Failed to generate DMAcc username/password");
+		return NULL;
+	}
+
+	status = "Provisioning complete, request sppUpdateResponse";
+	spp_node = build_post_dev_data_response(ctx, &ns, session_id, status,
+						NULL);
+	if (!spp_node)
+		return NULL;
+
+	policy = db_get_osu_config_val(ctx, realm, "sim_policy");
+	if (policy) {
+		policy_node = read_policy_file(ctx, policy);
+		os_free(policy);
+		if (!policy_node) {
+			xml_node_free(ctx->xml, spp_node);
+			return NULL;
+		}
+		update_policy_update_uri(ctx, realm, policy_node);
+		node = get_node_uri(ctx->xml, policy_node,
+				    "Policy/PolicyUpdate");
+		if (node)
+			build_username_password(ctx, node, dmacc_username,
+						dmacc_password);
+	}
+
+	pps = build_pps(ctx, NULL, realm, NULL, NULL, 0, NULL, ctx->imsi,
+			dmacc_username, dmacc_password, policy_node);
+	if (!pps) {
+		xml_node_free(ctx->xml, spp_node);
+		return NULL;
+	}
+
+	debug_print(ctx, 1,
+		    "Request DB subscription registration on success notification");
+	if (!user || !user[0])
+		user = ctx->imsi;
+	db_add_session(ctx, user, realm, session_id, NULL, NULL,
+		       SUBSCRIPTION_REGISTRATION, NULL);
+	db_add_session_dmacc(ctx, session_id, dmacc_username, dmacc_password);
+	if (ctx->eap_method)
+		db_add_session_eap_method(ctx, session_id, ctx->eap_method);
+	if (ctx->id_hash)
+		db_add_session_id_hash(ctx, session_id, ctx->id_hash);
+	db_add_session_pps(ctx, user, realm, session_id, pps);
+
+	hs20_eventlog_node(ctx, user, realm, session_id,
+			   "new subscription", pps);
+
+	tnds = mo_to_tnds(ctx->xml, pps, 0, URN_HS20_PPS, NULL);
+	xml_node_free(ctx->xml, pps);
+	if (!tnds) {
+		xml_node_free(ctx->xml, spp_node);
+		return NULL;
+	}
+
+	str = xml_node_to_str(ctx->xml, tnds);
+	xml_node_free(ctx->xml, tnds);
+	if (!str) {
+		xml_node_free(ctx->xml, spp_node);
+		return NULL;
+	}
+
+	node = xml_node_create_text(ctx->xml, spp_node, ns, "addMO", str);
+	free(str);
+	snprintf(buf, sizeof(buf), "./Wi-Fi/%s/PerProviderSubscription", realm);
+	xml_node_add_attr(ctx->xml, node, ns, "managementTreeURI", buf);
+	xml_node_add_attr(ctx->xml, node, ns, "moURN", URN_HS20_PPS);
+
+	return spp_node;
+}
+
+
 static xml_node_t * hs20_spp_post_dev_data(struct hs20_svc *ctx,
 					   xml_node_t *node,
 					   const char *user,
@@ -1964,6 +2343,15 @@
 		goto out;
 	}
 
+	if (strcasecmp(req_reason, "Subscription provisioning") == 0) {
+		ret = hs20_sim_provisioning(ctx, user, realm, dmacc,
+					    session_id);
+		hs20_eventlog_node(ctx, user, realm, session_id,
+				   "subscription provisioning response",
+				   ret);
+		goto out;
+	}
+
 	debug_print(ctx, 1, "Unsupported requestReason '%s' user '%s'",
 		    req_reason, user);
 out:
@@ -2006,6 +2394,8 @@
 static int add_subscription(struct hs20_svc *ctx, const char *session_id)
 {
 	char *user, *realm, *pw, *pw_mm, *pps, *str;
+	char *osu_user, *osu_password, *eap_method;
+	char *policy = NULL;
 	char *sql;
 	int ret = -1;
 	char *free_account;
@@ -2013,6 +2403,7 @@
 	char *type;
 	int cert = 0;
 	char *cert_pem, *fingerprint;
+	const char *method;
 
 	user = db_get_session_val(ctx, NULL, NULL, session_id, "user");
 	realm = db_get_session_val(ctx, NULL, NULL, session_id, "realm");
@@ -2026,6 +2417,11 @@
 	if (type && strcmp(type, "cert") == 0)
 		cert = 1;
 	free(type);
+	osu_user = db_get_session_val(ctx, NULL, NULL, session_id, "osu_user");
+	osu_password = db_get_session_val(ctx, NULL, NULL, session_id,
+					  "osu_password");
+	eap_method = db_get_session_val(ctx, NULL, NULL, session_id,
+					"eap_method");
 
 	if (!user || !realm || !pw) {
 		debug_print(ctx, 1, "Could not find session info from DB for "
@@ -2037,6 +2433,8 @@
 	free_acc = free_account && strcmp(free_account, user) == 0;
 	free(free_account);
 
+	policy = db_get_osu_config_val(ctx, realm, "sim_policy");
+
 	debug_print(ctx, 1,
 		    "New subscription: user='%s' realm='%s' free_acc=%d",
 		    user, realm, free_acc);
@@ -2065,12 +2463,20 @@
 
 	str = db_get_session_val(ctx, NULL, NULL, session_id, "mac_addr");
 
-	sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,methods,cert,cert_pem,machine_managed,mac_addr) VALUES (%Q,%Q,1,%Q,%Q,%Q,%d,%Q)",
-			      user, realm, cert ? "TLS" : "TTLS-MSCHAPV2",
+	if (eap_method && eap_method[0])
+		method = eap_method;
+	else
+		method = cert ? "TLS" : "TTLS-MSCHAPV2";
+	sql = sqlite3_mprintf("INSERT INTO users(identity,realm,phase2,methods,cert,cert_pem,machine_managed,mac_addr,osu_user,osu_password,policy) VALUES (%Q,%Q,%d,%Q,%Q,%Q,%d,%Q,%Q,%Q,%Q)",
+			      user, realm, cert ? 0 : 1,
+			      method,
 			      fingerprint ? fingerprint : "",
 			      cert_pem ? cert_pem : "",
 			      pw_mm && atoi(pw_mm) ? 1 : 0,
-			      str ? str : "");
+			      str ? str : "",
+			      osu_user ? osu_user : "",
+			      osu_password ? osu_password : "",
+			      policy ? policy : "");
 	free(str);
 	if (sql == NULL)
 		goto out;
@@ -2088,8 +2494,7 @@
 	else
 		ret = update_password(ctx, user, realm, pw, 0);
 	if (ret < 0) {
-		sql = sqlite3_mprintf("DELETE FROM users WHERE identity=%Q AND "
-				      "realm=%Q AND phase2=1",
+		sql = sqlite3_mprintf("DELETE FROM users WHERE identity=%Q AND realm=%Q AND (phase2=1 OR methods='TLS')",
 				      user, realm);
 		if (sql) {
 			debug_print(ctx, 1, "DB: %s", sql);
@@ -2139,6 +2544,24 @@
 		}
 	}
 
+	str = db_get_session_val(ctx, NULL, NULL, session_id,
+				 "mobile_identifier_hash");
+	if (str) {
+		sql = sqlite3_mprintf("DELETE FROM sim_provisioning WHERE mobile_identifier_hash=%Q",
+				      str);
+		if (sql) {
+			debug_print(ctx, 1, "DB: %s", sql);
+			if (sqlite3_exec(ctx->db, sql, NULL, NULL, NULL) !=
+			    SQLITE_OK) {
+				debug_print(ctx, 1,
+					    "Failed to delete pending sim_provisioning entry: %s",
+					    sqlite3_errmsg(ctx->db));
+			}
+			sqlite3_free(sql);
+		}
+		os_free(str);
+	}
+
 	if (ret == 0) {
 		hs20_eventlog(ctx, user, realm, session_id,
 			      "completed subscription registration", NULL);
@@ -2152,6 +2575,10 @@
 	free(pps);
 	free(cert_pem);
 	free(fingerprint);
+	free(osu_user);
+	free(osu_password);
+	free(eap_method);
+	os_free(policy);
 	return ret;
 }
 
@@ -2178,11 +2605,11 @@
 	debug_print(ctx, 1, "sppUpdateResponse: sppStatus: %s  sessionID: %s",
 		    status, session_id);
 
-	val = db_get_session_val(ctx, user, realm, session_id, "operation");
+	val = db_get_session_val(ctx, NULL, NULL, session_id, "operation");
 	if (!val) {
 		debug_print(ctx, 1,
-			    "No session active for user: %s  sessionID: %s",
-			    user, session_id);
+			    "No session active for sessionID: %s",
+			    session_id);
 		oper = NO_OPERATION;
 	} else
 		oper = atoi(val);
@@ -2239,6 +2666,29 @@
 				      session_id, "Updated user password "
 				      "in database", NULL);
 		}
+		if (oper == CLEAR_REMEDIATION) {
+			debug_print(ctx, 1,
+				    "Clear remediation requirement for user '%s' in DB",
+				    user);
+			if (clear_remediation(ctx, user, realm, dmacc) < 0) {
+				debug_print(ctx, 1,
+					    "Failed to clear remediation requirement for user '%s' in DB",
+					    user);
+				ret = build_spp_exchange_complete(
+					ctx, session_id, "Error occurred",
+					"Other");
+				hs20_eventlog_node(ctx, user, realm,
+						   session_id,
+						   "Failed to update database",
+						   ret);
+				db_remove_session(ctx, user, realm, session_id);
+				return ret;
+			}
+			hs20_eventlog(ctx, user, realm,
+				      session_id,
+				      "Cleared remediation requirement in database",
+				      NULL);
+		}
 		if (oper == SUBSCRIPTION_REGISTRATION) {
 			if (add_subscription(ctx, session_id) < 0) {
 				debug_print(ctx, 1, "Failed to add "
@@ -2265,12 +2715,60 @@
 		if (oper == POLICY_UPDATE)
 			db_update_val(ctx, user, realm, "polupd_done", "1",
 				      dmacc);
+		if (oper == CERT_REENROLL) {
+			char *new_user;
+			char event[200];
+
+			new_user = db_get_session_val(ctx, NULL, NULL,
+						      session_id, "user");
+			if (!new_user) {
+				debug_print(ctx, 1,
+					    "Failed to find new user name (cert-serialnum)");
+				ret = build_spp_exchange_complete(
+					ctx, session_id, "Error occurred",
+					"Other");
+				hs20_eventlog_node(ctx, user, realm,
+						   session_id,
+						   "Failed to find new user name (cert reenroll)",
+						   ret);
+				db_remove_session(ctx, NULL, NULL, session_id);
+				return ret;
+			}
+
+			debug_print(ctx, 1,
+				    "Update certificate user entry to use the new serial number (old=%s new=%s)",
+				    user, new_user);
+			os_snprintf(event, sizeof(event), "renamed user to: %s",
+				    new_user);
+			hs20_eventlog(ctx, user, realm, session_id, event,
+				      NULL);
+
+			if (db_update_val(ctx, user, realm, "identity",
+					  new_user, 0) < 0 ||
+			    db_update_val(ctx, new_user, realm, "remediation",
+					  "", 0) < 0) {
+				debug_print(ctx, 1,
+					    "Failed to update user name (cert-serialnum)");
+				ret = build_spp_exchange_complete(
+					ctx, session_id, "Error occurred",
+					"Other");
+				hs20_eventlog_node(ctx, user, realm,
+						   session_id,
+						   "Failed to update user name (cert reenroll)",
+						   ret);
+				db_remove_session(ctx, NULL, NULL, session_id);
+				os_free(new_user);
+				return ret;
+			}
+
+			os_free(new_user);
+		}
 		ret = build_spp_exchange_complete(
 			ctx, session_id,
 			"Exchange complete, release TLS connection", NULL);
 		hs20_eventlog_node(ctx, user, realm, session_id,
 				   "Exchange completed", ret);
-		db_remove_session(ctx, user, realm, session_id);
+		db_remove_session(ctx, NULL, NULL, session_id);
 		return ret;
 	}