/** Convert a pair of redis reply objects to a map * * The maps can then be applied using #map_to_request. * * @param[in,out] ctx to allocate maps in. * @param[out] out Where to write the head of the new maps list. * @param[in] request The current request. * @param[in] key to process. * @param[in] op to process. * @param[in] value to process. * @return * - 0 on success. * - -1 on failure. */ int fr_redis_reply_to_map(TALLOC_CTX *ctx, vp_map_t **out, REQUEST *request, redisReply *key, redisReply *op, redisReply *value) { vp_map_t *map = NULL; ssize_t slen; *out = NULL; if (key->type != REDIS_REPLY_STRING) { REDEBUG("Bad key type, expected string, got %s", fr_int2str(redis_reply_types, key->type, "<UNKNOWN>")); error: TALLOC_FREE(map); return -1; } if (op->type != REDIS_REPLY_STRING) { REDEBUG("Bad key type, expected string, got %s", fr_int2str(redis_reply_types, op->type, "<UNKNOWN>")); goto error; } RDEBUG3("Got key : %s", key->str); RDEBUG3("Got op : %s", op->str); RDEBUG3("Got value : %pV", fr_box_strvalue_len(value->str, value->len)); map = talloc_zero(ctx, vp_map_t); slen = tmpl_afrom_attr_str(map, NULL, &map->lhs, key->str, &(vp_tmpl_rules_t){ .dict_def = request->dict });
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; }
static int vector_gsm_from_triplets(eap_session_t *eap_session, VALUE_PAIR *vps, int idx, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; VALUE_PAIR *rand = NULL, *sres = NULL, *kc = NULL; fr_cursor_t cursor; int i; for (i = 0, (kc = fr_cursor_iter_by_da_init(&cursor, &vps, attr_eap_sim_kc)); (i < idx) && (kc = fr_cursor_next(&cursor)); i++); if (!kc) { RDEBUG3("No &control:%s[%i] attribute found, not using GSM triplets", attr_eap_sim_kc->name, idx); return 1; } if (kc->vp_length != SIM_VECTOR_GSM_KC_SIZE) { REDEBUG("&control:%s[%i] is not " STRINGIFY(SIM_VECTOR_GSM_KC_SIZE) " bytes, got %zu bytes", attr_eap_sim_kc->name, idx, kc->vp_length); return -1; } for (i = 0, (rand = fr_cursor_iter_by_da_init(&cursor, &vps, attr_eap_sim_rand)); (i < idx) && (rand = fr_cursor_next(&cursor)); i++); if (!rand) { RDEBUG3("No &control:%s[%i] attribute found, not using GSM triplets", attr_eap_sim_rand->name, idx); return 1; } if (rand->vp_length != SIM_VECTOR_GSM_RAND_SIZE) { REDEBUG("&control:EAP-SIM-Rand[%i] is not " STRINGIFY(SIM_RAND_SIZE) " bytes, got %zu bytes", idx, rand->vp_length); return -1; } for (i = 0, (sres = fr_cursor_iter_by_da_init(&cursor, &vps, attr_eap_sim_sres)); (i < idx) && (sres = fr_cursor_next(&cursor)); i++); if (!sres) { RDEBUG3("No &control:%s[%i] attribute found, not using GSM triplets", attr_eap_sim_sres->name, idx); return 1; } if (sres->vp_length != SIM_VECTOR_GSM_SRES_SIZE) { REDEBUG("&control:%s[%i] is not " STRINGIFY(SIM_VECTOR_GSM_SRES_SIZE) " bytes, got %zu bytes", attr_eap_sim_sres->name, idx, sres->vp_length); return -1; } memcpy(keys->gsm.vector[idx].kc, kc->vp_strvalue, SIM_VECTOR_GSM_KC_SIZE); memcpy(keys->gsm.vector[idx].rand, rand->vp_octets, SIM_VECTOR_GSM_RAND_SIZE); memcpy(keys->gsm.vector[idx].sres, sres->vp_octets, SIM_VECTOR_GSM_SRES_SIZE); return 0; }
/* * Compare prefix/suffix. * * If they compare: * - if FR_STRIP_USER_NAME is present in check_list, * strip the username of prefix/suffix. * - if FR_STRIP_USER_NAME is not present in check_list, * add a FR_STRIPPED_USER_NAME to the request. */ static int prefix_suffix_cmp(UNUSED void *instance, REQUEST *request, VALUE_PAIR *req, VALUE_PAIR *check, VALUE_PAIR *check_list, UNUSED VALUE_PAIR **reply_list) { VALUE_PAIR *vp; char const *name; char rest[FR_MAX_STRING_LEN]; int len, namelen; int ret = -1; if (!request || !request->username) return -1; VP_VERIFY(check); name = request->username->vp_strvalue; RDEBUG3("Comparing name \"%s\" and check value \"%s\"", name, check->vp_strvalue); len = strlen(check->vp_strvalue); if (check->da == attr_prefix) { ret = strncmp(name, check->vp_strvalue, len); if (ret == 0) strlcpy(rest, name + len, sizeof(rest)); } else if (check->da == attr_suffix) { namelen = strlen(name); if (namelen >= len) { ret = strcmp(name + namelen - len, check->vp_strvalue); if (ret == 0) strlcpy(rest, name, namelen - len + 1); } } if (ret != 0) return ret; /* * If Strip-User-Name == No, then don't do any more. */ vp = fr_pair_find_by_da(check_list, attr_strip_user_name, TAG_ANY); if (vp && !vp->vp_uint32) return ret; /* * See where to put the stripped user name. */ vp = fr_pair_find_by_da(check_list, attr_stripped_user_name, TAG_ANY); if (!vp) { /* * If "request" is NULL, then the memory will be * lost! */ MEM(vp = fr_pair_afrom_da(request->packet, attr_stripped_user_name)); fr_pair_add(&req, vp); request->username = vp; } fr_pair_value_strcpy(vp, rest); return ret; }
static int tls_socket_write(rad_listen_t *listener, REQUEST *request) { uint8_t *p; ssize_t rcode; listen_socket_t *sock = listener->data; p = sock->ssn->dirty_out.data; while (p < (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used)) { RDEBUG3("Writing to socket %d", request->packet->sockfd); rcode = write(request->packet->sockfd, p, (sock->ssn->dirty_out.data + sock->ssn->dirty_out.used) - p); if (rcode <= 0) { RDEBUG("Error writing to TLS socket: %s", strerror(errno)); tls_socket_close(listener); return 0; } p += rcode; } sock->ssn->dirty_out.used = 0; return 1; }
static void check_pair(REQUEST *request, VALUE_PAIR *check_item, VALUE_PAIR *reply_item, int *pass, int *fail) { int compare; if (check_item->op == T_OP_SET) return; compare = paircmp(check_item, reply_item); if (compare < 0) { REDEBUG("Comparison failed: %s", fr_strerror()); } if (compare == 1) { ++*(pass); } else { ++*(fail); } if (RDEBUG_ENABLED3) { char rule[1024], pair[1024]; vp_prints(rule, sizeof(rule), check_item); vp_prints(pair, sizeof(pair), reply_item); RDEBUG3("%s %s %s", pair, compare == 1 ? "allowed by" : "disallowed by", rule); } return; }
/** Called resume_at the delay is complete, and we're running from the interpreter * */ static rlm_rcode_t mod_delay_return(REQUEST *request, UNUSED void *instance, UNUSED void *thread, void *ctx) { struct timeval *yielded = talloc_get_type_abort(ctx, struct timeval); /* * Print how long the delay *really* was. */ if (RDEBUG_ENABLED3) { struct timeval delayed, now; gettimeofday(&now, NULL); fr_timeval_subtract(&delayed, &now, yielded); RDEBUG3("Request delayed by %pV", fr_box_timeval(delayed)); } talloc_free(yielded); 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; }
static xlat_action_t xlat_delay_resume(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request, UNUSED void const *xlat_inst, UNUSED void *xlat_thread_inst, UNUSED fr_value_box_t **in, void *rctx) { struct timeval *yielded_at = talloc_get_type_abort(rctx, struct timeval); struct timeval delayed, now; fr_value_box_t *vb; gettimeofday(&now, NULL); fr_timeval_subtract(&delayed, &now, yielded_at); talloc_free(yielded_at); RDEBUG3("Request delayed by %pVs", fr_box_timeval(delayed)); MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_TIMEVAL, NULL, false)); vb->vb_timeval = delayed; fr_cursor_insert(out, vb); return XLAT_ACTION_DONE; }
static rlm_rcode_t CC_HINT(nonnull) mod_delay(void *instance, UNUSED void *thread, REQUEST *request) { rlm_delay_t const *inst = instance; struct timeval delay, resume_at, *yielded_at; if (inst->delay) { if (tmpl_aexpand(request, &delay, request, inst->delay, NULL, NULL) < 0) return RLM_MODULE_FAIL; } else { memset(&delay, 0, sizeof(delay)); } /* * Record the time that we yielded the request */ MEM(yielded_at = talloc(request, struct timeval)); if (gettimeofday(yielded_at, NULL) < 0) { REDEBUG("Failed getting current time: %s", fr_syserror(errno)); return RLM_MODULE_FAIL; } /* * Setup the delay for this request */ if (delay_add(request, &resume_at, yielded_at, &delay, inst->force_reschedule, inst->delay) != 0) { return RLM_MODULE_NOOP; } RDEBUG3("Current time %pV, resume time %pV", fr_box_timeval(*yielded_at), fr_box_timeval(resume_at)); if (unlang_event_module_timeout_add(request, _delay_done, yielded_at, &resume_at) < 0) { RPEDEBUG("Adding event failed"); return RLM_MODULE_FAIL; } return unlang_module_yield(request, mod_delay_return, mod_delay_cancel, yielded_at); }
/* * The S flag is set only within the EAP-TLS start message sent * from the EAP server to the peer. * * Similarly, when the EAP server receives an EAP-Response with * the M bit set, it MUST respond with an EAP-Request with * EAP-Type=EAP-TLS and no data. This serves as a fragment * ACK. The EAP peer MUST wait. */ static fr_tls_status_t eaptls_verify(eap_handler_t *handler) { EAP_DS *eap_ds = handler->eap_ds; tls_session_t *tls_session = handler->opaque; EAP_DS *prev_eap_ds = handler->prev_eap_ds; eaptls_packet_t *eaptls_packet; REQUEST *request = handler->request; size_t frag_len; /* * We don't check ANY of the input parameters. It's all * code which works together, so if something is wrong, * we SHOULD core dump. * * e.g. if eap_ds is NULL, of if eap_ds->response is * NULL, of if it's NOT an EAP-Response, or if the packet * is too short. See eap_validation()., in ../../eap.c */ eaptls_packet = (eaptls_packet_t *)eap_ds->response->type.data; /* * First output the flags (for debugging) */ RDEBUG3("Peer sent flags %c%c%c", TLS_START(eaptls_packet->flags) ? 'S' : '-', TLS_MORE_FRAGMENTS(eaptls_packet->flags) ? 'M' : '-', TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 'L' : '-'); /* * check for ACK * * If there's no TLS data, or there's 1 byte of TLS data, * with the flags set to zero, then it's an ACK. * * Find if this is a reply to the previous request sent */ if ((!eaptls_packet) || ((eap_ds->response->length == EAP_HEADER_LEN + 2) && ((eaptls_packet->flags & 0xc0) == 0x00))) { if (prev_eap_ds && (prev_eap_ds->request->id == eap_ds->response->id)) { return tls_ack_handler(handler->opaque, request); } else { REDEBUG("Received Invalid TLS ACK"); return FR_TLS_INVALID; } } /* * We send TLS_START, but do not receive it. */ if (TLS_START(eaptls_packet->flags)) { REDEBUG("Peer sent EAP-TLS Start message (only the server is allowed to do this)"); return FR_TLS_INVALID; } /* * Calculate this fragment's length */ frag_len = eap_ds->response->length - (EAP_HEADER_LEN + (TLS_LENGTH_INCLUDED(eaptls_packet->flags) ? 6 : 2)); /* * The L bit (length included) is set to indicate the * presence of the four octet TLS Message Length field, * and MUST be set for the first fragment of a fragmented * TLS message or set of messages. * * The M bit (more fragments) is set on all but the last * fragment. * * The S bit (EAP-TLS start) is set in an EAP-TLS Start * message. This differentiates the EAP-TLS Start message * from a fragment acknowledgement. */ if (TLS_LENGTH_INCLUDED(eaptls_packet->flags)) { size_t total_len = eaptls_packet->data[2] * 256 | eaptls_packet->data[3]; if (frag_len > total_len) { REDEBUG("TLS fragment length (%zu bytes) greater than TLS record length (%zu bytes)", frag_len, total_len); return FR_TLS_INVALID; } if (tls_session->tls_record_transfer_started) { REDEBUG("TLS Length Included (L) flag set, which indicates a new fragment transfer, " "but previous transfer was not complete"); return FR_TLS_INVALID; } /* * This is the first fragment of a fragmented TLS record transfer. */ RDEBUG2("Peer indicated complete TLS record size will be %zu bytes", total_len); if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { /* * The supplicant is free to send fragments of wildly varying * lengths, but the vast majority won't. * * In this calculation we take into account the fact that the future * fragments are likely to be 4 bytes larger than the initial one * as they won't contain the length field. */ if (frag_len + 4) { /* check for wrap, else clang scan gets excited */ RDEBUG2("Expecting %i TLS record fragments", (int)((((total_len - frag_len) + ((frag_len + 4) - 1)) / (frag_len + 4)) + 1)); } /* * First fragment. tls_record_transfer_started bool was false, * and we received a length included + more fragments packet. */ RDEBUG2("Got first TLS record fragment (%zu bytes). Peer indicated more fragments " "to follow", frag_len); tls_session->tls_record_in_total_len = total_len; tls_session->tls_record_in_recvd_len = frag_len; tls_session->tls_record_transfer_started = true; return FR_TLS_FIRST_FRAGMENT; } /* * Else this is the complete TLS record. */ if (total_len != frag_len) { REDEBUG("Peer indicated no more fragments, but TLS record length (%zu bytes) " "does not match EAP-TLS data length (%zu bytes)", total_len, frag_len); return FR_TLS_INVALID; } /* * RFC5216 doesn't specify explicitly whether a non-fragmented * packet should include the length or not. * * We support both options for maximum compatibility. */ RDEBUG2("Got complete TLS record, with length (%zu bytes)", frag_len); return FR_TLS_LENGTH_INCLUDED; } if (TLS_MORE_FRAGMENTS(eaptls_packet->flags)) { /* * If this is not an ongoing transfer, and we have the M flag * then this record transfer is invalid. */ if (!tls_session->tls_record_transfer_started) { REDEBUG("TLS More (M) flag set, but no fragmented record transfer was in progress"); return FR_TLS_INVALID; } /* * If this is an ongoing transfer, and we have the M flag, * then this is just an additional fragment. */ RDEBUG2("Got additional TLS record fragment (%zu bytes). Peer indicated more fragments to follow", frag_len); tls_session->tls_record_in_recvd_len += frag_len; if (tls_session->tls_record_in_recvd_len > tls_session->tls_record_in_total_len) { REDEBUG("Total received TLS record fragments (%zu bytes), exceeds " "indicated TLS record length (%zu bytes)", tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); return FR_TLS_INVALID; } return FR_TLS_MORE_FRAGMENTS; } /* * No L flag and no M flag. This is either the final fragment, * or a new transfer that was not started with a L flag, which * RFC5216 hints, may be acceptable. * * If it's an in-progress record transfer, check we now have * the complete record. */ if (tls_session->tls_record_transfer_started) { tls_session->tls_record_transfer_started = false; RDEBUG2("Got final TLS record fragment (%zu bytes)", frag_len); tls_session->tls_record_in_recvd_len += frag_len; if (tls_session->tls_record_in_recvd_len != tls_session->tls_record_in_total_len) { REDEBUG("Total received TLS record fragments (%zu bytes), does not equal indicated " "TLS record length (%zu bytes)", tls_session->tls_record_in_recvd_len, tls_session->tls_record_in_total_len); return FR_TLS_INVALID; } return FR_TLS_OK; } /* * None of the flags are set, it wasn't an in progress transfer, * but it's still a valid EAP-TLS packet. */ RDEBUG2("Got complete TLS record (%zu bytes)", frag_len); return FR_TLS_OK; }
/* * *presult is "did comparison match or not" */ static int radius_do_cmp(REQUEST *request, int *presult, FR_TOKEN lt, const char *pleft, FR_TOKEN token, FR_TOKEN rt, const char *pright, int cflags, int modreturn) { int result; uint32_t lint, rint; VALUE_PAIR *vp = NULL; #ifdef HAVE_REGEX_H char buffer[8192]; #else cflags = cflags; /* -Wunused */ #endif rt = rt; /* -Wunused */ if (lt == T_BARE_WORD) { /* * Maybe check the last return code. */ if (token == T_OP_CMP_TRUE) { int isreturn; /* * Looks like a return code, treat is as such. */ isreturn = fr_str2int(modreturn_table, pleft, -1); if (isreturn != -1) { *presult = (modreturn == isreturn); return TRUE; } } /* * Bare words on the left can be attribute names. */ if (radius_get_vp(request, pleft, &vp)) { VALUE_PAIR myvp; /* * VP exists, and that's all we're looking for. */ if (token == T_OP_CMP_TRUE) { *presult = (vp != NULL); return TRUE; } if (!vp) { DICT_ATTR *da; /* * The attribute on the LHS may * have been a dynamically * registered callback. i.e. it * doesn't exist as a VALUE_PAIR. * If so, try looking for it. */ da = dict_attrbyname(pleft); if (da && (da->vendor == 0) && radius_find_compare(da->attr)) { VALUE_PAIR *check = pairmake(pleft, pright, token); *presult = (radius_callback_compare(request, NULL, check, NULL, NULL) == 0); RDEBUG3(" Callback returns %d", *presult); pairfree(&check); return TRUE; } RDEBUG2(" (Attribute %s was not found)", pleft); *presult = 0; return TRUE; } #ifdef HAVE_REGEX_H /* * Regex comparisons treat everything as * strings. */ if ((token == T_OP_REG_EQ) || (token == T_OP_REG_NE)) { vp_prints_value(buffer, sizeof(buffer), vp, 0); pleft = buffer; goto do_checks; } #endif memcpy(&myvp, vp, sizeof(myvp)); if (!pairparsevalue(&myvp, pright)) { RDEBUG2("Failed parsing \"%s\": %s", pright, fr_strerror()); return FALSE; } myvp.operator = token; *presult = paircmp(&myvp, vp); RDEBUG3(" paircmp -> %d", *presult); return TRUE; } /* else it's not a VP in a list */ } #ifdef HAVE_REGEX_H do_checks: #endif switch (token) { case T_OP_GE: case T_OP_GT: case T_OP_LE: case T_OP_LT: if (!all_digits(pright)) { RDEBUG2(" (Right field is not a number at: %s)", pright); return FALSE; } rint = strtoul(pright, NULL, 0); if (!all_digits(pleft)) { RDEBUG2(" (Left field is not a number at: %s)", pleft); return FALSE; } lint = strtoul(pleft, NULL, 0); break; default: lint = rint = 0; /* quiet the compiler */ break; } switch (token) { case T_OP_CMP_TRUE: /* * Check for truth or falsehood. */ if (all_digits(pleft)) { lint = strtoul(pleft, NULL, 0); result = (lint != 0); } else { result = (*pleft != '\0'); } break; case T_OP_CMP_EQ: result = (strcmp(pleft, pright) == 0); break; case T_OP_NE: result = (strcmp(pleft, pright) != 0); break; case T_OP_GE: result = (lint >= rint); break; case T_OP_GT: result = (lint > rint); break; case T_OP_LE: result = (lint <= rint); break; case T_OP_LT: result = (lint < rint); break; #ifdef HAVE_REGEX_H case T_OP_REG_EQ: { int i, compare; regex_t reg; regmatch_t rxmatch[REQUEST_MAX_REGEX + 1]; /* * Include substring matches. */ compare = regcomp(®, pright, cflags); if (compare != 0) { if (debug_flag) { char errbuf[128]; regerror(compare, ®, errbuf, sizeof(errbuf)); DEBUG("ERROR: Failed compiling regular expression: %s", errbuf); } return FALSE; } compare = regexec(®, pleft, REQUEST_MAX_REGEX + 1, rxmatch, 0); regfree(®); /* * Add new %{0}, %{1}, etc. */ if (compare == 0) for (i = 0; i <= REQUEST_MAX_REGEX; i++) { char *r; free(request_data_get(request, request, REQUEST_DATA_REGEX | i)); /* * No %{i}, skip it. * We MAY have %{2} without %{1}. */ if (rxmatch[i].rm_so == -1) continue; /* * Copy substring into allocated buffer */ r = rad_malloc(rxmatch[i].rm_eo -rxmatch[i].rm_so + 1); memcpy(r, pleft + rxmatch[i].rm_so, rxmatch[i].rm_eo - rxmatch[i].rm_so); r[rxmatch[i].rm_eo - rxmatch[i].rm_so] = '\0'; request_data_add(request, request, REQUEST_DATA_REGEX | i, r, free); } result = (compare == 0); } break; case T_OP_REG_NE: { int compare; regex_t reg; regmatch_t rxmatch[REQUEST_MAX_REGEX + 1]; /* * Include substring matches. */ compare = regcomp(®, pright, cflags); if (compare != 0) { if (debug_flag) { char errbuf[128]; regerror(compare, ®, errbuf, sizeof(errbuf)); DEBUG("ERROR: Failed compiling regular expression: %s", errbuf); } return FALSE; } compare = regexec(®, pleft, REQUEST_MAX_REGEX + 1, rxmatch, 0); regfree(®); result = (compare != 0); } break; #endif default: DEBUG("ERROR: Comparison operator %s is not supported", fr_token_name(token)); result = FALSE; break; } *presult = result; return TRUE; }
static rlm_rcode_t mod_authorize(void *instance, REQUEST * request) { rlm_rcode_t rcode = RLM_MODULE_NOOP; rlm_sql_t *inst = instance; rlm_sql_handle_t *handle; VALUE_PAIR *check_tmp = NULL; VALUE_PAIR *reply_tmp = NULL; VALUE_PAIR *user_profile = NULL; bool user_found = false; bool dofallthrough = true; int rows; char *expanded = NULL; rad_assert(request != NULL); rad_assert(request->packet != NULL); rad_assert(request->reply != NULL); /* * Set, escape, and check the user attr here */ if (sql_set_user(inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } /* * Reserve a socket * * After this point use goto error or goto release to cleanup socket temporary pairlists and * temporary attributes. */ handle = sql_get_socket(inst); if (!handle) { rcode = RLM_MODULE_FAIL; goto error; } /* * Query the check table to find any conditions associated with this user/realm/whatever... */ if (inst->config->authorize_check_query && (inst->config->authorize_check_query[0] != '\0')) { if (radius_axlat(&expanded, request, inst->config->authorize_check_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(inst, &handle, request, &check_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) { goto skipreply; } /* * 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) { goto skipreply; } RDEBUG2("Check items matched"); radius_pairmove(request, &request->config_items, check_tmp, true); rcode = RLM_MODULE_OK; } if (inst->config->authorize_reply_query && (inst->config->authorize_reply_query[0] != '\0')) { /* * Now get the reply pairs since the paircompare matched */ if (radius_axlat(&expanded, request, inst->config->authorize_reply_query, sql_escape_func, inst) < 0) { REDEBUG("Error generating query"); rcode = RLM_MODULE_FAIL; goto error; } rows = sql_getvpdata(inst, &handle, request->reply, &reply_tmp, expanded); TALLOC_FREE(expanded); if (rows < 0) { REDEBUG("SQL query error"); rcode = RLM_MODULE_FAIL; goto error; } if (rows == 0) { goto skipreply; } if (!inst->config->read_groups) { dofallthrough = fallthrough(reply_tmp); } RDEBUG2("User found in radreply table"); user_found = true; radius_pairmove(request, &request->reply->vps, reply_tmp, true); rcode = RLM_MODULE_OK; } skipreply: /* * Clear out the pairlists */ pairfree(&check_tmp); pairfree(&reply_tmp); /* * dofallthrough is set to 1 by default so that if the user information * is not found, we will still process groups. If the user information, * however, *is* found, Fall-Through must be set in order to process * the groups as well. */ if (dofallthrough) { rlm_rcode_t ret; RDEBUG3("... falling-through to group processing"); ret = rlm_sql_process_groups(inst, request, handle, &dofallthrough); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * Repeat the above process with the default profile or User-Profile */ if (dofallthrough) { rlm_rcode_t ret; /* * Check for a default_profile or for a User-Profile. */ RDEBUG3("... falling-through to profile processing"); user_profile = pairfind(request->config_items, PW_USER_PROFILE, 0, TAG_ANY); char const *profile = user_profile ? user_profile->vp_strvalue : inst->config->default_profile; if (!profile || !*profile) { goto release; } RDEBUG2("Checking profile %s", profile); if (sql_set_user(inst, request, profile) < 0) { REDEBUG("Error setting profile"); rcode = RLM_MODULE_FAIL; goto error; } ret = rlm_sql_process_groups(inst, request, handle, &dofallthrough); switch (ret) { /* * Nothing bad happened, continue... */ case RLM_MODULE_UPDATED: rcode = RLM_MODULE_UPDATED; /* FALL-THROUGH */ case RLM_MODULE_OK: if (rcode != RLM_MODULE_UPDATED) { rcode = RLM_MODULE_OK; } /* FALL-THROUGH */ case RLM_MODULE_NOOP: user_found = true; break; case RLM_MODULE_NOTFOUND: break; default: rcode = ret; goto release; } } /* * At this point the key (user) hasn't be found in the check table, the reply table * or the group mapping table, and there was no matching profile. */ release: if (!user_found) { rcode = RLM_MODULE_NOTFOUND; } error: sql_release_socket(inst, handle); pairfree(&check_tmp); pairfree(&reply_tmp); 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; }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param thread specific data. * @param request The authorization request. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->user_key, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); return RLM_MODULE_FAIL; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || !cookie->jobj) { /* log error */ RERROR("failed to fetch document or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj)); { TALLOC_CTX *pool = talloc_pool(request, 1024); /* We need to do lots of allocs */ fr_cursor_t maps, vlms; vp_map_t *map_head = NULL, *map; vp_list_mod_t *vlm_head = NULL, *vlm; fr_cursor_init(&maps, &map_head); /* * Convert JSON data into maps */ if ((mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_CONTROL) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REPLY) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REQUEST) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_STATE) < 0)) { invalid: talloc_free(pool); rcode = RLM_MODULE_INVALID; goto finish; } fr_cursor_init(&vlms, &vlm_head); /* * Convert all the maps into list modifications, * which are guaranteed to succeed. */ for (map = fr_cursor_head(&maps); map; map = fr_cursor_next(&maps)) { if (map_to_list_mod(pool, &vlm, request, map, NULL, NULL) < 0) goto invalid; fr_cursor_insert(&vlms, vlm); } if (!vlm_head) { RDEBUG2("Nothing to update"); talloc_free(pool); rcode = RLM_MODULE_NOOP; goto finish; } /* * Apply the list of modifications */ for (vlm = fr_cursor_head(&vlms); vlm; vlm = fr_cursor_next(&vlms)) { int ret; ret = map_list_mod_apply(request, vlm); /* SHOULD NOT FAIL */ if (!fr_cond_assert(ret == 0)) { talloc_free(pool); rcode = RLM_MODULE_FAIL; goto finish; } } talloc_free(pool); } finish: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) fr_pool_connection_release(inst->pool, request, handle); /* return */ return rcode; }
/** Print out attribute info * * Prints out all instances of a current attribute, or all attributes in a list. * * At higher debugging levels, also prints out alternative decodings of the same * value. This is helpful to determine types for unknown attributes of long * passed vendors, or just crazy/broken NAS. * * It's also useful for exposing issues in the packet decoding functions, as in * some cases they get fed random garbage data. * * This expands to a zero length string. */ static ssize_t xlat_debug_attr(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, UNUSED size_t outlen) { VALUE_PAIR *vp, **vps; REQUEST *current; value_pair_tmpl_t vpt; vp_cursor_t cursor; char buffer[1024]; if (!RDEBUG_ENABLED2) { *out = '\0'; return -1; } while (isspace((int) *fmt)) fmt++; if (*fmt == '&') fmt++; if (radius_parse_attr(fmt, &vpt, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { return -1; } current = request; if (radius_request(¤t, vpt.request) < 0) return -2; vps = radius_list(current, vpt.list); if (!vps) { return -2; } RIDEBUG("Attributes matching \"%s\"", fmt); vp = fr_cursor_init(&cursor, vps); if (vpt.da) { vp = fr_cursor_next_by_num(&cursor, vpt.da->attr, vpt.da->vendor, TAG_ANY); } while (vp) { DICT_ATTR *dac = NULL; DICT_VENDOR *dv; VALUE_PAIR *vpc = NULL; FR_NAME_NUMBER const *type; vp_prints_value(buffer, sizeof(buffer), vp, '\''); if (vp->da->flags.has_tag) { RIDEBUG2("\t%s:%s:%i %s %s", fr_int2str(pair_lists, vpt.list, "<INVALID>"), vp->da->name, vp->tag, fr_int2str(fr_tokens, vp->op, "<INVALID>"), buffer); } else { RIDEBUG2("\t%s:%s %s %s", fr_int2str(pair_lists, vpt.list, "<INVALID>"), vp->da->name, fr_int2str(fr_tokens, vp->op, "<INVALID>"), buffer); } if (!RDEBUG_ENABLED3) { goto next_vp; } if (vp->da->vendor) { dv = dict_vendorbyvalue(vp->da->vendor); RDEBUG3("\t\tvendor : %i (%s)", vp->da->vendor, dv ? dv->name : "unknown"); } RDEBUG3("\t\ttype : %s", fr_int2str(dict_attr_types, vp->da->type, "<INVALID>")); RDEBUG3("\t\tlength : %zu", vp->length); dac = talloc_memdup(request, vp->da, sizeof(DICT_ATTR)); if (!dac) { return -1; } dac->flags.vp_free = 0; if (!RDEBUG_ENABLED4) { goto next_vp; } type = dict_attr_types; while (type->name) { int pad; ssize_t len; uint8_t const *data = NULL; vpc = NULL; if ((PW_TYPE) type->number == vp->da->type) { goto next_type; } switch (type->number) { case PW_TYPE_INVALID: /* Not real type */ case PW_TYPE_MAX: /* Not real type */ case PW_TYPE_EXTENDED: /* Not safe/appropriate */ case PW_TYPE_LONG_EXTENDED: /* Not safe/appropriate */ case PW_TYPE_TLV: /* Not safe/appropriate */ case PW_TYPE_VSA: /* @fixme We need special behaviour for these */ goto next_type; default: break; } dac->type = type->number; len = rad_vp2data(&data, vp); if (len < 0) { goto next_type; } if (data2vp(NULL, NULL, NULL, dac, data, len, len, &vpc) < 0) { goto next_type; } /* * data2vp has knowledge of expected format lengths, if the length * from rad_vp2data doesn't match, it encodes the attribute * as raw octets. This results in many useless debug lines with * the same hex string. */ if ((type->number != PW_TYPE_OCTETS) && (vpc->da->type == PW_TYPE_OCTETS)) { goto next_type; } if (!vp_prints_value(buffer, sizeof(buffer), vpc, '\'')) { goto next_type; } if ((pad = (11 - strlen(type->name))) < 0) { pad = 0; } /* * @fixme: if the value happens to decode as a VSA * (someone put a VSA into a VSA?), we probably to print * extended info for that/reparse */ RDEBUG4("\t\tas %s%*s: %s", type->name, pad, " ", buffer); next_type: talloc_free(vpc); type++; } next_vp: talloc_free(dac); if (vpt.da) { vp = fr_cursor_next_by_num(&cursor, vpt.da->attr, vpt.da->vendor, TAG_ANY); } else { vp = fr_cursor_next(&cursor); } } *out = '\0'; return 0; }
/** Locate a cache entry in redis * * @copydetails cache_entry_find_t */ static cache_status_t cache_entry_find(rlm_cache_entry_t **out, 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; size_t i; fr_redis_cluster_state_t state; fr_redis_conn_t *conn; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; vp_map_t *head = NULL, **last = &head; #ifdef HAVE_TALLOC_POOLED_OBJECT size_t pool_size = 0; #endif rlm_cache_entry_t *c; 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)) { /* * Grab all the data for this hash, should return an array * of alternating keys/values which we then convert into maps. */ if (RDEBUG_ENABLED3) { char *p; p = fr_asprint(NULL, (char const *)key, key_len, '"'); RDEBUG3("LRANGE %s 0 -1", key); talloc_free(p); } reply = redisCommand(conn->handle, "LRANGE %b 0 -1", key, key_len); status = fr_redis_command_status(conn, reply); } if (s_ret != REDIS_RCODE_SUCCESS) { RERROR("Failed retrieving entry"); fr_redis_reply_free(reply); return CACHE_ERROR; } rad_assert(reply); /* clang scan */ if (reply->type != REDIS_REPLY_ARRAY) { REDEBUG("Bad result type, expected array, got %s", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); fr_redis_reply_free(reply); return CACHE_ERROR; } RDEBUG3("Entry contains %zu elements", reply->elements); if (reply->elements == 0) { fr_redis_reply_free(reply); return CACHE_MISS; } if (reply->elements % 3) { REDEBUG("Invalid number of reply elements (%zu). " "Reply must contain triplets of keys operators and values", reply->elements); fr_redis_reply_free(reply); return CACHE_ERROR; } #ifdef HAVE_TALLOC_POOLED_OBJECT /* * We can get a pretty good idea of the required size of the pool */ for (i = 0; i < reply->elements; i += 3) { pool_size += sizeof(vp_map_t) + (sizeof(vp_tmpl_t) * 2); if (reply->element[i]->type == REDIS_REPLY_STRING) pool_size += reply->element[i]->len + 1; } /* * reply->elements gives us the number of chunks, as the maps are triplets, and there * are three chunks per map */ c = talloc_pooled_object(NULL, rlm_cache_entry_t, reply->elements, pool_size); memset(&pool, 0, sizeof(rlm_cache_entry_t)); #else c = talloc_zero(NULL, rlm_cache_entry_t); #endif /* * Convert the key/value pairs back into maps */ for (i = 0; i < reply->elements; i += 3) { if (fr_redis_reply_to_map(c, last, request, reply->element[i], reply->element[i + 1], reply->element[i + 2]) < 0) { talloc_free(c); fr_redis_reply_free(reply); return CACHE_ERROR; } last = &(*last)->next; } fr_redis_reply_free(reply); /* * Pull out the cache created date */ if ((head->lhs->tmpl_da->vendor == 0) && (head->lhs->tmpl_da->attr == PW_CACHE_CREATED)) { vp_map_t *map; c->created = head->rhs->tmpl_data_value.date; map = head; head = head->next; talloc_free(map); } /* * Pull out the cache expires date */ if ((head->lhs->tmpl_da->vendor == 0) && (head->lhs->tmpl_da->attr == PW_CACHE_EXPIRES)) { vp_map_t *map; c->expires = head->rhs->tmpl_data_value.date; map = head; head = head->next; talloc_free(map); } c->key = talloc_memdup(c, key, key_len); c->key_len = key_len; c->maps = head; *out = c; return CACHE_OK; }
/** Get one set of quintuplets from the request * */ static int vector_umts_from_quintuplets(eap_session_t *eap_session, VALUE_PAIR *vps, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; VALUE_PAIR *rand_vp = NULL, *xres_vp = NULL, *ck_vp = NULL, *ik_vp = NULL; VALUE_PAIR *autn_vp = NULL, *sqn_vp = NULL, *ak_vp = NULL; /* * Fetch AUTN */ autn_vp = fr_pair_find_by_da(vps, attr_eap_aka_autn, TAG_ANY); if (!autn_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_autn->name); return 1; } if (autn_vp->vp_length > SIM_VECTOR_UMTS_AUTN_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(SIM_VECTOR_UMTS_AUTN_SIZE) " bytes, got %zu bytes", attr_eap_aka_autn->name, autn_vp->vp_length); return -1; } /* * Fetch CK */ ck_vp = fr_pair_find_by_da(vps, attr_eap_aka_ck, TAG_ANY); if (!ck_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_ck->name); return 1; } if (ck_vp->vp_length > SIM_VECTOR_UMTS_CK_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(EAP_AKA_XRES_MAX_SIZE) " bytes, got %zu bytes", attr_eap_aka_ck->name, ck_vp->vp_length); return -1; } /* * Fetch IK */ ik_vp = fr_pair_find_by_da(vps, attr_eap_aka_ik, TAG_ANY); if (!ik_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_ik->name); return 1; } if (ik_vp->vp_length > SIM_VECTOR_UMTS_IK_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(SIM_VECTOR_UMTS_IK_SIZE) " bytes, got %zu bytes", attr_eap_aka_ik->name, ik_vp->vp_length); return -1; } /* * Fetch RAND */ rand_vp = fr_pair_find_by_da(vps, attr_eap_aka_rand, TAG_ANY); if (!rand_vp) { RDEBUG3("No &control:%s attribute found, not using quintuplet derivation", attr_eap_aka_rand->name); return 1; } if (rand_vp->vp_length != SIM_VECTOR_UMTS_RAND_SIZE) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(SIM_VECTOR_UMTS_RAND_SIZE) " bytes, " "got %zu bytes", attr_eap_aka_rand->name, rand_vp->vp_length); return -1; } /* * Fetch XRES */ xres_vp = fr_pair_find_by_da(vps, attr_eap_aka_xres, TAG_ANY); if (!xres_vp) { RDEBUG3("No &control:%s attribute found, not using UMTS quintuplets", attr_eap_aka_xres->name); return 1; } if (xres_vp->vp_length > SIM_VECTOR_UMTS_XRES_MAX_SIZE) { REDEBUG("&control:%s incorrect length. Expected < " STRINGIFY(EAP_AKA_XRES_MAX_SIZE) " bytes, got %zu bytes", attr_eap_aka_xres->name, xres_vp->vp_length); return -1; } /* * Fetch (optional) AK */ ak_vp = fr_pair_find_by_da(vps, attr_eap_aka_ak, TAG_ANY); if (ak_vp && (ak_vp->vp_length != MILENAGE_AK_SIZE)) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(MILENAGE_AK_SIZE) " bytes, got %zu bytes", attr_eap_aka_ak->name, ak_vp->vp_length); return -1; } /* * Fetch (optional) SQN */ sqn_vp = fr_pair_find_by_da(vps, attr_sim_sqn, TAG_ANY); if (sqn_vp && (sqn_vp->vp_length != MILENAGE_SQN_SIZE)) { REDEBUG("&control:%s incorrect length. Expected " STRINGIFY(MILENAGE_AK_SIZE) " bytes, got %zu bytes", attr_sim_sqn->name, sqn_vp->vp_length); return -1; } /* * SQN = AUTN[0..5] ⊕ AK * AK = AK */ if (ak_vp && !sqn_vp) { keys->sqn = uint48_from_buff(autn_vp->vp_octets) ^ uint48_from_buff(ak_vp->vp_octets); memcpy(keys->umts.vector.ak, ak_vp->vp_octets, sizeof(keys->umts.vector.ak)); /* * SQN = SQN * AK = AUTN[0..5] ⊕ SQN */ } else if (sqn_vp && !ak_vp) { keys->sqn = sqn_vp->vp_uint64; uint48_to_buff(keys->umts.vector.ak, uint48_from_buff(autn_vp->vp_octets) ^ sqn_vp->vp_uint64); /* * SQN = SQN * AK = AK */ } else if (sqn_vp && ak_vp) { keys->sqn = sqn_vp->vp_uint64; memcpy(keys->umts.vector.ak, ak_vp->vp_octets, sizeof(keys->umts.vector.ak)); /* * SQN = AUTN[0..5] * AK = 0x000000000000 */ } else { keys->sqn = uint48_from_buff(autn_vp->vp_octets); memset(keys->umts.vector.ak, 0, sizeof(keys->umts.vector.ak)); } memcpy(keys->umts.vector.autn, autn_vp->vp_octets, SIM_VECTOR_UMTS_AUTN_SIZE); memcpy(keys->umts.vector.ck, ck_vp->vp_octets, SIM_VECTOR_UMTS_CK_SIZE); memcpy(keys->umts.vector.ik, ik_vp->vp_octets, SIM_VECTOR_UMTS_IK_SIZE); memcpy(keys->umts.vector.rand, rand_vp->vp_octets, SIM_VECTOR_UMTS_RAND_SIZE); memcpy(keys->umts.vector.xres, xres_vp->vp_octets, xres_vp->vp_length); keys->umts.vector.xres_len = xres_vp->vp_length; /* xres is variable length */ return 0; }
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; }
/** Write accounting data to Couchbase documents * * Handle accounting requests and store the associated data into JSON documents * in couchbase mapping attribute names to JSON element names per the module configuration. * * When an existing document already exists for the same accounting section the new attributes * will be merged with the currently existing data. When conflicts arrise the new attribute * value will replace or be added to the existing value. * * @param instance The module instance. * @param thread specific data. * @param request The accounting request object. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_accounting(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ VALUE_PAIR *vp; /* radius value pair linked list */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ char document[MAX_VALUE_SIZE]; /* our document body */ char element[MAX_KEY_SIZE]; /* mapped radius attribute to element name */ int status = 0; /* account status type */ int docfound = 0; /* document found toggle */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* sanity check */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) == NULL) { /* log debug */ RDEBUG2("could not find status type in packet"); /* return */ return RLM_MODULE_NOOP; } /* set status */ status = vp->vp_uint32; /* acknowledge the request but take no action */ if (status == FR_STATUS_ACCOUNTING_ON || status == FR_STATUS_ACCOUNTING_OFF) { /* log debug */ RDEBUG2("handling accounting on/off request without action"); /* return */ return RLM_MODULE_OK; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->acct_key, NULL, NULL); if (slen < 0) { rcode = RLM_MODULE_FAIL; goto finish; } if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* attempt to fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error and object */ if (cb_error != LCB_SUCCESS || cookie->jerr != json_tokener_success || !cookie->jobj) { /* log error */ RERROR("failed to execute get request or parse returned json object"); /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* check cookie json object */ } else if (cookie->jobj) { /* set doc found */ docfound = 1; /* debugging */ RDEBUG3("parsed json body from couchbase: %s", json_object_to_json_string(cookie->jobj)); } /* start json document if needed */ if (docfound != 1) { /* debugging */ RDEBUG2("no existing document found - creating new json document"); /* create new json object */ cookie->jobj = json_object_new_object(); /* set 'docType' element for new document */ json_object_object_add(cookie->jobj, "docType", json_object_new_string(inst->doctype)); /* default startTimestamp and stopTimestamp to null values */ json_object_object_add(cookie->jobj, "startTimestamp", NULL); json_object_object_add(cookie->jobj, "stopTimestamp", NULL); } /* status specific replacements for start/stop time */ switch (status) { case FR_STATUS_START: /* add start time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_acct_status_type, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "startTimestamp", mod_value_pair_to_json_object(request, vp)); } break; case FR_STATUS_STOP: /* add stop time */ if ((vp = fr_pair_find_by_da(request->packet->vps, attr_event_timestamp, TAG_ANY)) != NULL) { /* add to json object */ json_object_object_add(cookie->jobj, "stopTimestamp", mod_value_pair_to_json_object(request, vp)); } /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; case FR_STATUS_ALIVE: /* check start timestamp and adjust if needed */ mod_ensure_start_timestamp(cookie->jobj, request->packet->vps); break; default: /* don't doing anything */ rcode = RLM_MODULE_NOOP; /* return */ goto finish; } /* loop through pairs and add to json document */ for (vp = request->packet->vps; vp; vp = vp->next) { /* map attribute to element */ if (mod_attribute_to_element(vp->da->name, inst->map, &element) == 0) { /* debug */ RDEBUG3("mapped attribute %s => %s", vp->da->name, element); /* add to json object with mapped name */ json_object_object_add(cookie->jobj, element, mod_value_pair_to_json_object(request, vp)); } } /* copy json string to document and check size */ if (strlcpy(document, json_object_to_json_string(cookie->jobj), sizeof(document)) >= sizeof(document)) { /* this isn't good */ RERROR("could not write json document - insufficient buffer space"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("setting '%s' => '%s'", dockey, document); /* store document/key in couchbase */ cb_error = couchbase_set_key(cb_inst, dockey, document, inst->expire); /* check return */ if (cb_error != LCB_SUCCESS) { RERROR("failed to store document (%s): %s (0x%x)", dockey, lcb_strerror(NULL, cb_error), cb_error); } finish: /* free and reset json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release our connection handle */ if (handle) { fr_pool_connection_release(inst->pool, request, handle); } /* return */ return rcode; }
/** Start a process * * @param cmd Command to execute. This is parsed into argv[] parts, * then each individual argv part is xlat'ed. * @param request Current reuqest * @param exec_wait set to 1 if you want to read from or write to child * @param[in,out] input_fd pointer to int, receives the stdin file. * descriptor. Set to NULL and the child will have /dev/null on stdin * @param[in,out] output_fd pinter to int, receives the stdout file * descriptor. Set to NULL and child will have /dev/null on stdout. * @param input_pairs list of value pairs - these will be put into * the environment variables of the child. * @param shell_escape values before passing them as arguments. * @return PID of the child process, -1 on error. */ pid_t radius_start_program(char const *cmd, REQUEST *request, int exec_wait, int *input_fd, int *output_fd, VALUE_PAIR *input_pairs, int shell_escape) { #ifndef __MINGW32__ char *p; VALUE_PAIR *vp; int n; int to_child[2] = {-1, -1}; int from_child[2] = {-1, -1}; pid_t pid; #endif int argc; int i; char *argv[MAX_ARGV]; char argv_buf[4096]; #define MAX_ENVP 1024 char *envp[MAX_ENVP]; argc = rad_expand_xlat(request, cmd, MAX_ARGV, argv, true, sizeof(argv_buf), argv_buf); if (argc <= 0) { RDEBUG("invalid command line '%s'.", cmd); return -1; } #ifndef NDEBUG if (debug_flag > 2) { RDEBUG3("executing cmd %s", cmd); for (i = 0; i < argc; i++) { RDEBUG3("\t[%d] %s", i, argv[i]); } } #endif #ifndef __MINGW32__ /* * Open a pipe for child/parent communication, if necessary. */ if (exec_wait) { if (input_fd) { if (pipe(to_child) != 0) { RDEBUG("Couldn't open pipe to child: %s", strerror(errno)); return -1; } } if (output_fd) { if (pipe(from_child) != 0) { RDEBUG("Couldn't open pipe from child: %s", strerror(errno)); /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); return -1; } } } envp[0] = NULL; if (input_pairs) { vp_cursor_t cursor; int envlen; char buffer[1024]; /* * Set up the environment variables in the * parent, so we don't call libc functions that * hold mutexes. They might be locked when we fork, * and will remain locked in the child. */ envlen = 0; for (vp = paircursor(&cursor, &input_pairs); vp; vp = pairnext(&cursor)) { /* * Hmm... maybe we shouldn't pass the * user's password in an environment * variable... */ snprintf(buffer, sizeof(buffer), "%s=", vp->da->name); if (shell_escape) { for (p = buffer; *p != '='; p++) { if (*p == '-') { *p = '_'; } else if (isalpha((int) *p)) { *p = toupper(*p); } } } n = strlen(buffer); vp_prints_value(buffer+n, sizeof(buffer) - n, vp, shell_escape ? '"' : 0); envp[envlen++] = strdup(buffer); /* * Don't add too many attributes. */ if (envlen == (MAX_ENVP - 1)) break; } envp[envlen] = NULL; } if (exec_wait) { pid = rad_fork(); /* remember PID */ } else { pid = fork(); /* don't wait */ } if (pid == 0) { int devnull; /* * Child process. * * We try to be fail-safe here. So if ANYTHING * goes wrong, we exit with status 1. */ /* * Open STDIN to /dev/null */ devnull = open("/dev/null", O_RDWR); if (devnull < 0) { RDEBUG("Failed opening /dev/null: %s\n", strerror(errno)); /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Only massage the pipe handles if the parent * has created them. */ if (exec_wait) { if (input_fd) { close(to_child[1]); dup2(to_child[0], STDIN_FILENO); } else { dup2(devnull, STDIN_FILENO); } if (output_fd) { close(from_child[0]); dup2(from_child[1], STDOUT_FILENO); } else { dup2(devnull, STDOUT_FILENO); } } else { /* no pipe, STDOUT should be /dev/null */ dup2(devnull, STDIN_FILENO); dup2(devnull, STDOUT_FILENO); } /* * If we're not debugging, then we can't do * anything with the error messages, so we throw * them away. * * If we are debugging, then we want the error * messages to go to the STDERR of the server. */ if (debug_flag == 0) { dup2(devnull, STDERR_FILENO); } close(devnull); /* * The server may have MANY FD's open. We don't * want to leave dangling FD's for the child process * to play funky games with, so we close them. */ closefrom(3); /* * I swear the signature for execve is wrong and should take 'char const * const argv[]'. */ execve(argv[0], argv, envp); printf("Failed to execute \"%s\": %s", argv[0], strerror(errno)); /* fork output will be captured */ /* * Where the status code is interpreted as a module rcode * one is subtracted from it, to allow 0 to equal success * * 2 is RLM_MODULE_FAIL + 1 */ exit(2); } /* * Free child environment variables */ for (i = 0; envp[i] != NULL; i++) { free(envp[i]); } /* * Parent process. */ if (pid < 0) { RDEBUG("Couldn't fork %s: %s", argv[0], strerror(errno)); if (exec_wait) { /* safe because these either need closing or are == -1 */ close(to_child[0]); close(to_child[1]); close(from_child[0]); close(from_child[0]); } return -1; } /* * We're not waiting, exit, and ignore any child's status. */ if (exec_wait) { /* * Close the ends of the pipe(s) the child is using * return the ends of the pipe(s) our caller wants * */ if (input_fd) { *input_fd = to_child[1]; close(to_child[0]); } if (output_fd) { *output_fd = from_child[0]; close(from_child[1]); } } return pid; #else if (exec_wait) { RDEBUG("Wait is not supported"); return -1; } { /* * The _spawn and _exec families of functions are * found in Windows compiler libraries for * portability from UNIX. There is a variety of * functions, including the ability to pass * either a list or array of parameters, to * search in the PATH or otherwise, and whether * or not to pass an environment (a set of * environment variables). Using _spawn, you can * also specify whether you want the new process * to close your program (_P_OVERLAY), to wait * until the new process is finished (_P_WAIT) or * for the two to run concurrently (_P_NOWAIT). * _spawn and _exec are useful for instances in * which you have simple requirements for running * the program, don't want the overhead of the * Windows header file, or are interested * primarily in portability. */ /* * FIXME: check return code... what is it? */ _spawnve(_P_NOWAIT, argv[0], argv, envp); } return 0; #endif }
/* * 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 int rlm_ldap_map_getvalue(VALUE_PAIR **out, REQUEST *request, value_pair_map_t const *map, void *ctx) { rlm_ldap_result_t *self = ctx; VALUE_PAIR *head = NULL, *vp; vp_cursor_t cursor; int i; fr_cursor_init(&cursor, &head); switch (map->dst->type) { /* * This is a mapping in the form of: * <list>: += <ldap attr> * * Where <ldap attr> is: * <list>:<attr> <op> <value> * * It is to allow for legacy installations which stored * RADIUS control and reply attributes in separate LDAP * attributes. */ case VPT_TYPE_LIST: for (i = 0; i < self->count; i++) { value_pair_map_t *attr = NULL; RDEBUG3("Parsing valuepair string \"%s\"", self->values[i]); if (radius_strpair2map(&attr, request, self->values[i], map->dst->vpt_request, map->dst->vpt_list, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing \"%s\" as valuepair, skipping...", self->values[i]); continue; } if (attr->dst->vpt_request != map->dst->vpt_request) { RWDEBUG("valuepair \"%s\" has conflicting request qualifier (%s vs %s), skipping...", self->values[i], fr_int2str(request_refs, attr->dst->vpt_request, "<INVALID>"), fr_int2str(request_refs, map->dst->vpt_request, "<INVALID>")); next_pair: talloc_free(attr); continue; } if ((attr->dst->vpt_list != map->dst->vpt_list)) { RWDEBUG("valuepair \"%s\" has conflicting list qualifier (%s vs %s), skipping...", self->values[i], fr_int2str(pair_lists, attr->dst->vpt_list, "<INVALID>"), fr_int2str(pair_lists, map->dst->vpt_list, "<INVALID>")); goto next_pair; } if (radius_map2vp(&vp, request, attr, NULL) < 0) { RWDEBUG("Failed creating attribute for \"%s\", skipping...", self->values[i]); goto next_pair; } fr_cursor_insert(&cursor, vp); talloc_free(attr); } break; /* * Iterate over all the retrieved values, * don't try and be clever about changing operators * just use whatever was set in the attribute map. */ case VPT_TYPE_ATTR: for (i = 0; i < self->count; i++) { vp = pairalloc(request, map->dst->vpt_da); rad_assert(vp); if (!pairparsevalue(vp, self->values[i])) { RDEBUG("Failed parsing value for \"%s\"", map->dst->vpt_da->name); talloc_free(vp); continue; } vp->op = map->op; fr_cursor_insert(&cursor, vp); } break; default: rad_assert(0); } *out = head; return 0; }
static int vector_gsm_from_ki(eap_session_t *eap_session, VALUE_PAIR *vps, int idx, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; VALUE_PAIR *ki_vp, *version_vp; uint8_t opc_buff[MILENAGE_OPC_SIZE]; uint8_t const *opc_p; uint32_t version; int i; /* * Generate a new RAND value, and derive Kc and SRES from Ki */ ki_vp = fr_pair_find_by_da(vps, attr_sim_ki, TAG_ANY); if (!ki_vp) { RDEBUG3("No &control:%sfound, not generating triplets locally", attr_sim_ki->name); return 1; } else if (ki_vp->vp_length != MILENAGE_KI_SIZE) { REDEBUG("&control:%s has incorrect length, expected 16 bytes got %zu bytes", attr_sim_ki->name, ki_vp->vp_length); return -1; } /* * Check to see if have a Ki for the IMSI, this allows us to generate the rest * of the triplets. */ version_vp = fr_pair_find_by_da(vps, attr_sim_algo_version, TAG_ANY); if (!version_vp) { if (vector_opc_from_op(request, &opc_p, opc_buff, vps, ki_vp->vp_octets) < 0) return -1; version = opc_p ? 4 : 3; /* * Version we explicitly specified, see if we can find the prerequisite * attributes. */ } else { version = version_vp->vp_uint32; if (version == 4) { if (vector_opc_from_op(request, &opc_p, opc_buff, vps, ki_vp->vp_octets) < 0) return -1; if (!opc_p) { RPEDEBUG2("No &control:%s or &control:%s found, " "can't run Milenage (COMP128-4)", attr_sim_op->name, attr_sim_opc->name); return -1; } } } for (i = 0; i < SIM_VECTOR_GSM_RAND_SIZE; i += sizeof(uint32_t)) { uint32_t rand = fr_rand(); memcpy(&keys->gsm.vector[idx].rand[i], &rand, sizeof(rand)); } switch (version) { case 1: comp128v1(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, ki_vp->vp_octets, keys->gsm.vector[idx].rand); break; case 2: comp128v23(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, ki_vp->vp_octets, keys->gsm.vector[idx].rand, true); break; case 3: comp128v23(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, ki_vp->vp_octets, keys->gsm.vector[idx].rand, false); break; case 4: if (milenage_gsm_generate(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, opc_p, ki_vp->vp_octets, keys->gsm.vector[idx].rand) < 0) { RPEDEBUG2("Failed deriving GSM triplet"); return -1; } return 0; default: REDEBUG("Unknown/unsupported algorithm %i", version); return -1; } return 0; }
/** 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... */ void rlm_ldap_map_do(UNUSED const ldap_instance_t *inst, REQUEST *request, LDAP *handle, rlm_ldap_map_xlat_t const *expanded, LDAPMessage *entry) { value_pair_map_t const *map; unsigned int total = 0; rlm_ldap_result_t result; char const *name; for (map = expanded->maps; map != NULL; map = map->next) { name = expanded->attrs[total++]; result.values = ldap_get_values(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(result.values); /* * If something bad happened, just skip, this is probably * a case of the dst being incorrect for the current * request context */ if (radius_map2request(request, map, name, rlm_ldap_map_getvalue, &result) == -1) { return; /* Fail */ } next: ldap_value_free(result.values); } /* * Retrieve any valuepair attributes from the result, these are generic values specifying * a radius list, operator and value. */ if (inst->valuepair_attr) { char **values; int count, i; values = ldap_get_values(handle, entry, inst->valuepair_attr); count = ldap_count_values(values); for (i = 0; i < count; i++) { value_pair_map_t *attr; RDEBUG3("Parsing attribute string '%s'", values[i]); if (radius_strpair2map(&attr, request, values[i], REQUEST_CURRENT, PAIR_LIST_REPLY, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing '%s' value \"%s\" as valuepair, skipping...", inst->valuepair_attr, values[i]); continue; } if (radius_map2request(request, attr, NULL, radius_map2vp, NULL) < 0) { RWDEBUG("Failed adding \"%s\" to request, skipping...", values[i]); } talloc_free(attr); } ldap_value_free(values); } }
/** 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, };
static int tls_socket_recv(rad_listen_t *listener) { int doing_init = FALSE; ssize_t rcode; RADIUS_PACKET *packet; REQUEST *request; listen_socket_t *sock = listener->data; fr_tls_status_t status; RADCLIENT *client = sock->client; if (!sock->packet) { sock->packet = rad_alloc(0); if (!sock->packet) return 0; sock->packet->sockfd = listener->fd; sock->packet->src_ipaddr = sock->other_ipaddr; sock->packet->src_port = sock->other_port; sock->packet->dst_ipaddr = sock->my_ipaddr; sock->packet->dst_port = sock->my_port; if (sock->request) sock->request->packet = sock->packet; } /* * Allocate a REQUEST for debugging. */ if (!sock->request) { sock->request = request = request_alloc(); if (!sock->request) { radlog(L_ERR, "Out of memory"); return 0; } rad_assert(request->packet == NULL); rad_assert(sock->packet != NULL); request->packet = sock->packet; request->component = "<core>"; request->component = "<tls-connect>"; /* * Not sure if we should do this on every packet... */ request->reply = rad_alloc(0); if (!request->reply) return 0; request->options = RAD_REQUEST_OPTION_DEBUG2; rad_assert(sock->ssn == NULL); sock->ssn = tls_new_session(listener->tls, sock->request, listener->tls->require_client_cert); if (!sock->ssn) { request_free(&sock->request); sock->packet = NULL; return 0; } SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_REQUEST, (void *)request); SSL_set_ex_data(sock->ssn->ssl, FR_TLS_EX_INDEX_CERTS, (void *)&request->packet->vps); doing_init = TRUE; } rad_assert(sock->request != NULL); rad_assert(sock->request->packet != NULL); rad_assert(sock->packet != NULL); rad_assert(sock->ssn != NULL); request = sock->request; RDEBUG3("Reading from socket %d", request->packet->sockfd); PTHREAD_MUTEX_LOCK(&sock->mutex); rcode = read(request->packet->sockfd, sock->ssn->dirty_in.data, sizeof(sock->ssn->dirty_in.data)); if ((rcode < 0) && (errno == ECONNRESET)) { do_close: PTHREAD_MUTEX_UNLOCK(&sock->mutex); tls_socket_close(listener); return 0; } if (rcode < 0) { RDEBUG("Error reading TLS socket: %s", strerror(errno)); goto do_close; } /* * Normal socket close. */ if (rcode == 0) goto do_close; sock->ssn->dirty_in.used = rcode; memset(sock->ssn->dirty_in.data + sock->ssn->dirty_in.used, 0, 16); dump_hex("READ FROM SSL", sock->ssn->dirty_in.data, sock->ssn->dirty_in.used); /* * Catch attempts to use non-SSL. */ if (doing_init && (sock->ssn->dirty_in.data[0] != handshake)) { RDEBUG("Non-TLS data sent to TLS socket: closing"); goto do_close; } /* * Skip ahead to reading application data. */ if (SSL_is_init_finished(sock->ssn->ssl)) goto app; if (!tls_handshake_recv(request, sock->ssn)) { RDEBUG("FAILED in TLS handshake receive"); goto do_close; } if (sock->ssn->dirty_out.used > 0) { tls_socket_write(listener, request); PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; } app: /* * FIXME: Run the packet through a virtual server in * order to see if we like the certificate presented by * the client. */ status = tls_application_data(sock->ssn, request); RDEBUG("Application data status %d", status); if (status == FR_TLS_MORE_FRAGMENTS) { PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; } if (sock->ssn->clean_out.used == 0) { PTHREAD_MUTEX_UNLOCK(&sock->mutex); return 0; } dump_hex("TUNNELED DATA", sock->ssn->clean_out.data, sock->ssn->clean_out.used); /* * If the packet is a complete RADIUS packet, return it to * the caller. Otherwise... */ if ((sock->ssn->clean_out.used < 20) || (((sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]) != (int) sock->ssn->clean_out.used)) { RDEBUG("Received bad packet: Length %d contents %d", sock->ssn->clean_out.used, (sock->ssn->clean_out.data[2] << 8) | sock->ssn->clean_out.data[3]); goto do_close; } packet = sock->packet; packet->data = rad_malloc(sock->ssn->clean_out.used); packet->data_len = sock->ssn->clean_out.used; sock->ssn->record_minus(&sock->ssn->clean_out, packet->data, packet->data_len); packet->vps = NULL; PTHREAD_MUTEX_UNLOCK(&sock->mutex); if (!rad_packet_ok(packet, 0)) { RDEBUG("Received bad packet: %s", fr_strerror()); tls_socket_close(listener); return 0; /* do_close unlocks the mutex */ } /* * Copied from src/lib/radius.c, rad_recv(); */ if (fr_debug_flag) { char host_ipaddr[128]; if ((packet->code > 0) && (packet->code < FR_MAX_PACKET_CODE)) { RDEBUG("tls_recv: %s packet from host %s port %d, id=%d, length=%d", fr_packet_codes[packet->code], inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, host_ipaddr, sizeof(host_ipaddr)), packet->src_port, packet->id, (int) packet->data_len); } else { RDEBUG("tls_recv: Packet from host %s port %d code=%d, id=%d, length=%d", inet_ntop(packet->src_ipaddr.af, &packet->src_ipaddr.ipaddr, host_ipaddr, sizeof(host_ipaddr)), packet->src_port, packet->code, packet->id, (int) packet->data_len); } } FR_STATS_INC(auth, total_requests); return 1; }
/** Derive triplets from quintuplets * */ static int vector_gsm_from_quintuplets(eap_session_t *eap_session, VALUE_PAIR *vps, int idx, fr_sim_keys_t *keys) { REQUEST *request = eap_session->request; fr_cursor_t cursor; VALUE_PAIR *ck = NULL, *ik = NULL, *rand = NULL, *xres = NULL; int i; /* * Fetch CK */ for (i = 0, (ck = fr_cursor_iter_by_da_init(&cursor, &vps, attr_eap_aka_ck)); (i < idx) && (ck = fr_cursor_next(&cursor)); i++); if (!ck) { RDEBUG3("No &control:%s[%i] attribute found, not using quintuplet derivation", attr_eap_aka_ck->name, idx); return 1; } /* * Fetch IK */ for (i = 0, (ik = fr_cursor_iter_by_da_init(&cursor, &vps, attr_eap_aka_ik)); (i < idx) && (ik = fr_cursor_next(&cursor)); i++); if (!ik) { RDEBUG3("No &control:%s[%i] attribute found, not using quintuplet derivation", attr_eap_aka_ik->name, idx); return 1; } /* * Fetch RAND */ for (i = 0, (rand = fr_cursor_iter_by_da_init(&cursor, &vps, attr_eap_aka_rand)); (i < idx) && (rand = fr_cursor_next(&cursor)); i++); if (!rand) { RDEBUG3("No &control:%s[%i] attribute found, not using quintuplet derivation", attr_eap_aka_rand->name, idx); return 1; } if (rand->vp_length != SIM_VECTOR_UMTS_RAND_SIZE) { REDEBUG("&control:%s[%i] incorrect length. Expected " STRINGIFY(SIM_VECTOR_UMTS_RAND_SIZE) " bytes, " "got %zu bytes", attr_eap_aka_rand->name, idx, rand->vp_length); return -1; } /* * Fetch XRES */ for (i = 0, (xres = fr_cursor_iter_by_da_init(&cursor, &vps, attr_eap_aka_xres)); (i < idx) && (xres = fr_cursor_next(&cursor)); i++); if (!xres) { RDEBUG3("No &control:%s[%i] attribute found, not using quintuplet derivation", attr_eap_aka_xres->name, idx); return 1; } memcpy(keys->gsm.vector[idx].rand, rand->vp_octets, SIM_VECTOR_GSM_RAND_SIZE); milenage_gsm_from_umts(keys->gsm.vector[idx].sres, keys->gsm.vector[idx].kc, ik->vp_octets, ck->vp_octets, xres->vp_octets); return 0; }
/* * Find the named user in this modules database. Create the set * of attribute-value pairs to check and reply with for this user * from the database. The authentication code only needs to check * the password, the rest is done here. */ static rlm_rcode_t 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 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; } }