/************************************************************************
 *   Bahamut IRCd, src/confparse.c
 *   Copyright (C) 2004, Aaron Wiebe
 *
 *   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 1, 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.
 */

/* $Id: confparse.c,v 1.1.1.1 2005/06/27 03:02:27 sheik Exp $ */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#include "userban.h"
#define CONF_TABS
#include "confparse.h"

#ifndef LINE_MAX
#define LINE_MAX 256
#endif

/* notes on confparse.c
 * While this initial revision requires a fair bit of trimming down,
 * my primary goal right now was to build an extendable system that will
 * allow for fairly easy changes to the config file.
 * Heres a few notes on how to go about that.
 *
 * The parser works on two primary depths - blocks and tokens:
 *
 * block { 
 *      token value;
 *      token "string value";
 *      token 123;      # int value
 *      token;          # nonvar token
 * };
 *
 * It can also parse non-token blocks:
 *
 * block (
 *      "string string string";
 *      "string blah";
 * };
 *
 * Blocks are defined by tconftab (in confparse.h)
 * Tokens are defined by sconftab (^^^^^^^^^^^^^^)
 *
 * Each block must have a function that takes the values collected
 * and checks them against the requirements.  These functions are also
 * handy for getting variables out of the array that they are stored in.
 *
 * The array variables are placed in (an array of cVar structs) contains
 * all values for the the block, and the corrisponding sconftab item.
 *
 * I feel the need to rewrite large sections of this still, but I'll just
 * be happy to have it working for now.
 *
 * Feb 24/04
 * -epi
 */

extern int forked;
extern char *set_classes(void);
extern aPort *new_ports;
extern Conf_Me *new_MeLine;

/* free_vars()
 * clear our temp variable array used by parse_block and children
 */

static void
free_vars(cVar *vars[])
{
    int i = 0;

    while(vars[i])
    {
        MyFree(vars[i]->value);
        MyFree(vars[i]);
        i++;
    }
}

/* error handler */

static char *current_file = "unknown";

void
confparse_error(char *problem, int line)
{
    if(!forked)
        printf("ERROR:  %s near line %d of %s\n", problem, line, current_file);
    else
        sendto_realops("Conf Error:  %s near line %d of %s", problem, line,
                        current_file);
    return;
}

/* check_quote
 * this routine skips over any ignored items inside our file
 */

static int quote = 0;

static char *
check_quote(char *cur)
{
    if(quote)
    {
        while((cur = strchr(cur, '*')))
            if((*(++cur) == '/'))
            {
                cur++;
                quote = 0;
                break;
            }
        if(!cur)
            return cur;
    }
    while((*cur == ' ') || (*cur == '\t'))
        cur++;
    /* now we've hit something .. check for single line quotes */
    if (!*cur || *cur == '#' || *cur == '\n' ||
            (*cur == '/' && *(cur+1) == '/'))
        return NULL;
    /* check for multiple line quotes */
    if((*cur == '/') && (*(cur+1) == '*'))
    {
        cur += 2;
        quote = 1;
        while((cur = strchr(cur, '*')))
            if((*(++cur) == '/'))
            {
                cur++;
                quote = 0;
                break;
            }
        if(!cur)
            return cur;
        else
            return check_quote(cur);
    }
    return cur;
}

#define MAX_VALUES 128  /* maximum values per block */

