/*
 * dcc.cpp -- Part of the ezbounce IRC proxy
 *
 * (C) 1999-2002 Murat Deligonul
 *
 * 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 of the License, 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.  
 *
 * ---
 * This handles the dcc proxying and sending for ezbounce. 
 * The dcc command interception and redirecting is
 * done in conn.cpp.
 * ---
 *
 */
                   
/*
 * How big shall the buffer be expandable too?
 * If you keep needing to increase this it means I goofed up somewhere!
 */
 
#define BUFFSIZE 32720

/*
 * Packetsize for sending files 
 * Ought to be runtime configurable 
 */
 
#define PACKETSIZE 2048
 

#include "autoconf.h"

#include <sys/types.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#ifdef HAVE_SYS_FILIO_H
    #include <sys/filio.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
    #include <sys/ioctl.h>
#endif
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "debug.h"
#include "dcc.h"
#include "linkedlist.h"
#include "general.h"
#include "config.h" 
#include "ezbounce.h"


/* Status of a DCC connection: */
enum {
        DCC_ACCEPTED     = 0x8,              /* Connection accepted */
        DCC_CONNECTING   = 0x10,             /* DCC Proxy trying to establish full link */
        DCC_CONNECTED    = 0x20,             /* Full proxy link established */

/*
 * More stupid flags so I can do an efficient way of checking for
 * timeouts on waiting for receiver to connect. The timeout right now
 * is 90 seconds.
 */
        DCC_TIMES_UP     = 0x800,
        FILE_COMPLETE    = 0x1000
};



/* Does the port binding for the DCC listen sockets,
 * finding an open port in the config-specified range if
 * required */
static unsigned short bind_port(int fd, struct sockaddr_in * psin)
{
    if (pcfg.dcc_ports)
    {
        char tok[20];
        int x = 0;
        while (gettok(pcfg.dcc_ports, tok, sizeof(tok), ',', ++x))
        {
            char _lbound[8], _ubound[8];
            unsigned short lbound, ubound = 0;
            gettok(tok, _lbound, sizeof _lbound, '-', 1);
            lbound = atoi(_lbound);
            if (strchr(tok, '-'))
            {
                gettok(tok, _ubound, sizeof _ubound, '-', 2);
                ubound = atoi(_ubound);
            }

            DEBUG("bind_port(): Testing listen ports lower: %d upper: %d\n", lbound, ubound);
            if (!ubound)
                ubound = lbound;
            for (int i = lbound; i <= ubound; i++)
            {
                DEBUG("bind_port(): Now calling bind() on port %d\n", i);
                psin->sin_port = htons(i);
                if (!bind(fd, (struct sockaddr *) psin, sizeof(struct sockaddr_in)))
                    return psin->sin_port;
            }
        }
    }

    /* No dice? Bind to somewhere random */
    DEBUG("bind_port(): Binding to random port\n");
    psin->sin_port = htons(0);
    return bind(fd, (struct sockaddr *) psin, sizeof(struct sockaddr_in));
}


/*
 * The base dcc constructor, sets up the listening socket
 *
 */
dcc::dcc(conn * owner, struct sockaddr_in * host_info, unsigned short * listen_port)
{
    struct sockaddr_in sin;
    int listen_fd;
    bool success;
    
    sender = receiver = 0;
    priv = 0;
    this->owner = owner;

    memset(&sin, 0, sizeof(sin));
    if (host_info)
        memcpy(&sin, host_info, sizeof(sin));
    sin.sin_family = AF_INET;

    listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (listen_fd < 0 
        || bind_port(listen_fd, &sin) == (u_short) -1
        || listen(listen_fd,2) < 0)
    {
        *listen_port = 0;
        close_all();
        return;
    }

    socklen_t dummy = sizeof(sin);
    getsockname(listen_fd, (struct sockaddr *) &sin, &dummy);
    *listen_port = ntohs(sin.sin_port);
    if (host_info)
        memcpy(host_info, &sin, sizeof(sin));
    
    lsock = new dccsock(this, listen_fd, &success, POLLIN);
    if (!success)
    {
        *listen_port = 0;
        delete lsock;
        lsock = 0;
        close_all();
        return;
    }
    idle_timer = new generic_timer(90, &dcc::idle_timer_proc, this, timer::TIMER_ONESHOT);
    idle_timer->enable();

    stat = 0;
    start_time = time(NULL);
}

