/*  Configuration file handling

    ©1998, 1999, 2000 Joseph Walton

    This software is distributed under the terms of the GNU General Public
    License; either version 2 of the License, or (at your option) any
    later version.
*/

/*
    Here we read in a configuration file and parse it. It assumes a single
     global configuration that is never changed, which is hardly ideal
     but sufficient for our purposes.
*/

/*
    30-Nov-1999 - No longer prints a mask of 255.255.255.255 when summarising.
    22-Feb-2000 - Correctly frees an existing configuration.
*/

#include "nfsd.h"
#include "memory.h"
#include "util.h"
#include "config.h"

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

#include <dos/dos.h>

#include <clib/alib_protos.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/socket.h>

/* This is the format of the configuration lines */
#define TEMPLATE "PATH/A,EXPORTAS/A,IP/A,RW=READWRITE/S,UID/K/N,GID/K/N,PERM/K/N,ALL=FORCEALL/S"

/* Options are returned here - it must match the format above */
LONG opts[8];

/* This is the buffer lines are read into. It should be enough for most
    configurations. */
char buffer[1024];

/* Internal prototypes */
static struct ConfigEntry *create_configentry(STRPTR path,
    STRPTR exportas, STRPTR exportip, BOOL readwrite,
    LONG *uid, LONG *gid, LONG *perm, BOOL forceall);
void free_configentry(struct ConfigEntry *ce);

BOOL configuration_load()
{
    BPTR handle;
    BOOL success = TRUE;

    int line_number = 0;

    /* Free old configuration first */
    {
        struct ConfigEntry *ce;

        while (ce = RemTail(&global.config)) {
            free_configentry(ce);
        }
    }

    /* Monotonically increase the config serial number to keep it unique */
    global.cfgSerial++;

    if (handle = Open(global.cfgFileName, MODE_OLDFILE)) {
        struct RDArgs *rda = AllocDosObject(DOS_RDARGS, NULL);

        if (rda) {
            rda->RDA_Flags = RDAF_NOPROMPT;
            rda->RDA_DAList = NULL;

            while (success && FGets(handle, buffer, sizeof(buffer))) {
                int len;
                STRPTR cmnt;

                line_number++;

                /* First end the line at any comment char */
                if (cmnt = strchr(buffer, ';'))
                    *cmnt = 0;
                if (cmnt = strchr(buffer, '#'))
                    *cmnt = 0;

                len = strlen(buffer);

                /* Does this line still have any characters? */
                if ( (len > 0)

                /* Are they non-whitespace? */
                    && (strspn(buffer, " \t\n") < len) )
                {
                    /* Process that line */
                    rda->RDA_Source.CS_Buffer = buffer;
                    rda->RDA_Source.CS_Length = len;
                    rda->RDA_Source.CS_CurChr = 0;
                    rda->RDA_Buffer = NULL;
                    rda->RDA_BufSiz = 0;

                    /* Zero everything */
                    opts[0] = 0; opts[1] = 0;
                    opts[2] = 0; opts[3] = 0;
                    opts[4] = 0; opts[5] = 0; /* for UID,GID,permissions */
                    opts[6] = 0;
                    opts[7] = 0;

                    if (ReadArgs(TEMPLATE, opts, rda)) {
                        struct ConfigEntry *nce;

                        nce = create_configentry((STRPTR) opts[0],
                            (STRPTR) opts[1],
                            (STRPTR) opts[2], (BOOL) opts[3],
                            (LONG *) opts[4], (LONG *) opts[5],
                            (LONG *) opts[6], (BOOL) opts[7]);

                        if (nce) {
                            AddTail(&global.config, (struct Node *)nce);
                        } else {
                            printf("Problem with line %d\n", line_number);
                            success = FALSE;
                        }

                        FreeArgs(rda);
                    } else {
                        printf("Error at line %d\n", line_number);
                        PrintFault(IoErr(), "Error parsing configuration");
                        success = FALSE;
                    }
                }
            }

            if (IoErr() != 0) {/* Not the normal end of file */
                PrintFault(IoErr(), "Error reading configuration");
                success = FALSE;
            }

            FreeDosObject(DOS_RDARGS, rda);
        }
        Close(handle);
    } else {
        PrintFault(IoErr(), "Error opening configuration");
        printf("Unable to open %s\n", global.cfgFileName);
        success = FALSE;
    }

    return success;
}