static char *
parse_block(tConf *block, char *cur, FILE *file, int *lnum)
{
    char *tok, *var, *var2;
    char line[LINE_MAX];
    tConf *b2 = NULL;
    sConf *item = NULL;
    sConf *sconftab = block->subtok;
    cVar  *vars[MAX_VALUES] = { 0 };
    int   vnum = 0, tlnum = 0, clear = 0, done = 0, skip = 0;

    if((sconftab) && (sconftab->flag == SCONFF_STRING))
    {
        /* this subtype only takes freeform variables
         * dont bother looking for tokens
         */
        int i = 0;
        while(!BadPtr(cur) || ((fgets(line, LINE_MAX, file) != NULL) && 
                (*lnum)++ && (cur = line)))
        {
            cur = check_quote(cur);
            if(BadPtr(cur))
                continue;
            if(clear)
            {
                if(*cur != ';')
                {
                    confparse_error("Missing semicolon", *lnum);
                    free_vars(vars);
                    return NULL;
                }
                else
                    cur++;
                clear = 0;
                cur = check_quote(cur);
                if(BadPtr(cur))
                    continue;
            }
            if(done)
            {
                if(*cur != ';')
                {
                    confparse_error("Missing block end semicolon", *lnum);
                    free_vars(vars);
                    return NULL;
                }
                else
                    cur++;
                if(((*block->func) (vars, *lnum)) == -1)
                {
                    free_vars(vars);
                    return NULL;
                }
                if(BadPtr(cur))
                    *cur = '#';     /* we cant return a bad pointer because
                                     * that will pull us out of the conf read
                                     * so this will just get ignored
                                     * kludgy, but effective */
                free_vars(vars);
                return cur;
            }
            cur = check_quote(cur);
            if(BadPtr(cur))
                continue;
            if(*cur == '}')
            {
                done = 1;
                cur++;
                cur = check_quote(cur);
                if(BadPtr(cur))
                    continue;
                if(*cur != ';')
                {
                    confparse_error("Missing block end semicolon", *lnum);
                    free_vars(vars);
                    return NULL;
                }
                else
                    cur++;
                if(((*block->func) (vars, *lnum)) == -1)
                {
                    free_vars(vars);
                    return NULL;
                }
                if(BadPtr(cur))
                    *cur = '#';     /* we cant return a bad pointer because
                                     * that will pull us out of the conf read
                                     * so this will just get ignored
                                     * kludgy, but effective */
                free_vars(vars);
                return cur;
            }
            vars[vnum] = (cVar *) MyMalloc(sizeof(cVar));
            memset((char *) vars[vnum], '\0', sizeof(cVar));
            vars[vnum]->loaded = 1;
            vars[vnum]->type = NULL;
            tok = cur;
            if(*cur == '"')
            {
                i = 1;
                cur++;
            }
            var = cur;
            if(i == 1)
            {
                while(!BadPtr(cur) && (*cur != '"'))
                    cur++;
                if(BadPtr(cur))
                {
                    confparse_error("Cant find closequote", *lnum);
                    free_vars(vars);
                    return NULL;
                }
                *cur = '\0';
                cur++;
                while(!BadPtr(cur) && (*cur != ';'))
                    cur++;
            }
            else
            {
                while(!BadPtr(cur) && (*cur != ';'))
                {
                    if((*cur == ' '))
                    {
                        *cur = '\0';
                        if(vars[vnum]->loaded == 1)
                        {
                            DupString(vars[vnum]->value, var);
                            vars[vnum]->loaded = 2;
                        }
                    }
                    else if(vars[vnum]->loaded == 2)
                    {
                        confparse_error("Junk after value", *lnum);
                        free_vars(vars);
                        return NULL;
                    }
                    cur++;
                }
            }
            tlnum = *lnum;
            if(BadPtr(cur))
            {
                clear = 1;
                continue;
            }
            *cur = '\0';
            cur++;
            if(vars[vnum]->loaded == 1)
                DupString(vars[vnum]->value, var);
            vars[vnum]->loaded = 3;
            vnum++;
        }
        confparse_error("Unexpected EOF: Syntax Error", tlnum);
        free_vars(vars);
        return NULL;
    }

    while(!BadPtr(cur) || ((fgets(line, LINE_MAX, file) != NULL) && (*lnum)++
             && (cur = line)))
    {
        cur = check_quote(cur);
        if(BadPtr(cur))
            continue;
        if(clear)
        {
            /* if we're looking for a closing semicolon, check for it first
             * if we cant find it, ignore it and hope for the best
             */
            if(*cur != ';')
            {
                confparse_error("Missing semicolon ", *lnum);
                free_vars(vars);
                return NULL;
            }
            else
                cur++;
            clear = 0;
            if(vars[vnum])
            {
                vars[vnum]->loaded = 3;
                vnum++;
            }
            item = NULL;
            cur = check_quote(cur);
            if(BadPtr(cur))
                continue;
        }
        if(done)
        {
            /* we've found the end of our block, now we're looking for the
             * closing semicolon.  if we cant find it, ignore it and
             * hope for the best
             */
            if(*cur != ';')
            {
                confparse_error("Missing block end semicolon", *lnum);
                free_vars(vars);
                return NULL;
            }
            else
                cur++;
            if(((*block->func) (vars, *lnum)) == -1)
            {
                free_vars(vars);
                return NULL;
            }
            if(BadPtr(cur))
                *cur = '#';     /* we cant return a bad pointer because
                                 * that will pull us out of the conf read
                                 * so this will just get ignored
                                 * kludgy, but effective */
            free_vars(vars);
            return cur;
        }
        if(b2 && b2->tok)
        {
            /* we've identified a nested block in a previous loop.
             * we didnt get an openquote yet, so look for that.
             * we must find this.  keep looking til we do.
             */
            if(*cur != '{')
            {
                confparse_error("Junk after nested block token", *lnum);
                free_vars(vars);
                return NULL;
            }
            cur++;
            cur = check_quote(cur);
            cur = parse_block(b2, cur, file, lnum);
            b2 = NULL;
            continue;
        }
        if(!item || !item->tok)
        {
            /* if we dont already have a specific token we're working on
             * find one here.
             */
            cur = check_quote(cur);
            if(BadPtr(cur))
                continue;
            tok = cur;
            tlnum = *lnum;
            if(*cur == '}')
            {
                /* if we've got a closebracket, then we've hit the end
                 * of our block.
                 */
                done = 1;
                cur++;
                cur = check_quote(cur);
                if(BadPtr(cur))
                    continue;
                if(*cur != ';')
                {
                    confparse_error("Missing block end semicolon", *lnum);
                    free_vars(vars);
                    return NULL;
                }
                else
                    cur++;
                if(((*block->func) (vars, *lnum)) == -1)
                {
                    free_vars(vars);
                    return NULL;
                }
                if(BadPtr(cur))
                    *cur = '#';     /* we cant return a bad pointer because
                                     * that will pull us out of the conf read
                                     * so this will just get ignored
                                     * kludgy, but effective */
                free_vars(vars);
                return cur;

            }
            /* our token ends where whitespace or a semicolon begins */
            while(!BadPtr(cur) && ((*cur != ' ') && (*cur != ';') &&
                   (*cur != '\t') && (*cur != '\n')))
                cur++;
            if(BadPtr(cur))
            {
                confparse_error("Unterminated token", *lnum);
                free_vars(vars);
                return NULL;
            }
            else 
            {
                if(*cur == ';')
                    skip = 1;
                *cur = '\0';
            }
            cur++;
            if(block->nest)
            {
                /* we allow nested stuff inside here, so check for it. */
                for(b2 = tconftab; b2->tok; b2++)
                    if(!mycmp(b2->tok, tok))
                        break;
                if(b2 && b2->tok)
                    if(!(block->nest & b2->flag))
                        b2 = NULL;
                if(b2 && b2->tok)
                {
                    /* recurse through the block we found */
                    tlnum = *lnum;
                    cur = check_quote(cur);
                    if(BadPtr(cur))
                        continue;
                    if(*cur != '{')
                    {
                        confparse_error("Junk after nested block name", *lnum);
                        free_vars(vars);
                        return NULL;
                    }
                    cur++;
                    cur = check_quote(cur);
                    cur = parse_block(b2, cur, file, lnum);
                    if(!cur)
                    {
                        free_vars(vars);
                        return NULL;
                    }
                    b2 = NULL;
                    continue;
                }
            }
            /* find our token */
            for(item = sconftab; item && item->tok; item++)
                if(!mycmp(item->tok, tok))
                    break;
            if(!item->tok)
            {
                confparse_error("Unknown token", *lnum);
                free_vars(vars);
                return NULL;
            }
            /* create our variable */
            vars[vnum] = (cVar *) MyMalloc(sizeof(cVar));
            memset((char *) vars[vnum], '\0', sizeof(cVar));
            vars[vnum]->type = item;
            vars[vnum]->loaded = 1;
        }
        if(item->var & VARTYPE_NONE)
        {
            /* we dont need to grab a variable for this type 
             * just look for the closing semicolon, and move on */
            vars[vnum]->loaded = 2;
            if(!skip)   
            {
                /* we've already gotten our semicolon back
                 * at the end of our token.  dont look for it. */
                cur = check_quote(cur);
                while(!BadPtr(cur) && (*cur != ';'))
                    cur++;
                if(BadPtr(cur))
                {
                    clear = 1;
                    continue;
                }
                cur++;
            }
            skip = 0;
            vars[vnum]->loaded = 3;
            vnum++;
            item = NULL;
            continue;
        }
        if(item->var & VARTYPE_STRING)
        {
            /* we're looking for a string here, so we require
             * quotes around the string...
             */
            cur = check_quote(cur);
            while(!BadPtr(cur) && (*cur != '"'))
                cur++;
            if(BadPtr(cur))
                continue;
            cur++;
            var = cur;
            while(!BadPtr(cur) && (*cur != '"'))
                cur++;
            if(BadPtr(cur))
            {
                confparse_error("Unterminated quote", *lnum);
                free_vars(vars);
                return NULL;
            }
            *cur = '\0';
            cur++;
            DupString(vars[vnum]->value, var);
            vars[vnum]->loaded = 2;
            while(!BadPtr(cur) && (*cur != ';'))
                cur++;
            if(BadPtr(cur))
            {
                clear = 1;
                continue;
            }
            cur++;
            vars[vnum]->loaded = 3;
            vnum++;
            item = NULL;
            continue;
        }
        if(item->var & VARTYPE_INT)
        {
            cur = check_quote(cur);
            var = cur;
            while(!BadPtr(cur) && ((*cur != ';') && (*cur != '\t') &&
                    (*cur != '\n') && (*cur != ' ')))
                cur++;
            if(BadPtr(cur))
            {
                clear = 1;
                continue;
            }
            if(*cur != ';')
                clear = 1;
            *cur = '\0';
            cur++;
            var2 = var;
            while(*var) 
            {
                if(IsDigit(*var))
                    var++;
                else
                {
                    confparse_error("Expecting integer value", *lnum);
                    free_vars(vars);
                    return NULL;
                }
            }
            if(!item)
                continue;
            var = var2;
            DupString(vars[vnum]->value, var);
            vars[vnum]->loaded = 3;
            vnum++;
            item = NULL;
            continue;
        }
        if(item->var & VARTYPE_NAME)
        {
            cur = check_quote(cur);
            if(!BadPtr(cur) && (*cur == '"'))
                cur++;
            var = cur;
            while(!BadPtr(cur) && (*cur != ';'))
            {
                if((*cur == ' ') || (*cur == '"') || (*cur == '\t'))
                {
                    *cur = '\0';
                    if(vars[vnum]->loaded == 1)
                    {
                        DupString(vars[vnum]->value, var);
                        vars[vnum]->loaded = 2;
                    }
                }
                cur++;
            }
            if(BadPtr(cur))
            {
                clear = 1;
                continue;
            }
            *cur = '\0';
            cur++;
            if(vars[vnum]->loaded == 1)
                DupString(vars[vnum]->value, var);
            vars[vnum]->loaded = 3;
            vnum++;
            item = NULL;
            continue;
        }
        confparse_error("Unexpected EOF:  Syntax Error", tlnum);
        free_vars(vars);
        return NULL;
    }
    confparse_error("Unexpected EOF:  Syntax Error", tlnum);
    free_vars(vars);
    return NULL;
}
            
