diff options
Diffstat (limited to 'src/radius')
-rw-r--r-- | src/radius/radius.c | 341 | ||||
-rw-r--r-- | src/radius/radius.h | 38 | ||||
-rw-r--r-- | src/radius/radius_client.c | 26 | ||||
-rw-r--r-- | src/radius/radius_client.h | 12 | ||||
-rw-r--r-- | src/radius/radius_das.c | 364 | ||||
-rw-r--r-- | src/radius/radius_das.h | 47 | ||||
-rw-r--r-- | src/radius/radius_server.c | 63 | ||||
-rw-r--r-- | src/radius/radius_server.h | 23 |
8 files changed, 816 insertions, 98 deletions
diff --git a/src/radius/radius.c b/src/radius/radius.c index 70754ef5dd725..d1feec96842f5 100644 --- a/src/radius/radius.c +++ b/src/radius/radius.c @@ -1,15 +1,9 @@ /* * RADIUS message processing - * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi> + * Copyright (c) 2002-2009, 2011-2012, Jouni Malinen <j@w1.fi> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. + * This software may be distributed under the terms of the BSD license. + * See README for more details. */ #include "utils/includes.h" @@ -84,8 +78,8 @@ static void radius_msg_set_hdr(struct radius_msg *msg, u8 code, u8 identifier) static int radius_msg_initialize(struct radius_msg *msg) { - msg->attr_pos = - os_zalloc(RADIUS_DEFAULT_ATTR_COUNT * sizeof(*msg->attr_pos)); + msg->attr_pos = os_calloc(RADIUS_DEFAULT_ATTR_COUNT, + sizeof(*msg->attr_pos)); if (msg->attr_pos == NULL) return -1; @@ -153,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?"; } } @@ -218,6 +218,8 @@ static struct radius_attr_type radius_attrs[] = { RADIUS_ATTR_TUNNEL_TYPE, "Tunnel-Type", RADIUS_ATTR_HEXDUMP }, { RADIUS_ATTR_TUNNEL_MEDIUM_TYPE, "Tunnel-Medium-Type", RADIUS_ATTR_HEXDUMP }, + { RADIUS_ATTR_TUNNEL_PASSWORD, "Tunnel-Password", + RADIUS_ATTR_UNDIST }, { RADIUS_ATTR_CONNECT_INFO, "Connect-Info", RADIUS_ATTR_TEXT }, { RADIUS_ATTR_EAP_MESSAGE, "EAP-Message", RADIUS_ATTR_UNDIST }, { RADIUS_ATTR_MESSAGE_AUTHENTICATOR, "Message-Authenticator", @@ -226,9 +228,10 @@ static struct radius_attr_type radius_attrs[] = RADIUS_ATTR_HEXDUMP }, { RADIUS_ATTR_ACCT_INTERIM_INTERVAL, "Acct-Interim-Interval", RADIUS_ATTR_INT32 }, - { RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargable-User-Identity", + { RADIUS_ATTR_CHARGEABLE_USER_IDENTITY, "Chargeable-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])) @@ -266,7 +269,7 @@ static void radius_msg_dump_attr(struct radius_attr_hdr *hdr) printf(" Attribute %d (%s) length=%d\n", hdr->type, attr ? attr->name : "?Unknown?", hdr->length); - if (attr == NULL) + if (attr == NULL || hdr->length < sizeof(struct radius_attr_hdr)) return; len = hdr->length - sizeof(struct radius_attr_hdr); @@ -329,7 +332,7 @@ void radius_msg_dump(struct radius_msg *msg) printf("RADIUS message: code=%d (%s) identifier=%d length=%d\n", msg->hdr->code, radius_code_string(msg->hdr->code), - msg->hdr->identifier, ntohs(msg->hdr->length)); + msg->hdr->identifier, be_to_host16(msg->hdr->length)); for (i = 0; i < msg->attr_used; i++) { struct radius_attr_hdr *attr = radius_get_attr_hdr(msg, i); @@ -354,11 +357,11 @@ int radius_msg_finish(struct radius_msg *msg, const u8 *secret, "Message-Authenticator"); return -1; } - msg->hdr->length = htons(wpabuf_len(msg->buf)); + msg->hdr->length = host_to_be16(wpabuf_len(msg->buf)); hmac_md5(secret, secret_len, wpabuf_head(msg->buf), wpabuf_len(msg->buf), (u8 *) (attr + 1)); } else - msg->hdr->length = htons(wpabuf_len(msg->buf)); + msg->hdr->length = host_to_be16(wpabuf_len(msg->buf)); if (wpabuf_len(msg->buf) > 0xffff) { wpa_printf(MSG_WARNING, "RADIUS: Too long message (%lu)", @@ -384,7 +387,7 @@ int radius_msg_finish_srv(struct radius_msg *msg, const u8 *secret, printf("WARNING: Could not add Message-Authenticator\n"); return -1; } - msg->hdr->length = htons(wpabuf_len(msg->buf)); + msg->hdr->length = host_to_be16(wpabuf_len(msg->buf)); os_memcpy(msg->hdr->authenticator, req_authenticator, sizeof(msg->hdr->authenticator)); hmac_md5(secret, secret_len, wpabuf_head(msg->buf), @@ -410,13 +413,52 @@ 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 = host_to_be16(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) { const u8 *addr[2]; size_t len[2]; - msg->hdr->length = htons(wpabuf_len(msg->buf)); + msg->hdr->length = host_to_be16(wpabuf_len(msg->buf)); os_memset(msg->hdr->authenticator, 0, MD5_MAC_LEN); addr[0] = wpabuf_head(msg->buf); len[0] = wpabuf_len(msg->buf); @@ -431,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) { @@ -438,8 +562,8 @@ static int radius_msg_add_attr_to_array(struct radius_msg *msg, size_t *nattr_pos; int nlen = msg->attr_size * 2; - nattr_pos = os_realloc(msg->attr_pos, - nlen * sizeof(*msg->attr_pos)); + nattr_pos = os_realloc_array(msg->attr_pos, nlen, + sizeof(*msg->attr_pos)); if (nattr_pos == NULL) return -1; @@ -509,7 +633,7 @@ struct radius_msg * radius_msg_parse(const u8 *data, size_t len) hdr = (struct radius_hdr *) data; - msg_len = ntohs(hdr->length); + msg_len = be_to_host16(hdr->length); if (msg_len < sizeof(*hdr) || msg_len > len) { wpa_printf(MSG_INFO, "RADIUS: Invalid message length"); return NULL; @@ -583,9 +707,9 @@ int radius_msg_add_eap(struct radius_msg *msg, const u8 *data, size_t data_len) } -u8 *radius_msg_get_eap(struct radius_msg *msg, size_t *eap_len) +struct wpabuf * radius_msg_get_eap(struct radius_msg *msg) { - u8 *eap, *pos; + struct wpabuf *eap; size_t len, i; struct radius_attr_hdr *attr; @@ -595,30 +719,27 @@ u8 *radius_msg_get_eap(struct radius_msg *msg, size_t *eap_len) len = 0; for (i = 0; i < msg->attr_used; i++) { attr = radius_get_attr_hdr(msg, i); - if (attr->type == RADIUS_ATTR_EAP_MESSAGE) + if (attr->type == RADIUS_ATTR_EAP_MESSAGE && + attr->length > sizeof(struct radius_attr_hdr)) len += attr->length - sizeof(struct radius_attr_hdr); } if (len == 0) return NULL; - eap = os_malloc(len); + eap = wpabuf_alloc(len); if (eap == NULL) return NULL; - pos = eap; for (i = 0; i < msg->attr_used; i++) { attr = radius_get_attr_hdr(msg, i); - if (attr->type == RADIUS_ATTR_EAP_MESSAGE) { + if (attr->type == RADIUS_ATTR_EAP_MESSAGE && + attr->length > sizeof(struct radius_attr_hdr)) { int flen = attr->length - sizeof(*attr); - os_memcpy(pos, attr + 1, flen); - pos += flen; + wpabuf_put_data(eap, attr + 1, flen); } } - if (eap_len) - *eap_len = len; - return eap; } @@ -719,7 +840,7 @@ int radius_msg_copy_attr(struct radius_msg *dst, struct radius_msg *src, for (i = 0; i < src->attr_used; i++) { attr = radius_get_attr_hdr(src, i); - if (attr->type == type) { + if (attr->type == type && attr->length >= sizeof(*attr)) { if (!radius_msg_add_attr(dst, type, (u8 *) (attr + 1), attr->length - sizeof(*attr))) return -1; @@ -776,7 +897,8 @@ static u8 *radius_msg_get_vendor_attr(struct radius_msg *msg, u32 vendor, u32 vendor_id; struct radius_attr_vendor *vhdr; - if (attr->type != RADIUS_ATTR_VENDOR_SPECIFIC) + if (attr->type != RADIUS_ATTR_VENDOR_SPECIFIC || + attr->length < sizeof(*attr)) continue; left = attr->length - sizeof(*attr); @@ -1090,8 +1212,7 @@ radius_msg_add_attr_user_password(struct radius_msg *msg, const u8 *secret, size_t secret_len) { u8 buf[128]; - int padlen, i; - size_t buf_len, pos; + size_t padlen, i, buf_len, pos; const u8 *addr[2]; size_t len[2]; u8 hash[16]; @@ -1103,7 +1224,7 @@ radius_msg_add_attr_user_password(struct radius_msg *msg, buf_len = data_len; padlen = data_len % 16; - if (padlen) { + if (padlen && data_len < sizeof(buf)) { padlen = 16 - padlen; os_memset(buf + data_len, 0, padlen); buf_len += padlen; @@ -1150,7 +1271,7 @@ int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len) } } - if (!attr) + if (!attr || attr->length < sizeof(*attr)) return -1; dlen = attr->length - sizeof(*attr); @@ -1175,7 +1296,7 @@ int radius_msg_get_attr_ptr(struct radius_msg *msg, u8 type, u8 **buf, } } - if (!attr) + if (!attr || attr->length < sizeof(*attr)) return -1; *buf = (u8 *) (attr + 1); @@ -1226,6 +1347,8 @@ int radius_msg_get_vlanid(struct radius_msg *msg) for (i = 0; i < msg->attr_used; i++) { attr = radius_get_attr_hdr(msg, i); + if (attr->length < sizeof(*attr)) + return -1; data = (const u8 *) (attr + 1); dlen = attr->length - sizeof(*attr); if (attr->length < 3) @@ -1276,6 +1399,123 @@ int radius_msg_get_vlanid(struct radius_msg *msg) } +/** + * radius_msg_get_tunnel_password - Parse RADIUS attribute Tunnel-Password + * @msg: Received RADIUS message + * @keylen: Length of returned password + * @secret: RADIUS shared secret + * @secret_len: Length of secret + * @sent_msg: Sent RADIUS message + * @n: Number of password attribute to return (starting with 0) + * Returns: Pointer to n-th password (free with os_free) or %NULL + */ +char * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen, + const u8 *secret, size_t secret_len, + struct radius_msg *sent_msg, size_t n) +{ + u8 *buf = NULL; + size_t buflen; + const u8 *salt; + u8 *str; + const u8 *addr[3]; + size_t len[3]; + u8 hash[16]; + u8 *pos; + size_t i, j = 0; + struct radius_attr_hdr *attr; + const u8 *data; + size_t dlen; + const u8 *fdata = NULL; /* points to found item */ + size_t fdlen = -1; + char *ret = NULL; + + /* find n-th valid Tunnel-Password attribute */ + for (i = 0; i < msg->attr_used; i++) { + attr = radius_get_attr_hdr(msg, i); + if (attr == NULL || + attr->type != RADIUS_ATTR_TUNNEL_PASSWORD) { + continue; + } + if (attr->length <= 5) + continue; + data = (const u8 *) (attr + 1); + dlen = attr->length - sizeof(*attr); + if (dlen <= 3 || dlen % 16 != 3) + continue; + j++; + if (j <= n) + continue; + + fdata = data; + fdlen = dlen; + break; + } + if (fdata == NULL) + goto out; + + /* alloc writable memory for decryption */ + buf = os_malloc(fdlen); + if (buf == NULL) + goto out; + os_memcpy(buf, fdata, fdlen); + buflen = fdlen; + + /* init pointers */ + salt = buf + 1; + str = buf + 3; + + /* decrypt blocks */ + pos = buf + buflen - 16; /* last block */ + while (pos >= str + 16) { /* all but the first block */ + addr[0] = secret; + len[0] = secret_len; + addr[1] = pos - 16; + len[1] = 16; + md5_vector(2, addr, len, hash); + + for (i = 0; i < 16; i++) + pos[i] ^= hash[i]; + + pos -= 16; + } + + /* decrypt first block */ + if (str != pos) + goto out; + addr[0] = secret; + len[0] = secret_len; + addr[1] = sent_msg->hdr->authenticator; + len[1] = 16; + addr[2] = salt; + len[2] = 2; + md5_vector(3, addr, len, hash); + + for (i = 0; i < 16; i++) + pos[i] ^= hash[i]; + + /* derive plaintext length from first subfield */ + *keylen = (unsigned char) str[0]; + if ((u8 *) (str + *keylen) >= (u8 *) (buf + buflen)) { + /* decryption error - invalid key length */ + goto out; + } + if (*keylen == 0) { + /* empty password */ + goto out; + } + + /* copy passphrase into new buffer */ + ret = os_malloc(*keylen); + if (ret) + os_memcpy(ret, str + 1, *keylen); + +out: + /* return new buffer */ + os_free(buf); + return ret; +} + + void radius_free_class(struct radius_class_data *c) { size_t i; @@ -1297,7 +1537,7 @@ int radius_copy_class(struct radius_class_data *dst, if (src->attr == NULL) return 0; - dst->attr = os_zalloc(src->count * sizeof(struct radius_attr_data)); + dst->attr = os_calloc(src->count, sizeof(struct radius_attr_data)); if (dst->attr == NULL) return -1; @@ -1315,3 +1555,24 @@ int radius_copy_class(struct radius_class_data *dst, return 0; } + + +u8 radius_msg_find_unlisted_attr(struct radius_msg *msg, u8 *attrs) +{ + size_t i, j; + struct radius_attr_hdr *attr; + + for (i = 0; i < msg->attr_used; i++) { + attr = radius_get_attr_hdr(msg, i); + + for (j = 0; attrs[j]; j++) { + if (attr->type == attrs[j]) + break; + } + + if (attrs[j] == 0) + return attr->type; /* unlisted attr */ + } + + return 0; +} diff --git a/src/radius/radius.h b/src/radius/radius.h index a3cdac0dac0aa..2031054b1d231 100644 --- a/src/radius/radius.h +++ b/src/radius/radius.h @@ -1,15 +1,9 @@ /* * RADIUS message processing - * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi> + * Copyright (c) 2002-2009, 2012, Jouni Malinen <j@w1.fi> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. + * This software may be distributed under the terms of the BSD license. + * See README for more details. */ #ifndef RADIUS_H @@ -24,7 +18,7 @@ struct radius_hdr { u8 code; u8 identifier; - u16 length; /* including this header */ + be16 length; /* including this header */ u8 authenticator[16]; /* followed by length-20 octets of attributes */ } STRUCT_PACKED; @@ -37,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 }; @@ -82,13 +82,15 @@ enum { RADIUS_ATTR_USER_NAME = 1, RADIUS_ATTR_NAS_PORT_TYPE = 61, RADIUS_ATTR_TUNNEL_TYPE = 64, RADIUS_ATTR_TUNNEL_MEDIUM_TYPE = 65, + RADIUS_ATTR_TUNNEL_PASSWORD = 69, RADIUS_ATTR_CONNECT_INFO = 77, RADIUS_ATTR_EAP_MESSAGE = 79, RADIUS_ATTR_MESSAGE_AUTHENTICATOR = 80, 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 }; @@ -197,14 +199,21 @@ 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); int radius_msg_add_eap(struct radius_msg *msg, const u8 *data, size_t data_len); -u8 *radius_msg_get_eap(struct radius_msg *msg, size_t *len); +struct wpabuf * radius_msg_get_eap(struct radius_msg *msg); int radius_msg_verify(struct radius_msg *msg, const u8 *secret, size_t secret_len, struct radius_msg *sent_msg, int auth); @@ -231,6 +240,9 @@ radius_msg_add_attr_user_password(struct radius_msg *msg, const u8 *secret, size_t secret_len); int radius_msg_get_attr(struct radius_msg *msg, u8 type, u8 *buf, size_t len); int radius_msg_get_vlanid(struct radius_msg *msg); +char * radius_msg_get_tunnel_password(struct radius_msg *msg, int *keylen, + const u8 *secret, size_t secret_len, + struct radius_msg *sent_msg, size_t n); static inline int radius_msg_add_attr_int32(struct radius_msg *msg, u8 type, u32 value) @@ -270,4 +282,6 @@ void radius_free_class(struct radius_class_data *c); int radius_copy_class(struct radius_class_data *dst, const struct radius_class_data *src); +u8 radius_msg_find_unlisted_attr(struct radius_msg *msg, u8 *attrs); + #endif /* RADIUS_H */ diff --git a/src/radius/radius_client.c b/src/radius/radius_client.c index 171af2927b0fb..425ad935afbfe 100644 --- a/src/radius/radius_client.c +++ b/src/radius/radius_client.c @@ -2,14 +2,8 @@ * RADIUS client * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. + * This software may be distributed under the terms of the BSD license. + * See README for more details. */ #include "includes.h" @@ -287,8 +281,8 @@ int radius_client_register(struct radius_client_data *radius, num = &radius->num_auth_handlers; } - newh = os_realloc(*handlers, - (*num + 1) * sizeof(struct radius_rx_handler)); + newh = os_realloc_array(*handlers, *num + 1, + sizeof(struct radius_rx_handler)); if (newh == NULL) return -1; @@ -511,7 +505,7 @@ static void radius_client_update_timeout(struct radius_client_data *radius) NULL); hostapd_logger(radius->ctx, NULL, HOSTAPD_MODULE_RADIUS, HOSTAPD_LEVEL_DEBUG, "Next RADIUS client retransmit in" - " %ld seconds\n", (long int) (first - now.sec)); + " %ld seconds", (long int) (first - now.sec)); } @@ -684,7 +678,7 @@ int radius_client_send(struct radius_client_data *radius, radius_client_list_add(radius, msg, msg_type, shared_secret, shared_secret_len, addr); - return res; + return 0; } @@ -1489,3 +1483,11 @@ int radius_client_get_mib(struct radius_client_data *radius, char *buf, return count; } + + +void radius_client_reconfig(struct radius_client_data *radius, + struct hostapd_radius_servers *conf) +{ + if (radius) + radius->conf = conf; +} diff --git a/src/radius/radius_client.h b/src/radius/radius_client.h index 644ea234fd253..3db16aa282ba7 100644 --- a/src/radius/radius_client.h +++ b/src/radius/radius_client.h @@ -2,14 +2,8 @@ * RADIUS client * Copyright (c) 2002-2009, Jouni Malinen <j@w1.fi> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. + * This software may be distributed under the terms of the BSD license. + * See README for more details. */ #ifndef RADIUS_CLIENT_H @@ -259,5 +253,7 @@ void radius_client_flush_auth(struct radius_client_data *radius, const u8 *addr); int radius_client_get_mib(struct radius_client_data *radius, char *buf, size_t buflen); +void radius_client_reconfig(struct radius_client_data *radius, + struct hostapd_radius_servers *conf); #endif /* RADIUS_CLIENT_H */ diff --git a/src/radius/radius_das.c b/src/radius/radius_das.c new file mode 100644 index 0000000000000..bded96519929e --- /dev/null +++ b/src/radius/radius_das.c @@ -0,0 +1,364 @@ +/* + * 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; + 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 (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) { + 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; + + 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); +} diff --git a/src/radius/radius_das.h b/src/radius/radius_das.h new file mode 100644 index 0000000000000..738b18b059d63 --- /dev/null +++ b/src/radius/radius_das.h @@ -0,0 +1,47 @@ +/* + * 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; + +enum radius_das_res { + RADIUS_DAS_SUCCESS, + RADIUS_DAS_NAS_MISMATCH, + RADIUS_DAS_SESSION_NOT_FOUND +}; + +struct radius_das_attrs { + const u8 *sta_addr; + const u8 *user_name; + size_t user_name_len; + const u8 *acct_session_id; + size_t acct_session_id_len; + const u8 *cui; + size_t cui_len; +}; + +struct radius_das_conf { + int port; + const u8 *shared_secret; + size_t shared_secret_len; + const 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); +}; + +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 */ diff --git a/src/radius/radius_server.c b/src/radius/radius_server.c index f8780a6929476..5b2d711118109 100644 --- a/src/radius/radius_server.c +++ b/src/radius/radius_server.c @@ -1,15 +1,9 @@ /* * RADIUS authentication server - * Copyright (c) 2005-2009, Jouni Malinen <j@w1.fi> + * Copyright (c) 2005-2009, 2011, Jouni Malinen <j@w1.fi> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. + * This software may be distributed under the terms of the BSD license. + * See README for more details. */ #include "includes.h" @@ -222,6 +216,13 @@ struct radius_server_data { int tnc; /** + * pwd_group - The D-H group assigned for EAP-pwd + * + * If EAP-pwd is not used it can be set to zero. + */ + u16 pwd_group; + + /** * wps - Wi-Fi Protected Setup context * * If WPS is used with an external RADIUS server (which is quite @@ -285,6 +286,10 @@ struct radius_server_data { * msg_ctx - Context data for wpa_msg() calls */ void *msg_ctx; + +#ifdef CONFIG_RADIUS_TEST + char *dump_msk_file; +#endif /* CONFIG_RADIUS_TEST */ }; @@ -505,6 +510,7 @@ radius_server_get_new_session(struct radius_server_data *data, eap_conf.eap_sim_aka_result_ind = data->eap_sim_aka_result_ind; eap_conf.tnc = data->tnc; eap_conf.wps = data->wps; + eap_conf.pwd_group = data->pwd_group; sess->eap = eap_server_sm_init(sess, &radius_server_eapol_cb, &eap_conf); if (sess->eap == NULL) { @@ -566,6 +572,24 @@ radius_server_encapsulate_eap(struct radius_server_data *data, if (code == RADIUS_CODE_ACCESS_ACCEPT && sess->eap_if->eapKeyData) { int len; +#ifdef CONFIG_RADIUS_TEST + if (data->dump_msk_file) { + FILE *f; + char buf[2 * 64 + 1]; + f = fopen(data->dump_msk_file, "a"); + if (f) { + len = sess->eap_if->eapKeyDataLen; + if (len > 64) + len = 64; + len = wpa_snprintf_hex( + buf, sizeof(buf), + sess->eap_if->eapKeyData, len); + buf[len] = '\0'; + fprintf(f, "%s\n", buf); + fclose(f); + } + } +#endif /* CONFIG_RADIUS_TEST */ if (sess->eap_if->eapKeyDataLen > 64) { len = 32; } else { @@ -665,8 +689,7 @@ static int radius_server_request(struct radius_server_data *data, const char *from_addr, int from_port, struct radius_session *force_sess) { - u8 *eap = NULL; - size_t eap_len; + struct wpabuf *eap = NULL; int res, state_included = 0; u8 statebuf[4]; unsigned int state; @@ -730,7 +753,7 @@ static int radius_server_request(struct radius_server_data *data, return -1; } - eap = radius_msg_get_eap(msg, &eap_len); + eap = radius_msg_get_eap(msg); if (eap == NULL) { RADIUS_DEBUG("No EAP-Message in RADIUS packet from %s", from_addr); @@ -739,7 +762,7 @@ static int radius_server_request(struct radius_server_data *data, return -1; } - RADIUS_DUMP("Received EAP data", eap, eap_len); + RADIUS_DUMP("Received EAP data", wpabuf_head(eap), wpabuf_len(eap)); /* FIX: if Code is Request, Success, or Failure, send Access-Reject; * RFC3579 Sect. 2.6.2. @@ -749,10 +772,7 @@ static int radius_server_request(struct radius_server_data *data, * Or is this already done by the EAP state machine? */ wpabuf_free(sess->eap_if->eapRespData); - sess->eap_if->eapRespData = wpabuf_alloc_ext_data(eap, eap_len); - if (sess->eap_if->eapRespData == NULL) - os_free(eap); - eap = NULL; + sess->eap_if->eapRespData = eap; sess->eap_if->eapResp = TRUE; eap_server_sm_step(sess->eap); @@ -1259,6 +1279,7 @@ radius_server_init(struct radius_server_conf *conf) data->eap_sim_aka_result_ind = conf->eap_sim_aka_result_ind; data->tnc = conf->tnc; data->wps = conf->wps; + data->pwd_group = conf->pwd_group; if (conf->eap_req_id_text) { data->eap_req_id_text = os_malloc(conf->eap_req_id_text_len); if (data->eap_req_id_text) { @@ -1268,6 +1289,11 @@ radius_server_init(struct radius_server_conf *conf) } } +#ifdef CONFIG_RADIUS_TEST + if (conf->dump_msk_file) + data->dump_msk_file = os_strdup(conf->dump_msk_file); +#endif /* CONFIG_RADIUS_TEST */ + data->clients = radius_server_read_clients(conf->client_file, conf->ipv6); if (data->clients == NULL) { @@ -1319,6 +1345,9 @@ void radius_server_deinit(struct radius_server_data *data) os_free(data->eap_fast_a_id); os_free(data->eap_fast_a_id_info); os_free(data->eap_req_id_text); +#ifdef CONFIG_RADIUS_TEST + os_free(data->dump_msk_file); +#endif /* CONFIG_RADIUS_TEST */ os_free(data); } diff --git a/src/radius/radius_server.h b/src/radius/radius_server.h index f9c951d05f241..82466c30219f3 100644 --- a/src/radius/radius_server.h +++ b/src/radius/radius_server.h @@ -1,15 +1,9 @@ /* * RADIUS authentication server - * Copyright (c) 2005-2009, Jouni Malinen <j@w1.fi> + * Copyright (c) 2005-2009, 2011, Jouni Malinen <j@w1.fi> * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - * - * Alternatively, this software may be distributed under the terms of BSD - * license. - * - * See README and COPYING for more details. + * This software may be distributed under the terms of the BSD license. + * See README for more details. */ #ifndef RADIUS_SERVER_H @@ -143,6 +137,13 @@ struct radius_server_conf { int tnc; /** + * pwd_group - EAP-pwd D-H group + * + * This is used to select which D-H group to use with EAP-pwd. + */ + u16 pwd_group; + + /** * wps - Wi-Fi Protected Setup context * * If WPS is used with an external RADIUS server (which is quite @@ -194,6 +195,10 @@ struct radius_server_conf { * msg_ctx - Context data for wpa_msg() calls */ void *msg_ctx; + +#ifdef CONFIG_RADIUS_TEST + const char *dump_msk_file; +#endif /* CONFIG_RADIUS_TEST */ }; |