1252190Srpaulo/* 2252190Srpaulo * RADIUS Dynamic Authorization Server (DAS) (RFC 5176) 3252190Srpaulo * Copyright (c) 2012, Jouni Malinen <j@w1.fi> 4252190Srpaulo * 5252190Srpaulo * This software may be distributed under the terms of the BSD license. 6252190Srpaulo * See README for more details. 7252190Srpaulo */ 8252190Srpaulo 9252190Srpaulo#include "includes.h" 10252190Srpaulo#include <net/if.h> 11252190Srpaulo 12252190Srpaulo#include "utils/common.h" 13252190Srpaulo#include "utils/eloop.h" 14252190Srpaulo#include "utils/ip_addr.h" 15252190Srpaulo#include "radius.h" 16252190Srpaulo#include "radius_das.h" 17252190Srpaulo 18252190Srpaulo 19252190Srpauloextern int wpa_debug_level; 20252190Srpaulo 21252190Srpaulo 22252190Srpaulostruct radius_das_data { 23252190Srpaulo int sock; 24252190Srpaulo u8 *shared_secret; 25252190Srpaulo size_t shared_secret_len; 26252190Srpaulo struct hostapd_ip_addr client_addr; 27252190Srpaulo unsigned int time_window; 28252190Srpaulo int require_event_timestamp; 29252190Srpaulo void *ctx; 30252190Srpaulo enum radius_das_res (*disconnect)(void *ctx, 31252190Srpaulo struct radius_das_attrs *attr); 32252190Srpaulo}; 33252190Srpaulo 34252190Srpaulo 35252190Srpaulostatic struct radius_msg * radius_das_disconnect(struct radius_das_data *das, 36252190Srpaulo struct radius_msg *msg, 37252190Srpaulo const char *abuf, 38252190Srpaulo int from_port) 39252190Srpaulo{ 40252190Srpaulo struct radius_hdr *hdr; 41252190Srpaulo struct radius_msg *reply; 42252190Srpaulo u8 allowed[] = { 43252190Srpaulo RADIUS_ATTR_USER_NAME, 44252190Srpaulo RADIUS_ATTR_CALLING_STATION_ID, 45252190Srpaulo RADIUS_ATTR_ACCT_SESSION_ID, 46252190Srpaulo RADIUS_ATTR_EVENT_TIMESTAMP, 47252190Srpaulo RADIUS_ATTR_MESSAGE_AUTHENTICATOR, 48252190Srpaulo RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, 49252190Srpaulo 0 50252190Srpaulo }; 51252190Srpaulo int error = 405; 52252190Srpaulo u8 attr; 53252190Srpaulo enum radius_das_res res; 54252190Srpaulo struct radius_das_attrs attrs; 55252190Srpaulo u8 *buf; 56252190Srpaulo size_t len; 57252190Srpaulo char tmp[100]; 58252190Srpaulo u8 sta_addr[ETH_ALEN]; 59252190Srpaulo 60252190Srpaulo hdr = radius_msg_get_hdr(msg); 61252190Srpaulo 62252190Srpaulo attr = radius_msg_find_unlisted_attr(msg, allowed); 63252190Srpaulo if (attr) { 64252190Srpaulo wpa_printf(MSG_INFO, "DAS: Unsupported attribute %u in " 65252190Srpaulo "Disconnect-Request from %s:%d", attr, 66252190Srpaulo abuf, from_port); 67252190Srpaulo error = 401; 68252190Srpaulo goto fail; 69252190Srpaulo } 70252190Srpaulo 71252190Srpaulo os_memset(&attrs, 0, sizeof(attrs)); 72252190Srpaulo 73252190Srpaulo if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CALLING_STATION_ID, 74252190Srpaulo &buf, &len, NULL) == 0) { 75252190Srpaulo if (len >= sizeof(tmp)) 76252190Srpaulo len = sizeof(tmp) - 1; 77252190Srpaulo os_memcpy(tmp, buf, len); 78252190Srpaulo tmp[len] = '\0'; 79252190Srpaulo if (hwaddr_aton2(tmp, sta_addr) < 0) { 80252190Srpaulo wpa_printf(MSG_INFO, "DAS: Invalid Calling-Station-Id " 81252190Srpaulo "'%s' from %s:%d", tmp, abuf, from_port); 82252190Srpaulo error = 407; 83252190Srpaulo goto fail; 84252190Srpaulo } 85252190Srpaulo attrs.sta_addr = sta_addr; 86252190Srpaulo } 87252190Srpaulo 88252190Srpaulo if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_USER_NAME, 89252190Srpaulo &buf, &len, NULL) == 0) { 90252190Srpaulo attrs.user_name = buf; 91252190Srpaulo attrs.user_name_len = len; 92252190Srpaulo } 93252190Srpaulo 94252190Srpaulo if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_ACCT_SESSION_ID, 95252190Srpaulo &buf, &len, NULL) == 0) { 96252190Srpaulo attrs.acct_session_id = buf; 97252190Srpaulo attrs.acct_session_id_len = len; 98252190Srpaulo } 99252190Srpaulo 100252190Srpaulo if (radius_msg_get_attr_ptr(msg, RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, 101252190Srpaulo &buf, &len, NULL) == 0) { 102252190Srpaulo attrs.cui = buf; 103252190Srpaulo attrs.cui_len = len; 104252190Srpaulo } 105252190Srpaulo 106252190Srpaulo res = das->disconnect(das->ctx, &attrs); 107252190Srpaulo switch (res) { 108252190Srpaulo case RADIUS_DAS_NAS_MISMATCH: 109252190Srpaulo wpa_printf(MSG_INFO, "DAS: NAS mismatch from %s:%d", 110252190Srpaulo abuf, from_port); 111252190Srpaulo error = 403; 112252190Srpaulo break; 113252190Srpaulo case RADIUS_DAS_SESSION_NOT_FOUND: 114252190Srpaulo wpa_printf(MSG_INFO, "DAS: Session not found for request from " 115252190Srpaulo "%s:%d", abuf, from_port); 116252190Srpaulo error = 503; 117252190Srpaulo break; 118252190Srpaulo case RADIUS_DAS_SUCCESS: 119252190Srpaulo error = 0; 120252190Srpaulo break; 121252190Srpaulo } 122252190Srpaulo 123252190Srpaulofail: 124252190Srpaulo reply = radius_msg_new(error ? RADIUS_CODE_DISCONNECT_NAK : 125252190Srpaulo RADIUS_CODE_DISCONNECT_ACK, hdr->identifier); 126252190Srpaulo if (reply == NULL) 127252190Srpaulo return NULL; 128252190Srpaulo 129252190Srpaulo if (error) { 130252190Srpaulo if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE, 131252190Srpaulo error)) { 132252190Srpaulo radius_msg_free(reply); 133252190Srpaulo return NULL; 134252190Srpaulo } 135252190Srpaulo } 136252190Srpaulo 137252190Srpaulo return reply; 138252190Srpaulo} 139252190Srpaulo 140252190Srpaulo 141252190Srpaulostatic void radius_das_receive(int sock, void *eloop_ctx, void *sock_ctx) 142252190Srpaulo{ 143252190Srpaulo struct radius_das_data *das = eloop_ctx; 144252190Srpaulo u8 buf[1500]; 145252190Srpaulo union { 146252190Srpaulo struct sockaddr_storage ss; 147252190Srpaulo struct sockaddr_in sin; 148252190Srpaulo#ifdef CONFIG_IPV6 149252190Srpaulo struct sockaddr_in6 sin6; 150252190Srpaulo#endif /* CONFIG_IPV6 */ 151252190Srpaulo } from; 152252190Srpaulo char abuf[50]; 153252190Srpaulo int from_port = 0; 154252190Srpaulo socklen_t fromlen; 155252190Srpaulo int len; 156252190Srpaulo struct radius_msg *msg, *reply = NULL; 157252190Srpaulo struct radius_hdr *hdr; 158252190Srpaulo struct wpabuf *rbuf; 159252190Srpaulo u32 val; 160252190Srpaulo int res; 161252190Srpaulo struct os_time now; 162252190Srpaulo 163252190Srpaulo fromlen = sizeof(from); 164252190Srpaulo len = recvfrom(sock, buf, sizeof(buf), 0, 165252190Srpaulo (struct sockaddr *) &from.ss, &fromlen); 166252190Srpaulo if (len < 0) { 167252190Srpaulo wpa_printf(MSG_ERROR, "DAS: recvfrom: %s", strerror(errno)); 168252190Srpaulo return; 169252190Srpaulo } 170252190Srpaulo 171252190Srpaulo os_strlcpy(abuf, inet_ntoa(from.sin.sin_addr), sizeof(abuf)); 172252190Srpaulo from_port = ntohs(from.sin.sin_port); 173252190Srpaulo 174252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Received %d bytes from %s:%d", 175252190Srpaulo len, abuf, from_port); 176252190Srpaulo if (das->client_addr.u.v4.s_addr != from.sin.sin_addr.s_addr) { 177252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Drop message from unknown client"); 178252190Srpaulo return; 179252190Srpaulo } 180252190Srpaulo 181252190Srpaulo msg = radius_msg_parse(buf, len); 182252190Srpaulo if (msg == NULL) { 183252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Parsing incoming RADIUS packet " 184252190Srpaulo "from %s:%d failed", abuf, from_port); 185252190Srpaulo return; 186252190Srpaulo } 187252190Srpaulo 188252190Srpaulo if (wpa_debug_level <= MSG_MSGDUMP) 189252190Srpaulo radius_msg_dump(msg); 190252190Srpaulo 191252190Srpaulo if (radius_msg_verify_das_req(msg, das->shared_secret, 192252190Srpaulo das->shared_secret_len)) { 193252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Invalid authenticator in packet " 194252190Srpaulo "from %s:%d - drop", abuf, from_port); 195252190Srpaulo goto fail; 196252190Srpaulo } 197252190Srpaulo 198252190Srpaulo os_get_time(&now); 199252190Srpaulo res = radius_msg_get_attr(msg, RADIUS_ATTR_EVENT_TIMESTAMP, 200252190Srpaulo (u8 *) &val, 4); 201252190Srpaulo if (res == 4) { 202252190Srpaulo u32 timestamp = ntohl(val); 203252190Srpaulo if (abs(now.sec - timestamp) > das->time_window) { 204252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Unacceptable " 205252190Srpaulo "Event-Timestamp (%u; local time %u) in " 206252190Srpaulo "packet from %s:%d - drop", 207252190Srpaulo timestamp, (unsigned int) now.sec, 208252190Srpaulo abuf, from_port); 209252190Srpaulo goto fail; 210252190Srpaulo } 211252190Srpaulo } else if (das->require_event_timestamp) { 212252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Missing Event-Timestamp in packet " 213252190Srpaulo "from %s:%d - drop", abuf, from_port); 214252190Srpaulo goto fail; 215252190Srpaulo } 216252190Srpaulo 217252190Srpaulo hdr = radius_msg_get_hdr(msg); 218252190Srpaulo 219252190Srpaulo switch (hdr->code) { 220252190Srpaulo case RADIUS_CODE_DISCONNECT_REQUEST: 221252190Srpaulo reply = radius_das_disconnect(das, msg, abuf, from_port); 222252190Srpaulo break; 223252190Srpaulo case RADIUS_CODE_COA_REQUEST: 224252190Srpaulo /* TODO */ 225252190Srpaulo reply = radius_msg_new(RADIUS_CODE_COA_NAK, 226252190Srpaulo hdr->identifier); 227252190Srpaulo if (reply == NULL) 228252190Srpaulo break; 229252190Srpaulo 230252190Srpaulo /* Unsupported Service */ 231252190Srpaulo if (!radius_msg_add_attr_int32(reply, RADIUS_ATTR_ERROR_CAUSE, 232252190Srpaulo 405)) { 233252190Srpaulo radius_msg_free(reply); 234252190Srpaulo reply = NULL; 235252190Srpaulo break; 236252190Srpaulo } 237252190Srpaulo break; 238252190Srpaulo default: 239252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Unexpected RADIUS code %u in " 240252190Srpaulo "packet from %s:%d", 241252190Srpaulo hdr->code, abuf, from_port); 242252190Srpaulo } 243252190Srpaulo 244252190Srpaulo if (reply) { 245252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Reply to %s:%d", abuf, from_port); 246252190Srpaulo 247252190Srpaulo if (!radius_msg_add_attr_int32(reply, 248252190Srpaulo RADIUS_ATTR_EVENT_TIMESTAMP, 249252190Srpaulo now.sec)) { 250252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Failed to add " 251252190Srpaulo "Event-Timestamp attribute"); 252252190Srpaulo } 253252190Srpaulo 254252190Srpaulo if (radius_msg_finish_das_resp(reply, das->shared_secret, 255252190Srpaulo das->shared_secret_len, hdr) < 256252190Srpaulo 0) { 257252190Srpaulo wpa_printf(MSG_DEBUG, "DAS: Failed to add " 258252190Srpaulo "Message-Authenticator attribute"); 259252190Srpaulo } 260252190Srpaulo 261252190Srpaulo if (wpa_debug_level <= MSG_MSGDUMP) 262252190Srpaulo radius_msg_dump(reply); 263252190Srpaulo 264252190Srpaulo rbuf = radius_msg_get_buf(reply); 265252190Srpaulo res = sendto(das->sock, wpabuf_head(rbuf), 266252190Srpaulo wpabuf_len(rbuf), 0, 267252190Srpaulo (struct sockaddr *) &from.ss, fromlen); 268252190Srpaulo if (res < 0) { 269252190Srpaulo wpa_printf(MSG_ERROR, "DAS: sendto(to %s:%d): %s", 270252190Srpaulo abuf, from_port, strerror(errno)); 271252190Srpaulo } 272252190Srpaulo } 273252190Srpaulo 274252190Srpaulofail: 275252190Srpaulo radius_msg_free(msg); 276252190Srpaulo radius_msg_free(reply); 277252190Srpaulo} 278252190Srpaulo 279252190Srpaulo 280252190Srpaulostatic int radius_das_open_socket(int port) 281252190Srpaulo{ 282252190Srpaulo int s; 283252190Srpaulo struct sockaddr_in addr; 284252190Srpaulo 285252190Srpaulo s = socket(PF_INET, SOCK_DGRAM, 0); 286252190Srpaulo if (s < 0) { 287252190Srpaulo perror("socket"); 288252190Srpaulo return -1; 289252190Srpaulo } 290252190Srpaulo 291252190Srpaulo os_memset(&addr, 0, sizeof(addr)); 292252190Srpaulo addr.sin_family = AF_INET; 293252190Srpaulo addr.sin_port = htons(port); 294252190Srpaulo if (bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) { 295252190Srpaulo perror("bind"); 296252190Srpaulo close(s); 297252190Srpaulo return -1; 298252190Srpaulo } 299252190Srpaulo 300252190Srpaulo return s; 301252190Srpaulo} 302252190Srpaulo 303252190Srpaulo 304252190Srpaulostruct radius_das_data * 305252190Srpauloradius_das_init(struct radius_das_conf *conf) 306252190Srpaulo{ 307252190Srpaulo struct radius_das_data *das; 308252190Srpaulo 309252190Srpaulo if (conf->port == 0 || conf->shared_secret == NULL || 310252190Srpaulo conf->client_addr == NULL) 311252190Srpaulo return NULL; 312252190Srpaulo 313252190Srpaulo das = os_zalloc(sizeof(*das)); 314252190Srpaulo if (das == NULL) 315252190Srpaulo return NULL; 316252190Srpaulo 317252190Srpaulo das->time_window = conf->time_window; 318252190Srpaulo das->require_event_timestamp = conf->require_event_timestamp; 319252190Srpaulo das->ctx = conf->ctx; 320252190Srpaulo das->disconnect = conf->disconnect; 321252190Srpaulo 322252190Srpaulo os_memcpy(&das->client_addr, conf->client_addr, 323252190Srpaulo sizeof(das->client_addr)); 324252190Srpaulo 325252190Srpaulo das->shared_secret = os_malloc(conf->shared_secret_len); 326252190Srpaulo if (das->shared_secret == NULL) { 327252190Srpaulo radius_das_deinit(das); 328252190Srpaulo return NULL; 329252190Srpaulo } 330252190Srpaulo os_memcpy(das->shared_secret, conf->shared_secret, 331252190Srpaulo conf->shared_secret_len); 332252190Srpaulo das->shared_secret_len = conf->shared_secret_len; 333252190Srpaulo 334252190Srpaulo das->sock = radius_das_open_socket(conf->port); 335252190Srpaulo if (das->sock < 0) { 336252190Srpaulo wpa_printf(MSG_ERROR, "Failed to open UDP socket for RADIUS " 337252190Srpaulo "DAS"); 338252190Srpaulo radius_das_deinit(das); 339252190Srpaulo return NULL; 340252190Srpaulo } 341252190Srpaulo 342252190Srpaulo if (eloop_register_read_sock(das->sock, radius_das_receive, das, NULL)) 343252190Srpaulo { 344252190Srpaulo radius_das_deinit(das); 345252190Srpaulo return NULL; 346252190Srpaulo } 347252190Srpaulo 348252190Srpaulo return das; 349252190Srpaulo} 350252190Srpaulo 351252190Srpaulo 352252190Srpaulovoid radius_das_deinit(struct radius_das_data *das) 353252190Srpaulo{ 354252190Srpaulo if (das == NULL) 355252190Srpaulo return; 356252190Srpaulo 357252190Srpaulo if (das->sock >= 0) { 358252190Srpaulo eloop_unregister_read_sock(das->sock); 359252190Srpaulo close(das->sock); 360252190Srpaulo } 361252190Srpaulo 362252190Srpaulo os_free(das->shared_secret); 363252190Srpaulo os_free(das); 364252190Srpaulo} 365