
/******************************************************************************
**
**  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_utilities.c
**  Date:    June 3, 2002
**
**  Author:  Brian Wotring
**  Purpose: common operations.
**
******************************************************************************/

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

#include "common.h"

#include "md_hosts.h"
#include "md_config.h"
#include "md_compare.h"
#include "md_database.h"
#include "md_control.h"
#include "md_utilities.h"

#include "logging.h"


extern char root_path[MAX_PATH_LENGTH];
extern SSL_CTX *ssl_context;

/******************************************************************************
**
**    Function: perform_session_key_negotiation
**
**    Purpose:  communicate with the daemon to verify it's session key.  If
**              the process doesn't have a key, we give it one, otherwise
**              we verify that the hash matches the key we are given, if not,
**              return false.
**
******************************************************************************/

osi_bool perform_session_key_negotiation( SSL *ssl, OSI_HOST *host )
{
    int length;
    osi_uint16 type;

    MESSAGE message;

    if( ( ssl == NULL ) && ( host == NULL ) )
    {
        return FALSE;
    }

    /* first, we expect to receive a message from the daemon that */
    /* contains the actual session key.                           */

    if( osi_ssl_read_message( &message, ssl ) != MESSAGE_OK )
    {
        log_error( LOG_MEDIUM, host->host,
                   "daemon did not present session key." );

        return FALSE;
    }

    type   = GET_MESSAGE_TYPE( ((MESSAGE *)&message ) );
    length = GET_MESSAGE_LENGTH( ((MESSAGE *)&message ) );

    /* we weren't expecting this type of message, negotiation has failed. */

    if( type != MESSAGE_TYPE_SESSION_KEY )
    {
        log_error( LOG_MEDIUM, host->name,
                   "unexpected message type, expecting a session key." );

        return FALSE;
    }

    /* we have a valid session message from the daemon, we were given */
    /* a key so verify it, if it matches, our job is done, we have    */
    /* negotiated and passed.                                         */

    if( length > 0 )
    {
        unsigned char checksum[HOST_SESSION_KEY_HASH_LENGTH];

        /* generate a hash of the presented session key. */

        if( generate_session_key_hash( message.data, length, checksum,
                                       sizeof( checksum ) ) == FALSE )
        {
            log_error( LOG_MEDIUM, host->name,
                       "unable to checksum session key." );

            return FALSE;
        }

        /* if we don't have a hash, we log this and write it to the file. */

        if( strlen( host->session_key ) == 0 )
        {
            log_warning( LOG_LOW, host->name,
                   "daemon has key we have no record of, updating host file." );

            osi_strlcpy( host->session_key, checksum,
                         sizeof( host->session_key ) );

            osi_write_host( host );

            return TRUE;
        }

        /* otherwise, compare to the hash we have in our host file. */

        if( strcmp( host->session_key, checksum ) == 0 )
        {
            log_info( LOG_HIGH, host->name,
                      "received valid session key from daemon." );

            return TRUE;
        }

        else
        {
            log_warning( LOG_LOW, host->name,
                         "received invalid key from daemon." );

            return FALSE;
        }
    }

    /* the daemon is claiming not to have a key.  this means it never */
    /* had one, or if there doesn't exist a hash, then we can assume  */
    /*  it never had one.                                             */

    else
    {
        unsigned char key[HOST_SESSION_KEY_LENGTH];

        log_info( LOG_MEDIUM, host->name,
                  "daemon doesn't have a session key, assigning it one." );

        if( generate_session_key( HOST_SESSION_KEY_LENGTH, key,
                                  sizeof( key ) ) == FALSE )
        {
            log_error( LOG_MEDIUM, host->name,
                       "unable to generate session key for remote daemon." );

            return FALSE;
        }

        initialize_message( &message, MESSAGE_TYPE_SESSION_KEY );
        message_set_payload( &message, key, HOST_SESSION_KEY_LENGTH, 0 );

        /* first, see if we think this host should have a key. */

        if( strlen( host->session_key ) > 0 )
        {
            log_warning( LOG_LOW, host->host,
     "scanning daemon lost its session key, most likely the daemon was restarted." );
        }

        if( osi_ssl_write_message( &message, ssl ) == MESSAGE_OK )
        {
            unsigned char checksum[HOST_SESSION_KEY_HASH_LENGTH];
            log_info( LOG_LOW, host->host, "new session key assigned." );

            /* now save it locally in the host's config file, we only */
            /* save the hash, not the actual key.                     */

            if( generate_session_key_hash( key, HOST_SESSION_KEY_LENGTH,
                                       checksum, sizeof( checksum ) ) == FALSE )
            {
                log_error( LOG_LOW, host->host,
                           "unable to hash new session key." );

                return FALSE;
            }

            osi_strlcpy( host->session_key, checksum,
                         sizeof( host->session_key ) );

            osi_write_host( host );

            return TRUE;
        }

        else
        {
            log_error( LOG_LOW, host->host, 
                       "failed to send session key to scanning daemon." );
        }
    }

    return FALSE;
}


