/** Called by libcurl to register a socket that it's interested in receiving IO events for * * * @param[in] easy handle this fd relates to. * @param[in] fd File descriptor curl wants to be notified about. * @param[in] what Which events libcurl wants to be notified of, may be one of: * - CURL_POLL_IN Wait for incoming data. For the socket * to become readable. * - CURL_POLL_OUT Wait for outgoing data. For the socket * to become writable. * - CURL_POLL_INOUT Wait for incoming and outgoing data. * For the socket to become readable or writable. * - CURL_POLL_REMOVE The specified socket/file descriptor is no * longer used by libcurl. * @param[in] ctx The rlm_rest_thread_t specific to this thread. * @param[in] fd_ctx Private data associated with the socket. */ static int _rest_io_event_modify(UNUSED CURL *easy, curl_socket_t fd, int what, void *ctx, UNUSED void *fd_ctx) { rlm_rest_thread_t *thread = talloc_get_type_abort(ctx, rlm_rest_thread_t); switch (what) { case CURL_POLL_IN: if (fr_event_fd_insert(thread, thread->el, fd, _rest_io_service_readable, NULL, _rest_io_service_errored, thread) < 0) { PERROR("multi-handle %p registration failed for read+error events on FD %i", thread->mandle, fd); return -1; } DEBUG4("multi-handle %p registered for read+error events on FD %i", thread->mandle, fd); break; case CURL_POLL_OUT: if (fr_event_fd_insert(thread, thread->el, fd, NULL, _rest_io_service_writable, _rest_io_service_errored, thread) < 0) { PERROR("multi-handle %p registration failed for write+error events on FD %i", thread->mandle, fd); return -1; } DEBUG4("multi-handle %p registered for write+error events on FD %i", thread->mandle, fd); break; case CURL_POLL_INOUT: if (fr_event_fd_insert(thread, thread->el, fd, _rest_io_service_readable, _rest_io_service_writable, _rest_io_service_errored, thread) < 0) { PERROR("multi-handle %p registration failed for read+write+error events on FD %i", thread->mandle, fd); return -1; } DEBUG4("multi-handle %p registered for read+write+error events on FD %i", thread->mandle, fd); break; case CURL_POLL_REMOVE: if (fr_event_fd_delete(thread->el, fd, FR_EVENT_FILTER_IO) < 0) { PERROR("multi-handle %p de-registration failed for FD %i", thread->mandle, fd); return -1; } DEBUG4("multi-handle %p unregistered events for FD %i", thread->mandle, fd); break; default: rad_assert(0); return -1; } return CURLM_OK; }
/** Set the socket to active * * We have messages we want to send, so need to know when the socket is writable. * * @param[in] t Thread instance containing the connection. */ static void logtee_fd_active(rlm_logtee_thread_t *t) { DEBUG3("Marking socket (%i) as active - Draining requests", fr_connection_get_fd(t->conn)); if (fr_event_fd_insert(t->conn, t->el, fr_connection_get_fd(t->conn), _logtee_conn_read, _logtee_conn_writable, _logtee_conn_error, t) < 0) { PERROR("Failed inserting FD event"); } }
/** Set the socket to idle * * If the other side is sending back garbage, we want to drain it so our buffer doesn't fill up. * * @param[in] t Thread instance containing the connection. */ static void logtee_fd_idle(rlm_logtee_thread_t *t) { DEBUG3("Marking socket (%i) as idle", fr_connection_get_fd(t->conn)); if (fr_event_fd_insert(t->conn, t->el, fr_connection_get_fd(t->conn), _logtee_conn_read, NULL, _logtee_conn_error, t) < 0) { PERROR("Failed inserting FD event"); } }
/** Set a callback for the request. * * Used when a module needs to read from an FD. 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] read callback. Used for receiving and demuxing/decoding data. * @param[in] write callback. Used for writing and encoding data. * Where a 3rd party library is used, this should be the function * issuing queries, and writing data to the socket. This should * not be done in the module itself. * This allows write operations to be retried in some instances, * and means if the write buffer is full, the request is kept in * a suspended state. * @param[in] error callback. If the fd enters an error state. Should cleanup any * handles wrapping the file descriptor, and any outstanding requests. * @param[in] ctx for the callback. * @param[in] fd to watch. * @return * - 0 on success. * - <0 on error. */ int unlang_module_fd_add(REQUEST *request, fr_unlang_module_fd_event_t read, fr_unlang_module_fd_event_t write, fr_unlang_module_fd_event_t error, void const *ctx, int fd) { 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 = fd; ev->fd_read = read; ev->fd_write = write; ev->fd_error = error; ev->inst = sp->module_instance->dl_inst->data; ev->thread = ms->thread; ev->ctx = ctx; /* * Register for events on the file descriptor */ if (fr_event_fd_insert(request, request->el, fd, ev->fd_read ? unlang_event_fd_read_handler : NULL, ev->fd_write ? unlang_event_fd_write_handler : NULL, ev->fd_error ? unlang_event_fd_error_handler: NULL, ev) < 0) { talloc_free(ev); return -1; } (void) request_data_talloc_add(request, ctx, fd, unlang_module_event_t, ev, true, false, false); talloc_set_destructor(ev, _unlang_event_free); 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; }
int main(int argc, char *argv[]) { rs_t *conf; fr_pcap_t *in = NULL, *in_p; fr_pcap_t **in_head = ∈ fr_pcap_t *out = NULL; int ret = 1; /* Exit status */ int limit = -1; /* How many packets to sniff */ char errbuf[PCAP_ERRBUF_SIZE]; /* Error buffer */ int port = 1812; char buffer[1024]; int opt; FR_TOKEN parsecode; char const *radius_dir = RADIUS_DIR; rs_stats_t stats; fr_debug_flag = 2; log_dst = stdout; talloc_set_log_stderr(); conf = talloc_zero(NULL, rs_t); if (!fr_assert(conf)) { exit (1); } /* * We don't really want probes taking down machines */ #ifdef HAVE_TALLOC_SET_MEMLIMIT talloc_set_memlimit(conf, 52428800); /* 50 MB */ #endif /* * Get options */ while ((opt = getopt(argc, argv, "c:d:DFf:hi:I:p:qr:s:Svw:xXW:P:O:")) != EOF) { switch (opt) { case 'c': limit = atoi(optarg); if (limit <= 0) { fprintf(stderr, "radsniff: Invalid number of packets \"%s\"", optarg); exit(1); } break; case 'd': radius_dir = optarg; break; case 'D': { pcap_if_t *all_devices = NULL; pcap_if_t *dev_p; if (pcap_findalldevs(&all_devices, errbuf) < 0) { ERROR("Error getting available capture devices: %s", errbuf); goto finish; } int i = 1; for (dev_p = all_devices; dev_p; dev_p = dev_p->next) { INFO("%i.%s", i++, dev_p->name); } ret = 0; goto finish; } case 'F': conf->from_stdin = true; conf->to_stdout = true; break; case 'f': conf->pcap_filter = optarg; break; case 'h': usage(0); break; case 'i': *in_head = fr_pcap_init(conf, optarg, PCAP_INTERFACE_IN); if (!*in_head) { goto finish; } in_head = &(*in_head)->next; conf->from_dev = true; break; case 'I': *in_head = fr_pcap_init(conf, optarg, PCAP_FILE_IN); if (!*in_head) { goto finish; } in_head = &(*in_head)->next; conf->from_file = true; break; case 'p': port = atoi(optarg); break; case 'q': if (fr_debug_flag > 0) { fr_debug_flag--; } break; case 'r': conf->radius_filter = optarg; break; case 's': conf->radius_secret = optarg; break; case 'S': conf->do_sort = true; break; case 'v': #ifdef HAVE_COLLECTDC_H INFO("%s, %s, collectdclient version %s", radsniff_version, pcap_lib_version(), lcc_version_string()); #else INFO("%s %s", radsniff_version, pcap_lib_version()); #endif exit(0); break; case 'w': out = fr_pcap_init(conf, optarg, PCAP_FILE_OUT); conf->to_file = true; break; case 'x': case 'X': fr_debug_flag++; break; case 'W': conf->stats.interval = atoi(optarg); if (conf->stats.interval <= 0) { ERROR("Stats interval must be > 0"); usage(64); } break; case 'T': conf->stats.timeout = atoi(optarg); if (conf->stats.timeout <= 0) { ERROR("Timeout value must be > 0"); usage(64); } break; #ifdef HAVE_COLLECTDC_H case 'P': conf->stats.prefix = optarg; break; case 'O': conf->stats.collectd = optarg; conf->stats.out = RS_STATS_OUT_COLLECTD; break; #endif default: usage(64); } } /* What's the point in specifying -F ?! */ if (conf->from_stdin && conf->from_file && conf->to_file) { usage(64); } /* Can't read from both... */ if (conf->from_file && conf->from_dev) { usage(64); } /* Reading from file overrides stdin */ if (conf->from_stdin && (conf->from_file || conf->from_dev)) { conf->from_stdin = false; } /* Writing to file overrides stdout */ if (conf->to_file && conf->to_stdout) { conf->to_stdout = false; } if (conf->to_stdout) { out = fr_pcap_init(conf, "stdout", PCAP_STDIO_OUT); if (!out) { goto finish; } } if (conf->from_stdin) { *in_head = fr_pcap_init(conf, "stdin", PCAP_STDIO_IN); if (!*in_head) { goto finish; } in_head = &(*in_head)->next; } if (!conf->radius_secret) { conf->radius_secret = RS_DEFAULT_SECRET; } if (conf->stats.interval && !conf->stats.out) { conf->stats.out = RS_STATS_OUT_STDIO; } if (conf->stats.timeout == 0) { conf->stats.timeout = RS_DEFAULT_TIMEOUT; } /* * If were writing pcap data stdout we *really* don't want to send * logging there as well. */ log_dst = conf->to_stdout ? stderr : stdout; #if !defined(HAVE_PCAP_FOPEN_OFFLINE) || !defined(HAVE_PCAP_DUMP_FOPEN) if (conf->from_stdin || conf->to_stdout) { ERROR("PCAP streams not supported"); goto finish; } #endif if (!conf->pcap_filter) { snprintf(buffer, sizeof(buffer), "udp port %d or %d or %d", port, port + 1, 3799); conf->pcap_filter = buffer; } if (dict_init(radius_dir, RADIUS_DICTIONARY) < 0) { fr_perror("radsniff"); ret = 64; goto finish; } fr_strerror(); /* Clear out any non-fatal errors */ if (conf->radius_filter) { parsecode = userparse(NULL, conf->radius_filter, &filter_vps); if (parsecode == T_OP_INVALID) { ERROR("Invalid RADIUS filter \"%s\" (%s)", conf->radius_filter, fr_strerror()); ret = 64; goto finish; } if (!filter_vps) { ERROR("Empty RADIUS filter \"%s\"", conf->radius_filter); ret = 64; goto finish; } filter_tree = rbtree_create((rbcmp) fr_packet_cmp, _rb_rad_free, 0); if (!filter_tree) { ERROR("Failed creating filter tree"); ret = 64; goto finish; } } /* * Setup the request tree */ request_tree = rbtree_create((rbcmp) fr_packet_cmp, _rb_rad_free, 0); if (!request_tree) { ERROR("Failed creating request tree"); goto finish; } /* * Allocate a null packet for decrypting attributes in CoA requests */ nullpacket = rad_alloc(conf, 0); if (!nullpacket) { ERROR("Out of memory"); goto finish; } /* * Get the default capture device */ if (!conf->from_stdin && !conf->from_file && !conf->from_dev) { pcap_if_t *all_devices; /* List of all devices libpcap can listen on */ pcap_if_t *dev_p; if (pcap_findalldevs(&all_devices, errbuf) < 0) { ERROR("Error getting available capture devices: %s", errbuf); goto finish; } if (!all_devices) { ERROR("No capture files specified and no live interfaces available"); ret = 64; goto finish; } for (dev_p = all_devices; dev_p; dev_p = dev_p->next) { /* Don't use the any devices, it's horribly broken */ if (!strcmp(dev_p->name, "any")) continue; *in_head = fr_pcap_init(conf, dev_p->name, PCAP_INTERFACE_IN); in_head = &(*in_head)->next; } conf->from_auto = true; conf->from_dev = true; INFO("Defaulting to capture on all interfaces"); } /* * Print captures values which will be used */ if (fr_debug_flag > 2) { DEBUG1("Sniffing with options:"); if (conf->from_dev) { char *buff = fr_pcap_device_names(conf, in, ' '); DEBUG1(" Device(s) : [%s]", buff); talloc_free(buff); } if (conf->to_file || conf->to_stdout) { DEBUG1(" Writing to : [%s]", out->name); } if (limit > 0) { DEBUG1(" Capture limit (packets) : [%d]", limit); } DEBUG1(" PCAP filter : [%s]", conf->pcap_filter); DEBUG1(" RADIUS secret : [%s]", conf->radius_secret); if (filter_vps){ DEBUG1(" RADIUS filter :"); vp_printlist(log_dst, filter_vps); } } /* * Open our interface to collectd */ #ifdef HAVE_COLLECTDC_H if (conf->stats.out == RS_STATS_OUT_COLLECTD) { size_t i; rs_stats_tmpl_t *tmpl, **next; if (rs_stats_collectd_open(conf) < 0) { exit(1); } next = &conf->stats.tmpl; for (i = 0; i < (sizeof(rs_useful_codes) / sizeof(*rs_useful_codes)); i++) { tmpl = rs_stats_collectd_init_latency(conf, next, conf, "radius_pkt_ex", &stats.exchange[rs_useful_codes[i]], rs_useful_codes[i]); if (!tmpl) { goto tmpl_error; } next = &(tmpl->next); tmpl = rs_stats_collectd_init_counter(conf, next, conf, "radius_pkt", &stats.gauge.type[rs_useful_codes[i]], rs_useful_codes[i]); if (!tmpl) { tmpl_error: ERROR("Error allocating memory for stats template"); goto finish; } next = &(tmpl->next); } } #endif /* * This actually opens the capture interfaces/files (we just allocated the memory earlier) */ { fr_pcap_t *prev = NULL; for (in_p = in; in_p; in_p = in_p->next) { if (fr_pcap_open(in_p) < 0) { if (!conf->from_auto) { ERROR("Failed opening pcap handle for %s", in_p->name); goto finish; } DEBUG("Failed opening pcap handle: %s", fr_strerror()); /* Unlink it from the list */ if (prev) { prev->next = in_p->next; talloc_free(in_p); in_p = prev; } else { in = in_p->next; talloc_free(in_p); in_p = in; } goto next; } if (conf->pcap_filter) { if (fr_pcap_apply_filter(in_p, conf->pcap_filter) < 0) { ERROR("Failed applying filter"); goto finish; } } next: prev = in_p; } } /* * Open our output interface (if we have one); */ if (out) { if (fr_pcap_open(out) < 0) { ERROR("Failed opening pcap output"); goto finish; } } /* * Setup and enter the main event loop. Who needs libev when you can roll your own... */ { struct timeval now; fr_event_list_t *events; rs_update_t update; char *buff; memset(&stats, 0, sizeof(stats)); memset(&update, 0, sizeof(update)); events = fr_event_list_create(conf, _rs_event_status); if (!events) { ERROR(); goto finish; } for (in_p = in; in_p; in_p = in_p->next) { rs_event_t *event; event = talloc_zero(events, rs_event_t); event->conf = conf; event->in = in_p; event->out = out; event->stats = &stats; if (!fr_event_fd_insert(events, 0, in_p->fd, rs_got_packet, event)) { ERROR("Failed inserting file descriptor"); goto finish; } } buff = fr_pcap_device_names(conf, in, ' '); INFO("Sniffing on (%s)", buff); talloc_free(buff); gettimeofday(&now, NULL); start_pcap = now; /* * Insert our stats processor */ if (conf->stats.interval) { update.list = events; update.conf = conf; update.stats = &stats; update.in = in; now.tv_sec += conf->stats.interval; now.tv_usec = 0; fr_event_insert(events, rs_stats_process, (void *) &update, &now, NULL); } ret = fr_event_loop(events); /* Enter the main event loop */ } INFO("Done sniffing"); finish: if (filter_tree) { rbtree_free(filter_tree); } INFO("Exiting..."); /* * Free all the things! This also closes all the sockets and file descriptors */ talloc_free(conf); return ret; }
/** 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); }
/** Handle a network control message callback for a new socket * * @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_socket_callback(void *ctx, void const *data, size_t data_size, UNUSED fr_time_t now) { fr_network_t *nr = ctx; fr_network_socket_t *s; fr_app_io_t const *app_io; size_t size; int num_messages; rad_assert(data_size == sizeof(s->listen)); if (data_size != sizeof(s->listen)) return; s = talloc_zero(nr, fr_network_socket_t); rad_assert(s != NULL); s->nr = nr; memcpy(&s->listen, data, sizeof(s->listen)); s->number = nr->num_sockets++; MEM(s->waiting = fr_heap_create(s, waiting_cmp, fr_channel_data_t, channel.heap_id)); talloc_set_destructor(s, _network_socket_free); /* * Put reasonable limits on the ring buffer size. Then * round it up to the nearest power of 2, which is * required by the ring buffer code. */ num_messages = s->listen->num_messages; if (num_messages < 8) num_messages = 8; size = s->listen->default_message_size * num_messages; if (!size) size = (1 << 17); /* * Allocate the ring buffer for messages and packets. */ s->ms = fr_message_set_create(s, num_messages, sizeof(fr_channel_data_t), size); if (!s->ms) { fr_log(nr->log, L_ERR, "Failed creating message buffers for network IO: %s", fr_strerror()); talloc_free(s); return; } app_io = s->listen->app_io; rad_assert(app_io->fd); s->fd = app_io->fd(s->listen->app_io_instance); s->filter = FR_EVENT_FILTER_IO; 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 network event loop"); talloc_free(s); return; } if (app_io->event_list_set) app_io->event_list_set(s->listen->app_io_instance, nr->el, nr); (void) rbtree_insert(nr->sockets, s); (void) rbtree_insert(nr->sockets_by_num, s); DEBUG3("Using new socket with FD %d", s->fd); }
/** 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); } }
/** 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); } }
static int mod_instantiate(void *instance, CONF_SECTION *conf) { rlm_unbound_t *inst = instance; int res; char *optval; fr_log_dst_t log_dst; int log_level; int log_fd = -1; char k[64]; /* To silence const warns until newer unbound in distros */ /* * @todo - move this to the thread-instantiate function */ inst->el = fr_global_event_list(); inst->log_pipe_stream[0] = NULL; inst->log_pipe_stream[1] = NULL; inst->log_fd = -1; inst->log_pipe_in_use = false; inst->ub = ub_ctx_create(); if (!inst->ub) { cf_log_err(conf, "ub_ctx_create failed"); return -1; } /* * Note unbound threads WILL happen with -s option, if it matters. * We cannot tell from here whether that option is in effect. */ res = ub_ctx_async(inst->ub, 1); if (res) goto error; /* Glean some default settings to match the main server. */ /* TODO: debug_level can be changed at runtime. */ /* TODO: log until fork when stdout or stderr and !rad_debug_lvl. */ log_level = 0; if (rad_debug_lvl > 0) { log_level = rad_debug_lvl; } else if (main_config->debug_level > 0) { log_level = main_config->debug_level; } switch (log_level) { /* TODO: This will need some tweaking */ case 0: case 1: break; case 2: log_level = 1; break; case 3: case 4: log_level = 2; /* mid-to-heavy levels of output */ break; case 5: case 6: case 7: case 8: log_level = 3; /* Pretty crazy amounts of output */ break; default: log_level = 4; /* Insane amounts of output including crypts */ break; } res = ub_ctx_debuglevel(inst->ub, log_level); if (res) goto error; switch (default_log.dst) { case L_DST_STDOUT: if (!rad_debug_lvl) { log_dst = L_DST_NULL; break; } log_dst = L_DST_STDOUT; log_fd = dup(STDOUT_FILENO); break; case L_DST_STDERR: if (!rad_debug_lvl) { log_dst = L_DST_NULL; break; } log_dst = L_DST_STDOUT; log_fd = dup(STDERR_FILENO); break; case L_DST_FILES: if (main_config->log_file) { char *log_file; strcpy(k, "logfile:"); /* 3rd argument isn't const'd in libunbounds API */ memcpy(&log_file, &main_config->log_file, sizeof(log_file)); res = ub_ctx_set_option(inst->ub, k, log_file); if (res) { goto error; } log_dst = L_DST_FILES; break; } /* FALL-THROUGH */ case L_DST_NULL: log_dst = L_DST_NULL; break; default: log_dst = L_DST_SYSLOG; break; } /* Now load the config file, which can override gleaned settings. */ { char *file; memcpy(&file, &inst->filename, sizeof(file)); res = ub_ctx_config(inst->ub, file); if (res) goto error; } /* * Check if the config file tried to use syslog. Unbound * does not share syslog gracefully. */ strcpy(k, "use-syslog"); res = ub_ctx_get_option(inst->ub, k, &optval); if (res || !optval) goto error; if (!strcmp(optval, "yes")) { char v[3]; free(optval); WARN("Overriding syslog settings"); strcpy(k, "use-syslog:"); strcpy(v, "no"); res = ub_ctx_set_option(inst->ub, k, v); if (res) goto error; if (log_dst == L_DST_FILES) { char *log_file; /* Reinstate the log file name JIC */ strcpy(k, "logfile:"); /* 3rd argument isn't const'd in libunbounds API */ memcpy(&log_file, &main_config->log_file, sizeof(log_file)); res = ub_ctx_set_option(inst->ub, k, log_file); if (res) goto error; } } else { if (optval) free(optval); strcpy(k, "logfile"); res = ub_ctx_get_option(inst->ub, k, &optval); if (res) goto error; if (optval && strlen(optval)) { log_dst = L_DST_FILES; /* * We open log_fd early in the process, * so that libunbound doesn't close * stdout / stderr on us (grrr, stupid * software). But if the config say to * use files, we now have to close the * dup'd FD. */ if (log_fd >= 0) { close(log_fd); log_fd = -1; } } else if (!rad_debug_lvl) { log_dst = L_DST_NULL; } if (optval) free(optval); } switch (log_dst) { case L_DST_STDOUT: /* * We have an fd to log to. And we've already attempted to * dup it so libunbound doesn't close it on us. */ if (log_fd == -1) { cf_log_err(conf, "Could not dup fd"); goto error_nores; } inst->log_stream = fdopen(log_fd, "w"); if (!inst->log_stream) { cf_log_err(conf, "error setting up log stream"); goto error_nores; } res = ub_ctx_debugout(inst->ub, inst->log_stream); if (res) goto error; break; case L_DST_FILES: /* We gave libunbound a filename. It is on its own now. */ break; case L_DST_NULL: /* We tell libunbound not to log at all. */ res = ub_ctx_debugout(inst->ub, NULL); if (res) goto error; break; case L_DST_SYSLOG: /* * Currently this wreaks havoc when running threaded, so just * turn logging off until that gets figured out. */ res = ub_ctx_debugout(inst->ub, NULL); if (res) goto error; break; default: break; } /* * Now we need to finalize the context. * * There's no clean API to just finalize the context made public * in libunbound. But we can trick it by trying to delete data * which as it happens fails quickly and quietly even though the * data did not exist. */ strcpy(k, "notar33lsite.foo123.nottld A 127.0.0.1"); ub_ctx_data_remove(inst->ub, k); inst->log_fd = ub_fd(inst->ub); if (inst->log_fd >= 0) { if (fr_event_fd_insert(inst, inst->el, inst->log_fd, ub_fd_handler, NULL, NULL, inst) < 0) { cf_log_err(conf, "could not insert async fd"); inst->log_fd = -1; goto error_nores; } } return 0; error: cf_log_err(conf, "%s", ub_strerror(res)); error_nores: if (log_fd > -1) close(log_fd); return -1; }