
/*****************************************************************************
**
**  Copyright (C) 2001  - the shmoo group -
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  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.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
*****************************************************************************/

/*****************************************************************************
**
**    The Shmoo Group (TSG)
**
**    File:      configuration.c
**    Author:    Brian Wotring
**
**    Date:      June 22, 2001.
**    Project:   osiris
**
*****************************************************************************/

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

#include <sys/types.h>
#include <sys/stat.h>

#include <ctype.h>
#include <string.h>

#include <openssl/md5.h>

#include "utilities.h"
#include "status.h"

#include "list.h"
#include "string_list.h"

#include "scan_record.h"

#include "filter.h"
#include "configuration.h"


/*****************************************************************************
**
**    Function: osi_config_new
**
**    Purpose:  create a new config module with the specified
**              file name as the name of the config file.  here
**              we create the global block and add it to the
**              block list.
**
******************************************************************************/

configuration * osi_config_new()
{
    configuration *new_config;
    block *global_block;

    new_config = osi_malloc( sizeof( configuration ) );
    memset( new_config, 0, sizeof( configuration ) );

    global_block              = new_block();

    new_config->data          = string_list_new();
    new_config->state	      = CONFIG_STATE_NULL;

    new_config->blocks        = list_new();
    new_config->block_names   = string_list_new();

    new_config->warning_count = 0;
    new_config->error_count   = 0;

    /* we create a default global block here. */

    list_add( new_config->blocks, global_block );

    return new_config;
}


/*****************************************************************************
**
**    Function: osi_config_destroy
**
**    Purpose:  destroy the config file data, and destroy the
**              list of blocks.
**
******************************************************************************/

void osi_config_destroy( configuration *cfg )
{
    if( cfg == NULL )
    {
        return;
    }

    /* destroy config file data. */

    string_list_destroy( cfg->data );

    /* destroy the block names list. */

    string_list_destroy( cfg->block_names );

    /* get first block, then go through and */
    /* destroy all blocks, then destroy the */
    /* block list itself.                   */

    destroy_block_list( cfg->blocks );

    /* now destroy the config structure itself. */

    osi_free( cfg );
}

/*****************************************************************************
**
**    Function: osi_config_receive_first
**
**    Purpose:  receive the first portion of data for this pushed config.
**              Add data to the data store to set state to receiving.
**
*****************************************************************************/

osi_bool osi_config_receive_first( configuration *cfg, unsigned char *data,
                                   int data_length )
{
    if( cfg != NULL )
    {
        /* destroy data if not destroyed already, then create a new list */
        /* and set state to receiving.  then add incoming data.          */

        string_list_destroy( cfg->data );
        cfg->data = string_list_new();

        cfg->state = CONFIG_STATE_RECEIVING;
        return osi_config_receive( cfg, data, data_length );
    }

    return FALSE;
}

/******************************************************************************
**
**    Function: osi_config_receive
**
**    Purpose:  receive a portion of data for this pushed config.  Add data
**		        to the data store.
**
*****************************************************************************/

osi_bool osi_config_receive( configuration *cfg, unsigned char *data,
                             int data_length )
{
    if( ( cfg != NULL ) && ( data != NULL ) && ( data_length > 0 ) )
    {
        data = trim_white_space( data );
        string_list_add_item( cfg->data, data );

        return TRUE;
    }

    return FALSE;
}

/*****************************************************************************
**
**    Function: osi_config_receive_last
**
**    Purpose:  receive the last portion of data for this pushed config.
**              Add data to the data store and perform evaluation.  Set
**              state accordingly.
**
******************************************************************************/

osi_bool osi_config_receive_last( configuration *cfg, unsigned char *data,
                                  int data_length )
{
    osi_bool result = FALSE;

    if( cfg != NULL )
    {
        osi_config_receive( cfg, data, data_length );

        if( osi_config_verify( cfg ) )
        {
            result = osi_config_parse( cfg );

            /* if we have a valid config, generate the id. */

            if( result )
            {
                osi_config_generate_id( cfg );
            }
        }
    }

    return result;
}

/*****************************************************************************
**
**    Function: osi_config_verify
**
**    Purpose:  open the config file, read in the data, then
**              verify both the syntax and the semantis.
**
******************************************************************************/

osi_bool osi_config_verify( configuration *cfg )
{
    if( osi_config_verify_syntax( cfg ) == FALSE )
    {
        cfg->state = CONFIG_STATE_INVALID;
        return FALSE;
    }

    if( osi_config_verify_semantics( cfg ) == FALSE )
    {
        cfg->state = CONFIG_STATE_INVALID;
        return FALSE;
    }

    cfg->state = CONFIG_STATE_VALID;
    return TRUE;
}

/****************************************************************************
**
**    Function: osi_config_get_global_block
**
**    Purpose:  peer into the block list structure and return the
**              first block in the list, which is always the global one.
**
*****************************************************************************/

