summaryrefslogtreecommitdiffstats
path: root/src/modules/rlm_eap/eap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/modules/rlm_eap/eap.c')
-rw-r--r--src/modules/rlm_eap/eap.c1270
1 files changed, 1270 insertions, 0 deletions
diff --git a/src/modules/rlm_eap/eap.c b/src/modules/rlm_eap/eap.c
new file mode 100644
index 0000000..1ece323
--- /dev/null
+++ b/src/modules/rlm_eap/eap.c
@@ -0,0 +1,1270 @@
+/*
+ * eap.c rfc2284 & rfc2869 implementation
+ *
+ * Version: $Id$
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2000-2003,2006 The FreeRADIUS server project
+ * Copyright 2001 hereUare Communications, Inc. <raghud@hereuare.com>
+ * Copyright 2003 Alan DeKok <aland@freeradius.org>
+ */
+/*
+ * EAP 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Code | Identifier | Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Data ...
+ * +-+-+-+-+
+ *
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Code | Identifier | Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Type | Type-Data ...
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
+ *
+ *
+ * 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
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Code | Identifier | Length |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ */
+
+#include <freeradius-devel/modpriv.h>
+
+RCSID("$Id$")
+
+#include "rlm_eap.h"
+#include <ctype.h>
+
+static char const *eap_codes[] = {
+ "", /* 0 is invalid */
+ "Request",
+ "Response",
+ "Success",
+ "Failure"
+};
+
+static int _eap_module_free(eap_module_t *inst)
+{
+ /*
+ * We have to check inst->type as it's only allocated
+ * if we loaded the eap method.
+ */
+ if (inst->type && inst->type->detach) (inst->type->detach)(inst->instance);
+
+#ifndef NDEBUG
+ /*
+ * Don't dlclose() modules if we're doing memory
+ * debugging. This removes the symbols needed by
+ * valgrind.
+ */
+ if (!main_config.debug_memory)
+#endif
+ if (inst->handle) dlclose(inst->handle);
+
+ return 0;
+}
+
+/** Load required EAP sub-modules (methods)
+ *
+ */
+int eap_module_instantiate(rlm_eap_t *inst, eap_module_t **m_inst, eap_type_t num, CONF_SECTION *cs)
+{
+ eap_module_t *method;
+ char *mod_name, *p;
+
+ /* Make room for the EAP-Type */
+ *m_inst = method = talloc_zero(cs, eap_module_t);
+ if (!inst) return -1;
+
+ talloc_set_destructor(method, _eap_module_free);
+
+ /* fill in the structure */
+ method->cs = cs;
+ method->name = eap_type2name(num);
+
+ /*
+ * The name of the module were trying to load
+ */
+ mod_name = talloc_typed_asprintf(method, "rlm_eap_%s", method->name);
+
+ /*
+ * dlopen is case sensitive
+ */
+ p = mod_name;
+ while (*p) {
+ *p = tolower((uint8_t) *p);
+ p++;
+ }
+
+ /*
+ * Link the loaded EAP-Type
+ */
+ method->handle = fr_dlopenext(mod_name);
+ if (!method->handle) {
+ ERROR("rlm_eap (%s): Failed to link %s: %s", inst->xlat_name, mod_name, fr_strerror());
+
+ return -1;
+ }
+
+ method->type = dlsym(method->handle, mod_name);
+ if (!method->type) {
+ ERROR("rlm_eap (%s): Failed linking to structure in %s: %s", inst->xlat_name,
+ method->name, dlerror());
+
+ return -1;
+ }
+
+ cf_log_module(cs, "Linked to sub-module %s", mod_name);
+
+ /*
+ * Call the attach num in the EAP num module
+ */
+ if ((method->type->instantiate) && ((method->type->instantiate)(method->cs, &(method->instance)) < 0)) {
+ ERROR("rlm_eap (%s): Failed to initialise %s", inst->xlat_name, mod_name);
+
+ if (method->instance) {
+ (void) talloc_steal(method, method->instance);
+ }
+
+ return -1;
+ }
+
+ if (method->instance) {
+ (void) talloc_steal(method, method->instance);
+ }
+
+ return 0;
+}
+
+/*
+ * Call the appropriate handle with the right eap_method.
+ */
+static int eap_module_call(eap_module_t *module, eap_handler_t *handler)
+{
+ int rcode = 1;
+ REQUEST *request = handler->request;
+
+ char const *caller = request->module;
+
+ rad_assert(module != NULL);
+
+ RDEBUG2("Calling submodule %s to process data", module->type->name);
+
+ request->module = module->type->name;
+
+ switch (handler->stage) {
+ case INITIATE:
+ if (!module->type->session_init(module->instance, handler)) {
+ rcode = 0;
+ }
+
+ break;
+
+ case PROCESS:
+ /*
+ * The called function updates the EAP reply packet.
+ */
+ if (!module->type->process ||
+ !module->type->process(module->instance, handler)) {
+ rcode = 0;
+ }
+
+ break;
+
+ default:
+ /* Should never enter here */
+ RDEBUG("Internal sanity check failed on EAP");
+ rcode = 0;
+ break;
+ }
+
+ request->module = caller;
+ return rcode;
+}
+
+/** Process NAK data from EAP peer
+ *
+ */
+static eap_type_t eap_process_nak(rlm_eap_t *inst, REQUEST *request,
+ eap_type_t type,
+ eap_type_data_t *nak)
+{
+ unsigned int i;
+ VALUE_PAIR *vp;
+ eap_type_t method = PW_EAP_INVALID;
+
+ /*
+ * 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.
+ */
+ if (!nak->data) {
+ REDEBUG("Peer sent empty (invalid) NAK. "
+ "Can't select method to continue with");
+
+ return PW_EAP_INVALID;
+ }
+
+ /*
+ * Pick one type out of the one they asked for,
+ * as they may have asked for many.
+ */
+ vp = fr_pair_find_by_num(request->config, PW_EAP_TYPE, 0, TAG_ANY);
+ for (i = 0; i < nak->length; i++) {
+ /*
+ * Type 0 is valid, and means there are no
+ * common choices.
+ */
+ if (nak->data[i] == 0) {
+ RDEBUG("Peer NAK'd indicating it is not willing to "
+ "continue ");
+
+ return PW_EAP_INVALID;
+ }
+
+ /*
+ * It is invalid to request identity,
+ * notification & nak in nak.
+ */
+ if (nak->data[i] < PW_EAP_MD5) {
+ REDEBUG("Peer NAK'd asking for bad "
+ "type %s (%d)",
+ eap_type2name(nak->data[i]),
+ nak->data[i]);
+
+ return PW_EAP_INVALID;
+ }
+
+ if ((nak->data[i] >= PW_EAP_MAX_TYPES) ||
+ !inst->methods[nak->data[i]]) {
+ RDEBUG2("Peer NAK'd asking for "
+ "unsupported EAP type %s (%d), skipping...",
+ eap_type2name(nak->data[i]),
+ nak->data[i]);
+
+ continue;
+ }
+
+ /*
+ * Prevent a firestorm if the client is confused.
+ */
+ if (type == nak->data[i]) {
+ RDEBUG2("Peer NAK'd our request for "
+ "%s (%d) with a request for "
+ "%s (%d), skipping...",
+ eap_type2name(nak->data[i]),
+ nak->data[i],
+ eap_type2name(nak->data[i]),
+ nak->data[i]);
+
+ RWARN("!!! We requested to use an EAP type as normal.");
+ RWARN("!!! The supplicant rejected that, and requested to use the same EAP type.");
+ RWARN("!!! i.e. the supplicant said 'I don't like X, please use X instead.");
+ RWARN("!!! The supplicant software is broken and does not work properly.");
+ RWARN("!!! Please upgrade it to software that works.");
+
+ continue;
+ }
+
+ /*
+ * Enforce per-user configuration of EAP
+ * types.
+ */
+ if (vp && (vp->vp_integer != nak->data[i])) {
+ RDEBUG2("Peer wants %s (%d), while we "
+ "require %s (%d), skipping",
+ eap_type2name(nak->data[i]),
+ nak->data[i],
+ eap_type2name(vp->vp_integer),
+ vp->vp_integer);
+
+ continue;
+ }
+
+ RDEBUG("Found mutually acceptable type %s (%d)",
+ eap_type2name(nak->data[i]), nak->data[i]);
+
+ method = nak->data[i];
+
+ break;
+ }
+
+ if (method == PW_EAP_INVALID) {
+ REDEBUG("No mutually acceptable types found");
+ }
+
+ return method;
+}
+
+/** Select the correct callback based on a response
+ *
+ * Based on the EAP response from the supplicant, call the appropriate
+ * method callback.
+ *
+ * Default to the configured EAP-Type for all Unsupported EAP-Types.
+ *
+ * @param inst Configuration data for this instance of rlm_eap.
+ * @param handler State data that persists over multiple rounds of EAP.
+ * @return a status code.
+ */
+eap_rcode_t eap_method_select(rlm_eap_t *inst, eap_handler_t *handler)
+{
+ eap_type_data_t *type = &handler->eap_ds->response->type;
+ REQUEST *request = handler->request;
+
+ eap_type_t next = inst->default_method;
+ VALUE_PAIR *vp;
+
+ /*
+ * Don't trust anyone.
+ */
+ if ((type->num == 0) || (type->num >= PW_EAP_MAX_TYPES)) {
+ REDEBUG("Peer sent EAP method number %d, which is outside known range", type->num);
+
+ return EAP_INVALID;
+ }
+
+ /*
+ * Multiple levels of TLS nesting are invalid. But if
+ * the parent has a home_server defined, then this
+ * request is being processed through a virtual
+ * server... so that's OK.
+ *
+ * i.e. we're inside an EAP tunnel, which means we have a
+ * parent. If the outer session exists, and doesn't have
+ * a home server, then it's multiple layers of tunneling.
+ */
+ if (handler->request->parent &&
+ handler->request->parent->parent &&
+ !handler->request->parent->parent->home_server) {
+ RERROR("Multiple levels of TLS nesting are invalid");
+
+ return EAP_INVALID;
+ }
+
+ RDEBUG2("Peer sent packet with method EAP %s (%d)", eap_type2name(type->num), type->num);
+ /*
+ * Figure out what to do.
+ */
+ switch (type->num) {
+ case PW_EAP_IDENTITY:
+ /*
+ * Allow per-user configuration of EAP types.
+ */
+ vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TYPE, 0,
+ TAG_ANY);
+ if (vp) next = vp->vp_integer;
+
+ /*
+ * Ensure it's valid.
+ */
+ if ((next < PW_EAP_MD5) ||
+ (next >= PW_EAP_MAX_TYPES) ||
+ (!inst->methods[next])) {
+ REDEBUG2("Tried to start unsupported EAP type %s (%d)",
+ eap_type2name(next), next);
+
+ return EAP_INVALID;
+ }
+
+ do_initiate:
+ /*
+ * If any of these fail, we messed badly somewhere
+ */
+ rad_assert(next >= PW_EAP_MD5);
+ rad_assert(next < PW_EAP_MAX_TYPES);
+ rad_assert(inst->methods[next]);
+
+ handler->stage = INITIATE;
+ handler->type = next;
+
+ if (eap_module_call(inst->methods[next], handler) == 0) {
+ REDEBUG2("Failed starting EAP %s (%d) session. EAP sub-module failed",
+ eap_type2name(next), next);
+
+ return EAP_INVALID;
+ }
+ break;
+
+ case PW_EAP_NAK:
+ /*
+ * Delete old data, if necessary.
+ */
+ if (handler->opaque && handler->free_opaque) {
+ handler->free_opaque(handler->opaque);
+ handler->free_opaque = NULL;
+ handler->opaque = NULL;
+ }
+
+ /*
+ * We got a NAK after the peer started doing a
+ * particular EAP type. That's rude, tell the
+ * peer to go away.
+ */
+ if (handler->started) return EAP_INVALID;
+
+ next = eap_process_nak(inst, handler->request,
+ handler->type, type);
+
+ /*
+ * We probably want to return 'fail' here...
+ */
+ if (!next) {
+ return EAP_INVALID;
+ }
+
+ goto do_initiate;
+
+ /*
+ * Key off of the configured sub-modules.
+ */
+ default:
+ /*
+ * We haven't configured it, it doesn't exist.
+ */
+ if (!inst->methods[type->num]) {
+ REDEBUG2("Client asked for unsupported EAP type %s (%d)",
+ eap_type2name(type->num),
+ type->num);
+
+ return EAP_INVALID;
+ }
+
+ rad_assert(handler->stage == PROCESS);
+ handler->type = type->num;
+ if (eap_module_call(inst->methods[type->num],
+ handler) == 0) {
+ REDEBUG2("Failed continuing EAP %s (%d) session. EAP sub-module failed",
+ eap_type2name(type->num),
+ type->num);
+
+ return EAP_INVALID;
+ }
+ handler->started = true;
+ break;
+ }
+
+ return EAP_OK;
+}
+
+
+/*
+ * compose EAP reply packet in EAP-Message attr of RADIUS.
+ *
+ * Set the RADIUS reply codes based on EAP request codes. Append
+ * any additonal VPs to RADIUS reply
+ */
+rlm_rcode_t eap_compose(eap_handler_t *handler)
+{
+ VALUE_PAIR *vp;
+ eap_packet_raw_t *eap_packet;
+ REQUEST *request;
+ EAP_DS *eap_ds;
+ eap_packet_t *reply;
+ int rcode;
+
+#ifndef NDEBUG
+ handler = talloc_get_type_abort(handler, eap_handler_t);
+ request = talloc_get_type_abort(handler->request, REQUEST);
+ eap_ds = talloc_get_type_abort(handler->eap_ds, EAP_DS);
+ reply = talloc_get_type_abort(eap_ds->request, eap_packet_t);
+#else
+ request = handler->request;
+ eap_ds = handler->eap_ds;
+ reply = eap_ds->request;
+#endif
+
+ /*
+ * The Id for the EAP packet to the NAS wasn't set.
+ * Do so now.
+ */
+ if (!eap_ds->set_request_id) {
+ /*
+ * Id serves to suppport request/response
+ * retransmission in the EAP layer and as such
+ * must be different for 'adjacent' packets
+ * except in case of success/failure-replies.
+ *
+ * RFC2716 (EAP-TLS) requires this to be
+ * incremented, RFC2284 only makes the above-
+ * 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.
+ */
+ default:
+ ++reply->id;
+ }
+ }
+
+ /*
+ * For Request & Response packets, set the EAP sub-type,
+ * if the EAP sub-module didn't already set it.
+ *
+ * This allows the TLS module to be "morphic", and means
+ * that the TTLS and PEAP modules can call it to do most
+ * of their dirty work.
+ */
+ if (((eap_ds->request->code == PW_EAP_REQUEST) ||
+ (eap_ds->request->code == PW_EAP_RESPONSE)) &&
+ (eap_ds->request->type.num == 0)) {
+ rad_assert(handler->type >= PW_EAP_MD5);
+ rad_assert(handler->type < PW_EAP_MAX_TYPES);
+
+ eap_ds->request->type.num = handler->type;
+ }
+
+ if (eap_wireformat(reply) == EAP_INVALID) {
+ return RLM_MODULE_INVALID;
+ }
+ eap_packet = (eap_packet_raw_t *)reply->packet;
+
+ vp = radius_pair_create(request->reply, &request->reply->vps, PW_EAP_MESSAGE, 0);
+ if (!vp) return RLM_MODULE_INVALID;
+
+ vp->vp_length = eap_packet->length[0] * 256 + eap_packet->length[1];
+ vp->vp_octets = talloc_steal(vp, reply->packet);
+ reply->packet = NULL;
+
+ /*
+ * EAP-Message is always associated with
+ * Message-Authenticator but not vice-versa.
+ *
+ * Don't add a Message-Authenticator if it's already
+ * there.
+ */
+ vp = fr_pair_find_by_num(request->reply->vps, PW_MESSAGE_AUTHENTICATOR, 0, TAG_ANY);
+ if (!vp) {
+ vp = fr_pair_afrom_num(request->reply, PW_MESSAGE_AUTHENTICATOR, 0);
+ vp->vp_length = AUTH_VECTOR_LEN;
+ vp->vp_octets = talloc_zero_array(vp, uint8_t, vp->vp_length);
+ fr_pair_add(&(request->reply->vps), vp);
+ }
+
+ /* Set request reply code, but only if it's not already set. */
+ rcode = RLM_MODULE_OK;
+ if (!request->reply->code) switch (reply->code) {
+ case PW_EAP_RESPONSE:
+ request->reply->code = PW_CODE_ACCESS_REJECT;
+ rcode = RLM_MODULE_REJECT;
+ break;
+ case PW_EAP_SUCCESS:
+ request->reply->code = PW_CODE_ACCESS_ACCEPT;
+ rcode = RLM_MODULE_OK;
+ break;
+ case PW_EAP_FAILURE:
+ request->reply->code = PW_CODE_ACCESS_REJECT;
+ rcode = RLM_MODULE_REJECT;
+ break;
+ case PW_EAP_REQUEST:
+ request->reply->code = PW_CODE_ACCESS_CHALLENGE;
+ 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 */
+ REDEBUG("Reply code %d is unknown, rejecting the request", reply->code);
+ request->reply->code = PW_CODE_ACCESS_REJECT;
+ reply->code = PW_EAP_FAILURE;
+ rcode = RLM_MODULE_REJECT;
+ break;
+ }
+
+ RDEBUG2("Sending EAP %s (code %i) ID %d length %i",
+ eap_codes[eap_packet->code], eap_packet->code, reply->id,
+ eap_packet->length[0] * 256 + eap_packet->length[1]);
+
+ return rcode;
+}
+
+/*
+ * Radius criteria, EAP-Message is invalid without Message-Authenticator
+ * For EAP_START, send Access-Challenge with EAP Identity request.
+ */
+int eap_start(rlm_eap_t *inst, REQUEST *request)
+{
+ VALUE_PAIR *vp, *proxy;
+ VALUE_PAIR *eap_msg;
+
+ eap_msg = fr_pair_find_by_num(request->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
+ if (!eap_msg) {
+ RDEBUG2("No EAP-Message, not doing EAP");
+ return EAP_NOOP;
+ }
+
+ /*
+ * Look for EAP-Type = None (FreeRADIUS specific attribute)
+ * this allows you to NOT do EAP for some users.
+ */
+ vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
+ 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
+ *
+ * Checks for Message-Authenticator are handled by rad_recv().
+ */
+
+ /*
+ * Check for a Proxy-To-Realm. Don't get excited over LOCAL
+ * realms (sigh).
+ */
+ proxy = fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY);
+ if (proxy) {
+ REALM *realm;
+
+ /*
+ * 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))) {
+ proxy = NULL;
+ }
+ }
+
+ /*
+ * 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.
+ *
+ * Lengths of two are what we see in practice as
+ * EAP-Starts.
+ */
+ if ((eap_msg->vp_length == 0) || (eap_msg->vp_length == 2)) {
+ uint8_t *p;
+
+ /*
+ * 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");
+ vp = fr_pair_afrom_num(request->reply, PW_EAP_MESSAGE, 0);
+ if (!vp) return EAP_FAIL;
+ fr_pair_add(&request->reply->vps, vp);
+
+ /*
+ * Manually create an EAP Identity request
+ */
+ vp->vp_length = 5;
+ vp->vp_octets = p = talloc_array(vp, uint8_t, vp->vp_length);
+
+ p[0] = PW_EAP_REQUEST;
+ p[1] = 0; /* ID */
+ p[2] = 0;
+ p[3] = 5; /* length */
+ p[4] = PW_EAP_IDENTITY;
+
+ request->reply->code = PW_CODE_ACCESS_CHALLENGE;
+ 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->vp_length < (EAP_HEADER_LEN + 1)) {
+ if (proxy) goto do_proxy;
+
+ RDEBUG2("Ignoring EAP-Message which is too short to be meaningful");
+ return EAP_FAIL;
+ }
+
+ /*
+ * Create an EAP-Type containing the EAP-type
+ * from the packet.
+ */
+ vp = fr_pair_afrom_num(request->packet, PW_EAP_TYPE, 0);
+ if (vp) {
+ vp->vp_integer = eap_msg->vp_octets[4];
+ fr_pair_add(&(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("Peer sent EAP packet with unknown code %i", eap_msg->vp_octets[0]);
+ } else {
+ RDEBUG2("Peer sent EAP %s (code %i) ID %d length %zu",
+ eap_codes[eap_msg->vp_octets[0]],
+ eap_msg->vp_octets[0],
+ eap_msg->vp_octets[1],
+ eap_msg->vp_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;
+ }
+
+ /*
+ * We've been told to ignore unknown EAP types, AND it's
+ * an unknown type. Return "NOOP", which will cause the
+ * mod_authorize() to return NOOP.
+ *
+ * EAP-Identity, Notification, and NAK are all handled
+ * internally, so they never have handlers.
+ */
+ if ((eap_msg->vp_octets[4] >= PW_EAP_MD5) &&
+ inst->ignore_unknown_types &&
+ ((eap_msg->vp_octets[4] == 0) ||
+ (eap_msg->vp_octets[4] >= PW_EAP_MAX_TYPES) ||
+ (!inst->methods[eap_msg->vp_octets[4]]))) {
+ RDEBUG2("Ignoring Unknown EAP type");
+ return EAP_NOOP;
+ }
+
+ /*
+ * 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.
+ */
+ if ((eap_msg->vp_octets[4] == PW_EAP_NAK) &&
+ (eap_msg->vp_length >= (EAP_HEADER_LEN + 2)) &&
+ inst->ignore_unknown_types &&
+ ((eap_msg->vp_octets[5] == 0) ||
+ (eap_msg->vp_octets[5] >= PW_EAP_MAX_TYPES) ||
+ (!inst->methods[eap_msg->vp_octets[5]]))) {
+ RDEBUG2("Ignoring NAK with request for unknown EAP type");
+ return EAP_NOOP;
+ }
+
+ if ((eap_msg->vp_octets[4] == PW_EAP_TTLS) ||
+ (eap_msg->vp_octets[4] == PW_EAP_PEAP)) {
+ RDEBUG2("Continuing tunnel setup");
+ return EAP_OK;
+ }
+ /*
+ * We return ok in response to EAP identity
+ * This means we can write:
+ *
+ * eap {
+ * ok = return
+ * }
+ * ldap
+ * sql
+ *
+ * ...in the inner-tunnel, to avoid expensive and unnecessary SQL/LDAP lookups
+ */
+ if (eap_msg->vp_octets[4] == PW_EAP_IDENTITY) {
+ RDEBUG2("EAP-Identity reply, returning 'ok' so we can short-circuit the rest of authorize");
+ return EAP_OK;
+ }
+
+ /*
+ * 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;
+}
+
+/*
+ * compose EAP FAILURE packet in EAP-Message
+ */
+void eap_fail(eap_handler_t *handler)
+{
+ /*
+ * Delete any previous replies.
+ */
+ fr_pair_delete_by_num(&handler->request->reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
+ fr_pair_delete_by_num(&handler->request->reply->vps, PW_STATE, 0, TAG_ANY);
+
+ talloc_free(handler->eap_ds->request);
+ handler->eap_ds->request = talloc_zero(handler->eap_ds, eap_packet_t);
+ handler->eap_ds->request->code = PW_EAP_FAILURE;
+ handler->finished = true;
+ eap_compose(handler);
+}
+
+/*
+ * compose EAP SUCCESS packet in EAP-Message
+ */
+void eap_success(eap_handler_t *handler)
+{
+ handler->eap_ds->request->code = PW_EAP_SUCCESS;
+ handler->finished = true;
+ eap_compose(handler);
+}
+
+/*
+ * Basic EAP packet verfications & validations
+ */
+static int eap_validation(REQUEST *request, eap_packet_raw_t **eap_packet_p)
+{
+ uint16_t len;
+ eap_packet_raw_t *eap_packet = *eap_packet_p;
+
+ memcpy(&len, eap_packet->length, sizeof(uint16_t));
+ len = ntohs(len);
+
+ /*
+ * High level EAP packet checks
+ */
+ if (len <= EAP_HEADER_LEN) {
+ RAUTH("EAP packet is too small: Ignoring it.");
+ return EAP_INVALID;
+ }
+
+ if (eap_packet->code == PW_EAP_REQUEST) {
+ VALUE_PAIR *vp;
+ RAUTH("Unexpected EAP-Request. NAKing it.");
+
+ vp = pair_make_reply("EAP-Message", "123456", T_OP_SET);
+ if (vp) {
+ uint8_t buffer[6];
+
+ buffer[0] = PW_EAP_RESPONSE;
+ buffer[1] = eap_packet->id;
+ buffer[2] = 0;
+ buffer[3] = 6;
+ buffer[4] = PW_EAP_NAK;
+ buffer[5] = 0; /* no overlapping EAP types */
+
+ fr_pair_value_memcpy(vp, buffer, 6);
+ }
+
+ /*
+ * Ensure that the Access-Reject has a Message-Authenticator
+ */
+ vp = fr_pair_find_by_num(request->reply->vps, PW_MESSAGE_AUTHENTICATOR, 0, TAG_ANY);
+ if (!vp) {
+ vp = fr_pair_afrom_num(request->reply, PW_MESSAGE_AUTHENTICATOR, 0);
+ vp->vp_length = AUTH_VECTOR_LEN;
+ vp->vp_octets = talloc_zero_array(vp, uint8_t, vp->vp_length);
+ fr_pair_add(&(request->reply->vps), vp);
+ }
+ request->reply->code = PW_CODE_ACCESS_REJECT;
+
+ return EAP_INVALID;
+ }
+
+ /*
+ * We only allow responses from the peer. The peer
+ * CANNOT ask us to authenticate outselves.
+ */
+ if (eap_packet->code != PW_EAP_RESPONSE) {
+ RAUTH("Unexpected packet code %02x: Ignoring it.", eap_packet->code);
+ return EAP_INVALID;
+ }
+
+ if ((eap_packet->data[0] <= 0) ||
+ (eap_packet->data[0] >= PW_EAP_MAX_TYPES)) {
+ /*
+ * Handle expanded types by smashing them to
+ * normal types.
+ */
+ if (eap_packet->data[0] == PW_EAP_EXPANDED_TYPE) {
+ uint8_t *p, *q;
+
+ if (len <= (EAP_HEADER_LEN + 1 + 3 + 4)) {
+ RAUTH("Expanded EAP type is too short: ignoring the packet");
+ return EAP_INVALID;
+ }
+
+ if ((eap_packet->data[1] != 0) ||
+ (eap_packet->data[2] != 0) ||
+ (eap_packet->data[3] != 0)) {
+ RAUTH("Expanded EAP type has unknown Vendor-ID: ignoring the packet");
+ return EAP_INVALID;
+ }
+
+ if ((eap_packet->data[4] != 0) ||
+ (eap_packet->data[5] != 0) ||
+ (eap_packet->data[6] != 0)) {
+ RAUTH("Expanded EAP type has unknown Vendor-Type: ignoring the packet");
+ return EAP_INVALID;
+ }
+
+ if ((eap_packet->data[7] == 0) ||
+ (eap_packet->data[7] >= PW_EAP_MAX_TYPES)) {
+ RAUTH("Unsupported Expanded EAP type %s (%u): ignoring the packet",
+ eap_type2name(eap_packet->data[7]), eap_packet->data[7]);
+ return EAP_INVALID;
+ }
+
+ if (eap_packet->data[7] == PW_EAP_NAK) {
+ RAUTH("Unsupported Expanded EAP-NAK: ignoring the packet");
+ return EAP_INVALID;
+ }
+
+ /*
+ * Re-write the EAP packet to NOT have the expanded type.
+ */
+ q = (uint8_t *) eap_packet;
+ memmove(q + EAP_HEADER_LEN, q + EAP_HEADER_LEN + 7, len - 7 - EAP_HEADER_LEN);
+
+ p = talloc_realloc(talloc_parent(eap_packet), eap_packet, uint8_t, len - 7);
+ if (!p) {
+ RAUTH("Unsupported EAP type %s (%u): ignoring the packet",
+ eap_type2name(eap_packet->data[0]), eap_packet->data[0]);
+ return EAP_INVALID;
+ }
+
+ len -= 7;
+ p[2] = (len >> 8) & 0xff;
+ p[3] = len & 0xff;
+
+ *eap_packet_p = (eap_packet_raw_t *) p;
+ RWARN("Converting Expanded EAP to normal EAP.");
+ RWARN("Unnecessary use of Expanded EAP types is not recommended.");
+
+ return EAP_VALID;
+ }
+
+ RAUTH("Unsupported EAP type %s (%u): ignoring the packet",
+ eap_type2name(eap_packet->data[0]), eap_packet->data[0]);
+ return EAP_INVALID;
+ }
+
+ /* we don't expect notification, but we send it */
+ if (eap_packet->data[0] == PW_EAP_NOTIFICATION) {
+ RAUTH("Got NOTIFICATION, "
+ "Ignoring the packet");
+ return EAP_INVALID;
+ }
+
+ return EAP_VALID;
+}
+
+
+/*
+ * Get the user Identity only from EAP-Identity packets
+ */
+static char *eap_identity(REQUEST *request, eap_handler_t *handler, eap_packet_raw_t *eap_packet)
+{
+ int size;
+ uint16_t len;
+ char *identity;
+
+ if ((!eap_packet) ||
+ (eap_packet->code != PW_EAP_RESPONSE) ||
+ (eap_packet->data[0] != PW_EAP_IDENTITY)) {
+ return NULL;
+ }
+
+ memcpy(&len, eap_packet->length, sizeof(uint16_t));
+ len = ntohs(len);
+
+ if ((len <= 5) || (eap_packet->data[1] == 0x00)) {
+ REDEBUG("EAP-Identity Unknown");
+ return NULL;
+ }
+
+ if (len > 1024) {
+ REDEBUG("EAP-Identity too long");
+ return NULL;
+ }
+
+ size = len - 5;
+ identity = talloc_array(handler, char, size + 1);
+ memcpy(identity, &eap_packet->data[1], size);
+ identity[size] = '\0';
+
+ return identity;
+}
+
+
+/*
+ * Create our Request-Response data structure with the eap packet
+ */
+static EAP_DS *eap_buildds(eap_handler_t *handler,
+ eap_packet_raw_t **eap_packet_p)
+{
+ EAP_DS *eap_ds = NULL;
+ eap_packet_raw_t *eap_packet = *eap_packet_p;
+ int typelen;
+ uint16_t len;
+
+ if ((eap_ds = eap_ds_alloc(handler)) == NULL) {
+ return NULL;
+ }
+
+ eap_ds->response->packet = (uint8_t *) eap_packet;
+ (void) talloc_steal(eap_ds, eap_packet);
+ eap_ds->response->code = eap_packet->code;
+ eap_ds->response->id = eap_packet->id;
+ eap_ds->response->type.num = eap_packet->data[0];
+
+ memcpy(&len, eap_packet->length, sizeof(uint16_t));
+ len = ntohs(len);
+ eap_ds->response->length = len;
+
+ /*
+ * We've eaten the eap packet into the eap_ds.
+ */
+ *eap_packet_p = NULL;
+
+ /*
+ * First 5 bytes in eap, are code + id + length(2) + type.
+ *
+ * The rest is type-specific data. We skip type while
+ * getting typedata from data.
+ */
+ typelen = len - 5/*code + id + length + type */;
+ if (typelen > 0) {
+ /*
+ * Since the packet contains the complete
+ * eap_packet, typedata will be a ptr in packet
+ * to its typedata
+ */
+ eap_ds->response->type.data = eap_ds->response->packet + 5/*code+id+length+type*/;
+ eap_ds->response->type.length = typelen;
+ } else {
+ eap_ds->response->type.length = 0;
+ eap_ds->response->type.data = NULL;
+ }
+
+ return eap_ds;
+}
+
+
+/*
+ * If identity response then create a fresh handler & fill the identity
+ * else handler MUST be in our list, get that.
+ * This handler creation cannot fail
+ *
+ * username contains REQUEST->username which might have been stripped.
+ * identity contains the one sent in EAP-Identity response
+ */
+eap_handler_t *eap_handler(rlm_eap_t *inst, eap_packet_raw_t **eap_packet_p,
+ REQUEST *request)
+{
+ eap_handler_t *handler = NULL;
+ eap_packet_raw_t *eap_packet;
+ VALUE_PAIR *vp;
+
+ /*
+ * Ensure it's a valid EAP-Request, or EAP-Response.
+ */
+ if (eap_validation(request, eap_packet_p) == EAP_INVALID) {
+ error:
+ talloc_free(*eap_packet_p);
+ *eap_packet_p = NULL;
+ return NULL;
+ }
+
+ eap_packet = *eap_packet_p;
+
+ /*
+ * eap_handler_t MUST be found in the list if it is not
+ * EAP-Identity response
+ */
+ if (eap_packet->data[0] != PW_EAP_IDENTITY) {
+ handler = eaplist_find(inst, request, eap_packet);
+ if (!handler) {
+ /* Either send EAP_Identity or EAP-Fail */
+ RDEBUG("Either EAP-request timed out OR EAP-response to an unknown EAP-request");
+ goto error;
+ }
+
+ /*
+ * Even more paranoia. Without this, some weird
+ * clients could do crazy things.
+ *
+ * It's ok to send EAP sub-type NAK in response
+ * to a request for a particular type, but it's NOT
+ * OK to blindly return data for another type.
+ */
+ if ((eap_packet->data[0] != PW_EAP_NAK) &&
+ (eap_packet->data[0] != handler->type)) {
+ RERROR("Response appears to match a previous request, but the EAP type is wrong");
+ RERROR("We expected EAP type %s, but received type %s",
+ eap_type2name(handler->type),
+ eap_type2name(eap_packet->data[0]));
+ RERROR("Your Supplicant or NAS is probably broken");
+ goto error;
+ }
+
+ vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
+ 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 = fr_pair_make(request->packet, &request->packet->vps, "User-Name", handler->identity, T_OP_EQ);
+ if (!vp) {
+ goto error;
+ }
+ } 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");
+ goto error;
+ }
+ }
+ } else { /* packet was EAP identity */
+ handler = eap_handler_alloc(inst);
+ if (!handler) {
+ goto error;
+ }
+
+ /*
+ * All fields in the handler are set to zero.
+ */
+ handler->identity = eap_identity(request, handler, eap_packet);
+ if (!handler->identity) {
+ RDEBUG("Identity Unknown, authentication failed");
+ error2:
+ talloc_free(handler);
+ goto error;
+ }
+
+ vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
+ 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
+ */
+ RWDEBUG2("NAS did not set User-Name. Setting it locally from EAP Identity");
+ vp = fr_pair_make(request->packet, &request->packet->vps, "User-Name", handler->identity, T_OP_EQ);
+ if (!vp) {
+ goto error2;
+ }
+ } 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");
+ goto error2;
+ }
+ }
+ }
+
+ handler->eap_ds = eap_buildds(handler, eap_packet_p);
+ if (!handler->eap_ds) {
+ goto error2;
+ }
+
+ handler->timestamp = request->timestamp;
+ handler->request = request;
+ return handler;
+}