int osi_host_push_config( const char *host_name, const char *name )
{
    int index;
    int result = OSI_ERROR_NULL_ARGUMENT;

    int connection_socket = 0;
    configuration *cfg = NULL;

    string_list *lines = NULL;

    MESSAGE cfg_data;
    MESSAGE res;

    SSL *connection_ssl = NULL;
    OSI_HOST *host = NULL;

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

    cfg = osi_host_read_config_with_name( host_name, name );

    if( ( cfg == NULL ) || ( cfg->data == NULL ) )
    {
        return OSI_ERROR_CONFIG_DOES_NOT_EXIST;
    }

    if( ( host = osi_read_host( host_name ) ) == NULL )
    {
        result = OSI_ERROR_HOST_DOES_NOT_EXIST;
        goto exit_gracefully;
    }

    lines = cfg->data;
    connection_ssl = osi_ssl_connect_to_host_on_port( host->host,
                                                      DAEMON_PORT,
                                                      ssl_context,
                                                      FALSE );

    if( connection_ssl == NULL )
    {
        result = OSI_ERROR_UNABLE_TO_CONNECT;
        goto exit_gracefully;
    }

    connection_socket = SSL_get_fd( connection_ssl );

    if( perform_session_key_negotiation( connection_ssl, host ) == FALSE )
    {
        result = OSI_ERROR_SESSION_KEY_NEGOTIATION_FAILED;

        log_error( LOG_MEDIUM, host->host,
                   "session key negotiation failed." );

        goto exit_gracefully;

    }

    initialize_message( &cfg_data, MESSAGE_TYPE_CONFIG_DATA_FIRST );

    result = message_set_payload( &cfg_data, lines->list[0],
                                      strlen( lines->list[0] ), 0 );

    if( result == MESSAGE_OK )
    {
        osi_ssl_write_message( &cfg_data, connection_ssl );
    }

    for( index = 1; (unsigned int)index < lines->size; index++ )
    {
        if( (unsigned int)( index + 1 ) != lines->size )
        {
            initialize_message( &cfg_data, MESSAGE_TYPE_CONFIG_DATA );
        }

        else
        {
            initialize_message( &cfg_data, MESSAGE_TYPE_CONFIG_DATA_LAST );
        }

        message_set_payload( &cfg_data, lines->list[index],
                             strlen( lines->list[index] ), index );

        osi_ssl_write_message( &cfg_data, connection_ssl );
    }

    /* now try to get the success message from the daemon. */

    if( osi_ssl_read_message( &res, connection_ssl ) == MESSAGE_OK )
    {
        result = OSI_OK;
    }

    else
    {
        result = OSI_ERROR_NO_RESPONSE;
    }

exit_gracefully:

    if( connection_socket != 0 )
    {
        osi_close_socket( connection_socket );
    }

    if( connection_ssl != NULL )
    {
        osi_ssl_destroy( &connection_ssl );
    }

    if( cfg != NULL )
    {
        osi_config_destroy( cfg );
    }

    if( host != NULL )
    {
        osi_host_destroy( host );
    }

    return result;
}   


int osi_host_push_base_db_config( const char *host_name )
{
    int result = OSI_ERROR_NULL_ARGUMENT;

    OSI_HOST *host;
    OSI_DATABASE_BRIEF db_brief;

    if( host_name == NULL )
    {
        return result;
    }

    if( ( host = osi_read_host( host_name ) ) == NULL )
    {
        return OSI_ERROR_HOST_DOES_NOT_EXIST;
    }
   
    /* read the database brief to get the config name. */

    result = osi_host_read_database_brief_with_name( &db_brief, host_name,
                                                     host->base_db );

    if( result != OSI_DB_OK )
    {
        result = OSI_ERROR_BASE_DB_NOT_FOUND;
        goto exit_gracefully;
    }

    /* now get the config name and attempt to push it. */

    result = osi_host_push_config( host_name, db_brief.config_name );

exit_gracefully:

    osi_host_destroy( host );
    return result;
}

