/** Find an existing module instance and verify it implements the specified method * * Extracts the method from the module name where the format is @verbatim <module>.<method> @endverbatim * and ensures the module implements the specified method. * * @param[out] method the method component we found associated with the module. May be NULL. * @param[in] modules section in the main config. * @param[in] name The name of the module we're attempting to find, concatenated with * the method. * @return * - The module instance on success. * - NULL on error (or not found). */ module_instance_t *module_find_with_method(rlm_components_t *method, CONF_SECTION *modules, char const *name) { char *p; rlm_components_t i; module_instance_t *mi; /* * Module names are allowed to contain '.' * so we search for the bare module name first. */ mi = module_find(modules, name); if (mi) return mi; /* * Find out if the instance name contains * a method, if it doesn't, then the module * doesn't exist. */ p = strrchr(name, '.'); if (!p) return NULL; /* * Find the component. */ for (i = MOD_AUTHENTICATE; i < MOD_COUNT; i++) { if (strcmp(p + 1, section_type_value[i].section) == 0) { char *inst_name; inst_name = talloc_bstrndup(NULL, name, p - name); mi = module_find(modules, inst_name); talloc_free(inst_name); if (!mi) return NULL; /* * Verify the module actually implements * the specified method. */ if (!mi->module->methods[i]) { cf_log_debug(modules, "%s does not implement method \"%s\"", mi->module->name, p + 1); return NULL; } if (method) *method = i; return mi; } } return mi; }
/** Convert attr tmpl to an xlat for &attr[*] * * @param ctx to allocate new xlat_expt_t in. * @param vpt to convert. * @return * - NULL if unable to convert (not necessarily error). * - a new #vp_tmpl_t. */ xlat_exp_t *xlat_from_tmpl_attr(TALLOC_CTX *ctx, vp_tmpl_t *vpt) { xlat_exp_t *node; if (vpt->type != TMPL_TYPE_ATTR) return NULL; node = talloc_zero(ctx, xlat_exp_t); node->type = XLAT_ATTRIBUTE; node->fmt = talloc_bstrndup(node, vpt->name, vpt->len); node->attr = tmpl_alloc(node, TMPL_TYPE_ATTR, node->fmt, talloc_array_length(node->fmt) - 1, T_BARE_WORD); memcpy(&node->attr->data, &vpt->data, sizeof(vpt->data)); return node; }
/** Extract a subcapture value from the request * * @note This is the POSIX variant of the function. * * @param[in] ctx To allocate subcapture buffer in. * @param[out] out Where to write the subcapture string. * @param[in] request to extract. * @param[in] num Subcapture index (0 for entire match). * @return * - 0 on success. * - -1 on notfound. */ int regex_request_to_sub(TALLOC_CTX *ctx, char **out, REQUEST *request, uint32_t num) { fr_regcapture_t *rc; char *buff; char const *start; size_t len; regmatch_t *match_data; rc = request_data_reference(request, request, REQUEST_DATA_REGEX); if (!rc) { RDEBUG4("No subcapture data found"); *out = NULL; return -1; } match_data = rc->regmatch->match_data; /* * Greater than our capture array * * -1 means no value in this capture group. */ if ((num >= rc->regmatch->used) || (match_data[num].rm_eo == -1) || (match_data[num].rm_so == -1)) { RDEBUG4("%i/%zu Not found", num + 1, rc->regmatch->used); *out = NULL; return -1; } /* * Sanity checks on the offsets */ rad_assert(match_data[num].rm_eo <= (regoff_t)talloc_array_length(rc->regmatch->subject)); rad_assert(match_data[num].rm_so <= (regoff_t)talloc_array_length(rc->regmatch->subject)); start = rc->regmatch->subject + match_data[num].rm_so; len = match_data[num].rm_eo - match_data[num].rm_so; MEM(buff = talloc_bstrndup(ctx, start, len)); RDEBUG4("%i/%zu Found: %pV (%zu)", num + 1, rc->regmatch->used, fr_box_strvalue_buffer(buff), len); *out = buff; 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; }
/** Execute an arbitrary SQL query * * For selects the first value of the first column will be returned, * for inserts, updates and deletes the number of rows affected will be * returned instead. */ static ssize_t sql_xlat(char **out, UNUSED size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt) { rlm_sql_handle_t *handle = NULL; rlm_sql_row_t row; rlm_sql_t const *inst = mod_inst; sql_rcode_t rcode; ssize_t ret = 0; /* * Add SQL-User-Name attribute just in case it is needed * We could search the string fmt for SQL-User-Name to see if this is * needed or not */ sql_set_user(inst, request, NULL); handle = fr_connection_get(inst->pool); /* connection pool should produce error */ if (!handle) return 0; rlm_sql_query_log(inst, request, NULL, fmt); /* * If the query starts with any of the following prefixes, * then return the number of rows affected */ if ((strncasecmp(fmt, "insert", 6) == 0) || (strncasecmp(fmt, "update", 6) == 0) || (strncasecmp(fmt, "delete", 6) == 0)) { int numaffected; rcode = rlm_sql_query(inst, request, &handle, fmt); if (rcode != RLM_SQL_OK) { query_error: RERROR("SQL query failed: %s", fr_int2str(sql_rcode_table, rcode, "<INVALID>")); ret = -1; goto finish; } numaffected = (inst->module->sql_affected_rows)(handle, inst->config); if (numaffected < 1) { RDEBUG("SQL query affected no rows"); goto finish; } MEM(*out = talloc_asprintf(request, "%d", numaffected)); ret = talloc_array_length(*out) - 1; (inst->module->sql_finish_query)(handle, inst->config); goto finish; } /* else it's a SELECT statement */ rcode = rlm_sql_select_query(inst, request, &handle, fmt); if (rcode != RLM_SQL_OK) goto query_error; rcode = rlm_sql_fetch_row(&row, inst, request, &handle); if (rcode) { (inst->module->sql_finish_select_query)(handle, inst->config); goto query_error; } if (!row) { RDEBUG("SQL query returned no results"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } if (!row[0]){ RDEBUG("NULL value in first column of result"); (inst->module->sql_finish_select_query)(handle, inst->config); ret = -1; goto finish; } *out = talloc_bstrndup(request, row[0], strlen(row[0])); ret = talloc_array_length(*out) - 1; (inst->module->sql_finish_select_query)(handle, inst->config); finish: fr_connection_release(inst->pool, handle); return ret; }