/*
 *	Find the named user in this modules database.  Create the set
 *	of attribute-value pairs to check and reply with for this user
 *	from the database. The authentication code only needs to check
 *	the password, the rest is done here.
 */
static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(UNUSED void *instance,
				     REQUEST *request)
{
	VALUE_PAIR *passwd_item, *chap;
	uint8_t pass_str[MAX_STRING_LEN];

	if (!request->username) {
		RWDEBUG("Attribute 'User-Name' is required for authentication.\n");
		return RLM_MODULE_INVALID;
	}

	chap = pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY);
	if (!chap) {
		REDEBUG("You set 'Auth-Type = CHAP' for a request that does not contain a CHAP-Password attribute!");
		return RLM_MODULE_INVALID;
	}

	if (chap->vp_length == 0) {
		REDEBUG("CHAP-Password is empty");
		return RLM_MODULE_INVALID;
	}

	if (chap->vp_length != CHAP_VALUE_LENGTH + 1) {
		REDEBUG("CHAP-Password has invalid length");
		return RLM_MODULE_INVALID;
	}

	/*
	 *	Don't print out the CHAP password here.  It's binary crap.
	 */
	RDEBUG("Login attempt by \"%s\" with CHAP password",
		request->username->vp_strvalue);

	if ((passwd_item = pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL){
		if (pairfind(request->config_items, PW_USER_PASSWORD, 0, TAG_ANY) != NULL){
			REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
			REDEBUG("!!! Please update your configuration so that the \"known !!!");
			REDEBUG("!!! good\" cleartext password is in Cleartext-Password,  !!!");
			REDEBUG("!!! and NOT in User-Password.                            !!!");
			REDEBUG("!!!						          !!!");
			REDEBUG("!!! Authentication will fail because of this.	          !!!");
			REDEBUG("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
		}

		REDEBUG("Cleartext password is required for authentication");
		return RLM_MODULE_INVALID;
	}

	rad_chap_encode(request->packet, pass_str,
			chap->vp_octets[0], passwd_item);

	if (RDEBUG_ENABLED3) {
		uint8_t const *p;
		size_t length;
		VALUE_PAIR *vp;
		char buffer[CHAP_VALUE_LENGTH * 2 + 1];

		RDEBUG3("Comparing with \"known good\" Cleartext-Password \"%s\"", passwd_item->vp_strvalue);

		vp = pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY);
		if (vp) {
			p = vp->vp_octets;
			length = vp->vp_length;
		} else {
			p = request->packet->vector;
			length = sizeof(request->packet->vector);
		}

		fr_bin2hex(buffer, p, length);
		RINDENT();
		RDEBUG3("CHAP challenge : %s", buffer);

		fr_bin2hex(buffer, chap->vp_octets + 1, CHAP_VALUE_LENGTH);
		RDEBUG3("Client sent    : %s", buffer);

		fr_bin2hex(buffer, pass_str + 1, CHAP_VALUE_LENGTH);
		RDEBUG3("We calculated  : %s", buffer);
		REXDENT();
	} else {
		RDEBUG2("Comparing with \"known good\" Cleartext-Password");
	}

	if (rad_digest_cmp(pass_str + 1, chap->vp_octets + 1,
			   CHAP_VALUE_LENGTH) != 0) {
		REDEBUG("Password comparison failed: password is incorrect");
		return RLM_MODULE_REJECT;
	}

	RDEBUG("CHAP user \"%s\" authenticated successfully",
	      request->username->vp_strvalue);

	return RLM_MODULE_OK;
}
Example #2
0
/*
 *  Dispatch an exec method
 */
static int exec_dispatch(void *instance, REQUEST *request)
{
    rlm_exec_t *inst = (rlm_exec_t *) instance;
    int result;
    VALUE_PAIR	**input_pairs, **output_pairs;
    VALUE_PAIR	*answer = NULL;
    char		msg[1024];
    size_t		len;

    /*
     *	We need a program to execute.
     */
    if (!inst->program) {
        radlog(L_ERR, "rlm_exec (%s): We require a program to execute",
               inst->xlat_name);
        return RLM_MODULE_FAIL;
    }

    /*
     *	See if we're supposed to execute it now.
     */
    if (!((inst->packet_code == 0) ||
            (request->packet->code == inst->packet_code) ||
            (request->reply->code == inst->packet_code)
#ifdef WITH_PROXY
            || (request->proxy &&
                (request->proxy->code == inst->packet_code)) ||
            (request->proxy_reply &&
             (request->proxy_reply->code == inst->packet_code))
#endif
         )) {
        RDEBUG2("Packet type is not %s.  Not executing.",
                inst->packet_type);
        return RLM_MODULE_NOOP;
    }

    /*
     *	Decide what input/output the program takes.
     */
    input_pairs = decode_string(request, inst->input);
    output_pairs = decode_string(request, inst->output);

    if (!input_pairs) {
        RDEBUG2("WARNING: Possible parse error in %s",
                inst->input);
        return RLM_MODULE_NOOP;
    }

    /*
     *	It points to the attribute list, but the attribute
     *	list is empty.
     */
    if (!*input_pairs) {
        RDEBUG2("WARNING! Input pairs are empty.  No attributes will be passed to the script");
    }

    /*
     *	This function does it's own xlat of the input program
     *	to execute.
     *
     *	FIXME: if inst->program starts with %{, then
     *	do an xlat ourselves.  This will allow us to do
     *	program = %{Exec-Program}, which this module
     *	xlat's into it's string value, and then the
     *	exec program function xlat's it's string value
     *	into something else.
     */
    result = radius_exec_program(inst->program, request,
                                 inst->wait, msg, sizeof(msg),
                                 *input_pairs, &answer, inst->shell_escape);
    if (result < 0) {
        radlog(L_ERR, "rlm_exec (%s): External script failed",
               inst->xlat_name);
        return RLM_MODULE_FAIL;
    }

    /*
     *	Move the answer over to the output pairs.
     *
     *	If we're not waiting, then there are no output pairs.
     */
    if (output_pairs) pairmove(output_pairs, &answer);

    pairfree(&answer);

    if (result == 0) {
        return RLM_MODULE_OK;
    }
    if (result > RLM_MODULE_NUMCODES) {
        return RLM_MODULE_FAIL;
    }

    /*
     *	Write any exec output to module failure message
     */
    if (*msg) {
        /* Trim off returns and newlines */
        len = strlen(msg);
        if (msg[len - 1] == '\n' || msg[len - 1] == '\r') {
            msg[len - 1] = '\0';
        }

        module_failure_msg(request, "rlm_exec (%s): %s",
                           inst->xlat_name, msg);
    }

    return result-1;
}
Example #3
0
int soh_verify(REQUEST *request, VALUE_PAIR *sohvp, 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, sohvp, 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("SoH-MS-Windows-Health-Status", NULL, T_OP_EQ);
					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("SoH-Other-Health-Status", NULL, T_OP_EQ);
					/* FIXME: what to do with the payload? */
					snprintf(vp->vp_strvalue, sizeof(vp->vp_strvalue), "%08x/%i ?", curr_shid, curr_shid_c);
				}
				pairadd(&sohvp, vp);
				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;
}
Example #4
0
/** 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.
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_cacheable_userobj(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
				       LDAPMessage *entry)
{
	rlm_rcode_t rcode = RLM_MODULE_OK;
	
	char **vals;

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

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

	int is_dn, i;

	/*
	 *	Parse the membership information we got in the initial user query.
	 */
	vals = ldap_get_values((*pconn)->handle, entry, inst->userobj_membership_attr);
	if (!vals) {
		RDEBUG2("No cacheable group memberships found in user object");
		
		return RLM_MODULE_OK;
	}

	for (i = 0; (vals[i] != NULL) && (i < LDAP_MAX_CACHEABLE); i++) {
		is_dn = rlm_ldap_is_dn(vals[i]);
	
		if (inst->cacheable_group_dn) {
			/*
			 *	The easy case, were caching DNs and we got a DN.
			 */
			if (is_dn) {
				pairmake(request, &request->config_items, inst->group_da->name, vals[i], T_OP_ADD);
				RDEBUG("Added %s with value \"%s\" to control list", inst->group_da->name, vals[i]);
			
			/*
			 *	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++ = vals[i];
			}
		}
	
		if (inst->cacheable_group_name) {
			/*
			 *	The easy case, were caching names and we got a name.
			 */
			if (!is_dn) {
				pairmake(request, &request->config_items, inst->group_da->name, vals[i], T_OP_ADD);
				RDEBUG("Added %s with value \"%s\" to control list", inst->group_da->name, vals[i]);
			/*
			 *	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 {
				rcode = rlm_ldap_group_dn2name(inst, request, pconn, vals[i], &name);
				if (rcode != RLM_MODULE_OK) {
					ldap_value_free(vals);
				
					return rcode;
				}
			
				pairmake(request, &request->config_items, inst->group_da->name, name, T_OP_ADD);
				RDEBUG("Added %s with value \"%s\" to control list", inst->group_da->name, name);
				talloc_free(name);
			}
		}
	}
	*name_p = NULL;

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

	ldap_value_free(vals);

	if (rcode != RLM_MODULE_OK) {
		return rcode;
	}

	dn_p = group_dn;
	while(*dn_p) {
		pairmake(request, &request->config_items, inst->group_da->name, *dn_p, T_OP_ADD);
		RDEBUG("Added %s with value \"%s\" to control list", inst->group_da->name, *dn_p);
		ldap_memfree(*dn_p);
	
		dn_p++;
	}
	
	return rcode;
}
Example #5
0
/** Query the LDAP directory to check if a group object includes a user object as a member
 *
 * @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] check vp containing the group value (name or dn).
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_check_groupobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
					    VALUE_PAIR *check)
					    
{
	ldap_rcode_t	status;

	char		base_dn[LDAP_MAX_DN_STR_LEN + 1];
	char 		filter[LDAP_MAX_FILTER_STR_LEN + 1];
	char const	*dn = base_dn;
	
	char const     	*name = check->vp_strvalue;

	RDEBUG2("Checking for user in group objects");
	
	if (rlm_ldap_is_dn(name)) {
		char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };		
		
		if (rlm_ldap_xlat_filter(request,
					 filters, sizeof(filters) / sizeof(*filters),
					 filter, sizeof(filter)) < 0) {
			return RLM_MODULE_INVALID;
		}

		dn = name;	
	} else {
		char name_filter[LDAP_MAX_FILTER_STR_LEN];
		char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter };
		
		if (!inst->groupobj_name_attr) {
			REDEBUG("Told to search for group by name, but missing 'group.name_attribute' "
				"directive");
				
			return RLM_MODULE_INVALID;
		}

		snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, name);
		if (rlm_ldap_xlat_filter(request,
					 filters, sizeof(filters) / sizeof(*filters),
					 filter, sizeof(filter)) < 0) {
			return RLM_MODULE_INVALID;
		}
	
		/*
		 *	rlm_ldap_find_user does this, too.  Oh well.
		 */
		if (radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn, 
				rlm_ldap_escape_func, NULL) < 0) {		
			REDEBUG("Failed creating base_dn");
		
			return RLM_MODULE_INVALID;
		}
	}

	status = rlm_ldap_search(inst, request, pconn, dn, inst->groupobj_scope, filter, NULL, NULL);
	switch (status) {
		case LDAP_PROC_SUCCESS:
			RDEBUG("User found in group object");
			
			break;
		case LDAP_PROC_NO_RESULT:
			RDEBUG("Search returned not found");
			
			return RLM_MODULE_NOTFOUND;
		default:
			return RLM_MODULE_FAIL;
	}
	
	return RLM_MODULE_OK;
}
static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST * request)
{
	rlm_rcode_t rcode = RLM_MODULE_NOOP;

	rlm_sql_t *inst = instance;
	rlm_sql_handle_t  *handle;

	VALUE_PAIR *check_tmp = NULL;
	VALUE_PAIR *reply_tmp = NULL;
	VALUE_PAIR *user_profile = NULL;

	bool	user_found = false;
	bool	dofallthrough = true;
	int	rows;

	char	*expanded = NULL;

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

	/*
	 *  Set, escape, and check the user attr here
	 */
	if (sql_set_user(inst, request, NULL) < 0) {
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Reserve a socket
	 *
	 *	After this point use goto error or goto release to cleanup socket temporary pairlists and
	 *	temporary attributes.
	 */
	handle = sql_get_socket(inst);
	if (!handle) {
		rcode = RLM_MODULE_FAIL;
		goto error;
	}

	/*
	 *	Query the check table to find any conditions associated with this user/realm/whatever...
	 */
	if (inst->config->authorize_check_query && (inst->config->authorize_check_query[0] != '\0')) {
		if (radius_axlat(&expanded, request, inst->config->authorize_check_query,
				 sql_escape_func, inst) < 0) {
			REDEBUG("Error generating query");
			rcode = RLM_MODULE_FAIL;
			goto error;
		}

		rows = sql_getvpdata(inst, &handle, request, &check_tmp, expanded);
		TALLOC_FREE(expanded);
		if (rows < 0) {
			REDEBUG("SQL query error");
			rcode = RLM_MODULE_FAIL;
			goto error;
		}

		if (rows == 0) goto skipreply;	/* Don't need to free VPs we don't have */

		/*
		 *	Only do this if *some* check pairs were returned
		 */
		RDEBUG2("User found in radcheck table");
		user_found = true;
		if (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0) {
			pairfree(&check_tmp);
			check_tmp = NULL;
			goto skipreply;
		}

		RDEBUG2("Check items matched");
		radius_pairmove(request, &request->config_items, check_tmp, true);
		rcode = RLM_MODULE_OK;
		check_tmp = NULL;
	}

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

		rows = sql_getvpdata(inst, &handle, request->reply, &reply_tmp, expanded);
		TALLOC_FREE(expanded);
		if (rows < 0) {
			REDEBUG("SQL query error");
			rcode = RLM_MODULE_FAIL;
			goto error;
		}

		if (rows == 0) goto skipreply;

		if (!inst->config->read_groups) {
			dofallthrough = fallthrough(reply_tmp);
		}

		RDEBUG2("User found in radreply table");
		user_found = true;
		radius_pairmove(request, &request->reply->vps, reply_tmp, true);
		rcode = RLM_MODULE_OK;
		reply_tmp = NULL;
	}

skipreply:
	/*
	 *	dofallthrough is set to 1 by default so that if the user information
	 *	is not found, we will still process groups.  If the user information,
	 *	however, *is* found, Fall-Through must be set in order to process
	 *	the groups as well.
	 */
	if (dofallthrough) {
		rlm_rcode_t ret;

		RDEBUG3("... falling-through to group processing");
		ret = rlm_sql_process_groups(inst, request, handle, &dofallthrough);
		switch (ret) {
			/*
			 *	Nothing bad happened, continue...
			 */
			case RLM_MODULE_UPDATED:
				rcode = RLM_MODULE_UPDATED;
				/* FALL-THROUGH */
			case RLM_MODULE_OK:
				if (rcode != RLM_MODULE_UPDATED) {
					rcode = RLM_MODULE_OK;
				}
				/* FALL-THROUGH */
			case RLM_MODULE_NOOP:
				user_found = true;
				break;

			case RLM_MODULE_NOTFOUND:
				break;

			default:
				rcode = ret;
				goto release;
		}
	}

	/*
	 *	Repeat the above process with the default profile or User-Profile
	 */
	if (dofallthrough) {
		rlm_rcode_t ret;

		/*
		 *  Check for a default_profile or for a User-Profile.
		 */
		RDEBUG3("... falling-through to profile processing");
		user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY);

		char const *profile = user_profile ?
				      user_profile->vp_strvalue :
				      inst->config->default_profile;

		if (!profile || !*profile) {
			goto release;
		}

		RDEBUG2("Checking profile %s", profile);

		if (sql_set_user(inst, request, profile) < 0) {
			REDEBUG("Error setting profile");
			rcode = RLM_MODULE_FAIL;
			goto error;
		}

		ret = rlm_sql_process_groups(inst, request, handle, &dofallthrough);
		switch (ret) {
			/*
			 *	Nothing bad happened, continue...
			 */
			case RLM_MODULE_UPDATED:
				rcode = RLM_MODULE_UPDATED;
				/* FALL-THROUGH */
			case RLM_MODULE_OK:
				if (rcode != RLM_MODULE_UPDATED) {
					rcode = RLM_MODULE_OK;
				}
				/* FALL-THROUGH */
			case RLM_MODULE_NOOP:
				user_found = true;
				break;

			case RLM_MODULE_NOTFOUND:
				break;

			default:
				rcode = ret;
				goto release;
		}
	}

	/*
	 *	At this point the key (user) hasn't be found in the check table, the reply table
	 *	or the group mapping table, and there was no matching profile.
	 */
