
/******************************************************************************
**
**  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:    osirisd.c
**  Date:    February 17, 2002
**
**  Author:  Brian Wotring
**  Purpose: scanning daemon main implementation
**
******************************************************************************/

#include "libosiris.h"
#include "libfileapi.h"
#include "rootpriv.h"
#include "common.h"

#ifndef WIN32

#include <sys/wait.h>
#include <syslog.h>
#include <signal.h>
#include <unistd.h>
#include <netinet/in.h>

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

#ifdef HAVE_SYS_UTSNAME_H
#include <sys/utsname.h>
#endif

#endif /* END NOT WIN32 */

#include "scanner.h"
#include "logging.h"
#include "unix_utilities.h"

#ifdef WIN32
#include "windows_service.h"
#endif

#include "osirisd.h"


/******************************************************************************
**
**    Function: main
**
**    Purpose:  system driver for this daemon.  we parse the arguments
**	        	then kick off the main event loop.
**
******************************************************************************/

int main( int argument_count, char *argument_list[] )
{
    initialize_winsock();
    initialize_priv_pipes();

    fork_daemons();
    set_platform_info();

    parse_arguments( argument_count, argument_list );

    become_windows_service();
    run( NULL );

    return 0;
}

/******************************************************************************
**
**    Function: run
**
**    Purpose:  main event loop.  this is necessary as a seperate
**		        function to give the windows service an entry point
**		        into the functionality of this daemon.
**
******************************************************************************/

THREAD_FUNCTION_TYPE run( void *unused )
{
    initialize_logger();
    initialize_root_path();
    initialize_ssl();
    initialize_signals();

    start_server();
    process();
    halt( EXIT_CODE_NORMAL );

    return THREAD_FUNCTION_RETURN;
}



/******************************************************************************
**
**    Function: parse_arguments
**
**    Purpose:  read in any command line arguments for this daemon.
**
******************************************************************************/

void parse_arguments( int argument_count, char *argument_list[] )
{
    int count;

    for( count = 1; count < argument_count; count++ )
    {
        if( ( strcmp( argument_list[count], "-h" ) ==0 ) ||
            ( strcmp( argument_list[count], "--help" ) == 0 ) )
        {
            print_usage();
            halt(0);
        }

        if( ( strncmp( argument_list[count], "-l", 2 ) == 0 ) )
        {
            if( strlen( argument_list[count] ) > 2 )
            {
                log_level = atoi( &argument_list[count][2] );
            }

            else if( count < ( argument_count - 1 ) )
            {
                count++;
                log_level = atoi( argument_list[count] );
            }

            /* some sanity. */

            if( log_level < 0 )
            {
                log_level = 0;
            }

            log_info( LOG_MEDIUM, NULL, "log level: %d", log_level );
        }

#ifdef WIN32

        else if( strcmp( argument_list[count], "-i" ) == 0 )
        {
            /* install as a windows service, then exit. */

            install_windows_service();
            halt(0);
        }

        else if( strcmp( argument_list[count], "-u" ) == 0 )
        {
            uninstall_windows_service();
            halt(0);
        }

        else if( strcmp( argument_list[count], "-d" ) == 0 )
        {
			run_as_daemon = TRUE;
	    }

#endif
        else
        {
            print_usage();
            halt(-1);
        }
    }
}


/******************************************************************************
**
**    Function: become_windows_service
**
**    Purpose:  start hook into windows service, then die.
**
******************************************************************************/

void become_windows_service()
{
#ifdef WIN32

    SERVICE_TABLE_ENTRY dispatchTable[] =
    {
        { TEXT(SERVICE_NAME), (LPSERVICE_MAIN_FUNCTION)service_main },
        { NULL, NULL}
    };

    if( run_as_daemon == FALSE )
    {
        if( !StartServiceCtrlDispatcher(dispatchTable) )
        {
            AddToMessageLog(TEXT("StartServiceCtrlDispatcher failed."));
        }
    }

#endif
}

/******************************************************************************
**
**    Function: halt
**
**    Purpose:  perform necessary cleanup and calls exit with given code.
**
******************************************************************************/

void halt( int code )
{
    shutdown_server();
    shutdown_ssl();
    close_logger();
    
#ifdef WIN32
    if( stop_scan_event )
    {
        CloseHandle( stop_scan_event );
    }
#endif

#ifdef USE_PRIVSEP
    close( rootpriv_pipe[0] );
    close( rootpriv_pipe[1] );
#endif

    log_info( LOG_LOW, NULL, "server is shut down, daemon exiting." );

    exit( code );
}


/******************************************************************************
**
**    Function: start_server
**
**    Purpose:  bind to the specified or default port and listen for requests.
**
******************************************************************************/

void start_server()
{
    server_socket = osi_listen_on_local_port( SERVER_PORT, LISTEN_QUEUE_MAX );

    if( server_socket <= 0 )
    {
        log_error( LOG_LOW, NULL, "server unable to bind to port: %d.",
                   SERVER_PORT );
    
        osi_print_stderr( "server unable to bind to port (%d).", SERVER_PORT );
        halt( EXIT_CODE_ERROR );
    }

    /* set our initial startup time value now.  we set our server      */
    /* socket to be non-blocking in case we are really busy and an     */
    /* incoming connection is closed between the select() and accept() */
    /* function calls.   it will prevent accept() from blocking.       */

    start_time = osi_get_time();
    osi_set_socket_non_blocking( server_socket );

    log_info( LOG_LOW, NULL, "server started on port: %d.",
                 SERVER_PORT );
}

/******************************************************************************
**
**    Function: shutdown_server
**
**    Purpose:  perform necessary cleanup for server.
**
******************************************************************************/

void shutdown_server()
{
    osi_close_socket( server_socket );
}

/******************************************************************************
**
**    Function: shutdown_ssl
**
**    Purpose:  perform necessary cleanup for SSL library.
**
******************************************************************************/

void shutdown_ssl()
{
    if( ssl_context != NULL )
    {
        SSL_CTX_free( ssl_context );
    }

    ERR_free_strings();
    ERR_remove_state(0);

    if( bio_error != NULL )
    {
        BIO_free( bio_error );
    }
}


