/*
 * ezbounce.cpp
 *
 * (C) 1998-2004 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 <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "server.h"
#include "general.h"

#include "ezbounce.h"
#include "config.h"
#include "debug.h"

time_t start_time;
static const char * const ezbounce_banner = EZBOUNCE_VERSION
                                    "\n(c) 1998-2004 by Murat Deligonul (druglord@erupt.com)\n\n";
struct proxy_options pcfg;
list<userdef> * users;
list<ruleset> * shitlist;
strlist       * vhosts;

static void sig_handler(int);
static void setup_signals();
static void usage(const char *);
static bool parse_cmdline(int, char **, char **, bool *, int *, pid_t *);
void redir_stdxxx(void);

/* disable for now */
#undef HAVE_SET_NEW_HANDLER

#ifdef HAVE_SET_NEW_HANDLER
void new_failed();
#endif

/* #define NOFORK */

static void setup_signals()
{
    struct sigaction sv;
    sv.sa_flags = 0;
    sv.sa_handler = SIG_IGN;
    sigaction(SIGPIPE, &sv, NULL);
    sv.sa_handler = &sig_handler;
    sigaction(SIGINT, &sv, NULL);
    sigaction(SIGTERM, &sv, NULL);
    sigaction(SIGHUP, &sv, NULL);
    sigaction(SIGALRM, &sv, NULL);
/*    sigaction(SIGSEGV, &sv, NULL); */
}

int main(int argc, char *argv[])
{  
    char tmp[20];
    unsigned num_listening = 0;
    
#ifndef NOFORK
    pid_t pid;
#endif
    pid_t uid = 0;                /* become this user */
    bool nobg = 0;                /* go to the back ground? */
    char *iface_listen = NULL;    /* interface to bind to for listening*/
    int logindex  = 0;            /* argv[logindex] is the log */

#if defined(HAVE_NEW_H) && defined(HAVE_SET_NEW_HANDLER)
    set_new_handler(new_failed);
#endif
    printf(ezbounce_banner);
    if (argc < 2 || !parse_cmdline(argc, argv, &iface_listen, &nobg, &logindex, &uid))
    {
        usage(argv[0]);
        return 1;
    }

    printf("Reading config file %s ...\n", argv[logindex]);
    if ((argc = load_config_file(argv[logindex], &pcfg, &users, &shitlist, &vhosts)) < 0)
    {
        fprintf(stderr, "Error(s) while loading config file -- exiting.\n");
        return 1;
    }
    else if (argc == 0)
    {
        perror("Unable to open config file");
        return 1;
    }
    else
        printf("Config file successfully loaded.\n");

    /* Init sock table */
    printf("Socket table initialized: %d%s%d = %.02fkB\n",
                pollsocket::create_table(pcfg.max_sockets), "x", sizeof(pollsocket *),
                (float) sizeof(pollsocket *) * (float) pcfg.max_sockets / 1024.0);
    /* Do listen vhost crap */
    if (iface_listen)
    {
        struct in_addr in;
        switch (fill_in_addr(iface_listen, &in))
        {
        case 0:
            fprintf(stderr, "Can't listen on: %s: unknown host\n", iface_listen);
            return 1;
        case -1:
            fprintf(stderr, "Can't listen on: %s: %s\n", iface_listen, strerror(errno));
            return 1;
        default:
            pcfg.iface_listen = in;
        }
    }

    printf("Listening on interface: %s\n", inet_ntoa(pcfg.iface_listen));

    start_time = time(NULL);

    if(pcfg.ports) {
        argc = 1;
        printf("Listening on ports");
        while (gettok(pcfg.ports, tmp, 20, ',', argc++))
        {
            if (!ircproxy_listen((u_short)atoi(tmp), &pcfg.iface_listen,0))
                printf(" (%s:%s)", tmp, strerror(errno));
            else {
                printf(" %s", tmp);
                num_listening++;
            }
        }
    	fputc('\n', stdout);
    }

#ifdef _USE_SSL
    bool ssl_ready = pollsocket::init_ssl(pcfg.certfile);
    if (!ssl_ready)
        printf("SSL: cannot init SSL! (check your cert-file '%s')\n", pcfg.certfile);
    else
        printf("SSL: subsystem initialized\n");
#endif

    if(pcfg.sslports
#ifdef _USE_SSL
        && ssl_ready
#endif
    )
    {
        argc = 1;
        printf("SSL: listening on ports");
#ifndef _USE_SSL
        printf(" [note: SSL support was NOT compiled]");
#endif
        while (gettok(pcfg.sslports, tmp, 20, ',', argc++))
        {
            if (!ircproxy_listen((u_short)atoi(tmp), &pcfg.iface_listen,1))
                printf(" (%s:%s)", tmp, strerror(errno));
            else {
                printf(" %s", tmp);
                num_listening++;
            }
        }
	    fputc('\n', stdout);
    }

    printf("Listening on total of %d ports\n", num_listening);
    if (!num_listening)
    {
        fprintf(stderr, "oops, I can't run without listening on any ports\n");
        return 1;
    }
    /* change uid if needed */
    if (uid)
    {   
        if (!setuid(uid))
            printf("Now operating under userid %d\n.", uid);
        else
            perror("setuid");
    }
    
    /* start the log file */
    if (!ircproxy_startlog(pcfg.logfile))
        perror("WARNING: Unable to open log file");
        
#ifndef NOFORK
    if ((nobg) || ((pid = fork()) == 0))
    { 
        setsid();
        /* redirect stderr to our silly little log file.
           close stdin & stdout. */
        close(STDIN_FILENO);
        /* close(STDOUT_FILENO); -- config file reloading will output to stdout */
        if (!nobg)
            ircproxy_redir_stdxxx();
#endif
        /* Write out pid-file if needed */
        if (pcfg.pidfile)
        {
            int f = open(pcfg.pidfile, O_CREAT | O_WRONLY);
            fdprintf(f, "%d", getpid());
            fchmod(f, 0644);
	    close(f);
        }
        
        /* no longer needed */
        delete[] pcfg.logfile;
        delete[] pcfg.pidfile;
        pcfg.logfile = NULL;
        pcfg.pidfile = NULL;
        
        setup_signals(); 
        set_dns_timeout(pcfg.max_dns_wait_time);
        if (pcfg.userfile)
        {
            printf("Loading user preferences from disk (%s) .... \n", pcfg.userfile);
            ircproxy_load_prefs(::users, pcfg.userfile);
        }

        if (nobg)
            printf("Starting IRC proxy ...\n");
        start_proxy();

        /* Server is finished. */
        ircproxy_closelog();
        stop_proxy();

        delete[] pcfg.configfile;
        delete[] pcfg.logfile;
        delete[] pcfg.pidfile;
        delete[] pcfg.motdfile;
        delete[] pcfg.userfile;
        delete[] pcfg.logdir;
        delete[] pcfg.ports;
        delete[] pcfg.dcc_ports;
        destroy_list(users, 0);
        destroy_list(shitlist, 0);
        destroy_list(vhosts, 1);
        delete users;
        delete shitlist;
        delete vhosts;
        return 0;
#ifndef NOFORK
    }
    else if (pid < 0)
        perror("Can't go into the background");
    else
        printf("IRC Proxy started -- pid: %d\n", pid);
#endif
    return 0;
}

