/** Send a failure message * */ static int eap_sim_send_eap_failure_notification(eap_session_t *eap_session) { REQUEST *request = eap_session->request; RADIUS_PACKET *packet = eap_session->request->reply; fr_cursor_t cursor; VALUE_PAIR *vp; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_cursor_init(&cursor, &packet->vps); vp = fr_pair_find_by_da(packet->vps, attr_eap_sim_notification, TAG_ANY); if (!vp) { vp = fr_pair_afrom_da(packet, attr_eap_sim_notification); vp->vp_uint16 = FR_EAP_SIM_NOTIFICATION_VALUE_GENERAL_FAILURE; fr_cursor_append(&cursor, vp); } /* * Change the failure notification depending where * we are in the state machine. */ if (eap_sim_session->challenge_success) { vp->vp_uint16 &= ~0x4000; /* Unset phase bit */ } else { vp->vp_uint16 |= 0x4000; /* Set phase bit */ } vp->vp_uint16 &= ~0x8000; /* In both cases success bit should be low */ RDEBUG2("Sending SIM-Notification (%pV)", &vp->data); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* * Set the subtype to notification */ vp = fr_pair_afrom_da(packet, attr_eap_sim_subtype); vp->vp_uint16 = FR_EAP_SIM_SUBTYPE_VALUE_SIM_NOTIFICATION; fr_cursor_append(&cursor, vp); /* * If we're after the challenge phase * then we need to include a MAC to * protect notifications. */ if (eap_sim_session->challenge_success) { vp = fr_pair_afrom_da(packet, attr_eap_sim_mac); fr_pair_replace(&packet->vps, vp); } /* * Encode the packet */ if (eap_sim_compose(eap_session, NULL, 0) < 0) { fr_pair_list_free(&packet->vps); return -1; } return 0; }
/** Add a child * * @param[in] parent to add child to. * @param[in] child to add. */ void _cf_item_add(CONF_ITEM *parent, CONF_ITEM *child) { fr_cursor_t to_merge; CONF_ITEM *ci; rad_assert(parent != child); if (!parent || !child) return; /* * New child, add child trees. */ if (!parent->ident1) parent->ident1 = rbtree_create(parent, _cf_ident1_cmp, NULL, RBTREE_FLAG_NONE); if (!parent->ident2) parent->ident2 = rbtree_create(parent, _cf_ident2_cmp, NULL, RBTREE_FLAG_NONE); fr_cursor_init(&to_merge, &child); for (ci = fr_cursor_head(&to_merge); ci; ci = fr_cursor_next(&to_merge)) { rbtree_insert(parent->ident1, ci); rbtree_insert(parent->ident2, ci); /* NULL ident2 is still a value */ fr_cursor_append(&parent->cursor, ci); /* Append to the list of children */ } }
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; }
/** Send a success notification * */ static int eap_sim_send_eap_success_notification(eap_session_t *eap_session) { REQUEST *request = eap_session->request; RADIUS_PACKET *packet = eap_session->request->reply; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_cursor_t cursor; VALUE_PAIR *vp; RDEBUG2("Sending SIM-Notification (Success)"); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; if (!fr_cond_assert(eap_sim_session->challenge_success)) return -1; fr_cursor_init(&cursor, &packet->vps); /* * Set the subtype to notification */ vp = fr_pair_afrom_da(packet, attr_eap_sim_subtype); vp->vp_uint16 = FR_EAP_SIM_SUBTYPE_VALUE_SIM_NOTIFICATION; fr_cursor_append(&cursor, vp); vp = fr_pair_afrom_da(packet, attr_eap_sim_notification); vp->vp_uint16 = FR_EAP_SIM_NOTIFICATION_VALUE_SUCCESS; fr_cursor_append(&cursor, vp); /* * Need to include an AT_MAC attribute so that it will get * calculated. */ vp = fr_pair_afrom_da(packet, attr_eap_sim_mac); fr_pair_replace(&packet->vps, vp); /* * Encode the packet */ if (eap_sim_compose(eap_session, NULL, 0) < 0) { fr_pair_list_free(&packet->vps); return -1; } return 0; }
/* * 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 xlat_action_t redis_remap_xlat(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request, void const *xlat_inst, UNUSED void *xlat_thread_inst, fr_value_box_t **in) { rlm_redis_t const *inst = talloc_get_type_abort_const(*((void const * const *)xlat_inst), rlm_redis_t); fr_socket_addr_t node_addr; fr_pool_t *pool; fr_redis_conn_t *conn; fr_redis_cluster_rcode_t rcode; fr_value_box_t *vb; if (!in) { REDEBUG("Missing key"); return XLAT_ACTION_FAIL; } if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) { RPEDEBUG("Failed concatenating input"); return XLAT_ACTION_FAIL; } if (fr_inet_pton_port(&node_addr.ipaddr, &node_addr.port, (*in)->vb_strvalue, (*in)->vb_length, AF_UNSPEC, true, true) < 0) { RPEDEBUG("Failed parsing node address"); return XLAT_ACTION_FAIL; } if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &node_addr, true) < 0) { RPEDEBUG("Failed locating cluster node"); return XLAT_ACTION_FAIL; } conn = fr_pool_connection_get(pool, request); if (!conn) { REDEBUG("No connections available for cluster node"); return XLAT_ACTION_FAIL; } rcode = fr_redis_cluster_remap(request, inst->cluster, conn); fr_pool_connection_release(pool, request, conn); MEM(vb = fr_value_box_alloc_null(ctx)); fr_value_box_strdup(vb, vb, NULL, fr_int2str(fr_redis_cluster_rcodes_table, rcode, "<INVALID>"), false); fr_cursor_append(out, vb); return XLAT_ACTION_DONE; }
/** * * FIXME do something with mandatory */ ssize_t eap_fast_decode_pair(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t data_len, void *decoder_ctx) { fr_dict_attr_t const *da; uint8_t const *p = data, *end = p + data_len; /* * Decode the TLVs */ while (p < end) { ssize_t ret; uint16_t attr; uint16_t len; VALUE_PAIR *vp; attr = fr_ntoh16_bin(p) & EAP_FAST_TLV_TYPE; p += 2; len = fr_ntoh16_bin(p); p += 2; da = fr_dict_attr_child_by_num(parent, attr); if (!da) { MEM(vp = fr_pair_afrom_child_num(ctx, parent, attr)); } else if (da->type == FR_TYPE_TLV) { p += (size_t) eap_fast_decode_pair(ctx, cursor, parent, p, len, decoder_ctx); continue; } else { MEM(vp = fr_pair_afrom_da(ctx, da)); } ret = fr_value_box_from_network(vp, &vp->data, vp->vp_type, vp->da, p, len, true); if (ret != len) { fr_pair_to_unknown(vp); fr_pair_value_memcpy(vp, p, len, true); } fr_cursor_append(cursor, vp); p += len; } return p - data; }
/** Add our logging destination to the linked list of logging destinations (if it doesn't already exist) * * @param[in] instance of rlm_logtee. * @param[in] thread Thread specific data. * @param[in] request request to add our log destination to. * @return * - #RLM_MODULE_NOOP if log destination already exists. * - #RLM_MODULE_OK if we added a new destination. */ static rlm_rcode_t mod_insert_logtee(UNUSED void *instance, void *thread, REQUEST *request) { fr_cursor_t cursor; log_dst_t *dst; bool exists = false; for (dst = fr_cursor_init(&cursor, &request->log.dst); dst; dst = fr_cursor_next(&cursor)) { if (dst->uctx == thread) exists = true; } if (exists) return RLM_MODULE_NOOP; dst = talloc_zero(request, log_dst_t); dst->func = logtee_it; dst->uctx = thread; fr_cursor_append(&cursor, dst); 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; }
/** Return the node that is currently servicing a particular key * * */ static xlat_action_t redis_node_xlat(TALLOC_CTX *ctx, fr_cursor_t *out, REQUEST *request, void const *xlat_inst, UNUSED void *xlat_thread_inst, fr_value_box_t **in) { rlm_redis_t const *inst = talloc_get_type_abort_const(*((void const * const *)xlat_inst), rlm_redis_t); fr_redis_cluster_key_slot_t const *key_slot; fr_redis_cluster_node_t const *node; fr_ipaddr_t ipaddr; uint16_t port; char const *p; char *q; char const *key; size_t key_len; unsigned long idx = 0; fr_value_box_t *vb; if (!in) { REDEBUG("Missing key"); return XLAT_ACTION_FAIL; } if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) { RPEDEBUG("Failed concatenating input"); return XLAT_ACTION_FAIL; } key = p = (*in)->vb_strvalue; p = strchr(p, ' '); /* Look for index */ if (p) { key_len = p - key; idx = strtoul(p, &q, 10); if (q == p) { REDEBUG("Tailing garbage after node index"); return XLAT_ACTION_FAIL; } } else { key_len = (*in)->vb_length; } key_slot = fr_redis_cluster_slot_by_key(inst->cluster, request, (uint8_t const *)key, key_len); if (idx == 0) { node = fr_redis_cluster_master(inst->cluster, key_slot); } else { node = fr_redis_cluster_slave(inst->cluster, key_slot, idx - 1); } if (!node) { RDEBUG2("No node available for this key slot"); return XLAT_ACTION_DONE; } if ((fr_redis_cluster_ipaddr(&ipaddr, node) < 0) || (fr_redis_cluster_port(&port, node) < 0)) { REDEBUG("Failed retrieving node information"); return XLAT_ACTION_FAIL; } MEM(vb = fr_value_box_alloc_null(ctx)); fr_value_box_asprintf(vb, vb, NULL, false, "%pV:%u", fr_box_ipaddr(ipaddr), port); fr_cursor_append(out, vb); return XLAT_ACTION_DONE; }
static int radsnmp_send_recv(radsnmp_conf_t *conf, int fd) { fr_strerror(); #define NEXT_LINE(_line, _buffer) \ { \ size_t _len; \ if (stop) return 0; \ errno = 0;\ _line = fgets(_buffer, sizeof(_buffer), stdin); \ if (_line) { \ _len = strlen(_line); \ if ((_len > 0) && (_line[_len - 1] == '\n')) _line[_len - 1] = '\0'; \ DEBUG2("read: %s", _line); \ } \ } /* * Read commands from pass_persist */ while (!stop) { radsnmp_command_t command; char buffer[256]; char *line; ssize_t slen; fr_cursor_t cursor; VALUE_PAIR *vp; RADIUS_PACKET *request; /* * Alloc a new request so we can start adding * new pairs to it. */ request = radsnmp_alloc(conf, fd); if (!request) { ERROR("Failed allocating request"); return EXIT_FAILURE; } fr_cursor_init(&cursor, &request->vps); NEXT_LINE(line, buffer); /* * Determine the type of SNMP operation */ command = fr_str2int(radsnmp_command_str, line, RADSNMP_UNKNOWN); switch (command) { case RADSNMP_EXIT: DEBUG("Empty command, exiting"); return 0; case RADSNMP_PING: RESPOND_STATIC("PONG"); continue; case RADSNMP_SET: { char value_buff[254]; /* RADIUS attribute length + 1 */ char *value; char type_str[64]; char *p; fr_dict_enum_t *type; NEXT_LINE(line, buffer); /* Should be the OID */ NEXT_LINE(value, value_buff); /* Should be the value */ p = strchr(value, ' '); if (!p) { ERROR("No SNMP type specified (or type/value string was malformed)"); RESPOND_STATIC("NONE"); continue; } if ((size_t)(p - value) >= sizeof(type_str)) { ERROR("SNMP Type string too long"); RESPOND_STATIC("NONE"); continue; } strlcpy(type_str, value, (p - value) + 1); type = fr_dict_enum_by_alias(attr_freeradius_snmp_type, type_str, -1); if (!type) { ERROR("Unknown type \"%s\"", type_str); RESPOND_STATIC("NONE"); continue; } slen = radsnmp_pair_from_oid(conf, conf, &cursor, line, type->value->vb_uint32, p + 1); } break; case RADSNMP_GET: case RADSNMP_GETNEXT: NEXT_LINE(line, buffer); /* Should be the OID */ slen = radsnmp_pair_from_oid(conf, conf, &cursor, line, 0, NULL); break; default: ERROR("Unknown command \"%s\"", line); RESPOND_STATIC("NONE"); talloc_free(request); continue; } /* * Deal with any errors from the GET/GETNEXT/SET command */ if (slen <= 0) { char *spaces, *text; fr_canonicalize_error(conf, &spaces, &text, slen, line); ERROR("Failed evaluating OID:"); ERROR("%s", text); ERROR("%s^ %s", spaces, fr_strerror()); talloc_free(spaces); talloc_free(text); talloc_free(request); RESPOND_STATIC("NONE"); continue; } /* * Now add an attribute indicating what the * SNMP operation was */ vp = fr_pair_afrom_da(request, attr_freeradius_snmp_operation); if (!vp) { ERROR("Failed allocating SNMP operation attribute"); return EXIT_FAILURE; } vp->vp_uint32 = (unsigned int)command; /* Commands must match dictionary */ fr_cursor_append(&cursor, vp); /* * Add message authenticator or the stats * request will be rejected. */ MEM(vp = fr_pair_afrom_da(request, attr_message_authenticator)); fr_pair_value_memcpy(vp, (uint8_t const *)"\0", 1, true); fr_cursor_append(&cursor, vp); /* * Send the packet */ { RADIUS_PACKET *reply = NULL; ssize_t rcode; fd_set set; unsigned int ret; unsigned int i; if (fr_radius_packet_encode(request, NULL, conf->secret) < 0) { ERROR("Failed encoding request: %s", fr_strerror()); return EXIT_FAILURE; } if (fr_radius_packet_sign(request, NULL, conf->secret) < 0) { ERROR("Failed signing request: %s", fr_strerror()); return EXIT_FAILURE; } /* * Print the attributes we're about to send */ if (fr_log_fp) fr_packet_header_print(fr_log_fp, request, false); if (fr_debug_lvl > 0) fr_pair_list_fprint(fr_log_fp, request->vps); #ifndef NDEBUG if (fr_log_fp && (fr_debug_lvl > 3)) fr_radius_packet_print_hex(request); #endif FD_ZERO(&set); /* clear the set */ FD_SET(fd, &set); /* * Any connection issues cause us to exit, so * the connection can be re-initialised on the * next call. */ for (i = 0; i < conf->retries; i++) { rcode = write(request->sockfd, request->data, request->data_len); if (rcode < 0) { ERROR("Failed sending: %s", fr_syserror(errno)); return EXIT_FAILURE; } rcode = select(fd + 1, &set, NULL, NULL, &conf->timeout); switch (rcode) { case -1: ERROR("Select failed: %s", fr_syserror(errno)); return EXIT_FAILURE; case 0: DEBUG("Response timeout. Retrying %d/%u...", i + 1, conf->retries); continue; /* Timeout */ case 1: reply = fr_radius_packet_recv(request, request->sockfd, UDP_FLAGS_NONE, RADIUS_MAX_ATTRIBUTES, false); if (!reply) { ERROR("Failed receiving reply: %s", fr_strerror()); recv_error: RESPOND_STATIC("NONE"); talloc_free(request); continue; } if (fr_radius_packet_decode(reply, request, RADIUS_MAX_ATTRIBUTES, false, conf->secret) < 0) { ERROR("Failed decoding reply: %s", fr_strerror()); goto recv_error; } break; default: DEBUG("Invalid select() return value %zd", rcode); return EXIT_FAILURE; } break; } if (!reply) { ERROR("Server didn't respond"); return EXIT_FAILURE; } /* * Print the attributes we received in response */ if (fr_log_fp) fr_packet_header_print(fr_log_fp, reply, true); if (fr_debug_lvl > 0) fr_pair_list_fprint(fr_log_fp, reply->vps); #ifndef NDEBUG if (fr_log_fp && (fr_debug_lvl > 3)) fr_radius_packet_print_hex(reply); #endif switch (command) { case RADSNMP_GET: case RADSNMP_GETNEXT: ret = radsnmp_get_response(STDOUT_FILENO, conf->snmp_oid_root, attr_freeradius_snmp_type, reply->vps); switch (ret) { case -1: ERROR("Failed converting pairs to varbind response: %s", fr_strerror()); return EXIT_FAILURE; case 0: DEBUG("Empty response"); break; default: DEBUG("Returned %u varbind responses", ret); break; } break; case RADSNMP_SET: if (radsnmp_set_response(STDOUT_FILENO, attr_freeradius_snmp_failure, reply->vps) < 0) { ERROR("Failed writing SET response: %s", fr_strerror()); return EXIT_FAILURE; } break; default: assert(0); return EXIT_FAILURE; } talloc_free(request); } } return EXIT_SUCCESS; }
/* * Decode ONE value into a VP */ static ssize_t decode_value_internal(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *da, uint8_t const *data, size_t data_len) { VALUE_PAIR *vp; uint8_t const *p = data; FR_PROTO_TRACE("%s called to parse %zu bytes", __FUNCTION__, data_len); FR_PROTO_HEX_DUMP(data, data_len, NULL); vp = fr_pair_afrom_da(ctx, da); if (!vp) return -1; /* * Unknown attributes always get converted to * octet types, so there's no way there could * be multiple attributes, so its safe to * steal the unknown attribute into the context * of the pair. */ if (da->flags.is_unknown) talloc_steal(vp, da); if (vp->da->type == FR_TYPE_STRING) { uint8_t const *q, *end; q = end = data + data_len; /* * Not allowed to be an array, copy the whole value */ if (!vp->da->flags.array) { fr_pair_value_bstrncpy(vp, (char const *)p, end - p); p = end; goto finish; } for (;;) { q = memchr(p, '\0', q - p); /* Malformed but recoverable */ if (!q) q = end; fr_pair_value_bstrncpy(vp, (char const *)p, q - p); p = q + 1; vp->vp_tainted = true; /* Need another VP for the next round */ if (p < end) { fr_cursor_append(cursor, vp); vp = fr_pair_afrom_da(ctx, da); if (!vp) return -1; continue; } break; } goto finish; } switch (vp->da->type) { /* * Doesn't include scope, whereas the generic format can */ case FR_TYPE_IPV6_ADDR: memcpy(&vp->vp_ipv6addr, p, sizeof(vp->vp_ipv6addr)); vp->vp_ip.af = AF_INET6; vp->vp_ip.scope_id = 0; vp->vp_ip.prefix = 128; vp->vp_tainted = true; p += sizeof(vp->vp_ipv6addr); break; case FR_TYPE_IPV6_PREFIX: memcpy(&vp->vp_ipv6addr, p + 1, sizeof(vp->vp_ipv6addr)); vp->vp_ip.af = AF_INET6; vp->vp_ip.scope_id = 0; vp->vp_ip.prefix = p[0]; vp->vp_tainted = true; p += sizeof(vp->vp_ipv6addr) + 1; break; default: { ssize_t ret; ret = fr_value_box_from_network(vp, &vp->data, vp->da->type, da, p, data_len, true); if (ret < 0) { FR_PROTO_TRACE("decoding as unknown type"); if (fr_pair_to_unknown(vp) < 0) return -1; fr_pair_value_memcpy(vp, p, data_len); ret = data_len; } p += (size_t) ret; } } finish: FR_PROTO_TRACE("decoding value complete, adding new pair and returning %zu byte(s)", p - data); fr_cursor_append(cursor, vp); return p - data; }
/** 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; }
/** Decode SIM/AKA/AKA' specific packet data * * @note data should point to the subtype field in the EAP packet. * * Extracts the SUBTYPE and adds it an attribute, then decodes any TLVs in the * SIM/AKA/AKA' packet. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Code | Identifier | Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | Type | Subtype | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * * The first byte of the data pointer should be the subtype. * * @param[in] request the current request. * @param[in] decoded where to write decoded attributes. * @param[in] dict for looking up attributes. * @param[in] data to convert to pairs. * @param[in] data_len length of data to convert. * @param[in] decoder_ctx holds the state of the decoder. * @return * - 0 on success. * - -1 on failure. */ int fr_sim_decode(REQUEST *request, fr_cursor_t *decoded, fr_dict_t const *dict, uint8_t const *data, size_t data_len, fr_sim_decode_ctx_t *decoder_ctx) { ssize_t rcode; uint8_t const *p = data; uint8_t const *end = p + data_len; /* * Move the cursor to the end, so we know if * any additional attributes were added. */ fr_cursor_tail(decoded); /* * We need at least enough data for the subtype * and reserved bytes. * * Note: Not all packets should contain attrs. * When the client acknowledges an * AKA-Notification from the server, the * AKA-Notification is returns contains no * attributes. */ if (data_len < 3) { fr_strerror_printf("Packet data too small, expected at least 3 bytes got %zu bytes", data_len); return -1; } p += 3; /* * Loop over all the attributes decoding * them into the appropriate attributes * in the SIM/AKA/AKA' dict. */ while (p < end) { rcode = fr_sim_decode_pair(request->packet, decoded, dict, p, end - p, decoder_ctx); if (rcode <= 0) { RPEDEBUG("Failed decoding AT"); error: fr_cursor_free_list(decoded); /* Free any attributes we added */ return -1; } p += rcode; rad_assert(p <= end); } /* * No point in doing packet_ctx until we known the rest * of the data is OK! */ { VALUE_PAIR *vp; vp = fr_pair_afrom_child_num(request->packet, fr_dict_root(dict), FR_SIM_SUBTYPE); if (!vp) { fr_strerror_printf("Failed allocating subtype attribute"); goto error; } vp->vp_uint32 = data[0]; fr_cursor_append(decoded, vp); } return 0; }
/* * Do the statistics */ static rlm_rcode_t CC_HINT(nonnull) mod_stats(void *instance, void *thread, REQUEST *request) { int i; uint32_t stats_type; rlm_stats_thread_t *t = thread; rlm_stats_t *inst = instance; VALUE_PAIR *vp; rlm_stats_data_t mydata, *stats; fr_cursor_t cursor; char buffer[64]; uint64_t local_stats[sizeof(inst->stats) / sizeof(inst->stats[0])]; /* * Increment counters only in "send foo" sections. * * i.e. only when we have a reply to send. */ if (request->request_state == REQUEST_SEND) { int src_code, dst_code; src_code = request->packet->code; if (src_code >= FR_MAX_PACKET_CODE) src_code = 0; dst_code = request->reply->code; if (dst_code >= FR_MAX_PACKET_CODE) dst_code = 0; t->stats[src_code]++; t->stats[dst_code]++; /* * Update source statistics */ mydata.ipaddr = request->packet->src_ipaddr; stats = rbtree_finddata(t->src, &mydata); if (!stats) { MEM(stats = talloc_zero(t, rlm_stats_data_t)); stats->ipaddr = request->packet->src_ipaddr; stats->created = request->async->recv_time; (void) rbtree_insert(t->src, stats); } stats->last_packet = request->async->recv_time; stats->stats[src_code]++; stats->stats[dst_code]++; /* * Update destination statistics */ mydata.ipaddr = request->packet->dst_ipaddr; stats = rbtree_finddata(t->dst, &mydata); if (!stats) { MEM(stats = talloc_zero(t, rlm_stats_data_t)); stats->ipaddr = request->packet->dst_ipaddr; stats->created = request->async->recv_time; (void) rbtree_insert(t->dst, stats); } stats->last_packet = request->async->recv_time; stats->stats[src_code]++; stats->stats[dst_code]++; /* * @todo - periodically clean up old entries. */ if ((t->last_global_update + NANOSEC) > request->async->recv_time) { return RLM_MODULE_UPDATED; } t->last_global_update = request->async->recv_time; pthread_mutex_lock(&inst->mutex); for (i = 0; i < FR_MAX_PACKET_CODE; i++) { inst->stats[i] += t->stats[i]; t->stats[i] = 0; } pthread_mutex_unlock(&inst->mutex); return RLM_MODULE_UPDATED; } /* * Ignore "authenticate" and anything other than Status-Server */ if ((request->request_state != REQUEST_RECV) || (request->packet->code != FR_CODE_STATUS_SERVER)) { return RLM_MODULE_NOOP; } vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_type, TAG_ANY); if (!vp) { stats_type = FR_FREERADIUS_STATS4_TYPE_VALUE_GLOBAL; } else { stats_type = vp->vp_uint32; } /* * Create attributes based on the statistics. */ fr_cursor_init(&cursor, &request->reply->vps); MEM(pair_update_reply(&vp, attr_freeradius_stats4_type) >= 0); vp->vp_uint32 = stats_type; switch (stats_type) { case FR_FREERADIUS_STATS4_TYPE_VALUE_GLOBAL: /* global */ /* * Merge our stats with the global stats, and then copy * the global stats to a thread-local variable. * * The copy helps minimize mutex contention. */ pthread_mutex_lock(&inst->mutex); for (i = 0; i < FR_MAX_PACKET_CODE; i++) { inst->stats[i] += t->stats[i]; t->stats[i] = 0; } memcpy(&local_stats, inst->stats, sizeof(inst->stats)); pthread_mutex_unlock(&inst->mutex); vp = NULL; break; case FR_FREERADIUS_STATS4_TYPE_VALUE_CLIENT: /* src */ vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv4_address, TAG_ANY); if (!vp) vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv6_address, TAG_ANY); if (!vp) return RLM_MODULE_NOOP; mydata.ipaddr = vp->vp_ip; coalesce(local_stats, t, offsetof(rlm_stats_thread_t, src), &mydata); break; case FR_FREERADIUS_STATS4_TYPE_VALUE_LISTENER: /* dst */ vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv4_address, TAG_ANY); if (!vp) vp = fr_pair_find_by_da(request->packet->vps, attr_freeradius_stats4_ipv6_address, TAG_ANY); if (!vp) return RLM_MODULE_NOOP; mydata.ipaddr = vp->vp_ip; coalesce(local_stats, t, offsetof(rlm_stats_thread_t, dst), &mydata); break; default: REDEBUG("Invalid value '%d' for FreeRADIUS-Stats4-type", stats_type); return RLM_MODULE_FAIL; } if (vp ) { vp = fr_pair_copy(request->reply, vp); if (vp) { fr_cursor_append(&cursor, vp); (void) fr_cursor_tail(&cursor); } } strcpy(buffer, "FreeRADIUS-Stats4-"); for (i = 0; i < FR_MAX_PACKET_CODE; i++) { fr_dict_attr_t const *da; if (!local_stats[i]) continue; strlcpy(buffer + 18, fr_packet_codes[i], sizeof(buffer) - 18); da = fr_dict_attr_by_name(dict_radius, buffer); if (!da) continue; vp = fr_pair_afrom_da(request->reply, da); if (!vp) return RLM_MODULE_FAIL; vp->vp_uint64 = local_stats[i]; fr_cursor_append(&cursor, vp); (void) fr_cursor_tail(&cursor); } return RLM_MODULE_OK; }
/** Create any kind of VP from the attribute contents * * @param[in] ctx to allocate new attributes in. * @param[in] cursor to addd new attributes to. * @param[in] parent the current attribute we're processing. * @param[in] data to parse. Points to the data field of the attribute. * @param[in] attr_len length of the attribute being parsed. * @param[in] data_len length of the remaining data in the packet. * @param[in] decoder_ctx IVs, keys etc... * @return * - Length on success. * - -1 on failure. */ static ssize_t sim_decode_pair_value(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, size_t const data_len, void *decoder_ctx) { VALUE_PAIR *vp; uint8_t const *p = data; size_t prefix = 0; fr_sim_decode_ctx_t *packet_ctx = decoder_ctx; if (!fr_cond_assert(attr_len <= data_len)) return -1; if (!fr_cond_assert(parent)) return -1; FR_PROTO_TRACE("Parent %s len %zu", parent->name, attr_len); FR_PROTO_HEX_DUMP(data, attr_len, __FUNCTION__ ); FR_PROTO_TRACE("Type \"%s\" (%u)", fr_int2str(fr_value_box_type_table, parent->type, "?Unknown?"), parent->type); /* * Special cases, attributes that either have odd formats, or need * have information we need to decode the packet. */ switch (parent->attr) { /* * We need to record packet_ctx so we can decrypt AT_ENCR attributes. * * If we don't find it before, then that's fine, we'll try and * find it in the rest of the packet after the encrypted * attribute. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_IV | Length = 5 | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | Initialization Vector | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_SIM_IV: if (sim_iv_extract(&packet_ctx->iv[0], data, attr_len) < 0) return -1; packet_ctx->have_iv = true; break; /* Now create the attribute */ /* * AT_RES - Special case (RES length is in bits) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_RES | Length | RES Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-| * | | * | RES | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_EAP_AKA_RES: { uint16_t res_len; if (attr_len < 2) goto raw; /* Need at least two bytes for the length field */ res_len = (p[0] << 8) | p[1]; if (res_len % 8) { fr_strerror_printf("%s: RES Length (%hu) is not a multiple of 8", __FUNCTION__, res_len); return -1; } res_len /= 8; if (res_len > (attr_len - 2)) { fr_strerror_printf("%s: RES Length field value (%u bits) > attribute value length (%zu bits)", __FUNCTION__, res_len * 8, (attr_len - 2) * 8); return -1; } if ((res_len < 4) || (res_len > 16)) { fr_strerror_printf("%s: RES Length field value must be between 32-128 bits, got %u bits", __FUNCTION__, (res_len * 8)); return -1; } vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; fr_pair_value_memcpy(vp, p + 2, res_len, true); } goto done; /* * AT_CHECKCODE - Special case (Variable length with no length field) * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_CHECKCODE | Length | Reserved | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * | Checkcode (0 or 20 bytes) | * | | * | | * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_EAP_AKA_CHECKCODE: if (attr_len < 2) goto raw; /* Need at least two bytes for reserved field */ vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; fr_pair_value_memcpy(vp, p + 2, attr_len - 2, true); goto done; default: break; } switch (parent->type) { case FR_TYPE_STRING: if (attr_len < 2) goto raw; /* Need at least two bytes for the length field */ if (parent->flags.length && (attr_len != parent->flags.length)) { wrong_len: fr_strerror_printf("%s: Attribute \"%s\" needs a value of exactly %zu bytes, " "but value was %zu bytes", __FUNCTION__, parent->name, (size_t)parent->flags.length, attr_len); goto raw; } break; case FR_TYPE_OCTETS: /* * Get the number of bytes we expect before the value */ prefix = fr_sim_octets_prefix_len(parent); if (attr_len < prefix) goto raw; if (parent->flags.length && (attr_len != (parent->flags.length + prefix))) goto wrong_len; break; case FR_TYPE_BOOL: case FR_TYPE_UINT8: case FR_TYPE_UINT16: case FR_TYPE_UINT32: case FR_TYPE_UINT64: if (attr_len != fr_sim_attr_sizes[parent->type][0]) goto raw; break; case FR_TYPE_TLV: if (attr_len < 2) goto raw; /* * We presume that the TLVs all fit into one * attribute, OR they've already been grouped * into a contiguous memory buffer. */ return sim_decode_tlv(ctx, cursor, parent, p, attr_len, data_len, decoder_ctx); default: raw: /* * We can't create unknowns for non-skippable attributes * as we're prohibited from continuing by the SIM RFCs. */ if (parent->attr <= SIM_SKIPPABLE_MAX) { fr_strerror_printf_push("%s: Failed parsing non-skippable attribute '%s'", __FUNCTION__, parent->name); return -1; } #ifdef __clang_analyzer__ if (!parent->parent) return -1; /* stupid static analyzers */ #endif rad_assert(parent->parent); /* * Re-write the attribute to be "raw". It is * therefore of type "octets", and will be * handled below. */ parent = fr_dict_unknown_afrom_fields(ctx, parent->parent, fr_dict_vendor_num_by_da(parent), parent->attr); if (!parent) { fr_strerror_printf_push("%s[%d]: Internal sanity check failed", __FUNCTION__, __LINE__); return -1; } } vp = fr_pair_afrom_da(ctx, parent); if (!vp) return -1; /* * For unknown attributes copy the entire value, not skipping * any reserved bytes. */ if (parent->flags.is_unknown || parent->flags.is_raw) { fr_pair_value_memcpy(vp, p, attr_len, true); vp->vp_length = attr_len; goto done; } switch (parent->type) { /* * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<STRING> | Length | Actual <STRING> Length | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | | * . String . * . . * | | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ case FR_TYPE_STRING: { uint16_t actual_len = (p[0] << 8) | p[1]; if (actual_len > (attr_len - 2)) { fr_strerror_printf("%s: Actual length field value (%hu) > attribute value length (%zu)", __FUNCTION__, actual_len, attr_len - 2); return -1; } fr_pair_value_bstrncpy(vp, p + 2, actual_len); } break; case FR_TYPE_OCTETS: /* * Variable length octets buffer */ if (!parent->flags.length) { uint16_t actual_len = (p[0] << 8) | p[1]; if (actual_len > (attr_len - prefix)) { fr_strerror_printf("%s: Actual length field value (%hu) > attribute value length (%zu)", __FUNCTION__, actual_len, attr_len - 2); return -1; } fr_pair_value_memcpy(vp, p + prefix, actual_len, true); /* * Fixed length octets buffer */ } else { fr_pair_value_memcpy(vp, p + prefix, attr_len - prefix, true); } break; /* * Not proper bool. We Use packet_ctx to represent * flag attributes like AT_FULLAUTH_ID_REQ * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<BOOL> | Length = 1 | Reserved | * +---------------+---------------+-------------------------------+ */ case FR_TYPE_BOOL: vp->vp_bool = true; break; /* * Numbers are network byte order. * * In the base RFCs only short (16bit) unsigned integers are used. * We add support for more, just for completeness. * * 0 1 2 3 * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ * | AT_<SHORT> | Length = 1 | Short 1 | Short 2 | * +---------------+---------------+-------------------------------+ */ case FR_TYPE_UINT8: vp->vp_uint8 = p[0]; break; case FR_TYPE_UINT16: memcpy(&vp->vp_uint16, p, sizeof(vp->vp_uint16)); vp->vp_uint16 = ntohs(vp->vp_uint32); break; case FR_TYPE_UINT32: memcpy(&vp->vp_uint32, p, sizeof(vp->vp_uint32)); vp->vp_uint32 = ntohl(vp->vp_uint32); break; case FR_TYPE_UINT64: memcpy(&vp->vp_uint64, p, sizeof(vp->vp_uint64)); vp->vp_uint64 = ntohll(vp->vp_uint64); break; default: fr_pair_list_free(&vp); fr_strerror_printf_push("%s[%d]: Internal sanity check failed", __FUNCTION__, __LINE__); return -1; } done: vp->type = VT_DATA; fr_cursor_append(cursor, vp); return attr_len; }
/** 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; }
static int eap_aka_compose(eap_session_t *eap_session) { eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); fr_cursor_t cursor; fr_cursor_t to_encode; VALUE_PAIR *head = NULL, *vp; REQUEST *request = eap_session->request; ssize_t ret; fr_sim_encode_ctx_t encoder_ctx = { .root = fr_dict_root(dict_eap_aka), .keys = &eap_aka_session->keys, .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, .iv_included = false, .hmac_md = eap_aka_session->mac_md, .eap_packet = eap_session->this_round->request, .hmac_extra = NULL, .hmac_extra_len = 0 }; fr_cursor_init(&cursor, &eap_session->request->reply->vps); fr_cursor_init(&to_encode, &head); while ((vp = fr_cursor_current(&cursor))) { if (!fr_dict_parent_common(encoder_ctx.root, vp->da, true)) { fr_cursor_next(&cursor); continue; } vp = fr_cursor_remove(&cursor); /* * Silently discard encrypted attributes until * the peer should have k_encr. These can be * added by policy, and seem to cause * wpa_supplicant to fail if sent before the challenge. */ if (!eap_aka_session->allow_encrypted && fr_dict_parent_common(attr_eap_aka_encr_data, vp->da, true)) { RWDEBUG("Silently discarding &reply:%s: Encrypted attributes not allowed in this round", vp->da->name); talloc_free(vp); continue; } fr_cursor_append(&to_encode, vp); } RDEBUG2("Encoding EAP-AKA attributes"); log_request_pair_list(L_DBG_LVL_2, request, head, NULL); eap_session->this_round->request->type.num = eap_aka_session->type; eap_session->this_round->request->id = eap_aka_session->aka_id++ & 0xff; eap_session->this_round->set_request_id = true; ret = fr_sim_encode(eap_session->request, head, &encoder_ctx); fr_cursor_head(&to_encode); fr_cursor_free_list(&to_encode); if (ret < 0) { RPEDEBUG("Failed encoding EAP-AKA data"); return -1; } return 0; } /** Send an EAP-AKA identity request to the supplicant * * There are three types of user identities that can be implemented * - Permanent identities such as [email protected] * Permanent identities can be identified by the leading zero followed by * by 15 digits (the IMSI number). * - Ephemeral identities (pseudonyms). These are identities assigned for * identity privacy so the user can't be tracked. These can identities * can either be generated as per the 3GPP 'Security aspects of non-3GPP accesses' * document section 14, where a set of up to 16 encryption keys are used * to reversibly encrypt the IMSI. Alternatively the pseudonym can be completely * randomised and stored in a datastore. * - A fast resumption ID which resolves to data used for fast resumption. * * In order to perform full authentication the original IMSI is required for * forwarding to the HLR. In the case where we can't match/decrypt the pseudonym, * or can't perform fast resumption, we need to request the full identity from * the supplicant. * * @param[in] eap_session to continue. * @return * - 0 on success. * - <0 on failure. */ static int eap_aka_send_identity_request(eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); VALUE_PAIR *vp; RADIUS_PACKET *packet; fr_cursor_t cursor; RDEBUG2("Sending AKA-Identity (%s)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>")); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; eap_aka_session->allow_encrypted = false; /* In case this is after failed fast-resumption */ packet = request->reply; fr_cursor_init(&cursor, &packet->vps); /* * Set the subtype to identity request */ vp = fr_pair_afrom_da(packet, attr_eap_aka_subtype); vp->vp_uint16 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_IDENTITY; fr_cursor_append(&cursor, vp); /* * Select the right type of identity request attribute */ switch (eap_aka_session->id_req) { case SIM_ANY_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_any_id_req); break; case SIM_PERMANENT_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_permanent_id_req); break; case SIM_FULLAUTH_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_aka_fullauth_id_req); break; default: rad_assert(0); } vp->vp_bool = true; fr_cursor_append(&cursor, vp); /* * Encode the packet */ if (eap_aka_compose(eap_session) < 0) { failure: fr_pair_list_free(&packet->vps); return -1; } /* * Digest the packet contents, updating our checkcode. */ if (!eap_aka_session->checkcode_state && fr_sim_crypto_init_checkcode(eap_aka_session, &eap_aka_session->checkcode_state, eap_aka_session->checkcode_md) < 0) { RPEDEBUG("Failed initialising checkcode"); goto failure; } if (fr_sim_crypto_update_checkcode(eap_aka_session->checkcode_state, eap_session->this_round->request) < 0) { RPEDEBUG("Failed updating checkcode"); goto failure; } return 0; }
/** Logging callback to write log messages to a destination * * This allows the logging destination to be customised on a per request basis. * * @note Function does not write log output immediately * * @param[in] type What type of message this is (error, warn, info, debug). * @param[in] lvl At what logging level this message should be output. * @param[in] request The current request. * @param[in] file src file the log message was generated in. * @param[in] line number the log message was generated on. * @param[in] fmt sprintf style fmt string. * @param[in] ap Arguments for the fmt string. * @param[in] uctx Context data for the log function. */ static void logtee_it(fr_log_type_t type, fr_log_lvl_t lvl, REQUEST *request, UNUSED char const *file, UNUSED int line, char const *fmt, va_list ap, void *uctx) { rlm_logtee_thread_t *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t); rlm_logtee_t const *inst = t->inst; char *msg, *exp; fr_cursor_t cursor; VALUE_PAIR *vp; log_dst_t *dst; rad_assert(t->msg->vp_length == 0); /* Should have been cleared before returning */ /* * None of this should involve mallocs unless msg > 1k */ msg = talloc_typed_vasprintf(t->msg, fmt, ap); fr_value_box_strdup_buffer_shallow(NULL, &t->msg->data, attr_log_message, msg, true); t->type->vp_uint32 = (uint32_t) type; t->lvl->vp_uint32 = (uint32_t) lvl; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_prepend(&cursor, t->msg); fr_cursor_prepend(&cursor, t->type); fr_cursor_prepend(&cursor, t->lvl); fr_cursor_head(&cursor); /* * Now expand our fmt string to encapsulate the * message and any metadata * * Fixme: Would be better to call tmpl_expand * into a variable length ring buffer. */ dst = request->log.dst; request->log.dst = NULL; if (tmpl_aexpand(t, &exp, request, inst->log_fmt, NULL, NULL) < 0) goto finish; request->log.dst = dst; fr_fring_overwrite(t->fring, exp); /* Insert it into the buffer */ if (!t->pending) { t->pending = true; logtee_fd_active(t); /* Listen for when the fd is writable */ } finish: /* * Don't free, we re-use the VALUE_PAIRs for the next message */ vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->lvl)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->type)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->msg)) fr_cursor_append(&cursor, vp); fr_value_box_clear(&t->msg->data); /* Clear message data */ }
/* * build a reply to be sent. */ static int eap_sim_compose(eap_session_t *eap_session, uint8_t const *hmac_extra, size_t hmac_extra_len) { eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_cursor_t cursor; fr_cursor_t to_encode; VALUE_PAIR *head = NULL, *vp; REQUEST *request = eap_session->request; fr_sim_encode_ctx_t encoder_ctx = { .root = fr_dict_root(dict_eap_sim), .keys = &eap_sim_session->keys, .iv = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, .iv_included = false, .hmac_md = EVP_sha1(), .eap_packet = eap_session->this_round->request, .hmac_extra = hmac_extra, .hmac_extra_len = hmac_extra_len }; ssize_t ret; /* we will set the ID on requests, since we have to HMAC it */ eap_session->this_round->set_request_id = true; fr_cursor_init(&cursor, &eap_session->request->reply->vps); fr_cursor_init(&to_encode, &head); while ((vp = fr_cursor_current(&cursor))) { if (!fr_dict_parent_common(fr_dict_root(dict_eap_sim), vp->da, true)) { fr_cursor_next(&cursor); continue; } vp = fr_cursor_remove(&cursor); /* * Silently discard encrypted attributes until * the peer should have k_encr. These can be * added by policy, and seem to cause * wpa_supplicant to fail if sent before the challenge. */ if (!eap_sim_session->allow_encrypted && fr_dict_parent_common(attr_eap_sim_encr_data, vp->da, true)) { RWDEBUG("Silently discarding &reply:%s: Encrypted attributes not allowed in this round", vp->da->name); talloc_free(vp); continue; } fr_cursor_append(&to_encode, vp); } RDEBUG2("Encoding EAP-SIM attributes"); log_request_pair_list(L_DBG_LVL_2, request, head, NULL); eap_session->this_round->request->type.num = FR_EAP_SIM; eap_session->this_round->request->id = eap_sim_session->sim_id++ & 0xff; eap_session->this_round->set_request_id = true; ret = fr_sim_encode(eap_session->request, head, &encoder_ctx); fr_cursor_head(&to_encode); fr_cursor_free_list(&to_encode); if (ret < 0) { RPEDEBUG("Failed encoding EAP-SIM data"); return -1; } return 0; } static int eap_sim_send_start(eap_session_t *eap_session) { REQUEST *request = eap_session->request; VALUE_PAIR **vps, *vp; uint16_t version; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); RADIUS_PACKET *packet; rad_assert(eap_session->request != NULL); rad_assert(eap_session->request->reply); RDEBUG2("Sending SIM-State"); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; eap_sim_session->allow_encrypted = false; /* In case this is after failed fast-resumption */ /* these are the outgoing attributes */ packet = eap_session->request->reply; vps = &packet->vps; rad_assert(vps != NULL); /* * Add appropriate TLVs for the EAP things we wish to send. */ vp = fr_pair_afrom_da(packet, attr_eap_sim_version_list); vp->vp_uint16 = EAP_SIM_VERSION; fr_pair_add(vps, vp); /* record it in the ess */ version = htons(EAP_SIM_VERSION); memcpy(eap_sim_session->keys.gsm.version_list, &version, sizeof(version)); eap_sim_session->keys.gsm.version_list_len = 2; /* * Select the right type of identity request attribute */ switch (eap_sim_session->id_req) { case SIM_ANY_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_sim_any_id_req); break; case SIM_PERMANENT_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_sim_permanent_id_req); break; case SIM_FULLAUTH_ID_REQ: vp = fr_pair_afrom_da(packet, attr_eap_sim_fullauth_id_req); break; default: rad_assert(0); } vp->vp_bool = true; fr_pair_replace(vps, vp); /* the SUBTYPE, set to start. */ vp = fr_pair_afrom_da(packet, attr_eap_sim_subtype); vp->vp_uint16 = EAP_SIM_START; fr_pair_replace(vps, vp); /* * Encode the packet */ if (eap_sim_compose(eap_session, NULL, 0) < 0) { fr_pair_list_free(&packet->vps); return -1; } return 0; }