/* 
 * config.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:
 *    Parsing configuration file, loading options to various data structures
 *    rulesets, etc.
 */

#include "autoconf.h"

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#ifdef HAVE_FCNTL_H
    #include <fcntl.h>
#endif
#include "config.h"
#include "ruleset.h"
#include "general.h"
#include "dynbuff.h"
#include "ezbounce.h"
#include "debug.h"

#include "hash.h"

struct config_symbol {
    const char * symbol;
    int id;
    int flags;
};

class cfghash : public __htbl {
public:
    cfghash(int buckets) : __htbl(buckets) { }

    int insert(const struct config_symbol * );
    const struct config_symbol * lookup(const char *);
};

/* store the config hash stats */

struct __htbl::hash_stat_t cfght;

/* temp lists: */
static list<userdef> * __users;
static list<ruleset> * __rulesets;
static list<char>    * __vhosts;
static proxy_options * __popt;

static cfghash * chash;

/* Additional Constants -- flags for symbol table */
enum {
    MATCH_GLOBAL = 0x8,
    MATCH_USER   = 0x10,
    NEED_ARG     = 0x20,
    NEED_2ARG    = 0x40,
    PROXY_OPTION = 0x80,
    USER_OPTION  = 0x100
};

/* Additional IDs for symbols */

enum {
    CFG_CMD_SET     = 0x100000,
    CFG_CMD_LISTEN,     CFG_CMD_ALLOW,      CFG_CMD_DENY,   CFG_CMD_USER,
    CFG_CMD_VHOSTS,     CFG_CMD_SSL_LISTEN,

    LOGFILE,            PIDFILE,            MOTD_FILE,      DCC_LISTEN_PORT_RANGE,
    LOG_DIR,            MAX_DNS_WAIT_TIME,  USERFILE,
    MAX_REGISTER_TIME,  MAX_FAILED_PASSWORDS,
    MAX_FAILED_ADMINS,  LISTEN_VHOST,       MAX_SOCKETS,
    MAX_BUFFER_SIZE,    MIN_BUFFER_SIZE,    CERTFILE, ID_SILENT_REJECTION,
    ID_NO_REVERSE_LOOKUPS, ID_PREVENT_SELF_CONNECTS, ID_KILL_WHEN_FULL
};