static void sig_handler(int sig)
{
    switch (sig)
    {
    case SIGHUP:
        printlog("Got SIGHUP; reloading configuration file\n");
        ircproxy_request_rehash();
        break;
    case SIGTERM:
        printlog("Got terminate signal, killing server now\n");
        ircproxy_die(1, "got terminate signal");
        break;
    case SIGSEGV:
        printlog("Got SIGSEGV: goodbye :-(\n");
        break;
    case SIGINT:
        printlog("Interrupt signal: waiting for server to shut down\n");
        ircproxy_die(0, "keyboard interrupt");
    default:
        break;
    }
}

static void usage(const char *bname)
{
    fprintf(stderr, "Usage:\n%s [options] <configuration file>\n", bname);
    fprintf(stderr, "Available options:\n");
    fprintf(stderr, "  -f              don't go into the background\n");
    fprintf(stderr, "  -b <hostname>   listen for connections on a different interface\n");
    fprintf(stderr, "  -u <uid>        become user uid after binding listen socket\n\n");
}

static bool parse_cmdline(int argc, char **argv, char **pIface_listen,
                          bool * pNobg, int *pIndex, pid_t * uid)
{
    for (int z = 1; z < argc; z++)
    {
        switch (argv[z][0])
        {
        /* a -flag, next char must be -B or -F */
        case '-':
            switch (argv[z][1])
            {
            case 'u':
            case 'U':
                *uid = atoi(argv[++z]);
                continue;
            case 'f':
            case 'F':
                *pNobg = 1;
                break;
            case 'b':
            case 'B':              
                if (z + 1 < argc)
                    *pIface_listen = argv[++z];
                continue;
            case '-':
                if (strcasecmp(&argv[z][2], "help") != 0)
                    continue;
            case 'h':
                return 0;
            default:
                break;
            }
        default:
            *pIndex = z;
        }
    }
    return (*pIndex != 0);
}

#ifdef HAVE_SET_NEW_HANDLER
void new_failed()
{
    printlog("fatal memory allocation error :(\n");
    printlog("notify author\n");
    abort();
}
#endif


syntax highlighted by Code2HTML, v. 0.9.1