Home | History | Annotate | Download | only in radius
      1 /*
      2  * RADIUS Dynamic Authorization Server (DAS) (RFC 5176)
      3  * Copyright (c) 2012-2013, Jouni Malinen <j (at) w1.fi>
      4  *
      5  * This software may be distributed under the terms of the BSD license.
      6  * See README for more details.
      7  */
      8 
      9 #include "includes.h"
     10 #include <net/if.h>
     11 
     12 #include "utils/common.h"
     13 #include "utils/eloop.h"
     14 #include "utils/ip_addr.h"
     15 #include "radius.h"
     16 #include "radius_das.h"
     17 
     18 
     19 struct radius_das_data {
     20 	int sock;
     21 	u8 *shared_secret;
     22 	size_t shared_secret_len;
     23 	struct hostapd_ip_addr client_addr;
     24 	unsigned int time_window;
     25 	int require_event_timestamp;
     26 	void *ctx;
     27 	enum radius_das_res (*disconnect)(void *ctx,
     28 					  struct radius_das_attrs *attr);
     29 };
     30 
     31 
     32 static struct radius_msg * radius_das_disconnect(struct radius_das_data *das,
     33 						 struct radius_msg *msg,
     34 						 const char *abuf,
     35 						 int from_port)
     36 {
     37 	struct radius_hdr *hdr;
     38 	struct radius_msg *reply;
     39 	u8 allowed[] = {
     40 		RADIUS_ATTR_USER_NAME,
     41 		RADIUS_ATTR_NAS_IP_ADDRESS,
     42 		RADIUS_ATTR_CALLING_STATION_ID,
     43 		RADIUS_ATTR_NAS_IDENTIFIER,
     44 		RADIUS_ATTR_ACCT_SESSION_ID,
     45 		RADIUS_ATTR_EVENT_TIMESTAMP,
     46 		RADIUS_ATTR_MESSAGE_AUTHENTICATOR,
     47 		RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
     48 #ifdef CONFIG_IPV6
     49 		RADIUS_ATTR_NAS_IPV6_ADDRESS,
     50 #endif /* CONFIG_IPV6 */
     51 		0
     52 	};
     53 	int error = 405;
     54 	u8 attr;
     55 	enum radius_das_res res;
     56 	struct radius_das_attrs attrs;
     57 	u8 *buf;
     58 	size_t len;
     59 	char tmp[100];
     60 	u8 sta_addr[ETH_ALEN];
     61 
     62 	hdr = radius_msg_get_hdr(msg);
     63 
     64 	attr = radius_msg_find_unlisted_attr(msg, allowed);
     65 	if (attr) {
     66 		wpa_printf(MSG_INFO, "DAS: Unsupported attribute %u in "
     67 			   "Disconnect-Request from %s:%d", attr,
     68 			   abuf, from_port);
     69 		error = 401;
     70 		goto fail;
     71 	}
     72 
     73 	os_memset(&attrs, 0, sizeof(attrs));
     74 
     75 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IP_ADDRESS,
     76 				    &buf, &len, NULL) == 0) {
     77 		if (len != 4) {
     78 			wpa_printf(MSG_INFO, "DAS: Invalid NAS-IP-Address from %s:%d",
     79 				   abuf, from_port);
     80 			error = 407;
     81 			goto fail;
     82 		}
     83 		attrs.nas_ip_addr = buf;
     84 	}
     85 
     86 #ifdef CONFIG_IPV6
     87 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IPV6_ADDRESS,
     88 				    &buf, &len, NULL) == 0) {
     89 		if (len != 16) {
     90 			wpa_printf(MSG_INFO, "DAS: Invalid NAS-IPv6-Address from %s:%d",
     91 				   abuf, from_port);
     92 			error = 407;
     93 			goto fail;
     94 		}
     95 		attrs.nas_ipv6_addr = buf;
     96 	}
     97 #endif /* CONFIG_IPV6 */
     98 
     99 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_NAS_IDENTIFIER,
    100 				    &buf, &len, NULL) == 0) {
    101 		attrs.nas_identifier = buf;
    102 		attrs.nas_identifier_len = len;
    103 	}
    104 
    105 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID,
    106 				    &buf, &len, NULL) == 0) {
    107 		if (len >= sizeof(tmp))
    108 			len = sizeof(tmp) - 1;
    109 		os_memcpy(tmp, buf, len);
    110 		tmp[len] = '\0';
    111 		if (hwaddr_aton2(tmp, sta_addr) < 0) {
    112 			wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id "
    113 				   "'%s' from %s:%d", tmp, abuf, from_port);
    114 			error = 407;
    115 			goto fail;
    116 		}
    117 		attrs.sta_addr = sta_addr;
    118 	}
    119 
    120 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME,
    121 				    &buf, &len, NULL) == 0) {
    122 		attrs.user_name = buf;
    123 		attrs.user_name_len = len;
    124 	}
    125 
    126 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID,
    127 				    &buf, &len, NULL) == 0) {
    128 		attrs.acct_session_id = buf;
    129 		attrs.acct_session_id_len = len;
    130 	}
    131 
    132 	if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY,
    133 				    &buf, &len, NULL) == 0) {
    134 		attrs.cui = buf;
    135 		attrs.cui_len = len;
    136 	}
    137 
    138 	res = das->disconnect(das->ctx, &attrs);
    139 	switch (res) {
    140 	case RADIUS_DAS_NAS_MISMATCH:
    141 		wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d",
    142 			   abuf, from_port);
    143 		error = 403;
    144 		break;
    145 	case RADIUS_DAS_SESSION_NOT_FOUND:
    146 		wpa_printf(MSG_INFO, "DAS: Session not found for request from "
    147 			   "%s:%d", abuf, from_port);
    148 		error = 503;
    149 		break;
    150 	case RADIUS_DAS_SUCCESS:
    151 		error = 0;
    152 		break;
    153 	}
    154 
    155 fail:
    156 	reply = radius_msg_new(error ? RADIUS_CODE_DISCONNECT_NAK :
    157 			       RADIUS_CODE_DISCONNECT_ACK, hdr->identifier);
    158 	if (reply == NULL)
    159 		return NULL;
    160 
    161 	if (error) {
    162 		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
    163 					       error)) {
    164 			radius_msg_free(reply);
    165 			return NULL;
    166 		}
    167 	}
    168 
    169 	return reply;
    170 }
    171 
    172 
    173 static void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx)
    174 {
    175 	struct radius_das_data *das = eloop_ctx;
    176 	u8 buf[1500];
    177 	union {
    178 		struct sockaddr_storage ss;
    179 		struct sockaddr_in sin;
    180 #ifdef CONFIG_IPV6
    181 		struct sockaddr_in6 sin6;
    182 #endif /* CONFIG_IPV6 */
    183 	} from;
    184 	char abuf[50];
    185 	int from_port = 0;
    186 	socklen_t fromlen;
    187 	int len;
    188 	struct radius_msg *msg, *reply = NULL;
    189 	struct radius_hdr *hdr;
    190 	struct wpabuf *rbuf;
    191 	u32 val;
    192 	int res;
    193 	struct os_time now;
    194 
    195 	fromlen = sizeof(from);
    196 	len = recvfrom(sock, buf, sizeof(buf), 0,
    197 		       (struct sockaddr *) &from.ss, &fromlen);
    198 	if (len < 0) {
    199 		wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno));
    200 		return;
    201 	}
    202 
    203 	os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf));
    204 	from_port = ntohs(from.sin.sin_port);
    205 
    206 	wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d",
    207 		   len, abuf, from_port);
    208 	if (das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) {
    209 		wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client");
    210 		return;
    211 	}
    212 
    213 	msg = radius_msg_parse(buf, len);
    214 	if (msg == NULL) {
    215 		wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet "
    216 			   "from %s:%d failed", abuf, from_port);
    217 		return;
    218 	}
    219 
    220 	if (wpa_debug_level <= MSG_MSGDUMP)
    221 		radius_msg_dump(msg);
    222 
    223 	if (radius_msg_verify_das_req(msg, das->shared_secret,
    224 				       das->shared_secret_len)) {
    225 		wpa_printf(MSG_DEBUG, "DAS: Invalid authenticator in packet "
    226 			   "from %s:%d - drop", abuf, from_port);
    227 		goto fail;
    228 	}
    229 
    230 	os_get_time(&now);
    231 	res = radius_msg_get_attr(msg, RADIUS_ATTR_EVENT_TIMESTAMP,
    232 				  (u8 *) &val, 4);
    233 	if (res == 4) {
    234 		u32 timestamp = ntohl(val);
    235 		if ((unsigned int) abs(now.sec - timestamp) >
    236 		    das->time_window) {
    237 			wpa_printf(MSG_DEBUG, "DAS: Unacceptable "
    238 				   "Event-Timestamp (%u; local time %u) in "
    239 				   "packet from %s:%d - drop",
    240 				   timestamp, (unsigned int) now.sec,
    241 				   abuf, from_port);
    242 			goto fail;
    243 		}
    244 	} else if (das->require_event_timestamp) {
    245 		wpa_printf(MSG_DEBUG, "DAS: Missing Event-Timestamp in packet "
    246 			   "from %s:%d - drop", abuf, from_port);
    247 		goto fail;
    248 	}
    249 
    250 	hdr = radius_msg_get_hdr(msg);
    251 
    252 	switch (hdr->code) {
    253 	case RADIUS_CODE_DISCONNECT_REQUEST:
    254 		reply = radius_das_disconnect(das, msg, abuf, from_port);
    255 		break;
    256 	case RADIUS_CODE_COA_REQUEST:
    257 		/* TODO */
    258 		reply = radius_msg_new(RADIUS_CODE_COA_NAK,
    259 				       hdr->identifier);
    260 		if (reply == NULL)
    261 			break;
    262 
    263 		/* Unsupported Service */
    264 		if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE,
    265 					       405)) {
    266 			radius_msg_free(reply);
    267 			reply = NULL;
    268 			break;
    269 		}
    270 		break;
    271 	default:
    272 		wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in "
    273 			   "packet from %s:%d",
    274 			   hdr->code, abuf, from_port);
    275 	}
    276 
    277 	if (reply) {
    278 		wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port);
    279 
    280 		if (!radius_msg_add_attr_int32(reply,
    281 					       RADIUS_ATTR_EVENT_TIMESTAMP,
    282 					       now.sec)) {
    283 			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
    284 				   "Event-Timestamp attribute");
    285 		}
    286 
    287 		if (radius_msg_finish_das_resp(reply, das->shared_secret,
    288 					       das->shared_secret_len, hdr) <
    289 		    0) {
    290 			wpa_printf(MSG_DEBUG, "DAS: Failed to add "
    291 				   "Message-Authenticator attribute");
    292 		}
    293 
    294 		if (wpa_debug_level <= MSG_MSGDUMP)
    295 			radius_msg_dump(reply);
    296 
    297 		rbuf = radius_msg_get_buf(reply);
    298 		res = sendto(das->sock, wpabuf_head(rbuf),
    299 			     wpabuf_len(rbuf), 0,
    300 			     (struct sockaddr *) &from.ss, fromlen);
    301 		if (res < 0) {
    302 			wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s",
    303 				   abuf, from_port, strerror(errno));
    304 		}
    305 	}
    306 
    307 fail:
    308 	radius_msg_free(msg);
    309 	radius_msg_free(reply);
    310 }
    311 
    312 
    313 static int radius_das_open_socket(int port)
    314 {
    315 	int s;
    316 	struct sockaddr_in addr;
    317 
    318 	s = socket(PF_INET, SOCK_DGRAM, 0);
    319 	if (s < 0) {
    320 		wpa_printf(MSG_INFO, "RADIUS DAS: socket: %s", strerror(errno));
    321 		return -1;
    322 	}
    323 
    324 	os_memset(&addr, 0, sizeof(addr));
    325 	addr.sin_family = AF_INET;
    326 	addr.sin_port = htons(port);
    327 	if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
    328 		wpa_printf(MSG_INFO, "RADIUS DAS: bind: %s", strerror(errno));
    329 		close(s);
    330 		return -1;
    331 	}
    332 
    333 	return s;
    334 }
    335 
    336 
    337 struct radius_das_data *
    338 radius_das_init(struct radius_das_conf *conf)
    339 {
    340 	struct radius_das_data *das;
    341 
    342 	if (conf->port == 0 || conf->shared_secret == NULL ||
    343 	    conf->client_addr == NULL)
    344 		return NULL;
    345 
    346 	das = os_zalloc(sizeof(*das));
    347 	if (das == NULL)
    348 		return NULL;
    349 
    350 	das->time_window = conf->time_window;
    351 	das->require_event_timestamp = conf->require_event_timestamp;
    352 	das->ctx = conf->ctx;
    353 	das->disconnect = conf->disconnect;
    354 
    355 	os_memcpy(&das->client_addr, conf->client_addr,
    356 		  sizeof(das->client_addr));
    357 
    358 	das->shared_secret = os_malloc(conf->shared_secret_len);
    359 	if (das->shared_secret == NULL) {
    360 		radius_das_deinit(das);
    361 		return NULL;
    362 	}
    363 	os_memcpy(das->shared_secret, conf->shared_secret,
    364 		  conf->shared_secret_len);
    365 	das->shared_secret_len = conf->shared_secret_len;
    366 
    367 	das->sock = radius_das_open_socket(conf->port);
    368 	if (das->sock < 0) {
    369 		wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS "
    370 			   "DAS");
    371 		radius_das_deinit(das);
    372 		return NULL;
    373 	}
    374 
    375 	if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL))
    376 	{
    377 		radius_das_deinit(das);
    378 		return NULL;
    379 	}
    380 
    381 	return das;
    382 }
    383 
    384 
    385 void radius_das_deinit(struct radius_das_data *das)
    386 {
    387 	if (das == NULL)
    388 		return;
    389 
    390 	if (das->sock >= 0) {
    391 		eloop_unregister_read_sock(das->sock);
    392 		close(das->sock);
    393 	}
    394 
    395 	os_free(das->shared_secret);
    396 	os_free(das);
    397 }
    398