/************************************************************************
 * config.c                                                             *
 *                                                                      *
 * Copyright (C) 2002 - 2020 Joshua Birnbaum <engineer@noorg.org>.      *
 * All Rights Reserved.                                                 *
 *                                                                      *
 * 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 SOFTWARE IS PROVIDED BY THE AUTHOR ``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 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.         *
 *                                                                      *
 * You should have received a copy of the GNU General Public License    *
 * along with this program; see the file COPYING. If not, write to the  *
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, *
 * MA 02111-1307, USA.                                                  *
 ************************************************************************/

/******************************************************************
 * Project ifchk -- Host based network interface promiscuous      *
 *                  mode detection and handling.                  *
 *                                                                *
 * Version:         1.1.2                                         *
 *                                                                *
 * -------------------------------------------------------------- *
 *                                                                *
 * config.c         ifchk configuration file parser.              *
 *                                                                *
 * Functions:     - Reads in each line of an ifchk configuration  *
 *                  file and prepares the keyword/value pairs     *
 *                  therein for subsequent processing.            *
 *                - Checks for redundant keyword/value pair ent-  *
 *                  ries in an ifchk configuration file.          *
 *                - Checks each keyword in an ifchk configuration *
 *                  file keyword/value pair for conformance to an *
 *                  established formal grammar.                   *
 *                - Checks each value in an ifchk configuration   *
 *                  file keyword/value pair for conformance to an *
 *                  established formal grammer.                   *
 *                - Sets ifchk configuration file keyword/value   *
 *                  pairs to define program runtime behavior.     *
 ******************************************************************/
 
/**********************
 * include directives *
 **********************/

#include "config.h"


/************************
 * global declarations. *
 ************************/

int errLogOpt = 0;              /* errLog redefinition flag. */
int promiscOpt = 0;             /* disableOnPromisc redefinition flag. */
int ifStatusOpt = 0;            /* ifStatusDumpFreq redefinition flag. */
int ifMetricOpt = 0;            /* ifMetricDumpFreq redefinition flag. */


/***************************************************************
 * loadConfig() - Performs initial processing of each keyword/ *
 *                value pair in an ifchk configuration file.   *
 *                                                             *
 * Parameters:    *confFile: character pointer to an ifchk     *
 *                           configuration file pathname.      *
 *                                                             *
 * Returns:       Linked list of ifchk configuration file key- *
 *                word/value pairs on success.                 *
 *                NULL on failure.                             *
 ***************************************************************/

struct conf *loadConfig( char *confFile )
{
    char buf[STRBUFSZ];             /* Config file line buffer. */
    int lineNum = 0;                /* Config file line number. */
    char *charPtr = NULL;           /* Ptr to current character in line. */
    char *lfPtr = NULL;             /* Ptr for trailing new line processing. */
    char *wsPtr = NULL;             /* Ptr for trailing white space processing. */
    int i = 0;                      /* For kwdBuf[]. */
    size_t lineLen = 0;             /* For strlen(). */
    char kwdBuf[STRBUFSZ];          /* Config file keyword buffer. */
    struct conf *head = NULL;       /* Linked list \ */
    struct conf *tail = NULL;       /* of config file \ */
    struct conf *new = NULL;        /* keyword/value \ */
    struct conf *cur = NULL;        /* pairs. */
    int setConf = 0;                /* For setConfig(). */
    char *redefPtr = NULL;          /* Ptr to redefined line. */
    char *cfFile = NULL;            /* Ptr to ifchk configuration file. */
    FILE *conffilefd = NULL;        /* For statFile(). */
    struct fsObj confObject;        /* For statFile(). */

    /*
     * Reset all statFile() file checks.
     */
    confObject.openRead = FALSE;
    confObject.procLen = FALSE;
    confObject.procOwner = FALSE;
    confObject.procMode = FALSE;
    confObject.regLen = FALSE;
    confObject.regMode = FALSE;