dcc::~dcc()
{
    close_all();
    
    delete sender;
    delete receiver;
    if (priv)
    {
        delete[] priv->str1;
        delete[] priv->str2;
    }
    delete priv;
    DEBUG("dcc::~dcc() for %p\n", this);
}

void dcc::close_all(void)
{
    delete receiver;
    delete sender;
    if (lsock)
    {
        if (idle_timer)
            idle_timer->disable();
        delete idle_timer;
        idle_timer = 0;
    }
    delete lsock;
    lsock = receiver = sender = 0;
    stat = 0;
}

dcc::dcc_priv * dcc::set_priv(const dcc::dcc_priv * pp)
{
    if (priv)
    {
        delete[] priv->str1;
        delete[] priv->str2;
    }
    else
        priv = new dcc_priv;

    priv->str1 = my_strdup(pp->str1);
    priv->str2 = my_strdup(pp->str2);
    priv->i    = pp->i;
    return priv;
}

/*
 * Create a dcc pipe object. Fill 'sender' with info given.
 * By default binds to default interface on random port, and
 * waits for the receiver to connect.
 * By supplying something non-null for host_info you can
 * control what port or interface. Also when function completes
 * you will know what IP address the sock was bound.
 *
 * On success: listen_port is assigned non-0 value.
 */
dccpipe::dccpipe(conn * owner, unsigned long sender_address, unsigned short sender_port,
         struct sockaddr_in * host_info, unsigned short * listen_port)
            : dcc::dcc(owner, host_info, listen_port)
{
    /*
     * At this point the constructor for the base class has been called
     * and we should have a socket setup, waiting for connections.
     */
    if (!*listen_port)
    {
        close_all();
        return;
    }

    sender = 0;
    receiver = 0;

    s_address = htonl(sender_address);
    s_port = htons(sender_port);
}


/*
 * Check an indiviual dccpipe object's sockets for events.
 * Handle connect completion, accepting connections and relaying data.
 *
 * Return any of the following:
 *
 * DCC_ACCEPTED    -- connection has been accepted - now trying to connect to the sender
 * DCC_ESTABLISHED -- async connect worked ok. transfer now in progress
 * DCC_CLOSED      -- dcc connection closed by eof from one side. (transfer complete? who knows) Kill me now
 * DCC_ERROR       -- fatal error somewhere. Kill me now.
 *
 */
