
/******************************************************************************
**
**  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.
**
******************************************************************************/

/*****************************************************************************
**
**  File:    md_compare.c
**  Date:    Jan 17, 2003
**
**  Author:  Brian Wotring
**  Purpose: handle database comparisons.
**
******************************************************************************/

#include "libosiris.h"
#include "libosirism.h"
#include "libosirisdb.h"
#include "libosirisctl.h"
#include "libfileapi.h"

#include "md_hosts.h"
#include "md_log.h"
#include "md_compare.h"
#include "md_database.h"
#include "md_notify.h"
#include "md_filter.h"

#include "common.h"
#include "logging.h"

extern char root_path[MAX_PATH_LENGTH];


OSI_DB_CTX * osi_db_ctx_new()
{
    OSI_DB_CTX *context = osi_malloc( sizeof( OSI_DB_CTX ) );

    if( context != NULL )
    {

        memset( context, 0, sizeof( OSI_DB_CTX ) );

        /* setup the defaults for a compare context. */

        context->host[0]      = '\0';
        context->filepath[0]  = '\0';

        context->file_handle  = NULL;

        context->compare_mask = CMP_MASK_DEFAULT;

        context->use_system_log     = TRUE;

        context->include_missing    = TRUE;
        context->include_new        = TRUE;
        context->need_to_notify     = TRUE;

        context->diff_count          = 0;
        context->notify_count        = 0;

        context->checksum_diff_count = 0;
        context->perm_diff_count     = 0;
        context->suid_diff_count     = 0;
        context->root_diff_count     = 0;
        context->new_count           = 0;
        context->missing_count       = 0;
    }

    return context;
}

void osi_db_ctx_destroy( OSI_DB_CTX *context )
{
    if( context != NULL )
    {
        if( context->file_handle != NULL )
        {
            fclose( context->file_handle );
            context->file_handle = NULL;
        }

        /* don't bother deleting the temporary log file. */

        osi_free( context );
    }
}

void osi_db_ctx_preflight( OSI_DB_CTX *context )
{
    if( context == NULL )
    {
        return;
    }

    /* if we have a filename to log to, we open it for write.  */

    if( context->filepath[0] != '\0' )
    {
        context->file_handle = osi_fopen( context->filepath, "w+", 
                                          OSI_LOG_DEFAULT_PERMS );
        if( context->file_handle )
        {
            print_log_file_heading( context );
        }
    }
}

void osi_db_ctx_postflight( OSI_DB_CTX *context )
{
    if( context == NULL )
    {
        return;
    }

    /* if we have an open file handle, we close it. */

    if( context->file_handle != NULL )
    {
        print_log_file_footer( context );

        fclose( context->file_handle );
        context->file_handle = NULL;
    }
}

void osi_db_ctx_set_use_system_log( OSI_DB_CTX *context, osi_bool use )
{
    if( context != NULL )
    {
        context->use_system_log = use;
    }
}

void osi_db_ctx_set_host( OSI_DB_CTX *context, const char *host )
{
    if( context != NULL )
    {
        osi_strlcpy( context->host, host, sizeof( context->host ) );
    }
}

void osi_db_ctx_set_log_file( OSI_DB_CTX *context, const char *filepath )
{
    if( ( context != NULL ) && ( filepath != NULL ) )
    {
        osi_strlcpy( context->filepath, filepath, sizeof( context->filepath ) );
    }
}


/* generic comparison routine.  the first is the base db, the second */
/* is the untrusted db (in most cases).                              */

