Пример #1
0
/** De-multiplex incoming LDAP Messages
 *
 * This callback should be called when the LDAP socket is readable.  It drains
 * any outstanding LDAPMessages using sync_demux.  sync_demux in-turn calls one
 * of the following callbacks:
 *	- _proto_ldap_entry		We received a notification an entry
 *					was added, deleted, or modified.
 *	- _proto_ldap_cookie_store	We received a new cookie value.
 *	- _proto_ldap_present		The server wants to perform a refresh present
 *					phase (which we don't allow).
 *	- _proto_ldap_refresh_required	The server wants us to download all content
 *					specified by our sync/search.
 *
 * These callbacks may result in requests being enqueued or syncs restarted.
 *
 * @note We do not currently make enqueuing the cookie requests dependent on all
 *	previous LDAP entries being processed, so we may miss updates in some
 *	circumstances.  This needs to be fixed, but is waiting on v4.0.0
 *	re-architecture.
 *
 * @param[in] listen	encapsulating the libldap socket.
 * @return
 *	- 1 on success.
 *	- 0 on failure.
 */
static int proto_ldap_socket_recv(rad_listen_t *listen)
{
	proto_ldap_inst_t	*inst = talloc_get_type_abort(listen->data, proto_ldap_inst_t);
	int			sync_id;
 	void			*ctx;
 	sync_config_t const	*config;
 	struct timeval		now, when;

	/*
	 *	Demultiplex drains any outstanding messages from the socket,
	 *	and calls the _proto_ldap_entry() callback above to create
	 *	the request..
	 *
	 *	Multiple requests may be created from one call to sync_demux.
	 */
 	switch (sync_demux(&sync_id, inst->conn)) {
 	default:
		return 1;

 	case -1:
		PERROR("Sync failed - will retry in %pT seconds", &inst->sync_retry_interval);

 		config = sync_state_config_get(inst->conn, sync_id);
		sync_state_destroy(inst->conn, sync_id);	/* Destroy the old state */

		/*
		 *	Schedule sync reinit, but don't perform it immediately.
		 */
		memcpy(&ctx, &config, sizeof(ctx));
		gettimeofday(&now, 0);
		fr_timeval_add(&when, &now, &inst->sync_retry_interval);
		if (fr_event_timer_insert(inst, inst->el, &inst->sync_retry_ev,
					  &when, proto_ldap_sync_reinit, ctx) < 0) {
			log_fatal("Failed inserting event: %s", fr_strerror());
		}
 		return 1;

 	case -2:
 		PERROR("Connection failed - will retry in %pT seconds", &inst->conn_retry_interval);

		/*
		 *	Schedule conn reinit, but don't perform it immediately
		 */
 		memcpy(&ctx, &config, sizeof(ctx));
		gettimeofday(&now, 0);
		fr_timeval_add(&when, &now, &inst->conn_retry_interval);
		if (fr_event_timer_insert(inst, inst->el, &inst->conn_retry_ev,
					  &when, proto_ldap_connection_init, listen) < 0) {
			log_fatal("Failed inserting event: %s", fr_strerror());
		}

 		return 0;
 	}
}
Пример #2
0
static int delay_add(REQUEST *request, struct timeval *resume_at, struct timeval *now,
		     struct timeval *delay, bool force_reschedule, bool relative)
{
	int		cmp;

	/*
	 *	Delay is zero (and reschedule is not forced)
	 */
	if (!force_reschedule && (delay->tv_sec == 0) && (delay->tv_usec == 0)) return 1;

	/*
	 *	Process the delay relative to the start of packet processing
	 */
	if (relative) {
		fr_timeval_add(resume_at, &request->packet->timestamp, delay);
	} else {
		fr_timeval_add(resume_at, now, delay);
	}

	/*
	 *	If resume_at is in the past (and reschedule is not forced), just return noop
	 */
	cmp = fr_timeval_cmp(now, resume_at);
	if (!force_reschedule && (cmp >= 0)) return 1;

	if (cmp < 0) {
		struct timeval delay_by;

		fr_timeval_subtract(&delay_by, resume_at, now);

		RDEBUG2("Delaying request by ~%pVs", fr_box_timeval(delay_by));
	} else {
		RDEBUG2("Rescheduling request");
	}

	return 0;
}
Пример #3
0
/** Attempt to reinitialise a sync
 *
 * It's perfectly fine to re-initialise individual sync without tearing down the
 * connection completely.
 *
 * @param[in] el	the event list managing listen event.
 * @param[in] now	current time.
 * @param[in] user_ctx	Sync config.
 */
