/******************************************************************************
**
** conf.c
**
** This file is part of the ABYSS Web server project.
**
** Copyright (C) 2000 by Moez Mahfoudh <mmoez@bigfoot.com>.
** All rights reserved.
**
** Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions
** are met:
** 1. Redistributions of source code must retain the above copyright
**    notice, this list of conditions and the following disclaimer.
** 2. Redistributions in binary form must reproduce the above copyright
**    notice, this list of conditions and the following disclaimer in the
**    documentation and/or other materials provided with the distribution.
** 3. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
** 
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
** ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
** SUCH DAMAGE.
**
******************************************************************************/

#define _XOPEN_SOURCE 600   /* For strdup() */
#define _BSD_SOURCE  /* For xmlrpc_strcaseeq */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#if defined(_WIN32)
#include <direct.h>
#endif

#ifndef _WIN32
#include <pwd.h>
#endif

#include "xmlrpc_config.h"
#include "bool.h"
#include "xmlrpc-c/string_int.h"
#include "xmlrpc-c/abyss.h"
#include "trace.h"
#include "file.h"
#include "server.h"
#include "http.h"
#include "handler.h"

/*********************************************************************
** Configuration Files Parsing Functions
*********************************************************************/



static bool
ConfReadLine(TFile *  const fileP,
             char *   const buffer,
             uint32_t const lenArg) {

    bool r;
    char c;
    char * p;
    char * z;
    uint32_t len;

    len = lenArg;  /* initial value */
    r = TRUE;  /* initial value */
    z = buffer;  /* initial value */

    while (--len > 0) {
        int32_t bytesRead;
        
        bytesRead = FileRead(fileP, z, 1);
        if (bytesRead < 1) {
            if (z == buffer)
                r = FALSE;
            break;
        };

        if (*z == CR || *z == LF)
            break;

        ++z;
    }

    if (len == 0)
        while (FileRead(fileP, &c, 1) == 1)
            if (c == CR || c == LF)
                break;

    *z = '\0';

    /* Discard comments */
    p = strchr(buffer, '#');
    if (p)
        *p = '\0';

    return r;
}



static bool
ConfNextToken(char ** const p) {

    while (1)
        switch (**p) {
        case '\t':
        case ' ':
            (*p)++;
            break;
        case '\0':
            return FALSE;
        default:
            return TRUE;
        };
}

static char *
ConfGetToken(char **p) {
    char *p0=*p;

    while (1)
        switch (**p)
        {
        case '\t':
        case ' ':
        case CR:
        case LF:
        case '\0':
            if (p0==*p)
                return NULL;

            if (**p)
            {
                **p='\0';
                (*p)++;
            };
            return p0;

        default:
            (*p)++;
        };
}

static bool
ConfReadInt(const char * const p,
            int32_t *    const n,
            int32_t      const min,
            int32_t      const max) {
/*----------------------------------------------------------------------------
   Convert string 'p' to integer *n.

   If it isn't a valid integer or is not with the bounds [min, max],
   return FALSE.  Otherwise, return TRUE.
-----------------------------------------------------------------------------*/
    char * e;

    *n = strtol(p, &e, 10);

    if (min != max)
        return ((e != p) && (*n >= min) && (*n <= max));
    else
        return (e != p);
}



static bool
ConfReadBool(const char * const token,
             bool *       const bP) {

    bool succeeded;

    if (xmlrpc_strcaseeq(token, "yes")) {
        *bP = TRUE;
        succeeded = TRUE;
    } else if (xmlrpc_strcaseeq(token, "no")) {
        *bP = FALSE;
        succeeded = TRUE;
    } else
        succeeded = FALSE;

    return succeeded;
}

/*********************************************************************
** MIME Types File
*********************************************************************/

static void
readMIMETypesFile(const char * const filename,
                  MIMEType **  const MIMETypePP) {

    bool success;
    MIMEType * MIMETypeP;

    MIMETypeP = MIMETypeCreate();
    if (MIMETypeP) {
        TFile * fileP;
        bool fileOpened;

        fileOpened = FileOpen(&fileP, filename, O_RDONLY);
        if (fileOpened) {
            char z[512];
            while (ConfReadLine(fileP, z, 512)) {
                char * p;
                p = &z[0];
            
                if (ConfNextToken(&p)) {
                    const char * mimetype = ConfGetToken(&p);
                    if (mimetype) {
                        while (ConfNextToken(&p)) {
                            const char * const ext = ConfGetToken(&p);
                            if (ext)
                                MIMETypeAdd2(MIMETypeP, mimetype, ext);
                            else
                                break;
                        }
                    }
                }
            }
            FileClose(fileP);
            success = TRUE;
        } else
            success = FALSE;
        if (!success)
            MIMETypeDestroy(MIMETypeP);
    } else
        success = FALSE;

    if (success)
        *MIMETypePP = MIMETypeP;
    else
        *MIMETypePP = NULL;
}

