/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: handler.c,v 1.112 2001/02/15 08:39:45 drscholl Exp $ */

#include <string.h>
#include <errno.h>
#include <stdlib.h>
#ifndef WIN32
#include <unistd.h>
#endif
#include "opennap.h"
#include "debug.h"
#if DEBUG
#include <ctype.h>
#endif

static  HANDLER (histogram);

/* 214 */
HANDLER (server_stats)
{
    (void) pkt;
    (void) len;
    (void) tag;
    send_cmd (con, MSG_SERVER_STATS, "%d %d %d", Users->dbsize, Num_Files,
              (int) (Num_Gigs / 1048576.));
}

/* 10018 :<server> <target> <packet>
   allows a server to send an arbitrary message to a remote user */
HANDLER (encapsulated)
{
    char   *nick, ch, *ptr;
    USER   *user;

    (void) tag;
    ASSERT (validate_connection (con));
    CHECK_SERVER_CLASS ("encapsulated");
    if (*pkt != ':')
    {
        log ("encapsulated: server message does not begin with a colon (:)");
        return;
    }
    nick = strchr (pkt + 1, ' ');
    if (!nick)
    {
        log ("encapsulated: missing target nick");
        return;
    }
    nick++;
    ptr = strchr (nick, ' ');
    if (!ptr)
    {
        log ("encapsulated: missing encapsulated packet");
        return;
    }
    ch = *ptr;
    *ptr = 0;
    user = hash_lookup (Users, nick);
    if (!user)
    {
        log ("encapsulated: no such user %s", nick);
        return;
    }
    if (user->local)
    {
        ptr++;
        queue_data (user->con, ptr, len - (ptr - pkt));
    }
    else
    {
        *ptr = ch;
        /* avoid copying the data twice by peeking into the send buffer to
           grab the message header and body together */
        pass_message (con, con->recvbuf->data + con->recvbuf->consumed,
                      4 + len);
    }
}

/* the windows napster client will hang indefinitely waiting for this, so
 * return what it expects.
 */
static HANDLER (version_check)
{
    (void) pkt;
    (void) tag;
    (void) len;
    if (ISUSER (con))
        send_cmd (con, MSG_CLIENT_VERSION_CHECK, "");
}

/* certain user commands need to be exempt from flood control or the server
 * won't work correctly.
 */
#define F_EXEMPT                1       /* exempt from flood control */

typedef struct
{
    unsigned int message;
            HANDLER ((*handler));
    unsigned int flags;
    unsigned long count;
    double  bytes;
}
HANDLER;

#define NORMAL(a,b) {a,b,0,0,0}
#define EXEMPT(a,b) {a,b,F_EXEMPT,0,0}

/* this is the table of valid commands we accept from both users and servers
   THIS TABLE MUST BE SORTED BY MESSAGE TYPE */
