예제 #1
0
/** Check group membership attributes to see if a user is a member.
 *
 * @param[in] inst rlm_ldap configuration.
 * @param[in] request Current request.
 * @param[in] check vp containing the group value (name or dn).
 *
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_check_cached(ldap_instance_t const *inst, REQUEST *request, VALUE_PAIR *check)
{
	VALUE_PAIR	*vp;
	int		ret;
	vp_cursor_t	cursor;

	fr_cursor_init(&cursor, &request->config_items);

	/*
	 *	We return RLM_MODULE_INVALID here as an indication
	 *	the caller should try a dynamic group lookup instead.
	 */
	vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY);
	if (!vp) return RLM_MODULE_INVALID;
	fr_cursor_first(&cursor);

	while ((vp = fr_cursor_next_by_num(&cursor, inst->cache_da->attr, inst->cache_da->vendor, TAG_ANY))) {
		ret = paircmp_op(T_OP_CMP_EQ, vp, check);
		if (ret == 1) {
			RDEBUG2("User found. Matched cached membership");
			return RLM_MODULE_OK;
		}

		if (ret < -1) {
			return RLM_MODULE_FAIL;
		}
	}

	RDEBUG2("Cached membership not found");
	return RLM_MODULE_NOTFOUND;
}
예제 #2
0
/** Wind cursor to the last pair in the list
 *
 * @addtogroup module_safe
 *
 * @param cursor to operate on.
 * @return #VALUE_PAIR at the end of the list.
 */
VALUE_PAIR *fr_cursor_last(vp_cursor_t *cursor)
{
	if (!cursor->first || !*cursor->first) return NULL;

	/* Need to start at the start */
	if (!cursor->current) fr_cursor_first(cursor);

	/* Wind to the end */
	while (cursor->next) fr_cursor_next(cursor);

	return cursor->current;
}
예제 #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);
}
예제 #4
0
/** Uses paircmp to verify all VALUE_PAIRs in list match the filter defined by check
 *
 * @note will sort both filter and list in place.
 *
 * @param failed pointer to an array to write the pointers of the filter/list attributes that didn't match.
 *	  May be NULL.
 * @param filter attributes to check list against.
 * @param list attributes, probably a request or reply
 */
bool pairvalidate_relaxed(VALUE_PAIR const *failed[2], VALUE_PAIR *filter, VALUE_PAIR *list)
{
	vp_cursor_t filter_cursor;
	vp_cursor_t list_cursor;

	VALUE_PAIR *check, *last_check = NULL, *match = NULL;

	if (!filter && !list) {
		return true;
	}

	/*
	 *	This allows us to verify the sets of validate and reply are equal
	 *	i.e. we have a validate rule which matches every reply attribute.
	 *
	 *	@todo this should be removed one we have sets and lists
	 */
	pairsort(&filter, attrtagcmp);
	pairsort(&list, attrtagcmp);

	fr_cursor_init(&list_cursor, &list);
	for (check = fr_cursor_init(&filter_cursor, &filter);
	     check;
	     check = fr_cursor_next(&filter_cursor)) {
		/*
		 *	Were processing check attributes of a new type.
		 */
		if (!ATTRIBUTE_EQ(last_check, check)) {
			/*
			 *	Record the start of the matching attributes in the pair list
			 *	For every other operator we require the match to be present
			 */
			match = fr_cursor_next_by_da(&list_cursor, check->da, check->tag);
			if (!match) {
				if (check->op == T_OP_CMP_FALSE) continue;
				goto mismatch;
			}

			fr_cursor_init(&list_cursor, &match);
			last_check = check;
		}

		/*
		 *	Now iterate over all attributes of the same type.
		 */
		for (match = fr_cursor_first(&list_cursor);
		     ATTRIBUTE_EQ(match, check);
		     match = fr_cursor_next(&list_cursor)) {
			/*
			 *	This attribute passed the filter
			 */
			if (!paircmp(check, match)) goto mismatch;
		}
	}

	return true;

mismatch:
	if (failed) {
		failed[0] = check;
		failed[1] = match;
	}
	return false;
}
예제 #5
0
/*
 *	Common attr_filter checks
 */
