/* * * Verify that a Perl SV is a string and save it in FreeRadius * Value Pair Format * */ static int pairadd_sv(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, char *key, SV *sv, FR_TOKEN op, const char *hash_name, const char *list_name) { char *val; VALUE_PAIR *vp; if (SvOK(sv)) { STRLEN len; val = SvPV(sv, len); vp = fr_pair_make(ctx, vps, key, NULL, op); if (!vp) { fail: REDEBUG("Failed to create pair %s:%s %s %s", list_name, key, fr_int2str(fr_tokens, op, "<INVALID>"), val); return -1; } switch (vp->da->type) { case PW_TYPE_STRING: fr_pair_value_bstrncpy(vp, val, len); break; default: if (fr_pair_value_from_str(vp, val, len) < 0) goto fail; } RDEBUG("&%s:%s %s $%s{'%s'} -> '%s'", list_name, key, fr_int2str(fr_tokens, op, "<INVALID>"), hash_name, key, val); return 0; } return -1; }
/** Converts a string value into a #VALUE_PAIR * * @param[in,out] ctx to allocate #VALUE_PAIR (s). * @param[out] out where to write the resulting #VALUE_PAIR. * @param[in] request The current request. * @param[in] map to process. * @param[in] uctx The value to parse. * @return * - 0 on success. * - -1 on failure. */ static int _sql_map_proc_get_value(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { VALUE_PAIR *vp; char const *value = uctx; vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da); /* * Buffer not always talloced, sometimes it's * just a pointer to a field in a result struct. */ if (fr_pair_value_from_str(vp, value, strlen(value)) < 0) { char *escaped; escaped = fr_asprint(vp, value, talloc_array_length(value), '"'); REDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped, map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); return -1; } vp->op = map->op; *out = vp; return 0; }
static int _map_proc_client_get_vp(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { client_get_vp_ctx_t *client = uctx; VALUE_PAIR *head = NULL, *vp; fr_cursor_t cursor; fr_dict_attr_t const *da; CONF_PAIR const *cp; rad_assert(ctx != NULL); fr_cursor_init(&cursor, &head); /* * FIXME: allow multiple entries. */ if (map->lhs->type == TMPL_TYPE_ATTR) { da = map->lhs->tmpl_da; } else { char *attr; if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) { RWDEBUG("Failed expanding string"); return -1; } da = fr_dict_attr_by_name(request->dict, attr); if (!da) { RWDEBUG("No such attribute '%s'", attr); return -1; } talloc_free(attr); } for (cp = client->cp; cp; cp = cf_pair_find_next(client->cs, cp, client->field)) { char const *value = cf_pair_value(cp); MEM(vp = fr_pair_afrom_da(ctx, da)); if (fr_pair_value_from_str(vp, value, talloc_array_length(value) - 1, '\0', false) < 0) { RWDEBUG("Failed parsing value \"%pV\" for attribute %s: %s", fr_box_strvalue(value), map->lhs->tmpl_da->name, fr_strerror()); fr_pair_list_free(&head); talloc_free(vp); return -1; } vp->op = map->op; fr_cursor_append(&cursor, vp); if (map->op != T_OP_ADD) break; /* Create multiple attribute for multiple CONF_PAIRs */ } *out = head; return 0; }
/* * Generic comparisons, via xlat. */ static int generic_cmp(UNUSED void *instance, REQUEST *request, VALUE_PAIR *req, VALUE_PAIR *check, UNUSED VALUE_PAIR *check_list, UNUSED VALUE_PAIR **reply_list) { VP_VERIFY(check); if ((check->op != T_OP_REG_EQ) && (check->op != T_OP_REG_NE)) { int rcode; char name[1024]; char value[1024]; VALUE_PAIR *vp; snprintf(name, sizeof(name), "%%{%s}", check->da->name); if (xlat_eval(value, sizeof(value), request, name, NULL, NULL) < 0) return 0; vp = fr_pair_afrom_da(req, check->da); vp->op = check->op; fr_pair_value_from_str(vp, value, -1, '"', false); /* * Paircmp returns 0 for failed comparison, 1 for succeeded -1 for error. */ rcode = fr_pair_cmp(check, vp); /* * We're being called from paircmp_func, * which wants 0 for success, and 1 for fail (sigh) * * We should really fix the API so that it is * consistent. i.e. the comparison callbacks should * return ONLY the resut of comparing A to B. * The radius_callback_cmp function should then * take care of using the operator to see if the * condition (A OP B) is true or not. * * This would also allow "<", etc. to work in the * callback functions... * * See rlm_ldap, ...groupcmp() for something that * returns 0 for matched, and 1 for didn't match. */ rcode = !rcode; fr_pair_list_free(&vp); return rcode; } /* * Will do the xlat for us */ return paircmp_pairs(request, check, NULL); }
/* * Convert field X to a VP. */ static int csv_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { char const *str = uctx; VALUE_PAIR *head = NULL, *vp; fr_cursor_t cursor; fr_dict_attr_t const *da; rad_assert(ctx != NULL); fr_cursor_init(&cursor, &head); /* * FIXME: allow multiple entries. */ if (map->lhs->type == TMPL_TYPE_ATTR) { da = map->lhs->tmpl_da; } else { char *attr; if (tmpl_aexpand(ctx, &attr, request, map->lhs, NULL, NULL) <= 0) { RWDEBUG("Failed expanding string"); return -1; } da = fr_dict_attr_by_name(request->dict, attr); if (!da) { RWDEBUG("No such attribute '%s'", attr); return -1; } talloc_free(attr); } vp = fr_pair_afrom_da(ctx, da); rad_assert(vp); if (fr_pair_value_from_str(vp, str, talloc_array_length(str) - 1, '\0', true) < 0) { RWDEBUG("Failed parsing value \"%pV\" for attribute %s: %s", fr_box_strvalue_buffer(str), map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); return -1; } vp->op = map->op; fr_cursor_append(&cursor, vp); *out = head; return 0; }
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; }
/* * * Verify that a Perl SV is a string and save it in FreeRadius * Value Pair Format * */ static int pairadd_sv(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, char *key, SV *sv, FR_TOKEN op, const char *hash_name, const char *list_name) { char *val; VALUE_PAIR *vp; STRLEN len; if (!SvOK(sv)) return -1; val = SvPV(sv, len); vp = fr_pair_make(ctx, request->dict, vps, key, NULL, op); if (!vp) { fail: REDEBUG("Failed to create pair %s:%s %s %s", list_name, key, fr_int2str(fr_tokens_table, op, "<INVALID>"), val); return -1; } switch (vp->vp_type) { case FR_TYPE_STRING: fr_pair_value_bstrncpy(vp, val, len); break; case FR_TYPE_OCTETS: fr_pair_value_memcpy(vp, (uint8_t const *)val, len); break; default: if (fr_pair_value_from_str(vp, val, len, '\0', false) < 0) goto fail; } VP_VERIFY(vp); RDEBUG2("&%s:%s %s $%s{'%s'} -> '%s'", list_name, key, fr_int2str(fr_tokens_table, op, "<INVALID>"), hash_name, key, val); return 0; }
/* * * Verify that a Perl SV is a string and save it in FreeRadius * Value Pair Format * */ static void pairadd_sv(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **vps, char *key, SV *sv, FR_TOKEN op, const char *hash_name, const char *list_name) { char *val = NULL; VALUE_PAIR *vp; STRLEN len; if (!SvOK(sv)) { REDEBUG("Internal failure creating pair &%s:%s %s $%s{'%s'} -> '%s'", list_name, key, fr_int2str(fr_tokens, op, "<INVALID>"), hash_name, key, (val ? val : "undef")); return; } val = SvPV(sv, len); vp = fr_pair_make(ctx, vps, key, NULL, op); if (!vp) { fail: REDEBUG("Failed to create pair - %s", fr_strerror()); REDEBUG(" &%s:%s %s $%s{'%s'} -> '%s'", list_name, key, fr_int2str(fr_tokens, op, "<INVALID>"), hash_name, key, (val ? val : "undef")); return; } switch (vp->da->type) { case PW_TYPE_STRING: fr_pair_value_bstrncpy(vp, val, len); break; default: VERIFY_VP(vp); if (fr_pair_value_from_str(vp, val, len) < 0) goto fail; } RDEBUG("&%s:%s %s $%s{'%s'} -> '%s'", list_name, key, fr_int2str(fr_tokens, op, "<INVALID>"), hash_name, key, val); }
/** Builds attribute representing OID string and adds 'index' attributes where required * * Will convert an OID string in the format @verbatim .1.2.3.4.5.0 @endverbatim * into a pair with a #fr_dict_attr_t of the dictionary attribute matching the OID * string, as evaluated from the specified parent. * * If an OID component does not match a child of a previous OID component, but a child * with attribute number 0 exists, and a child with attribute number 1 also exists, * the child with attribute number 0 will be used as an 'index' pair, and will be * created with the value of the non matching OID component. * * Parsing will then resume using the child with attribute number 1. * * This allows traversals of SNMP tables to be represented by the sequence of pairs * and allows the full range of entry indexes which would not be possible if we represented * table index numbers as TLV attributes. * * @param[in] ctx to allocate new pairs in. * @param[in] conf radsnmp config. * @param[in] cursor to add pairs to. * @param[in] oid string to evaluate. * @param[in] type SNMP value type. * @param[in] value to assign to OID attribute (SET operations only). * @return * - >0 on success (how much of the OID string we parsed). * - <=0 on failure (where format error occurred). */ static ssize_t radsnmp_pair_from_oid(TALLOC_CTX *ctx, radsnmp_conf_t *conf, fr_cursor_t *cursor, char const *oid, int type, char const *value) { ssize_t slen; char const *p = oid; unsigned int attr; fr_dict_attr_t const *index_attr, *da; fr_dict_attr_t const *parent = conf->snmp_root; VALUE_PAIR *vp; int ret; if (!oid) return 0; fr_cursor_tail(cursor); /* * Trim first. */ if (p[0] == '.') p++; /* * Support for indexes. If we can't find an attribute * matching a child at a given level in the OID tree, * look for attribute 0 (type integer) at that level. * We use this to represent the index instead. */ for (;;) { unsigned int num = 0; slen = fr_dict_attr_by_oid(conf->dict, &parent, &attr, p); if (slen > 0) break; p += -(slen); if (fr_dict_oid_component(&num, &p) < 0) break; /* Just advances the pointer */ assert(attr == num); p++; /* * Check for an index attribute */ index_attr = fr_dict_attr_child_by_num(parent, 0); if (!index_attr) { fr_strerror_printf("Unknown OID component: No index attribute at this level"); break; } if (index_attr->type != FR_TYPE_UINT32) { fr_strerror_printf("Index is not a \"integer\""); break; } /* * By convention SNMP entries are at .1 */ parent = fr_dict_attr_child_by_num(parent, 1); if (!parent) { fr_strerror_printf("Unknown OID component: No entry attribute at this level"); break; } /* * Entry must be a TLV type */ if (parent->type != FR_TYPE_TLV) { fr_strerror_printf("Entry is not \"tlv\""); break; } /* * We've skipped over the index attribute, and * the index number should be available in attr. */ MEM(vp = fr_pair_afrom_da(ctx, index_attr)); vp->vp_uint32 = attr; fr_cursor_append(cursor, vp); } /* * We errored out processing the OID. */ if (slen <= 0) { error: fr_cursor_free_list(cursor); return slen; } fr_strerror(); /* Clear pending errors */ /* * SNMP requests the leaf under the OID with .0. */ if (attr != 0) { da = fr_dict_attr_child_by_num(parent, attr); if (!da) { fr_strerror_printf("Unknown leaf attribute %i", attr); return -(slen); } } else { da = parent; } vp = fr_pair_afrom_da(ctx, da); if (!vp) { fr_strerror_printf("Failed allocating OID attribute"); return -(slen); } /* * VALUE_PAIRs with no value need a 1 byte value buffer. */ if (!value) { switch (da->type) { /* * We can blame the authors of RFC 6929 for * this hack. Apparently the presence or absence * of an attribute isn't considered a useful means * of conveying information, so empty TLVs are * disallowed. */ case FR_TYPE_TLV: fr_pair_to_unknown(vp); /* FALL-THROUGH */ case FR_TYPE_OCTETS: fr_pair_value_memcpy(vp, (uint8_t const *)"\0", 1, true); break; case FR_TYPE_STRING: fr_pair_value_bstrncpy(vp, "\0", 1); break; /* * Fine to leave other values zeroed out. */ default: break; } fr_cursor_append(cursor, vp); return slen; } if (da->type == FR_TYPE_TLV) { fr_strerror_printf("TLVs cannot hold values"); return -(slen); } ret = fr_pair_value_from_str(vp, value, strlen(value), '\0', true); if (ret < 0) { slen = -(slen); goto error; } vp = fr_pair_afrom_da(ctx, attr_freeradius_snmp_type); if (!vp) { slen = -(slen); goto error; } vp->vp_uint32 = type; fr_cursor_append(cursor, vp); return slen; }
/* * Allocate an IP number from the pool. */ static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, REQUEST *request) { rlm_sqlippool_t *inst = (rlm_sqlippool_t *) instance; char allocation[MAX_STRING_LEN]; int allocation_len; VALUE_PAIR *vp; rlm_sql_handle_t *handle; time_t now; /* * If there is a Framed-IP-Address attribute in the reply do nothing */ if (fr_pair_find_by_num(request->reply->vps, inst->framed_ip_address, 0, TAG_ANY) != NULL) { RDEBUG("Framed-IP-Address already exists"); return do_logging(request, inst->log_exists, RLM_MODULE_NOOP); } if (fr_pair_find_by_num(request->config, PW_POOL_NAME, 0, TAG_ANY) == NULL) { RDEBUG("No Pool-Name defined"); return do_logging(request, inst->log_nopool, RLM_MODULE_NOOP); } handle = fr_connection_get(inst->sql_inst->pool); if (!handle) { REDEBUG("Failed reserving SQL connection"); return RLM_MODULE_FAIL; } if (inst->sql_inst->sql_set_user(inst->sql_inst, request, NULL) < 0) { return RLM_MODULE_FAIL; } /* * Limit the number of clears we do. There are minor * race conditions for the check, but so what. The * actual work is protected by a transaction. The idea * here is that if we're allocating 100 IPs a second, * we're only do 1 CLEAR per second. */ now = time(NULL); if (inst->last_clear < now) { inst->last_clear = now; DO_PART(allocate_begin); DO_PART(allocate_clear); DO_PART(allocate_commit); } DO_PART(allocate_begin); allocation_len = sqlippool_query1(allocation, sizeof(allocation), inst->allocate_find, handle, inst, request, (char *) NULL, 0); /* * Nothing found... */ if (allocation_len == 0) { DO_PART(allocate_commit); /* *Should we perform pool-check ? */ if (inst->pool_check && *inst->pool_check) { /* *Ok, so the allocate-find query found nothing ... *Let's check if the pool exists at all */ allocation_len = sqlippool_query1(allocation, sizeof(allocation), inst->pool_check, handle, inst, request, (char *) NULL, 0); fr_connection_release(inst->sql_inst->pool, handle); if (allocation_len) { /* * Pool exists after all... So, * the failure to allocate the IP * address was most likely due to * the depletion of the pool. In * that case, we should return * NOTFOUND */ RDEBUG("pool appears to be full"); return do_logging(request, inst->log_failed, RLM_MODULE_NOTFOUND); } /* * Pool doesn't exist in the table. It * may be handled by some other instance of * sqlippool, so we should just ignore this * allocation failure and return NOOP */ RDEBUG("IP address could not be allocated as no pool exists with that name"); return RLM_MODULE_NOOP; } fr_connection_release(inst->sql_inst->pool, handle); RDEBUG("IP address could not be allocated"); return do_logging(request, inst->log_failed, RLM_MODULE_NOOP); } /* * See if we can create the VP from the returned data. If not, * error out. If so, add it to the list. */ vp = fr_pair_afrom_num(request->reply, inst->framed_ip_address, 0); if (fr_pair_value_from_str(vp, allocation, allocation_len) < 0) { DO_PART(allocate_commit); RDEBUG("Invalid IP number [%s] returned from instbase query.", allocation); fr_connection_release(inst->sql_inst->pool, handle); return do_logging(request, inst->log_failed, RLM_MODULE_NOOP); } RDEBUG("Allocated IP %s", allocation); fr_pair_add(&request->reply->vps, vp); /* * UPDATE */ sqlippool_command(inst->allocate_update, &handle, inst, request, allocation, allocation_len); DO_PART(allocate_commit); fr_connection_release(inst->sql_inst->pool, handle); return do_logging(request, inst->log_success, RLM_MODULE_OK); }
static FR_CODE eap_fast_eap_payload(REQUEST *request, eap_session_t *eap_session, tls_session_t *tls_session, VALUE_PAIR *tlv_eap_payload) { FR_CODE code = FR_CODE_ACCESS_REJECT; rlm_rcode_t rcode; VALUE_PAIR *vp; eap_fast_tunnel_t *t; REQUEST *fake; RDEBUG2("Processing received EAP Payload"); /* * Allocate a fake REQUEST structure. */ fake = request_alloc_fake(request, NULL); rad_assert(!fake->packet->vps); t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); /* * Add the tunneled attributes to the fake request. */ fake->packet->vps = fr_pair_afrom_da(fake->packet, attr_eap_message); fr_pair_value_memcpy(fake->packet->vps, tlv_eap_payload->vp_octets, tlv_eap_payload->vp_length, false); RDEBUG2("Got tunneled request"); log_request_pair_list(L_DBG_LVL_1, request, fake->packet->vps, NULL); /* * Tell the request that it's a fake one. */ MEM(fr_pair_add_by_da(fake->packet, &vp, &fake->packet->vps, attr_freeradius_proxied_to) >= 0); fr_pair_value_from_str(vp, "127.0.0.1", sizeof("127.0.0.1"), '\0', false); /* * Update other items in the REQUEST data structure. */ fake->username = fr_pair_find_by_da(fake->packet->vps, attr_user_name, TAG_ANY); fake->password = fr_pair_find_by_da(fake->packet->vps, attr_user_password, TAG_ANY); /* * No User-Name, try to create one from stored data. */ if (!fake->username) { /* * No User-Name in the stored data, look for * an EAP-Identity, and pull it out of there. */ if (!t->username) { vp = fr_pair_find_by_da(fake->packet->vps, attr_eap_message, TAG_ANY); if (vp && (vp->vp_length >= EAP_HEADER_LEN + 2) && (vp->vp_strvalue[0] == FR_EAP_CODE_RESPONSE) && (vp->vp_strvalue[EAP_HEADER_LEN] == FR_EAP_METHOD_IDENTITY) && (vp->vp_strvalue[EAP_HEADER_LEN + 1] != 0)) { /* * Create & remember a User-Name */ MEM(t->username = fr_pair_afrom_da(t, attr_user_name)); t->username->vp_tainted = true; fr_pair_value_bstrncpy(t->username, vp->vp_octets + 5, vp->vp_length - 5); RDEBUG2("Got tunneled identity of %pV", &t->username->data); } else { /* * Don't reject the request outright, * as it's permitted to do EAP without * user-name. */ RWDEBUG2("No EAP-Identity found to start EAP conversation"); } } /* else there WAS a t->username */ if (t->username) { vp = fr_pair_copy(fake->packet, t->username); fr_pair_add(&fake->packet->vps, vp); fake->username = vp; } } /* else the request ALREADY had a User-Name */ if (t->stage == EAP_FAST_AUTHENTICATION) { /* FIXME do this only for MSCHAPv2 */ VALUE_PAIR *tvp; tvp = fr_pair_afrom_da(fake, attr_eap_type); tvp->vp_uint32 = t->default_provisioning_method; fr_pair_add(&fake->control, tvp); /* * RFC 5422 section 3.2.3 - Authenticating Using EAP-FAST-MSCHAPv2 */ if (t->mode == EAP_FAST_PROVISIONING_ANON) { tvp = fr_pair_afrom_da(fake, attr_ms_chap_challenge); fr_pair_value_memcpy(tvp, t->keyblock->server_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, false); fr_pair_add(&fake->control, tvp); RHEXDUMP(L_DBG_LVL_MAX, t->keyblock->server_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, "MSCHAPv2 auth_challenge"); tvp = fr_pair_afrom_da(fake, attr_ms_chap_peer_challenge); fr_pair_value_memcpy(tvp, t->keyblock->client_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, false); fr_pair_add(&fake->control, tvp); RHEXDUMP(L_DBG_LVL_MAX, t->keyblock->client_challenge, RADIUS_CHAP_CHALLENGE_LENGTH, "MSCHAPv2 peer_challenge"); } } /* * Call authentication recursively, which will * do PAP, CHAP, MS-CHAP, etc. */ eap_virtual_server(request, fake, eap_session, t->virtual_server); /* * Decide what to do with the reply. */ switch (fake->reply->code) { case 0: /* No reply code, must be proxied... */ #ifdef WITH_PROXY vp = fr_pair_find_by_da(fake->control, attr_proxy_to_realm, TAG_ANY); if (vp) { int ret; eap_tunnel_data_t *tunnel; RDEBUG2("Tunneled authentication will be proxied to %pV", &vp->data); /* * Tell the original request that it's going to be proxied. */ fr_pair_list_copy_by_da(request, &request->control, fake->control, attr_proxy_to_realm); /* * Seed the proxy packet with the tunneled request. */ rad_assert(!request->proxy); /* * FIXME: Actually proxy stuff */ request->proxy = request_alloc_fake(request, NULL); request->proxy->packet = talloc_steal(request->proxy, fake->packet); memset(&request->proxy->packet->src_ipaddr, 0, sizeof(request->proxy->packet->src_ipaddr)); memset(&request->proxy->packet->src_ipaddr, 0, sizeof(request->proxy->packet->src_ipaddr)); request->proxy->packet->src_port = 0; request->proxy->packet->dst_port = 0; fake->packet = NULL; fr_radius_packet_free(&fake->reply); fake->reply = NULL; /* * Set up the callbacks for the tunnel */ tunnel = talloc_zero(request, eap_tunnel_data_t); tunnel->tls_session = tls_session; /* * Associate the callback with the request. */ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_TUNNEL_CALLBACK, tunnel, false, false, false); fr_cond_assert(ret == 0); /* * rlm_eap.c has taken care of associating the eap_session * with the fake request. * * So we associate the fake request with this request. */ ret = request_data_add(request, request->proxy, REQUEST_DATA_EAP_MSCHAP_TUNNEL_CALLBACK, fake, true, false, false); fr_cond_assert(ret == 0); fake = NULL; /* * Didn't authenticate the packet, but we're proxying it. */ code = FR_CODE_STATUS_CLIENT; } else #endif /* WITH_PROXY */ { REDEBUG("No tunneled reply was found, and the request was not proxied: rejecting the user"); code = FR_CODE_ACCESS_REJECT; } break; default: /* * Returns RLM_MODULE_FOO, and we want to return FR_FOO */ rcode = process_reply(eap_session, tls_session, request, fake->reply); switch (rcode) { case RLM_MODULE_REJECT: code = FR_CODE_ACCESS_REJECT; break; case RLM_MODULE_HANDLED: code = FR_CODE_ACCESS_CHALLENGE; break; case RLM_MODULE_OK: code = FR_CODE_ACCESS_ACCEPT; break; default: code = FR_CODE_ACCESS_REJECT; break; } break; } talloc_free(fake); return code; }
/************************************************************************* * * Function: sql_fr_pair_list_afrom_str * * Purpose: Read entries from the database and fill VALUE_PAIR structures * *************************************************************************/ int sql_fr_pair_list_afrom_str(TALLOC_CTX *ctx, REQUEST *request, VALUE_PAIR **head, rlm_sql_row_t row) { VALUE_PAIR *vp; char const *ptr, *value; char buf[MAX_STRING_LEN]; char do_xlat = 0; FR_TOKEN token, op = T_EOL; /* * Verify the 'Attribute' field */ if (!row[2] || row[2][0] == '\0') { REDEBUG("Attribute field is empty or NULL, skipping the entire row"); return -1; } /* * Verify the 'op' field */ if (row[4] != NULL && row[4][0] != '\0') { ptr = row[4]; op = gettoken(&ptr, buf, sizeof(buf), false); if (!fr_assignment_op[op] && !fr_equality_op[op]) { REDEBUG("Invalid op \"%s\" for attribute %s", row[4], row[2]); return -1; } } else { /* * Complain about empty or invalid 'op' field */ op = T_OP_CMP_EQ; REDEBUG("The op field for attribute '%s = %s' is NULL, or non-existent.", row[2], row[3]); REDEBUG("You MUST FIX THIS if you want the configuration to behave as you expect"); } /* * The 'Value' field may be empty or NULL */ if (!row[3]) { REDEBUG("Value field is empty or NULL, skipping the entire row"); return -1; } value = row[3]; /* * If we have a new-style quoted string, where the * *entire* string is quoted, do xlat's. */ if (row[3] != NULL && ((row[3][0] == '\'') || (row[3][0] == '`') || (row[3][0] == '"')) && (row[3][0] == row[3][strlen(row[3])-1])) { token = gettoken(&value, buf, sizeof(buf), false); switch (token) { /* * Take the unquoted string. */ case T_SINGLE_QUOTED_STRING: case T_DOUBLE_QUOTED_STRING: value = buf; break; /* * Mark the pair to be allocated later. */ case T_BACK_QUOTED_STRING: do_xlat = 1; /* FALL-THROUGH */ /* * Keep the original string. */ default: value = row[3]; break; } } /* * Create the pair */ vp = fr_pair_make(ctx, NULL, row[2], NULL, op); if (!vp) { REDEBUG("Failed to create the pair: %s", fr_strerror()); return -1; } if (do_xlat) { if (fr_pair_mark_xlat(vp, value) < 0) { REDEBUG("Error marking pair for xlat: %s", fr_strerror()); talloc_free(vp); return -1; } } else { if (fr_pair_value_from_str(vp, value, -1) < 0) { REDEBUG("Error parsing value: %s", fr_strerror()); talloc_free(vp); return -1; } } /* * Add the pair into the packet */ fr_pair_add(head, vp); return 0; }
/** Extract attributes from an X509 certificate * * @param cursor to copy attributes to. * @param ctx to allocate attributes in. * @param session current TLS session. * @param cert to validate. * @param depth the certificate is in the certificate chain (0 == leaf). * @return * - 0 on success. * - < 0 on failure. */ int tls_session_pairs_from_x509_cert(fr_cursor_t *cursor, TALLOC_CTX *ctx, tls_session_t *session, X509 *cert, int depth) { char buffer[1024]; char attribute[256]; char **identity; int attr_index, loc; #if OPENSSL_VERSION_NUMBER >= 0x10100000L STACK_OF(X509_EXTENSION) const *ext_list = NULL; #else STACK_OF(X509_EXTENSION) *ext_list = NULL; #endif ASN1_INTEGER *sn = NULL; ASN1_TIME *asn_time = NULL; VALUE_PAIR *vp = NULL; REQUEST *request; #define CERT_ATTR_ADD(_attr, _attr_index, _value) tls_session_cert_attr_add(ctx, request, cursor, _attr, _attr_index, _value) attr_index = depth; if (attr_index > 1) attr_index = 1; request = (REQUEST *)SSL_get_ex_data(session->ssl, FR_TLS_EX_INDEX_REQUEST); rad_assert(request != NULL); identity = (char **)SSL_get_ex_data(session->ssl, FR_TLS_EX_INDEX_IDENTITY); if (RDEBUG_ENABLED3) { buffer[0] = '\0'; X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; RDEBUG3("Creating attributes for \"%s\":", buffer[0] ? buffer : "Cert missing subject OID"); } /* * Get the Serial Number */ sn = X509_get_serialNumber(cert); if (sn && ((size_t) sn->length < (sizeof(buffer) / 2))) { char *p = buffer; int i; for (i = 0; i < sn->length; i++) { sprintf(p, "%02x", (unsigned int)sn->data[i]); p += 2; } CERT_ATTR_ADD(IDX_SERIAL, attr_index, buffer); } /* * Get the Expiration Date */ buffer[0] = '\0'; asn_time = X509_get_notAfter(cert); if (identity && asn_time && (asn_time->length < (int)sizeof(buffer))) { time_t expires; /* * Add expiration as a time since the epoch */ if (tls_utils_asn1time_to_epoch(&expires, asn_time) < 0) { RPWDEBUG("Failed parsing certificate expiry time"); } else { vp = CERT_ATTR_ADD(IDX_EXPIRATION, attr_index, NULL); vp->vp_date = expires; } } /* * Get the Subject & Issuer */ buffer[0] = '\0'; X509_NAME_oneline(X509_get_subject_name(cert), buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; if (identity && buffer[0]) { CERT_ATTR_ADD(IDX_SUBJECT, attr_index, buffer); /* * Get the Common Name, if there is a subject. */ X509_NAME_get_text_by_NID(X509_get_subject_name(cert), NID_commonName, buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; if (buffer[0]) { CERT_ATTR_ADD(IDX_COMMON_NAME, attr_index, buffer); } } X509_NAME_oneline(X509_get_issuer_name(cert), buffer, sizeof(buffer)); buffer[sizeof(buffer) - 1] = '\0'; if (identity && buffer[0]) { CERT_ATTR_ADD(IDX_ISSUER, attr_index, buffer); } /* * Get the RFC822 Subject Alternative Name */ loc = X509_get_ext_by_NID(cert, NID_subject_alt_name, 0); if (loc >= 0) { X509_EXTENSION *ext = NULL; GENERAL_NAMES *names = NULL; int i; ext = X509_get_ext(cert, loc); if (ext && (names = X509V3_EXT_d2i(ext))) { for (i = 0; i < sk_GENERAL_NAME_num(names); i++) { GENERAL_NAME *name = sk_GENERAL_NAME_value(names, i); switch (name->type) { #ifdef GEN_EMAIL case GEN_EMAIL: { #if OPENSSL_VERSION_NUMBER >= 0x10100000L char const *rfc822Name = (char const *)ASN1_STRING_get0_data(name->d.rfc822Name); #else char *rfc822Name = (char *)ASN1_STRING_data(name->d.rfc822Name); #endif CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_EMAIL, attr_index, rfc822Name); break; } #endif /* GEN_EMAIL */ #ifdef GEN_DNS case GEN_DNS: { #if OPENSSL_VERSION_NUMBER >= 0x10100000L char const *dNSName = (char const *)ASN1_STRING_get0_data(name->d.dNSName); #else char *dNSName = (char *)ASN1_STRING_data(name->d.dNSName); #endif CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_DNS, attr_index, dNSName); break; } #endif /* GEN_DNS */ #ifdef GEN_OTHERNAME case GEN_OTHERNAME: /* look for a MS UPN */ if (NID_ms_upn != OBJ_obj2nid(name->d.otherName->type_id)) break; /* we've got a UPN - Must be ASN1-encoded UTF8 string */ if (name->d.otherName->value->type == V_ASN1_UTF8STRING) { CERT_ATTR_ADD(IDX_SUBJECT_ALT_NAME_UPN, attr_index, (char *)name->d.otherName->value->value.utf8string); break; } RWARN("Invalid UPN in Subject Alt Name (should be UTF-8)"); break; #endif /* GEN_OTHERNAME */ default: /* XXX TODO handle other SAN types */ break; } } } if (names != NULL) GENERAL_NAMES_free(names); } /* * Only add extensions for the actual client certificate */ if (attr_index == 0) { #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER) ext_list = X509_get0_extensions(cert); #else ext_list = cert->cert_info->extensions; #endif /* * Grab the X509 extensions, and create attributes out of them. * For laziness, we re-use the OpenSSL names */ if (sk_X509_EXTENSION_num(ext_list) > 0) { int i, len; char *p; BIO *out; out = BIO_new(BIO_s_mem()); strlcpy(attribute, "TLS-Client-Cert-", sizeof(attribute)); for (i = 0; i < sk_X509_EXTENSION_num(ext_list); i++) { char value[1024]; ASN1_OBJECT *obj; X509_EXTENSION *ext; fr_dict_attr_t const *da; ext = sk_X509_EXTENSION_value(ext_list, i); obj = X509_EXTENSION_get_object(ext); i2a_ASN1_OBJECT(out, obj); len = BIO_read(out, attribute + 16 , sizeof(attribute) - 16 - 1); if (len <= 0) continue; attribute[16 + len] = '\0'; for (p = attribute + 16; *p != '\0'; p++) if (*p == ' ') *p = '-'; X509V3_EXT_print(out, ext, 0, 0); len = BIO_read(out, value , sizeof(value) - 1); if (len <= 0) continue; value[len] = '\0'; da = fr_dict_attr_by_name(dict_freeradius, attribute); if (!da) { RWDEBUG3("Skipping attribute %s: " "Add dictionary definition if you want to access it", attribute); continue; } MEM(vp = fr_pair_afrom_da(request, da)); if (fr_pair_value_from_str(vp, value, -1, '\0', true) < 0) { RPWDEBUG3("Skipping: %s += '%s'", attribute, value); talloc_free(vp); continue; } fr_cursor_append(cursor, vp); } BIO_free_all(out); } } return 0; }
/** Determine the PSK to use for an incoming connection * * @param[in] ssl session. * @param[in] identity The identity of the PSK to search for. * @param[out] psk Where to write the PSK we found (if any). * @param[in] max_psk_len The length of the buffer provided for PSK. * @return * - 0 if no PSK matching identity was found. * - >0 if a PSK matching identity was found (the length of bytes written to psk). */ unsigned int tls_session_psk_server_cb(SSL *ssl, const char *identity, unsigned char *psk, unsigned int max_psk_len) { unsigned int psk_len = 0; fr_tls_conf_t *conf; REQUEST *request; conf = (fr_tls_conf_t *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_CONF); if (!conf) return 0; request = (REQUEST *)SSL_get_ex_data(ssl, FR_TLS_EX_INDEX_REQUEST); if (request && conf->psk_query) { size_t hex_len; VALUE_PAIR *vp; char buffer[2 * PSK_MAX_PSK_LEN + 4]; /* allow for too-long keys */ /* * The passed identity is weird. Deny it. */ if (!session_psk_identity_is_safe(identity)) { RWDEBUG("Invalid characters in PSK identity %s", identity); return 0; } MEM(pair_update_request(&vp, attr_tls_psk_identity) >= 0); if (fr_pair_value_from_str(vp, identity, -1, '\0', true) < 0) { RPWDEBUG2("Failed parsing TLS PSK Identity"); talloc_free(vp); return 0; } hex_len = xlat_eval(buffer, sizeof(buffer), request, conf->psk_query, NULL, NULL); if (!hex_len) { RWDEBUG("PSK expansion returned an empty string."); return 0; } /* * The returned key is truncated at MORE than * OpenSSL can handle. That way we can detect * the truncation, and complain about it. */ if (hex_len > (2 * max_psk_len)) { RWDEBUG("Returned PSK is too long (%u > %u)", (unsigned int) hex_len, 2 * max_psk_len); return 0; } /* * Leave the TLS-PSK-Identity in the request, and * convert the expansion from printable string * back to hex. */ return fr_hex2bin(psk, max_psk_len, buffer, hex_len); } if (!conf->psk_identity) { DEBUG("No static PSK identity set. Rejecting the user"); return 0; } /* * No REQUEST, or no dynamic query. Just look for a * static identity. */ if (strcmp(identity, conf->psk_identity) != 0) { ERROR("Supplied PSK identity %s does not match configuration. Rejecting.", identity); return 0; } psk_len = strlen(conf->psk_password); if (psk_len > (2 * max_psk_len)) return 0; return fr_hex2bin(psk, max_psk_len, conf->psk_password, psk_len); }
/** Callback for map_to_request * * Performs exactly the same job as map_to_vp, but pulls attribute values from LDAP entries * * @see map_to_vp */ int rlm_ldap_map_getvalue(TALLOC_CTX *ctx, VALUE_PAIR **out, REQUEST *request, vp_map_t const *map, void *uctx) { rlm_ldap_result_t *self = uctx; VALUE_PAIR *head = NULL, *vp; vp_cursor_t cursor; int i; fr_cursor_init(&cursor, &head); switch (map->lhs->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 TMPL_TYPE_LIST: for (i = 0; i < self->count; i++) { vp_map_t *attr = NULL; RDEBUG3("Parsing valuepair string \"%s\"", self->values[i]->bv_val); if (map_afrom_attr_str(ctx, &attr, self->values[i]->bv_val, map->lhs->tmpl_request, map->lhs->tmpl_list, REQUEST_CURRENT, PAIR_LIST_REQUEST) < 0) { RWDEBUG("Failed parsing \"%s\" as valuepair (%s), skipping...", fr_strerror(), self->values[i]->bv_val); continue; } if (attr->lhs->tmpl_request != map->lhs->tmpl_request) { RWDEBUG("valuepair \"%s\" has conflicting request qualifier (%s vs %s), skipping...", self->values[i]->bv_val, fr_int2str(request_refs, attr->lhs->tmpl_request, "<INVALID>"), fr_int2str(request_refs, map->lhs->tmpl_request, "<INVALID>")); next_pair: talloc_free(attr); continue; } if ((attr->lhs->tmpl_list != map->lhs->tmpl_list)) { RWDEBUG("valuepair \"%s\" has conflicting list qualifier (%s vs %s), skipping...", self->values[i]->bv_val, fr_int2str(pair_lists, attr->lhs->tmpl_list, "<INVALID>"), fr_int2str(pair_lists, map->lhs->tmpl_list, "<INVALID>")); goto next_pair; } if (map_to_vp(request, &vp, request, attr, NULL) < 0) { RWDEBUG("Failed creating attribute for valuepair \"%s\", skipping...", self->values[i]->bv_val); goto next_pair; } fr_cursor_merge(&cursor, vp); talloc_free(attr); /* * Only process the first value, unless the operator is += */ if (map->op != T_OP_ADD) break; } 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 TMPL_TYPE_ATTR: for (i = 0; i < self->count; i++) { if (!self->values[i]->bv_len) continue; vp = fr_pair_afrom_da(ctx, map->lhs->tmpl_da); rad_assert(vp); if (fr_pair_value_from_str(vp, self->values[i]->bv_val, self->values[i]->bv_len) < 0) { char *escaped; escaped = fr_asprint(vp, self->values[i]->bv_val, self->values[i]->bv_len, '"'); RWDEBUG("Failed parsing value \"%s\" for attribute %s: %s", escaped, map->lhs->tmpl_da->name, fr_strerror()); talloc_free(vp); /* also frees escaped */ continue; } vp->op = map->op; fr_cursor_insert(&cursor, vp); /* * Only process the first value, unless the operator is += */ if (map->op != T_OP_ADD) break; } break; default: rad_assert(0); } *out = head; return 0; }