/* * 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 #include #ifdef HAVE_SYS_SELECT_H #include #endif #ifdef HAVE_SYS_FILIO_H #include #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #include #include #include #include #include #include #include #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 */