static void worker_start (void) { worker_t *handler = calloc (1, sizeof(worker_t)); worker_control_create (&handler->wakeup_fd[0]); handler->pending_clients_tail = &handler->pending_clients; thread_spin_create (&handler->lock); handler->last_p = &handler->clients; thread_rwlock_wlock (&workers_lock); if (worker_incoming == NULL) { worker_incoming = handler; handler->thread = thread_create ("worker", worker, handler, THREAD_ATTACHED); thread_rwlock_unlock (&workers_lock); INFO0 ("starting incoming worker thread"); worker_start(); // single level recursion, just get a special worker thread set up return; } handler->next = workers; workers = handler; worker_count++; worker_least_used = worker_balance_to_check = workers; thread_rwlock_unlock (&workers_lock); handler->thread = thread_create ("worker", worker, handler, THREAD_ATTACHED); }
static int command_move_clients (client_t *client, source_t *source, int response) { const char *dest_source; xmlDocPtr doc; xmlNodePtr node; int parameters_passed = 0; char buf[255]; if((COMMAND_OPTIONAL(client, "destination", dest_source))) { parameters_passed = 1; } if (!parameters_passed) { doc = admin_build_sourcelist(source->mount, 0); thread_rwlock_unlock (&source->lock); return admin_send_response(doc, client, response, "moveclients.xsl"); } INFO2 ("source is \"%s\", destination is \"%s\"", source->mount, dest_source); doc = xmlNewDoc(XMLSTR("1.0")); node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL); xmlDocSetRootElement(doc, node); source_set_fallback (source, dest_source); source->termination_count = source->listeners; source->flags |= SOURCE_LISTENERS_SYNC; snprintf (buf, sizeof(buf), "Clients moved from %s to %s", source->mount, dest_source); thread_rwlock_unlock (&source->lock); xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf)); xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1")); return admin_send_response (doc, client, response, "response.xsl"); }
int xslt_transform (xmlDocPtr doc, const char *xslfilename, client_t *client) { int i, ret; xsl_req *x; thread_rwlock_rlock (&xslt_lock); i = xslt_cached (xslfilename, NULL, client->worker->current_time.tv_sec); i = xslt_req_sheet (client, doc, xslfilename, i); x = client->shared_data; switch (i) { case -1: thread_rwlock_unlock (&xslt_lock); xmlFreeDoc (doc); client->shared_data = NULL; ret = client_send_404 (client, "Could not parse XSLT file"); break; case CACHESIZE: // delayed thread_rwlock_unlock (&xslt_lock); return 0; default: // found it and ok to use ret = xslt_send_sheet (client, doc, i); break; } if (x) { free (x->cache.filename); free (x->cache.disposition); free (x); } return ret; }
static auth_result htpasswd_auth (auth_client *auth_user) { auth_t *auth = auth_user->client->auth; htpasswd_auth_state *htpasswd = auth->state; client_t *client = auth_user->client; htpasswd_user entry; void *result; if (client->username == NULL || client->password == NULL) return AUTH_FAILED; htpasswd_recheckfile (htpasswd); thread_rwlock_rlock (&htpasswd->file_rwlock); entry.name = client->username; if (avl_get_by_key (htpasswd->users, &entry, &result) == 0) { htpasswd_user *found = result; char *hashed_pw; thread_rwlock_unlock (&htpasswd->file_rwlock); hashed_pw = get_hash (client->password, strlen (client->password)); if (strcmp (found->pass, hashed_pw) == 0) { free (hashed_pw); return AUTH_OK; } free (hashed_pw); DEBUG0 ("incorrect password for client"); return AUTH_FAILED; } DEBUG1 ("no such username: %s", client->username); thread_rwlock_unlock (&htpasswd->file_rwlock); return AUTH_FAILED; }
static void *yp_update_thread(void *arg) { if (!kitsune_is_updating()) { /**DSU control */ INFO0("YP update thread started"); yp_running = 1; } while (yp_running) { kitsune_update("yp_update"); /**DSU updatepoint */ struct yp_server *server; thread_sleep (200000); /* do the YP communication */ thread_rwlock_rlock (&yp_lock); server = (struct yp_server *)active_yps; while (server) { /* DEBUG1 ("trying %s", server->url); */ yp_process_server (server); server = server->next; } thread_rwlock_unlock (&yp_lock); /* update the local YP structure */ if (yp_update) { thread_rwlock_wlock (&yp_lock); check_servers (); server = (struct yp_server *)active_yps; while (server) { /* DEBUG1 ("Checking yps %s", server->url); */ add_pending_yp (server); delete_marked_yp (server); server = server->next; } yp_update = 0; thread_rwlock_unlock (&yp_lock); } } thread_rwlock_destroy (&yp_lock); thread_mutex_destroy (&yp_pending_lock); /* free server and ypdata left */ while (active_yps) { struct yp_server *server = (struct yp_server *)active_yps; active_yps = server->next; destroy_yp_server (server); } return NULL; }
/* Not efficient; opens and scans the entire file for every request */ static auth_result htpasswd_auth(auth_t *auth, source_t *source, char *username, char *password) { htpasswd_auth_state *state = auth->state; FILE *passwdfile = NULL; char line[MAX_LINE_LEN]; char *sep; thread_rwlock_rlock(&state->file_rwlock); if (!state->allow_duplicate_users) { if (auth_is_listener_connected(source, username)) { thread_rwlock_unlock(&state->file_rwlock); return AUTH_FORBIDDEN; } } passwdfile = fopen(state->filename, "rb"); if(passwdfile == NULL) { WARN2("Failed to open authentication database \"%s\": %s", state->filename, strerror(errno)); thread_rwlock_unlock(&state->file_rwlock); return AUTH_FAILED; } while(get_line(passwdfile, line, MAX_LINE_LEN)) { if(!line[0] || line[0] == '#') continue; sep = strchr(line, ':'); if(sep == NULL) { DEBUG0("No seperator in line"); continue; } *sep = 0; if(!strcmp(username, line)) { /* Found our user, now: does the hash of password match hash? */ char *hash = sep+1; char *hashed_password = get_hash(password, strlen(password)); if(!strcmp(hash, hashed_password)) { fclose(passwdfile); free(hashed_password); thread_rwlock_unlock(&state->file_rwlock); return AUTH_OK; } free(hashed_password); /* We don't keep searching through the file */ break; } } fclose(passwdfile); thread_rwlock_unlock(&state->file_rwlock); return AUTH_FAILED; }
/* Mark an existing entry in the YP list as to be marked for deletion */ void yp_remove (const char *mount) { struct yp_server *server = (struct yp_server *)active_yps; thread_rwlock_rlock (&yp_lock); while (server) { ypdata_t *list = server->mounts; while (1) { ypdata_t *yp = find_yp_mount (list, mount); if (yp == NULL) break; if (yp->release || yp->remove) { list = yp->next; continue; /* search again these are old entries */ } DEBUG2 ("release %s on YP %s", mount, server->url); yp->release = 1; yp->next_update = 0; yp_update = 1; } server = server->next; } thread_rwlock_unlock (&yp_lock); }
static int directory_recheck (client_t *client) { int ret = -1; thread_rwlock_rlock (&yp_lock); do { if (ypclient.connection.error) break; if (active_yps || yp_update) { ret = 0; if (yp_update || active_yps->mounts) { if (yp_update || client->counter <= client->worker->current_time.tv_sec) { client->counter = (uint64_t)-1; client->flags &= ~CLIENT_ACTIVE; thread_create ("YP Thread", yp_update_thread, NULL, THREAD_DETACHED); break; } } } client->schedule_ms = client->worker->time_ms + 1000; } while (0); thread_rwlock_unlock (&yp_lock); return ret; }
static void worker_stop (void) { worker_t *handler; if (workers == NULL) return; thread_rwlock_wlock (&workers_lock); handler = workers; workers = handler->next; worker_least_used = worker_balance_to_check = workers; if (workers) workers->move_allocations = 100; worker_count--; thread_rwlock_unlock (&workers_lock); handler->running = 0; worker_wakeup (handler); thread_join (handler->thread); thread_spin_destroy (&handler->lock); sock_close (handler->wakeup_fd[1]); sock_close (handler->wakeup_fd[0]); free (handler); }
void connection_accept_loop(void) { connection_t *con; _build_pool(); while (global.running == ICE_RUNNING) { if (global . schedule_config_reread) { /* reread config file */ INFO0("Scheduling config reread ..."); connection_inject_event(EVENT_CONFIG_READ, NULL); global . schedule_config_reread = 0; } con = _accept_connection(); if (con) { _add_connection(con); } } /* Give all the other threads notification to shut down */ thread_cond_broadcast(&global.shutdown_cond); _destroy_pool(); /* wait for all the sources to shutdown */ thread_rwlock_wlock(&_source_shutdown_rwlock); thread_rwlock_unlock(&_source_shutdown_rwlock); }
/* Add YP entries to active servers */ void yp_add (const char *mount) { struct yp_server *server; /* make sure YP thread is not modifying the lists */ thread_rwlock_rlock (&yp_lock); /* make sure we don't race against another yp_add */ thread_mutex_lock (&yp_pending_lock); server = (struct yp_server *)active_yps; while (server) { ypdata_t *yp; /* add new ypdata to each servers pending yp */ if ((yp = create_yp_entry (mount)) != NULL) { DEBUG2 ("Adding %s to %s", mount, server->url); yp->server = server; yp->touch_interval = server->touch_interval; yp->next = server->pending_mounts; yp->next_update = time(NULL) + 5; server->pending_mounts = yp; yp_update = 1; } server = server->next; } thread_mutex_unlock (&yp_pending_lock); thread_rwlock_unlock (&yp_lock); }
/* This is similar to yp_remove, but we force a touch * attempt */ void yp_touch (const char *mount) { struct yp_server *server = (struct yp_server *)active_yps; ypdata_t *search_list = NULL; thread_rwlock_rlock (&yp_lock); if (server) search_list = server->mounts; while (server) { ypdata_t *yp = find_yp_mount (search_list, mount); if (yp) { /* we may of found old entries not purged yet, so skip them */ if (yp->release != 0 || yp->remove != 0) { search_list = yp->next; continue; } /* don't update the directory if there is a touch scheduled soon */ if (yp->process == do_yp_touch && now + yp->touch_interval - yp->next_update > 60) yp_schedule (yp, 0); } server = server->next; if (server) search_list = server->mounts; } thread_rwlock_unlock (&yp_lock); }
static int command_fallback (client_t *client, source_t *source, int response) { char *mount = strdup (source->mount); mount_proxy *mountinfo; ice_config_t *config; thread_rwlock_unlock (&source->lock); DEBUG0("Got fallback request"); config = config_grab_config(); mountinfo = config_find_mount (config, mount); free (mount); if (mountinfo) { const char *fallback; char buffer[200]; if (COMMAND_REQUIRE(client, "fallback", fallback) < 0) return client_send_400 (client, "missing arg, fallback"); xmlFree (mountinfo->fallback_mount); mountinfo->fallback_mount = (char *)xmlCharStrdup (fallback); snprintf (buffer, sizeof (buffer), "Fallback for \"%s\" configured", mountinfo->mountname); config_release_config (); return html_success (client, buffer); } config_release_config (); return client_send_400 (client, "no mount details available"); }
/* This is similar to yp_remove, but we force a touch * attempt */ void yp_touch (const char *mount) { struct yp_server *server = (struct yp_server *)active_yps; time_t trigger; ypdata_t *search_list = NULL; thread_rwlock_rlock (&yp_lock); /* do update in 3 secs, give stats chance to update */ trigger = time(NULL) + 3; if (server) search_list = server->mounts; while (server) { ypdata_t *yp = find_yp_mount (search_list, mount); if (yp) { /* we may of found old entries not purged yet, so skip them */ if (yp->release != 0 || yp->remove != 0) { search_list = yp->next; continue; } /* only force if touch */ if (yp->process == do_yp_touch) yp->next_update = trigger; } server = server->next; if (server) search_list = server->mounts; } thread_rwlock_unlock (&yp_lock); }
static int command_show_listeners (client_t *client, source_t *source, int response) { xmlDocPtr doc; xmlNodePtr node, srcnode; uint64_t id = -1; const char *ID_str = NULL; char buf[22]; doc = xmlNewDoc(XMLSTR("1.0")); node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL); srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL); xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount)); xmlDocSetRootElement(doc, node); snprintf(buf, sizeof(buf), "%lu", source->listeners); xmlNewChild(srcnode, NULL, XMLSTR("listeners"), XMLSTR(buf)); COMMAND_OPTIONAL(client, "id", ID_str); if (ID_str) sscanf (ID_str, "%" SCNu64, &id); if (id == -1) admin_source_listeners (source, srcnode); else { client_t *listener = source_find_client (source, id); if (listener) stats_listener_to_xml (listener, srcnode); } thread_rwlock_unlock (&source->lock); return admin_send_response (doc, client, response, "listclients.xsl"); }
void yp_recheck_config (ice_config_t *config) { int i; struct yp_server *server; DEBUG0("Updating YP configuration"); thread_rwlock_rlock (&yp_lock); server = (struct yp_server *)active_yps; while (server) { server->remove = 1; server = server->next; } /* for each yp url in config, check to see if one exists if not, then add it. */ for (i=0 ; i < config->num_yp_directories; i++) { server = find_yp_server (config->yp_url[i]); if (server == NULL) { server = calloc (1, sizeof (struct yp_server)); if (server == NULL) { destroy_yp_server (server); break; } server->url = strdup (config->yp_url[i]); server->url_timeout = config->yp_url_timeout[i]; server->touch_interval = config->yp_touch_interval[i]; server->curl = curl_easy_init(); if (server->curl == NULL) { destroy_yp_server (server); break; } if (server->touch_interval < 30) server->touch_interval = 30; curl_easy_setopt (server->curl, CURLOPT_URL, server->url); curl_easy_setopt (server->curl, CURLOPT_HEADERFUNCTION, handle_returned_header); curl_easy_setopt (server->curl, CURLOPT_WRITEFUNCTION, handle_returned_data); curl_easy_setopt (server->curl, CURLOPT_WRITEDATA, server->curl); curl_easy_setopt (server->curl, CURLOPT_TIMEOUT, server->url_timeout); curl_easy_setopt (server->curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt (server->curl, CURLOPT_ERRORBUFFER, &(server->curl_error[0])); server->next = (struct yp_server *)pending_yps; pending_yps = server; INFO3 ("Adding new YP server \"%s\" (timeout %ds, default interval %ds)", server->url, server->url_timeout, server->touch_interval); } else { server->remove = 0; } } thread_rwlock_unlock (&yp_lock); yp_update = 1; }
static void *yp_update_thread(void *arg) { struct yp_server *server; yp_thread = thread_self(); /* DEBUG0("YP thread started"); */ /* do the YP communication */ thread_rwlock_rlock (&yp_lock); ypclient.counter = -1; server = (struct yp_server *)active_yps; while (server) { /* DEBUG1 ("trying %s", server->url); */ yp_process_server (server); server = server->next; } thread_rwlock_unlock (&yp_lock); /* update the local YP structure */ if (yp_update) { thread_rwlock_wlock (&yp_lock); check_servers (); server = (struct yp_server *)active_yps; while (server) { /* DEBUG1 ("Checking yps %s", server->url); */ add_pending_yp (server); delete_marked_yp (server); server = server->next; } yp_update = 0; thread_rwlock_unlock (&yp_lock); } yp_thread = NULL; /* DEBUG0("YP thread shutdown"); */ ypclient.flags |= CLIENT_ACTIVE; worker_wakeup (ypclient.worker); return NULL; }
void auth_shutdown (void) { if (allow_auth == 0) return; allow_auth = 0; thread_rwlock_wlock (&auth_lock); thread_rwlock_unlock (&auth_lock); thread_rwlock_destroy (&auth_lock); INFO0 ("Auth shutdown complete"); }
int main(void) { int rv; thread_rwlock_t *thread_rwlock = NULL; rv = thread_rwlock_create(&thread_rwlock); rv = thread_rwlock_wrlock(thread_rwlock); rv = thread_rwlock_unlock(thread_rwlock); thread_rwlock_destroy(thread_rwlock); return 0; }
static int command_kill_client (client_t *client, source_t *source, int response) { const char *idtext; uint64_t id; client_t *listener; xmlDocPtr doc; xmlNodePtr node; char buf[50] = ""; if (COMMAND_REQUIRE(client, "id", idtext) < 0) { thread_rwlock_unlock (&source->lock); return client_send_400 (client, "missing arg, id"); } sscanf (idtext, "%" SCNu64, &id); listener = source_find_client(source, id); doc = xmlNewDoc(XMLSTR("1.0")); node = xmlNewDocNode(doc, NULL, XMLSTR("iceresponse"), NULL); xmlDocSetRootElement(doc, node); if(listener != NULL) { INFO1("Admin request: client %d removed", id); /* This tags it for removal on the next iteration of the main source * loop */ listener->connection.error = 1; snprintf(buf, sizeof(buf), "Client %" PRIu64 " removed", id); xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf)); xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("1")); } else { snprintf(buf, sizeof(buf), "Client %" PRIu64 " not found", id); xmlNewChild(node, NULL, XMLSTR("message"), XMLSTR(buf)); xmlNewChild(node, NULL, XMLSTR("return"), XMLSTR("0")); } thread_rwlock_unlock (&source->lock); return admin_send_response (doc, client, response, "response.xsl"); }
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock) { if (rwlock == NULL) return_errno(EINVAL, EINVAL); if (*rwlock == PTHREAD_RWLOCK_INITIALIZER) if (pthread_rwlock_init(rwlock, NULL) != OK) return errno; if (!thread_rwlock_unlock((rwlock_t *)(*rwlock))) return errno; return OK; }
/* Check for changes in the YP servers configured */ static void check_servers (void) { struct yp_server *server = (struct yp_server *)active_yps, **server_p = (struct yp_server **)&active_yps; while (server) { if (server->remove) { struct yp_server *to_go = server; DEBUG1 ("YP server \"%s\"removed", server->url); *server_p = server->next; server = server->next; destroy_yp_server (to_go); continue; } server_p = &server->next; server = server->next; } /* add new server entries */ while (pending_yps) { avl_node *node; server = (struct yp_server *)pending_yps; pending_yps = server->next; DEBUG1("Add pending yps %s", server->url); server->next = (struct yp_server *)active_yps; active_yps = server; /* new YP server configured, need to populate with existing sources */ avl_tree_rlock (global.source_tree); node = avl_get_first (global.source_tree); while (node) { ypdata_t *yp; source_t *source = node->key; thread_rwlock_rlock (&source->lock); if (source->yp_public && (yp = create_yp_entry (source->mount)) != NULL) { DEBUG1 ("Adding existing mount %s", source->mount); yp->server = server; yp->touch_interval = server->touch_interval; yp->next = server->mounts; server->mounts = yp; } thread_rwlock_unlock (&source->lock); node = avl_get_next (node); } avl_tree_unlock (global.source_tree); } }
static int command_show_image (client_t *client, const char *mount) { source_t *source; avl_tree_rlock (global.source_tree); source = source_find_mount_raw (mount); if (source && source->format && source->format->get_image) { thread_rwlock_rlock (&source->lock); avl_tree_unlock (global.source_tree); if (source->format->get_image (client, source->format) == 0) { thread_rwlock_unlock (&source->lock); return fserve_setup_client (client); } thread_rwlock_unlock (&source->lock); } else avl_tree_unlock (global.source_tree); return client_send_404 (client, "No image available"); }
static int command_shoutcast_metadata (client_t *client, source_t *source) { const char *action; const char *value; int same_ip = 1; if (COMMAND_REQUIRE(client, "mode", action) < 0) { thread_rwlock_unlock (&source->lock); return client_send_400 (client, "missing arg, mode"); } if ((source->flags & SOURCE_SHOUTCAST_COMPAT) == 0) { thread_rwlock_unlock (&source->lock); ERROR0 ("illegal request on non-shoutcast compatible stream"); return client_send_400 (client, "Not a shoutcast compatible stream"); } if (strcmp (action, "updinfo") == 0) { DEBUG0("Got shoutcast metadata update request"); if (COMMAND_REQUIRE (client, "song", value) < 0) { thread_rwlock_unlock (&source->lock); return client_send_400 (client, "missing arg, song"); } if (source->client && strcmp (client->connection.ip, source->client->connection.ip) != 0) if (connection_check_admin_pass (client->parser) == 0) same_ip = 0; if (same_ip && source->format && source->format->set_tag) { httpp_set_query_param (client->parser, "mount", client->server_conn->shoutcast_mount); source->format->set_tag (source->format, "title", value, NULL); source->format->set_tag (source->format, NULL, NULL, NULL); DEBUG2("Metadata on mountpoint %s changed to \"%s\"", source->mount, value); thread_rwlock_unlock (&source->lock); return html_success(client, "Metadata update successful"); } thread_rwlock_unlock (&source->lock); return client_send_400 (client, "mountpoint will not accept URL updates"); } if (strcmp (action, "viewxml") == 0) { xmlDocPtr doc; DEBUG0("Got shoutcast viewxml request"); thread_rwlock_unlock (&source->lock); doc = stats_get_xml (STATS_ALL, source->mount); return admin_send_response (doc, client, XSLT, "viewxml.xsl"); } thread_rwlock_unlock (&source->lock); return client_send_400 (client, "No such action"); }
static auth_result htpasswd_adduser (auth_t *auth, const char *username, const char *password) { FILE *passwdfile; char *hashed_password = NULL; htpasswd_auth_state *state = auth->state; htpasswd_user entry; void *result; htpasswd_recheckfile (state); thread_rwlock_wlock (&state->file_rwlock); entry.name = (char*)username; if (avl_get_by_key (state->users, &entry, &result) == 0) { thread_rwlock_unlock (&state->file_rwlock); return AUTH_USEREXISTS; } passwdfile = fopen(state->filename, "ab"); if (passwdfile == NULL) { thread_rwlock_unlock (&state->file_rwlock); WARN2("Failed to open authentication database \"%s\": %s", state->filename, strerror(errno)); return AUTH_FAILED; } hashed_password = get_hash(password, strlen(password)); if (hashed_password) { fprintf(passwdfile, "%s:%s\n", username, hashed_password); free(hashed_password); } fclose(passwdfile); thread_rwlock_unlock (&state->file_rwlock); return AUTH_USERADDED; }
void client_add_incoming (client_t *client) { worker_t *handler; thread_rwlock_rlock (&workers_lock); handler = worker_incoming; thread_spin_lock (&handler->lock); thread_rwlock_unlock (&workers_lock); worker_add_client (handler, client); thread_spin_unlock (&handler->lock); worker_wakeup (handler); }
void client_add_worker (client_t *client) { worker_t *handler; thread_rwlock_rlock (&workers_lock); /* add client to the handler with the least number of clients */ handler = worker_selected(); thread_spin_lock (&handler->lock); thread_rwlock_unlock (&workers_lock); worker_add_client (handler, client); thread_spin_unlock (&handler->lock); worker_wakeup (handler); }
static int command_updatemetadata(client_t *client, source_t *source, int response) { xmlDocPtr doc; xmlNodePtr node, srcnode; thread_rwlock_unlock (&source->lock); doc = xmlNewDoc(XMLSTR("1.0")); node = xmlNewDocNode(doc, NULL, XMLSTR("icestats"), NULL); srcnode = xmlNewChild(node, NULL, XMLSTR("source"), NULL); xmlSetProp(srcnode, XMLSTR("mount"), XMLSTR(source->mount)); xmlDocSetRootElement(doc, node); return admin_send_response (doc, client, response, "updatemetadata.xsl"); }
int auth_get_userlist(source_t *source, xmlNodePtr srcnode) { int ret = 0; htpasswd_auth_state *state; if (source->authenticator) { if (!strcmp(source->authenticator->type, "htpasswd")) { state = source->authenticator->state; thread_rwlock_rlock(&state->file_rwlock); ret = auth_get_htpasswd_userlist(source->authenticator, srcnode); thread_rwlock_unlock(&state->file_rwlock); } } return ret; }
int auth_deleteuser(source_t *source, char *username) { htpasswd_auth_state *state; int ret = 0; if (source->authenticator) { if (!strcmp(source->authenticator->type, "htpasswd")) { state = source->authenticator->state; thread_rwlock_wlock(&state->file_rwlock); ret = auth_htpasswd_deleteuser(source->authenticator, username); thread_rwlock_unlock(&state->file_rwlock); } } return ret; }