block * osi_config_get_global_block( configuration *cfg )
{
    if( ( cfg != NULL ) && ( cfg->blocks != NULL ) &&
        ( cfg->blocks->next != NULL ) )
    {
        return (block *)( cfg->blocks->next->data );
    }

    return NULL;
}

/*****************************************************************************
**
**    Function: osi_config_get_first_block
**
**    Purpose:  peer into the block list structure and return the
**              first block after the global block.
**
******************************************************************************/

block * osi_config_get_first_block( configuration *cfg )
{
    block *global = osi_config_get_global_block( cfg );

    if( global != NULL )
    {
        return osi_config_get_next_block( cfg, global );
    }

    return NULL;
}

/*****************************************************************************
**
**    Function: osi_config_get_next_block
**
**    Purpose:  peer into the block list structure and return the
**              first block after the specified block.
**
******************************************************************************/

block * osi_config_get_next_block( configuration *cfg, block *current_block )
{
    osi_list head;

    if( ( cfg != NULL ) && ( current_block != NULL ) )
    {
        head = list_get_first_node( cfg->blocks );

        while( head )
        {
            block *b = (block *)head->data;

            if( ( b == current_block ) && ( head->next != NULL ) )
            {
                return ( (block *)head->next->data );
            }

            head = head->next;
        }
    }

    return NULL;
}

/*****************************************************************************
**
**    Function: new_block_from_block
**
**    Purpose:  allocate for a new block, but inherit the attributes
**              from the parent, everything except the action list.
**
*****************************************************************************/

block *new_block_from_block( block *parent_block )
{
    /* create a blank config block. */

    block *config_block = new_block();

    /* now copy all the options from the parent block. */

    config_block->recursive    = parent_block->recursive;
    config_block->follow_links = parent_block->follow_links;
    config_block->hash         = parent_block->hash;

    /* we don't copy the actions....*/

    return config_block;
}

/*****************************************************************************
**
**    Function: new_block
**
**    Purpose:  default allocation for a block, initialized to
**              nothing.
**
*****************************************************************************/

block *new_block()
{
    struct block *config_block = osi_malloc( sizeof( struct block ) );
    memset( config_block->directory, 0, sizeof( config_block->directory ) );

    /* new block options according to our specified defaults. */

    config_block->recursive     = FALSE;
    config_block->follow_links  = FALSE;

    config_block->hash          = HASH_DEFAULT;

    config_block->actions       = list_new();
    config_block->no_entry_list = string_list_new();

    return config_block;
}


/*****************************************************************************
**
**    Function: new_action
**
**    Purpose:  default allocation for an action, initialize to nothing.
**
*****************************************************************************/

action *new_action()
{
    struct action *config_action = osi_malloc( sizeof( struct action ) );

    memset( config_action, 0, sizeof( struct action ) );
    memset( config_action->action_filter.argument, 0,
            sizeof( config_action->action_filter.argument ) );
    config_action->type = 0;

    return config_action;
}


/*****************************************************************************
**
**    Function: destroy_block
**
**    Purpose:  free the specified block, destroy the dynamic data
**              found in a block include the noentry and action lists.
**
*****************************************************************************/

void destroy_block( block *config_block )
{
    if( config_block != NULL )
    {
        destroy_action_list( config_block->actions );
        string_list_destroy( config_block->no_entry_list );
        osi_free( config_block );
    }
}


/*****************************************************************************
**
**    Function: destroy_action
**
**    Purpose:  free the specified action.
**
****************************************************************************/

void destroy_action( action *config_action )
{
    if( config_action != NULL )
    {
        osi_free( config_action );
    }
}


/*****************************************************************************
**
**    Function: osi_config_open_and_read
**
**    Purpose:  frees all of the blocks in the list, then frees
**              the list itself.
**
*****************************************************************************/

void destroy_block_list( osi_list block_list )
{
    block *temp_block;
    node *temp_node;

    if( block_list == NULL )
    {
        return;
    }

    /* go to first data node. */

    temp_node = block_list->next;

    /* while there are data nodes, get each */
    /* block and destroy it.                */

    while( temp_node )
    {
        temp_block = temp_node->data;
        destroy_block( temp_block );
        temp_node = temp_node->next;
    }

    list_destroy( block_list );
}


/*****************************************************************************
**
**    Function: destroy_action_list
**
**    Purpose:  frees all of the actions in the list, then frees
**              the list itself.
**
*****************************************************************************/

void destroy_action_list( osi_list action_list )
{
    action *temp_action;
    node *temp_node;

    if( action_list == NULL )
    {
        return;
    }

    /* go to first data node. */

    temp_node = action_list->next;

    /* while there are data nodes, get each */
    /* block and destroy it.                */

    while( temp_node )
    {
        temp_action = temp_node->data;
        destroy_action( temp_action );
        temp_node = temp_node->next;
    }

    list_destroy( action_list );
}