    memset( buf, '\000', sizeof( buf ) );
    memset( kwdBuf, '\000', sizeof( kwdBuf ) );

    /*
     * Prepare to securely open the ifchk config file using statFile().
     * Amongst other things, statFile() should check the file's size
     * (in bytes) and the number of blocks allocated to it.
     */
    confObject.openRead = TRUE;
    confObject.regLen = TRUE;
    confObject.regMode = TRUE;

    /*
     * Check ifchk config file attributes.
     * Open the file if it is deemed safe to do so.
     */
    cfFile = confFile;

    if( ( conffilefd = statFile( cfFile, confObject ) ) == NULL )
    {
        return (NULL);
     }

    /*
     * Loop through the ifchk config file, each time read-
     * ing in a line consisting of a keyword/value pair.
     */
    while( ( fgets( buf, sizeof( buf ), conffilefd ) ) != NULL )
    {
        /*
         * Set the line number for the current line.
         */
        lineNum++;

        /*
         * Does the current line length exceed 1024 characters?
         */
        lineLen = strlen( buf );

        if( buf[lineLen - 1] != '\n' )
        {
            errHandler( "ERROR: %s:%d: line length exceeds 1024 characters\n", cfFile, lineNum );
            return (NULL);
         }

        /*
         * Strip trailing white space and terminating
         * new line character for later processing.
         */
        if( ( lfPtr = strchr( buf, '\n' ) ) )
        {
            *lfPtr = '\000';

            wsPtr = lfPtr - 1;

            while( ( *wsPtr == ' ' ) || ( *wsPtr == '\t' ) )
            {
                memmove( wsPtr, wsPtr + 1, strlen( wsPtr ) );
                wsPtr--;
             }
         }

        /*
         * Set our character pointer to the beginning of the current line.
         */
        charPtr = buf;

        /*
         * Strip all initial white space characters.
         */
        while( ( *charPtr == ' ' ) || ( *charPtr == '\t' ) )
        {
            memmove( charPtr, charPtr + 1, strlen( charPtr ) );
         }

        /*
         * Ignore lines beginning with a comment character.
         */
        if( *charPtr == '#' )
        {
             memset( buf, '\000', sizeof( buf ) );

             continue;
         }

        /*
         * Ignore blank lines.
         */
        else if( *charPtr == '\000' )
        {
             memset( buf, '\000', sizeof( buf ) );

             continue;
         }

        /*
         * Check the current keyword to see if it was seen in a previously
         * validated keyword/value pair. This cannot be done before the
         * "commented out" check above (config.c:189:).
         */
        if( ( ( ( redefPtr = strstr( buf, "errLog" ) ) != NULL ) &&
                ( errLogOpt == 1 ) ) ||
            ( ( ( redefPtr = strstr( buf, "disableOnPromisc" ) ) != NULL ) &&
                ( promiscOpt == 1 ) ) ||
            ( ( ( redefPtr = strstr( buf, "ifStatusDumpFreq" ) ) != NULL ) &&
                ( ifStatusOpt == 1 ) ) || 
            ( ( ( redefPtr = strstr( buf, "ifMetricDumpFreq" ) ) != NULL ) &&
                ( ifMetricOpt == 1 ) ) )
        {

            /*
	         * We've seen this keyword before in the ifchk
	         * configuration file. Confirm & log this fact.
	         */
            if( chkRedef( redefPtr, lineNum, cfFile ) == -1 )
            {
                return (NULL); 
             }

            memset( buf, '\000', sizeof( buf ) );

            continue;
         }

        /*
         * Process the keyword in the current keyword/value pair. 
         */
        while( ( *charPtr != ' ' ) && ( *charPtr != '\t' ) )
        {
            if( *charPtr == '\0' )
            {
                errHandler( "ERROR: %s:%d: incomplete keyword/value pair\n", cfFile, lineNum );
                return (NULL);
             }

            else
            {
                kwdBuf[i] = *charPtr;
                charPtr++;
                i++;
             }
         }

        kwdBuf[STRBUFSZ - 1] = '\000';

        /*
         * Process the keyword in the current keyword/value pair. 
         */
        if( chkKeyword( kwdBuf, lineNum, cfFile ) == -1 )
        {
            return (NULL);
         }

        /*
         * Read past all space/tab characters between
         * the keyword and it's associated value.
         */
        while( ( *charPtr == ' ' ) || ( *charPtr == '\t' ) )
        {
            charPtr++;
         }

        /*
         * Process the value in the current keyword/value pair. 
         */
        if( chkValue( kwdBuf, charPtr, lineNum, cfFile ) == -1 )
        {
            return (NULL);
         }

        /*
         * Construct a linked list of legal keyword/value pairs.
         */
        if( ( new = malloc( sizeof( struct conf ) ) ) == NULL )
        {
            errHandler( "ERROR: malloc(): memory allocation failure: %s\n", strerror( errno ) );
            return (NULL);
         }

        /*
         * Create a new node.
         */
        memset( new, '\000', sizeof( new ) );
        strncpy( new -> keyword, kwdBuf, sizeof( new -> keyword ) );
        new -> keyword[STRBUFSZ - 1] = '\000';
        strncpy( new -> value, charPtr, sizeof( new -> value ) );
        new -> value[STRBUFSZ - 1] = '\000';
        new -> lineNum = lineNum;
        cur = new;

        /*
         * Append new node to list end.
         */
        if( head == NULL )
        {
            head = cur;
         }

        else
        {
            tail -> next = cur;
         }

        tail = cur;
        cur -> next = NULL;

        i = 0;

        memset( buf, '\000', sizeof( buf ) );
        memset( kwdBuf, '\000', sizeof( kwdBuf ) );
     }