static const struct config_symbol symtab[] = {
    {"SET",                     CFG_CMD_SET,                MATCH_GLOBAL | MATCH_USER | NEED_2ARG},
    {"LISTEN",                  CFG_CMD_LISTEN,             MATCH_GLOBAL | NEED_ARG},
    {"SSL-LISTEN",              CFG_CMD_SSL_LISTEN,         MATCH_GLOBAL | NEED_ARG},
    {"ALLOW",                   CFG_CMD_ALLOW,              MATCH_USER},
    {"DENY",                    CFG_CMD_DENY,               MATCH_GLOBAL | MATCH_USER},
    {"USER",                    CFG_CMD_USER,               MATCH_GLOBAL | NEED_ARG},
    {"VHOSTS",                  CFG_CMD_VHOSTS,             MATCH_GLOBAL | MATCH_USER},

    /* variables - proxy-only settings: */
    { "LOGFILE",                LOGFILE,                    PROXY_OPTION},
    { "MOTDFILE",               MOTD_FILE,                  PROXY_OPTION},
    { "PIDFILE",                PIDFILE,                    PROXY_OPTION},
    { "CERTFILE",               CERTFILE,                   PROXY_OPTION},
    { "USERFILE",		        USERFILE,		            PROXY_OPTION},
    { "LOG-FILE",               LOGFILE,                    PROXY_OPTION},
    { "MOTD-FILE",              MOTD_FILE,                  PROXY_OPTION},
    { "PID-FILE",               PIDFILE,                    PROXY_OPTION},
    { "CERT-FILE",              CERTFILE,                   PROXY_OPTION},
    { "USER-FILE",		        USERFILE,		            PROXY_OPTION},
    { "LOG-DIR",                LOG_DIR,                    PROXY_OPTION},
    { "NO-REVERSE-LOOKUPS",     ID_NO_REVERSE_LOOKUPS,      PROXY_OPTION},
    { "DCC-LISTEN-PORT-RANGE",  DCC_LISTEN_PORT_RANGE,      PROXY_OPTION},
    { "MIN-BUFFER-SIZE",        MIN_BUFFER_SIZE,            PROXY_OPTION},
    { "MAX-BUFFER-SIZE",        MAX_BUFFER_SIZE,            PROXY_OPTION},
    { "PREVENT-SELF-CONNECTS",  ID_PREVENT_SELF_CONNECTS,   PROXY_OPTION},
    { "MAX-DNS-WAIT-TIME",      MAX_DNS_WAIT_TIME,          PROXY_OPTION},
    { "KILL-ON-FULL-QUEUE",     ID_KILL_WHEN_FULL,          PROXY_OPTION},
    { "MAX-REGISTRATION-TIME",  MAX_REGISTER_TIME,          PROXY_OPTION},
    { "MAX-FAILED-PASSWORDS",   MAX_FAILED_PASSWORDS,       PROXY_OPTION},
    { "SILENT-REJECTION",       ID_SILENT_REJECTION,        PROXY_OPTION},
    { "LISTEN-VHOST",           LISTEN_VHOST,               PROXY_OPTION},
    { "MAX-SOCKETS",            MAX_SOCKETS,                PROXY_OPTION},

    /* User configurable */
    { "PASSWORD",               OPT_PASSWORD,                   USER_OPTION},
    { "MAX-IDLE-TIME",          OPT_MAX_IDLE_TIME,              USER_OPTION},
    { "DROP-ON-DISCONNECT",     OPT_DROP_ON_DISCONNECT,         USER_OPTION},
    { "ENABLE-DETACH-COMMAND",  OPT_ENABLE_DETACH_COMMAND,      USER_OPTION},
    { "ENABLE-AUTO-DETACH",     OPT_ENABLE_AUTO_DETACH,         USER_OPTION},
    { "ENABLE-VHOST-COMMAND",   OPT_ENABLE_VHOST_COMMAND,       USER_OPTION},
    { "ENABLE-FAKE-IDENTS",     OPT_ENABLE_FAKE_IDENTS,         USER_OPTION},
    { "AUTO-FAKE-IDENTS",       OPT_AUTO_FAKE_IDENT,            USER_OPTION},
    { "AUTO-SERVER",            OPT_AUTOSERVER,                 USER_OPTION},
    { "ENABLE-OUTGOING-DCC-PROXYING", OPT_ENABLE_OUTGOING_DCC_PROXYING, USER_OPTION},
    { "ENABLE-INCOMING-DCC-PROXYING", OPT_ENABLE_INCOMING_DCC_PROXYING, USER_OPTION},
    { "IS-ADMIN",               OPT_USER_IS_ADMIN,              USER_OPTION},
    { "DEFAULT-VHOST",          OPT_DEFAULT_VHOST,              USER_OPTION},
    /* Logging options */
    { "ENABLE-PRIVATE-LOGGING", OPT_ENABLE_PRIVATE_LOGGING,     USER_OPTION},
    { "ENABLE-PUBLIC-LOGGING",  OPT_ENABLE_PUBLIC_LOGGING,      USER_OPTION},
    { "ENABLE-SEPERATE-LOGGING",OPT_ENABLE_SEPERATE_LOGGING,    USER_OPTION},
    { "DEFAULT-LOG-OPTIONS",    OPT_DEFAULT_LOG_OPTIONS,        USER_OPTION},
    { "MAX-LOGFILE-SIZE",       OPT_MAX_LOGFILE_SIZE,           USER_OPTION},
};

/*
 * default options for a proxy_options structure */
const struct proxy_options default_proxy_options = {
    0,      /* ports */
    0,      /* sslports */
    0,      /* dcc_ports */
    0,      /* logdir */
    0,      /* configfile */
    0,      /* logfile */
    0,      /* pidfile */
    0,      /* certfile */
    0,  	/* userfile */
    0,      /* motdfile */
    30,     /* max_register */
    3,      /* max_failed_pass */
    DEF_MIN_BUFFER_SIZE,    /* min. buffer size */
    DEF_MAX_BUFFER_SIZE,    /* max. buffer size */
    DEF_MAX_SOCKETS,    /* socket table size */
    10,             /* dns look up time out */
    KILL_WHEN_FULL | PREVENT_SELF_CONNECTS, /* flags */
    {0}     /* default interface */
};


