/* * Copyright (C) 1991-2005 Lysator Academic Computer Association. * * This file is part of the LysKOM server. * * LysKOM is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 1, or (at your option) * any later version. * * LysKOM is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * for more details. * * You should have received a copy of the GNU General Public License * along with LysKOM; see the file COPYING. If not, write to * Lysator, c/o ISY, Linkoping University, S-581 83 Linkoping, SWEDEN, * or the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, * MA 02139, USA. * * Please report bugs at http://bugzilla.lysator.liu.se/. */ /* * connections.c * * Denna fil inneh}ller niv}n ovanf|r isc. * * Created by Willf|r 31/3-90. Mostly written by ceder. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #ifdef HAVE_STRING_H # include #endif #include #include #include "timewrap.h" #include #include #include #include #include #include "adns.h" #include "oop.h" #include "unused.h" #include "ldifftime.h" #include "misc-types.h" #include "s-string.h" #include "kom-types.h" #include "kom-memory.h" #include "debug.h" #include "isc-interface.h" #include "com.h" #include "async.h" #include "connections.h" #include "internal-connections.h" #include "prot-a-parse-arg.h" #include "log.h" #include "lyskomd.h" #include "services.h" #include "isc-parse.h" #include "prot-a.h" #include "prot-a-parse.h" #include "server/smalloc.h" #include "end-of-atomic.h" #include "send-async.h" #include "cache.h" #include "rfc931.h" #include "conf-file.h" #include "param.h" #include "kom-config.h" #include "kom-errno.h" #include "sigflags.h" #include "server-time.h" #include "aux-items.h" #include "eintr.h" #include "text-garb.h" #include "timeval-util.h" #include "stats.h" #include "string-malloc.h" #include "manipulate.h" oop_source_sys * kom_server_oop_src = NULL; struct isc_mcb * kom_server_mcb = NULL; Connection * active_connection = NULL; /* * This is set TRUE when the server should be closed. It is checked * each time around the main loop. It is set if someone with enough * privileges issues a `shutdown', or of lyskomd receives a SIGTERM. * This not an abort: all data is saved before we exit. */ Bool go_and_die = FALSE; /* * The number of times that the session penalties has been averaged. * Beware: this number will wrap around. */ static unsigned int penalty_generation = 0; /* * These state variables are used to find out if we are busy or not. * When a packet arrives, data_available_callback() will set work_done * to TRUE and is_idle to FALSE. */ static Bool work_done = FALSE; static Bool is_idle = FALSE; jmp_buf parse_env; const Fnc_descriptor fnc_defs[]={ #include "fnc-def-init.incl" }; const int num_fnc_defs = sizeof (fnc_defs) / sizeof (Fnc_descriptor); unsigned long service_statistics[sizeof (fnc_defs) / sizeof (Fnc_descriptor)]; BUGDECL; static oop_call_fd data_available_callback; static oop_call_time check_kill_flg; static oop_call_time check_idle_callback; static isc_write_error_cb write_err_cb; static isc_stale_output_cb stale_cb; static isc_stale_output_cb idle_cb; /* head and tail pointers for the wait queue that uses the queue_prev, queue_next and on_queue fields of Connection. */ static Connection *queue_first = NULL; static Connection *queue_last = NULL; static void busy(void); /* Add c to the end of the wait queue. */ static void queue_add(Connection *c) { assert(c->on_queue == FALSE); c->on_queue = TRUE; c->queue_prev = queue_last; c->queue_next = NULL; if (queue_first == NULL) queue_first = c; else queue_last->queue_next = c; queue_last = c; update_stat(STAT_RUN_QUEUE, 1); } /* Remove c from the wait queue. Any element on the queue can be removed. */ static void queue_remove(Connection *c) { assert(c->on_queue == TRUE); c->on_queue = FALSE; if (c->queue_next != NULL) c->queue_next->queue_prev = c->queue_prev; else queue_last = c->queue_prev; if (c->queue_prev != NULL) c->queue_prev->queue_next = c->queue_next; else queue_first = c->queue_next; c->queue_prev = NULL; c->queue_next = NULL; update_stat(STAT_RUN_QUEUE, -1); } static void logout_client(Connection *cp) { Connection *real_active_connection; int ret; if ( active_connection != NULL ) { kom_log("BUGCHK: logout_client(%ld): connection %ld is active.\n", cp->session_no, active_connection->session_no); } if ( cp->pers_no != 0 ) { int ctr = 0; if (active_connection != NULL) { kom_log("WNG: logout_client(): active_connection != NULL\n"); if (ctr < 100) ctr++; else kom_log("WNG: won't log the above message more\n"); } real_active_connection = active_connection; active_connection = cp; logout(); active_connection = real_active_connection; } else { #if 0 /* FIXME (bug 908): send a new async here instead. This causes the elisp client to say that a secret (or unknown) person has left the system. */ async_logout( 0, cp->session_no ); #endif } switch(cp->protocol) { case 0: /* Hasn't yet allocated any protocol. */ break; case 'A': prot_a_destruct(cp); break; default: restart_kom("logout_client(): Bad protocol.\n"); } ret = isc_destroy(kom_server_mcb, cp->isc_session); if (ret < 0) kom_log("logout_client(): isc_destroyed returned %d\n", ret); cp->isc_session = NULL; if (cp->on_queue) queue_remove(cp); kill_client(cp); /* Free the Connection */ update_stat(STAT_CLIENTS, -1); } /* * This function is part of the shutdown tidy-up sequence. */ void logout_all_clients(void) { Session_no sess = 0; Connection *conn; while ( (sess = traverse_connections (sess)) != 0) { conn = get_conn_by_number (sess); if ( conn == NULL ) restart_kom("logout_all_clients(): cant get session %ld.\n", sess); else logout_client (conn); } if ( traverse_connections (0) != 0) restart_kom("logout_all_clients(): traverse_connections(0) == %ld.\n", traverse_connections(0)); } /* * Call a function in services.c. A pointer to the result is returned. * The pointer points to static data which is overwritten on each call. */ static Success call_function(Connection *client, union result_holder *res) { Success status=FAILURE; /* OK if the call was successful. */ if ( active_connection != NULL ) { kom_log("call_function(%ld): active_connection = %ld\n", client->session_no, active_connection->session_no); } if (client->function == illegal_fnc) { err_stat = 0; kom_errno = KOM_NOT_IMPL; return FAILURE; } active_connection = client; service_statistics[client->function_index]++; #include "call-switch.incl" active_connection = NULL; return status; } static void parse_packet(Connection *client) { if ( client->protocol == '\0' ) /* Not known yet. */ { client->protocol = parse_char(client); switch(client->protocol) { case 'A': prot_a_init(client); break; default: client->protocol = '\0'; isc_puts("%%LysKOM unsupported protocol.\n", client->isc_session); isc_flush(client->isc_session); BUG(("%%%%Unsupported protocol.\n")); longjmp(parse_env, KOM_LOGOUT); } } switch(client->protocol) { case 'A': prot_a_parse_packet(client); break; default: restart_kom("parse_packet(): Bad protocol.\n"); break; } } /* * Free all parsed areas which are no longer needed. Re-initialize all * parse_pos fields so that the parse will expect a new function. * * This function is called * when a parse error occurs * when a parse is complete and the function has executed. */ static void free_parsed(Connection *client) { s_clear(&client->c_string0); s_clear(&client->c_string1); client->string0 = EMPTY_STRING; /* So that no one frees it. */ sfree(client->misc_info_list.misc); client->misc_info_list.misc = 0; client->misc_info_list.no_of_misc = 0; s_clear(&client->aux_item.data); s_clear(&client->dummy_aux_item.data); sfree(client->read_range_list.ranges); client->read_range_list.ranges = NULL; client->read_range_list.length = 0; client->parse_pos = 0; client->fnc_parse_pos = 0; client->array_parse_index = 0; client->array_parse_parsed_length = 0; client->array_parse_pos = 0; client->struct_parse_pos = 0; client->string_parse_pos = 0; client->hunt_parse_pos = 0; client->array_hunt_num = 0; client->array_hunt_depth = 0; sfree(client->num_list.data); client->num_list.data = NULL; client->num_list.length = 0; free_aux_item_list(&client->aux_item_list); client->info.highest_aux_no = 0; } /* * Send a reply to a call. */ static void reply(Connection *client, Success status, union result_holder *result) { switch(client->protocol) { case 'A': prot_a_reply(client, status, result); break; default: restart_kom("reply(): Bad protocol.\n"); break; } } /* * Try to parse enough data from client->unparsed to call a function. * If more data is needed set client->more_to_parse to FALSE. Returns * TRUE if anything was (or might have been) written to the client. */ static Bool parse_unparsed(Connection *client) { Success status; union result_holder result; switch ( setjmp(parse_env) ) { case 0 : /* Parse message. If message is complete call function and reply. */ parse_packet(client); if (client->blocked_by_dns) return TRUE; #ifdef DEBUG_CALLS if (client->blocked_by_disable_client) return TRUE; #endif update_stat(STAT_REQUESTS, 1); status = call_function(client, &result); update_stat(STAT_REQUESTS, -1); reply(client, status, &result); client->penalty += param.penalty_per_call; free_parsed(client); end_of_atomic(); return TRUE; case KOM_PROTOCOL_ERR: s_clear(&client->string0); free_parsed(client); isc_puts("%% LysKOM protocol error.\n", client->isc_session); BUG(("%%%% Protocol error.\n")); client->penalty += param.max_penalty; s_clear(&client->unparsed); client->first_to_parse = 0; client->more_to_parse = FALSE; end_of_atomic(); return TRUE; case KOM_MSG_INCOMPLETE: client->more_to_parse = FALSE; return FALSE; case KOM_LOGOUT: add_to_kill_list(client); client->more_to_parse = FALSE; return TRUE; default: restart_kom("Bad longjmp return value.\n"); } /*NOTREACHED*/ } /* Return 1 if the named file exists, 0 otherwise */ static int fexists(const char *filename) { struct stat buf; int code; code = !stat(filename, &buf); errno = 0; return code; } void dump_statistics(void) { static struct timeval last_dump = {0, 0}; int i; FILE *fp; if ((fp = i_fopen(param.statistic_name, "a")) == NULL) { kom_log("dump_statistics(): can't open file %s\n", param.statistic_name); return; } if (timeval_zero(last_dump)) { fprintf(fp, "RESTART\n"); last_dump = current_time; } fprintf(fp, "TIME: %s", ctime(¤t_time.tv_sec)); fprintf(fp, "SECONDS: %ld\n", timeval_diff_sec(current_time, last_dump)); fprintf(fp, "STATISTICS:"); /* The last entry corresponds to the dummy entry that is used to skip arguments to unimplemented requests. Skip that, since it contains no useful statistics. */ for (i = 0; i < num_fnc_defs - 1; i++) { fprintf(fp, " %d:%lu", fnc_defs[i].function, service_statistics[i]); service_statistics[i]=0; } fprintf(fp, "\n"); i_fclose(fp); last_dump = current_time; } /* List of connections to kill. */ static Session_no *kill_list = NULL; static int kill_list_size = 0; static int kill_pending = 0; /* Schedule this client for termination. */ void add_to_kill_list(Connection *conn) { oop_source *source; int i; switch (conn->kill_status) { case ks_pending: /* A kill is already pending. Do nothing--but check that the the client really is present on the kill_list. */ for (i = 0; i < kill_list_size; i++) if (kill_list[i] == conn->session_no) return; restart_kom("add_to_kill_list(): kill_pending set but not on list.\n"); return; case ks_dying: /* Don't add this client to the kill list while it is being killed. */ return; case ks_none: /* The normal case. Do all the work below. */ break; } /* Check that the client isn't already present on the kill_list. */ for (i = 0; i < kill_list_size; i++) if (kill_list[i] == conn->session_no) restart_kom("add_to_kill_list(): on list but not kill_pending.\n"); if (kill_list == NULL) { if (kill_list_size != 0) restart_kom("add_to_kill_list(): size = %d\n", kill_list_size); kill_list_size = 1; kill_list = smalloc(sizeof(Session_no)); } else { kill_list_size++; kill_list = srealloc(kill_list, kill_list_size * sizeof(Session_no)); } kill_list[kill_list_size-1] = conn->session_no; conn->kill_status = ks_pending; if (!kill_pending) { source = isc_getoopsource(conn->isc_session); source->on_time(source, OOP_TIME_NOW, check_kill_flg, NULL); kill_pending = 1; } } static void dump_connections(void) { Session_no s; Connection *conn; FILE *fp; if ((fp = i_fopen(param.connection_status_file_tmp, "w")) == NULL) { kom_log("dump_connections(): can't open file %s: %s\n", param.connection_status_file_tmp, strerror(errno)); return; } for (s = 0; (s = traverse_connections(s)) != 0;) { conn = get_conn_by_number(s); fprintf(fp, "%d %lu %d %s\n", conn->isc_session->fd, conn->session_no, handshake_ok(conn, 0), conn->peer); } if (fflush(fp) != 0) kom_log("dump_connections(): fflush() says an error has occured.\n"); if (ferror(fp)) kom_log("dump_connections(): ferror() says an error has occured.\n"); if (i_fclose(fp) < 0) { kom_log("dump_connections(): fclose failed: %s (ignored)\n", strerror(errno)); } errno = 0; if (i_rename(param.connection_status_file_tmp, param.connection_status_file) < 0) { kom_log("dump_connections(): can't rename %s to %s: %s\n", param.connection_status_file_tmp, param.connection_status_file, strerror(errno)); } } /* * check_kill_flg must NEVER be called inside an atomic call! */ static void * check_kill_flg(oop_source *UNUSED(source), struct timeval UNUSED(tv), void *UNUSED(user)) { Connection *conn; Bool changed = FALSE; kill_pending = 0; if ( active_connection != NULL ) { restart_kom("check_kill_flg: active_connection == %ld", active_connection->session_no); } while (kill_list_size > 0) { --kill_list_size; conn = get_conn_by_number (kill_list[kill_list_size]); if (conn == NULL) { kom_log("check_kill_flg(): Connection %ld doesn't exist.\n", kill_list[kill_list_size]); } else { assert(conn->kill_status == ks_pending); conn->kill_status = ks_dying; logout_client(conn); end_of_atomic(); changed = TRUE; } } if (kill_list != NULL) { sfree (kill_list); kill_list = NULL; } if (changed == TRUE) dump_connections(); return OOP_CONTINUE; } static void * dns_resolution(struct isc_scb *scb, enum isc_resolve_status res, long errcode) { struct timeval after; double diff = -1.0; Connection *conn; char *hostname = NULL; update_stat(STAT_DNS_QUEUE, -1); conn = scb->udg; conn->dns_done = TRUE; if (conn->blocked_by_dns) { conn->blocked_by_dns = FALSE; if (!conn->on_queue) queue_add(conn); } if (res == isc_resolve_aborted) return OOP_CONTINUE; if (gettimeofday(&after, NULL) < 0) kom_log("gettimeofday failed: %s\n", strerror(errno)); diff = timeval_diff_d(after, conn->connect_time); busy(); switch (res) { case isc_resolve_h_errno: if (hostname == NULL) hostname = s_crea_c_str(scb->remote); if (errcode == HOST_NOT_FOUND) kom_log("No hostname found for %s.\n", hostname); else if (errcode == TRY_AGAIN) kom_log("Lookup of %s timed out.\n", hostname); else if (errcode == NO_RECOVERY) kom_log("Non-recoverable error looking up %s.\n", hostname); else if (errcode == NO_ADDRESS) kom_log("Got NO_ADDRESS error looking up %s.\n", hostname); else kom_log("Unknown resolver error %ld looking up %s.\n", errcode, hostname); break; case isc_resolve_adns_error: if (hostname == NULL) hostname = s_crea_c_str(scb->remote); /* Misconfigurations of localhost are common and harmless. Don't bother logging them, since that makes the test cases fail. */ if ((errcode != adns_s_inconsistent && errcode != adns_s_nxdomain) || strcmp(hostname, "127.0.0.1") != 0) kom_log("Error looking up %s: %s\n", hostname, adns_strerror(errcode)); break; case isc_resolve_aborted: abort(); case isc_resolve_ok: break; } if (diff > param.dns_log_threshold) { if (hostname == NULL) hostname = s_crea_c_str(scb->remote); if (res == isc_resolve_ok) kom_log("Slow DNS: got %s after %f seconds\n", hostname, diff); else kom_log("Slow bad DNS: %s failed after %f seconds\n", hostname, diff); } if (hostname != NULL) string_free(hostname); if (handshake_ok(conn, 0)) dump_connections(); return OOP_CONTINUE; } static void write_err_cb(struct isc_scb *cb_session, int saved_errno) { Connection * cp = cb_session->udg; if (saved_errno != ECONNRESET && saved_errno != EPIPE) kom_log("Failed to write to client %lu from %s: %s\n", cp->session_no, cp->peer, strerror(saved_errno)); cp->penalty += param.max_penalty; add_to_kill_list(cp); } static void stale_cb(struct isc_scb *cb_session) { Connection *cp = cb_session->udg; kom_log("Client %lu from %s has stalled. Killing it.\n", cp->session_no, cp->peer); cp->penalty += param.max_penalty; add_to_kill_list(cp); } static void idle_cb(struct isc_scb *cb_session) { Connection *cp = cb_session->udg; kom_log("Client %lu from %s has been idle too long. Killing it.\n", cp->session_no, cp->peer); cp->penalty += param.max_penalty; add_to_kill_list(cp); } static void login_request(struct isc_scb *session) { Connection * cp; const char *realuser; char portbuf[1+2+3*sizeof(long)]; size_t portlen; char *remote_ip = NULL; Bool dns_submitted = FALSE; /* Supress logins if /etc/nologin exists */ if (fexists(param.nologin_file)) { isc_puts("%% No logins allowed.\n", session); isc_flush(session); isc_destroy(kom_server_mcb, session); return; } /* Create a Connection, and link the Connection and the isc_session together. */ cp = new_client(); cp->isc_session = session; session->udg = cp; update_stat(STAT_CLIENTS, 1); /* Store the IP address in readable form. */ s_crea_str(&cp->remote_ip, isc_getipnum(session->raddr, NULL, 0)); remote_ip = s_crea_c_str(cp->remote_ip); /* Initiate DNS lookup. */ if (param.use_dns) { int rv = isc_resolve_remote(session, dns_resolution); if (rv == 0) { dns_submitted = TRUE; update_stat(STAT_DNS_QUEUE, 1); } else { kom_log("WNG: isc_resolve_remote of %s failed: %s\n", remote_ip, strerror(rv)); } } if (!dns_submitted) { s_strcpy(&session->remote, cp->remote_ip); cp->blocked_by_dns = FALSE; cp->dns_done = TRUE; } /* Update the status file that contains all connection. */ sprintf(portbuf, " %d", isc_getportnum(session->raddr)); portlen = strlen(portbuf); cp->peer = smalloc(s_strlen(cp->remote_ip) + portlen + 1); strcpy(cp->peer, remote_ip); strcpy(cp->peer + s_strlen(cp->remote_ip), portbuf); dump_connections(); /* Start with max penalty, so that it doesn't pay to make a lot of new connections. */ cp->penalty = param.max_penalty; cp->penalty_generation = penalty_generation; cp->schedule.priority = param.default_priority; cp->schedule.weight = param.default_weight; /* Get the real user name, as returned by the Ident protocol (rfc 931). */ realuser = get_real_username(session, remote_ip); if (realuser == NULL && param.authentication_level == 2) { kom_log("Connection from %s rejected - no IDENT available.\n", remote_ip); isc_puts("%% No IDENT server reachable at your site.\n", session); isc_flush(session); logout_client(cp); string_free(remote_ip); return; } if (realuser != NULL) s_crea_str(&cp->ident_user, realuser); BUG(("\n[Client %lu from %s is connecting]\n", cp->session_no, remote_ip)); isc_set_read_callback(session, data_available_callback, write_err_cb, stale_cb, idle_cb); string_free(remote_ip); } static void adjust_penalty(Connection *conn) { unsigned int gens = penalty_generation - conn->penalty_generation; if (gens > 0) { /* The weight is in the range 1-0xffff (inclusive). The penalty is in the range 0-0x10000 (inclusive). This means that the multiplication can never overflow. */ if (conn->penalty <= gens) conn->penalty = 0; else { unsigned int tmp = conn->penalty - gens * conn->schedule.weight; if (tmp < conn->penalty) conn->penalty = tmp; else conn->penalty = 0; } conn->penalty_generation = penalty_generation; } } static Bool may_read_more(Connection *conn) { if (go_and_die) return FALSE; if (conn->penalty >= param.max_penalty) return FALSE; if (conn->kill_status != ks_none) return FALSE; if (conn->blocked_by_dns) return FALSE; #ifdef DEBUG_CALLS if (conn->blocked_by_disable_client == 1) return FALSE; #endif return TRUE; } static Bool should_be_disabled(Connection *conn) { #ifdef DEBUG_CALLS if (conn->blocked_by_disable_client == 1) return TRUE; #endif return conn->penalty >= param.max_penalty && !conn->blocked_by_dns; } static void read_from_connection(Connection *conn) { Bool would_block = FALSE; Bool need_flush = FALSE; String_size pre; adjust_penalty(conn); while (!would_block && may_read_more(conn)) { pre = s_strlen(conn->unparsed) - conn->first_to_parse; while (conn->more_to_parse && may_read_more(conn)) need_flush |= parse_unparsed(conn); update_stat(STAT_RECV_QUEUE, s_strlen(conn->unparsed) - conn->first_to_parse - pre); if (!may_read_more(conn)) break; if (!conn->more_to_parse) { pre = s_strlen(conn->unparsed) - conn->first_to_parse; switch (isc_read_data(conn->isc_session, &conn->unparsed, &conn->first_to_parse)) { case ISC_READ_DATA: update_stat(STAT_RECV_QUEUE, s_strlen(conn->unparsed) - conn->first_to_parse - pre); conn->penalty += param.penalty_per_read; conn->more_to_parse = TRUE; break; case ISC_READ_ERROR: if (errno != ECONNRESET && errno != ETIMEDOUT) kom_log("Error reading from client: %s\n", strerror(errno)); /*FALLTHROUGH*/ case ISC_READ_LOGOUT: add_to_kill_list(conn); break; case ISC_READ_WOULDBLOCK: would_block = TRUE; break; case ISC_READ_NOMEM: restart_kom("isc_read_data() reports no memory\n"); } if (!conn->more_to_parse) break; } } if (need_flush) isc_flush(conn->isc_session); /* Delete the parsed part of 'unparsed' */ if (s_trim_left(&conn->unparsed, conn->first_to_parse) != OK) restart_kom("parse_unparsed: s_trim_left\n"); conn->first_to_parse = 0; if (should_be_disabled(conn)) { /* isc_disable() will fail if we have received EPIPE on this socket. In that case will soon close it, since write_err_cb() has added it to the kill list. */ if (isc_disable(conn->isc_session) == 0) queue_add(conn); } } static void enable_idle_check(void) { oop_source *source = oop_sys_source(kom_server_oop_src); source->on_time(source, OOP_TIME_NOW, check_idle_callback, NULL); } static void * check_idle_callback(oop_source *UNUSED(source), struct timeval UNUSED(tv), void *UNUSED(user)) { Connection *c; Connection *next; Connection *head = NULL; Connection *tail = NULL; set_time(); is_idle = !work_done; work_done = FALSE; if (is_idle && queue_first != NULL) { is_idle = FALSE; ++penalty_generation; for (next = queue_first; next != NULL; ) { c = next; next = next->queue_next; adjust_penalty(c); if (!should_be_disabled(c)) { queue_remove(c); if (head == NULL) head = c; else tail->queue_next = c; tail = c; } } } for (next = head; next != NULL; ) { c = next; next = next->queue_next; c->queue_next = NULL; if (isc_enable(c->isc_session) < 0) { kom_log("failed to re-enable session %ld\n", (long)c->session_no); } else if (!go_and_die) { /* The call to read_from_connection() might add the connection to the queue. That's why we remove all entries first, and use a private queue within this function. */ read_from_connection(c); } } if (!is_idle) enable_idle_check(); /* Check if a client issued a shutdown command. */ return go_and_die ? OOP_HALT : OOP_CONTINUE; } static void * saver_callback(oop_source *source, struct timeval UNUSED(tv), void *user) { struct timeval timeout; struct timeval *next_timer = user; timeout = end_of_atomic(); if (setup_timer(next_timer, timeout) < 0) kom_log("gettimeofday failed: %s\n", strerror(errno)); source->on_time(source, *next_timer, saver_callback, user); return OOP_CONTINUE; } static void busy(void) { /* Something arrived, so we are busy. */ if (is_idle) { is_idle = FALSE; enable_idle_check(); } work_done = TRUE; } static void * data_available_callback(oop_source *source, int fd, oop_event event, void *user) { Connection *conn = ((struct isc_scb*)user)->udg; assert(event == OOP_READ); assert(conn->isc_session->fd == fd); assert(isc_getoopsource(conn->isc_session) == source); assert(conn->on_queue == FALSE); busy(); set_time(); read_from_connection(conn); /* Check if the client issued a shutdown command. */ return go_and_die ? OOP_HALT : OOP_CONTINUE; } void * handle_accept_event(struct isc_scb *UNUSED(accepting_session), struct isc_scb *new_session) { new_session->udg = NULL; set_time(); if (new_session->fd <= PROTECTED_FDS || new_session->fd >= fd_ceiling) { BUG(("Connection attempt rejected.\n")); isc_puts("%% No connections left.\n", new_session); isc_flush(new_session); isc_destroy(new_session->master, new_session); async_rejected_connection(); } else login_request(new_session); return OOP_CONTINUE; } void toploop(void) { struct timeval saver_timer; void *exit_reason; oop_source *source = oop_sys_source(kom_server_oop_src); /* Start the garb right away. */ start_garb_thread(source); /* Hack to find out when we are idle. */ enable_idle_check(); /* Save the database even if we happen to be idle. */ saver_timer = OOP_TIME_NOW; source->on_time(source, saver_timer, saver_callback, &saver_timer); exit_reason = oop_sys_run(kom_server_oop_src); if (exit_reason == OOP_ERROR) kom_log("ERROR: unexpected error from oop_sys_run: %s\n", strerror(errno)); else if (exit_reason == OOP_CONTINUE) kom_log("ERROR: all oop sinks disappeared\n"); else if (exit_reason != OOP_HALT) kom_log("ERROR: unexpected error from oop_sys_run: %s\n", strerror(errno)); if (kill_pending) { source->cancel_time(source, OOP_TIME_NOW, check_kill_flg, NULL); check_kill_flg(NULL, OOP_TIME_NOW, NULL); } if (is_idle == FALSE) source->cancel_time(source, OOP_TIME_NOW, check_idle_callback, NULL); stop_garb_thread(source); source->cancel_time(source, saver_timer, saver_callback, &saver_timer); } Bool server_idle(void) { return is_idle; } Success get_scheduling(Session_no session_no, Scheduling_info *result) { Connection *cptr; CHK_CONNECTION(FAILURE); if (session_no != 0 && session_no != active_connection->session_no) CHK_LOGIN(FAILURE); if ((cptr = get_conn_by_number(session_no)) == NULL || !handshake_ok(cptr, 0)) { kom_errno = KOM_UNDEF_SESSION; err_stat = session_no; return FAILURE; } *result = cptr->schedule; return OK; } static Bool may_change_scheduling(Session_no session_no, Connection *cptr) { if (session_no == 0) return TRUE; if (session_no == active_connection->session_no) return TRUE; if (cptr->pers_no == 0 && (ENA_C(active_connection, admin, 2) || ENA_C(active_connection, wheel, 8))) return TRUE; if (has_access(cptr->pers_no, active_connection, unlimited)) return TRUE; return FALSE; } Success set_scheduling(Session_no session_no, unsigned short priority, unsigned short weight) { Connection *cptr; CHK_CONNECTION(FAILURE); if (weight == 0) { kom_errno = KOM_WEIGHT_ZERO; err_stat = 0; return FAILURE; } if (session_no != 0 && session_no != active_connection->session_no) CHK_LOGIN(FAILURE); if ((cptr = get_conn_by_number(session_no)) == NULL || !handshake_ok(cptr, 0)) { kom_errno = KOM_UNDEF_SESSION; err_stat = session_no; return FAILURE; } if (!may_change_scheduling(session_no, cptr)) { kom_errno = KOM_ACCESS; err_stat = session_no; return FAILURE; } if (priority > param.max_priority) { kom_errno = KOM_INDEX_OUT_OF_RANGE; err_stat = param.max_priority; return FAILURE; } #if 0 /* Since priority is unsigned this can never happen. */ if (priority < 0) { kom_errno = KOM_PRIORITY_DENIED; err_stat = 0; return FAILURE; } #endif if (weight > param.max_weight) { kom_errno = KOM_WEIGHT_DENIED; err_stat = param.max_weight; return FAILURE; } cptr->schedule.priority = priority; cptr->schedule.weight = weight; return OK; } #ifdef DEBUG_CALLS Success disable_client(Session_no session_no, int state) { Connection *cptr; CHK_CONNECTION(FAILURE); if (state != 0 && state != 1) { kom_errno = KOM_BAD_BOOL; err_stat = 0; return FAILURE; } if (session_no != 0 && session_no != active_connection->session_no) CHK_LOGIN(FAILURE); if ((cptr = get_conn_by_number(session_no)) == NULL || !handshake_ok(cptr, 0)) { kom_errno = KOM_UNDEF_SESSION; err_stat = session_no; return FAILURE; } cptr->blocked_by_disable_client = state; return OK; } #endif