release:
	if (!user_found) {
		rcode = RLM_MODULE_NOTFOUND;
	}

	sql_release_socket(inst, handle);

	return rcode;

error:
	pairfree(&check_tmp);
	pairfree(&reply_tmp);

	sql_release_socket(inst, handle);

	return rcode;
}
Example #7
0
/** Convert a map to a VALUE_PAIR.
 *
 * @param[out] out Where to write the VALUE_PAIR(s).
 * @param[in] request structure (used only for talloc)
 * @param[in] map the map. The LHS (dst) has to be VPT_TYPE_ATTR or VPT_TYPE_LIST.
 * @param[in] ctx unused
 * @return 0 on success, -1 on failure, -2 on attribute not found/equivalent
 */
int radius_map2vp(VALUE_PAIR **out, REQUEST *request, value_pair_map_t const *map, UNUSED void *ctx)
{
	int rcode = 0;
	VALUE_PAIR *vp = NULL, *found, **from = NULL;
	DICT_ATTR const *da;
	REQUEST *context = request;
	vp_cursor_t cursor;

	rad_assert(request != NULL);
	rad_assert(map != NULL);

	*out = NULL;

	/*
	 *	Special case for !*, we don't need to parse RHS as this is a unary operator.
	 */
	if (map->op == T_OP_CMP_FALSE) {
		/*
		 *  Were deleting all the attributes in a list. This isn't like the other
		 *  mappings because lists aren't represented as attributes (yet),
		 *  so we can't return a <list> attribute with the !* operator for
		 *  radius_pairmove() to consume, and need to do the work here instead.
		 */
		if (map->dst->type == VPT_TYPE_LIST) {
			if (radius_request(&context, map->dst->request) == 0) {
				from = radius_list(context, map->dst->list);
			}
			if (!from) return -2;

			pairfree(from);

			/* @fixme hacky! */
			if (map->dst->list == PAIR_LIST_REQUEST) {
				context->username = NULL;
				context->password = NULL;
			}

			return 0;
		}

		/* Not a list, but an attribute, radius_pairmove() will perform that actual delete */
		vp = pairalloc(request, map->dst->da);
		if (!vp) return -1;
		vp->op = map->op;
		*out = vp;

		return 0;
	}

	/*
	 *	List to list found, this is a special case because we don't need
	 *	to allocate any attributes, just finding the current list, and change
	 *	the op.
	 */
	if ((map->dst->type == VPT_TYPE_LIST) && (map->src->type == VPT_TYPE_LIST)) {
		if (radius_request(&context, map->src->request) == 0) {
			from = radius_list(context, map->src->list);
		}
		if (!from) return -2;

		found = paircopy(request, *from);
		/*
		 *	List to list copy is invalid if the src list has no attributes.
		 */
		if (!found) return -2;

		for (vp = fr_cursor_init(&cursor, &found);
		     vp;
		     vp = fr_cursor_next(&cursor)) {
		 	vp->op = T_OP_ADD;
		}

		*out = found;

		return 0;
	}

	/*
	 *	Deal with all non-list operations.
	 */
	da = map->dst->da ? map->dst->da : map->src->da;

	switch (map->src->type) {
	case VPT_TYPE_XLAT:
	case VPT_TYPE_XLAT_STRUCT:
	case VPT_TYPE_LITERAL:
	case VPT_TYPE_DATA:
		vp = pairalloc(request, da);
		if (!vp) return -1;
		vp->op = map->op;
		break;
	default:
		break;
	}


	/*
	 *	And parse the RHS
	 */
	switch (map->src->type) {
		ssize_t slen;
		char *str;

	case VPT_TYPE_XLAT_STRUCT:
		rad_assert(map->dst->da);	/* Need to know where were going to write the new attribute */
		rad_assert(map->src->xlat != NULL);

		str = NULL;
		slen = radius_axlat_struct(&str, request, map->src->xlat, NULL, NULL);
		if (slen < 0) {
			rcode = slen;
			goto error;
		}

		/*
		 *	We do the debug printing because radius_axlat_struct
		 *	doesn't have access to the original string.  It's been
		 *	mangled during the parsing to xlat_exp_t
		 */
		RDEBUG2("EXPAND %s", map->src->name);
		RDEBUG2("   --> %s", str);

		rcode = pairparsevalue(vp, str);
		talloc_free(str);
		if (!rcode) {
			pairfree(&vp);
			rcode = -1;
			goto error;
		}
		break;

	case VPT_TYPE_XLAT:
		rad_assert(map->dst->da);	/* Need to know where were going to write the new attribute */

		str = NULL;
		slen = radius_axlat(&str, request, map->src->name, NULL, NULL);
		if (slen < 0) {
			rcode = slen;
			goto error;
		}
		rcode = pairparsevalue(vp, str);
		talloc_free(str);
		if (!rcode) {
			pairfree(&vp);
			rcode = -1;
			goto error;
		}
		break;

	case VPT_TYPE_LITERAL:
		if (!pairparsevalue(vp, map->src->name)) {
			rcode = -2;
			goto error;
		}
		break;

	case VPT_TYPE_ATTR:
		rad_assert(!map->dst->da ||
			   (map->src->da->type == map->dst->da->type) ||
			   (map->src->da->type == PW_TYPE_OCTETS) ||
			   (map->dst->da->type == PW_TYPE_OCTETS));

		/*
		 *	Special case, destination is a list, found all instance of an attribute.
		 */
		if (map->dst->type == VPT_TYPE_LIST) {
			context = request;

			if (radius_request(&context, map->src->request) == 0) {
				from = radius_list(context, map->src->list);
			}

			/*
			 *	Can't add the attribute if the list isn't
			 *	valid.
			 */
			if (!from) {
				rcode = -2;
				goto error;
			}

			found = paircopy2(request, *from, map->src->da->attr, map->src->da->vendor, TAG_ANY);
			if (!found) {
				REDEBUG("Attribute \"%s\" not found in request", map->src->name);
				rcode = -2;
				goto error;
			}

			for (vp = fr_cursor_init(&cursor, &found);
			     vp;
			     vp = fr_cursor_next(&cursor)) {
				vp->op = T_OP_ADD;
			}

			*out = found;
			return 0;
		}

		if (radius_vpt_get_vp(&found, request, map->src) < 0) {
			REDEBUG("Attribute \"%s\" not found in request", map->src->name);
			rcode = -2;
			goto error;
		}

		/*
		 *	Copy the data over verbatim, assuming it's
		 *	actually data.
		 */
		vp = paircopyvpdata(request, da, found);
		if (!vp) {
			return -1;
		}
		vp->op = map->op;

		break;

	case VPT_TYPE_DATA:
		rad_assert(map->src && map->src->da);
		rad_assert(map->dst && map->dst->da);
		rad_assert(map->src->da->type == map->dst->da->type);
		memcpy(&vp->data, map->src->vpd, sizeof(vp->data));
		vp->length = map->src->length;
		break;

	/*
	 *	This essentially does the same as rlm_exec xlat, except it's non-configurable.
	 *	It's only really here as a convenience for people who expect the contents of
	 *	backticks to be executed in a shell.
	 *
	 *	exec string is xlat expanded and arguments are shell escaped.
	 */
	case VPT_TYPE_EXEC:
		return radius_mapexec(out, request, map);
	default:
		rad_assert(0);	/* Should of been caught at parse time */
	error:
		pairfree(&vp);
		return rcode;
	}

	*out = vp;
	return 0;
}
Example #8
0
static int get_number(REQUEST *request, const char **string, int64_t *answer)
{
	int		i, found;
	int64_t		result;
	int64_t		x;
	const char	*p;
	expr_token_t	this;

	/*
	 *  Loop over the input.
	 */
	result = 0;
	this = TOKEN_NONE;

	for (p = *string; *p != '\0'; /* nothing */) {
		if ((*p == ' ') ||
		    (*p == '\t')) {
			p++;
			continue;
		}

		/*
		 *  Discover which token it is.
		 */
		found = FALSE;
		for (i = 0; map[i].token != TOKEN_LAST; i++) {
			if (*p == map[i].op) {
				if (this != TOKEN_NONE) {
					RDEBUG2("Invalid operator at \"%s\"", p);
					return -1;
				}
				this = map[i].token;
				p++;
				found = TRUE;
				break;
			}
		}

		/*
		 *  Found the algebraic operator.  Get the next number.
		 */
		if (found) {
			continue;
		}

		/*
		 *  End of a group.  Stop.
		 */
		if (*p == ')') {
			if (this != TOKEN_NONE) {
				RDEBUG2("Trailing operator before end sub-expression at \"%s\"", p);
				return -1;
			}
			p++;
			break;
		}

		/*
		 *  Start of a group.  Call ourselves recursively.
		 */
		if (*p == '(') {
			p++;

			found = get_number(request, &p, &x);
			if (found < 0) {
				return -1;
			}
		} else {
			/*
			 *  No algrebraic operator found, the next thing
			 *  MUST be a number.
			 *
			 *  If it isn't, then we die.
			 */
			if ((*p == '0') && (p[1] == 'x')) {
				char *end;

				x = strtoul(p, &end, 16);
				p = end;
				goto calc;
			}


			if ((*p < '0') || (*p > '9')) {
				RDEBUG2("Not a number at \"%s\"", p);
				return -1;
			}

			/*
			 *  This is doing it the hard way, but it also allows
			 *  us to increment 'p'.
			 */
			x = 0;
			while ((*p >= '0') && (*p <= '9')) {
				x *= 10;
				x += (*p - '0');
				p++;
			}
		}

	calc:
		switch (this) {
		default:
		case TOKEN_NONE:
			result = x;
			break;

		case TOKEN_ADD:
			result += x;
			break;

		case TOKEN_SUBTRACT:
			result -= x;
			break;

		case TOKEN_DIVIDE:
			if (x == 0) {
				result = 0; /* we don't have NaN for integers */
				break;
			}
			result /= x;
			break;

		case TOKEN_REMAINDER:
			if (x == 0) {
				result = 0; /* we don't have NaN for integers */
				break;
			}
			result %= x;
			break;

		case TOKEN_MULTIPLY:
			result *= x;
			break;

		case TOKEN_AND:
			result &= x;
			break;

		case TOKEN_OR:
			result |= x;
			break;
		}

		/*
		 *  We've used this token.
		 */
		this = TOKEN_NONE;
	}

	/*
	 *  And return the answer to the caller.
	 */
	*string = p;
	*answer = result;
	return 0;
}
Example #9
0
/*
 *	Find the named user in this modules database.  Create the set
 *	of attribute-value pairs to check and reply with for this user
 *	from the database. The authentication code only needs to check
 *	the password, the rest is done here.
 */
