/** Add a module failure message VALUE_PAIR to the request
 */
void vmodule_failure_msg(REQUEST *request, char const *fmt, va_list ap)
{
	char *p;
	VALUE_PAIR *vp;
	va_list aq;

	if (!fmt || !request->packet) {
		return;
	}

	/*
	 *  If we don't copy the original ap we get a segfault from vasprintf. This is apparently
	 *  due to ap sometimes being implemented with a stack offset which is invalidated if
	 *  ap is passed into another function. See here:
	 *  http://julipedia.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html
	 *
	 *  I don't buy that explanation, but doing a va_copy here does prevent SEGVs seen when
	 *  running unit tests which generate errors under CI.
	 */
	va_copy(aq, ap);
	p = talloc_vasprintf(request, fmt, aq);
	va_end(aq);

	MEM(vp = pairmake_packet("Module-Failure-Message", NULL, T_OP_ADD));
	if (request->module && (request->module[0] != '\0')) {
		pairsprintf(vp, "%s: %s", request->module, p);
	} else {
		pairsprintf(vp, "%s", p);
	}
	talloc_free(p);
}
/*
 *	This hack strips out Cisco's VSA duplicities in lines
 *	(Cisco not implemented VSA's in standard way.
 *
 *	Cisco sends it's VSA attributes with the attribute name *again*
 *	in the string, like:  H323-Attribute = "h323-attribute=value".
 *	This sort of behaviour is nonsense.
 */
static void cisco_vsa_hack(REQUEST *request)
{
	int		vendorcode;
	char		*ptr;
	char		newattr[MAX_STRING_LEN];
	VALUE_PAIR	*vp;
	vp_cursor_t	cursor;
	for (vp = fr_cursor_init(&cursor, &request->packet->vps);
	     vp;
	     vp = fr_cursor_next(&cursor)) {
		vendorcode = vp->da->vendor;
		if (!((vendorcode == 9) || (vendorcode == 6618))) {
			continue; /* not a Cisco or Quintum VSA, continue */
		}

		if (vp->da->type != PW_TYPE_STRING) {
			continue;
		}

		/*
		 *  No weird packing.  Ignore it.
		 */
		ptr = strchr(vp->vp_strvalue, '='); /* find an '=' */
		if (!ptr) {
			continue;
		}

		/*
		 *	Cisco-AVPair's get packed as:
		 *
		 *	Cisco-AVPair = "h323-foo-bar = baz"
		 *	Cisco-AVPair = "h323-foo-bar=baz"
		 *
		 *	which makes sense only if you're a lunatic.
		 *	This code looks for the attribute named inside
		 *	of the string, and if it exists, adds it as a new
		 *	attribute.
		 */
		if (vp->da->attr == 1) {
			char const *p;

			p = vp->vp_strvalue;
			gettoken(&p, newattr, sizeof(newattr), false);

			if (dict_attrbyname(newattr) != NULL) {
				pairmake_packet(newattr, ptr + 1, T_OP_EQ);
			}
		} else {	/* h322-foo-bar = "h323-foo-bar = baz" */
			/*
			 *	We strip out the duplicity from the
			 *	value field, we use only the value on
			 *	the right side of the '=' character.
			 */
			pairstrcpy(vp, ptr + 1);
		}
	}
}
Exemple #3
0
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t *handle,
					  bool *dofallthrough)
{
	rlm_rcode_t		rcode = RLM_MODULE_NOOP;
	VALUE_PAIR		*check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
	rlm_sql_grouplist_t	*head = NULL, *entry = NULL;

	char			*expanded = NULL;
	int			rows;

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

	/*
	 *	Get the list of groups this user is a member of
	 */
	rows = sql_get_grouplist(inst, handle, request, &head);
	if (rows < 0) {
		REDEBUG("Error retrieving group list");

		return RLM_MODULE_FAIL;
	}
	if (rows == 0) {
		RDEBUG2("User not found in any groups");
		rcode = RLM_MODULE_NOTFOUND;
		goto finish;
	}

	RDEBUG2("User found in the group table");

	for (entry = head; entry != NULL && (*dofallthrough != 0); entry = entry->next) {
		/*
		 *	Add the Sql-Group attribute to the request list so we know
		 *	which group we're retrieving attributes for
		 */
		sql_group = pairmake_packet("Sql-Group", entry->name, T_OP_EQ);
		if (!sql_group) {
			REDEBUG("Error creating Sql-Group attribute");
			rcode = RLM_MODULE_FAIL;
			goto finish;
		}

		if (inst->config->authorize_group_check_query && (inst->config->authorize_group_check_query != '\0')) {
			/*
			 *	Expand the group query
			 */
			if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
					 sql_escape_func, inst) < 0) {
				REDEBUG("Error generating query");
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}

			rows = sql_getvpdata(inst, &handle, request, &check_tmp, expanded);
			TALLOC_FREE(expanded);
			if (rows < 0) {
				REDEBUG("Error retrieving check pairs for group %s", entry->name);
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}

			/*
			 *	If we got check rows we need to process them before we decide to process the reply rows
			 */
			if ((rows > 0) &&
			    (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
				pairfree(&check_tmp);
				pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);

				continue;
			}

			RDEBUG2("Group \"%s\" check items matched", entry->name);
			rcode = RLM_MODULE_OK;

			radius_pairmove(request, &request->config_items, check_tmp, true);
			check_tmp = NULL;
		}

		if (inst->config->authorize_group_reply_query && (inst->config->authorize_group_reply_query != '\0')) {
			/*
			 *	Now get the reply pairs since the paircompare matched
			 */
			if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
					 sql_escape_func, inst) < 0) {
				REDEBUG("Error generating query");
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}

			rows = sql_getvpdata(inst, &handle, request->reply, &reply_tmp, expanded);
			TALLOC_FREE(expanded);
			if (rows < 0) {
				REDEBUG("Error retrieving reply pairs for group %s", entry->name);
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}

			*dofallthrough = fallthrough(reply_tmp);

			RDEBUG2("Group \"%s\" reply items processed", entry->name);
			rcode = RLM_MODULE_OK;

			radius_pairmove(request, &request->reply->vps, reply_tmp, true);
			reply_tmp = NULL;
		}

		pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);
	}

	finish:

	talloc_free(head);
	pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);

	return rcode;
}
/*
 *	Internal function to cut down on duplicated code.
 *
 *	Returns -1 on failure, 0 on no failure.  returnrealm
 *	is NULL on don't proxy, realm otherwise.
 */
