static void fix_mppe_keys(eap_handler_t *handler, mschapv2_opaque_t *data)
{
	pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY);
	pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY);
	pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
	pairfilter(data, &data->mppe_keys, &handler->request->reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);
}
/*
 *	Do authentication, by letting EAP-TLS do most of the work.
 */
static int mod_authenticate(void *arg, eap_handler_t *handler)
{
    int rcode;
    fr_tls_status_t status;
    rlm_eap_peap_t *inst = (rlm_eap_peap_t *) arg;
    tls_session_t *tls_session = (tls_session_t *) handler->opaque;
    peap_tunnel_t *peap = tls_session->opaque;
    REQUEST *request = handler->request;

    /*
     *	Session resumption requires the storage of data, so
     *	allocate it if it doesn't already exist.
     */
    if (!tls_session->opaque) {
        peap = tls_session->opaque = peap_alloc(tls_session, inst);
    }

    status = eaptls_process(handler);
    RDEBUG2("eaptls_process returned %d\n", status);
    switch (status) {
    /*
     *	EAP-TLS handshake was successful, tell the
     *	client to keep talking.
     *
     *	If this was EAP-TLS, we would just return
     *	an EAP-TLS-Success packet here.
     */
    case FR_TLS_SUCCESS:
        RDEBUG2("FR_TLS_SUCCESS");
        peap->status = PEAP_STATUS_TUNNEL_ESTABLISHED;
        break;

    /*
     *	The TLS code is still working on the TLS
     *	exchange, and it's a valid TLS request.
     *	do nothing.
     */
    case FR_TLS_HANDLED:
        /*
         *	FIXME: If the SSL session is established, grab the state
         *	and EAP id from the inner tunnel, and update it with
         *	the expected EAP id!
         */
        RDEBUG2("FR_TLS_HANDLED");
        return 1;

    /*
     *	Handshake is done, proceed with decoding tunneled
     *	data.
     */
    case FR_TLS_OK:
        RDEBUG2("FR_TLS_OK");
        break;

    /*
     *	Anything else: fail.
     */
    default:
        RDEBUG2("FR_TLS_OTHERS");
        return 0;
    }

    /*
     *	Session is established, proceed with decoding
     *	tunneled data.
     */
    RDEBUG2("Session established.  Decoding tunneled attributes");

    /*
     *	We may need PEAP data associated with the session, so
     *	allocate it here, if it wasn't already alloacted.
     */
    if (!tls_session->opaque) {
        tls_session->opaque = peap_alloc(tls_session, inst);
    }

    /*
     *	Process the PEAP portion of the request.
     */
    rcode = eappeap_process(handler, tls_session);
    switch (rcode) {
    case RLM_MODULE_REJECT:
        eaptls_fail(handler, 0);
        return 0;

    case RLM_MODULE_HANDLED:
        eaptls_request(handler->eap_ds, tls_session);
        return 1;

    case RLM_MODULE_OK:
        /*
         *	Move the saved VP's from the Access-Accept to
         *	our Access-Accept.
         */
        peap = tls_session->opaque;
        if (peap->soh_reply_vps) {
            RDEBUG2("Using saved attributes from the SoH reply");
            debug_pair_list(peap->soh_reply_vps);
            pairfilter(handler->request->reply,
                       &handler->request->reply->vps,
                       &peap->soh_reply_vps, 0, 0, TAG_ANY);
        }
        if (peap->accept_vps) {
            RDEBUG2("Using saved attributes from the original Access-Accept");
            debug_pair_list(peap->accept_vps);
            pairfilter(handler->request->reply,
                       &handler->request->reply->vps,
                       &peap->accept_vps, 0, 0, TAG_ANY);
        }

        /*
         *	Success: Automatically return MPPE keys.
         */
        return eaptls_success(handler, 0);

    /*
     *	No response packet, MUST be proxying it.
     *	The main EAP module will take care of discovering
     *	that the request now has a "proxy" packet, and
     *	will proxy it, rather than returning an EAP packet.
     */
    case RLM_MODULE_UPDATED:
#ifdef WITH_PROXY
        rad_assert(handler->request->proxy != NULL);
#endif
        return 1;
        break;

    default:
        break;
    }

    eaptls_fail(handler, 0);
    return 0;
}
Esempio n. 3
0
/*
 *  	get the vps and put them in perl hash
 *  	If one VP have multiple values it is added as array_ref
 *  	Example for this is Cisco-AVPair that holds multiple values.
 *  	Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'}
 */
static void perl_store_vps(TALLOC_CTX *ctx, VALUE_PAIR *vps, HV *rad_hv)
{
	VALUE_PAIR *head, *sublist;
	AV *av;
	char const *name;
	char namebuf[256];
	char buffer[1024];
	int len;

	hv_undef(rad_hv);

	/*
	 *	Copy the valuepair list so we can remove attributes
	 *	we've already processed.  This is a horrible hack to
	 *	get around various other stupidity.
	 */
	head = paircopy(ctx, vps);

	while (head) {
		vp_cursor_t cursor;
		/*
		 *	Tagged attributes are added to the hash with name
		 *	<attribute>:<tag>, others just use the normal attribute
		 *	name as the key.
		 */
		if (head->da->flags.has_tag && (head->tag != 0)) {
			snprintf(namebuf, sizeof(namebuf), "%s:%d",
				 head->da->name, head->tag);
			name = namebuf;
		} else {
			name = head->da->name;
		}

		/*
		 *	Create a new list with all the attributes like this one
		 *	which are in the same tag group.
		 */
		sublist = NULL;
		pairfilter(ctx, &sublist, &head, head->da->attr, head->da->vendor, head->tag);

		fr_cursor_init(&cursor, &sublist);
		/*
		 *	Attribute has multiple values
		 */
		if (fr_cursor_next(&cursor)) {
			VALUE_PAIR *vp;

			av = newAV();
			for (vp = fr_cursor_first(&cursor);
			     vp;
			     vp = fr_cursor_next(&cursor)) {
				len = vp_prints_value(buffer, sizeof(buffer), vp, 0);
				av_push(av, newSVpv(buffer, len));
			}
			(void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0);

			/*
			 *	Attribute has a single value, so its value just gets
			 *	added to the hash.
			 */
		} else {
			len = vp_prints_value(buffer, sizeof(buffer), sublist, 0);
			(void)hv_store(rad_hv, name, strlen(name), newSVpv(buffer, len), 0);
		}

		pairfree(&sublist);
	}

	rad_assert(!head);
}
Esempio n. 4
0
/*
 *	Do authentication, by letting EAP-TLS do most of the work.
 */