int dccpipe::poll(struct sockaddr_in * psin)
{
    int ret = 0;

    if (stat & DCC_TIMES_UP)
        return DCC_PIPE_TIMEDOUT;
    if (lsock && lsock->revents(POLLIN))
    {
        bool success;
        socklen_t size = sizeof (struct sockaddr_in);
        int f = accept(lsock->fd, (struct sockaddr *) psin, &size);
        receiver = new dccsock(this, f, &success, POLLIN);
        if (!success)
            goto error;

        stat |= DCC_ACCEPTED;
        stat |= DCC_CONNECTING;
        nonblocking(receiver->fd);
        delete lsock;
        lsock = 0;
        /* kill idle timer */
        idle_timer->disable();
        delete idle_timer;
        idle_timer = 0;

        ret = DCC_CONNECTION_ACCEPTED;
        if (!do_connect())
            goto error;
    }
    /*
     * Remember: the receiver connects to the socket
     * we setup. So by now any read/write errors
     * from it mean that he's gone. So reset if that happens
     */
    else if (receiver && receiver->revents(POLLIN))
    {
        DEBUG("dccpipe::poll(): Readability on receiver\n");
        if (receiver->read() <= 0)
            goto error;
        if (sender)
            receiver->flush(sender);
        ret = 1;
    }
    if ((stat & DCC_CONNECTED) && sender->revents(POLLIN))
    {
        /*
         * Just check that we can get the data from the sender w/o
         * errors..
         */
        DEBUG("dccpipe::poll(): Readability on sender\n");
        if (sender->read() <= 0)
            goto error;
        sender->flush(receiver);
        receiver->set_events(POLLIN | POLLOUT);
        ret = 1;
    }

    if (receiver)
    {
        if (receiver->revents(POLLOUT))
        {
            DEBUG("dccpipe::poll(): Writability on receiver\n");
             /* Stuff that got queued */
            if (receiver->flushO() < 0 && errno != EAGAIN)
                goto error;
            ret = 1;
        }
        else if (receiver->revents(POLLERR) || receiver->revents(POLLHUP))
            goto error;
    }
    if (sender)
    {
        if (sender->revents(POLLOUT))
        {
            DEBUG("dccpipe::poll(): Writability on sender\n");
            if (stat & DCC_CONNECTING)
            {
                /*
                 * To test if the nonblocking connect worked, we
                 * need to another stupid recv_test and if that works
                 * then the connection is ok.
                 */
                if (recv_test(sender->fd) < 1)
                {
                    on_connect(errno);
                    return DCC_ERROR;
                }
                else
                {
                    /*
                     * Connection to the other guy worked.
                     * Send any crap that the receiver has sent to us to
                     * the sender that we have queued here.
                     */
                    ret = DCC_PIPE_ESTABLISHED;
                    on_connect(0);
                    receiver->flush(sender);
                    sender->flushO();
                    sender->set_events(POLLIN);
                }
            }
            else
            {
                /* Already connected -- Send anything that's queued */
                if (sender->flushO() < 0 && errno != EAGAIN)
                    goto error;
                ret = 1;
            }
        } else if (sender->revents(POLLERR) || sender->revents(POLLHUP))
            goto error;
    }

    /* Need to manage readability/writability checks and buffer size
     * regulations here:
     *  1   - mark sockets writable if their buffers
     *        have things waiting in them
     *  2   - if any sockets oBuff gets too big, don't mark
     *        the other one readable. */
    if ((ret == 1) && (stat & DCC_CONNECTED))
    {
        u_long snd = sender->obuff->get_size();
        u_long rcv = receiver->obuff->get_size();

        if (!snd)
            sender->set_events(POLLIN);
        if (!rcv)
            receiver->set_events(POLLIN);

        if (snd)
        {
            if (snd > BUFFSIZE)
            {
                DEBUG("dccpipe::poll() About to blow load to sender -- silencing receiver for a bit\n");
                receiver->set_events(POLLOUT);
                sender->set_events(POLLIN | POLLOUT);
            } else {
                DEBUG("dccpipe::poll() %ld left in ---> sender buffer\n", snd);
                sender->set_events(POLLIN | POLLOUT);
            }
        } else if (rcv) {
            if (rcv > BUFFSIZE)
            {
                DEBUG("dccpipe::poll() About to blow load to receiver -- silencing sender for a bit\n");
                sender->set_events(POLLOUT);
                receiver->set_events(POLLIN | POLLOUT);
            } else {
                DEBUG("dccpipe::poll() %ld left in ---> receiver buffer\n", rcv);
                receiver->set_events(POLLIN | POLLOUT);
            }
        }
    }
    if (sender)
        sender->set_revents(0);
    if (receiver)
        receiver->set_revents(0);
    return ret;

error:
    close_all();
    return DCC_ERROR;
}