static int check_for_realm(void *instance, REQUEST *request, REALM **returnrealm)
{
	char *namebuf;
	char *username;
	char const *realmname = NULL;
	char *ptr;
	VALUE_PAIR *vp;
	REALM *realm;

	struct realm_config_t *inst = instance;

	/* initiate returnrealm */
	*returnrealm = NULL;

	/*
	 *	If the request has a proxy entry, then it's a proxy
	 *	reply, and we're walking through the module list again.
	 *
	 *	In that case, don't bother trying to proxy the request
	 *	again.
	 *
	 *	Also, if there's no User-Name attribute, we can't
	 *	proxy it, either.
	 */
	if ((!request->username)
#ifdef WITH_PROXY
	    || (request->proxy != NULL)
#endif
	    ) {
	
		RDEBUG2("Proxy reply, or no User-Name.  Ignoring.");
		return RLM_MODULE_OK;
	}

	/*
	 *      Check for 'Realm' attribute.  If it exists, then we've proxied
	 *      it already ( via another rlm_realm instance ) and should return.
	 */

	if (pairfind(request->packet->vps, PW_REALM, 0, TAG_ANY) != NULL ) {
		RDEBUG2("Request already proxied.  Ignoring.");
		return RLM_MODULE_OK;
	}

	/*
	 *	We will be modifing this later, so we want our own copy
	 *	of it.
	 */
	namebuf = talloc_strdup(request,  request->username->vp_strvalue);
	username = namebuf;

	switch(inst->format) {
	case REALM_FORMAT_SUFFIX:

	  /* DEBUG2("  rlm_realm: Checking for suffix after \"%c\"", inst->delim[0]); */
		ptr = strrchr(username, inst->delim[0]);
		if (ptr) {
			*ptr = '\0';
			realmname = ptr + 1;
		}
		break;

	case REALM_FORMAT_PREFIX:

		/* DEBUG2("  rlm_realm: Checking for prefix before \"%c\"", inst->delim[0]); */

		ptr = strchr(username, inst->delim[0]);
		if (ptr) {
			*ptr = '\0';
		     ptr++;
		     realmname = username;
		     username = ptr;
		}
		break;

	default:
		realmname = NULL;
		break;
	}

	/*
	 *	Print out excruciatingly descriptive debugging messages
	 *	for the people who find it too difficult to think about
	 *	what's going on.
	 */
	if (realmname) {
		RDEBUG2("Looking up realm \"%s\" for User-Name = \"%s\"",
		       realmname, request->username->vp_strvalue);
	} else {
		if (inst->ignore_null ) {
			RDEBUG2("No '%c' in User-Name = \"%s\", skipping NULL due to config.",
			inst->delim[0], request->username->vp_strvalue);
			talloc_free(namebuf);
			return RLM_MODULE_NOOP;
		}
		RDEBUG2("No '%c' in User-Name = \"%s\", looking up realm NULL",
		       inst->delim[0], request->username->vp_strvalue);
	}

	/*
	 *	Allow DEFAULT realms unless told not to.
	 */
	realm = realm_find(realmname);
	if (!realm) {
		RDEBUG2("No such realm \"%s\"",
			(!realmname) ? "NULL" : realmname);
		talloc_free(namebuf);
		return RLM_MODULE_NOOP;
	}
	if( inst->ignore_default &&
	    (strcmp(realm->name, "DEFAULT")) == 0) {
		RDEBUG2("Found DEFAULT, but skipping due to config.");
		talloc_free(namebuf);
		return RLM_MODULE_NOOP;
	}

	RDEBUG2("Found realm \"%s\"", realm->name);

	/*
	 *	If we've been told to strip the realm off, then do so.
	 */
	if (realm->striprealm) {
		/*
		 *	Create the Stripped-User-Name attribute, if it
		 *	doesn't exist.
		 *
		 */
		if (request->username->da->attr != PW_STRIPPED_USER_NAME) {
			vp = radius_paircreate(request, &request->packet->vps,
					       PW_STRIPPED_USER_NAME, 0);
			RDEBUG2("Adding Stripped-User-Name = \"%s\"", username);
		} else {
			vp = request->username;
			RDEBUG2("Setting Stripped-User-Name = \"%s\"", username);
		}

		pairstrcpy(vp, username);
		request->username = vp;
	}

	/*
	 *	Add the realm name to the request.
	 *	If the realm is a regex, the use the realm as entered
	 *	by the user.  Otherwise, use the configured realm name,
	 *	as realm name comparison is case insensitive.  We want
	 *	to use the configured name, rather than what the user
	 *	entered.
	 */
	if (realm->name[0] != '~') realmname = realm->name;
	pairmake_packet("Realm", realmname, T_OP_EQ);
	RDEBUG2("Adding Realm = \"%s\"", realmname);

	talloc_free(namebuf);
	realmname = username = NULL;

	/*
	 *	Figure out what to do with the request.
	 */
	switch (request->packet->code) {
	default:
		RDEBUG2("Unknown packet code %d\n",
		       request->packet->code);
		return RLM_MODULE_OK;		/* don't do anything */

		/*
		 *	Perhaps accounting proxying was turned off.
		 */
	case PW_ACCOUNTING_REQUEST:
		if (!realm->acct_pool) {
			RDEBUG2("Accounting realm is LOCAL.");
			return RLM_MODULE_OK;
		}
		break;

		/*
		 *	Perhaps authentication proxying was turned off.
		 */
	case PW_AUTHENTICATION_REQUEST:
		if (!realm->auth_pool) {
			RDEBUG2("Authentication realm is LOCAL.");
			return RLM_MODULE_OK;
		}
		break;
	}

#ifdef WITH_PROXY
	RDEBUG2("Proxying request from user %s to realm %s",
	       request->username->vp_strvalue, realm->name);

	/*
	 *	Skip additional checks if it's not an accounting
	 *	request.
	 */
	if (request->packet->code != PW_ACCOUNTING_REQUEST) {
		*returnrealm = realm;
		return RLM_MODULE_UPDATED;
	}

	/*
	 *	FIXME: Each server should have a unique server key,
	 *	and put it in the accounting packet.  Every server
	 *	should know about the keys, and NOT proxy requests to
	 *	a server with key X if the packet already contains key
	 *	X.
	 */

	/*
	 *      If this request has arrived from another freeradius server
	 *      that has already proxied the request, we don't need to do
	 *      it again.
	 */
	vp = pairfind(request->packet->vps, PW_FREERADIUS_PROXIED_TO, 0, TAG_ANY);
	if (vp && (request->packet->src_ipaddr.af == AF_INET)) {
		int i;
		fr_ipaddr_t my_ipaddr;

		my_ipaddr.af = AF_INET;
		my_ipaddr.ipaddr.ip4addr.s_addr = vp->vp_ipaddr;

		/*
		 *	Loop over the home accounting servers for this
		 *	realm.  If one of them has the same IP as the
		 *	FreeRADIUS-Proxied-To attribute, then the
		 *	packet has already been sent there.  Don't
		 *	send it there again.
		 */
		for (i = 0; i < realm->acct_pool->num_home_servers; i++) {
			if (fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr,
					    &my_ipaddr) == 0) {
				RDEBUG2("Suppressing proxy due to FreeRADIUS-Proxied-To");
				return RLM_MODULE_OK;
			}
		}

		/*
		 *	See detail_recv() in src/main/listen.c for the
		 *	additional checks.
		 */
#ifdef WITH_DETAIL
	} else if ((request->listener->type == RAD_LISTEN_DETAIL) &&
		   !fr_inaddr_any(&request->packet->src_ipaddr)) {
		int i;

		/*
		 *	Loop over the home accounting servers for this
		 *	realm.  If one of them has the same IP as the
		 *	FreeRADIUS-Proxied-To attribute, then the
		 *	packet has already been sent there.  Don't
		 *	send it there again.
		 */
		for (i = 0; i < realm->acct_pool->num_home_servers; i++) {
			if ((fr_ipaddr_cmp(&realm->acct_pool->servers[i]->ipaddr,
					     &request->packet->src_ipaddr) == 0) &&
			    (realm->acct_pool->servers[i]->port == request->packet->src_port)) {
				RDEBUG2("Suppressing proxy because packet was already sent to a server in that realm");
				return RLM_MODULE_OK;
			}
		}
#endif	/* WITH_DETAIL */
	}