osi_bool osi_compare_db( OSI_DB_CTX *context, OSI_DB *db1, OSI_DB *db2 )
{
    int result;
    int record_size;

    int r1_type;
    int r2_type;

    SCAN_RECORD *r1;
    SCAN_RECORD *r2;

    OSI_DB_HEADER db1_header;
    OSI_DB_HEADER db2_header;

    OSI_DB filter_db;

    if( ( db1 == NULL ) || ( !db1->open ) ||
        ( db2 == NULL ) || ( !db2->open ) )
    {
        return FALSE;
    }

    r1_type = osi_scan_db_get_record_type( db1 );
    r2_type = osi_scan_db_get_record_type( db2 );

    record_size = determine_scan_record_size_from_type( r1_type );

    /* some sanity checks. */

    if( ( record_size <= 0 ) || ( r1_type != r2_type ) )
    {
        return FALSE;
    }

    /* open the filter database. */

    open_filter_db( &filter_db );

    osi_host_read_database_header_from_path( &db1_header, db1->path );
    osi_host_read_database_header_from_path( &db2_header, db2->path );

    /* copy in db names, then setup context. */

    osi_strlcpy( context->db1_name, db1_header.name,
                 sizeof( context->db1_name ) );

    osi_strlcpy( context->db2_name, db2_header.name,
                 sizeof( context->db2_name ) );

    osi_db_ctx_preflight( context );

    /* create temporary objects for holding one from each db. */ 

    r1 = osi_malloc( record_size );
    r2 = osi_malloc( record_size );

    result = osi_scan_db_get_first_record( db1, r1, record_size );

    log_info( LOG_MEDIUM, context->host, "compare begin (%s,%s)",
              db1_header.name, db2_header.name );

    /* look for changes and missing records.  If we had ordered */
    /* lists we could do this in one pass.  instead, we have to */
    /* do a second pass if we are checking for new records.     */

    while( result == OSI_DB_OK )
    {
        char *name = get_scan_record_path( r1 );
        result = osi_scan_db_get_record_with_name( db2, r2, record_size, name );

        if( result == OSI_DB_OK )
        {
            /* compare record attributes, then see if changes are filtered. */

            osi_compare_db_records( context, r1, r2 );
            
            if( context->changed_mask )
            {
                if( changes_are_filtered( &filter_db, context->host,
                                        context->changed_mask, name ) == FALSE )
                {
                    context->notify_count++;
                }
            }
        }

        if ( ( result == OSI_ERROR_DB_ITEM_NOT_FOUND ) &&
             ( context->include_missing ) )
        {
            log_cmp_message( context, "[%s][missing][%s]",context->host, name );
            context->diff_count++;
            context->missing_count++;

            /* consult the filter database. */

            if( changes_are_filtered( &filter_db, context->host,
                                      ATTR_MISSING, name ) == FALSE )
            {
                context->notify_count++;
            }
        }

        result = osi_scan_db_get_next_record( db1, r1, record_size );
    }

    /* now look for new records. */

    if( context->include_new )
    {
        result = osi_scan_db_get_first_record( db2, r2, record_size );

        while( result == OSI_DB_OK )
        {
            char *name = get_scan_record_path( r2 );
            result = osi_scan_db_get_record_with_name( db1, r1,
                                                       record_size, name );

            if( result == OSI_ERROR_DB_ITEM_NOT_FOUND ) 
            {
                log_cmp_message( context, "[%s][new][%s]",context->host, name );
                context->diff_count++;
                context->new_count++;

                /* consult the filter database. */

                if( changes_are_filtered( &filter_db, context->host,
                                          ATTR_NEW, name ) == FALSE )
                {
                    context->notify_count++;
                }
            }

            result = osi_scan_db_get_next_record( db2, r2, record_size );
        }
    }

    /* if we have no deltas, we set notify flag. */

    if( context->notify_count > 0 )
    {
        context->need_to_notify = TRUE; 
    }

    else
    {
        context->need_to_notify = FALSE;
    }

    log_info( LOG_MEDIUM, context->host, "compare end (%s,%s)",
              db1_header.name, db2_header.name );

    osi_free( r1 );
    osi_free( r2 );

    osi_db_ctx_postflight( context );
    close_filter_db( &filter_db );

    return TRUE;
}

