/*      config.c  - config file parser by Peter Gutmann
        Parses config file for PGP

        Modified 24 Jun 92 - HAJK
        Misc fixes for VAX C restrictions.

*/

#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "usuals.h"
#include "fileio.h"
#include "pgp.h"
#include "config.h"
#include "charset.h"

static int lookup( char *key, int keyLength, char *keyWords[], int range );
static int extractToken( char *buffer, int *endIndex, int *length );
static int getaString( char *buffer, int *endIndex );
static int getAssignment( char *buffer, int *endIndex, INPUT_TYPE settingType );
static void processAssignment( int intrinsicIndex );

/* The external config variables we can set here are referenced in pgp.h */

/* Return values */

#define ERROR   -1
#define OK              0

/* The types of error we check for */

enum { NO_ERROR, ILLEGAL_CHAR_ERROR, LINELENGTH_ERROR };

#define CPM_EOF                 0x1A    /* ^Z = CPM EOF char */

#define MAX_ERRORS              3               /* Max.no.errors before we give up */

#define LINEBUF_SIZE    100             /* Size of input buffer */

static int line;                                /* The line on which an error occurred */
static int errCount;                    /* Total error count */
static boolean hasError;                /* Whether this line has an error in it */

/* The settings parsed out by getAssignment() */

static char str[ LINEBUF_SIZE ];
static int value;
static boolean flag;
static char *errtag;    /* prefix for printing error messages */
static char optstr[100];        /* option being processed */

/* A .CFG file roughly follows the format used in the world-famous HPACK
   archiver and is as follows:

        - Leading spaces/tabs (whitespace) are ignored.

        - Lines with a '#' as the first non-whitespace character are treated as
          comment lines.

        - All other lines are treated as config options for the program.

        - Lines may be terminated by either linefeeds, carriage returns, or
          carriage return/linefeed pairs (the latter being the DOS default
          method of storing text files).

        - Config options have the form:

          <option> '=' <setting>

          where <setting> may be 'on', 'off', a numeric value, or a string
          value.

        - If strings have spaces or the '#' character inside them they must be
          surrounded by quote marks '"' */

/* Intrinsic variables */

#define NO_INTRINSICS           (sizeof(intrinsics) / sizeof(intrinsics[0]))

enum
{       ARMOR, COMPRESS, SHOWPASS, KEEPBINARY, LANGUAGE,
        MYNAME, TEXTMODE, TMP, TZFIX, VERBOSE, BAKRING,
        ARMORLINES, COMPLETES_NEEDED, MARGINALS_NEEDED, PAGER,
        CERT_DEPTH, CHARSET, CLEAR, SELF_ENCRYPT,
        INTERACTIVE, PKCS_COMPAT, ARMOR_VERSION, VERSION_BYTE,
        SECUREWIPE,
        /* options below this line can only be used as command line
         * "long" options */
#define CONFIG_INTRINSICS       BATCHMODE
        BATCHMODE, FORCE
};

static char *intrinsics[] =
{       "ARMOR", "COMPRESS", "SHOWPASS", "KEEPBINARY", "LANGUAGE",
        "MYNAME", "TEXTMODE", "TMP", "TZFIX", "VERBOSE", "BAKRING",
        "ARMORLINES", "COMPLETES_NEEDED", "MARGINALS_NEEDED", "PAGER",
        "CERT_DEPTH", "CHARSET", "CLEARSIG", "ENCRYPTTOSELF",
        "INTERACTIVE", "PKCS_COMPAT", "ARMOR_VERSION", "VERSION_BYTE",
        "SECUREWIPE",
        /* command line only */
        "BATCHMODE", "FORCE",
};

static INPUT_TYPE intrinsicType[] =
{       BOOL, BOOL, BOOL, BOOL, STRING,
        STRING, BOOL, STRING, NUMERIC, NUMERIC, STRING,
        NUMERIC, NUMERIC, NUMERIC, STRING,
        NUMERIC, STRING, BOOL, BOOL,
        BOOL, NUMERIC, STRING, NUMERIC,
        BOOL,
        /* command line only */
        BOOL, BOOL,
};

/* Possible settings for variables */

#define NO_SETTINGS                     2

static char *settings[] = { "OFF", "ON" };


/* Search a list of keywords for a match */