/*********************************************************************
** Server Configuration File
*********************************************************************/

static void
chdirx(const char * const newdir,
       bool *       const successP) {
    
    *successP = XMLRPC_CHDIR(newdir) == 0;
}



static void
parseUser(const char *      const p, 
          struct _TServer * const srvP) {
#ifdef _WIN32
    /* *srvP has no 'uid' or 'gid' member; system has no getpwnam() */
    TraceMsg("User option ignored");
#else
    if (p[0] == '#') {
        int32_t n;
        
        if (!ConfReadInt(&p[1], &n, 0, 0))
            TraceExit("Bad user number '%s'", p);
        else
            srvP->uid = n;
    } else {
        struct passwd * pwd;

        if (!(pwd = getpwnam(p)))
            TraceExit("Unknown user '%s'", p);
        
        srvP->uid = pwd->pw_uid;
        if ((int)srvP->gid==(-1))
            srvP->gid = pwd->pw_gid;
    };
#endif
}



static void
parsePidfile(const char *      const p,
             struct _TServer * const srvP) {
    bool succeeded;
    succeeded = FileOpenCreate(&srvP->pidfileP, p, O_TRUNC | O_WRONLY);
    if (!succeeded) {
        srvP->pidfileP = NULL;
        TraceMsg("Bad PidFile value '%s'", p);
    };
}



abyss_bool
ConfReadServerFile(const char * const filename,
                   TServer *    const serverP) {

    struct _TServer * const srvP     = serverP->srvP;
    BIHandler *       const handlerP = srvP->builtinHandlerP;

    TFile * fileP;
    char z[512];
    char * p;
    unsigned int lineNum;
    TFileStat fs;

    if (!FileOpen(&fileP, filename, O_RDONLY))
        return FALSE;

    lineNum = 0;

    while (ConfReadLine(fileP, z, 512)) {
        ++lineNum;
        p = z;

        if (ConfNextToken(&p)) {
            const char * const option = ConfGetToken(&p);
            if (option) {
                ConfNextToken(&p);

                if (xmlrpc_strcaseeq(option, "port")) {
                    int32_t n;
                    if (ConfReadInt(p, &n, 1, 65535))
                        srvP->port = n;
                    else
                        TraceExit("Invalid port '%s'", p);
                } else if (xmlrpc_strcaseeq(option, "serverroot")) {
                    bool success;
                    chdirx(p, &success);
                    if (!success)
                        TraceExit("Invalid server root '%s'",p);
                } else if (xmlrpc_strcaseeq(option, "path")) {
                    if (FileStat(p, &fs))
                        if (fs.st_mode & S_IFDIR) {
                            HandlerSetFilesPath(handlerP, p);
                            continue;
                        }
                    TraceExit("Invalid path '%s'", p);
                } else if (xmlrpc_strcaseeq(option, "default")) {
                    const char * filename;
                    
                    while ((filename = ConfGetToken(&p))) {
                        HandlerAddDefaultFN(handlerP, filename);
                        if (!ConfNextToken(&p))
                            break;
                    }
                } else if (xmlrpc_strcaseeq(option, "keepalive")) {
                    int32_t n;
                    if (ConfReadInt(p, &n, 1, 65535))
                        srvP->keepalivemaxconn = n;
                    else
                        TraceExit("Invalid KeepAlive value '%s'", p);
                } else if (xmlrpc_strcaseeq(option, "timeout")) {
                    int32_t n;
                    if (ConfReadInt(p, &n, 1, 3600)) {
                        srvP->keepalivetimeout = n;
                        /* Must see what to do with that */
                        srvP->timeout = n;
                    } else
                        TraceExit("Invalid TimeOut value '%s'", p);
                } else if (xmlrpc_strcaseeq(option, "mimetypes")) {
                    MIMEType * mimeTypeP;
                    readMIMETypesFile(p, &mimeTypeP);
                    if (!mimeTypeP)
                        TraceExit("Can't read MIME Types file '%s'", p);
                    else
                        HandlerSetMimeType(handlerP, mimeTypeP);
                } else if (xmlrpc_strcaseeq(option,"logfile")) {
                    srvP->logfilename = strdup(p);
                } else if (xmlrpc_strcaseeq(option,"user")) {
                    parseUser(p, srvP);
                } else if (xmlrpc_strcaseeq(option, "pidfile")) {
                    parsePidfile(p, srvP);
                } else if (xmlrpc_strcaseeq(option, "advertiseserver")) {
                    if (!ConfReadBool(p, &srvP->advertise))
                        TraceExit("Invalid boolean value "
                                  "for AdvertiseServer option");
                } else
                    TraceExit("Invalid option '%s' at line %u",
                              option, lineNum);
            }
        }
    }

    FileClose(fileP);
    return TRUE;
}