osi_bool osi_compare_db_records( OSI_DB_CTX *context,
                                 SCAN_RECORD *record1,
                                 SCAN_RECORD *record2 )
{
    osi_uint16 type;
    OSI_ATTR_MASK mask;

    unsigned long pre_diff_count;

    /* sanity check */

    if( record1 == NULL || record2 == NULL || context == NULL )
    {
        return FALSE;
    }

    context->changed_mask = 0;

    /* we kind of have to have the same record types or this will */
    /* be a mess of a comparison.                                 */

    if( record1->type != record2->type )
    {
        return FALSE;
    }

    type = record1->type;
    mask = context->compare_mask;
    pre_diff_count = context->diff_count;

    if( (int)type == SCAN_RECORD_TYPE_UNIX1 )
    {
        SCAN_RECORD_UNIX1 *r1 = (SCAN_RECORD_UNIX1 *)record1;
        SCAN_RECORD_UNIX1 *r2 = (SCAN_RECORD_UNIX1 *)record2;

        if( ( mask & ATTR_CHECKSUM ) && 
            ( r1->checksum_algorithm == r2->checksum_algorithm ) &&
              strcmp( r1->checksum, r2->checksum ) != 0 )
        {
            log_cmp_message( context,
                             "[%s][cmp][%s][checksum][%s,%s]",
                             context->host, r1->path,
                             r1->checksum, r2->checksum );

            context->diff_count++;
            context->checksum_diff_count++;
            
            context->changed_mask |= ATTR_CHECKSUM;
        }

        if( ( mask & ATTR_DEVICE ) && ( r1->device != r2->device ) )
        { 
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->device );
            osi_snprintf( right, sizeof(right), "%llu", r2->device );

            log_cmp_message( context,
                             "[%s][cmp][%s][device][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context,
                             "[%s][cmp][%s][device][%llu,%llu]",
                             context->host, r1->path, r1->device, r2->device );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_DEVICE;
        }

        if( ( mask & ATTR_INODE ) && ( r1->inode != r2->inode ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->inode );
            osi_snprintf( right, sizeof(right), "%llu", r2->inode );

            log_cmp_message( context,
                             "[%s][cmp][%s][inode][%s,%s]",
                             context->host, r1->path, left, right );

            mask |= ATTR_INODE;

            #else

            
            log_cmp_message( context,
                             "[%s][cmp][%s][inode][%llu,%llu]",
                              context->host,r1->path, r1->inode, r2->inode );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_INODE;
        }

        if( ( mask & ATTR_PERMISSIONS ) && 
            ( r1->permissions != r2->permissions ) )
        {
            log_cmp_message( context, "[%s][cmp][%s][perm][%s,%s]",
                             context->host,
                             r1->path,
                             r1->permissions_string, 
                             r2->permissions_string );

            context->diff_count++;
            context->perm_diff_count++;

            context->changed_mask |= ATTR_PERMISSIONS;
        }

        if( ( mask & ATTR_LINKS ) & ( r1->links != r2->links ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->links );
            osi_snprintf( right, sizeof(right), "%llu", r2->links );

            log_cmp_message( context,
                             "[%s][cmp][%s][links][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context,
                             "[%s][cmp][%s][links][%llu,%llu]",
                             context->host,
                             r1->path, r1->links, r2->links );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_LINKS;
        }

        if( ( mask & ATTR_UID ) && ( r1->uid != r2->uid ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->uid );
            osi_snprintf( right, sizeof(right), "%llu", r2->uid );

            log_cmp_message( context,
                             "[%s][cmp][%s][uid][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context, "[%s][cmp][%s][uid][%llu,%llu]",
                             context->host, r1->path, r1->uid, r2->uid );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_UID;
        }

        if( ( mask & ATTR_GID ) && (  r1->gid != r2->gid ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->gid );
            osi_snprintf( right, sizeof(right), "%llu", r2->gid );

            log_cmp_message( context,
                             "[%s][cmp][%s][gid][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context, "[%s][cmp][%s][gid][%llu,%llu]",
                             context->host, r1->path, r1->gid, r2->gid );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_GID;
        }

        if( ( mask & ATTR_MTIME ) && ( r1->mtime != r2->mtime ) )
        {
            char left_mtime[25];
            char right_mtime[25];

            osi_strlcpy( left_mtime, osi_time_to_string( (long)r1->mtime ),
                         sizeof( left_mtime ) );

            osi_strlcpy( right_mtime, osi_time_to_string( (long)r2->mtime ),
                         sizeof( right_mtime ) );

            log_cmp_message( context, "[%s][cmp][%s][mtime][%s,%s]",
                             context->host, r1->path, left_mtime, right_mtime );

            context->diff_count++;
            context->changed_mask |= ATTR_MTIME;
        }

        if( ( mask & ATTR_ATIME ) && ( r1->atime != r2->atime ) )
        {
            char left_atime[25];
            char right_atime[25];

            osi_strlcpy( left_atime, osi_time_to_string( (long)r1->atime ),
                         sizeof( left_atime ) );

            osi_strlcpy( right_atime, osi_time_to_string( (long)r2->atime ),
                         sizeof( right_atime ) );

            log_cmp_message( context, "[%s][cmp][%s][atime][%s,%s]",
                             context->host, r1->path, left_atime, right_atime );

            context->diff_count++;
            context->changed_mask |= ATTR_ATIME;
        }

        if( ( mask & ATTR_CTIME ) && ( r1->ctime != r2->ctime ) )
        {
            char left_ctime[25];
            char right_ctime[25];

            osi_strlcpy( left_ctime, osi_time_to_string( (long)r1->ctime ),
                         sizeof( left_ctime ) );

            osi_strlcpy( right_ctime, osi_time_to_string( (long)r2->ctime ),
                         sizeof( right_ctime ) );

            log_cmp_message( context, "[%s][cmp][%s][ctime][%s,%s]",
                             context->host, r1->path, left_ctime, right_ctime );

            context->diff_count++;
            context->changed_mask |= ATTR_CTIME;
        }

        if( ( mask & ATTR_DEVICE_TYPE ) && 
            ( r1->device_type != r2->device_type ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->device_type );
            osi_snprintf( right, sizeof(right), "%llu", r2->device_type );

            log_cmp_message( context,
                             "[%s][cmp][%s][devicetype][%s,%s]",
                             context->host, r1->path, left, right );
            #else

           log_cmp_message( context, 
                           "[%s][cmp][%s][devicetype][%llu,%llu]",
                            context->host, r1->path,
                            r1->device_type, r2->device_type );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_DEVICE_TYPE;
        }

        if( ( mask & ATTR_BYTES ) && ( r1->bytes != r2->bytes ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->bytes );
            osi_snprintf( right, sizeof(right), "%llu", r2->bytes );

            log_cmp_message( context,
                             "[%s][cmp][%s][bytes][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context,
                             "[%s][cmp][%s][bytes][%llu,%llu]",
                             context->host, r1->path, r1->bytes, r2->bytes );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_BYTES;
        }

        if( ( mask & ATTR_BLOCKS ) && (  r1->blocks != r2->blocks ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->blocks );
            osi_snprintf( right, sizeof(right), "%llu", r2->blocks );

            log_cmp_message( context,
                             "[%s][cmp][%s][blocks][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context,
                             "[%s][cmp][%s][blocks][%llu,%llu]",
                             context->host, r1->path, r1->blocks, r2->blocks );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_BLOCKS;
        }

        if( ( mask & ATTR_BLOCK_SIZE ) && 
            (  r1->block_size != r2->block_size ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->block_size );
            osi_snprintf( right, sizeof(right), "%llu", r2->block_size );

            log_cmp_message( context,
                             "[%s][cmp][%s][blocksize][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context,
                             "[%s][cmp][%s][blocksize][%llu,%llu]",
                             context->host, r1->path,
                             r1->block_size, r2->block_size );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_BLOCK_SIZE;
        }

        /* if we had a change, look to update some of our stats. */

        if( pre_diff_count != context->diff_count )
        {
            /* look for root owned changes. */ 

            if( ( strcmp( r1->user, "root" ) == 0 ) ||
                ( strcmp( r2->user, "root" ) == 0 ) )
            {
                context->root_diff_count++;
            }

            /* look for suid changes. */

            if( ( r1->permissions_string[3] == 's' ) ||
                ( r1->permissions_string[3] == 'S' ) ||
                ( r2->permissions_string[3] == 's' ) ||
                ( r2->permissions_string[3] == 'S' ) )
            {
                context->suid_diff_count++;
            }
        }
    }

    else if( (int)type == SCAN_RECORD_TYPE_UNIX2 )
    {

    }

    else if( (int)type == SCAN_RECORD_TYPE_WIN32 )
    {

    }

    else if( (int)type == SCAN_RECORD_TYPE_WINNT )
    {
        SCAN_RECORD_WINNT *r1 = (SCAN_RECORD_WINNT *)record1;
        SCAN_RECORD_WINNT *r2 = (SCAN_RECORD_WINNT *)record2;

        if( ( mask & ATTR_CHECKSUM ) &&
            ( r1->checksum_algorithm == r2->checksum_algorithm ) &&
              strcmp( r1->checksum, r2->checksum ) != 0 )
        {
            log_cmp_message( context,
                             "[%s][cmp][%s][checksum][%s,%s]",
                             context->host, r1->path,
                             r1->checksum, r2->checksum );

            context->diff_count++;
            context->changed_mask |= ATTR_CHECKSUM;
        }

        if( ( mask & ATTR_DEVICE ) && ( r1->device != r2->device ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->device );
            osi_snprintf( right, sizeof(right), "%llu", r2->device );

            log_cmp_message( context,
                             "[%s][cmp][%s][device][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context,
                             "[%s][cmp][%s][device][%llu,%llu]",
                             context->host, r1->path, 
                             r1->device, r2->device );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_DEVICE;
        }

        if( ( mask & ATTR_BYTES ) && ( r1->bytes != r2->bytes ) )
        {
            #ifdef WIN32

            char left[32];
            char right[32];

            osi_snprintf( left, sizeof(left), "%llu", r1->bytes );
            osi_snprintf( right, sizeof(right), "%llu", r2->bytes );

            log_cmp_message( context,
                             "[%s][cmp][%s][bytes][%s,%s]",
                             context->host, r1->path, left, right );
            #else

            log_cmp_message( context,
                             "[%s][cmp][%s][bytes][%llu,%llu]",
                             context->host, r1->path,
                             r1->bytes, r2->bytes );
            #endif

            context->diff_count++;
            context->changed_mask |= ATTR_BYTES;
        }

        if( ( mask & ATTR_MTIME ) && ( r1->mtime != r2->mtime ) )
        {
            char left_time[25];
            char right_time[25];

            osi_strlcpy( left_time, osi_time_to_string( (long)r1->mtime ),
                         sizeof( left_time ) );

            osi_strlcpy( right_time, osi_time_to_string( (long)r2->mtime ),
                         sizeof( right_time ) );


            log_cmp_message( context,
                             "[%s][cmp][%s][mtime][%s,%s]",
                             context->host, r1->path, left_time, right_time );

            context->diff_count++;
            context->changed_mask |= ATTR_MTIME;
        }

        if( ( mask & ATTR_ATIME ) && ( r1->atime != r2->atime ) )
        {
            char left_time[25];
            char right_time[25];

            osi_strlcpy( left_time, osi_time_to_string( (long)r1->atime ),
                         sizeof( left_time ) );

            osi_strlcpy( right_time, osi_time_to_string( (long)r2->atime ),
                         sizeof( right_time ) );

            log_cmp_message( context,
                             "[%s][cmp][%s][atime][%s,%s]",
                             context->host, r1->path, left_time, right_time );

            context->diff_count++;
            context->changed_mask |= ATTR_ATIME;
        }

        if( ( mask & ATTR_CTIME ) && ( r1->ctime != r2->ctime ) )
        {
            char left_time[25];
            char right_time[25];

            osi_strlcpy( left_time, osi_time_to_string( (long)r1->ctime ),
                         sizeof( left_time ) );

            osi_strlcpy( right_time, osi_time_to_string( (long)r2->ctime ),
                         sizeof( right_time ) );

            log_cmp_message( context,
                             "[%s][cmp][%s][ctime][%s,%s]",
                             context->host, r1->path, left_time, right_time );

            context->diff_count++;
            context->changed_mask |= ATTR_CTIME;
        }

        if( ( mask & ATTR_OWNER_SID ) &&
            ( strcmp( r1->owner_sid, r2->owner_sid ) != 0 ) )
        {
            log_cmp_message( context,
                      "[%s][cmp][%s][owner_sid][%s:%s,%s:%s]",
                      context->host, r1->path,
                      r1->owner, r1->owner_sid,
                      r2->owner, r2->owner_sid );

            context->diff_count++;
            context->changed_mask |= ATTR_OWNER_SID;
        }

        if( ( mask & ATTR_GROUP_SID ) &&
            ( strcmp( r1->group_sid, r2->group_sid ) != 0 ) )
        {
            log_cmp_message( context,
                      "[%s][cmp][%s][group_sid][%s:%s,%s:%s]",
                      context->host, r1->path,
                      r1->group, r1->group_sid,
                      r2->group, r2->group_sid );

            context->diff_count++;
            context->changed_mask |= ATTR_GROUP_SID;
        }

        if( ( mask & ATTR_WIN32_FILE ) &&
            ( r1->attributes != r2->attributes ) )
        {
            char buffer[1024];

            get_win32_file_attr_delta_string( r1->attributes, r2->attributes,
                                              buffer, sizeof( buffer ) );

            log_cmp_message( context,
                             "[%s][cmp][%s][file_attributes][%s]",
                             context->host, r1->path, buffer ); 

            context->diff_count++;
            context->changed_mask |= ATTR_WIN32_FILE;
        } 

        /* END WIN32 compare. */

        /* if we had a change, look to update some of our stats. */

        if( pre_diff_count != context->diff_count )
        {
            /* look for root owned changes. */

            if( ( strcmp( r1->owner, "Administrator" ) == 0 ) ||
                ( strcmp( r2->owner, "Administrator" ) == 0 ) )
            {
                context->root_diff_count++;
            } 
        }
    }   

    return TRUE;
}