static int lookup( char *key, int keyLength, char *keyWords[], int range )
{
        int indx, pos = 0, matches = 0;

        strncpy(optstr, key, keyLength);
        optstr[keyLength] = '\0';
        /* Make the search case insensitive */
        for( indx = 0; indx < keyLength; indx++ )
                key[ indx ] = to_upper( key[ indx ] );

        for( indx = 0; indx < range; indx++ )
                if( !strncmp( key, keyWords[ indx ], keyLength ) )
                {       if (strlen(keyWords[indx]) == keyLength)
                                return indx;    /* exact match */
                        pos = indx;
                        ++matches;
                }

        switch (matches)
        {       case 0: fprintf(stderr, "%s: unknown keyword: \"%s\"\n", errtag, optstr); break;
                case 1: return pos;
                default: fprintf(stderr, "%s: \"%s\" is ambiguous\n", errtag, optstr);
        }
        return ERROR;
}

/* Extract a token from a buffer */
static int extractToken( char *buffer, int *endIndex, int *length )
{
        int indx = 0, tokenStart;
        char ch;

        /* Skip whitespace */
        for( ch = buffer[ indx ]; ch && ( ch == ' ' || ch == '\t' ); ch = buffer[ indx ] )
                indx++;
        tokenStart = indx;

        /* Find end of setting */
        while( indx < LINEBUF_SIZE && ( ch = buffer[ indx ] ) != '\0' && ch != ' ' && ch != '\t' )
                indx++;
        *endIndex += indx;
        *length = indx - tokenStart;

        /* Return start position of token in buffer */
        return( tokenStart );
}


/* Get a string constant */
static int getaString( char *buffer, int *endIndex )
        {
        boolean noQuote = FALSE;
        int stringIndex = 0, bufIndex = 1;
        char ch = *buffer;

        /* Skip whitespace */
        while( ch && ( ch == ' ' || ch == '\t' ) )
                ch = buffer[ bufIndex++ ];

        /* Check for non-string */
        if( ch != '\"' )
                {
                *endIndex += bufIndex;

                /* Check for special case of null string */
                if( !ch )
                        {
                        *str = '\0';
                        return( OK );
                        }

                /* Use nasty non-rigorous string format */
                noQuote = TRUE;
                }

        /* Get first char of string */
        if( !noQuote )
                ch = buffer[ bufIndex++ ];

        /* Get string into string */
        while( ch && ch != '\"' )
                {
                /* Exit on '#' if using non-rigorous format */
                if( noQuote && ch == '#' )
                        break;

                str[ stringIndex++ ] = ch;
                ch = buffer[ bufIndex++ ];
                }

        /* If using the non-rigorous format, stomp trailing spaces */
        if( noQuote )
                while( stringIndex > 0 && str[ stringIndex - 1 ] == ' ' )
                        stringIndex--;

        str[ stringIndex++ ] = '\0';
        *endIndex += bufIndex;

        /* Check for missing string terminator */
        if( ch != '\"' && !noQuote )
                {
                if (line)
                        fprintf(stderr, "%s: unterminated string in line %d\n", errtag, line );
                else
                        fprintf(stderr, "unterminated string: '\"%s'\n", str );
                hasError = TRUE;
                errCount++;
                return( ERROR );
                }

        return( OK );
        }

/* Get an assignment to an intrinsic */
static int getAssignment( char *buffer, int *endIndex, INPUT_TYPE settingType )
{
        int settingIndex = 0, length;

        buffer += extractToken( buffer, endIndex, &length );

        /* Check for an assignment operator */
        if ( *buffer != '=' )
        {
                if (line)
                        fprintf(stderr, "%s: expected '=' in line %d\n", errtag, line );
                else
                        fprintf(stderr, "%s: expected '=' after \"%s\"\n", errtag, optstr);
                hasError = TRUE;
                errCount++;
                return( ERROR );
        }
        buffer++;               /* Skip '=' */

        buffer += extractToken( buffer, endIndex, &length );

        switch( settingType )
        {
                case BOOL:
                        /* Check for known intrinsic - really more general than just
                           checking for TRUE or FALSE */
                        if( ( settingIndex = lookup( buffer, length, settings, NO_SETTINGS ) ) == ERROR )
                        {
                                hasError = TRUE;
                                errCount++;
                                return( ERROR );
                        }

                        flag = ( settingIndex == 0 ) ? FALSE : TRUE;
                        break;

                case STRING:
                        /* Get a string */
                        getaString( buffer, &length );
                        break;

                case NUMERIC:
                        /* Get numeric input.  Error checking is a pain since atoi()
                                has no real equivalent of NAN */
                        value = atoi( buffer );
                        break;
        }

        return( settingIndex );
}

