/*- * Copyright (c) 2004-2005 Sam Leffler, Errno Consulting * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * Alternatively, this software may be distributed under the terms of the * GNU General Public License ("GPL") version 2 as published by the Free * Software Foundation. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef EXPORT_SYMTAB #define EXPORT_SYMTAB #endif /* * Radius client support for the 802.1x+WPA authenticator. * * This support is optional (it is not present when the authenticator * uses only pre-shared keys). We override the authenticator's node * management methods to allocate additional space for the radius client * and supply methods for the 802.1x backend state machine to communicate * with a radius server. This module creates one thread for receiving * messages from the radius server. All other communication happens * asynchronously through callbacks from the 802.1x authenticator code. * Likewise we communicate state changes to the authenticator by setting * the state variables in a node's state block and then running the * authenticator state machine for that node. * * To use this support the radius server state must be configured before * enabling 802.1x authentication. Specifically you must set the radius * server's IP address and port and the shared secret used to communicate. * These are all set using sysctl's (or through /proc). To change any * of these settings while the client is running the device must be marked * down and up again, or similar (e.g. change authentication mode). */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* XXX for sk_allocation/allocation */ #include "if_media.h" #include "if_ethersubr.h" /* for ETHER_MAX_LEN */ #include "if_llc.h" /* for LLC_SNAPFRAMELEN */ #include #include #include #include "rc4.h" /* XXX */ #define arc4_ctxlen() sizeof (struct rc4_state) #define arc4_setkey(_c,_k,_l) rc4_init(_c,_k,_l) #define arc4_encrypt(_c,_d,_s,_l) rc4_crypt_skip(_c,_s,_d,_l,0) struct auth_hdr { u_int8_t ah_code; u_int8_t ah_id; u_int16_t ah_len; u_int8_t ah_auth[16]; /* MD5 hash */ /* variable length attributes follow */ } __attribute__((__packed__)); enum { RAD_ACCESS_REQUEST = 1, RAD_ACCESS_ACCEPT = 2, RAD_ACCESS_REJECT = 3, RAD_ACCESS_CHALLENGE = 11, }; enum { RAD_ATTR_USER_NAME = 1, RAD_ATTR_USER_PASSWD = 2, RAD_ATTR_NAS_IP_ADDRESS = 4, RAD_ATTR_NAS_PORT = 5, RAD_ATTR_SERVICE_TYPE = 6, #define RAD_SERVICE_TYPE_FRAMED 2 RAD_ATTR_FRAMED_MTU = 12, RAD_ATTR_REPLY_MESSAGE = 18, RAD_ATTR_STATE = 24, RAD_ATTR_CLASS = 25, RAD_ATTR_VENDOR_SPECIFIC = 26, RAD_ATTR_SESSION_TIMEOUT = 27, RAD_ATTR_CALLED_STATION_ID = 30, RAD_ATTR_CALLING_STATION_ID = 31, RAD_ATTR_NAS_IDENTIFIER = 32, RAD_ATTR_NAS_PORT_TYPE = 61, #define RAD_NAS_PORT_TYPE_WIRELESS 19 /* IEEE 802.11 */ RAD_ATTR_CONNECT_INFO = 77, RAD_ATTR_EAP_MESSAGE = 79, RAD_ATTR_MESSAGE_AUTHENTICATOR = 80, RAD_ATTR_NAS_PORT_ID = 87, }; #define RAD_MAX_ATTR_LEN (255 - 2) #define RAD_MAX_SECRET 48 /* NB: must be a multiple of 16 */ #ifndef TRUE enum { TRUE = 1, FALSE = 0 }; /* for consistency with spec */ #endif static struct sockaddr_in radius_serveraddr; /* IP address of radius server */ static struct sockaddr_in radius_clientaddr; /* IP address of client */ static u_int8_t radius_secret[RAD_MAX_SECRET+1];/* backend shared secret */ static u_int radius_secretlen = 0; static int radius_check_auth(struct radiuscom *, struct eapol_auth_radius_node *, struct auth_hdr *); static int radius_check_msg_auth(struct radiuscom *, struct eapol_auth_radius_node *, struct auth_hdr *); static struct sk_buff *radius_create_reply(struct radiuscom *, struct auth_hdr *, struct eapol_auth_node *); static int radius_extract_key(struct radiuscom *, struct eapol_auth_radius_node *, struct auth_hdr *); static void radius_dumppkt(const char *tag, const struct eapol_auth_node *, const struct auth_hdr *); static u_int8_t * radius_find_attr(struct auth_hdr *, int attr); static void sendRespToServer(struct eapol_auth_node *); static struct eapol_auth_radius_node *radius_get_reply(struct radiuscom *, u_int8_t id); static void radius_add_reply(struct radiuscom *, struct eapol_auth_radius_node *); static void radius_cleanup(struct radiuscom *); #ifdef IEEE80211_DEBUG static void radius_dumpbytes(const char *tag, const void *data, u_int len); #endif static int radius_get_integer(u_int8_t *ap) { u_int32_t v; if (ap[1] >= 2+sizeof(v)) memcpy(&v, ap+2, sizeof(v)); else v = 0; return ntohl(v); } #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) #include static void _daemonize(const char *procname) { lock_kernel(); daemonize(); current->tty = NULL; strcpy(current->comm, procname); unlock_kernel(); } static int allow_signal(int sig) { if (sig < 1 || sig > _NSIG) return -EINVAL; #ifdef INIT_SIGHAND /* * "The test on INIT_SIGHAND is not perfect but will at * least allow this to compile on RedHat kernels." * * http://www.mail-archive.com/jfs-discussion@www-124.ibm.com/msg00803.html */ spin_lock_irq(¤t->sighand->siglock); sigdelset(¤t->blocked, sig); /* XXX current->mm? */ recalc_sigpending(); spin_unlock_irq(¤t->sighand->siglock); #else spin_lock_irq(¤t->sigmask_lock); sigdelset(¤t->blocked, sig); /* XXX current->mm? */ recalc_sigpending(current); spin_unlock_irq(¤t->sigmask_lock); #endif return 0; } #else #define _daemonize(s) daemonize(s) #endif /* LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) */ /* * Thread to handle responses from the radius server. * This just listens for packets, decodes them, and * applies their contents to the appropriate session * state, then kicks the state machine as necessary. */ static int radiusd(void *arg) { struct radiuscom *rc = arg; struct eapolcom *ec = rc->rc_ec; mm_segment_t oldfs; struct auth_hdr *ahp; struct msghdr msg; struct iovec iov; struct sockaddr_in sin; struct eapol_auth_node *ean; struct eapol_auth_radius_node *ern; u_int8_t *ap; int len; /* XXX return what */ _MOD_INC_USE(THIS_MODULE, return -1); _daemonize("kradiusd"); allow_signal(SIGKILL); for (;;) { msg.msg_name = &sin; msg.msg_namelen = sizeof(sin); iov.iov_base = rc->rc_buf; iov.iov_len = sizeof(rc->rc_buf); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; /* * Gag, override task limits to tell sock_recvmsg & co * that our recv buffer is in kernel space and not user! */ oldfs = get_fs(); set_fs(KERNEL_DS); len = sock_recvmsg(rc->rc_sock, &msg, iov.iov_len, 0); set_fs(oldfs); /* * We receive a SIGKILL to detach; ignore any * message that might have been received. */ if (signal_pending(current)) break; if (len <= 0) { printf("%s: sock_recvmsg returns %d\n", __func__, len); continue; } /* * Warn about msgs not from the server (note * this is not meant to be security measure...). */ if (msg.msg_namelen != sizeof(sin) || sin.sin_addr.s_addr != rc->rc_server.sin_addr.s_addr) { /* * NB: some versions of Linux apparently * do not return the sender's address in * which case we'll want to conditionally * disable this complaint. */ sin.sin_addr.s_addr = ntohl(sin.sin_addr.s_addr); printf("%s: Warning, bad sender address, " "len %u addr %u.%u.%u.%u\n", __func__, msg.msg_namelen, (sin.sin_addr.s_addr>>24)&0xff, (sin.sin_addr.s_addr>>16)&0xff, (sin.sin_addr.s_addr>> 8)&0xff, (sin.sin_addr.s_addr>> 0)&0xff); eapolstats.rs_addrmismatch++; /* fall thru... */ } /* * Process the radius packet. */ if (len < sizeof(struct auth_hdr)) { printf("%s: msg too short, len %u, discarded\n", __func__, len); eapolstats.rs_tooshort++; continue; } ahp = (struct auth_hdr *)rc->rc_buf; if (ahp->ah_code == RAD_ACCESS_REQUEST) { printf("%s: bad code %u, msg discarded\n", __func__, ahp->ah_code); eapolstats.rs_request++; continue; } /* * NB: many places we assume the packet contents * are not altered so don't do "optimizations" * like fixing the byte order of ah_len. */ if (ntohs(ahp->ah_len) > len) { printf("%s: auth len %u > msg len %u, msg discarded\n", __func__, ahp->ah_len, len); eapolstats.rs_badlen++; continue; } /* * We use the EAPOL state lock to synchronize our * work (which should not take long to complete). */ EAPOL_LOCK(ec); /* * Check the radius server response against the * list of outstanding requests. Only one should * have a matching id. */ ern = radius_get_reply(rc, ahp->ah_id); if (ern == NULL) { EAPOL_UNLOCK(ec); printf("%s: RADIUS msg id %u has no pending request\n", __func__, ahp->ah_id); eapolstats.rs_badid++; continue; } ean = &ern->ern_base; #ifdef IEEE80211_DEBUG if (ieee80211_msg_dumpradius(ean->ean_ic)) radius_dumppkt("RECV", ean, ahp); #endif /* * Check the response authenticator. */ if (!radius_check_auth(rc, ern, ahp)) { EAPOL_UNLOCK(ec); IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] BAD response authenticator\n", ether_sprintf(ean->ean_node->ni_macaddr))); eapolstats.rs_badrespauth++; /* XXX put ean back on reply list? */ continue; } if (ahp->ah_code != RAD_ACCESS_REJECT) { /* * Check the Message-Authenticator hash * before trusting the message contents. */ if (!radius_check_msg_auth(rc, ern, ahp)) { EAPOL_UNLOCK(ec); IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] BAD message-auth hash\n", ether_sprintf(ean->ean_node->ni_macaddr))); eapolstats.rs_badmsgauth++; continue; } ap = radius_find_attr(ahp, RAD_ATTR_STATE); if (ap != NULL) { ern->ern_statelen = ap[1] - 2; memcpy(ern->ern_state, ap+2, ern->ern_statelen); } else ern->ern_statelen = 0; ap = radius_find_attr(ahp, RAD_ATTR_CLASS); if (ap != NULL) { ern->ern_classlen = ap[1] - 2; memcpy(ern->ern_class, ap+2, ern->ern_classlen); } else ern->ern_classlen = 0; /* * Construct EAPOL reply frame based on the * contents of the radius message just received. * radius_create_reply will only return a reply * buffer if the radius message has at least one EAP * message in it; this is used below. */ if (ern->ern_msg != NULL) kfree_skb(ern->ern_msg); ern->ern_msg = radius_create_reply(rc, ahp, ean); } else ern->ern_msg = NULL; switch (ahp->ah_code) { case RAD_ACCESS_ACCEPT: if (ern->ern_msg == NULL) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] RECV RAD_ACCESS_ACCEPT, no EAP\n", ether_sprintf(ean->ean_node->ni_macaddr))); ean->ean_backendAuthFails++; break; } IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] RECV RAD_ACCESS_ACCEPT, success\n", ether_sprintf(ean->ean_node->ni_macaddr))); /* * Setup the re-authentication timer based on * any supplied timeout. Any value we supply * from the packet is sanity-checked before use. */ ap = radius_find_attr(ahp, RAD_ATTR_SESSION_TIMEOUT); eapol_reauth_setperiod(ean, ap ? radius_get_integer(ap) : 0); /* * Collect key state from the message. */ if (radius_extract_key(rc, ern, ahp)) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] KEYS available\n", ether_sprintf(ean->ean_node->ni_macaddr))); ean->ean_keyAvailable = TRUE; } ean->ean_aFail = FALSE; ean->ean_aSuccess = TRUE; ean->ean_backendAuthSuccesses++; eapol_fsm_run(ean); break; case RAD_ACCESS_REJECT: IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] RECV RAD_ACCESS_REJECT\n", ether_sprintf(ean->ean_node->ni_macaddr))); ean->ean_aFail = TRUE; ean->ean_aSuccess = FALSE; ean->ean_backendAuthFails++; /* NB: it's ok for a reject to omit EAP */ if (ern->ern_msg != NULL) ean->ean_currentId = ean->ean_idFromServer; eapol_fsm_run(ean); break; case RAD_ACCESS_CHALLENGE: if (ern->ern_msg == NULL) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] RECV RAD_ACCESS_CHALLENGE, no EAP\n", ether_sprintf(ean->ean_node->ni_macaddr))); /* XXX */ break; } IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] RECV RAD_ACCESS_CHALLENGE, ok\n", ether_sprintf(ean->ean_node->ni_macaddr))); ean->ean_currentId = ean->ean_idFromServer; ean->ean_aReq = TRUE; ean->ean_backendAccessChallenges++; eapol_fsm_run(ean); break; default: IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] RECV RADIUS msg code %u ignored\n", ether_sprintf(ean->ean_node->ni_macaddr), ahp->ah_code)); if (ern->ern_msg != NULL) { kfree_skb(ern->ern_msg); ern->ern_msg = NULL; } eapolstats.rs_badcode++; break; } EAPOL_UNLOCK(ec); } /* * ieee80211_radius_detach leaves the final * cleanup work to us to avoid race conditions. */ radius_cleanup(rc); #if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,0) module_put_and_exit(0); #else return 0; #endif } /* * Write-around for bogus crypto API; requiring the address * address be specified twice can easily cause mistakes. */ static __inline void digest_update(struct crypto_tfm *md5, void *data, u_int len) { struct scatterlist sg; sg.page = virt_to_page(data); sg.offset = offset_in_page(data); sg.length = len; crypto_digest_update(md5, &sg, 1); } /* * Verify the authenticator hash in each reply using * the shared secret and the hash calculated for the * associated request. The frame contents are assumed * to have been checked that they have a valid length. */ static int radius_check_auth(struct radiuscom *rc, struct eapol_auth_radius_node *ern, struct auth_hdr *ahp) { u_int8_t orighash[16]; u_int8_t hash[16]; struct crypto_tfm *md5 = rc->rc_ec->ec_md5; crypto_digest_init(md5); memcpy(orighash, ahp->ah_auth, sizeof(ahp->ah_auth)); memcpy(ahp->ah_auth, ern->ern_reqauth, sizeof(ern->ern_reqauth)); digest_update(md5, ahp, ntohs(ahp->ah_len)); digest_update(md5, rc->rc_secret, rc->rc_secretlen); crypto_digest_final(md5, hash); memcpy(ahp->ah_auth, orighash, sizeof(ahp->ah_auth)); return (memcmp(ahp->ah_auth, hash, sizeof(hash)) == 0); } /* * Validate the Message-Authenticator hash. */ static int radius_check_msg_auth(struct radiuscom *rc, struct eapol_auth_radius_node *ern, struct auth_hdr *ahp) { u_int8_t ahash[16], hash[16]; u_int8_t *ap; int authlen; ap = radius_find_attr(ahp, RAD_ATTR_MESSAGE_AUTHENTICATOR); if (ap == NULL) { eapolstats.rs_nomsgauth++; return FALSE; } authlen = ap[1] - 2; if (authlen != sizeof(ahp->ah_auth)) { eapolstats.rs_lenmismatch++; return FALSE; } memcpy(ahash, ap+2, authlen); /* save hash */ memset(ap+2, 0, authlen); /* zero it for calculations */ /* NB: we smash ah_auth and don't repair it... */ memcpy(ahp->ah_auth, ern->ern_reqauth, authlen); eapol_hmac_md5(rc->rc_ec, ahp, ntohs(ahp->ah_len), rc->rc_secret, rc->rc_secretlen, hash); if (memcmp(ahash, hash, sizeof(hash)) == 0) { memcpy(ap+2, ahash, authlen); /* restore original data */ return TRUE; /* pass */ } else { return FALSE; /* fail */ } } /* * Create an EAPOL/EAP reply message for the supplicant * based on a message from the radius server. We reserve * space at the front for the EAPOL header and then copy * all the EAP message items from the radius packet. */ static struct sk_buff * radius_create_reply(struct radiuscom *rc, struct auth_hdr *ahp, struct eapol_auth_node *ean) { int len = ntohs(ahp->ah_len); const u_int8_t *ap = (const u_int8_t *)&ahp[1]; const u_int8_t *ep = ap + (len - sizeof(*ahp)); struct sk_buff *skb; /* * Use the received length to size the payload * in the reply. This is likely an overestimate but * simplifies things. Note also that eapol_alloc_skb * reserves headroom for the headers, including the * EAPOL header. */ skb = eapol_alloc_skb(len); if (skb == NULL) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_ANY, ("[%s] could not alloc sk_buff (%s)\n", ether_sprintf(ean->ean_node->ni_macaddr), __func__)); return NULL; } /* * Copy EAP messages from radius message to the sk_buff. */ for (; ap < ep; ap += ap[1]) { /* validate attribute length */ if (ap[1] < 2 || ap + ap[1] > ep) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] bad attribute length %u (%s)\n", ether_sprintf(ean->ean_node->ni_macaddr), ap[1], __func__)); eapolstats.rs_badattrlen++; goto bad; } if (ap[0] == RAD_ATTR_EAP_MESSAGE) { int alen = ap[1] - 2; memcpy(skb_put(skb, alen), ap+2, alen); } } /* * Check result. */ if (skb->len < sizeof(struct eap_hdr_short)) { /* no EAP messages were found, discard the buf */ IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] no EAP msg found (%s)\n", ether_sprintf(ean->ean_node->ni_macaddr), __func__)); eapolstats.rs_emptyreply++; bad: dev_kfree_skb(skb); return NULL; } else { /* extract EAP identifier for later use */ struct eap_hdr_short *eap = (struct eap_hdr_short *)skb->data; ean->ean_idFromServer = eap->eap_id; return skb; } } #define RAD_VENDOR_MICROSOFT 311 #define RAD_KEYTYPE_SEND 16 /* MPPE-Send-Key */ #define RAD_KEYTYPE_RECV 17 /* MPPE-Recv-Key */ /* * Microsoft Vendor-specific radius attributes used for * distributing keys. See MS-MPPE-Send-Key and * MS-MPPE-Recv-Key in RFC 2548 for documentation on the * handling of this data. */ struct vendor_key { u_int32_t vk_vid; /* vendor id */ u_int8_t vk_type; /* key type */ u_int8_t vk_len; /* length of type+len+salt+key */ u_int16_t vk_salt; /* variable length key data follows */ } __attribute__((__packed__)); /* * Extract a station transmit/receive key by mixing the * MPPE key with an md5 hash of the shared secret, request * authenticator, salt, and MPPE key contents. */ static int radius_extract_mppe(struct radiuscom *rc, struct eapol_auth_radius_node *ern, struct vendor_key *vk, struct eapol_radius_key *key) { u_int8_t hash[16], keybuf[sizeof(key->rk_data)]; u_int8_t *vkey; u_int8_t *dkey; int keylen, total, i, j; struct crypto_tfm *md5 = rc->rc_ec->ec_md5; /* * vk_len is the vendor attribute payload size, so deduct * the header (including salt) to get the key length. If * the key length is not a multiple of the md5 hash size; * then it is zero-padded (the rfc talks about an optional * padding sub-field but we assume clients may not include * it--this may be wrong if they include it and do not use * zero for padding, in practice everyone so far just sends * keys that are a multiple of 16). Also, verify the result * fits in the storage we have set aside for it. */ keylen = vk->vk_len - (sizeof(vk->vk_type) + sizeof(vk->vk_len) + sizeof(vk->vk_salt)); /* XXX is keylen == 0 ok? */ total = roundup(keylen, 16); if (total > sizeof(keybuf)) { IEEE80211_DPRINTF(ern->ern_ic, IEEE80211_MSG_ANY, ("[%s] MPPE key too large, total %u (%s)\n", ether_sprintf(ern->ern_node->ni_macaddr), total, __func__)); /* XXX should we just truncate it? */ eapolstats.rs_vkeytoolong++; return FALSE; } /* * Create intermediate, potentially zero-padded, copy. */ vkey = (u_int8_t *)&vk[1]; memcpy(keybuf, vkey, keylen); if (keylen != total) /* zero-pad key */ memset(keybuf+keylen, 0, total-keylen); /* * The first 16 bytes are xor'd with a hash constructed * from (shared secret | req-authenticator | salt). * Subsequent 16-byte chunks of the key are xor'd with * the hash of (shared secret | key). */ dkey = key->rk_data; for (i = 0; i < total; i += 16) { crypto_digest_init(md5); digest_update(md5, rc->rc_secret, rc->rc_secretlen); if (i == 0) { digest_update(md5, ern->ern_reqauth, sizeof(ern->ern_reqauth)); digest_update(md5, &vk->vk_salt, sizeof(vk->vk_salt)); } else { digest_update(md5, &keybuf[i-16], 16); } crypto_digest_final(md5, hash); for (j = 0; j < 16; j++) dkey[i+j] = keybuf[i+j] ^ hash[j]; } return TRUE; } /* * Extract Microsoft MPPE key state from the frame. */ static int radius_extract_key(struct radiuscom *rc, struct eapol_auth_radius_node *ern, struct auth_hdr *ahp) { #define ALLKEYS (RAD_KEYTYPE_SEND+RAD_KEYTYPE_RECV) int len = ntohs(ahp->ah_len); u_int8_t *ap = (u_int8_t *)&ahp[1]; u_int8_t *ep = ap + (len - sizeof(*ahp)); struct vendor_key *vk; int found = 0; for (; ap < ep && found != ALLKEYS; ap += ap[1]) { /* validate attribute length */ if (ap[1] < 2 || ap + ap[1] > ep) { eapolstats.rs_badattrlen++; break; } if (ap[0] != RAD_ATTR_VENDOR_SPECIFIC) continue; if (ap[1] < 2+sizeof(struct vendor_key)) { eapolstats.rs_vkeytooshort++; continue; } vk = (struct vendor_key *)&ap[2]; /* XXX ntohl alignment */ if (ntohl(vk->vk_vid) != RAD_VENDOR_MICROSOFT) { eapolstats.rs_vkeybadvid++; continue; } if ((ntohs(vk->vk_salt) & 0x8000) == 0) { eapolstats.rs_vkeybadsalt++; continue; } switch (vk->vk_type) { case RAD_KEYTYPE_SEND: if (found && vk->vk_salt == ern->ern_rxkey.rk_salt) { eapolstats.rs_vkeydupsalt++; continue; } if (radius_extract_mppe(rc, ern, vk, &ern->ern_txkey)) { #ifdef IEEE80211_DEBUG if (ieee80211_msg_dumpradkeys(ern->ern_ic)) radius_dumpbytes("send key", &ern->ern_txkey.rk_data[1], ern->ern_txkey.rk_len); #endif ern->ern_txkey.rk_salt = vk->vk_salt; found += RAD_KEYTYPE_SEND; } break; case RAD_KEYTYPE_RECV: if (found && vk->vk_salt == ern->ern_txkey.rk_salt) { eapolstats.rs_vkeydupsalt++; continue; } if (radius_extract_mppe(rc, ern, vk, &ern->ern_rxkey)) { #ifdef IEEE80211_DEBUG if (ieee80211_msg_dumpradkeys(ern->ern_ic)) radius_dumpbytes("recv key", &ern->ern_rxkey.rk_data[1], ern->ern_rxkey.rk_len); #endif ern->ern_rxkey.rk_salt = vk->vk_salt; found += RAD_KEYTYPE_RECV; } break; } } return (found == ALLKEYS); #undef ALLKEYS } /* * Locate an attribute in the message and sanity * check the length field so the caller can assume * it's minimally valid (i.e. it covers the attribute * itself and any data is within the bound of the * message as specified in the header. */ static u_int8_t * radius_find_attr(struct auth_hdr *ahp, int attr) { int len = ntohs(ahp->ah_len); u_int8_t *ap = (u_int8_t *)&ahp[1]; u_int8_t *ep = ap + (len - sizeof(*ahp)); for (; ap < ep; ap += ap[1]) { /* validate attribute length */ if (ap[1] < 2 || ap + ap[1] > ep) { eapolstats.rs_badattrlen++; return NULL; } if (ap[0] == attr) return ap; } return NULL; } /* * Helper functions for constructing a radius message. */ static u_int8_t * radius_add_bytes(u_int8_t *dp, int attr, const void *val, u_int len) { dp[0] = attr; dp[1] = 2 + len; memcpy(&dp[2], val, len); return dp + dp[1]; } static u_int8_t * radius_add_space(u_int8_t *dp, int attr, u_int len) { dp[0] = attr; dp[1] = 2 + len; memset(&dp[2], 0, len); return dp + dp[1]; } static u_int8_t * radius_add_integer(u_int8_t *dp, int attr, u_int32_t val) { val = htonl(val); return radius_add_bytes(dp, attr, &val, sizeof(val)); } static u_int8_t * radius_add_string(u_int8_t *dp, int attr, const char *val) { return radius_add_bytes(dp, attr, val, strlen(val)); } static u_int8_t * radius_add_stationid(u_int8_t *dp, int attr, struct eapol_auth_node *ean) { struct ieee80211com *ic = ean->ean_ic; const u_int8_t *mac = ic->ic_bss->ni_macaddr; char buf[255]; snprintf(buf, sizeof(buf), "%02x-%02x-%02x-%02x-%02x-%02x:%.*s", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], ic->ic_des_esslen, ic->ic_des_essid); return radius_add_string(dp, attr, buf); } static u_int8_t * radius_add_macaddr(u_int8_t *dp, int attr, struct eapol_auth_node *ean) { const u_int8_t *mac = ean->ean_node->ni_macaddr; char buf[20]; snprintf(buf, sizeof(buf), "%02x-%02x-%02x-%02x-%02x-%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); return radius_add_string(dp, attr, buf); } static u_int8_t * radius_add_nasport(u_int8_t *dp, int port) { char buf[32]; int len = snprintf(buf, sizeof(buf), "STA port # %d", port); return radius_add_bytes(dp, RAD_ATTR_NAS_PORT_ID, buf, len); } #ifdef IEEE80211_DEBUG /* * Return the name for certain attributes. */ static const char * radius_attrname(int attr) { static char buf[8]; switch (attr) { case RAD_ATTR_USER_NAME: return "User-Name"; case RAD_ATTR_USER_PASSWD: return "User-Passwd"; case RAD_ATTR_NAS_IP_ADDRESS: return "NAS-Ip-Address"; case RAD_ATTR_NAS_PORT: return "NAS-Port"; case RAD_ATTR_SERVICE_TYPE: return "Service-Type"; case RAD_ATTR_FRAMED_MTU: return "Framed-MTU"; case RAD_ATTR_REPLY_MESSAGE: return "Reply-Message"; case RAD_ATTR_STATE: return "State"; case RAD_ATTR_CLASS: return "Class"; case RAD_ATTR_VENDOR_SPECIFIC: return "Vendor-Specific"; case RAD_ATTR_SESSION_TIMEOUT: return "Session-Timeout"; case RAD_ATTR_CALLED_STATION_ID:return "Called-Station-ID"; case RAD_ATTR_CALLING_STATION_ID:return "Calling-Station-ID"; case RAD_ATTR_NAS_IDENTIFIER: return "NAS-Identifier"; case RAD_ATTR_NAS_PORT_TYPE: return "NAS-Port-Type"; case RAD_ATTR_CONNECT_INFO: return "Connect-Info"; case RAD_ATTR_EAP_MESSAGE: return "EAP-Message"; case RAD_ATTR_MESSAGE_AUTHENTICATOR: return "Message-Authenticator"; case RAD_ATTR_NAS_PORT_ID: return "NAS-Port-ID"; } snprintf(buf, sizeof(buf), "%u", attr); return buf; }; static void radius_dumpbytes(const char *tag, const void *data, u_int len) { const u_int8_t *tp; printf("%s = 0x", tag); for (tp = data; len > 0; tp++, len--) printf("%02x", *tp); printf("\n"); } /* * Dump the contents of a RADIUS msg to the console. */ static void radius_dumppkt(const char *tag, const struct eapol_auth_node *ean, const struct auth_hdr *ahp) { int len = ntohs(ahp->ah_len); const u_int8_t *ap = (const u_int8_t *)&ahp[1]; const u_int8_t *ep = ap + (len - sizeof(*ahp)); const char *attrname; u_int32_t v; printf("[%s] %s RADIUS msg: code %u id %u len %u\n", ether_sprintf(ean->ean_node->ni_macaddr), tag, ahp->ah_code, ahp->ah_id, len); for (; ap < ep; ap += ap[1]) { if (ap + ap[1] > ep) { printf("%s: bogus attribute length %u for attr %u\n", __func__, ap[1], ap[0]); return; } attrname = radius_attrname(ap[0]); len = ap[1] - 2; switch (ap[0]) { case RAD_ATTR_SERVICE_TYPE: case RAD_ATTR_FRAMED_MTU: case RAD_ATTR_NAS_PORT_TYPE: case RAD_ATTR_NAS_PORT: memcpy(&v, ap+2, sizeof(v)); v = ntohl(v); printf("\t%s = %u (0x%x)\n", attrname, v, v); break; case RAD_ATTR_USER_NAME: case RAD_ATTR_CALLED_STATION_ID: case RAD_ATTR_CALLING_STATION_ID: case RAD_ATTR_NAS_IDENTIFIER: case RAD_ATTR_CONNECT_INFO: case RAD_ATTR_NAS_PORT_ID: printf("\t%s = \"%.*s\"\n", attrname, len, ap+2); break; case RAD_ATTR_NAS_IP_ADDRESS: memcpy(&v, ap+2, sizeof(v)); v = ntohl(v); printf("\t%s = %u.%u.%u.%u\n", attrname, (v>>24)&0xff, (v>>16)&0xff, (v>>8)&0xff, (v>>0)&0xff); break; default: printf("\t"); radius_dumpbytes(attrname, ap+2, len); break; } } } #endif /* IEEE80211_DEBUG */ /* * Create a response msg for transmission to the radius server. * The response is an EAP-encapsulated version of the supplicant * request that is left in an outbound packet buffer. * * XXX bounds checking */ int radius_make_response(struct radiuscom *rc, struct eapol_auth_radius_node *ern) { struct eapol_auth_node *ean = &ern->ern_base; struct sk_buff *skb = ean->ean_skb; struct eap_hdr *eap; u_int8_t *dp, *cp, *map; u_int8_t msgauth[16]; struct auth_hdr *ahp; u_int len, cc; if (skb == NULL) { /* XXX something is wrong */ return FALSE; } eap = (struct eap_hdr *)skb->data; if (ern->ern_radmsg == NULL) { /* XXX 4096 */ MALLOC(ern->ern_radmsg, u_int8_t *, 4096, M_DEVBUF, M_NOWAIT); if (ern->ern_radmsg == NULL) { eapolstats.rs_nomem++; /* XXX unique stat */ return FALSE; } } ahp = (struct auth_hdr *)ern->ern_radmsg; ahp->ah_code = RAD_ACCESS_REQUEST; ahp->ah_id = eap->eap_id; /* copy from supplicant */ /* * Save authenticator hash for use in validating replies * and for extracting MPPE keys returned by the server. */ get_random_bytes(ern->ern_reqauth, sizeof(ern->ern_reqauth)); memcpy(ahp->ah_auth, ern->ern_reqauth, sizeof(ern->ern_reqauth)); /* * Add the attribute-value pairs. */ dp = (u_int8_t *)&ahp[1]; map = dp; /* hold a pointer for fixup later */ dp = radius_add_space(dp, RAD_ATTR_MESSAGE_AUTHENTICATOR, 16); dp = radius_add_integer(dp, RAD_ATTR_SERVICE_TYPE, RAD_SERVICE_TYPE_FRAMED); dp = radius_add_bytes(dp, RAD_ATTR_USER_NAME, ean->ean_id, ean->ean_idlen); /* NB: use Ethernet MTU rather than 802.11 MTU */ dp = radius_add_integer(dp, RAD_ATTR_FRAMED_MTU, ETHERMTU - LLC_SNAPFRAMELEN - sizeof(struct eapol_hdr)); if (ern->ern_statelen != 0) dp = radius_add_bytes(dp, RAD_ATTR_STATE, ern->ern_state, ern->ern_statelen); if (ern->ern_classlen != 0) dp = radius_add_bytes(dp, RAD_ATTR_CLASS, ern->ern_class, ern->ern_classlen); dp = radius_add_stationid(dp, RAD_ATTR_CALLED_STATION_ID, ean); dp = radius_add_macaddr(dp, RAD_ATTR_CALLING_STATION_ID, ean); dp = radius_add_string(dp, RAD_ATTR_NAS_IDENTIFIER, system_utsname.nodename); dp = radius_add_integer(dp, RAD_ATTR_NAS_PORT_TYPE, RAD_NAS_PORT_TYPE_WIRELESS); switch (ean->ean_ic->ic_curmode) { case IEEE80211_MODE_11A: case IEEE80211_MODE_TURBO: dp = radius_add_string(dp, RAD_ATTR_CONNECT_INFO, "CONNECT 54Mbps 802.11a"); break; case IEEE80211_MODE_11B: dp = radius_add_string(dp, RAD_ATTR_CONNECT_INFO, "CONNECT 11Mbps 802.11b"); break; case IEEE80211_MODE_11G: dp = radius_add_string(dp, RAD_ATTR_CONNECT_INFO, "CONNECT 54Mbps 802.11g"); break; default: /* XXX bitch */ eapolstats.rs_badmode++; break; } /* * Copy in the original EAP message. Note that * we have to byte swap the length field in the * header as it is put in host order on receipt. */ len = eap->eap_len; eap->eap_len = htons(len); for (cp = (u_int8_t *)eap; len > 0; cp += cc, len -= cc) { cc = len; if (cc > RAD_MAX_ATTR_LEN) cc = RAD_MAX_ATTR_LEN; dp = radius_add_bytes(dp, RAD_ATTR_EAP_MESSAGE, cp, cc); } eap->eap_len = ntohs(eap->eap_len); dp = radius_add_bytes(dp, RAD_ATTR_NAS_IP_ADDRESS, &rc->rc_local.sin_addr.s_addr, sizeof(struct in_addr)); dp = radius_add_integer(dp, RAD_ATTR_NAS_PORT, IEEE80211_NODE_AID(ean->ean_node)); dp = radius_add_nasport(dp, IEEE80211_NODE_AID(ean->ean_node)); ern->ern_radmsglen = dp - ern->ern_radmsg; /* * Now patch up stuff that required the message be * formulated and calculate the message authenticator * hash. */ ahp->ah_len = htons(ern->ern_radmsglen); eapol_hmac_md5(rc->rc_ec, ern->ern_radmsg, ern->ern_radmsglen, rc->rc_secret, rc->rc_secretlen, msgauth); memcpy(map+2, msgauth, sizeof(msgauth)); return TRUE; } /* * Callback from the 802.1x state machine on * receiving an Identity packet from the supplicant. */ static void radius_identity_input(struct eapol_auth_node *ean, struct sk_buff *skb) { struct eapol_auth_radius_node *ern = EAPOL_RADIUSNODE(ean); memset(ern->ern_state, 0, sizeof(ern->ern_state)); ern->ern_statelen = 0; memset(ern->ern_class, 0, sizeof(ern->ern_class)); ern->ern_classlen = 0; } /* * Callback from the 802.1x backend state machine to * send/resend a response message to the server. */ static void sendRespToServer(struct eapol_auth_node *ean) { struct eapol_auth_radius_node *ern = EAPOL_RADIUSNODE(ean); struct radiuscom *rc = ean->ean_ec->ec_radius; struct msghdr msg; struct iovec iov; if (ean->ean_reqSrvCount == 0) { /* * Not a retry; formulate the message from scratch * and add an entry to the reply list. This entry * stays until a response is received from the server * or the node is reset/reclaimed. */ radius_make_response(rc, ern); radius_add_reply(rc, ern); } IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS | IEEE80211_MSG_DOT1X, ("[%s] %s buf 0x%p len %u\n", ether_sprintf(ean->ean_node->ni_macaddr), __func__, ern->ern_radmsg, ern->ern_radmsglen)); #ifdef IEEE80211_DEBUG if (ieee80211_msg_dumpradius(ean->ean_ic)) radius_dumppkt("SEND", ean, (struct auth_hdr *)ern->ern_radmsg); #endif msg.msg_name = &rc->rc_server; msg.msg_namelen = sizeof(rc->rc_server); iov.iov_base = ern->ern_radmsg; iov.iov_len = ern->ern_radmsglen; msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_control = NULL; msg.msg_controllen = 0; msg.msg_flags = 0; /* XXXX check return */ (void) sock_sendmsg(rc->rc_sock, &msg, iov.iov_len); } /* * Callback from the 802.1x backend state machine to * transmit/retransmit the EAP message from the radius sever * to the supplicant. The message should have been setup by * the thread that receives messages from the radius server * (see radius_create_reply). */ static void txReq(struct eapol_auth_node *ean) { struct eapol_auth_radius_node *ern = EAPOL_RADIUSNODE(ean); struct sk_buff *skb; if (ern->ern_msg == NULL) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS | IEEE80211_MSG_DOT1X, ("[%s] no msg to send (%s)!\n", ether_sprintf(ean->ean_node->ni_macaddr), __func__)); eapolstats.rs_nomsg++; return; } skb = skb_clone(ern->ern_msg, GFP_ATOMIC); if (skb == NULL) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS | IEEE80211_MSG_DOT1X, ("[%s] could not clone msg for xmit (%s)\n", ether_sprintf(ean->ean_node->ni_macaddr), __func__)); eapolstats.rs_noclone++; } else { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS | IEEE80211_MSG_DOT1X, ("[%s] %s msg 0x%p len %u\n", ether_sprintf(ean->ean_node->ni_macaddr), __func__, skb, skb->len)); eapol_send(&ean->ean_base, skb, EAPOL_TYPE_EAP); } } /* * Construct and send an EAPOL RC4 Key message to the supplicant. */ static void radius_txkey(struct eapol_auth_radius_node *ern, int keytype, int keyix, struct ieee80211_key *key) { struct eapol_auth_node *ean = &ern->ern_base; struct radiuscom *rc = ean->ean_ec->ec_radius; struct sk_buff *skb; struct eapol_key *kp; struct eapol_hdr *eapol; struct rc4_state ctx; u_int8_t *keydata, rc4key[16+64], hash[16]; struct crypto_tfm *md5 = rc->rc_ec->ec_md5; KASSERT(keyix != IEEE80211_KEYIX_NONE, ("no key index")); if (key->wk_cipher->ic_cipher != IEEE80211_CIPHER_WEP) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_ANY, ("[%s] key type %u not WEP\n", ether_sprintf(ean->ean_node->ni_macaddr), key->wk_cipher->ic_cipher)); return; } if (key->wk_keylen != 16 && key->wk_keylen != 13 && key->wk_keylen != 5) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_ANY, ("[%s] bad key length %u\n", ether_sprintf(ean->ean_node->ni_macaddr), key->wk_keylen)); return; } skb = eapol_alloc_skb(sizeof(struct eapol_key) + key->wk_keylen); if (skb == NULL) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_ANY, ("[%s] could not alloc sk_buff (%s)\n", ether_sprintf(ean->ean_node->ni_macaddr), __func__)); return; } kp = (struct eapol_key *)skb_put(skb, sizeof(struct eapol_key)); memset(kp, 0, sizeof(struct eapol_key)); kp->ek_type = EAPOL_KEY_TYPE_RC4; kp->ek_length = htons(key->wk_keylen); /* NB: there is no htonll */ kp->ek_replay = cpu_to_be64(get_jiffies_64()); get_random_bytes(kp->ek_iv, sizeof(kp->ek_iv)); crypto_digest_init(md5); digest_update(md5, kp->ek_iv, sizeof(kp->ek_iv)); crypto_digest_final(md5, kp->ek_iv); kp->ek_index = keyix | keytype; /* * NB: this ``cannot happen'' as the rxkey has already been * verified to fit in ern_rxkey. */ if (sizeof(kp->ek_iv)+ern->ern_rxkey.rk_len > sizeof(rc4key)) { IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_ANY, ("[%s] intermediate key buf too small, key len %u (%s)\n", ether_sprintf(ean->ean_node->ni_macaddr), ern->ern_rxkey.rk_len, __func__)); return; } /* encrypt the station key with rc4 key of (iv | rxkey) */ memcpy(rc4key, kp->ek_iv, sizeof(kp->ek_iv)); memcpy(rc4key+sizeof(kp->ek_iv), &ern->ern_rxkey.rk_data[1], ern->ern_rxkey.rk_len); keydata = (u_int8_t *)skb_put(skb, key->wk_keylen); arc4_setkey(&ctx, rc4key, sizeof(kp->ek_iv) + ern->ern_rxkey.rk_len); arc4_encrypt(&ctx, keydata, key->wk_key, key->wk_keylen); memset(rc4key, 0, sizeof(rc4key)); /* * Finally, sign the message. To do this we must * formulate the EAPOL header so we can calculate * the signature that goes in the key payload. This * violates the normal layering (sigh, who designs * these protocols). */ eapol = (struct eapol_hdr *)skb_push(skb, sizeof(struct eapol_hdr)); eapol->eapol_ver = EAPOL_VERSION; eapol->eapol_type = EAPOL_TYPE_KEY; eapol->eapol_len = htons(skb->len - sizeof(struct eapol_hdr)); eapol_hmac_md5(rc->rc_ec, eapol, skb->len, &ern->ern_txkey.rk_data[1], ern->ern_txkey.rk_len, hash); memcpy(kp->ek_sig, hash, sizeof(hash)); IEEE80211_DPRINTF(ean->ean_ic, IEEE80211_MSG_RADIUS, ("[%s] SEND %s EAPOL-Key ix %u len %u bits\n", ether_sprintf(ean->ean_node->ni_macaddr), keytype == EAPOL_KEY_BCAST ? "bcast" : "ucast", keyix, key->wk_keylen * NBBY)); eapol_send_raw(&ean->ean_base, skb); } /* * Construct unicast key for station. * * XXX just use random data for now; probably want something better * XXX cipher suites */ static int setUnicastKey(struct ieee80211com *ic, struct ieee80211_node *ni) { struct ieee80211_key *key = &ni->ni_ucastkey; int ok = 1; ieee80211_key_update_begin(ic); key->wk_keylen = ni->ni_rsn.rsn_ucastkeylen; get_random_bytes(key->wk_key, key->wk_keylen); if (!ieee80211_crypto_newkey(ic, ni->ni_rsn.rsn_ucastcipher, key)) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_RADIUS, ("[%s] problem creating unicast key for station\n", ether_sprintf(ni->ni_macaddr))); ok = 0; } else if (!ieee80211_crypto_setkey(ic, key, ni->ni_macaddr)) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_RADIUS, ("[%s] problem installing unicast key for station\n", ether_sprintf(ni->ni_macaddr))); /* XXX recovery? */ ok = 0; } ieee80211_key_update_end(ic); return ok; } /* * Callback from the 802.1x backend state machine to transmit * broadcast+unicast keys to the supplicant. The keys are * constructed from the key material received from the radius * server when access is granted. * * NB: there must be a multicast key; the unicast key is optional. */ static void txKey(struct eapol_auth_node *ean) { struct eapol_auth_radius_node *ern = EAPOL_RADIUSNODE(ean); struct ieee80211com *ic = ean->ean_ic; struct ieee80211_node *ni = ean->ean_node; KASSERT(ean->ean_keyAvailable == TRUE, ("no keys!")); /* * Send multicast key with index > 0. */ if (ic->ic_def_txkey == IEEE80211_KEYIX_NONE) { IEEE80211_DPRINTF(ic, IEEE80211_MSG_RADIUS, ("[%s] no multicast key for station\n", ether_sprintf(ni->ni_macaddr))); return; } radius_txkey(ern, EAPOL_KEY_BCAST, ic->ic_def_txkey, &ic->ic_nw_keys[ic->ic_def_txkey]); /* * Construct unicast key for station and send it with index 0. */ if (setUnicastKey(ic, ni)) radius_txkey(ern, EAPOL_KEY_UCAST, 0, &ni->ni_ucastkey); } /* * Look for a session waiting for a reply from the * radius server. If found the session is taken off * the list. */ static struct eapol_auth_radius_node * radius_get_reply(struct radiuscom *rc, u_int8_t id) { struct eapol_auth_radius_node *ern; EAPOL_LOCK_ASSERT(rc->rc_ec); LIST_FOREACH(ern, &rc->rc_replies, ern_next) if (ern->ern_base.ean_currentId == id) { LIST_REMOVE(ern, ern_next); ern->ern_onreply = FALSE; return ern; } return NULL; } static void radius_remove_reply(struct radiuscom *rc, struct eapol_auth_radius_node *p) { struct eapol_auth_radius_node *ern; EAPOL_LOCK_ASSERT(rc->rc_ec); LIST_FOREACH(ern, &rc->rc_replies, ern_next) if (ern == p) { LIST_REMOVE(ern, ern_next); ern->ern_onreply = FALSE; break; } } /* * Add a session to the list of those waiting for a * reply from the radius server. */ static void radius_add_reply(struct radiuscom *rc, struct eapol_auth_radius_node *ern) { EAPOL_LOCK_ASSERT(rc->rc_ec); if (!ern->ern_onreply) { LIST_INSERT_HEAD(&rc->rc_replies, ern, ern_next); ern->ern_onreply = TRUE; } } /* * Radius-specific node allocate/free routines that * allocate the larger nodes needed when communicating * with a radius server. This routine is installed * when we attach to the authenticator */ static struct eapol_auth_node * radius_node_alloc(struct eapolcom *ec) { struct eapol_auth_radius_node *ern; MALLOC(ern, struct eapol_auth_radius_node *, sizeof(struct eapol_auth_radius_node), M_EAPOL_NODE, M_NOWAIT | M_ZERO); return ern ? &ern->ern_base : NULL; } /* * Reclaim resources specific to the radius server. This * routine is installed when we attach to the authenticator. */ static void radius_node_free(struct eapol_auth_node *ean) { struct eapol_auth_radius_node *ern = EAPOL_RADIUSNODE(ean); struct radiuscom *rc = ean->ean_ec->ec_radius; /* * NB: we're called from eapol_node_leave which drops it's * EAPOL lock before calling us as it's already removed the * reference to us from the table and needs to drop the lock * before calling back in to the 802.11 layer. However we * need to grab the lock again to safeguard removing any * reference to use on the reply list. There's probably * a better way to do this. */ EAPOL_LOCK(ean->ean_ec); radius_remove_reply(rc, ern); EAPOL_UNLOCK(ean->ean_ec); if (ern->ern_msg != NULL) kfree_skb(ern->ern_msg); if (ern->ern_radmsg != NULL) FREE(ern->ern_radmsg, M_DEVBUF); (*rc->rc_node_free)(ean); } /* * Reset non-fsm state for the specified node. This * routine is installed when we attach to the authenticator. */ static void radius_node_reset(struct eapol_auth_node *ean) { struct eapol_auth_radius_node *ern = EAPOL_RADIUSNODE(ean); struct radiuscom *rc = ean->ean_ec->ec_radius; EAPOL_LOCK_ASSERT(rc->rc_ec); radius_remove_reply(rc, ern); if (ern->ern_msg != NULL) { kfree_skb(ern->ern_msg); ern->ern_msg = NULL; } (*rc->rc_node_reset)(ean); } /* * Cleanup radius client state. */ static void radius_cleanup(struct radiuscom *rc) { if (rc->rc_sock != NULL) sock_release(rc->rc_sock); if (rc->rc_secret != NULL) FREE(rc->rc_secret, M_DEVBUF); FREE(rc, M_DEVBUF); printk(KERN_INFO "802.1x radius client stopped\n"); } /* * Detach a radius client from an authenticator. */ static void ieee80211_radius_detach(struct eapolcom *ec) { struct radiuscom *rc = ec->ec_radius; if (rc != NULL) { printk(KERN_INFO "802.1x radius client stopping\n"); ec->ec_radius = NULL; /* restore original methods */ rc->rc_ec->ec_node_alloc = rc->rc_node_alloc; rc->rc_ec->ec_node_free = rc->rc_node_free; rc->rc_ec->ec_node_reset = rc->rc_node_reset; /* * If we started the receiver thread then just signal * it and let it complete the cleanup work when it's * dropped out of the receive loop. Otherwise we must * do the cleanup directly. * * XXX wrong, must wait to remove module reference */ if (rc->rc_pid != -1) kill_proc(rc->rc_pid, SIGKILL, 1); else radius_cleanup(rc); _MOD_DEC_USE(THIS_MODULE); } } /* * Attach a radius client to an authenticator. We setup * private state, override the node allocation alloc/free * methods, and start a thread to receive messages from * the radius server. We also setup callsbacks used by * the 802.1x backend state machine. */ static int ieee80211_radius_attach(struct eapolcom *ec) { struct radiuscom *rc; int error, secretlen; _MOD_INC_USE(THIS_MODULE, return FALSE); /* XXX require master key be setup? */ secretlen = strlen(radius_secret); if (secretlen == 0) { printf("%s: no radius server shared secret setup", __func__); eapolstats.rs_nosecret++; _MOD_DEC_USE(THIS_MODULE); return FALSE; } MALLOC(rc, struct radiuscom *, sizeof(struct radiuscom), M_DEVBUF, M_NOWAIT | M_ZERO); if (rc == NULL) { printf("%s: unable to allocate memory for client state\n", __func__); eapolstats.rs_nomem++; _MOD_DEC_USE(THIS_MODULE); return FALSE; } LIST_INIT(&rc->rc_replies); rc->rc_pid = -1; MALLOC(rc->rc_secret, u_int8_t*, radius_secretlen, M_DEVBUF, M_NOWAIT); if (rc->rc_secret == NULL) { eapolstats.rs_nomem++; printf("%s: unable to allocate memory for shared secret\n", __func__); goto bad; } memcpy(rc->rc_secret, radius_secret, radius_secretlen); rc->rc_secretlen = radius_secretlen; rc->rc_local = radius_clientaddr; error = sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &rc->rc_sock); if (error < 0) { printf("%s: cannot create socket, error %u\n", __func__, -error); eapolstats.rs_nosocket++; goto bad; } /* * Force atomic allocation since we sometimes send * messages from interrupt level. */ /* XXX 2.6.0 is just a guess */ #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) rc->rc_sock->sk->allocation = GFP_ATOMIC; #else rc->rc_sock->sk->sk_allocation = GFP_ATOMIC; #endif error = (*rc->rc_sock->ops->bind)(rc->rc_sock, (struct sockaddr *)&rc->rc_local, sizeof(rc->rc_local)); if (error < 0) { /* XXX */ printf("%s: cannot bind local address, error %u\n", __func__, -error); eapolstats.rs_cannotbind++; goto bad; } /* XXX check result */ rc->rc_server = radius_serveraddr; rc->rc_pid = kernel_thread(radiusd, rc, CLONE_KERNEL); if (rc->rc_pid < 0) { printf("%s: cannot start radiusd thread; error %d\n", __func__, -rc->rc_pid); eapolstats.rs_nothread++; goto bad; } rc->rc_identity_input = radius_identity_input; rc->rc_txreq = txReq; rc->rc_sendsrvr = sendRespToServer; rc->rc_txkey = txKey; /* * Override node management methods so we have the * extra space needed for the radius client and so * we properly manage our private state. */ rc->rc_node_alloc = ec->ec_node_alloc; ec->ec_node_alloc = radius_node_alloc; rc->rc_node_free = ec->ec_node_free; ec->ec_node_free = radius_node_free; rc->rc_node_reset = ec->ec_node_reset; ec->ec_node_reset = radius_node_reset; rc->rc_ec = ec; ec->ec_radius = rc; printk(KERN_INFO "802.1x radius client started\n"); return TRUE; bad: ieee80211_radius_detach(ec); /* NB: does _MOD_DEC_USE */ return FALSE; } /* * Handle a sysctl that read/writes an IP address. */ static int radius_sysctl_ipaddr(ctl_table *ctl, int write, struct file *filp, void *buf, size_t *lenp) { struct sockaddr_in *sin = (struct sockaddr_in *) ctl->data; if (write) { u_int a1, a2, a3, a4; if (sscanf(buf, "%d.%d.%d.%d", &a1, &a2, &a3, &a4) != 4) return -EINVAL; sin->sin_addr.s_addr = htonl( ((a1 & 0xff) << 24) | ((a2 & 0xff) << 16) | ((a3 & 0xff) << 8) | ((a4 & 0xff) << 0)); } else if (*lenp && filp->f_pos == 0) { u_int32_t v = ntohl(sin->sin_addr.s_addr); *lenp = snprintf(buf, *lenp, "%d.%d.%d.%d\n", (v >> 24) & 0xff, (v >> 16) & 0xff, (v >> 8) & 0xff, (v >> 0) & 0xff); filp->f_pos += *lenp; } else { *lenp = 0; } return 0; } /* * Handle a sysctl that reads/writes an IP port number. */ static int radius_sysctl_ipport(ctl_table *ctl, int write, struct file *filp, void *buf, size_t *lenp) { struct sockaddr_in *sin = (struct sockaddr_in *) ctl->data; if (write) { u_int a1; if (sscanf(buf, "%d", &a1) != 1) return -EINVAL; sin->sin_port = htons(a1 & 0xffff); } else if (*lenp && filp->f_pos == 0) { *lenp = snprintf(buf, *lenp, "%d\n", ntohs(sin->sin_port)); filp->f_pos += *lenp; } else { *lenp = 0; } return 0; } /* * Handle a sysctl to read/write the radius server shared secret. */ static int radius_sysctl_secret(ctl_table *ctl, int write, struct file *filp, void *buf, size_t *lenp) { int len = *lenp; if (write) { if (len > RAD_MAX_SECRET) /* should not happen */ len = RAD_MAX_SECRET; memset(radius_secret, 0, sizeof(radius_secret)); memcpy(radius_secret, buf, len); radius_secret[RAD_MAX_SECRET] = '\0'; radius_secretlen = len; } else if (*lenp && filp->f_pos == 0) { if (len >= radius_secretlen) len = strlen(radius_secret); memcpy(buf, radius_secret, len); *lenp = len; filp->f_pos += *lenp; } else { *lenp = 0; } return 0; } #define CTL_AUTO -2 /* cannot be CTL_ANY or CTL_NONE */ static ctl_table radius_sysctls[] = { { .ctl_name = CTL_AUTO, .procname = "serveraddr", .data = &radius_serveraddr, .mode = 0644, .proc_handler = radius_sysctl_ipaddr }, { .ctl_name = CTL_AUTO, .procname = "serverport", .data = &radius_serveraddr, .mode = 0644, .proc_handler = radius_sysctl_ipport }, { .ctl_name = CTL_AUTO, .procname = "clientaddr", .data = &radius_clientaddr, .mode = 0644, .proc_handler = radius_sysctl_ipaddr }, { .ctl_name = CTL_AUTO, .procname = "secret", .maxlen = RAD_MAX_SECRET, .mode = 0600, .proc_handler = radius_sysctl_secret }, { 0 } }; static ctl_table radius_table[] = { { .ctl_name = CTL_AUTO, .procname = "radius", .mode = 0555, .child = radius_sysctls }, { 0 } }; static ctl_table dot1x_table[] = { { .ctl_name = NET_8021X, .procname = "8021x", .mode = 0555, .child = radius_table }, { 0 } }; static ctl_table net_table[] = { #ifdef CONFIG_PROC_FS { .ctl_name = CTL_NET, .procname = "net", .mode = 0555, .child = dot1x_table }, #endif /* CONFIG_PROC_FS */ { 0 } }; static struct ctl_table_header *radius_sys; /* * Module glue. */ MODULE_AUTHOR("Errno Consulting, Sam Leffler"); MODULE_DESCRIPTION("802.11 wireless support: radius backend "); #ifdef MODULE_LICENSE MODULE_LICENSE("Dual BSD/GPL"); #endif static const struct ieee80211_authenticator_backend radius = { .iab_name = "radius", .iab_attach = ieee80211_radius_attach, .iab_detach = ieee80211_radius_detach, }; static int __init init_ieee80211_radius(void) { #ifndef __linux__ radius_clientaddr.sin_len = sizeof(radius_clientaddr); #endif radius_clientaddr.sin_family = AF_INET; #ifndef __linux__ radius_serveraddr.sin_len = sizeof(radius_serveraddr); #endif radius_serveraddr.sin_family = AF_INET; radius_serveraddr.sin_port = htons(1812); /* default port */ radius_sys = register_sysctl_table(net_table, 0); ieee80211_authenticator_backend_register(&radius); return 0; } module_init(init_ieee80211_radius); static void __exit exit_ieee80211_radius(void) { if (radius_sys) unregister_sysctl_table(radius_sys); ieee80211_authenticator_backend_unregister(&radius); } module_exit(exit_ieee80211_radius);