/******************************************************************************
**
**    Function: process
**
**    Purpose:  main event loop for message processing.  This event loop is
**		        obviously infinite and will process events as they are received,
**		        not concurrently.
**
******************************************************************************/

void process()
{
    for(;;)
    {
        check_for_signals();
        wait_for_data();

#ifdef HAVE_THREADS
        cleanup_threads();
#endif

        /* we have data waiting, see if it is to the server.  if it */
        /* is, we read and process the message.                     */

        if( INCOMING_SERVER_REQUEST() )
        {
            process_new_request();
        }

        if( state == DAEMON_STATE_RECEIVING )
        {
            if( INCOMING_CONFIG_REQUEST() )
            {
                process_config_request();
            }
        }
    }
}

void process_new_request()
{
    int result = 0;
    MESSAGE message;

    struct sockaddr_in client;
    socklen_t client_length;

    char host[MAX_HOSTNAME_LENGTH];

    client_length = sizeof( client );

    connection_socket = osi_accept( server_socket,
                                    ( struct sockaddr * )&client,
                                     &client_length );

    if( connection_socket <= 0 )
    {
        log_error( LOG_MEDIUM, NULL, "error accepting new request." );
        return;
    } 

    if( inet_ntop( AF_INET, &client.sin_addr, host, sizeof( host ) ) )
    {
        log_info( LOG_HIGH, NULL, "connection from: %s", host );
    }

    connection_ssl = osi_ssl_accept_with_socket( connection_socket,
                                                 ssl_context, TRUE ); 

    if( connection_ssl == NULL )
    {
        log_error( LOG_MEDIUM, NULL, "error with SSL negotiation." );
        osi_close_socket( connection_socket );
    
        return;
    }

    /* if authentication failed, we drop this request right now. */

    if( cert_authentication_failed )
    {
        goto exit_gracefully;
    }

    /* before we can receive messages, we need to provide the */
    /* MH our session key or receive one if we don't have one */
    /* already then receive the actual message from the MH.   */

    perform_session_key_negotiation();
    result = osi_ssl_read_message( &message, connection_ssl );

    if( result == MESSAGE_OK )
    {
        process_incoming_message( &message );
    }

    else
    {
        log_error( LOG_MEDIUM, NULL, "reading message: %s.",
                   osi_get_name_for_error( result ) );
    }

exit_gracefully:

    if( connection_ssl )
    {
        SSL_shutdown( connection_ssl );
        SSL_free( connection_ssl );
    }

    osi_close_socket( connection_socket );
}

void process_config_request()
{
    int result;
    MESSAGE message;

    result = osi_ssl_read_message( &message, config_ssl );

    if( result == MESSAGE_OK )
    {
        process_incoming_message( &message );
    }

    /* if socket is closed, shut down this config receiving session. */

    else if( result == OSI_ERROR_SOCKET_CLOSED )
    {
        reset_config();
        set_state( DAEMON_STATE_IDLE );
        osi_ssl_destroy( &config_ssl );
    }
}

/******************************************************************************
**
**    Function: process_incoming_message
**
**    Purpose:  based upon type, utilize this message.
**
******************************************************************************/

int process_incoming_message( MESSAGE *message )
{
    int result = OSI_OK;

    if( message != NULL )
    {
        /* these are the only incoming message types we care about */
        /* at this point in time.                                  */

        switch( (int)GET_MESSAGE_TYPE( message ) )
        {
            case MESSAGE_TYPE_STATUS_REQUEST:

                log_info( LOG_HIGH, NULL, "incoming: status" );
                issue_status_response();

                break;

            case MESSAGE_TYPE_START_SCAN:

                log_info( LOG_HIGH, NULL, "incoming: start-scan" );
                start_scan();

                break;

            case MESSAGE_TYPE_STOP_SCAN:

                log_info( LOG_HIGH, NULL, "incoming: stop-scan" );
                stop_scan();

                break;

            case MESSAGE_TYPE_DROP_CONFIG:

                log_info( LOG_HIGH, NULL, "incoming: drop-config" );
                drop_config();

                break;

            case MESSAGE_TYPE_CONFIG_DATA_FIRST:

                log_info( LOG_HIGH,NULL,"incoming: start-receive-config" );
                start_config_push( message );

                break;

            case MESSAGE_TYPE_CONFIG_DATA:

                log_info( LOG_HIGH, NULL, "incoming: config-data" );
                process_config_data( message );

                break;

            case MESSAGE_TYPE_CONFIG_DATA_LAST:

                log_info( LOG_HIGH, NULL,"incoming: finish-receive-config" );
                stop_config_push( message );

                break;

            default:

                log_error( LOG_HIGH, NULL, "received unknown message type!" );
                result = OSI_ERROR_UNKNOWN_MESSAGE_TYPE;

                break;
        }
    }

    return result;
}


/******************************************************************************
**
**    Function: process_outgoing_message
**
**    Purpose:  based upon type, utilize this message.
**
******************************************************************************/

