/* * 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 #include #include #include #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_STDARG_H #include #endif #include #include #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 sock_list; static list conns; enum { TIMER_30_SEC, TIMER_60_SEC, }; int stop_proxy(void) { destroy_list(&sock_list, 0); kill_conns(); return 1; } list * 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 i(::shitlist); list_iterator 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 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 * 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 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 * 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 * __shitlist; list * __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; }