/*
* 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