
/******************************************************************************
**
**  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:    database.h
**  Date:    June 2, 2002
**
**  Author:  Brian Wotring
**  Purpose: handle various database operations for management daemon.
**
******************************************************************************/

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

#include "md_hosts.h"
#include "md_database.h"
#include "md_compare.h"

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

extern char root_path[MAX_PATH_LENGTH];


int create_new_database_for_host( OSI_DB *db,
                                  OSI_HOST *host )
{
    int result = OSI_ERROR_NULL_ARGUMENT;
    char name[MAX_HOSTNAME_LENGTH];

    if( ( db != NULL ) && ( host != NULL ) )
    {
        result = generate_new_database_name_for_host( name, sizeof( name ),
                                                   host->name,
                                                   (int)host->archive_databases,
                                                   host->base_db );

        /* create full path, place in db structure. */

        if( result == OSI_DB_OK )
        {
            result = osi_host_set_database_path_with_name( db, host->name,
                                                           name );
        }

        /* open db for write access. */

        if( result == OSI_DB_OK )
        {
            if( ( result = osi_scan_db_create( db ) ) != OSI_OK )
            {
                return OSI_ERROR_DB_CREATE_FAILED;
            }

            if( osi_scan_db_open( db, OSI_DB_WRITE ) != OSI_OK )
            {
                return OSI_ERROR_DB_OPEN_FAILED;
            }

            /* store the database name in the header. */

            osi_scan_db_store_header_item( db,
                                      OSI_DB_HEADER_ITEM_NAME,
                                      name,
                                      sizeof( name ) );

            /* if this host doesn't have a default database */
            /* or if the one specified doesn't exist, make  */
            /* this newly created one the base db.          */

            if( ( host->base_db[0] == '\0' ) ||
                ( osi_host_database_exists( host->name,
                                            host->base_db ) == FALSE  ) )
            {
                osi_strlcpy( host->base_db, name, sizeof( host->base_db ) );
                osi_write_host( host );
            }

            result = OSI_DB_OK;
        }

        else
        {
            return OSI_ERROR_DB_PATH;
        }
    }

    return result;
}


/* kind of dumb, we simply put together the full path to this database, */
/* and put it into the db path field.                                   */

int osi_host_set_database_path_with_name( OSI_DB *db, const char *host,
                                          const char *name )
{
    int result = OSI_ERROR_NULL_ARGUMENT;

    if( ( db != NULL ) && ( host != NULL ) && ( name != NULL ) )
    {
        char path_separator[2] = { PATH_SEPARATOR, '\0' };
        
        osi_strlcpy( db->path, root_path, sizeof( db->path ) );
        osi_strlcat( db->path, path_separator, sizeof( db->path ) );
        osi_strlcat( db->path, HOSTS_DIRECTORY_NAME, sizeof( db->path ) );
        osi_strlcat( db->path, path_separator, sizeof( db->path ) );
        osi_strlcat( db->path, host, sizeof( db->path ) );
        osi_strlcat( db->path, path_separator, sizeof( db->path ) );

        osi_strlcat( db->path, HOST_DATABASE_DIRECTORY_NAME,
                     sizeof( db->path ) );

        osi_strlcat( db->path, path_separator, sizeof( db->path ) );
        osi_strlcat( db->path, name, sizeof( db->path ) );
        
        result = OSI_DB_OK;
    }
    
    return result;
}


int generate_new_database_name_for_host( char *name, int name_size,
                                         const char *host, osi_bool archiving,
                                         const char *base_db )
{
    int result = OSI_ERROR_UNKNOWN;

    unsigned int number = 0;
    char path[MAX_PATH_LENGTH];

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

    OSI_DIRECTORY directory;

    if( ( host == NULL ) || ( name == NULL ) || ( name_size <= 0 ) )
    {
        return result;
    }

    memset( &directory, 0, sizeof( OSI_DIRECTORY ) );

    /* put the path to the db directory first. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) ); 
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) ); 
    osi_strlcat( path, host, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );

    /* now find the next free number to use for the filename. */

    if( osi_open_directory( path, &directory ) == FALSE )
    {
        return OSI_ERROR_INVALID_PATH;
    }

    do
    {
        int num;

        if( directory.filename != NULL )
        {
            num = atoi( directory.filename );

            if( (unsigned int)num > number )
            {
                number = num;
            }
        }

    } while( osi_get_next_file( &directory ) );

    osi_close_directory( &directory );

    /* if we are archiving the databases, or if the trusted database. */
    /* is this database, we use the next number.                      */

    if( archiving || ( base_db && ( atoi( base_db ) == (int)number )  ) )
    {
        number++;
    }

    /* make sure we don't start with zero. */

    if( number == 0 )
    {
        number = 1;
    }

    /* turn number into string. */

    osi_snprintf( name, name_size, "%d", number );

    /* append the filename to the path. */

    return OSI_DB_OK;
}