int process_outgoing_message( MESSAGE *message )
{
    int result = OSI_ERROR_NULL_ARGUMENT;
    osi_uint16 type;

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

    type = GET_MESSAGE_TYPE( message );

    /* these are the only message we care about sending, so far. */

    switch( (int)type )
    {
        case MESSAGE_TYPE_SCAN_DATA_FIRST:

            log_info( LOG_HIGH, NULL, "outgoing: scan-data-begin-stream" );

            if( scan_data_ssl != NULL )
            {
                result = osi_ssl_write_message( message, scan_data_ssl );

                if( result != MESSAGE_OK )
                {
                    log_error( LOG_MEDIUM,NULL,"unable to start scan stream." );
                    stop_scan();
                }
            }

            break;

        case MESSAGE_TYPE_SCAN_DATA:

            log_info( LOG_HIGH, NULL, "outgoing: scan-data" );

            if( scan_data_ssl != NULL )
            {
                result = osi_ssl_write_message( message, scan_data_ssl );

                if( result != MESSAGE_OK )
                {
                    log_error( LOG_MEDIUM, NULL, "unable to send scan-data." );
                    stop_scan();
                }
            }

            break;

        case MESSAGE_TYPE_SCAN_DATA_LAST:

            log_info( LOG_HIGH, NULL, "outgoing: scan-stream end." );

            if( scan_data_ssl > 0 )
            {
                result = osi_ssl_write_message( message, scan_data_ssl );

                if( result == MESSAGE_OK )
                {
                    osi_close_socket( scan_data_socket );
                    osi_ssl_destroy( &scan_data_ssl );
                }

                else
                {
                    log_error( LOG_MEDIUM, NULL,
                               "unable to send last scan data" );

                    stop_scan();
                }
            }

            break;

        case MESSAGE_TYPE_ERROR:

            log_info( LOG_HIGH, NULL, "outgoing: error message." );

            if( state == DAEMON_STATE_RECEIVING )
            {
                result = osi_ssl_write_message( message, config_ssl );

                if( result != MESSAGE_OK )
                {
                    log_error( LOG_MEDIUM, NULL,
                               "unable to send error message." );
                }
            }

            else if( state == DAEMON_STATE_SCANNING )
            {
                result = osi_ssl_write_message( message, scan_data_ssl );

                if( result != MESSAGE_OK )
                {
                    log_error( LOG_MEDIUM, NULL,
                               "unable to send scan-error message." );
                }
            }

            else
            {
                log_error( LOG_MEDIUM, NULL, "received an error while idle: %s",
                          message->data );
            }

            break;

        default:

            log_error( LOG_MEDIUM, NULL, "unknown outgoing message type (%d)!.",
                      (int)GET_MESSAGE_TYPE( message ) );

        break;
    }

    return result;
}


/******************************************************************************
**
**    Function: process_config_data
**
**    Purpose:  give message to configuration module for processing.
**              If this is a start of a new config, we destroy the current
**              config, set state to receiving.  Then add data to config
**              module.  If this is the end of a config, we run a validation
**              check and set state accordingly.
**
******************************************************************************/

void process_config_data( MESSAGE *message )
{
    int data_length;

    if( ( message != NULL ) && ( state != DAEMON_STATE_SCANNING ) )
    {
        data_length = GET_MESSAGE_LENGTH( message );
        osi_config_receive( config, message->data, data_length );
    }
}

/******************************************************************************
**
**    Function: perform_session_key_negotiation
**
**    Purpose:  communicate with the connecting process to verify our session
**		        key.  If we don't have one, our message will be empty.  If we
**		        don't have a current key, we expect one to be given to us.
**
******************************************************************************/

void perform_session_key_negotiation()
{
    MESSAGE message;
    int result;

    /* make sure we have a means of sending our key. */

    if( connection_ssl == NULL )
    {
        return;
    }

    /* create a message for the MH that contains our current key */
    /* and send it off for verification.                         */

    initialize_message( &message, MESSAGE_TYPE_SESSION_KEY );

    if( have_session_key )
    {
        message_set_payload( &message, session_key,
                             HOST_SESSION_KEY_LENGTH, 0 );
    }

    log_info( LOG_HIGH, NULL, "sending session-key to management host." );

    result = osi_ssl_write_message( &message, connection_ssl );

    if( result != MESSAGE_OK )
    {
        log_error( LOG_LOW, NULL,
                   "sending session key to management host." );
    }

    /* if we didn't have a session key, then we should expect one back  */
    /* we read the next message, if it's a key message, we save the key */
    /* otherwise, we drop it.                                           */

    if( !have_session_key )
    {
        log_info( LOG_HIGH, NULL, "waiting for session key." );

        result = osi_ssl_read_message( &message, connection_ssl );

        if( result == MESSAGE_OK )
        {
            int length;
            osi_uint16 type;

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

            /* save session key that was given to us, make sure it's    */
            /* not nulland that it can fit into our session key buffer. */

            if( ( type == MESSAGE_TYPE_SESSION_KEY ) &&
                ( length > 0 ) &&
                ( length <= sizeof( session_key ) ) )
            {
                memcpy( session_key, message.data, length );

                log_info( LOG_HIGH, NULL, "received new session key." );
                have_session_key = TRUE;
            }

            else
            {
                log_error( LOG_LOW, NULL, "no session key!" );
            }
        }

        else
        {
            log_error( LOG_MEDIUM, NULL, "reading message: %s.",
                       osi_get_name_for_error( result ) );
        }
    }
}


/******************************************************************************
**
**    Function: start_scan
**
**    Purpose:  become root, fork a process, become user.  The scanning
**              process will then start scanning, then exit.
**
******************************************************************************/

void start_scan()
{
    MESSAGE message;

    /* only start a scan if we are idle and have a valid config. */

    if( ( state == DAEMON_STATE_IDLE ) && config &&
        ( config->state == CONFIG_STATE_VALID ) )
    {
        set_state( DAEMON_STATE_SCANNING );

        /* keep these from getting closed and freed. */

        assume_connection_socket( &scan_data_socket );
        assume_ssl_connection( &scan_data_ssl );

        log_info( LOG_HIGH, NULL, "scanner started." );

        /* send back an okay message containing current */
        /* config id as a payload.                      */

        initialize_message( &message, MESSAGE_TYPE_SUCCESS );

        if( ( config != NULL ) && ( config->id != NULL ) &&
            ( strlen( config->id ) > 0 ) )
        {
             message_set_payload( &message, config->id,
                                  strlen( config->id ), 0 );
        }

        osi_ssl_write_message( &message, scan_data_ssl  );

        /* win32 and threads leaves here. */

        fork_scanning_process();

        /* NON-THREADED CODE ONLY HERE! parent frees up some */
        /* resources and continues back into the main        */
        /* processing loop for more requests.                */

        if( scanning_pid > 0 )
        {
            return;
        }

        /* child process continues here for non-threaded, child */
        /* starts scanning here.                                */

#ifndef WIN32
        scanning_pid = getpid();
#endif

        /* unix hosts call the handler here, they also exit within  */
        /* this function.                                           */

        run_scanner( NULL );
    }

    else
    {
        if( config == NULL )
        {
            log_error( LOG_HIGH, NULL, "cannnot start scan, no config." );

            osi_ssl_write_error_message( OSI_ERROR_NO_CONFIG,
                "cannot start scan at this time, we have no config.",
                 connection_ssl );
        }

        else if( state != DAEMON_STATE_IDLE )
        {
            log_error( LOG_HIGH,NULL,"cannot start scan, currently scanning." );

            osi_ssl_write_error_message( OSI_ERROR_DAEMON_NOT_IDLE,
                                       "cannot start scan, currently scanning.",
                                         connection_ssl );
        }

        else if( config->state != CONFIG_STATE_VALID )
        {
            log_error( LOG_HIGH, NULL, "cannot start scan, invalid config." );

            osi_ssl_write_error_message( OSI_ERROR_CONFIG_DOES_NOT_EXIST,
                                         "cannot start scan, invalid config.",
                                         connection_ssl );
        }
    }
}