static void proto_ldap_sync_reinit(fr_event_list_t *el, struct timeval *now, void *user_ctx)
{
	sync_config_t		*config = talloc_get_type_abort(user_ctx, sync_config_t);
	proto_ldap_inst_t	*inst = talloc_get_type_abort(config->user_ctx, proto_ldap_inst_t);
	struct timeval		when;

	/*
	 *	Reinitialise the sync
	 */
	if (sync_state_init(inst->conn, config, NULL, true) == 0) return;

	PERROR("Failed reinitialising sync, will retry in %pT seconds", &inst->sync_retry_interval);

	fr_timeval_add(&when, now, &inst->sync_retry_interval);
	if (fr_event_timer_insert(inst, el, &inst->sync_retry_ev,
				  &when, proto_ldap_sync_reinit, user_ctx) < 0) {
		log_fatal("Failed inserting event: %s", fr_strerror());
	}
}
Пример #4
0
/** 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;
}
Пример #5
0
/** Open a handle to the LDAP directory
 *
 * @note This is performed synchronously.
 *
 * @param[in] cs	specifying the listener configuration.
 * @param[in] listen	structure encapsulating the libldap socket.
 * @return
 *	- 0 on success.
 *	- -1 on error.
 */
static int proto_ldap_socket_open(UNUSED CONF_SECTION *cs, rad_listen_t *listen)
{
	proto_ldap_inst_t		*inst = listen->data;
	fr_ldap_rcode_t			status;
	size_t				i;

	struct sockaddr_storage		addr;
	socklen_t			len = sizeof(addr);

	/*
	 *	Fixme - Should be the network thread's event loop?
	 */
	inst->el = fr_global_event_list();

	/*
	 *	Destroys any existing syncs and connections
	 */
	TALLOC_FREE(inst->conn);

	/*
	 *	Allocate a brand-new connection
	 */
	inst->conn = fr_ldap_connection_alloc(inst);
	if (!inst->conn) goto error;

	if (fr_ldap_connection_configure(inst->conn, &inst->handle_config) < 0) goto error;

	if (inst->conn->config->start_tls) {
		if (ldap_start_tls_s(inst->conn->handle, NULL, NULL) != LDAP_SUCCESS) {
			int		ldap_errno;
			struct timeval	now, when;

			gettimeofday(&now, NULL);

			ldap_get_option(inst->conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);

			ERROR("Failed starting TLS: %s", ldap_err2string(ldap_errno));

		error:
			TALLOC_FREE(inst->conn);

			PERROR("Failed (re)initialising connection, will retry in %pT seconds",
			      &inst->conn_retry_interval);

			fr_timeval_add(&when, &now, &inst->conn_retry_interval);

			if (fr_event_timer_insert(inst, inst->el, &inst->conn_retry_ev,
						  &when, proto_ldap_connection_init, listen) < 0) {
				log_fatal("Failed inserting event: %s", fr_strerror());
			}

			return -1;
		}
	}

	status = fr_ldap_bind(NULL,
			      &inst->conn,
			      inst->conn->config->admin_identity, inst->conn->config->admin_password,
			      &(inst->conn->config->admin_sasl),
			      NULL,
			      NULL, NULL);
	if (status != LDAP_PROC_SUCCESS) goto error;

	/*
	 *	We need to know the directory type so we can synthesize cookies
	 */
	if (fr_ldap_directory_alloc(inst->conn, &inst->conn->directory, &inst->conn) < 0) goto error;

	if (ldap_get_option(inst->conn->handle, LDAP_OPT_DESC, &listen->fd) != LDAP_OPT_SUCCESS) {
		int ldap_errno;

		ldap_get_option(inst->conn->handle, LDAP_OPT_ERROR_NUMBER, &ldap_errno);

		ERROR("Failed retrieving file descriptor from LDAP handle: %s", ldap_err2string(ldap_errno));
		goto error;
	}

	/*
	 *	Work back to get src/dst ip address and ports from the file descriptor
	 */
	if (getsockname(listen->fd, (struct sockaddr *)&addr, &len) < 0) {
		ERROR("Failed getting socket information: %s", fr_syserror(errno));
		goto error;
	}
	fr_ipaddr_from_sockaddr(&addr, len, &inst->src_ipaddr, &inst->src_port);

	if (getpeername(listen->fd, (struct sockaddr *)&addr, &len) < 0) {
		ERROR("Failed getting socket information: %s", fr_syserror(errno));
		goto error;
	}

	/*
	 *	Allocate a fake client to use in requests
	 */
	fr_ipaddr_from_sockaddr(&addr, len, &inst->dst_ipaddr, &inst->dst_port);
	inst->client = proto_ldap_fake_client_alloc(inst);

	DEBUG2("Starting sync(s)");
	for (i = 0; i < talloc_array_length(inst->sync_config); i++) {
		uint8_t *cookie;
		int	ret;

		/*
		 *	Synchronously load the cookie... ewww
		 */
		if (proto_ldap_cookie_load(inst, &cookie, listen, inst->sync_config[i]) < 0) goto error;
		ret = sync_state_init(inst->conn, inst->sync_config[i], cookie, false);
		talloc_free(cookie);
		if (ret < 0) goto error;
	}

	return 0;
}
static void work_init(proto_detail_file_thread_t *thread)
{
	proto_detail_file_t const *inst = thread->inst;
	int fd, rcode;
	bool has_worker;

	pthread_mutex_lock(&thread->worker_mutex);
	has_worker = (thread->num_workers != 0);
	pthread_mutex_unlock(&thread->worker_mutex);

	/*
	 *	The worker is still processing the file, poll until
	 *	it's done.
	 */
	if (has_worker) {
		DEBUG3("proto_detail (%s): worker %s is still alive, waiting for it to finish.",
		       thread->name, inst->filename_work);
		goto delay;
	}

	rad_assert(thread->vnode_fd < 0);

	/*
	 *	See if there is a "detail.work" file.  If not, try to
	 *	rename an existing file to "detail.work".
	 */
	DEBUG3("Trying to open %s", inst->filename_work);
	fd = open(inst->filename_work, inst->mode);

	/*
	 *	If the work file didn't exist, try to rename detail* ->
	 *	detail.work, and return the newly opened file.
	 */
	if (fd < 0) {
		if (errno != ENOENT) {
			DEBUG("proto_detail (%s): Failed opening %s: %s",
			      thread->name, inst->filename_work,
			      fr_syserror(errno));
			goto delay;
		}

retry:
		fd = work_rename(thread);
	}

	/*
	 *	The work file still doesn't exist.  Go set up timers,
	 *	or wait for an event which signals us that something
	 *	in the directory changed.
	 */
	if (fd < 0) {
		struct timeval when, now;

		/*
		 *	Wait for the directory to change before
		 *	looking for another "detail" file.
		 */
		if (!inst->poll_interval) return;

delay:
		/*
		 *	Check every N seconds.
		 */
		when.tv_sec = inst->poll_interval;
		when.tv_usec = 0;

		DEBUG3("Waiting %d.%06ds for new files in %s",
		       (int) when.tv_sec, (int) when.tv_usec, thread->name);

		gettimeofday(&now, NULL);

		fr_timeval_add(&when, &when, &now);

		if (fr_event_timer_insert(thread, thread->el, &thread->ev,
					  &when, work_retry_timer, thread) < 0) {
			ERROR("Failed inserting poll timer for %s", inst->filename_work);
		}
		return;
	}

	thread->lock_interval = USEC / 10;

	/*
	 *	It exists, go process it!
	 *
	 *	We will get back to the main loop when the
	 *	"detail.work" file is deleted.
	 */
	rcode = work_exists(thread, fd);
	if (rcode < 0) goto delay;

	/*
	 *	The file was empty, so we try to get another one.
	 */
	if (rcode == 1) goto retry;

	/*
	 *	Otherwise the child is successfully processing the
	 *	file.
	 */
}
/*
 *	The "detail.work" file exists, and is open in the 'fd'.
 */