/*****************************************************************************
**
**    Function: get_keyword_type
**
**    Purpose:  determine the type of keyword this is based upon
**              our lists.
**
*****************************************************************************/

int get_keyword_type( char *string )
{
    char word[MAX_LINE_LENGTH] = "";
    int index;

    if( string == NULL )
    {
        return CONFIG_KEYWORD_NULL;
    }

    get_token( string, word, sizeof( word ) );
    lowercase_string( word );

    for( index = 0; config_keywords[index].word != NULL; index++ )
    {
        if( strncmp( word, config_keywords[index].word,
            strlen( config_keywords[index].word ) ) == 0 )
        {
            return ( config_keywords[index].type );
        }
    }

    return CONFIG_KEYWORD_UNKNOWN;
}


/*****************************************************************************
**
**    Function: verify_multiple_values
**
**    Purpose:  determine if there are any invalid items in the
**              list based upon the list values given.
**
*****************************************************************************/

char * verify_multiple_values( char *values, char *valid_values[] )
{
    char *token = NULL;

    int index   = 0;
    int valid   = 0;

    char temp_values[MAX_LINE_LENGTH] = "";

    if( valid_values == NULL || values == NULL )
    {
        return NULL;
    }

    /* strtok will ruin the string we were given */
    /* so we copy the values data.               */

    osi_strlcpy( temp_values, values, ( strlen( values ) + 1 ) );

    /* only one value, don't seperate. */

    token = strtok( temp_values, "," );
    token = trim_white_space( token );

    while( token )
    {
        valid = 0;
        lowercase_string( token );

        for( index = 0; valid_values[index] != NULL; index++ )
        {
            if( strcmp( token, valid_values[index] ) == 0 )
            {
                valid = 1;
            }
        }

        if( !valid )
        {
            return token;
        }

        token = strtok( NULL, "," );
        token = trim_white_space( token );
    }

    return NULL;
}


/*****************************************************************************
**
**    Function: verify_single_value
**
**    Purpose:  determine if the value is invalid based upon the
**              valid values list given.
**
******************************************************************************/

char * verify_single_value( char *value, char *valid_values[] )
{
    int index;

    if( valid_values == NULL || value == NULL )
    {
        return NULL;
    }

    lowercase_string( value );

    for( index = 0; valid_values[index] != NULL; index++ )
    {
        if( strcmp( value, valid_values[index] ) == 0 )
        {
            return NULL;
        }
    }

    return value;
}


/*****************************************************************************
**
**    Function: print_config_error
**
**    Purpose:  display pretty error information includeing
**              line number nad error message.
**
*****************************************************************************/

void print_config_error( int line_number, char *line, char *error, ... )
{
    va_list ap;

    fprintf( stderr, "\nline %d: ", line_number );
    fprintf( stderr, "==> %s\n", line );

    va_start( ap, error );
    vfprintf( stderr, error, ap );
    va_end( ap );

    fprintf( stderr, "\n\n" );
}


/*****************************************************************************
**
**    Function: get_name_value
**
**    Purpose:  copy as much of the remainder of this line into the name
**              for this config, it's a pretty name, not required.
**
*****************************************************************************/

int get_name_value( configuration *cfg, char *line )
{
    if( ( line != NULL ) && ( cfg != NULL ) )
    {
        osi_strlcpy( cfg->name, line, sizeof( cfg->name ) );
        return TRUE;
    }

    return FALSE;
}

/*****************************************************************************
**
**    Function: get_boolean_value
**
**    Purpose:  examine keyword based upon our list of valid booleans
**              and return TRUE or FALSE appropriatly.
**
*****************************************************************************/

int get_boolean_value( char *keyword, osi_bool *value )
{
    int index;

    if( keyword == NULL || value == NULL )
    {
        return 0;
    }

    lowercase_string( keyword );

    for( index = 0; boolean_keywords[index].word != NULL; index++ )
    {
        if( strcmp( keyword, boolean_keywords[index].word ) == 0 )
        {
            *value = boolean_keywords[index].type;
            return 1;
        }
    }

    return 0;
}


/****************************************************************************
**
**    Function: get_keyword_type
**
**    Purpose:  determine the hash type of the string given, e.g. md5
**              and return the constant value associated with that algorithm.
**
******************************************************************************/

int get_hash_value( char *keyword, unsigned int *value )
{
    int index;

    if( keyword == NULL || value == NULL )
    {
        return 0;
    }

    lowercase_string( keyword );

    for( index = 0; hash_keywords[index].word != NULL; index++ )
    {
        if( strcmp( keyword, hash_keywords[index].word ) == 0 )
        {
            *value = hash_keywords[index].type;
            return 1;
        }
    }

    return 0;
}


/******************************************************************************
**
**    Function: get_hash_name_from_type
**
**    Purpose:  return string pretty name of checksum algorithm, based upon
**		the constant type given.
**
******************************************************************************/