/* This constructs a new ConfigEntry from the split up strings */
static struct ConfigEntry *create_configentry(STRPTR path,
    STRPTR exportas, STRPTR exportip, BOOL readwrite,
    LONG *uid, LONG *gid, LONG *perm, BOOL forceall)
{
    struct ConfigEntry *nce;

    /* Allocate store for this new entry */
    nce = AllocVecPooled(sizeof(struct ConfigEntry));

    if (nce) {
        nce->ce_Path = DupString(path);
        nce->ce_ExportAs = DupString(exportas);
    }

    /* Yes, I know, memory may be left unfreed by this. But if this
        fails, we quit out and our memory pool is freed anyway. */

    if (!(nce && nce->ce_Path && nce->ce_ExportAs)) {
        puts("create_configentry had memory problems");
        return NULL;
    }

    nce->ce_ReadWrite = readwrite;

    /* Let's actually parse the IP thing */
    if (!strcmp(exportip, "-")) {
        nce->ce_ExportIP = 0;     /* Match anything */
        nce->ce_IPMask = 0;
    } else {
        STRPTR slash = strchr(exportip, '/');
        if (slash)
            *slash++ = 0;

        /* We're checking for failure here, so 255.255.255.255
            ceases to be a valid address or mask.
            If this is the mask you wanted, just omit it - it's the default. */

        #define FAILED 0xffffffff

        if ( FAILED == (nce->ce_ExportIP = inet_addr(exportip))) {
            puts("Unable to parse export IP address");
            return NULL;
        }

        if (slash) {
            if (FAILED == (nce->ce_IPMask = inet_addr(slash))) {
                puts("Unable to parse export IP mask");
                return NULL;
            }
        } else
            nce->ce_IPMask = 0xffffffff;

        #undef FAILED
    }

    /* Set the UID/GID/permissions */
    nce->ce_UID = (uid ? *uid : -1);
    nce->ce_GID = (gid ? *gid : -1);

    if (perm) {
        /* We get it in 777 format, so we have to convert */
        u_int unix_mode = (*perm / 100) % 10;
        unix_mode <<= 3;
        unix_mode |= (*perm / 10) % 10;
        unix_mode <<= 3;
        unix_mode |= (*perm % 10);

        nce->ce_Protection = Amiga_prot(unix_mode);
    } else {
        nce->ce_Protection = -1;
    }

    nce->ce_ForceAll = forceall;

    /* Now summarise this line to standard output */
    if (global.verbose) {
        printf("Export: %s exported as %s ",
            nce->ce_Path, nce->ce_ExportAs);
        if (nce->ce_ReadWrite)
            printf("(readwrite) ");

        printf("IP=%s, ", Inet_NtoA(nce->ce_ExportIP));

        if (nce->ce_IPMask != ~0)
            printf("mask=%s", Inet_NtoA(nce->ce_IPMask));

        if (nce->ce_UID != -1)
            printf(" UID=%d", nce->ce_UID);
        if (nce->ce_GID != -1)
            printf(" GID=%d", nce->ce_GID);
        if (nce->ce_Protection != -1)
            printf(" Mode=%03o", NFS_mode(nce->ce_Protection));

        if (nce->ce_ForceAll) {
            puts(" FORCEALL");
        } else {
            puts(""); /* Make a newline */
        }
    }

    return nce;
}


void free_configentry(struct ConfigEntry *ce)
{
    if (ce) {
        FreeVecPooled(ce->ce_ExportAs);
        FreeVecPooled(ce->ce_Path);
        FreeVecPooled(ce);
    }
}

