/***********************************************************************
 *   IRC - Internet Relay Chat, src/send.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: send.c,v 1.9 2005/01/18 17:00:15 tr-ircd Exp $ 
 */

#include "struct.h"
#include "channel.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#include "numeric.h"
#include "dh.h"
#include "zlink.h"
#include "msg.h"
#include "s_bsd.h"
#include "s_conf.h"

#ifdef HAVE_ENCRYPTION_ON
static char rc4buf[16384];
#endif

static unsigned char transtab[] = "..A.....................EHI.O.YW.ABGDEZH0IKLMN3OPR STYFXCWIYaehi.abgdezh8iklmn3oprsstufxywiuouw.";

/*
 * dead_link_on_write
 *
 * somewhere along the lines of sending out, there was an error.
 * we can't close it from the send loop, so mark it as dead
 * and close it from the main loop.
 *
 * if this link is a server, tell routing people.
 */

static int dead_link_on_write(aClient *to, char *notice, int sockerr)
{

    int errtmp = errno;		/* so we don't munge this later */

    to->flags |= FLAGS_DEADSOCKET;
    /*
     * If because of BUFFERPOOL problem then clean linebuf's now so that
     * notices don't hurt operators below.
     */
    linebuf_donebuf(&to->recvQ);
    linebuf_donebuf(&to->sendQ);
    /*
     * Ok, if the link we're dropping is a server, send a routing
     * * notice..
     */
    if (IsServer(to)) {
    char fbuf[512];

	ircsprintf(fbuf, "from %C: %s", &me, notice);
	sendto_gnotice(fbuf, get_client_name(to, HIDEME), strerror(errtmp));
	sendto_serv_butone(to, &me, TOK1_GNOTICE, notice,
			   get_client_name(to, HIDEME), strerror(errtmp));
    }

    // exit_client(to, to, "Dead Socket");
    return -1;
}

/*
 * * send_message *   Internal utility which delivers one message
 * buffer to the *      socket. Takes care of the error handling and
 * buffering, if *      needed.
 */
int send_message(aClient *cptr, char *msg, int len)
{
    buf_head_t linebuf;
    u_char c;
    short i;
    aClient *to = cptr;

    if (cptr->from)
	to = cptr->from;

    if (IsAnon(to))
	return 0;	
    if (to->fd < 0)
        return 0;               /* Thou shalt not write to closed descriptors */

    if (IsDead(to))
        return 0;

    c = 0;
    i = 0;
    if (IsForeigner(to))
	for (i = 0; (c = msg[i]); i++)
	    if (c > (u_char) 159)
		msg[i] = transtab[c - 160];

    logevent_call(LogSys.send_debug, to, len, msg);

    /* Chop trailing CRLF's .. */
    while((msg[len] == '\r') || (msg[len] == '\n') || (msg[len] == '\0'))
    	len--;

    if (IsServer(to) || IsNegoServer(to)) {
	if (len > 510) 
	    len = 510;
	msg[++len] = '\n';
	msg[++len] = '\0';
    } else {
	if (len > 509) 
	    len = 509;
	msg[++len] = '\r';
	msg[++len] = '\n';
	msg[++len] = '\0';
    }
    if (ZipOut(to)) {
	msg = zip_buffer(to, msg, &len);
	if (len == -1) {
	    sendto_ops("Zipout error for %C: %s", to, msg);
	    return dead_link_on_write(to, ":Zip output error for %s", IRCERR_ZIP);
	}

	if (len == 0)
	    return 0;
    }
#ifdef HAVE_ENCRYPTION_ON
    if (IsRC4OUT(to)) {
	rc4_process_stream_to_buf(to->serv->rc4_out, msg, rc4buf, len);
	msg = rc4buf;
    }
#endif

    if (linebuf_len(&to->sendQ) > to->sendqlen) {
        /*
         * this would be a duplicate notice, but it contains some useful information that
         * would be spamming the rest of the network. Kept in. - lucas 
         */
        if (IsServer(to))
            sendto_ops("Max SendQ limit exceeded for %s: %d > %d",
                       get_client_name(to, HIDEME), linebuf_len(&to->sendQ), get_sendq(to));
        to->flags |= FLAGS_SENDQEX;
        return dead_link_on_write(to, ":Max Sendq exceeded for %s, closing link", 0);
    }

    /*
     * * Update statistics. The following is slightly incorrect *
     * because it counts messages even if queued, but bytes * only
     * really sent. Queued bytes get updated in SendQueued.
     */
    to->sendM += 1;
    me.sendM += 1;

    linebuf_newbuf(&linebuf);
    linebuf_put(&linebuf, msg, len);
    linebuf_attach(&to->sendQ, &linebuf);
    linebuf_donebuf(&linebuf);
    send_queued(to->fd, to);
    return 0;
}

int will_exceed_sendq(aClient *cptr)
{
    if (linebuf_len(&cptr->sendQ) > cptr->sendqlen / 2)
	return 1;
    return 0;
} 

/*
 * * send_queued *    This function is called from the main
 * select-loop (or whatever) *  when there is a chance the some output
 * would be possible. This *    attempts to empty the send queue as far
 * as possible...
 */
void send_queued(int fd, void *data)
{
    int rlen;
    aClient *to = data;

    if (IsDead(to))
	return;

    if (linebuf_len(&to->sendQ) > 0) {
	while ((rlen = linebuf_flush(to)) > 0) {
	    to->sendB += rlen;
	    me.sendB += rlen;
	    if (to->sendB > 1023) {
		to->sendK += (to->sendB >> 10);
		to->sendB &= 0x03ff;	/* 2^10 = 1024, 3ff = 1023 */
	    } else if (me.sendB > 1023) {
		me.sendK += (me.sendB >> 10);
		me.sendB &= 0x03ff;
	    }
	}
	if ((rlen < 0) && (ignoreErrno(errno))) {
	    /* we have a non-fatal error, so just continue */
	} else if (rlen < 0) {
	    /* We have a fatal error */
	    dead_link_on_write(to, ":Write error to %s, closing link", errno);
	    return;
	} else if (rlen == 0) {
	    /* 0 bytes is an EOF .. */
	    dead_link_on_write(to, ":EOF during write to %s, closing link", errno);
	    return;
	}
    }

    if (linebuf_len(&to->sendQ) > 0)
	comm_setselect(fd, FDLIST_IRCD, COMM_SELECT_WRITE, send_queued, to, 0);

    if ((to->flags & FLAGS_SOBSENT) && !IsULine(to) && linebuf_len(&to->sendQ) < 20480) {	/* 20k */
	if (!(to->flags & FLAGS_BURST)) {
	    to->flags &= (~FLAGS_SOBSENT);
	    sendto_one_server(to, NULL, TOK1_BURST, "%d", linebuf_len(&to->sendQ));
	}
    }

    if (IsLocalClient(to) && to->lopts) {
	if (to->lopts->starthash) {
	    send_list(to, to->lopts);
	}
    }


    return;

}


syntax highlighted by Code2HTML, v. 0.9.1