/*
 * 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