fragattacks/src/radius/radius_das.c
Jouni Malinen 0187c41d88 Declare wpa_debug_* variables in src/utils/wpa_debug.h
These were somewhat more hidden to avoid direct use, but there are now
numerous places where these are needed and more justification to make
the extern int declarations available from wpa_debug.h. In addition,
this avoids some warnings from sparse.

Signed-hostap: Jouni Malinen <j@w1.fi>
2013-12-31 19:29:52 +02:00

363 lines
8.4 KiB
C

/*
* 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;
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_CALLING_STATION_ID,
RADIUS_ATTR_ACCT_SESSION_ID,
RADIUS_ATTR_EVENT_TIMESTAMP,
RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
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_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_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_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)) {
wpa_printf(MSG_DEBUG, "DAS: Invalid 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(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->ctx = conf->ctx;
das->disconnect = conf->disconnect;
os_memcpy(&das->client_addr, conf->client_addr,
sizeof(das->client_addr));
das->shared_secret = os_malloc(conf->shared_secret_len);
if (das->shared_secret == NULL) {
radius_das_deinit(das);
return NULL;
}
os_memcpy(das->shared_secret, conf->shared_secret,
conf->shared_secret_len);
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);
}