static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
{
	rlm_sqlcounter_t *inst = instance;
	int rcode = RLM_MODULE_NOOP;
	uint64_t counter, res;
	DICT_ATTR const *da;
	VALUE_PAIR *key_vp, *check_vp;
	VALUE_PAIR *reply_item;
	char msg[128];

	char *p;
	char query[MAX_QUERY_LEN];
	char *expanded = NULL;

	size_t len;

	rad_assert(instance != NULL);
	rad_assert(request != NULL);

	/*
	 *	Before doing anything else, see if we have to reset
	 *	the counters.
	 */
	if (inst->reset_time && (inst->reset_time <= request->timestamp)) {
		/*
		 *	Re-set the next time and prev_time for this counters range
		 */
		inst->last_reset = inst->reset_time;
		find_next_reset(inst,request->timestamp);
	}

	/*
	 *      Look for the key.  User-Name is special.  It means
	 *      The REAL username, after stripping.
	 */
	RDEBUG2("Entering module authorize code");
	key_vp = ((inst->key_attr->vendor == 0) && (inst->key_attr->attr == PW_USER_NAME)) ?
			request->username :
			pairfind(request->packet->vps, inst->key_attr->attr, inst->key_attr->vendor, TAG_ANY);
	if (!key_vp) {
		RDEBUG2("Could not find Key value pair");
		return rcode;
	}

	/*
	 *      Look for the check item
	 */
	if ((da = dict_attrbyname(inst->check_name)) == NULL) {
		return rcode;
	}
	/* DEBUG2("rlm_sqlcounter: Found Check item attribute %d", da->attr); */
	if ((check_vp = pairfind(request->config_items, da->attr, da->vendor, TAG_ANY)) == NULL) {
		RDEBUG2("Could not find Check item value pair");
		return rcode;
	}

	len = snprintf(query, sizeof(query), "%%{%s:%s}", inst->sqlmod_inst, inst->query);
	if (len >= sizeof(query) - 1) {
		REDEBUG("Insufficient query buffer space");

		return RLM_MODULE_FAIL;
	}

	p = query + len;

	/* first, expand %k, %b and %e in query */
	len = sqlcounter_expand(p, p - query, inst->query, inst);
	if (len <= 0) {
		REDEBUG("Insufficient query buffer space");

		return RLM_MODULE_FAIL;
	}

	p += len;

	if ((p - query) < 2) {
		REDEBUG("Insufficient query buffer space");

		return RLM_MODULE_FAIL;
	}

	p[0] = '}';
	p[1] = '\0';

	/* Finally, xlat resulting SQL query */
	if (radius_axlat(&expanded, request, query, NULL, NULL) < 0) {
		return RLM_MODULE_FAIL;
	}

	if (sscanf(expanded, "%" PRIu64, &counter) != 1) {
		RDEBUG2("No integer found in string \"%s\"", expanded);
		return RLM_MODULE_NOOP;
	}

	talloc_free(expanded);

	/*
	 *	Check if check item > counter
	 */
	if (check_vp->vp_integer64 <= counter) {
		RDEBUG2("(Check item - counter) is less than zero");

		/*
		 * User is denied access, send back a reply message
		 */
		snprintf(msg, sizeof(msg), "Your maximum %s usage time has been reached", inst->reset);
		pairmake_reply("Reply-Message", msg, T_OP_EQ);

		REDEBUG("Maximum %s usage time reached", inst->reset);

		RDEBUG2("Rejected user %s, check_item=%" PRIu64 ", counter=%" PRIu64,
			key_vp->vp_strvalue, check_vp->vp_integer64, counter);

		return RLM_MODULE_REJECT;

	}

	res = check_vp->vp_integer64 - counter;
	RDEBUG2("Check item is greater than query result");
	/*
	 *	We are assuming that simultaneous-use=1. But
	 *	even if that does not happen then our user
	 *	could login at max for 2*max-usage-time Is
	 *	that acceptable?
	 */

	/*
	 *	If we are near a reset then add the next
	 *	limit, so that the user will not need to login
	 *	again.  Do this only for Session-Timeout.
	 */
	if (((inst->reply_attr->vendor == 0) && (inst->reply_attr->attr == PW_SESSION_TIMEOUT)) &&
	    inst->reset_time && ((int) res >= (inst->reset_time - request->timestamp))) {
		res = inst->reset_time - request->timestamp;
		res += check_vp->vp_integer;
	}

	/*
	 *	Limit the reply attribute to the minimum of
	 *	the existing value, or this new one.
	 */
	reply_item = pairfind(request->reply->vps, inst->reply_attr->attr, inst->reply_attr->vendor, TAG_ANY);
	if (reply_item) {
		if (reply_item->vp_integer64 > res) {
			reply_item->vp_integer64 = res;
		} else {
			RDEBUG2("Leaving existing limit of %" PRIu64, reply_item->vp_integer64);
		}
	} else {
		reply_item = radius_paircreate(request, &request->reply->vps, inst->reply_attr->attr,
					       inst->reply_attr->vendor);
		reply_item->vp_integer64 = res;
	}

	RDEBUG2("Authorized user %s, check_item=%" PRIu64 ", counter=%" PRIu64 ,
		key_vp->vp_strvalue, check_vp->vp_integer64, counter);
	RDEBUG2("Sent Reply-Item for user %s, Type=%s, value=%" PRIu64,
		key_vp->vp_strvalue, inst->reply_name, reply_item->vp_integer64);

	return RLM_MODULE_OK;
}
Example #10
0
/**  Process an EAP-AKA/Response/Challenge
 *
 * Verify that MAC, and RES match what we expect.
 */
static int process_eap_aka_challenge(eap_session_t *eap_session, VALUE_PAIR *vps)
{
	REQUEST			*request = eap_session->request;
	eap_aka_session_t	*eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t);

	uint8_t			calc_mac[SIM_MAC_DIGEST_SIZE];
	ssize_t			slen;
	VALUE_PAIR		*vp = NULL, *mac, *checkcode;

	mac = fr_pair_find_by_da(vps, attr_eap_aka_mac, TAG_ANY);
	if (!mac) {
		REDEBUG("Missing AT_MAC attribute");
		return -1;
	}
	if (mac->vp_length != SIM_MAC_DIGEST_SIZE) {
		REDEBUG("EAP-AKA-MAC has incorrect length, expected %u bytes got %zu bytes",
			SIM_MAC_DIGEST_SIZE, mac->vp_length);
		return -1;
	}

	slen = fr_sim_crypto_sign_packet(calc_mac, eap_session->this_round->response, true,
					 eap_aka_session->mac_md,
					 eap_aka_session->keys.k_aut, eap_aka_session->keys.k_aut_len,
					 NULL, 0);
	if (slen < 0) {
		RPEDEBUG("Failed calculating MAC");
		return -1;
	} else if (slen == 0) {
		REDEBUG("Missing EAP-AKA-MAC attribute in packet buffer");
		return -1;
	}

	if (memcmp(mac->vp_octets, calc_mac, sizeof(calc_mac)) == 0) {
		RDEBUG2("EAP-AKA-MAC matches calculated MAC");
	} else {
		REDEBUG("EAP-AKA-MAC does not match calculated MAC");
		RHEXDUMP_INLINE(L_DBG_LVL_2, mac->vp_octets, SIM_MAC_DIGEST_SIZE, "Received");
		RHEXDUMP_INLINE(L_DBG_LVL_2, calc_mac, SIM_MAC_DIGEST_SIZE, "Expected");
		return -1;
	}

	/*
	 *	If the peer doesn't include a checkcode then that
	 *	means they don't support it, and we can't validate
	 *	their view of the identity packets.
	 */
	checkcode = fr_pair_find_by_da(vps, attr_eap_aka_checkcode, TAG_ANY);
	if (checkcode) {
		if (checkcode->vp_length != eap_aka_session->checkcode_len) {
			REDEBUG("Checkcode length (%zu) does not match calculated checkcode length (%zu)",
				checkcode->vp_length, eap_aka_session->checkcode_len);
			return -1;
		}

		if (memcmp(checkcode->vp_octets, eap_aka_session->checkcode, eap_aka_session->checkcode_len) == 0) {
			RDEBUG2("EAP-AKA-Checkcode matches calculated checkcode");
		} else {
			REDEBUG("EAP-AKA-Checkcode does not match calculated checkcode");
			RHEXDUMP_INLINE(L_DBG_LVL_2, checkcode->vp_octets, checkcode->vp_length, "Received");
			RHEXDUMP_INLINE(L_DBG_LVL_2, eap_aka_session->checkcode,
					eap_aka_session->checkcode_len, "Expected");
			return -1;
		}
	/*
	 *	Only print something if we calculated a checkcode
	 */
	} else if (eap_aka_session->checkcode_len > 0){
		RDEBUG2("Peer didn't include EAP-AKA-Checkcode, skipping checkcode validation");
	}

	vp = fr_pair_find_by_da(vps, attr_eap_aka_res, TAG_ANY);
	if (!vp) {
		REDEBUG("Missing EAP-AKA-RES from challenge response");
		return -1;
	}

	if (vp->vp_length != eap_aka_session->keys.umts.vector.xres_len) {
		REDEBUG("EAP-AKA-RES length (%zu) does not match XRES length (%zu)",
			vp->vp_length, eap_aka_session->keys.umts.vector.xres_len);
		return -1;
	}

  	if (memcmp(vp->vp_octets, eap_aka_session->keys.umts.vector.xres, vp->vp_length)) {
    		REDEBUG("EAP-AKA-RES from client does match XRES");
		RHEXDUMP_INLINE(L_DBG_LVL_2, vp->vp_octets, vp->vp_length, "RES  :");
		RHEXDUMP_INLINE(L_DBG_LVL_2, eap_aka_session->keys.umts.vector.xres,
				eap_aka_session->keys.umts.vector.xres_len, "XRES :");
		return -1;
	}

	RDEBUG2("EAP-AKA-RES matches XRES");

	eap_aka_session->challenge_success = true;

	/*
	 *	If the peer wants a Success notification, then
	 *	send a success notification, otherwise send a
	 *	normal EAP-Success.
	 */
	if (fr_pair_find_by_da(vps, attr_eap_aka_result_ind, TAG_ANY)) {
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_SUCCESS_NOTIFICATION);
		return 1;
	}

	eap_aka_state_enter(eap_session, EAP_AKA_SERVER_SUCCESS);
	return 0;
}
Example #11
0
/** Process the Peer's response and advantage the state machine
 *
 */
static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session)
{
	REQUEST			*request = eap_session->request;
	eap_aka_session_t	*eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t);

	fr_sim_decode_ctx_t	ctx = {
					.keys = &eap_aka_session->keys,
				};
	VALUE_PAIR		*vp, *vps, *subtype_vp;
	fr_cursor_t		cursor;

	eap_aka_subtype_t	subtype;

	int			ret;

	/*
	 *	RFC 4187 says we ignore the contents of the
	 *	next packet after we send our success notification
	 *	and always send a success.
	 */
	if (eap_aka_session->state == EAP_AKA_SERVER_SUCCESS_NOTIFICATION) {
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_SUCCESS);
		return RLM_MODULE_HANDLED;
	}

	/* vps is the data from the client */
	vps = request->packet->vps;

	fr_cursor_init(&cursor, &request->packet->vps);
	fr_cursor_tail(&cursor);

	ret = fr_sim_decode(eap_session->request,
			    &cursor,
			    dict_eap_aka,
			    eap_session->this_round->response->type.data,
			    eap_session->this_round->response->type.length,
			    &ctx);
	/*
	 *	RFC 4187 says we *MUST* notify, not just
	 *	send an EAP-Failure in this case where
	 *	we cannot decode an EAP-AKA packet.
	 */
	if (ret < 0) {
		RPEDEBUG2("Failed decoding EAP-AKA attributes");
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
		return RLM_MODULE_HANDLED;	/* We need to process more packets */
	}

	vp = fr_cursor_current(&cursor);
	if (vp && RDEBUG_ENABLED2) {
		RDEBUG2("EAP-AKA decoded attributes");
		log_request_pair_list(L_DBG_LVL_2, request, vp, NULL);
	}

	subtype_vp = fr_pair_find_by_da(vps, attr_eap_aka_subtype, TAG_ANY);
	if (!subtype_vp) {
		REDEBUG("Missing EAP-AKA-Subtype");
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
		return RLM_MODULE_HANDLED;				/* We need to process more packets */
	}
	subtype = subtype_vp->vp_uint16;

	switch (eap_aka_session->state) {
	/*
	 *	Here we expected the peer to send
	 *	us identities for validation.
	 */
	case EAP_AKA_SERVER_IDENTITY:
		switch (subtype) {
		case EAP_AKA_IDENTITY:
			if (process_eap_aka_identity(eap_session, vps) == 0) return RLM_MODULE_HANDLED;
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
			return RLM_MODULE_HANDLED;	/* We need to process more packets */

		/*
		 *	Case 1 where we're allowed to send an EAP-Failure
		 *
		 *	This can happen in the case of a conservative
		 *	peer, where it refuses to provide the permanent
		 *	identity.
		 */
		case EAP_AKA_CLIENT_ERROR:
		{
			char buff[20];

			vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY);
			if (!vp) {
				REDEBUG("EAP-AKA Peer rejected AKA-Identity (%s) with client-error message but "
					"has not supplied a client error code",
					fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>"));
			} else {
				REDEBUG("Client rejected AKA-Identity (%s) with error: %s (%i)",
					fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>"),
					fr_pair_value_enum(vp, buff), vp->vp_uint16);
			}
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE);
			return RLM_MODULE_REJECT;
		}

		case EAP_AKA_NOTIFICATION:
		notification:
		{
			char buff[20];

			vp = fr_pair_afrom_da(vps, attr_eap_aka_notification);
			if (!vp) {
				REDEBUG2("Received AKA-Notification with no notification code");
				eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
				return RLM_MODULE_HANDLED;			/* We need to process more packets */
			}

			/*
			 *	Case 3 where we're allowed to send an EAP-Failure
			 */
			if (!(vp->vp_uint16 & 0x8000)) {
				REDEBUG2("AKA-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff),
					 vp->vp_uint16);
				eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE);
				return RLM_MODULE_REJECT;
			}

			/*
			 *	...if it's not a failure, then re-enter the
			 *	current state.
			 */
			REDEBUG2("Got AKA-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16);
			eap_aka_state_enter(eap_session, eap_aka_session->state);
			return RLM_MODULE_HANDLED;
		}

		default:
		unexpected_subtype:
			/*
			 *	RFC 4187 says we *MUST* notify, not just
			 *	send an EAP-Failure in this case.
			 */
			REDEBUG("Unexpected subtype %pV", &subtype_vp->data);
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
			return RLM_MODULE_HANDLED;				/* We need to process more packets */
		}

	/*
	 *	Process the response to our previous challenge.
	 */
	case EAP_AKA_SERVER_CHALLENGE:
		switch (subtype) {
		case EAP_AKA_CHALLENGE:
			switch (process_eap_aka_challenge(eap_session, vps)) {
			case 1:
				return RLM_MODULE_HANDLED;

			case 0:
				return RLM_MODULE_OK;

			case -1:
				eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
				return RLM_MODULE_HANDLED;			/* We need to process more packets */
			}


		case EAP_AKA_SYNCHRONIZATION_FAILURE:
			REDEBUG("EAP-AKA Peer synchronization failure");	/* We can't handle these yet */
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
			return RLM_MODULE_HANDLED;				/* We need to process more packets */

		/*
		 *	Case 1 where we're allowed to send an EAP-Failure
		 */
		case EAP_AKA_CLIENT_ERROR:
		{
			char buff[20];

			vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY);
			if (!vp) {
				REDEBUG("EAP-AKA Peer rejected AKA-Challenge with client-error message but "
					"has not supplied a client error code");
			} else {
				REDEBUG("Client rejected AKA-Challenge with error: %s (%i)",
					fr_pair_value_enum(vp, buff), vp->vp_uint16);
			}
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE);
			return RLM_MODULE_REJECT;
		}

		/*
		 *	Case 2 where we're allowed to send an EAP-Failure
		 */
		case EAP_AKA_AUTHENTICATION_REJECT:
			REDEBUG("EAP-AKA Peer Rejected AUTN");
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE);
			return RLM_MODULE_REJECT;

		case EAP_AKA_NOTIFICATION:
			goto notification;

		default:
			goto unexpected_subtype;
		}

	/*
	 *	Peer acked our failure
	 */
	case EAP_AKA_SERVER_FAILURE_NOTIFICATION:
		switch (subtype) {
		case EAP_AKA_NOTIFICATION:
			RDEBUG2("AKA-Notification ACKed, sending EAP-Failure");
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE);
			return RLM_MODULE_REJECT;

		default:
			goto unexpected_subtype;
		}

	/*
	 *	Something bad happened...
	 */
	default:
		rad_assert(0);
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
		return RLM_MODULE_HANDLED;				/* We need to process more packets */
	}
}