void print_log_file_heading( OSI_DB_CTX *context )
{
    time_t current_time;

    if( ( context == NULL ) && ( context->file_handle == NULL ) )
    {
        return;
    }

    current_time = osi_get_time();

    if( current_time > 0 )
    {
        char time_string[32];
        char buf[512];
        char *temp;

        /* write out timestamp. */

        osi_strlcpy( time_string, ctime( &current_time ),
                     sizeof( time_string ) );

        time_string[24] = '\0';
        osi_snprintf( buf, sizeof( buf ), "     compare time: %s\n",
                      time_string );
        fwrite( buf, 1, strlen( buf ), context->file_handle );

        /* write out hostname. */

        osi_snprintf( buf, sizeof( buf ),
                      "             host: %s\n", context->host );
        fwrite( buf, 1, strlen( buf ), context->file_handle );

        if( (temp = strrchr( context->filepath, PATH_SEPARATOR ) ) != NULL )
        {
            temp++;

            if( strcmp( temp, TEMP_SCAN_LOG_FILE_NAME ) == 0 )
            {
                osi_snprintf( buf, sizeof( buf ), "         log file: %s\n",
                              "no log file generated, see system log." );
            }

            else
            {
                osi_snprintf( buf, sizeof( buf ), "         log file: %s\n",
                              temp );
            }

            fwrite( buf, 1, strlen( buf ), context->file_handle );
        }

        /* write out the db names. */

        osi_snprintf( buf, sizeof( buf ),
                      "          base db: %s\n       compare db: %s\n\n",
                      context->db1_name, context->db2_name );

        fwrite( buf, 1, strlen( buf ), context->file_handle );
    }
}