static rlm_rcode_t CC_HINT(nonnull(1,2)) attr_filter_common(void *instance, REQUEST *request, RADIUS_PACKET *packet)
{
	rlm_attr_filter_t *inst = instance;
	VALUE_PAIR	*vp;
	vp_cursor_t	input, check, out;
	VALUE_PAIR	*input_item, *check_item, *output;
	PAIR_LIST	*pl;
	int		found = 0;
	int		pass, fail = 0;
	char const	*keyname = NULL;
	char		buffer[256];

	if (!packet) return RLM_MODULE_NOOP;

	if (!inst->key) {
		VALUE_PAIR	*namepair;

		namepair = pairfind(request->packet->vps, PW_REALM, 0, TAG_ANY);
		if (!namepair) {
			return (RLM_MODULE_NOOP);
		}
		keyname = namepair->vp_strvalue;
	} else {
		int len;

		len = radius_xlat(buffer, sizeof(buffer), request, inst->key, NULL, NULL);
		if (len < 0) {
			return RLM_MODULE_FAIL;
		}
		if (len == 0) {
			return RLM_MODULE_NOOP;
		}
		keyname = buffer;
	}

	/*
	 *	Head of the output list
	 */
	output = NULL;
	fr_cursor_init(&out, &output);

	/*
	 *      Find the attr_filter profile entry for the entry.
	 */
	for (pl = inst->attrs; pl; pl = pl->next) {
		int fall_through = 0;
		int relax_filter = inst->relaxed;

		/*
		 *  If the current entry is NOT a default,
		 *  AND the realm does NOT match the current entry,
		 *  then skip to the next entry.
		 */
		if ((strcmp(pl->name, "DEFAULT") != 0) &&
		    (strcmp(keyname, pl->name) != 0))  {
		    continue;
		}

		RDEBUG2("Matched entry %s at line %d", pl->name, pl->lineno);
		found = 1;

		for (check_item = fr_cursor_init(&check, &pl->check);
		     check_item;
		     check_item = fr_cursor_next(&check)) {
			if (!check_item->da->vendor &&
			    (check_item->da->attr == PW_FALL_THROUGH) &&
				(check_item->vp_integer == 1)) {
				fall_through = 1;
				continue;
			}
			else if (!check_item->da->vendor && check_item->da->attr == PW_RELAX_FILTER) {
				relax_filter = check_item->vp_integer;
				continue;
			}

			/*
			 *    If it is a SET operator, add the attribute to
			 *    the output list without checking it.
			 */
			if (check_item->op == T_OP_SET ) {
				vp = paircopyvp(packet, check_item);
				if (!vp) {
					goto error;
				}
				radius_xlat_do(request, vp);
				fr_cursor_insert(&out, vp);
			}
		}

		/*
		 *	Iterate through the input items, comparing
		 *	each item to every rule, then moving it to the
		 *	output list only if it matches all rules
		 *	for that attribute.  IE, Idle-Timeout is moved
		 *	only if it matches all rules that describe an
		 *	Idle-Timeout.
		 */
		for (input_item = fr_cursor_init(&input, &packet->vps);
		     input_item;
		     input_item = fr_cursor_next(&input)) {
			pass = fail = 0; /* reset the pass,fail vars for each reply item */

			/*
			 *  Reset the check_item pointer to beginning of the list
			 */
			for (check_item = fr_cursor_first(&check);
			     check_item;
			     check_item = fr_cursor_next(&check)) {
				/*
				 *  Vendor-Specific is special, and matches any VSA if the
				 *  comparison is always true.
				 */
				if ((check_item->da->attr == PW_VENDOR_SPECIFIC) && (input_item->da->vendor != 0) &&
				    (check_item->op == T_OP_CMP_TRUE)) {
					pass++;
					continue;
				}

				if (input_item->da == check_item->da) {
					check_pair(request, check_item, input_item, &pass, &fail);
				}
			}

			RDEBUG3("Attribute \"%s\" allowed by %i rules, disallowed by %i rules",
				input_item->da->name, pass, fail);
			/*
			 *  Only move attribute if it passed all rules, or if the config says we
			 *  should copy unmatched attributes ('relaxed' mode).
			 */
			if (fail == 0 && (pass > 0 || relax_filter)) {
				if (!pass) {
					RDEBUG3("Attribute \"%s\" allowed by relaxed mode", input_item->da->name);
				}
				vp = paircopyvp(packet, input_item);
				if (!vp) {
					goto error;
				}
				fr_cursor_insert(&out, vp);
			}
		}

		/* If we shouldn't fall through, break */
		if (!fall_through) {
			break;
		}
	}

	/*
	 *	No entry matched.  We didn't do anything.
	 */
	if (!found) {
		rad_assert(!output);
		return RLM_MODULE_NOOP;
	}

	/*
	 *	Replace the existing request list with our filtered one
	 */
	pairfree(&packet->vps);
	packet->vps = output;

	if (request->packet->code == PW_CODE_AUTHENTICATION_REQUEST) {
		request->username = pairfind(request->packet->vps, PW_STRIPPED_USER_NAME, 0, TAG_ANY);
		if (!request->username) {
			request->username = pairfind(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
		}
		request->password = pairfind(request->packet->vps, PW_USER_PASSWORD, 0, TAG_ANY);
	}

	return RLM_MODULE_UPDATED;

	error:
	pairfree(&output);
	return RLM_MODULE_FAIL;
}
예제 #6
0
/*
 * Handles multiple EAP-Message attrs
 * ie concatenates all to get the complete EAP packet.
 *
 * NOTE: Sometimes Framed-MTU might contain the length of EAP-Message,
 *      refer fragmentation in rfc2869.
 */
eap_packet_raw_t *eap_vp2packet(TALLOC_CTX *ctx, VALUE_PAIR *vps)
{
	VALUE_PAIR *first, *i;
	eap_packet_raw_t *eap_packet;
	unsigned char *ptr;
	uint16_t len;
	int total_len;
	vp_cursor_t cursor;

	/*
	 *	Get only EAP-Message attribute list
	 */
	first = fr_pair_find_by_num(vps, 0, PW_EAP_MESSAGE, TAG_ANY);
	if (!first) {
		fr_strerror_printf("EAP-Message not found");
		return NULL;
	}

	/*
	 *	Sanity check the length before doing anything.
	 */
	if (first->vp_length < 4) {
		fr_strerror_printf("EAP packet is too short");
		return NULL;
	}

	/*
	 *	Get the Actual length from the EAP packet
	 *	First EAP-Message contains the EAP packet header
	 */
	memcpy(&len, first->vp_strvalue + 2, sizeof(len));
	len = ntohs(len);

	/*
	 *	Take out even more weird things.
	 */
	if (len < 4) {
		fr_strerror_printf("EAP packet has invalid length (less than 4 bytes)");
		return NULL;
	}

	/*
	 *	Sanity check the length, BEFORE allocating  memory.
	 */
	total_len = 0;
	fr_cursor_init(&cursor, &first);
	while ((i = fr_cursor_next_by_num(&cursor, 0, PW_EAP_MESSAGE, TAG_ANY))) {
		total_len += i->vp_length;

		if (total_len > len) {
			fr_strerror_printf("Malformed EAP packet.  Length in packet header %i, "
					   "does not match actual length %i", len, total_len);
			return NULL;
		}
	}

	/*
	 *	If the length is SMALLER, die, too.
	 */
	if (total_len < len) {
		fr_strerror_printf("Malformed EAP packet.  Length in packet header does not "
				   "match actual length");
		return NULL;
	}

	/*
	 *	Now that we know the lengths are OK, allocate memory.
	 */
	eap_packet = (eap_packet_raw_t *) talloc_zero_array(ctx, uint8_t, len);
	if (!eap_packet) {
		return NULL;
	}

	/*
	 *	Copy the data from EAP-Message's over to our EAP packet.
	 */
	ptr = (unsigned char *)eap_packet;

	/* RADIUS ensures order of attrs, so just concatenate all */
	fr_cursor_first(&cursor);
	while ((i = fr_cursor_next_by_num(&cursor, 0, PW_EAP_MESSAGE, TAG_ANY))) {
		memcpy(ptr, i->vp_strvalue, i->vp_length);
		ptr += i->vp_length;
	}

	return eap_packet;
}
예제 #7
0
파일: groups.c 프로젝트: janetuk/freeradius
/** Convert group membership information into attributes
 *
 * @param[in] inst rlm_ldap configuration.
 * @param[in] request Current request.
 * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect.
 * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search.
 * @param[in] attr membership attribute to look for in the entry.
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn,
				       LDAPMessage *entry, char const *attr)
{
	rlm_rcode_t rcode = RLM_MODULE_OK;

	struct berval **values;

	char *group_name[LDAP_MAX_CACHEABLE + 1];
	char **name_p = group_name;

	char *group_dn[LDAP_MAX_CACHEABLE + 1];
	char **dn_p;

	char *name;

	VALUE_PAIR *vp, **list, *groups = NULL;
	TALLOC_CTX *list_ctx, *value_ctx;
	vp_cursor_t list_cursor, groups_cursor;

	int is_dn, i, count;

	rad_assert(entry);
	rad_assert(attr);

	/*
	 *	Parse the membership information we got in the initial user query.
	 */
	values = ldap_get_values_len((*pconn)->handle, entry, attr);
	if (!values) {
		RDEBUG2("No cacheable group memberships found in user object");

		return RLM_MODULE_OK;
	}
	count = ldap_count_values_len(values);

	list = radius_list(request, PAIR_LIST_CONTROL);
	list_ctx = radius_list_ctx(request, PAIR_LIST_CONTROL);

	/*
	 *	Simplifies freeing temporary values
	 */
	value_ctx = talloc_new(request);

	/*
	 *	Temporary list to hold new group VPs, will be merged
	 *	once all group info has been gathered/resolved
	 *	successfully.
	 */
	fr_cursor_init(&groups_cursor, &groups);

	for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) {
		is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len);

		if (inst->cacheable_group_dn) {
			/*
			 *	The easy case, we're caching DNs and we got a DN.
			 */
			if (is_dn) {
				MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
				fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len);
				fr_cursor_insert(&groups_cursor, vp);
			/*
			 *	We were told to cache DNs but we got a name, we now need to resolve
			 *	this to a DN. Store all the group names in an array so we can do one query.
			 */
			} else {
				*name_p++ = rlm_ldap_berval_to_string(value_ctx, values[i]);
			}
		}

		if (inst->cacheable_group_name) {
			/*
			 *	The easy case, we're caching names and we got a name.
			 */
			if (!is_dn) {
				MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
				fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len);
				fr_cursor_insert(&groups_cursor, vp);
			/*
			 *	We were told to cache names but we got a DN, we now need to resolve
			 *	this to a name.
			 *	Only Active Directory supports filtering on DN, so we have to search
			 *	for each individual group.
			 */
			} else {
				char *dn;

				dn = rlm_ldap_berval_to_string(value_ctx, values[i]);
				rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name);
				talloc_free(dn);
				if (rcode != RLM_MODULE_OK) {
					ldap_value_free_len(values);
					talloc_free(value_ctx);
					fr_pair_list_free(&groups);

					return rcode;
				}

				MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
				fr_pair_value_bstrncpy(vp, name, talloc_array_length(name) - 1);
				fr_cursor_insert(&groups_cursor, vp);
				talloc_free(name);
			}
		}
	}
	*name_p = NULL;

	rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn));

	ldap_value_free_len(values);
	talloc_free(value_ctx);

	if (rcode != RLM_MODULE_OK) return rcode;

	fr_cursor_init(&list_cursor, list);

	RDEBUG("Adding cacheable user object memberships");
	RINDENT();
	if (RDEBUG_ENABLED) {
		for (vp = fr_cursor_first(&groups_cursor);
		     vp;
		     vp = fr_cursor_next(&groups_cursor)) {
			RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue);
		}
	}

	fr_cursor_merge(&list_cursor, groups);

	for (dn_p = group_dn; *dn_p; dn_p++) {
		MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da));
		fr_pair_value_strcpy(vp, *dn_p);
		fr_cursor_insert(&list_cursor, vp);

		RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue);
		ldap_memfree(*dn_p);
	}
	REXDENT();

	return rcode;
}
예제 #8
0
/*
 * given a radius request with many attributes in the EAP-SIM range, build
 * them all into a single EAP-SIM body.
 *
 */
