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)
|
if (hostapd_dpp_pkex_remove(hapd, buf + 16) < 0)
|
||||||
reply_len = -1;
|
reply_len = -1;
|
||||||
#endif /* CONFIG_DPP */
|
#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 {
|
} else {
|
||||||
os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
|
os_memcpy(reply, "UNKNOWN COMMAND\n", 16);
|
||||||
reply_len = 16;
|
reply_len = 16;
|
||||||
|
|
|
@ -115,6 +115,14 @@ struct radius_client {
|
||||||
int shared_secret_len;
|
int shared_secret_len;
|
||||||
struct radius_session *sessions;
|
struct radius_session *sessions;
|
||||||
struct radius_server_counters counters;
|
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,
|
static void radius_server_receive_auth(int sock, void *eloop_ctx,
|
||||||
void *sock_ctx)
|
void *sock_ctx)
|
||||||
{
|
{
|
||||||
|
@ -1477,6 +1595,26 @@ static void radius_server_receive_auth(int sock, void *eloop_ctx,
|
||||||
radius_msg_dump(msg);
|
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) {
|
if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCESS_REQUEST) {
|
||||||
RADIUS_DEBUG("Unexpected RADIUS code %d",
|
RADIUS_DEBUG("Unexpected RADIUS code %d",
|
||||||
radius_msg_get_hdr(msg)->code);
|
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);
|
radius_server_free_sessions(data, prev->sessions);
|
||||||
os_free(prev->shared_secret);
|
os_free(prev->shared_secret);
|
||||||
|
radius_msg_free(prev->pending_dac_coa_req);
|
||||||
|
radius_msg_free(prev->pending_dac_disconnect_req);
|
||||||
os_free(prev);
|
os_free(prev);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2388,3 +2528,212 @@ void radius_server_eap_pending_cb(struct radius_server_data *data, void *ctx)
|
||||||
|
|
||||||
radius_msg_free(msg);
|
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);
|
size_t buflen);
|
||||||
|
|
||||||
void radius_server_eap_pending_cb(struct radius_server_data *data, void *ctx);
|
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 */
|
#endif /* RADIUS_SERVER_H */
|
||||||
|
|
Loading…
Reference in a new issue