Skip to content
Snippets Groups Projects
Select Git revision
  • 0940c2d1fc2da1787b82fc1261b3a1d408a42ad4
  • master default
  • dbck-q-n-d-link
  • foutput-text_stat-override
  • generations
  • text-stat-sha256
  • use-nettle
  • import-nettle
  • refactor-cached_get_text
  • refactor-cached_get_text-part-2
  • add-text_store
  • introduce-generation_position
  • remove-reclamation
  • dbfile-temp-filenames
  • sstrdup
  • dbfile_open_read-check-magic
  • adns_dist
  • liboop_dist
  • search
  • isc
  • dbdbckmultiplechoice
  • last.cvs.revision
  • 2.1.2
  • 2.1.1
  • 2.1.0
  • adns_1_0
  • liboop_0_9
  • 2.0.7
  • search_bp
  • 2.0.6
  • 2.0.5
  • isc_1_01
  • Protocol-A-10.4
  • 2.0.4
  • 2.0.3
  • 2.0.2
  • 2.0.1
  • 2.0.0
  • isc_1_00
  • isc_merge_1999_05_01
  • isc_merge_1999_04_21
41 results

parser.h

Blame
  • connections.c 31.18 KiB
    /*
     * 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 <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 <netdb.h>
    #include <stdlib.h>
    
    #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 "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);
    }
    
    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 moved backward at least %g seconds.\n",
    		timeval_diff_d(last_time, current_time));
    	/* 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->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(&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;
    
        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