static HANDLER Protocol[] = {
    NORMAL (MSG_SERVER_ERROR, server_error),    /* 0 */
    NORMAL (MSG_CLIENT_LOGIN, login),   /* 2 */
    NORMAL (MSG_CLIENT_VERSION_CHECK, version_check),   /* 4 */
    NORMAL (MSG_CLIENT_LOGIN_REGISTER, login),  /* 6 */
    NORMAL (MSG_CLIENT_REGISTER, register_nick),        /* 7 */
    NORMAL (MSG_CLIENT_CHECK_PASS, check_password),     /* 11 */
    NORMAL (MSG_CLIENT_REGISTRATION_INFO, ignore_command),      /* 14 */
#ifndef ROUTING_ONLY
    EXEMPT (MSG_CLIENT_ADD_FILE, add_file),     /* 100 */
    EXEMPT (MSG_CLIENT_REMOVE_FILE, remove_file),       /* 102 */
#endif
    NORMAL (MSG_CLIENT_UNSHARE_ALL, unshare_all),       /* 110 */
#ifndef ROUTING_ONLY
    NORMAL (MSG_CLIENT_SEARCH, search), /* 200 */
#endif
    NORMAL (MSG_CLIENT_DOWNLOAD, download),     /* 203 */
    NORMAL (MSG_CLIENT_PRIVMSG, privmsg),       /* 205 */
    EXEMPT (MSG_CLIENT_ADD_HOTLIST, add_hotlist),       /* 207 */
    EXEMPT (MSG_CLIENT_ADD_HOTLIST_SEQ, add_hotlist),   /* 208 */
    NORMAL (MSG_CLIENT_BROWSE, browse), /* 211 */
    NORMAL (MSG_SERVER_STATS, server_stats),    /* 214 */
    NORMAL (MSG_CLIENT_RESUME_REQUEST, resume), /* 215 */
    NORMAL (MSG_CLIENT_DOWNLOAD_START, download_start), /* 218 */
    NORMAL (MSG_CLIENT_DOWNLOAD_END, download_end),     /* 219 */
    NORMAL (MSG_CLIENT_UPLOAD_START, upload_start),     /* 220 */
    NORMAL (MSG_CLIENT_UPLOAD_END, upload_end), /* 221 */
    NORMAL (MSG_CLIENT_CHECK_PORT, ignore_command),     /* 300 */
    NORMAL (MSG_CLIENT_REMOVE_HOTLIST, remove_hotlist), /* 303 */
    NORMAL (MSG_CLIENT_IGNORE_LIST, ignore_list),       /* 320 */
    NORMAL (MSG_CLIENT_IGNORE_USER, ignore),    /* 322 */
    NORMAL (MSG_CLIENT_UNIGNORE_USER, unignore),        /* 323 */
    NORMAL (MSG_CLIENT_CLEAR_IGNORE, clear_ignore),     /* 326 */
    NORMAL (MSG_CLIENT_JOIN, join),     /* 400 */
    NORMAL (MSG_CLIENT_PART, part),     /* 401 */
    NORMAL (MSG_CLIENT_PUBLIC, public), /* 402 */
    NORMAL (MSG_SERVER_PUBLIC, public), /* 403 */
    NORMAL (MSG_SERVER_NOSUCH, server_error),   /* 404 */
    NORMAL (MSG_SERVER_TOPIC, topic),   /* 410 */
    NORMAL (MSG_CLIENT_CHANNEL_BAN_LIST, channel_banlist),      /* 420 */
    NORMAL (MSG_CLIENT_CHANNEL_BAN, channel_ban),       /* 422 */
    NORMAL (MSG_CLIENT_CHANNEL_UNBAN, channel_ban),     /* 423 */
    NORMAL (MSG_CLIENT_CHANNEL_CLEAR_BANS, channel_clear_bans), /* 424 */
    NORMAL (MSG_CLIENT_DOWNLOAD_FIREWALL, download),    /* 500 */
    NORMAL (MSG_CLIENT_USERSPEED, user_speed),  /* 600 */
    NORMAL (MSG_CLIENT_WHOIS, whois),   /* 603 */
    NORMAL (MSG_CLIENT_SETUSERLEVEL, level),    /* 606 */
    NORMAL (MSG_SERVER_UPLOAD_REQUEST, upload_request), /* 607 */
    NORMAL (MSG_CLIENT_UPLOAD_OK, upload_ok),   /* 608 */
    NORMAL (MSG_CLIENT_KILL, kill_user),        /* 610 */
    NORMAL (MSG_CLIENT_NUKE, nuke),     /* 611 */
    NORMAL (MSG_CLIENT_BAN, ban),       /* 612 */
    NORMAL (MSG_CLIENT_ALTER_PORT, alter_port), /* 613 */
    NORMAL (MSG_CLIENT_UNBAN, unban),   /* 614 */
    NORMAL (MSG_CLIENT_BANLIST, banlist),       /* 615 */
    NORMAL (MSG_CLIENT_LIST_CHANNELS, list_channels),   /* 618 */
    NORMAL (MSG_CLIENT_LIMIT, queue_limit),     /* 619 */
    NORMAL (MSG_CLIENT_MOTD, show_motd),        /* 621 */
    NORMAL (MSG_CLIENT_MUZZLE, muzzle), /* 622 */
    NORMAL (MSG_CLIENT_UNMUZZLE, muzzle),       /* 623 */
    NORMAL (MSG_CLIENT_ALTER_SPEED, alter_speed),       /* 625 */
    NORMAL (MSG_CLIENT_DATA_PORT_ERROR, data_port_error),       /* 626 */
    NORMAL (MSG_CLIENT_WALLOP, wallop), /* 627 */
    NORMAL (MSG_CLIENT_ANNOUNCE, announce),     /* 628 */
    NORMAL (MSG_CLIENT_BROWSE_DIRECT, browse_direct),   /* 640 */
    NORMAL (MSG_SERVER_BROWSE_DIRECT_OK, browse_direct_ok),     /* 641 */
    NORMAL (MSG_CLIENT_CLOAK, cloak),   /* 652 */
    NORMAL (MSG_CLIENT_CHANGE_SPEED, change_speed),     /* 700 */
    NORMAL (MSG_CLIENT_CHANGE_PASS, change_pass),       /* 701 */
    NORMAL (MSG_CLIENT_CHANGE_EMAIL, change_email),     /* 702 */
    NORMAL (MSG_CLIENT_CHANGE_DATA_PORT, change_data_port),     /* 703 */
    NORMAL (MSG_CLIENT_PING_SERVER, ping_server),       /* 750 */
    NORMAL (MSG_CLIENT_PING, ping),     /* 751 */
    NORMAL (MSG_CLIENT_PONG, ping),     /* 752 */
    NORMAL (MSG_CLIENT_ALTER_PASS, alter_pass), /* 753 */
    NORMAL (MSG_CLIENT_SERVER_RECONFIG, server_reconfig),       /* 800 */
    NORMAL (MSG_CLIENT_SERVER_VERSION, server_version), /* 801 */
    NORMAL (MSG_CLIENT_SERVER_CONFIG, server_config),   /* 810 */
    NORMAL (MSG_CLIENT_CLEAR_CHANNEL, clear_channel),   /* 820 */
    NORMAL (MSG_CLIENT_REDIRECT, redirect_client),      /* 821 */
    NORMAL (MSG_CLIENT_CYCLE, cycle_client),    /* 822 */
    NORMAL (MSG_CLIENT_SET_CHAN_LEVEL, channel_level),  /* 823 */
    NORMAL (MSG_CLIENT_EMOTE, emote),   /* 824 */
    NORMAL (MSG_CLIENT_CHANNEL_LIMIT, channel_limit),   /* 826 */
    NORMAL (MSG_CLIENT_FULL_CHANNEL_LIST, full_channel_list),   /* 827 */
    NORMAL (MSG_CLIENT_KICK, kick),     /* 829 */
    NORMAL (MSG_CLIENT_NAMES_LIST, list_users), /* 830 */
    NORMAL (MSG_CLIENT_GLOBAL_USER_LIST, global_user_list),     /* 831 */
#ifndef ROUTING_ONLY
    EXEMPT (MSG_CLIENT_ADD_DIRECTORY, add_directory),   /* 870 */
#endif
    NORMAL (920, ignore_command),       /* 920 */

    /* non-standard messages */
    NORMAL (MSG_CLIENT_QUIT, client_quit),      /* 10000 */
    NORMAL (MSG_SERVER_LOGIN, server_login),    /* 10010 */
    NORMAL (MSG_SERVER_LOGIN_ACK, server_login_ack),    /* 10011 */
    NORMAL (MSG_SERVER_USER_SHARING, user_sharing),     /* 10012 */
    NORMAL (MSG_SERVER_REGINFO, reginfo),       /* 10014 */
    NORMAL (MSG_SERVER_REMOTE_SEARCH, remote_search),   /* 10015 */
    NORMAL (MSG_SERVER_REMOTE_SEARCH_RESULT, remote_search_result),     /* 10016 */
    NORMAL (MSG_SERVER_REMOTE_SEARCH_END, remote_search_end),   /* 10017 */
    NORMAL (MSG_SERVER_ENCAPSULATED, encapsulated),     /* 10018 */
    NORMAL (MSG_SERVER_LINK_INFO, link_info),   /* 10019 */
    NORMAL (MSG_SERVER_QUIT, server_disconnect),        /* 10020 - deprecated by 10101 */
    NORMAL (MSG_SERVER_NOTIFY_MODS, remote_notify_mods),        /* 10021 */
    NORMAL (MSG_SERVER_SERVER_PONG, server_pong),       /* 10022 */
    NORMAL (MSG_SERVER_TIME_CHECK, time_check), /* 10023 */
    NORMAL (MSG_SERVER_WHOIS_NOTIFY, whois_notify),     /* 10024 */
    NORMAL (MSG_CLIENT_CONNECT, server_connect),        /* 10100 */
    NORMAL (MSG_CLIENT_DISCONNECT, server_disconnect),  /* 10101 */
    NORMAL (MSG_CLIENT_KILL_SERVER, kill_server),       /* 10110 */
    NORMAL (MSG_CLIENT_REMOVE_SERVER, remove_server),   /* 10111 */
    NORMAL (MSG_CLIENT_LINKS, server_links),    /* 10112 */
    NORMAL (MSG_CLIENT_USAGE_STATS, server_usage),      /* 10115 */
    NORMAL (MSG_CLIENT_REHASH, rehash), /* 10116 */
    NORMAL (MSG_CLIENT_VERSION_STATS, client_version_stats),    /* 10118 */
    NORMAL (MSG_CLIENT_WHICH_SERVER, which_server),     /* 10119 */
    NORMAL (MSG_CLIENT_PING_ALL_SERVERS, ping_all_servers),     /* 10120 */
    NORMAL (MSG_CLIENT_WHO_WAS, who_was),       /* 10121 */
    NORMAL (MSG_CLIENT_MASS_KILL, mass_kill),   /* 10122 */
    NORMAL (MSG_CLIENT_HISTOGRAM, histogram),   /* 10123 */
    NORMAL (MSG_CLIENT_REGISTER_USER, register_user),   /* 10200 */
    NORMAL (MSG_CLIENT_USER_MODE, user_mode_cmd),       /* 10203 */
    NORMAL (MSG_CLIENT_OP, channel_op), /* 10204 */
    NORMAL (MSG_CLIENT_DEOP, channel_op),       /* 10205 */
    NORMAL (MSG_CLIENT_CHANNEL_WALLOP, channel_wallop), /* 10208 */
    NORMAL (MSG_CLIENT_CHANNEL_MODE, channel_mode),     /* 10209 */
    NORMAL (MSG_CLIENT_CHANNEL_INVITE, channel_invite), /* 10210 */
    NORMAL (MSG_CLIENT_CHANNEL_VOICE, channel_op),      /* 10211 */
    NORMAL (MSG_CLIENT_CHANNEL_UNVOICE, channel_op),    /* 10212 */
    NORMAL (MSG_CLIENT_CHANNEL_MUZZLE, channel_muzzle), /* 10213 */
    NORMAL (MSG_CLIENT_CHANNEL_UNMUZZLE, channel_muzzle),       /* 10214 */
    NORMAL (MSG_CLIENT_CLASS_ADD, generic_acl_add), /* 10250 */
    NORMAL (MSG_CLIENT_CLASS_DEL, generic_acl_del), /* 10251 */
    NORMAL (MSG_CLIENT_CLASS_LIST, generic_acl_list), /* 10252 */
    NORMAL (MSG_CLIENT_DLINE_ADD, generic_acl_add),
    NORMAL (MSG_CLIENT_DLINE_DEL, generic_acl_del),
    NORMAL (MSG_CLIENT_DLINE_LIST, generic_acl_list),
    NORMAL (MSG_CLIENT_ILINE_ADD, generic_acl_add),
    NORMAL (MSG_CLIENT_ILINE_DEL, generic_acl_del),
    NORMAL (MSG_CLIENT_ILINE_LIST, generic_acl_list),
    NORMAL (MSG_CLIENT_ELINE_ADD, generic_acl_add),
    NORMAL (MSG_CLIENT_ELINE_DEL, generic_acl_del),
    NORMAL (MSG_CLIENT_ELINE_LIST, generic_acl_list),

#ifndef ROUTING_ONLY
    EXEMPT (MSG_CLIENT_SHARE_FILE, share_file), /* 10300 */
#endif
#if 0
    NORMAL (MSG_CLIENT_BROWSE_NEW, browse_new), /* 10301 */
#endif
};
static int Protocol_Size = sizeof (Protocol) / sizeof (HANDLER);