static int CC_HINT(nonnull) mod_authenticate(void *type_arg, eap_handler_t *handler)
{
	fr_tls_status_t	status;
	tls_session_t *tls_session = (tls_session_t *) handler->opaque;
	REQUEST *request = handler->request;
	rlm_eap_tls_t *inst;

	inst = type_arg;

	RDEBUG2("Authenticate");

	status = eaptls_process(handler);
	RDEBUG2("eaptls_process returned %d\n", status);
	switch (status) {
		/*
		 *	EAP-TLS handshake was successful, return an
		 *	EAP-TLS-Success packet here.
		 *
		 *	If a virtual server was configured, check that
		 *	it accepts the certificates, too.
		 */
	case FR_TLS_SUCCESS:
		if (inst->virtual_server) {
			VALUE_PAIR *vp;
			REQUEST *fake;

			/* create a fake request */
			fake = request_alloc_fake(request);
			rad_assert(!fake->packet->vps);

			fake->packet->vps = paircopy(fake->packet, request->packet->vps);

			/* set the virtual server to use */
			if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
				fake->server = vp->vp_strvalue;
			} else {
				fake->server = inst->virtual_server;
			}

			RDEBUG("Processing EAP-TLS Certificate check:");
			debug_pair_list(fake->packet->vps);

			RDEBUG("server %s {", fake->server);

			rad_virtual_server(fake);

			RDEBUG("} # server %s", fake->server);

			/* copy the reply vps back to our reply */
			pairfilter(request->reply, &request->reply->vps,
				  &fake->reply->vps, 0, 0, TAG_ANY);

			/* reject if virtual server didn't return accept */
			if (fake->reply->code != PW_CODE_AUTHENTICATION_ACK) {
				RDEBUG2("Certificates were rejected by the virtual server");
				request_free(&fake);
				eaptls_fail(handler, 0);
				return 0;
			}

			request_free(&fake);
			/* success */
		}
		break;

		/*
		 *	The TLS code is still working on the TLS
		 *	exchange, and it's a valid TLS request.
		 *	do nothing.
		 */
	case FR_TLS_HANDLED:
		return 1;

		/*
		 *	Handshake is done, proceed with decoding tunneled
		 *	data.
		 */
	case FR_TLS_OK:
		RDEBUG2("Received unexpected tunneled data after successful handshake");
#ifndef NDEBUG
		if ((debug_flag > 2) && fr_log_fp) {
			unsigned int i;
			unsigned int data_len;
			unsigned char buffer[1024];

			data_len = (tls_session->record_minus)(&tls_session->dirty_in,
						buffer, sizeof(buffer));
			DEBUG("  Tunneled data (%u bytes)", data_len);
			for (i = 0; i < data_len; i++) {
				if ((i & 0x0f) == 0x00) fprintf(fr_log_fp, "  %x: ", i);
				if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n");

				fprintf(fr_log_fp, "%02x ", buffer[i]);
			}
			fprintf(fr_log_fp, "\n");
		}
#endif

		eaptls_fail(handler, 0);
		return 0;
		break;

		/*
		 *	Anything else: fail.
		 *
		 *	Also, remove the session from the cache so that
		 *	the client can't re-use it.
		 */
	default:
		tls_fail(tls_session);

		return 0;
	}

	/*
	 *	Success: Automatically return MPPE keys.
	 */
	return eaptls_success(handler, 0);
}
Esempio n. 5
0
/*
 *	Process the "diameter" contents of the tunneled data.
 */
