/************************************************************************
 *   IRC - Internet Relay Chat, server/s_packet.c
 *
 *   Copyright (C) 2000-2003 TR-IRCD Development
 *
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Co Center
 *
 *   This program 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 2, or (at your option)
 *   any later version.
 *
 *   This program 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 this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *
 *   $Id: s_packet.c,v 1.6 2004/07/14 14:26:04 tr-ircd Exp $
 */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#include "s_bsd.h"
#include "parse.h"
#include "fd.h"
#include "packet.h"
#include "zlink.h"
#include "dh.h"
#include "s_conf.h"

static char readBuf[READBUF_SIZE];

int client_dopacket(aClient *cptr, char *buffer, size_t length);
int server_dopacket(aClient *cptr, char *buffer, size_t length);
int binary_dopacket(aClient *cptr, char *buffer, size_t length);

void init_dopacket(void *unused)
{
    memset(readBuf, 0, READBUF_SIZE);
    return;
}

void parse_client_queued(aClient *client_p)
{
    int dolen = 0;
    int fd_r;

    /* 
     * Handle flood protection here - if we exceed our flood limit on
     * messages in this loop, we simply drop out of the loop prematurely.
     *   -- adrian
     */
    for (;;) {
	if (!IsAnOper(client_p) && (client_p->sent_parsed > client_p->allow_read))
	    break;
	dolen = linebuf_get(&client_p->recvQ, readBuf, READBUF_SIZE,
			    LINEBUF_COMPLETE, LINEBUF_PARSED);
	if (!dolen)
	    break;
	if (client_dopacket(client_p, readBuf, dolen) == -2)
	    return;
#if 0
	client_p->sent_parsed++;
#endif
    }

    /* server fd may have changed */
    fd_r = client_p->fd;

    if (!IsDead(client_p)) {
        /* If we get here, we need to register for another COMM_SELECT_READ */
        if (IsServer(client_p) || IsNegoServer(client_p))
            comm_setselect(fd_r, FDLIST_IRCD, COMM_SELECT_READ, read_server_packet, client_p, 0);
        else
            comm_setselect(fd_r, FDLIST_IRCD, COMM_SELECT_READ, read_client_packet, client_p, 0);
    }

}

void read_client_packet(int fd, void *data)
{
    aClient *client_p = data;
    int length = 0;
    int lbuf_len;
    int fd_r = client_p->fd;
    int binary = 0;

    /* if the client is dead, kill it off now -davidt */
    if (IsDead(client_p)) {
	exit_client(client_p, &me,
		    (client_p->flags & FLAGS_SENDQEX) ? "SendQ exceeded" : "Dead socket");
	return;
    }

    /*
     * Read some data. We *used to* do anti-flood protection here, but
     * I personally think it makes the code too hairy to make sane.
     *     -- adrian
     */

    memset(readBuf, 0, READBUF_SIZE);

#ifdef HAVE_ENCRYPTION_ON
    if (IsSSL(client_p))
	length = safe_SSL_read(client_p, readBuf, READBUF_SIZE);
    else
#endif
	length = read(fd_r, readBuf, READBUF_SIZE);

    if (length <= 0) {
	if ((length == -1) && ignoreErrno(errno)) {
	    comm_setselect(fd_r, FDLIST_IRCD, COMM_SELECT_READ, read_client_packet, client_p, 0);
	    return;
	}
	dead_link_on_read(client_p, length);
	return;
    }

    if (client_p->lasttime < timeofday)
	client_p->lasttime = timeofday;
    if (client_p->lasttime > client_p->since)
	client_p->since = timeofday;
    client_p->flags &= ~FLAGS_PINGSENT;

    /*
     * Before we even think of parsing what we just read, stick
     * it on the end of the receive queue and do it when its
     * turn comes around.
     */

    if (IsServer(client_p) || IsNegoServer(client_p) || DoZipThis(client_p) || WantDKEY(client_p))
	binary = 1;

    if (binary) {
	binary_dopacket(client_p, readBuf, length);
    } else {

	lbuf_len = linebuf_parse(&client_p->recvQ, readBuf, length, binary);

	if (lbuf_len < 0) {
	    dead_link_on_read(client_p, 0);
	    return;
	}

	/* Check to make sure we're not flooding */
	if (IsPerson(client_p) && !IsAnOper(client_p) &&
	    (linebuf_alloclen(&client_p->recvQ) > ServerOpts.client_flood)) {
	    exit_client(client_p, client_p, "Excess Flood");
	    return;
	}

	parse_client_queued(client_p);
    }

    remove_exited_clients(NULL);
}

void read_server_packet(int fd, void *data)
{
    aClient *client_p = data;
    int length = 0;
    int fd_r = client_p->fd;

    /* if the client is dead, kill it off now -davidt */
    if (IsDead(client_p)) {
	exit_client(client_p, &me,
		    (client_p->flags & FLAGS_SENDQEX) ? "SendQ exceeded" : "Dead socket");
	return;
    }

    /*
     * Read some data. We *used to* do anti-flood protection here, but
     * I personally think it makes the code too hairy to make sane.
     *     -- adrian
     */

    memset(readBuf, 0, READBUF_SIZE);

    length = read(fd_r, readBuf, READBUF_SIZE);
    if (length <= 0) {
	if ((length == -1) && ignoreErrno(errno)) {
	    comm_setselect(fd_r, FDLIST_IRCD, COMM_SELECT_READ, read_server_packet, client_p, 0);
	    return;
	}
	dead_link_on_read(client_p, length);
	return;
    }

    if (client_p->lasttime < timeofday)
	client_p->lasttime = timeofday;
    if (client_p->lasttime > client_p->since)
	client_p->since = timeofday;
    client_p->flags &= ~FLAGS_PINGSENT;

    binary_dopacket(client_p, readBuf, length);

    /* server fd may have changed */
    fd_r = client_p->fd;

    if (!IsDead(client_p))
	comm_setselect(fd_r, FDLIST_IRCD, COMM_SELECT_READ, read_server_packet, client_p, 0);

    remove_exited_clients(NULL);
}