/** Initiate the EAP-SIM session by starting the state machine
 *
 */
static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session)
{
	REQUEST				*request = eap_session->request;
	eap_aka_session_t		*eap_aka_session;
	rlm_eap_aka_t			*inst = instance;
	fr_sim_id_type_t		type;
	fr_sim_method_hint_t		method;

	MEM(eap_aka_session = talloc_zero(eap_session, eap_aka_session_t));

	eap_session->opaque = eap_aka_session;

	/*
	 *	Set default configuration, we may allow these
	 *	to be toggled by attributes later.
	 */
	eap_aka_session->request_identity = inst->request_identity;
	eap_aka_session->send_result_ind = inst->protected_success;
	eap_aka_session->id_req = SIM_NO_ID_REQ;	/* Set the default */

	/*
	 *	This value doesn't have be strong, but it is
	 *	good if it is different now and then.
	 */
	eap_aka_session->aka_id = (fr_rand() & 0xff);

	/*
	 *	Process the identity that we received in the
	 *	EAP-Identity-Response and use it to determine
	 *	the initial request we send to the Supplicant.
	 */
	if (fr_sim_id_type(&type, &method,
			   eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) {
		RPWDEBUG2("Failed parsing identity, continuing anyway");
	}

	/*
	 *	Unless AKA-Prime is explicitly disabled,
	 *	use it... It has stronger keying, and
	 *	binds authentication to the network.
	 */
	switch (eap_session->type) {
	case FR_EAP_AKA_PRIME:
	default:
		RDEBUG2("New EAP-AKA' session");
		eap_aka_session->type = FR_EAP_AKA_PRIME;
		eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA_PRIME_WITH_CK_PRIME_IK_PRIME;
		eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha256();
		eap_aka_session->keys.network = (uint8_t *)talloc_bstrndup(eap_aka_session, inst->network_name,
									   talloc_array_length(inst->network_name) - 1);
		eap_aka_session->keys.network_len = talloc_array_length(eap_aka_session->keys.network) - 1;
		switch (method) {
		default:
			RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're "
				"attempting EAP-AKA'", fr_int2str(sim_id_method_hint_table, method, "<INVALID>"));
			break;

		case SIM_METHOD_HINT_AKA_PRIME:
		case SIM_METHOD_HINT_UNKNOWN:
			break;
		}
		break;

	case FR_EAP_AKA:
		RDEBUG2("New EAP-AKA session");
		eap_aka_session->type = FR_EAP_AKA;
		eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA;	/* Not actually sent */
		eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha1();
		eap_aka_session->send_at_bidding = true;
		switch (method) {
		default:
			RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're "
				"attempting EAP-AKA", fr_int2str(sim_id_method_hint_table, method, "<INVALID>"));
			break;

		case SIM_METHOD_HINT_AKA:
		case SIM_METHOD_HINT_UNKNOWN:
			break;
		}
		break;
	}
	eap_session->process = mod_process;

	/*
	 *	Admin wants us to always request an identity
	 *	initially.  The RFC says this is also the
	 *	better way to operate, as the supplicant
	 *	can 'decorate' the identity in the identity
	 *	response.
	 */
	if (inst->request_identity) {
	request_id:
		/*
		 *	We always start by requesting
		 *	any ID initially as we can
		 *	always negotiate down.
		 */
		eap_aka_session->id_req = SIM_ANY_ID_REQ;
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_IDENTITY);
		return RLM_MODULE_HANDLED;
	}
	/*
	 *	Figure out what type of identity we have
	 *	and use it to determine the initial
	 *	request we send.
	 */
	switch (type) {
	/*
	 *	If there's no valid tag on the identity
	 *	then it's probably been decorated by the
	 *	supplicant.
	 *
	 *	Request the unmolested identity
	 */
	case SIM_ID_TYPE_UNKNOWN:
		RWDEBUG("Identity format unknown, sending Identity request");
		goto request_id;

	/*
	 *	These types need to be transformed into something
	 *	usable before we can do anything.
	 */
	case SIM_ID_TYPE_PSEUDONYM:
	case SIM_ID_TYPE_FASTAUTH:

	/*
	 *	Permanent ID means we can just send the challenge
	 */
	case SIM_ID_TYPE_PERMANENT:
		eap_aka_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1;
		MEM(eap_aka_session->keys.identity = talloc_memdup(eap_aka_session, eap_session->identity,
								   eap_aka_session->keys.identity_len));
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_CHALLENGE);
		return RLM_MODULE_HANDLED;
	}

	return RLM_MODULE_HANDLED;
}
Example #12
0
/** Run the server state machine
 *
 */
static void eap_aka_state_enter(eap_session_t *eap_session, eap_aka_server_state_t new_state)
{
	REQUEST			*request = eap_session->request;
	eap_aka_session_t	*eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t);

	if (new_state != eap_aka_session->state) {
		RDEBUG2("Changed state %s -> %s",
			fr_int2str(aka_state_table, eap_aka_session->state, "<unknown>"),
			fr_int2str(aka_state_table, new_state, "<unknown>"));
		eap_aka_session->state = new_state;
	} else {
		RDEBUG2("Reentering state %s",
			fr_int2str(aka_state_table, eap_aka_session->state, "<unknown>"));
	}

	switch (new_state) {
	/*
	 *	Send an EAP-AKA Identity request
	 */
	case EAP_AKA_SERVER_IDENTITY:
		if (eap_aka_send_identity_request(eap_session) < 0) {
		notify_failure:
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
			return;
		}
		break;

	/*
	 *	Send the EAP-AKA Challenge message.
	 */
	case EAP_AKA_SERVER_CHALLENGE:
		if (eap_aka_send_challenge(eap_session) < 0) goto notify_failure;
		break;

	/*
	 *	Sent a protected success notification
	 */
	case EAP_AKA_SERVER_SUCCESS_NOTIFICATION:
		if (eap_aka_send_eap_success_notification(eap_session) < 0) goto notify_failure;
		break;

	/*
	 *	Send the EAP Success message (we're done)
	 */
	case EAP_AKA_SERVER_SUCCESS:
		if (eap_aka_send_eap_success(eap_session) < 0) goto notify_failure;
		return;

	/*
	 *	Send a general failure notification
	 */
	case EAP_AKA_SERVER_FAILURE_NOTIFICATION:
		if (eap_aka_send_eap_failure_notification(eap_session) < 0) {	/* Fallback to EAP-Failure */
			eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE);
		}
		return;

	/*
	 *	Send an EAP-Failure (we're done)
	 */
	case EAP_AKA_SERVER_FAILURE:
		eap_aka_send_eap_failure(eap_session);
		return;

	default:
		rad_assert(0);	/* Invalid transition */
		eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION);
		return;
	}
}
Example #13
0
/** Send the challenge itself
 *
 * Challenges will come from one of three places eventually:
 *
 * 1  from attributes like FR_EAP_AKA_RANDx
 *	    (these might be retrieved from a database)
 *
 * 2  from internally implemented SIM authenticators
 *	    (a simple one based upon XOR will be provided)
 *
 * 3  from some kind of SS7 interface.
 *
 * For now, they only come from attributes.
 * It might be that the best way to do 2/3 will be with a different
 * module to generate/calculate things.
 */
static int eap_aka_send_challenge(eap_session_t *eap_session)
{
	REQUEST			*request = eap_session->request;
	eap_aka_session_t	*eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t);
	VALUE_PAIR		**to_peer, *vp;
	RADIUS_PACKET		*packet;
	fr_sim_vector_src_t	src = SIM_VECTOR_SRC_AUTO;

	rad_assert(request);
	rad_assert(request->reply);

	/*
	 *	to_peer is the data to the client
	 */
	packet = eap_session->request->reply;
	to_peer = &packet->vps;

	RDEBUG2("Acquiring UMTS vector(s)");

	/*
	 *	Toggle the AMF high bit to indicate we're doing AKA'
	 */
	if (eap_aka_session->type == FR_EAP_AKA_PRIME) {
		uint8_t	amf_buff[2] = { 0x80, 0x00 };	/* Set the AMF separation bit high */

		MEM(pair_update_control(&vp, attr_sim_amf) >= 0);
		fr_pair_value_memcpy(vp, amf_buff, sizeof(amf_buff));
	}

	/*
	 *	Get vectors from attribute or generate
	 *	them using COMP128-* or Milenage.
	 */
	if (fr_sim_vector_umts_from_attrs(eap_session, request->control, &eap_aka_session->keys, &src) != 0) {
	    	REDEBUG("Failed retrieving UMTS vectors");
		return RLM_MODULE_FAIL;
	}

	/*
	 *	Don't leave the AMF hanging around
	 */
	if (eap_aka_session->type == FR_EAP_AKA_PRIME) pair_delete_control(attr_sim_amf);

	/*
	 *	All set, calculate keys!
	 */
	switch (eap_aka_session->kdf) {
	case FR_EAP_AKA_KDF_VALUE_EAP_AKA_PRIME_WITH_CK_PRIME_IK_PRIME:
		fr_sim_crypto_kdf_1_umts(&eap_aka_session->keys);
		break;

	default:
		fr_sim_crypto_kdf_0_umts(&eap_aka_session->keys);
		break;
	}
	if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_aka_session->keys);

	RDEBUG2("Sending AKA-Challenge");
	eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;

	/*
	 *	Set the subtype to challenge
	 */
	MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_subtype));
	vp->vp_uint16 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_CHALLENGE;
	fr_pair_replace(to_peer, vp);

	/*
	 *	Indicate we'd like to use protected success messages
	 */
	if (eap_aka_session->send_result_ind) {
		MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_result_ind));
		vp->vp_bool = true;
		fr_pair_replace(to_peer, vp);
	}

	/*
	 *	We support EAP-AKA' and the peer should use that
	 *	if it's able to...
	 */
	if (eap_aka_session->send_at_bidding) {
		MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_bidding));
		vp->vp_uint16 = FR_EAP_AKA_BIDDING_VALUE_PREFER_AKA_PRIME;
		fr_pair_replace(to_peer, vp);
	}

	/*
	 *	Send the network name and KDF to the peer
	 */
	if (eap_aka_session->type == FR_EAP_AKA_PRIME) {
		if (!eap_aka_session->keys.network_len) {
			REDEBUG2("No network name available, can't set EAP-AKA-KDF-Input");
		failure:
			fr_pair_list_free(&packet->vps);
			return -1;
		}
		MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_kdf_input));
		fr_pair_value_bstrncpy(vp, eap_aka_session->keys.network, eap_aka_session->keys.network_len);
		fr_pair_replace(to_peer, vp);

		MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_kdf));
		vp->vp_uint16 = eap_aka_session->kdf;
		fr_pair_replace(to_peer, vp);
	}

	/*
	 *	Okay, we got the challenge! Put it into an attribute.
	 */
	MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_rand));
	fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.rand, SIM_VECTOR_UMTS_RAND_SIZE);
	fr_pair_replace(to_peer, vp);

	/*
	 *	Send the AUTN value to the client, so it can authenticate
	 *	whoever has knowledge of the Ki.
	 */
	MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_autn));
	fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.autn, SIM_VECTOR_UMTS_AUTN_SIZE);
	fr_pair_replace(to_peer, vp);

	/*
	 *	need to include an AT_MAC attribute so that it will get
	 *	calculated.
	 */
	MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_mac));
	fr_pair_replace(to_peer, vp);

	/*
	 *	If we have checkcode data, send that to the peer
	 *	for validation.
	 */
	if (eap_aka_session->checkcode_state) {
		ssize_t	slen;

		slen = fr_sim_crypto_finalise_checkcode(eap_aka_session->checkcode, &eap_aka_session->checkcode_state);
		if (slen < 0) {
			RPEDEBUG("Failed calculating checkcode");
			goto failure;
		}
		eap_aka_session->checkcode_len = slen;

		MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_checkcode));
		fr_pair_value_memcpy(vp, eap_aka_session->checkcode, slen);
	/*
	 *	If we don't have checkcode data, then we exchanged
	 *	no identity packets, so checkcode is zero.
	 */
	} else {
		MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_checkcode));
		eap_aka_session->checkcode_len = 0;
	}
	fr_pair_replace(to_peer, vp);

	/*
	 *	We've sent the challenge so the peer should now be able
	 *	to accept encrypted attributes.
	 */
	eap_aka_session->allow_encrypted = true;

	/*
	 *	Encode the packet
	 */
	if (eap_aka_compose(eap_session) < 0) goto failure;

	return 0;
}
Example #14
0
static int eap_aka_compose(eap_session_t *eap_session)
{
	eap_aka_session_t	*eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t);
	fr_cursor_t		cursor;
	fr_cursor_t		to_encode;
	VALUE_PAIR		*head = NULL, *vp;
	REQUEST			*request = eap_session->request;
	ssize_t			ret;
	fr_sim_encode_ctx_t	encoder_ctx = {
					.root = fr_dict_root(dict_eap_aka),
					.keys = &eap_aka_session->keys,

					.iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
						0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
					.iv_included = false,

					.hmac_md = eap_aka_session->mac_md,
					.eap_packet = eap_session->this_round->request,
					.hmac_extra = NULL,
					.hmac_extra_len = 0
				};

	fr_cursor_init(&cursor, &eap_session->request->reply->vps);
	fr_cursor_init(&to_encode, &head);

	while ((vp = fr_cursor_current(&cursor))) {
		if (!fr_dict_parent_common(encoder_ctx.root, vp->da, true)) {
			fr_cursor_next(&cursor);
			continue;
		}
		vp = fr_cursor_remove(&cursor);

		/*
		 *	Silently discard encrypted attributes until
		 *	the peer should have k_encr.  These can be
		 *	added by policy, and seem to cause
		 *	wpa_supplicant to fail if sent before the challenge.
		 */
		if (!eap_aka_session->allow_encrypted && fr_dict_parent_common(attr_eap_aka_encr_data, vp->da, true)) {
			RWDEBUG("Silently discarding &reply:%s: Encrypted attributes not allowed in this round",
				vp->da->name);
			talloc_free(vp);
			continue;
		}

		fr_cursor_append(&to_encode, vp);
	}

	RDEBUG2("Encoding EAP-AKA attributes");
	log_request_pair_list(L_DBG_LVL_2, request, head, NULL);

	eap_session->this_round->request->type.num = eap_aka_session->type;
	eap_session->this_round->request->id = eap_aka_session->aka_id++ & 0xff;
	eap_session->this_round->set_request_id = true;

	ret = fr_sim_encode(eap_session->request, head, &encoder_ctx);
	fr_cursor_head(&to_encode);
	fr_cursor_free_list(&to_encode);

	if (ret < 0) {
		RPEDEBUG("Failed encoding EAP-AKA data");
		return -1;
	}
	return 0;
}