/******************************************************************************
**
**    Function: run_scanner
**
**    Purpose:  called by the creation of a windows thread, or after a
**              fork on unix to start the scanning process.
**
******************************************************************************/

THREAD_FUNCTION_TYPE run_scanner( void *unused )
{
    int result;
    SCANNER *scanner;

    /* we are now the scanning thread or process, we need to  */
    /* set our state to scanning, when we are always scanning */
    /* since we are the scanning process/thread.              */

    set_state( DAEMON_STATE_SCANNING );

    /* create a scanner object and give it the config. */

    scanner = osi_scanner_new();
    osi_scanner_set_config( scanner, config );

    result = osi_scanner_run( scanner );

    if( result == SCANNER_OK )
    {
        log_info( LOG_MEDIUM, NULL, "scan completed succesfully." );
    }

    else
    {
        log_error( LOG_MEDIUM, NULL, "scan failed." );
    }

    /* destroy the scanner. */

    osi_sleep(2);
    osi_scanner_destroy( scanner );

    /* windows kills the scanning thread, other platforms exit the */
    /* scanning process here.                                      */

#ifdef WIN32

    set_state( DAEMON_STATE_IDLE );
    _endthreadex(0);

    return THREAD_FUNCTION_RETURN;

#else
    exit( EXIT_CODE_NORMAL );
#endif

}

/******************************************************************************
**
**    Function: stop_scan
**
**    Purpose:  tell scanning module to stop scanning.
**
******************************************************************************/

void stop_scan()
{
    int result = -1;

    if( ( state != DAEMON_STATE_SCANNING ) || ( scanning_pid == 0 ) )
    {
        osi_ssl_write_error_message( OSI_ERROR_NO_SCANNING_PROCESS,
                 "no scanning process exists, ignoring stop-scan message.",
                  connection_ssl );

        return;
    }

    /* for windows, we kill the thread, for others, */
    /* we kill the scanning process.                */

#ifdef WIN32

    if( stop_scan_event && SetEvent( stop_scan_event ) ) 
    { 
        result = 0;
    }

#else
    result = kill( scanning_pid, SIGKILL );
#endif

    if( result == 0 )
    {
        /* send error message to management host thread so this is */
        /* in the db as being a killed scan.                       */

        osi_ssl_write_error_message( OSI_ERROR_SCANNER_WAS_STOPPED,
                              "scanning process stopped by management host.",
                               scan_data_ssl );

        /* inform management host that this kill was succesfull. */

        osi_ssl_write_success_message( connection_ssl );
        set_state( DAEMON_STATE_IDLE );

        log_info( LOG_LOW, NULL, "scanning process was halted." );
    }

    else
    {
        switch( errno )
        {
            case ESRCH:
                osi_ssl_write_error_message( OSI_ERROR_NO_SCANNING_PROCESS,
                                             "stop failed, no scanner process.",
                                             connection_ssl );

                log_error( LOG_MEDIUM, NULL,
                           "stop failed, no scanner process." );

                set_state( DAEMON_STATE_IDLE );
                break;

            case EPERM:
                osi_ssl_write_error_message( OSI_ERROR_NO_PRIVLEDGES,
                                             "stop failed, invalid privleges.",
                                             connection_ssl );

                log_error( LOG_MEDIUM, NULL,"stop failed, invalid privleges." );

                break;

            default:
                osi_ssl_write_error_message( OSI_ERROR_SCAN_START_FAILED,
                                             "stop failed, unknown reason.",
                                             connection_ssl );

                log_error( LOG_MEDIUM, NULL, "stop failed." );
                break;
        }
    }

    /* threads should kill their own scan socket and SSL objects. */

#ifndef HAVE_THREADS
    osi_close_socket( scan_data_socket );
    osi_ssl_destroy( &scan_data_ssl );
#endif

    scanning_pid = 0;
}

/******************************************************************************
**
**    Function: drop_config
**
**    Purpose:  destroy current config module and create a new one, only
**		        if we aren't scanning, then send back success to requesting
**		        management application.
**
******************************************************************************/

void drop_config()
{
    if( reset_config() )
    {
        log_info( LOG_HIGH, NULL, "current config was dropped." );
        osi_ssl_write_success_message( connection_ssl );
    }

    else
    {
        log_error( LOG_HIGH, NULL, "config drop failed, scanning." );

        osi_ssl_write_error_message( OSI_ERROR_DAEMON_NOT_IDLE,
                                     "config drop failed, currently scanning.",
                                     connection_ssl );
    }
}

/******************************************************************************
**
**    Function: reset_config
**
**    Purpose:  destroy current config module and create a new one.
**
******************************************************************************/

osi_bool reset_config()
{
    osi_bool result = FALSE;

    if( state != DAEMON_STATE_SCANNING )
    {
        osi_config_destroy( config );
        config = osi_config_new();

        result = TRUE;
    }

    return result;
}

/*****************************************************************************
**
**    Function: start_config_push
**
**    Purpose:  receive first config message.  create new config, set server
**		        socket to be non-blocking so we can receive rest of config.
**
******************************************************************************/