/* dummy entry used to keep track of invalid commands */
static HANDLER unknown_numeric = { 0, 0, 0, 0, 0 };

/* 10123
 * report statistics for server commands.
 */
static HANDLER (histogram)
{
    unsigned long  count = 0;
    double  bytes = 0;
    int     l;

    (void) pkt;
    (void) len;
    CHECK_USER_CLASS ("histogram");
    if (con->user->level < LEVEL_ELITE)
    {
        permission_denied (con);
        return;
    }
    for (l = 0; l < Protocol_Size; l++)
    {
        send_cmd (con, tag, "%d %u %.0f", Protocol[l].message,
                  Protocol[l].count, Protocol[l].bytes);
        count += Protocol[l].count;
        bytes += Protocol[l].bytes;
    }
    send_cmd (con, MSG_SERVER_HISTOGRAM, "%d %u %.0f %lu %.0f",
              unknown_numeric.message, unknown_numeric.count,
              unknown_numeric.bytes, count, bytes);
}

/* use a binary search to find the table in the entry */
static int
find_handler (unsigned int tag)
{
    int     min = 0, max = Protocol_Size - 1, try;

    while (!SigCaught)
    {
        try = (max + min) / 2;
        if (tag == Protocol[try].message)
            return try;
        else if (min == max)
            return -1;          /* not found */
        else if (tag < Protocol[try].message)
        {
            if (try == min)
                return -1;
            max = try - 1;
        }
        else
        {
            if (try == max)
                return -1;
            min = try + 1;
        }
        ASSERT (min <= max);
    }
    return -1;
}