int eapttls_process(eap_handler_t *handler, tls_session_t *tls_session)
{
	int rcode = PW_AUTHENTICATION_REJECT;
	REQUEST *fake;
	VALUE_PAIR *vp;
	ttls_tunnel_t *t;
	const uint8_t *data;
	size_t data_len;
	REQUEST *request = handler->request;

	rad_assert(request != NULL);

	/*
	 *	Just look at the buffer directly, without doing
	 *	record_minus.
	 */
	data_len = tls_session->clean_out.used;
	tls_session->clean_out.used = 0;
	data = tls_session->clean_out.data;

	t = (ttls_tunnel_t *) tls_session->opaque;

	/*
	 *	If there's no data, maybe this is an ACK to an
	 *	MS-CHAP2-Success.
	 */
	if (data_len == 0) {
		if (t->authenticated) {
			RDEBUG("Got ACK, and the user was already authenticated.");
			return PW_AUTHENTICATION_ACK;
		} /* else no session, no data, die. */

		/*
		 *	FIXME: Call SSL_get_error() to see what went
		 *	wrong.
		 */
		RDEBUG2("SSL_read Error");
		return PW_AUTHENTICATION_REJECT;
	}

#ifndef NDEBUG
	if ((debug_flag > 2) && fr_log_fp) {
		size_t i;

		for (i = 0; i < data_len; i++) {
			if ((i & 0x0f) == 0) fprintf(fr_log_fp, "  TTLS tunnel data in %04x: ", (int) i);

			fprintf(fr_log_fp, "%02x ", data[i]);

			if ((i & 0x0f) == 0x0f) fprintf(fr_log_fp, "\n");
		}
		if ((data_len & 0x0f) != 0) fprintf(fr_log_fp, "\n");
	}
#endif

	if (!diameter_verify(request, data, data_len)) {
		return PW_AUTHENTICATION_REJECT;
	}

	/*
	 *	Allocate a fake REQUEST structe.
	 */
	fake = request_alloc_fake(request);

	rad_assert(!fake->packet->vps);

	/*
	 *	Add the tunneled attributes to the fake request.
	 */
	fake->packet->vps = diameter2vp(request, fake, tls_session->ssl, data, data_len);
	if (!fake->packet->vps) {
		request_free(&fake);
		return PW_AUTHENTICATION_REJECT;
	}

	/*
	 *	Tell the request that it's a fake one.
	 */
	pairmake_packet("Freeradius-Proxied-To", "127.0.0.1", T_OP_EQ);

	if ((debug_flag > 0) && fr_log_fp) {
		RDEBUG("Got tunneled request");

		debug_pair_list(fake->packet->vps);
	}

	/*
	 *	Update other items in the REQUEST data structure.
	 */
	fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY);
	fake->password = pairfind(fake->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);

	/*
	 *	No User-Name, try to create one from stored data.
	 */
	if (!fake->username) {
		/*
		 *	No User-Name in the stored data, look for
		 *	an EAP-Identity, and pull it out of there.
		 */
		if (!t->username) {
			vp = pairfind(fake->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
			if (vp &&
			    (vp->length >= EAP_HEADER_LEN + 2) &&
			    (vp->vp_strvalue[0] == PW_EAP_RESPONSE) &&
			    (vp->vp_strvalue[EAP_HEADER_LEN] == PW_EAP_IDENTITY) &&
			    (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) {
				/*
				 *	Create & remember a User-Name
				 */
				t->username = pairmake(t, NULL, "User-Name", "", T_OP_EQ);
				rad_assert(t->username != NULL);

				memcpy(t->username->vp_strvalue, vp->vp_strvalue + 5,
				       vp->length - 5);
				t->username->length = vp->length - 5;
				t->username->vp_strvalue[t->username->length] = 0;

				RDEBUG("Got tunneled identity of %s",
				       t->username->vp_strvalue);

				/*
				 *	If there's a default EAP type,
				 *	set it here.
				 */
				if (t->default_method != 0) {
					RDEBUG("Setting default EAP type for tunneled EAP session.");
					vp = paircreate(fake, PW_EAP_TYPE, 0);
					rad_assert(vp != NULL);
					vp->vp_integer = t->default_method;
					pairadd(&fake->config_items, vp);
				}

			} else {
				/*
				 *	Don't reject the request outright,
				 *	as it's permitted to do EAP without
				 *	user-name.
				 */
				RDEBUG2W("No EAP-Identity found to start EAP conversation.");
			}
		} /* else there WAS a t->username */

		if (t->username) {
			vp = paircopy(fake->packet, t->username);
			pairadd(&fake->packet->vps, vp);
			fake->username = pairfind(fake->packet->vps, PW_USER_NAME, 0, TAG_ANY);
		}
	} /* else the request ALREADY had a User-Name */

	/*
	 *	Add the State attribute, too, if it exists.
	 */
	if (t->state) {
		vp = paircopy(fake->packet, t->state);
		if (vp) pairadd(&fake->packet->vps, vp);
	}

	/*
	 *	If this is set, we copy SOME of the request attributes
	 *	from outside of the tunnel to inside of the tunnel.
	 *
	 *	We copy ONLY those attributes which do NOT already
	 *	exist in the tunneled request.
	 */
	if (t->copy_request_to_tunnel) {
		VALUE_PAIR *copy;

		for (vp = request->packet->vps; vp != NULL; vp = vp->next) {
			/*
			 *	The attribute is a server-side thingy,
			 *	don't copy it.
			 */
			if ((vp->da->attr > 255) &&
			    (vp->da->vendor == 0)) {
				continue;
			}

			/*
			 *	The outside attribute is already in the
			 *	tunnel, don't copy it.
			 *
			 *	This works for BOTH attributes which
			 *	are originally in the tunneled request,
			 *	AND attributes which are copied there
			 *	from below.
			 */
			if (pairfind(fake->packet->vps, vp->da->attr, vp->da->vendor, TAG_ANY)) {
				continue;
			}

			/*
			 *	Some attributes are handled specially.
			 */
			switch (vp->da->attr) {
				/*
				 *	NEVER copy Message-Authenticator,
				 *	EAP-Message, or State.  They're
				 *	only for outside of the tunnel.
				 */
			case PW_USER_NAME:
			case PW_USER_PASSWORD:
			case PW_CHAP_PASSWORD:
			case PW_CHAP_CHALLENGE:
			case PW_PROXY_STATE:
			case PW_MESSAGE_AUTHENTICATOR:
			case PW_EAP_MESSAGE:
			case PW_STATE:
				continue;
				break;

				/*
				 *	By default, copy it over.
				 */
			default:
				break;
			}

			/*
			 *	Don't copy from the head, we've already
			 *	checked it.
			 */
			copy = paircopy2(fake->packet, vp, vp->da->attr, vp->da->vendor, TAG_ANY);
			pairadd(&fake->packet->vps, copy);
		}
	}

	if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
		fake->server = vp->vp_strvalue;

	} else if (t->virtual_server) {
		fake->server = t->virtual_server;

	} /* else fake->server == request->server */


	if ((debug_flag > 0) && fr_log_fp) {
		RDEBUG("Sending tunneled request");

		debug_pair_list(fake->packet->vps);

		fprintf(fr_log_fp, "server %s {\n",
			(!fake->server) ? "" : fake->server);
	}

	/*
	 *	Call authentication recursively, which will
	 *	do PAP, CHAP, MS-CHAP, etc.
	 */
	rad_virtual_server(fake);

	/*
	 *	Note that we don't do *anything* with the reply
	 *	attributes.
	 */
	if ((debug_flag > 0) && fr_log_fp) {
		fprintf(fr_log_fp, "} # server %s\n",
			(!fake->server) ? "" : fake->server);

		RDEBUG("Got tunneled reply code %d", fake->reply->code);
		
		debug_pair_list(fake->reply->vps);
	}

	/*
	 *	Decide what to do with the reply.
	 */
	switch (fake->reply->code) {
	case 0:			/* No reply code, must be proxied... */
#ifdef WITH_PROXY
		vp = pairfind(fake->config_items, PW_PROXY_TO_REALM, 0, TAG_ANY);
		if (vp) {
			eap_tunnel_data_t *tunnel;
			RDEBUG("Tunneled authentication will be proxied to %s", vp->vp_strvalue);

			/*
			 *	Tell the original request that it's going
			 *	to be proxied.
			 */
			pairfilter(request, &(request->config_items),
				  &(fake->config_items),
				  PW_PROXY_TO_REALM, 0, TAG_ANY);

			/*
			 *	Seed the proxy packet with the
			 *	tunneled request.
			 */
			rad_assert(!request->proxy);
			request->proxy = fake->packet;
			memset(&request->proxy->src_ipaddr, 0,
			       sizeof(request->proxy->src_ipaddr));
			memset(&request->proxy->src_ipaddr, 0,
			       sizeof(request->proxy->src_ipaddr));
			request->proxy->src_port = 0;
			request->proxy->dst_port = 0;
			fake->packet = NULL;
			rad_free(&fake->reply);
			fake->reply = NULL;

			/*
			 *	Set up the callbacks for the tunnel
			 */
			tunnel = talloc_zero(request, eap_tunnel_data_t);
			tunnel->tls_session = tls_session;
			tunnel->callback = eapttls_postproxy;

			/*
			 *	Associate the callback with the request.
			 */
			rcode = request_data_add(request,
						 request->proxy,
						 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
						 tunnel, NULL);
			rad_assert(rcode == 0);

			/*
			 *	rlm_eap.c has taken care of associating
			 *	the handler with the fake request.
			 *
			 *	So we associate the fake request with
			 *	this request.
			 */
			rcode = request_data_add(request,
						 request->proxy,
						 REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK,
						 fake, my_request_free);
			rad_assert(rcode == 0);
			fake = NULL;

			/*
			 *	Didn't authenticate the packet, but
			 *	we're proxying it.
			 */
			rcode = PW_STATUS_CLIENT;

		} else
#endif	/* WITH_PROXY */
		  {
			RDEBUG("No tunneled reply was found for request %d , and the request was not proxied: rejecting the user.",
			       request->number);
			rcode = PW_AUTHENTICATION_REJECT;
		}
		break;

	default:
		/*
		 *	Returns RLM_MODULE_FOO, and we want to return
		 *	PW_FOO
		 */
		rcode = process_reply(handler, tls_session, request,
				      fake->reply);
		switch (rcode) {
		case RLM_MODULE_REJECT:
			rcode = PW_AUTHENTICATION_REJECT;
			break;

		case RLM_MODULE_HANDLED:
			rcode = PW_ACCESS_CHALLENGE;
			break;

		case RLM_MODULE_OK:
			rcode = PW_AUTHENTICATION_ACK;
			break;

		default:
			rcode = PW_AUTHENTICATION_REJECT;
			break;
		}
		break;
	}

	request_free(&fake);

	return rcode;
}
Esempio n. 6
0
/*
 *	Use a reply packet to determine what to do.
 */