char * get_hash_name_from_type( int type )
{
    int index;
    char *name = NULL;
    
    for( index = 0; hash_keywords[index].word != NULL; index++ )
    {
        if( type == hash_keywords[index].type )
        {
            name = hash_keywords[index].word;
            return name;
        }
    }
    
    return name;
}


/******************************************************************************
**
**    Function: get_block_with_name
**
**    Purpose:  get the block in the configuration with the specified
**              name.
**
******************************************************************************/

block * get_block_with_name( configuration *cfg, char *name )
{
    osi_list head;

    if( cfg != NULL && name != NULL )
    {
        head = list_get_first_node( cfg->blocks );

        while( head )
        {
            block *b = (block *)head->data;

            if( b && ( strcmp( b->directory, name ) == 0 ) )
            {
                return ( (block *)head->data );
            }

            head = head->next;
        }
    }

    return NULL;
}

/******************************************************************************
**
**    Function: get_action_from_block_with_filter
**
**    Purpose:  get the action in the block with the specified
**              filter type.
**
******************************************************************************/

action * get_action_from_block_with_filter( block *config_block,
                                            int filter_type,
                                            char *filter_argument )
{
    osi_list head;

    if( config_block != NULL )
    {
        head = list_get_first_node( config_block->actions );

        while( head )
        {
            action *a = (action *)head->data;

            if( a && ( a->action_filter.type == filter_type ) )
            {
                /* filter argument is null, so there shouldn't be */
                /* multiple, this is it.                          */

                if( filter_argument == NULL )
                {
                    return a;
                }

                /* otherwise, there is an argument and we must make   */
                /* sure we find the correct action inside this block. */

                else if( strcmp( filter_argument,
                                 a->action_filter.argument ) == 0 )
                {
                    return a;
                }
            }

            head = head->next;
        }
    }

    return NULL;
}




/* verify basic semantics.  check for the following: */
/*                                                   */
/*   - unseen directives                             */
/*                                                   */
/*     ExcludeAll                                    */
/*     Include user( "bob" )                         */
/*                                                   */
/*   - duplicate directives                          */
/*                                                   */
/*     Options Recursive,FollowLinks                 */
/*     Options Recursive                             */
/*                                                   */
/*   - multiple blocks for the same directory.       */
/*                                                   */
/*   - unecessary directives                         */
/*                                                   */
/*     IncludeAll                                    */
/*     ...                                           */
/*     <directory /usr>                              */
/*         Include file( "some_file" )               */
/*     </directory>                                  */
/*                                                   */
/* since these are not fatal errors, we just up the  */
/* warning count to give any app the chance to warn  */
/* user before continuing.                           */

osi_bool osi_config_verify_semantics( configuration *cfg )
{
    return TRUE;
}

/******************************************************************************
**
**    Function: osi_config_verify_syntax
**
**    Purpose:  examine all of the config file data to make
**              certain there are no syntactical errors in
**              the file, return FALSE upon failure and print
**              any errors with detailed messages
**
******************************************************************************/

