/*
* conn.cpp -- part of the ezbounce IRC proxy.
*
* (C) 1998-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.
*
*/
#include "autoconf.h"
#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#ifdef HAVE_POLL_H
# include <poll.h>
#endif
#ifdef HAVE_SYS_POLL_H
# include <sys/poll.h>
#endif
#ifdef _USE_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/rand.h>
#endif
#include <sys/socket.h>
#include <sys/resource.h>
#include <arpa/inet.h>
#include <time.h>
#include <netdb.h>
#include <errno.h>
#include "ezbounce.h"
#include "conn.h"
#include "linkedlist.h"
#include "commands.h"
#include "dcc.h"
#include "server.h"
#include "debug.h"
#include "ruleset.h"
#include "general.h"
#include "lib_mdidentd.h"
#include "messages.h"
#include "ircaddr.h"
extern bool port_in_set(u_short, const char *);
int mk_ppchar(char *, char *(*buff)[MAX_PPCHAR_ARGS]);
unsigned conn::num_active = 0;
unsigned conn::current_id = 0;
unsigned conn::bytes_tos = 0;
unsigned conn::bytes_froms = 0;
unsigned conn::bytes_toc = 0;
unsigned conn::bytes_fromc = 0;
list<dcc> conn::dcc_list;
htbl conn::cmdhash(41);
htbl conn::incoming_hash(7);
conn::conn(int fd2accept, bool in_ssl)
{
socklen_t size = sizeof(struct sockaddr_in);
int fd_client = accept(fd2accept, (struct sockaddr *) &client_saddr, &size);
bool success;
last_recved = connect_time = ircproxy_time();
failed_passwords = 0;
stat = 0;
client = 0;
loglist = 0;
server = 0;
log = 0;
user = 0;
config = 0;
detach_timer = 0;
++num_active;
if (fd_client < 0)
{
printlog("failure in accept(): %s\n", strerror(errno));
return;
}
client = new psock(this, fd_client, &success, pcfg.min_buffer_size, pcfg.max_buffer_size);
if (!success)
{
printlog("Cannot accept connection from %s. Out of sockets.\n", inet_ntoa(client_saddr.sin_addr));
close(fd_client);
delete client;
client = 0;
return;
}
id = current_id++;
nonblocking(client->fd);
uinfo.irc = new ircaddr("(unknown)");
uinfo.fulladdr = new char[40];
sprintf(uinfo.fulladdr, "%lu:(unknown)@%s", id, inet_ntoa(client_saddr.sin_addr));
size = sizeof(struct sockaddr_in);
getsockname(client->fd, (struct sockaddr *) &local_saddr, &size);
printlog("CONNECTION FROM: %s (assigned id %d)\n", inet_ntoa(client_saddr.sin_addr), id);
#ifdef _USE_SSL
if (in_ssl) {
if (client->accept_to_ssl()!=1) {
printlog("Killing connection -- SSL negotiation failed\n");
printlog("ssl_accept() returned: %s\n", ERR_error_string(ERR_get_error(), NULL));
close(fd_client);
delete client;
client = 0;
return;
}
}
#endif
printlog("CONNECTION FROM: %s (assigned id %d)\n", inet_ntoa(client_saddr.sin_addr), id);
if (!is_authorized())
on_client_disconnect(0);
}
/**
* FIXME: when to call user->remove() ??
*/
conn::~conn()
{
DEBUG("conn::~conn() -- %p\n", this);
assert(num_active >= 0 && num_active < 500000);
die();
if (user)
user->remove(this);
num_active--;
}
/**
* Effectively puts the conn in a zombie state
*/
void conn::die()
{
DEBUG("conn::die() -- %p\n", this);
on_server_disconnect();
on_client_disconnect(0);
disown_dccs(0);
delete config;
config = 0;
assert(!server && !client);
}
int conn::check_server(int events)
{
int ret = 0;
if (dead())
return CONN_KILLME; /* dead object */
if (!server)
return CONN_NOTCONNECTED; /* client has not connected to any irc server */
if ((events & POLLERR) || (events & POLLHUP))
goto error;
else if ((events & POLLOUT))
{
if (checkf(CONNECTING))
{
/* Test if the non-blocking connect worked */
char dummy;
#ifdef _USE_SSL
if (server->ssl)
ret = SSL_pending(server->ssl);
else
#endif
ret = recv(server->fd, &dummy, 1, MSG_PEEK);
if (ret < 0 && errno != EAGAIN)
{
on_server_connect(errno); /* Will call on_server_disconnect() and stuff */
return 1;
}
on_server_connect(0);
return CONN_LINK_ACTIVATED;
}
/* Not connecting, send queued data --
* if there's none left, remove writability check */
if (server->flushO() < 0)
goto error;
if (!server->obuff->get_size())
{
server->set_events(POLLIN);
if (client)
client->set_events(client->events() | POLLIN);
}
}
if (!(events & POLLIN))
return 1;
/* get from server and *
* ... check results */
ret = server->read();
switch (ret)
{
case 0:
/* getting 0 from FIONREAD if socket is marked readable
* means it has been closed */
error:
case SOCK_ERR_READ:
on_server_disconnect();
if (checkf(DETACHED))
return CONN_KILLME;
cprintf(msg_disconnected);
return CONN_DISCONNECTED;
case SOCK_ERR_FULL:
return CONN_FULL;
case SOCK_ERR_MEM:
printlog("FATAL: memory allocation failure -- aborting server\n");
abort();
default:
bytes_froms += ret;
if (parse_from_server() < 0)
return CONN_KILLME;
return 1;
}
return 0;
}
/*
* Check client's socket. Parse and relay.
*/
int conn::check_client(int events)
{
int ret = 0;
if (checkf(DETACHED))
return 1;
if (dead())
return CONN_KILLME;
if ((events & POLLERR))
{
printlog("POLLERR caught: tell author\n");
goto error;
}
else if ((events & POLLHUP))
goto error;
else if (events & POLLOUT)
{
/* Send queued data */
if (client->flushO() < 0)
goto error;
if (!client->obuff->get_size())
{
client->set_events(POLLIN);
if (server)
server->set_events(server->events() | POLLIN);
}
}
if (!(events & POLLIN))
return 1;
ret = client->read();
switch (ret)
{
case 0:
/* getting 0 from FIONREAD if socket is marked readable
* means it has been closed */
case SOCK_ERR_READ:
printlog("Disconnect: %s (while reading).\n", addr());
error:
if ((config) && config->decide(PREF_AUTO_DETACH)
&& checkf(BOUNCED))
{
if (do_auto_detach())
return 1;
}
/* If client died, terminate server connection as well */
on_server_disconnect();
on_client_disconnect();
return CONN_KILLME;
case SOCK_ERR_FULL:
return CONN_FULL;
case SOCK_ERR_MEM:
printlog("FATAL: failed to allocate some memory -- bailing out.\n");
abort();
default:
/* Update stats & parse & relay ... */
last_recved = ircproxy_time();
bytes_fromc += ret;
if (parse() < 0)
return CONN_KILLME;
return 1;
}
return 0;
}
/*
* return values for parse() and parse_from_server():
* 1: OK
* -1: something occured requring destruction of the object
*/
int conn::parse(void)
{
char command[12];
int r;
const struct cmd * c;
char * lp;
db_parser db(client->ibuff);
while ((client) && (lp = db.get_next_line(1)))
{
if (!*lp)
continue; /* blank line */
bool del_lp = 0;
char *argv[MAX_PPCHAR_ARGS];
gettok(lp, command,12,' ', 1);
ToUpper(command);
c = cmdhash.lookup(command,
stat | ((stat & BOUNCED) ? NDM : 0));
if (c)
{
memset(argv, 0, sizeof (argv));
int argc = mk_ppchar(lp + strlen(command) + 1,
&argv);
argv[1] = no_leading(argv[1]);
DEBUG(">>>>>> HANDLER: %s(%d, %d, %s)\n", c->msg, c->id, argc, argv[0]);
#ifndef GCC_COMPILER_BUG
r = (this->*(c->handler))(c->id, argc, argv);
#else
r = (this->*(handlers[c->idx]))(c->id, argc, argv);
#endif
if (r < 0)
{
delete[] argv[0];
if (client)
db.zap();
if (server)
server->flushO();
return -1;
}
else if (r > 0)
{
delete[] argv[0];
continue;
}
/* This is for commands that are detected but not handled: */
lp = argv[0];
del_lp = 1;
}
if (checkf(BOUNCED))
{
/* If we get here, we are sending it to the server:
* if (del_lp), original line has been clobbered, and we have
* argv[0] that is missing the original command. so queue
* that up too */
if (del_lp)
{
server->queue(command, strlen(command));
server->queue(" ", 1);
}
if (lp)
server->queue(lp, strlen(lp));
server->queue("\r\n", 2);
}
if (del_lp)
delete[] lp;
}
if (checkf(BOUNCED))
{
/* Send everything and do rate limiting */
r = server->flushO();
if (r > 0)
bytes_tos += r;
r = (server) ? server->obuff->get_size() : 0;
if (r)
{
if ((unsigned) r > (pcfg.max_buffer_size / 2))
{
DEBUG("conn::parse() -- limiting client read -- about to blow buffer\n");
client->set_events(client->events() & ~POLLOUT);
server->set_events(POLLIN | POLLOUT);
}
else {
DEBUG("conn::parse() -- %d left in server buffer: doing POLLOUT\n", r);
server->set_events(POLLIN | POLLOUT);
}
}
}
if (client)
db.zap();
return 1;
}
/*
* Handles the connection process to a server.
* Checks if rule sets permit it, and handles registering
* w/ the rule sets.
*/
int conn::do_connect(const char *where, u_short port, const char *pass, int inssl)
{
struct sockaddr_in sin;
int s;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
return 0;
memset(&sin, 0, sizeof(sin));
DEBUG("do_connect(%s, %d, %s, %d)\n", where, port, pass, inssl);
sin.sin_addr.s_addr = config->get(PREF_VHOST);
sin.sin_family = AF_INET;
if ((bind(s, (const struct sockaddr *) &sin, sizeof(sin)) < 0)
|| (!resolve_address(where, &sin)))
{
close(s);
return -1;
}
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
nonblocking(s);
/*
* Right about to connect.. we have local port and remote? port
* so we register the fake ident if needed
*/
char * fake = config->get(PREF_FAKE_IDENT, 0);
if (config->checkf(OPT_ENABLE_FAKE_IDENTS) && fake)
{
int x = sizeof(struct sockaddr_in);
struct sockaddr_in sin2;
getsockname(s, (struct sockaddr *) &sin2, (socklen_t *) &x);
x = register_fake_ident(MDIDENTD_PATH, fake, ntohs(sin2.sin_port), port);
if (x != IDENT_OK)
{
cprintf("Error registering fake ident '%s' for you (but continuing anyway): %s\r\n",
fake, ident_error(x));
printlog("Fake ident FAILED: '%s' for %s: %s\n",
fake, addr(), ident_error(x));
} else
printlog("Fake ident registered: '%s' for %s\n", fake, addr());
}
switch (connect(s, (struct sockaddr *) &sin, sizeof(sin)))
{
case -1:
/* Non blocking connect in progress or something f*cked up */
bool succ;
if (errno != EINPROGRESS)
break;
server = new psock(this, s, &succ, pcfg.min_buffer_size, pcfg.max_buffer_size);
#ifdef _USE_SSL
if (succ&&inssl) {
DEBUG("Switching to SSL mode...\n");
succ = server->switch_to_ssl();
if (succ) {
cprintf("Switched to SSL...\r\n");
DEBUG("Switched to SSL...\n");
} else {
printlog("Connection failed: Failed switching to SSL\n");
cprintf("Connection failed: Failed switching to SSL\r\n");
delete server;
server = 0;
break;
}
}
#endif
if (!succ)
{
printlog("Connection failed: socket table full\n");
cprintf("Unable to connect: socket table full\r\n");
delete server;
server = 0;
break;
}
setf(CONNECTING);
uinfo.port = port;
delete[] uinfo.ircpass;
if (pass)
uinfo.ircpass = my_strdup(pass);
goto success;
case 0:
/* Connection worked immediately, this *might* happen, and we'll
* handle it to be safe */
server = new psock(this, s, &succ);
#ifdef _USE_SSL
if (succ&&inssl)
{
DEBUG("Switching to SSL mode...\n");
succ = server->switch_to_ssl();
}
#endif
if (!succ)
{
printlog("Connection failed: socket table full\n");
cprintf("Unable to connect: socket table full\n");
delete server;
server = 0;
break;
}
uinfo.port = port;
delete[] uinfo.ircpass;
if (pass)
uinfo.ircpass = my_strdup(pass);
on_server_connect(0);
goto success;
}
close(s);
return 0;
success:
server->set_events(POLLIN | POLLOUT);
ruleset::list_register_to(&rulesets, where, port);
setf(TO_RULESETS_REGISTERED);
return 1;
}
/**
* Look for a conn with an id 'i' in list 'l'
*/
/* static */ conn * conn::lookup(list<conn> * l, unsigned i)
{
list_iterator<conn> li(l);
while (li.has_next())
{
conn * c = li.next();
if (c->id == i)
return c;
}
return 0;
}
void conn::kill(const char *killer, const char *reason)
{
if (client)
{
cprintf("You were killed by %s: %s\r\n", killer, reason);
client->printf("ERROR :Closing Link: %s (Killed)\r\n", addr());
}
die();
}
/*
* Go thru rules and make sure he can connect there.
* Display nice little warning message if can't.
*/
bool conn::can_connect(const char *towhere, u_short port)
{
char reasonbuff[200];
if (checkf(ADMIN))
return 1;
/* First, check for self connection attempts if needed */
if (pcfg.flags & PREVENT_SELF_CONNECTS)
{
struct sockaddr_in sin;
bool resolved = resolve_address(towhere, &sin);
/*
* Deny access if:
* port matches one of the ones we're listening on
* AND:
* client is trying to connect to 127.*
* OR
* client is trying to connect to interface we are listening on
*/
if ((port_in_set(port, pcfg.ports) || port_in_set(port, pcfg.sslports))
&& ((resolved && sin.sin_addr.s_addr == pcfg.iface_listen.s_addr) ||
(resolved && wild_match("127.*", inet_ntoa(sin.sin_addr)))
)
)
{
cprintf(msg_cannot_connect, towhere, "Self-connects are not permitted");
return 0;
}
}
int r = ruleset::list_is_allowed_to(&rulesets, inet_ntoa(client_saddr.sin_addr), towhere, port,
reasonbuff, sizeof(reasonbuff));
if (r < 0)
{
cprintf(msg_cannot_connect, towhere, reasonbuff);
return 0;
} else if (r > 0)
return 1;
cprintf(msg_cannot_connect, towhere, "Host and/or port is not on my allowed-to list");
return 0;
}
/**
* Sends a message to all users
*/
/* static */ void conn::broadcast(list<conn> * l, const char *msg)
{
list_iterator<conn> iter(l);
while (iter.has_next())
{
conn * c = iter.next();
if (c->client)
c->cprintf("%s\r\n", msg);
}
}
/**
* Called socket gets connected, err != 0 in case it failed.
* use 0x29A for err for user abort.
*/
int conn::on_server_connect(int err)
{
socklen_t size = sizeof(serv_saddr);
if (!err)
{
/* If connection ok, update internal variables */
getpeername(server->fd, (struct sockaddr *) &serv_saddr, &size);
clearf(CONNECTING);
setf(BOUNCED);
server->set_events(POLLIN);
client->clearO();
cprintf(msg_conn_successful, uinfo.server);
/* Now register with the server */
if (uinfo.ircpass)
server->printf("PASS :%s\n", uinfo.ircpass);
server->printf("USER %s\nNICK :%s\n", uinfo.usercmd, uinfo.irc->nick);
printlog("Connect SUCCESSFUL: %s to %s:%d\n",
addr(), uinfo.server, uinfo.port);
delete[] uinfo.ircpass;
uinfo.ircpass = NULL;
return 1;
}
const char * error = (err == 0x29A) ? "cancel request from user" : strerror(errno);
cprintf(msg_conn_failed, uinfo.server, error);
printlog("Connect attempt FAILED: %s to %s:%d: %s\n", addr(), uinfo.server, uinfo.port,
error);
on_server_disconnect();
return 1;
}
/*
* Return:
* 0 -- Not idle
* 1 -- Idle too long
* 2 -- Idle too long; didn't register in time
*
* Idle time limits do not apply to admins.
* Idle time limits do not apply when when connected to an irc server.
*/
int conn::is_idle(time_t now)
{
if (dead() || checkf(ADMIN) || checkf(BOUNCED) || checkf(DETACHED))
return 0;
/* User not registered */
if (!checkf(USERED) || !checkf(NICKED))
if (now - connect_time > pcfg.max_register_time
&& pcfg.max_register_time != 0)
return 2;
if (checkf(USERED) && checkf(NICKED) && checkf(PWED))
if (now - last_recved > config->get(OPT_MAX_IDLE_TIME)
&& config->get(OPT_MAX_IDLE_TIME) != 0)
return 1;
return 0;
}
int conn::cprintf(const char *message, ...) const
{
static const char *hdr = ":" EZBOUNCE_HEADER " NOTICE ";
int i;
va_list ap;
if (!client)
{
DEBUG("Warning: cprintf() for non-existant client %p\n", this);
return 0;
}
bytes_toc += client->printf("%s%s :", hdr, uinfo.irc->nick);
va_start(ap, message);
i = client->printf_raw(message, &ap);
va_end(ap);
client->flushO();
bytes_toc +=i;
return i;
}
/* Allow sending of multi-lined messages. \n is the seperator. */
int conn::cprintf_multiline(const char * message) const
{
char line[128];
int cur = 1;
while (gettok(message, line, sizeof(line), '\n', cur++))
cprintf("%s\n", line);
return 1;
}
/*
* Detach client from proxy but keep his connection to server alive
*
* client must:
* be connected to a server
* not already be detached
* (we assume caller has checked so)
*/
int conn::detach(const char * password)
{
/* get rid of these */
delete[] uinfo.ircpass;
delete[] uinfo.usercmd;
uinfo.ircpass = uinfo.usercmd = NULL;
uinfo.detachpass = my_strdup(password);
/*
* Setup logging now. Must have proper options or be admin.
* Inform user of errors and wonderful options
*/
if (config->get(OPT_LOG_OPTIONS))
{
int result;
log = new logfile(pcfg.logdir, user->name, id, config->get(OPT_LOG_OPTIONS),
(checkf(ADMIN) ? 0 : config->get(OPT_MAX_LOGFILE_SIZE)), password, &result);
if (result < 0)
{
cprintf("Tried to setup logging but failed. Error is: %s\r\n", strerror(errno));
printlog("Failed log setup for %s: %s\n", addr(), strerror(errno));
delete log;
log = 0;
}
else if (result == 0)
{
cprintf("Some errors setting up logs, but trying to continue. Error was: %s\r\n", strerror(errno));
printlog("Errors during log setup for %s: %s (but continuing)\n", addr(), strerror(errno));
}
else
{
char buff[15];
logfile::intflags_to_char(config->get(OPT_LOG_OPTIONS), buff);
cprintf("Session will be logged.\r\n");
cprintf("Logging options are: %s.\r\n", buff);
cprintf("Use '/quote ezb log send all' to retrieve log files after reattach...\r\n");
printlog("Log setup OK for %s\n", addr());
}
}
cprintf("Detach succesful.\r\n");
cprintf("Use `/quote sessions' after reconnect to attach to session.\r\n");
if (password)
cprintf("Remember, your password for this session is: %s\r\n", password);
cprintf("---> Closing connection to you...\r\n");
client->printf("ERROR :Closing Link: %s (Detaching)\r\n", addr());
printlog("Detach SUCCESFUL: %s from connection to %s:%d\n",
addr(), uinfo.server, uinfo.port);
detach_time = ircproxy_time();
disown_dccs(0);
/**
* Create the anti-idle timer
*/
detach_timer = new generic_timer(300, &conn::detached_timer_proc, this);
detach_timer->enable();
/* Pretend user has disconnected. Protect the FROM rulesets,
* because they are needed on reattach
* We have to reset some flags as on_client_disconnect() zeroes out stat */
clearf(FROM_RULESETS_REGISTERED);
on_client_disconnect(0);
setf(FROM_RULESETS_REGISTERED);
setf(DETACHED | USERED | NICKED | PWED | BOUNCED);
return 1;
}
/* The reattaching process:
*
* 1) We simulate connection to server by sending numerics 000-003
* 2) We then send a dummy motd
* 3) And finally a bunch of JOINS
*/
int conn::reattach(const char * password, conn * c2)
{
if (!c2->checkf(DETACHED))
{
cprintf("REATTACH: Nonexistant or bad target userid.\r\n");
return 0;
}
if (password && c2->uinfo.detachpass &&
strcasecmp(password, c2->uinfo.detachpass))
{
cprintf("REATTACH: password incorrect.\r\n");
return 0;
}
char * server = c2->uinfo.server;
char * nick = c2->uinfo.irc->nick;
/* It's simpler to do the text sending first so do that */
/* Sync nickname first -- we do it twice, to be safe */
client->printf(":%s!%s NICK :%s\r\n", uinfo.irc->nick, addr(), c2->uinfo.irc->nick);
/*
* first: send fake numerics and pretend client has connected to an irc server
*/
client->printf(":%s 001 %s :Welcome to the Internet Relay Network %s\r\n",
server, nick, nick);
client->printf(":%s 002 %s :Your host is %s, running version %s\r\n",
server, nick, server, c2->uinfo.serverversion);
client->printf(":%s NOTICE %s :Your host is %s running version %s\r\n",
server, nick, server, c2->uinfo.serverversion);
client->printf(":%s 003 %s :This server was created %s\r\n",
server, nick, c2->uinfo.servercreated);
client->printf(":%s 004 %s %s %s %s\r\n",
server, nick, server, c2->uinfo.serverversion, c2->uinfo.servermodes);
if (c2->uinfo.server005_1)
client->printf(":%s 005 %s %s\r\n",
server, nick, c2->uinfo.server005_1);
if (c2->uinfo.server005_2)
client->printf(":%s 005 %s %s\r\n",
server, nick, c2->uinfo.server005_2);
client->printf(":%s NOTICE %s :Your host is %s and %s\r\n",
server, nick, server, EZBOUNCE_VERSION);
client->printf(":%s 375 %s :-%s Message of the Day -\r\n",
server, nick, server);
client->printf(":%s 372 %s :This is a dummy MOTD\r\n",
server, nick);
client->printf(":%s 376 %s :End of /MOTD command.\r\n",
server, nick);
c2->server->printf("LUSERS\r\n");
c2->server->printf("AWAY\r\n");
c2->server->printf("MODE %s\r\n", nick);
/* Sync the nickname again -- I'm not sure if this is needed, or even wise ?? */
client->printf(":%s!%s NICK :%s\r\n", uinfo.irc->nick, addr(), nick);
/*
* Simulate joins
*/
list_iterator<char> i(&c2->uinfo.channels);
while (i.has_next())
{
char * chan = i.next();
client->printf(":%s!%s@%s JOIN :%s\r\n",
nick, c2->uinfo.irc->ident, c2->uinfo.irc->host, chan);
c2->server->printf("NAMES %s\r\n", chan);
c2->server->printf("TOPIC %s\r\n", chan);
}
printlog("Reattach SUCCESFUL: %s ----> id %d; is now %s on server %s\n",
addr(), c2->id, nick, server);
/* Transfer settings.
* Actual reattaching happens here... */
c2->clearf(DETACHED);
c2->setf(BOUNCED);
c2->setf(GOTSERVINFO | GOTSERVINFO2);
if (c2->config->checkf(OPT_USER_IS_ADMIN))
c2->setf(ADMIN);
c2->uinfo.usercmd = uinfo.usercmd;
uinfo.usercmd = 0;
/* Update the 'from' info */
memcpy(&c2->client_saddr, &this->client_saddr, sizeof(client_saddr));
delete[] c2->uinfo.fulladdr;
c2->uinfo.fulladdr = new char[10 + strlen(c2->uinfo.irc->nick) + 18];
sprintf(c2->uinfo.fulladdr, "%lu:%s@%s", c2->id, c2->uinfo.irc->nick, inet_ntoa(c2->client_saddr.sin_addr));
/*
* What we do w/ rulesets on detach:
* Keep them alive. It is easier that way.
* On reattach, the user simply unregisters his and he becomes
* the target so rulesets are taken care of there.
* Remember that the from rulesets of the detached target match the address
* of the original user and not necessarily the user attempting to reattach.
* So, we keep the original address and port of connection in local_saddr.
*/
unregister_rulesets(ruleset::FROM);
stat = 0;
client->clear();
c2->client = this->client;
c2->client->owner = c2;
this->client = 0;
disown_dccs(c2);
on_client_disconnect(); /* We don't exist anymore.. */
/** Destroy the detached anti-idle timer */
c2->detach_timer->disable();
delete c2->detach_timer;
c2->detach_timer = 0;
/* Do the logfiles now */
if (c2->log)
{
char *names[2];
c2->log->dump("****** Reattach Completed *******\n");
c2->log->stop();
c2->log->get_filenames(names);
c2->cprintf("-------------------------------------------------\r\n");
c2->cprintf("- Ended log files\r\n");
c2->cprintf("- You may RETRIEVE your log files by typing\r\n");
c2->cprintf("- /quote ezb \002LOG SEND\002 all\r\n");
c2->cprintf("- --or--\r\n");
c2->cprintf("- /quote ezb \002LOG VIEW\002 all\r\n");
c2->cprintf("- \r\n");
c2->cprintf("- Type /quote ezb log help for logging help\r\n");
c2->cprintf("-------------------------------------------------\r\n");
delete c2->log;
c2->log = 0;
destroy_list(c2->loglist, 1);
delete c2->loglist;
c2->loglist = new strlist;
if (names[0])
c2->loglist->add(names[0]); /* already duplicated by get_filenames() call */
if (names[1])
c2->loglist->add(names[1]);
}
c2->cprintf("Reattach successful: you are now %s!%s@%s on %s:%d\r\n",
nick, c2->uinfo.irc->ident, c2->uinfo.irc->host,
server, c2->uinfo.port);
return 1;
}
/*
* Responds to server pings, handles numeric stuff
* does incoming dcc proxying.
*/
int conn::parse_from_server(void)
{
char command[40];
const struct cmd * c;
char * lp;
db_parser db(server->ibuff);
int r;
while ((server) && (lp = db.get_next_line(1)))
{
if (!*lp)
continue; /* blank line */
bool del_lp = 0;
char *argv[MAX_PPCHAR_ARGS];
gettok(lp, command,sizeof(command),' ', 2);
ToUpper(command);
c = incoming_hash.lookup(command,stat);
if (!c)
{
/* Didn't match. It could be a server ping, or a pong
* or an ERROR string. In that case the command is the first
* token. So try the hash lookup again on that */
gettok(lp, command, sizeof(command), ' ', 1);
ToUpper(command);
c = incoming_hash.lookup(command, stat | PPE);
}
if (c)
{
memset(argv, 0, sizeof (argv));
int argc = mk_ppchar(lp, &argv);
DEBUG("<<<<<< HANDLER: %s(%d, %d, %s)\n", c->msg, c->id, argc, argv[0]);
#ifndef GCC_COMPILER_BUG
r = (this->*(c->handler))(c->id, argc, argv);
#else
r = (this->*(handlers[c->idx]))(c->id, argc, argv);
#endif
if (r < 0)
{
delete[] argv[0];
return -1;
}
else if (r > 0)
{
delete[] argv[0];
continue /* ??? */;
}
lp = argv[0];
del_lp = 1;
}
if (!checkf(DETACHED)) {
/* Wasn't handled or intercepted: relay */
client->queue(lp, strlen(lp));
client->queue("\r\n", 2);
}
if (del_lp)
delete[] lp;
}
if (!checkf(DETACHED))
{
r = client->flushO();
if (r > 0)
bytes_toc += r;
r = client ? client->obuff->get_size() : 0;
if (r)
{
if ((unsigned) r > (pcfg.max_buffer_size / 2))
{
DEBUG("conn::parse_from_server() -- limiting server read -- buffer about to blow up\n");
client->set_events(POLLIN | POLLOUT);
server->set_events(server->events() & ~POLLIN);
} else {
DEBUG("conn::parse_from_server() %d left in client buffer: checking for POLLOUT\n", r);
client->set_events(POLLIN | POLLOUT);
}
}
}
if (server)
db.zap();
return 1;
}
bool conn::copy_fake_ident()
{
char id[20];
if (config->get(PREF_FAKE_IDENT, 0))
return 0; /* Already got a fake ident set */
if (gettok(uinfo.usercmd, id, sizeof(id), ' ', 1))
{
config->set(PREF_FAKE_IDENT, id);
return 1;
}
return 0;
}
/*
* Handle trapped CTCPs and do any special things we want for
* them. Right now it's only used for DCC proxying.
* Handles both outgoing and incoming CTCPs, as long as you
* tell it that ;)
*
* Return:
* 0 - not handled; relay to IRC server
* 1 - handled; will not relay to IRC server
*/
int conn::do_ctcp(bool incoming, const char * ctcp, const char * source,
const char * target, const char * args)
{
psock * out = server, * in = client;
if (incoming) /* Coming from IRC server to the proxy */
{
in = server;
out = client;
}
if (strcasecmp(ctcp, "DCC") == 0)
{
static const char *dcc_types[] = {
"SEND",
"CHAT",
"TSEND",
/* Gay VIRC extensions */
"TVIDEO",
"TVOICE",
/* mIRC DCC RESUME is NOT yet supported */
NULL
};
/*
* Format is:
* DCC [TYPE] [FILENAME] [IP] [PORT] [size]
* FIXME: test with corrupted input
*/
char arg1[8], arg2[256];
unsigned long ip, size;
unsigned short port;
int num_args = sscanf(args, "%7s %255s %lu %hu %lu",
arg1,arg2, &ip, &port, &size);
if (num_args < 4)
return 0;
/* Find out first what type of dcc this is */
int t = 0;
do {
if (strcasecmp(arg1, dcc_types[t]) == 0)
break;
} while (++t && dcc_types[t]);
if (!dcc_types[t])
return 0;
else
{
unsigned short plisten;
struct sockaddr_in sin;
socklen_t dummy = sizeof(sin);
/*
* We want to bind to and send the ip of the interface that
* we are either connected to IRC to (if outgoing)
* or the client connected to us on (if its incoming)
*/
getsockname(out->fd, (struct sockaddr *) &sin, &dummy);
sin.sin_port = 0;
ip = htonl(ip);
struct in_addr in_ip = { ip };
if (!incoming)
{
if (ip != client_saddr.sin_addr.s_addr)
{
char * tmp = my_strdup(inet_ntoa(in_ip));
printlog("DCC Proxy: Warning: IP of sender (%s) doesn't match IP in CTCP DCC (%s), using sender's IP\n",
inet_ntoa(client_saddr.sin_addr),
tmp);
in_ip.s_addr = ip = client_saddr.sin_addr.s_addr;
delete[] tmp;
}
}
dcc * d = new dccpipe(this, ntohl(ip),port, &sin, &plisten);
if (!d || !plisten)
{
printlog("DCC proxy FAILED: (%s) for %s: %s:%hu (errno=%s)" ,
dcc_types[t],
addr(), inet_ntoa(in_ip), port, strerror(errno));
delete d;
return 0;
}
printlog("DCC Proxy STARTED: (%s) for %s: (sender) %s:%hu\n",
dcc_types[t],
addr(), inet_ntoa(in_ip), port);
printlog(" (me) %s:%hu \n", inet_ntoa(sin.sin_addr), plisten);
dcc_list.add(d);
if (incoming) /* From IRC server to proxy */
out->printf(":%s PRIVMSG %s :\001DCC %s %s %lu %hu",
source, target, dcc_types[t], arg2, ntohl(sin.sin_addr.s_addr),
ntohs(sin.sin_port));
else
out->printf( "PRIVMSG %s :\001DCC %s %s %lu %hu",
target, dcc_types[t], arg2, ntohl(sin.sin_addr.s_addr), ntohs(sin.sin_port));
if (num_args == 5)
out->printf(" %lu", size);
out->printf("\001\r\n");
}
return 1;
}
return 0;
}
/* static */ void conn::kill_dccs(void)
{
destroy_list(&dcc_list, 1);
}
void conn::disown_dccs(conn * target)
{
printlog("DEBUG: Transfering DCC ownership of %p to %p\n", this, target);
list_iterator<dcc> i(&dcc_list);
while (i.has_next())
{
dcc * d = i.next();
/* impossible
if (!d)
break;
*/
if (d->get_owner() == this)
d->set_owner(target);
}
}
int conn::unregister_rulesets(char type)
{
if (type == ruleset::FROM && checkf(FROM_RULESETS_REGISTERED))
{
ruleset::list_unregister_from(&rulesets, inet_ntoa(client_saddr.sin_addr),
ntohs(local_saddr.sin_port));
clearf(FROM_RULESETS_REGISTERED);
return 1;
}
else if (type == ruleset::TO && checkf(TO_RULESETS_REGISTERED))
{
ruleset::list_unregister_to(&rulesets, uinfo.server, ntohs(serv_saddr.sin_port));
clearf(TO_RULESETS_REGISTERED);
return 1;
}
return 0;
}
int conn::on_client_disconnect(int /* error -- not used right now.. */)
{
unregister_rulesets(ruleset::FROM); /* Function does all needed checks */
if (client)
{
delete client;
client = 0;
}
destroy_list(loglist, 1);
delete loglist;
delete[] uinfo.usercmd;
delete[] uinfo.ircpass;
loglist = 0;
uinfo.usercmd = uinfo.ircpass = NULL;
clearf(PWED | NICKED | USERED | ADMIN | CONNECTING | DEFAULT_USER);
return 1;
}
int conn::on_server_disconnect(int /* error -- not used right now.. */)
{
if (server)
{
delete server;
server = 0;
}
unregister_rulesets(ruleset::TO); /* Does all needed checks */
delete[] uinfo.server;
destroy_list(&uinfo.channels, 1);
if (log)
{
/* Welp.. user disappeared */
log->dump("******* Connection to server was lost *******\n");
log->stop();
delete log;
log = 0;
}
if (detach_timer)
{
detach_timer->disable();
delete detach_timer;
detach_timer = 0;
}
uinfo.server = NULL;
clearf(BOUNCED | CONNECTING | GOTSERVINFO | GOTSERVINFO2);
return 1;
}
/*
* Not really what it seems..
* The constructor is the true on_client_connect..
* This one greets the user and informs them of special crap
* and sends motd and such
*/
int conn::on_client_connect(int /* error */)
{
delete[] uinfo.fulladdr;
uinfo.fulladdr = new char[10 + strlen(uinfo.irc->nick) + 18];
sprintf(uinfo.fulladdr, "%lu:%s@%s", id, uinfo.irc->nick, inet_ntoa(client_saddr.sin_addr));
printlog("LOGIN: %s as user `%s'\n", addr(), user->name);
/* MOTD */
if (pcfg.motdfile)
show_motd();
/* Greet user */
cprintf(" \r\n");
cprintf(" [%s]\r\n", EZBOUNCE_VERSION);
cprintf(" \r\n");
cprintf("\02login\02: %s\r\n", user->name);
cprintf("\02from\02: %s@%s\r\n", uinfo.irc->nick, inet_ntoa(client_saddr.sin_addr));
cprintf("\02time\02: %s\r\n", timestamp());
/* Establish a few settings */
if (!strcasecmp(user->name, "default"))
setf(DEFAULT_USER);
if (config->checkf(OPT_USER_IS_ADMIN))
{
/* Admins get all privs */
cprintf("\02privs\02: admin!!\r\n");
setf(ADMIN);
config->setf(OPT_ENABLE_INCOMING_DCC_PROXYING
| OPT_ENABLE_OUTGOING_DCC_PROXYING
| OPT_LOG_OPTIONS
| OPT_ENABLE_AUTO_DETACH
| OPT_ENABLE_FAKE_IDENTS
| OPT_ENABLE_VHOST_COMMAND);
printlog("Granted admin privileges to %s\n", addr());
}
/* Show detached connections */
if (!checkf(DEFAULT_USER))
{
cprintf(" \r\n");
do_sessions_cmd(CMD_SESSIONS, 0, 0);
}
cprintf(" \r\n");
cprintf("[use /quote CONN <server> to connect]\r\n");
setf(PWED);
/* If auto-fake-ident... store first part of user into fake_ident */
if (config->checkf(OPT_AUTO_FAKE_IDENT))
copy_fake_ident();
/* Auto-detach crap */
if (config->decide(PREF_AUTO_DETACH))
{
cprintf("You will be automatically detached unless you do `/quote ezb quit'\r\n");
if (checkf(DEFAULT_USER) && !config->get(PREF_AUTO_PASS, 0))
cprintf("You must first set a password with `/quote SET auto-detach-pass'\r\n");
}
/* Now, do Automatic server connection */
char * s = my_strdup(config->get(OPT_AUTOSERVER, 0));
if (s)
{
char *argv[MAX_PPCHAR_ARGS];
memset(argv, 0, sizeof (argv));
cprintf("Automatically connecting you to: %s\r\n", s);
printlog("CONNECT: Attempting auto connect of %s to %s\n", addr(), s);
int argc = mk_ppchar(s,&argv);
do_conn_cmd(CMD_CONN, argc, argv);
delete[] s;
delete[] argv[0];
}
return 1;
}
/* Dump motd -- very simple */
void conn::show_motd(void) const
{
cprintf("Message of the day: \r\n");
if (!pcfg.motdfile)
return;
int fd = open(pcfg.motdfile, O_RDONLY);
if (fd < 0)
{
printlog("Request for MOTD file %s failed: %s\r\n", pcfg.motdfile,
strerror(errno));
return;
}
dynbuff d(512, 65536);
char * line;
d.add(fd);
close(fd);
db_parser dparser(&d);
while ((line = dparser.get_next_line(1)))
{
/* check for empty lines */
if (!*line)
cprintf(" \r\n");
else
cprintf("%s\r\n", line);
}
cprintf(" \r\n");
cprintf("---\r\n");
return;
}
dcc * conn::dcc_send_file(const char * file, const char * send_as, struct sockaddr_in * psin, bool islog, bool ischat)
{
unsigned short port;
struct sockaddr_in sin;
unsigned long filesize;
socklen_t len = sizeof(sin);
memset(&sin, 0, sizeof(sin));
getsockname(client->fd, (struct sockaddr *) &sin, &len);
sin.sin_port = 0;
dccsend * d = new dccsend(this, file, &sin, &port, &filesize);
if (!port)
{
printlog("DCC Send: Error sending %s: %s\n", file, strerror(errno));
delete d;
return 0;
}
struct dcc::dcc_priv p = {
(char *) file,
0,
islog
};
d->set_priv(&p);
if (ischat)
{
printlog("DCC SEND: Sending %s to %s (as DCC CHAT)\n", file, addr());
cprintf("Ok.. sending %s (as DCC CHAT).. waiting on port %d\r\n", file, port);
client->printf(":%s PRIVMSG %s :\001DCC CHAT CHAT %lu %hu %lu\001\r\n",
EZBOUNCE_HEADER, uinfo.irc->nick, ntohl(sin.sin_addr.s_addr),
ntohs(sin.sin_port), filesize);
}
else
{
printlog("DCC SEND: Sending %s to %s.\n", file, addr());
cprintf("Ok.. sending %s.. waiting on port %d\r\n", file, port);
client->printf(":%s PRIVMSG %s :\001DCC SEND %s %lu %hu %lu\001\r\n",
EZBOUNCE_HEADER, uinfo.irc->nick, (send_as ? remove_path(send_as) : remove_path(file)), ntohl(sin.sin_addr.s_addr),
ntohs(sin.sin_port), filesize);
}
if (psin)
memcpy(psin, &sin, sizeof(sin));
dcc_list.add(d);
return d;
}
int conn::psock::event_handler(const struct pollfd *)
{
/* One of the socks has an event pending.
* Might as well check both. Always check server first.
* This is kinda dangerous. We might be deleted by our
* owner. There is no guarantee we will even exist in some of the
* next few lines, so save the pointer!!!
*
* Also, clear the data ready flags for the other socket pair.
* We don't want to be called again. */
conn * c = owner;
int e = c->server ? c->server->revents() : 0;
if (e)
{
c->server->set_revents(0);
switch (c->check_server(e))
{
case CONN_DISCONNECTED:
printlog("DISCONNECT: %s lost connection to server: %s.\n",
c->addr(), strerror(errno));
if (c->config->checkf(OPT_DROP_ON_DISCONNECT))
{
printlog("... removed client from proxy\n");
delete c;
ircproxy_conn_list()->remove(c);
goto out;
}
break;
case CONN_KILLME:
printlog("DISCONNECT: %s lost connection to server: %s -- killing.\n",
c->addr(), strerror(errno));
delete c;
ircproxy_conn_list()->remove(c);
goto out;
case CONN_FULL:
if (pcfg.flags & KILL_WHEN_FULL)
{
printlog("KILLING: %s -- full input queue(s)\n",
c->addr());
delete c;
ircproxy_conn_list()->remove(c);
}
goto out;
default:
break;
}
}
e = c->client ? c->client->revents() : 0;
if (e)
{
c->client->set_revents(0);
switch (c->check_client(e))
{
case CONN_LINK_DEACTIVATED:
printlog("DISCONNECT: %s lost connection to server: %s.\n",
c->addr(), strerror(errno));
if (c->config->checkf(OPT_DROP_ON_DISCONNECT))
{
printlog("... removed client from proxy\n");
delete c;
ircproxy_conn_list()->remove(c);
goto out;
}
break;
case CONN_DISCONNECTED:
case CONN_KILLME:
delete c;
ircproxy_conn_list()->remove(c);
goto out;
case CONN_FULL:
if (pcfg.flags & KILL_WHEN_FULL)
{
printlog("KILLING: %s -- full input queue(s).\n",
c->addr());
delete c;
ircproxy_conn_list()->remove(c);
}
goto out;
default:
break;
}
}
out:
return 1;
}
/*
* Like above function, we may be destroyed w/o knowing it so
* be a little careful */
int dcc::dccsock::event_handler(const struct pollfd *)
{
struct sockaddr_in sin;
struct dcc::dcc_priv * dp = owner->get_priv();
conn * c = owner->owner;
const char * name;
const char * filename = dp ? dp->str1 : 0;
bool islog = dp ? dp->i : 0;
dcc * d = owner;
if (c)
name = c->addr();
else
name = "[orphan]";
int ret = owner->poll(&sin);
switch (ret)
{
case DCC_PIPE_TIMEDOUT:
printlog("DCC: Proxy session timed out for %s\n", name);
if (c)
c->cprintf("DCC: Proxy session timed out\r\n");
goto delete_dcc;
case DCC_SEND_TIMEDOUT:
printlog("DCC: TIMEOUT: Send of file %s to %s timed out after 90 secs\n", filename, name);
if (c)
c->cprintf("DCC Send Timed Out: %s\r\n", nopath(filename));
if (islog)
logfile::release(filename);
goto delete_dcc;
case DCC_SEND_COMPLETE:
printlog("DCC: Send complete of file %s to %s\n", filename, name);
if (c)
c->cprintf("DCC Send complete: %s\r\n", nopath(filename));
if (islog)
{
unlink(filename);
logfile::release(filename);
if (c)
strlist_remove(c->get_loglist(), filename);
printlog("DCC: ... unlocking and deleting finished log file dcc send.\n");
}
goto delete_dcc;
case DCC_CLOSED:
case DCC_ERROR:
if (filename)
{
if (islog)
logfile::release(filename);
printlog("DCC: Send of %s to %s incomplete: %s\n", filename, name, strerror(errno));
if (c)
c->cprintf("DCC Send of %s incomplete: %s\r\n", nopath(filename), strerror(errno));
} else
{
printlog("DCC: Closing DCC session of %s: %s\n", name, strerror(errno));
if (c)
c->cprintf("DCC: Closing DCC Session: %s\r\n", strerror(errno));
}
delete_dcc:
DEBUG("Reached delete_dcc for %p\n", d);
delete d;
conn::dcc_list.remove(d);
break;
case DCC_PIPE_ESTABLISHED:
printlog("DCC: Full DCC link established for proxied DCC of %s\n",
name);
break;
case DCC_CONNECTION_ACCEPTED:
printlog("DCC: Connection from receipient accepted for proxied DCC of %s\n",
name);
printlog("DCC: Receipient is from %s:%hu\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
break;
case DCC_SEND_ESTABLISHED:
printlog("DCC: Send established to %s (%s:%hu)\n", name,
inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
break;
}
return 1;
}
conn::__uinfo::__uinfo()
{
usercmd = server = ircpass = detachpass =
serverversion = servercreated =
server005_1 = server005_2 = servermodes = fulladdr = NULL;
port = 0;
irc = 0;
}
conn::__uinfo::~__uinfo()
{
delete[] usercmd;
delete[] ircpass;
delete[] server;
delete[] detachpass;
delete[] serverversion;
delete[] servercreated;
delete[] servermodes;
delete[] server005_1;
delete[] server005_2;
delete[] fulladdr;
delete irc;
destroy_list(&channels, 1);
}
int conn::do_auto_detach(void)
{
char * pass = config->get(PREF_AUTO_PASS, 0);
printlog("Auto-detaching %s (%s) -- password is: %s \n",
addr(), strerror(errno), pass);
detach(pass);
return 1;
}
/* Called upon client connection:
* - make sure not on global ban list
* - make sure there exists a matching user definition
*/
int conn::is_authorized(void)
{
char * address = my_strdup(inet_ntoa(client_saddr.sin_addr));
char reason[200];
u_short port = ntohs(local_saddr.sin_port);
/* Check if on global ban list */
DEBUG("conn::is_authorized() -- checking %s\n", address);
if (ruleset::list_is_allowed_from(shitlist, address, port,
reason, sizeof(reason)) == -1)
{
if (!(pcfg.flags & SILENT_REJECTION))
cprintf(msg_banned, reason, reason);
printlog("Connection DENIED: (banned) from %s on port %d\n", address, port);
on_client_disconnect(0);
delete[] address;
return 0;
}
list_iterator<userdef> i(users);
while (i.has_next())
{
userdef * u = i.next();
if (!u->is_obsolete() && ruleset::list_is_allowed_from(u->rulesets, address,port,
reason, sizeof(reason)) == 1)
{
delete[] address;
return 1;
}
}
if (!(pcfg.flags & SILENT_REJECTION))
cprintf("No Authorization\r\n");
printf("Connection DENIED: (no authorization) from %s on port %d\n", address, port);
on_client_disconnect(0);
delete[] address;
return 0;
}
/* convert numeric flags to something human
* readable */
char * conn::mkstat(char * str_stat) const
{
int z = 0;
if (stat & BOUNCED) str_stat[z++] = 'b';
if (stat & CONNECTING) str_stat[z++] = 'c';
if ((stat & NICKED) &&
(stat & USERED)) str_stat[z++] = 'r';
if (stat & ADMIN) str_stat[z++] = 'a';
if (stat & PWED) str_stat[z++] = 'p';
if (stat & DETACHED) str_stat[z++] = 'd';
if (!stat) str_stat[z++] = 'w'; /* (waiting) */
if (dead())
{
strcpy(str_stat, "(zombie)");
z = 8;
}
str_stat[z] = 0;
return str_stat;
}
/**
* Called every so often so we can dump a random message
* for anti-idling purposes
*/
int conn::detached_timer_proc(time_t t, int i, void * data)
{
DEBUG("conn::detached_timer_proc() -- %p\n", data);
conn * c = (conn *) data;
assert(c->checkf(DETACHED));
srand(t);
static const char *anti_idle[5] = {
"2md :hey, whats up?",
"~habib :i need to crash",
"987987 :somebody set up us the bomb!",
"@^542 :fool",
".ds :my fellow americans, i feel your pain, and your "
};
c->server->printf("PRIVMSG %s\r\n", anti_idle[random()%5]);
return 1;
}
/*
* Original string will be modified
* Copy will be made, refered as argv[0]
* So we have char * pointer, and char *(*buff)[x]
* set buff[0] = to newly created string
* buff[1] = original.
*/
int mk_ppchar(char * string, char *(*buff)[MAX_PPCHAR_ARGS])
{
char * p = string;
int w = 2;
int isw = 0;
if (!strlen(string))
return 0;
(*buff)[0] = my_strdup(string);
(*buff)[1] = string;
while (*p && w < MAX_PPCHAR_ARGS + 1)
{
if (isspace(*p))
{
isw = 1;
*p = 0;
} else if (isw) {
isw = 0;
if (w < MAX_PPCHAR_ARGS)
(*buff)[w++] = p;
}
p++;
}
return w - 1;
}
syntax highlighted by Code2HTML, v. 0.9.1