From a1dd890a856cb3188d34482de7c78249fd34ec38 Mon Sep 17 00:00:00 2001 From: Jouni Malinen Date: Sat, 15 Feb 2014 15:37:53 +0200 Subject: [PATCH] RADIUS: Add minimal accounting server support This can be used to test RADIUS Accounting in hostapd. Signed-off-by: Jouni Malinen --- hostapd/config_file.c | 4 +- hostapd/hostapd.conf | 5 + src/ap/ap_config.h | 1 + src/ap/authsrv.c | 1 + src/radius/radius.c | 23 +++- src/radius/radius.h | 5 +- src/radius/radius_server.c | 208 ++++++++++++++++++++++++++++++++++++- src/radius/radius_server.h | 5 + 8 files changed, 244 insertions(+), 8 deletions(-) diff --git a/hostapd/config_file.c b/hostapd/config_file.c index 54e4af9d3..19d6ad324 100644 --- a/hostapd/config_file.c +++ b/hostapd/config_file.c @@ -1,6 +1,6 @@ /* * hostapd / Configuration file parser - * Copyright (c) 2003-2013, Jouni Malinen + * Copyright (c) 2003-2014, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -2188,6 +2188,8 @@ static int hostapd_config_fill(struct hostapd_config *conf, bss->radius_server_clients = os_strdup(pos); } else if (os_strcmp(buf, "radius_server_auth_port") == 0) { bss->radius_server_auth_port = atoi(pos); + } else if (os_strcmp(buf, "radius_server_acct_port") == 0) { + bss->radius_server_acct_port = atoi(pos); } else if (os_strcmp(buf, "radius_server_ipv6") == 0) { bss->radius_server_ipv6 = atoi(pos); #endif /* RADIUS_SERVER */ diff --git a/hostapd/hostapd.conf b/hostapd/hostapd.conf index da7817f42..c503ce290 100644 --- a/hostapd/hostapd.conf +++ b/hostapd/hostapd.conf @@ -971,6 +971,11 @@ own_ip_addr=127.0.0.1 # The UDP port number for the RADIUS authentication server #radius_server_auth_port=1812 +# The UDP port number for the RADIUS accounting server +# Commenting this out or setting this to 0 can be used to disable RADIUS +# accounting while still enabling RADIUS authentication. +#radius_server_acct_port=1813 + # Use IPv6 with RADIUS server (IPv4 will also be supported using IPv6 API) #radius_server_ipv6=1 diff --git a/src/ap/ap_config.h b/src/ap/ap_config.h index b4860a08b..4631ca9ed 100644 --- a/src/ap/ap_config.h +++ b/src/ap/ap_config.h @@ -311,6 +311,7 @@ struct hostapd_bss_config { char *radius_server_clients; int radius_server_auth_port; + int radius_server_acct_port; int radius_server_ipv6; char *test_socket; /* UNIX domain socket path for driver_test */ diff --git a/src/ap/authsrv.c b/src/ap/authsrv.c index 8bb58a6f6..7183abae4 100644 --- a/src/ap/authsrv.c +++ b/src/ap/authsrv.c @@ -92,6 +92,7 @@ static int hostapd_setup_radius_srv(struct hostapd_data *hapd) os_memset(&srv, 0, sizeof(srv)); srv.client_file = conf->radius_server_clients; srv.auth_port = conf->radius_server_auth_port; + srv.acct_port = conf->radius_server_acct_port; srv.conf_ctx = hapd; srv.eap_sim_db_priv = hapd->eap_sim_db_priv; srv.ssl_ctx = hapd->ssl_ctx; diff --git a/src/radius/radius.c b/src/radius/radius.c index 494f92d19..dbad8481f 100644 --- a/src/radius/radius.c +++ b/src/radius/radius.c @@ -1,6 +1,6 @@ /* * RADIUS message processing - * Copyright (c) 2002-2009, 2011-2012, Jouni Malinen + * Copyright (c) 2002-2009, 2011-2014, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -473,6 +473,27 @@ void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret, } +void radius_msg_finish_acct_resp(struct radius_msg *msg, const u8 *secret, + size_t secret_len, const u8 *req_authenticator) +{ + const u8 *addr[2]; + size_t len[2]; + + msg->hdr->length = host_to_be16(wpabuf_len(msg->buf)); + os_memcpy(msg->hdr->authenticator, req_authenticator, MD5_MAC_LEN); + addr[0] = wpabuf_head(msg->buf); + len[0] = wpabuf_len(msg->buf); + addr[1] = secret; + len[1] = secret_len; + md5_vector(2, addr, len, msg->hdr->authenticator); + + if (wpabuf_len(msg->buf) > 0xffff) { + wpa_printf(MSG_WARNING, "RADIUS: Too long messages (%lu)", + (unsigned long) wpabuf_len(msg->buf)); + } +} + + int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret, size_t secret_len) { diff --git a/src/radius/radius.h b/src/radius/radius.h index 2031054b1..ad65b04b8 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -1,6 +1,6 @@ /* * RADIUS message processing - * Copyright (c) 2002-2009, 2012, Jouni Malinen + * Copyright (c) 2002-2009, 2012, 2014, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -204,6 +204,9 @@ int radius_msg_finish_das_resp(struct radius_msg *msg, const u8 *secret, const struct radius_hdr *req_hdr); void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret, size_t secret_len); +void radius_msg_finish_acct_resp(struct radius_msg *msg, const u8 *secret, + size_t secret_len, + const u8 *req_authenticator); int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret, size_t secret_len); int radius_msg_verify_das_req(struct radius_msg *msg, const u8 *secret, diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index 1063d65ac..2904b2f7f 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -1,6 +1,6 @@ /* * RADIUS authentication server - * Copyright (c) 2005-2009, 2011, Jouni Malinen + * Copyright (c) 2005-2009, 2011-2014, Jouni Malinen * * This software may be distributed under the terms of the BSD license. * See README for more details. @@ -49,6 +49,13 @@ struct radius_server_counters { u32 bad_authenticators; u32 packets_dropped; u32 unknown_types; + + u32 acct_requests; + u32 invalid_acct_requests; + u32 acct_responses; + u32 malformed_acct_requests; + u32 acct_bad_authenticators; + u32 unknown_acct_types; }; /** @@ -98,6 +105,11 @@ struct radius_server_data { */ int auth_sock; + /** + * acct_sock - Socket for RADIUS accounting messages + */ + int acct_sock; + /** * clients - List of authorized RADIUS clients */ @@ -979,6 +991,140 @@ fail: } +static void radius_server_receive_acct(int sock, void *eloop_ctx, + void *sock_ctx) +{ + struct radius_server_data *data = eloop_ctx; + u8 *buf = NULL; + union { + struct sockaddr_storage ss; + struct sockaddr_in sin; +#ifdef CONFIG_IPV6 + struct sockaddr_in6 sin6; +#endif /* CONFIG_IPV6 */ + } from; + socklen_t fromlen; + int len, res; + struct radius_client *client = NULL; + struct radius_msg *msg = NULL, *resp = NULL; + char abuf[50]; + int from_port = 0; + struct radius_hdr *hdr; + struct wpabuf *rbuf; + + buf = os_malloc(RADIUS_MAX_MSG_LEN); + if (buf == NULL) { + goto fail; + } + + fromlen = sizeof(from); + len = recvfrom(sock, buf, RADIUS_MAX_MSG_LEN, 0, + (struct sockaddr *) &from.ss, &fromlen); + if (len < 0) { + wpa_printf(MSG_INFO, "recvfrom[radius_server]: %s", + strerror(errno)); + goto fail; + } + +#ifdef CONFIG_IPV6 + if (data->ipv6) { + if (inet_ntop(AF_INET6, &from.sin6.sin6_addr, abuf, + sizeof(abuf)) == NULL) + abuf[0] = '\0'; + from_port = ntohs(from.sin6.sin6_port); + RADIUS_DEBUG("Received %d bytes from %s:%d", + len, abuf, from_port); + + client = radius_server_get_client(data, + (struct in_addr *) + &from.sin6.sin6_addr, 1); + } +#endif /* CONFIG_IPV6 */ + + if (!data->ipv6) { + os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf)); + from_port = ntohs(from.sin.sin_port); + RADIUS_DEBUG("Received %d bytes from %s:%d", + len, abuf, from_port); + + client = radius_server_get_client(data, &from.sin.sin_addr, 0); + } + + RADIUS_DUMP("Received data", buf, len); + + if (client == NULL) { + RADIUS_DEBUG("Unknown client %s - packet ignored", abuf); + data->counters.invalid_acct_requests++; + goto fail; + } + + msg = radius_msg_parse(buf, len); + if (msg == NULL) { + RADIUS_DEBUG("Parsing incoming RADIUS frame failed"); + data->counters.malformed_acct_requests++; + client->counters.malformed_acct_requests++; + goto fail; + } + + os_free(buf); + buf = NULL; + + if (wpa_debug_level <= MSG_MSGDUMP) { + radius_msg_dump(msg); + } + + if (radius_msg_get_hdr(msg)->code != RADIUS_CODE_ACCOUNTING_REQUEST) { + RADIUS_DEBUG("Unexpected RADIUS code %d", + radius_msg_get_hdr(msg)->code); + data->counters.unknown_acct_types++; + client->counters.unknown_acct_types++; + goto fail; + } + + data->counters.acct_requests++; + client->counters.acct_requests++; + + if (radius_msg_verify_acct_req(msg, (u8 *) client->shared_secret, + client->shared_secret_len)) { + RADIUS_DEBUG("Invalid Authenticator from %s", abuf); + data->counters.acct_bad_authenticators++; + client->counters.acct_bad_authenticators++; + goto fail; + } + + /* TODO: Write accounting information to a file or database */ + + hdr = radius_msg_get_hdr(msg); + + resp = radius_msg_new(RADIUS_CODE_ACCOUNTING_RESPONSE, hdr->identifier); + if (resp == NULL) + goto fail; + + radius_msg_finish_acct_resp(resp, (u8 *) client->shared_secret, + client->shared_secret_len, + hdr->authenticator); + + RADIUS_DEBUG("Reply to %s:%d", abuf, from_port); + if (wpa_debug_level <= MSG_MSGDUMP) { + radius_msg_dump(resp); + } + rbuf = radius_msg_get_buf(resp); + data->counters.acct_responses++; + client->counters.acct_responses++; + res = sendto(data->acct_sock, wpabuf_head(rbuf), wpabuf_len(rbuf), 0, + (struct sockaddr *) &from.ss, fromlen); + if (res < 0) { + wpa_printf(MSG_INFO, "sendto[RADIUS SRV]: %s", + strerror(errno)); + } + +fail: + radius_msg_free(resp); + radius_msg_free(msg); + os_free(buf); +} + + static int radius_server_disable_pmtu_discovery(int s) { int r = -1; @@ -1329,6 +1475,29 @@ radius_server_init(struct radius_server_conf *conf) return NULL; } + if (conf->acct_port) { +#ifdef CONFIG_IPV6 + if (conf->ipv6) + data->acct_sock = radius_server_open_socket6( + conf->acct_port); + else +#endif /* CONFIG_IPV6 */ + data->acct_sock = radius_server_open_socket(conf->acct_port); + if (data->acct_sock < 0) { + wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS accounting server"); + radius_server_deinit(data); + return NULL; + } + if (eloop_register_read_sock(data->acct_sock, + radius_server_receive_acct, + data, NULL)) { + radius_server_deinit(data); + return NULL; + } + } else { + data->acct_sock = -1; + } + return data; } @@ -1347,6 +1516,11 @@ void radius_server_deinit(struct radius_server_data *data) close(data->auth_sock); } + if (data->acct_sock >= 0) { + eloop_unregister_read_sock(data->acct_sock); + close(data->acct_sock); + } + radius_server_free_clients(data, data->clients); os_free(data->pac_opaque_encr_key); @@ -1410,7 +1584,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, "radiusAuthServTotalMalformedAccessRequests=%u\n" "radiusAuthServTotalBadAuthenticators=%u\n" "radiusAuthServTotalPacketsDropped=%u\n" - "radiusAuthServTotalUnknownTypes=%u\n", + "radiusAuthServTotalUnknownTypes=%u\n" + "radiusAccServTotalRequests=%u\n" + "radiusAccServTotalInvalidRequests=%u\n" + "radiusAccServTotalResponses=%u\n" + "radiusAccServTotalMalformedRequests=%u\n" + "radiusAccServTotalBadAuthenticators=%u\n" + "radiusAccServTotalUnknownTypes=%u\n", data->counters.access_requests, data->counters.invalid_requests, data->counters.dup_access_requests, @@ -1420,7 +1600,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, data->counters.malformed_access_requests, data->counters.bad_authenticators, data->counters.packets_dropped, - data->counters.unknown_types); + data->counters.unknown_types, + data->counters.acct_requests, + data->counters.invalid_acct_requests, + data->counters.acct_responses, + data->counters.malformed_acct_requests, + data->counters.acct_bad_authenticators, + data->counters.unknown_acct_types); if (ret < 0 || ret >= end - pos) { *pos = '\0'; return pos - buf; @@ -1455,7 +1641,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, "radiusAuthServMalformedAccessRequests=%u\n" "radiusAuthServBadAuthenticators=%u\n" "radiusAuthServPacketsDropped=%u\n" - "radiusAuthServUnknownTypes=%u\n", + "radiusAuthServUnknownTypes=%u\n" + "radiusAccServTotalRequests=%u\n" + "radiusAccServTotalInvalidRequests=%u\n" + "radiusAccServTotalResponses=%u\n" + "radiusAccServTotalMalformedRequests=%u\n" + "radiusAccServTotalBadAuthenticators=%u\n" + "radiusAccServTotalUnknownTypes=%u\n", idx, abuf, mbuf, cli->counters.access_requests, @@ -1466,7 +1658,13 @@ int radius_server_get_mib(struct radius_server_data *data, char *buf, cli->counters.malformed_access_requests, cli->counters.bad_authenticators, cli->counters.packets_dropped, - cli->counters.unknown_types); + cli->counters.unknown_types, + cli->counters.acct_requests, + cli->counters.invalid_acct_requests, + cli->counters.acct_responses, + cli->counters.malformed_acct_requests, + cli->counters.acct_bad_authenticators, + cli->counters.unknown_acct_types); if (ret < 0 || ret >= end - pos) { *pos = '\0'; return pos - buf; diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index 284bd59d7..78f5fc23a 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -21,6 +21,11 @@ struct radius_server_conf { */ int auth_port; + /** + * acct_port - UDP port to listen to as an accounting server + */ + int acct_port; + /** * client_file - RADIUS client configuration file *