X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=src%2Fmodules%2Frlm_eap%2Feap.c;h=4eb9c046fc57fe9971a604bad80490df9e6ad7b5;hb=deca39b0fd7db4296aceb4b185a8a9ffa5297faf;hp=504e8c6a343486f8df43ceea6257b6012b5a471b;hpb=fb42d3055bbca56b05b93d3519ea2a11bf720298;p=freeradius.git diff --git a/src/modules/rlm_eap/eap.c b/src/modules/rlm_eap/eap.c index 504e8c6..4eb9c04 100644 --- a/src/modules/rlm_eap/eap.c +++ b/src/modules/rlm_eap/eap.c @@ -15,9 +15,9 @@ * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA * - * Copyright 2000-2003 The FreeRADIUS server project + * Copyright 2000-2003,2006 The FreeRADIUS server project * Copyright 2001 hereUare Communications, Inc. * Copyright 2003 Alan DeKok */ @@ -33,7 +33,7 @@ * +-+-+-+-+ * * - * EAP Request and Response Packet Format + * EAP Request and Response Packet Format * --- ------- --- -------- ------ ------ * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -44,7 +44,7 @@ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+- * * - * EAP Success and Failure Packet Format + * EAP Success and Failure Packet Format * --- ------- --- ------- ------ ------ * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 @@ -54,9 +54,11 @@ * */ -#include "rlm_eap.h" +#include +#include +RCSID("$Id$") -static const char rcsid[] = "$Id$"; +#include "rlm_eap.h" static const char *eap_codes[] = { "", /* 0 is invalid */ @@ -66,74 +68,19 @@ static const char *eap_codes[] = { "failure" }; -static const char *eap_types[] = { - "", - "identity", - "notification", - "nak", /* NAK */ - "md5", - "otp", - "gtc", - "7", - "8", - "9", - "10", - "11", - "12", - "tls", /* 13 */ - "14", - "15", - "16", - "leap", /* 17 */ - "18", - "19", - "20", - "ttls", /* 21 */ - "22", - "23", - "24", - "peap", /* 25 */ - "26", - "27", - "28", - "mschapv2" -}; - -/* - * Return an EAP-Type for a particular name. - */ -int eaptype_name2type(const char *name) -{ - int i; - - for (i = 0; i <= PW_EAP_MAX_TYPES; i++) { - if (strcmp(name, eap_types[i]) == 0) { - return i; - } - } - - return -1; -} - /* * Load all the required eap authentication types. - * Get all the supported EAP-types from config file. + * Get all the supported EAP-types from config file. */ int eaptype_load(EAP_TYPES **type, int eap_type, CONF_SECTION *cs) { char buffer[64]; - lt_dlhandle handle; + char namebuf[64]; + const char *eaptype_name; EAP_TYPES *node; - snprintf(buffer, sizeof(buffer), "rlm_eap_%s", eap_types[eap_type]); - - /* Link the loaded EAP-Type */ - handle = lt_dlopenext(buffer); - if (handle == NULL) { - radlog(L_ERR, "rlm_eap: Failed to link EAP-Type/%s: %s", - eap_types[eap_type], lt_dlerror()); - return -1; - } + eaptype_name = eaptype_type2name(eap_type, namebuf, sizeof(namebuf)); + snprintf(buffer, sizeof(buffer), "rlm_eap_%s", eaptype_name); /* Make room for the EAP-Type */ node = (EAP_TYPES *)malloc(sizeof(EAP_TYPES)); @@ -144,43 +91,79 @@ int eaptype_load(EAP_TYPES **type, int eap_type, CONF_SECTION *cs) memset(node, 0, sizeof(*node)); /* fill in the structure */ - node->handle = handle; node->cs = cs; - node->typename = eap_types[eap_type]; + + /* + * In general, this is a terrible idea. It works here + * solely because the eap_type2name function returns a + * 'static const char *' pointer sometimes, and we can + * ONLY link to module which are named in that static + * array. + */ + node->typename = eaptype_name; node->type_data = NULL; - + +#ifdef WITHOUT_LIBLTDL +#ifdef WITH_DLOPEN +#include + +#ifdef RTLD_SELF + node->type = (EAP_TYPE *)lt_dlsym(RTLD_SELF, buffer); + if (node->type) goto open_self; +#endif +#endif +#endif + + /* Link the loaded EAP-Type */ + node->handle = lt_dlopenext(buffer); + if (node->handle == NULL) { + free(node); + radlog(L_ERR, "rlm_eap: Failed to link EAP-Type/%s: %s", + eaptype_name, lt_dlerror()); + return -1; + } + node->type = (EAP_TYPE *)lt_dlsym(node->handle, buffer); if (!node->type) { radlog(L_ERR, "rlm_eap: Failed linking to %s structure in %s: %s", - buffer, eap_types[eap_type], lt_dlerror()); + buffer, eaptype_name, lt_dlerror()); lt_dlclose(node->handle); /* ignore any errors */ free(node); return -1; } - if ((node->type->attach) && - ((node->type->attach)(node->cs, &(node->type_data)) < 0)) { + +#if defined(WITHOUT_LIBLTDL) && defined (WITH_DLOPEN) && defined(RTLD_SELF) +open_self: +#endif + cf_log_module(cs, "Linked to sub-module %s", buffer); + + cf_log_module(cs, "Instantiating eap-%s", eaptype_name); + + if ((node->type->attach) && + ((node->type->attach)(node->cs, &(node->type_data)) < 0)) { radlog(L_ERR, "rlm_eap: Failed to initialize type %s", - eap_types[eap_type]); + eaptype_name); lt_dlclose(node->handle); free(node); return -1; } - DEBUG("rlm_eap: Loaded and initialized type %s", eap_types[eap_type]); *type = node; return 0; } - /* * Call the appropriate handle with the right eap_type. */ static int eaptype_call(EAP_TYPES *atype, EAP_HANDLER *handler) { int rcode = 1; + REQUEST *request = handler->request; + const char *module = request->module; - DEBUG2(" rlm_eap: processing type %s", atype->typename); + RDEBUG2("processing type %s", atype->typename); + request->module = atype->typename; rad_assert(atype != NULL); @@ -210,23 +193,29 @@ static int eaptype_call(EAP_TYPES *atype, EAP_HANDLER *handler) default: /* Should never enter here */ - radlog(L_DBG, "rlm_eap: Invalid operation on eap_type"); + RDEBUG("Internal sanity check failed on eap_type"); rcode = 0; break; } + request->module = module; return rcode; } /* * Based on TYPE, call the appropriate EAP-type handler - * Default to the configured EAP-Type - * for all Unsupported EAP-Types + * Default to the configured EAP-Type + * for all Unsupported EAP-Types */ int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler) { + size_t i; + unsigned int default_eap_type = inst->default_eap_type; eaptype_t *eaptype; - int default_eap_type = inst->default_eap_type; + VALUE_PAIR *vp; + char namebuf[64]; + const char *eaptype_name; + REQUEST *request = handler->request; eaptype = &handler->eap_ds->response->type; @@ -235,7 +224,15 @@ int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler) */ if ((eaptype->type == 0) || (eaptype->type > PW_EAP_MAX_TYPES)) { - DEBUG2(" rlm_eap: Asked to select bad type"); + RDEBUG2("Asked to select bad type"); + return EAP_INVALID; + } + + /* + * Multiple levels of nesting are invalid. + */ + if (handler->request->parent && handler->request->parent->parent) { + RDEBUG2("Multiple levels of TLS nesting is invalid."); return EAP_INVALID; } @@ -244,72 +241,69 @@ int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler) */ switch(eaptype->type) { case PW_EAP_IDENTITY: - { - VALUE_PAIR *vp; + RDEBUG2("EAP Identity"); - DEBUG2(" rlm_eap: EAP Identity"); - - /* - * Allow per-user configuration of EAP types. - */ - vp = pairfind(handler->request->config_items, - PW_EAP_TYPE); - if (vp) default_eap_type = vp->lvalue; + /* + * Allow per-user configuration of EAP types. + */ + vp = pairfind(handler->request->config_items, + PW_EAP_TYPE, 0); + if (vp) default_eap_type = vp->vp_integer; - do_initiate: - /* - * Ensure it's valid. - */ - if ((default_eap_type < PW_EAP_MD5) || - (default_eap_type > PW_EAP_MAX_TYPES) || - (inst->types[default_eap_type] == NULL)) { - DEBUG2(" rlm_eap: No such EAP type %d", - default_eap_type); - return EAP_INVALID; - } - - handler->stage = INITIATE; - handler->eap_type = default_eap_type; + do_initiate: + /* + * Ensure it's valid. + */ + if ((default_eap_type < PW_EAP_MD5) || + (default_eap_type > PW_EAP_MAX_TYPES) || + (inst->types[default_eap_type] == NULL)) { + RDEBUG2("No such EAP type %s", + eaptype_type2name(default_eap_type, + namebuf, sizeof(namebuf))); + return EAP_INVALID; + } - /* - * Wild & crazy stuff! For TTLS & PEAP, - * we initiate a TLS session, and then pass - * that session data to TTLS or PEAP for - * the authenticate stage. - * - * Handler->eap_type holds the TRUE - * type. - */ - if ((default_eap_type == PW_EAP_TTLS) || - (default_eap_type == PW_EAP_PEAP)) { - default_eap_type = PW_EAP_TLS; - } + handler->stage = INITIATE; + handler->eap_type = default_eap_type; + /* + * Wild & crazy stuff! For TTLS & PEAP, we + * initiate a TLS session, and then pass that + * session data to TTLS or PEAP for the + * authenticate stage. + * + * Handler->eap_type holds the TRUE type. + */ + if ((default_eap_type == PW_EAP_TTLS) || + (default_eap_type == PW_EAP_PEAP)) { + default_eap_type = PW_EAP_TLS; + } - /* - * We don't do TLS inside of TLS, as it's - * a bad idea... - */ - if (((handler->request->options & RAD_REQUEST_OPTION_FAKE_REQUEST) != 0) && - (default_eap_type == PW_EAP_TLS)) { - DEBUG2(" rlm_eap: Unable to tunnel TLS inside of TLS"); - return EAP_INVALID; - } + if ((default_eap_type == PW_EAP_TNC) && + !handler->request->parent) { + RDEBUG2("ERROR: EAP-TNC must be run inside of a TLS method."); + return EAP_INVALID; + } - if (eaptype_call(inst->types[default_eap_type], - handler) == 0) { - DEBUG2(" rlm_eap: Default EAP type %s failed in initiate", eap_types[default_eap_type]); - return EAP_INVALID; - } + if (eaptype_call(inst->types[default_eap_type], + handler) == 0) { + RDEBUG2("Default EAP type %s failed in initiate", + eaptype_type2name(default_eap_type, + namebuf, sizeof(namebuf))); + return EAP_INVALID; } break; case PW_EAP_NAK: /* - * The one byte of NAK data is the preferred EAP type - * of the client. + * The NAK data is the preferred EAP type(s) of + * the client. + * + * RFC 3748 says to list one or more proposed + * alternative types, one per octet, or to use + * 0 for no alternative. */ - DEBUG2(" rlm_eap: EAP NAK"); + RDEBUG2("EAP NAK"); /* * Delete old data, if necessary. @@ -320,25 +314,89 @@ int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler) handler->opaque = NULL; } - /* - * It is invalid to request identity, - * notification & nak in nak - */ if (eaptype->data == NULL) { - DEBUG2(" rlm_eap: Empty NAK packet, cannot decide what EAP type the client wants."); + RDEBUG2("Empty NAK packet, cannot decide what EAP type the client wants."); return EAP_INVALID; } - if ((eaptype->data[0] < PW_EAP_MD5) || - (eaptype->data[0] > PW_EAP_MAX_TYPES)) { - DEBUG2(" rlm_eap: NAK asked for bad type %d", - eaptype->data[0]); - return EAP_INVALID; + /* + * Pick one type out of the one they asked for, + * as they may have asked for many. + */ + default_eap_type = 0; + vp = pairfind(handler->request->config_items, + PW_EAP_TYPE, 0); + for (i = 0; i < eaptype->length; i++) { + /* + * It is invalid to request identity, + * notification & nak in nak. + * + * Type 0 is valid, and means there are no + * common choices. + */ + if (eaptype->data[i] < PW_EAP_MD5) { + RDEBUG2("NAK asked for bad type %d", + eaptype->data[i]); + return EAP_INVALID; + } + + if ((eaptype->data[i] > PW_EAP_MAX_TYPES) || + !inst->types[eaptype->data[i]]) { + DICT_VALUE *dv; + + dv = dict_valbyattr(PW_EAP_TYPE, 0, eaptype->data[i]); + if (dv) { + RDEBUG2("NAK asked for unsupported type %s", + dv->name); + } else { + RDEBUG2("NAK asked for unsupported type %d", + eaptype->data[i]); + } + continue; + } + + eaptype_name = eaptype_type2name(eaptype->data[i], + namebuf, + sizeof(namebuf)); + + /* + * Prevent a firestorm if the client is confused. + */ + if (handler->eap_type == eaptype->data[i]) { + RDEBUG2("ERROR! Our request for %s was NAK'd with a request for %s. Skipping the requested type.", + eaptype_name, eaptype_name); + continue; + } + + /* + * Enforce per-user configuration of EAP + * types. + */ + if (vp && (vp->vp_integer != eaptype->data[i])) { + char mynamebuf[64]; + RDEBUG2("Client wants %s, while we require %s. Skipping the requested type.", + eaptype_name, + eaptype_type2name(vp->vp_integer, + mynamebuf, + sizeof(mynamebuf))); + continue; + } + + default_eap_type = eaptype->data[i]; + break; } - default_eap_type = eaptype->data[0]; - DEBUG2(" rlm_eap: EAP-NAK asked for EAP-Type/%s", - eap_types[default_eap_type]); + /* + * We probably want to return 'fail' here... + */ + if (!default_eap_type) { + RDEBUG2("No common EAP types found."); + return EAP_INVALID; + } + eaptype_name = eaptype_type2name(default_eap_type, + namebuf, sizeof(namebuf)); + RDEBUG2("EAP-NAK asked for EAP-Type/%s", + eaptype_name); goto do_initiate; break; @@ -347,24 +405,26 @@ int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler) * Key off of the configured sub-modules. */ default: - DEBUG2(" rlm_eap: EAP_TYPE - %s", - eap_types[eaptype->type]); - + eaptype_name = eaptype_type2name(eaptype->type, + namebuf, + sizeof(namebuf)); + RDEBUG2("EAP/%s", eaptype_name); + /* * We haven't configured it, it doesn't exit. */ if (!inst->types[eaptype->type]) { - DEBUG2(" rlm_eap: EAP type %d is unsupported", + RDEBUG2("EAP type %d is unsupported", eaptype->type); return EAP_INVALID; } - + rad_assert(handler->stage == AUTHENTICATE); handler->eap_type = eaptype->type; if (eaptype_call(inst->types[eaptype->type], handler) == 0) { - DEBUG2(" rlm_eap: Handler failed in EAP type %d", - eaptype->type); + RDEBUG2("Handler failed in EAP/%s", + eaptype_name); return EAP_INVALID; } break; @@ -373,154 +433,6 @@ int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler) return EAP_OK; } -/* - * Handles multiple EAP-Message attrs - * ie concatenates all to get the complete EAP packet - * - * NOTE: Sometimes Framed-MTU might contain the length of EAP-Message, - * refer fragmentation in rfc2869. - */ -eap_packet_t *eap_attribute(VALUE_PAIR *vps) -{ - VALUE_PAIR *first, *vp; - eap_packet_t *eap_packet; - unsigned char *ptr; - uint16_t len; - int total_len; - - /* - * Get only EAP-Message attribute list - */ - first = pairfind(vps, PW_EAP_MESSAGE); - if (first == NULL) { - radlog(L_ERR, "rlm_eap: EAP-Message not found"); - return NULL; - } - - /* - * Sanity check the length before doing anything. - */ - if (first->length < 4) { - radlog(L_ERR, "rlm_eap: EAP packet is too short."); - return NULL; - } - - /* - * Get the Actual length from the EAP packet - * First EAP-Message contains the EAP packet header - */ - memcpy(&len, first->strvalue + 2, sizeof(len)); - len = ntohs(len); - - /* - * Take out even more weird things. - */ - if (len < 4) { - radlog(L_ERR, "rlm_eap: EAP packet has invalid length."); - return NULL; - } - - /* - * Sanity check the length, BEFORE malloc'ing memory. - */ - total_len = 0; - for (vp = first; vp; vp = pairfind(vp->next, PW_EAP_MESSAGE)) { - total_len += vp->length; - - if (total_len > len) { - radlog(L_ERR, "rlm_eap: Malformed EAP packet. Length in packet header does not match actual length"); - return NULL; - } - } - - /* - * If the length is SMALLER, die, too. - */ - if (total_len < len) { - radlog(L_ERR, "rlm_eap: Malformed EAP packet. Length in packet header does not match actual length"); - return NULL; - } - - /* - * Now that we know the lengths are OK, allocate memory. - */ - eap_packet = (eap_packet_t *) malloc(len); - if (eap_packet == NULL) { - radlog(L_ERR, "rlm_eap: out of memory"); - return NULL; - } - - /* - * Copy the data from EAP-Message's over to out EAP packet. - */ - ptr = (unsigned char *)eap_packet; - - /* RADIUS ensures order of attrs, so just concatenate all */ - for (vp = first; vp; vp = pairfind(vp->next, PW_EAP_MESSAGE)) { - memcpy(ptr, vp->strvalue, vp->length); - ptr += vp->length; - } - - return eap_packet; -} - -/* - * EAP packet format to be sent over the wire - * - * i.e. code+id+length+data where data = null/type+typedata - * based on code. - */ -static int eap_wireformat(EAP_PACKET *reply) -{ - eap_packet_t *hdr; - uint16_t total_length = 0; - - if (reply == NULL) return EAP_INVALID; - - total_length = EAP_HEADER_LEN; - if (reply->code < 3) { - total_length += 1/*EAPtype*/; - if (reply->type.data && reply->type.length > 0) { - total_length += reply->type.length; - } - } - - reply->packet = (unsigned char *)malloc(total_length); - hdr = (eap_packet_t *)reply->packet; - if (!hdr) { - radlog(L_ERR, "rlm_eap: out of memory"); - return EAP_INVALID; - } - - hdr->code = (reply->code & 0xFF); - hdr->id = (reply->id & 0xFF); - total_length = htons(total_length); - memcpy(hdr->length, &total_length, sizeof(uint16_t)); - - /* - * Request and Response packets are special. - */ - if ((reply->code == PW_EAP_REQUEST) || - (reply->code == PW_EAP_RESPONSE)) { - hdr->data[0] = (reply->type.type & 0xFF); - - /* - * Here since we cannot know the typedata format and length - * - * Type_data is expected to be wired by each EAP-Type - * - * Zero length/No typedata is supported as long as - * type is defined - */ - if (reply->type.data && reply->type.length > 0) { - memcpy(&hdr->data[1], reply->type.data, reply->type.length); - free(reply->type.data); - reply->type.data = reply->packet + EAP_HEADER_LEN + 1/*EAPtype*/; - } - } - - return EAP_VALID; -} /* * compose EAP reply packet in EAP-Message attr of RADIUS. If @@ -531,11 +443,8 @@ static int eap_wireformat(EAP_PACKET *reply) */ int eap_compose(EAP_HANDLER *handler) { - uint16_t eap_len, len; - VALUE_PAIR *eap_msg; VALUE_PAIR *vp; eap_packet_t *eap_packet; - unsigned char *ptr; REQUEST *request = handler->request; EAP_DS *eap_ds = handler->eap_ds; EAP_PACKET *reply = eap_ds->request; @@ -562,16 +471,23 @@ int eap_compose(EAP_HANDLER *handler) * mentioned restriction. */ reply->id = handler->eap_ds->response->id; - + switch (reply->code) { /* * The Id is a simple "ack" for success * and failure. + * + * RFC 3748 section 4.2 says + * + * ... The Identifier field MUST match + * the Identifier field of the Response + * packet that it is sent in response + * to. */ case PW_EAP_SUCCESS: case PW_EAP_FAILURE: break; - + /* * We've sent a response to their * request, the Id is incremented. @@ -580,7 +496,7 @@ int eap_compose(EAP_HANDLER *handler) ++reply->id; } } else { - DEBUG2(" rlm_eap: Underlying EAP-Type set EAP ID to %d", + RDEBUG2("Underlying EAP-Type set EAP ID to %d", reply->id); } @@ -600,37 +516,20 @@ int eap_compose(EAP_HANDLER *handler) eap_ds->request->type.type = handler->eap_type; } - + /* + * FIXME: We malloc memory for the eap packet, and then + * immediately copy that data into VALUE_PAIRs. This + * could be done more efficiently... + */ if (eap_wireformat(reply) == EAP_INVALID) { return RLM_MODULE_INVALID; } eap_packet = (eap_packet_t *)reply->packet; - memcpy(&eap_len, &(eap_packet->length), sizeof(uint16_t)); - len = eap_len = ntohs(eap_len); - ptr = (unsigned char *)eap_packet; - - do { - if (eap_len > 253) { - len = 253; - eap_len -= 253; - } else { - len = eap_len; - eap_len = 0; - } - - /* - * create a value pair & append it to the request reply list - * This memory gets freed up when request is freed up - */ - eap_msg = paircreate(PW_EAP_MESSAGE, PW_TYPE_OCTETS); - memcpy(eap_msg->strvalue, ptr, len); - eap_msg->length = len; - pairadd(&(request->reply->vps), eap_msg); - ptr += len; - eap_msg = NULL; - } while (eap_len); + vp = eap_packet2vp(eap_packet); + if (!vp) return RLM_MODULE_INVALID; + pairadd(&(request->reply->vps), vp); /* * EAP-Message is always associated with @@ -639,10 +538,10 @@ int eap_compose(EAP_HANDLER *handler) * Don't add a Message-Authenticator if it's already * there. */ - vp = pairfind(request->reply->vps, PW_MESSAGE_AUTHENTICATOR); + vp = pairfind(request->reply->vps, PW_MESSAGE_AUTHENTICATOR, 0); if (!vp) { - vp = paircreate(PW_MESSAGE_AUTHENTICATOR, PW_TYPE_OCTETS); - memset(vp->strvalue, 0, AUTH_VECTOR_LEN); + vp = paircreate(PW_MESSAGE_AUTHENTICATOR, 0, PW_TYPE_OCTETS); + memset(vp->vp_octets, 0, AUTH_VECTOR_LEN); vp->length = AUTH_VECTOR_LEN; pairadd(&(request->reply->vps), vp); } @@ -667,9 +566,19 @@ int eap_compose(EAP_HANDLER *handler) rcode = RLM_MODULE_HANDLED; break; default: + /* + * When we're pulling MS-CHAPv2 out of EAP-MS-CHAPv2, + * we do so WITHOUT setting a reply code, as the + * request is being proxied. + */ + if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) { + return RLM_MODULE_HANDLED; + } + /* Should never enter here */ radlog(L_ERR, "rlm_eap: reply code %d is unknown, Rejecting the request.", reply->code); request->reply->code = PW_AUTHENTICATION_REJECT; + reply->code = PW_EAP_FAILURE; rcode = RLM_MODULE_REJECT; break; } @@ -683,14 +592,12 @@ int eap_compose(EAP_HANDLER *handler) */ int eap_start(rlm_eap_t *inst, REQUEST *request) { - VALUE_PAIR *vp; + VALUE_PAIR *vp, *proxy; VALUE_PAIR *eap_msg; - EAP_DS *eap_ds; - EAP_HANDLER handler; - eap_msg = pairfind(request->packet->vps, PW_EAP_MESSAGE); + eap_msg = pairfind(request->packet->vps, PW_EAP_MESSAGE, 0); if (eap_msg == NULL) { - DEBUG2(" rlm_eap: No EAP-Message, not doing EAP"); + RDEBUG2("No EAP-Message, not doing EAP"); return EAP_NOOP; } @@ -698,156 +605,210 @@ int eap_start(rlm_eap_t *inst, REQUEST *request) * Look for EAP-Type = None (FreeRADIUS specific attribute) * this allows you to NOT do EAP for some users. */ - vp = pairfind(request->packet->vps, PW_EAP_TYPE); - if (vp && vp->lvalue == 0) { - DEBUG2(" rlm_eap: Found EAP-Message, but EAP-Type = None, so we're not doing EAP."); + vp = pairfind(request->packet->vps, PW_EAP_TYPE, 0); + if (vp && vp->vp_integer == 0) { + RDEBUG2("Found EAP-Message, but EAP-Type = None, so we're not doing EAP."); return EAP_NOOP; } /* * http://www.freeradius.org/rfc/rfc2869.html#EAP-Message * - * This is handled by rad_recv(). + * Checks for Message-Authenticator are handled by rad_recv(). */ /* - * We're allowed only a few codes. Request, Response, - * Success, or Failure. + * Check for a Proxy-To-Realm. Don't get excited over LOCAL + * realms (sigh). */ - if ((eap_msg->strvalue[0] == 0) || - (eap_msg->strvalue[0] > PW_EAP_MAX_CODES)) { - DEBUG2(" rlm_eap: Unknown EAP packet"); - } else { - DEBUG2(" rlm_eap: EAP packet type %s id %d length %d", - eap_codes[eap_msg->strvalue[0]], - eap_msg->strvalue[1], - eap_msg->length); - } + proxy = pairfind(request->config_items, PW_PROXY_TO_REALM, 0); + if (proxy) { + REALM *realm; - /* - * If we've been configured to proxy, do nothing. - * - * Note that we don't check if the realm is local. - * We figure that anyone bright enough to add - * Proxy-To-Realm is bright enough to NOT do so - * when it's a local realm. - */ - if (pairfind(request->config_items, PW_PROXY_TO_REALM) != NULL) { - return EAP_NOOP; + /* + * If it's a LOCAL realm, then we're not proxying + * to it. + */ + realm = realm_find(proxy->vp_strvalue); + if (!realm || (realm && (realm->auth_pool == NULL))) { + proxy = NULL; + } } /* - * Not a start message. Don't start anything. + * Check the length before de-referencing the contents. + * + * Lengths of zero are required by the RFC for EAP-Start, + * but we've never seen them in practice. * - * Later EAP messages are longer than the 'start' message, - * so this function returns 'no start found', so that - * the rest of the EAP code can use the State attribute - * to match this EAP-Message to an ongoing conversation. + * Lengths of two are what we see in practice as + * EAP-Starts. */ - if (eap_msg->length != EAP_START) { - DEBUG2(" rlm_eap: No EAP Start, assuming it's an on-going EAP conversation"); + if ((eap_msg->length == 0) || (eap_msg->length == 2)) { + EAP_DS *eap_ds; + EAP_HANDLER handler; /* - * Add the 'EAP-Type' attribute to the request, - * if it's part of an EAP conversation, and the - * EAP sub-type is in the EAP packet. + * It's a valid EAP-Start, but the request + * was marked as being proxied. So we don't + * do EAP, as the home server will do it. + */ + if (proxy) { + do_proxy: + RDEBUG2("Request is supposed to be proxied to Realm %s. Not doing EAP.", proxy->vp_strvalue); + return EAP_NOOP; + } + + RDEBUG2("Got EAP_START message"); + if ((eap_ds = eap_ds_alloc()) == NULL) { + RDEBUG2("EAP Start failed in allocation"); + return EAP_FAIL; + } + + /* + * It's an EAP-Start packet. Tell them to stop wasting + * our time, and give us an EAP-Identity packet. * - * Store the EAP type in the request, so modules - * outside of EAP can check & use it. + * Hmm... we should probably check the contents of the + * EAP-Start packet for something... */ - if (((eap_msg->strvalue[0] == PW_EAP_REQUEST) || - (eap_msg->strvalue[0] == PW_EAP_RESPONSE)) && - (eap_msg->length >= (EAP_HEADER_LEN + 1))) { - /* - * Create an EAP-Type of the type which - * was NAK'd, or of the type in the packet. - */ - vp = paircreate(PW_EAP_TYPE, PW_TYPE_INTEGER); - if (vp) { - vp->lvalue = eap_msg->strvalue[4]; - pairadd(&(request->packet->vps), vp); - } - - /* - * We've been told to ignore unknown EAP - * types, AND it's an unknown type. - * Return "NOOP", which will cause the - * eap_authorize() to return NOOP. - * - * EAP-Identity, Notification, and NAK - * are all handled internally, so they - * never have handlers. - */ - if ((eap_msg->strvalue[4] >= PW_EAP_MD5) && - inst->ignore_unknown_eap_types && - ((eap_msg->strvalue[4] == 0) || - (eap_msg->strvalue[4] > PW_EAP_MAX_TYPES) || - (inst->types[eap_msg->strvalue[4]] == NULL))) { - return EAP_NOOP; - } + eap_ds->request->code = PW_EAP_REQUEST; + eap_ds->request->type.type = PW_EAP_IDENTITY; - /* - * They're NAKing the EAP type we wanted - * to use, and asking for one which we don't - * support. - * - * NAK is code + id + length1 + length + NAK - * + requested EAP type. - * - * We know at this point that we can't - * handle the request. We could either - * return an EAP-Fail here, but it's not - * too critical. - * - * By returning "noop", we can ensure - * that authorize() returns NOOP, and - * another module may choose to proxy - * the request. - */ - if ((eap_msg->strvalue[4] == PW_EAP_NAK) && - (eap_msg->length >= (EAP_HEADER_LEN + 2)) && - inst->ignore_unknown_eap_types && - ((eap_msg->strvalue[5] == 0) || - (eap_msg->strvalue[5] > PW_EAP_MAX_TYPES) || - (inst->types[eap_msg->strvalue[5]] == NULL))) { - return EAP_NOOP; - } - } /* else it's not an EAP-Request or EAP-Response */ - /* - * No EAP-Start found. + * We don't have a handler, but eap_compose needs one, + * (for various reasons), so we fake it out here. */ - return EAP_NOTFOUND; + memset(&handler, 0, sizeof(handler)); + handler.request = request; + handler.eap_ds = eap_ds; + + eap_compose(&handler); + + eap_ds_free(&eap_ds); + return EAP_FOUND; + } /* end of handling EAP-Start */ + + /* + * The EAP packet header is 4 bytes, plus one byte of + * EAP sub-type. Short packets are discarded, unless + * we're proxying. + */ + if (eap_msg->length < (EAP_HEADER_LEN + 1)) { + if (proxy) goto do_proxy; + + RDEBUG2("Ignoring EAP-Message which is too short to be meaningful."); + return EAP_FAIL; } - DEBUG2(" rlm_eap: Got EAP_START message"); - if ((eap_ds = eap_ds_alloc()) == NULL) { - DEBUG2(" rlm_eap: EAP Start failed in allocation"); + /* + * Create an EAP-Type containing the EAP-type + * from the packet. + */ + vp = paircreate(PW_EAP_TYPE, 0, PW_TYPE_INTEGER); + if (vp) { + vp->vp_integer = eap_msg->vp_octets[4]; + pairadd(&(request->packet->vps), vp); + } + + /* + * If the request was marked to be proxied, do it now. + * This is done after checking for a valid length + * (which may not be good), and after adding the EAP-Type + * attribute. This lets other modules selectively cancel + * proxying based on EAP-Type. + */ + if (proxy) goto do_proxy; + + /* + * From now on, we're supposed to be handling the + * EAP packet. We better understand it... + */ + + /* + * We're allowed only a few codes. Request, Response, + * Success, or Failure. + */ + if ((eap_msg->vp_octets[0] == 0) || + (eap_msg->vp_octets[0] > PW_EAP_MAX_CODES)) { + RDEBUG2("Unknown EAP packet"); + } else { + RDEBUG2("EAP packet type %s id %d length %d", + eap_codes[eap_msg->vp_octets[0]], + eap_msg->vp_octets[1], + eap_msg->length); + } + + /* + * We handle request and responses. The only other defined + * codes are success and fail. The client SHOULD NOT be + * sending success/fail packets to us, as it doesn't make + * sense. + */ + if ((eap_msg->vp_octets[0] != PW_EAP_REQUEST) && + (eap_msg->vp_octets[0] != PW_EAP_RESPONSE)) { + RDEBUG2("Ignoring EAP packet which we don't know how to handle."); return EAP_FAIL; } /* - * It's an EAP-Start packet. Tell them to stop wasting - * our time, and give us an EAP-Identity packet. + * We've been told to ignore unknown EAP types, AND it's + * an unknown type. Return "NOOP", which will cause the + * eap_authorize() to return NOOP. * - * Hmm... we should probably check the contents of the - * EAP-Start packet for something... + * EAP-Identity, Notification, and NAK are all handled + * internally, so they never have handlers. */ - eap_ds->request->code = PW_EAP_REQUEST; - eap_ds->request->type.type = PW_EAP_IDENTITY; + if ((eap_msg->vp_octets[4] >= PW_EAP_MD5) && + inst->ignore_unknown_eap_types && + ((eap_msg->vp_octets[4] == 0) || + (eap_msg->vp_octets[4] > PW_EAP_MAX_TYPES) || + (inst->types[eap_msg->vp_octets[4]] == NULL))) { + RDEBUG2(" Ignoring Unknown EAP type"); + return EAP_NOOP; + } /* - * We don't have a handler, but eap_compose needs one, - * (for various reasons), so we fake it out here. + * They're NAKing the EAP type we wanted to use, and + * asking for one which we don't support. + * + * NAK is code + id + length1 + length + NAK + * + requested EAP type(s). + * + * We know at this point that we can't handle the + * request. We could either return an EAP-Fail here, but + * it's not too critical. + * + * By returning "noop", we can ensure that authorize() + * returns NOOP, and another module may choose to proxy + * the request. */ - memset(&handler, 0, sizeof(handler)); - handler.request = request; - handler.eap_ds = eap_ds; + if ((eap_msg->vp_octets[4] == PW_EAP_NAK) && + (eap_msg->length >= (EAP_HEADER_LEN + 2)) && + inst->ignore_unknown_eap_types && + ((eap_msg->vp_octets[5] == 0) || + (eap_msg->vp_octets[5] > PW_EAP_MAX_TYPES) || + (inst->types[eap_msg->vp_octets[5]] == NULL))) { + RDEBUG2("Ignoring NAK with request for unknown EAP type"); + return EAP_NOOP; + } - eap_compose(&handler); + if ((eap_msg->vp_octets[4] == PW_EAP_TTLS) || + (eap_msg->vp_octets[4] == PW_EAP_PEAP)) { + RDEBUG2("Continuing tunnel setup."); + return EAP_OK; + } - eap_ds_free(&eap_ds); - return EAP_FOUND; + /* + * Later EAP messages are longer than the 'start' + * message, so if everything is OK, this function returns + * 'no start found', so that the rest of the EAP code can + * use the State attribute to match this EAP-Message to + * an ongoing conversation. + */ + RDEBUG2("No EAP Start, assuming it's an on-going EAP conversation"); + + return EAP_NOTFOUND; } /* @@ -855,6 +816,15 @@ int eap_start(rlm_eap_t *inst, REQUEST *request) */ void eap_fail(EAP_HANDLER *handler) { + /* + * Delete any previous replies. + */ + pairdelete(&handler->request->reply->vps, PW_EAP_MESSAGE, 0); + pairdelete(&handler->request->reply->vps, PW_STATE, 0); + + eap_packet_free(&handler->eap_ds->request); + handler->eap_ds->request = eap_packet_alloc(); + handler->eap_ds->request->code = PW_EAP_FAILURE; eap_compose(handler); } @@ -871,7 +841,7 @@ void eap_success(EAP_HANDLER *handler) /* * Basic EAP packet verfications & validations */ -static int eap_validation(eap_packet_t *eap_packet) +static int eap_validation(REQUEST *request, eap_packet_t *eap_packet) { uint16_t len; @@ -887,15 +857,15 @@ static int eap_validation(eap_packet_t *eap_packet) (eap_packet->data[0] <= 0) || (eap_packet->data[0] > PW_EAP_MAX_TYPES)) { - radlog(L_AUTH, "rlm_eap: Incorrect EAP Message, " - "Ignoring the packet"); + radlog_request(L_AUTH, 0, request, + "Badly formatted EAP Message: Ignoring the packet"); return EAP_INVALID; } /* we don't expect notification, but we send it */ if (eap_packet->data[0] == PW_EAP_NOTIFICATION) { - radlog(L_AUTH, "rlm_eap: Got NOTIFICATION, " - "Ignoring the packet"); + radlog_request(L_AUTH, 0, request, "Got NOTIFICATION, " + "Ignoring the packet"); return EAP_INVALID; } @@ -906,7 +876,7 @@ static int eap_validation(eap_packet_t *eap_packet) /* * Get the user Identity only from EAP-Identity packets */ -static char *eap_identity(eap_packet_t *eap_packet) +static char *eap_identity(REQUEST *request, eap_packet_t *eap_packet) { int size; uint16_t len; @@ -922,16 +892,12 @@ static char *eap_identity(eap_packet_t *eap_packet) len = ntohs(len); if ((len <= 5) || (eap_packet->data[1] == 0x00)) { - radlog(L_ERR, "rlm_eap: UserIdentity Unknown "); + RDEBUG("UserIdentity Unknown "); return NULL; } size = len - 5; - identity = (char *)malloc(size + 1); - if (identity == NULL) { - radlog(L_ERR, "rlm_eap: out of memory"); - return NULL; - } + identity = rad_malloc(size + 1); memcpy(identity, &eap_packet->data[1], size); identity[size] = '\0'; @@ -1004,11 +970,12 @@ EAP_HANDLER *eap_handler(rlm_eap_t *inst, eap_packet_t **eap_packet_p, { EAP_HANDLER *handler = NULL; eap_packet_t *eap_packet = *eap_packet_p; + VALUE_PAIR *vp; /* * Ensure it's a valid EAP-Request, or EAP-Response. */ - if (eap_validation(eap_packet) == EAP_INVALID) { + if (eap_validation(request, eap_packet) == EAP_INVALID) { free(*eap_packet_p); *eap_packet_p = NULL; return NULL; @@ -1022,8 +989,8 @@ EAP_HANDLER *eap_handler(rlm_eap_t *inst, eap_packet_t **eap_packet_p, handler = eaplist_find(inst, request, eap_packet); if (handler == NULL) { /* Either send EAP_Identity or EAP-Fail */ - radlog(L_ERR, "rlm_eap: Either EAP-request timed out OR" - " EAP-response to an unknown EAP-request"); + RDEBUG("Either EAP-request timed out OR" + " EAP-response to an unknown EAP-request"); free(*eap_packet_p); *eap_packet_p = NULL; return NULL; @@ -1039,15 +1006,54 @@ EAP_HANDLER *eap_handler(rlm_eap_t *inst, eap_packet_t **eap_packet_p, */ if ((eap_packet->data[0] != PW_EAP_NAK) && (eap_packet->data[0] != handler->eap_type)) { - radlog(L_ERR, "rlm_eap: Response appears to match, but EAP type is wrong."); + RDEBUG("Response appears to match, but EAP type is wrong."); free(*eap_packet_p); *eap_packet_p = NULL; return NULL; } - } else { - handler = eap_handler_alloc(); + + vp = pairfind(request->packet->vps, PW_USER_NAME, 0); + if (!vp) { + /* + * NAS did not set the User-Name + * attribute, so we set it here and + * prepend it to the beginning of the + * request vps so that autz's work + * correctly + */ + RDEBUG2("Broken NAS did not set User-Name, setting from EAP Identity"); + vp = pairmake("User-Name", handler->identity, T_OP_EQ); + if (vp == NULL) { + RDEBUG("Out of memory"); + free(*eap_packet_p); + *eap_packet_p = NULL; + return NULL; + } + vp->next = request->packet->vps; + request->packet->vps = vp; + + } else { + /* + * A little more paranoia. If the NAS + * *did* set the User-Name, and it doesn't + * match the identity, (i.e. If they + * change their User-Name part way through + * the EAP transaction), then reject the + * request as the NAS is doing something + * funny. + */ + if (strncmp(handler->identity, vp->vp_strvalue, + MAX_STRING_LEN) != 0) { + RDEBUG("Identity does not match User-Name. Authentication failed."); + free(*eap_packet_p); + *eap_packet_p = NULL; + return NULL; + } + } + } else { /* packet was EAP identity */ + handler = eap_handler_alloc(inst); if (handler == NULL) { - radlog(L_ERR, "rlm_eap: out of memory"); + RDEBUG("Out of memory."); free(*eap_packet_p); *eap_packet_p = NULL; return NULL; @@ -1056,26 +1062,62 @@ EAP_HANDLER *eap_handler(rlm_eap_t *inst, eap_packet_t **eap_packet_p, /* * All fields in the handler are set to zero. */ - - handler->identity = eap_identity(eap_packet); + handler->identity = eap_identity(request, eap_packet); if (handler->identity == NULL) { - radlog(L_ERR, "rlm_eap: Identity Unknown, authentication failed"); + RDEBUG("Identity Unknown, authentication failed"); free(*eap_packet_p); *eap_packet_p = NULL; - eap_handler_free(&handler); + eap_handler_free(inst, handler); return NULL; } + + vp = pairfind(request->packet->vps, PW_USER_NAME, 0); + if (!vp) { + /* + * NAS did not set the User-Name + * attribute, so we set it here and + * prepend it to the beginning of the + * request vps so that autz's work + * correctly + */ + RDEBUG2("WARNING NAS did not set User-Name. Setting it locally from EAP Identity"); + vp = pairmake("User-Name", handler->identity, T_OP_EQ); + if (vp == NULL) { + RDEBUG("Out of memory"); + free(*eap_packet_p); + *eap_packet_p = NULL; + eap_handler_free(inst, handler); + return NULL; + } + vp->next = request->packet->vps; + request->packet->vps = vp; + } else { + /* + * Paranoia. If the NAS *did* set the + * User-Name, and it doesn't match the + * identity, the NAS is doing something + * funny, so reject the request. + */ + if (strncmp(handler->identity, vp->vp_strvalue, + MAX_STRING_LEN) != 0) { + RDEBUG("Identity does not match User-Name, setting from EAP Identity."); + free(*eap_packet_p); + *eap_packet_p = NULL; + eap_handler_free(inst, handler); + return NULL; + } + } } handler->eap_ds = eap_buildds(eap_packet_p); if (handler->eap_ds == NULL) { free(*eap_packet_p); *eap_packet_p = NULL; - eap_handler_free(&handler); + eap_handler_free(inst, handler); return NULL; } - handler->timestamp = time(NULL); + handler->timestamp = request->timestamp; handler->request = request; /* LEAP needs this */ return handler; }