    /*
     * We're processing an anomalous config file (e.g.,
     * one consisting entirely of two newline characters).
     */
    if( new == NULL )
    {
        errHandler( "ERROR: %s:%d: unrecognizable character sequence\n", cfFile, lineNum );
        return (NULL);
     }

    /*
     * Clean up and exit.
     */
    if( fclose( conffilefd ) != 0 )
    {
        errHandler( "ERROR: fclose(): could not close %s: %s\n", cfFile, strerror( errno ) );
        return (NULL);
     }

    setConf = setConfig( head );

    return ( head );
}


/*************************************************************
 * setConfig() - Sets ifchk configuration file keyword/value *
 *               pairs.                                      *
 *                                                           *
 * Parameters:   *conf: Pointer to a linked list of ifchk    *
 *                      configuration file parameters.       *
 *                                                           *
 * Returns:      0 on success.                               *
 *               -1 on failure.                              *
 *************************************************************/

int setConfig( struct conf *conf )
{
    int ifStatusSecs = 0;       /* Interface status scan frequency. */
    int ifMetricSecs = 0;       /* Interface metrics dump frequency. */
    int disablePromisc = 0;     /* Enable/disable promiscuous interface shutdown. */
    char *errFile = NULL;       /* Error log file pathname. */
    char paramBuf[STRBUFSZ];    /* Configuration parameter buffer. */
    struct conf *cur = NULL;    /* Sanity checked configuration keyword/value pairs. */
    struct fsObj cfObject;      /* For statFile(). */

    cur = conf;

    /*
     * Reset all statFile() file checks.
     */
    cfObject.openRead = FALSE;
    cfObject.procLen = FALSE;
    cfObject.procOwner = FALSE;
    cfObject.procMode = FALSE;
    cfObject.regLen = FALSE;
    cfObject.regMode = FALSE;

    /*
     * With the file parsed, we can now set our configuration parameters.
     */
    while( cur )
    {
        memset( paramBuf, '\000', sizeof( paramBuf ) );

        strncpy( paramBuf, cur -> keyword, sizeof( paramBuf ) );

        paramBuf[STRBUFSZ - 1] = '\000';

        /*
         * errLog - the ifchk error log pathname.
         */
        if( ( strncmp( paramBuf, "errLog", sizeof( paramBuf ) ) ) == 0 )
        {
            errFile = cur -> value;
            cfObject.regMode = TRUE;
         }

        /*
         * disableOnPromisc - promiscuous interface management policy.
         */
        else if( ( strncmp( paramBuf, "disableOnPromisc", sizeof( paramBuf ) ) ) == 0 )
        {
            disablePromisc = atoi( cur -> value );
         }

        /*
         * ifStatusDumpFreq - interface status dump frequency.
         */
        else if( ( strncmp( paramBuf, "ifStatusDumpFreq", sizeof( paramBuf ) ) ) == 0 )
        {
            ifStatusSecs = atoi( cur -> value );
         }

        /*
         * ifMetricDumpFreq - interface metric dump frequency.
         */
        else if( ( strncmp( paramBuf, "ifMetricDumpFreq", sizeof( paramBuf ) ) ) == 0 )
        {
            ifMetricSecs = atoi( cur -> value );
         }

        /*
         * Default: signals an error condition.
         */
        else
        {
            exit( EXIT_FAILURE );
         }

        cur = cur -> next;
     }

    return (0);
}


