Esempio n. 1
0
/** 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;
}
Esempio n. 2
0
/** 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");
	}
}
Esempio n. 3
0
/** 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");
	}
}
Esempio n. 4
0
/** 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;
}
Esempio n. 5
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;
}
Esempio n. 6
0
int main(int argc, char *argv[])
{
	rs_t *conf;

	fr_pcap_t *in = NULL, *in_p;
	fr_pcap_t **in_head = &in;
	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;
}
Esempio n. 7
0
/** 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);
}
Esempio n. 8
0
/** 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);
}
Esempio n. 9
0
/** 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);
	}
}
Esempio n. 10
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);
	}
}
Esempio n. 11
0
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;
}