static int do_set_command(userdef *, int, char *, char *);
static char * strip(char *);
static char * remove_trailing(char *);
static int grab_block(dynbuff *, dynbuff *, int);
static int parse_block(dynbuff * , int , userdef * );
static ruleset * do_ruleset(dynbuff * db, int, char *, u_long);
static bool check_config(void);
static bool check_user_config(userdef * );

/* Return pointer past leading space and tab chars.
 * Remove all tabs from line.
 * If # encountered before anything else, return NULL */
static char * strip(char * c)
{
    if (*c == '#')
        return 0;
    while (isspace(*c))
        if (*++c == '#')
            return 0;
    if (!*c)
        return 0;
    char * b = c;
    while (*b)
        if (*b++ == '\t')
            *(b-1) = ' ';
    return c;
}

/* Remove whitespace characters from
 * end of string */
static char * remove_trailing(char * str)
{
    if (!str)
        return 0;
    char * p = str + strlen(str);
    while (isspace(*p))
        *p-- = 0;
    return str;
}

int load_config_file(const char *file, struct proxy_options * pc,
        list<userdef> ** ulist, list<ruleset> ** rlist, list<char> ** vlist)
{
    int fd = open(file, O_RDONLY);
    struct stat st;
    dynbuff db(8048, 0);
    if (fd < 0)
        return 0;

    fstat(fd, &st);
    db.add(fd, st.st_size);
    close(fd);

    /* Got the config file in memory *
     * Init the symbol table */
    chash = new cfghash(47);
    for (unsigned c = 0; c < (sizeof(symtab) / sizeof(struct config_symbol)); c++)
        chash->insert(&symtab[c]);

    /* init other globals */
    __popt = new proxy_options;
    __users = new list<userdef>;
    __rulesets = new list<ruleset>;
    __vhosts = new list<char>;

    memcpy(__popt, &default_proxy_options, sizeof(proxy_options));

    if (!parse_block(&db, MATCH_GLOBAL, (userdef *) 0))
    {
        fprintf(stderr, "master parse_block() failed -- aborting\n");
        goto error;
        return -1;
    }

    fflush(stderr);
    printf("Checking config....");
    fflush(stdout);
    if (check_config())
        printf("OK\n");
    else
    {
error:
        destroy_list(__users, 0);
        destroy_list(__rulesets, 0);
        destroy_list(__vhosts, 1);
        delete[] __popt->ports;
        delete[] __popt->sslports;
        delete[] __popt->dcc_ports;
        delete[] __popt->logdir;
        delete[] __popt->logfile;
        delete[] __popt->configfile;
        delete[] __popt->pidfile;
        delete[] __popt->certfile;
        delete[] __popt->motdfile;
        delete[] __popt->userfile;
        delete __users;
        delete __rulesets;
        delete __vhosts;
        delete __popt;
        delete chash;
        return -1;
    }
    	
    /* copy stuff back */
    *ulist = __users;
    *rlist = __rulesets;
    *vlist = __vhosts;
    memcpy(pc, __popt, sizeof(struct proxy_options));
    pc->configfile = my_strdup(file);
    /* get hash table stats so we can look at them later */
    memset(&cfght, 0, sizeof(cfght));
    chash->stat(&cfght);
    delete chash;
    delete __popt;
    return 1;
}

/* NOTE: will not tell how many lines if returns an
 * error. We'll have to use a pointer argument, perhaps
 * for curline itself. However, since error conditions stop
 * the parsing altogether, this issue does not matter right
 * now ..... */