static int process_reply(UNUSED eap_handler_t *handler, tls_session_t *tls_session,
			 REQUEST *request, RADIUS_PACKET *reply)
{
	int rcode = RLM_MODULE_REJECT;
	VALUE_PAIR *vp;
	ttls_tunnel_t *t = tls_session->opaque;

	rad_assert(request != NULL);
	rad_assert(handler->request == request);

	/*
	 *	If the response packet was Access-Accept, then
	 *	we're OK.  If not, die horribly.
	 *
	 *	FIXME: Take MS-CHAP2-Success attribute, and
	 *	tunnel it back to the client, to authenticate
	 *	ourselves to the client.
	 *
	 *	FIXME: If we have an Access-Challenge, then
	 *	the Reply-Message is tunneled back to the client.
	 *
	 *	FIXME: If we have an EAP-Message, then that message
	 *	must be tunneled back to the client.
	 *
	 *	FIXME: If we have an Access-Challenge with a State
	 *	attribute, then do we tunnel that to the client, or
	 *	keep track of it ourselves?
	 *
	 *	FIXME: EAP-Messages can only start with 'identity',
	 *	NOT 'eap start', so we should check for that....
	 */
	switch (reply->code) {
	case PW_AUTHENTICATION_ACK:
		RDEBUG("Got tunneled Access-Accept");

		rcode = RLM_MODULE_OK;

		/*
		 *	MS-CHAP2-Success means that we do NOT return
		 *	an Access-Accept, but instead tunnel that
		 *	attribute to the client, and keep going with
		 *	the TTLS session.  Once the client accepts
		 *	our identity, it will respond with an empty
		 *	packet, and we will send EAP-Success.
		 */
		vp = NULL;
		pairfilter(tls_session, &vp, &reply->vps, PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
		if (vp) {
			RDEBUG("Got MS-CHAP2-Success, tunneling it to the client in a challenge.");
			rcode = RLM_MODULE_HANDLED;
			t->authenticated = TRUE;

			/*
			 *	Delete MPPE keys & encryption policy.  We don't
			 *	want these here.
			 */
			pairdelete(&reply->vps, 7, VENDORPEC_MICROSOFT, TAG_ANY);
			pairdelete(&reply->vps, 8, VENDORPEC_MICROSOFT, TAG_ANY);
			pairdelete(&reply->vps, 16, VENDORPEC_MICROSOFT, TAG_ANY);
			pairdelete(&reply->vps, 17, VENDORPEC_MICROSOFT, TAG_ANY);

			/*
			 *	Use the tunneled reply, but not now.
			 */
			if (t->use_tunneled_reply) {
				rad_assert(!t->accept_vps);
				pairfilter(t, &t->accept_vps, &reply->vps,
					  0, 0, TAG_ANY);
				rad_assert(!reply->vps);
			}

		} else { /* no MS-CHAP2-Success */
			/*
			 *	Can only have EAP-Message if there's
			 *	no MS-CHAP2-Success.  (FIXME: EAP-MSCHAP?)
			 *
			 *	We also do NOT tunnel the EAP-Success
			 *	attribute back to the client, as the client
			 *	can figure it out, from the non-tunneled
			 *	EAP-Success packet.
			 */
			pairfilter(tls_session, &vp, &reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
			pairfree(&vp);
		}

		/*
		 *	Handle the ACK, by tunneling any necessary reply
		 *	VP's back to the client.
		 */
		if (vp) {
			vp2diameter(request, tls_session, vp);
			pairfree(&vp);
		}

		/*
		 *	If we've been told to use the attributes from
		 *	the reply, then do so.
		 *
		 *	WARNING: This may leak information about the
		 *	tunneled user!
		 */
		if (t->use_tunneled_reply) {
			pairdelete(&reply->vps, PW_PROXY_STATE, 0, TAG_ANY);
			pairfilter(request->reply, &request->reply->vps,
				  &reply->vps, 0, 0, TAG_ANY);
		}
		break;


	case PW_AUTHENTICATION_REJECT:
		RDEBUG("Got tunneled Access-Reject");
		rcode = RLM_MODULE_REJECT;
		break;

		/*
		 *	Handle Access-Challenge, but only if we
		 *	send tunneled reply data.  This is because
		 *	an Access-Challenge means that we MUST tunnel
		 *	a Reply-Message to the client.
		 */
	case PW_ACCESS_CHALLENGE:
		RDEBUG("Got tunneled Access-Challenge");

		/*
		 *	Keep the State attribute, if necessary.
		 *
		 *	Get rid of the old State, too.
		 */
		pairfree(&t->state);
		pairfilter(t, &t->state, &reply->vps, PW_STATE, 0, TAG_ANY);

		/*
		 *	We should really be a bit smarter about this,
		 *	and move over only those attributes which
		 *	are relevant to the authentication request,
		 *	but that's a lot more work, and this "dumb"
		 *	method works in 99.9% of the situations.
		 */
		vp = NULL;
		pairfilter(t, &vp, &reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY);

		/*
		 *	There MUST be a Reply-Message in the challenge,
		 *	which we tunnel back to the client.
		 *
		 *	If there isn't one in the reply VP's, then
		 *	we MUST create one, with an empty string as
		 *	it's value.
		 */
		pairfilter(t, &vp, &reply->vps, PW_REPLY_MESSAGE, 0, TAG_ANY);

		/*
		 *	Handle the ACK, by tunneling any necessary reply
		 *	VP's back to the client.
		 */
		if (vp) {
			vp2diameter(request, tls_session, vp);
			pairfree(&vp);
		}
		rcode = RLM_MODULE_HANDLED;
		break;

	default:
		RDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code);
		rcode = RLM_MODULE_INVALID;
		break;
	}

	return rcode;
}
/*
 *	Authenticate a previously sent challenge.
 */
static int mschapv2_authenticate(void *arg, eap_handler_t *handler)
{
	int rcode, ccode;
	mschapv2_opaque_t *data;
	EAP_DS *eap_ds = handler->eap_ds;
	VALUE_PAIR *challenge, *response, *name;
	rlm_eap_mschapv2_t *inst = (rlm_eap_mschapv2_t *) arg;
	REQUEST *request = handler->request;

	rad_assert(request != NULL);
	rad_assert(handler->stage == AUTHENTICATE);

	data = (mschapv2_opaque_t *) handler->opaque;

	/*
	 *	Sanity check the response.
	 */
	if (eap_ds->response->length <= 5) {
		RDEBUGE("corrupted data");
		return 0;
	}

	ccode = eap_ds->response->type.data[0];

	switch (data->code) {
		case PW_EAP_MSCHAPV2_FAILURE:
			if (ccode == PW_EAP_MSCHAPV2_RESPONSE) {
				RDEBUG2("authentication re-try from client after we sent a failure");
				break;
			}

			/*
			 * if we sent error 648 (password expired) to the client
			 * we might get an MSCHAP-CPW packet here; turn it into a
			 * regular MS-CHAP2-CPW packet and pass it to rlm_mschap
			 * (or proxy it, I guess)
			 */
			if (ccode == PW_EAP_MSCHAPV2_CHGPASSWD) {
				VALUE_PAIR *cpw;
				int mschap_id = eap_ds->response->type.data[1];
				int copied=0,seq=1;

				RDEBUG2("password change packet received");

				challenge = pairmake_packet("MS-CHAP-Challenge", "0x00", T_OP_EQ);
				if (!challenge) {
					return 0;
				}
				challenge->length = MSCHAPV2_CHALLENGE_LEN;
				memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);

				cpw = pairmake_packet("MS-CHAP2-CPW", "", T_OP_EQ);
				cpw->vp_octets[0] = 7;
				cpw->vp_octets[1] = mschap_id;
				memcpy(cpw->vp_octets+2, eap_ds->response->type.data + 520, 66);
				cpw->length = 68;

				/*
				 * break the encoded password into VPs (3 of them)
				 */
				while (copied < 516) {
					VALUE_PAIR *nt_enc;

					int to_copy = 516 - copied;
					if (to_copy > 243)
						to_copy = 243;

					nt_enc = pairmake_packet("MS-CHAP-NT-Enc-PW", "", T_OP_ADD);
					nt_enc->vp_octets[0] = 6;
					nt_enc->vp_octets[1] = mschap_id;
					nt_enc->vp_octets[2] = 0;
					nt_enc->vp_octets[3] = seq++;

					memcpy(nt_enc->vp_octets + 4, eap_ds->response->type.data + 4 + copied, to_copy);
					copied += to_copy;
					nt_enc->length = 4 + to_copy;
				}

				RDEBUG2("built change password packet");
				debug_pair_list(request->packet->vps);

				/*
				 * jump to "authentication"
				 */
				goto packet_ready;
			}

			/*
			 * we sent a failure and are expecting a failure back
			 */
			if (ccode != PW_EAP_MSCHAPV2_FAILURE) {
				RDEBUGE("Sent FAILURE expecting FAILURE but got %d", ccode);
				return 0;
			}

	failure:
			request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
			eap_ds->request->code = PW_EAP_FAILURE;
			return 1;

		case PW_EAP_MSCHAPV2_SUCCESS:
			/*
			 * we sent a success to the client; some clients send a
			 * success back as-per the RFC, some send an ACK. Permit
			 * both, I guess...
			 */

			switch (ccode) {
				case PW_EAP_MSCHAPV2_SUCCESS:
					eap_ds->request->code = PW_EAP_SUCCESS;

					pairfilter(request->reply,
						  &request->reply->vps,
						  &data->mppe_keys, 0, 0, TAG_ANY);
					/* fall through... */

				case PW_EAP_MSCHAPV2_ACK:
#ifdef WITH_PROXY
					/*
					 *	It's a success.  Don't proxy it.
					 */
					request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
#endif
					pairfilter(request->reply,
						  &request->reply->vps,
						  &data->reply, 0, 0, TAG_ANY);
					return 1;
			}
			RDEBUGE("Sent SUCCESS expecting SUCCESS (or ACK) but got %d", ccode);
			return 0;

		case PW_EAP_MSCHAPV2_CHALLENGE:
			if (ccode == PW_EAP_MSCHAPV2_FAILURE) goto failure;

			/*
			 * we sent a challenge, expecting a response
			 */
			if (ccode != PW_EAP_MSCHAPV2_RESPONSE) {
				RDEBUGE("Sent CHALLENGE expecting RESPONSE but got %d", ccode);
				return 0;
			}
			/* authentication happens below */
			break;


		default:
			/* should never happen */
			RDEBUGE("unknown state %d", data->code);
			return 0;
	}


	/*
	 *	Ensure that we have at least enough data
	 *	to do the following checks.
	 *
	 *	EAP header (4), EAP type, MS-CHAP opcode,
	 *	MS-CHAP ident, MS-CHAP data length (2),
	 *	MS-CHAP value length.
	 */
	if (eap_ds->response->length < (4 + 1 + 1 + 1 + 2 + 1)) {
		RDEBUGE("Response is too short");
		return 0;
	}

	/*
	 *	The 'value_size' is the size of the response,
	 *	which is supposed to be the response (48
	 *	bytes) plus 1 byte of flags at the end.
	 */
	if (eap_ds->response->type.data[4] != 49) {
		RDEBUGE("Response is of incorrect length %d", eap_ds->response->type.data[4]);
		return 0;
	}

	/*
	 *	The MS-Length field is 5 + value_size + length
	 *	of name, which is put after the response.
	 */
	if (((eap_ds->response->type.data[2] << 8) |
	     eap_ds->response->type.data[3]) < (5 + 49)) {
		RDEBUGE("Response contains contradictory length %d %d",
			(eap_ds->response->type.data[2] << 8) |
		       eap_ds->response->type.data[3], 5 + 49);
		return 0;
	}

	/*
	 *	We now know that the user has sent us a response
	 *	to the challenge.  Let's try to authenticate it.
	 *
	 *	We do this by taking the challenge from 'data',
	 *	the response from the EAP packet, and creating VALUE_PAIR's
	 *	to pass to the 'mschap' module.  This is a little wonky,
	 *	but it works.
	 */
	challenge = pairmake_packet("MS-CHAP-Challenge", "0x00", T_OP_EQ);
	if (!challenge) {
		return 0;
	}
	challenge->length = MSCHAPV2_CHALLENGE_LEN;
	memcpy(challenge->vp_strvalue, data->challenge, MSCHAPV2_CHALLENGE_LEN);

	response = pairmake_packet("MS-CHAP2-Response", "0x00", T_OP_EQ);
	if (!response) {
		return 0;
	}

	response->length = MSCHAPV2_RESPONSE_LEN;
	memcpy(response->vp_strvalue + 2, &eap_ds->response->type.data[5],
	       MSCHAPV2_RESPONSE_LEN - 2);
	response->vp_strvalue[0] = eap_ds->response->type.data[1];
	response->vp_strvalue[1] = eap_ds->response->type.data[5 + MSCHAPV2_RESPONSE_LEN];

	name = pairmake_packet("NTLM-User-Name", "", T_OP_EQ);
	if (!name) {
		return 0;
	}
	
	/*
	 *	MS-Length - MS-Value - 5.
	 */
	name->length = (((eap_ds->response->type.data[2] << 8) |
			 eap_ds->response->type.data[3]) -
			eap_ds->response->type.data[4] - 5);
	if (name->length >= sizeof(name->vp_strvalue)) {
		name->length = sizeof(name->vp_strvalue) - 1;
	}

	memcpy(name->vp_strvalue,
	       &eap_ds->response->type.data[4 + MSCHAPV2_RESPONSE_LEN],
	       name->length);
	name->vp_strvalue[name->length] = '\0';

packet_ready:

#ifdef WITH_PROXY
	/*
	 *	If this options is set, then we do NOT authenticate the
	 *	user here.  Instead, now that we've added the MS-CHAP
	 *	attributes to the request, we STOP, and let the outer
	 *	tunnel code handle it.
	 *
	 *	This means that the outer tunnel code will DELETE the
	 *	EAP attributes, and proxy the MS-CHAP attributes to a
	 *	home server.
	 */
	if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
		char *username = NULL;
		eap_tunnel_data_t *tunnel;

		RDEBUG2("cancelling authentication and letting it be proxied");

		/*
		 *	Set up the callbacks for the tunnel
		 */
		tunnel = talloc_zero(request, eap_tunnel_data_t);

		tunnel->tls_session = arg;
		tunnel->callback = mschap_postproxy;

		/*
		 *	Associate the callback with the request.
		 */
		rcode = request_data_add(request,
					 request->proxy,
					 REQUEST_DATA_EAP_TUNNEL_CALLBACK,
					 tunnel, NULL);
		rad_assert(rcode == 0);

		/*
		 *	The State attribute is NOT supposed to
		 *	go into the proxied packet, it will confuse
		 *	other RADIUS servers, and they will discard
		 *	the request.
		 *
		 *	The PEAP module will take care of adding
		 *	the State attribute back, before passing
		 *	the handler & request back into the tunnel.
		 */
		pairdelete(&request->packet->vps, PW_STATE, 0, TAG_ANY);

		/*
		 *	Fix the User-Name when proxying, to strip off
		 *	the NT Domain, if we're told to, and a User-Name
		 *	exists, and there's a \\, meaning an NT-Domain
		 *	in the user name, THEN discard the user name.
		 */
		if (inst->with_ntdomain_hack &&
		    ((challenge = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY)) != NULL) &&
		    ((username = strchr(challenge->vp_strvalue, '\\')) != NULL)) {
			/*
			 *	Wipe out the NT domain.
			 *
			 *	FIXME: Put it into MS-CHAP-Domain?
			 */
			username++; /* skip the \\ */
			memmove(challenge->vp_strvalue,
				username,
				strlen(username) + 1); /* include \0 */
			challenge->length = strlen(challenge->vp_strvalue);
		}

		/*
		 *	Remember that in the post-proxy stage, we've got
		 *	to do the work below, AFTER the call to MS-CHAP
		 *	authentication...
		 */
		return 1;
	}