void start_config_push( MESSAGE *message )
{
    int data_length;

    if( ( message != NULL ) && ( state != DAEMON_STATE_SCANNING ) )
    {
        data_length = GET_MESSAGE_LENGTH( message );
        set_state( DAEMON_STATE_RECEIVING );

        assume_connection_socket( &config_socket );
        assume_ssl_connection( &config_ssl );

        reset_config();
        osi_config_receive_first( config, message->data, data_length );

        log_info( LOG_HIGH, NULL, "receiving configuration data." );
    }
}

/******************************************************************************
**
**    Function: stop_config_push
**
**    Purpose:  receive last config message, set server socket to be blocking
**		        again.
**
******************************************************************************/

void stop_config_push( MESSAGE *message )
{
    int data_length;

    if( ( message != NULL ) && ( state != DAEMON_STATE_SCANNING ) )
    {
        data_length = GET_MESSAGE_LENGTH( message );
        set_state( DAEMON_STATE_IDLE );

        /* respond with a success message so the pusher knows we have it. */

        osi_ssl_write_success_message( config_ssl );

        osi_close_socket( config_socket );
        osi_ssl_destroy( &config_ssl );

        osi_config_receive_last( config, message->data, data_length );

        if( config->state == CONFIG_STATE_VALID )
        {
            config_time = osi_get_time();
        }

        log_info( LOG_HIGH, NULL, "received a new configuration." );
    }
}

/******************************************************************************
**
**    Function: issue_status_response
**
**    Purpose:  broadcast status message.
**
******************************************************************************/

void issue_status_response()
{
    int result;

    OSI_STATUS status;
    MESSAGE message;

    initialize_message( &message, MESSAGE_TYPE_STATUS_RESPONSE );
    current_time = osi_get_time();

    memset( &status, 0, sizeof( OSI_STATUS ) );

    status.start_time   = start_time;
    status.current_time = current_time;
    status.config_time  = config_time;

    if( config != NULL )
    {
        status.config_state = config->state;

        if( ( config->id != NULL ) && (  strlen( config->id ) > 0 ) )
        {
            osi_strlcpy( status.config_id, config->id,
                         sizeof( status.config_id ) );
        }
    }

    else
    {
        status.config_state = CONFIG_STATE_NULL;
    }

    status.daemon_state = state;

    osi_strlcpy( status.osiris_version, PROGRAM_VERSION,
                 sizeof( status.osiris_version ) );

    osi_strlcpy( status.os_name, os_name, sizeof( status.os_name ) );
    osi_strlcpy( status.os_version, os_version, sizeof( status.os_version ) );

    wrap_status( &status );

    message_set_payload( &message, &status, sizeof( OSI_STATUS ), 0 );
    result = osi_ssl_write_message( &message, connection_ssl );

    if( result != MESSAGE_OK )
    {
        log_error( LOG_MEDIUM, NULL, "issuing status message." );
    }

    log_info( LOG_HIGH, NULL, "issued status message." );
}


/******************************************************************************
**
**    Function: assume_connection_socket
**
**    Purpose:  set the passed in socket to the value of the connection
**		        socket and set the connection socket to -1, to keep it
**              from being closed.
**
******************************************************************************/

void assume_connection_socket( int *new_socket )
{
    if( new_socket != NULL )
    {
        *new_socket = connection_socket;
        connection_socket = -1;
    }
}


/******************************************************************************
**
**    Function: assume_ssl_connection
**
**    Purpose:  set the passed in ssl to the value of the global
**              ssl_connection and set the ssl_connection to NULL,
**              to keep it from being shut down.
**
******************************************************************************/

void assume_ssl_connection( SSL **new_ssl_connection )
{
    if( new_ssl_connection != NULL )
    {
        *new_ssl_connection = connection_ssl;
        connection_ssl = NULL;
    }
}

void handle_sigint( int signal )
{
#ifdef WIN32
    daemon_terminated = 1;
#else
    if( getpid() == scanning_pid )
    {
        scanner_terminated = 1;
    }

    else
    {
        daemon_terminated = 1;
    }
#endif

    received_sigint = 1;
}

void handle_sigterm( int signal )
{
#ifdef WIN32
    daemon_terminated = 1;
#else
    if( getpid() == scanning_pid )
    {
        scanner_terminated = 1;
    }

    else
    {
        daemon_terminated = 1;
    }
#endif

    received_sigterm = 1;
}

void handle_sigchld( int signal )
{
#ifndef WIN32
    received_sigchld = 1;
#endif
}

void handle_sigpipe( int signal )
{
#ifndef WIN32
    if( getpid() == scanning_pid )
    {
        exit( EXIT_CODE_ERROR );
    }

    received_sigpipe = 1;

#endif
}

void handle_sigkill( int signal )
{
#ifndef WIN32
    if( getpid() == scanning_pid )
    {
        scanner_terminated = 1;
    }

    else
    {
        daemon_terminated = 1;
    }

    received_sigkill = 1;
#endif
}

void check_for_signals()
{
    int status = 0;
    int exiting = ( received_sigint | received_sigterm | received_sigkill );

    if( received_sigint )
    {
        received_sigint = 0;
        log_info( LOG_MEDIUM, NULL, "SIGINT" );

        if( scanner_terminated )
        {
            scanner_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "scanner process was killed." );
        }

        if( daemon_terminated )
        {
            daemon_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "daemon process was killed." );
        }
    }

    if( received_sigterm )
    {
        received_sigterm = 0;
        log_info( LOG_MEDIUM, NULL, "SIGTERM" );

        if( scanner_terminated )
        {
            scanner_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "scanner process was killed." );
        }

        if( daemon_terminated )
        {
            daemon_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "daemon process was killed." );
        }
    }