void print_log_file_footer( OSI_DB_CTX *context )
{
    char buffer[2048] = "";

#define COMPARE_STATS_FORMAT "\n\nChange Statistics:\n----------\
------------------------\n\n        checksums: %lu\n\
       SUID files: %lu\n root-owned files: %lu\n file permissions: %lu\n\
        new files: %lu\n    missing files: %lu\n\ntotal differences: %lu\n\n"

    if( ( context == NULL ) && ( context->file_handle == NULL ) )
    {
        return;
    }

    osi_snprintf( buffer, sizeof( buffer ), COMPARE_STATS_FORMAT,
                  context->checksum_diff_count, context->suid_diff_count,
                  context->root_diff_count, context->perm_diff_count,
                  context->new_count, context->missing_count,
                  context->diff_count );

    fwrite( buffer, 1, strlen( buffer ), context->file_handle );
}

void compare_db_with_default_for_host( OSI_DB *db, OSI_HOST *host )
{
    int result;
    OSI_DB base_db;
    OSI_DB_CTX *context;

    if( ( db == NULL ) || ( host == NULL ) )
    {
        return;
    }

    memset( &base_db, 0, sizeof( base_db ) );
    result = osi_host_open_default_db( &base_db, host );

    if( result != OSI_DB_OK )
    {
        log_error( LOG_MEDIUM, host->name, "opening base db." );
        return;
    }

    /* make sure we aren't tring to compare a db against itself. */

    if( strcmp( base_db.path, db->path ) == 0 )
    {
        return;
    }

    context = osi_db_ctx_new();

    if( context )
    {
        char *log_name;

        osi_db_ctx_set_host( context, host->name );
    
        /* if file logging is enabled, we create a new log file name. */
 
        if( host->file_log_enabled )
        {
            log_name = generate_log_file_name( host->name );
        }

        else
        {
            log_name = generate_temp_log_file_name( host->name );
        }

        osi_db_ctx_set_log_file( context, log_name );
        osi_compare_db( context, &base_db, db );

        /* if this host has notifications enabled, we kick off the    */
        /* notifications routine.  If the diff count is zero, we only */
        /* notify if the notify_always flag is set.                   */

        if( host->notify_enabled )
        {
            if( ( host->notify_always ) || ( context->need_to_notify ) )
            {
                osi_notify_admin( context );
            }
        }

        osi_db_ctx_destroy( context );
    }

    osi_scan_db_close( &base_db );
}