/** Send an EAP-AKA identity request to the supplicant
 *
 * There are three types of user identities that can be implemented
 * - Permanent identities such as [email protected]
 *   Permanent identities can be identified by the leading zero followed by
 *   by 15 digits (the IMSI number).
 * - Ephemeral identities (pseudonyms).  These are identities assigned for
 *   identity privacy so the user can't be tracked.  These can identities
 *   can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses'
 *   document section 14, where a set of up to 16 encryption keys are used
 *   to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely
 *   randomised and stored in a datastore.
 * - A fast resumption ID which resolves to data used for fast resumption.
 *
 * In order to perform full authentication the original IMSI is required for
 * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym,
 * or can't perform fast resumption, we need to request the full identity from
 * the supplicant.
 *
 * @param[in] eap_session	to continue.
 * @return
 *	- 0 on success.
 *	- <0 on failure.
 */
static int eap_aka_send_identity_request(eap_session_t *eap_session)
{
	REQUEST			*request = eap_session->request;
	eap_aka_session_t	*eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t);
	VALUE_PAIR		*vp;
	RADIUS_PACKET		*packet;
	fr_cursor_t		cursor;

	RDEBUG2("Sending AKA-Identity (%s)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>"));
	eap_session->this_round->request->code = FR_EAP_CODE_REQUEST;
	eap_aka_session->allow_encrypted = false;	/* In case this is after failed fast-resumption */

	packet = request->reply;
	fr_cursor_init(&cursor, &packet->vps);

	/*
	 *	Set the subtype to identity request
	 */
	vp = fr_pair_afrom_da(packet, attr_eap_aka_subtype);
	vp->vp_uint16 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_IDENTITY;
	fr_cursor_append(&cursor, vp);

	/*
	 *	Select the right type of identity request attribute
	 */
	switch (eap_aka_session->id_req) {
	case SIM_ANY_ID_REQ:
		vp = fr_pair_afrom_da(packet, attr_eap_aka_any_id_req);
		break;

	case SIM_PERMANENT_ID_REQ:
		vp = fr_pair_afrom_da(packet, attr_eap_aka_permanent_id_req);
		break;

	case SIM_FULLAUTH_ID_REQ:
		vp = fr_pair_afrom_da(packet, attr_eap_aka_fullauth_id_req);
		break;

	default:
		rad_assert(0);
	}
	vp->vp_bool = true;
	fr_cursor_append(&cursor, vp);

	/*
	 *	Encode the packet
	 */
	if (eap_aka_compose(eap_session) < 0) {
	failure:
		fr_pair_list_free(&packet->vps);
		return -1;
	}

	/*
	 *	Digest the packet contents, updating our checkcode.
	 */
	if (!eap_aka_session->checkcode_state &&
	    fr_sim_crypto_init_checkcode(eap_aka_session, &eap_aka_session->checkcode_state,
	    				 eap_aka_session->checkcode_md) < 0) {
		RPEDEBUG("Failed initialising checkcode");
		goto failure;
	}
	if (fr_sim_crypto_update_checkcode(eap_aka_session->checkcode_state, eap_session->this_round->request) < 0) {
		RPEDEBUG("Failed updating checkcode");
		goto failure;
	}

	return 0;
}
/*
 *	Generic function for failing between a bunch of queries.
 *
 *	Uses the same principle as rlm_linelog, expanding the 'reference' config
 *	item using xlat to figure out what query it should execute.
 *
 *	If the reference matches multiple config items, and a query fails or
 *	doesn't update any rows, the next matching config item is used.
 *
 */
static int acct_redundant(rlm_sql_t *inst, REQUEST *request, sql_acct_section_t *section)
{
	rlm_rcode_t		rcode = RLM_MODULE_OK;

	rlm_sql_handle_t	*handle = NULL;
	int			sql_ret;
	int			numaffected = 0;

	CONF_ITEM		*item;
	CONF_PAIR 		*pair;
	char const		*attr = NULL;
	char const		*value;

	char			path[MAX_STRING_LEN];
	char			*p = path;
	char			*expanded = NULL;

	rad_assert(section);

	if (section->reference[0] != '.') {
		*p++ = '.';
	}

	if (radius_xlat(p, sizeof(path) - (p - path), request, section->reference, NULL, NULL) < 0) {
		rcode = RLM_MODULE_FAIL;

		goto finish;
	}

	item = cf_reference_item(NULL, section->cs, path);
	if (!item) {
		rcode = RLM_MODULE_FAIL;

		goto finish;
	}

	if (cf_item_is_section(item)){
		REDEBUG("Sections are not supported as references");
		rcode = RLM_MODULE_FAIL;

		goto finish;
	}

	pair = cf_itemtopair(item);
	attr = cf_pair_attr(pair);

	RDEBUG2("Using query template '%s'", attr);

	handle = sql_get_socket(inst);
	if (!handle) {
		rcode = RLM_MODULE_FAIL;

		goto finish;
	}

	sql_set_user(inst, request, NULL);

	while (true) {
		value = cf_pair_value(pair);
		if (!value) {
			RDEBUG("Ignoring null query");
			rcode = RLM_MODULE_NOOP;

			goto finish;
		}

		if (radius_axlat(&expanded, request, value, sql_escape_func, inst) < 0) {
			rcode = RLM_MODULE_FAIL;

			goto finish;
		}

		if (!*expanded) {
			RDEBUG("Ignoring null query");
			rcode = RLM_MODULE_NOOP;
			talloc_free(expanded);

			goto finish;
		}

		rlm_sql_query_log(inst, request, section, expanded);

		/*
		 *  If rlm_sql_query cannot use the socket it'll try and
		 *  reconnect. Reconnecting will automatically release
		 *  the current socket, and try to select a new one.
		 *
		 *  If we get RLM_SQL_RECONNECT it means all connections in the pool
		 *  were exhausted, and we couldn't create a new connection,
		 *  so we do not need to call sql_release_socket.
		 */
		sql_ret = rlm_sql_query(&handle, inst, expanded);
		TALLOC_FREE(expanded);

		if (sql_ret == RLM_SQL_RECONNECT) {
			rcode = RLM_MODULE_FAIL;

			goto finish;
		}
		rad_assert(handle);

		/*
		 *  Assume all other errors are incidental, and just meant our
		 *  operation failed and its not a client or SQL syntax error.
		 *
		 *  @fixme We should actually be able to distinguish between key
		 *  constraint violations (which we expect) and other errors.
		 */
		if (sql_ret == RLM_SQL_OK) {
			numaffected = (inst->module->sql_affected_rows)(handle, inst->config);
			if (numaffected > 0) {
				break;	/* A query succeeded, were done! */
			}

			RDEBUG("No records updated");
		}

		(inst->module->sql_finish_query)(handle, inst->config);

		/*
		 *  We assume all entries with the same name form a redundant
		 *  set of queries.
		 */
		pair = cf_pair_find_next(section->cs, pair, attr);

		if (!pair) {
			RDEBUG("No additional queries configured");
			rcode = RLM_MODULE_NOOP;

			goto finish;
		}

		RDEBUG("Trying next query...");
	}

	(inst->module->sql_finish_query)(handle, inst->config);

	finish:
	talloc_free(expanded);
	sql_release_socket(inst, handle);

	return rcode;
}
Example #16
0
/** 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(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
				       LDAPMessage *entry, char const *attr)
{
	rlm_rcode_t rcode = RLM_MODULE_OK;

	struct berval **values;
	size_t value_len = 0;
	TALLOC_CTX *value_pool;

	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, **vps;
	TALLOC_CTX *ctx;
	vp_cursor_t 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);

	vps = radius_list(request, PAIR_LIST_CONTROL);
	ctx = radius_list_ctx(request, PAIR_LIST_CONTROL);
	fr_cursor_init(&cursor, vps);

	/*
	 *	Avoid allocing buffers for each value.
	 *
	 *	The old code used ldap_get_values, which was likely doing
	 *	a very similar thing internally to produce \0 terminated
	 *	buffers from bervalues.
	 */
	for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) value_len += values[i]->bv_len + 1;
	value_pool = talloc_pool(request, value_len);

	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 = pairalloc(ctx, inst->cache_da));
				pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len);
				fr_cursor_insert(&cursor, vp);

				RDEBUG("Added %s with value \"%s\" to control list", inst->cache_da->name,
				       vp->vp_strvalue);
			/*
			 *	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_pool, values[i]);
			}
		}

		if (inst->cacheable_group_name) {
			/*
			 *	The easy case, we're caching names and we got a name.
			 */
			if (!is_dn) {
				MEM(vp = pairalloc(ctx, inst->cache_da));
				pairstrncpy(vp, values[i]->bv_val, values[i]->bv_len);
				fr_cursor_insert(&cursor, vp);

				RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name,
				       vp->vp_strvalue);
			/*
			 *	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_pool, 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_pool);

					return rcode;
				}

				MEM(vp = pairalloc(ctx, inst->cache_da));
				pairstrncpy(vp, name, talloc_array_length(name) - 1);
				fr_cursor_insert(&cursor, vp);

				RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, name);
				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_pool);

	if (rcode != RLM_MODULE_OK) return rcode;

	dn_p = group_dn;
	while (*dn_p) {
		MEM(vp = pairalloc(ctx, inst->cache_da));
		pairstrcpy(vp, *dn_p);
		fr_cursor_insert(&cursor, vp);

		RDEBUG("Added control:%s with value \"%s\"", inst->cache_da->name, *dn_p);
		ldap_memfree(*dn_p);

		dn_p++;
	}

	return rcode;
}
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->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;
}
Example #18
0
/** Query the LDAP directory to check if a group object includes a user object as a member
 *
 * @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] check vp containing the group value (name or dn).
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_check_groupobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
					    VALUE_PAIR *check)

{
	ldap_rcode_t	status;

	char		base_dn[LDAP_MAX_DN_STR_LEN + 1];
	char 		filter[LDAP_MAX_FILTER_STR_LEN + 1];
	char const	*dn = base_dn;

	int		ret;

	rad_assert(inst->groupobj_base_dn);

	switch (check->op) {
	case T_OP_CMP_EQ:
	case T_OP_CMP_FALSE:
	case T_OP_CMP_TRUE:
	case T_OP_REG_EQ:
	case T_OP_REG_NE:
		break;

	default:
		REDEBUG("Operator \"%s\" not allowed for LDAP group comparisons",
			fr_int2str(fr_tokens, check->op, "<INVALID>"));
		return 1;
	}

	RDEBUG2("Checking for user in group objects");

	if (rlm_ldap_is_dn(check->vp_strvalue, check->vp_length)) {
		char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };

		RINDENT();
		ret = rlm_ldap_xlat_filter(request,
					   filters, sizeof(filters) / sizeof(*filters),
					   filter, sizeof(filter));
		REXDENT();

		if (ret < 0) return RLM_MODULE_INVALID;

		dn = check->vp_strvalue;
	} else {
		char name_filter[LDAP_MAX_FILTER_STR_LEN];
		char const *filters[] = { name_filter, inst->groupobj_filter, inst->groupobj_membership_filter };

		if (!inst->groupobj_name_attr) {
			REDEBUG("Told to search for group by name, but missing 'group.name_attribute' "
				"directive");

			return RLM_MODULE_INVALID;
		}

		snprintf(name_filter, sizeof(name_filter), "(%s=%s)", inst->groupobj_name_attr, check->vp_strvalue);
		RINDENT();
		ret = rlm_ldap_xlat_filter(request,
					   filters, sizeof(filters) / sizeof(*filters),
					   filter, sizeof(filter));
		REXDENT();
		if (ret < 0) return RLM_MODULE_INVALID;


		/*
		 *	rlm_ldap_find_user does this, too.  Oh well.
		 */
		RINDENT();
		ret = radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn,
				  rlm_ldap_escape_func, NULL);
		REXDENT();
		if (ret < 0) {
			REDEBUG("Failed creating base_dn");

			return RLM_MODULE_INVALID;
		}
	}

	RINDENT();
	status = rlm_ldap_search(inst, request, pconn, dn, inst->groupobj_scope, filter, NULL, NULL);
	REXDENT();
	switch (status) {
	case LDAP_PROC_SUCCESS:
		RDEBUG("User found in group object \"%s\"", dn);
		break;

	case LDAP_PROC_NO_RESULT:
		return RLM_MODULE_NOTFOUND;

	default:
		return RLM_MODULE_FAIL;
	}

	return RLM_MODULE_OK;
}
/*
 *	Check NTLM authentication direct to winbind via
 *	Samba's libwbclient library
 *
 *	Returns:
 *	 0    success
 *	 -1   auth failure
 *	 -648 password expired
 */