#ifndef WIN32

    if( received_sigchld )
    {
        pid_t pid = 0;
        received_sigchld = 0;

        log_info( LOG_MEDIUM, NULL,"SIGCHLD, scanner process has terminated." );

        osi_close_socket( scan_data_socket );
        osi_ssl_destroy( &scan_data_ssl );

        /* clean up and resume idle state. */

        set_state( DAEMON_STATE_IDLE );

        /* wait for child to finish. */

        while( ( pid = waitpid( -1, &status, WNOHANG ) ) > 0 ||
               ( ( pid < 0 ) && ( errno == EINTR ) ) )
        ;
    }

    if( received_sigpipe )
    {
        received_sigpipe = 0;
        log_info( LOG_MEDIUM, NULL, "SIGPIPE" );
    }

    if( received_sigkill )
    {
        received_sigkill = 0;
        log_info( LOG_MEDIUM, NULL, "SIGKILL" );

        if( scanner_terminated )
        {
            scanner_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "scanner process was killed." );
        }

        if( daemon_terminated )
        {
            daemon_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "daemon process was killed." );
        }
    }

    /* if we are dying, we clean up some stuff if it is hanging around. */

    if( exiting )
    {
        osi_close_socket( scan_data_socket );
        osi_ssl_destroy( &scan_data_ssl );

        halt( EXIT_CODE_NORMAL );
    }
#endif
}


/******************************************************************************
**
**    Function: set_state
**
**    Purpose:  central point of state changes for this daemon.
**
******************************************************************************/

void cleanup_threads()
{
#ifdef WIN32

    int result;

    /* if we have a scanning thread handle, and it has signaled */
    /* that it has died, we close the handle.                   */

    if( scanning_thread_handle != 0 ) 
    {
        result = WaitForSingleObject( scanning_thread_handle, 0 );

        if( result == WAIT_OBJECT_0 )
        {
            log_info( LOG_HIGH, NULL, "cleaning up old scan handler thread." );

            CloseHandle( scanning_thread_handle );
            scanning_thread_handle = 0;
        }
    }

#endif
}

/******************************************************************************
**
**    Function: set_state
**
**    Purpose:  central point of state changes for this daemon.
**
******************************************************************************/

void set_state( int new_state )
{
    log_info( LOG_HIGH, NULL, "changing state to: %s",
              get_name_for_daemon_status( new_state ) );

    state = new_state;
}


/******************************************************************************
**
**    Function: wait_for_data
**
**    Purpose:  waits for data to arrive on either the config socket or
**		        the server socket, returns when data is available on either.
**
******************************************************************************/

void wait_for_data()
{
    int max_descriptors;
    int result;

wait_start:

    FD_ZERO( &read_set );

    /* always add the server socket to the set. */

    FD_SET( server_socket, &read_set );
    max_descriptors = server_socket;

    /* if we are receiving a config, add this socket to te */
    /* set of sockets to monitor for read.                  */

    if( state == DAEMON_STATE_RECEIVING )
    {
        FD_SET( config_socket, &read_set );
        max_descriptors = MAXIMUM( max_descriptors, config_socket );
    }

    log_info( LOG_HIGH, NULL, "waiting for connection." );
    result = select( ( max_descriptors + 1 ), &read_set, NULL, NULL, NULL );

    if( result < 0 )
    {
        if( errno == EINTR )
        {
            check_for_signals();
        }

        goto wait_start;
    }
}

/******************************************************************************
**
**    Function: initialize_logger
**
**    Purpose:  open syslog with specified priority.
**
******************************************************************************/

void initialize_logger()
{
#ifdef WIN32
    event_source = RegisterEventSource( NULL, TEXT(SERVICE_NAME) );
#else
    openlog( PROGRAM_NAME, SYSLOG_OPTIONS, SYSLOG_FACILITY );
#endif
}


/******************************************************************************
**
**    Function: close_logger
**
**    Purpose:  open syslog with specified priority.
**
******************************************************************************/

void close_logger()
{
#ifdef WIN32
    DeregisterEventSource( event_source );
#else
    closelog();
#endif
}

/******************************************************************************
**
**    Function: fork_daemons
**
**    Purpose:  fork the working daemon, and the root run daemon here.
**
******************************************************************************/

void fork_daemons()
{
#ifdef WIN32

#else
    pid_t pid = osi_create_process();

    if( pid == OSI_ERROR_UNABLE_TO_CREATE_PROCESS )
    {
        log_error( LOG_LOW, NULL, "spawning root daemon process." );
        osi_print_stderr( "error: unable to spawn root daemon process." );

        halt( EXIT_CODE_ERROR );
    }

    /* parent process dies. */

    if( pid > 0 )
    {
        exit( EXIT_CODE_NORMAL );
    }

    /* we are now the root daemon process, we fork a child    */
    /* that will handle requests and the buld of this daemon. */    

    root_pid = getpid();
    child_pid = osi_create_process();

    if( child_pid == OSI_ERROR_UNABLE_TO_CREATE_PROCESS )
    {
        log_error( LOG_LOW, NULL, "spawning daemon process." );
        osi_print_stderr( "error: unable to spawn daemon process." );

        halt( EXIT_CODE_ERROR );
    }

    /* parent goes off to handle requests...forever. */

    if( child_pid > 0 )
    {
        #ifdef USE_PRIVSEP
        handle_root_requests();
        #endif

        exit( EXIT_CODE_NORMAL );
    }

    /* child daemon will drop privs and go on to handle requests. */
    /* only drop privs if we are using priv separation.           */

    else
    {
        #ifdef USE_PRIVSEP
        struct passwd *pwd = getpwnam( OSIRIS_USERNAME );

        if( pwd == NULL )
        {
            fprintf( stderr, "unable to locate osiris user." );
            log_error( LOG_LOW, NULL, "unable to locate osiris user." );

            exit( EXIT_CODE_ERROR );
        }

        if( setgid( pwd->pw_gid ) != 0 )
        {
            fprintf( stderr, "unable to drop group privs." );
            log_error( LOG_LOW, NULL, "unable to drop group privs." );

            exit( EXIT_CODE_ERROR );
        }

        if( setuid( pwd->pw_uid ) != 0 )
        {
            fprintf( stderr, "unable to drop user privs." );
            log_error( LOG_LOW, NULL, "unable to drop user privs." );

            exit( EXIT_CODE_ERROR ); 
        }

        #endif /* USE_PRIVSEP */
    }

#endif
}

/******************************************************************************
**
**    Function: fork_scanning_process
**
**    Purpose:  start a child process to perform a scan, as root.
**
******************************************************************************/