int
initconf(char *filename)
{
    int lnum = 0, blnum = 0, clear = 0;
    char line[LINE_MAX];
    char *cur = NULL;
    char *tok;
    tConf *block = NULL;
    FILE *file;
    int including = 0;

    current_file = filename;

    if(!(file = fopen(filename, "r")))
    {
        if(forked)
            sendto_realops("Unable to open config file %s", filename);
        else
            printf("Unable to open config file %s\n", filename);
        return -1;
    }

    while(!BadPtr(cur) || ((fgets(line, LINE_MAX, file) != NULL) && ++lnum
             && (cur = line)))
    {
        cur = check_quote(cur);
        if(BadPtr(cur))
            continue;

        if (including)
        {
            if (including == 1)
            {
jmp_including:
                if (*cur == '"' || *cur == '<')
                    cur++;
                tok = cur;
                while (*cur && *cur != ' ' && *cur != '\t' && *cur != '"'
                       && *cur != '>' && *cur != ';' && *cur != '\n')
                    cur++;
                if (*cur == ';')
                    including = 0;
                else
                    including++;
                *cur++ = 0;

                if (!*tok)
                {
                    confparse_error("Bad include filename", lnum);
                    return -1;
                }

                /* parse new file */
                if(initconf(tok) == -1)
                {
                    current_file = filename;
                    confparse_error("while processing include directive",lnum);
                    return -1;
                }

                /* reset */
                current_file = filename;

                cur = check_quote(cur);
                if (BadPtr(cur))
                    continue;
            }
            if (including == 2)
            {
                if (*cur != ';')
                {
                    confparse_error("Missing semicolon", lnum);
                    return -1;
                }
                including = 0;
                cur++;
                cur = check_quote(cur);
                if (BadPtr(cur))
                    continue;
            }
        }

        /* now, we should be ok to get that token.. */
        if(!block)
        {
            tok = cur;
            while((*cur != ' ') && (*cur != '\n') && (*cur != '{'))
                cur++;      /* find the whitespace following the token */
            if(*cur == '{')
                clear = 1;
            *cur = '\0';
            cur++;

            if (!mycmp("INCLUDE", tok))
            {
                if(clear)
                {
                    confparse_error("Unexpected opening bracket", lnum);
                    return -1;
                }
                including++;
                cur = check_quote(cur);
                if (BadPtr(cur))
                    continue;
                goto jmp_including; /* XXX */
            }

            for(block = tconftab; block->tok; block++)
                if(!mycmp(block->tok, tok))
                    break;
            if(!block->tok)
            {
                confparse_error("Unknown block type", lnum);
                return -1;
            }
            blnum = lnum;
        }
        cur = check_quote(cur);
        if(BadPtr(cur))
            continue;
        if((*cur ==  '{') || clear)
            cur++;
        else
        {
            confparse_error("Junk after block name", lnum);
            return -1;
        }
        if((cur = parse_block(block, cur, file, &lnum)) == NULL)
        {
            return -1;
        }
        clear = 0;
        block = NULL;
        continue;
    }
    if(clear)
    {
        confparse_error("Unexpected EOF:  Syntax error", blnum);
        return -1;
    }
    return 1;
}

inline char *
finishconf(void)
{
    static char buf[256];
    char *ret;

    if (!new_MeLine || !new_MeLine->servername)
        return "Missing global block";
    if ((ret = set_classes()))
    {
        ircsnprintf(buf, sizeof(buf), "Missing class block for referenced "
                    "class '%s'", ret);
        return buf;
    }
    if (!new_ports)
        return "No ports defined";
    return NULL;
}


syntax highlighted by Code2HTML, v. 0.9.1