From abed6136ae254ce4c4225ce719680482e9f654a8 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Fri, 22 Jun 2018 19:32:46 +0300 Subject: [PATCH] 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 [t_c_clear] Signed-off-by: Jouni Malinen --- hostapd/ctrl_iface.c | 5 + src/radius/radius_server.c | 349 +++++++++++++++++++++++++++++++++++++ src/radius/radius_server.h | 1 + 3 files changed, 355 insertions(+) diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c index 8e3dcc4e4..2d68e88f6 100644 --- a/hostapd/ctrl_iface.c +++ b/hostapd/ctrl_iface.c @@ -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; diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index fdd3609dd..e2801aed8 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -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: [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 */ +} diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 298f3c105..167bbf5b2 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -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 */