/* * 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; }
/* * 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; }
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; }
/** 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; }
/** 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; }
/** 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; }
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; }
/* * 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; }
/** 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; }
/** 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; }
/** 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; } }
/** 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; }
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; }
/** 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; }
/** 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; }
/** 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; }
/* * 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; }
/** 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; }
/** 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; }
/** 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; }
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); }
/* * 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; } } }