/** 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; } }
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; }
/** 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()); } }
/** 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; }
/** 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; }