bool dccpipe::on_connect(int err)
{
    stat &= ~DCC_CONNECTING;
    if (err)
    {
        close_all();
        return 0;
    }
    stat |= DCC_CONNECTED;
    return 1;
}

/*
 * Do a nonblocking connect to the receiver
 */
bool dccpipe::do_connect(void)
{
    struct sockaddr_in sin;
    bool success;
    int f = socket(AF_INET, SOCK_STREAM, 0);
    if (f < 0)
        return 0;

    sender = new dccsock(this, f, &success, POLLIN | POLLOUT);
    if (!success)
        return 0;

    sin.sin_family = AF_INET;
    sin.sin_port = 0;
    sin.sin_addr.s_addr = s_address;
    sin.sin_port = s_port;

    if (!nonblocking(f))
        return 0;

    switch (connect(f, (struct sockaddr *) &sin, sizeof sin))
    {
    case -1:
        if (errno != EINPROGRESS)
            return 0;
    default:
        return 1;
    }
}

dccsend::dccsend(conn * owner, const char * filename, struct sockaddr_in * host_info, unsigned short * listen_port, unsigned long * filesize)
            : dcc::dcc(owner, host_info, listen_port)
{
    /*
     * At this point the constructor for the base class has been called
     * and we should have a socket setup, waiting for connections.
     * *listen_port will be > 0 if this all ok.
     * Also check that we can actually read and send this file.
     */
    this->filename = 0;
    file = -1;
    sent = packets = bytes2send = 0;
    end_time = 0;

    if (!*listen_port || access(filename, R_OK))
    {
        *listen_port = 0;
        close_all();
        return;
    }

    struct stat st;
    ::stat(filename, &st);

    *filesize = st.st_size;
    this->filename = my_strdup(filename);
}

dccsend::~dccsend()
{
    close_all();
    delete[] filename;
    filename = 0;
    DEBUG("dccsend::~dccsend() for %p\n", this);
}

/*
 * Again like dcc::poll. Call once you've got a confirmation
 * that there is data waiting. Does the work needed.
 * Return:
 * DCC_SEND_ESTABLISHED:    Connection accepted. Send is happening.
 * DCC_CLOSED:              Socket was closed, but send is not complete. Kill object.
 * DCC_SEND_COMPLETE:       Ditto. Kill object.
 */
int dccsend::poll(struct sockaddr_in * psin)
{
    int ret = 0;

    if (stat & DCC_TIMES_UP)
    {
        if (stat & FILE_COMPLETE)
            return DCC_SEND_COMPLETE;
        return DCC_SEND_TIMEDOUT;
    }
    if (lsock && lsock->revents(POLLIN))
    {
        bool success;
        socklen_t size = sizeof (struct sockaddr_in);
        if ((file = open(filename, O_RDONLY)) < 0)
        {
            close_all();
            return DCC_ERROR;
        }

        struct stat st;
        fstat(file, &st);
        bytes2send = st.st_size;

        int f = accept(lsock->fd, (struct sockaddr *) psin, &size);

        receiver = new dccsock(this, f, &success, POLLOUT);
        if (!success)
        {
            close_all();
            return DCC_ERROR;
        }

        stat |= DCC_ACCEPTED;

        nonblocking(receiver->fd);
        delete lsock;
        lsock = 0;

        /* kill the idle timer */
        idle_timer->disable();
        delete idle_timer;
        idle_timer = 0;

        ret = DCC_SEND_ESTABLISHED;
        start_time = time(NULL);
    }
    if (receiver)
    {
        if (receiver->revents(POLLHUP) || receiver->revents(POLLERR))
        {
            close_all();
            end_time = time(NULL);
            return DCC_ERROR;
        }
        if (receiver->revents(POLLOUT))
        {
            if (file < 0)
            {
                /* done */
                close_all();
                return DCC_SEND_COMPLETE;
            }

            ret = send_next_packet();
            if (ret <= 0)
            {
               end_time = time(NULL);
               if (!ret)
                   if (sent == bytes2send)
                   {
                        /* DCC send is complete,
                         * however, since we don't bother to check for
                         * acknowledgement packets, we'll just close the
                         * file and chill until the other end closes
                         * the connection
                         */
                         stat |= FILE_COMPLETE;
                         receiver->set_events(0);

                         /* kill this DCC in 30 seconds */
                         idle_timer = new generic_timer(30, dccsend::idle_timer_proc, this, timer::TIMER_ONESHOT);
                         idle_timer->enable();

                         close(file);
                         file = -1;
                         return 1;
                   }

                /* link prematurely closed */
                close_all();
                return DCC_CLOSED;
            }
            if (sent == bytes2send)
            {
                receiver->set_events(0);
                stat |= FILE_COMPLETE;
                /* kill this DCC in 30 seconds */
                idle_timer = new generic_timer(30, dccsend::idle_timer_proc, this, timer::TIMER_ONESHOT);
                idle_timer->enable();
                close(file);
                file = -1;
                end_time = time(NULL);
                return 1;
            }
            ret = 1;
        }
    }
    return ret;
}

