elemental-ircd/servlink/io.c

658 lines
14 KiB
C

/************************************************************************
* IRC - Internet Relay Chat, servlink/io.c
*
* 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 1, 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: io.c 3319 2007-03-29 20:03:06Z jilles $
*/
#include "setup.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#ifdef HAVE_LIBZ
#include <zlib.h>
#endif
#include "servlink.h"
#include "io.h"
#include "control.h"
static int check_error(int, int, int);
static const char *
fd_name(int fd)
{
if(fd == CONTROL.fd)
return "control";
if(fd == LOCAL.fd)
return "data";
if(fd == REMOTE.fd)
return "network";
/* uh oh... */
return "unknown";
}
#if defined( HAVE_LIBZ )
static unsigned char tmp_buf[BUFLEN];
static unsigned char tmp2_buf[BUFLEN];
#endif
static unsigned char ctrl_buf[256] = "";
static unsigned int ctrl_len = 0;
static unsigned int ctrl_ofs = 0;
void
io_loop(int nfds)
{
fd_set rfds;
fd_set wfds;
int i, ret;
/* loop forever */
for (;;)
{
FD_ZERO(&rfds);
FD_ZERO(&wfds);
for (i = 0; i < 3; i++)
{
if(fds[i].read_cb)
FD_SET(fds[i].fd, &rfds);
if(fds[i].write_cb)
FD_SET(fds[i].fd, &wfds);
}
/* we have <3 fds ever, so I don't think select is too painful */
ret = select(nfds, &rfds, &wfds, NULL, NULL);
if(ret < 0)
{
check_error(ret, IO_SELECT, -1); /* exit on fatal errors */
}
else if(ret > 0)
{
/* call any callbacks */
for (i = 2; i >= 0; i--)
{
if(FD_ISSET(fds[i].fd, &rfds) && fds[i].read_cb)
(*fds[i].read_cb) ();
if(FD_ISSET(fds[i].fd, &wfds) && fds[i].write_cb)
(*fds[i].write_cb) ();
}
}
}
}
void
send_data_blocking(int fd, unsigned char *data, int datalen)
{
int ret;
fd_set wfds;
while (1)
{
ret = write(fd, data, datalen);
if(ret == datalen)
return;
else if(ret > 0)
{
data += ret;
datalen -= ret;
}
ret = check_error(ret, IO_WRITE, fd);
FD_ZERO(&wfds);
FD_SET(fd, &wfds);
/* sleep until we can write to the fd */
while (1)
{
ret = select(fd + 1, NULL, &wfds, NULL, NULL);
if(ret > 0) /* break out so we can write */
break;
if(ret < 0) /* error ? */
check_error(ret, IO_SELECT, fd); /* exit on fatal errors */
/* loop on non-fatal errors */
}
}
}
/*
* process_sendq:
*
* used before CMD_INIT to pass contents of SendQ from ircd
* to servlink. This data must _not_ be encrypted/compressed.
*/
void
process_sendq(struct ctrl_command *cmd)
{
send_data_blocking(REMOTE.fd, cmd->data, cmd->datalen);
}
/*
* process_recvq:
*
* used before CMD_INIT to pass contents of RecvQ from ircd
* to servlink. This data must be decrypted/decopmressed before
* sending back to the ircd.
*/
void
process_recvq(struct ctrl_command *cmd)
{
int ret;
unsigned char *buf;
int blen;
unsigned char *data = cmd->data;
unsigned int datalen = cmd->datalen;
buf = data;
blen = datalen;
ret = -1;
if(datalen > READLEN)
send_error("Error processing INJECT_RECVQ - buffer too long (%d > %d)",
datalen, READLEN);
#ifdef HAVE_LIBZ
if(in_state.zip)
{
/* decompress data */
in_state.zip_state.z_stream.next_in = buf;
in_state.zip_state.z_stream.avail_in = blen;
in_state.zip_state.z_stream.next_out = tmp2_buf;
in_state.zip_state.z_stream.avail_out = BUFLEN;
buf = tmp2_buf;
while (in_state.zip_state.z_stream.avail_in)
{
if((ret = inflate(&in_state.zip_state.z_stream, Z_NO_FLUSH)) != Z_OK)
{
if(!strncmp("ERROR ", (char *)in_state.zip_state.z_stream.next_in, 6))
send_error("Received uncompressed ERROR");
else
send_error("Inflate failed: %s", zError(ret));
}
blen = BUFLEN - in_state.zip_state.z_stream.avail_out;
if(in_state.zip_state.z_stream.avail_in)
{
send_data_blocking(LOCAL.fd, buf, blen);
blen = 0;
in_state.zip_state.z_stream.next_out = buf;
in_state.zip_state.z_stream.avail_out = BUFLEN;
}
}
if(!blen)
return;
}
#endif
send_data_blocking(LOCAL.fd, buf, blen);
}
void
send_zipstats(struct ctrl_command *unused)
{
#ifdef HAVE_LIBZ
int i = 0;
int ret;
u_int32_t len;
if(!in_state.active || !out_state.active)
send_error("Error processing CMD_ZIPSTATS - link is not active!");
if(!in_state.zip || !out_state.zip)
send_error("Error processing CMD_ZIPSTATS - link is not compressed!");
ctrl_buf[i++] = RPL_ZIPSTATS;
ctrl_buf[i++] = 0;
ctrl_buf[i++] = 16;
len = (u_int32_t) in_state.zip_state.z_stream.total_out;
ctrl_buf[i++] = ((len >> 24) & 0xFF);
ctrl_buf[i++] = ((len >> 16) & 0xFF);
ctrl_buf[i++] = ((len >> 8) & 0xFF);
ctrl_buf[i++] = ((len) & 0xFF);
len = (u_int32_t) in_state.zip_state.z_stream.total_in;
ctrl_buf[i++] = ((len >> 24) & 0xFF);
ctrl_buf[i++] = ((len >> 16) & 0xFF);
ctrl_buf[i++] = ((len >> 8) & 0xFF);
ctrl_buf[i++] = ((len) & 0xFF);
len = (u_int32_t) out_state.zip_state.z_stream.total_in;
ctrl_buf[i++] = ((len >> 24) & 0xFF);
ctrl_buf[i++] = ((len >> 16) & 0xFF);
ctrl_buf[i++] = ((len >> 8) & 0xFF);
ctrl_buf[i++] = ((len) & 0xFF);
len = (u_int32_t) out_state.zip_state.z_stream.total_out;
ctrl_buf[i++] = ((len >> 24) & 0xFF);
ctrl_buf[i++] = ((len >> 16) & 0xFF);
ctrl_buf[i++] = ((len >> 8) & 0xFF);
ctrl_buf[i++] = ((len) & 0xFF);
in_state.zip_state.z_stream.total_in = 0;
in_state.zip_state.z_stream.total_out = 0;
out_state.zip_state.z_stream.total_in = 0;
out_state.zip_state.z_stream.total_out = 0;
ret = check_error(write(CONTROL.fd, ctrl_buf, i), IO_WRITE, CONTROL.fd);
if(ret < i)
{
/* write incomplete, register write cb */
CONTROL.write_cb = write_ctrl;
/* deregister read_cb */
CONTROL.read_cb = NULL;
ctrl_ofs = ret;
ctrl_len = i - ret;
return;
}
#else
send_error("can't send_zipstats -- no zlib support!");
#endif
}
/* send_error
* - we ran into some problem, make a last ditch effort to
* flush the control fd sendq, then (blocking) send an
* error message over the control fd.
*/
void
send_error(const char *message, ...)
{
va_list args;
static int sending_error = 0;
struct linger linger_opt = { 1, 30 }; /* wait 30 seconds */
int len;
if(sending_error)
exit(1); /* we did _try_ */
sending_error = 1;
if(ctrl_len) /* attempt to flush any data we have... */
{
send_data_blocking(CONTROL.fd, (ctrl_buf + ctrl_ofs), ctrl_len);
}
/* prepare the message, in in_buf, since we won't be using it again.. */
in_state.buf[0] = RPL_ERROR;
in_state.buf[1] = 0;
in_state.buf[2] = 0;
va_start(args, message);
len = vsprintf((char *) in_state.buf + 3, message, args);
va_end(args);
in_state.buf[3 + len++] = '\0';
in_state.buf[1] = len >> 8;
in_state.buf[2] = len & 0xFF;
len += 3;
send_data_blocking(CONTROL.fd, in_state.buf, len);
/* XXX - is this portable?
* this obviously will fail on a non socket.. */
setsockopt(CONTROL.fd, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(struct linger));
/* well, we've tried... */
exit(1); /* now abort */
}
/* read_ctrl
* called when a command is waiting on the control pipe
*/
void
read_ctrl(void)
{
int ret;
unsigned char tmp[2];
unsigned char *len;
struct command_def *cdef;
static struct ctrl_command cmd = { 0, 0, 0, 0, NULL };
if(cmd.command == 0) /* we don't have a command yet */
{
cmd.gotdatalen = 0;
cmd.datalen = 0;
cmd.readdata = 0;
cmd.data = NULL;
/* read the command */
if(!(ret = check_error(read(CONTROL.fd, tmp, 1), IO_READ, CONTROL.fd)))
return;
cmd.command = tmp[0];
}
for (cdef = command_table; cdef->commandid; cdef++)
{
if((int)cdef->commandid == cmd.command)
break;
}
if(!cdef->commandid)
{
send_error("Unsupported command (servlink/ircd out of sync?): %d", cmd.command);
/* NOTREACHED */
}
/* read datalen for commands including data */
if(cdef->flags & COMMAND_FLAG_DATA)
{
if(cmd.gotdatalen < 2)
{
len = tmp;
if(!(ret = check_error(read(CONTROL.fd, len,
(2 - cmd.gotdatalen)), IO_READ, CONTROL.fd)))
return;
if(cmd.gotdatalen == 0)
{
cmd.datalen = len[0] << 8;
cmd.gotdatalen++;
ret--;
len++;
}
if(ret && (cmd.gotdatalen == 1))
{
cmd.datalen |= len[0];
cmd.gotdatalen++;
if(cmd.datalen > 0)
cmd.data = calloc(cmd.datalen, 1);
}
}
}
if(cmd.readdata < cmd.datalen) /* try to get any remaining data */
{
if(!(ret = check_error(read(CONTROL.fd,
(cmd.data + cmd.readdata),
cmd.datalen - cmd.readdata), IO_READ, CONTROL.fd)))
return;
cmd.readdata += ret;
if(cmd.readdata < cmd.datalen)
return;
}
/* we now have the command and any data */
(*cdef->handler) (&cmd);
if(cmd.datalen > 0)
free(cmd.data);
cmd.command = 0;
}
void
write_ctrl(void)
{
int ret;
assert(ctrl_len);
if(!(ret = check_error(write(CONTROL.fd, (ctrl_buf + ctrl_ofs),
ctrl_len), IO_WRITE, CONTROL.fd)))
return; /* no data waiting */
ctrl_len -= ret;
if(!ctrl_len)
{
/* write completed, de-register write cb */
CONTROL.write_cb = NULL;
/* reregister read_cb */
CONTROL.read_cb = read_ctrl;
ctrl_ofs = 0;
}
else
ctrl_ofs += ret;
}
void
read_data(void)
{
int ret, ret2;
unsigned char *buf = out_state.buf;
int blen;
ret2 = -1;
assert(!out_state.len);
#if defined(HAVE_LIBZ)
if(out_state.zip || out_state.crypt)
buf = tmp_buf;
#endif
while ((ret = check_error(read(LOCAL.fd, buf, READLEN), IO_READ, LOCAL.fd)))
{
blen = ret;
#ifdef HAVE_LIBZ
if(out_state.zip)
{
out_state.zip_state.z_stream.next_in = buf;
out_state.zip_state.z_stream.avail_in = ret;
buf = out_state.buf;
out_state.zip_state.z_stream.next_out = buf;
out_state.zip_state.z_stream.avail_out = BUFLEN;
if(!(ret2 = deflate(&out_state.zip_state.z_stream,
Z_PARTIAL_FLUSH)) == Z_OK)
send_error("error compressing outgoing data - deflate returned: %s",
zError(ret2));
if(!out_state.zip_state.z_stream.avail_out)
send_error("error compressing outgoing data - avail_out == 0");
if(out_state.zip_state.z_stream.avail_in)
send_error("error compressing outgoing data - avail_in != 0");
blen = BUFLEN - out_state.zip_state.z_stream.avail_out;
}
#endif
ret = check_error(write(REMOTE.fd, out_state.buf, blen), IO_WRITE, REMOTE.fd);
if(ret < blen)
{
/* write incomplete, register write cb */
REMOTE.write_cb = write_net;
/* deregister read_cb */
LOCAL.read_cb = NULL;
out_state.ofs = ret;
out_state.len = blen - ret;
return;
}
#if defined(HAVE_LIBZ)
if(out_state.zip)
buf = tmp_buf;
#endif
}
}
void
write_net(void)
{
int ret;
assert(out_state.len);
if(!(ret = check_error(write(REMOTE.fd,
(out_state.buf + out_state.ofs),
out_state.len), IO_WRITE, REMOTE.fd)))
return; /* no data waiting */
out_state.len -= ret;
if(!out_state.len)
{
/* write completed, de-register write cb */
REMOTE.write_cb = NULL;
/* reregister read_cb */
LOCAL.read_cb = read_data;
out_state.ofs = 0;
}
else
out_state.ofs += ret;
}
void
read_net(void)
{
int ret;
int ret2;
unsigned char *buf = in_state.buf;
int blen;
ret2 = -1;
assert(!in_state.len);
#if defined(HAVE_LIBZ)
if(in_state.zip)
buf = tmp_buf;
#endif
while ((ret = check_error(read(REMOTE.fd, buf, READLEN), IO_READ, REMOTE.fd)))
{
blen = ret;
#ifdef HAVE_LIBZ
if(in_state.zip)
{
/* decompress data */
in_state.zip_state.z_stream.next_in = buf;
in_state.zip_state.z_stream.avail_in = ret;
in_state.zip_state.z_stream.next_out = in_state.buf;
in_state.zip_state.z_stream.avail_out = BUFLEN;
while (in_state.zip_state.z_stream.avail_in)
{
if((ret2 = inflate(&in_state.zip_state.z_stream,
Z_NO_FLUSH)) != Z_OK)
{
if(!strncmp("ERROR ", (char *)buf, 6))
send_error("Received uncompressed ERROR");
send_error("Inflate failed: %s", zError(ret2));
}
blen = BUFLEN - in_state.zip_state.z_stream.avail_out;
if(in_state.zip_state.z_stream.avail_in)
{
if(blen)
{
send_data_blocking(LOCAL.fd, in_state.buf, blen);
blen = 0;
}
in_state.zip_state.z_stream.next_out = in_state.buf;
in_state.zip_state.z_stream.avail_out = BUFLEN;
}
}
if(!blen)
return; /* that didn't generate any decompressed input.. */
}
#endif
ret = check_error(write(LOCAL.fd, in_state.buf, blen), IO_WRITE, LOCAL.fd);
if(ret < blen)
{
in_state.ofs = ret;
in_state.len = blen - ret;
/* write incomplete, register write cb */
LOCAL.write_cb = write_data;
/* deregister read_cb */
REMOTE.read_cb = NULL;
return;
}
#if defined(HAVE_LIBZ)
if(in_state.zip)
buf = tmp_buf;
#endif
}
}
void
write_data(void)
{
int ret;
assert(in_state.len);
if(!(ret = check_error(write(LOCAL.fd,
(in_state.buf + in_state.ofs),
in_state.len), IO_WRITE, LOCAL.fd)))
return;
in_state.len -= ret;
if(!in_state.len)
{
/* write completed, de-register write cb */
LOCAL.write_cb = NULL;
/* reregister read_cb */
REMOTE.read_cb = read_net;
in_state.ofs = 0;
}
else
in_state.ofs += ret;
}
int
check_error(int ret, int io, int fd)
{
if(ret > 0) /* no error */
return ret;
if(ret == 0) /* EOF */
{
send_error("%s failed on %s: EOF", IO_TYPE(io), FD_NAME(fd));
exit(1); /* NOTREACHED */
}
/* ret == -1.. */
switch (errno)
{
case EINPROGRESS:
case EWOULDBLOCK:
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
case EALREADY:
case EINTR:
#ifdef ERESTART
case ERESTART:
#endif
/* non-fatal error, 0 bytes read */
return 0;
}
/* fatal error */
send_error("%s failed on %s: %s", IO_TYPE(io), FD_NAME(fd), strerror(errno));
exit(1); /* NOTREACHED */
}