Add preliminary RADIUS dynamic authorization server (RFC 5176)

This adds the basic DAS mechanism to enable hostapd to be configured
to request dynamic authorization requests (Disconnect-Request and
CoA-Request). This commit does not add actual processing of the
requests, i.e., this will only receive and authenticate the requests
and NAK them regardless of what operation is requested.

Signed-hostap: Jouni Malinen <j@w1.fi>
master
Jouni Malinen 12 years ago
parent af35e7af7f
commit b031338cf0

@ -110,6 +110,7 @@ CONFIG_NO_ACCOUNTING=y
else
OBJS += ../src/radius/radius.o
OBJS += ../src/radius/radius_client.o
OBJS += ../src/radius/radius_das.o
endif
ifdef CONFIG_NO_ACCOUNTING

@ -561,6 +561,34 @@ hostapd_parse_radius_attr(const char *value)
return attr;
}
static int hostapd_parse_das_client(struct hostapd_bss_config *bss,
const char *val)
{
char *secret;
size_t len;
secret = os_strchr(val, ' ');
if (secret == NULL)
return -1;
secret++;
len = os_strlen(secret);
if (hostapd_parse_ip_addr(val, &bss->radius_das_client_addr))
return -1;
os_free(bss->radius_das_shared_secret);
bss->radius_das_shared_secret = os_malloc(len);
if (bss->radius_das_shared_secret == NULL)
return -1;
os_memcpy(bss->radius_das_shared_secret, secret, len);
bss->radius_das_shared_secret_len = len;
return 0;
}
#endif /* CONFIG_NO_RADIUS */
@ -1657,6 +1685,14 @@ static int hostapd_config_fill(struct hostapd_config *conf,
a = a->next;
a->next = attr;
}
} else if (os_strcmp(buf, "radius_das_port") == 0) {
bss->radius_das_port = atoi(pos);
} else if (os_strcmp(buf, "radius_das_client") == 0) {
if (hostapd_parse_das_client(bss, pos) < 0) {
wpa_printf(MSG_ERROR, "Line %d: invalid "
"DAS client", line);
errors++;
}
#endif /* CONFIG_NO_RADIUS */
} else if (os_strcmp(buf, "auth_algs") == 0) {
bss->auth_algs = atoi(pos);

@ -696,6 +696,19 @@ own_ip_addr=127.0.0.1
# Operator-Name = "Operator"
#radius_acct_req_attr=126:s:Operator
# Dynamic Authorization Extensions (RFC 5176)
# This mechanism can be used to allow dynamic changes to user session based on
# commands from a RADIUS server (or some other disconnect client that has the
# needed session information). For example, Disconnect message can be used to
# request an associated station to be disconnected.
#
# This is disabled by default. Set radius_das_port to non-zero UDP port
# number to enable.
#radius_das_port=3799
#
# DAS client (the host that can send Disconnect/CoA requests) and shared secret
#radius_das_client=192.168.1.123 shared secret here
##### RADIUS authentication server configuration ##############################
# hostapd can be used as a RADIUS authentication server for other hosts. This

@ -432,6 +432,7 @@ static void hostapd_config_free_bss(struct hostapd_bss_config *conf)
os_free(conf->radius_server_clients);
os_free(conf->test_socket);
os_free(conf->radius);
os_free(conf->radius_das_shared_secret);
hostapd_config_free_vlan(conf);
if (conf->ssid.dyn_vlan_keys) {
struct hostapd_ssid *ssid = &conf->ssid;

@ -186,6 +186,10 @@ struct hostapd_bss_config {
int radius_request_cui;
struct hostapd_radius_attr *radius_auth_req_attr;
struct hostapd_radius_attr *radius_acct_req_attr;
int radius_das_port;
struct hostapd_ip_addr radius_das_client_addr;
u8 *radius_das_shared_secret;
size_t radius_das_shared_secret_len;
struct hostapd_ssid ssid;

@ -1,6 +1,6 @@
/*
* hostapd / Initialization and configuration
* Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
* Copyright (c) 2002-2012, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
@ -12,6 +12,7 @@
#include "utils/eloop.h"
#include "common/ieee802_11_defs.h"
#include "radius/radius_client.h"
#include "radius/radius_das.h"
#include "drivers/driver.h"
#include "hostapd.h"
#include "authsrv.h"
@ -241,6 +242,8 @@ static void hostapd_free_hapd_data(struct hostapd_data *hapd)
#ifndef CONFIG_NO_RADIUS
radius_client_deinit(hapd->radius);
hapd->radius = NULL;
radius_das_deinit(hapd->radius_das);
hapd->radius_das = NULL;
#endif /* CONFIG_NO_RADIUS */
hostapd_deinit_wps(hapd);
@ -627,6 +630,22 @@ static int hostapd_setup_bss(struct hostapd_data *hapd, int first)
wpa_printf(MSG_ERROR, "RADIUS client initialization failed.");
return -1;
}
if (hapd->conf->radius_das_port) {
struct radius_das_conf das_conf;
os_memset(&das_conf, 0, sizeof(das_conf));
das_conf.port = hapd->conf->radius_das_port;
das_conf.shared_secret = hapd->conf->radius_das_shared_secret;
das_conf.shared_secret_len =
hapd->conf->radius_das_shared_secret_len;
das_conf.client_addr = &hapd->conf->radius_das_client_addr;
hapd->radius_das = radius_das_init(&das_conf);
if (hapd->radius_das == NULL) {
wpa_printf(MSG_ERROR, "RADIUS DAS initialization "
"failed.");
return -1;
}
}
#endif /* CONFIG_NO_RADIUS */
if (hostapd_acl_init(hapd)) {

@ -85,6 +85,7 @@ struct hostapd_data {
struct radius_client_data *radius;
u32 acct_session_id_hi, acct_session_id_lo;
struct radius_das_data *radius_das;
struct iapp_data *iapp;

@ -1,6 +1,6 @@
/*
* RADIUS message processing
* Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
* Copyright (c) 2002-2009, 2011-2012, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
@ -147,6 +147,12 @@ static const char *radius_code_string(u8 code)
case RADIUS_CODE_STATUS_SERVER: return "Status-Server";
case RADIUS_CODE_STATUS_CLIENT: return "Status-Client";
case RADIUS_CODE_RESERVED: return "Reserved";
case RADIUS_CODE_DISCONNECT_REQUEST: return "Disconnect-Request";
case RADIUS_CODE_DISCONNECT_ACK: return "Disconnect-ACK";
case RADIUS_CODE_DISCONNECT_NAK: return "Disconnect-NAK";
case RADIUS_CODE_COA_REQUEST: return "CoA-Request";
case RADIUS_CODE_COA_ACK: return "CoA-ACK";
case RADIUS_CODE_COA_NAK: return "CoA-NAK";
default: return "?Unknown?";
}
}
@ -225,6 +231,7 @@ static struct radius_attr_type radius_attrs[] =
{ RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargable-User-Identity",
RADIUS_ATTR_TEXT },
{ RADIUS_ATTR_NAS_IPV6_ADDRESS, "NAS-IPv6-Address", RADIUS_ATTR_IPV6 },
{ RADIUS_ATTR_ERROR_CAUSE, "Error-Cause", RADIUS_ATTR_INT32 }
};
#define RADIUS_ATTRS (sizeof(radius_attrs) / sizeof(radius_attrs[0]))
@ -406,6 +413,45 @@ int radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret,
}
int radius_msg_finish_das_resp(struct radius_msg *msg, const u8 *secret,
size_t secret_len,
const struct radius_hdr *req_hdr)
{
const u8 *addr[2];
size_t len[2];
u8 auth[MD5_MAC_LEN];
struct radius_attr_hdr *attr;
os_memset(auth, 0, MD5_MAC_LEN);
attr = radius_msg_add_attr(msg, RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
auth, MD5_MAC_LEN);
if (attr == NULL) {
wpa_printf(MSG_WARNING, "Could not add Message-Authenticator");
return -1;
}
msg->hdr->length = htons(wpabuf_len(msg->buf));
os_memcpy(msg->hdr->authenticator, req_hdr->authenticator, 16);
hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
wpabuf_len(msg->buf), (u8 *) (attr + 1));
/* ResponseAuth = MD5(Code+ID+Length+RequestAuth+Attributes+Secret) */
addr[0] = wpabuf_head_u8(msg->buf);
len[0] = wpabuf_len(msg->buf);
addr[1] = secret;
len[1] = secret_len;
if (md5_vector(2, addr, len, msg->hdr->authenticator) < 0)
return -1;
if (wpabuf_len(msg->buf) > 0xffff) {
wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)",
(unsigned long) wpabuf_len(msg->buf));
return -1;
}
return 0;
}
void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
size_t secret_len)
{
@ -427,6 +473,88 @@ void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
}
int radius_msg_verify_acct_req(struct radius_msg *msg, const u8 *secret,
size_t secret_len)
{
const u8 *addr[4];
size_t len[4];
u8 zero[MD5_MAC_LEN];
u8 hash[MD5_MAC_LEN];
os_memset(zero, 0, sizeof(zero));
addr[0] = (u8 *) msg->hdr;
len[0] = sizeof(struct radius_hdr) - MD5_MAC_LEN;
addr[1] = zero;
len[1] = MD5_MAC_LEN;
addr[2] = (u8 *) (msg->hdr + 1);
len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
addr[3] = secret;
len[3] = secret_len;
md5_vector(4, addr, len, hash);
return os_memcmp(msg->hdr->authenticator, hash, MD5_MAC_LEN) != 0;
}
int radius_msg_verify_das_req(struct radius_msg *msg, const u8 *secret,
size_t secret_len)
{
const u8 *addr[4];
size_t len[4];
u8 zero[MD5_MAC_LEN];
u8 hash[MD5_MAC_LEN];
u8 auth[MD5_MAC_LEN], orig[MD5_MAC_LEN];
u8 orig_authenticator[16];
struct radius_attr_hdr *attr = NULL, *tmp;
size_t i;
os_memset(zero, 0, sizeof(zero));
addr[0] = (u8 *) msg->hdr;
len[0] = sizeof(struct radius_hdr) - MD5_MAC_LEN;
addr[1] = zero;
len[1] = MD5_MAC_LEN;
addr[2] = (u8 *) (msg->hdr + 1);
len[2] = wpabuf_len(msg->buf) - sizeof(struct radius_hdr);
addr[3] = secret;
len[3] = secret_len;
md5_vector(4, addr, len, hash);
if (os_memcmp(msg->hdr->authenticator, hash, MD5_MAC_LEN) != 0)
return 1;
for (i = 0; i < msg->attr_used; i++) {
tmp = radius_get_attr_hdr(msg, i);
if (tmp->type == RADIUS_ATTR_MESSAGE_AUTHENTICATOR) {
if (attr != NULL) {
wpa_printf(MSG_WARNING, "Multiple "
"Message-Authenticator attributes "
"in RADIUS message");
return 1;
}
attr = tmp;
}
}
if (attr == NULL) {
/* Message-Authenticator is MAY; not required */
return 0;
}
os_memcpy(orig, attr + 1, MD5_MAC_LEN);
os_memset(attr + 1, 0, MD5_MAC_LEN);
os_memcpy(orig_authenticator, msg->hdr->authenticator,
sizeof(orig_authenticator));
os_memset(msg->hdr->authenticator, 0,
sizeof(msg->hdr->authenticator));
hmac_md5(secret, secret_len, wpabuf_head(msg->buf),
wpabuf_len(msg->buf), auth);
os_memcpy(attr + 1, orig, MD5_MAC_LEN);
os_memcpy(msg->hdr->authenticator, orig_authenticator,
sizeof(orig_authenticator));
return os_memcmp(orig, auth, MD5_MAC_LEN) != 0;
}
static int radius_msg_add_attr_to_array(struct radius_msg *msg,
struct radius_attr_hdr *attr)
{

@ -1,6 +1,6 @@
/*
* RADIUS message processing
* Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi>
* Copyright (c) 2002-2009, 2012, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
@ -31,6 +31,12 @@ enum { RADIUS_CODE_ACCESS_REQUEST = 1,
RADIUS_CODE_ACCESS_CHALLENGE = 11,
RADIUS_CODE_STATUS_SERVER = 12,
RADIUS_CODE_STATUS_CLIENT = 13,
RADIUS_CODE_DISCONNECT_REQUEST = 40,
RADIUS_CODE_DISCONNECT_ACK = 41,
RADIUS_CODE_DISCONNECT_NAK = 42,
RADIUS_CODE_COA_REQUEST = 43,
RADIUS_CODE_COA_ACK = 44,
RADIUS_CODE_COA_NAK = 45,
RADIUS_CODE_RESERVED = 255
};
@ -83,7 +89,8 @@ enum { RADIUS_ATTR_USER_NAME = 1,
RADIUS_ATTR_TUNNEL_PRIVATE_GROUP_ID = 81,
RADIUS_ATTR_ACCT_INTERIM_INTERVAL = 85,
RADIUS_ATTR_CHARGEABLE_USER_IDENTITY = 89,
RADIUS_ATTR_NAS_IPV6_ADDRESS = 95
RADIUS_ATTR_NAS_IPV6_ADDRESS = 95,
RADIUS_ATTR_ERROR_CAUSE = 101
};
@ -192,8 +199,15 @@ int radius_msg_finish(struct radius_msg *msg, const u8 *secret,
size_t secret_len);
int radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret,
size_t secret_len, const u8 *req_authenticator);
int radius_msg_finish_das_resp(struct radius_msg *msg, const u8 *secret,
size_t secret_len,
const struct radius_hdr *req_hdr);
void radius_msg_finish_acct(struct radius_msg *msg, const u8 *secret,
size_t secret_len);
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,
size_t secret_len);
struct radius_attr_hdr * radius_msg_add_attr(struct radius_msg *msg, u8 type,
const u8 *data, size_t data_len);
struct radius_msg * radius_msg_parse(const u8 *data, size_t len);

@ -0,0 +1,222 @@
/*
* RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
* Copyright (c) 2012, 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"
extern int wpa_debug_level;
struct radius_das_data {
int sock;
u8 *shared_secret;
size_t shared_secret_len;
struct hostapd_ip_addr client_addr;
};
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;
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;
}
hdr = radius_msg_get_hdr(msg);
switch (hdr->code) {
case RADIUS_CODE_DISCONNECT_REQUEST:
/* TODO */
reply = radius_msg_new(RADIUS_CODE_DISCONNECT_NAK,
hdr->identifier);
if (reply == NULL)
break;
radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE, 405);
break;
case RADIUS_CODE_COA_REQUEST:
/* TODO */
reply = radius_msg_new(RADIUS_CODE_COA_NAK,
hdr->identifier);
if (reply == NULL)
break;
/* Unsupported Service */
radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE, 405);
break;
default:
wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in "
"packet from %s:%d",
hdr->code, abuf, from_port);
}
if (reply) {
int res;
wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
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) {
perror("socket");
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) {
perror("bind");
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;
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);
}

@ -0,0 +1,26 @@
/*
* RADIUS Dynamic Authorization Server (DAS)
* Copyright (c) 2012, Jouni Malinen <j@w1.fi>
*
* This software may be distributed under the terms of the BSD license.
* See README for more details.
*/
#ifndef RADIUS_DAS_H
#define RADIUS_DAS_H
struct radius_das_data;
struct radius_das_conf {
int port;
const u8 *shared_secret;
size_t shared_secret_len;
const struct hostapd_ip_addr *client_addr;
};
struct radius_das_data *
radius_das_init(struct radius_das_conf *conf);
void radius_das_deinit(struct radius_das_data *data);
#endif /* RADIUS_DAS_H */
Loading…
Cancel
Save