static int work_exists(proto_detail_file_thread_t *thread, int fd)
{
	proto_detail_file_t const *inst = thread->inst;
	bool			opened = false;
	proto_detail_work_thread_t     *work;
	fr_listen_t		*li = NULL;
	struct stat		st;

	fr_event_vnode_func_t	funcs = { .delete = mod_vnode_delete };

	DEBUG3("proto_detail (%s): Trying to lock %s", thread->name, inst->filename_work);

	/*
	 *	"detail.work" exists, try to lock it.
	 */
	if (rad_lockfd_nonblock(fd, 0) < 0) {
		struct timeval when, now;

		DEBUG3("proto_detail (%s): Failed locking %s: %s",
		       thread->name, inst->filename_work, fr_syserror(errno));

		close(fd);

		when.tv_usec = thread->lock_interval % USEC;
		when.tv_sec = thread->lock_interval / USEC;

		/*
		 *	Ensure that we don't do massive busy-polling.
		 */
		thread->lock_interval += thread->lock_interval / 2;
		if (thread->lock_interval > (30 * USEC)) thread->lock_interval = 30 * USEC;

		DEBUG3("proto_detail (%s): Waiting %d.%06ds for lock on file %s",
		       thread->name, (int) when.tv_sec, (int) when.tv_usec, inst->filename_work);

		gettimeofday(&now, NULL);
		fr_timeval_add(&when, &when, &now);

		if (fr_event_timer_insert(thread, thread->el, &thread->ev,
					  &when, work_retry_timer, thread) < 0) {
			ERROR("Failed inserting retry timer for %s", inst->filename_work);
		}
		return 0;
	}

	DEBUG3("proto_detail (%s): Obtained lock and starting to process file %s",
	       thread->name, inst->filename_work);

	/*
	 *	Ignore empty files.
	 */
	if (fstat(fd, &st) < 0) {
		ERROR("Failed opening %s: %s", inst->filename_work,
		      fr_syserror(errno));
		unlink(inst->filename_work);
		close(fd);
		return 1;
	}

	if (!st.st_size) {
		DEBUG3("proto_detail (%s): %s file is empty, ignoring it.",
		       thread->name, inst->filename_work);
		unlink(inst->filename_work);
		close(fd);
		return 1;
	}

	/*
	 *	This listener is allocated in a thread-specific
	 *	context, so it doesn't need a destructor,
	 */
	MEM(li = talloc_zero(NULL, fr_listen_t));

	/*
	 *	Create a new listener, and insert it into the
	 *	scheduler.  Shamelessly copied from proto_detail.c
	 *	mod_open(), with changes.
	 *
	 *	This listener is parented from the worker.  So that
	 *	when the worker goes away, so does the listener.
	 */
	li->app_io = inst->parent->work_io;

	li->app = inst->parent->self;
	li->app_instance = inst->parent;
	li->server_cs = inst->parent->server_cs;

	/*
	 *	The worker may be in a different thread, so avoid
	 *	talloc threading issues by using a NULL TALLOC_CTX.
	 */
	MEM(li->thread_instance = work = talloc_zero(li, proto_detail_work_thread_t));

	li->app_io_instance = inst->parent->work_io_instance;
	work->inst = li->app_io_instance;
	work->file_parent = thread;
	work->ev = NULL;

	li->fd = work->fd = dup(fd);
	if (work->fd < 0) {
		DEBUG("proto_detail (%s): Failed opening %s: %s",
		      thread->name, inst->filename_work, fr_syserror(errno));

		close(fd);
		talloc_free(li);
		return -1;
	}

	/*
	 *	Don't do anything until the file has been deleted.
	 *
	 *	@todo - ensure that proto_detail_work is done the file...
	 *	maybe by creating a new instance?
	 */
	if (fr_event_filter_insert(thread, thread->el, fd, FR_EVENT_FILTER_VNODE,
				   &funcs, NULL, thread) < 0) {
		PERROR("Failed adding work socket to event loop");
		close(fd);
		talloc_free(li);
		return -1;
	}

	/*
	 *	Remember this for later.
	 */
	thread->vnode_fd = fd;

	/*
	 *	For us, this is the worker listener.
	 *	For the worker, this is it's own parent
	 */
	thread->listen = li;

	work->filename_work = talloc_strdup(work, inst->filename_work);

	/*
	 *	Set configurable parameters for message ring buffer.
	 */
	li->default_message_size = inst->parent->max_packet_size;
	li->num_messages = inst->parent->num_messages;

	pthread_mutex_lock(&thread->worker_mutex);
	thread->num_workers++;
	pthread_mutex_unlock(&thread->worker_mutex);

	/*
	 *	Open the detail.work file.
	 */
	if (li->app_io->open(li) < 0) {
		ERROR("Failed opening %s", li->app_io->name);
		goto error;
	}
	opened = true;

	rad_assert(li->app_io->get_name);
	li->name = li->app_io->get_name(li);

	if (!fr_schedule_listen_add(inst->parent->sc, li)) {
	error:
		if (fr_event_fd_delete(thread->el, thread->vnode_fd, FR_EVENT_FILTER_VNODE) < 0) {
			PERROR("Failed removing DELETE callback when opening work file");
		}
		close(thread->vnode_fd);
		thread->vnode_fd = -1;

		if (opened) {
			(void) li->app_io->close(li);
			thread->listen = NULL;
			li = NULL;
		}

		talloc_free(li);
		return -1;
	}

	/*
	 *	Tell the worker to clean itself up.
	 */
	work->listen = li;

	return 0;
}