int do_auth_wbclient(rlm_mschap_t *inst, REQUEST *request,
		     uint8_t const *challenge, uint8_t const *response,
		     uint8_t nthashhash[NT_DIGEST_LENGTH])
{
	int rcode = -1;
	struct wbcContext *wb_ctx;
	struct wbcAuthUserParams authparams;
	wbcErr err;
	int len;
	struct wbcAuthUserInfo *info = NULL;
	struct wbcAuthErrorInfo *error = NULL;
	char user_name_buf[500];
	char domain_name_buf[500];
	uint8_t resp[NT_LENGTH];

	/*
	 * Clear the auth parameters - this is important, as
	 * there are options that will cause wbcAuthenticateUserEx
	 * to bomb out if not zero.
	 */
	memset(&authparams, 0, sizeof(authparams));

	/*
	 * wb_username must be set for this function to be called
	 */
	rad_assert(inst->wb_username);

	/*
	 * Get the username and domain from the configuration
	 */
	len = tmpl_expand(&authparams.account_name, user_name_buf, sizeof(user_name_buf),
			  request, inst->wb_username, NULL, NULL);
	if (len < 0) {
		REDEBUG2("Unable to expand winbind_username");
		goto done;
	}

	if (inst->wb_domain) {
		len = tmpl_expand(&authparams.domain_name, domain_name_buf, sizeof(domain_name_buf),
				  request, inst->wb_domain, NULL, NULL);
		if (len < 0) {
			REDEBUG2("Unable to expand winbind_domain");
			goto done;
		}
	} else {
		RWDEBUG2("No domain specified; authentication may fail because of this");
	}


	/*
	 * Build the wbcAuthUserParams structure with what we know
	 */
	authparams.level = WBC_AUTH_USER_LEVEL_RESPONSE;
	authparams.password.response.nt_length = NT_LENGTH;

	memcpy(resp, response, NT_LENGTH);
	authparams.password.response.nt_data = resp;

	memcpy(authparams.password.response.challenge, challenge,
	       sizeof(authparams.password.response.challenge));


	/*
	 * Send auth request across to winbind
	 */
	wb_ctx = fr_connection_get(inst->wb_pool);
	if (wb_ctx == NULL) {
		RERROR("Unable to get winbind connection from pool");
		goto done;
	}

	RDEBUG2("sending authentication request user='******' domain='%s'", authparams.account_name,
									authparams.domain_name);

	err = wbcCtxAuthenticateUserEx(wb_ctx, &authparams, &info, &error);

	fr_connection_release(inst->wb_pool, wb_ctx);


	/*
	 * Try and give some useful feedback on what happened. There are only
	 * a few errors that can actually be returned from wbcCtxAuthenticateUserEx.
	 */
	switch (err) {
	case WBC_ERR_SUCCESS:
		rcode = 0;
		RDEBUG2("Authenticated successfully");
		/* Grab the nthashhash from the result */
		memcpy(nthashhash, info->user_session_key, NT_DIGEST_LENGTH);
		break;
	case WBC_ERR_WINBIND_NOT_AVAILABLE:
		RERROR("Unable to contact winbind!");
		RDEBUG2("Check that winbind is running and that FreeRADIUS has");
		RDEBUG2("permission to connect to the winbind privileged socket.");
		break;
	case WBC_ERR_DOMAIN_NOT_FOUND:
		REDEBUG2("Domain not found");
		break;
	case WBC_ERR_AUTH_ERROR:
		if (!error) {
			REDEBUG2("Authentication failed");
			break;
		}

		/*
		 * The password needs to be changed, so set rcode appropriately.
		 */
		if (error->nt_status & NT_STATUS_PASSWORD_EXPIRED ||
		    error->nt_status & NT_STATUS_PASSWORD_MUST_CHANGE) {
			rcode = -648;
		}

		/*
		 * Return the NT_STATUS human readable error string, if there is one.
		 */
		if (error->display_string) {
			REDEBUG2("%s [0x%X]", error->display_string, error->nt_status);
		} else {
			REDEBUG2("Authentication failed [0x%X]", error->nt_status);
		}
		break;
	default:
		/*
		 * Only errors left are 
		 *   WBC_ERR_INVALID_PARAM
		 *   WBC_ERR_NO_MEMORY
		 * neither of which are particularly likely.
		 */
		if (error && error->display_string) {
			REDEBUG2("libwbclient error: wbcErr %d (%s)", err, error->display_string);
		} else {
			REDEBUG2("libwbclient error: wbcErr %d", err);
		}
		break;
	}


done:
	if (info) wbcFreeMemory(info);
	if (error) wbcFreeMemory(error);

	return rcode;
}
Example #20
0
/** Query the LDAP directory to check if a user object is a member of a group
 *
 * @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] dn of user object.
 * @param[in] check vp containing the group value (name or dn).
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_check_userobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
					   char const *dn, VALUE_PAIR *check)
{
	rlm_rcode_t	rcode = RLM_MODULE_NOTFOUND, ret;
	ldap_rcode_t	status;
	bool		name_is_dn = false, value_is_dn = false;

	LDAPMessage     *result = NULL;
	LDAPMessage     *entry = NULL;
	struct berval	**values = NULL;

	char const	*attrs[] = { inst->userobj_membership_attr, NULL };
	int		i, count, ldap_errno;

	RDEBUG2("Checking user object's %s attributes", inst->userobj_membership_attr);
	RINDENT();
	status = rlm_ldap_search(inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, &result);
	REXDENT();
	switch (status) {
	case LDAP_PROC_SUCCESS:
		break;

	case LDAP_PROC_NO_RESULT:
		RDEBUG("Can't check membership attributes, user object not found");

		rcode = RLM_MODULE_NOTFOUND;

		/* FALL-THROUGH */
	default:
		goto finish;
	}

	entry = ldap_first_entry((*pconn)->handle, result);
	if (!entry) {
		ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
		REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));

		rcode = RLM_MODULE_FAIL;

		goto finish;
	}

	values = ldap_get_values_len((*pconn)->handle, entry, inst->userobj_membership_attr);
	if (!values) {
		RDEBUG("No group membership attribute(s) found in user object");

		goto finish;
	}

	/*
	 *	Loop over the list of groups the user is a member of,
	 *	looking for a match.
	 */
	name_is_dn = rlm_ldap_is_dn(check->vp_strvalue, check->vp_length);
	count = ldap_count_values_len(values);
	for (i = 0; i < count; i++) {
		value_is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len);

		RDEBUG2("Processing %s value \"%.*s\" as a %s", inst->userobj_membership_attr,
			(int)values[i]->bv_len, values[i]->bv_val, value_is_dn ? "DN" : "group name");

		/*
		 *	Both literal group names, do case sensitive comparison
		 */
		if (!name_is_dn && !value_is_dn) {
			if ((check->vp_length == values[i]->bv_len) &&
			    (memcmp(values[i]->bv_val, check->vp_strvalue, values[i]->bv_len) == 0)) {
				RDEBUG("User found in group \"%s\". Comparison between membership: name, check: name",
				       check->vp_strvalue);
				rcode = RLM_MODULE_OK;

				goto finish;
			}

			continue;
		}

		/*
		 *	Both DNs, do case insensitive, binary safe comparison
		 */
		if (name_is_dn && value_is_dn) {
			if (check->vp_length == values[i]->bv_len) {
				int j;

				for (j = 0; j < (int)values[i]->bv_len; j++) {
					if (tolower(values[i]->bv_val[j]) != tolower(check->vp_strvalue[j])) break;
				}
				if (j == (int)values[i]->bv_len) {
					RDEBUG("User found in group DN \"%s\". "
					       "Comparison between membership: dn, check: dn", check->vp_strvalue);
					rcode = RLM_MODULE_OK;

					goto finish;
				}
			}

			continue;
		}

		/*
		 *	If the value is not a DN, and the name we were given is a dn
		 *	convert the value to a DN and do a comparison.
		 */
		if (!value_is_dn && name_is_dn) {
			char *resolved;
			bool eq = false;

			RINDENT();
			ret = rlm_ldap_group_dn2name(inst, request, pconn, check->vp_strvalue, &resolved);
			REXDENT();
			if (ret != RLM_MODULE_OK) {
				rcode = ret;
				goto finish;
			}

			if (((talloc_array_length(resolved) - 1) == values[i]->bv_len) &&
			    (memcmp(values[i]->bv_val, resolved, values[i]->bv_len) == 0)) eq = true;
			talloc_free(resolved);
			if (eq) {
				RDEBUG("User found in group \"%.*s\". Comparison between membership: name, check: name "
				       "(resolved from DN \"%s\")", (int)values[i]->bv_len,
				       values[i]->bv_val, check->vp_strvalue);
				rcode = RLM_MODULE_OK;

				goto finish;
			}

			continue;
		}

		/*
		 *	We have a value which is a DN, and a check item which specifies the name of a group,
		 *	convert the value to a name so we can do a comparison.
		 */
		if (value_is_dn && !name_is_dn) {
			char *resolved;
			char *value;
			bool eq = false;

			value = rlm_ldap_berval_to_string(request, values[i]);
			RINDENT();
			ret = rlm_ldap_group_dn2name(inst, request, pconn, value, &resolved);
			REXDENT();
			talloc_free(value);
			if (ret != RLM_MODULE_OK) {
				rcode = ret;
				goto finish;
			}

			if (((talloc_array_length(resolved) - 1) == check->vp_length) &&
			    (memcmp(check->vp_strvalue, resolved, check->vp_length) == 0)) eq = true;
			talloc_free(resolved);
			if (eq) {
				RDEBUG("User found in group \"%s\". Comparison between membership: name "
				       "(resolved from DN \"%s\"), check: name", check->vp_strvalue, value);
				rcode = RLM_MODULE_OK;

				goto finish;
			}

			continue;
		}
		rad_assert(0);
	}

finish:
	if (values) ldap_value_free_len(values);
	if (result) ldap_msgfree(result);

	return rcode;
}
Example #21
0
/*
 *	Do authentication, by letting EAP-TLS do most of the work.
 */
static int eapttls_authenticate(void *arg, EAP_HANDLER *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);
			pairadd(&handler->request->reply->vps,
				t->accept_vps);
			t->accept_vps = NULL;
		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);
		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;
}
Example #22
0
/** Copy packet to multiple servers
 *
 * Create a duplicate of the packet and send it to a list of realms
 * defined by the presence of the Replicate-To-Realm VP in the control
 * list of the current request.
 * 
 * This is pretty hacky and is 100% fire and forget. If you're looking
 * to forward authentication requests to multiple realms and process
 * the responses, this function will not allow you to do that.
 *
 * @param[in] instance 	of this module.
 * @param[in] request 	The current request.
 * @param[in] list	of attributes to copy to the duplicate packet.
 * @param[in] code	to write into the code field of the duplicate packet.
 * @return RCODE fail on error, invalid if list does not exist, noop if no
 * 	   replications succeeded, else ok.
 */
static int replicate_packet(void *instance, REQUEST *request,
			    pair_lists_t list, unsigned int code)
{
	int rcode = RLM_MODULE_NOOP;
	VALUE_PAIR *vp, **vps, *last;
	home_server *home;
	REALM *realm;
	home_pool_t *pool;
	RADIUS_PACKET *packet = NULL;

	instance = instance;	/* -Wunused */
	last = request->config_items;

	/*
	 *	Send as many packets as necessary to different
	 *	destinations.
	 */
	while (1) {
		vp = pairfind(last, PW_REPLICATE_TO_REALM, 0, TAG_ANY);
		if (!vp) break;

		last = vp->next;

		realm = realm_find2(vp->vp_strvalue);
		if (!realm) {
			RDEBUG2("ERROR: Cannot Replicate to unknown realm %s", realm);
			continue;
		}
		
		/*
		 *	We shouldn't really do this on every loop.
		 */
		switch (request->packet->code) {
		default:
			RDEBUG2("ERROR: Cannot replicate unknown packet code %d",
				request->packet->code);
			cleanup(packet);
			return RLM_MODULE_FAIL;
		
		case PW_AUTHENTICATION_REQUEST:
			pool = realm->auth_pool;
			break;
			
#ifdef WITH_ACCOUNTING
			
		case PW_ACCOUNTING_REQUEST:
			pool = realm->acct_pool;
			break;
#endif
			
#ifdef WITH_COA
		case PW_COA_REQUEST:
		case PW_DISCONNECT_REQUEST:
			pool = realm->acct_pool;
			break;
#endif
		}
		
		if (!pool) {
			RDEBUG2(" WARNING: Cancelling replication to Realm %s, as the realm is local.", realm->name);
			continue;
		}
		
		home = home_server_ldb(realm->name, pool, request);
		if (!home) {
			RDEBUG2("ERROR: Failed to find live home server for realm %s",
				realm->name);
			continue;
		}
		
		/*
		 *	For replication to multiple servers we re-use the packet
		 *	we built here.
		 */
		if (!packet) {
			packet = rad_alloc(1);
			if (!packet) return RLM_MODULE_FAIL;
			packet->sockfd = -1;
			packet->code = code;
			packet->id = fr_rand() & 0xff;

			packet->sockfd = fr_socket(&home->src_ipaddr, 0);
			if (packet->sockfd < 0) {
				RDEBUG("ERROR: Failed opening socket: %s", fr_strerror());
				rcode = RLM_MODULE_FAIL;
				goto done;
			}
			
			vps = radius_list(request, list);
			if (!vps) {
				RDEBUG("WARNING: List '%s' doesn't exist for "
				       "this packet", fr_int2str(pair_lists,
				       list, "¿unknown?"));
				rcode = RLM_MODULE_INVALID;
				goto done;
			}
			
			/*
			 *	Don't assume the list actually contains any
			 *	attributes.
			 */
			if (*vps) {
				packet->vps = paircopy(*vps);
				if (!packet->vps) {
					RDEBUG("ERROR: Out of memory!");
					rcode = RLM_MODULE_FAIL;
					goto done;
				}
			}
			


			/*
			 *	For CHAP, create the CHAP-Challenge if
			 *	it doesn't exist.
			 */
			if ((code == PW_AUTHENTICATION_REQUEST) &&
			    (pairfind(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY) != NULL) &&
			    (pairfind(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY) == NULL)) {
				vp = radius_paircreate(request, &packet->vps,
						       PW_CHAP_CHALLENGE, 0,
						       PW_TYPE_OCTETS);
				vp->length = AUTH_VECTOR_LEN;
				memcpy(vp->vp_strvalue, request->packet->vector,
				       AUTH_VECTOR_LEN);
			}
		} else {
			size_t i;

			for (i = 0; i < sizeof(packet->vector); i++) {
				packet->vector[i] = fr_rand() & 0xff;
			}

			packet->id++;
			free(packet->data);
			packet->data = NULL;
			packet->data_len = 0;
		}

		/*
		 *	(Re)-Write these.
		 */
		packet->dst_ipaddr = home->ipaddr;
		packet->dst_port = home->port;
		memset(&packet->src_ipaddr, 0, sizeof(packet->src_ipaddr));
		packet->src_port = 0;
		
		/*
		 *	Encode, sign and then send the packet.
		 */
		RDEBUG("Replicating list '%s' to Realm '%s'",
		       fr_int2str(pair_lists, list, "¿unknown?"),realm->name);
		if (rad_send(packet, NULL, home->secret) < 0) {
			RDEBUG("ERROR: Failed replicating packet: %s",
			       fr_strerror());
			rcode = RLM_MODULE_FAIL;
			goto done;
		}

		/*
		 *	We've sent it to at least one destination.
		 */
		rcode = RLM_MODULE_OK;
	}
	
	done:
	
	cleanup(packet);
	return rcode;
}
Example #23
0
/** 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.
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_cacheable_groupobj(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn)
{
	rlm_rcode_t rcode = RLM_MODULE_OK;
	ldap_rcode_t status;
	int ldap_errno;
	
	char **vals;
	
	LDAPMessage *result = NULL;
	LDAPMessage *entry;

	char base_dn[LDAP_MAX_DN_STR_LEN];
	
	char const *filters[] = { inst->groupobj_filter, inst->groupobj_membership_filter };
	char filter[LDAP_MAX_FILTER_STR_LEN + 1];
	
	char const *attrs[] = { inst->groupobj_name_attr, NULL };
	
	char *dn;

	if (!inst->groupobj_membership_filter) {
		RDEBUG2("Skipping caching group objects as directive 'group.membership_filter' is not set");
	
		return RLM_MODULE_OK;
	}
	
	if (rlm_ldap_xlat_filter(request,
				 filters, sizeof(filters) / sizeof(*filters),
				 filter, sizeof(filter)) < 0) {
		return RLM_MODULE_INVALID;
	}

	if (radius_xlat(base_dn, sizeof(base_dn), request, inst->groupobj_base_dn, rlm_ldap_escape_func, NULL) < 0) {
		REDEBUG("Failed creating base_dn");
	
		return RLM_MODULE_INVALID;
	}

	status = rlm_ldap_search(inst, request, pconn, base_dn, inst->groupobj_scope, filter, attrs, &result);
	switch (status) {
		case LDAP_PROC_SUCCESS:
			break;
		case LDAP_PROC_NO_RESULT:
			RDEBUG2("No cacheable group memberships found in group objects");
		default:
			goto finish;
	}

	entry = ldap_first_entry((*pconn)->handle, result);
	if (!entry) {
		ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
		REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
			 
		goto finish;
	}
	
	do {
		if (inst->cacheable_group_dn) {
			dn = ldap_get_dn((*pconn)->handle, entry);
			pairmake(request, &request->config_items, inst->group_da->name, dn, T_OP_ADD);
			RDEBUG("Added %s with value \"%s\" to control list", inst->group_da->name, dn);
			ldap_memfree(dn);
		}
		
		if (inst->cacheable_group_name) {		
			vals = ldap_get_values((*pconn)->handle, entry, inst->groupobj_name_attr);
			if (!vals) {
				continue;
			}
			
			pairmake(request, &request->config_items, inst->group_da->name, *vals, T_OP_ADD);
			RDEBUG("Added %s with value \"%s\" to control list", inst->group_da->name, *vals);
			
			ldap_value_free(vals);
		}		
	} while((entry = ldap_next_entry((*pconn)->handle, entry)));
	
	finish:
	if (result) {
		ldap_msgfree(result);
	}
	
	return rcode;
}
int eaptls_success(EAP_HANDLER *handler, int peap_flag)
{
	EAPTLS_PACKET	reply;
	VALUE_PAIR *vp, *vps = NULL;
	REQUEST *request = handler->request;
	tls_session_t *tls_session = handler->opaque;

	handler->finished = TRUE;
	reply.code = EAPTLS_SUCCESS;
	reply.length = TLS_HEADER_LEN;
	reply.flags = peap_flag;
	reply.data = NULL;
	reply.dlen = 0;

	/*
	 *	If there's no session resumption, delete the entry
	 *	from the cache.  This means either it's disabled
	 *	globally for this SSL context, OR we were told to
	 *	disable it for this user.
	 *
	 *	This also means you can't turn it on just for one
	 *	user.
	 */
	if ((!tls_session->allow_session_resumption) ||
	    (((vp = pairfind(request->config_items, 1127)) != NULL) &&
	     (vp->vp_integer == 0))) {
		SSL_CTX_remove_session(tls_session->ctx,
				       tls_session->ssl->session);
		tls_session->allow_session_resumption = 0;

		/*
		 *	If we're in a resumed session and it's
		 *	not allowed, 
		 */
		if (SSL_session_reused(tls_session->ssl)) {
			RDEBUG("FAIL: Forcibly stopping session resumption as it is not allowed.");
			return eaptls_fail(handler, peap_flag);
		}
		
		/*
		 *	Else resumption IS allowed, so we store the
		 *	user data in the cache.
		 */
	} else if (!SSL_session_reused(tls_session->ssl)) {
		RDEBUG2("Saving response in the cache");
		
		vp = paircopy2(request->reply->vps, PW_USER_NAME);
		if (vp) pairadd(&vps, vp);
		
		vp = paircopy2(request->packet->vps, PW_STRIPPED_USER_NAME);
		if (vp) pairadd(&vps, vp);

		vp = paircopy2(request->reply->vps, PW_CHARGEABLE_USER_IDENTITY);
		if (vp) pairadd(&vps, vp);
		
		vp = paircopy2(request->reply->vps, PW_CACHED_SESSION_POLICY);
		if (vp) pairadd(&vps, vp);

		if (handler->certs) {
			pairadd(&vps, paircopy(handler->certs));
		}

		if (vps) {
			SSL_SESSION_set_ex_data(tls_session->ssl->session,
						eaptls_session_idx, vps);
		} else {
			RDEBUG2("WARNING: No information to cache: session caching will be disabled for this session.");
			SSL_CTX_remove_session(tls_session->ctx,
					       tls_session->ssl->session);
		}

		/*
		 *	Else the session WAS allowed.  Copy the cached
		 *	reply.
		 */
	} else {
	       
		vps = SSL_SESSION_get_ex_data(tls_session->ssl->session,
					     eaptls_session_idx);
		if (!vps) {
			RDEBUG("WARNING: No information in cached session!");
			return eaptls_fail(handler, peap_flag);
		} else {
			RDEBUG("Adding cached attributes:");
			debug_pair_list(vps);

			for (vp = vps; vp != NULL; vp = vp->next) {
				/*
				 *	TLS-* attrs get added back to
				 *	the request list.
				 */
				if ((vp->attribute >= 1910) &&
				    (vp->attribute < 1929)) {
					pairadd(&request->packet->vps,
						paircopyvp(vp));
				} else {
					pairadd(&request->reply->vps,
						paircopyvp(vp));
				}
			}

			/*
			 *	Mark the request as resumed.
			 */
			vp = pairmake("EAP-Session-Resumed", "1", T_OP_SET);
			if (vp) pairadd(&request->packet->vps, vp);
		}
	}

	/*
	 *	Call compose AFTER checking for cached data.
	 */
	eaptls_compose(handler->eap_ds, &reply);

	/*
	 *	Automatically generate MPPE keying material.
	 */
	if (tls_session->prf_label) {
		eaptls_gen_mppe_keys(&handler->request->reply->vps,
				     tls_session->ssl, tls_session->prf_label);
	} else {
		RDEBUG("WARNING: Not adding MPPE keys because there is no PRF label");
	}

	eaptls_gen_eap_key(tls_session->ssl,
			   handler->eap_type, &handler->request->reply->vps);
	return 1;
}
Example #25
0
/** Query the LDAP directory to check if a user object is a member of a group
 *
 * @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] dn of user object.
 * @param[in] check vp containing the group value (name or dn).
 * @return One of the RLM_MODULE_* values.
 */