osi_bool osi_config_verify_syntax( configuration *cfg )
{
    unsigned int index;

    int line_type;
    int line_number;
    int filter_type;

    char *line;
    char *complete_line;
    char *attributes;

    char keyword[MAX_LINE_LENGTH] = "";
    osi_bool inside_directory_block   = FALSE;

    for( index = 0; index < cfg->data->size; index++ )
    {
        line          = cfg->data->list[index];
        complete_line = line;

        line_type   = get_keyword_type( line );
        line        = get_token( line, keyword, sizeof( keyword ) );
        line_number = ( index + 1 );

        if( cfg->error_count > CONFIG_MAX_ERRORS )
        {
            return FALSE;
        }

        switch( line_type )
        {
            /* skip over empty lines and comments. */

            case CONFIG_KEYWORD_NULL:
            case CONFIG_KEYWORD_COMMENT:

                break;

            case CONFIG_KEYWORD_DIRECTORY_START:

                if( inside_directory_block )
                {
                    cfg->error_count++;
                    break;
                }

                inside_directory_block = TRUE;

                if( line == NULL || strcmp( line, ">" ) == 0 )
                {
                    cfg->error_count++;
                    break;
                }

                break;

            case CONFIG_KEYWORD_DIRECTORY_END:

                if( !inside_directory_block )
                {
                    cfg->error_count++;
                    break;
                }

                inside_directory_block = FALSE;
                break;

            case CONFIG_KEYWORD_RECURSIVE:
            case CONFIG_KEYWORD_FOLLOWLINKS:

                if( line == NULL )
                {
                    cfg->error_count++;
                    break;
                }

                if( verify_single_value( line, valid_boolean_values ) != NULL )
                {
                    cfg->error_count++;
                }

                break;

            case CONFIG_KEYWORD_HASH:

                if( line == NULL )
                {
                    cfg->error_count++;
                    break;
                }

                if( verify_single_value( line, valid_hash_values ) != NULL )
                {
                    cfg->error_count++;
                }

                break;

            case CONFIG_KEYWORD_NAME:
                break;

            case CONFIG_KEYWORD_INCLUDEALL:
                break;

            case CONFIG_KEYWORD_EXCLUDEALL:
                break;

            case CONFIG_KEYWORD_INCLUDE:

                if( inside_directory_block )
                {
                    if( line == NULL )
                    {
                        cfg->error_count++;
                        break;
                    }

                    /* verify filter name. */

                    filter_type = get_filter_type( line );

                    if( filter_type == FILTER_TYPE_UNKNOWN )
                    {
                        cfg->error_count++;
                        break;
                    }

                    /* verify filter value, and any attribute list. */

                    if( filter_is_dynamic( filter_type ) )
                    {
                        attributes = get_filter_value( line, keyword );

                        if( attributes == NULL )
                        {
                            cfg->error_count++;
                            break;
                        }

                        break;
                    }

                }

                else
                {
                    cfg->error_count++;
                }

                break;

            case CONFIG_KEYWORD_EXCLUDE:

                if( inside_directory_block )
                {
                    if( line == NULL )
                    {
                        cfg->error_count++;
                        break;
                    }

                    /* verify filter name. */

                    filter_type = get_filter_type( line );

                    if( filter_type == FILTER_TYPE_UNKNOWN )
                    {
                        cfg->error_count++;
                        break;
                    }

                    /* if dynamic, verify it's value/syntax. */

                    if( filter_is_dynamic( filter_type ) &&
                        get_filter_value( line, keyword ) == NULL )
                    {
                        cfg->error_count++;
                        break;
                    }
                }

                else
                {
                    cfg->error_count++;
                }

                break;

            case CONFIG_KEYWORD_NOENTRY:

                if( !inside_directory_block )
                {
                    cfg->error_count++;
                }

                break;

            case CONFIG_KEYWORD_UNKNOWN:
            default:

                cfg->error_count++;
                break;

        } /* end switch */

    } /* end for loop */

    return ( cfg->error_count == 0 ) ? TRUE : FALSE;
}

/******************************************************************************
**
**    Function: osi_config_parse
**
**    Purpose:  parse the global data first, then we parse the block
**              data.  we do this because the blocks are to inherit
**              from the global block.
**
******************************************************************************/

osi_bool osi_config_parse( configuration *cfg )
{
    if( osi_config_parse_globals( cfg ) == FALSE )
    {
        return FALSE;
    }

    if( osi_config_parse_non_globals( cfg ) == FALSE )
    {
        return FALSE;
    }

    return TRUE;
}


/******************************************************************************
**
**    Function: osi_config_parse_globals
**
**    Purpose:  parse the global block data into the specified config module.
**
******************************************************************************/

