aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--hostapd/ctrl_iface.c5
-rw-r--r--src/radius/radius_server.c349
-rw-r--r--src/radius/radius_server.h1
3 files changed, 355 insertions, 0 deletions
diff --git a/hostapd/ctrl_iface.c b/hostapd/ctrl_iface.c
index 8e3dcc4..2d68e88 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 fdd3609..e2801ae 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: <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 */
+}
diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h
index 298f3c1..167bbf5 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 */