rlm_rcode_t rlm_ldap_check_userobj_dynamic(ldap_instance_t const *inst, REQUEST *request, ldap_handle_t **pconn,
					   char const *dn, VALUE_PAIR *check)
{
	rlm_rcode_t	rcode = RLM_MODULE_NOTFOUND;
	ldap_rcode_t	status;
	int		name_is_dn = false, value_is_dn = false;
	
	LDAPMessage     *result = NULL;
	LDAPMessage     *entry = NULL;
	char		**vals = NULL;
	
	char const     	*name = check->vp_strvalue;
	
	char const	*attrs[] = { inst->userobj_membership_attr, NULL };
	int		i, count, ldap_errno; 
		
	RDEBUG2("Checking user object membership (%s) attributes", inst->userobj_membership_attr);

	status = rlm_ldap_search(inst, request, pconn, dn, LDAP_SCOPE_BASE, NULL, attrs, &result);
	switch (status) {
		case LDAP_PROC_SUCCESS:
			break;
		case LDAP_PROC_NO_RESULT:
			RDEBUG("Can't check membership attributes, user object not found");
			
			rcode = RLM_MODULE_NOTFOUND;
			
			/* FALL-THROUGH */
		default:
			goto finish;
	}

	entry = ldap_first_entry((*pconn)->handle, result);
	if (!entry) {
		ldap_get_option((*pconn)->handle, LDAP_OPT_RESULT_CODE, &ldap_errno);
		REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno));
		
		rcode = RLM_MODULE_FAIL;
		
		goto finish;
	}

	vals = ldap_get_values((*pconn)->handle, entry, inst->userobj_membership_attr);
	if (!vals) {
		RDEBUG("No group membership attribute(s) found in user object");
		
		goto finish;
	}

	/*
	 *	Loop over the list of groups the user is a member of,
	 *	looking for a match.
	 */
	name_is_dn = rlm_ldap_is_dn(name);
	count = ldap_count_values(vals);
	for (i = 0; i < count; i++) {
		value_is_dn = rlm_ldap_is_dn(vals[i]);
	
		RDEBUG2("Processing group membership value \"%s\"", vals[i]);

		/*
		 *	Both literal group names, do case sensitive comparison
		 */
		if (!name_is_dn && !value_is_dn) {
			if (strcmp(vals[i], name) == 0){
				RDEBUG("User found. Comparison between membership: name, check: name]");
				rcode = RLM_MODULE_OK;
				
				goto finish;
			}
		
			continue;
		}

		/*
		 *	Both DNs, do case insensitive comparison
		 */
		if (name_is_dn && value_is_dn) {
			if (strcasecmp(vals[i], name) == 0){
				RDEBUG("User found. Comparison between membership: dn, check: dn");
				rcode = RLM_MODULE_OK;
				
				goto finish;
			}
		
			continue;
		}
	
		/*
		 *	If the value is not a DN, and the name we were given is a dn
		 *	convert the value to a DN and do a comparison.
		 */
		if (!value_is_dn && name_is_dn) {
			char *name_dn;
			int eq;
			
			rcode = rlm_ldap_group_dn2name(inst, request, pconn, name, &name_dn);
			if (rcode != RLM_MODULE_OK) {
				goto finish;
			}
			
			eq = strcmp(vals[i], name_dn);
			ldap_memfree(name_dn);
			if (eq == 0){
				RDEBUG("User found. Comparison between membership: name, check: name "
				       "(resolved from DN)");
				rcode = RLM_MODULE_OK;
				
				goto finish;
			}	

			continue;
		}

		/*
		 *	We have a value which is a DN, and a check item which specifies the name of a group,
		 *	convert the value to a name so we can do a comparison.
		 */
		if (value_is_dn && !name_is_dn) {
			char *value_dn;
			int eq;
			
			rcode = rlm_ldap_group_dn2name(inst, request, pconn, vals[i], &value_dn);
			if (rcode != RLM_MODULE_OK) {
				goto finish;
			}
			
			eq = strcmp(vals[i], value_dn);
			ldap_memfree(value_dn);
			if (eq == 0){
				RDEBUG("User found. Comparison between membership: name (resolved from DN), "
				       "check: name");
				rcode = RLM_MODULE_OK;
				
				goto finish;
			}	

			continue;
		}
		
		rad_assert(0);
	}

	finish:

	if (vals) {
		ldap_value_free(vals);
	}
	
	if (result) {
		ldap_msgfree(result);
	}
	
	return rcode;
}
/*
 *	The S flag is set only within the EAP-TLS start message sent
 *	from the EAP server to the peer.
 *
 *	Similarly, when the EAP server receives an EAP-Response with
 *	the M bit set, it MUST respond with an EAP-Request with
 *	EAP-Type=EAP-TLS and no data. This serves as a fragment
 *	ACK. The EAP peer MUST wait.
 */
static eaptls_status_t eaptls_verify(EAP_HANDLER *handler)
{
	EAP_DS *eap_ds = handler->eap_ds;
	EAP_DS *prev_eap_ds = handler->prev_eapds;
	eaptls_packet_t	*eaptls_packet, *eaptls_prev = NULL;
	REQUEST *request = handler->request;

	/*
	 *	We don't check ANY of the input parameters.  It's all
	 *	code which works together, so if something is wrong,
	 *	we SHOULD core dump.
	 *
	 *	e.g. if eap_ds is NULL, of if eap_ds->response is
	 *	NULL, of if it's NOT an EAP-Response, or if the packet
	 *	is too short.  See eap_validation()., in ../../eap.c
	 *
	 *	Also, eaptype_select() takes care of selecting the
	 *	appropriate type, so we don't need to check
	 *	eap_ds->response->type.type == PW_EAP_TLS, or anything
	 *	else.
	 */
	eaptls_packet = (eaptls_packet_t *)eap_ds->response->type.data;
	if (prev_eap_ds && prev_eap_ds->response)
		eaptls_prev = (eaptls_packet_t *)prev_eap_ds->response->type.data;

	/*
	 *	check for ACK
	 *
	 *	If there's no TLS data, or there's 1 byte of TLS data,
	 *	with the flags set to zero, then it's an ACK.
	 *
	 *	Find if this is a reply to the previous request sent
	 */
	if ((eaptls_packet == NULL) ||
	    ((eap_ds->response->length == EAP_HEADER_LEN + 2) &&
	     ((eaptls_packet->flags & 0xc0) == 0x00))) {

#if 0
		/*
		 *	Un-comment this for TLS inside of TTLS/PEAP
		 */
		RDEBUG2("Received EAP-TLS ACK message");
		return eaptls_ack_handler(handler);
#else
		if (prev_eap_ds &&
		    (prev_eap_ds->request->id == eap_ds->response->id)) {
			/*
			 *	Run the ACK handler directly from here.
			 */
			RDEBUG2("Received TLS ACK");
			return eaptls_ack_handler(handler);
		} else {
			radlog_request(L_ERR, 0, request, "Received Invalid TLS ACK");
			return EAPTLS_INVALID;
		}
#endif
	}

	/*
	 *	We send TLS_START, but do not receive it.
	 */
	if (TLS_START(eaptls_packet->flags)) {
		RDEBUG("Received unexpected EAP-TLS Start message");
		return EAPTLS_INVALID;
	}

	/*
	 *	The L bit (length included) is set to indicate the
	 *	presence of the four octet TLS Message Length field,
	 *	and MUST be set for the first fragment of a fragmented
	 *	TLS message or set of messages.
	 *
	 *	The M bit (more fragments) is set on all but the last
	 *	fragment.
	 *
	 *	The S bit (EAP-TLS start) is set in an EAP-TLS Start
	 *	message. This differentiates the EAP-TLS Start message
	 *	from a fragment acknowledgement.
	 */
	if (TLS_LENGTH_INCLUDED(eaptls_packet->flags)) {
		DEBUG2("  TLS Length %d",
		       eaptls_packet->data[2] * 256 | eaptls_packet->data[3]);
		if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
			/*
			 * FIRST_FRAGMENT is identified
			 * 1. If there is no previous EAP-response received.
			 * 2. If EAP-response received, then its M bit not set.
			 * 	(It is because Last fragment will not have M bit set)
			 */
			if (!prev_eap_ds ||
			    (prev_eap_ds->response == NULL) ||
			    (eaptls_prev == NULL) ||
			    !TLS_MORE_FRAGMENTS(eaptls_prev->flags)) {

				RDEBUG2("Received EAP-TLS First Fragment of the message");
				return EAPTLS_FIRST_FRAGMENT;
			} else {

				RDEBUG2("More Fragments with length included");
				return EAPTLS_MORE_FRAGMENTS_WITH_LENGTH;
			}
		} else {
			RDEBUG2("Length Included");
			return EAPTLS_LENGTH_INCLUDED;
		}
	}

	if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) {
		RDEBUG2("More fragments to follow");
		return EAPTLS_MORE_FRAGMENTS;
	}

	/*
	 *	None of the flags are set, but it's still a valid
	 *	EAPTLS packet.
	 */
	return EAPTLS_OK;
}
Example #27
0
static rlm_rcode_t CC_HINT(nonnull) mod_do_linelog(void *instance, REQUEST *request)
{
    int fd = -1;
    rlm_linelog_t *inst = (rlm_linelog_t*) instance;
    char const *value = inst->line;

#ifdef HAVE_GRP_H
    gid_t gid;
    char *endptr;
#endif
    char path[2048];
    char line[4096];

    line[0] = '\0';

    if (inst->reference) {
        CONF_ITEM *ci;
        CONF_PAIR *cp;

        if (radius_xlat(line + 1, sizeof(line) - 1, request, inst->reference, linelog_escape_func, NULL) < 0) {
            return RLM_MODULE_FAIL;
        }

        line[0] = '.';	/* force to be in current section */

        /*
         *	Don't allow it to go back up
         */
        if (line[1] == '.') goto do_log;

        ci = cf_reference_item(NULL, inst->cs, line);
        if (!ci) {
            RDEBUG2("No such entry \"%s\"", line);
            return RLM_MODULE_NOOP;
        }

        if (!cf_item_is_pair(ci)) {
            RDEBUG2("Entry \"%s\" is not a variable assignment ", line);
            goto do_log;
        }

        cp = cf_item_to_pair(ci);
        value = cf_pair_value(cp);
        if (!value) {
            RWDEBUG2("Entry \"%s\" has no value", line);
            return RLM_MODULE_OK;
        }

        /*
         *	Value exists, but is empty.  Don't log anything.
         */
        if (!*value) return RLM_MODULE_OK;
    }

do_log:
    /*
     *	FIXME: Check length.
     */
    if (radius_xlat(line, sizeof(line) - 1, request, value, linelog_escape_func, NULL) < 0) {
        return RLM_MODULE_FAIL;
    }

#ifdef HAVE_SYSLOG_H
    if (strcmp(inst->filename, "syslog") == 0) {
        syslog(inst->syslog_priority, "%s", line);
        return RLM_MODULE_OK;
    }
#endif

    /*
     *	We're using a real filename now.
     */
    if (radius_xlat(path, sizeof(path), request, inst->filename, inst->escape_func, NULL) < 0) {
        return RLM_MODULE_FAIL;
    }

    fd = exfile_open(inst->ef, path, inst->permissions, true);
    if (fd < 0) {
        ERROR("rlm_linelog: Failed to open %s: %s", path, fr_syserror(errno));
        return RLM_MODULE_FAIL;
    }

    if (inst->group != NULL) {
        gid = strtol(inst->group, &endptr, 10);
        if (*endptr != '\0') {
            if (rad_getgid(request, &gid, inst->group) < 0) {
                RDEBUG2("Unable to find system group \"%s\"", inst->group);
                goto skip_group;
            }
        }

        if (chown(path, -1, gid) == -1) {
            RDEBUG2("Unable to change system group of \"%s\"", path);
        }
    }

skip_group:
    strcat(line, "\n");

    if (write(fd, line, strlen(line)) < 0) {
        exfile_close(inst->ef, fd);
        ERROR("rlm_linelog: Failed writing: %s", fr_syserror(errno));
        return RLM_MODULE_FAIL;
    }

    exfile_close(inst->ef, fd);
    return RLM_MODULE_OK;
}
/*
 *	Process an EAP request
 */