#endif

	/*
	 *	This is a wild & crazy hack.
	 */
	rcode = process_authenticate(PW_AUTHTYPE_MS_CHAP, request);

	/*
	 *	Delete MPPE keys & encryption policy.  We don't
	 *	want these here.
	 */
	fix_mppe_keys(handler, data);

	/*
	 *	Take the response from the mschap module, and
	 *	return success or failure, depending on the result.
	 */
	response = NULL;
	if (rcode == RLM_MODULE_OK) {
		pairfilter(data, &response, &request->reply->vps,
			 PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
		data->code = PW_EAP_MSCHAPV2_SUCCESS;

	} else if (inst->send_error) {
		pairfilter(data, &response, &request->reply->vps,
			  PW_MSCHAP_ERROR, VENDORPEC_MICROSOFT, TAG_ANY);
		if (response) {
			int n,err,retry;
			char buf[34];

			RDEBUG2("MSCHAP-Error: %s", response->vp_strvalue);

			/*
			 *	Pxarse the new challenge out of the
			 *	MS-CHAP-Error, so that if the client
			 *	issues a re-try, we will know which
			 *	challenge value that they used.
			 */
			n = sscanf(response->vp_strvalue, "%*cE=%d R=%d C=%32s", &err, &retry, &buf[0]);
			if (n == 3) {
				DEBUG2("Found new challenge from MS-CHAP-Error: err=%d retry=%d challenge=%s", err, retry, buf);
				fr_hex2bin(buf, data->challenge, 16);
			} else {
				DEBUG2("Could not parse new challenge from MS-CHAP-Error: %d", n);
			}
		}
		data->code = PW_EAP_MSCHAPV2_FAILURE;
	} else {
		eap_ds->request->code = PW_EAP_FAILURE;
		return 1;
	}

	/*
	 *	No response, die.
	 */
	if (!response) {
		RDEBUGE("No MS-CHAP-Success or MS-CHAP-Error was found.");
		return 0;
	}

	/*
	 *	Compose the response (whatever it is),
	 *	and return it to the over-lying EAP module.
	 */
	eapmschapv2_compose(handler, response);
	pairfree(&response);

	return 1;
}
/*
 *	Do post-proxy processing,
 *	0 = fail
 *	1 = OK.
 *
 *	Called from rlm_eap.c, eap_postproxy().
 */