#endif	/* WITH_PROXY */

	/*
	 *	We got this far, which means we have a realm, set returnrealm
	 */
	*returnrealm = realm;

	return RLM_MODULE_UPDATED;
}
static int
mod_authenticate (void *arg, eap_handler_t *handler)
{
    pwd_session_t *pwd_session;
    pwd_hdr *hdr;
    pwd_id_packet *id;
    eap_packet_t *response;
    REQUEST *request, *fake;
    VALUE_PAIR *pw, *vp;
    EAP_DS *eap_ds;
    int len, ret = 0;
    eap_pwd_t *inst = (eap_pwd_t *)arg;
    uint16_t offset;
    uint8_t exch, *buf, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN];
    uint8_t peer_confirm[SHA256_DIGEST_LENGTH];
    BIGNUM *x = NULL, *y = NULL;

    if ((!handler) ||
	((eap_ds = handler->eap_ds) == NULL) ||
	(!inst)) {
	return 0;
    }
    pwd_session = (pwd_session_t *)handler->opaque;
    request = handler->request;
    response = handler->eap_ds->response;
    hdr = (pwd_hdr *)response->type.data;

    buf = hdr->data;
    len = response->type.length - sizeof(pwd_hdr);

    /*
     * see if we're fragmenting, if so continue until we're done
     */
    if (pwd_session->out_buf_pos) {
	if (len) {
	    RDEBUG2("pwd got something more than an ACK for a fragment");
	}
	return send_pwd_request(pwd_session, eap_ds);
    }

    /*
     * the first fragment will have a total length, make a
     * buffer to hold all the fragments
     */
    if (EAP_PWD_GET_LENGTH_BIT(hdr)) {
	if (pwd_session->in_buf) {
	    RDEBUG2("pwd already alloced buffer for fragments");
	    return 0;
	}
	pwd_session->in_buf_len = ntohs(buf[0] * 256 | buf[1]);
	if ((pwd_session->in_buf = talloc_zero_array(pwd_session, uint8_t,
						     pwd_session->in_buf_len)) == NULL) {
	    RDEBUG2("pwd cannot allocate %d buffer to hold fragments",
		    pwd_session->in_buf_len);
	    return 0;
	}
	memset(pwd_session->in_buf, 0, pwd_session->in_buf_len);
	pwd_session->in_buf_pos = 0;
	buf += sizeof(uint16_t);
	len -= sizeof(uint16_t);
    }

    /*
     * all fragments, including the 1st will have the M(ore) bit set,
     * buffer those fragments!
     */
    if (EAP_PWD_GET_MORE_BIT(hdr)) {
	rad_assert(pwd_session->in_buf != NULL);
	if ((pwd_session->in_buf_pos + len) > pwd_session->in_buf_len) {
	    RDEBUG2("pwd will not overflow a fragment buffer. Nope, not prudent.");
	    return 0;
	}
	memcpy(pwd_session->in_buf + pwd_session->in_buf_pos, buf, len);
	pwd_session->in_buf_pos += len;

	/*
	 * send back an ACK for this fragment
	 */
	exch = EAP_PWD_GET_EXCHANGE(hdr);
	eap_ds->request->code = PW_EAP_REQUEST;
	eap_ds->request->type.num = PW_EAP_PWD;
	eap_ds->request->type.length = sizeof(pwd_hdr);
	if ((eap_ds->request->type.data = talloc_array(eap_ds->request,
						       uint8_t, sizeof(pwd_hdr))) == NULL) {
	    return 0;
	}
	hdr = (pwd_hdr *)eap_ds->request->type.data;
	EAP_PWD_SET_EXCHANGE(hdr, exch);
	return 1;

    }

    if (pwd_session->in_buf) {
	/*
	 * the last fragment...
	 */
	if ((pwd_session->in_buf_pos + len) > pwd_session->in_buf_len) {
	    RDEBUG2("pwd will not overflow a fragment buffer. Nope, not prudent.");
	    return 0;
	}
	memcpy(pwd_session->in_buf + pwd_session->in_buf_pos, buf, len);
	buf = pwd_session->in_buf;
	len = pwd_session->in_buf_len;
    }

    switch (pwd_session->state) {
	case PWD_STATE_ID_REQ:
	    if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_ID) {
		RDEBUG2("pwd exchange is incorrect: not ID");
		return 0;
	    }
	    id = (pwd_id_packet *)buf;
	    if ((id->prf != EAP_PWD_DEF_PRF) ||
		(id->random_function != EAP_PWD_DEF_RAND_FUN) ||
		(id->prep != EAP_PWD_PREP_NONE) ||
		(memcmp(id->token, (char *)&pwd_session->token, 4)) ||
		(id->group_num != ntohs(pwd_session->group_num))) {
		RDEBUG2("pwd id response is invalid");
		return 0;
	    }
	    /*
	     * we've agreed on the ciphersuite, record it...
	     */
	    ptr = (uint8_t *)&pwd_session->ciphersuite;
	    memcpy(ptr, (char *)&id->group_num, sizeof(uint16_t));
	    ptr += sizeof(uint16_t);
	    *ptr = EAP_PWD_DEF_RAND_FUN;
	    ptr += sizeof(uint8_t);
	    *ptr = EAP_PWD_DEF_PRF;

	    pwd_session->peer_id_len = len - sizeof(pwd_id_packet);
	    if (pwd_session->peer_id_len >= sizeof(pwd_session->peer_id)) {
		RDEBUG2("pwd id response is malformed");
		return 0;
	    }
	    memcpy(pwd_session->peer_id, id->identity,
		    pwd_session->peer_id_len);
	    pwd_session->peer_id[pwd_session->peer_id_len] = '\0';

	    /*
	     * make fake request to get the password for the usable ID
	     */
	    if ((fake = request_alloc_fake(handler->request)) == NULL) {
		RDEBUG("pwd unable to create fake request!");
		return 0;
	    }
	    fake->username = pairmake_packet("User-Name", "", T_OP_EQ);
	    if (!fake->username) {
		RDEBUG("pwd unanable to create value pair for username!");
		request_free(&fake);
		return 0;
	    }
	    memcpy(fake->username->vp_strvalue, pwd_session->peer_id,
		   pwd_session->peer_id_len);
	    fake->username->length = pwd_session->peer_id_len;
	    fake->username->vp_strvalue[fake->username->length] = 0;

	    if ((vp = pairfind(request->config_items, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) {
		    fake->server = vp->vp_strvalue;
		
	    } else if (inst->conf->virtual_server) {
		    fake->server = inst->conf->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 authorization recursively, which will
	     *	get the password.
	     */
	    process_authorize(0, 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);
	    }

	    if ((pw = pairfind(fake->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) {
		DEBUG2("failed to find password for %s to do pwd authentication",
		       pwd_session->peer_id);
		request_free(&fake);
		return 0;
	    }

	    if (compute_password_element(pwd_session, pwd_session->group_num,
					 pw->data.strvalue, strlen(pw->data.strvalue),
					 inst->conf->server_id, strlen(inst->conf->server_id),
					 pwd_session->peer_id, strlen(pwd_session->peer_id),
					 &pwd_session->token)) {
		DEBUG2("failed to obtain password element :-(");
		request_free(&fake);
		return 0;
	    }
	    request_free(&fake);

	    /*
	     * compute our scalar and element
	     */
	    if (compute_scalar_element(pwd_session, inst->bnctx)) {
		DEBUG2("failed to compute server's scalar and element");
		return 0;
	    }
	    if (((x = BN_new()) == NULL) ||
		((y = BN_new()) == NULL)) {
		DEBUG2("server point allocation failed");
		return 0;
	    }
	    /*
	     * element is a point, get both coordinates: x and y
	     */
	    if (!EC_POINT_get_affine_coordinates_GFp(pwd_session->group,
						     pwd_session->my_element, x, y,
						     inst->bnctx)) {
		DEBUG2("server point assignment failed");
		BN_free(x);
		BN_free(y);
		return 0;
	    }
	    /*
	     * construct request
	     */
	    pwd_session->out_buf_len = BN_num_bytes(pwd_session->order) +
		(2 * BN_num_bytes(pwd_session->prime));
	    if ((pwd_session->out_buf = talloc_array(pwd_session, uint8_t,
						     pwd_session->out_buf_len)) == NULL) {
		return 0;
	    }
	    memset(pwd_session->out_buf, 0, pwd_session->out_buf_len);

	    ptr = pwd_session->out_buf;
	    offset = BN_num_bytes(pwd_session->prime) - BN_num_bytes(x);
	    BN_bn2bin(x, ptr + offset);

	    ptr += BN_num_bytes(pwd_session->prime);
	    offset = BN_num_bytes(pwd_session->prime) - BN_num_bytes(y);
	    BN_bn2bin(y, ptr + offset);

	    ptr += BN_num_bytes(pwd_session->prime);
	    offset = BN_num_bytes(pwd_session->order) - BN_num_bytes(pwd_session->my_scalar);
	    BN_bn2bin(pwd_session->my_scalar, ptr + offset);

	    pwd_session->state = PWD_STATE_COMMIT;
	    ret = send_pwd_request(pwd_session, eap_ds);
	    break;
	case PWD_STATE_COMMIT:
	    if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) {
		RDEBUG2("pwd exchange is incorrect: not commit!");
		return 0;
	    }
	    /*
	     * process the peer's commit and generate the shared key, k
	     */
	    if (process_peer_commit(pwd_session, buf, inst->bnctx)) {
		RDEBUG2("failed to process peer's commit");
		return 0;
	    }

	    /*
	     * compute our confirm blob
	     */
	    if (compute_server_confirm(pwd_session, pwd_session->my_confirm, inst->bnctx)) {
		ERROR("rlm_eap_pwd: failed to compute confirm!");
		return 0;
	    }
	    /*
	     * construct a response...which is just our confirm blob
	     */
	    pwd_session->out_buf_len = SHA256_DIGEST_LENGTH;
	    if ((pwd_session->out_buf = talloc_array(pwd_session, uint8_t,
						     pwd_session->out_buf_len)) == NULL) {
		return 0;
	    }
	    memset(pwd_session->out_buf, 0, pwd_session->out_buf_len);
	    memcpy(pwd_session->out_buf, pwd_session->my_confirm, SHA256_DIGEST_LENGTH);

	    pwd_session->state = PWD_STATE_CONFIRM;
	    ret = send_pwd_request(pwd_session, eap_ds);
	    break;
	case PWD_STATE_CONFIRM:
	    if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_CONFIRM) {
		RDEBUG2("pwd exchange is incorrect: not commit!");
		return 0;
	    }
	    if (compute_peer_confirm(pwd_session, peer_confirm, inst->bnctx)) {
		RDEBUG2("pwd exchange cannot compute peer's confirm");
		return 0;
	    }
	    if (memcmp(peer_confirm, buf, SHA256_DIGEST_LENGTH)) {
		RDEBUG2("pwd exchange fails: peer confirm is incorrect!");
		return 0;
	    }
	    if (compute_keys(pwd_session, peer_confirm, msk, emsk)) {
		RDEBUG2("pwd exchange cannot generate (E)MSK!");
		return 0;
	    }
	    eap_ds->request->code = PW_EAP_SUCCESS;
	    /*
	     * return the MSK (in halves)
	     */
	    eap_add_reply(handler->request,
			  "MS-MPPE-Recv-Key", msk, MPPE_KEY_LEN);
	    eap_add_reply(handler->request,
			  "MS-MPPE-Send-Key", msk+MPPE_KEY_LEN, MPPE_KEY_LEN);
	    ret = 1;
	    break;
	default:
	    RDEBUG2("unknown PWD state");
	    return 0;
    }

    /*
     * we processed the buffered fragments, get rid of them
     */
    if (pwd_session->in_buf) {
	    talloc_free(pwd_session->in_buf);
	    pwd_session->in_buf = NULL;
    }

    return ret;
}
Exemple #6
0
/**
 * @brief Parse the MS-SOH response in data and update sohvp.
 *
 * Note that sohvp might still have been updated in event of a failure.
 *
 * @param request Current request
 * @param data MS-SOH blob
 * @param data_len length of MS-SOH blob
 *
 * @return 0 on success, -1 on failure
 *
 */