/* Process an assignment */

static void processAssignment( int intrinsicIndex )
        {
        if( !hasError )
                switch( intrinsicIndex )
                        {
                        case ARMOR:
                                emit_radix_64 = flag;
                                break;

                        case COMPRESS:
                                compress_enabled = flag;
                                break;

                        case SHOWPASS:
                                showpass = flag;
                                break;

                        case KEEPBINARY:
                                keepctx = flag;
                                break;

                        case LANGUAGE:
                                strncpy(language, str, 15);
                                break;

                        case BAKRING:
                                strcpy(floppyring, str);
                                break;

                        case MYNAME:
                                strcpy(my_name, str);
                                break;

                        case TEXTMODE:
                                if( flag )
                                        literal_mode = MODE_TEXT;
                                else
                                        literal_mode = MODE_BINARY;
                                break;

                        case TMP:
                                /* directory pathname to store temp files */
                                settmpdir(str);
                                break;

                        case TZFIX:
                                /* How many hours to add to time() to get GMT. */
                                /* Compute seconds from hours to shift to GMT: */
                                timeshift = 3600L * (long) value;
                                break;

                        case VERBOSE:
                                switch (value)
                                {
                                        case 0: quietmode = TRUE; verbose = FALSE; break;
                                        case 1: quietmode = FALSE; verbose = FALSE; break;
                                        case 2: quietmode = FALSE; verbose = TRUE; break;
                                        default: quietmode = FALSE; verbose = FALSE;
                                }
                                break;

                        case ARMORLINES:
                                pem_lines = value;
                                break;

                        case MARGINALS_NEEDED:
                                marg_min = value;
                                if (marg_min < 1)
                                        marg_min = 1;
                                break;

                        case COMPLETES_NEEDED:
                                compl_min = value;
                                if (compl_min < 1)
                                        compl_min = 1;
                                if (compl_min > 4)
                                        compl_min = 4;
                                break;

                        case CERT_DEPTH:
                                max_cert_depth = value;
                                if (max_cert_depth < 0)
                                        max_cert_depth = 0;
                                if (max_cert_depth > 8)
                                        max_cert_depth = 8;
                                break;

                        case PAGER:
                                strcpy(pager, str);
                                break;

                        case CHARSET:
                                strcpy(charset, str);
                                break;

                        case CLEAR:
                                clear_signatures = flag;
                                break;

                        case SELF_ENCRYPT:
                                encrypt_to_self = flag;
                                break;

                        case INTERACTIVE:
                                interactive_add = flag;
                                break;

                        case ARMOR_VERSION:
                                strncpy(armor_version, str,
                                        MAX_VERSION_LENGTH);
                                armor_version[MAX_VERSION_LENGTH-1] = '\0';
                                break;

                        case VERSION_BYTE:
                                version_byte = value;
                                if (version_byte < VERSION_BYTE_MIN)
                                        version_byte = VERSION_BYTE_MIN;
                                if (version_byte > VERSION_BYTE_MAX)
                                        version_byte = VERSION_BYTE_MAX;
                                break;

                        case SECUREWIPE:
                                secure_wipe = flag;
                                break;

                        case BATCHMODE: batchmode = flag; break;
                        case FORCE: force_flag = flag; break;
                        case PKCS_COMPAT: pkcs_compat = value; break;
                        }
        }

/* Process an option on a line by itself.  This expects options which are
   taken from the command-line, and is less finicky about errors than the
   config-file version */

int processConfigLine( char *option )
        {
        int indx, intrinsicIndex;
        char ch;

        /* Give it a pseudo-linenumber of 0 */
        line = 0;

        errtag = "pgp";
        errCount = 0;
        for( indx = 0;
                 indx < LINEBUF_SIZE && ( ch = option[ indx ] ) != '\0' &&
                                ch != ' ' && ch != '\t' && ch != '=';
                 indx++ );
        if( ( intrinsicIndex = lookup( ( char * ) option, indx, intrinsics, NO_INTRINSICS ) ) == ERROR )
                return -1;
        if (option[indx] == '\0' && intrinsicType[intrinsicIndex] == BOOL)
        {       /* boolean option, no '=' means TRUE */
                flag = TRUE;
                processAssignment(intrinsicIndex);
        }
        else /* Get the value to set to, either as a string, a
                    numeric value, or a boolean flag */
                if (getAssignment( ( char * ) option + indx, &indx, intrinsicType[ intrinsicIndex ] ) != ERROR)
                        processAssignment( intrinsicIndex );
        return(errCount ? -1 : 0);
}