/* this is not a real handler, but takes the same arguments as one */
HANDLER (dispatch_command)
{
    int     l;
    u_char  byte;

    ASSERT (validate_connection (con));
    ASSERT (pkt != 0);

    /* HACK ALERT
       the handler routines all assume that the `pkt' argument is nul (\0)
       terminated, so we have to replace the byte after the last byte in
       this packet with a \0 to make sure we dont read overflow in the
       handlers.  the handle_connection() function should always allocate 1
       byte more than necessary for this purpose */
    ASSERT (VALID_LEN
            (con->recvbuf->data, con->recvbuf->consumed + 4 + len + 1));
    byte = *(pkt + len);
    *(pkt + len) = 0;
    l = find_handler (tag);
    if (l != -1)
    {
        ASSERT (Protocol[l].handler != 0);

        /* do flood control if enabled */
        if (Flood_Time > 0 && !(Protocol[l].flags & F_EXEMPT) && ISUSER (con))
        {
            /* this command is subject to flood control. */
            if (con->flood_start + Flood_Time < global.current_time)
            {
                /* flood expired, reset counters */
                con->flood_start = global.current_time;
                con->flood_commands = 0;
            }
            else if (++con->flood_commands >= Flood_Commands)
            {
                LIST   *list;

                log
                    ("dispatch_command: flooding from %s!%s (numeric = %hu)",
                     con->user->nick, con->host, tag);
                notify_mods (FLOODLOG_MODE,
                             "Flooding from %s!%s (numeric = %hu)",
                             con->user->nick, con->host, tag);
                /* stop reading from the descriptor until the flood counter
                 * expires.
                 */
                clear_read (con->fd);

                /* add to the list of flooders that is check in the main
                 * loop.  Since we don't traverse the entire client list we
                 * have to keep track of which ones to check for expiration
                 */
                list = CALLOC (1, sizeof (LIST));
                list->data = con;
                Flooders = list_push (Flooders, list);
            }
        }

        /* note that we pass only the data part of the packet */
        Protocol[l].handler (con, tag, len, pkt);
        Protocol[l].count++;
        Protocol[l].bytes += len;
        goto done;
    }
    log ("dispatch_command: unknown message: tag=%hu, length=%hu, data=%s",
         tag, len, pkt);
    unknown_numeric.message = tag;
    unknown_numeric.count++;
    unknown_numeric.bytes += len;

    send_cmd (con, MSG_SERVER_NOSUCH, "Unknown command code %hu", tag);
#if DEBUG
    /* if this is a server connection, shut it down to avoid flooding the
       other server with these messages */
    if (ISSERVER (con))
    {
        u_char  ch;
        int     bytes;

        /* dump some bytes from the input buffer to see if it helps aid
           debugging */
        bytes = con->recvbuf->datasize - con->recvbuf->consumed;
        /* print at most 128 bytes */
        if (bytes > 128)
            bytes = 128;
        fprintf (stdout, "Dump(%d): ",
                 con->recvbuf->datasize - con->recvbuf->consumed);
        for (l = con->recvbuf->consumed; bytes > 0; bytes--, l++)
        {
            ch = *(con->recvbuf->data + l);
            fputc (isprint (ch) ? ch : '.', stdout);
        }
        fputc ('\n', stdout);
    }
#endif /* DEBUG */
  done:
    /* restore the byte we overwrite at the beginning of this function */
    *(pkt + len) = byte;
}