/*
 * Send the next packet and update internal counters
 * Return:
 * -1: Fatal error. Object will need to be killed
 * 0: Nothing to send, or could not send anything because of non-fatal error or
 *      something else
 * >0: # of bytes sent in this packet
 *
 * Does NOT close any files on error!
 */
int dccsend::send_next_packet(void)
{
    static char buffer[PACKETSIZE + 1];

    int bytesread = read(file, buffer, PACKETSIZE);

    if (bytesread == 0)
        /* Looks like we're at the end. Waiting for the final ack i guess */
        return 0;
    else if (bytesread < 0)
    {
        DEBUG("dccsend::send_next_packet(): Error in read: %s\n", strerror(errno));
        return -1;
    }

    int sent = send(receiver->fd, buffer, bytesread, 0);
    if (sent != bytesread)
    {
        if (sent == -1)
        {
            if (errno == EWOULDBLOCK || errno == ENOBUFS)
	            lseek(receiver->fd, -bytesread, SEEK_CUR);
            else
            {
                DEBUG("Non-recoverable failure in send(): %s\n", strerror(errno));
                return -1;
            }
         }
  		 else
            lseek(receiver->fd, -(bytesread - sent), SEEK_CUR);
    }
    this->sent += sent;
    this->packets++;
    DEBUG("dccsend::send_next_packet() total is: %d\n", this->sent);
    
    return sent;
}

void dccsend::close_all()
{
    if (file > -1)
    {
        close(file);
        file = -1;
    }
    dcc::close_all();
}

/**
 * This is the handler for the idle timer
 * It is a one-shot timer, i.e. in will execute 90 seconds
 * after creation of the listen socket.
 * Unless the connection is accepted before then; in that
 * case the timer will be destroyed.
 */
int dcc::idle_timer_proc(time_t t, int, void * data)
{
    DEBUG("dcc::idle_timer_proc() -- %p\n", data);
    dcc * d  = (dcc *) data;
    if (d->lsock)
    {
        d->stat |= DCC_TIMES_UP;
        delete d->idle_timer;
        d->idle_timer = 0;
        d->lsock->event_handler(0);
        return -1;      /* Force removal of this timer */
    }

    DEBUG("..confused\n");
    return 1;
}


int dccsend::idle_timer_proc(time_t t, int, void * data)
{
    DEBUG("dccsend::idle_timer_proc() -- %p\n", data);
    dccsend * d = (dccsend *) data;
    d->stat |= DCC_TIMES_UP;
    delete d->idle_timer;
    d->idle_timer = 0;
    d->receiver->event_handler(0);
    return -1;
}

/* int dcc::dccsock::event_handler(struct pollfd * pfd)
 * written in conn.cpp */




syntax highlighted by Code2HTML, v. 0.9.1