int grab_block(dynbuff * src, dynbuff * target, int curline)
{
    char line[256];
    int num_O = 0, num_C =0;
    int __curline = 0;

    while (src->copy_line(curline++, line, sizeof(line), 1, 0) != -1)
    {
        __curline++;
        char * p = strip(line);
        remove_trailing(p);
        bool add = 0;

        if (!p)
            continue;
        if (*p == '{')
        {
            if (++num_O > 1)
                add = 1;
        }
        else if (*p == '}')
        {
            if (!num_O)
                return 0;   /* } before { -- invalid */
            if (++num_C != num_O)
                add = 1;
        }
        else {
            /* couldn't find a {. It could be a "xxx {" type of construct.
             * Make sure it is such a thing and not a { character in a
             * set command for example.
             */
            char * n = strchr(p, '{');

            if (!n)
            {
                if (!num_O && __curline != 1)
                    return 0;
                else if (num_O)
                    add = 1;
            }
            else
            {
                if (!num_O)
                    num_O++;
                else {
                    /* { found, and it's not the first one we were looking
                     * for.. make sure its not part of set command or whatever
                     */
                    if (isspace(*(n-1)) &&
                        (isspace(*(n+1)) || *(n+1) == 0))
                           num_O++;
                    add = 1;
                }
            }
        }

        if (add)
        {
            target->add(line);
            target->add("\n");
        }
        if (num_O == num_C && num_O)
            return curline;
    }
    return -1; /* premature end */
}

/*
 * Parse a block. A block is considered a section of commands
 * between { } braces (not including the braces).
 *
 * Return 0 or 1 (fail or success)
 */
