Skip to content
Snippets Groups Projects
Select Git revision
  • 6ca0e6b9db2901f87ba6b534d56e5e31a6d33644
  • master default protected
  • hpke
  • ppc-chacha-4core
  • delete-internal-name-mangling
  • master-updates
  • ppc-gcm
  • ppc-chacha-2core
  • refactor-ecc-mod
  • ppc-chacha-core
  • use-mpn_cnd-functions
  • optimize-ecc-invert
  • default-m4-quote-char
  • power-asm-wip
  • test-fat
  • chacha-3core-neon
  • x86_64-salsa20-2core
  • salsa20-2core-neon
  • bcrypt
  • arm-salsa20-chacha-vsra
  • test-shlib-dir
  • nettle_3.6_release_20200429
  • nettle_3.6rc3
  • nettle_3.6rc2
  • nettle_3.6rc1
  • nettle_3.5.1_release_20190627
  • nettle_3.5_release_20190626
  • nettle_3.5rc1
  • nettle_3.4.1_release_20181204
  • nettle_3.4.1rc1
  • nettle_3.4_release_20171119
  • nettle_3.4rc2
  • nettle_3.4rc1
  • nettle_3.3_release_20161001
  • nettle_3.2_release_20160128
  • nettle_3.1.1_release_20150424
  • nettle_3.1_release_20150407
  • nettle_3.1rc3
  • nettle_3.1rc2
  • nettle_3.1rc1
  • nettle_3.0_release_20140607
41 results

util.c