static int mschap_postproxy(eap_handler_t *handler, UNUSED void *tunnel_data)
{
	VALUE_PAIR *response = NULL;
	mschapv2_opaque_t *data;
	REQUEST *request = handler->request;

	data = (mschapv2_opaque_t *) handler->opaque;
	rad_assert(data != NULL);

	RDEBUG2("Passing reply from proxy back into the tunnel %d.",
		request->reply->code);

	/*
	 *	There is only a limited number of possibilities.
	 */
	switch (request->reply->code) {
	case PW_AUTHENTICATION_ACK:
		RDEBUG2("Proxied authentication succeeded.");

		/*
		 *	Move the attribute, so it doesn't go into
		 *	the reply.
		 */
		pairfilter(data, &response,
			  &request->reply->vps,
			  PW_MSCHAP2_SUCCESS, VENDORPEC_MICROSOFT, TAG_ANY);
		break;

	default:
	case PW_AUTHENTICATION_REJECT:
		RDEBUG("Proxied authentication did not succeed.");
		return 0;
	}

	/*
	 *	No response, die.
	 */
	if (!response) {
		RDEBUGE("Proxied reply contained no MS-CHAP-Success or MS-CHAP-Error");
		return 0;
	}

	/*
	 *	Done doing EAP proxy stuff.
	 */
	request->options &= ~RAD_REQUEST_OPTION_PROXY_EAP;
	eapmschapv2_compose(handler, response);
	data->code = PW_EAP_MSCHAPV2_SUCCESS;

	/*
	 *	Delete MPPE keys & encryption policy
	 *
	 *	FIXME: Use intelligent names...
	 */
	fix_mppe_keys(handler, data);

	/*
	 * save any other attributes for re-use in the final
	 * access-accept e.g. vlan, etc. This lets the PEAP
	 * use_tunneled_reply code work
	 */
	data->reply = paircopy(data, request->reply->vps);

	/*
	 *	And we need to challenge the user, not ack/reject them,
	 *	so we re-write the ACK to a challenge.  Yuck.
	 */
	request->reply->code = PW_ACCESS_CHALLENGE;
	pairfree(&response);

	return 1;
}
Esempio n. 9
0
/*
 *	Do authentication, by letting EAP-TLS do most of the work.
 */