/*************************************************************
 * chkRedef() - Flags and reports on redundant keyword/value *
 *              pair entries in an ifchk configuration file. *
 *                                                           *
 * Parameters:  *redefPtr: character pointer to a redundant  *
 *                         keyword/value pair.               *
 *              lineNum:   integer describing line number    *
 *                         that redundancy occurs on.        *
 *              *confFile: character pointer to an ifchk     *
 *                         configuration file pathname.      *
 *                                                           *
 * Returns:     0 on success.                                *
 *              -1 on failure.                               *
 *************************************************************/

int chkRedef( char *redefPtr, int lineNum, char *confFile )
{
    char redefBuf[STRBUFSZ];    /* Config file line redefinition buffer. */
    int k = 0;                  /* For redefBuf[]. */

    memset( redefBuf, '\000', sizeof( redefBuf ) );

    /*
     * Process the redefined keyword.
     */
    while( ( *redefPtr != ' ' ) && ( *redefPtr != '\t' ) )
    {
        if( *redefPtr == '\0' )
        {
            errHandler( "ERROR: %s:%d: option redefinition check failed\n", confFile, lineNum );
            return (-1);
         }

        else
        {
            redefBuf[k] = *redefPtr;
            redefPtr++;
            k++;
          }
      }

    redefBuf[STRBUFSZ - 1] = '\000';

    /*
     * Identify and report that we found
     * a redefined keyword/value pair.
     */
    if( ( ( strncmp( redefBuf, "errLog", sizeof( redefBuf ) ) ) != 0 ) &&
        ( ( strncmp( redefBuf, "disableOnPromisc", sizeof( redefBuf ) ) ) != 0 ) &&
        ( ( strncmp( redefBuf, "ifStatusDumpFreq", sizeof( redefBuf ) ) ) != 0 ) &&
        ( ( strncmp( redefBuf, "ifMetricDumpFreq", sizeof( redefBuf ) ) ) != 0 ) )
    {
        errHandler( "ERROR: %s:%d: option redefinition check failed\n", confFile, lineNum );
        return (-1);
     }

    printf( "ifchk[%d]: NOTICE: %s:%d: option \"%s\" redefined - ignoring\n",
                                        getpid(), confFile, lineNum, redefBuf );

    if( ( writeLog( LOG_NOTICE, "NOTICE: %s:%d: option \"%s\" redefined - ignoring\n",
                                                  confFile, lineNum, redefBuf ) ) != 0 )
    {
        return (-1);
     }

    return (0);
}