int osi_host_open_default_db( OSI_DB *db,
                              OSI_HOST *host )
{
    int result = OSI_ERROR_NULL_ARGUMENT;

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

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

    /* first, make sure we have a base db to open. */

    if( host->base_db[0] == '\0' )
    {
        return result;
    }

    /* construct full path to this file. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host->name, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host->base_db, sizeof( path ) );

    osi_strlcpy( db->path, path, sizeof( db->path ) );
    result = osi_scan_db_open( db, OSI_DB_READ );

    return result;
}

int osi_host_read_database_header_from_path( OSI_DB_HEADER *db_header,
                                             const char *path )
{
    int result = OSI_ERROR_NULL_ARGUMENT;
    int status;

    OSI_DB db;

    if( ( db_header == NULL ) || ( path == NULL ) )
    {
        return result;
    }

    memset( &db, 0, sizeof( db ) );
    osi_strlcpy( db.path, path, sizeof( db.path ) );
    status = osi_scan_db_open( &db, OSI_DB_READ );

    if( status == OSI_DB_OK )
    {
        int length;

       /* read database header elements into this header object we    */
       /* were given, if the lengths come back zero, we zero out our  */
       /* buffers so they don't go back with garbage in them.         */

        osi_scan_db_get_header_item( &db, OSI_DB_HEADER_ITEM_COMPLETE,
                                &( db_header->complete ),
                                sizeof( db_header->complete ), &length );

        if( length == 0 )
        {
            db_header->complete = 0;
        }

        osi_scan_db_get_header_item( &db, OSI_DB_HEADER_ITEM_NAME,
                                     db_header->name,
                                     sizeof( db_header->name ), &length );

        db_header->name[length] = '\0';

        osi_scan_db_get_header_item( &db, OSI_DB_HEADER_ITEM_HOST,
                                     db_header->host, sizeof( db_header->host ),
                                     &length );

        db_header->host[length] = '\0';

        osi_scan_db_get_header_item( &db, OSI_DB_HEADER_ITEM_USERNAME,
                                     db_header->username,
                                     sizeof( db_header->username ), &length );

        db_header->username[length] = '\0';

        osi_scan_db_get_header_item( &db, OSI_DB_HEADER_ITEM_CONFIG_NAME,
                                     db_header->config_name,
                                     sizeof( db_header->config_name ),
                                     &length );

        db_header->config_name[length] = '\0';

        osi_scan_db_get_header_item( &db, OSI_DB_HEADER_ITEM_CONFIG_ID,
                                     db_header->config_id,
                                     sizeof( db_header->config_id ), &length );

        db_header->config_id[length] = '\0';

        osi_scan_db_get_header_item( &db, OSI_DB_HEADER_ITEM_SCAN_RESULTS,
                                     &( db_header->scan_results ),
                                     sizeof( db_header->scan_results ),
                                     &length );
        osi_scan_db_close( &db );
        result = OSI_DB_OK;
    }
 
    return result;
}

int osi_host_read_database_header_with_name( OSI_DB_HEADER *db_header,
                                             const char *host,
                                             const char *name )
{
    int result = OSI_ERROR_NULL_ARGUMENT;

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

    if( ( db_header == NULL ) || ( host == NULL ) || ( name == NULL ) )
    {
        return result;
    }

    /* construct full path to this file. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, name, sizeof( path ) );

    return osi_host_read_database_header_from_path( db_header,
                                                    (const char *)&path );
}

int osi_host_read_database_brief_with_name( OSI_DATABASE_BRIEF *db_brief, 
                                            const char *host,
                                            const char *name )
{
    int result = OSI_ERROR_NULL_ARGUMENT;
    OSI_DB_HEADER db_header;

    if( ( db_brief == NULL ) || ( host == NULL ) || ( name == NULL ) )
    {
        return result;
    }
        /* try to read database header. */

    result = osi_host_read_database_header_with_name( &db_header, host, name );

    if( result == OSI_DB_OK )
    {
        /* populate the database brief structure. */

        db_brief->complete = db_header.complete;
        db_brief->error_count  = osi_host_get_db_error_count( host, name );

        db_brief->record_count = osi_host_get_db_record_count( host, name );

        osi_strlcpy( db_brief->name, name, sizeof( db_brief->name ) );

        osi_strlcpy( db_brief->config_name, db_header.config_name,
                     sizeof( db_brief->config_name ) );

        osi_strlcpy( db_brief->config_id, db_header.config_id,
                     sizeof( db_brief->config_id ) );

        memcpy( &( db_brief->scan_results ),
                &( db_header.scan_results ),
                sizeof( db_brief->scan_results ) );

        result = OSI_DB_OK;
    }

    return result;;
}