int soh_verify(REQUEST *request, const uint8_t *data, unsigned int data_len) {

	VALUE_PAIR *vp;
	eap_soh hdr;
	soh_response resp;
	soh_mode_subheader mode;
	soh_tlv tlv;
	int curr_shid=-1, curr_shid_c=-1, curr_hc=-1;

	hdr.tlv_type = soh_pull_be_16(data); data += 2;
	hdr.tlv_len = soh_pull_be_16(data); data += 2;
	hdr.tlv_vendor = soh_pull_be_32(data); data += 4;

	if (hdr.tlv_type != 7 || hdr.tlv_vendor != 0x137) {
		RDEBUG("SoH payload is %i %08x not a ms-vendor packet", hdr.tlv_type, hdr.tlv_vendor);
		return -1;
	}

	hdr.soh_type = soh_pull_be_16(data); data += 2;
	hdr.soh_len = soh_pull_be_16(data); data += 2;
	if (hdr.soh_type != 1) {
		RDEBUG("SoH tlv %04x is not a response", hdr.soh_type);
		return -1;
	}

	/* FIXME: check for sufficient data */
	resp.outer_type = soh_pull_be_16(data); data += 2;
	resp.outer_len = soh_pull_be_16(data); data += 2;
	resp.vendor = soh_pull_be_32(data); data += 4;
	resp.inner_type = soh_pull_be_16(data); data += 2;
	resp.inner_len = soh_pull_be_16(data); data += 2;


	if (resp.outer_type!=7 || resp.vendor != 0x137) {
		RDEBUG("SoH response outer type %i/vendor %08x not recognised", resp.outer_type, resp.vendor);
		return -1;
	}
	switch (resp.inner_type) {
		case 1:
			/* no mode sub-header */
			RDEBUG("SoH without mode subheader");
			break;
		case 2:
			mode.outer_type = soh_pull_be_16(data); data += 2;
			mode.outer_len = soh_pull_be_16(data); data += 2;
			mode.vendor = soh_pull_be_32(data); data += 4;
			memcpy(mode.corrid, data, 24); data += 24;
			mode.intent = data[0];
			mode.content_type = data[1];
			data += 2;

			if (mode.outer_type != 7 || mode.vendor != 0x137 || mode.content_type != 0) {
				RDEBUG3("SoH mode subheader outer type %i/vendor %08x/content type %i invalid", mode.outer_type, mode.vendor, mode.content_type);
				return -1;
			}
			RDEBUG3("SoH with mode subheader");
			break;
		default:
			RDEBUG("SoH invalid inner type %i", resp.inner_type);
			return -1;
	}

	/* subtract off the relevant amount of data */
	if (resp.inner_type==2) {
		data_len = resp.inner_len - 34;
	} else {
		data_len = resp.inner_len;
	}

	/* TLV
	 * MS-SOH 2.2.1
	 * See also 2.2.3
	 *
	 * 1 bit mandatory
	 * 1 bit reserved
	 * 14 bits tlv type
	 * 2 bytes tlv length
	 * N bytes payload
	 *
	 */
	while (data_len >= 4) {
		tlv.tlv_type = soh_pull_be_16(data); data += 2;
		tlv.tlv_len = soh_pull_be_16(data); data += 2;

		data_len -= 4;

		switch (tlv.tlv_type) {
			case 2:
				/* System-Health-Id TLV
				 * MS-SOH 2.2.3.1
				 *
				 * 3 bytes IANA/SMI vendor code
				 * 1 byte component (i.e. within vendor, which SoH component
				 */
				curr_shid = soh_pull_be_24(data);
				curr_shid_c = data[3];
				RDEBUG2("SoH System-Health-ID vendor %08x component=%i", curr_shid, curr_shid_c);
				break;

			case 7:
				/* Vendor-Specific packet
				 * MS-SOH 2.2.3.3
				 *
				 * 4 bytes vendor, supposedly ignored by NAP
				 * N bytes payload; for Microsoft component#0 this is the MS TV stuff
				 */
				if (curr_shid==0x137 && curr_shid_c==0) {
					RDEBUG2("SoH MS type-value payload");
					eapsoh_mstlv(request, data + 4, tlv.tlv_len - 4);
				} else {
					RDEBUG2("SoH unhandled vendor-specific TLV %08x/component=%i %i bytes payload", curr_shid, curr_shid_c, tlv.tlv_len);
				}
				break;

			case 8:
				/* Health-Class
				 * MS-SOH 2.2.3.5.6
				 *
				 * 1 byte integer
				 */
				RDEBUG2("SoH Health-Class %i", data[0]);
				curr_hc = data[0];
				break;

			case 9:
				/* Software-Version
				 * MS-SOH 2.2.3.5.7
				 *
				 * 1 byte integer
				 */
				RDEBUG2("SoH Software-Version %i", data[0]);
				break;

			case 11:
				/* Health-Class status
				 * MS-SOH 2.2.3.5.9
				 *
				 * variable data; for the MS System Health vendor, these are 4-byte
				 * integers which are a really, really dumb format:
				 *
				 *  28 bits ignore
				 *  1 bit - 1==product snoozed
				 *  1 bit - 1==microsoft product
				 *  1 bit - 1==product up-to-date
				 *  1 bit - 1==product enabled
				 */
				RDEBUG2("SoH Health-Class-Status - current shid=%08x component=%i", curr_shid, curr_shid_c);

				if (curr_shid==0x137 && curr_shid_c==128) {

					const char *s, *t;
					uint32_t hcstatus = soh_pull_be_32(data);

					RDEBUG2("SoH Health-Class-Status microsoft DWORD=%08x", hcstatus);

					vp = pairmake_packet("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
					if (!vp) return 0;

					switch (curr_hc) {
						case 4:
							/* security updates */
							s = "security-updates";
							switch (hcstatus) {
								case 0xff0005:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok all-installed", s);
									break;
								case 0xff0006:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn some-missing", s);
									break;
								case 0xff0008:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
									break;
								case 0xc0ff000c:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-srv", s);
									break;
								case 0xc0ff000d:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error no-wsus-clid", s);
									break;
								case 0xc0ff000e:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn wsus-disabled", s);
									break;
								case 0xc0ff000f:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error comm-failure", s);
									break;
								case 0xc0ff0010:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn needs-reboot", s);
									break;
								default:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
									break;
							}
							break;

						case 3:
							/* auto updates */
							s = "auto-updates";
							switch (hcstatus) {
								case 1:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn disabled", s);
									break;
								case 2:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=check-only", s);
									break;
								case 3:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=download", s);
									break;
								case 4:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s ok action=install", s);
									break;
								case 5:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn unconfigured", s);
									break;
								case 0xc0ff0003:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn service-down", s);
									break;
								case 0xc0ff0018:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s warn never-started", s);
									break;
								default:
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
									break;
							}
							break;

						default:
							/* other - firewall, antivirus, antispyware */
							s = healthclass2str(curr_hc);
							if (s) {
								/* bah. this is vile. stupid microsoft
								 */
								if (hcstatus & 0xff000000) {
									/* top octet non-zero means an error
									 * FIXME: is this always correct? MS-WSH 2.2.8 is unclear
									 */
									t = clientstatus2str(hcstatus);
									if (t) {
										snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %s", s, t);
									} else {
										snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%s error %08x", s, hcstatus);
									}
								} else {
									snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue),
											"%s ok snoozed=%i microsoft=%i up2date=%i enabled=%i",
											s,
											hcstatus & 0x8 ? 1 : 0,
											hcstatus & 0x4 ? 1 : 0,
											hcstatus & 0x2 ? 1 : 0,
											hcstatus & 0x1 ? 1 : 0
											);
								}
							} else {
								snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%i unknown %08x", curr_hc, hcstatus);
							}
							break;
					}
				} else {
					vp = pairmake_packet("SoH-MS-Health-Other", NULL, T_OP_EQ);
					if (!vp) return 0;

					/* FIXME: what to do with the payload? */
					snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
				}
				break;

			default:
				RDEBUG("SoH Unknown TLV %i len=%i", tlv.tlv_type, tlv.tlv_len);
				break;
		}

		data += tlv.tlv_len;
		data_len -= tlv.tlv_len;

	}

	return 0;
}
Exemple #7
0
/**
 * @brief Parses the MS-SOH type/value (note: NOT type/length/value) data and
 * 	update the sohvp list
 *
 * See section 2.2.4 of MS-SOH. Because there's no "length" field we CANNOT just skip
 * unknown types; we need to know their length ahead of time. Therefore, we abort
 * if we find an unknown type. Note that sohvp may still have been modified in the
 * failure case.
 *
 * @param request Current request
 * @param p binary blob
 * @param data_len length of blob
 * @return 1 on success, 0 on failure
 */
