/** Set the event list for a new IO instance * * @param[in] li the listener * @param[in] el the event list * @param[in] nr context from the network side */ static void mod_event_list_set(fr_listen_t *li, fr_event_list_t *el, UNUSED void *nr) { proto_detail_file_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_detail_file_thread_t); #ifdef __linux__ struct timeval when; #endif thread->el = el; /* * Initialize the work state machine. */ #ifndef __linux__ work_init(thread); #else /* * We're not changing UID, etc. Start processing the * detail files now. */ if (!main_config->allow_core_dumps) { work_init(thread); return; } /* * Delay for a bit, before reading the detail files. * This gives the server time to call * rad_suid_down_permanent(), and for /proc/PID to * therefore change permissions, so that libkqueue can * read it. */ gettimeofday(&when, NULL); when.tv_sec +=1; if (fr_event_timer_insert(thread, thread->el, &thread->ev, &when, work_retry_timer, thread) < 0) { ERROR("Failed inserting poll timer for %s", thread->filename_work); } #endif }
static int mod_bootstrap(void *instance, CONF_SECTION *cs) { proto_dhcpv4_udp_t *inst = talloc_get_type_abort(instance, proto_dhcpv4_udp_t); size_t num; inst->cs = cs; /* * Complain if no "ipaddr" is set. */ if (inst->ipaddr.af == AF_UNSPEC) { cf_log_err(cs, "No 'ipaddr' was specified in the 'udp' section"); return -1; } if (inst->ipaddr.af != AF_INET) { cf_log_err(cs, "DHCPv4 transport cannot use IPv6 for 'ipaddr'"); return -1; } /* * If src_ipaddr is defined, it must be of the same address family as "ipaddr" */ if ((inst->src_ipaddr.af != AF_UNSPEC) && (inst->src_ipaddr.af != inst->ipaddr.af)) { cf_log_err(cs, "Both 'ipaddr' and 'src_ipaddr' must be from the same address family"); return -1; } /* * Set src_ipaddr to INADDR_NONE if not otherwise specified */ if (inst->src_ipaddr.af == AF_UNSPEC) { memset(&inst->src_ipaddr, 0, sizeof(inst->src_ipaddr)); inst->src_ipaddr.af = AF_INET; } if (inst->recv_buff_is_set) { FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, >=, 32); FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, INT_MAX); }
static int mod_bootstrap(void *instance, CONF_SECTION *cs) { proto_radius_udp_t *inst = talloc_get_type_abort(instance, proto_radius_udp_t); size_t num; CONF_ITEM *ci; CONF_SECTION *server_cs; inst->cs = cs; /* * Complain if no "ipaddr" is set. */ if (inst->ipaddr.af == AF_UNSPEC) { cf_log_err(cs, "No 'ipaddr' was specified in the 'udp' section"); return -1; } if (inst->recv_buff_is_set) { FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, >=, 32); FR_INTEGER_BOUND_CHECK("recv_buff", inst->recv_buff, <=, INT_MAX); }
/** Callback called by libcurl to set/unset timers * * Each rlm_rest_thread_t has a timer event which is controller by libcurl. * This allows libcurl to honour timeouts set on requests to remote hosts, * and means we don't need to set timeouts for individual I/O events. * * @param[in] mandle handle requesting the timer be set/unset. * @param[in] timeout_ms If > 0, how long to wait before calling curl_multi_socket_action. * If == 0, we call curl_multi_socket_action as soon as possible. * If < 0, we delete the timer. * @param[in] ctx The rlm_rest_thread_t specific to this thread. * @return * - 0 on success. * - -1 on error. */ static int _rest_io_timer_modify(CURLM *mandle, long timeout_ms, void *ctx) { rlm_rest_thread_t *t = talloc_get_type_abort(ctx, rlm_rest_thread_t); CURLMcode ret; int running = 0; struct timeval now, to_add, when; if (timeout_ms == 0) { ret = curl_multi_socket_action(mandle, CURL_SOCKET_TIMEOUT, 0, &running); if (ret != CURLM_OK) { ERROR("Failed servicing curl multi-handle: %s (%i)", curl_multi_strerror(ret), ret); return -1; } DEBUG3("multi-handle %p serviced from CURLMOPT_TIMERFUNCTION callback (%s). " "%i request(s) in progress, %i requests(s) to dequeue", mandle, __FUNCTION__, running, t->transfers - running); return 0; } if (timeout_ms < 0) { if (fr_event_timer_delete(t->el, &t->ev) < 0) { PERROR("Failed deleting multi-handle timer"); return -1; } DEBUG3("multi-handle %p timer removed", mandle); return 0; } DEBUG3("multi-handle %p will need servicing in %li ms", mandle, timeout_ms); gettimeofday(&now, NULL); fr_timeval_from_ms(&to_add, (uint64_t)timeout_ms); fr_timeval_add(&when, &now, &to_add); (void) fr_event_timer_insert(NULL, t->el, &t->ev, &when, _rest_io_timer_expired, t); return 0; }
/** Destroy a network * * @param[in] nr the network * @return * - <0 on error * - 0 on success */ int fr_network_destroy(fr_network_t *nr) { int i; fr_channel_data_t *cd; (void) talloc_get_type_abort(nr, fr_network_t); /* * Pop all of the workers, and signal them that we're * closing/ */ for (i = 0; i < nr->num_workers; i++) { fr_network_worker_t *worker = nr->workers[i]; fr_channel_signal_worker_close(worker->channel); } /* * @todo wait for all workers to acknowledge the channel * close. */ /* * Clean up all of the replies. * * @todo - call transport "done" for the reply, so that * it knows the replies are done, too. */ while ((cd = fr_heap_pop(nr->replies)) != NULL) { fr_message_done(&cd->m); } (void) fr_event_post_delete(nr->el, fr_network_post_event, nr); /* * The caller has to free 'nr'. */ return 0; }
/** Initialise a new outbound connection * * @param[out] fd_out Where to write the new file descriptor. * @param[in] uctx A #rlm_logtee_thread_t. */ static fr_connection_state_t _logtee_conn_init(int *fd_out, void *uctx) { rlm_logtee_thread_t *t = talloc_get_type_abort(uctx, rlm_logtee_thread_t); rlm_logtee_t const *inst = t->inst; int fd = -1; switch (inst->log_dst) { case LOGTEE_DST_UNIX: DEBUG2("Opening UNIX socket at \"%s\"", inst->unix_sock.path); fd = fr_socket_client_unix(inst->unix_sock.path, true); if (fd < 0) return FR_CONNECTION_STATE_FAILED; break; case LOGTEE_DST_TCP: DEBUG2("Opening TCP connection to %pV:%u", fr_box_ipaddr(inst->tcp.dst_ipaddr), inst->tcp.port); fd = fr_socket_client_tcp(NULL, &inst->tcp.dst_ipaddr, inst->tcp.port, true); if (fd < 0) return FR_CONNECTION_STATE_FAILED; break; case LOGTEE_DST_UDP: DEBUG2("Opening UDP connection to %pV:%u", fr_box_ipaddr(inst->udp.dst_ipaddr), inst->udp.port); fd = fr_socket_client_udp(NULL, NULL, &inst->udp.dst_ipaddr, inst->udp.port, true); if (fd < 0) return FR_CONNECTION_STATE_FAILED; break; /* * Are not connection oriented destinations */ case LOGTEE_DST_INVALID: case LOGTEE_DST_FILE: rad_assert(0); return FR_CONNECTION_STATE_FAILED; } *fd_out = fd; return FR_CONNECTION_STATE_CONNECTING; }
static FR_CODE eap_fast_crypto_binding(REQUEST *request, UNUSED eap_session_t *eap_session, tls_session_t *tls_session, eap_tlv_crypto_binding_tlv_t *binding) { uint8_t cmac[sizeof(binding->compound_mac)]; eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); memcpy(cmac, binding->compound_mac, sizeof(cmac)); memset(binding->compound_mac, 0, sizeof(binding->compound_mac)); RHEXDUMP(L_DBG_LVL_MAX, (uint8_t const *) binding, sizeof(*binding), "Crypto-Binding TLV for Compound MAC calculation"); RHEXDUMP(L_DBG_LVL_MAX, cmac, sizeof(cmac), "Received Compound MAC"); fr_hmac_sha1(binding->compound_mac, (uint8_t *)binding, sizeof(*binding), t->cmk, EAP_FAST_CMK_LEN); if (memcmp(binding->compound_mac, cmac, sizeof(cmac))) { RDEBUG2("Crypto-Binding TLV mis-match"); RHEXDUMP(L_DBG_LVL_MAX, (uint8_t const *) binding->compound_mac, sizeof(binding->compound_mac), "Calculated Compound MAC"); return FR_CODE_ACCESS_REJECT; } return FR_CODE_ACCESS_ACCEPT; }
/** Set a timeout for the request. * * Used when a module needs wait for an event. Typically the callback is set, and then the * module returns unlang_module_yield(). * * @note The callback is automatically removed on unlang_interpret_resumable(). * * param[in] request the current request. * param[in] callback to call. * param[in] ctx for the callback. * param[in] timeout when to call the timeout (i.e. now + timeout). * @return * - 0 on success. * - <0 on error. */ int unlang_module_timeout_add(REQUEST *request, fr_unlang_module_timeout_t callback, void const *ctx, struct timeval *when) { unlang_stack_t *stack = request->stack; unlang_stack_frame_t *frame = &stack->frame[stack->depth]; unlang_module_event_t *ev; unlang_module_t *sp; unlang_frame_state_module_t *ms = talloc_get_type_abort(frame->state, unlang_frame_state_module_t); rad_assert(stack->depth > 0); rad_assert((frame->instruction->type == UNLANG_TYPE_MODULE) || (frame->instruction->type == UNLANG_TYPE_RESUME)); sp = unlang_generic_to_module(frame->instruction); ev = talloc_zero(request, unlang_module_event_t); if (!ev) return -1; ev->request = request; ev->fd = -1; ev->timeout = callback; ev->inst = sp->module_instance->dl_inst->data; ev->thread = ms->thread; ev->ctx = ctx; if (fr_event_timer_insert(request, request->el, &ev->ev, when, unlang_module_event_timeout_handler, ev) < 0) { RPEDEBUG("Failed inserting event"); talloc_free(ev); return -1; } (void) request_data_talloc_add(request, ctx, UNLANG_TYPE_MODULE, unlang_module_event_t, ev, true, false, false); talloc_set_destructor(ev, _unlang_event_free); 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; } }
static ssize_t test_read(void *ctx, UNUSED void **packet_ctx, fr_time_t **recv_time, uint8_t *buffer, size_t buffer_len, size_t *leftover, uint32_t *priority, bool *is_dup) { ssize_t data_size; fr_listen_test_t const *io_ctx = talloc_get_type_abort(ctx, fr_listen_test_t); tpc.salen = sizeof(tpc.src); *leftover = 0; *is_dup = false; data_size = recvfrom(io_ctx->sockfd, buffer, buffer_len, 0, (struct sockaddr *) &tpc.src, &tpc.salen); if (data_size <= 0) return data_size; /* * @todo - check if it's RADIUS. */ tpc.id = buffer[1]; memcpy(tpc.vector, buffer + 4, sizeof(tpc.vector)); start_time = fr_time(); *recv_time = &start_time; *priority = 0; return data_size; }
/** Handle a network control message callback for a new worker * * @param[in] ctx the network * @param[in] data the message * @param[in] data_size size of the data * @param[in] now the current time */ static void fr_network_worker_callback(void *ctx, void const *data, size_t data_size, UNUSED fr_time_t now) { int i; fr_network_t *nr = ctx; fr_worker_t *worker; fr_network_worker_t *w; rad_assert(data_size == sizeof(worker)); memcpy(&worker, data, data_size); (void) talloc_get_type_abort(worker, fr_worker_t); MEM(w = talloc_zero(nr, fr_network_worker_t)); w->worker = worker; w->channel = fr_worker_channel_create(worker, w, nr->control); if (!w->channel) fr_exit_now(1); fr_channel_master_ctx_add(w->channel, w); /* * Insert the worker into the array of workers. */ for (i = 0; i < nr->max_workers; i++) { if (nr->workers[i]) continue; nr->workers[i] = w; nr->num_workers++; return; } /* * Run out of room to put workers! */ rad_assert(0 == 1); }
/* * read the config section and load all the eap authentication types present. */ static int mod_instantiate(CONF_SECTION *cs, void *instance) { int i, ret; eap_type_t method; int num_methods; CONF_SECTION *scs; rlm_eap_t *inst = instance; /* * Create our own random pool. */ for (i = 0; i < 256; i++) { inst->rand_pool.randrsl[i] = fr_rand(); } fr_randinit(&inst->rand_pool, 1); inst->rand_pool.randcnt = 0; inst->xlat_name = cf_section_name2(cs); if (!inst->xlat_name) inst->xlat_name = "EAP"; /* Load all the configured EAP-Types */ num_methods = 0; for(scs = cf_subsection_find_next(cs, NULL, NULL); scs != NULL; scs = cf_subsection_find_next(cs, scs, NULL)) { const char *name; name = cf_section_name1(scs); if (!name) continue; if (!strcmp(name, TLS_CONFIG_SECTION)) continue; method = eap_name2type(name); if (method == PW_EAP_INVALID) { cf_log_err_cs(cs, "Unknown EAP method %s", name); return -1; } if ((method < PW_EAP_MD5) || (method >= PW_EAP_MAX_TYPES)) { cf_log_err_cs(cs, "Invalid EAP method %s (unsupported)", name); return -1; } #if !defined(HAVE_OPENSSL_SSL_H) || !defined(HAVE_LIBSSL) /* * This allows the default configuration to be * shipped with EAP-TLS, etc. enabled. If the * system doesn't have OpenSSL, they will be * ignored. * * If the system does have OpenSSL, then this * code will not be used. The administrator will * then have to delete the tls, * etc. configurations from eap.conf in order to * have EAP without the TLS types. */ if ((method == PW_EAP_TLS) || (method == PW_EAP_TTLS) || (method == PW_EAP_PEAP)) { DEBUG2("rlm_eap (%s): Ignoring EAP method %s because we do not have OpenSSL support", inst->xlat_name, name); continue; } #endif /* * Load the type. */ ret = eap_module_load(inst, &inst->methods[method], method, scs); (void) talloc_get_type_abort(inst->methods[method], eap_module_t); if (ret < 0) { (void) talloc_steal(inst, inst->methods[method]); return -1; } (void) talloc_steal(inst, inst->methods[method]); num_methods++; /* successfully loaded one more methods */ } if (num_methods == 0) { cf_log_err_cs(cs, "No EAP method configured, module cannot do anything"); return -1; } /* * Ensure that the default EAP type is loaded. */ method = eap_name2type(inst->default_method_name); if (method == PW_EAP_INVALID) { cf_log_err_cs(cs, "Unknown default EAP method '%s'", inst->default_method_name); return -1; } if (!inst->methods[method]) { cf_log_err_cs(cs, "No such sub-type for default EAP method %s", inst->default_method_name); return -1; } inst->default_method = method; /* save the numerical method */ /* * List of sessions are set to NULL by the memset * of 'inst', above. */ /* * Lookup sessions in the tree. We don't free them in * the tree, as that's taken care of elsewhere... */ inst->session_tree = rbtree_create(eap_handler_cmp, NULL, 0); if (!inst->session_tree) { radlog(L_ERR, "rlm_eap (%s): Cannot initialize tree", inst->xlat_name); return -1; } if (fr_debug_flag) { inst->handler_tree = rbtree_create(eap_handler_ptr_cmp, NULL, 0); if (!inst->handler_tree) { radlog(L_ERR, "rlm_eap (%s): Cannot initialize tree", inst->xlat_name); return -1; } #ifdef HAVE_PTHREAD_H if (pthread_mutex_init(&(inst->handler_mutex), NULL) < 0) { radlog(L_ERR, "rlm_eap (%s): Failed initializing mutex: %s", inst->xlat_name, strerror(errno)); return -1; } #endif } #ifdef HAVE_PTHREAD_H if (pthread_mutex_init(&(inst->session_mutex), NULL) < 0) { radlog(L_ERR, "rlm_eap (%s): Failed initializing mutex: %s", inst->xlat_name, strerror(errno)); return -1; } #endif return 0; }
/** Handle replies after all FD and timer events have been serviced * * @param el the event loop * @param now the current time (mostly) * @param uctx the fr_network_t */ static void fr_network_post_event(UNUSED fr_event_list_t *el, UNUSED struct timeval *now, void *uctx) { fr_channel_data_t *cd; fr_network_t *nr = talloc_get_type_abort(uctx, fr_network_t); while ((cd = fr_heap_pop(nr->replies)) != NULL) { ssize_t rcode; fr_listen_t const *listen; fr_message_t *lm; fr_network_socket_t my_socket, *s; listen = cd->listen; /* * @todo - cache this somewhere so we don't need * to do an rbtree lookup for every packet. */ my_socket.listen = listen; s = rbtree_finddata(nr->sockets, &my_socket); /* * This shouldn't happen, but be safe... */ if (!s) { fr_message_done(&cd->m); continue; } rad_assert(s->outstanding > 0); s->outstanding--; /* * Just mark the message done, and skip it. */ if (s->dead) { fr_message_done(&cd->m); /* * No more packets, it's safe to delete * the socket. */ if (!s->outstanding) { talloc_free(s); } continue; } /* * No data to write to the socket, so we skip it. */ if (!cd->m.data_size) { fr_message_done(&cd->m); continue; } /* * There are queued entries for this socket. * Append the packet into the list of packets to * write. * * For sanity, we localize the message first. * Doing so ensures that the worker has it's * message buffers cleaned up quickly. */ if (s->pending) { lm = fr_message_localize(s, &cd->m, sizeof(*cd)); fr_message_done(&cd->m); if (!lm) { ERROR("Failed copying packet. Discarding it."); continue; } cd = (fr_channel_data_t *) lm; (void) fr_heap_insert(s->waiting, cd); continue; } /* * The write function is responsible for ensuring * that NAKs are not written to the network. */ rcode = listen->app_io->write(listen->app_io_instance, cd->packet_ctx, cd->reply.request_time, cd->m.data, cd->m.data_size, 0); if (rcode < 0) { s->pending = 0; if (errno == EWOULDBLOCK) { save_pending: if (fr_event_fd_insert(nr, nr->el, s->fd, fr_network_read, fr_network_write, fr_network_error, s) < 0) { PERROR("Failed adding write callback to event loop"); goto error; } /* * Localize the message, and add * it as the current pending / * partially written packet. */ lm = fr_message_localize(s, &cd->m, sizeof(*cd)); fr_message_done(&cd->m); if (!lm) { ERROR("Failed copying packet. Discarding it."); continue; } cd = (fr_channel_data_t *) lm; s->pending = cd; continue; } /* * Tell the socket that there was an error. * * Don't call close, as that will be done * in the destructor. */ PERROR("Failed writing to socket %d", s->fd); error: fr_message_done(&cd->m); if (listen->app_io->error) listen->app_io->error(listen->app_io_instance); fr_network_socket_dead(nr, s); continue; } /* * If there's a partial write, save the write * callback for later. */ if ((rcode > 0) && ((size_t) rcode < cd->m.data_size)) { s->written = rcode; goto save_pending; } DEBUG3("Sending reply to socket %d", s->fd); fr_message_done(&cd->m); s->pending = NULL; s->written = 0; /* * As a special case, allow write() to return * "0", which means "close the socket". */ if (rcode == 0) fr_network_socket_dead(nr, s); } }
/* * Process the inner tunnel data */ FR_CODE eap_fast_process(eap_session_t *eap_session, tls_session_t *tls_session) { FR_CODE code; VALUE_PAIR *fast_vps = NULL; fr_cursor_t cursor; uint8_t const *data; size_t data_len; eap_fast_tunnel_t *t; REQUEST *request = eap_session->request; /* * Just look at the buffer directly, without doing * record_to_buff. */ data_len = tls_session->clean_out.used; tls_session->clean_out.used = 0; data = tls_session->clean_out.data; t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); /* * See if the tunneled data is well formed. */ if (!eap_fast_verify(request, tls_session, data, data_len)) return FR_CODE_ACCESS_REJECT; if (t->stage == EAP_FAST_TLS_SESSION_HANDSHAKE) { rad_assert(t->mode == EAP_FAST_UNKNOWN); char buf[256]; if (strstr(SSL_CIPHER_description(SSL_get_current_cipher(tls_session->ssl), buf, sizeof(buf)), "Au=None")) { /* FIXME enforce MSCHAPv2 - RFC 5422 section 3.2.2 */ RDEBUG2("Using anonymous provisioning"); t->mode = EAP_FAST_PROVISIONING_ANON; t->pac.send = true; } else { if (SSL_session_reused(tls_session->ssl)) { RDEBUG2("Session Resumed from PAC"); t->mode = EAP_FAST_NORMAL_AUTH; } else { RDEBUG2("Using authenticated provisioning"); t->mode = EAP_FAST_PROVISIONING_AUTH; } if (!t->pac.expires || t->pac.expired || t->pac.expires - time(NULL) < t->pac_lifetime * 0.6) { t->pac.send = true; } } eap_fast_init_keys(request, tls_session); eap_fast_send_identity_request(request, tls_session, eap_session); t->stage = EAP_FAST_AUTHENTICATION; return FR_CODE_ACCESS_CHALLENGE; } fr_cursor_init(&cursor, &fast_vps); if (eap_fast_decode_pair(request, &cursor, attr_eap_fast_tlv, data, data_len, NULL) < 0) return FR_CODE_ACCESS_REJECT; RDEBUG2("Got Tunneled FAST TLVs"); log_request_pair_list(L_DBG_LVL_1, request, fast_vps, NULL); code = eap_fast_process_tlvs(request, eap_session, tls_session, fast_vps); fr_pair_list_free(&fast_vps); if (code == FR_CODE_ACCESS_REJECT) return FR_CODE_ACCESS_REJECT; switch (t->stage) { case EAP_FAST_AUTHENTICATION: code = FR_CODE_ACCESS_CHALLENGE; break; case EAP_FAST_CRYPTOBIND_CHECK: { if (t->mode != EAP_FAST_PROVISIONING_ANON && !t->pac.send) t->result_final = true; eap_fast_append_result(tls_session, code); eap_fast_update_icmk(request, tls_session, (uint8_t *)&t->isk); eap_fast_append_crypto_binding(request, tls_session); code = FR_CODE_ACCESS_CHALLENGE; break; } case EAP_FAST_PROVISIONING: t->result_final = true; eap_fast_append_result(tls_session, code); if (t->pac.send) { RDEBUG2("Peer requires new PAC"); eap_fast_send_pac_tunnel(request, tls_session); code = FR_CODE_ACCESS_CHALLENGE; break; } t->stage = EAP_FAST_COMPLETE; /* fallthrough */ case EAP_FAST_COMPLETE: /* * RFC 5422 section 3.5 - Network Access after EAP-FAST Provisioning */ if (t->pac.type && t->pac.expired) { REDEBUG("Rejecting expired PAC."); code = FR_CODE_ACCESS_REJECT; break; } if (t->mode == EAP_FAST_PROVISIONING_ANON) { REDEBUG("Rejecting unauthenticated provisioning"); code = FR_CODE_ACCESS_REJECT; break; } /* * eap_crypto_mppe_keys() is unsuitable for EAP-FAST as Cisco decided * it would be a great idea to flip the recv/send keys around */ #define EAPTLS_MPPE_KEY_LEN 32 eap_add_reply(request, attr_ms_mppe_recv_key, t->msk, EAPTLS_MPPE_KEY_LEN); eap_add_reply(request, attr_ms_mppe_send_key, &t->msk[EAPTLS_MPPE_KEY_LEN], EAPTLS_MPPE_KEY_LEN); eap_add_reply(request, attr_eap_msk, t->msk, EAP_FAST_KEY_LEN); eap_add_reply(request, attr_eap_emsk, t->emsk, EAP_EMSK_LEN); break; default: RERROR("Internal sanity check failed in EAP-FAST at %d", t->stage); code = FR_CODE_ACCESS_REJECT; } return code; }
static FR_CODE eap_fast_process_tlvs(REQUEST *request, eap_session_t *eap_session, tls_session_t *tls_session, VALUE_PAIR *fast_vps) { eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); VALUE_PAIR *vp; fr_cursor_t cursor; eap_tlv_crypto_binding_tlv_t my_binding, *binding = NULL; memset(&my_binding, 0, sizeof(my_binding)); for (vp = fr_cursor_init(&cursor, &fast_vps); vp; vp = fr_cursor_next(&cursor)) { FR_CODE code = FR_CODE_ACCESS_REJECT; char *value; if (vp->da->parent == attr_eap_fast_tlv) { if (vp->da == attr_eap_fast_eap_payload) { code = eap_fast_eap_payload(request, eap_session, tls_session, vp); if (code == FR_CODE_ACCESS_ACCEPT) t->stage = EAP_FAST_CRYPTOBIND_CHECK; } else if ((vp->da == attr_eap_fast_result) || (vp->da == attr_eap_fast_intermediate_result)) { code = FR_CODE_ACCESS_ACCEPT; t->stage = EAP_FAST_PROVISIONING; } else { value = fr_pair_asprint(request->packet, vp, '"'); RDEBUG2("ignoring unknown %s", value); talloc_free(value); continue; } } else if (vp->da->parent == attr_eap_fast_crypto_binding) { binding = &my_binding; /* * fr_radius_encode_pair() does not work for structures */ switch (vp->da->attr) { case 1: /* FR_EAP_FAST_CRYPTO_BINDING_RESERVED */ binding->reserved = vp->vp_uint8; break; case 2: /* FR_EAP_FAST_CRYPTO_BINDING_VERSION */ binding->version = vp->vp_uint8; break; case 3: /* FR_EAP_FAST_CRYPTO_BINDING_RECV_VERSION */ binding->received_version = vp->vp_uint8; break; case 4: /* FR_EAP_FAST_CRYPTO_BINDING_SUB_TYPE */ binding->subtype = vp->vp_uint8; break; case 5: /* FR_EAP_FAST_CRYPTO_BINDING_NONCE */ if (vp->vp_length >= sizeof(binding->nonce)) { memcpy(binding->nonce, vp->vp_octets, vp->vp_length); } break; case 6: /* FR_EAP_FAST_CRYPTO_BINDING_COMPOUND_MAC */ if (vp->vp_length >= sizeof(binding->compound_mac)) { memcpy(binding->compound_mac, vp->vp_octets, sizeof(binding->compound_mac)); } break; } continue; } else if (vp->da->parent == attr_eap_fast_pac_tlv) { if (vp->da == attr_eap_fast_pac_acknowledge) { if (vp->vp_uint32 == EAP_FAST_TLV_RESULT_SUCCESS) { code = FR_CODE_ACCESS_ACCEPT; t->pac.expires = UINT32_MAX; t->pac.expired = false; t->stage = EAP_FAST_COMPLETE; } } else if (vp->da == attr_eap_fast_pac_info_pac_type) { if (vp->vp_uint32 != PAC_TYPE_TUNNEL) { RDEBUG2("only able to serve Tunnel PAC's, ignoring request"); continue; } t->pac.send = true; continue; } else { value = fr_pair_asprint(request->packet, vp, '"'); RDEBUG2("ignoring unknown EAP-FAST-PAC-TLV %s", value); talloc_free(value); continue; } } else { value = fr_pair_asprint(request->packet, vp, '"'); RDEBUG2("ignoring non-EAP-FAST TLV %s", value); talloc_free(value); continue; } if (code == FR_CODE_ACCESS_REJECT) return FR_CODE_ACCESS_REJECT; } if (binding) { FR_CODE code = eap_fast_crypto_binding(request, eap_session, tls_session, binding); if (code == FR_CODE_ACCESS_ACCEPT) { t->stage = EAP_FAST_PROVISIONING; } return code; } return FR_CODE_ACCESS_ACCEPT; }
static ssize_t mod_read(fr_listen_t *li, void **packet_ctx, fr_time_t **recv_time, uint8_t *buffer, size_t buffer_len, size_t *leftover, UNUSED uint32_t *priority, UNUSED bool *is_dup) { proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); fr_io_address_t *address, **address_p; int flags; ssize_t data_size; size_t packet_len; struct timeval timestamp; uint8_t message_type; uint32_t xid, ipaddr; dhcp_packet_t *packet; fr_time_t *recv_time_p; *leftover = 0; /* always for UDP */ /* * Where the addresses should go. This is a special case * for proto_dhcpv4. */ address_p = (fr_io_address_t **) packet_ctx; address = *address_p; recv_time_p = *recv_time; /* * Tell udp_recv if we're connected or not. */ flags = UDP_FLAGS_CONNECTED * (thread->connection != NULL); data_size = udp_recv(thread->sockfd, buffer, buffer_len, flags, &address->src_ipaddr, &address->src_port, &address->dst_ipaddr, &address->dst_port, &address->if_index, ×tamp); if (data_size < 0) { DEBUG2("proto_dhvpv4_udp got read error %zd: %s", data_size, fr_strerror()); return data_size; } if (!data_size) { DEBUG2("proto_dhcpv4_udp got no data: ignoring"); return 0; } /* * @todo - make this take "&packet_len", as the DHCPv4 * packet may be smaller than the parent UDP packet. */ if (!fr_dhcpv4_ok(buffer, data_size, &message_type, &xid)) { DEBUG2("proto_dhcpv4_udp got invalid packet, ignoring it - %s", fr_strerror()); return 0; } packet_len = data_size; /* * We've seen a server reply to this port, but the giaddr * is *not* our address. Drop it. */ packet = (dhcp_packet_t *) buffer; memcpy(&ipaddr, &packet->giaddr, 4); if ((packet->opcode == 2) && (ipaddr != address->dst_ipaddr.addr.v4.s_addr)) { DEBUG2("Ignoring server reply which was not meant for us (was for 0x%x).", ntohl(address->dst_ipaddr.addr.v4.s_addr)); return 0; } // @todo - maybe convert timestamp? *recv_time_p = fr_time(); /* * proto_dhcpv4 sets the priority */ /* * Print out what we received. */ DEBUG2("proto_dhcpv4_udp - Received %s XID %08x length %d %s", dhcp_message_types[message_type], xid, (int) packet_len, thread->name); return packet_len; }
static int redis_xlat_instantiate(void *xlat_inst, UNUSED xlat_exp_t const *exp, void *uctx) { *((rlm_redis_t **)xlat_inst) = talloc_get_type_abort(uctx, rlm_redis_t); return 0; }
static int eap_fast_verify(REQUEST *request, tls_session_t *tls_session, uint8_t const *data, unsigned int data_len) { uint16_t attr; uint16_t length; unsigned int remaining = data_len; int total = 0; int num[EAP_FAST_TLV_MAX] = {0}; eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); uint32_t present = 0; rad_assert(sizeof(present) * 8 > EAP_FAST_TLV_MAX); while (remaining > 0) { if (remaining < 4) { RDEBUG2("EAP-FAST TLV is too small (%u) to contain a EAP-FAST TLV header", remaining); return 0; } memcpy(&attr, data, sizeof(attr)); attr = ntohs(attr) & EAP_FAST_TLV_TYPE; if ((attr == attr_eap_fast_result->attr) || (attr == attr_eap_fast_nak->attr) || (attr == attr_eap_fast_error->attr) || (attr == attr_eap_fast_vendor_specific->attr) || (attr == attr_eap_fast_eap_payload->attr) || (attr == attr_eap_fast_intermediate_result->attr) || (attr == attr_eap_fast_pac_tlv->attr) || (attr == attr_eap_fast_crypto_binding->attr)) { num[attr]++; present |= 1 << attr; if (num[attr_eap_fast_eap_payload->attr] > 1) { REDEBUG("Too many EAP-Payload TLVs"); unexpected: for (int i = 0; i < EAP_FAST_TLV_MAX; i++) { if (present & (1 << i)) RDEBUG2(" - attribute %d is present", i); } eap_fast_send_error(tls_session, EAP_FAST_ERR_UNEXPECTED_TLV); return 0; } if (num[attr_eap_fast_intermediate_result->attr] > 1) { REDEBUG("Too many Intermediate-Result TLVs"); goto unexpected; } } else { if ((data[0] & 0x80) != 0) { REDEBUG("Unknown mandatory TLV %02x", attr); goto unexpected; } num[0]++; } total++; memcpy(&length, data + 2, sizeof(length)); length = ntohs(length); data += 4; remaining -= 4; if (length > remaining) { RDEBUG2("EAP-FAST TLV %u is longer than room remaining in the packet (%u > %u).", attr, length, remaining); return 0; } /* * If the rest of the TLVs are larger than * this attribute, continue. * * Otherwise, if the attribute over-flows the end * of the TLCs, die. */ if (remaining < length) { RDEBUG2("EAP-FAST TLV overflows packet!"); return 0; } /* * If there's an error, we bail out of the * authentication process before allocating * memory. */ if ((attr == attr_eap_fast_intermediate_result->attr) || (attr == attr_eap_fast_result->attr)) { uint16_t status; if (length < 2) { REDEBUG("EAP-FAST TLV %u is too short. Expected 2, got %d", attr, length); return 0; } memcpy(&status, data, 2); status = ntohs(status); if (status == EAP_FAST_TLV_RESULT_FAILURE) { REDEBUG("EAP-FAST TLV %u indicates failure. Rejecting request", attr); return 0; } if (status != EAP_FAST_TLV_RESULT_SUCCESS) { REDEBUG("EAP-FAST TLV %u contains unknown value. Rejecting request", attr); goto unexpected; } } /* * remaining > length, continue. */ remaining -= length; data += length; } /* * Check if the peer mixed & matched TLVs. */ if ((num[attr_eap_fast_nak->attr] > 0) && (num[attr_eap_fast_nak->attr] != total)) { REDEBUG("NAK TLV sent with non-NAK TLVs. Rejecting request"); goto unexpected; } if (num[attr_eap_fast_intermediate_result->attr] > 0) { REDEBUG("NAK TLV sent with non-NAK TLVs. Rejecting request"); goto unexpected; } /* * Check mandatory or not mandatory TLVs. */ switch (t->stage) { case EAP_FAST_TLS_SESSION_HANDSHAKE: if (present) { REDEBUG("Unexpected TLVs in TLS Session Handshake stage"); goto unexpected; } break; case EAP_FAST_AUTHENTICATION: if (present != (uint32_t)(1 << attr_eap_fast_eap_payload->attr)) { REDEBUG("Unexpected TLVs in authentication stage"); goto unexpected; } break; case EAP_FAST_CRYPTOBIND_CHECK: { uint32_t bits = (t->result_final) ? 1 << attr_eap_fast_result->attr : 1 << attr_eap_fast_intermediate_result->attr; if (present & ~(bits | (1 << attr_eap_fast_crypto_binding->attr) | (1 << attr_eap_fast_pac_tlv->attr))) { REDEBUG("Unexpected TLVs in cryptobind checking stage"); goto unexpected; } break; } case EAP_FAST_PROVISIONING: if (present & ~((1 << attr_eap_fast_pac_tlv->attr) | (1 << attr_eap_fast_result->attr))) { REDEBUG("Unexpected TLVs in provisioning stage"); goto unexpected; } break; case EAP_FAST_COMPLETE: if (present) { REDEBUG("Unexpected TLVs in complete stage"); goto unexpected; } break; default: REDEBUG("Unexpected stage %d", t->stage); return 0; } /* * We got this far. It looks OK. */ return 1; }
/** * This callback is invoked by archive_open(). It returns ARCHIVE_OK * if the underlying file or data source is successfully opened. If * the open fails, it calls archive_set_error() to register an error * code and message and returns ARCHIVE_FATAL. * * -- man 3 archive_read_open. */ static int open_callback(struct archive *archive, void *data_) { CallbackData *data = talloc_get_type_abort(data_, CallbackData); AutoExtractInfo info; struct stat statf; off_t offset; int status; /* Note: data->fd will be closed by close_callback(). */ data->fd = open(data->path, O_RDONLY); if (data->fd < 0) { archive_set_error(archive, errno, "can't open archive"); return ARCHIVE_FATAL; } status = fstat(data->fd, &statf); if (status < 0) { archive_set_error(archive, errno, "can't stat archive"); return ARCHIVE_FATAL; } /* Assume it's a regular archive if it physically can't be a * self-extracting one. */ if (statf.st_size < (off_t) sizeof(AutoExtractInfo)) return ARCHIVE_OK; offset = lseek(data->fd, statf.st_size - sizeof(AutoExtractInfo), SEEK_SET); if (offset == (off_t) -1) { archive_set_error(archive, errno, "can't seek in archive"); return ARCHIVE_FATAL; } status = read(data->fd, &info, sizeof(AutoExtractInfo)); if (status < 0) { archive_set_error(archive, errno, "can't read archive"); return ARCHIVE_FATAL; } if ( status == sizeof(AutoExtractInfo) && strcmp(info.signature, AUTOEXTRACT_SIGNATURE) == 0) { /* This is a self-extracting archive, retrive it's * offset and size. */ data->size = be64toh(info.size); offset = statf.st_size - data->size - sizeof(AutoExtractInfo); note(NULL, INFO, USER, "archive found: offset = %" PRIu64 ", size = %" PRIu64 "", (uint64_t) offset, data->size); } else { /* This is not a self-extracting archive, assume it's * a regular one... */ offset = 0; /* ... unless a self-extracting archive really was * expected. */ if (strcmp(data->path, "/proc/self/exe") == 0) return ARCHIVE_FATAL; } offset = lseek(data->fd, offset, SEEK_SET); if (offset == (off_t) -1) { archive_set_error(archive, errno, "can't seek in archive"); return ARCHIVE_FATAL; } return ARCHIVE_OK; }
/** Write packets to the network. * * @param el the event list * @param sockfd the socket which is ready to write * @param flags returned by kevent. * @param ctx the network socket context. */ static void fr_network_write(UNUSED fr_event_list_t *el, UNUSED int sockfd, UNUSED int flags, void *ctx) { fr_network_socket_t *s = ctx; fr_listen_t const *listen = s->listen; fr_network_t *nr = s->nr; fr_channel_data_t *cd; (void) talloc_get_type_abort(nr, fr_network_t); rad_assert(s->pending != NULL); /* * @todo - this code is much the same as in * fr_network_post_event(). Fix it so we only have one * copy! */ /* * Start with the currently pending message, and then * work through the priority heap. */ for (cd = s->pending; cd != NULL; cd = fr_heap_pop(s->waiting)) { int rcode; rad_assert(listen == cd->listen); rad_assert(cd->m.status == FR_MESSAGE_LOCALIZED); rcode = listen->app_io->write(listen->app_io_instance, cd->packet_ctx, cd->reply.request_time, cd->m.data, cd->m.data_size, 0); if (rcode < 0) { /* * Stop processing the heap, and set the * pending message to the current one. */ if (errno == EWOULDBLOCK) { s->pending = cd; return; } /* * As a special hack, check for something * that will never be returned from a * real write() routine. Which then * signals to us that we have to close * the socket, but NOT complain about it. */ if (errno == ECONNREFUSED) { fr_network_socket_dead(nr, s); return; } PERROR("Failed writing to socket %d", s->fd); fr_network_socket_dead(nr, s); return; } /* * If we've done a partial write, localize the message and continue. */ if ((rcode > 0) && ((size_t) rcode < cd->m.data_size)) { s->written = rcode; s->pending = cd; return; } s->pending = NULL; s->written = 0; /* * Reset for the next message. */ fr_message_done(&cd->m); nr->stats.out++; s->stats.out++; /* * As a special case, allow write() to return * "0", which means "close the socket". */ if (rcode == 0) { fr_network_socket_dead(nr, s); return; } } /* * We've successfully written all of the packets. Remove * the write callback. */ if (fr_event_fd_insert(nr, nr->el, s->fd, fr_network_read, NULL, fr_network_error, s) < 0) { PERROR("Failed adding new socket to event loop"); fr_network_socket_dead(nr, s); } }
static char const *mod_name(fr_listen_t *li) { proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); return thread->name; }
/** Open a UDP listener for DHCPV4 * */ static int mod_open(fr_listen_t *li) { proto_dhcpv4_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dhcpv4_udp_t); proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); int sockfd, rcode; uint16_t port = inst->port; CONF_SECTION *server_cs; CONF_ITEM *ci; li->fd = sockfd = fr_socket_server_udp(&inst->ipaddr, &port, inst->port_name, true); if (sockfd < 0) { PERROR("Failed opening UDP socket"); error: return -1; } /* * Set SO_REUSEPORT before bind, so that all packets can * listen on the same destination IP address. */ if (1) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &on, sizeof(on)) < 0) { ERROR("Failed to set socket 'reuseport': %s", fr_syserror(errno)); close(sockfd); return -1; } } if (inst->broadcast) { int on = 1; if (setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) { ERROR("Failed to set broadcast option: %s", fr_syserror(errno)); close(sockfd); return -1; } } /* * SUID up is really only needed if interface is set, OR port <1024. */ rad_suid_up(); rcode = fr_socket_bind(sockfd, &inst->ipaddr, &port, inst->interface); rad_suid_down(); if (rcode < 0) { close(sockfd); PERROR("Failed binding socket"); goto error; } thread->sockfd = sockfd; ci = cf_parent(inst->cs); /* listen { ... } */ rad_assert(ci != NULL); ci = cf_parent(ci); rad_assert(ci != NULL); server_cs = cf_item_to_section(ci); thread->name = fr_app_io_socket_name(thread, &proto_dhcpv4_udp, NULL, 0, &inst->ipaddr, inst->port, inst->interface); DEBUG("Listening on dhcpv4 address %s bound to virtual server %s", thread->name, cf_section_name2(server_cs)); return 0; }
static ssize_t mod_write(fr_listen_t *li, void *packet_ctx, UNUSED fr_time_t request_time, uint8_t *buffer, size_t buffer_len, UNUSED size_t written) { proto_dhcpv4_udp_t const *inst = talloc_get_type_abort_const(li->app_io_instance, proto_dhcpv4_udp_t); proto_dhcpv4_udp_thread_t *thread = talloc_get_type_abort(li->thread_instance, proto_dhcpv4_udp_thread_t); fr_io_track_t *track = talloc_get_type_abort(packet_ctx, fr_io_track_t); fr_io_address_t address; int flags; ssize_t data_size; /* * @todo - share a stats interface with the parent? or * put the stats in the listener, so that proto_dhcpv4 * can update them, too.. <sigh> */ thread->stats.total_responses++; flags = UDP_FLAGS_CONNECTED * (thread->connection != NULL); rad_assert(track->reply_len == 0); /* * Swap src/dst IP/port */ address.src_ipaddr = track->address->dst_ipaddr; address.src_port = track->address->dst_port; address.dst_ipaddr = track->address->src_ipaddr; address.dst_port = track->address->src_port; address.if_index = track->address->if_index; /* * Figure out which kind of packet we're sending. */ if (!thread->connection) { uint8_t const *code, *sid; uint32_t ipaddr; dhcp_packet_t *packet = (dhcp_packet_t *) buffer; dhcp_packet_t *request = (dhcp_packet_t *) track->packet; /* only 20 bytes tho! */ #ifdef WITH_IFINDEX_IPADDR_RESOLUTION fr_ipaddr_t primary; #endif /* * This isn't available in the packet header. */ code = fr_dhcpv4_packet_get_option(packet, buffer_len, attr_message_type); if (!code || (code[1] < 1) || (code[2] == 0) || (code[2] >= FR_DHCP_INFORM)) { DEBUG("WARNING - silently discarding reply due to invalid or missing message type"); return 0; } /* * Set the source IP of the packet. * * - if src_ipaddr is unicast, use that * - else if socket wasn't bound to *, then use that * - else if we have if_index, get main IP from that interface and use that. * - else for offer/ack, look at option 54, for Server Identification and use that * - else leave source IP as whatever is already in "address.src_ipaddr". */ if (inst->src_ipaddr.addr.v4.s_addr != INADDR_ANY) { address.src_ipaddr = inst->src_ipaddr; } else if (inst->ipaddr.addr.v4.s_addr != INADDR_ANY) { address.src_ipaddr = inst->ipaddr; #ifdef WITH_IFINDEX_IPADDR_RESOLUTION } else if ((address->if_index > 0) && (fr_ipaddr_from_ifindex(&primary, thread->sockfd, &address.dst_ipaddr.af, &address.if_index) == 0)) { address.src_ipaddr = primary; #endif } else if (((code[2] == FR_DHCP_OFFER) || (code[2] == FR_DHCP_ACK)) && ((sid = fr_dhcpv4_packet_get_option(packet, buffer_len, attr_dhcp_server_identifier)) != NULL) && (sid[1] == 4)) { memcpy(&address.src_ipaddr.addr.v4.s_addr, sid + 2, 4); } /* * We have GIADDR in the packet, so send it * there. The packet is FROM our IP address and * port, TO the destination IP address, at the * same (i.e. server) port. */ memcpy(&ipaddr, &packet->giaddr, 4); if (ipaddr != INADDR_ANY) { DEBUG("Reply will be sent to giaddr."); address.dst_ipaddr.addr.v4.s_addr = ipaddr; address.dst_port = inst->port; address.src_port = inst->port; /* * Increase the hop count for client * packets sent to the next gateway. */ if ((code[2] == FR_DHCP_DISCOVER) || (code[2] == FR_DHCP_REQUEST)) { packet->opcode = 1; /* client message */ packet->hops = request->hops + 1; } else { packet->opcode = 2; /* server message */ } goto send_reply; } /* * If there's no GIADDR, we don't know where to * send client packets. */ if ((code[2] == FR_DHCP_DISCOVER) || (code[2] == FR_DHCP_REQUEST)) { DEBUG("WARNING - silently discarding client reply, as there is no GIADDR to send it to."); return 0; } packet->opcode = 2; /* server message */ /* * The original packet requested a broadcast * reply, and CIADDR is empty, go broadcast the * reply. RFC 2131 page 23. */ if (((request->flags & FR_DHCP_FLAGS_VALUE_BROADCAST) != 0) && (request->ciaddr == INADDR_ANY)) { DEBUG("Reply will be broadcast due to client request."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; goto send_reply; } /* * The original packet has CIADDR, so we unicast * the reply there. RFC 2131 page 23. */ if (request->ciaddr != INADDR_ANY) { DEBUG("Reply will be unicast to CIADDR from original packet."); memcpy(&address.dst_ipaddr.addr.v4.s_addr, &request->ciaddr, 4); goto send_reply; } /* * The original packet was unicast to us, such as * via a relay. We have a unicast destination * address, so we just use that. */ if ((packet->yiaddr == htonl(INADDR_ANY)) && (address.dst_ipaddr.addr.v4.s_addr != htonl(INADDR_BROADCAST))) { DEBUG("Reply will be unicast to source IP from original packet."); goto send_reply; } switch (code[2]) { /* * Offers are sent to YIADDR if we * received a unicast packet from YIADDR. * Otherwise, they are unicast to YIADDR * (if we can update ARP), otherwise they * are broadcast. */ case FR_DHCP_OFFER: /* * If the packet was unicast from the * client, unicast it back. */ if (memcmp(&address.dst_ipaddr.addr.v4.s_addr, &packet->yiaddr, 4) == 0) { DEBUG("Reply will be unicast to YIADDR."); #ifdef SIOCSARP } else if (inst->broadcast && inst->interface) { if (fr_dhcpv4_udp_add_arp_entry(thread->sockfd, inst->interface, &packet->yiaddr, &packet->chaddr) < 0) { DEBUG("Failed adding ARP entry. Reply will be broadcast."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; } else { DEBUG("Reply will be unicast to YIADDR after ARP table updates."); } #endif } else { DEBUG("Reply will be broadcast due to OFFER."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; } break; /* * ACKs are unicast to YIADDR */ case FR_DHCP_ACK: DEBUG("Reply will be unicast to YIADDR."); memcpy(&address.dst_ipaddr.addr.v4.s_addr, &packet->yiaddr, 4); break; /* * NAKs are broadcast. */ case FR_DHCP_NAK: DEBUG("Reply will be broadcast due to NAK."); address.dst_ipaddr.addr.v4.s_addr = INADDR_BROADCAST; break; default: DEBUG("WARNING - silently discarding reply due to invalid message type %d", code[2]); return 0; } } send_reply: /* * proto_dhcpv4 takes care of suppressing do-not-respond, etc. */ data_size = udp_send(thread->sockfd, buffer, buffer_len, flags, &address.src_ipaddr, address.src_port, address.if_index, &address.dst_ipaddr, address.dst_port); /* * This socket is dead. That's an error... */ if (data_size <= 0) return data_size; return data_size; }
/** 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); }
static int CC_HINT(nonnull) mod_process(UNUSED void *instance, eap_handler_t *handler) { int rcode; REQUEST *request = handler->request; leap_session_t *session; leap_packet_t *packet; leap_packet_t *reply; VALUE_PAIR *password; if (!handler->opaque) { REDEBUG("Cannot authenticate without LEAP history"); return 0; } session = talloc_get_type_abort(handler->opaque, leap_session_t); reply = NULL; /* * Extract the LEAP packet. */ if (!(packet = eapleap_extract(request, handler->eap_ds))) { return 0; } /* * The password is never sent over the wire. * Always get the configured password, for each user. */ password = pairfind(handler->request->config, PW_CLEARTEXT_PASSWORD, 0, TAG_ANY); if (!password) { password = pairfind(handler->request->config, PW_NT_PASSWORD, 0, TAG_ANY); } if (!password) { REDEBUG("No Cleartext-Password or NT-Password configured for this user"); talloc_free(packet); return 0; } /* * We've already sent the AP challenge. This packet * should contain the NtChallengeResponse */ switch (session->stage) { case 4: /* Verify NtChallengeResponse */ RDEBUG2("Stage 4"); rcode = eapleap_stage4(request, packet, password, session); session->stage = 6; /* * We send EAP-Success or EAP-Fail, and not * any LEAP packet. So we return here. */ if (!rcode) { handler->eap_ds->request->code = PW_EAP_FAILURE; talloc_free(packet); return 0; } handler->eap_ds->request->code = PW_EAP_SUCCESS; /* * Do this only for Success. */ handler->eap_ds->request->id = handler->eap_ds->response->id + 1; handler->eap_ds->set_request_id = 1; /* * LEAP requires a challenge in stage 4, not * an Access-Accept, which is normally returned * by eap_compose() in eap.c, when the EAP reply code * is EAP_SUCCESS. */ handler->request->reply->code = PW_CODE_ACCESS_CHALLENGE; talloc_free(packet); return 1; case 6: /* Issue session key */ RDEBUG2("Stage 6"); reply = eapleap_stage6(request, packet, handler->request->username, password, session); break; /* * Stages 1, 3, and 5 are requests from the AP. * Stage 2 is handled by initiate() */ default: RDEBUG("Internal sanity check failed on stage"); break; } talloc_free(packet); /* * Process the packet. We don't care about any previous * EAP packets, as */ if (!reply) { return 0; } eapleap_compose(request, handler->eap_ds, reply); talloc_free(reply); return 1; }
/* * Use a reply packet to determine what to do. */ static rlm_rcode_t CC_HINT(nonnull) process_reply(NDEBUG_UNUSED eap_session_t *eap_session, tls_session_t *tls_session, REQUEST *request, RADIUS_PACKET *reply) { rlm_rcode_t rcode = RLM_MODULE_REJECT; VALUE_PAIR *vp; fr_cursor_t cursor; eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); rad_assert(eap_session->request == request); /* * If the response packet was Access-Accept, then * we're OK. If not, die horribly. * * FIXME: EAP-Messages can only start with 'identity', * NOT 'eap start', so we should check for that.... */ switch (reply->code) { case FR_CODE_ACCESS_ACCEPT: RDEBUG2("Got tunneled Access-Accept"); rcode = RLM_MODULE_OK; /* * Copy what we need into the TTLS tunnel and leave * the rest to be cleaned up. */ for (vp = fr_cursor_init(&cursor, &reply->vps); vp; vp = fr_cursor_next(&cursor)) { if (fr_dict_vendor_num_by_da(vp->da) != VENDORPEC_MICROSOFT) continue; /* FIXME must be a better way to capture/re-derive this later for ISK */ switch (vp->da->attr) { case FR_MSCHAP_MPPE_SEND_KEY: if (vp->vp_length != RADIUS_CHAP_CHALLENGE_LENGTH) { wrong_length: REDEBUG("Found %s with incorrect length. Expected %u, got %zu", vp->da->name, RADIUS_CHAP_CHALLENGE_LENGTH, vp->vp_length); rcode = RLM_MODULE_INVALID; break; } memcpy(t->isk.mppe_send, vp->vp_octets, RADIUS_CHAP_CHALLENGE_LENGTH); break; case FR_MSCHAP_MPPE_RECV_KEY: if (vp->vp_length != RADIUS_CHAP_CHALLENGE_LENGTH) goto wrong_length; memcpy(t->isk.mppe_recv, vp->vp_octets, RADIUS_CHAP_CHALLENGE_LENGTH); break; case FR_MSCHAP2_SUCCESS: RDEBUG2("Got %s, tunneling it to the client in a challenge", vp->da->name); rcode = RLM_MODULE_HANDLED; t->authenticated = true; break; default: break; } } RHEXDUMP(L_DBG_LVL_MAX, (uint8_t *)&t->isk, 2 * RADIUS_CHAP_CHALLENGE_LENGTH, "ISK[j]"); /* FIXME (part of above) */ break; case FR_CODE_ACCESS_REJECT: REDEBUG("Got tunneled Access-Reject"); rcode = RLM_MODULE_REJECT; break; case FR_CODE_ACCESS_CHALLENGE: RDEBUG2("Got tunneled Access-Challenge"); /* * Copy the EAP-Message back to the tunnel. */ (void) fr_cursor_init(&cursor, &reply->vps); for (vp = fr_cursor_iter_by_da_init(&cursor, &reply->vps, attr_eap_message); vp; vp = fr_cursor_next(&cursor)) { eap_fast_tlv_append(tls_session, attr_eap_fast_eap_payload, true, vp->vp_length, vp->vp_octets); } rcode = RLM_MODULE_HANDLED; break; default: REDEBUG("Unknown RADIUS packet type %d: rejecting tunneled user", reply->code); rcode = RLM_MODULE_INVALID; break; } return rcode; }
static int _virtual_server_free(virtual_server_t *server) { server = talloc_get_type_abort(server, virtual_server_t); if (server->components) rbtree_free(server->components); return 0; }
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; }
static void eap_fast_send_pac_tunnel(REQUEST *request, tls_session_t *tls_session) { eap_fast_tunnel_t *t = talloc_get_type_abort(tls_session->opaque, eap_fast_tunnel_t); eap_fast_pac_t pac; eap_fast_attr_pac_opaque_plaintext_t opaque_plaintext; int alen, dlen; memset(&pac, 0, sizeof(pac)); memset(&opaque_plaintext, 0, sizeof(opaque_plaintext)); RDEBUG2("Sending Tunnel PAC"); pac.key.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_key->attr); pac.key.hdr.length = htons(sizeof(pac.key.data)); rad_assert(sizeof(pac.key.data) % sizeof(uint32_t) == 0); RANDFILL(pac.key.data); pac.info.lifetime.hdr.type = htons(attr_eap_fast_pac_info_pac_lifetime->attr); pac.info.lifetime.hdr.length = htons(sizeof(pac.info.lifetime.data)); pac.info.lifetime.data = htonl(time(NULL) + t->pac_lifetime); pac.info.a_id.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_a_id->attr); pac.info.a_id.hdr.length = htons(sizeof(pac.info.a_id.data)); memcpy(pac.info.a_id.data, t->a_id, sizeof(pac.info.a_id.data)); pac.info.a_id_info.hdr.type = htons(attr_eap_fast_pac_a_id->attr); pac.info.a_id_info.hdr.length = htons(sizeof(pac.info.a_id_info.data)); #define MIN(a,b) (((a)>(b)) ? (b) : (a)) alen = MIN(talloc_array_length(t->authority_identity) - 1, sizeof(pac.info.a_id_info.data)); memcpy(pac.info.a_id_info.data, t->authority_identity, alen); pac.info.type.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_pac_type->attr); pac.info.type.hdr.length = htons(sizeof(pac.info.type.data)); pac.info.type.data = htons(PAC_TYPE_TUNNEL); pac.info.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_info_tlv->attr); pac.info.hdr.length = htons(sizeof(pac.info.lifetime) + sizeof(pac.info.a_id) + sizeof(pac.info.a_id_info) + sizeof(pac.info.type)); memcpy(&opaque_plaintext.type, &pac.info.type, sizeof(opaque_plaintext.type)); memcpy(&opaque_plaintext.lifetime, &pac.info.lifetime, sizeof(opaque_plaintext.lifetime)); memcpy(&opaque_plaintext.key, &pac.key, sizeof(opaque_plaintext.key)); RHEXDUMP(L_DBG_LVL_MAX, (uint8_t const *)&opaque_plaintext, sizeof(opaque_plaintext), "PAC-Opaque plaintext data section"); rad_assert(PAC_A_ID_LENGTH <= EVP_GCM_TLS_TAG_LEN); memcpy(pac.opaque.aad, t->a_id, PAC_A_ID_LENGTH); rad_assert(RAND_bytes(pac.opaque.iv, sizeof(pac.opaque.iv)) != 0); dlen = eap_fast_encrypt((unsigned const char *)&opaque_plaintext, sizeof(opaque_plaintext), t->a_id, PAC_A_ID_LENGTH, t->pac_opaque_key, pac.opaque.iv, pac.opaque.data, pac.opaque.tag); pac.opaque.hdr.type = htons(EAP_FAST_TLV_MANDATORY | attr_eap_fast_pac_opaque_tlv->attr); pac.opaque.hdr.length = htons(sizeof(pac.opaque) - sizeof(pac.opaque.hdr) - sizeof(pac.opaque.data) + dlen); RHEXDUMP(L_DBG_LVL_MAX, (uint8_t const *)&pac.opaque, sizeof(pac.opaque) - sizeof(pac.opaque.data) + dlen, "PAC-Opaque"); eap_fast_tlv_append(tls_session, attr_eap_fast_pac_tlv, true, sizeof(pac) - sizeof(pac.opaque.data) + dlen, &pac); }
/** Send a message on the "best" channel. * * @param nr the network * @param cd the message we've received */ static bool fr_network_send_request(fr_network_t *nr, fr_channel_data_t *cd) { fr_network_worker_t *worker; fr_channel_data_t *reply; (void) talloc_get_type_abort(nr, fr_network_t); if (nr->num_workers == 1) { worker = nr->workers[0]; } else { uint32_t one, two; if (nr->num_workers == 2) { one = 0; two = 1; } else { one = fr_rand() % nr->num_workers; do { two = fr_rand() % nr->num_workers; } while (two == one); } if (nr->workers[one]->cpu_time < nr->workers[two]->cpu_time) { worker = nr->workers[one]; } else { worker = nr->workers[two]; } } (void) talloc_get_type_abort(worker, fr_network_worker_t); /* * Send the message to the channel. If we fail, drop the * packet. The only reason for failure is that the * worker isn't servicing it's input queue. When that * happens, we have no idea what to do, and the whole * thing falls over. */ if (fr_channel_send_request(worker->channel, cd, &reply) < 0) { worker->stats.dropped++; return false; } worker->stats.in++; /* * We're projecting that the worker will use more CPU * time to process this request. The CPU time will be * updated with a more accurate number when we receive a * reply from this channel. */ worker->cpu_time += worker->predicted; /* * If we have a reply, push it onto our local queue, and * poll for more replies. */ if (reply) fr_network_drain_input(nr, worker->channel, reply); return true; }