void
handle_connection (CONNECTION * con)
{
    int     n;
    u_short tag, len;

    ASSERT (validate_connection (con));

    if (ISSERVER (con))
    {
        /* server data is compressed.  read as much as we can and pass it
           to the decompressor.  we attempt to read all data from the socket
           in this loop, which will prevent unnecessary passes through the
           main loop (since select would return immediately) */
        do
        {
            n = READ (con->fd, Buf, sizeof (Buf));
            if (n <= 0)
            {
                if (n == -1)
                {
                    /* try to empty the socket each time, so we read until
                     * we hit this error (queue empty).  this should only
                     * happen in the rare event that the data in the queue
                     * is a multiple of sizeof(Buf)
                     */
                    if (N_ERRNO == EWOULDBLOCK)
                        break;  /* not an error */
                    log
                        ("handle_connection: read: %s (errno %d) for host %s (fd %d)",
                         strerror (N_ERRNO), N_ERRNO, con->host, con->fd);
                }
                else
                    log ("handle_connection: EOF from %s", con->host);
                destroy_connection (con);
                return;
            }

            if (global.min_read > 0 && n < global.min_read)
            {
                log ("handle_connection: %d bytes from %s", n,
                        con->host);
            }

            global.bytes_in += n;
            /* this can safely be called multiple times in this loop.  the
             * decompressor will realloc the output buffer if there is not
             * enough room to store everything
             */
            if (buffer_decompress (con->recvbuf, con->sopt->zin, Buf, n))
            {
                destroy_connection (con);
                return;
            }
            /* if what we read was equal to sizeof(Buf) it's very likely
             * that more data exists in the queue
             */
        }
        while (n == sizeof (Buf));
    }
    else
    {
        /* create the input buffer if it doesn't yet exist */
        if (!con->recvbuf)
        {
            con->recvbuf = CALLOC (1, sizeof (BUFFER));
            if (!con->recvbuf)
            {
                OUTOFMEMORY ("handle_connection");
                destroy_connection (con);
                return;
            }
#if DEBUG
            con->recvbuf->magic = MAGIC_BUFFER;
#endif
            con->recvbuf->data = MALLOC (5);
            if (!con->recvbuf->data)
            {
                OUTOFMEMORY ("handle_connection");
                destroy_connection (con);
                return;
            }
            con->recvbuf->datamax = 4;
        }
        /* read the packet header if we haven't seen it already */
        while (con->recvbuf->datasize < 4)
        {
            n = READ (con->fd, con->recvbuf->data + con->recvbuf->datasize,
                      4 - con->recvbuf->datasize);
            if (n == -1)
            {
                if (N_ERRNO != EWOULDBLOCK)
                {
                    log
                        ("handle_connection: read: %s (errno %d) for host %s",
                         strerror (N_ERRNO), N_ERRNO, con->host);
                    destroy_connection (con);
                }
                return;
            }
            else if (n == 0)
            {
                destroy_connection (con);
                return;
            }
            global.bytes_in += n;
            con->recvbuf->datasize += n;
        }
        /* read the packet body */
        memcpy (&len, con->recvbuf->data, 2);
        len = BSWAP16 (len);
        if (len > 0)
        {
            if (len > Max_Command_Length)
            {
                log ("handle_connection: %hu byte message from %s",
                     len, con->host);
                destroy_connection (con);
                return;
            }

            /* if there isn't enough space to read the entire body, resize the
               input buffer */
            if (con->recvbuf->datamax < 4 + len)
            {
                /* allocate 1 extra byte for the \0 that dispatch_command()
                   requires */
                if (safe_realloc ((void **) &con->recvbuf->data, 4 + len + 1))
                {
                    OUTOFMEMORY ("handle_connection");
                    destroy_connection (con);
                    return;
                }
                con->recvbuf->datamax = 4 + len;
            }
            n = READ (con->fd, con->recvbuf->data + con->recvbuf->datasize,
                      len + 4 - con->recvbuf->datasize);
            if (n == -1)
            {
                /* since the header and body could arrive in separate packets,
                   we have to check for this here so we don't close the
                   connection on this nonfatal error.  we just wait for the
                   next packet to arrive */
                if (N_ERRNO != EWOULDBLOCK)
                {
                    log
                        ("handle_connection: read: %s (errno %d) for host %s",
                         strerror (N_ERRNO), N_ERRNO, con->host);
                    destroy_connection (con);
                }
                return;
            }
            else if (n == 0)
            {
                log ("handle_connection: EOF from %s", con->host);
                destroy_connection (con);
                return;
            }
            con->recvbuf->datasize += n;
            global.bytes_in += n;
        }
    }
    /* process as many complete commands as possible.  for a client this
       will be exactly one, but a server link may have sent multiple commands
       in one compressed packet */
    while (con->recvbuf->consumed < con->recvbuf->datasize)
    {
        /* if we don't have the complete packet header, wait until we
           read more data */

        if (con->recvbuf->datasize - con->recvbuf->consumed < 4)
            break;

        /* read the packet header */
        memcpy (&len, con->recvbuf->data + con->recvbuf->consumed, 2);
        memcpy (&tag, con->recvbuf->data + con->recvbuf->consumed + 2, 2);

        len = BSWAP16 (len);
        tag = BSWAP16 (tag);

        /* check if the entire packet body has arrived */
        if (con->recvbuf->consumed + 4 + len > con->recvbuf->datasize)
            break;
        /* require that the client register before doing anything else */
        if (con->class == CLASS_UNKNOWN &&
            (tag != MSG_CLIENT_LOGIN && tag != MSG_CLIENT_LOGIN_REGISTER &&
             tag != MSG_CLIENT_REGISTER && tag != MSG_SERVER_LOGIN &&
             tag != MSG_SERVER_LOGIN_ACK && tag != MSG_SERVER_ERROR &&
             tag != 4 &&        /* unknown: v2.0 beta 5a sends this? */
             tag != 300 && tag != 11 && tag != 920))
        {
            log ("handle_connection: %s is not registered", con->host);
            *(con->recvbuf->data + con->recvbuf->consumed + 4 + len) = 0;
            log ("handle_connection: tag=%hu, len=%hu, data=%s", tag, len,
                 con->recvbuf->data + con->recvbuf->consumed + 4);

#if 0
            /* not sure why the official servers do this, but lets
             * be compatible.
             */
            send_cmd (con, MSG_SERVER_ECHO, "%hu: %s", tag,
                      con->recvbuf->data + con->recvbuf->consumed + 4);

            con->recvbuf->consumed += 4 + len;
            break;
#else
            send_cmd (con, MSG_SERVER_ERROR, "invalid command");
            destroy_connection (con);
            return;
#endif
        }

        if (Servers && ISUSER (con))
        {
            /* check for end of share/unshare sequence.  in order to avoid
               having to send a single message for each shared file,
               the add_file and remove_file commands set a flag noting the
               start of a possible series of commands.  this routine checks
               to see if the end of the sequence has been reached (a command
               other than share/unshare has been issued) and then relays
               the final result to the peer servers.
               NOTE: the only issue with this is that if the user doesn't
               issue any commands after sharing files, the information will
               never get passed to the peer servers.  This is probably ok
               since this case will seldom happen */
            if (con->user->sharing)
            {
                if (tag != MSG_CLIENT_ADD_FILE
                    && tag != MSG_CLIENT_SHARE_FILE
                    && tag != MSG_CLIENT_ADD_DIRECTORY)
                {
                    pass_message_args (con, MSG_SERVER_USER_SHARING,
                                       "%s %hu %u", con->user->nick,
                                       con->user->shared, con->user->libsize);
                    con->user->sharing = 0;
                }
            }
            else if (con->user->unsharing)
            {
                if (tag != MSG_CLIENT_REMOVE_FILE)
                {
                    pass_message_args (con, MSG_SERVER_USER_SHARING,
                                       "%s %hu %u", con->user->nick,
                                       con->user->shared, con->user->libsize);
                    con->user->unsharing = 0;
                }
            }
        }
        /* call the protocol handler */
        dispatch_command (con, tag, len,
                          con->recvbuf->data + con->recvbuf->consumed + 4);
        /* mark data as processed */
        con->recvbuf->consumed += 4 + len;
    }
    if (con->recvbuf->consumed)
    {
        n = con->recvbuf->datasize - con->recvbuf->consumed;
        if (n > 0)
        {
            /* shift down unprocessed data */
            memmove (con->recvbuf->data,
                     con->recvbuf->data + con->recvbuf->consumed, n);
        }
        con->recvbuf->datasize = n;
        con->recvbuf->consumed = 0;     /* reset */
    }
}