int parse_block(dynbuff * db, int flags, userdef * user)
{
    char buff[256], cmd[64];
    int line = 0;

    while (db->copy_line(line++, buff, sizeof(buff), 1, 0) != -1)
    {
        char * p = strip(buff);
        char arg2[64] = "", arg3[64] = "";

        const struct config_symbol * csym = 0;
        if (   !p                             /* comment .. */
            || !gettok(p, cmd, sizeof(cmd), ' ', 1)) /* blank line.. */
            continue;

        ToUpper(cmd);
        csym = chash->lookup(cmd);
        gettok(p, arg2, sizeof(arg2), ' ', 2);
        gettok(p, arg3, sizeof(arg3), ' ', 3);

        if (!csym)
        {
            fprintf(stderr, "*** error: %d: invalid command '%s'\n", line, cmd);
            return 0;
        }
        if ((csym->flags & NEED_ARG) && !*arg2)
        {
             fprintf(stderr, "*** error: %d: command '%s' needs a parameter\n", line, cmd);
             return 0;
        }
        if ((csym->flags & NEED_2ARG) && !*arg3)
        {
            fprintf(stderr, "*** error: %d: command '%s %s' needs an additional parameter\n", line, cmd, arg2);
            abort();
            return 0;
        }
        if ((flags & MATCH_USER) && !(csym->flags & MATCH_USER))
        {
            fprintf(stderr, "*** error: %d: command '%s' is not valid in a user definition block\n", line,cmd);
            return 0;
        }
        /* iff MATCH_GLOBAL is set */
        if (!(flags & MATCH_USER) && !(csym->flags & MATCH_GLOBAL))
        {
            fprintf(stderr, "*** error: %d: command '%s' is only valid in user block\n", line,cmd);
            return 0;
        }

        switch (csym->id)
        {
        case CFG_CMD_LISTEN:
            if (!__popt->ports)
                __popt->ports = my_strdup(arg2);
            else
                fprintf(stderr,"*** warning: %d: ignoring `listen' command: ports were already set!\n", line);
            break;
        case CFG_CMD_SSL_LISTEN:
            if (!__popt->sslports)
                __popt->sslports = my_strdup(arg2);
            else
                fprintf(stderr,"*** warning: %d: ignoring `ssl-listen' command: ports were already set!\n", line);
            break;

        case CFG_CMD_DENY:
        case CFG_CMD_ALLOW:
        {
            int r;
            dynbuff n(512, 0);
            r = grab_block(db, &n, line-1);
            switch (r)
            {
            case 0:
            case -1:
                fprintf(stderr, "*** error: %d: Parse Error in deny/allow block (ended prematurely?)\n", line);
                return 0;
            default:
              {
                ruleset * rs;
                char reason[100];

                DEBUG("parse_block(): creating new allow/deny block\n");
                rs = do_ruleset(&n, csym->id, reason, sizeof(reason));
                if (!rs)
                {
                    fprintf(stderr, "*** error: %d: Unable to create ruleset: %s\n", line, reason);
                    return 0;
                }

                if (user)
                    user->rulesets->add(rs);
                else
                    __rulesets->add(rs);
              }
            }
            line = r;
            break;
        }

        case CFG_CMD_SET:
            DEBUG("Encountered set option %s %s\n", arg2, arg3);
            switch(do_set_command(user, flags, arg2, arg3))
            {
            case 0:
                fprintf(stderr, "*** error: %d: Cannot set `%s': unknown variable\n", line, arg2);
                return 0;
            case -3:
                fprintf(stderr, "*** error: %d: Cannot set `%s': bad value '%s' (possible error: %s)\n", line, arg2, arg3, strerror(errno));
                return 0;
            case -2:
                fprintf(stderr, "*** error: %d: Cannot set `%s': must be set *inside* `user xxx {...}' block\n", line, arg2);
                return 0;
            case -1:
                fprintf(stderr, "*** error: %d: Cannot set `%s': must be set *outside* `user xxx {...}' block\n", line, arg2);
                return 0;
            }
            break;


        case CFG_CMD_VHOSTS:
          {
            int r;
            dynbuff n(512, 0);
            r = grab_block(db, &n, line-1);
            switch (r)
            {
            case 0:
            case -1:
                fprintf(stderr, "*** error: %d: Parse Error in vhost block (ended prematurely?)\n", line);
                return 0;
            default:
              {
                DEBUG("parse_block(): create new vhost block\n");
                char buff2[200];
                int line2 = 0;
                while (n.copy_line(line2++, buff2, sizeof(buff2), 1, 0) != -1)
                {
                    DEBUG("parse_block(): vhost: got %s\n", buff2);
                    if (user)
                    {
                        if (!user->vhosts)
                            user->vhosts = new list<char>;
                        user->vhosts->add(my_strdup(strip(buff2)));
                    }
                    else
                        __vhosts->add(my_strdup(strip(buff2)));
                }
              }
            }
            line = r;
            break;
          }
        case CFG_CMD_USER:
          {
            dynbuff n(2048,0);
            int r;
            userdef * u;
            DEBUG("parse_block(): creating new user block\n");

            if (arg2[0] == '{')
            {
                fprintf(stderr, "*** error: %d: user block must in the form: `user <username> {'\n", line);
                return 0;
            }
            if (userdef::find(__users, arg2))
            {
                fprintf(stderr, "*** error: %d: user `%s' already defined!\n", line, arg2);
                return 0;
            }
            r = grab_block(db, &n, line-1);
            if (!r)
            {
                fprintf(stderr, "*** error: %d: Unable to grab block after `user' statement\n", line);
                return 0;
            }

            u = new userdef(arg2);

            if (!parse_block(&n, flags | MATCH_USER, u))
            {
                fprintf(stderr, "*** error: %d: Error parsing `user' command block at line %d\n", line, line);
                delete u;
                return 0;
            }

            __users->add(u);
            line = r;

            DEBUG("parse_block(): ...parsed successfully\n");
            break;
          }
        }
    }

    /* Check that the user created is valid: */
    if (user)
    {
        if (!check_user_config(user))
        {
            fprintf(stderr, "*** error: %d: Error(s) creating user `%s'\n", line, user->name);
            /* delete user; -- we do this elsewhere */
            return 0;
        }

        struct in_addr in = { user->cfg.get(PREF_VHOST) };

        printf("---> Created user     `%s'\n", user->name);
        printf("  |---> Password:      %s\n", user->cfg.get(OPT_PASSWORD, 0));
        printf("  |---> Default VHost: %s\n", inet_ntoa(in));
        printf("  |---> Admin:         %s\n", user->cfg.checkf(OPT_USER_IS_ADMIN) ? "yes" : "no");
        printf("  \\---> Detachable:    %s\n", user->cfg.checkf(OPT_ENABLE_DETACH_COMMAND) ? "yes" : "no");
    }
    return 1;
}