/* Process a config file */
int processConfigFile( char *configFileName )
{
        FILE *configFilePtr;
        int ch = 0, theCh;
        int errType, errPos = 0, lineBufCount, intrinsicIndex;
        int indx;
        char inBuffer[ LINEBUF_SIZE ];

        line = 1;
        errCount = 0;
        errtag = "config.txt";

        if( ( configFilePtr = fopen( configFileName, FOPRTXT ) ) == NULL )
        {
                fprintf(stderr, "Cannot open configuration file %s\n", configFileName );
                return( OK );   /* treat like empty config file */
        }

        /* Process each line in the configFile */
        while( ch != EOF )
        {
                /* Skip whitespace */
                while( ( ( ch = getc( configFilePtr ) ) == ' ' || ch == '\t' ) && ch != EOF )
                ;

                /* Get a line into the inBuffer */
                hasError = FALSE;
                lineBufCount = 0;
                errType = NO_ERROR;
                while( ch != '\r' && ch != '\n' && ch != CPM_EOF && ch != EOF )
                {
                        /* Check for an illegal char in the data */
                        if( ( ch < ' ' || ch > '~' ) && ch != '\r' && ch != '\n' &&
                                ch != ' ' && ch != '\t' && ch != CPM_EOF && ch != EOF )
                        {
                                if( errType == NO_ERROR )
                                        /* Save position of first illegal char */
                                        errPos = lineBufCount;
                                errType = ILLEGAL_CHAR_ERROR;
                        }

                        /* Make sure the path is of the correct length.  Note that the
                           code is ordered so that a LINELENGTH_ERROR takes precedence over
                           an ILLEGAL_CHAR_ERROR */
                        if( lineBufCount > LINEBUF_SIZE )
                                errType = LINELENGTH_ERROR;
                        else
                                inBuffer[ lineBufCount++ ] = ch;

                        if( ( ch = getc( configFilePtr ) ) == '#' )
                        {
                                /* Skip comment section and trailing whitespace */
                                while( ch != '\r' && ch != '\n' && ch != CPM_EOF && ch != EOF )
                                        ch = getc( configFilePtr );
                                break;
                        }
                }

                /* Skip trailing whitespace and add der terminador */
                while(lineBufCount && (( theCh = inBuffer[ lineBufCount - 1 ] ) == ' ' || theCh == '\t' ))
                        lineBufCount--;

                inBuffer[ lineBufCount ] = '\0';

                /* Process the line unless its a blank or comment line */
                if( lineBufCount && *inBuffer != '#' )
                {
                        switch( errType )
                        {
                                case LINELENGTH_ERROR:
                                        fprintf(stderr, "%s: line '%.30s...' too long\n", errtag, inBuffer );
                                        errCount++;
                                        break;

                                case ILLEGAL_CHAR_ERROR:
                                        fprintf(stderr, "> %s\n  ", inBuffer );
                                        fprintf(stderr, "%*s^\n", errPos, "");
                                        fprintf(stderr, "%s: bad character in command on line %d\n", errtag, line );
                                        errCount++;
                                        break;

                                default:
                                        for( indx = 0;
                                                 indx < LINEBUF_SIZE && ( ch = inBuffer[ indx ] ) != '\0'
                                                                && ch != ' ' && ch != '\t' && ch != '=';
                                                 indx++ )
                                                        ;
                                        if( ( intrinsicIndex = lookup( inBuffer, indx, intrinsics, CONFIG_INTRINSICS ) ) == ERROR )
                                        {
                                                errCount++;
                                        }
                                        else
                                        {
                                                /* Get the value to set to, either as a string, a
                                                   numeric value, or a boolean flag */
                                                getAssignment( inBuffer + indx, &indx, intrinsicType[ intrinsicIndex ] );
                                                processAssignment( intrinsicIndex );
                                        }
                        }
                }

                /* Handle special-case of ^Z if configFile came off an MSDOS system */
                if( ch == CPM_EOF )
                        ch = EOF;

                /* Exit if there are too many errors */
                if( errCount >= MAX_ERRORS )
                        break;

                line++;
        }

        fclose( configFilePtr );

        /* Exit if there were errors */
        if( errCount )
        {
                fprintf(stderr, "%s: %s%d error(s) detected\n\n", configFileName, ( errCount >= MAX_ERRORS ) ?
                                "Maximum level of " : "", errCount );
                return( ERROR );
        }

        return( OK );
}