static int eapsoh_mstlv(REQUEST *request, const uint8_t *p, unsigned int data_len) {
	VALUE_PAIR *vp;
	uint8_t c;
	int t;

	while (data_len > 0) {
		c = *p++;
		data_len--;

		switch (c) {
			case 1:
				/* MS-Machine-Inventory-Packet
				 * MS-SOH section 2.2.4.1
				 */
				if (data_len < 18) {
					RDEBUG("insufficient data for MS-Machine-Inventory-Packet");
					return 0;
				}
				data_len -= 18;

				vp = pairmake_packet("SoH-MS-Machine-OS-vendor", "Microsoft", T_OP_EQ);
				if (!vp) return 0;

				vp = pairmake_packet("SoH-MS-Machine-OS-version", NULL, T_OP_EQ);
				if (!vp) return 0;

				vp->vp_integer = soh_pull_be_32(p); p+=4;

				vp = pairmake_packet("SoH-MS-Machine-OS-release", NULL, T_OP_EQ);
				if (!vp) return 0;

				vp->vp_integer = soh_pull_be_32(p); p+=4;

				vp = pairmake_packet("SoH-MS-Machine-OS-build", NULL, T_OP_EQ);
				if (!vp) return 0;

				vp->vp_integer = soh_pull_be_32(p); p+=4;

				vp = pairmake_packet("SoH-MS-Machine-SP-version", NULL, T_OP_EQ);
				if (!vp) return 0;

				vp->vp_integer = soh_pull_be_16(p); p+=2;

				vp = pairmake_packet("SoH-MS-Machine-SP-release", NULL, T_OP_EQ);
				if (!vp) return 0;

				vp->vp_integer = soh_pull_be_16(p); p+=2;

				vp = pairmake_packet("SoH-MS-Machine-Processor", NULL, T_OP_EQ);
				if (!vp) return 0;

				vp->vp_integer = soh_pull_be_16(p); p+=2;
				break;

			case 2:
				/* MS-Quarantine-State - FIXME: currently unhandled
				 * MS-SOH 2.2.4.1
				 *
				 * 1 byte reserved
				 * 1 byte flags
				 * 8 bytes NT Time field (100-nanosec since 1 Jan 1601)
				 * 2 byte urilen
				 * N bytes uri
				 */
				p += 10;
				t = soh_pull_be_16(p);	/* t == uri len */
				p += 2;
				p += t;
				data_len -= 12 + t;
				break;

			case 3:
				/* MS-Packet-Info
				 * MS-SOH 2.2.4.3
				 */
				RDEBUG3("SoH MS-Packet-Info %s vers=%i", *p & 0x10 ? "request" : "response", *p & 0xf);
				p++;
				data_len--;
				break;

			case 4:
				/* MS-SystemGenerated-Ids - FIXME: currently unhandled
				 * MS-SOH 2.2.4.4
				 *
				 * 2 byte length
				 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
				 */
				t = soh_pull_be_16(p);
				p += 2;
				p += t;
				data_len -= 2 + t;
				break;

			case 5:
				/* MS-MachineName
				 * MS-SOH 2.2.4.5
				 *
				 * 1 byte namelen
				 * N bytes name
				 */
				t = soh_pull_be_16(p);
				p += 2;

				vp = pairmake_packet("SoH-MS-Machine-Name", NULL, T_OP_EQ);
				if (!vp) return 0;

				memcpy(vp->vp_strvalue, p, t);
				vp->vp_strvalue[t] = 0;

				p += t;
				data_len -= 2 + t;
				break;

			case 6:
				/* MS-CorrelationId
				 * MS-SOH 2.2.4.6
				 *
				 * 24 bytes opaque binary which we might, in future, have
				 * to echo back to the client in a final SoHR
				 */
				vp = pairmake_packet("SoH-MS-Correlation-Id", NULL, T_OP_EQ);
				if (!vp) return 0;

				memcpy(vp->vp_octets, p, 24);
				vp->length = 24;
				p += 24;
				data_len -= 24;
				break;

			case 7:
				/* MS-Installed-Shvs - FIXME: currently unhandled
				 * MS-SOH 2.2.4.7
				 *
				 * 2 bytes length
				 * N bytes (3 bytes IANA enterprise# + 1 byte component id#)
				 */
				t = soh_pull_be_16(p);
				p += 2;
				p += t;
				data_len -= 2 + t;
				break;

			case 8:
				/* MS-Machine-Inventory-Ex
				 * MS-SOH 2.2.4.8
				 *
				 * 4 bytes reserved
				 * 1 byte product type (client=1 domain_controller=2 server=3)
				 */
				p += 4;
				vp = pairmake_packet("SoH-MS-Machine-Role", NULL, T_OP_EQ);
				if (!vp) return 0;

				vp->vp_integer = *p;
				p++;
				data_len -= 5;
				break;

			default:
				RDEBUG("SoH Unknown MS TV %i stopping", c);
				return 0;
		}
	}
	return 1;
}
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle,
					  sql_fall_through_t *do_fall_through)
{
	rlm_rcode_t		rcode = RLM_MODULE_NOOP;
	VALUE_PAIR		*check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL;
	rlm_sql_grouplist_t	*head = NULL, *entry = NULL;

	char			*expanded = NULL;
	int			rows;

	rad_assert(request->packet != NULL);

	/*
	 *	Get the list of groups this user is a member of
	 */
	rows = sql_get_grouplist(inst, handle, request, &head);
	if (rows < 0) {
		REDEBUG("Error retrieving group list");

		return RLM_MODULE_FAIL;
	}
	if (rows == 0) {
		RDEBUG2("User not found in any groups");
		rcode = RLM_MODULE_NOTFOUND;
		*do_fall_through = FALL_THROUGH_DEFAULT;

		goto finish;
	}
	rad_assert(head);

	RDEBUG2("User found in the group table");

	/*
	 *	Add the Sql-Group attribute to the request list so we know
	 *	which group we're retrieving attributes for
	 */
	sql_group = pairmake_packet("Sql-Group", NULL, T_OP_EQ);
	if (!sql_group) {
		REDEBUG("Error creating Sql-Group attribute");
		rcode = RLM_MODULE_FAIL;
		goto finish;
	}

	entry = head;
	do {
	next:
		rad_assert(entry != NULL);
		pairstrcpy(sql_group, entry->name);

		if (inst->config->authorize_group_check_query) {
			vp_cursor_t cursor;
			VALUE_PAIR *vp;

			/*
			 *	Expand the group query
			 */
			if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query,
					 sql_escape_func, inst) < 0) {
				REDEBUG("Error generating query");
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}

			rows = sql_getvpdata(request, inst, request, handle, &check_tmp, expanded);
			TALLOC_FREE(expanded);
			if (rows < 0) {
				REDEBUG("Error retrieving check pairs for group %s", entry->name);
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}

			/*
			 *	If we got check rows we need to process them before we decide to
			 *	process the reply rows
			 */
			if ((rows > 0) &&
			    (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) {
				pairfree(&check_tmp);
				entry = entry->next;

				goto next;	/* != continue */
			}

			RDEBUG2("Group \"%s\": Conditional check items matched", entry->name);
			rcode = RLM_MODULE_OK;

			RDEBUG2("Group \"%s\": Merging assignment check items", entry->name);
			RINDENT();
			for (vp = fr_cursor_init(&cursor, &check_tmp);
			     vp;
			     vp = fr_cursor_next(&cursor)) {
			 	if (!fr_assignment_op[vp->op]) continue;

			 	rdebug_pair(L_DBG_LVL_2, request, vp, NULL);
			}
			REXDENT();
			radius_pairmove(request, &request->config_items, check_tmp, true);
			check_tmp = NULL;
		}

		if (inst->config->authorize_group_reply_query) {
			/*
			 *	Now get the reply pairs since the paircompare matched
			 */
			if (radius_axlat(&expanded, request, inst->config->authorize_group_reply_query,
					 sql_escape_func, inst) < 0) {
				REDEBUG("Error generating query");
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}

			rows = sql_getvpdata(request->reply, inst, request, handle, &reply_tmp, expanded);
			TALLOC_FREE(expanded);
			if (rows < 0) {
				REDEBUG("Error retrieving reply pairs for group %s", entry->name);
				rcode = RLM_MODULE_FAIL;
				goto finish;
			}
			*do_fall_through = fall_through(reply_tmp);

			RDEBUG2("Group \"%s\": Merging reply items", entry->name);
			rcode = RLM_MODULE_OK;

			rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp, NULL);

			radius_pairmove(request, &request->reply->vps, reply_tmp, true);
			reply_tmp = NULL;
		/*
		 *	If there's no reply query configured, then we assume
		 *	FALL_THROUGH_NO, which is the same as the users file if you
		 *	had no reply attributes.
		 */
		} else {
			*do_fall_through = FALL_THROUGH_DEFAULT;
		}

		entry = entry->next;
	} while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES));

finish:
	talloc_free(head);
	pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY);

	return rcode;
}
Exemple #9
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;
}
/*
 *	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;
}