/** Compare module instances by parent and name * * The reason why we need parent, is because we could have submodules with names * that conflict with their parent. */ static int module_instance_name_cmp(void const *one, void const *two) { module_instance_t const *a = one; module_instance_t const *b = two; dl_instance_t const *dl_inst; int a_depth = 0, b_depth = 0; int ret; /* * Sort by depth, so for tree walking we start * at the shallowest node, and finish with * the deepest child. */ for (dl_inst = a->dl_inst; dl_inst; dl_inst = dl_inst->parent) a_depth++; for (dl_inst = b->dl_inst; dl_inst; dl_inst = dl_inst->parent) b_depth++; ret = (a_depth > b_depth) - (a_depth < b_depth); if (ret != 0) return ret; /* * This happens, as dl_inst is is used in * as the loop condition above. */ #ifdef __clang_analyzer__ if (!fr_cond_assert(a->dl_inst)) return +1; if (!fr_cond_assert(b->dl_inst)) return -1; #endif ret = (a->dl_inst->parent > b->dl_inst->parent) - (a->dl_inst->parent < b->dl_inst->parent); if (ret != 0) return ret; return strcmp(a->name, b->name); }
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; }
static void xlat_delay_cancel(REQUEST *request, UNUSED void *instance, UNUSED void *thread, void *rctx, fr_state_signal_t action) { if (action != FR_SIGNAL_CANCEL) return; RDEBUG2("Cancelling delay"); if (!fr_cond_assert(unlang_event_timeout_delete(request, rctx) == 0)) return; }
static ssize_t sim_decode_array(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, UNUSED size_t data_len, void *decoder_ctx) { uint8_t const *p = data, *end = p + attr_len; uint16_t actual_len; int elements, i; size_t element_len; ssize_t rcode; FR_PROTO_TRACE("Array attribute"); rad_assert(parent->flags.array); rad_assert(attr_len >= 2); /* Should have been caught earlier */ /* * Arrays with fixed length members that * are a multiple of 4 don't need an * actual_len value, as we can get the * number of elements from the attribute * length. */ if (!parent->flags.length || (parent->flags.length % 4)) { 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; } } else { actual_len = attr_len - 2; /* -2 for the reserved bytes */ } p += 2; /* * Zero length array */ if (!actual_len) return p - data; /* * Get the number of elements */ elements = sim_array_members(&element_len, actual_len, parent); if (elements < 0) return elements; for (i = 0; i < elements; i++) { rcode = sim_decode_pair_value(ctx, cursor, parent, p, element_len, end - p, decoder_ctx); if (rcode < 0) return rcode; p += rcode; if (!fr_cond_assert(p <= end)) break; } return attr_len; /* Say we consumed attr_len because it may have padding */ }
/** Remove item from parent and fixup trees * * @param[in] parent to remove child from. * @param[in] child to remove. * @return * - The item removed. * - NULL if the item wasn't set. */ CONF_ITEM *cf_remove(CONF_ITEM *parent, CONF_ITEM *child) { CONF_ITEM *found; bool in_ident1, in_ident2; if (!parent || !parent->child) return NULL; if (parent != child->parent) return NULL; for (found = fr_cursor_head(&parent->cursor); found && (child != found); found = fr_cursor_next(&parent->cursor)); if (!found) return NULL; /* * Fixup the linked list */ found = fr_cursor_remove(&parent->cursor); if (!fr_cond_assert(found == child)) return NULL; in_ident1 = (rbtree_finddata(parent->ident1, child) == child); if (in_ident1 && (!rbtree_deletebydata(parent->ident1, child))) { rad_assert(0); return NULL; } in_ident2 = (rbtree_finddata(parent->ident2, child) == child); if (in_ident2 && (!rbtree_deletebydata(parent->ident2, child))) { rad_assert(0); return NULL; } /* * Look for twins */ for (found = fr_cursor_head(&parent->cursor); found && (in_ident1 || in_ident2); found = fr_cursor_next(&parent->cursor)) { if (in_ident1 && (_cf_ident1_cmp(found, child) == 0)) { rbtree_insert(parent->ident1, child); in_ident1 = false; } if (in_ident2 && (_cf_ident2_cmp(found, child) == 0)) { rbtree_insert(parent->ident2, child); in_ident2 = false; } } return child; }
/** Called when the timeout has expired * * Marks the request as resumable, and prints the delayed delay time. * * @param[in] request The current request. * @param[in] instance This instance of the delay module. * @param[in] thread Thread specific module instance. * @param[in] ctx Scheduled end of the delay. * @param[in] fired When request processing was resumed. */ static void _delay_done(REQUEST *request, UNUSED void *instance, UNUSED void *thread, void *ctx, struct timeval *fired) { struct timeval *yielded = talloc_get_type_abort(ctx, struct timeval); RDEBUG2("Delay done"); /* * timeout should never be *before* the scheduled time, * if it is, something is very broken. */ if (!fr_cond_assert(fr_timeval_cmp(fired, yielded) >= 0)) REDEBUG("Unexpected resume time"); unlang_resumable(request); }
/** Remove the current pair * * @todo this is really inefficient and should be fixed... * * The current VP will be set to the one before the VP being removed, * this is so the commonly used check and remove loop (below) works * as expected. @code {.c} for (vp = fr_pair_cursor_init(&cursor, head); vp; vp = fr_pair_cursor_next(&cursor) { if (<condition>) { vp = fr_pair_cursor_remove(&cursor); talloc_free(vp); } } @endcode * * @param cursor to remove the current pair from. * @return * - #VALUE_PAIR we just replaced. * - NULL on error. */ VALUE_PAIR *fr_pair_cursor_remove(vp_cursor_t *cursor) { VALUE_PAIR *vp, *before; if (!fr_cond_assert(cursor->first)) return NULL; /* cursor must have been initialised */ vp = cursor->current; if (!vp) return NULL; /* * Where VP is head of the list */ if (*(cursor->first) == vp) { *(cursor->first) = vp->next; cursor->current = vp->next; cursor->next = vp->next ? vp->next->next : NULL; before = NULL; goto fixup; } /* * Where VP is not head of the list */ before = *(cursor->first); if (!before) return NULL; /* * Find the VP immediately preceding the one being removed */ while (before->next != vp) before = before->next; cursor->next = before->next = vp->next; /* close the gap */ cursor->current = before; /* current jumps back one, but this is usually desirable */ fixup: vp->next = NULL; /* limit scope of fr_pair_list_free() */ /* * Fixup cursor->found if we removed the VP it was referring to, * and point to the previous one. */ if (vp == cursor->found) cursor->found = before; /* * Fixup cursor->last if we removed the VP it was referring to */ if (vp == cursor->last) cursor->last = cursor->current; return vp; }
/** Merges multiple VALUE_PAIR into the cursor * * Add multiple VALUE_PAIR from add to cursor. * * @param cursor to insert VALUE_PAIRs with * @param add one or more VALUE_PAIRs (may be NULL, which results in noop). */ void fr_pair_cursor_merge(vp_cursor_t *cursor, VALUE_PAIR *add) { vp_cursor_t from; VALUE_PAIR *vp; if (!add) return; if (!fr_cond_assert(cursor->first)) return; /* cursor must have been initialised */ for (vp = fr_pair_cursor_init(&from, &add); vp; vp = fr_pair_cursor_next(&from)) { fr_pair_cursor_append(cursor, vp); } }
/** Get a notification that a vnode changed * * @param[in] el the event list. * @param[in] sockfd the socket which is ready to read. * @param[in] fflags from kevent. * @param[in] ctx the network socket context. */ static void fr_network_vnode_extend(UNUSED fr_event_list_t *el, int sockfd, int fflags, void *ctx) { fr_network_socket_t *s = ctx; fr_network_t *nr = s->nr; fr_cond_assert(s->fd == sockfd); DEBUG3("network vnode"); /* * Tell the IO handler that something has happened to the * file. */ s->listen->app_io->vnode(s->listen->app_io_instance, fflags); }
/** Returns the number of array members for arrays with fixed element sizes * * @param[out] out The element length. * @param[in] len the total length of the array. * @param[in] da the specifying the array type. * @return * - The number of elements in the array on success. * - < 0 on error (array length not a multiple of element size). */ static int sim_array_members(size_t *out, size_t len, fr_dict_attr_t const *da) { size_t element_len; /* * Could be an array of bytes, integers, etc. */ switch (da->type) { case FR_TYPE_OCTETS: if (da->flags.length == 0) { fr_strerror_printf("%s: Octets array must have fixed length elements", __FUNCTION__); return -1; } element_len = da->flags.length; break; default: element_len = fr_sim_attr_sizes[da->type][0]; break; } if (element_len == 1) { *out = 1; return 1; /* Fast path */ } if (!fr_cond_assert(element_len > 0)) return -1; if (element_len > len) { fr_strerror_printf("%s: Element length (%zu) > array length (%zu)", __FUNCTION__, element_len, len); return -1; } /* * Number of elements must divide exactly */ if (len % element_len) { fr_strerror_printf("%s: Expected array actual length to be multiple of %zu, got %zu", __FUNCTION__, element_len, len); return -1; } *out = element_len; return len / element_len; }
/** Insert a single VALUE_PAIR at the start of the list * * @note Will not advance cursor position to new attribute, but will set cursor * to this attribute, if it's the first one in the list. * * Insert a VALUE_PAIR at the start of the list. * * @param cursor to operate on. * @param vp to insert. */ void fr_pair_cursor_prepend(vp_cursor_t *cursor, VALUE_PAIR *vp) { if (!fr_cond_assert(cursor->first)) return; /* cursor must have been initialised */ if (!vp) return; VP_VERIFY(vp); LIST_VERIFY(*(cursor->first)); /* * Only allow one VP to by inserted at a time */ vp->next = NULL; /* * Cursor was initialised with a pointer to a NULL value_pair */ if (!*(cursor->first)) { *cursor->first = vp; cursor->current = vp; return; } /* * Append to the head of the list */ vp->next = *cursor->first; *cursor->first = vp; /* * Either current was never set, or something iterated to the * end of the attribute list. In both cases the newly inserted * VALUE_PAIR should be set as the current VALUE_PAIR. */ if (!cursor->current) cursor->current = vp; /* * If the next pointer was NULL, and the VALUE_PAIR * just added has a next pointer value, set the cursor's next * pointer to the VALUE_PAIR's next pointer. */ if (!cursor->next) cursor->next = cursor->current->next; LIST_VERIFY(*(cursor->first)); }
/** 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; }
/** Compare the first identifier of a child * * For CONF_ITEM_PAIR this is 'attr'. * For CONF_ITEM_SECTION this is 'name1'. * For CONF_ITEM_DATA this is 'type'. * * @param[in] a First CONF_ITEM to compare. * @param[in] b Second CONF_ITEM to compare. * @return * - >0 if a > b. * - <0 if a < b. * - 0 if a == b. */ static inline int _cf_ident1_cmp(void const *a, void const *b) { CONF_ITEM_TYPE type; { CONF_ITEM const *one = a; CONF_ITEM const *two = b; if (one->type > two->type) return +1; if (one->type < two->type) return -1; type = one->type; } switch (type) { case CONF_ITEM_PAIR: { CONF_PAIR const *one = a; CONF_PAIR const *two = b; return strcmp(one->attr, two->attr); } case CONF_ITEM_SECTION: { CONF_SECTION const *one = a; CONF_SECTION const *two = b; return strcmp(one->name1, two->name1); } case CONF_ITEM_DATA: { CONF_DATA const *one = a; CONF_DATA const *two = b; return strcmp(one->type, two->type); } default: if (!fr_cond_assert(0)) return 0; } }
/** Install I/O handlers for the bind operation * * @param[in] c connection to StartTLS on. * @param[in] bind_dn Identity to bind with. * @param[in] password Password to bind with. * @param[in] serverctrls Extra controls to pass to the server. * @param[in] clientctrls Extra controls to pass to libldap. * @return * - 0 on success. * - -1 on failure. */ int fr_ldap_bind_async(fr_ldap_connection_t *c, char const *bind_dn, char const *password, LDAPControl **serverctrls, LDAPControl **clientctrls) { int fd = -1; fr_ldap_bind_ctx_t *bind_ctx; fr_event_list_t *el; DEBUG2("Starting bind operation"); MEM(bind_ctx = talloc_zero(c, fr_ldap_bind_ctx_t)); bind_ctx->c = c; /* * Bind as anonymous user */ bind_ctx->bind_dn = bind_dn ? bind_dn : ""; bind_ctx->password = password; bind_ctx->serverctrls = serverctrls; bind_ctx->clientctrls = clientctrls; el = fr_connection_get_el(c->conn); if (ldap_get_option(c->handle, LDAP_OPT_DESC, &fd) == LDAP_SUCCESS) { int ret; ret = fr_event_fd_insert(bind_ctx, el, fd, NULL, _ldap_bind_io_write, _ldap_bind_io_error, bind_ctx); if (!fr_cond_assert(ret == 0)) { talloc_free(bind_ctx); return -1; } } else { _ldap_bind_io_write(el, -1, 0, bind_ctx); } return 0; }
/** Yield a request back to the interpreter from within a module * * This passes control of the request back to the unlang interpreter, setting * callbacks to execute when the request is 'signalled' asynchronously, or whatever * timer or I/O event the module was waiting for occurs. * * @note The module function which calls #unlang_module_yield should return control * of the C stack to the unlang interpreter immediately after calling #unlang_module_yield. * A common pattern is to use ``return unlang_module_yield(...)``. * * @param[in] request The current request. * @param[in] resume Called on unlang_resumable(). * @param[in] signal Called on unlang_action(). * @param[in] rctx to pass to the callbacks. * @return * - RLM_MODULE_YIELD on success. * - RLM_MODULE_FAIL (or asserts) if the current frame is not a module call or * resume frame. */ rlm_rcode_t unlang_module_yield(REQUEST *request, fr_unlang_module_resume_t resume, fr_unlang_module_signal_t signal, void *rctx) { unlang_stack_t *stack = request->stack; unlang_stack_frame_t *frame = &stack->frame[stack->depth]; unlang_resume_t *mr; rad_assert(stack->depth > 0); REQUEST_VERIFY(request); /* Check the yielded request is sane */ switch (frame->instruction->type) { case UNLANG_TYPE_MODULE: mr = unlang_resume_alloc(request, (void *)resume, (void *)signal, rctx); if (!fr_cond_assert(mr)) { return RLM_MODULE_FAIL; } return RLM_MODULE_YIELD; case UNLANG_TYPE_RESUME: mr = talloc_get_type_abort(frame->instruction, unlang_resume_t); rad_assert(mr->parent->type == UNLANG_TYPE_MODULE); /* * Re-use the current RESUME frame, but over-ride * the callbacks and context. */ mr->resume = (void *)resume; mr->signal = (void *)signal; mr->rctx = rctx; return RLM_MODULE_YIELD; default: rad_assert(0); return RLM_MODULE_FAIL; } }
/** Compare only the second identifier of a child * * For CONF_ITEM_SECTION this is 'name2'. * For CONF_ITEM_DATA this is 'name'. * * @param[in] a First CONF_ITEM to compare. * @param[in] b Second CONF_ITEM to compare. * @return * - >0 if a > b. * - <0 if a < b. * - 0 if a == b. */ static inline int cf_ident2_cmp(void const *a, void const *b) { CONF_ITEM const *ci = a; switch (ci->type) { case CONF_ITEM_PAIR: return 0; case CONF_ITEM_SECTION: { CONF_SECTION const *one = a; CONF_SECTION const *two = b; if (!two->name2 && one->name2) return +1; if (two->name2 && !one->name2) return -1; if (!two->name2 && !one->name2) return 0; return strcmp(one->name2, two->name2); } case CONF_ITEM_DATA: { CONF_DATA const *one = a; CONF_DATA const *two = b; if (!two->name && one->name) return +1; if (two->name && !one->name) return -1; if (!two->name && !one->name) return 0; return strcmp(one->name, two->name); } default: if (!fr_cond_assert(0)) return 0; } }
/** Handle authorization requests using Couchbase document data * * Attempt to fetch the document assocaited with the requested user by * using the deterministic key defined in the configuration. When a valid * document is found it will be parsed and the containing value pairs will be * injected into the request. * * @param instance The module instance. * @param thread specific data. * @param request The authorization request. * @return Operation status (#rlm_rcode_t). */ static rlm_rcode_t mod_authorize(void *instance, UNUSED void *thread, REQUEST *request) { rlm_couchbase_t const *inst = instance; /* our module instance */ rlm_couchbase_handle_t *handle = NULL; /* connection pool handle */ char buffer[MAX_KEY_SIZE]; char const *dockey; /* our document key */ lcb_error_t cb_error = LCB_SUCCESS; /* couchbase error holder */ rlm_rcode_t rcode = RLM_MODULE_OK; /* return code */ ssize_t slen; /* assert packet as not null */ rad_assert(request->packet != NULL); /* attempt to build document key */ slen = tmpl_expand(&dockey, buffer, sizeof(buffer), request, inst->user_key, NULL, NULL); if (slen < 0) return RLM_MODULE_FAIL; if ((dockey == buffer) && is_truncated((size_t)slen, sizeof(buffer))) { REDEBUG("Key too long, expected < " STRINGIFY(sizeof(buffer)) " bytes, got %zi bytes", slen); return RLM_MODULE_FAIL; } /* get handle */ handle = fr_pool_connection_get(inst->pool, request); /* check handle */ if (!handle) return RLM_MODULE_FAIL; /* set couchbase instance */ lcb_t cb_inst = handle->handle; /* set cookie */ cookie_t *cookie = handle->cookie; /* fetch document */ cb_error = couchbase_get_key(cb_inst, cookie, dockey); /* check error */ if (cb_error != LCB_SUCCESS || !cookie->jobj) { /* log error */ RERROR("failed to fetch document or parse return"); /* set return */ rcode = RLM_MODULE_FAIL; /* return */ goto finish; } /* debugging */ RDEBUG3("parsed user document == %s", json_object_to_json_string(cookie->jobj)); { TALLOC_CTX *pool = talloc_pool(request, 1024); /* We need to do lots of allocs */ fr_cursor_t maps, vlms; vp_map_t *map_head = NULL, *map; vp_list_mod_t *vlm_head = NULL, *vlm; fr_cursor_init(&maps, &map_head); /* * Convert JSON data into maps */ if ((mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_CONTROL) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REPLY) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_REQUEST) < 0) || (mod_json_object_to_map(pool, &maps, request, cookie->jobj, PAIR_LIST_STATE) < 0)) { invalid: talloc_free(pool); rcode = RLM_MODULE_INVALID; goto finish; } fr_cursor_init(&vlms, &vlm_head); /* * Convert all the maps into list modifications, * which are guaranteed to succeed. */ for (map = fr_cursor_head(&maps); map; map = fr_cursor_next(&maps)) { if (map_to_list_mod(pool, &vlm, request, map, NULL, NULL) < 0) goto invalid; fr_cursor_insert(&vlms, vlm); } if (!vlm_head) { RDEBUG2("Nothing to update"); talloc_free(pool); rcode = RLM_MODULE_NOOP; goto finish; } /* * Apply the list of modifications */ for (vlm = fr_cursor_head(&vlms); vlm; vlm = fr_cursor_next(&vlms)) { int ret; ret = map_list_mod_apply(request, vlm); /* SHOULD NOT FAIL */ if (!fr_cond_assert(ret == 0)) { talloc_free(pool); rcode = RLM_MODULE_FAIL; goto finish; } } talloc_free(pool); } finish: /* free json object */ if (cookie->jobj) { json_object_put(cookie->jobj); cookie->jobj = NULL; } /* release handle */ if (handle) fr_pool_connection_release(inst->pool, request, handle); /* return */ return rcode; }
/** Compile virtual server sections * * Called twice, once when a server with an eap-aka namespace is found, and once * when an EAP-AKA module is instantiated. * * The first time is with actions == NULL and is to compile the sections and * perform validation. * The second time is to write out pointers to the compiled sections which the * EAP-AKA module will use to execute unlang code. * */ static int mod_section_compile(eap_aka_actions_t *actions, CONF_SECTION *server_cs) { bool found = false; if (!fr_cond_assert(server_cs)) return -1; /* * Initial Identity-Response * * We then either: * - Request a new identity * - Start full authentication * - Start fast re-authentication * - Fail... */ ACTION_SECTION(actions, recv_eap_identity_response, "recv", "EAP-Identity-Response"); /* * Identity negotiation */ ACTION_SECTION(actions, send_identity_request, "send", "Identity-Request"); ACTION_SECTION(actions, recv_identity_response, "recv", "Identity-Response"); /* * Full-Authentication */ ACTION_SECTION(actions, send_challenge_request, "send", "Challenge-Request"); ACTION_SECTION(actions, recv_challenge_response, "recv", "Challenge-Response"); /* * Fast-Re-Authentication */ ACTION_SECTION(actions, send_fast_reauth_request, "send", "Fast-Reauth-Request"); ACTION_SECTION(actions, recv_fast_reauth_response, "recv", "Fast-Reauth-Response"); /* * Failures originating from the supplicant */ ACTION_SECTION(actions, recv_client_error, "recv", "Client-Error"); ACTION_SECTION(actions, recv_authentication_reject, "recv", "Authentication-Reject"); ACTION_SECTION(actions, recv_syncronization_failure, "recv", "Syncronization-Failure"); /* * Failure originating from the server */ ACTION_SECTION(actions, send_failure_notification, "send", "Failure-Notification"); ACTION_SECTION(actions, recv_failure_notification_ack, "recv", "Failure-Notification-ACK"); /* * Protected success indication */ ACTION_SECTION(actions, send_success_notification, "send", "Success-Notification"); ACTION_SECTION(actions, recv_success_notification_ack, "recv", "Success-Notification-ACK"); /* * Final EAP-Success and EAP-Failure messages */ ACTION_SECTION(actions, send_eap_success, "send", "EAP-Success"); ACTION_SECTION(actions, send_eap_failure, "send", "EAP-Failure"); /* * Fast-Reauth vectors */ ACTION_SECTION(actions, load_session, "load", "session"); ACTION_SECTION(actions, store_session, "store", "session"); ACTION_SECTION(actions, clear_session, "clear", "session"); /* * Warn if we couldn't find any actions. */ if (!found) { cf_log_warn(server_cs, "No \"eap-aka\" actions found in virtual server \"%s\"", cf_section_name2(server_cs)); } return 0; }
/** Return the next child that's of the specified type with the specified identifiers * * @param[in] parent The section we're searching in. * @param[in] prev item we found, or NULL to start from the beginning. * @param[in] type of #CONF_ITEM we're searching for. * @param[in] ident1 The first identifier. * @param[in] ident2 The second identifier. Special value CF_IDENT_ANY * can be used to match any ident2 value. * @return * - The first matching item. * - NULL if no items matched. */ static CONF_ITEM *cf_find_next(CONF_ITEM const *parent, CONF_ITEM const *prev, CONF_ITEM_TYPE type, char const *ident1, char const *ident2) { CONF_SECTION cs_find; CONF_PAIR cp_find; CONF_DATA cd_find; CONF_ITEM *find; CONF_ITEM *ci; if (!parent) return NULL; if (!prev) { if (!ident1) return cf_next(parent, NULL, type); return cf_find(parent, type, ident1, ident2); } if (!ident1) return cf_next(parent, prev, type); switch (type) { case CONF_ITEM_SECTION: memset(&cs_find, 0, sizeof(cs_find)); cs_find.item.type = CONF_ITEM_SECTION; cs_find.name1 = ident1; if (!IS_WILDCARD(ident2)) cs_find.name2 = ident2; find = (CONF_ITEM *)&cs_find; break; case CONF_ITEM_PAIR: rad_assert((ident2 == NULL) || IS_WILDCARD(ident2)); memset(&cp_find, 0, sizeof(cp_find)); cp_find.item.type = CONF_ITEM_PAIR; cp_find.attr = ident1; find = (CONF_ITEM *)&cp_find; break; case CONF_ITEM_DATA: memset(&cd_find, 0, sizeof(cd_find)); cd_find.item.type = CONF_ITEM_DATA; cd_find.type = ident1; if (!IS_WILDCARD(ident2)) cd_find.name = ident2; find = (CONF_ITEM *)&cd_find; break; default: if (!fr_cond_assert(0)) return NULL; } if (IS_WILDCARD(ident1)) { for (ci = prev->next; ci && (cf_ident2_cmp(ci, find) != 0); ci = ci->next); return ci; } if (IS_WILDCARD(ident2)) { for (ci = prev->next; ci && (_cf_ident1_cmp(ci, find) != 0); ci = ci->next) { rad_assert(ci->next != ci); } return ci; } for (ci = prev->next; ci && (_cf_ident2_cmp(ci, find) != 0); ci = ci->next); return ci; }
/** Insert a single VALUE_PAIR at the end of the list * * @note Will not advance cursor position to new attribute, but will set cursor * to this attribute, if it's the first one in the list. * * Insert a VALUE_PAIR at the end of the list. * * @param cursor to operate on. * @param vp to insert. */ void fr_pair_cursor_append(vp_cursor_t *cursor, VALUE_PAIR *vp) { VALUE_PAIR *i; if (!fr_cond_assert(cursor->first)) return; /* cursor must have been initialised */ if (!vp) return; VP_VERIFY(vp); LIST_VERIFY(*(cursor->first)); /* * Only allow one VP to by inserted at a time */ vp->next = NULL; /* * Cursor was initialised with a pointer to a NULL value_pair */ if (!*(cursor->first)) { *cursor->first = vp; cursor->current = vp; return; } /* * We don't yet know where the last VALUE_PAIR is * * Assume current is closer to the end of the list and * use that if available. */ if (!cursor->last) cursor->last = cursor->current ? cursor->current : *cursor->first; VP_VERIFY(cursor->last); /* * Wind last to the end of the list. */ if (cursor->last->next) { for (i = cursor->last; i; i = i->next) { VP_VERIFY(i); cursor->last = i; } } /* * Either current was never set, or something iterated to the * end of the attribute list. In both cases the newly inserted * VALUE_PAIR should be set as the current VALUE_PAIR. */ if (!cursor->current) cursor->current = vp; /* * Add the VALUE_PAIR to the end of the list */ cursor->last->next = vp; cursor->last = vp; /* Wind it forward a little more */ /* * If the next pointer was NULL, and the VALUE_PAIR * just added has a next pointer value, set the cursor's next * pointer to the VALUE_PAIR's next pointer. */ if (!cursor->next) cursor->next = cursor->current->next; LIST_VERIFY(*(cursor->first)); }
/** Return the next child that's of the specified type with the specified identifiers * * @param[in] parent The section we're searching in. * @param[in] type of #CONF_ITEM we're searching for. * @param[in] ident1 The first identifier. * @param[in] ident2 The second identifier. Special value CF_IDENT_ANY * can be used to match any ident2 value. * @return * - The first matching item. * - NULL if no items matched. */ static CONF_ITEM *cf_find(CONF_ITEM const *parent, CONF_ITEM_TYPE type, char const *ident1, char const *ident2) { CONF_SECTION cs_find; CONF_PAIR cp_find; CONF_DATA cd_find; CONF_ITEM *find; if (!parent) return NULL; if (!parent->child) return NULL; /* No children */ if (!ident1) return cf_next(parent, NULL, type); switch (type) { case CONF_ITEM_SECTION: memset(&cs_find, 0, sizeof(cs_find)); cs_find.item.type = CONF_ITEM_SECTION; cs_find.name1 = ident1; if (!IS_WILDCARD(ident2)) cs_find.name2 = ident2; find = (CONF_ITEM *)&cs_find; break; case CONF_ITEM_PAIR: rad_assert((ident2 == NULL) || IS_WILDCARD(ident2)); memset(&cp_find, 0, sizeof(cp_find)); cp_find.item.type = CONF_ITEM_PAIR; cp_find.attr = ident1; find = (CONF_ITEM *)&cp_find; break; case CONF_ITEM_DATA: memset(&cd_find, 0, sizeof(cd_find)); cd_find.item.type = CONF_ITEM_DATA; cd_find.type = ident1; if (!IS_WILDCARD(ident2)) cd_find.name = ident2; find = (CONF_ITEM *)&cd_find; break; default: if (!fr_cond_assert(0)) return NULL; } /* * No ident1, iterate over the child list */ if (IS_WILDCARD(ident1)) { CONF_ITEM *ci; for (ci = parent->child; ci && (cf_ident2_cmp(find, ci) != 0); ci = ci->next); return ci; } /* * No ident2, use the ident1 tree. */ if (IS_WILDCARD(ident2)) return rbtree_finddata(parent->ident1, find); /* * Both ident1 and ident2 use the ident2 tree. */ return rbtree_finddata(parent->ident2, find); }
/** 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; }
/** Send a bind request to a aserver * * @param[in] el the event occurred in. * @param[in] fd the event occurred on. * @param[in] flags from kevent. * @param[in] uctx bind_ctx containing credentials, and connection config/handle. */ static void _ldap_bind_io_write(fr_event_list_t *el, int fd, UNUSED int flags, void *uctx) { fr_ldap_bind_ctx_t *bind_ctx = talloc_get_type_abort(uctx, fr_ldap_bind_ctx_t); fr_ldap_connection_t *c = bind_ctx->c; LDAPControl *our_serverctrls[LDAP_MAX_CONTROLS]; LDAPControl *our_clientctrls[LDAP_MAX_CONTROLS]; struct timeval tv = { 0, 0 }; int ret; struct berval cred; fr_ldap_control_merge(our_serverctrls, our_clientctrls, sizeof(our_serverctrls) / sizeof(*our_serverctrls), sizeof(our_clientctrls) / sizeof(*our_clientctrls), c, bind_ctx->serverctrls, bind_ctx->clientctrls); /* * Set timeout to be 0.0, which is the magic * non-blocking value. */ (void) ldap_set_option(c->handle, LDAP_OPT_NETWORK_TIMEOUT, &tv); if (bind_ctx->password) { memcpy(&cred.bv_val, &bind_ctx->password, sizeof(cred.bv_val)); cred.bv_len = talloc_array_length(bind_ctx->password) - 1; } else { cred.bv_val = NULL; cred.bv_len = 0; } /* * Yes, confusingly named. This is the simple version * of the SASL bind function that should always be * available. */ ret = ldap_sasl_bind(c->handle, bind_ctx->bind_dn, LDAP_SASL_SIMPLE, &cred, our_serverctrls, our_clientctrls, &bind_ctx->msgid); switch (ret) { /* * If the handle was not connected, this operation * can return either LDAP_X_CONNECTING or LDAP_SUCCESS * depending on how fast the connection came up * and whether it was connectionless. */ case LDAP_X_CONNECTING: /* Connection in progress - retry later */ ret = ldap_get_option(c->handle, LDAP_OPT_DESC, &fd); if (!fr_cond_assert(ret == LDAP_OPT_SUCCESS)) { error: talloc_free(bind_ctx); fr_ldap_connection_timeout_reset(c); fr_ldap_state_error(c); /* Restart the connection state machine */ return; } ret = fr_event_fd_insert(bind_ctx, el, fd, NULL, _ldap_bind_io_write, /* We'll be called again when the conn is open */ _ldap_bind_io_error, bind_ctx); if (!fr_cond_assert(ret == 0)) goto error; break; case LDAP_SUCCESS: ret = fr_event_fd_insert(bind_ctx, el, fd, _ldap_bind_io_read, NULL, _ldap_bind_io_error, bind_ctx); if (!fr_cond_assert(ret == 0)) goto error; break; default: ERROR("Bind failed: %s", ldap_err2string(ret)); goto error; } fr_ldap_connection_timeout_reset(c); }
/** Break apart a TLV attribute into individual attributes * * @param[in] ctx to allocate new attributes in. * @param[in] cursor to addd new attributes to. * @param[in] parent the current attribute TLV attribute we're processing. * @param[in] data to parse. Points to the data field of the attribute. * @param[in] attr_len length of the TLV attribute. * @param[in] data_len remaining data in the packet. * @param[in] decoder_ctx IVs, keys etc... * @return * - Length on success. * - < 0 on malformed attribute. */ static ssize_t sim_decode_tlv(TALLOC_CTX *ctx, fr_cursor_t *cursor, fr_dict_attr_t const *parent, uint8_t const *data, size_t const attr_len, size_t data_len, void *decoder_ctx) { uint8_t const *p = data, *end = p + attr_len; uint8_t *decr = NULL; ssize_t decr_len; fr_dict_attr_t const *child; VALUE_PAIR *head = NULL; fr_cursor_t tlv_cursor; ssize_t rcode; if (data_len < 2) { fr_strerror_printf("%s: Insufficient data", __FUNCTION__); return -1; /* minimum attr size */ } /* * We have an AES-128-CBC encrypted attribute * * IV is from AT_IV, key is from k_encr. * * unfortunately the ordering of these two attributes * aren't specified, so we may have to hunt for the IV. */ if (parent->flags.encrypt) { FR_PROTO_TRACE("found encrypted attribute '%s'", parent->name); decr_len = sim_value_decrypt(ctx, &decr, p + 2, attr_len - 2, data_len - 2, decoder_ctx); /* Skip reserved */ if (decr_len < 0) return -1; p = decr; end = p + decr_len; } else { p += 2; /* Skip the reserved bytes */ } FR_PROTO_HEX_DUMP(p, end - p, "tlvs"); /* * Record where we were in the list when packet_ctx function was called */ fr_cursor_init(&tlv_cursor, &head); while ((size_t)(end - p) >= sizeof(uint32_t)) { uint8_t sim_at = p[0]; size_t sim_at_len = ((size_t)p[1]) << 2; if ((p + sim_at_len) > end) { fr_strerror_printf("%s: Malformed nested attribute %d: Length field (%zu bytes) value " "longer than remaining data in parent (%zu bytes)", __FUNCTION__, sim_at, sim_at_len, end - p); error: talloc_free(decr); fr_pair_list_free(&head); return -1; } if (sim_at_len == 0) { fr_strerror_printf("%s: Malformed nested attribute %d: Length field 0", __FUNCTION__, sim_at); goto error; } /* * Padding attributes are cleartext inside of * encrypted TLVs to pad out the value to the * correct length for the block cipher * (16 in the case of AES-128-CBC). */ if (sim_at == FR_SIM_PADDING) { uint8_t zero = 0; uint8_t i; if (!parent->flags.encrypt) { fr_strerror_printf("%s: Found padding attribute outside of an encrypted TLV", __FUNCTION__); goto error; } if (!fr_cond_assert(data_len % 4)) goto error; if (sim_at_len > 12) { fr_strerror_printf("%s: Expected padding attribute length <= 12 bytes, got %zu bytes", __FUNCTION__, sim_at_len); goto error; } /* * RFC says we MUST verify that FR_SIM_PADDING * data is zeroed out. */ for (i = 2; i < sim_at_len; i++) zero |= p[i]; if (zero) { fr_strerror_printf("%s: Padding attribute value not zeroed 0x%pH", __FUNCTION__, fr_box_octets(p + 2, sim_at_len - 2)); goto error; } p += sim_at_len; continue; } child = fr_dict_attr_child_by_num(parent, p[0]); if (!child) { fr_dict_attr_t const *unknown_child; FR_PROTO_TRACE("Failed to find child %u of TLV %s", p[0], parent->name); /* * Encountered none skippable attribute * * RFC says we need to die on these if we don't * understand them. non-skippables are < 128. */ if (sim_at <= SIM_SKIPPABLE_MAX) { fr_strerror_printf("%s: Unknown (non-skippable) attribute %i", __FUNCTION__, sim_at); goto error; } /* * Build an unknown attr */ unknown_child = fr_dict_unknown_afrom_fields(ctx, parent, fr_dict_vendor_num_by_da(parent), p[0]); if (!unknown_child) goto error; child = unknown_child; } FR_PROTO_TRACE("decode context changed %s -> %s", parent->name, child->name); rcode = sim_decode_pair_value(ctx, &tlv_cursor, child, p + 2, sim_at_len - 2, (end - p) - 2, decoder_ctx); if (rcode < 0) goto error; p += sim_at_len; } fr_cursor_head(&tlv_cursor); fr_cursor_tail(cursor); fr_cursor_merge(cursor, &tlv_cursor); /* Wind to the end of the new pairs */ talloc_free(decr); return attr_len; }
/** Logging callback to write log messages to a destination * * This allows the logging destination to be customised on a per request basis. * * @note Function does not write log output immediately * * @param[in] type What type of message this is (error, warn, info, debug). * @param[in] lvl At what logging level this message should be output. * @param[in] request The current request. * @param[in] file src file the log message was generated in. * @param[in] line number the log message was generated on. * @param[in] fmt sprintf style fmt string. * @param[in] ap Arguments for the fmt string. * @param[in] uctx Context data for the log function. */ static void logtee_it(fr_log_type_t type, fr_log_lvl_t lvl, REQUEST *request, UNUSED char const *file, UNUSED int line, char const *fmt, va_list ap, void *uctx) { rlm_logtee_thread_t *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t); rlm_logtee_t const *inst = t->inst; char *msg, *exp; fr_cursor_t cursor; VALUE_PAIR *vp; log_dst_t *dst; rad_assert(t->msg->vp_length == 0); /* Should have been cleared before returning */ /* * None of this should involve mallocs unless msg > 1k */ msg = talloc_typed_vasprintf(t->msg, fmt, ap); fr_value_box_strdup_buffer_shallow(NULL, &t->msg->data, attr_log_message, msg, true); t->type->vp_uint32 = (uint32_t) type; t->lvl->vp_uint32 = (uint32_t) lvl; fr_cursor_init(&cursor, &request->packet->vps); fr_cursor_prepend(&cursor, t->msg); fr_cursor_prepend(&cursor, t->type); fr_cursor_prepend(&cursor, t->lvl); fr_cursor_head(&cursor); /* * Now expand our fmt string to encapsulate the * message and any metadata * * Fixme: Would be better to call tmpl_expand * into a variable length ring buffer. */ dst = request->log.dst; request->log.dst = NULL; if (tmpl_aexpand(t, &exp, request, inst->log_fmt, NULL, NULL) < 0) goto finish; request->log.dst = dst; fr_fring_overwrite(t->fring, exp); /* Insert it into the buffer */ if (!t->pending) { t->pending = true; logtee_fd_active(t); /* Listen for when the fd is writable */ } finish: /* * Don't free, we re-use the VALUE_PAIRs for the next message */ vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->lvl)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->type)) fr_cursor_append(&cursor, vp); vp = fr_cursor_remove(&cursor); if (!fr_cond_assert(vp == t->msg)) fr_cursor_append(&cursor, vp); fr_value_box_clear(&t->msg->data); /* Clear message data */ }
static xlat_action_t xlat_delay(TALLOC_CTX *ctx, UNUSED fr_cursor_t *out, REQUEST *request, void const *xlat_inst, UNUSED void *xlat_thread_inst, fr_value_box_t **in) { rlm_delay_t const *inst; void *instance; struct timeval resume_at, delay, *yielded_at; memcpy(&instance, xlat_inst, sizeof(instance)); /* Stupid const issues */ inst = talloc_get_type_abort(instance, rlm_delay_t); /* * Record the time that we yielded the request */ MEM(yielded_at = talloc(request, struct timeval)); if (gettimeofday(yielded_at, NULL) < 0) { REDEBUG("Failed getting current time: %s", fr_syserror(errno)); return XLAT_ACTION_FAIL; } /* * If there's no input delay, just yield and * immediately re-enqueue the request. * This is very useful for testing. */ if (!*in) { memset(&delay, 0, sizeof(delay)); if (!fr_cond_assert(delay_add(request, &resume_at, yielded_at, &delay, true, true) == 0)) { return XLAT_ACTION_FAIL; } goto yield; } if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) { RPEDEBUG("Failed concatenating input"); talloc_free(yielded_at); return XLAT_ACTION_FAIL; } if (fr_timeval_from_str(&delay, (*in)->vb_strvalue) < 0) { RPEDEBUG("Failed parsing delay time"); talloc_free(yielded_at); return XLAT_ACTION_FAIL; } if (delay_add(request, &resume_at, yielded_at, &delay, inst->force_reschedule, inst->relative) != 0) { RDEBUG2("Not adding delay"); talloc_free(yielded_at); return XLAT_ACTION_DONE; } yield: RDEBUG3("Current time %pV, resume time %pV", fr_box_timeval(*yielded_at), fr_box_timeval(resume_at)); if (unlang_xlat_event_timeout_add(request, _delay_done, yielded_at, &resume_at) < 0) { RPEDEBUG("Adding event failed"); return XLAT_ACTION_FAIL; } return unlang_xlat_yield(request, xlat_delay_resume, xlat_delay_cancel, yielded_at); }
/** Decrypt an AES-128-CBC encrypted attribute * * @param[in] ctx to allocate decr buffer in. * @param[out] out where to write pointer to decr buffer. * @param[in] data to decrypt. * @param[in] attr_len length of encrypted data. * @param[in] data_len length of data remaining in the packet. * @param[in] decoder_ctx containing keys, and the IV (if we already found it). * @return * - Number of decr bytes decrypted on success. * - < 0 on failure. */ static ssize_t sim_value_decrypt(TALLOC_CTX *ctx, uint8_t **out, uint8_t const *data, size_t const attr_len, size_t const data_len, void *decoder_ctx) { fr_sim_decode_ctx_t *packet_ctx = decoder_ctx; EVP_CIPHER_CTX *evp_ctx; EVP_CIPHER const *evp_cipher = EVP_aes_128_cbc(); size_t block_size = EVP_CIPHER_block_size(evp_cipher); size_t len = 0, decr_len = 0; uint8_t *decr = NULL; if (!fr_cond_assert(attr_len <= data_len)) return -1; FR_PROTO_HEX_DUMP(data, attr_len, "ciphertext"); /* * Encrypted values must be a multiple of 16. * * There's a padding attribute to ensure they * always can be... */ if (attr_len % block_size) { fr_strerror_printf("%s: Encrypted attribute is not a multiple of cipher's block size (%zu)", __FUNCTION__, block_size); return -1; } /* * Ugh, now we have to go hunting for it.... */ if (!packet_ctx->have_iv) { uint8_t const *p = data + attr_len; /* Skip to the end of packet_ctx attribute */ uint8_t const *end = data + data_len; while ((size_t)(end - p) >= sizeof(uint32_t)) { uint8_t sim_at = p[0]; size_t sim_at_len = p[1] * sizeof(uint32_t); if (sim_at_len == 0) { fr_strerror_printf("%s: Failed IV search. AT Length field is zero", __FUNCTION__); return -1; } if ((p + sim_at_len) > end) { fr_strerror_printf("%s: Invalid IV length, longer than remaining data", __FUNCTION__); return -1; } if (sim_at == FR_SIM_IV) { if (sim_iv_extract(&(packet_ctx->iv[0]), p + 2, sim_at_len - 2) < 0) return -1; packet_ctx->have_iv = true; break; } p += sim_at_len; } if (!packet_ctx->have_iv) { fr_strerror_printf("%s: No IV present in packet, can't decrypt data", __FUNCTION__); return -1; } } evp_ctx = EVP_CIPHER_CTX_new(); if (!evp_ctx) { tls_strerror_printf("%s: Failed initialising EVP ctx", __FUNCTION__); return -1; } if (!EVP_DecryptInit_ex(evp_ctx, evp_cipher, NULL, packet_ctx->keys->k_encr, packet_ctx->iv)) { tls_strerror_printf("%s: Failed setting decryption parameters", __FUNCTION__); error: talloc_free(decr); EVP_CIPHER_CTX_free(evp_ctx); return -1; } MEM(decr = talloc_zero_array(ctx, uint8_t, attr_len)); /* * By default OpenSSL expects 16 bytes of cleartext * to produce 32 bytes of ciphertext, due to padding * being added if the decr is a multiple of 16. * * There's no way for OpenSSL to determine if a * 16 byte ciphertext was padded or not, so we need to * inform OpenSSL explicitly that there's no padding. */ EVP_CIPHER_CTX_set_padding(evp_ctx, 0); if (!EVP_DecryptUpdate(evp_ctx, decr, (int *)&len, data, attr_len)) { tls_strerror_printf("%s: Failed decrypting attribute", __FUNCTION__); goto error; } decr_len = len; if (!EVP_DecryptFinal_ex(evp_ctx, decr + decr_len, (int *)&len)) { tls_strerror_printf("%s: Failed decrypting attribute", __FUNCTION__); goto error; } decr_len += len; EVP_CIPHER_CTX_free(evp_ctx); /* * Note: packet_ctx implicitly validates the length of the padding * attribute (if present), so we don't have to do it later. */ if (decr_len % block_size) { fr_strerror_printf("%s: Expected decrypted value length to be multiple of %zu, got %zu", __FUNCTION__, block_size, decr_len); goto error; } /* * Ciphertext should be same length as plaintext. */ if (unlikely(attr_len != decr_len)) { fr_strerror_printf("%s: Invalid plaintext length, expected %zu, got %zu", __FUNCTION__, attr_len, decr_len); goto error; } FR_PROTO_TRACE("decryption successful, got %zu bytes of cleartext", decr_len); FR_PROTO_HEX_DUMP(decr, decr_len, "cleartext"); *out = decr; return decr_len; }
/** Read a packet from the network. * * @param[in] el the event list. * @param[in] sockfd the socket which is ready to read. * @param[in] flags from kevent. * @param[in] ctx the network socket context. */ static void fr_network_read(UNUSED fr_event_list_t *el, int sockfd, UNUSED int flags, void *ctx) { int num_messages = 0; fr_network_socket_t *s = ctx; fr_network_t *nr = s->nr; ssize_t data_size; fr_channel_data_t *cd, *next; fr_time_t *recv_time; if (!fr_cond_assert(s->fd == sockfd)) return; DEBUG3("network read"); if (!s->cd) { cd = (fr_channel_data_t *) fr_message_reserve(s->ms, s->listen->default_message_size); if (!cd) { fr_log(nr->log, L_ERR, "Failed allocating message size %zd! - Closing socket", s->listen->default_message_size); fr_network_socket_dead(nr, s); return; } } else { cd = s->cd; } rad_assert(cd->m.data != NULL); rad_assert(cd->m.rb_size >= 256); next_message: /* * Poll this socket, but not too often. We have to go * service other sockets, too. */ if (num_messages > 16) { s->cd = cd; return; } cd->request.is_dup = false; cd->priority = PRIORITY_NORMAL; /* * Read data from the network. * * Return of 0 means "no data", which is fine for UDP. * For TCP, if an underlying read() on the TCP socket * returns 0, (which signals that the FD is no longer * usable) this function should return -1, so that the * network side knows that it needs to close the * connection. */ data_size = s->listen->app_io->read(s->listen->app_io_instance, &cd->packet_ctx, &recv_time, cd->m.data, cd->m.rb_size, &s->leftover, &cd->priority, &cd->request.is_dup); if (data_size == 0) { /* * Cache the message for later. This is * important for stream sockets, which can do * partial reads into the current buffer. We * need to be able to give the same buffer back * to the stream socket for subsequent reads. * * Since we have a message set for each * fr_io_socket_t, no "head of line" * blocking issues can happen for stream sockets. */ s->cd = cd; return; } /* * Error: close the connection, and remove the fr_listen_t */ if (data_size < 0) { // fr_log(nr->log, L_DBG_ERR, "error from transport read on socket %d", sockfd); fr_network_socket_dead(nr, s); return; } s->cd = NULL; DEBUG("Network received packet size %zd", data_size); nr->stats.in++; s->stats.in++; /* * Initialize the rest of the fields of the channel data. * * We always use "now" as the time of the message, as the * packet MAY be a duplicate packet magically resurrected * from the past. */ cd->m.when = fr_time(); cd->listen = s->listen; cd->request.recv_time = recv_time; /* * Nothing in the buffer yet. Allocate room for one * packet. */ if ((cd->m.data_size == 0) && (!s->leftover)) { (void) fr_message_alloc(s->ms, &cd->m, data_size); next = NULL; } else { /* * There are leftover bytes in the buffer, feed * them to the next round of reading. */ next = (fr_channel_data_t *) fr_message_alloc_reserve(s->ms, &cd->m, data_size, s->leftover, s->listen->default_message_size); if (!next) { fr_log(nr->log, L_ERR, "Failed reserving partial packet."); // @todo - probably close the socket... rad_assert(0 == 1); } } if (!fr_network_send_request(nr, cd)) { fr_log(nr->log, L_ERR, "Failed sending packet to worker"); fr_message_done(&cd->m); nr->stats.dropped++; s->stats.dropped++; } else { /* * One more packet sent to a worker. */ s->outstanding++; } /* * If there is a next message, go read it from the buffer. * * @todo - note that this calls read(), even if the * app_io has paused the reader. We likely want to be * able to check that, too. We might just remove this * "goto"... */ if (next) { cd = next; num_messages++; goto next_message; } }
static ssize_t redis_xlat(UNUSED TALLOC_CTX *ctx, char **out, size_t outlen, void const *mod_inst, UNUSED void const *xlat_inst, REQUEST *request, char const *fmt) { rlm_redis_t const *inst = mod_inst; fr_redis_conn_t *conn; bool read_only = false; uint8_t const *key = NULL; size_t key_len = 0; fr_redis_cluster_state_t state; fr_redis_rcode_t status; redisReply *reply = NULL; int s_ret; size_t len; int ret; char const *p = fmt, *q; int argc; char const *argv[MAX_REDIS_ARGS]; char argv_buf[MAX_REDIS_COMMAND_LEN]; if (p[0] == '-') { p++; read_only = true; } /* * Hack to allow querying against a specific node for testing */ if (p[0] == '@') { fr_socket_addr_t node_addr; fr_pool_t *pool; RDEBUG3("Overriding node selection"); p++; q = strchr(p, ' '); if (!q) { REDEBUG("Found node specifier but no command, format is [-][@<host>[:port]] <redis command>"); return -1; } if (fr_inet_pton_port(&node_addr.ipaddr, &node_addr.port, p, q - p, AF_UNSPEC, true, true) < 0) { RPEDEBUG("Failed parsing node address"); return -1; } p = q + 1; if (fr_redis_cluster_pool_by_node_addr(&pool, inst->cluster, &node_addr, true) < 0) { RPEDEBUG("Failed locating cluster node"); return -1; } conn = fr_pool_connection_get(pool, request); if (!conn) { REDEBUG("No connections available for cluster node"); return -1; } argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf); if (argc <= 0) { RPEDEBUG("Invalid command: %s", p); arg_error: fr_pool_connection_release(pool, request, conn); return -1; } if (argc >= (MAX_REDIS_ARGS - 1)) { RPEDEBUG("Too many parameters; increase MAX_REDIS_ARGS and recompile: %s", p); goto arg_error; } RDEBUG2("Executing command: %s", argv[0]); if (argc > 1) { RDEBUG2("With argments"); RINDENT(); for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]); REXDENT(); } if (!read_only) { reply = redisCommandArgv(conn->handle, argc, argv, NULL); status = fr_redis_command_status(conn, reply); } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) { goto close_conn; } if (!reply) goto fail; switch (status) { case REDIS_RCODE_SUCCESS: goto reply_parse; case REDIS_RCODE_RECONNECT: close_conn: fr_pool_connection_close(pool, request, conn); ret = -1; goto finish; default: fail: fr_pool_connection_release(pool, request, conn); ret = -1; goto finish; } } /* * Normal node selection and execution based on key */ argc = rad_expand_xlat(request, p, MAX_REDIS_ARGS, argv, false, sizeof(argv_buf), argv_buf); if (argc <= 0) { RPEDEBUG("Invalid command: %s", p); ret = -1; goto finish; } if (argc >= (MAX_REDIS_ARGS - 1)) { RPEDEBUG("Too many parameters; increase MAX_REDIS_ARGS and recompile: %s", p); ret = -1; goto finish; } /* * If we've got multiple arguments, the second one is usually the key. * The Redis docs say commands should be analysed first to get key * positions, but this involves sending them to the server, which is * just as expensive as sending them to the wrong server and receiving * a redirect. */ if (argc > 1) { key = (uint8_t const *)argv[1]; key_len = strlen((char const *)key); } for (s_ret = fr_redis_cluster_state_init(&state, &conn, inst->cluster, request, key, key_len, read_only); s_ret == REDIS_RCODE_TRY_AGAIN; /* Continue */ s_ret = fr_redis_cluster_state_next(&state, &conn, inst->cluster, request, status, &reply)) { RDEBUG2("Executing command: %s", argv[0]); if (argc > 1) { RDEBUG2("With arguments"); RINDENT(); for (int i = 1; i < argc; i++) RDEBUG2("[%i] %s", i, argv[i]); REXDENT(); } if (!read_only) { reply = redisCommandArgv(conn->handle, argc, argv, NULL); status = fr_redis_command_status(conn, reply); } else if (redis_command_read_only(&status, &reply, request, conn, argc, argv) == -2) { state.close_conn = true; } } if (s_ret != REDIS_RCODE_SUCCESS) { ret = -1; goto finish; } if (!fr_cond_assert(reply)) { ret = -1; goto finish; } reply_parse: switch (reply->type) { case REDIS_REPLY_INTEGER: ret = snprintf(*out, outlen, "%lld", reply->integer); break; case REDIS_REPLY_STATUS: case REDIS_REPLY_STRING: len = (((size_t)reply->len) >= outlen) ? outlen - 1: (size_t) reply->len; memcpy(*out, reply->str, len); (*out)[len] = '\0'; ret = reply->len; break; default: REDEBUG("Server returned non-value type \"%s\"", fr_int2str(redis_reply_types, reply->type, "<UNKNOWN>")); ret = -1; break; } finish: fr_redis_reply_free(reply); return ret; }
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; }