/*****************************************************************
 * chkKeyword() - Checks each keyword in an ifchk configuration  *
 *                file keyword/value pair for conformance to an  *
 *                established formal grammar.                    *
 *                                                               *
 * Parameters:    *keyword:  character pointer to an ifchk conf- *
 *                           iguration file keyword.             *
 *                lineNum:   integer describing line number that *
 *                           unrecognized keyword occurs on.     *
 *                *confFile: character pointer to an ifchk conf- *
 *                           iguration file pathname.            *
 *                                                               *
 * Returns:       0 on success.                                  *
 *                -1 on failure.                                 *
 *****************************************************************/

int chkKeyword( char *keyword, int lineNum, char *confFile )
{
    char curKeyword[STRBUFSZ];  /* Config file keyword buffer. */

    memset( curKeyword, '\000', sizeof( curKeyword ) );
    strncpy( curKeyword, keyword, sizeof( curKeyword ) );

    curKeyword[STRBUFSZ - 1] = '\000';

    /*
     * Keyword check for "errLog" keyword/value pair.
     */
    if( ( strncmp( curKeyword, "errLog", sizeof( curKeyword ) ) ) == 0 )
    {
        return (0);
     }

    /*
     * Keyword check for "disableOnPromisc" keyword/value pair.
     */
    else if( ( strncmp( curKeyword, "disableOnPromisc", sizeof( curKeyword ) ) ) == 0 )
    {
        return (0);
     }

    /*
     * Keyword check for "ifStatusDumpFreq" keyword/value pair.
     */
    else if( ( strncmp( curKeyword, "ifStatusDumpFreq", sizeof( curKeyword ) ) ) == 0 )
    {
        return (0);
     }
 
    /*
     * Keyword check for "ifMetricDumpFreq" keyword/value pair.
     */
    else if( ( strncmp( curKeyword, "ifMetricDumpFreq", sizeof( curKeyword ) ) ) == 0 )
    {
        return (0);
     }

    /*
     * This is not a legal keyword.
     */
    else
    {
        errHandler( "ERROR: %s:%d: unrecognized keyword \"%s\"\n", confFile, lineNum, keyword );
        return (-1);
     }
}


/****************************************************************
 * chkValue()  - Checks each value in an ifchk configuration    *
 *               file keyword/value pair for conformance to an  *
 *               established formal grammar.                    *
 *                                                              *
 * Parameters:   *keyword:  pointer to an ifchk configuration   *
 *                          file keyword.                       *
 *               lineNum:   integer describing line number that *
 *                          unrecognized keyword occurs on.     *
 *               *confFile: character pointer to an ifchk conf- *
 *                          iguration file pathname.            *
 *                                                              *
 * Returns:      0 on success.                                  *
 *               -1 on failure.                                 *
 ****************************************************************/