int osi_host_read_database_briefs( OSI_HOST *host )
{
    char path[MAX_PATH_LENGTH];
    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    OSI_DIRECTORY directory;
    memset( &directory, 0, sizeof( OSI_DIRECTORY ) );

    if(  host != NULL )
    {
        /* first, we construct path to database directory. */

        osi_strlcpy( path, root_path, sizeof( path ) );
        osi_strlcat( path, path_separator, sizeof( path ) );
        osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
        osi_strlcat( path, path_separator, sizeof( path ) );
        osi_strlcat( path, host->name, sizeof( path ) );
        osi_strlcat( path, path_separator, sizeof( path ) );
        osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );

        log_info( LOG_HIGH, host->name, "looking for databases." );

        /* now traverse the directory, creating db brief objects and */
        /* adding them to this host's list of databases.             */

        if( osi_open_directory( path, &directory ) )
        {
            char file_path[MAX_PATH_LENGTH];

            log_info( LOG_HIGH, host->name, "opened database directory." );

            do
            {
                /* create full path to the current filename. */

                osi_strlcpy( file_path, path, sizeof( file_path ) );
                osi_strlcat( file_path, path_separator, sizeof( file_path ) );

                osi_strlcat( file_path, directory.filename,
                             sizeof( file_path ) );

                if( osi_file_is_regular_file( file_path ) )
                {
                    int result;
                    OSI_DATABASE_BRIEF *db_brief;

                    db_brief = osi_malloc( sizeof( OSI_DATABASE_BRIEF ) );

                    result = osi_host_read_database_brief_with_name( db_brief,
                                                         host->name,
                                                         directory.filename ) ;

                    if( result == OSI_DB_OK )
                    {
                        list_add( host->databases, db_brief );
                    }

                    else
                    {
                        log_error( LOG_MEDIUM, host->name,
                                   "reading database: %s.",
                                  file_path );
                    }
                }

            } while( osi_get_next_file( &directory ) );

            osi_close_directory( &directory );
        }

        else
        {
            log_error( LOG_MEDIUM, host->name, "no database directory." );
        }
    }

    return ( list_get_size( host->databases ) );
}

FILE * osi_host_open_db_file_read_only( const char *host, const char *name )
{    
    char path[MAX_PATH_LENGTH];
    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    /* construct full path to this file and open the database. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, name, sizeof( path ) );
    
    return osi_fopen( path, "r", 0 );;
}

unsigned long osi_host_determine_db_file_size( const char *host,
                                               const char *name )
{
    unsigned long size = -1;
    struct stat file_stats;
    
    char path[MAX_PATH_LENGTH];
    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    if( ( host == NULL ) || ( name == NULL ) )
    {
        return size;
    }

    /* construct full path to this file and open the database. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, name, sizeof( path ) );
    
    /* stat file to get file size. */
    
    if( stat( path, &file_stats ) == 0 )
    {
        size = (unsigned long)file_stats.st_size;
    }
    
    return size;
}



unsigned long osi_host_get_db_error_count( const char *host, const char *name )
{
    int status;
    unsigned long count = 0;

    OSI_DB db;
    DB_HASH_STAT *db_stats = NULL;

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

    if( ( host == NULL ) || ( name == NULL ) )
    {
        return count;
    }

    /* construct full path to this file and open the database. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, name, sizeof( path ) );

    memset( &db, 0, sizeof( db ) );
    osi_strlcpy( db.path, path, sizeof( db.path ) );
    status = osi_scan_db_open( &db, OSI_DB_READ );
    
    if( status == OSI_OK )
    {
        count = osi_scan_db_get_error_count( &db );
        osi_scan_db_close( &db );
    }
    
    return count;
}

unsigned long osi_host_get_db_record_count( const char *host, const char *name )
{
    int status;
    unsigned long count = 0;

    OSI_DB db;
    DB_HASH_STAT *db_stats = NULL;

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

    if( ( host == NULL ) || ( name == NULL ) )
    {
        return count;
    }

    /* construct full path to this file and open the database. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, name, sizeof( path ) );

    memset( &db, 0, sizeof( db ) );
    osi_strlcpy( db.path, path, sizeof( db.path ) );
    status = osi_scan_db_open( &db, OSI_DB_READ );

    if( status == OSI_OK )
    {
        count = osi_scan_db_get_record_count( &db );
        osi_scan_db_close( &db );
    }

    return count;
}


osi_bool osi_host_database_exists( const char *host,
                                   const char *name )
{
    char path[MAX_PATH_LENGTH];
    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    if( ( host == NULL ) || ( name == NULL ) )
    {
        return FALSE;
    }

    /* construct full path to this file and open the database. */

    osi_strlcpy( path, root_path, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOSTS_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, host, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, HOST_DATABASE_DIRECTORY_NAME, sizeof( path ) );
    osi_strlcat( path, path_separator, sizeof( path ) );
    osi_strlcat( path, name, sizeof( path ) );

    return osi_file_exists( path );
}