/*
 * Return:
 * 1 - Success
 * -1 - Failed: not a user option
 * -2 - Failed: not a proxy option
 * -3 - Failed: illegal value
 * 0: other error (command not found)
 */
int do_set_command(userdef *  user, int flags, char * var, char * arg)
{
    const struct config_symbol * c;
    ToUpper(var);
    c = chash->lookup(var);
    /* Check that this is a valid command, and that we don't match any other
     * non-set crap in the symbol table */
    if ((!c) || (!(c->flags & PROXY_OPTION) && !(c->flags & USER_OPTION)))
        return 0;

    /* there should not be a 'user' to set this option */
    if ((c->flags & PROXY_OPTION) && user)
        return -1;

    if ((c->flags & USER_OPTION) && !user)
        return -2;

    int v = atoi(arg);

    switch (c->id)
    {
        /* proxy options: boolean */
        case ID_NO_REVERSE_LOOKUPS:
        case ID_KILL_WHEN_FULL:
        case ID_PREVENT_SELF_CONNECTS:
        case ID_SILENT_REJECTION:
            if (v)
                __popt->flags |= c->id;
            else
                __popt->flags &= ~(c->id);
            return 1;


        /* user options: boolean */
        case OPT_ENABLE_VHOST_COMMAND:
        case OPT_ENABLE_DETACH_COMMAND:
        case OPT_DROP_ON_DISCONNECT:
        case OPT_ENABLE_FAKE_IDENTS:
        case OPT_AUTO_FAKE_IDENT:
        case OPT_ENABLE_OUTGOING_DCC_PROXYING:
        case OPT_ENABLE_INCOMING_DCC_PROXYING:
        case OPT_ENABLE_AUTO_DETACH:
        case OPT_ENABLE_PRIVATE_LOGGING:
        case OPT_ENABLE_PUBLIC_LOGGING:
        case OPT_ENABLE_SEPERATE_LOGGING:
        case OPT_USER_IS_ADMIN:
            if (v)
            {
                user->cfg.setf(c->id);
                user->cfg.setp(c->id);
            }
            else
            {
                user->cfg.clearf(c->id);
                user->cfg.clearp(c->id);
            }
            return 1;

        case OPT_MAX_IDLE_TIME:
        case OPT_MAX_LOGFILE_SIZE:
            user->cfg.set(c->id, v);
            return 1;

        case OPT_DEFAULT_LOG_OPTIONS:
            user->cfg.set(c->id, logfile::charflags_to_int(arg));
            return 1;

        /* user options: strings */
        case OPT_AUTOSERVER:
        case OPT_PASSWORD:
        case OPT_DEFAULT_VHOST:
            if (!user->cfg.set(c->id, arg))
                return -3;
            return 1;

        /* proxy options: numeric */
        case MAX_SOCKETS:
            __popt->max_sockets = v;
            return 1;
        case MAX_DNS_WAIT_TIME:
            __popt->max_dns_wait_time = v;
            return 1;
        case MAX_BUFFER_SIZE:
            __popt->max_buffer_size = v;
            return 1;
        case MIN_BUFFER_SIZE:
            __popt->min_buffer_size = v;
            return 1;
        case MAX_REGISTER_TIME:
            __popt->max_register_time = (time_t) v;
            return 1;
        case MAX_FAILED_PASSWORDS:
            __popt->max_failed_passwords = v;
            return 1;

        /* Proxy options: string values */
        case LOGFILE:
            __popt->logfile = my_strdup(arg);
            return 1;
        case PIDFILE:
            __popt->pidfile = my_strdup(arg);
            return 1;
        case CERTFILE:
            __popt->certfile = my_strdup(arg);
            return 1;
        case USERFILE:
			__popt->userfile = my_strdup(arg);
			return 1;
        case LOG_DIR:
            __popt->logdir = my_strdup(arg);
            return 1;
        case MOTD_FILE:
            __popt->motdfile = my_strdup(arg);
            return 1;
        case DCC_LISTEN_PORT_RANGE:
            __popt->dcc_ports = my_strdup(arg);
            return 1;
        case LISTEN_VHOST:
            if (fill_in_addr(arg, &__popt->iface_listen) < 1)
                return -3;
            return 1;
        default:
            break;
    }
    return 0;
}