int chkValue( char *keyword, char *value, int lineNum, char *confFile )
{
    char curKeyword[STRBUFSZ];          /* Config file keyword buffer. */
    char *curValue = NULL;              /* Config file value ptr. */
    char *charClass = "0123456789";     /* For strspn(). */
    long val = 0;                       /* For strtol(). */
    char *endPtr = NULL;                /* For strtol(). */
    const int minVal = 1;               /* Minimum integer config file value. */
    const int maxVal = 86400;           /* Maximum integer config file value. */

    memset( curKeyword, '\000', sizeof( curKeyword ) );
    strncpy( curKeyword, keyword, sizeof( curKeyword ) );

    curKeyword[STRBUFSZ - 1] = '\000';

    curValue = value;

    /*
     * Value check for "disableOnPromisc", "ifStatusDumpFreq",
     * "ifMetricDumpFreq" keyword/value pairs. Values should
     * consist of integers in the character class "0123456789".
     */
    if( ( strncmp( curKeyword, "errLog", sizeof( curKeyword ) ) ) != 0 )
    {
        /*
         * Does the current value consist _entirely_
         * of integers in the above character class?
         */
        if( strspn( curValue, charClass ) > 0 )
        {
            val = strtol( curValue, &endPtr, 10 );

            /*
             * The current value contains a non-numeric character
             * trailing a numeric character. (e.g., "5r", "5r5")
             */
            if( ( curValue == endPtr ) || ( *endPtr != '\000' ) )
            {
                errHandler( "ERROR: %s:%d: unrecognized value \"%s\"\n",
                                             confFile, lineNum, curValue );
                return (-1);
             }
         }

        /*
         * The current value contains a non-numeric character
         * preceeding a numeric character. (e.g., "r5", "rr5")
         */
        else
        {
            errHandler( "ERROR: %s:%d: unrecognized value \"%s\"\n",
                                         confFile, lineNum, curValue );
            return (-1);
         }
     }

    /*
     * Value check for "errLog" keyword/value pair.
     * errLog value should be an absolute pathname.
     */
    else
    {
        if( *curValue != '/' )
        {
            errHandler( "ERROR: %s:%d: value \"%s\" is not an absolute pathname\n",
                                                        confFile, lineNum, curValue );
            return (-1);
         }

	    /*
	     * Mark errLog keyword/value pair as a validated configu-
	     * ration file entry. Future occurances will be ignored.
	     */
        if( errLogOpt == 0 )
        {
            errLogOpt = 1;
         }

        return (0);
     }

    /*
     * Value check for "disableOnPromisc" keyword/value pair.
     * Value should either be '0' or '1'.
     * 0: don't disable promiscuous interfaces.
     * 1: disable promiscuous interfaces.
     */
    if( ( strncmp( curKeyword, "disableOnPromisc", sizeof( curKeyword ) ) ) == 0 )
    {
        if( ( ( *curValue == '0' ) && ( *( curValue + 1 ) != '\0' ) ) ||
            ( ( val != 0 ) && ( val != 1 ) ) )
        {
            errHandler( "ERROR: %s:%d: value of keyword \"%s\" is out of range\n",
                                                     confFile, lineNum, curKeyword );
            return (-1);
         }

        /*
         * Mark disableOnPromisc keyword/value pair as a validated
         * configration file entry. Future occurances will be ignored.
         */
        if( promiscOpt == 0 )
        {
            promiscOpt = 1;
         }

        return (0);
     }

    /*
     * Value check for "ifStatusDumpFreq" keyword/value pair.
     * Value should be between '1' and '86400' inclusive.
     * 1 - 86400: seconds between interface status dumps.
     */
    if( ( strncmp( curKeyword, "ifStatusDumpFreq", sizeof( curKeyword ) ) ) == 0 )
    {
        if( ( *curValue == '0' ) || ( val < minVal ) || ( val > maxVal ) )
        {
            errHandler( "ERROR: %s:%d: value of keyword \"%s\" is out of range\n",
                                                     confFile, lineNum, curKeyword );
            return (-1);
         }

        /*
         * Mark ifStatusDumpFreq keyword/value pair as a validated
         * configration file entry. Future occurances will be ignored.
         */
        if( ifStatusOpt == 0 )
        {
            ifStatusOpt = 1;
         }

        return (0);
     }

    /*
     * Value check for "ifMetricDumpFreq" keyword/value pair.
     * Value should be between '1' and '86400' inclusive.
     * 1 - 86400: seconds between interface metric dumps.
     */
    if( ( strncmp( curKeyword, "ifMetricDumpFreq", sizeof( curKeyword ) ) ) == 0 )
    {
        if( ( *curValue == '0' ) || ( val < minVal ) || ( val > maxVal ) )
        {
            errHandler( "ERROR: %s:%d: value of keyword \"%s\" is out of range\n",
	                                             confFile, lineNum, curKeyword );
            return (-1);
         }

        /*
         * Mark ifMetricDumpFreq keyword/value pair as a validated
         * configration file entry. Future occurances will be ignored.
         */
        if( ifMetricOpt == 0 )
        {
            ifMetricOpt = 1;
         }

        return (0);
     }
}
