]> git.ipfire.org Git - thirdparty/hostap.git/commitdiff
RADIUS: Add DAC implementation in hostapd(AS)
authorJouni Malinen <jouni@codeaurora.org>
Fri, 22 Jun 2018 16:32:46 +0000 (19:32 +0300)
committerJouni Malinen <j@w1.fi>
Fri, 22 Jun 2018 16:37:18 +0000 (19:37 +0300)
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>
hostapd/ctrl_iface.c
src/radius/radius_server.c
src/radius/radius_server.h

index 8e3dcc4e406a78186c336a86099f97dab383b945..2d68e88f6503a1641cbd5e66d26a61fc20c1ff77 100644 (file)
@@ -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;
index fdd3609ddaf78ad0817f0f90fab98038a5100bbb..e2801aed87502769a3f82e509dbcab94e5b6d948 100644 (file)
@@ -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 */
+}
index 298f3c10547e06c939abd9d73ab192b572b82c4e..167bbf5b28813d6ee471c7fa14c260b7b0ce5961 100644 (file)
@@ -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 */