/* 
 *   Return values:
 *      1  -     Success and number of lines in the block
 *      0  -     Minor syntax error or something, but object was still created.   
 *     -1  -     Error in creating rule set. No rule set created. Reason given in 'reason'.
 *
 */  
static ruleset * do_ruleset(dynbuff * db, int id, char * reason, u_long reason_max)
{
    char line[256];
    unsigned success_tos = 0, success_froms = 0, curline = 0;
    ruleset * rs = 0;

    if (id == CFG_CMD_ALLOW)
        rs = new allowed_ruleset;
    else if (id == CFG_CMD_DENY)
        rs = new denied_ruleset;

    while (db->copy_line(0, line, sizeof(line), 0) != -1)
    {
        int num = 0, type = 0, current = 1;          
        char * address = 0, * ports = 0,* ban_reason = 0;
        char * p = strip(line);
        extern int mk_ppchar(char * string, char *(*buff)[MAX_PPCHAR_ARGS]);
        char *argv[MAX_PPCHAR_ARGS];
        memset(&argv, 0, sizeof(argv));

        curline++;

        if (!p)
            continue;

        mk_ppchar(p, &argv);

        /* 
         * Get second word first, which can be
         *  'from' or 'to' or the address depending on if a number was entered
         * before them.
         */
        if (strcasecmp(argv[2], "from") == 0)
            type = ruleset::FROM;
        else if (strcasecmp(argv[2], "to") == 0)
            type = ruleset::TO;

        /* Get first word, which will either be the number  or the type. */
        if (type)
        {
            if ((num = atoi(argv[1])) == 0)
            {
                fprintf(stderr, "In line: %s\n\t'0' or text entered for number field, using unlimited.\n",
                        argv[0]);
                num = ruleset::UNLIMITED;
            }
            current = 3;
        }
        else {
            if (strcasecmp(argv[1], "from") == 0)
                type = ruleset::FROM;
            else if (strcasecmp(argv[1], "to") == 0)
                type = ruleset::TO;
            else {
                fprintf(stderr, "In line: %s\n\tFirst word must be 'from' or 'to'.\n", argv[0]);
                delete[] argv[0];
                continue;
            }
            num = ruleset::UNLIMITED;
            current = 2;
        }

        /* Can now get address */
        if (!(address = argv[current++]))
        {
            fprintf(stderr, "In line: %s\n\tThis line is too short. Minimum requirements are <from/to> <address>\n", argv[0]);
            delete[] argv[0];
            continue;
        }
        if (type == ruleset::FROM)
            success_froms++;
        else
            success_tos++;

        /* Optional argument: port. Defaults to 'all'/
           Will also handle 'on' (quite messily?) */
        if (!argv[current])
        {
            ports = "all";
            /* This means we are the last thing.. set a default reason too */
            if (id == CFG_CMD_DENY)
                ban_reason = "No reason was given!";
        }
        else {
            if (strcasecmp(argv[current], "on") == 0)
            {
                ports = argv[++current];
                current++;
            }
            else /* not a valid port string */
               ports = "all";
            /* Remaining text is the ban reason. Only useful when id == DENY.*/
            if (id == CFG_CMD_DENY)
            {
                if (argv[current])
                    ban_reason = gettok_ptr(argv[0], ' ', current);
                else
                    ban_reason = "No reason was given!";
            }
        }

        /* All done w/ this ruleset */
        if (type == ruleset::FROM)
            rs->add_host_from(address, ports, ban_reason, num);
        else
            rs->add_host_to(address, ports, ban_reason, num);
        delete[] argv[0];
//        DEBUG("CONFIG: Creating ruleset with address: %s | ports: %s | reason: %s | num %d\n",
//                    address, ports, ban_reason, num);
    }

    /* Is it valid rule set? */
    if ((id == CFG_CMD_ALLOW) && (success_tos == 0 || success_froms == 0))
    {
        safe_strcpy(reason, "Rule set of type 'allow' must contain at least one 'from' field and one 'to' field.\n", reason_max);
        delete rs;
        return 0;
    }
    else if (success_tos == 0 && success_froms == 0)
    {
        safe_strcpy(reason, "No valid 'from' or 'to' fields found in rule set. Rule set destroyed.\n", reason_max);
        delete rs;
        return 0;
    }
    /* yes it is valid */
    return rs;
}

