/*
* server.cpp
*
* (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.
*
* ---
* does: server code; listening for connections, accepting, logging
* ---
*
*/
#include "autoconf.h"
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_STDARG_H
#include <stdarg.h>
#endif
#include <errno.h>
#include <time.h>
#include "ruleset.h"
#include "conn.h"
#include "linkedlist.h"
#include "socket.h"
#include "server.h"
#include "timer.h"
#include "debug.h"
class listen_psock : public pollsocket
{
public:
listen_psock(int f, u_short port, bool * success,int in_ssl);
protected:
u_short port;
int inssl;
virtual int event_handler(const struct pollfd * pfd);
};
time_t time_ctr;
static void kill_conns(void);
static void create_timers(void);
static int do_proxy_timers(time_t t, int i, void *);
int check_timers(void);
static bool terminate_request = 0;
static bool rehash_request = 0;
static int fd_log = -1;
static list<listen_psock> sock_list;
static list<conn> conns;
enum {
TIMER_30_SEC,
TIMER_60_SEC,
};
int stop_proxy(void)
{
destroy_list(&sock_list, 0);
kill_conns();
return 1;
}
list<conn> * ircproxy_conn_list()
{
return &conns;
}
static void create_timers()
{
timer::enable(new generic_timer(30, do_proxy_timers, 0, 0, TIMER_30_SEC));
timer::enable(new generic_timer(60, do_proxy_timers, 0, 0, TIMER_60_SEC));
}
int ircproxy_listen(u_short port, const struct in_addr *iface, int inssl)
{
struct sockaddr_in sin;
int sock, parm;
bool success;
memset(&sin, 0, sizeof sin);
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
if (iface)
sin.sin_addr = *iface;
else
sin.sin_addr.s_addr = INADDR_ANY;
sock = socket(AF_INET, SOCK_STREAM, 0);
parm = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char * ) &parm, sizeof(int));
if (bind(sock, (struct sockaddr *) &sin, sizeof(sin)) == -1)
return 0;
if (::listen(sock, 100) == -1)
return 0;
/* every thing seems to work */
DEBUG("ircproxy_listen(): Adding a socket fd %d\n", sock);
listen_psock * p = new listen_psock(sock, port, &success, inssl);
if (!success)
{
delete p;
return 0;
}
sock_list.add(p);
return 1;
}
int start_proxy(void)
{
time_t wait_time, old_time, diff;
old_time = update_time_ctr();
create_timers();
conn::init_command_hash();
wait_time = timer::get_poll_interval();
printlog("Server ready\n");
while (1)
{
if (pollsocket::poll_all(wait_time * 1000) <= 0)
update_time_ctr();
if (terminate_request)
{
terminate_request = 0;
conn::broadcast(&conns,"... proxy shutting down now.");
kill_conns();
printlog("SIGTERM received: terminating...\n");
return 1;
}
if (rehash_request)
{
rehash_request = 0;
printlog("Rehashing the proxy config file (signal)\n");
switch (ircproxy_rehash())
{
case 1:
printlog("Rehash successful\n");
break;
case 0:
printlog("Rehash failed: %s\n", strerror(errno));
break;
case -1:
printlog("Rehash failed\n");
default:
break;
}
}
/**
* Handle timer events if necessary */
diff = time_ctr - old_time;
if (!diff)
continue;
if (diff < wait_time)
wait_time -= diff;
else
{
timer::poll(time_ctr);
wait_time = timer::get_poll_interval();
}
old_time = time_ctr;
}
return 1;
}
/*
* Requests server termination
* if now == 1, all conn objects are freed and server exit()s.
* if it's 0, a flag is set which the server can read and terminate
* when ready.
*/
int ircproxy_die(bool now, const char *reason)
{
char buff[256];
if (now)
{
strcpy(buff, "Server terminating: ");
strncat(buff, reason, (sizeof buff) - (strlen(buff) + 1));
conn::broadcast(&conns,buff);
ircproxy_save_prefs(users, pcfg.userfile);
kill_conns();
exit(0);
return 1;
}
terminate_request = 1;
strcpy(buff, "Terminate request: ");
strncat(buff, reason, (sizeof buff) - (strlen(buff) + 1));
conn::broadcast(&conns,buff);
ircproxy_save_prefs(users, pcfg.userfile);
return 1;
}
static void kill_conns(void)
{
destroy_list(&conns, 0);
}
int ircproxy_startlog(const char *logfile)
{
fd_log = open(logfile, O_CREAT | O_APPEND | O_WRONLY, 0600);
return (fd_log < 0) ? 0 : 1;
}
int ircproxy_closelog()
{
printlog("Server log ended\n");
int ret = close(fd_log);
fd_log = -1;
return (ret >= 0);
}
/* we could also have called fdprintf() with fd_log as first argument, but
since i wanted a nice little timestamp automatically put in front of every
log entry, i made a function just for it.*/
int printlog(const char *format, ...)
{
extern char __mbuffer[1024];
__mbuffer[0] = 0;
int len;
const char *z = timestamp();
va_list ap;
write(fd_log, z, strlen(z));
va_start(ap,format);
len = vsnprintf(__mbuffer, sizeof(__mbuffer), format, ap);
va_end(ap);
write(fd_log, __mbuffer, len);
return 1;
}
void ircproxy_request_rehash(void)
{
rehash_request = 1;
}
void ircproxy_redir_stdxxx(void)
{
dup2(fd_log, STDOUT_FILENO);
dup2(fd_log, STDERR_FILENO);
}
listen_psock::listen_psock(int f, u_short p, bool * success,int in_ssl)
: pollsocket::pollsocket(f, success, POLLIN)
{
port = p;
inssl=in_ssl;
}
int listen_psock::event_handler(const struct pollfd *)
{
conn * c = new conn(fd,inssl);
if (c->dead())
delete c;
else
conns.add(c);
return 1;
}
/*
* This function will be called every 30 seconds at least.
*/
int do_proxy_timers(time_t t, int interval, void *)
{
if (interval == TIMER_60_SEC)
{
DEBUG("check_timers(): 60-sec check\n");
list_iterator<ruleset> i(::shitlist);
list_iterator<userdef> iu(::users);
userdef * u;
ruleset * r;
/* Check global ban list first */
while (i.has_next())
{
r = i.next();
if (r->dead())
{
DEBUG("check_timers(): removed obsolete shitlist element %p\n", r);
delete r;
i.remove();
}
}
/* Find obsolete user definitions
* and clean up any obsolete rulesets they may have */
while (iu.has_next())
{
u = iu.next();
if (u->is_obsolete() && !u->conns->size())
{
DEBUG("check_timers(): Deleting obsolete user def. %p (%s)\n", u, u->name);
delete u;
iu.remove();
continue;
}
i.attach(u->rulesets);
while (i.has_next())
{
r = i.next();
if (r->dead())
{
delete r;
DEBUG("check_timers(): removed obsolete ruleset %p for (%s)\n", r, u->name);
i.remove();
}
}
}
pollsocket::compress();
} else if (interval == TIMER_30_SEC)
{
/**
* 30-second timer:
* check for dead conns,
* check for idling and unregistered conns
*/
DEBUG("check_timers(): 30-sec check\n");
list_iterator<conn> c_iter(&conns);
while (c_iter.has_next())
{
conn * c = c_iter.next();
if (c->dead())
{
DEBUG("Deleting dead conn %p\n", c);
c_iter.remove();
delete c;
}
int j = c->is_idle(t);
if (j == 1)
{
/* Exceeded idle time limit */
printlog("KILLING: %s for exceeding idle time limit\n", c->addr());
c->kill("The Server", "Exceeded idle time limit!\n");
delete c;
c_iter.remove();
} else if (j == 2) {
/* Failed to register in time */
printlog("KILLING: %s for failing to register in time\n", c->addr());
c->kill("The Server", "Failed to register in time!\n");
delete c;
c_iter.remove();
} else {
// optimize memory usage ??
}
}
}
return 1;
}
/*
* Save stuff to the user file ... right now we only save
* user preferences; in the future we might have a dynamic list of
* bans or some such */
int ircproxy_save_prefs(list<userdef> * ul, const char * file)
{
int f = open(file, O_WRONLY | O_CREAT, 0600);
if (f < 0)
{
printlog("Error saving prefs: %s\n", strerror(errno));
return -1;
}
list_iterator<userdef> i(ul);
ftruncate(f, 0);
DEBUG("save_prefs(): starting\n");
while (i.has_next())
{
userdef * u = i.next();
if (strcasecmp(u->name, "default"))
u->cfg.save(u->name, f);
}
close(f);
DEBUG("save_prefs(): finished\n");
printlog("Saved preferences to user file\n");
return 1;
}
int ircproxy_load_prefs(list<userdef> * ul, const char * file)
{
int f = open(file, O_RDONLY);
if (f < 0)
{
printlog("Error loading prefs from file `%s': %s\n", file, strerror(errno));
return -1;
}
dynbuff db(256, 0);
char line[256];
int curline = 0;
db.add(f);
DEBUG("load_prefs(): starting\n");
while (db.copy_line(curline++, line, sizeof(line), 1, 1) != -1)
{
char b[64] = "";
userdef * u = 0;
if (!(gettok(line, b, sizeof(b), ' ', 2))
|| !(u = userdef::find(ul, b)))
{
DEBUG("load_prefs(): ?? bad user %s\n", b);
continue;
}
if (strcasecmp(u->name, "default"))
u->cfg.load(line);
}
close(f);
DEBUG("load_prefs(): finished\n");
printlog("Loaded preferences from user file\n");
return 1;
}
int ircproxy_rehash(void)
{
struct proxy_options __pcfg;
list<ruleset> * __shitlist;
list<userdef> * __users;
strlist * __vhosts;
int r = load_config_file(pcfg.configfile, &__pcfg, &__users, &__shitlist, &__vhosts);
if (r < 1)
{
/* Failed */
return 0;
}
/* the sync_list() functions do most of the real work
* they will also take care of de-allocating the old lists */
DEBUG("ircproxy_rehash(): Syncing users...\n");
::users = userdef::sync_lists(::users, __users);
DEBUG("ircproxy_rehash(): Syncing shitlist\n");
::shitlist = ruleset::sync_lists(::shitlist, __shitlist);
destroy_list(vhosts, 1);
delete vhosts;
vhosts = __vhosts;
/* Fixme: here we can compare differences between old and
* new string fields, and update things accordingly.. for
* example, listen on new ports, open a diff logfile
*/
/* Delete all of the old strings, and copy
* new config structure onto old */
delete[] pcfg.ports;
delete[] pcfg.dcc_ports;
delete[] pcfg.logdir;
delete[] pcfg.configfile;
delete[] pcfg.logfile;
delete[] pcfg.pidfile;
delete[] pcfg.motdfile;
delete[] pcfg.userfile;
memcpy(&pcfg, &__pcfg, sizeof(__pcfg));
return 1;
}
syntax highlighted by Code2HTML, v. 0.9.1