static void process_bytecount(aClient *cptr, int length)
{
    ++me.receiveM;
    ++cptr->receiveM;
    cptr->receiveB += length;

    if (cptr->receiveB > 1023) {
	cptr->receiveK += (cptr->receiveB >> 10);
	cptr->receiveB &= 0x03ff;	/* 2^10 = 1024, 3ff = 1023 */
    }

    me.receiveB += length;

    if (me.receiveB > 1023) {
	me.receiveK += (me.receiveB >> 10);
	me.receiveB &= 0x03ff;
    }
}

/*
 * client_dopacket - copy packet to client buf and parse it
 *      client_p - pointer to client structure for which the buffer data
 *             applies.
 *      buffer - pointr to the buffer containing the newly read data
 *      length - number of valid bytes of data in the buffer
 *
 */

static inline void restbuf_create(aClient *cptr)
{

    if (!HasRestbuf(cptr)) {
	cptr->restbuf = MyMalloc(sizeof(char *) * READBUF_SIZE * 4);
	cptr->protoflags |= PFLAGS_HASRESTBUF;
    }
}

int binary_dopacket(aClient *cptr, char *buffer, size_t length)
{
#ifdef HAVE_ENCRYPTION_ON
    if (IsRC4IN(cptr))
	rc4_process_stream(cptr->serv->rc4_in, buffer, length);
#endif

    if (ZipIn(cptr)) {
	if (unzip_buffer(cptr, buffer, length, server_dopacket) < 0) {
	    sendto_gnotice("Unzip error for %s: (%d) %s", cptr->name, errno, buffer);
	    return exit_client(cptr, &me, "fatal error in unzip!");
	}
	return 0;
    }
    
    return server_dopacket(cptr, buffer, length);
}

int server_dopacket(aClient *cptr, char *buffer, size_t length)
{
    int broken = 1;
    int tmplen = 1;
    int parseval = 0;
    int fd_r = 0;
    char t;

    if (cptr->waitlen > 0) {
	memcpy(cptr->restbuf + cptr->waitlen, buffer, length);
	buffer = cptr->restbuf;
	length += cptr->waitlen;
	memset(cptr->restbuf + length, 0, sizeof(char) * ((READBUF_SIZE * 4) - length));
    }

    process_bytecount(cptr, length);

    /* Now we have to play the following game:
     * It is obvious, that the buffer can contain cr/lf characters. 
     * we have to parse it, as soon as we receive a \r or a \n.
     * and then we have to start parse again, for the next part of 
     * the buffer. this is done continously, until the buffer is
     * is finished. -TimeMr14C
     */

    while (length) {
	tmplen = 0;
	broken = 1;
	cptr->waitlen = 0;
	while (length) {
	    t = buffer[tmplen];
	    length--;
	    if ((t == '\n') || (t == '\r')) {
		broken = 0;
		break;
	    }
	    tmplen++;

	}
	if ((tmplen > 0) && !broken) {

	    buffer[tmplen] = '\0';

	    parseval = server_parse(cptr, buffer, buffer + tmplen);
	    buffer = buffer + tmplen + 1;

	    switch (parseval) {
		case CLIENT_EXITED:
		    /* Exit_client is finished, fd is removed, there is
		     * nothing to send to or read from. ignore. -TimeMr14C
		     */
		    return 0;
		case FLUSH_BUFFER:
		    comm_setselect(cptr->fd, FDLIST_IRCD, COMM_SELECT_WRITE, send_queued, cptr, 0);
		    break;
		case ZIP_NEXT_BUFFER:
		    if (ZipIn(cptr)) {
			if (unzip_buffer(cptr, buffer, length, server_dopacket) < 0) {
			    sendto_gnotice("Another Unzip error for %s: (%d) %s", cptr->name, errno,
					   buffer);
			    exit_client(cptr, &me, "fatal error in unzip!");
			    return parseval;
			}
			return 0;
		    }
		    break;
#ifdef HAVE_ENCRYPTION_ON
		case RC4_NEXT_BUFFER:
		    if (length)
			rc4_process_stream(cptr->serv->rc4_in, buffer, length);
		    break;
#endif
		default:
		    break;
	    }
	} else if ((tmplen <= 0) && !broken) {
	    buffer = buffer + tmplen + 1;
	} else if (broken) {
	    restbuf_create(cptr);
	    cptr->waitlen = tmplen;
	    memcpy(cptr->restbuf, buffer, cptr->waitlen);
	}
    }

    /* server fd may have changed */
    fd_r = cptr->fd;

    if (!IsDead(cptr))
        comm_setselect(fd_r, FDLIST_IRCD, COMM_SELECT_READ, read_server_packet, cptr, 0);

    return 0;
}

int client_dopacket(aClient *cptr, char *buffer, size_t length)
{
    process_bytecount(cptr, length);

    if (user_parse(cptr, buffer, buffer + length) < 0)
	return -2;
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1