static int mod_authenticate(void *arg, eap_handler_t *handler)
{
	int rcode;
	fr_tls_status_t	status;
	rlm_eap_ttls_t *inst = (rlm_eap_ttls_t *) arg;
	tls_session_t *tls_session = (tls_session_t *) handler->opaque;
	ttls_tunnel_t *t = (ttls_tunnel_t *) tls_session->opaque;
	REQUEST *request = handler->request;

	RDEBUG2("Authenticate");

	tls_session->length_flag = inst->include_length;

	/*
	 *	Process TLS layer until done.
	 */
	status = eaptls_process(handler);
	RDEBUG2("eaptls_process returned %d\n", status);
	switch (status) {
		/*
		 *	EAP-TLS handshake was successful, tell the
		 *	client to keep talking.
		 *
		 *	If this was EAP-TLS, we would just return
		 *	an EAP-TLS-Success packet here.
		 */
	case FR_TLS_SUCCESS:
		if (SSL_session_reused(tls_session->ssl)) {
			RDEBUG("Skipping Phase2 due to session resumption");
			goto do_keys;
		}

		if (t && t->authenticated) {
			RDEBUG2("Using saved attributes from the original Access-Accept");
			debug_pair_list(t->accept_vps);
			pairfilter(handler->request->reply,
				  &handler->request->reply->vps,
				  &t->accept_vps, 0, 0, TAG_ANY);
		do_keys:
			/*
			 *	Success: Automatically return MPPE keys.
			 */
			return eaptls_success(handler, 0);
		} else {
			eaptls_request(handler->eap_ds, tls_session);
		}
		return 1;

		/*
		 *	The TLS code is still working on the TLS
		 *	exchange, and it's a valid TLS request.
		 *	do nothing.
		 */
	case FR_TLS_HANDLED:
		return 1;

		/*
		 *	Handshake is done, proceed with decoding tunneled
		 *	data.
		 */
	case FR_TLS_OK:
		break;

		/*
		 *	Anything else: fail.
		 */
	default:
		return 0;
	}

	/*
	 *	Session is established, proceed with decoding
	 *	tunneled data.
	 */
	RDEBUG2("Session established.  Proceeding to decode tunneled attributes.");

	/*
	 *	We may need TTLS data associated with the session, so
	 *	allocate it here, if it wasn't already alloacted.
	 */
	if (!tls_session->opaque) {
		tls_session->opaque = ttls_alloc(inst, handler);
		tls_session->free_opaque = ttls_free;
	}

	/*
	 *	Process the TTLS portion of the request.
	 */
	rcode = eapttls_process(handler, tls_session);
	switch (rcode) {
	case PW_AUTHENTICATION_REJECT:
		eaptls_fail(handler, 0);
		return 0;

		/*
		 *	Access-Challenge, continue tunneled conversation.
		 */
	case PW_ACCESS_CHALLENGE:
		eaptls_request(handler->eap_ds, tls_session);
		return 1;

		/*
		 *	Success: Automatically return MPPE keys.
		 */
	case PW_AUTHENTICATION_ACK:
		return eaptls_success(handler, 0);

		/*
		 *	No response packet, MUST be proxying it.
		 *	The main EAP module will take care of discovering
		 *	that the request now has a "proxy" packet, and
		 *	will proxy it, rather than returning an EAP packet.
		 */
	case PW_STATUS_CLIENT:
#ifdef WITH_PROXY
		rad_assert(handler->request->proxy != NULL);
#endif
		return 1;
		break;

	default:
		break;
	}

	/*
	 *	Something we don't understand: Reject it.
	 */
	eaptls_fail(handler, 0);
	return 0;
}