USES_APPLE_DEPRECATED_API /* OpenSSL API has been deprecated by Apple */ #define __STDC_WANT_LIB_EXT1__ 1 #include <string.h> #include <openssl/hmac.h> #include <freeradius-devel/util/sha1.h> #include <freeradius-devel/tls/base.h> #include <freeradius-devel/tls/missing.h> #include "tls.h" #include "base.h" #include "attrs.h" #define EAP_TLS_MPPE_KEY_LEN 32 /** Generate keys according to RFC 2716 and add to the reply * */ int eap_crypto_mppe_keys(REQUEST *request, SSL *ssl, char const *prf_label, size_t prf_label_len) { uint8_t out[4 * EAP_TLS_MPPE_KEY_LEN]; uint8_t *p; if (SSL_export_keying_material(ssl, out, sizeof(out), prf_label, prf_label_len, NULL, 0, 0) != 1) { tls_log_error(request, "Failed generating MPPE keys"); return -1; } if (RDEBUG_ENABLED3) { uint8_t random[SSL3_RANDOM_SIZE]; size_t random_len; uint8_t master_key[SSL_MAX_MASTER_KEY_LENGTH]; size_t master_key_len; RDEBUG3("Key Derivation Function input"); RINDENT(); RDEBUG3("prf label : %pV", fr_box_strvalue_len(prf_label, prf_label_len)); master_key_len = SSL_SESSION_get_master_key(SSL_get_session(ssl), master_key, sizeof(master_key)); RDEBUG3("master session key : %pH", fr_box_octets(master_key, master_key_len)); random_len = SSL_get_client_random(ssl, random, SSL3_RANDOM_SIZE); RDEBUG3("client random : %pH", fr_box_octets(random, random_len)); random_len = SSL_get_server_random(ssl, random, SSL3_RANDOM_SIZE); RDEBUG3("server random : %pH", fr_box_octets(random, random_len)); REXDENT(); } RDEBUG2("Adding session keys"); p = out; eap_add_reply(request, attr_ms_mppe_recv_key, p, EAP_TLS_MPPE_KEY_LEN); p += EAP_TLS_MPPE_KEY_LEN; eap_add_reply(request, attr_ms_mppe_send_key, p, EAP_TLS_MPPE_KEY_LEN); eap_add_reply(request, attr_eap_msk, out, 64); eap_add_reply(request, attr_eap_emsk, out + 64, 64); return 0; }
/** Prints information to the debug log on the current timeout settings * * There are so many different timers in LDAP it's often hard to debug * issues with them, hence the need for this function. */ void fr_ldap_timeout_debug(REQUEST *request, fr_ldap_connection_t const *conn, struct timeval const *timeout, char const *prefix) { struct timeval *net = NULL, *client = NULL; int server = 0; fr_ldap_config_t const *handle_config = conn->config; if (request) RINDENT(); #ifdef LDAP_OPT_NETWORK_TIMEOUT if (ldap_get_option(conn->handle, LDAP_OPT_NETWORK_TIMEOUT, &net) != LDAP_OPT_SUCCESS) { ROPTIONAL(REDEBUG, ERROR, "Failed getting LDAP_OPT_NETWORK_TIMEOUT"); } #endif #ifdef LDAP_OPT_TIMEOUT if (ldap_get_option(conn->handle, LDAP_OPT_TIMEOUT, &client) != LDAP_OPT_SUCCESS) { ROPTIONAL(REDEBUG, ERROR, "Failed getting LDAP_OPT_TIMEOUT"); } #endif if (ldap_get_option(conn->handle, LDAP_OPT_TIMELIMIT, &server) != LDAP_OPT_SUCCESS) { ROPTIONAL(REDEBUG, ERROR, "Failed getting LDAP_OPT_TIMELIMIT"); } ROPTIONAL(RDEBUG4, DEBUG4, "%s: Timeout settings", prefix); if (timeout) { ROPTIONAL(RDEBUG4, DEBUG4, "Client side result timeout (ovr): %ld.%06ld", (long)timeout->tv_sec, (long)timeout->tv_usec); } else { ROPTIONAL(RDEBUG4, DEBUG4, "Client side result timeout (ovr): unset"); } #ifdef LDAP_OPT_TIMEOUT if (client && (client->tv_sec != -1)) { ROPTIONAL(RDEBUG4, DEBUG4, "Client side result timeout (dfl): %ld.%06ld", (long)client->tv_sec, (long)client->tv_usec); } else { ROPTIONAL(RDEBUG4, DEBUG4, "Client side result timeout (dfl): unset"); } #endif #ifdef LDAP_OPT_NETWORK_TIMEOUT if (net && (net->tv_sec != -1)) { ROPTIONAL(RDEBUG4, DEBUG4, "Client side network I/O timeout : %ld.%06ld", (long)net->tv_sec, (long)net->tv_usec); } else { ROPTIONAL(RDEBUG4, DEBUG4, "Client side network I/O timeout : unset"); } #endif ROPTIONAL(RDEBUG4, DEBUG4, "Server side result timeout : %i", server); if (request) REXDENT(); free(net); free(client); }
static void cache_merge(rlm_cache_t *inst, REQUEST *request, rlm_cache_entry_t *c) { VALUE_PAIR *vp; vp_map_t *map; vp = pairfind(request->config, PW_CACHE_MERGE, 0, TAG_ANY); if (vp && (vp->vp_integer == 0)) { RDEBUG2("Told not to merge entry into request"); return; } RDEBUG2("Merging cache entry into request"); RINDENT(); for (map = c->maps; map; map = map->next) { /* * The only reason that the application of a map entry * can fail, is if the destination list or request * isn't valid. For now we don't consider this fatal * and continue merging the rest of the maps. */ if (map_to_request(request, map, map_to_vp, NULL) < 0) { char buffer[1024]; map_prints(buffer, sizeof(buffer), map); REXDENT(); RDEBUG("Skipping %s", buffer); RINDENT(); continue; } } REXDENT(); if (inst->stats) { rad_assert(request->packet != NULL); vp = pairfind(request->packet->vps, PW_CACHE_ENTRY_HITS, 0, TAG_ANY); if (!vp) { vp = paircreate(request->packet, PW_CACHE_ENTRY_HITS, 0); rad_assert(vp != NULL); pairadd(&request->packet->vps, vp); } vp->vp_integer = c->hits; } }
/** Perform a search and map the result of the search to server attributes * * @param[in] mod_inst #rlm_csv_t * @param[in] proc_inst mapping map entries to field numbers. * @param[in,out] request The current request. * @param[in] key key to look for * @param[in] maps Head of the map list. * @return * - #RLM_MODULE_NOOP no rows were returned. * - #RLM_MODULE_UPDATED if one or more #VALUE_PAIR were added to the #REQUEST. * - #RLM_MODULE_FAIL if an error occurred. */ static rlm_rcode_t mod_map_proc(void *mod_inst, UNUSED void *proc_inst, REQUEST *request, char const *key, vp_map_t const *maps) { rlm_csv_t *inst = mod_inst; rlm_csv_entry_t *e, my_entry; vp_map_t const *map; my_entry.key = key; e = rbtree_finddata(inst->tree, &my_entry); if (!e) return RLM_MODULE_NOOP; RINDENT(); for (map = maps; map != NULL; map = map->next) { int field; char *field_name; /* * Avoid memory allocations if possible. */ if (map->rhs->type != TMPL_TYPE_LITERAL) { if (tmpl_aexpand(request, &field_name, request, map->rhs, NULL, NULL) < 0) { RDEBUG("Failed expanding RHS at %s", map->lhs->name); return RLM_MODULE_FAIL; } } else { memcpy(&field_name, &map->rhs->name, sizeof(field_name)); /* const */ } field = fieldname2offset(inst, field_name); if (field_name != map->rhs->name) talloc_free(field_name); if (field < 0) { RDEBUG("No such field name %s", map->rhs->name); return RLM_MODULE_FAIL; } /* * Pass the raw data to the callback, which will * create the VP and add it to the map. */ if (map_to_request(request, map, csv_map_getvalue, e->data[field]) < 0) { return RLM_MODULE_FAIL; } } return RLM_MODULE_UPDATED; }
static rlm_rcode_t cache_merge(rlm_cache_t const *inst, REQUEST *request, rlm_cache_entry_t *c) { VALUE_PAIR *vp; vp_map_t *map; int merged = 0; RDEBUG2("Merging cache entry into request"); RINDENT(); for (map = c->maps; map; map = map->next) { /* * The only reason that the application of a map entry * can fail, is if the destination list or request * isn't valid. For now we don't consider this fatal * and continue merging the rest of the maps. */ if (map_to_request(request, map, map_to_vp, NULL) < 0) { char buffer[1024]; map_snprint(buffer, sizeof(buffer), map); REXDENT(); RDEBUG2("Skipping %s", buffer); RINDENT(); continue; } merged++; } REXDENT(); if (inst->config.stats) { rad_assert(request->packet != NULL); MEM(pair_update_request(&vp, attr_cache_entry_hits) >= 0); vp->vp_uint32 = c->hits; } return merged > 0 ? RLM_MODULE_UPDATED : RLM_MODULE_OK; }
/** Print a list of VALUE_PAIRs. * * @param[in] lvl Debug lvl (1-4). * @param[in] request to read logging params from. * @param[in] vp to print. * @param[in] prefix (optional). */ void log_request_pair_list(fr_log_lvl_t lvl, REQUEST *request, VALUE_PAIR *vp, char const *prefix) { fr_cursor_t cursor; if (!vp || !request || !request->log.dst) return; if (!log_debug_enabled(L_DBG, lvl, request)) return; RINDENT(); for (vp = fr_cursor_init(&cursor, &vp); vp; vp = fr_cursor_next(&cursor)) { VP_VERIFY(vp); RDEBUGX(lvl, "%s%pP", prefix ? prefix : "&", vp); } REXDENT(); }
static rlm_rcode_t CC_HINT(nonnull) mod_authorize(UNUSED void *instance, REQUEST *request) { if (!fr_pair_find_by_num(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY)) { return RLM_MODULE_NOOP; } if (fr_pair_find_by_num(request->config, PW_AUTH_TYPE, 0, TAG_ANY) != NULL) { RWDEBUG2("&control:Auth-Type already set. Not setting to CHAP"); return RLM_MODULE_NOOP; } RINDENT(); RDEBUG("&control:Auth-Type := CHAP"); REXDENT(); pair_make_config("Auth-Type", "CHAP", T_OP_EQ); return RLM_MODULE_OK; }
static inline VALUE_PAIR *tls_session_cert_attr_add(TALLOC_CTX *ctx, REQUEST *request, fr_cursor_t *cursor, int attr, int attr_index, char const *value) { VALUE_PAIR *vp; fr_dict_attr_t const *da = *(cert_attr_names[attr][attr_index]); MEM(vp = fr_pair_afrom_da(ctx, da)); if (value) { if (fr_pair_value_from_str(vp, value, -1, '\0', true) < 0) { RPWDEBUG("Failed creating attribute %s", da->name); talloc_free(vp); return NULL; } } RINDENT(); RDEBUG3("%pP", vp); REXDENT(); fr_cursor_append(cursor, vp); return vp; }
/** Print the response data in a useful treelike form * * @param lvl to print data at. * @param reply to print. * @param request The current request. * @param idx Response number. */ void fr_redis_reply_print(fr_log_lvl_t lvl, redisReply *reply, REQUEST *request, int idx) { size_t i = 0; if (!reply) return; switch (reply->type) { case REDIS_REPLY_ERROR: REDEBUG("(%i) error : %s", idx, reply->str); break; case REDIS_REPLY_STATUS: RDEBUGX(lvl, "(%i) status : %s", idx, reply->str); break; case REDIS_REPLY_STRING: RDEBUGX(lvl, "(%i) string : %s", idx, reply->str); break; case REDIS_REPLY_INTEGER: RDEBUGX(lvl, "(%i) integer : %lld", idx, reply->integer); break; case REDIS_REPLY_NIL: RDEBUGX(lvl, "(%i) nil", idx); break; case REDIS_REPLY_ARRAY: RDEBUGX(lvl, "(%i) array[%zu]", idx, reply->elements); for (i = 0; i < reply->elements; i++) { RINDENT(); fr_redis_reply_print(lvl, reply->element[i], request, i); REXDENT(); } break; } }
/* * Run a virtual server auth and postauth * */ int rad_virtual_server(REQUEST *request) { VALUE_PAIR *vp; int result; RDEBUG("Virtual server %s received request", request->server); rdebug_pair_list(L_DBG_LVL_1, request, request->packet->vps, NULL); RDEBUG("server %s {", request->server); RINDENT(); /* * We currently only handle AUTH packets here. * This could be expanded to handle other packets as well if required. */ rad_assert(request->packet->code == PW_CODE_ACCESS_REQUEST); result = rad_authenticate(request); if (request->reply->code == PW_CODE_ACCESS_REJECT) { pairdelete(&request->config, PW_POST_AUTH_TYPE, 0, TAG_ANY); vp = pairmake_config("Post-Auth-Type", "Reject", T_OP_SET); if (vp) rad_postauth(request); } if (request->reply->code == PW_CODE_ACCESS_ACCEPT) { rad_postauth(request); } REXDENT(); RDEBUG("} # server %s", request->server); RDEBUG("Virtual server sending reply"); rdebug_pair_list(L_DBG_LVL_1, request, request->reply->vps, NULL); return result; }
/* * get the vps and put them in perl hash * If one VP have multiple values it is added as array_ref * Example for this is Cisco-AVPair that holds multiple values. * Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'} */ static void perl_store_vps(UNUSED TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, HV *rad_hv, const char *hash_name, const char *list_name) { VALUE_PAIR *vp; char *tbuff; size_t tbufflen = 1024; hv_undef(rad_hv); vp_cursor_t cursor; /* * Find out how much room to allocate. */ for (vp = fr_cursor_init(&cursor, vps); vp; vp = fr_cursor_next(&cursor)) { if (((vp->length * 2) + 3) > tbufflen) { tbufflen = (vp->vp_length * 2) + 3; } } tbuff = talloc_array(request, char, tbufflen); RINDENT(); fr_pair_list_sort(vps, fr_pair_cmp_by_da_tag); for (vp = fr_cursor_init(&cursor, vps); vp; vp = fr_cursor_next(&cursor)) { VALUE_PAIR *next; char const *name; size_t len; char namebuf[256]; /* * Tagged attributes are added to the hash with name * <attribute>:<tag>, others just use the normal attribute * name as the key. */ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { snprintf(namebuf, sizeof(namebuf), "%s:%d", vp->da->name, vp->tag); name = namebuf; } else { name = vp->da->name; } /* * We've sorted by type, then tag, so attributes of the * same type/tag should follow on from each other. */ if ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)) { int i = 0; AV *av; av = newAV(); perl_vp_to_svpvn_element(request, av, vp, &i, hash_name, list_name); do { perl_vp_to_svpvn_element(request, av, next, &i, hash_name, list_name); fr_cursor_next(&cursor); } while ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)); (void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0); continue; } /* * It's a normal single valued attribute */ switch (vp->da->type) { case PW_TYPE_STRING: RDEBUG("$%s{'%s'} = &%s:%s -> '%s'", hash_name, vp->da->name, list_name, vp->da->name, vp->vp_strvalue); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(vp->vp_strvalue, vp->vp_length), 0); break; default: len = vp_prints_value(tbuff, tbufflen, vp, 0); RDEBUG("$%s{'%s'} = &%s:%s -> '%s'", hash_name, vp->da->name, list_name, vp->da->name, tbuff); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(tbuff, truncate_len(len, tbufflen)), 0); break; } } REXDENT(); talloc_free(tbuff); }
/** Retrieve UMTS quintuplets from sets of attributes. * * Hunt for a source of UMTS quintuplets * * @param eap_session The current eap_session. * @param vps List to hunt for triplets in. * @param keys UMTS keys. * @param src Forces quintuplets to be retrieved from a particular src. * * @return * - 1 Vector could not be retrieved from the specified src. * - 0 Vector was retrieved OK and written to the specified index. * - -1 Error retrieving vector from the specified src. */ int fr_sim_vector_umts_from_attrs(eap_session_t *eap_session, VALUE_PAIR *vps, fr_sim_keys_t *keys, fr_sim_vector_src_t *src) { REQUEST *request = eap_session->request; int ret; rad_assert((keys->vector_type == SIM_VECTOR_NONE) || (keys->vector_type == SIM_VECTOR_UMTS)); switch (*src) { default: case SIM_VECTOR_SRC_KI: ret = vector_umts_from_ki(eap_session, vps, keys); if (ret == 0) { *src = SIM_VECTOR_SRC_KI; break; } if (ret < 0) return -1; if (*src != SIM_VECTOR_SRC_AUTO) return 1; /* FALL-THROUGH */ case SIM_VECTOR_SRC_QUINTUPLETS: ret = vector_umts_from_quintuplets(eap_session, vps, keys); if (ret == 0) { *src = SIM_VECTOR_SRC_QUINTUPLETS; break;; } if (ret < 0) return -1; break; } if (ret == 1) { RWDEBUG("Could not find or derive data for UMTS vector"); return 1; } if (RDEBUG_ENABLED2) { RDEBUG2("UMTS vector"); RINDENT(); /* * Don't change colon indent, matches other messages later... */ RHEXDUMP_INLINE(L_DBG_LVL_2, keys->umts.vector.autn, SIM_VECTOR_UMTS_AUTN_SIZE, "AUTN :"); RHEXDUMP_INLINE(L_DBG_LVL_2, keys->umts.vector.ck, SIM_VECTOR_UMTS_CK_SIZE, "CK :"); RHEXDUMP_INLINE(L_DBG_LVL_2, keys->umts.vector.ik, SIM_VECTOR_UMTS_IK_SIZE, "IK :"); RHEXDUMP_INLINE(L_DBG_LVL_2, keys->umts.vector.rand, SIM_VECTOR_UMTS_RAND_SIZE, "RAND :"); RHEXDUMP_INLINE(L_DBG_LVL_2, keys->umts.vector.xres, keys->umts.vector.xres_len, "XRES :"); REXDENT(); } keys->vector_type = SIM_VECTOR_UMTS; return 0; }
static int vector_umts_from_ki(eap_session_t *eap_session, VALUE_PAIR *vps, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; VALUE_PAIR *ki_vp, *amf_vp, *sqn_vp, *version_vp; uint64_t sqn; uint8_t amf_buff[MILENAGE_AMF_SIZE] = { 0x00, 0x00 }; uint8_t opc_buff[MILENAGE_OPC_SIZE]; uint8_t const *opc_p; uint32_t version = 4; int i; ki_vp = fr_pair_find_by_da(vps, attr_sim_ki, TAG_ANY); if (!ki_vp) { RDEBUG3("No &control:%s found, not generating quintuplets locally", attr_sim_ki->name); return 1; } else if (ki_vp->vp_length != MILENAGE_KI_SIZE) { REDEBUG("&control:%s has incorrect length, expected %u bytes got %zu bytes", attr_sim_ki->name, MILENAGE_KI_SIZE, ki_vp->vp_length); return -1; } if (vector_opc_from_op(request, &opc_p, opc_buff, vps, ki_vp->vp_octets) < 0) return -1; amf_vp = fr_pair_find_by_da(vps, attr_sim_amf, TAG_ANY); if (amf_vp) { if (amf_vp->vp_length != sizeof(amf_buff)) { REDEBUG("&control:%s has incorrect length, expected %u bytes got %zu bytes", attr_sim_amf->name, MILENAGE_AMF_SIZE, amf_vp->vp_length); return -1; } memcpy(amf_buff, amf_vp->vp_octets, sizeof(amf_buff)); } sqn_vp = fr_pair_find_by_da(vps, attr_sim_sqn, TAG_ANY); sqn = sqn_vp ? sqn_vp->vp_uint64 : 2; /* * We default to milenage */ version_vp = fr_pair_find_by_da(vps, attr_sim_algo_version, TAG_ANY); if (version_vp) version = version_vp->vp_uint32; for (i = 0; i < SIM_VECTOR_UMTS_RAND_SIZE; i += sizeof(uint32_t)) { uint32_t rand = fr_rand(); memcpy(&keys->umts.vector.rand[i], &rand, sizeof(rand)); } switch (version) { case 4: { uint8_t sqn_buff[MILENAGE_SQN_SIZE]; keys->sqn = sqn_vp ? sqn_vp->vp_uint64 : 0; uint48_to_buff(sqn_buff, keys->sqn); RDEBUG3("Milenage inputs"); RINDENT(); /* * Don't change colon indent, matches other messages later... */ RHEXDUMP_INLINE(L_DBG_LVL_3, ki_vp->vp_octets, MILENAGE_KI_SIZE, "Ki :"); RHEXDUMP_INLINE(L_DBG_LVL_3, opc_p, MILENAGE_OPC_SIZE, "OPc :"); RHEXDUMP_INLINE(L_DBG_LVL_3, sqn_buff, MILENAGE_SQN_SIZE, "SQN :"); RHEXDUMP_INLINE(L_DBG_LVL_3, amf_buff, MILENAGE_AMF_SIZE, "AMF :"); REXDENT(); if (milenage_umts_generate(keys->umts.vector.autn, keys->umts.vector.ik, keys->umts.vector.ck, keys->umts.vector.ak, keys->umts.vector.xres, opc_p, amf_buff, ki_vp->vp_octets, sqn, keys->umts.vector.rand) < 0) { RPEDEBUG2("Failed deriving UMTS Quintuplet"); return -1; } keys->umts.vector.xres_len = 8; } return 0; case 1: case 2: case 3: REDEBUG("Only Milenage can be used to generate quintuplets"); return -1; default: REDEBUG("Unknown/unsupported algorithm %i", version); return -1; } }
/** Retrieve GSM triplets from sets of attributes. * * Hunt for a source of SIM triplets * * @param eap_session The current eap_session. * @param vps List to hunt for triplets in. * @param idx To write EAP-SIM triplets to. * @param keys EAP session keys. * @param src Forces triplets to be retrieved from a particular src * and ensures if multiple triplets are being retrieved * that they all come from the same src. * @return * - 1 Vector could not be retrieved from the specified src. * - 0 Vector was retrieved OK and written to the specified index. * - -1 Error retrieving vector from the specified src. */ int fr_sim_vector_gsm_from_attrs(eap_session_t *eap_session, VALUE_PAIR *vps, int idx, fr_sim_keys_t *keys, fr_sim_vector_src_t *src) { REQUEST *request = eap_session->request; int ret; rad_assert(idx >= 0 && idx < 3); rad_assert((keys->vector_type == SIM_VECTOR_NONE) || (keys->vector_type == SIM_VECTOR_GSM)); switch (*src) { default: case SIM_VECTOR_SRC_KI: ret = vector_gsm_from_ki(eap_session, vps, idx, keys); if (ret == 0) { *src = SIM_VECTOR_SRC_KI; break; } if (ret < 0) return -1; if (*src != SIM_VECTOR_SRC_AUTO) return 1; /* FALL-THROUGH */ case SIM_VECTOR_SRC_TRIPLETS: ret = vector_gsm_from_triplets(eap_session, vps, idx, keys); if (ret == 0) { *src = SIM_VECTOR_SRC_TRIPLETS; break; } if (ret < 0) return -1; if (*src != SIM_VECTOR_SRC_AUTO) return 1; /* FALL-THROUGH */ case SIM_VECTOR_SRC_QUINTUPLETS: ret = vector_gsm_from_quintuplets(eap_session, vps, idx, keys); if (ret == 0) { *src = SIM_VECTOR_SRC_QUINTUPLETS; break; } if (ret < 0) return -1; break; } if (ret == 1) { RWDEBUG("Could not find or derive data for GSM vector[%i]", idx); return 1; } if (RDEBUG_ENABLED2) { RDEBUG2("GSM vector[%i]", idx); RINDENT(); /* * Don't change colon indent, matches other messages later... */ RHEXDUMP_INLINE(L_DBG_LVL_2, keys->gsm.vector[idx].kc, SIM_VECTOR_GSM_KC_SIZE, "KC :"); RHEXDUMP_INLINE(L_DBG_LVL_2, keys->gsm.vector[idx].rand, SIM_VECTOR_GSM_RAND_SIZE, "RAND :"); RHEXDUMP_INLINE(L_DBG_LVL_2, keys->gsm.vector[idx].sres, SIM_VECTOR_GSM_SRES_SIZE, "SRES :"); REXDENT(); } keys->vector_type = SIM_VECTOR_GSM; return 0; }
static int eap_sim_get_challenge(eap_handler_t *handler, VALUE_PAIR *vps, int idx, eap_sim_state_t *ess) { REQUEST *request = handler->request; VALUE_PAIR *vp, *ki, *algo_version; rad_assert(idx >= 0 && idx < 3); /* * Generate a new RAND value, and derive Kc and SRES from Ki */ ki = pairfind(vps, PW_EAP_SIM_KI, 0, TAG_ANY); if (ki) { int i; /* * Check to see if have a Ki for the IMSI, this allows us to generate the rest * of the triplets. */ algo_version = pairfind(vps, PW_EAP_SIM_ALGO_VERSION, 0, TAG_ANY); if (!algo_version) { REDEBUG("Found Ki, but missing EAP-Sim-Algo-Version"); return 0; } for (i = 0; i < EAPSIM_RAND_SIZE; i++) { ess->keys.rand[idx][i] = fr_rand(); } switch (algo_version->vp_integer) { case 1: comp128v1(ess->keys.sres[idx], ess->keys.Kc[idx], ki->vp_octets, ess->keys.rand[idx]); break; case 2: comp128v23(ess->keys.sres[idx], ess->keys.Kc[idx], ki->vp_octets, ess->keys.rand[idx], true); break; case 3: comp128v23(ess->keys.sres[idx], ess->keys.Kc[idx], ki->vp_octets, ess->keys.rand[idx], false); break; case 4: REDEBUG("Comp128-4 algorithm is not supported as details have not yet been published. " "If you have details of this algorithm please contact the FreeRADIUS " "maintainers"); return 0; default: REDEBUG("Unknown/unsupported algorithm Comp128-%i", algo_version->vp_integer); } if (RDEBUG_ENABLED2) { char buffer[33]; /* 32 hexits (16 bytes) + 1 */ char *p; RDEBUG2("Generated following triplets for round %i:", idx); RINDENT(); p = buffer; for (i = 0; i < EAPSIM_RAND_SIZE; i++) { p += sprintf(p, "%02x", ess->keys.rand[idx][i]); } RDEBUG2("RAND : 0x%s", buffer); p = buffer; for (i = 0; i < EAPSIM_SRES_SIZE; i++) { p += sprintf(p, "%02x", ess->keys.sres[idx][i]); } RDEBUG2("SRES : 0x%s", buffer); p = buffer; for (i = 0; i < EAPSIM_KC_SIZE; i++) { p += sprintf(p, "%02x", ess->keys.Kc[idx][i]); } RDEBUG2("Kc : 0x%s", buffer); REXDENT(); } return 1; } /* * Use known RAND, SRES, and Kc values, these may of been pulled in from an AuC, * or created by sending challenges to the SIM directly. */ vp = pairfind(vps, PW_EAP_SIM_RAND1 + idx, 0, TAG_ANY); if (!vp) { /* bad, we can't find stuff! */ REDEBUG("control:EAP-SIM-RAND%i not found", idx + 1); return 0; } if (vp->length != EAPSIM_RAND_SIZE) { REDEBUG("control:EAP-SIM-RAND%i is not " STRINGIFY(EAPSIM_RAND_SIZE) " bytes, got %zu bytes", idx + 1, vp->length); return 0; } memcpy(ess->keys.rand[idx], vp->vp_octets, EAPSIM_RAND_SIZE); vp = pairfind(vps, PW_EAP_SIM_SRES1 + idx, 0, TAG_ANY); if (!vp) { /* bad, we can't find stuff! */ REDEBUG("control:EAP-SIM-SRES%i not found", idx + 1); return 0; } if (vp->length != EAPSIM_SRES_SIZE) { REDEBUG("control:EAP-SIM-SRES%i is not " STRINGIFY(EAPSIM_SRES_SIZE) " bytes, got %zu bytes", idx + 1, vp->length); return 0; } memcpy(ess->keys.sres[idx], vp->vp_octets, EAPSIM_SRES_SIZE); vp = pairfind(vps, PW_EAP_SIM_KC1 + idx, 0, TAG_ANY); if (!vp) { /* bad, we can't find stuff! */ REDEBUG("control:EAP-SIM-Kc%i not found", idx + 1); return 0; } if (vp->length != EAPSIM_KC_SIZE) { REDEBUG("control:EAP-SIM-Kc%i is not 8 bytes, got %zu bytes", idx + 1, vp->length); return 0; } memcpy(ess->keys.Kc[idx], vp->vp_octets, EAPSIM_KC_SIZE); if (vp->length != EAPSIM_KC_SIZE) { REDEBUG("control:EAP-SIM-Kc%i is not " STRINGIFY(EAPSIM_KC_SIZE) " bytes, got %zu bytes", idx + 1, vp->length); return 0; } memcpy(ess->keys.Kc[idx], vp->vp_strvalue, EAPSIM_KC_SIZE); return 1; }
/* * 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 *password, *chap; uint8_t pass_str[MAX_STRING_LEN]; if (!request->username) { REDEBUG("&request:User-Name attribute is required for authentication"); return RLM_MODULE_INVALID; } chap = fr_pair_find_by_num(request->packet->vps, PW_CHAP_PASSWORD, 0, TAG_ANY); if (!chap) { REDEBUG("You set '&control:Auth-Type = CHAP' for a request that " "does not contain a CHAP-Password attribute!"); return RLM_MODULE_INVALID; } if (chap->vp_length == 0) { REDEBUG("&request:CHAP-Password is empty"); return RLM_MODULE_INVALID; } if (chap->vp_length != CHAP_VALUE_LENGTH + 1) { REDEBUG("&request:CHAP-Password has invalid length"); return RLM_MODULE_INVALID; } password = fr_pair_find_by_num(request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); if (password == NULL) { if (fr_pair_find_by_num(request->config, 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("&control:Cleartext-Password is required for authentication"); return RLM_MODULE_FAIL; } rad_chap_encode(request->packet, pass_str, chap->vp_octets[0], password); if (RDEBUG_ENABLED3) { uint8_t const *p; size_t length; VALUE_PAIR *vp; char buffer[MAX_STRING_LEN * 2 + 1]; RDEBUG3("Comparing with \"known good\" &control:Cleartext-Password value \"%s\"", password->vp_strvalue); vp = fr_pair_find_by_num(request->packet->vps, PW_CHAP_CHALLENGE, 0, TAG_ANY); if (vp) { RDEBUG2("Using challenge from &request:CHAP-Challenge"); p = vp->vp_octets; length = vp->vp_length; } else { RDEBUG2("Using challenge from authenticator field"); 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; }
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; sql_fall_through_t do_fall_through = FALL_THROUGH_DEFAULT; int rows; char *expanded = NULL; rad_assert(request->packet != NULL); rad_assert(request->reply != NULL); if (!inst->config->authorize_check_query && !inst->config->authorize_reply_query && !inst->config->read_groups && !inst->config->read_profiles) { RWDEBUG("No authorization checks configured, returning noop"); return RLM_MODULE_NOOP; } /* * 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) { vp_cursor_t cursor; VALUE_PAIR *vp; 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(request, inst, &handle, &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("Conditional check items matched, merging assignment check items"); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(2, request, vp); } REXDENT(); radius_pairmove(request, &request->config_items, check_tmp, true); rcode = RLM_MODULE_OK; check_tmp = NULL; } if (inst->config->authorize_reply_query) { /* * 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(request->reply, inst, &handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) goto skipreply; do_fall_through = fall_through(reply_tmp); RDEBUG2("User found in radreply table, merging reply items"); user_found = true; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp); radius_pairmove(request, &request->reply->vps, reply_tmp, true); rcode = RLM_MODULE_OK; reply_tmp = NULL; } skipreply: if ((do_fall_through == FALL_THROUGH_YES) || (inst->config->read_groups && (do_fall_through == FALL_THROUGH_DEFAULT))) { rlm_rcode_t ret; RDEBUG3("... falling-through to group processing"); ret = rlm_sql_process_groups(inst, request, &handle, &do_fall_through); 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 ((do_fall_through == FALL_THROUGH_YES) || (inst->config->read_profiles && (do_fall_through == FALL_THROUGH_DEFAULT))) { 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, &do_fall_through); 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); sql_unset_user(inst, request); return rcode; error: pairfree(&check_tmp); pairfree(&reply_tmp); sql_unset_user(inst, request); sql_release_socket(inst, handle); return rcode; }
static rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, REQUEST *request) { rlm_rcode_t rcode = RLM_MODULE_OK; ldap_rcode_t status; int ldap_errno; int i; ldap_instance_t *inst = instance; struct berval **values; VALUE_PAIR *vp; ldap_handle_t *conn; LDAPMessage *result, *entry; char const *dn = NULL; rlm_ldap_map_xlat_t expanded; /* faster than mallocing every time */ if (!request->username) { RDEBUG2("Attribute \"User-Name\" is required for authorization"); return RLM_MODULE_NOOP; } /* * Check for valid input, zero length names not permitted */ if (request->username->vp_length == 0) { RDEBUG2("Zero length username not permitted"); return RLM_MODULE_INVALID; } if (rlm_ldap_map_xlat(request, inst->user_map, &expanded) < 0) { return RLM_MODULE_FAIL; } conn = mod_conn_get(inst, request); if (!conn) return RLM_MODULE_FAIL; /* * Add any additional attributes we need for checking access, memberships, and profiles */ if (inst->userobj_access_attr) { expanded.attrs[expanded.count++] = inst->userobj_access_attr; } if (inst->userobj_membership_attr && (inst->cacheable_group_dn || inst->cacheable_group_name)) { expanded.attrs[expanded.count++] = inst->userobj_membership_attr; } if (inst->profile_attr) { expanded.attrs[expanded.count++] = inst->profile_attr; } if (inst->valuepair_attr) { expanded.attrs[expanded.count++] = inst->valuepair_attr; } expanded.attrs[expanded.count] = NULL; dn = rlm_ldap_find_user(inst, request, &conn, expanded.attrs, true, &result, &rcode); if (!dn) { goto finish; } entry = ldap_first_entry(conn->handle, result); if (!entry) { ldap_get_option(conn->handle, LDAP_OPT_RESULT_CODE, &ldap_errno); REDEBUG("Failed retrieving entry: %s", ldap_err2string(ldap_errno)); goto finish; } /* * Check for access. */ if (inst->userobj_access_attr) { rcode = rlm_ldap_check_access(inst, request, conn, entry); if (rcode != RLM_MODULE_OK) { goto finish; } } /* * Check if we need to cache group memberships */ if (inst->cacheable_group_dn || inst->cacheable_group_name) { if (inst->userobj_membership_attr) { rcode = rlm_ldap_cacheable_userobj(inst, request, &conn, entry, inst->userobj_membership_attr); if (rcode != RLM_MODULE_OK) { goto finish; } } rcode = rlm_ldap_cacheable_groupobj(inst, request, &conn); if (rcode != RLM_MODULE_OK) { goto finish; } } #ifdef WITH_EDIR /* * We already have a Cleartext-Password. Skip edir. */ if (pairfind(request->config_items, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) { goto skip_edir; } /* * Retrieve Universal Password if we use eDirectory */ if (inst->edir) { int res = 0; char password[256]; size_t pass_size = sizeof(password); /* * Retrive universal password */ res = nmasldap_get_password(conn->handle, dn, password, &pass_size); if (res != 0) { REDEBUG("Failed to retrieve eDirectory password: (%i) %s", res, edir_errstr(res)); rcode = RLM_MODULE_FAIL; goto finish; } /* * Add Cleartext-Password attribute to the request */ vp = radius_paircreate(request, &request->config_items, PW_CLEARTEXT_PASSWORD, 0); pairstrcpy(vp, password); vp->vp_length = pass_size; if (RDEBUG_ENABLED3) { RDEBUG3("Added eDirectory password. control:%s += '%s'", vp->da->name, vp->vp_strvalue); } else { RDEBUG2("Added eDirectory password"); } if (inst->edir_autz) { RDEBUG2("Binding as user for eDirectory authorization checks"); /* * Bind as the user */ conn->rebound = true; status = rlm_ldap_bind(inst, request, &conn, dn, vp->vp_strvalue, true); switch (status) { case LDAP_PROC_SUCCESS: rcode = RLM_MODULE_OK; RDEBUG("Bind as user '%s' was successful", dn); break; case LDAP_PROC_NOT_PERMITTED: rcode = RLM_MODULE_USERLOCK; goto finish; case LDAP_PROC_REJECT: rcode = RLM_MODULE_REJECT; goto finish; case LDAP_PROC_BAD_DN: rcode = RLM_MODULE_INVALID; goto finish; case LDAP_PROC_NO_RESULT: rcode = RLM_MODULE_NOTFOUND; goto finish; default: rcode = RLM_MODULE_FAIL; goto finish; }; } } skip_edir: #endif /* * Apply ONE user profile, or a default user profile. */ if (inst->default_profile) { char profile[1024]; if (radius_xlat(profile, sizeof(profile), request, inst->default_profile, NULL, NULL) < 0) { REDEBUG("Failed creating default profile string"); rcode = RLM_MODULE_INVALID; goto finish; } rlm_ldap_map_profile(inst, request, &conn, profile, &expanded); } /* * Apply a SET of user profiles. */ if (inst->profile_attr) { values = ldap_get_values_len(conn->handle, entry, inst->profile_attr); if (values != NULL) { for (i = 0; values[i] != NULL; i++) { char *value; value = rlm_ldap_berval_to_string(request, values[i]); rlm_ldap_map_profile(inst, request, &conn, value, &expanded); talloc_free(value); } ldap_value_free_len(values); } } if (inst->user_map || inst->valuepair_attr) { RDEBUG("Processing user attributes"); RINDENT(); rlm_ldap_map_do(inst, request, conn->handle, &expanded, entry); REXDENT(); rlm_ldap_check_reply(inst, request); } finish: rlm_ldap_map_xlat_free(&expanded); if (result) ldap_msgfree(result); mod_conn_release(inst, conn); return rcode; }
static ssize_t redis_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt) { rlm_redis_t const *inst = mod_inst; fr_redis_conn_t *conn; bool read_only = false; uint8_t const *key = NULL; size_t key_len = 0; fr_redis_cluster_state_t state; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; size_t len; int ret; char const *p = fmt, *q; int argc; char const *argv[MAX_REDIS_ARGS]; char argv_buf[MAX_REDIS_COMMAND_LEN]; if (p[0] == '-') { p++; read_only = true; } /* * Hack to allow querying against a specific node for testing */ if (p[0] == '@') { fr_socket_addr_t node_addr; fr_pool_t *pool; RDEBUG3("Overriding node selection"); p++; q = strchr(p, ' '); if (!q) { REDEBUG("Found node specifier but no command, format is [-][@<host>[:port]] <redis command>"); return -1; } if (fr_inet_pton_port(&node_addr.ipaddr, &node_addr.port, p, q - p, AF_UNSPEC, true, true) < 0) { RPEDEBUG("Failed parsing node address"); return -1; } p = q + 1; if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &node_addr, true) < 0) { RPEDEBUG("Failed locating cluster node"); return -1; } conn = fr_pool_connection_get(pool, request); if (!conn) { REDEBUG("No connections available for cluster node"); return -1; } argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf); if (argc <= 0) { RPEDEBUG("Invalid command: %s", p); arg_error: fr_pool_connection_release(pool, request, conn); return -1; } if (argc >= (MAX_REDIS_ARGS - 1)) { RPEDEBUG("Too many parameters; increase MAX_REDIS_ARGS and recompile: %s", p); goto arg_error; } RDEBUG2("Executing command: %s", argv[0]); if (argc > 1) { RDEBUG2("With argments"); RINDENT(); for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]); REXDENT(); } if (!read_only) { reply = redisCommandArgv(conn->handle, argc, argv, NULL); status = fr_redis_command_status(conn, reply); } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) { goto close_conn; } if (!reply) goto fail; switch (status) { case REDIS_RCODE_SUCCESS: goto reply_parse; case REDIS_RCODE_RECONNECT: close_conn: fr_pool_connection_close(pool, request, conn); ret = -1; goto finish; default: fail: fr_pool_connection_release(pool, request, conn); ret = -1; goto finish; } } /* * Normal node selection and execution based on key */ argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf); if (argc <= 0) { RPEDEBUG("Invalid command: %s", p); ret = -1; goto finish; } if (argc >= (MAX_REDIS_ARGS - 1)) { RPEDEBUG("Too many parameters; increase MAX_REDIS_ARGS and recompile: %s", p); ret = -1; goto finish; } /* * If we've got multiple arguments, the second one is usually the key. * The Redis docs say commands should be analysed first to get key * positions, but this involves sending them to the server, which is * just as expensive as sending them to the wrong server and receiving * a redirect. */ if (argc > 1) { key = (uint8_t const *)argv[1]; key_len = strlen((char const *)key); } for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, request, key, key_len, read_only); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, request, status, &reply)) { RDEBUG2("Executing command: %s", argv[0]); if (argc > 1) { RDEBUG2("With arguments"); RINDENT(); for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]); REXDENT(); } if (!read_only) { reply = redisCommandArgv(conn->handle, argc, argv, NULL); status = fr_redis_command_status(conn, reply); } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) { state.close_conn = true; } } if (s_ret != REDIS_RCODE_SUCCESS) { ret = -1; goto finish; } if (!fr_cond_assert(reply)) { ret = -1; goto finish; } reply_parse: switch (reply->type) { case REDIS_REPLY_INTEGER: ret = snprintf(*out, outlen, "%lld", reply->integer); break; case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: len = (((size_t)reply->len) >= outlen) ? outlen - 1: (size_t) reply->len; memcpy(*out, reply->str, len); (*out)[len] = '\0'; ret = reply->len; break; default: REDEBUG("Server returned non-value type \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = -1; break; } finish: fr_redis_reply_free(reply); return ret; }
/* * 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->length == 0) { REDEBUG("CHAP-Password is empty"); return RLM_MODULE_INVALID; } if (chap->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->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 is comparison failed: password is incorrect"); return RLM_MODULE_REJECT; } RDEBUG("CHAP user \"%s\" authenticated successfully", request->username->vp_strvalue); return RLM_MODULE_OK; }
static int mod_process(void *arg, eap_handler_t *handler) { pwd_session_t *session; pwd_hdr *hdr; pwd_id_packet_t *packet; eap_packet_t *response; REQUEST *request, *fake; VALUE_PAIR *pw, *vp; EAP_DS *eap_ds; size_t in_len; int ret = 0; eap_pwd_t *inst = (eap_pwd_t *)arg; uint16_t offset; uint8_t exch, *in, *ptr, msk[MSK_EMSK_LEN], emsk[MSK_EMSK_LEN]; uint8_t peer_confirm[SHA256_DIGEST_LENGTH]; if (((eap_ds = handler->eap_ds) == NULL) || !inst) return 0; session = (pwd_session_t *)handler->opaque; request = handler->request; response = handler->eap_ds->response; hdr = (pwd_hdr *)response->type.data; /* * The header must be at least one byte. */ if (!hdr || (response->type.length < sizeof(pwd_hdr))) { RDEBUG("Packet with insufficient data"); return 0; } in = hdr->data; in_len = response->type.length - sizeof(pwd_hdr); /* * see if we're fragmenting, if so continue until we're done */ if (session->out_pos) { if (in_len) RDEBUG2("pwd got something more than an ACK for a fragment"); return send_pwd_request(session, eap_ds); } /* * the first fragment will have a total length, make a * buffer to hold all the fragments */ if (EAP_PWD_GET_LENGTH_BIT(hdr)) { if (session->in) { RDEBUG2("pwd already alloced buffer for fragments"); return 0; } if (in_len < 2) { RDEBUG("Invalid packet: length bit set, but no length field"); return 0; } session->in_len = ntohs(in[0] * 256 | in[1]); if ((session->in = talloc_zero_array(session, uint8_t, session->in_len)) == NULL) { RDEBUG2("pwd cannot allocate %zd buffer to hold fragments", session->in_len); return 0; } memset(session->in, 0, session->in_len); session->in_pos = 0; in += sizeof(uint16_t); in_len -= sizeof(uint16_t); } /* * all fragments, including the 1st will have the M(ore) bit set, * buffer those fragments! */ if (EAP_PWD_GET_MORE_BIT(hdr)) { rad_assert(session->in != NULL); if ((session->in_pos + in_len) > session->in_len) { RDEBUG2("Fragment overflows packet."); return 0; } memcpy(session->in + session->in_pos, in, in_len); session->in_pos += in_len; /* * send back an ACK for this fragment */ exch = EAP_PWD_GET_EXCHANGE(hdr); eap_ds->request->code = PW_EAP_REQUEST; eap_ds->request->type.num = PW_EAP_PWD; eap_ds->request->type.length = sizeof(pwd_hdr); if ((eap_ds->request->type.data = talloc_array(eap_ds->request, uint8_t, sizeof(pwd_hdr))) == NULL) { return 0; } hdr = (pwd_hdr *)eap_ds->request->type.data; EAP_PWD_SET_EXCHANGE(hdr, exch); return 1; } if (session->in) { /* * the last fragment... */ if ((session->in_pos + in_len) > session->in_len) { RDEBUG2("pwd will not overflow a fragment buffer. Nope, not prudent"); return 0; } memcpy(session->in + session->in_pos, in, in_len); in = session->in; in_len = session->in_len; } switch (session->state) { case PWD_STATE_ID_REQ: { BIGNUM *x = NULL, *y = NULL; if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_ID) { RDEBUG2("pwd exchange is incorrect: not ID"); return 0; } packet = (pwd_id_packet_t *) in; if (in_len < sizeof(*packet)) { RDEBUG("Packet is too small (%zd < %zd).", in_len, sizeof(*packet)); return 0; } if ((packet->prf != EAP_PWD_DEF_PRF) || (packet->random_function != EAP_PWD_DEF_RAND_FUN) || (packet->prep != EAP_PWD_PREP_NONE) || (CRYPTO_memcmp(packet->token, &session->token, 4)) || (packet->group_num != ntohs(session->group_num))) { RDEBUG2("pwd id response is invalid"); return 0; } /* * we've agreed on the ciphersuite, record it... */ ptr = (uint8_t *)&session->ciphersuite; memcpy(ptr, (char *)&packet->group_num, sizeof(uint16_t)); ptr += sizeof(uint16_t); *ptr = EAP_PWD_DEF_RAND_FUN; ptr += sizeof(uint8_t); *ptr = EAP_PWD_DEF_PRF; session->peer_id_len = in_len - sizeof(pwd_id_packet_t); if (session->peer_id_len >= sizeof(session->peer_id)) { RDEBUG2("pwd id response is malformed"); return 0; } memcpy(session->peer_id, packet->identity, session->peer_id_len); session->peer_id[session->peer_id_len] = '\0'; /* * make fake request to get the password for the usable ID */ if ((fake = request_alloc_fake(handler->request)) == NULL) { RDEBUG("pwd unable to create fake request!"); return 0; } fake->username = fr_pair_afrom_num(fake->packet, PW_USER_NAME, 0); if (!fake->username) { RDEBUG("Failed creating pair for peer id"); talloc_free(fake); return 0; } fr_pair_value_bstrncpy(fake->username, session->peer_id, session->peer_id_len); fr_pair_add(&fake->packet->vps, fake->username); if ((vp = fr_pair_find_by_num(request->config, PW_VIRTUAL_SERVER, 0, TAG_ANY)) != NULL) { fake->server = vp->vp_strvalue; } else if (inst->virtual_server) { fake->server = inst->virtual_server; } /* else fake->server == request->server */ RDEBUG("Sending tunneled request"); rdebug_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); if (fake->server) { RDEBUG("server %s {", fake->server); } else { RDEBUG("server {"); } /* * Call authorization recursively, which will * get the password. */ RINDENT(); process_authorize(0, fake); REXDENT(); /* * Note that we don't do *anything* with the reply * attributes. */ if (fake->server) { RDEBUG("} # server %s", fake->server); } else { RDEBUG("}"); } RDEBUG("Got tunneled reply code %d", fake->reply->code); rdebug_pair_list(L_DBG_LVL_1, request, fake->reply->vps, NULL); if ((pw = fr_pair_find_by_num(fake->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY)) == NULL) { DEBUG2("failed to find password for %s to do pwd authentication", session->peer_id); talloc_free(fake); return 0; } if (compute_password_element(session, session->group_num, pw->data.strvalue, strlen(pw->data.strvalue), inst->server_id, strlen(inst->server_id), session->peer_id, strlen(session->peer_id), &session->token)) { DEBUG2("failed to obtain password element"); talloc_free(fake); return 0; } TALLOC_FREE(fake); /* * compute our scalar and element */ if (compute_scalar_element(session, inst->bnctx)) { DEBUG2("failed to compute server's scalar and element"); return 0; } MEM(x = BN_new()); MEM(y = BN_new()); /* * element is a point, get both coordinates: x and y */ if (!EC_POINT_get_affine_coordinates_GFp(session->group, session->my_element, x, y, inst->bnctx)) { DEBUG2("server point assignment failed"); BN_clear_free(x); BN_clear_free(y); return 0; } /* * construct request */ session->out_len = BN_num_bytes(session->order) + (2 * BN_num_bytes(session->prime)); if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) { return 0; } memset(session->out, 0, session->out_len); ptr = session->out; offset = BN_num_bytes(session->prime) - BN_num_bytes(x); BN_bn2bin(x, ptr + offset); BN_clear_free(x); ptr += BN_num_bytes(session->prime); offset = BN_num_bytes(session->prime) - BN_num_bytes(y); BN_bn2bin(y, ptr + offset); BN_clear_free(y); ptr += BN_num_bytes(session->prime); offset = BN_num_bytes(session->order) - BN_num_bytes(session->my_scalar); BN_bn2bin(session->my_scalar, ptr + offset); session->state = PWD_STATE_COMMIT; ret = send_pwd_request(session, eap_ds); } break; case PWD_STATE_COMMIT: if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_COMMIT) { RDEBUG2("pwd exchange is incorrect: not commit!"); return 0; } /* * process the peer's commit and generate the shared key, k */ if (process_peer_commit(session, in, in_len, inst->bnctx)) { RDEBUG2("failed to process peer's commit"); return 0; } /* * compute our confirm blob */ if (compute_server_confirm(session, session->my_confirm, inst->bnctx)) { ERROR("rlm_eap_pwd: failed to compute confirm!"); return 0; } /* * construct a response...which is just our confirm blob */ session->out_len = SHA256_DIGEST_LENGTH; if ((session->out = talloc_array(session, uint8_t, session->out_len)) == NULL) { return 0; } memset(session->out, 0, session->out_len); memcpy(session->out, session->my_confirm, SHA256_DIGEST_LENGTH); session->state = PWD_STATE_CONFIRM; ret = send_pwd_request(session, eap_ds); break; case PWD_STATE_CONFIRM: if (in_len < SHA256_DIGEST_LENGTH) { RDEBUG("Peer confirm is too short (%zd < %d)", in_len, SHA256_DIGEST_LENGTH); return 0; } if (EAP_PWD_GET_EXCHANGE(hdr) != EAP_PWD_EXCH_CONFIRM) { RDEBUG2("pwd exchange is incorrect: not commit!"); return 0; } if (compute_peer_confirm(session, peer_confirm, inst->bnctx)) { RDEBUG2("pwd exchange cannot compute peer's confirm"); return 0; } if (CRYPTO_memcmp(peer_confirm, in, SHA256_DIGEST_LENGTH)) { RDEBUG2("pwd exchange fails: peer confirm is incorrect!"); return 0; } if (compute_keys(session, peer_confirm, msk, emsk)) { RDEBUG2("pwd exchange cannot generate (E)MSK!"); return 0; } eap_ds->request->code = PW_EAP_SUCCESS; /* * return the MSK (in halves) */ eap_add_reply(handler->request, "MS-MPPE-Recv-Key", msk, MPPE_KEY_LEN); eap_add_reply(handler->request, "MS-MPPE-Send-Key", msk + MPPE_KEY_LEN, MPPE_KEY_LEN); ret = 1; break; default: RDEBUG2("unknown PWD state"); return 0; } /* * we processed the buffered fragments, get rid of them */ if (session->in) { talloc_free(session->in); session->in = NULL; } return ret; }
static rlm_rcode_t mod_cache_it(void *instance, UNUSED void *thread, REQUEST *request) { rlm_cache_entry_t *c = NULL; rlm_cache_t const *inst = instance; rlm_cache_handle_t *handle; fr_cursor_t cursor; VALUE_PAIR *vp; bool merge = true, insert = true, expire = false, set_ttl = false; int exists = -1; uint8_t buffer[1024]; uint8_t const *key; ssize_t key_len; rlm_rcode_t rcode = RLM_MODULE_NOOP; int ttl = inst->config.ttl; key_len = tmpl_expand((char const **)&key, (char *)buffer, sizeof(buffer), request, inst->config.key, NULL, NULL); if (key_len < 0) return RLM_MODULE_FAIL; if (key_len == 0) { REDEBUG("Zero length key string is invalid"); return RLM_MODULE_INVALID; } /* * If Cache-Status-Only == yes, only return whether we found a * valid cache entry */ vp = fr_pair_find_by_da(request->control, attr_cache_status_only, TAG_ANY); if (vp && vp->vp_bool) { RINDENT(); RDEBUG3("status-only: yes"); REXDENT(); if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL; rcode = cache_find(&c, inst, request, &handle, key, key_len); if (rcode == RLM_MODULE_FAIL) goto finish; rad_assert(!inst->driver->acquire || handle); rcode = c ? RLM_MODULE_OK: RLM_MODULE_NOTFOUND; goto finish; } /* * Figure out what operation we're doing */ vp = fr_pair_find_by_da(request->control, attr_cache_allow_merge, TAG_ANY); if (vp) merge = vp->vp_bool; vp = fr_pair_find_by_da(request->control, attr_cache_allow_insert, TAG_ANY); if (vp) insert = vp->vp_bool; vp = fr_pair_find_by_da(request->control, attr_cache_ttl, TAG_ANY); if (vp) { if (vp->vp_int32 == 0) { expire = true; } else if (vp->vp_int32 < 0) { expire = true; ttl = -(vp->vp_int32); /* Updating the TTL */ } else { set_ttl = true; ttl = vp->vp_int32; } } RINDENT(); RDEBUG3("merge : %s", merge ? "yes" : "no"); RDEBUG3("insert : %s", insert ? "yes" : "no"); RDEBUG3("expire : %s", expire ? "yes" : "no"); RDEBUG3("ttl : %i", ttl); REXDENT(); if (cache_acquire(&handle, inst, request) < 0) return RLM_MODULE_FAIL; /* * Retrieve the cache entry and merge it with the current request * recording whether the entry existed. */ if (merge) { rcode = cache_find(&c, inst, request, &handle, key, key_len); switch (rcode) { case RLM_MODULE_FAIL: goto finish; case RLM_MODULE_OK: rcode = cache_merge(inst, request, c); exists = 1; break; case RLM_MODULE_NOTFOUND: rcode = RLM_MODULE_NOTFOUND; exists = 0; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); } /* * Expire the entry if told to, and we either don't know whether * it exists, or we know it does. * * We only expire if we're not inserting, as driver insert methods * should perform upserts. */ if (expire && ((exists == -1) || (exists == 1))) { if (!insert) { rad_assert(!set_ttl); switch (cache_expire(inst, request, &handle, key, key_len)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: if (rcode == RLM_MODULE_NOOP) rcode = RLM_MODULE_OK; break; case RLM_MODULE_NOTFOUND: if (rcode == RLM_MODULE_NOOP) rcode = RLM_MODULE_NOTFOUND; break; default: rad_assert(0); break; } /* If it previously existed, it doesn't now */ } /* Otherwise use insert to overwrite */ exists = 0; } /* * If we still don't know whether it exists or not * and we need to do an insert or set_ttl operation * determine that now. */ if ((exists < 0) && (insert || set_ttl)) { switch (cache_find(&c, inst, request, &handle, key, key_len)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: exists = 1; if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; break; case RLM_MODULE_NOTFOUND: exists = 0; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); } /* * We can only alter the TTL on an entry if it exists. */ if (set_ttl && (exists == 1)) { rad_assert(c); c->expires = request->packet->timestamp.tv_sec + ttl; switch (cache_set_ttl(inst, request, &handle, c)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_NOTFOUND: case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; goto finish; default: rad_assert(0); } } /* * Inserts are upserts, so we don't care about the * entry state, just that we're not meant to be * setting the TTL, which precludes performing an * insert. */ if (insert && (exists == 0)) { switch (cache_insert(inst, request, &handle, key, key_len, ttl)) { case RLM_MODULE_FAIL: rcode = RLM_MODULE_FAIL; goto finish; case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) rcode = RLM_MODULE_OK; break; case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; break; default: rad_assert(0); } rad_assert(!inst->driver->acquire || handle); goto finish; } finish: cache_free(inst, &c); cache_release(inst, request, &handle); /* * Clear control attributes */ for (vp = fr_cursor_init(&cursor, &request->control); vp; vp = fr_cursor_next(&cursor)) { again: if (!fr_dict_attr_is_top_level(vp->da)) continue; switch (vp->da->attr) { case FR_CACHE_TTL: case FR_CACHE_STATUS_ONLY: case FR_CACHE_ALLOW_MERGE: case FR_CACHE_ALLOW_INSERT: case FR_CACHE_MERGE_NEW: RDEBUG2("Removing &control:%s", vp->da->name); vp = fr_cursor_remove(&cursor); talloc_free(vp); vp = fr_cursor_current(&cursor); if (!vp) break; goto again; } } return rcode; }
/** Create and insert a cache entry * * @return * - #RLM_MODULE_OK on success. * - #RLM_MODULE_UPDATED if we merged the cache entry. * - #RLM_MODULE_FAIL on failure. */ static rlm_rcode_t cache_insert(rlm_cache_t const *inst, REQUEST *request, rlm_cache_handle_t **handle, uint8_t const *key, size_t key_len, int ttl) { vp_map_t const *map; vp_map_t **last, *c_map; VALUE_PAIR *vp; bool merge = false; rlm_cache_entry_t *c; size_t len; TALLOC_CTX *pool; if ((inst->config.max_entries > 0) && inst->driver->count && (inst->driver->count(&inst->config, inst->driver_inst->data, request, handle) > inst->config.max_entries)) { RWDEBUG("Cache is full: %d entries", inst->config.max_entries); return RLM_MODULE_FAIL; } c = cache_alloc(inst, request); if (!c) return RLM_MODULE_FAIL; c->key = talloc_memdup(c, key, key_len); c->key_len = key_len; c->created = c->expires = request->packet->timestamp.tv_sec; c->expires += ttl; last = &c->maps; RDEBUG2("Creating new cache entry"); /* * Alloc a pool so we don't have excessive allocs when * gathering VALUE_PAIRs to cache. */ pool = talloc_pool(NULL, 2048); for (map = inst->maps; map != NULL; map = map->next) { VALUE_PAIR *to_cache = NULL; fr_cursor_t cursor; rad_assert(map->lhs && map->rhs); /* * Calling map_to_vp gives us exactly the same result, * as if this were an update section. */ if (map_to_vp(pool, &to_cache, request, map, NULL) < 0) { RDEBUG2("Skipping %s", map->rhs->name); continue; } for (vp = fr_cursor_init(&cursor, &to_cache); vp; vp = fr_cursor_next(&cursor)) { /* * Prevent people from accidentally caching * cache control attributes. */ if (map->rhs->type == TMPL_TYPE_LIST) switch (vp->da->attr) { case FR_CACHE_TTL: case FR_CACHE_STATUS_ONLY: case FR_CACHE_MERGE_NEW: case FR_CACHE_ENTRY_HITS: RDEBUG2("Skipping %s", vp->da->name); continue; default: break; } RINDENT(); if (RDEBUG_ENABLED2) map_debug_log(request, map, vp); REXDENT(); MEM(c_map = talloc_zero(c, vp_map_t)); c_map->op = map->op; /* * Now we turn the VALUE_PAIRs into maps. */ switch (map->lhs->type) { /* * Attributes are easy, reuse the LHS, and create a new * RHS with the fr_value_box_t from the VALUE_PAIR. */ case TMPL_TYPE_ATTR: c_map->lhs = map->lhs; /* lhs shouldn't be touched, so this is ok */ do_rhs: MEM(c_map->rhs = tmpl_init(talloc(c_map, vp_tmpl_t), TMPL_TYPE_DATA, map->rhs->name, map->rhs->len, T_BARE_WORD)); if (fr_value_box_copy(c_map->rhs, &c_map->rhs->tmpl_value, &vp->data) < 0) { REDEBUG("Failed copying attribute value"); error: talloc_free(pool); talloc_free(c); return RLM_MODULE_FAIL; } c_map->rhs->tmpl_value_type = vp->vp_type; if (vp->vp_type == FR_TYPE_STRING) { c_map->rhs->quote = is_printable(vp->vp_strvalue, vp->vp_length) ? T_SINGLE_QUOTED_STRING : T_DOUBLE_QUOTED_STRING; } break; /* * Lists are weird... We need to fudge a new LHS template, * which is a combination of the LHS list and the attribute. */ case TMPL_TYPE_LIST: { char attr[256]; MEM(c_map->lhs = tmpl_init(talloc(c_map, vp_tmpl_t), TMPL_TYPE_ATTR, map->lhs->name, map->lhs->len, T_BARE_WORD)); c_map->lhs->tmpl_da = vp->da; if (vp->da->flags.is_unknown) { /* for tmpl_verify() */ c_map->lhs->tmpl_unknown = fr_dict_unknown_acopy(c_map->lhs, vp->da); c_map->lhs->tmpl_da = c_map->lhs->tmpl_unknown; } c_map->lhs->tmpl_tag = vp->tag; c_map->lhs->tmpl_list = map->lhs->tmpl_list; c_map->lhs->tmpl_num = map->lhs->tmpl_num; c_map->lhs->tmpl_request = map->lhs->tmpl_request; /* * We need to rebuild the attribute name, to be the * one we copied from the source list. */ len = tmpl_snprint(attr, sizeof(attr), c_map->lhs); if (is_truncated(len, sizeof(attr))) { REDEBUG("Serialized attribute too long. Must be < " STRINGIFY(sizeof(attr)) " bytes, got %zu bytes", len); goto error; } c_map->lhs->len = len; c_map->lhs->name = talloc_typed_strdup(c_map->lhs, attr); } goto do_rhs; default: rad_assert(0); } *last = c_map; last = &(*last)->next; } talloc_free_children(pool); /* reset pool state */ } talloc_free(pool); /* * Check to see if we need to merge the entry into the request */ vp = fr_pair_find_by_da(request->control, attr_cache_merge_new, TAG_ANY); if (vp && vp->vp_bool) merge = true; if (merge) cache_merge(inst, request, c); for (;;) { cache_status_t ret; ret = inst->driver->insert(&inst->config, inst->driver_inst->data, request, *handle, c); switch (ret) { case CACHE_RECONNECT: if (cache_reconnect(handle, inst, request) == 0) continue; return RLM_MODULE_FAIL; case CACHE_OK: RDEBUG2("Committed 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; } } }
/** Convert attribute map into valuepairs * * Use the attribute map built earlier to convert LDAP values into valuepairs and insert them into whichever * list they need to go into. * * This is *NOT* atomic, but there's no condition for which we should error out... * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in] handle associated with entry. * @param[in] expanded attributes (rhs of map). * @param[in] entry to retrieve attributes from. * @return * - Number of maps successfully applied. * - -1 on failure. */ int rlm_ldap_map_do(const rlm_ldap_t *inst, REQUEST *request, LDAP *handle, rlm_ldap_map_exp_t const *expanded, LDAPMessage *entry) { vp_map_t const *map; unsigned int total = 0; int applied = 0; /* How many maps have been applied to the current request */ rlm_ldap_result_t result; char const *name; RINDENT(); for (map = expanded->maps; map != NULL; map = map->next) { int ret; name = expanded->attrs[total++]; /* * Binary safe */ result.values = ldap_get_values_len(handle, entry, name); if (!result.values) { RDEBUG3("Attribute \"%s\" not found in LDAP object", name); goto next; } /* * Find out how many values there are for the * attribute and extract all of them. */ result.count = ldap_count_values_len(result.values); /* * If something bad happened, just skip, this is probably * a case of the dst being incorrect for the current * request context */ ret = map_to_request(request, map, rlm_ldap_map_getvalue, &result); if (ret == -1) return -1; /* Fail */ /* * How many maps we've processed */ applied++; next: ldap_value_free_len(result.values); } REXDENT(); /* * Retrieve any valuepair attributes from the result, these are generic values specifying * a radius list, operator and value. */ if (inst->valuepair_attr) { struct berval **values; int count, i; values = ldap_get_values_len(handle, entry, inst->valuepair_attr); count = ldap_count_values_len(values); RINDENT(); for (i = 0; i < count; i++) { vp_map_t *attr; char *value; value = rlm_ldap_berval_to_string(request, values[i]); RDEBUG3("Parsing attribute string '%s'", value); if (map_afrom_attr_str(request, &attr, value, REQUEST_CURRENT, PAIR_LIST_REPLY, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing '%s' value \"%s\" as valuepair (%s), skipping...", fr_strerror(), inst->valuepair_attr, value); talloc_free(value); continue; } if (map_to_request(request, attr, map_to_vp, NULL) < 0) { RWDEBUG("Failed adding \"%s\" to request, skipping...", value); } else { applied++; } talloc_free(attr); talloc_free(value); } REXDENT(); ldap_value_free_len(values); } return applied; }
/* * Process an EAP request */ fr_tls_status_t eaptls_process(eap_handler_t *handler) { tls_session_t *tls_session = (tls_session_t *) handler->opaque; EAPTLS_PACKET *tlspacket; fr_tls_status_t status; REQUEST *request = handler->request; if (!request) return FR_TLS_FAIL; RDEBUG2("Continuing EAP-TLS"); SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, request); if (handler->certs) fr_pair_add(&request->packet->vps, fr_pair_list_copy(request->packet, 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); if ((status == FR_TLS_INVALID) || (status == FR_TLS_FAIL)) { REDEBUG("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); } else { RDEBUG2("[eaptls verify] = %s", fr_int2str(fr_tls_status_table, status, "<INVALID>")); } switch (status) { default: case FR_TLS_INVALID: case FR_TLS_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 FR_TLS_SUCCESS: goto done; /* * Normal TLS request, continue with the "get rest * of fragments" phase. */ case FR_TLS_REQUEST: eaptls_request(handler->eap_ds, tls_session); status = FR_TLS_HANDLED; goto done; /* * The handshake is done, and we're in the "tunnel * data" phase. */ case FR_TLS_OK: RDEBUG2("Done initial handshake"); /* * Get the rest of the fragments. */ case FR_TLS_FIRST_FRAGMENT: case FR_TLS_MORE_FRAGMENTS: case FR_TLS_LENGTH_INCLUDED: break; } /* * Extract the TLS packet from the buffer. */ if ((tlspacket = eaptls_extract(request, handler->eap_ds, status)) == NULL) { status = FR_TLS_FAIL; goto done; } /* * 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)) { talloc_free(tlspacket); REDEBUG("Exceeded maximum record size"); status = FR_TLS_FAIL; goto done; } /* * No longer needed. */ talloc_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)) { /* * The initialization may be finished, but if * there more fragments coming, then send ACK, * and get the caller to continue the * conversation. */ if ((status == FR_TLS_MORE_FRAGMENTS) || (status == FR_TLS_FIRST_FRAGMENT)) { /* * Send the ACK. */ eaptls_send_ack(handler, tls_session->peap_flag); RDEBUG2("Init is done, but tunneled data is fragmented"); status = FR_TLS_HANDLED; goto done; } status = tls_application_data(tls_session, request); goto done; } /* * Continue the handshake. */ status = eaptls_operation(status, handler); if (status == FR_TLS_SUCCESS) { #define MAX_SESSION_SIZE (256) size_t size; VALUE_PAIR *vps; char buffer[2 * MAX_SESSION_SIZE + 1]; /* * Restore the cached VPs before processing the * application data. */ size = tls_session->ssl->session->session_id_length; if (size > MAX_SESSION_SIZE) size = MAX_SESSION_SIZE; fr_bin2hex(buffer, tls_session->ssl->session->session_id, size); vps = SSL_SESSION_get_ex_data(tls_session->ssl->session, fr_tls_ex_index_vps); if (!vps) { RWDEBUG("No information in cached session %s", buffer); } else { vp_cursor_t cursor; VALUE_PAIR *vp; RDEBUG("Adding cached attributes from session %s", buffer); /* * The cbtls_get_session() function doesn't have * access to sock->certs or handler->certs, which * is where the certificates normally live. So * the certs are all in the VPS list here, and * have to be manually extracted. */ RINDENT(); for (vp = fr_cursor_init(&cursor, &vps); vp; vp = fr_cursor_next(&cursor)) { /* * TLS-* attrs get added back to * the request list. */ if ((vp->da->vendor == 0) && (vp->da->attr >= PW_TLS_CERT_SERIAL) && (vp->da->attr <= PW_TLS_CLIENT_CERT_SUBJECT_ALT_NAME_UPN)) { /* * Certs already exist. Don't re-add them. */ if (!handler->certs) { rdebug_pair(L_DBG_LVL_2, request, vp, "request:"); fr_pair_add(&request->packet->vps, fr_pair_copy(request->packet, vp)); } } else { rdebug_pair(L_DBG_LVL_2, request, vp, "reply:"); fr_pair_add(&request->reply->vps, fr_pair_copy(request->reply, vp)); } } REXDENT(); } } done: SSL_set_ex_data(tls_session->ssl, FR_TLS_EX_INDEX_REQUEST, NULL); return status; }
/** Insert a new entry into the data store * * @copydetails cache_entry_insert_t */ static cache_status_t cache_entry_insert(UNUSED rlm_cache_config_t const *config, void *driver_inst, REQUEST *request, UNUSED void *handle, const rlm_cache_entry_t *c) { rlm_cache_redis_t *driver = driver_inst; TALLOC_CTX *pool; vp_map_t *map; fr_redis_conn_t *conn; fr_redis_cluster_state_t state; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; static char const command[] = "RPUSH"; char const **argv; size_t *argv_len; char const **argv_p; size_t *argv_len_p; int pipelined = 0; /* How many commands pending in the pipeline */ redisReply *replies[5]; /* Should have the same number of elements as pipelined commands */ size_t reply_num = 0, i; char *p; int cnt; vp_tmpl_t expires_value; vp_map_t expires = { .op = T_OP_SET, .lhs = &driver->expires_attr, .rhs = &expires_value, }; vp_tmpl_t created_value; vp_map_t created = { .op = T_OP_SET, .lhs = &driver->created_attr, .rhs = &created_value, .next = &expires }; /* * Encode the entry created date */ tmpl_init(&created_value, TMPL_TYPE_DATA, "<TEMP>", 6, T_BARE_WORD); created_value.tmpl_data_type = PW_TYPE_DATE; created_value.tmpl_data_length = sizeof(created_value.tmpl_data_value.date); created_value.tmpl_data_value.date = c->created; /* * Encode the entry expiry time * * Although Redis objects expire on their own, we still need this * to ignore entries that were created before the last epoch. */ tmpl_init(&expires_value, TMPL_TYPE_DATA, "<TEMP>", 6, T_BARE_WORD); expires_value.tmpl_data_type = PW_TYPE_DATE; expires_value.tmpl_data_length = sizeof(expires_value.tmpl_data_value.date); expires_value.tmpl_data_value.date = c->expires; expires.next = c->maps; /* Head of the list */ for (cnt = 0, map = &created; map; cnt++, map = map->next); /* * The majority of serialized entries should be under 1k. * * @todo We should really calculate this using some sort of moving average. */ pool = talloc_pool(request, 1024); if (!pool) return CACHE_ERROR; argv_p = argv = talloc_array(pool, char const *, (cnt * 3) + 2); /* pair = 3 + cmd + key */ argv_len_p = argv_len = talloc_array(pool, size_t, (cnt * 3) + 2); /* pair = 3 + cmd + key */ *argv_p++ = command; *argv_len_p++ = sizeof(command) - 1; *argv_p++ = (char const *)c->key; *argv_len_p++ = c->key_len; /* * Add the maps to the command string in reverse order */ for (map = &created; map; map = map->next) { if (fr_redis_tuple_from_map(pool, argv_p, argv_len_p, map) < 0) { REDEBUG("Failed encoding map as Redis K/V pair"); talloc_free(pool); return CACHE_ERROR; } argv_p += 3; argv_len_p += 3; } RDEBUG3("Pipelining commands"); RINDENT(); for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, c->key, c->key_len, false); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) { /* * Start the transaction, as we need to set an expiry time too. */ if (c->expires > 0) { RDEBUG3("MULTI"); if (redisAppendCommand(conn->handle, "MULTI") != REDIS_OK) { append_error: REXDENT(); RERROR("Failed appending Redis command to output buffer: %s", conn->handle->errstr); talloc_free(pool); return CACHE_ERROR; } pipelined++; } if (RDEBUG_ENABLED3) { p = fr_asprint(request, (char const *)c->key, c->key_len, '\0'); RDEBUG3("DEL \"%s\"", p); talloc_free(p); } if (redisAppendCommand(conn->handle, "DEL %b", c->key, c->key_len) != REDIS_OK) goto append_error; pipelined++; if (RDEBUG_ENABLED3) { RDEBUG3("argv command"); RINDENT(); for (i = 0; i < talloc_array_length(argv); i++) { p = fr_asprint(request, argv[i], argv_len[i], '\0'); RDEBUG3("%s", p); talloc_free(p); } REXDENT(); } redisAppendCommandArgv(conn->handle, talloc_array_length(argv), argv, argv_len); pipelined++; /* * Set the expiry time and close out the transaction. */ if (c->expires > 0) { if (RDEBUG_ENABLED3) { p = fr_asprint(request, (char const *)c->key, c->key_len, '\"'); RDEBUG3("EXPIREAT \"%s\" %li", p, (long)c->expires); talloc_free(p); } if (redisAppendCommand(conn->handle, "EXPIREAT %b %i", c->key, c->key_len, c->expires) != REDIS_OK) goto append_error; pipelined++; RDEBUG3("EXEC"); if (redisAppendCommand(conn->handle, "EXEC") != REDIS_OK) goto append_error; pipelined++; } REXDENT(); reply_num = fr_redis_pipeline_result(&status, replies, sizeof(replies) / sizeof(*replies), conn, pipelined); reply = replies[0]; } talloc_free(pool); if (s_ret != REDIS_RCODE_SUCCESS) { RERROR("Failed inserting entry"); return CACHE_ERROR; } RDEBUG3("Command results"); RINDENT(); for (i = 0; i < reply_num; i++) { fr_redis_reply_print(L_DBG_LVL_3, replies[i], request, i); fr_redis_reply_free(replies[i]); } REXDENT(); return CACHE_OK; } /** Call delete the cache entry from redis * * @copydetails cache_entry_expire_t */ static cache_status_t cache_entry_expire(UNUSED rlm_cache_config_t const *config, void *driver_inst, REQUEST *request, UNUSED void *handle, uint8_t const *key, size_t key_len) { rlm_cache_redis_t *driver = driver_inst; fr_redis_cluster_state_t state; fr_redis_conn_t *conn; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; for (s_ret = fr_redis_cluster_state_init(&state, &conn, driver->cluster, request, key, key_len, false); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, driver->cluster, request, status, &reply)) { reply = redisCommand(conn->handle, "DEL %b", key, key_len); status = fr_redis_command_status(conn, reply); } if (s_ret != REDIS_RCODE_SUCCESS) { RERROR("Failed expiring entry"); fr_redis_reply_free(reply); return CACHE_ERROR; } rad_assert(reply); /* clang scan */ if (reply->type == REDIS_REPLY_INTEGER) { fr_redis_reply_free(reply); if (reply->integer) return CACHE_OK; /* Affected */ return CACHE_MISS; } REDEBUG("Bad result type, expected integer, got %s", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); fr_redis_reply_free(reply); return CACHE_ERROR; } extern cache_driver_t rlm_cache_redis; cache_driver_t rlm_cache_redis = { .name = "rlm_cache_redis", .instantiate = mod_instantiate, .inst_size = sizeof(rlm_cache_redis_t), .free = cache_entry_free, .find = cache_entry_find, .insert = cache_entry_insert, .expire = cache_entry_expire, };
/** 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; } } }
static rlm_rcode_t rlm_sql_process_groups(rlm_sql_t *inst, REQUEST *request, rlm_sql_handle_t **handle, sql_fall_through_t *do_fall_through) { rlm_rcode_t rcode = RLM_MODULE_NOOP; VALUE_PAIR *check_tmp = NULL, *reply_tmp = NULL, *sql_group = NULL; rlm_sql_grouplist_t *head = NULL, *entry = NULL; char *expanded = NULL; int rows; rad_assert(request->packet != NULL); /* * Get the list of groups this user is a member of */ rows = sql_get_grouplist(inst, handle, request, &head); if (rows < 0) { REDEBUG("Error retrieving group list"); return RLM_MODULE_FAIL; } if (rows == 0) { RDEBUG2("User not found in any groups"); rcode = RLM_MODULE_NOTFOUND; goto finish; } rad_assert(head); RDEBUG2("User found in the group table"); entry = head; do { /* * 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')) { vp_cursor_t cursor; VALUE_PAIR *vp; /* * Expand the group query */ if (radius_axlat(&expanded, request, inst->config->authorize_group_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto finish; } rows = sql_getvpdata(request, inst, handle, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving check pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } /* * If we got check rows we need to process them before we decide to process the reply rows */ if ((rows > 0) && (paircompare(request, request->packet->vps, check_tmp, &request->reply->vps) != 0)) { pairfree(&check_tmp); pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); continue; } RDEBUG2("Group \"%s\": Conditional check items matched", entry->name); rcode = RLM_MODULE_OK; RDEBUG2("Group \"%s\": Merging assignment check items", entry->name); RINDENT(); for (vp = fr_cursor_init(&cursor, &check_tmp); vp; vp = fr_cursor_next(&cursor)) { if (!fr_assignment_op[vp->op]) continue; rdebug_pair(2, request, vp); } REXDENT(); 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(request->reply, inst, handle, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("Error retrieving reply pairs for group %s", entry->name); rcode = RLM_MODULE_FAIL; goto finish; } *do_fall_through = fall_through(reply_tmp); RDEBUG2("Group \"%s\": Merging reply items", entry->name); rcode = RLM_MODULE_OK; rdebug_pair_list(L_DBG_LVL_2, request, reply_tmp); radius_pairmove(request, &request->reply->vps, reply_tmp, true); reply_tmp = NULL; } pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); entry = entry->next; } while (entry != NULL && (*do_fall_through == FALL_THROUGH_YES)); finish: talloc_free(head); pairdelete(&request->packet->vps, PW_SQL_GROUP, 0, TAG_ANY); return rcode; }
/* * get the vps and put them in perl hash * If one VP have multiple values it is added as array_ref * Example for this is Cisco-AVPair that holds multiple values. * Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'} */ static void perl_store_vps(UNUSED TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, HV *rad_hv, const char *hash_name, const char *list_name) { VALUE_PAIR *vp; hv_undef(rad_hv); fr_cursor_t cursor; RINDENT(); fr_pair_list_sort(vps, fr_pair_cmp_by_da_tag); for (vp = fr_cursor_init(&cursor, vps); vp; vp = fr_cursor_next(&cursor)) { VALUE_PAIR *next; char const *name; char namebuf[256]; /* * Tagged attributes are added to the hash with name * <attribute>:<tag>, others just use the normal attribute * name as the key. */ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { snprintf(namebuf, sizeof(namebuf), "%s:%d", vp->da->name, vp->tag); name = namebuf; } else { name = vp->da->name; } /* * We've sorted by type, then tag, so attributes of the * same type/tag should follow on from each other. */ if ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)) { int i = 0; AV *av; av = newAV(); perl_vp_to_svpvn_element(request, av, vp, &i, hash_name, list_name); do { perl_vp_to_svpvn_element(request, av, next, &i, hash_name, list_name); fr_cursor_next(&cursor); } while ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)); (void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0); continue; } /* * It's a normal single valued attribute */ switch (vp->vp_type) { case FR_TYPE_STRING: RDEBUG2("$%s{'%s'} = &%s:%s -> '%pV'", hash_name, vp->da->name, list_name, vp->da->name, &vp->data); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(vp->vp_strvalue, vp->vp_length), 0); break; case FR_TYPE_OCTETS: RDEBUG2("$%s{'%s'} = &%s:%s -> %pV", hash_name, vp->da->name, list_name, vp->da->name, &vp->data); (void)hv_store(rad_hv, name, strlen(name), newSVpvn((char const *)vp->vp_octets, vp->vp_length), 0); break; default: { char buffer[1024]; size_t len; len = fr_pair_value_snprint(buffer, sizeof(buffer), vp, '\0'); RDEBUG2("$%s{'%s'} = &%s:%s -> '%s'", hash_name, vp->da->name, list_name, vp->da->name, buffer); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(buffer, truncate_len(len, sizeof(buffer))), 0); } break; } } REXDENT(); }
/* * get the vps and put them in perl hash * If one VP have multiple values it is added as array_ref * Example for this is Cisco-AVPair that holds multiple values. * Which will be available as array_ref in $RAD_REQUEST{'Cisco-AVPair'} */ static void perl_store_vps(UNUSED TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR *vps, HV *rad_hv, const char *hashname, const char *list_name) { VALUE_PAIR *vp; hv_undef(rad_hv); vp_cursor_t cursor; RINDENT(); pairsort(&vps, attrtagcmp); for (vp = fr_cursor_init(&cursor, &vps); vp; vp = fr_cursor_next(&cursor)) { VALUE_PAIR *next; char const *name; char namebuf[256]; char buffer[1024]; size_t len; /* * Tagged attributes are added to the hash with name * <attribute>:<tag>, others just use the normal attribute * name as the key. */ if (vp->da->flags.has_tag && (vp->tag != TAG_ANY)) { snprintf(namebuf, sizeof(namebuf), "%s:%d", vp->da->name, vp->tag); name = namebuf; } else { name = vp->da->name; } /* * We've sorted by type, then tag, so attributes of the * same type/tag should follow on from each other. */ if ((next = fr_cursor_next_peek(&cursor)) && ATTRIBUTE_EQ(vp, next)) { int i; AV *av; av = newAV(); for (next = fr_cursor_next_by_da(&cursor, vp->da, vp->tag), i = 0; next; next = fr_cursor_next_by_da(&cursor, vp->da, vp->tag), i++) { switch (vp->da->type) { case PW_TYPE_STRING: RDEBUG("$%s{'%s'}[%i] = &%s:%s -> '%s'", hashname, next->da->name, i, list_name, next->da->name, next->vp_strvalue); av_push(av, newSVpvn(next->vp_strvalue, next->length)); break; case PW_TYPE_OCTETS: if (RDEBUG_ENABLED) { char *hex; hex = fr_abin2hex(request, next->vp_octets, next->length); RDEBUG("$%s{'%s'}[%i] = &%s:%s -> 0x%s", hashname, next->da->name, i, list_name, next->da->name, hex); talloc_free(hex); } av_push(av, newSVpvn((char const *)next->vp_octets, next->length)); break; default: len = vp_prints_value(buffer, sizeof(buffer), next, 0); RDEBUG("$%s{'%s'}[%i] = &%s:%s -> '%s'", hashname, next->da->name, i, list_name, next->da->name, buffer); av_push(av, newSVpvn(buffer, truncate_len(len, sizeof(buffer)))); break; } } (void)hv_store(rad_hv, name, strlen(name), newRV_noinc((SV *)av), 0); continue; } /* * It's a normal single valued attribute */ switch (vp->da->type) { case PW_TYPE_STRING: RDEBUG("$%s{'%s'} = &%s:%s -> '%s'", hashname, vp->da->name, list_name, vp->da->name, vp->vp_strvalue); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(vp->vp_strvalue, vp->length), 0); break; case PW_TYPE_OCTETS: if (RDEBUG_ENABLED) { char *hex; hex = fr_abin2hex(request, vp->vp_octets, vp->length); RDEBUG("$%s{'%s'} = &%s:%s -> 0x%s", hashname, vp->da->name, list_name, vp->da->name, hex); talloc_free(hex); } (void)hv_store(rad_hv, name, strlen(name), newSVpvn((char const *)vp->vp_octets, vp->length), 0); break; default: len = vp_prints_value(buffer, sizeof(buffer), vp, 0); RDEBUG("$%s{'%s'} = &%s:%s -> '%s'", hashname, vp->da->name, list_name, vp->da->name, buffer); (void)hv_store(rad_hv, name, strlen(name), newSVpvn(buffer, truncate_len(len, sizeof(buffer))), 0); break; } } REXDENT(); }