void fork_scanning_process()
{
#ifdef WIN32

    scanning_thread_handle = (HANDLE)_beginthreadex( NULL, 0, &run_scanner,
                                                     NULL, 0, &scanning_pid );

    if( scanning_thread_handle != 0 )
    {
        log_info( LOG_MEDIUM, NULL, "scanning process started." );
    }

    else
    {
        scanning_pid = 0;
        log_error( LOG_LOW, NULL, "unable to spawn scanning thread." );
    }

#else

    scanning_pid = osi_create_process();

    if( scanning_pid == OSI_ERROR_UNABLE_TO_CREATE_PROCESS )
    {
        log_error( LOG_MEDIUM, NULL, "starting scanning process." );
        return;
    }

#endif
}

/******************************************************************************
**
**    Function: initialize_signals
**
**    Purpose:  initializes all of the signal handler callbacks we need to
**		        intercept.  We'll add more later.
**
******************************************************************************/

void initialize_signals()
{
#ifdef WIN32

    /* create a manual event that is not initially signaled, and */
    /* will automatically reset itself after being detected.     */

    stop_scan_event = CreateEvent( NULL, FALSE, FALSE, "stop" );

#else
    signal( SIGINT, handle_sigint );
    signal( SIGTERM, handle_sigterm );
    signal( SIGCHLD, handle_sigchld );
    signal( SIGPIPE, handle_sigpipe );
    signal( SIGKILL, handle_sigkill );
#endif
}

/******************************************************************************
**
**    Function: initialize_root_path
**
**    Purpose:  try to find the osiris home directory, where we can store
**		        the cert, if it doesn't exist, we bail.
**
******************************************************************************/

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

    if ( osi_osiris_home_directory_exists() == FALSE )
    {
        log_error( LOG_LOW, NULL, "finding osiris root directory." );
        osi_print_stderr( "error: unable to find osiris root directory." );

        osi_print_stderr( "" );
        osi_print_stderr(
                "The osiris root directory where the root certificate is" );
        osi_print_stderr(
                "stored does not exist.  This directory must be created " );
        osi_print_stderr(
                "manually, e.g. /usr/local/osiris or c:\\WINNT\\osiris and" );
        osi_print_stderr(
                "must be owned by the same user that this daemon" );
        osi_print_stderr(
                "is run as.  This root directory should not have world read" );
        osi_print_stderr(
                "or write permissions." );
        osi_print_stderr( "" );

        halt( -1 );
    }

    if( osi_get_osiris_home_directory( path, sizeof( path ) ) )
    {
        osi_strlcpy( root_cert_file, path, sizeof( root_cert_file ) );
        osi_strlcat( root_cert_file, path_separator, sizeof( root_cert_file ) );
        osi_strlcat( root_cert_file, ROOT_CERT_NAME, sizeof( root_cert_file ) );
    }

    log_info( LOG_MEDIUM, NULL, "using root directory: %s", path );
}

/******************************************************************************
**
**    Function: initialize_ssl
**
**    Purpose:  initializes the SSL library, and creates a connection
**		        context that we will use for all connections.
**
******************************************************************************/

void initialize_ssl()
{
    bio_error = BIO_new_fp( stderr, BIO_NOCLOSE );

    SSL_library_init();
    SSL_load_error_strings();

    ssl_context = SSL_CTX_new( SSLv23_client_method() );

    if( ssl_context == NULL )
    {
        ERR_print_errors( bio_error );

        log_error( LOG_LOW, NULL, "initializing SSL." );
        osi_print_stderr( "error: initializing SSL." );

        halt( EXIT_CODE_ERROR );
    }

    /* if we can't load the certificate, we won't do any authentication */
    /* on the server cert, we exit here, this is required.              */

    if( SSL_CTX_load_verify_locations( ssl_context,
                                       root_cert_file, NULL ) != 1 )
    {
        log_error( LOG_MEDIUM, NULL, "loading root cert: %s.",
                   root_cert_file );

        /* set flag to save peer cert next time we see one. */

        save_peer_cert = TRUE;
    }

    SSL_CTX_set_verify( ssl_context, ( SSL_VERIFY_NONE ), ssl_verify_callback );
    SSL_CTX_set_verify_depth( ssl_context, OSIRISD_SERVER_CERT_CHAIN_DEPTH );
    SSL_CTX_set_options( ssl_context, OSIRISD_SSL_OPTIONS );

    if( SSL_CTX_set_cipher_list( ssl_context, OSIRISD_CIPHER_LIST ) != 1 )
    {
        log_error( LOG_LOW, NULL, "setting SSL cipher list." );
        osi_print_stderr( "error: setting cipher list." );

        halt( -1 );
    }

    log_info( LOG_HIGH, NULL, "SSL server running." );
}

/******************************************************************************
**
**    Function: ssl_verify_callback
**
**    Purpose:  callback to get and display detailed error information
**		        in the case of a handshake failure, probably to send it
**		        to the logs.
**
******************************************************************************/