eaptls_status_t eaptls_process(EAP_HANDLER *handler)
{
	tls_session_t *tls_session = (tls_session_t *) handler->opaque;
	EAPTLS_PACKET	*tlspacket;
	eaptls_status_t	status;
	REQUEST *request = handler->request;

	RDEBUG2("processing EAP-TLS");
	if (handler->certs) pairadd(&request->packet->vps,
				    paircopy(handler->certs));

	/* This case is when SSL generates Alert then we
	 * send that alert to the client and then send the EAP-Failure
	 */
	status = eaptls_verify(handler);
	RDEBUG2("eaptls_verify returned %d\n", status);

	switch (status) {
	default:
	case EAPTLS_INVALID:
	case EAPTLS_FAIL:

		/*
		 *	Success means that we're done the initial
		 *	handshake.  For TTLS, this means send stuff
		 *	back to the client, and the client sends us
		 *	more tunneled data.
		 */
	case EAPTLS_SUCCESS:
		return status;
		break;

		/*
		 *	Normal TLS request, continue with the "get rest
		 *	of fragments" phase.
		 */
	case EAPTLS_REQUEST:
		eaptls_request(handler->eap_ds, tls_session);
		return EAPTLS_HANDLED;
		break;

		/*
		 *	The handshake is done, and we're in the "tunnel
		 *	data" phase.
		 */
	case EAPTLS_OK:
		RDEBUG2("Done initial handshake");

		/*
		 *	Get the rest of the fragments.
		 */
	case EAPTLS_FIRST_FRAGMENT:
	case EAPTLS_MORE_FRAGMENTS:
	case EAPTLS_LENGTH_INCLUDED:
	case EAPTLS_MORE_FRAGMENTS_WITH_LENGTH:
		break;
	}

	/*
	 *	Extract the TLS packet from the buffer.
	 */
	if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL)
		return EAPTLS_FAIL;

	/*
	 *	Get the session struct from the handler
	 *
	 *	update the dirty_in buffer
	 *
	 *	NOTE: This buffer will contain partial data when M bit is set.
	 *
	 * 	CAUTION while reinitializing this buffer, it should be
	 * 	reinitialized only when this M bit is NOT set.
	 */
	if (tlspacket->dlen !=
	    (tls_session->record_plus)(&tls_session->dirty_in, tlspacket->data, tlspacket->dlen)) {
		eaptls_free(&tlspacket);
		RDEBUG("Exceeded maximum record size");
		return EAPTLS_FAIL;
	}

	/*
	 *	No longer needed.
	 */
	eaptls_free(&tlspacket);

	/*
	 *	SSL initalization is done.  Return.
	 *
	 *	The TLS data will be in the tls_session structure.
	 */
	if (SSL_is_init_finished(tls_session->ssl)) {
		int err;

		/*
		 *	The initialization may be finished, but if
		 *	there more fragments coming, then send ACK,
		 *	and get the caller to continue the
		 *	conversation.
		 */	
	        if ((status == EAPTLS_MORE_FRAGMENTS) ||
        	    (status == EAPTLS_MORE_FRAGMENTS_WITH_LENGTH) ||
            	    (status == EAPTLS_FIRST_FRAGMENT)) {
			/*
			 *	Send the ACK.
			 */
			eaptls_send_ack(handler->eap_ds,
					tls_session->peap_flag);
			RDEBUG2("Init is done, but tunneled data is fragmented");
			return EAPTLS_HANDLED;
		}

		/*	
		 *	Decrypt the complete record.
		 */
		BIO_write(tls_session->into_ssl, tls_session->dirty_in.data,
			  tls_session->dirty_in.used);

		/*
		 *      Clear the dirty buffer now that we are done with it
		 *      and init the clean_out buffer to store decrypted data
		 */
		(tls_session->record_init)(&tls_session->dirty_in);
		(tls_session->record_init)(&tls_session->clean_out);

		/*
		 *      Read (and decrypt) the tunneled data from the
		 *      SSL session, and put it into the decrypted
		 *      data buffer.
		 */
		err = SSL_read(tls_session->ssl, tls_session->clean_out.data,
			       sizeof(tls_session->clean_out.data));

		if (err < 0) {
			RDEBUG("SSL_read Error");

			switch (SSL_get_error(tls_session->ssl, err)) {
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
				RDEBUG("Error in fragmentation logic");
				break;
			default:
				/*
				 *	FIXME: Call int_ssl_check?
				 */
				break;
			}
			return EAPTLS_FAIL;
		}

		if (err == 0) {
			RDEBUG("WARNING: No data inside of the tunnel.");
		}
	
		/*
		 *	Passed all checks, successfully decrypted data
		 */
		tls_session->clean_out.used = err;
		
		return EAPTLS_OK;
	}

	/*
	 *	Continue the handshake.
	 */
	return eaptls_operation(status, handler);
}
Example #29
0
/*
 *	Common code called by everything below.
 */
static rlm_rcode_t file_common(rlm_files_t const *inst, REQUEST *request, char const *filename, rbtree_t *tree,
			       RADIUS_PACKET *request_packet, RADIUS_PACKET *reply_packet)
{
	char const	*name;
	VALUE_PAIR	*check_tmp = NULL;
	VALUE_PAIR	*reply_tmp = NULL;
	PAIR_LIST const *user_pl, *default_pl;
	bool		found = false;
	PAIR_LIST	my_pl;
	char		buffer[256];

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

		namepair = request->username;
		name = namepair ? namepair->vp_strvalue : "NONE";
	} else {
		int len;

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

		name = len ? buffer : "NONE";
	}

	if (!tree) return RLM_MODULE_NOOP;

	my_pl.name = name;
	user_pl = rbtree_finddata(tree, &my_pl);
	my_pl.name = "DEFAULT";
	default_pl = rbtree_finddata(tree, &my_pl);

	/*
	 *	Find the entry for the user.
	 */
	while (user_pl || default_pl) {
		fr_cursor_t cursor;
		VALUE_PAIR *vp;
		PAIR_LIST const *pl;

		/*
		 *	Figure out which entry to match on.
		 */

		if (!default_pl && user_pl) {
			pl = user_pl;
			user_pl = user_pl->next;

		} else if (!user_pl && default_pl) {
			pl = default_pl;
			default_pl = default_pl->next;

		} else if (user_pl->order < default_pl->order) {
			pl = user_pl;
			user_pl = user_pl->next;

		} else {
			pl = default_pl;
			default_pl = default_pl->next;
		}

		MEM(fr_pair_list_copy(request, &check_tmp, pl->check) >= 0);
		for (vp = fr_cursor_init(&cursor, &check_tmp);
		     vp;
		     vp = fr_cursor_next(&cursor)) {
			if (xlat_eval_pair(request, vp) < 0) {
				RWARN("Failed parsing expanded value for check item, skipping entry: %s", fr_strerror());
				fr_pair_list_free(&check_tmp);
				continue;
			}
		}

		if (paircmp(request, request_packet->vps, check_tmp, &reply_packet->vps) == 0) {
			RDEBUG2("Found match \"%s\" one line %d of %s", pl->name, pl->lineno, filename);
			found = true;

			/* ctx may be reply or proxy */
			MEM(fr_pair_list_copy(reply_packet, &reply_tmp, pl->reply) >= 0);

			radius_pairmove(request, &reply_packet->vps, reply_tmp, true);
			fr_pair_list_move(request, &request->control, &check_tmp);

			reply_tmp = NULL;	/* radius_pairmove() frees input attributes */
			fr_pair_list_free(&check_tmp);

			/*
			 *	Fallthrough?
			 */
			if (!fall_through(pl->reply)) break;
		}
	}

	/*
	 *	Remove server internal parameters.
	 */
	fr_pair_delete_by_da(&reply_packet->vps, attr_fall_through);

	/*
	 *	See if we succeeded.
	 */
	if (!found)
		return RLM_MODULE_NOOP; /* on to the next module */

	return RLM_MODULE_OK;

}
/** Create and insert a cache entry.
 *
 * @return RLM_MODULE_OK on success, RLM_MODULE_UPDATED if we merged the cache entry and RLM_MODULE_FAIL on failure.
 */
static rlm_rcode_t cache_insert(rlm_cache_t *inst, REQUEST *request, rlm_cache_handle_t **handle,
				char const *key, int ttl)
{
	VALUE_PAIR *vp, *to_cache;
	vp_cursor_t src_list, cached_request, cached_reply, cached_control;

	value_pair_map_t const *map;

	bool merge = false;
	rlm_cache_entry_t *c;

	if ((inst->max_entries > 0) && inst->module->count &&
	    (inst->module->count(inst, request, handle) > inst->max_entries)) {
		RWDEBUG("Cache is full: %d entries", inst->max_entries);
		return RLM_MODULE_FAIL;
	}

	c = cache_alloc(inst, request);
	if (!c) return RLM_MODULE_FAIL;

	c->key = talloc_typed_strdup(c, key);
	c->created = c->expires = request->timestamp;
	c->expires += ttl;

	RDEBUG("Creating new cache entry");

	fr_cursor_init(&cached_request, &c->packet);
	fr_cursor_init(&cached_reply, &c->reply);
	fr_cursor_init(&cached_control, &c->control);

	for (map = inst->maps; map != NULL; map = map->next) {
		rad_assert(map->lhs && map->rhs);

		if (map_to_vp(&to_cache, request, map, NULL) < 0) {
			RDEBUG("Skipping %s", map->rhs->name);
			continue;
		}

		/*
		 *	Reparent the VPs map_to_vp may return multiple.
		 */
		for (vp = fr_cursor_init(&src_list, &to_cache);
		     vp;
		     vp = fr_cursor_next(&src_list)) {
			VERIFY_VP(vp);

			/*
			 *	Prevent people from accidentally caching
			 *	cache control attributes.
			 */
			if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) {
			case PW_CACHE_TTL:
			case PW_CACHE_STATUS_ONLY:
			case PW_CACHE_READ_ONLY:
			case PW_CACHE_MERGE:
			case PW_CACHE_ENTRY_HITS:
				RDEBUG2("Skipping %s", vp->da->name);
				continue;

			default:
				break;
			}

			RINDENT();
			if (RDEBUG_ENABLED2) map_debug_log(request, map, vp);
			REXDENT();
			(void) talloc_steal(c, vp);

			vp->op = map->op;

			switch (map->lhs->tmpl_list) {
			case PAIR_LIST_REQUEST:
				fr_cursor_insert(&cached_request, vp);
				break;

			case PAIR_LIST_REPLY:
				fr_cursor_insert(&cached_reply, vp);
				break;

			case PAIR_LIST_CONTROL:
				fr_cursor_insert(&cached_control, vp);
				break;

			default:
				rad_assert(0);	/* should have been caught by validation */
			}
		}
	}

	/*
	 *	Check to see if we need to merge the entry into the request
	 */
	vp = pairfind(request->config_items, PW_CACHE_MERGE, 0, TAG_ANY);
	if (vp && (vp->vp_integer > 0)) merge = true;

	if (merge) cache_merge(inst, request, c);

	for (;;) {
		cache_status_t ret;

		ret = inst->module->insert(inst, request, handle, c);
		switch (ret) {
		case CACHE_RECONNECT:
			if (cache_reconnect(inst, request, handle) == 0) continue;
			return RLM_MODULE_FAIL;

		case CACHE_OK:
			RDEBUG("Commited entry, TTL %d seconds", ttl);
			cache_free(inst, &c);
			return merge ? RLM_MODULE_UPDATED :
				       RLM_MODULE_OK;

		default:
			talloc_free(c);	/* Failed insertion - use talloc_free not the driver free */
			return RLM_MODULE_FAIL;
		}
	}
}