/** Create thread-specific connections and buffers * * @param[in] conf section containing the configuration of this module instance. * @param[in] instance of rlm_logtee_t. * @param[in] el The event list serviced by this thread. * @param[in] thread specific data. * @return * - 0 on success. * - -1 on failure. */ static int mod_thread_instantiate(UNUSED CONF_SECTION const *conf, void *instance, fr_event_list_t *el, void *thread) { rlm_logtee_t *inst = talloc_get_type_abort(instance, rlm_logtee_t); rlm_logtee_thread_t *t = talloc_get_type_abort(thread, rlm_logtee_thread_t); MEM(t->fring = fr_fring_alloc(t, inst->buffer_depth, false)); t->inst = inst; t->el = el; /* * Pre-allocate temporary attributes */ MEM(t->msg_pool = talloc_pool(t, 1024)); MEM(t->msg = fr_pair_afrom_da(t->msg_pool, attr_log_message)); MEM(t->type = fr_pair_afrom_da(t, attr_log_type)); MEM(t->lvl = fr_pair_afrom_da(t, attr_log_level)); /* * This opens the outbound connection */ t->conn = fr_connection_alloc(t, el, &inst->connection_timeout, &inst->reconnection_delay, _logtee_conn_init, _logtee_conn_open, _logtee_conn_close, inst->name, t); if (t->conn == NULL) return -1; fr_connection_signal_init(t->conn); return 0; }
/** 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; }
static int arp_socket_decode(UNUSED rad_listen_t *listener, REQUEST *request) { int i; uint8_t const *p = request->packet->data, *end = p + request->packet->data_len; fr_cursor_t cursor; fr_cursor_init(&cursor, &request->packet->vps); for (i = 0; header_names[i].name != NULL; i++) { ssize_t ret; size_t len; fr_dict_attr_t const *da; VALUE_PAIR *vp = NULL; len = header_names[i].len; if (!fr_cond_assert((size_t)(end - p) < len)) return -1; /* Should have been detected in socket_recv */ da = fr_dict_attr_by_name(dict_arp, header_names[i].name); if (!da) return 0; MEM(vp = fr_pair_afrom_da(request->packet, da)); ret = fr_value_box_from_network(vp, &vp->data, da->type, da, p, len, true); if (ret <= 0) { fr_pair_to_unknown(vp); fr_pair_value_memcpy(vp, p, len); } DEBUG2("&%pP", vp); fr_cursor_insert(&cursor, vp); } return 0; }
/* * Set the SQL user name. * * We don't call the escape function here. The resulting string * will be escaped later in the queries xlat so we don't need to * escape it twice. (it will make things wrong if we have an * escape candidate character in the username) */ int sql_set_user(rlm_sql_t const *inst, REQUEST *request, char const *username) { char *expanded = NULL; VALUE_PAIR *vp = NULL; char const *sqluser; ssize_t len; rad_assert(request->packet != NULL); if (username != NULL) { sqluser = username; } else if (inst->config->query_user[0] != '\0') { sqluser = inst->config->query_user; } else { return 0; } len = radius_axlat(&expanded, request, sqluser, NULL, NULL); if (len < 0) { return -1; } vp = fr_pair_afrom_da(request->packet, inst->sql_user); if (!vp) { talloc_free(expanded); return -1; } fr_pair_value_strsteal(vp, expanded); RDEBUG2("SQL-User-Name set to '%s'", vp->vp_strvalue); vp->op = T_OP_SET; radius_pairmove(request, &request->packet->vps, vp, false); /* needs to be pair move else op is not respected */ return 0; }
/** 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 rlm_rcode_t CC_HINT(nonnull) mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_chap_t *inst = instance; VALUE_PAIR *vp; if (!fr_pair_find_by_da(request->packet->vps, attr_chap_password, TAG_ANY)) return RLM_MODULE_NOOP; /* * Create the CHAP-Challenge if it wasn't already in the packet. * * This is so that the rest of the code does not need to * understand CHAP. */ vp = fr_pair_find_by_da(request->packet->vps, attr_chap_challenge, TAG_ANY); if (!vp) { RDEBUG2("Creating CHAP-Challenge from the request authenticator"); MEM(vp = fr_pair_afrom_da(request->packet, attr_chap_challenge)); fr_pair_value_memcpy(vp, request->packet->vector, sizeof(request->packet->vector)); fr_pair_add(&request->packet->vps, vp); } if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) return RLM_MODULE_NOOP; return RLM_MODULE_OK; }
/* * 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 _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); }
/** 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; }
/** * * 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; }
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; }
/** Unpack data * * Example: %{unpack:&Class 0 integer} * * Expands Class, treating octet at offset 0 (bytes 0-3) as an "integer". */ static ssize_t unpack_xlat(UNUSED void *instance, REQUEST *request, char const *fmt, char *out, size_t outlen) { char *data_name, *data_size, *data_type; char *p; size_t len, input_len; int offset; PW_TYPE type; DICT_ATTR const *da; VALUE_PAIR *vp, *cast; uint8_t const *input; char buffer[256]; uint8_t blob[256]; /* * FIXME: copy only the fields here, as we parse them. */ strlcpy(buffer, fmt, sizeof(buffer)); p = buffer; while (isspace((int) *p)) p++; /* skip leading spaces */ data_name = p; while (*p && !isspace((int) *p)) p++; if (!*p) { error: REDEBUG("Format string should be '<data> <offset> <type>' e.g. '&Class 1 integer'"); nothing: *out = '\0'; return -1; } while (isspace((int) *p)) *(p++) = '\0'; if (!*p) GOTO_ERROR; data_size = p; while (*p && !isspace((int) *p)) p++; if (!*p) GOTO_ERROR; while (isspace((int) *p)) *(p++) = '\0'; if (!*p) GOTO_ERROR; data_type = p; while (*p && !isspace((int) *p)) p++; if (*p) GOTO_ERROR; /* anything after the type is an error */ /* * Attribute reference */ if (*data_name == '&') { if (radius_get_vp(&vp, request, data_name) < 0) goto nothing; if ((vp->da->type != PW_TYPE_OCTETS) && (vp->da->type != PW_TYPE_STRING)) { REDEBUG("unpack requires the input attribute to be 'string' or 'octets'"); goto nothing; } input = vp->vp_octets; input_len = vp->vp_length; } else if ((data_name[0] == '0') && (data_name[1] == 'x')) { /* * Hex data. */ len = strlen(data_name + 2); if ((len & 0x01) != 0) { RDEBUG("Invalid hex string in '%s'", data_name); goto nothing; } input = blob; input_len = fr_hex2bin(blob, sizeof(blob), data_name + 2, len); vp = NULL; } else { GOTO_ERROR; } offset = (int) strtoul(data_size, &p, 10); if (*p) { REDEBUG("unpack requires a decimal number, not '%s'", data_size); goto nothing; } if ((size_t) offset >= input_len) { REDEBUG("Offset is larget then the input."); goto nothing; } /* * Allow for string(4) or octets(4), which says "take 4 * bytes from the thing. */ p = strchr(data_type, '('); if (p) { char *end; unsigned long to_copy; *p = '\0'; to_copy = strtoul(p + 1, &end, 10); if (to_copy > input_len) { REDEBUG("Invalid length at '%s'", p + 1); goto nothing; } if ((end[0] != ')') || (end[1] != '\0')) { REDEBUG("Invalid ending at '%s'", end); goto nothing; } type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID); if (type == PW_TYPE_INVALID) { REDEBUG("Invalid data type '%s'", data_type); goto nothing; } if ((type != PW_TYPE_OCTETS) && (type != PW_TYPE_STRING)) { REDEBUG("Cannot take substring of data type '%s'", data_type); goto nothing; } if (input_len < (offset + to_copy)) { REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name); goto nothing; } /* * Just copy the string over. */ if (type == PW_TYPE_STRING) { if (outlen <= to_copy) { REDEBUG("Insufficient buffer space to unpack data"); goto nothing; } memcpy(out, input + offset, to_copy); out[to_copy] = '\0'; return to_copy; } /* * We hex encode octets. */ if (outlen <= (to_copy * 2)) { REDEBUG("Insufficient buffer space to unpack data"); goto nothing; } return fr_bin2hex(out, input + offset, to_copy); } type = fr_str2int(dict_attr_types, data_type, PW_TYPE_INVALID); if (type == PW_TYPE_INVALID) { REDEBUG("Invalid data type '%s'", data_type); goto nothing; } /* * Output must be a non-zero limited size. */ if ((dict_attr_sizes[type][0] == 0) || (dict_attr_sizes[type][0] != dict_attr_sizes[type][1])) { REDEBUG("unpack requires fixed-size output type, not '%s'", data_type); goto nothing; } if (input_len < (offset + dict_attr_sizes[type][0])) { REDEBUG("Insufficient data to unpack '%s' from '%s'", data_type, data_name); goto nothing; } da = dict_attrbyvalue(PW_CAST_BASE + type, 0); if (!da) { REDEBUG("Cannot decode type '%s'", data_type); goto nothing; } cast = fr_pair_afrom_da(request, da); if (!cast) goto nothing; memcpy(&(cast->data), input + offset, dict_attr_sizes[type][0]); cast->vp_length = dict_attr_sizes[type][0]; /* * Hacks */ switch (type) { case PW_TYPE_SIGNED: case PW_TYPE_INTEGER: case PW_TYPE_DATE: cast->vp_integer = ntohl(cast->vp_integer); break; case PW_TYPE_SHORT: cast->vp_short = ((input[offset] << 8) | input[offset + 1]); break; case PW_TYPE_INTEGER64: cast->vp_integer64 = ntohll(cast->vp_integer64); break; default: break; } len = vp_prints_value(out, outlen, cast, 0); talloc_free(cast); if (is_truncated(len, outlen)) { REDEBUG("Insufficient buffer space to unpack data"); goto nothing; } return len; }
/* * Convert diameter attributes to our VALUE_PAIR's */ static VALUE_PAIR *diameter2vp(REQUEST *request, REQUEST *fake, SSL *ssl, uint8_t const *data, size_t data_len) { uint32_t attr; uint32_t vendor; uint32_t length; size_t offset; size_t size; size_t data_left = data_len; VALUE_PAIR *first = NULL; VALUE_PAIR *vp; RADIUS_PACKET *packet = fake->packet; /* FIXME: api issues */ vp_cursor_t out; fr_cursor_init(&out, &first); while (data_left > 0) { rad_assert(data_left <= data_len); memcpy(&attr, data, sizeof(attr)); data += 4; attr = ntohl(attr); vendor = 0; memcpy(&length, data, sizeof(length)); data += 4; length = ntohl(length); /* * A "vendor" flag, with a vendor ID of zero, * is equivalent to no vendor. This is stupid. */ offset = 8; if ((length & ((uint32_t)1 << 31)) != 0) { memcpy(&vendor, data, sizeof(vendor)); vendor = ntohl(vendor); data += 4; /* skip the vendor field, it's zero */ offset += 4; /* offset to value field */ if (attr > 65535) goto next_attr; if (vendor > FR_MAX_VENDOR) goto next_attr; } /* * FIXME: Handle the M bit. For now, we assume that * some other module takes care of any attribute * with the M bit set. */ /* * Get the length. */ length &= 0x00ffffff; /* * Get the size of the value portion of the * attribute. */ size = length - offset; /* * Vendor attributes can be larger than 255. * Normal attributes cannot be. */ if ((attr > 255) && (vendor == 0)) { RWDEBUG2("Skipping Diameter attribute %u", attr); goto next_attr; } /* * EAP-Message AVPs can be larger than 253 octets. * * For now, we rely on the main decoder in * src/lib/radius to decode data into VPs. This * means putting the data into a RADIUS attribute * format. It also means that we can't handle * "extended" attributes in the Diameter space. Oh well... */ if ((size > 253) && !((vendor == 0) && (attr == PW_EAP_MESSAGE))) { RWDEBUG2("diameter2vp skipping long attribute %u", attr); goto next_attr; } /* * RADIUS VSAs are handled as Diameter attributes * with Vendor-Id == 0, and the VSA data packed * into the "String" field as per normal. * * EXCEPT for the MS-CHAP attributes. */ if ((vendor == 0) && (attr == PW_VENDOR_SPECIFIC)) { ssize_t decoded; uint8_t buffer[256]; buffer[0] = PW_VENDOR_SPECIFIC; buffer[1] = size + 2; memcpy(buffer + 2, data, size); vp = NULL; decoded = rad_attr2vp(packet, NULL, NULL, NULL, buffer, size + 2, &vp); if (decoded < 0) { REDEBUG2("diameter2vp failed decoding attr: %s", fr_strerror()); goto do_octets; } if ((size_t) decoded != size + 2) { REDEBUG2("diameter2vp failed to entirely decode VSA"); fr_pair_list_free(&vp); goto do_octets; } fr_cursor_merge(&out, vp); goto next_attr; } /* * Create it. If this fails, it's because we're OOM. */ do_octets: vp = fr_pair_afrom_num(packet, attr, vendor); if (!vp) { RDEBUG2("Failure in creating VP"); fr_pair_list_free(&first); return NULL; } /* * If it's a type from our dictionary, then * we need to put the data in a relevant place. * * @todo: Export the lib/radius.c decoder, and use it here! */ switch (vp->da->type) { case PW_TYPE_INTEGER: case PW_TYPE_DATE: if (size != vp->vp_length) { DICT_ATTR const *da; /* * Bad format. Create a "raw" * attribute. */ raw: if (vp) fr_pair_list_free(&vp); da = dict_unknown_afrom_fields(packet, attr, vendor); if (!da) return NULL; vp = fr_pair_afrom_da(packet, da); if (!vp) return NULL; fr_pair_value_memcpy(vp, data, size); break; } memcpy(&vp->vp_integer, data, vp->vp_length); /* * Stored in host byte order: change it. */ vp->vp_integer = ntohl(vp->vp_integer); break; case PW_TYPE_INTEGER64: if (size != vp->vp_length) goto raw; memcpy(&vp->vp_integer64, data, vp->vp_length); /* * Stored in host byte order: change it. */ vp->vp_integer64 = ntohll(vp->vp_integer64); break; case PW_TYPE_IPV4_ADDR: if (size != vp->vp_length) { RDEBUG2("Invalid length attribute %d", attr); fr_pair_list_free(&first); fr_pair_list_free(&vp); return NULL; } memcpy(&vp->vp_ipaddr, data, vp->vp_length); /* * Stored in network byte order: don't change it. */ break; case PW_TYPE_BYTE: if (size != vp->vp_length) goto raw; vp->vp_byte = data[0]; break; case PW_TYPE_SHORT: if (size != vp->vp_length) goto raw; vp->vp_short = (data[0] * 256) + data[1]; break; case PW_TYPE_SIGNED: if (size != vp->vp_length) goto raw; memcpy(&vp->vp_signed, data, vp->vp_length); vp->vp_signed = ntohl(vp->vp_signed); break; case PW_TYPE_IPV6_ADDR: if (size != vp->vp_length) goto raw; memcpy(&vp->vp_ipv6addr, data, vp->vp_length); break; case PW_TYPE_IPV6_PREFIX: if (size != vp->vp_length) goto raw; memcpy(vp->vp_ipv6prefix, data, vp->vp_length); break; case PW_TYPE_STRING: fr_pair_value_bstrncpy(vp, data, size); vp->vp_length = strlen(vp->vp_strvalue); /* embedded zeros are NOT allowed */ break; /* * Copy it over verbatim. */ case PW_TYPE_OCTETS: default: fr_pair_value_memcpy(vp, data, size); break; } /* * Ensure that the client is using the * correct challenge. This weirdness is * to protect against against replay * attacks, where anyone observing the * CHAP exchange could pose as that user, * by simply choosing to use the same * challenge. * * By using a challenge based on * information from the current session, * we can guarantee that the client is * not *choosing* a challenge. * * We're a little forgiving in that we * have loose checks on the length, and * we do NOT check the Id (first octet of * the response to the challenge) * * But if the client gets the challenge correct, * we're not too worried about the Id. */ if (((vp->da->vendor == 0) && (vp->da->attr == PW_CHAP_CHALLENGE)) || ((vp->da->vendor == VENDORPEC_MICROSOFT) && (vp->da->attr == PW_MSCHAP_CHALLENGE))) { uint8_t challenge[16]; if ((vp->vp_length < 8) || (vp->vp_length > 16)) { RDEBUG("Tunneled challenge has invalid length"); fr_pair_list_free(&first); fr_pair_list_free(&vp); return NULL; } eapttls_gen_challenge(ssl, challenge, sizeof(challenge)); if (memcmp(challenge, vp->vp_octets, vp->vp_length) != 0) { RDEBUG("Tunneled challenge is incorrect"); fr_pair_list_free(&first); fr_pair_list_free(&vp); return NULL; } } /* * Update the list. */ fr_cursor_insert(&out, vp); next_attr: /* * Catch non-aligned attributes. */ if (data_left == length) break; /* * The length does NOT include the padding, so * we've got to account for it here by rounding up * to the nearest 4-byte boundary. */ length += 0x03; length &= ~0x03; rad_assert(data_left >= length); data_left -= length; data += length - offset; /* already updated */ } /* * We got this far. It looks OK. */ return first; }
/* * 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; }
/* * 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; }
/** 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; }
static rlm_rcode_t CC_HINT(nonnull) mod_post_auth(void *instance, UNUSED void *thread, REQUEST *request) { #ifdef WITH_DHCP int rcode; VALUE_PAIR *vp; rlm_soh_t const *inst = instance; if (!inst->dhcp) return RLM_MODULE_NOOP; vp = fr_pair_find_by_da(request->packet->vps, attr_dhcp_vendor, TAG_ANY); if (vp) { /* * vendor-specific options contain * * vendor opt 220/0xdc - SoH payload, or null byte to probe, or string * "NAP" to indicate server-side support for SoH in OFFERs * * vendor opt 222/0xde - SoH correlation ID as utf-16 string, yuck... */ uint8_t vopt, vlen; uint8_t const *data; data = vp->vp_octets; while (data < vp->vp_octets + vp->vp_length) { vopt = *data++; vlen = *data++; switch (vopt) { case 220: if (vlen <= 1) { uint8_t *p; RDEBUG2("SoH adding NAP marker to DHCP reply"); /* client probe; send "NAP" in the reply */ vp = fr_pair_afrom_da(request->reply, attr_dhcp_vendor); p = talloc_array(vp, uint8_t, 5); p[0] = 220; p[1] = 3; p[4] = 'N'; p[3] = 'A'; p[2] = 'P'; fr_pair_value_memsteal(vp, p); fr_pair_add(&request->reply->vps, vp); } else { RDEBUG2("SoH decoding NAP from DHCP request"); /* SoH payload */ rcode = soh_verify(request, data, vlen); if (rcode < 0) { return RLM_MODULE_FAIL; } } break; default: /* nothing to do */ break; } data += vlen; } return RLM_MODULE_OK; } #endif return RLM_MODULE_NOOP; }
/** Process the Peer's response and advantage the state machine * */ static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session = talloc_get_type_abort(eap_session->opaque, eap_aka_session_t); fr_sim_decode_ctx_t ctx = { .keys = &eap_aka_session->keys, }; VALUE_PAIR *vp, *vps, *subtype_vp; fr_cursor_t cursor; eap_aka_subtype_t subtype; int ret; /* * RFC 4187 says we ignore the contents of the * next packet after we send our success notification * and always send a success. */ if (eap_aka_session->state == EAP_AKA_SERVER_SUCCESS_NOTIFICATION) { eap_aka_state_enter(eap_session, EAP_AKA_SERVER_SUCCESS); return RLM_MODULE_HANDLED; } /* vps is the data from the client */ vps = request->packet->vps; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_tail(&cursor); ret = fr_sim_decode(eap_session->request, &cursor, dict_eap_aka, eap_session->this_round->response->type.data, eap_session->this_round->response->type.length, &ctx); /* * RFC 4187 says we *MUST* notify, not just * send an EAP-Failure in this case where * we cannot decode an EAP-AKA packet. */ if (ret < 0) { RPEDEBUG2("Failed decoding EAP-AKA attributes"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } vp = fr_cursor_current(&cursor); if (vp && RDEBUG_ENABLED2) { RDEBUG2("EAP-AKA decoded attributes"); log_request_pair_list(L_DBG_LVL_2, request, vp, NULL); } subtype_vp = fr_pair_find_by_da(vps, attr_eap_aka_subtype, TAG_ANY); if (!subtype_vp) { REDEBUG("Missing EAP-AKA-Subtype"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } subtype = subtype_vp->vp_uint16; switch (eap_aka_session->state) { /* * Here we expected the peer to send * us identities for validation. */ case EAP_AKA_SERVER_IDENTITY: switch (subtype) { case EAP_AKA_IDENTITY: if (process_eap_aka_identity(eap_session, vps) == 0) return RLM_MODULE_HANDLED; eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure * * This can happen in the case of a conservative * peer, where it refuses to provide the permanent * identity. */ case EAP_AKA_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-AKA Peer rejected AKA-Identity (%s) with client-error message but " "has not supplied a client error code", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>")); } else { REDEBUG("Client rejected AKA-Identity (%s) with error: %s (%i)", fr_int2str(sim_id_request_table, eap_aka_session->id_req, "<INVALID>"), fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_AKA_NOTIFICATION: notification: { char buff[20]; vp = fr_pair_afrom_da(vps, attr_eap_aka_notification); if (!vp) { REDEBUG2("Received AKA-Notification with no notification code"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Case 3 where we're allowed to send an EAP-Failure */ if (!(vp->vp_uint16 & 0x8000)) { REDEBUG2("AKA-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * ...if it's not a failure, then re-enter the * current state. */ REDEBUG2("Got AKA-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_aka_state_enter(eap_session, eap_aka_session->state); return RLM_MODULE_HANDLED; } default: unexpected_subtype: /* * RFC 4187 says we *MUST* notify, not just * send an EAP-Failure in this case. */ REDEBUG("Unexpected subtype %pV", &subtype_vp->data); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Process the response to our previous challenge. */ case EAP_AKA_SERVER_CHALLENGE: switch (subtype) { case EAP_AKA_CHALLENGE: switch (process_eap_aka_challenge(eap_session, vps)) { case 1: return RLM_MODULE_HANDLED; case 0: return RLM_MODULE_OK; case -1: eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } case EAP_AKA_SYNCHRONIZATION_FAILURE: REDEBUG("EAP-AKA Peer synchronization failure"); /* We can't handle these yet */ eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure */ case EAP_AKA_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(vps, attr_eap_aka_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-AKA Peer rejected AKA-Challenge with client-error message but " "has not supplied a client error code"); } else { REDEBUG("Client rejected AKA-Challenge with error: %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * Case 2 where we're allowed to send an EAP-Failure */ case EAP_AKA_AUTHENTICATION_REJECT: REDEBUG("EAP-AKA Peer Rejected AUTN"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; case EAP_AKA_NOTIFICATION: goto notification; default: goto unexpected_subtype; } /* * Peer acked our failure */ case EAP_AKA_SERVER_FAILURE_NOTIFICATION: switch (subtype) { case EAP_AKA_NOTIFICATION: RDEBUG2("AKA-Notification ACKed, sending EAP-Failure"); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE); return RLM_MODULE_REJECT; default: goto unexpected_subtype; } /* * Something bad happened... */ default: rad_assert(0); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /** Initiate the EAP-SIM session by starting the state machine * */ static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_aka_session_t *eap_aka_session; rlm_eap_aka_t *inst = instance; fr_sim_id_type_t type; fr_sim_method_hint_t method; MEM(eap_aka_session = talloc_zero(eap_session, eap_aka_session_t)); eap_session->opaque = eap_aka_session; /* * Set default configuration, we may allow these * to be toggled by attributes later. */ eap_aka_session->request_identity = inst->request_identity; eap_aka_session->send_result_ind = inst->protected_success; eap_aka_session->id_req = SIM_NO_ID_REQ; /* Set the default */ /* * This value doesn't have be strong, but it is * good if it is different now and then. */ eap_aka_session->aka_id = (fr_rand() & 0xff); /* * Process the identity that we received in the * EAP-Identity-Response and use it to determine * the initial request we send to the Supplicant. */ if (fr_sim_id_type(&type, &method, eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { RPWDEBUG2("Failed parsing identity, continuing anyway"); } /* * Unless AKA-Prime is explicitly disabled, * use it... It has stronger keying, and * binds authentication to the network. */ switch (eap_session->type) { case FR_EAP_AKA_PRIME: default: RDEBUG2("New EAP-AKA' session"); eap_aka_session->type = FR_EAP_AKA_PRIME; eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA_PRIME_WITH_CK_PRIME_IK_PRIME; eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha256(); eap_aka_session->keys.network = (uint8_t *)talloc_bstrndup(eap_aka_session, inst->network_name, talloc_array_length(inst->network_name) - 1); eap_aka_session->keys.network_len = talloc_array_length(eap_aka_session->keys.network) - 1; switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're " "attempting EAP-AKA'", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_AKA_PRIME: case SIM_METHOD_HINT_UNKNOWN: break; } break; case FR_EAP_AKA: RDEBUG2("New EAP-AKA session"); eap_aka_session->type = FR_EAP_AKA; eap_aka_session->kdf = FR_EAP_AKA_KDF_VALUE_EAP_AKA; /* Not actually sent */ eap_aka_session->checkcode_md = eap_aka_session->mac_md = EVP_sha1(); eap_aka_session->send_at_bidding = true; switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're " "attempting EAP-AKA", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_AKA: case SIM_METHOD_HINT_UNKNOWN: break; } break; } eap_session->process = mod_process; /* * Admin wants us to always request an identity * initially. The RFC says this is also the * better way to operate, as the supplicant * can 'decorate' the identity in the identity * response. */ if (inst->request_identity) { request_id: /* * We always start by requesting * any ID initially as we can * always negotiate down. */ eap_aka_session->id_req = SIM_ANY_ID_REQ; eap_aka_state_enter(eap_session, EAP_AKA_SERVER_IDENTITY); return RLM_MODULE_HANDLED; } /* * Figure out what type of identity we have * and use it to determine the initial * request we send. */ switch (type) { /* * If there's no valid tag on the identity * then it's probably been decorated by the * supplicant. * * Request the unmolested identity */ case SIM_ID_TYPE_UNKNOWN: RWDEBUG("Identity format unknown, sending Identity request"); goto request_id; /* * These types need to be transformed into something * usable before we can do anything. */ case SIM_ID_TYPE_PSEUDONYM: case SIM_ID_TYPE_FASTAUTH: /* * Permanent ID means we can just send the challenge */ case SIM_ID_TYPE_PERMANENT: eap_aka_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; MEM(eap_aka_session->keys.identity = talloc_memdup(eap_aka_session, eap_session->identity, eap_aka_session->keys.identity_len)); eap_aka_state_enter(eap_session, EAP_AKA_SERVER_CHALLENGE); return RLM_MODULE_HANDLED; } return RLM_MODULE_HANDLED; }
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; }
/** Convert group membership information into attributes * * @param[in] inst rlm_ldap configuration. * @param[in] request Current request. * @param[in,out] pconn to use. May change as this function calls functions which auto re-connect. * @param[in] entry retrieved by rlm_ldap_find_user or rlm_ldap_search. * @param[in] attr membership attribute to look for in the entry. * @return One of the RLM_MODULE_* values. */ rlm_rcode_t rlm_ldap_cacheable_userobj(rlm_ldap_t const *inst, REQUEST *request, ldap_handle_t **pconn, LDAPMessage *entry, char const *attr) { rlm_rcode_t rcode = RLM_MODULE_OK; struct berval **values; char *group_name[LDAP_MAX_CACHEABLE + 1]; char **name_p = group_name; char *group_dn[LDAP_MAX_CACHEABLE + 1]; char **dn_p; char *name; VALUE_PAIR *vp, **list, *groups = NULL; TALLOC_CTX *list_ctx, *value_ctx; vp_cursor_t list_cursor, groups_cursor; int is_dn, i, count; rad_assert(entry); rad_assert(attr); /* * Parse the membership information we got in the initial user query. */ values = ldap_get_values_len((*pconn)->handle, entry, attr); if (!values) { RDEBUG2("No cacheable group memberships found in user object"); return RLM_MODULE_OK; } count = ldap_count_values_len(values); list = radius_list(request, PAIR_LIST_CONTROL); list_ctx = radius_list_ctx(request, PAIR_LIST_CONTROL); /* * Simplifies freeing temporary values */ value_ctx = talloc_new(request); /* * Temporary list to hold new group VPs, will be merged * once all group info has been gathered/resolved * successfully. */ fr_cursor_init(&groups_cursor, &groups); for (i = 0; (i < LDAP_MAX_CACHEABLE) && (i < count); i++) { is_dn = rlm_ldap_is_dn(values[i]->bv_val, values[i]->bv_len); if (inst->cacheable_group_dn) { /* * The easy case, we're caching DNs and we got a DN. */ if (is_dn) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&groups_cursor, vp); /* * We were told to cache DNs but we got a name, we now need to resolve * this to a DN. Store all the group names in an array so we can do one query. */ } else { *name_p++ = rlm_ldap_berval_to_string(value_ctx, values[i]); } } if (inst->cacheable_group_name) { /* * The easy case, we're caching names and we got a name. */ if (!is_dn) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, values[i]->bv_val, values[i]->bv_len); fr_cursor_insert(&groups_cursor, vp); /* * We were told to cache names but we got a DN, we now need to resolve * this to a name. * Only Active Directory supports filtering on DN, so we have to search * for each individual group. */ } else { char *dn; dn = rlm_ldap_berval_to_string(value_ctx, values[i]); rcode = rlm_ldap_group_dn2name(inst, request, pconn, dn, &name); talloc_free(dn); if (rcode != RLM_MODULE_OK) { ldap_value_free_len(values); talloc_free(value_ctx); fr_pair_list_free(&groups); return rcode; } MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_bstrncpy(vp, name, talloc_array_length(name) - 1); fr_cursor_insert(&groups_cursor, vp); talloc_free(name); } } } *name_p = NULL; rcode = rlm_ldap_group_name2dn(inst, request, pconn, group_name, group_dn, sizeof(group_dn)); ldap_value_free_len(values); talloc_free(value_ctx); if (rcode != RLM_MODULE_OK) return rcode; fr_cursor_init(&list_cursor, list); RDEBUG("Adding cacheable user object memberships"); RINDENT(); if (RDEBUG_ENABLED) { for (vp = fr_cursor_first(&groups_cursor); vp; vp = fr_cursor_next(&groups_cursor)) { RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue); } } fr_cursor_merge(&list_cursor, groups); for (dn_p = group_dn; *dn_p; dn_p++) { MEM(vp = fr_pair_afrom_da(list_ctx, inst->cache_da)); fr_pair_value_strcpy(vp, *dn_p); fr_cursor_insert(&list_cursor, vp); RDEBUG("&control:%s += \"%s\"", inst->cache_da->name, vp->vp_strvalue); ldap_memfree(*dn_p); } REXDENT(); return rcode; }
/** 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; }
/** Authenticate a previously sent challenge * */ static rlm_rcode_t mod_process(UNUSED void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); fr_sim_decode_ctx_t ctx = { .keys = &eap_sim_session->keys, }; VALUE_PAIR *subtype_vp, *from_peer, *vp; fr_cursor_t cursor; eap_sim_subtype_t subtype; int ret; /* * VPS is the data from the client */ from_peer = eap_session->request->packet->vps; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_tail(&cursor); ret = fr_sim_decode(eap_session->request, &cursor, dict_eap_sim, eap_session->this_round->response->type.data, eap_session->this_round->response->type.length, &ctx); /* * RFC 4186 says we *MUST* notify, not just * send an EAP-Failure in this case where * we cannot decode an EAP-AKA packet. */ if (ret < 0) { RPEDEBUG2("Failed decoding EAP-SIM attributes"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } vp = fr_cursor_current(&cursor); if (vp && RDEBUG_ENABLED2) { RDEBUG2("Decoded EAP-SIM attributes"); log_request_pair_list(L_DBG_LVL_2, request, vp, NULL); } subtype_vp = fr_pair_find_by_da(from_peer, attr_eap_sim_subtype, TAG_ANY); if (!subtype_vp) { REDEBUG("Missing EAP-SIM-Subtype"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } subtype = subtype_vp->vp_uint16; switch (eap_sim_session->state) { /* * Response to our advertised versions and request for an ID * This is very similar to Identity negotiation in EAP-AKA['] */ case EAP_SIM_SERVER_START: switch (subtype) { case EAP_SIM_START: if (process_eap_sim_start(eap_session, from_peer) == 0) return RLM_MODULE_HANDLED; eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ /* * Case 1 where we're allowed to send an EAP-Failure * * This can happen in the case of a conservative * peer, where it refuses to provide the permanent * identity. */ case EAP_SIM_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-SIM Peer rejected SIM-Start (%s) with client-error message but " "has not supplied a client error code", fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>")); } else { REDEBUG("Client rejected SIM-Start (%s) with error: %s (%i)", fr_int2str(sim_id_request_table, eap_sim_session->id_req, "<INVALID>"), fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_SIM_NOTIFICATION: notification: { char buff[20]; vp = fr_pair_afrom_da(from_peer, attr_eap_sim_notification); if (!vp) { REDEBUG2("Received SIM-Notification with no notification code"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } /* * Case 2 where we're allowed to send an EAP-Failure */ if (!(vp->vp_uint16 & 0x8000)) { REDEBUG2("SIM-Notification %s (%i) indicates failure", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } /* * ...if it's not a failure, then re-enter the * current state. */ REDEBUG2("Got SIM-Notification %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); eap_sim_state_enter(eap_session, eap_sim_session->state); return RLM_MODULE_HANDLED; default: unexpected_subtype: /* * RFC 4186 says we *MUST* notify, not just * send an EAP-Failure in this case. */ REDEBUG("Unexpected subtype %pV", &subtype_vp->data); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /* * Process the response to our previous challenge. */ case EAP_SIM_SERVER_CHALLENGE: switch (subtype) { /* * A response to our EAP-Sim/Request/Challenge! */ case EAP_SIM_CHALLENGE: switch (process_eap_sim_challenge(eap_session, from_peer)) { case 1: return RLM_MODULE_HANDLED; case 0: return RLM_MODULE_OK; case -1: eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } case EAP_SIM_CLIENT_ERROR: { char buff[20]; vp = fr_pair_find_by_da(from_peer, attr_eap_sim_client_error_code, TAG_ANY); if (!vp) { REDEBUG("EAP-SIM Peer rejected SIM-Challenge with client-error message but " "has not supplied a client error code"); } else { REDEBUG("Client rejected SIM-Challenge with error: %s (%i)", fr_pair_value_enum(vp, buff), vp->vp_uint16); } eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; } case EAP_SIM_NOTIFICATION: goto notification; default: goto unexpected_subtype; } /* * Peer acked our failure */ case EAP_SIM_SERVER_FAILURE_NOTIFICATION: switch (subtype) { case EAP_SIM_NOTIFICATION: RDEBUG2("SIM-Notification ACKed, sending EAP-Failure"); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE); return RLM_MODULE_REJECT; default: goto unexpected_subtype; } /* * Something bad happened... */ default: rad_assert(0); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_FAILURE_NOTIFICATION); return RLM_MODULE_HANDLED; /* We need to process more packets */ } } /* * Initiate the EAP-SIM session by starting the state machine * and initiating the state. */ static rlm_rcode_t mod_session_init(void *instance, eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session; rlm_eap_sim_t *inst = instance; fr_sim_id_type_t type; fr_sim_method_hint_t method; MEM(eap_sim_session = talloc_zero(eap_session, eap_sim_session_t)); eap_session->opaque = eap_sim_session; /* * Set default configuration, we may allow these * to be toggled by attributes later. */ eap_sim_session->send_result_ind = inst->protected_success; eap_sim_session->id_req = SIM_ANY_ID_REQ; /* Set the default */ /* * This value doesn't have be strong, but it is * good if it is different now and then. */ eap_sim_session->sim_id = (fr_rand() & 0xff); /* * Save the keying material, because it could change on a subsequent retrieval. */ RDEBUG2("New EAP-SIM session"); /* * Process the identity that we received in the * EAP-Identity-Response and use it to determine * the initial request we send to the Supplicant. */ if (fr_sim_id_type(&type, &method, eap_session->identity, talloc_array_length(eap_session->identity) - 1) < 0) { RPWDEBUG2("Failed parsing identity, continuing anyway"); } switch (method) { default: RWDEBUG("EAP-Identity-Response hints that EAP-%s should be started, but we're attempting EAP-SIM", fr_int2str(sim_id_method_hint_table, method, "<INVALID>")); break; case SIM_METHOD_HINT_SIM: case SIM_METHOD_HINT_UNKNOWN: break; } eap_session->process = mod_process; /* * Figure out what type of identity we have * and use it to determine the initial * request we send. */ switch (type) { /* * These types need to be transformed into something * usable before we can do anything. */ case SIM_ID_TYPE_UNKNOWN: case SIM_ID_TYPE_PSEUDONYM: case SIM_ID_TYPE_FASTAUTH: /* * Permanent ID means we can just send the challenge */ case SIM_ID_TYPE_PERMANENT: eap_sim_session->keys.identity_len = talloc_array_length(eap_session->identity) - 1; MEM(eap_sim_session->keys.identity = talloc_memdup(eap_sim_session, eap_session->identity, eap_sim_session->keys.identity_len)); eap_sim_state_enter(eap_session, EAP_SIM_SERVER_START); return RLM_MODULE_HANDLED; } return RLM_MODULE_HANDLED; }
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; }
/* * 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; }
/** Send NONCE_S and re-key * */ static int eap_sim_send_reauthentication(eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); VALUE_PAIR **to_peer, *vp, *mk, *counter; RADIUS_PACKET *packet; rad_assert(eap_session->request != NULL); rad_assert(eap_session->request->reply); /* * to_peer is the data to the client */ packet = eap_session->request->reply; to_peer = &packet->vps; /* * If any of the session resumption inputs (on our side) * are missing or malformed, return an error code * and the state machine will jump to the start state. */ mk = fr_pair_find_by_da(request->control, attr_eap_sim_mk, TAG_ANY); if (!mk) { RWDEBUG2("Missing &control:EAP-SIM-MK, skipping session resumption"); return -1; } if (mk->vp_length != SIM_MK_SIZE) { RWDEBUG("&control:EAP-SIM-MK has incorrect length, expected %u bytes got %zu bytes", SIM_MK_SIZE, mk->vp_length); return -1; } counter = fr_pair_find_by_da(request->control, attr_eap_sim_counter, TAG_ANY); if (!counter) { RWDEBUG2("Missing &control:EAP-SIM-Counter, skipping session resumption"); return -1; } /* * All set, calculate keys! */ fr_sim_crypto_keys_init_kdf_0_reauth(&eap_sim_session->keys, mk->vp_octets, counter->vp_uint16); fr_sim_crypto_kdf_0_reauth(&eap_sim_session->keys); if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_sim_session->keys); RDEBUG2("Sending SIM-Reauthentication"); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* * Set subtype to challenge. */ vp = fr_pair_afrom_da(packet, attr_eap_sim_subtype); vp->vp_uint16 = EAP_SIM_REAUTH; fr_pair_replace(to_peer, vp); /* * Add nonce_s */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_sim_nonce_s)); fr_pair_value_memcpy(vp, eap_sim_session->keys.reauth.nonce_s, sizeof(eap_sim_session->keys.reauth.nonce_s)); fr_pair_replace(to_peer, vp); /* * Indicate we'd like to use protected success messages */ if (eap_sim_session->send_result_ind) { MEM(vp = fr_pair_afrom_da(packet, attr_eap_sim_result_ind)); vp->vp_bool = true; fr_pair_replace(to_peer, 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(to_peer, vp); /* * We've sent the challenge so the peer should now be able * to accept encrypted attributes. */ eap_sim_session->allow_encrypted = true; /* * Encode the packet */ if (eap_sim_compose(eap_session, NULL, 0) < 0) { fr_pair_list_free(&packet->vps); return -1; } return 0; }
/** Send the challenge itself * * Challenges will come from one of three places eventually: * * 1 from attributes like FR_EAP_SIM_RANDx * (these might be retrieved from a database) * * 2 from internally implemented SIM authenticators * (a simple one based upon XOR will be provided) * * 3 from some kind of SS7 interface. * * For now, they only come from attributes. * It might be that the best way to do 2/3 will be with a different * module to generate/calculate things. */ static int eap_sim_send_challenge(eap_session_t *eap_session) { REQUEST *request = eap_session->request; eap_sim_session_t *eap_sim_session = talloc_get_type_abort(eap_session->opaque, eap_sim_session_t); VALUE_PAIR **to_peer, *vp; RADIUS_PACKET *packet; fr_sim_vector_src_t src = SIM_VECTOR_SRC_AUTO; rad_assert(eap_session->request != NULL); rad_assert(eap_session->request->reply); RDEBUG2("Acquiring GSM vector(s)"); if ((fr_sim_vector_gsm_from_attrs(eap_session, request->control, 0, &eap_sim_session->keys, &src) != 0) || (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 1, &eap_sim_session->keys, &src) != 0) || (fr_sim_vector_gsm_from_attrs(eap_session, request->control, 2, &eap_sim_session->keys, &src) != 0)) { REDEBUG("Failed retrieving SIM vectors"); return RLM_MODULE_FAIL; } /* * All set, calculate keys! */ fr_sim_crypto_kdf_0_gsm(&eap_sim_session->keys); if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_sim_session->keys); RDEBUG2("Sending SIM-Challenge"); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* * to_peer is the data to the client */ packet = eap_session->request->reply; to_peer = &packet->vps; /* * Okay, we got the challenges! Put them into attributes. */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_sim_rand)); fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[0].rand, SIM_VECTOR_GSM_RAND_SIZE); fr_pair_add(to_peer, vp); MEM(vp = fr_pair_afrom_da(packet, attr_eap_sim_rand)); fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[1].rand, SIM_VECTOR_GSM_RAND_SIZE); fr_pair_add(to_peer, vp); MEM(vp = fr_pair_afrom_da(packet, attr_eap_sim_rand)); fr_pair_value_memcpy(vp, eap_sim_session->keys.gsm.vector[2].rand, SIM_VECTOR_GSM_RAND_SIZE); fr_pair_add(to_peer, vp); /* * Set subtype to challenge. */ vp = fr_pair_afrom_da(packet, attr_eap_sim_subtype); vp->vp_uint16 = EAP_SIM_CHALLENGE; fr_pair_replace(to_peer, vp); /* * Indicate we'd like to use protected success messages */ if (eap_sim_session->send_result_ind) { MEM(vp = fr_pair_afrom_da(packet, attr_eap_sim_result_ind)); vp->vp_bool = true; fr_pair_replace(to_peer, 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(to_peer, vp); /* * We've sent the challenge so the peer should now be able * to accept encrypted attributes. */ eap_sim_session->allow_encrypted = true; /* * Encode the packet */ if (eap_sim_compose(eap_session, eap_sim_session->keys.gsm.nonce_mt, sizeof(eap_sim_session->keys.gsm.nonce_mt)) < 0) { fr_pair_list_free(&packet->vps); return -1; } return 0; }
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; }
/** Send the challenge itself * * Challenges will come from one of three places eventually: * * 1 from attributes like FR_EAP_AKA_RANDx * (these might be retrieved from a database) * * 2 from internally implemented SIM authenticators * (a simple one based upon XOR will be provided) * * 3 from some kind of SS7 interface. * * For now, they only come from attributes. * It might be that the best way to do 2/3 will be with a different * module to generate/calculate things. */ static int eap_aka_send_challenge(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 **to_peer, *vp; RADIUS_PACKET *packet; fr_sim_vector_src_t src = SIM_VECTOR_SRC_AUTO; rad_assert(request); rad_assert(request->reply); /* * to_peer is the data to the client */ packet = eap_session->request->reply; to_peer = &packet->vps; RDEBUG2("Acquiring UMTS vector(s)"); /* * Toggle the AMF high bit to indicate we're doing AKA' */ if (eap_aka_session->type == FR_EAP_AKA_PRIME) { uint8_t amf_buff[2] = { 0x80, 0x00 }; /* Set the AMF separation bit high */ MEM(pair_update_control(&vp, attr_sim_amf) >= 0); fr_pair_value_memcpy(vp, amf_buff, sizeof(amf_buff)); } /* * Get vectors from attribute or generate * them using COMP128-* or Milenage. */ if (fr_sim_vector_umts_from_attrs(eap_session, request->control, &eap_aka_session->keys, &src) != 0) { REDEBUG("Failed retrieving UMTS vectors"); return RLM_MODULE_FAIL; } /* * Don't leave the AMF hanging around */ if (eap_aka_session->type == FR_EAP_AKA_PRIME) pair_delete_control(attr_sim_amf); /* * All set, calculate keys! */ switch (eap_aka_session->kdf) { case FR_EAP_AKA_KDF_VALUE_EAP_AKA_PRIME_WITH_CK_PRIME_IK_PRIME: fr_sim_crypto_kdf_1_umts(&eap_aka_session->keys); break; default: fr_sim_crypto_kdf_0_umts(&eap_aka_session->keys); break; } if (RDEBUG_ENABLED3) fr_sim_crypto_keys_log(request, &eap_aka_session->keys); RDEBUG2("Sending AKA-Challenge"); eap_session->this_round->request->code = FR_EAP_CODE_REQUEST; /* * Set the subtype to challenge */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_subtype)); vp->vp_uint16 = FR_EAP_AKA_SUBTYPE_VALUE_AKA_CHALLENGE; fr_pair_replace(to_peer, vp); /* * Indicate we'd like to use protected success messages */ if (eap_aka_session->send_result_ind) { MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_result_ind)); vp->vp_bool = true; fr_pair_replace(to_peer, vp); } /* * We support EAP-AKA' and the peer should use that * if it's able to... */ if (eap_aka_session->send_at_bidding) { MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_bidding)); vp->vp_uint16 = FR_EAP_AKA_BIDDING_VALUE_PREFER_AKA_PRIME; fr_pair_replace(to_peer, vp); } /* * Send the network name and KDF to the peer */ if (eap_aka_session->type == FR_EAP_AKA_PRIME) { if (!eap_aka_session->keys.network_len) { REDEBUG2("No network name available, can't set EAP-AKA-KDF-Input"); failure: fr_pair_list_free(&packet->vps); return -1; } MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_kdf_input)); fr_pair_value_bstrncpy(vp, eap_aka_session->keys.network, eap_aka_session->keys.network_len); fr_pair_replace(to_peer, vp); MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_kdf)); vp->vp_uint16 = eap_aka_session->kdf; fr_pair_replace(to_peer, vp); } /* * Okay, we got the challenge! Put it into an attribute. */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_rand)); fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.rand, SIM_VECTOR_UMTS_RAND_SIZE); fr_pair_replace(to_peer, vp); /* * Send the AUTN value to the client, so it can authenticate * whoever has knowledge of the Ki. */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_autn)); fr_pair_value_memcpy(vp, eap_aka_session->keys.umts.vector.autn, SIM_VECTOR_UMTS_AUTN_SIZE); fr_pair_replace(to_peer, vp); /* * need to include an AT_MAC attribute so that it will get * calculated. */ MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_mac)); fr_pair_replace(to_peer, vp); /* * If we have checkcode data, send that to the peer * for validation. */ if (eap_aka_session->checkcode_state) { ssize_t slen; slen = fr_sim_crypto_finalise_checkcode(eap_aka_session->checkcode, &eap_aka_session->checkcode_state); if (slen < 0) { RPEDEBUG("Failed calculating checkcode"); goto failure; } eap_aka_session->checkcode_len = slen; MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_checkcode)); fr_pair_value_memcpy(vp, eap_aka_session->checkcode, slen); /* * If we don't have checkcode data, then we exchanged * no identity packets, so checkcode is zero. */ } else { MEM(vp = fr_pair_afrom_da(packet, attr_eap_aka_checkcode)); eap_aka_session->checkcode_len = 0; } fr_pair_replace(to_peer, vp); /* * We've sent the challenge so the peer should now be able * to accept encrypted attributes. */ eap_aka_session->allow_encrypted = true; /* * Encode the packet */ if (eap_aka_compose(eap_session) < 0) goto failure; return 0; }