int map_eapsim_basictypes(RADIUS_PACKET *r, eap_packet_t *ep)
{
	VALUE_PAIR	*vp;
	int		encoded_size;
	uint8_t		*encodedmsg, *attr;
	unsigned int	id, eapcode;
	uint8_t		*macspace;
	uint8_t const	*append;
	int		appendlen;
	unsigned char	subtype;
	vp_cursor_t	cursor;

	macspace = NULL;
	append = NULL;
	appendlen = 0;

	/*
	 * encodedmsg is now an EAP-SIM message.
	 * it might be too big for putting into an EAP-Type-SIM
	 *
	 */
	subtype = (vp = fr_pair_find_by_num(r->vps, PW_EAP_SIM_SUBTYPE, 0, TAG_ANY)) ?
		vp->vp_integer : EAPSIM_START;

	id = (vp = fr_pair_find_by_num(r->vps, PW_EAP_ID, 0, TAG_ANY)) ?
		vp->vp_integer : ((int)getpid() & 0xff);

	eapcode = (vp = fr_pair_find_by_num(r->vps, PW_EAP_CODE, 0, TAG_ANY)) ?
		vp->vp_integer : PW_EAP_REQUEST;

	/*
	 * take a walk through the attribute list to see how much space
	 * that we need to encode all of this.
	 */
	encoded_size = 0;
	for (vp = fr_cursor_init(&cursor, &r->vps);
	     vp;
	     vp = fr_cursor_next(&cursor)) {
		int roundedlen;
		int vplen;

		if ((vp->da->attr < PW_EAP_SIM_BASE) || (vp->da->attr >= (PW_EAP_SIM_BASE + 256))) {
			continue;
		}

		vplen = vp->vp_length;

		/*
		 * the AT_MAC attribute is a bit different, when we get to this
		 * attribute, we pull the contents out, save it for later
		 * processing, set the size to 16 bytes (plus 2 bytes padding).
		 *
		 * At this point, we only care about the size.
		 */
		if(vp->da->attr == PW_EAP_SIM_MAC) {
			vplen = 18;
		}

		/* round up to next multiple of 4, after taking in
		 * account the type and length bytes
		 */
		roundedlen = (vplen + 2 + 3) & ~3;
		encoded_size += roundedlen;
	}

	if (ep->code != PW_EAP_SUCCESS) {
		ep->code = eapcode;
	}

	ep->id = (id & 0xff);
	ep->type.num = PW_EAP_SIM;

	/*
	 * if no attributes were found, do very little.
	 *
	 */
	if (encoded_size == 0) {
		encodedmsg = talloc_array(ep, uint8_t, 3);
		/* FIX: could be NULL */

		encodedmsg[0] = subtype;
		encodedmsg[1] = 0;
		encodedmsg[2] = 0;

		ep->type.length = 3;
		ep->type.data = encodedmsg;

		return 0;
	}


	/*
	 * figured out the length, so allocate some space for the results.
	 *
	 * Note that we do not bother going through an "EAP" stage, which
	 * is a bit strange compared to the unmap, which expects to see
	 * an EAP-SIM virtual attributes.
	 *
	 * EAP is 1-code, 1-identifier, 2-length, 1-type = 5 overhead.
	 *
	 * SIM code adds a subtype, and 2 bytes of reserved = 3.
	 *
	 */
	encoded_size += 3;
	encodedmsg = talloc_array(ep, uint8_t, encoded_size);
	if (!encodedmsg) {
		return 0;
	}
	memset(encodedmsg, 0, encoded_size);

	/*
	 * now walk the attributes again, sticking them in.
	 *
	 * we go three bytes into the encoded message, because there are two
	 * bytes of reserved, and we will fill the "subtype" in later.
	 *
	 */
	attr = encodedmsg+3;

	for (vp = fr_cursor_first(&cursor); vp; vp = fr_cursor_next(&cursor)) {
		int roundedlen;

		if(vp->da->attr < PW_EAP_SIM_BASE ||
		   vp->da->attr >= PW_EAP_SIM_BASE + 256) {
			continue;
		}

		/*
		 * the AT_MAC attribute is a bit different, when we get to this
		 * attribute, we pull the contents out, save it for later
		 * processing, set the size to 16 bytes (plus 2 bytes padding).
		 *
		 * At this point, we put in zeros, and remember where the
		 * sixteen bytes go.
		 */
		if(vp->da->attr == PW_EAP_SIM_MAC) {
			roundedlen = 20;
			memset(&attr[2], 0, 18);
			macspace = &attr[4];
			append = vp->vp_octets;
			appendlen = vp->vp_length;
		} else {
			roundedlen = (vp->vp_length + 2 + 3) & ~3;
			memset(attr, 0, roundedlen);
			memcpy(&attr[2], vp->vp_strvalue, vp->vp_length);
		}
		attr[0] = vp->da->attr - PW_EAP_SIM_BASE;
		attr[1] = roundedlen >> 2;

		attr += roundedlen;
	}

	encodedmsg[0] = subtype;

	ep->type.length = encoded_size;
	ep->type.data = encodedmsg;

	/*
	 * if macspace was set and we have a key,
	 * then we should calculate the HMAC-SHA1 of the resulting EAP-SIM
	 * packet, appended with the value of append.
	 */
	vp = fr_pair_find_by_num(r->vps, PW_EAP_SIM_KEY, 0, TAG_ANY);
	if(macspace != NULL && vp != NULL) {
		unsigned char		*buffer;
		eap_packet_raw_t	*hdr;
		uint16_t		hmaclen, total_length = 0;
		unsigned char		sha1digest[20];

		total_length = EAP_HEADER_LEN + 1 + encoded_size;
		hmaclen = total_length + appendlen;
		buffer = talloc_array(r, uint8_t, hmaclen);
		hdr = (eap_packet_raw_t *) buffer;
		if (!hdr) {
			talloc_free(encodedmsg);
			return 0;
		}

		hdr->code = eapcode & 0xFF;
		hdr->id = (id & 0xFF);
		total_length = htons(total_length);
		memcpy(hdr->length, &total_length, sizeof(total_length));

		hdr->data[0] = PW_EAP_SIM;

		/* copy the data */
		memcpy(&hdr->data[1], encodedmsg, encoded_size);

		/* copy the nonce */
		memcpy(&hdr->data[encoded_size+1], append, appendlen);

		/* HMAC it! */
		fr_hmac_sha1(sha1digest, buffer, hmaclen, vp->vp_octets, vp->vp_length);

		/* done with the buffer, free it */
		talloc_free(buffer);

		/* now copy the digest to where it belongs in the AT_MAC */
		/* note that it is truncated to 128-bits */
		memcpy(macspace, sha1digest, 16);
	}

	/* if we had an AT_MAC and no key, then fail */
	if ((macspace != NULL) && !vp) {
		if (encodedmsg != NULL) {
			talloc_free(encodedmsg);
		}

		return 0;
	}

	return 1;
}