RADIUS: Add DAC implementation in hostapd(AS)
The new DAC_REQUEST control interface command can now be used to request hostapd to send out Disconnect-Request and CoA-Request packets for an existing session. DAC_REQUEST <disconnect|coa> <MAC Address> [t_c_clear] Signed-off-by: Jouni Malinen <jouni@codeaurora.org>
This commit is contained in:
		
							parent
							
								
									72aad113c2
								
							
						
					
					
						commit
						abed6136ae
					
				
					 3 changed files with 355 additions and 0 deletions
				
			
		|  | @ -3203,6 +3203,11 @@ static int hostapd_ctrl_iface_receive_process(struct hostapd_data *hapd, | |||
| 		if (hostapd_dpp_pkex_remove(hapd, buf + 16) < 0) | ||||
| 			reply_len = -1; | ||||
| #endif /* CONFIG_DPP */ | ||||
| #ifdef RADIUS_SERVER | ||||
| 	} else if (os_strncmp(buf, "DAC_REQUEST ", 12) == 0) { | ||||
| 		if (radius_server_dac_request(hapd->radius_srv, buf + 12) < 0) | ||||
| 			reply_len = -1; | ||||
| #endif /* RADIUS_SERVER */ | ||||
| 	} else { | ||||
| 		os_memcpy(reply, "UNKNOWN COMMAND\n", 16); | ||||
| 		reply_len = 16; | ||||
|  |  | |||
|  | @ -115,6 +115,14 @@ struct radius_client { | |||
| 	int shared_secret_len; | ||||
| 	struct radius_session *sessions; | ||||
| 	struct radius_server_counters counters; | ||||
| 
 | ||||
| 	u8 next_dac_identifier; | ||||
| 	struct radius_msg *pending_dac_coa_req; | ||||
| 	u8 pending_dac_coa_id; | ||||
| 	u8 pending_dac_coa_addr[ETH_ALEN]; | ||||
| 	struct radius_msg *pending_dac_disconnect_req; | ||||
| 	u8 pending_dac_disconnect_id; | ||||
| 	u8 pending_dac_disconnect_addr[ETH_ALEN]; | ||||
| }; | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -1397,6 +1405,116 @@ send_reply: | |||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void | ||||
| radius_server_receive_disconnect_resp(struct radius_server_data *data, | ||||
| 				      struct radius_client *client, | ||||
| 				      struct radius_msg *msg, int ack) | ||||
| { | ||||
| 	struct radius_hdr *hdr; | ||||
| 
 | ||||
| 	if (!client->pending_dac_disconnect_req) { | ||||
| 		RADIUS_DEBUG("Ignore unexpected Disconnect response"); | ||||
| 		radius_msg_free(msg); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	hdr = radius_msg_get_hdr(msg); | ||||
| 	if (hdr->identifier != client->pending_dac_disconnect_id) { | ||||
| 		RADIUS_DEBUG("Ignore unexpected Disconnect response with unexpected identifier %u (expected %u)", | ||||
| 			     hdr->identifier, | ||||
| 			     client->pending_dac_disconnect_id); | ||||
| 		radius_msg_free(msg); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (radius_msg_verify(msg, (const u8 *) client->shared_secret, | ||||
| 			      client->shared_secret_len, | ||||
| 			      client->pending_dac_disconnect_req, 0)) { | ||||
| 		RADIUS_DEBUG("Ignore Disconnect response with invalid authenticator"); | ||||
| 		radius_msg_free(msg); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	RADIUS_DEBUG("Disconnect-%s received for " MACSTR, | ||||
| 		     ack ? "ACK" : "NAK", | ||||
| 		     MAC2STR(client->pending_dac_disconnect_addr)); | ||||
| 
 | ||||
| 	radius_msg_free(msg); | ||||
| 	radius_msg_free(client->pending_dac_disconnect_req); | ||||
| 	client->pending_dac_disconnect_req = NULL; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void radius_server_receive_coa_resp(struct radius_server_data *data, | ||||
| 					   struct radius_client *client, | ||||
| 					   struct radius_msg *msg, int ack) | ||||
| { | ||||
| 	struct radius_hdr *hdr; | ||||
| #ifdef CONFIG_SQLITE | ||||
| 	char addrtxt[3 * ETH_ALEN]; | ||||
| 	char *sql; | ||||
| 	int res; | ||||
| #endif /* CONFIG_SQLITE */ | ||||
| 
 | ||||
| 	if (!client->pending_dac_coa_req) { | ||||
| 		RADIUS_DEBUG("Ignore unexpected CoA response"); | ||||
| 		radius_msg_free(msg); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	hdr = radius_msg_get_hdr(msg); | ||||
| 	if (hdr->identifier != client->pending_dac_coa_id) { | ||||
| 		RADIUS_DEBUG("Ignore unexpected CoA response with unexpected identifier %u (expected %u)", | ||||
| 			     hdr->identifier, | ||||
| 			     client->pending_dac_coa_id); | ||||
| 		radius_msg_free(msg); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (radius_msg_verify(msg, (const u8 *) client->shared_secret, | ||||
| 			      client->shared_secret_len, | ||||
| 			      client->pending_dac_coa_req, 0)) { | ||||
| 		RADIUS_DEBUG("Ignore CoA response with invalid authenticator"); | ||||
| 		radius_msg_free(msg); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	RADIUS_DEBUG("CoA-%s received for " MACSTR, | ||||
| 		     ack ? "ACK" : "NAK", | ||||
| 		     MAC2STR(client->pending_dac_coa_addr)); | ||||
| 
 | ||||
| 	radius_msg_free(msg); | ||||
| 	radius_msg_free(client->pending_dac_coa_req); | ||||
| 	client->pending_dac_coa_req = NULL; | ||||
| 
 | ||||
| #ifdef CONFIG_SQLITE | ||||
| 	if (!data->db) | ||||
| 		return; | ||||
| 
 | ||||
| 	os_snprintf(addrtxt, sizeof(addrtxt), MACSTR, | ||||
| 		    MAC2STR(client->pending_dac_coa_addr)); | ||||
| 
 | ||||
| 	if (ack) { | ||||
| 		sql = sqlite3_mprintf("UPDATE current_sessions SET hs20_t_c_filtering=0, waiting_coa_ack=0, coa_ack_received=1 WHERE mac_addr=%Q", | ||||
| 				      addrtxt); | ||||
| 	} else { | ||||
| 		sql = sqlite3_mprintf("UPDATE current_sessions SET waiting_coa_ack=0 WHERE mac_addr=%Q", | ||||
| 				      addrtxt); | ||||
| 	} | ||||
| 	if (!sql) | ||||
| 		return; | ||||
| 
 | ||||
| 	res = sqlite3_exec(data->db, sql, NULL, NULL, NULL); | ||||
| 	sqlite3_free(sql); | ||||
| 	if (res != SQLITE_OK) { | ||||
| 		RADIUS_ERROR("Failed to update current_sessions entry: %s", | ||||
| 			     sqlite3_errmsg(data->db)); | ||||
| 		return; | ||||
| 	} | ||||
| #endif /* CONFIG_SQLITE */ | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void radius_server_receive_auth(int sock, void *eloop_ctx, | ||||
| 				       void *sock_ctx) | ||||
| { | ||||
|  | @ -1477,6 +1595,26 @@ static void radius_server_receive_auth(int sock, void *eloop_ctx, | |||
| 		radius_msg_dump(msg); | ||||
| 	} | ||||
| 
 | ||||
| 	if (radius_msg_get_hdr(msg)->code == RADIUS_CODE_DISCONNECT_ACK) { | ||||
| 		radius_server_receive_disconnect_resp(data, client, msg, 1); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (radius_msg_get_hdr(msg)->code == RADIUS_CODE_DISCONNECT_NAK) { | ||||
| 		radius_server_receive_disconnect_resp(data, client, msg, 0); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (radius_msg_get_hdr(msg)->code == RADIUS_CODE_COA_ACK) { | ||||
| 		radius_server_receive_coa_resp(data, client, msg, 1); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (radius_msg_get_hdr(msg)->code == RADIUS_CODE_COA_NAK) { | ||||
| 		radius_server_receive_coa_resp(data, client, msg, 0); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCESS_REQUEST) { | ||||
| 		RADIUS_DEBUG("Unexpected RADIUS code %d", | ||||
| 			     radius_msg_get_hdr(msg)->code); | ||||
|  | @ -1737,6 +1875,8 @@ static void radius_server_free_clients(struct radius_server_data *data, | |||
| 
 | ||||
| 		radius_server_free_sessions(data, prev->sessions); | ||||
| 		os_free(prev->shared_secret); | ||||
| 		radius_msg_free(prev->pending_dac_coa_req); | ||||
| 		radius_msg_free(prev->pending_dac_disconnect_req); | ||||
| 		os_free(prev); | ||||
| 	} | ||||
| } | ||||
|  | @ -2388,3 +2528,212 @@ void radius_server_eap_pending_cb(struct radius_server_data *data, void *ctx) | |||
| 
 | ||||
| 	radius_msg_free(msg); | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #ifdef CONFIG_SQLITE | ||||
| 
 | ||||
| struct db_session_fields { | ||||
| 	char *identity; | ||||
| 	char *nas; | ||||
| 	int hs20_t_c_filtering; | ||||
| 	int waiting_coa_ack; | ||||
| 	int coa_ack_received; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| static int get_db_session_fields(void *ctx, int argc, char *argv[], char *col[]) | ||||
| { | ||||
| 	struct db_session_fields *fields = ctx; | ||||
| 	int i; | ||||
| 
 | ||||
| 	for (i = 0; i < argc; i++) { | ||||
| 		if (!argv[i]) | ||||
| 			continue; | ||||
| 
 | ||||
| 		RADIUS_DEBUG("Session DB: %s=%s", col[i], argv[i]); | ||||
| 
 | ||||
| 		if (os_strcmp(col[i], "identity") == 0) { | ||||
| 			os_free(fields->identity); | ||||
| 			fields->identity = os_strdup(argv[i]); | ||||
| 		} else if (os_strcmp(col[i], "nas") == 0) { | ||||
| 			os_free(fields->nas); | ||||
| 			fields->nas = os_strdup(argv[i]); | ||||
| 		} else if (os_strcmp(col[i], "hs20_t_c_filtering") == 0) { | ||||
| 			fields->hs20_t_c_filtering = atoi(argv[i]); | ||||
| 		} else if (os_strcmp(col[i], "waiting_coa_ack") == 0) { | ||||
| 			fields->waiting_coa_ack = atoi(argv[i]); | ||||
| 		} else if (os_strcmp(col[i], "coa_ack_received") == 0) { | ||||
| 			fields->coa_ack_received = atoi(argv[i]); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| static void free_db_session_fields(struct db_session_fields *fields) | ||||
| { | ||||
| 	os_free(fields->identity); | ||||
| 	fields->identity = NULL; | ||||
| 	os_free(fields->nas); | ||||
| 	fields->nas = NULL; | ||||
| } | ||||
| 
 | ||||
| #endif /* CONFIG_SQLITE */ | ||||
| 
 | ||||
| 
 | ||||
| int radius_server_dac_request(struct radius_server_data *data, const char *req) | ||||
| { | ||||
| #ifdef CONFIG_SQLITE | ||||
| 	char *sql; | ||||
| 	int res; | ||||
| 	int disconnect; | ||||
| 	const char *pos = req; | ||||
| 	u8 addr[ETH_ALEN]; | ||||
| 	char addrtxt[3 * ETH_ALEN]; | ||||
| 	int t_c_clear = 0; | ||||
| 	struct db_session_fields fields; | ||||
| 	struct sockaddr_in das; | ||||
| 	struct radius_client *client; | ||||
| 	struct radius_msg *msg; | ||||
| 	struct wpabuf *buf; | ||||
| 	u8 identifier; | ||||
| 	struct os_time now; | ||||
| 
 | ||||
| 	if (!data) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	/* req: <disconnect|coa> <MAC Address> [t_c_clear] */ | ||||
| 
 | ||||
| 	if (os_strncmp(pos, "disconnect ", 11) == 0) { | ||||
| 		disconnect = 1; | ||||
| 		pos += 11; | ||||
| 	} else if (os_strncmp(req, "coa ", 4) == 0) { | ||||
| 		disconnect = 0; | ||||
| 		pos += 4; | ||||
| 	} else { | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (hwaddr_aton(pos, addr)) | ||||
| 		return -1; | ||||
| 	pos = os_strchr(pos, ' '); | ||||
| 	if (pos) { | ||||
| 		if (os_strstr(pos, "t_c_clear")) | ||||
| 			t_c_clear = 1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!disconnect && !t_c_clear) { | ||||
| 		RADIUS_ERROR("DAC request for CoA without any authorization change"); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!data->db) { | ||||
| 		RADIUS_ERROR("SQLite database not in use"); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	os_snprintf(addrtxt, sizeof(addrtxt), MACSTR, MAC2STR(addr)); | ||||
| 
 | ||||
| 	sql = sqlite3_mprintf("SELECT * FROM current_sessions WHERE mac_addr=%Q", | ||||
| 			      addrtxt); | ||||
| 	if (!sql) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	os_memset(&fields, 0, sizeof(fields)); | ||||
| 	res = sqlite3_exec(data->db, sql, get_db_session_fields, &fields, NULL); | ||||
| 	sqlite3_free(sql); | ||||
| 	if (res != SQLITE_OK) { | ||||
| 		RADIUS_ERROR("Failed to find matching current_sessions entry from sqlite database: %s", | ||||
| 			     sqlite3_errmsg(data->db)); | ||||
| 		free_db_session_fields(&fields); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!fields.nas) { | ||||
| 		RADIUS_ERROR("No NAS information found from current_sessions"); | ||||
| 		free_db_session_fields(&fields); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	os_memset(&das, 0, sizeof(das)); | ||||
| 	das.sin_family = AF_INET; | ||||
| 	das.sin_addr.s_addr = inet_addr(fields.nas); | ||||
| 	das.sin_port = htons(3799); | ||||
| 
 | ||||
| 	free_db_session_fields(&fields); | ||||
| 
 | ||||
| 	client = radius_server_get_client(data, &das.sin_addr, 0); | ||||
| 	if (!client) { | ||||
| 		RADIUS_ERROR("No NAS information available to protect the packet"); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	identifier = client->next_dac_identifier++; | ||||
| 
 | ||||
| 	msg = radius_msg_new(disconnect ? RADIUS_CODE_DISCONNECT_REQUEST : | ||||
| 			     RADIUS_CODE_COA_REQUEST, identifier); | ||||
| 	if (!msg) | ||||
| 		return -1; | ||||
| 
 | ||||
| 	os_snprintf(addrtxt, sizeof(addrtxt), RADIUS_802_1X_ADDR_FORMAT, | ||||
| 		    MAC2STR(addr)); | ||||
| 	if (!radius_msg_add_attr(msg, RADIUS_ATTR_CALLING_STATION_ID, | ||||
| 				 (u8 *) addrtxt, os_strlen(addrtxt))) { | ||||
| 		RADIUS_ERROR("Could not add Calling-Station-Id"); | ||||
| 		radius_msg_free(msg); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (!disconnect && t_c_clear) { | ||||
| 		u8 val[4] = { 0x00, 0x00, 0x00, 0x00 }; /* E=0 */ | ||||
| 
 | ||||
| 		if (!radius_msg_add_wfa( | ||||
| 			    msg, RADIUS_VENDOR_ATTR_WFA_HS20_T_C_FILTERING, | ||||
| 			    val, sizeof(val))) { | ||||
| 			RADIUS_DEBUG("Failed to add WFA-HS20-T-C-Filtering"); | ||||
| 			radius_msg_free(msg); | ||||
| 			return -1; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	os_get_time(&now); | ||||
| 	if (!radius_msg_add_attr_int32(msg, RADIUS_ATTR_EVENT_TIMESTAMP, | ||||
| 				       now.sec)) { | ||||
| 		RADIUS_ERROR("Failed to add Event-Timestamp attribute"); | ||||
| 		radius_msg_free(msg); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	radius_msg_finish_acct(msg, (u8 *) client->shared_secret, | ||||
| 			       client->shared_secret_len); | ||||
| 
 | ||||
| 	if (wpa_debug_level <= MSG_MSGDUMP) | ||||
| 		radius_msg_dump(msg); | ||||
| 
 | ||||
| 	buf = radius_msg_get_buf(msg); | ||||
| 	if (sendto(data->auth_sock, wpabuf_head(buf), wpabuf_len(buf), 0, | ||||
| 		   (struct sockaddr *) &das, sizeof(das)) < 0) { | ||||
| 		RADIUS_ERROR("Failed to send packet - sendto: %s", | ||||
| 			     strerror(errno)); | ||||
| 		radius_msg_free(msg); | ||||
| 		return -1; | ||||
| 	} | ||||
| 
 | ||||
| 	if (disconnect) { | ||||
| 		radius_msg_free(client->pending_dac_disconnect_req); | ||||
| 		client->pending_dac_disconnect_req = msg; | ||||
| 		client->pending_dac_disconnect_id = identifier; | ||||
| 		os_memcpy(client->pending_dac_disconnect_addr, addr, ETH_ALEN); | ||||
| 	} else { | ||||
| 		radius_msg_free(client->pending_dac_coa_req); | ||||
| 		client->pending_dac_coa_req = msg; | ||||
| 		client->pending_dac_coa_id = identifier; | ||||
| 		os_memcpy(client->pending_dac_coa_addr, addr, ETH_ALEN); | ||||
| 	} | ||||
| 
 | ||||
| 	return 0; | ||||
| #else /* CONFIG_SQLITE */ | ||||
| 	return -1; | ||||
| #endif /* CONFIG_SQLITE */ | ||||
| } | ||||
|  |  | |||
|  | @ -248,5 +248,6 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, | |||
| 			  size_t buflen); | ||||
| 
 | ||||
| void radius_server_eap_pending_cb(struct radius_server_data *data, void *ctx); | ||||
| int radius_server_dac_request(struct radius_server_data *data, const char *req); | ||||
| 
 | ||||
| #endif /* RADIUS_SERVER_H */ | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue
	
	 Jouni Malinen
						Jouni Malinen