int ssl_verify_callback( int ok, X509_STORE_CTX *store )
{
    char data[256];

    if( ( !ok ) && ( save_peer_cert == FALSE ) )
    {
        X509 *cert = X509_STORE_CTX_get_current_cert( store );

        int depth = X509_STORE_CTX_get_error_depth( store );
        int err = X509_STORE_CTX_get_error( store );

        log_error( LOG_LOW, NULL, "cert authentication failure." );

        X509_NAME_oneline( X509_get_issuer_name( cert ), data, sizeof( data ) );
        log_error( LOG_LOW, NULL, "issuer = %s", data );

        X509_NAME_oneline( X509_get_subject_name( cert ),
                           data, sizeof( data ) );

        log_error( LOG_LOW, NULL, "subject = %s", data );

        if( osi_get_x509_fingerprint( cert, data, sizeof( data ) ) != NULL )
        {
            log_error( LOG_LOW, NULL, "MD5 fingerprint: %s.", data );
        }

        cert_authentication_failed = TRUE;
    }

    else
    {
        cert_authentication_failed = FALSE;
    }

    if( save_peer_cert )
    {
        X509 *cert = X509_STORE_CTX_get_current_cert( store );

        if( osi_get_x509_fingerprint( cert, data, sizeof( data ) ) != NULL )
        {
            log_info( LOG_MEDIUM, NULL,
                  "no loaded root cert, trusing cert with fingerprint: %s.",
                         data );
        }

        if( osi_write_x509_to_path( cert, root_cert_file,
                                    ROOT_CERT_PERMISSIONS ) == OSI_OK )
        {
            log_info( LOG_MEDIUM, NULL,
		      "saved root cert: %s.", root_cert_file );

            save_peer_cert = FALSE;

            /* now since we just saved the root, we load it for  */
            /* further communications.                           */

            if( SSL_CTX_load_verify_locations( ssl_context,
                                               root_cert_file, NULL ) != 1 )
            {
                log_error( LOG_LOW, NULL, "loading root cert: %s.",
                           root_cert_file );
            }
        }

        else
        {
            log_error( LOG_LOW, NULL, "saving root cert: %s.",
                         root_cert_file );

            /* we can't save the root cert, we are useless    */
            /* so we flag authentication failure, we cannot   */
            /* trust anybody.                                 */

            log_error( LOG_LOW, NULL,
        "we cannot save the root cert, so we cannot trust any connections." );

            cert_authentication_failed = TRUE;
        }
    }

    return ok;
}

/******************************************************************************
**
**    Function: initialize_priv_pipes
**
**    Purpose:  create some anonymous pipes that will be used to communicate
**              between the root and child process.
**
******************************************************************************/

void initialize_priv_pipes()
{
    /* the rules are that the host must have support for unix       */
    /* domain sockets for privsep to be enabled, we make sure here. */

#if defined( USE_PRIVSEP) && defined(HAVE_SOCKETPAIR)
    if( socketpair( AF_UNIX, SOCK_STREAM, 0, rootpriv_pipe ) != 0 )
    {
        log_error( LOG_LOW, NULL, "creating priv pipe!" );
        halt( EXIT_CODE_ERROR );
    }
#endif
}


/******************************************************************************
**
**    Function: initialize_winsock
**
**    Purpose:  windows needs this, for obvious reasons.
**
******************************************************************************/

void initialize_winsock()
{
#ifdef WIN32
    WORD wVersionRequested;
    WSADATA wsaData;
    int error;

    wVersionRequested = MAKEWORD( 2, 2 );

    error = WSAStartup( wVersionRequested, &wsaData );

    if ( error != 0 )
    {
            exit( EXIT_CODE_ERROR );
    }

    /* Confirm that the WinSock DLL supports 2.2.*/
    /* Note that if the DLL supports versions greater    */
    /* than 2.2 in addition to 2.2, it will still return */
    /* 2.2 in wVersion since that is the version we      */
    /* requested.                                        */

    if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
    {
            halt( EXIT_CODE_ERROR );
    }
#endif
}


/******************************************************************************
**
**    Function: print_usage
**
**    Purpose:  display usage statement.
**
******************************************************************************/

void print_usage()
{
    osi_print_stdout( "" );

#ifndef WIN32
    osi_print_stdout( "usage: osirisd [-l<level>] [-h]\n" );
#else
    osi_print_stdout( "usage: osirisd [-i|-u] [-h] [-d] [-l<level>]\n" );
    fprintf( stdout,  "  -i          " );
    osi_print_stdout( "install as a windows service on local machine." );
    fprintf( stdout,  "  -u          " );
    osi_print_stdout( "uninstall windows service from local machine." );
    fprintf( stdout,  "  -d          " );
    osi_print_stdout( "run this application as a daemon, not a service." );
#endif

    fprintf( stdout,  "  -h          " );
    osi_print_stdout( "print this usage statement." );
    fprintf( stdout, "  -l <level>  " );
    osi_print_stdout( "degree of logging for this daemon, default is low." );
    osi_print_stdout( "              ( 0=none, 1=low, 2=medium, 3=high )" );
    osi_print_stdout( "" );
}

/*****************************************************************************
**
**    Function: set_platform_info
**
**    Purpose:  read in uname style info for status responses.
**
******************************************************************************/

void set_platform_info()
{
#ifdef WIN32

    OSVERSIONINFO info;

    memset( &info, 0, sizeof( info ) );
    info.dwOSVersionInfoSize  = sizeof( info );

    if( GetVersionEx( &info ) )
    {
        if( info.dwMajorVersion == 3 )
        {
            osi_strlcpy( os_name, "WindowsNT", sizeof( os_name ) );
            osi_strlcpy( os_version, "3.51", sizeof( os_version ) );
        }

        else if( info.dwMajorVersion == 4 )
        {
            if( info.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS )
            {
                switch( info.dwMinorVersion )
                {
                    case 0:
                        osi_strlcpy( os_name, "Windows95", sizeof( os_name ) );
                        break;

                    case 10:
                        osi_strlcpy( os_name, "Windows98", sizeof( os_name ) );
                        break;

                    case 90:
                        osi_strlcpy( os_name, "WindowsME", sizeof( os_name ) );
                        break;

                    default:
                        break;
                }
            }

            else if( info.dwPlatformId == VER_PLATFORM_WIN32_NT )
            {
                osi_strlcpy( os_name, "WindowsNT", sizeof( os_name ) );
                osi_strlcpy( os_version, "4.0", sizeof( os_version ) );
            }
        }

        else if( info.dwMajorVersion == 5 )
        {
            switch( info.dwMinorVersion )
            {
                case 0:
                    osi_strlcpy( os_name, "Windows2000", sizeof( os_name ) );
                    break;

                case 1:
                    osi_strlcpy( os_name, "WindowsXP", sizeof( os_name ) );
                    break;

                case 2:
                    osi_strlcpy( os_name, "WindowsServer2003",
                                 sizeof( os_name ) );
                    break;

                default:
                    break;
            }
        }
    }

#else

    struct utsname name;

    if( uname( &name ) == 0 )
    {
        osi_strlcpy( os_name, name.sysname, sizeof( os_name ) );
        osi_strlcpy( os_version, name.release, sizeof( os_version ) );
    }

#endif
}