osi_bool osi_config_parse_globals( configuration *cfg )
{
    unsigned int index;
    int line_type;

    char *line;
    char *complete_line;

    char keyword[MAX_LINE_LENGTH] = "";
    action *temp_action;

    block *global_block;
    osi_bool inside_block = FALSE;

    if( osi_config_verify( cfg ) == FALSE )
    {
        return FALSE;
    }

    /* create the global block, and make it the */
    /* current block.  there should always be   */
    /* a global block.                          */

    global_block  = cfg->blocks->next->data;

    for( index = 0; index < cfg->data->size; index++ )
    {
        line          = cfg->data->list[index];
        complete_line = line;

        line_type     = get_keyword_type( line );
        line          = get_token( line, keyword, sizeof( keyword ) );

        switch( line_type )
        {
            /* skip over empty lines and comments. */

            case CONFIG_KEYWORD_NULL:
            case CONFIG_KEYWORD_COMMENT:

                break;

            case CONFIG_KEYWORD_DIRECTORY_START:

                inside_block = TRUE;
                break;

            case CONFIG_KEYWORD_DIRECTORY_END:

                inside_block = FALSE;
                break;

            case CONFIG_KEYWORD_RECURSIVE:

                if( inside_block )
                {
                    break;
                }

                get_token( line, keyword, sizeof( keyword ) );
                get_boolean_value( keyword, &global_block->recursive );

                break;

            case CONFIG_KEYWORD_FOLLOWLINKS:

                if( inside_block )
                {
                    break;
                }

                get_token( line, keyword, sizeof( keyword ) );
                get_boolean_value( keyword, &global_block->follow_links );

                break;

            case CONFIG_KEYWORD_HASH:

                if( inside_block )
                {
                    break;
                }

                get_token( line, keyword, sizeof( keyword ) );
                get_hash_value( keyword, &global_block->hash );

                break;

            case CONFIG_KEYWORD_NAME:

                if( inside_block )
                {
                    break;
                }

                get_name_value( cfg, line );

            case CONFIG_KEYWORD_INCLUDEALL:

                if( inside_block )
                {
                    break;
                }

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_INCLUDE_ALL;

                /* add action to block's list of actions. */

                list_add( global_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_EXCLUDEALL:

                if( inside_block )
                {
                    break;
                }

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_EXCLUDE_ALL;

                list_add( global_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_INCLUDE:

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_INCLUDE;

                /* set the filter type. */

                temp_action->action_filter.type = get_filter_type( line );

                /* get argument if there is one, setup filter, then */
                /* get the attributes.                              */

                if( filter_is_dynamic( temp_action->action_filter.type ) )
                {
                   line = get_filter_value( line,
                                temp_action->action_filter.argument );
                }

                setup_filter_function( &temp_action->action_filter,
                                       temp_action->action_filter.type );

                /* add action to block's list of actions. */

                list_add( global_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_EXCLUDE:

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_EXCLUDE;

                /* set the filter type. */

                temp_action->action_filter.type = get_filter_type( line );

                /* get argument if there is one, then setup filter function. */

                if( filter_is_dynamic( temp_action->action_filter.type ) )
                {
                    line = get_filter_value( line,
                            temp_action->action_filter.argument );
                }

                setup_filter_function( &temp_action->action_filter,
                                       temp_action->action_filter.type );

                /* add action to block's list of actions. */

                list_add( global_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_NOENTRY:

                get_token( line, keyword, sizeof( keyword ) );
                string_list_add_item( global_block->no_entry_list, keyword );

                break;

            case CONFIG_KEYWORD_UNKNOWN:
            default:
                exit(1);

                /* this should NEVER happen if config data was verified. */

                break;

        } /* end switch */

    } /* end for loop */

    return TRUE;
}

/******************************************************************************
**
**    Function: osi_config_parse_non_globals
**
**    Purpose:  parse all of the blocks, inheriting any missing portions
**              from the global block.  store blocks into the specified
**              config module.
**
******************************************************************************/

osi_bool osi_config_parse_non_globals( configuration *cfg )
{
    unsigned int index;
    int line_type;
    int default_attributes = 0;

    osi_bool inside_block = FALSE;
    osi_list action_list;

    char *line;
    char *complete_line;

    char keyword[MAX_LINE_LENGTH] = "";
    char temp[MAX_PATH_LENGTH] = "";

    action *temp_action;

    block *current_block;
    block *global_block;

    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    if( osi_config_verify( cfg ) == FALSE )
    {
        return FALSE;
    }

    /* get the global block, and make it the    */
    /* current block.  there should always be   */
    /* a global block.                          */

    global_block  = cfg->blocks->next->data;
    current_block = global_block;

    /* note default attribute list for the   */
    /* actions with none specified.  we have */
    /* to be careful here in case there is   */
    /* no global attributes list present.    */

    action_list = global_block->actions->next;

    if( action_list != NULL && action_list->data != NULL )
    {
        temp_action = action_list->data;
    }

    for( index = 0; index < cfg->data->size; index++ )
    {
        line          = cfg->data->list[index];
        complete_line = line;

        line_type     = get_keyword_type( line );
        line          = get_token( line, keyword, sizeof( keyword ) );

        switch( line_type )
        {
            /* skip over empty lines and comments. */

            case CONFIG_KEYWORD_NULL:
            case CONFIG_KEYWORD_COMMENT:

                break;

            case CONFIG_KEYWORD_DIRECTORY_START:

                inside_block = TRUE;

                /* create a new block, add it to the list.  it */
                /* is now the current block.                   */

                current_block = new_block_from_block( global_block );
                list_add( cfg->blocks, current_block );

                /* acquire the directory path and remove the   */
                /* closing ">" if there is one.                */

                if( line != NULL )
                {
                    osi_strlcpy( current_block->directory, line,
                                 sizeof( current_block->directory ) );

                    line = strrchr( current_block->directory, '>' );

                    if( line != NULL )
                    {
                        *line = '\0';
                    }
                }

                /* add to the block directory names list. */

                string_list_add_item( cfg->block_names,
                                      current_block->directory );

                break;

            case CONFIG_KEYWORD_DIRECTORY_END:

                current_block = global_block;
                inside_block  = FALSE;

                break;

            case CONFIG_KEYWORD_RECURSIVE:

                if( !inside_block )
                {
                    break;
                }

                get_token( line, keyword, sizeof( keyword ) );
                get_boolean_value( keyword, &current_block->recursive );

                break;

            case CONFIG_KEYWORD_FOLLOWLINKS:

                if( !inside_block )
                {
                    break;
                }

                get_token( line, keyword, sizeof( keyword ) );
                get_boolean_value( keyword, &current_block->follow_links );

                break;

            case CONFIG_KEYWORD_HASH:

                if( !inside_block )
                {
                    break;
                }

                get_token( line, keyword, sizeof( keyword ) );
                get_hash_value( keyword, &current_block->hash );

                break;

            case CONFIG_KEYWORD_INCLUDEALL:

                if( !inside_block )
                {
                    break;
                }

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_INCLUDE_ALL;

                /* add action to block's list of actions. */

                list_add( current_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_EXCLUDEALL:

                if( !inside_block )
                {
                     break;
                }

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_EXCLUDE_ALL;

                list_add( current_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_INCLUDE:

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_INCLUDE;

                /* set the filter type. */

                temp_action->action_filter.type = get_filter_type( line );

                /* get argument if there is one, setup filter, then */
                /* get the attributes.                              */

                if( filter_is_dynamic( temp_action->action_filter.type ) )
                {
                    line = get_filter_value( line,
                                 temp_action->action_filter.argument );
                }

                else
                {
                    line = strstr( line, " " );
                }

                setup_filter_function( &temp_action->action_filter,
                                       temp_action->action_filter.type );

                /* add action to block's list of actions. */

                list_add( current_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_EXCLUDE:

                temp_action = new_action();
                temp_action->type = ACTION_TYPE_EXCLUDE;

                /* set the filter type. */

                temp_action->action_filter.type = get_filter_type( line );

                /* get argument if there is one, then setup filter function. */

                if( filter_is_dynamic( temp_action->action_filter.type ) )
                {
                    line = get_filter_value( line,
                                temp_action->action_filter.argument );
                }

                setup_filter_function( &temp_action->action_filter,
                                       temp_action->action_filter.type );

                /* add action to block's list of actions. */

                list_add( current_block->actions, temp_action );

                break;

            case CONFIG_KEYWORD_NOENTRY:

                osi_strlcpy( temp, current_block->directory, sizeof( temp ) );
                osi_strlcat( temp, path_separator, sizeof( temp ) );
                osi_strlcat( temp, line, sizeof( temp ) );

                string_list_add_item( current_block->no_entry_list, temp );

                break;

            case CONFIG_KEYWORD_UNKNOWN:
            default:
                exit(1);

                /* this should NEVER happen if config data was verified. */

                break;

        } /* end switch */

    } /* end for loop */

        return TRUE;
}

/******************************************************************************
**
**    Function: osi_config_read_from_file
**
**    Purpose:  open and read data from the specified file into the passed in
**		        configuration structure.
**
******************************************************************************/

osi_bool osi_config_generate_id( configuration *cfg )
{
    MD5_CTX context;
    unsigned char digest[MD5_DIGEST_LENGTH];

    osi_bool result = FALSE;
    unsigned int index;

    char config_hash[MAX_CONFIG_ID_LENGTH];

    if( cfg != NULL )
    {
        MD5_Init( &context );

        /* go through each line in config data. */

        for( index = 0; ( index < cfg->data->size ); index++ )
        {
            char *line = cfg->data->list[index];

            /* ignore comments and empty lines. */

            if( ( line != NULL ) && ( line[0] != 0 ) && ( line[0] != '#' ) )
            {
                unsigned int position             = 0;
                unsigned int config_line_position = 0;

                char config_line[MAX_LINE_LENGTH];

                /* go through line copying only printable characters. */

                for( position = 0; ( position < strlen( line ) ); position++ )
                {
                    if( isgraph( line[position] ) )
                    {
                        config_line[config_line_position] = line[position];
                        config_line_position++;
                    }
                }

                config_line[config_line_position] = '\0';

                /* now add to the MD5 context. */

                if( config_line_position > 0 )
                {
                    MD5_Update( &context, config_line,
                                (unsigned long)config_line_position );
                }
            }
        }

        MD5_Final( &( digest[0] ), &context );
        
        /* we use only the last four bytes of the digest for an */
        /* identifier for configs, not the entire checksum.     */

        memset( config_hash, 0, sizeof( config_hash ) );

        osi_snprintf( &config_hash[0], 3, "%02x", digest[12] );
        osi_snprintf( &config_hash[2], 3, "%02x", digest[13] );
        osi_snprintf( &config_hash[4], 3, "%02x", digest[14] );
        osi_snprintf( &config_hash[6], 3, "%02x", digest[15] );

        config_hash[8] = '\0';

        osi_strlcpy( cfg->id, config_hash, sizeof( config_hash ) );
        result = TRUE;
    }

    return result;
}

/******************************************************************************
**
**    Function: osi_config_write_to_file
**
**    Purpose:  open and write data to the specified file from the passed in
**              configuration structure.
**
******************************************************************************/

osi_bool osi_config_write_to_file( configuration *cfg, const char *file_path )
{
    int result = FALSE;

    int index;
    char *line;

    FILE *config_file = NULL;

    if( ( cfg == NULL ) || ( file_path == NULL )  )
    {
        return FALSE;
    }

    if( ( cfg->data == NULL ) || ( cfg->data->size == 0 ) )
    {
        return FALSE;
    }

    config_file = osi_fopen( file_path, "w+", 0600 );

    if( config_file == NULL )
    {
        return FALSE;
    }

    for( index = 0; (unsigned int)index < cfg->data->size; index++ )
    {
        line = cfg->data->list[index];

        if( line != NULL )
        {
            fwrite( line, sizeof( char ), strlen( line ), config_file );
            fwrite( "\n", sizeof( char ), 1, config_file );
        }

        else
        {
            fwrite( "\n", sizeof( char ), 1, config_file );
        }
    }

    fclose( config_file );
    return TRUE;
}



/******************************************************************************
**
**    Function: osi_config_read_from_file
**
**    Purpose:  open and read data from the specified file into the passed in
**		        configuration structure.
**
******************************************************************************/

osi_bool osi_config_read_from_file( configuration *cfg, const char *file_path )
{
    FILE *file;
    string_list *lines;

    char *line;
    char temp_line[MAX_LINE_LENGTH] = "";

    unsigned int index;
    osi_bool result = FALSE;

    if( ( cfg == NULL ) || ( file_path == NULL ) )
    {
        return FALSE;
    }

    file = osi_fopen( file_path, "r", 0 );

    if( file == NULL )
    {
        return FALSE;
    }

    lines = string_list_new();

    /* create string list of lines from file. */

    for(;;)
    {
        line = fgets( temp_line, MAX_LINE_LENGTH, file );

        if( line  == NULL)
        {
            break;
        }

        line = trim_white_space( line );

        if( line != NULL )
        {
            string_list_add_item( lines, line );
        }
    }

    if( lines->size > 0 )
    {
        /* initialize config by sending first line. */

        osi_config_receive_first( cfg, lines->list[0],
                                  strlen( lines->list[0] ) );

        /* now populate the config structure with rest of lines. */

        for( index = 1; index < lines->size; index++ )
        {
            if( ( index + 1 ) != lines->size )
            {
                osi_config_receive( cfg, lines->list[index],
                                    strlen( lines->list[index] ) );
            }

            else
            {
                osi_config_receive_last( cfg, lines->list[index],
                                         strlen( lines->list[index] ) );
            }
        }

        result = TRUE;
    }

    string_list_destroy( lines );
    fclose( file );

    return result;
}


/******************************************************************************
**
**    Function: osi_config_dump
**
**    Purpose:  debug routine that is a very raw dump of the config data.
**              this is a bit old and unused since the implementation of
**              the debug macros.
**
******************************************************************************/

void osi_config_dump( configuration *cfg )
{
    node *temp_node;

    if( cfg == NULL )
    {
        return;
    }

    fprintf( stderr,"\n\n---------------- " );
    fprintf( stderr, "[ config (%s) ] ----------------\n\n",
           ( ( strlen( cfg->name ) > 0) ? cfg->name : "<no name>" ) );

    fprintf( stderr, "FILE:  " );
    fprintf( stderr, "LINES: " );

    if( cfg->data != NULL )
    {
        fprintf( stderr, "%d\n", cfg->data->size );
    }

    else
    {
        fprintf( stderr, "<unknown>\n" );
    }

    fprintf( stderr, "\nERRORS:   %d\nWARNINGS: %d\n\n",
             cfg->error_count, cfg->warning_count );

    /* dump all blocks in config. */

    if( cfg->blocks )
    {
        temp_node = cfg->blocks->next;
    }

    fprintf( stderr, "( GLOBAL BLOCK )\n" );
    osi_config_dump_block( temp_node->data );
    temp_node = temp_node->next;

    while( temp_node != NULL )
    {
        osi_config_dump_block( temp_node->data );
        temp_node = temp_node->next;
    }
}

/******************************************************************************
**
**    Function: osi_config_dump_block
**
**    Purpose:  raw dump of config block for debug purposes.
**
******************************************************************************/

void osi_config_dump_block( block *config_block )
{
    node *temp_node;

    if( config_block == NULL )
    {
        return;
    }

    fprintf( stderr, "\n====================\n\n" );

    fprintf( stderr, "DIRECTORY:  %s\n", config_block->directory );
    fprintf( stderr, "RECURSIVE:  %d\n", config_block->recursive );
    fprintf( stderr, "FOLLOWLINKS:%d\n", config_block->follow_links );
    fprintf( stderr, "HASH:       %d\n", config_block->hash );

    fprintf( stderr, "NO ENTRY LIST: \n" );
    string_list_dump( config_block->no_entry_list );

    if( config_block->actions )
    {
        temp_node = config_block->actions->next;
    }

    while( temp_node != NULL )
    {
        osi_config_dump_action( temp_node->data );
        temp_node = temp_node->next;
    }

    fprintf( stderr, "\n" );
}

/******************************************************************************
**
**    Function: osi_config_dump_action
**
**    Purpose:  raw dump of action for debugging purposes.
**
******************************************************************************/

void osi_config_dump_action( action *config_action )
{
    fprintf( stderr,
             "[action %d] type(%d) filter type(%d) filter argument(%s)\n",
              config_action, config_action->type,
              config_action->action_filter.type,
              config_action->action_filter.argument );
}

