This repository has been archived on 2025-02-12. You can view files and clone it, but cannot push or open issues or pull requests.
NeoStats-NeoIRCd/servlink/io.c

702 lines
17 KiB
C
Raw Permalink Normal View History

2002-08-13 14:34:25 +00:00
/************************************************************************
* 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.
*
2002-08-13 14:45:13 +00:00
* $Id: io.c,v 1.2 2002/08/13 14:45:12 fishwaldo Exp $
2002-08-13 14:34:25 +00:00
*/
#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_LIBCRYPTO
#include <openssl/evp.h>
#include <openssl/err.h>
#endif
#ifdef HAVE_LIBZ
#include <zlib.h>
#endif
#include "servlink.h"
#include "io.h"
#include "control.h"
static int check_error(int, int, int);
static char *fd_name(int fd)
{
if (fd == CONTROL_R.fd)
return "control read";
if (fd == CONTROL_W.fd)
return "control write";
if (fd == LOCAL_R.fd)
return "data read";
if (fd == LOCAL_W.fd)
return "data write";
if (fd == REMOTE_R.fd)
return "network";
/* uh oh... */
return "unknown";
}
#if defined( HAVE_LIBCRYPTO ) || defined( HAVE_LIBZ )
static unsigned char tmp_buf[BUFLEN];
#endif
#if defined( HAVE_LIBZ ) && defined( HAVE_LIBZ )
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 < 5; 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 <6 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 = 0; i < 5; 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_W.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_LIBCRYPTO
if (in_state.crypt)
{
assert(EVP_DecryptUpdate(&in_state.crypt_state.ctx,
tmp_buf, &blen,
data, datalen));
assert(blen == datalen);
buf = tmp_buf;
}
#endif
#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)
send_error("Inflate failed: %d", ret);
blen = BUFLEN - in_state.zip_state.z_stream.avail_out;
if (in_state.zip_state.z_stream.avail_in)
{
send_data_blocking(LOCAL_W.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_W.fd, buf, blen);
}
void send_zipstats(struct ctrl_command *unused)
{
#ifdef HAVE_LIBZ
int i = 0;
int ret;
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;
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_out >> 24) & 0xFF);
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_out >> 16) & 0xFF);
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_out >> 8) & 0xFF);
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_out ) & 0xFF);
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_in >> 24) & 0xFF);
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_in >> 16) & 0xFF);
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_in >> 8) & 0xFF);
ctrl_buf[i++] = ((in_state.zip_state.z_stream.total_in ) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_in >> 24) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_in >> 16) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_in >> 8) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_in ) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_out >> 24) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_out >> 16) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_out >> 8) & 0xFF);
ctrl_buf[i++] = ((out_state.zip_state.z_stream.total_out ) & 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_W.fd, ctrl_buf, i), IO_WRITE, CONTROL_W.fd);
if (ret < i)
{
/* write incomplete, register write cb */
CONTROL_W.write_cb = write_ctrl;
/* deregister read_cb */
CONTROL_R.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(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_W.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_W.fd, in_state.buf, len);
/* XXX - is this portable?
* this obviously will fail on a non socket.. */
setsockopt(CONTROL_W.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_R.fd, tmp, 1),
IO_READ, CONTROL_R.fd)))
return;
cmd.command = tmp[0];
}
for (cdef = command_table; cdef->commandid; cdef++)
{
if (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_R.fd, len,
(2 - cmd.gotdatalen)),
IO_READ, CONTROL_R.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_R.fd,
(cmd.data + cmd.readdata),
cmd.datalen - cmd.readdata),
IO_READ, CONTROL_R.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_W.fd, (ctrl_buf + ctrl_ofs),
ctrl_len),
IO_WRITE, CONTROL_W.fd)))
return; /* no data waiting */
ctrl_len -= ret;
if (!ctrl_len)
{
/* write completed, de-register write cb */
CONTROL_W.write_cb = NULL;
/* reregister read_cb */
CONTROL_R.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) || defined(HAVE_LIBCRYPTO)
if (out_state.zip || out_state.crypt)
buf = tmp_buf;
#endif
while ((ret = check_error(read(LOCAL_R.fd, buf, READLEN),
IO_READ, LOCAL_R.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;
#ifdef HAVE_LIB_CRYPTO
if (out_state.crypt)
buf = tmp2_buf;
#endif
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 %d",
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
#ifdef HAVE_LIBCRYPTO
if (out_state.crypt)
{
/* encrypt data */
ret = blen;
if (!EVP_EncryptUpdate(&out_state.crypt_state.ctx,
out_state.buf, &blen,
buf, ret))
send_error("error encrypting outgoing data: EncryptUpdate: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(blen == ret);
}
#endif
ret = check_error(write(REMOTE_W.fd, out_state.buf, blen),
IO_WRITE, REMOTE_W.fd);
if (ret < blen)
{
/* write incomplete, register write cb */
REMOTE_W.write_cb = write_net;
/* deregister read_cb */
LOCAL_R.read_cb = NULL;
out_state.ofs = ret;
out_state.len = blen - ret;
return;
}
#if defined(HAVE_LIBZ) || defined(HAVE_LIBCRYPTO)
if (out_state.zip || out_state.crypt)
buf = tmp_buf;
#endif
}
}
void write_net(void)
{
int ret;
assert(out_state.len);
if (!(ret = check_error(write(REMOTE_W.fd,
(out_state.buf + out_state.ofs),
out_state.len),
IO_WRITE, REMOTE_W.fd)))
return; /* no data waiting */
out_state.len -= ret;
if (!out_state.len)
{
/* write completed, de-register write cb */
REMOTE_W.write_cb = NULL;
/* reregister read_cb */
LOCAL_R.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_LIBCRYPTO) || defined(HAVE_LIBZ)
if (in_state.crypt || in_state.zip)
buf = tmp_buf;
#endif
while ((ret = check_error(read(REMOTE_R.fd, buf, READLEN),
IO_READ, REMOTE_R.fd)))
{
blen = ret;
#ifdef HAVE_LIBCRYPTO
if (in_state.crypt)
{
/* decrypt data */
buf = in_state.buf;
#ifdef HAVE_LIBZ
if (in_state.zip)
buf = tmp2_buf;
#endif
if (!EVP_DecryptUpdate(&in_state.crypt_state.ctx,
buf, &blen,
tmp_buf, ret))
send_error("error decompressing incoming data - DecryptUpdate: %s",
ERR_error_string(ERR_get_error(), NULL));
assert(blen == ret);
}
#endif
#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)
send_error("inflate failed: %d", 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_W.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_W.fd, in_state.buf, blen),
IO_WRITE, LOCAL_W.fd);
if (ret < blen)
{
in_state.ofs = ret;
in_state.len = blen - ret;
/* write incomplete, register write cb */
LOCAL_W.write_cb = write_data;
/* deregister read_cb */
REMOTE_R.read_cb = NULL;
return;
}
#if defined(HAVE_LIBCRYPTO) || defined(HAVE_LIBZ)
if (in_state.crypt || in_state.zip)
buf = tmp_buf;
#endif
}
}
void write_data(void)
{
int ret;
assert(in_state.len);
if (!(ret = check_error(write(LOCAL_W.fd,
(in_state.buf + in_state.ofs),
in_state.len),
IO_WRITE, LOCAL_W.fd)))
return;
in_state.len -= ret;
if (!in_state.len)
{
/* write completed, de-register write cb */
LOCAL_W.write_cb = NULL;
/* reregister read_cb */
REMOTE_R.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 */
}