Blame
  • Forked from Nettle / nettle
    Source project has a limited visibility.
    connections.c 24.74 KiB
    /*
     * $Id: connections.c,v 0.110 2003/08/01 09:37:19 ceder Exp $
     * Copyright (C) 1991-2002  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 mail bug reports to bug-lyskom@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 <config.h>
    #endif
    
    #include <errno.h>
    #include <stdio.h>
    #include <setjmp.h>
    #ifdef HAVE_STRING_H
    #  include <string.h>
    #endif
    #include <sys/types.h>
    #include <sys/stat.h>
    #include "timewrap.h"
    #include <sys/socket.h>
    #include <signal.h>
    #include <assert.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 "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"
    
    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 SIGHUP.
     * 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 Connection *queue_first = NULL;
    static Connection *queue_last = NULL;
    
    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);
    }
    
    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);
    }
    
    void
    set_time(void)
    {
        struct timeval last_time;
        static int limiter = 0;
    
        last_time = current_time;
        if (gettimeofday(&current_time, NULL) < 0)
        {
    	if (limiter < 50)
    	{
    	    kom_log("WARNING: gettimeofday failed: %s\n", strerror(errno));
    	    if (++limiter == 50)
    		kom_log("WARNING: will not log the above message again.\n");
    	}
        }
    
        if (timeval_less(current_time, last_time))
        {
    	kom_log("WARNING: Time is moving in the wrong direction.\n");
    	/* FIXME (bug 62): Should we take more decisive action here? */
        }
    }
    
    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->c_local_text_no_p);
        client->c_local_text_no_p = NULL;
        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);
    	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(&current_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;
    
        if (conn->kill_pending)
        {
    	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");
        }
    
        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_pending = TRUE;
    
        if (!kill_pending)
        {
    	source = isc_getoopsource(conn->isc_session);
    	source->on_time(source, OOP_TIME_NOW, check_kill_flg, NULL);
    	kill_pending = 1;
        }
    }
    
    
    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 %s\n", conn->isc_session->fd, conn->session_no,
    		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_pending);
    	    conn->kill_pending = FALSE;
    	    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 char *
    get_host_name(union isc_address *addr)
    {
        char *res = NULL;
        char *hostname = NULL;
        struct timeval before;
        struct timeval after;
        double diff = -1.0;
    
        if (param.use_dns)
        {
    	if (gettimeofday(&before, NULL) < 0)
    	    kom_log("gettimeofday failed: %s\n", strerror(errno));
    
    	update_stat(STAT_DNS_QUEUE, 1);
    	
    	res = hostname = isc_gethostname(addr, NULL, 0);
    
    	update_stat(STAT_DNS_QUEUE, -1);
    
    	if (gettimeofday(&after, NULL) < 0)
    	    kom_log("gettimeofday failed: %s\n", strerror(errno));
    
    	diff = (after.tv_sec - before.tv_sec
    		+ 1e-6 * (after.tv_usec - before.tv_usec));
        }
    
        if (res == NULL)
    	res = isc_getipnum(addr, NULL, 0);
    
        if (param.use_dns && diff > param.dns_log_threshold)
        {
    	if (hostname == NULL)
    	    kom_log("Slow bad DNS: %s failed after %f seconds\n", res, diff);
    	else
    	    kom_log("Slow DNS: got %s after %f seconds\n", res, diff);
        }
    
        return res;
    }
    
    static void
    write_err_cb(struct isc_mcb *UNUSED(cb_mcb),
    	     struct isc_scb *UNUSED(cb_session),
    	     int saved_errno,
    	     void *user)
    {
        Connection  * cp = user;
        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_mcb *UNUSED(cb_mcb),
    	 struct isc_scb *UNUSED(cb_session),
    	 void *user)
    {
        Connection *cp = user;
        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
    login_request(struct isc_scb *session)
    {
        Connection  * cp;
        const char *realuser;
        char *hostname = NULL;
        char peername[256];
        char portbuf[1+2+3*sizeof(long)];
        size_t hostlen;
        size_t portlen;
    
        /* 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;
        }
    
        hostname = get_host_name(session->raddr);
        if (hostname == NULL)
    	kom_log("WNG: login_request(): unknown hostid.\n");
    
        /* Get the real user name, as returned by the Ident protocol (rfc 931). */
        realuser = get_real_username(session, hostname);
        if (realuser == NULL && param.authentication_level == 2)
        {
    	kom_log("Connection from %s rejected - no IDENT available.\n", 
    	     hostname);
    
    	isc_puts("%% No IDENT server reachable at your site.\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;
    
        /* 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;
    
        if (hostname == NULL)
    	s_crea_str(&cp->hostname, "unknown");
        else
    	s_crea_str(&cp->hostname, hostname);
    
        if (realuser != NULL)
    	s_crea_str(&cp->ident_user, realuser);
    
        BUG(("\n[Client %lu from %s is connecting]\n", cp->session_no, hostname));
    
    
        isc_getipnum(session->raddr, peername, sizeof(peername));
        hostlen = strlen(peername);
        if (hostlen >= sizeof(peername)-1)
        {
    	kom_log("login_request(): truncated remote peer address %s.\n",
    		peername);
        }
        sprintf(portbuf, " %d", isc_getportnum(session->raddr));
        portlen = strlen(portbuf);
        cp->peer = smalloc(hostlen + portlen + 1);
        strcpy(cp->peer, peername);
        strcpy(cp->peer + hostlen, portbuf);
    
        update_stat(STAT_CLIENTS, 1);
        dump_connections();
    
        isc_set_read_callback(session, data_available_callback, write_err_cb,
    			  stale_cb, cp);
    }
    
    static void
    adjust_penalty(Connection *conn)
    {
        while (penalty_generation - conn->penalty_generation > 0)
        {
    	conn->penalty /= 2;
    	if (conn->penalty == 0)
    	    conn->penalty_generation = penalty_generation;
    	else
    	    conn->penalty_generation++;
        }
    }
    
    
    static void
    read_from_connection(Connection *conn)
    {
        Bool would_block = FALSE;
        Bool need_flush = FALSE;
    
        adjust_penalty(conn);
    
        while (!would_block && !go_and_die && conn->penalty < param.max_penalty
    	   && !conn->kill_pending)
        {
    	while (conn->more_to_parse
    	       && !go_and_die && conn->penalty < param.max_penalty
    	       && !conn->kill_pending)
    	    need_flush |= parse_unparsed(conn);
    
    	if (go_and_die || conn->penalty >= param.max_penalty
    	    || conn->kill_pending)
    	    break;
    
    	if (!conn->more_to_parse)
    	{
    	    switch (isc_read_data(conn->isc_session,
    				  &conn->unparsed,
    				  &conn->first_to_parse))
    	    {
    	    case ISC_READ_DATA:
    		conn->penalty += param.penalty_per_read;
    		conn->more_to_parse = TRUE;
    		break;
    	    case ISC_READ_ERROR:
    		if (errno != ECONNRESET)
    		    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 && !kill_pending)
    	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 (conn->penalty >= param.max_penalty)
        {
    	/* 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 (c->penalty < param.low_penalty)
    	    {
    		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)
    	    restart_kom("failed to re-enable session\n");
    
    	/* 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.  */
    	if (!go_and_die)
    	    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 *
    data_available_callback(oop_source *source,
    			int fd,
    			oop_event event,
    			void *user)
    {
        Connection *conn = user;
    
        assert(event == OOP_READ);
        assert(conn->isc_session->fd == fd);
        assert(isc_getoopsource(conn->isc_session) == source);
        assert(conn->on_queue == FALSE);
    
        /* Something arrived, so we are busy.  */
        if (is_idle)
        {
    	is_idle = FALSE;
    	enable_idle_check();
        }
    	
        work_done = TRUE;
    
        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_mcb *mcb,
    		    struct isc_scb *UNUSED(accepting_session),
    		    struct isc_scb *new_session)
    {
        assert(mcb == kom_server_mcb);
    
        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(mcb, 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. */
        source->on_time(source, OOP_TIME_NOW, check_idle_callback, NULL);
    
        /* 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)
    	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;
    }