123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414 |
- /*
- * RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
- * Copyright (c) 2012-2013, Jouni Malinen <j@w1.fi>
- *
- * This software may be distributed under the terms of the BSD license.
- * See README for more details.
- */
- #include "includes.h"
- #include <net/if.h>
- #include "utils/common.h"
- #include "utils/eloop.h"
- #include "utils/ip_addr.h"
- #include "radius.h"
- #include "radius_das.h"
- struct radius_das_data {
- int sock;
- u8 *shared_secret;
- size_t shared_secret_len;
- struct hostapd_ip_addr client_addr;
- unsigned int time_window;
- int require_event_timestamp;
- int require_message_authenticator;
- void *ctx;
- enum radius_das_res (*disconnect)(void *ctx,
- struct radius_das_attrs *attr);
- };
- static struct radius_msg * radius_das_disconnect(struct radius_das_data *das,
- struct radius_msg *msg,
- const char *abuf,
- int from_port)
- {
- struct radius_hdr *hdr;
- struct radius_msg *reply;
- u8 allowed[] = {
- RADIUS_ATTR_USER_NAME,
- RADIUS_ATTR_NAS_IP_ADDRESS,
- RADIUS_ATTR_CALLING_STATION_ID,
- RADIUS_ATTR_NAS_IDENTIFIER,
- RADIUS_ATTR_ACCT_SESSION_ID,
- RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
- RADIUS_ATTR_EVENT_TIMESTAMP,
- RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
- RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
- #ifdef CONFIG_IPV6
- RADIUS_ATTR_NAS_IPV6_ADDRESS,
- #endif /* CONFIG_IPV6 */
- 0
- };
- int error = 405;
- u8 attr;
- enum radius_das_res res;
- struct radius_das_attrs attrs;
- u8 *buf;
- size_t len;
- char tmp[100];
- u8 sta_addr[ETH_ALEN];
- hdr = radius_msg_get_hdr(msg);
- attr = radius_msg_find_unlisted_attr(msg, allowed);
- if (attr) {
- wpa_printf(MSG_INFO, "DAS: Unsupported attribute %u in "
- "Disconnect-Request from %s:%d", attr,
- abuf, from_port);
- error = 401;
- goto fail;
- }
- os_memset(&attrs, 0, sizeof(attrs));
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
- &buf, &len, NULL) == 0) {
- if (len != 4) {
- wpa_printf(MSG_INFO, "DAS: Invalid NAS-IP-Address from %s:%d",
- abuf, from_port);
- error = 407;
- goto fail;
- }
- attrs.nas_ip_addr = buf;
- }
- #ifdef CONFIG_IPV6
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
- &buf, &len, NULL) == 0) {
- if (len != 16) {
- wpa_printf(MSG_INFO, "DAS: Invalid NAS-IPv6-Address from %s:%d",
- abuf, from_port);
- error = 407;
- goto fail;
- }
- attrs.nas_ipv6_addr = buf;
- }
- #endif /* CONFIG_IPV6 */
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
- &buf, &len, NULL) == 0) {
- attrs.nas_identifier = buf;
- attrs.nas_identifier_len = len;
- }
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID,
- &buf, &len, NULL) == 0) {
- if (len >= sizeof(tmp))
- len = sizeof(tmp) - 1;
- os_memcpy(tmp, buf, len);
- tmp[len] = '\0';
- if (hwaddr_aton2(tmp, sta_addr) < 0) {
- wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id "
- "'%s' from %s:%d", tmp, abuf, from_port);
- error = 407;
- goto fail;
- }
- attrs.sta_addr = sta_addr;
- }
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME,
- &buf, &len, NULL) == 0) {
- attrs.user_name = buf;
- attrs.user_name_len = len;
- }
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
- &buf, &len, NULL) == 0) {
- attrs.acct_session_id = buf;
- attrs.acct_session_id_len = len;
- }
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_MULTI_SESSION_ID,
- &buf, &len, NULL) == 0) {
- attrs.acct_multi_session_id = buf;
- attrs.acct_multi_session_id_len = len;
- }
- if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
- &buf, &len, NULL) == 0) {
- attrs.cui = buf;
- attrs.cui_len = len;
- }
- res = das->disconnect(das->ctx, &attrs);
- switch (res) {
- case RADIUS_DAS_NAS_MISMATCH:
- wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d",
- abuf, from_port);
- error = 403;
- break;
- case RADIUS_DAS_SESSION_NOT_FOUND:
- wpa_printf(MSG_INFO, "DAS: Session not found for request from "
- "%s:%d", abuf, from_port);
- error = 503;
- break;
- case RADIUS_DAS_MULTI_SESSION_MATCH:
- wpa_printf(MSG_INFO,
- "DAS: Multiple sessions match for request from %s:%d",
- abuf, from_port);
- error = 508;
- break;
- case RADIUS_DAS_SUCCESS:
- error = 0;
- break;
- }
- fail:
- reply = radius_msg_new(error ? RADIUS_CODE_DISCONNECT_NAK :
- RADIUS_CODE_DISCONNECT_ACK, hdr->identifier);
- if (reply == NULL)
- return NULL;
- if (error) {
- if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
- error)) {
- radius_msg_free(reply);
- return NULL;
- }
- }
- return reply;
- }
- static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
- {
- struct radius_das_data *das = eloop_ctx;
- u8 buf[1500];
- union {
- struct sockaddr_storage ss;
- struct sockaddr_in sin;
- #ifdef CONFIG_IPV6
- struct sockaddr_in6 sin6;
- #endif /* CONFIG_IPV6 */
- } from;
- char abuf[50];
- int from_port = 0;
- socklen_t fromlen;
- int len;
- struct radius_msg *msg, *reply = NULL;
- struct radius_hdr *hdr;
- struct wpabuf *rbuf;
- u32 val;
- int res;
- struct os_time now;
- fromlen = sizeof(from);
- len = recvfrom(sock, buf, sizeof(buf), 0,
- (struct sockaddr *) &from.ss, &fromlen);
- if (len < 0) {
- wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
- return;
- }
- os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
- from_port = ntohs(from.sin.sin_port);
- wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
- len, abuf, from_port);
- if (das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
- wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
- return;
- }
- msg = radius_msg_parse(buf, len);
- if (msg == NULL) {
- wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
- "from %s:%d failed", abuf, from_port);
- return;
- }
- if (wpa_debug_level <= MSG_MSGDUMP)
- radius_msg_dump(msg);
- if (radius_msg_verify_das_req(msg, das->shared_secret,
- das->shared_secret_len,
- das->require_message_authenticator)) {
- wpa_printf(MSG_DEBUG,
- "DAS: Invalid authenticator or Message-Authenticator in packet from %s:%d - drop",
- abuf, from_port);
- goto fail;
- }
- os_get_time(&now);
- res = radius_msg_get_attr(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
- (u8 *) &val, 4);
- if (res == 4) {
- u32 timestamp = ntohl(val);
- if ((unsigned int) abs((int) (now.sec - timestamp)) >
- das->time_window) {
- wpa_printf(MSG_DEBUG, "DAS: Unacceptable "
- "Event-Timestamp (%u; local time %u) in "
- "packet from %s:%d - drop",
- timestamp, (unsigned int) now.sec,
- abuf, from_port);
- goto fail;
- }
- } else if (das->require_event_timestamp) {
- wpa_printf(MSG_DEBUG, "DAS: Missing Event-Timestamp in packet "
- "from %s:%d - drop", abuf, from_port);
- goto fail;
- }
- hdr = radius_msg_get_hdr(msg);
- switch (hdr->code) {
- case RADIUS_CODE_DISCONNECT_REQUEST:
- reply = radius_das_disconnect(das, msg, abuf, from_port);
- break;
- case RADIUS_CODE_COA_REQUEST:
- /* TODO */
- reply = radius_msg_new(RADIUS_CODE_COA_NAK,
- hdr->identifier);
- if (reply == NULL)
- break;
- /* Unsupported Service */
- if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
- 405)) {
- radius_msg_free(reply);
- reply = NULL;
- break;
- }
- break;
- default:
- wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in "
- "packet from %s:%d",
- hdr->code, abuf, from_port);
- }
- if (reply) {
- wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
- if (!radius_msg_add_attr_int32(reply,
- RADIUS_ATTR_EVENT_TIMESTAMP,
- now.sec)) {
- wpa_printf(MSG_DEBUG, "DAS: Failed to add "
- "Event-Timestamp attribute");
- }
- if (radius_msg_finish_das_resp(reply, das->shared_secret,
- das->shared_secret_len, hdr) <
- 0) {
- wpa_printf(MSG_DEBUG, "DAS: Failed to add "
- "Message-Authenticator attribute");
- }
- if (wpa_debug_level <= MSG_MSGDUMP)
- radius_msg_dump(reply);
- rbuf = radius_msg_get_buf(reply);
- res = sendto(das->sock, wpabuf_head(rbuf),
- wpabuf_len(rbuf), 0,
- (struct sockaddr *) &from.ss, fromlen);
- if (res < 0) {
- wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
- abuf, from_port, strerror(errno));
- }
- }
- fail:
- radius_msg_free(msg);
- radius_msg_free(reply);
- }
- static int radius_das_open_socket(int port)
- {
- int s;
- struct sockaddr_in addr;
- s = socket(PF_INET, SOCK_DGRAM, 0);
- if (s < 0) {
- wpa_printf(MSG_INFO, "RADIUS DAS: socket: %s", strerror(errno));
- return -1;
- }
- os_memset(&addr, 0, sizeof(addr));
- addr.sin_family = AF_INET;
- addr.sin_port = htons(port);
- if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
- wpa_printf(MSG_INFO, "RADIUS DAS: bind: %s", strerror(errno));
- close(s);
- return -1;
- }
- return s;
- }
- struct radius_das_data *
- radius_das_init(struct radius_das_conf *conf)
- {
- struct radius_das_data *das;
- if (conf->port == 0 || conf->shared_secret == NULL ||
- conf->client_addr == NULL)
- return NULL;
- das = os_zalloc(sizeof(*das));
- if (das == NULL)
- return NULL;
- das->time_window = conf->time_window;
- das->require_event_timestamp = conf->require_event_timestamp;
- das->require_message_authenticator =
- conf->require_message_authenticator;
- das->ctx = conf->ctx;
- das->disconnect = conf->disconnect;
- os_memcpy(&das->client_addr, conf->client_addr,
- sizeof(das->client_addr));
- das->shared_secret = os_memdup(conf->shared_secret,
- conf->shared_secret_len);
- if (das->shared_secret == NULL) {
- radius_das_deinit(das);
- return NULL;
- }
- das->shared_secret_len = conf->shared_secret_len;
- das->sock = radius_das_open_socket(conf->port);
- if (das->sock < 0) {
- wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
- "DAS");
- radius_das_deinit(das);
- return NULL;
- }
- if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
- {
- radius_das_deinit(das);
- return NULL;
- }
- return das;
- }
- void radius_das_deinit(struct radius_das_data *das)
- {
- if (das == NULL)
- return;
- if (das->sock >= 0) {
- eloop_unregister_read_sock(das->sock);
- close(das->sock);
- }
- os_free(das->shared_secret);
- os_free(das);
- }
|