static bool check_user_config(userdef * user)
{
    bool err = 0;
    if (!user->rulesets->size())
    {
        fprintf(stderr, "*** error: user definition `%s' has no allow rulesets\n", user->name);
        err = 1;
    }

    if (user->cfg.checkf(OPT_AUTO_FAKE_IDENT) && !user->cfg.checkf(OPT_ENABLE_FAKE_IDENTS))
    {
        fprintf(stderr,"*** error: user %s: auto-fake-idents set to 1, but enable-fake-idents not set to 1\n", user->name);
        err = 1;
    }
    if (user->cfg.checkf(OPT_ENABLE_AUTO_DETACH) && !user->cfg.checkf(OPT_ENABLE_DETACH_COMMAND))
    {
        fprintf(stderr,"*** error: user %s: enable-auto-detach enabled but detach/reattach commands are not.\n", user->name);
        err = 1;
    }

    if (!user->cfg.get(OPT_PASSWORD, 0))
    {
        fprintf(stderr, "*** error: user %s: must supply a password\n", user->name);
        err = 1;
    }

    if (user->cfg.checkf(OPT_ENABLE_PUBLIC_LOGGING) || user->cfg.checkf(OPT_ENABLE_PRIVATE_LOGGING))
    {
        if (!user->cfg.get(OPT_LOG_OPTIONS))
        {
            fprintf(stderr, "*** warning: user %s: No log options were set: defaulting to log all\n", user->name);
            user->cfg.set(OPT_LOG_OPTIONS, logfile::LOG_ALL);
        }
    }

    return !err;
}

static bool check_config(void)
{
    bool err = 0;

    if (!__users->size())
    {
        fprintf(stderr,"\n*** error: No users defined");
        err = 1;   
    }
    if (!__popt->logfile)
    {
        fprintf(stderr, "\n*** error: \"logfile\" variable wasn't set");
        err = 1;
    }
    if (!__popt->ports && !__popt->sslports)
    {
        fprintf(stderr, "\n*** error: No listen ports defined");
        err = 1;
    }
#ifdef _USE_SSL
    if (!__popt->certfile && __popt->sslports)
    {
        fprintf(stderr," \n*** error: SSL listen ports set, but no \"certfile\" defined");
        err = 1;
    }
#endif

    if (__popt->max_sockets < 10)
    {
        fprintf(stderr,"\n*** error: \"max-sockets\" variable set too low. Must be >= 10");
        err = 1;
    }

    if (__popt->logdir && access(__popt->logdir, R_OK | W_OK | X_OK))
    {
        fprintf(stderr, "\n*** error: Cannot access log file base dir `%s': %s", __popt->logdir, strerror(errno));
        err = 1;
    }
    if (__popt->max_buffer_size <= __popt->min_buffer_size)
    {
        fprintf(stderr, "\n*** error: \"max-buffer-size\" must be greater than \"min_buffer_size\"");
        err = 1;
    }

    if (err) {
        fputc('\n', stderr);
        return 0;
    }
    return 1;
}

int cfghash::insert(const struct config_symbol * c)
{
    int idx = hash(c->symbol) % buckets;
    list_add(&table[idx], (void *) c);
    return 1;
}

const struct config_symbol * cfghash::lookup(const char * str)
{
    int idx = hash(str) % (buckets);
    struct hashlist * l = &table[idx];
    struct node * n;
    const struct config_symbol * c;
    lookups++;
    for (n = l->head; n; n = n->next)
    {
        c = (const struct config_symbol *) n->data;
        if (!strcmp(c->symbol, str))
            return hits++, c;
    }
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1