
/******************************************************************************
**
**  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:    osirismd.c
**  Date:    May 19, 2002
**
**  Author:  Brian Wotring
**  Purpose: management host daemon
**
******************************************************************************/

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

#include "common.h"

#include <signal.h>

#ifndef WIN32
#include <syslog.h>
#include <netinet/in.h>
#include <sys/wait.h>
#endif

#include "md_auth.h"
#include "md_hosts.h"
#include "md_control.h"
#include "md_compare.h"
#include "md_schedule.h"
#include "md_http.h"
#include "md_filter.h"

#include "logging.h"

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

#include "osirismd.h"

static void genrsa_cb( int p, int n, void *arg );


/******************************************************************************
**
**    Function: main
**
**    Purpose:  system driver for this daemon.
**
******************************************************************************/

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

    initialize_pipes();
    initialize_root_path();
    
    parse_arguments( argument_count, argument_list );
    parse_configuration_file();

    initialize_log();
    become_windows_service();

    run( NULL );
    halt( EXIT_CODE_NORMAL );
    
    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 )
{
    verify_root_directory();
    verify_host_directories();

    initialize_ssl();
    start_scheduler();

    fork_daemon_process();
    start_servers();

    initialize_signals();
    load_auth_database();

    process();
    halt( EXIT_CODE_NORMAL );
    
    return THREAD_FUNCTION_RETURN;
}

/******************************************************************************
**
**    Function: parse_configuration_file
**
**    Purpose:  open and read in the configuration for this daemon, create and
**		        set it to defaults if it doesn't exist.
**
******************************************************************************/

void parse_configuration_file()
{
    /* create a blank config and stock it with defaults, destroy any */
    /* existing config, so we can call this multiple times.          */
    
    if( config != NULL )
    {
        osi_management_config_destroy( config );
    }
    
    config = osi_management_config_new();
    osi_management_config_set_defaults( config );
    
    if( config == NULL )
    {
        osi_print_stderr( "%s: malloc failed!\n", PROGRAM_NAME );
        halt( EXIT_CODE_ERROR );
    }
    
    /* if the file is there, we parse it, if we can't parse it, we  */
    /* complain and use the default values for this server.         */
    
    if( osi_file_exists( config_file_path ) )
    {
        if( osi_management_config_read_file( config_file_path, config ) )
        {
            log_info( LOG_MEDIUM, NULL, "using config file: %s.",
                      config_file_path );
        }
        
        else
        {
            osi_print_stderr( "%s: error reading configuration file: %s.",
                               PROGRAM_NAME, config_file_path );

            log_error( LOG_LOW, NULL, "reading configuration file: %s.",
                       config_file_path );
            
            halt( EXIT_CODE_ERROR );
        }
    }
    
    /* create file if it doesn't exist, if a file was specified */
    /* on the command line, we know it exists since a check     */
    /* is done, and it exits on failure; write it to disk.      */
    
    else
    {
        if( osi_create_file( config_file_path,
            MANAGEMENT_CONFIG_FILE_PERMISSIONS ) != OSI_FILE_OK )
        {
            osi_print_stderr( "%s: creating configuration file: %s.",
                              PROGRAM_NAME, config_file_path );

            log_error( LOG_MEDIUM, NULL, "creating configuration file: %s.",
                       config_file_path );

            halt( EXIT_CODE_ERROR );
        }
        
        if( osi_management_config_write_file( config_file_path, config ) )
        {
            osi_print_stderr(
               "%s: missing configuration file,\n  ==> created default in: %s.",
                PROGRAM_NAME, config_file_path );

            log_info( LOG_MEDIUM, NULL,
                      "missing config file, created default in: %s.",
                      config_file_path );
        }
        
        else
        {
            osi_print_stderr( "%s: error saving configuration file: %s.",
                              PROGRAM_NAME, config_file_path );

            log_error( LOG_MEDIUM, NULL, "saving configuration file: %s.",
                       config_file_path );

            halt( EXIT_CODE_ERROR );
        }
    }
        
    /* set our global syslog priority based upon what are config */
    /* now contains, which might be the defaults.                */

#ifndef WIN32    

    syslog_level = get_syslog_level_number( config->syslog_level );;
    syslog_facility = get_syslog_facility_number( config->syslog_facility );;

    if( ( syslog_level != -1 ) && ( syslog_facility != -1 ) )
    {
        SYSLOG_PRIORITY = ( syslog_level | syslog_facility );
    }
   
#endif

    log_intensity = config->log_intensity;
}

/******************************************************************************
**
**    Function: process
**
**    Purpose:  main event loop for client processing.  This event loop is
**              infinite and will accept new clients, creating a new
**              thread/process for each client.  Clients remain open until
**              to handle requests until the client closes the connection.
**
******************************************************************************/

void process()
{
    int result = 0;
    struct sockaddr_in client;
    socklen_t client_length;
    
    SSL *client_ssl = NULL;
    char host[MAX_HOSTNAME_LENGTH];

    for(;;)
    {
        client_length = sizeof( client );

        check_for_signals();
        wait_for_data();

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

        if( INCOMING_CONTROL_REQUEST() )
        {
            control_connection_socket = osi_accept( control_socket,
                                                   ( struct sockaddr * )&client,                                                   &client_length );
            
            if( control_connection_socket == -1 )
            {
                continue;
            }
            
            /* verify this host is allowed to talk to us. */
            
            inet_ntop( AF_INET, &client.sin_addr, host, sizeof( host ) );

            if( client_address_is_allowed( host ) == FALSE )
            {
                log_warning( LOG_MEDIUM, NULL, "unauthorized host: %s.", host );
                
                osi_close_socket( control_connection_socket );
                continue;
            }
            
            log_info( LOG_HIGH, NULL, "authorized connection from: %s.",
                         host );

            process_new_client( control_connection_socket );
        }

        if( INCOMING_HTTP_REQUEST() )
        {
            http_connection_socket = osi_accept( http_socket,
                                                ( struct sockaddr * )&client,
                                                  &client_length );

            if( http_connection_socket == -1 )
            {
                continue;
            }

            /* verify this host is allowed to talk to us. */

            inet_ntop( AF_INET, &client.sin_addr, host, sizeof( host ) );

            if( client_address_is_allowed( host ) == FALSE )
            {
                log_warning( LOG_MEDIUM, NULL, "unauthorized host: %s.", host );

                osi_close_socket( http_connection_socket );
                continue;
            }

            log_info( LOG_HIGH, NULL, "authorized connection from: %s.",
                         host );

            process_new_http_client( http_connection_socket );
        }

        if( INCOMING_AUTH_DB_EVENT() )
        {
            char x[2];

            log_info( LOG_MEDIUM, NULL, "auth db is stale, reloading." );

            osi_read( user_pipe[0], x, 1 );
            reload_auth_database();
        }

        if( INCOMING_RESTART_EVENT() )
        {
            char x[2];

            log_info( LOG_LOW, NULL, "master config altered, reloading." );

            osi_read( restart_pipe[0], x, 1 );
            handle_restart();
        }

    } /* end for loop. */
}

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

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 all host information 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 (not root!) 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 );
    }

    /* we  the root directory, save it, we will either use      */
    /* it to setup our other paths, or it will be overwritten   */
    /* by a command line argument.                              */
    
    if( osi_get_osiris_home_directory( path, sizeof( path ) ) )
    {
        osi_strlcpy( root_path, path, sizeof( root_path ) );
    }

    /* setup our conf file path here. */
    
    osi_strlcpy( config_file_path, root_path, sizeof( config_file_path ) );
    osi_strlcat( config_file_path, path_separator, sizeof( config_file_path ) );

    osi_strlcat( config_file_path, MANAGEMENT_CONFIG_FILE_NAME,
                 sizeof( config_file_path ) );

    /* setup the auth db path here. */

    osi_strlcpy( auth_db_path, root_path, sizeof( auth_db_path ) );
    osi_strlcat( auth_db_path, path_separator, sizeof( auth_db_path ) );
    osi_strlcat( auth_db_path, KEY_DIRECTORY_NAME, sizeof( auth_db_path ) );
    osi_strlcat( auth_db_path, path_separator, sizeof( auth_db_path ) );
    osi_strlcat( auth_db_path, AUTH_DB_NAME, sizeof( auth_db_path ) );

    /* setup our default configs path here. */

    osi_strlcpy( default_configs_path, root_path,
                 sizeof( default_configs_path ) );

    osi_strlcat( default_configs_path, path_separator,
                 sizeof( default_configs_path ) );

    osi_strlcat( default_configs_path, DEFAULT_CONFIGS_DIRECTORY_NAME,
                 sizeof( default_configs_path ) );

    /* setup our notification filter database path here. */

    osi_strlcpy( notify_filter_db_path, root_path,
                 sizeof( notify_filter_db_path ) );

    osi_strlcat( notify_filter_db_path, path_separator,
                 sizeof( notify_filter_db_path ) );

    osi_strlcat( notify_filter_db_path, NOTIFY_FILTER_DB_NAME,
                 sizeof( notify_filter_db_path ) );
}

/******************************************************************************
**
**    Function: verify_root_directory
**
**    Purpose:  if the root directory doesn't exist, we create one.
**
******************************************************************************/

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

    /* if we received a command line argument, we know the directory */
    /* exists because it was checked when we copied the argument.    */
    /* if no argument was specified, we have already made a good     */
    /* effort to guess where the root directory is.                  */
    
    if( osi_directory_exists( root_path ) == FALSE )
    {
        osi_print_stderr(
        "unable to locate root directory (%s)\ncreate if necessary.",
        root_path );

        halt( EXIT_CODE_ERROR );
    }

    /* now setup our paths to our cert and private keys, if they exist. */

    osi_strlcpy( cert_path, root_path, sizeof( cert_path ) );
    osi_strlcat( cert_path, path_separator, sizeof( cert_path ) );
    osi_strlcat( cert_path, CERTS_DIRECTORY_NAME, sizeof( cert_path ) );

    osi_strlcpy( cert_file, cert_path, sizeof( cert_file ) );
    osi_strlcat( cert_file, path_separator, sizeof( cert_file ) );
    osi_strlcat( cert_file, CERT_NAME, sizeof( cert_file ) );

    osi_strlcpy( private_key_path, root_path, sizeof( cert_path ) );
    osi_strlcat( private_key_path, path_separator, sizeof( cert_path ) );

    osi_strlcat( private_key_path, KEY_DIRECTORY_NAME,
                 sizeof( private_key_path ) );

    osi_strlcpy( private_key_file, private_key_path,
                 sizeof( private_key_file ) );

    osi_strlcat( private_key_file, path_separator, sizeof( private_key_file ) );
    osi_strlcat( private_key_file, KEY_NAME, sizeof( private_key_file ) );

    /* now verify that we have a 'hosts' directory, if we don't */
    /* then we create it now.                                   */

    osi_strlcpy( hosts_path, root_path, sizeof( hosts_path ) );
    osi_strlcat( hosts_path, path_separator, sizeof( hosts_path ) );
    osi_strlcat( hosts_path, HOSTS_DIRECTORY_NAME, sizeof( hosts_path ) );

    if( osi_directory_exists( hosts_path ) == FALSE )
    {
        log_info( LOG_MEDIUM, NULL, "hosts directory absent, creating one." );

        if( osi_create_directory( hosts_path ) == FALSE )
        {
            osi_print_stderr( "error: unable to create hosts directory: %s.",
                              root_path );

            halt( EXIT_CODE_ERROR );
        }
    }

    /* now verify that we have a 'certs' directory, if we don't */
    /* then we create it now.                                   */

    if( osi_directory_exists( cert_path ) == FALSE )
    {
        log_info( LOG_MEDIUM, NULL, "certs directory absent, creating one." );

        if( osi_create_directory( cert_path ) == FALSE )
        {
            osi_print_stderr( "error:  unable to create certs directory: %s.",
                              root_path );

            halt( EXIT_CODE_ERROR );
        }
    }

    /* now verify that we have a 'private' directory, if we don't */
    /* then we create it now.                                   */

    if( osi_directory_exists( private_key_path ) == FALSE )
    {
        log_info( LOG_MEDIUM, NULL, "key directory absent, creating one." );

        if( osi_create_directory( private_key_path ) == FALSE )
        {
            osi_print_stderr( "unable to create private key directory: %s.",
                              root_path );

            halt( EXIT_CODE_ERROR );
        }
    }

    /* now verify that we have a 'default-configs' directory, */
    /* if we don't then we create it now.                     */

    if( osi_directory_exists( default_configs_path ) == FALSE )
    {
        log_warning( LOG_LOW, NULL,
                    "default-configs directory absent, creating one." );

        if( osi_create_directory( default_configs_path ) == FALSE )
        {
            osi_print_stderr( "unable to create default configs directory: %s.",
                              root_path );

            halt( EXIT_CODE_ERROR );
        }
    }

    /* now verify that we have a user password database.  If we don't */
    /* we create an empty one now, with a single 'admin' user.        */

    if( osi_file_exists( auth_db_path ) == FALSE )
    {
        log_warning( LOG_LOW, NULL, "authentication DB absent, creating one." );

        if( create_auth_db( auth_db_path ) == FALSE )
        {
            osi_print_stderr( "unable to create user auth database in (%s)",
                              auth_db_path );

            halt( EXIT_CODE_ERROR );
        }
    }

    /* now verify that we have a notification filter database. */

    if( osi_file_exists( notify_filter_db_path ) == FALSE )
    {
        log_warning( LOG_LOW, NULL, "filter DB absent, creating one." );

        if( create_notify_filter_db( notify_filter_db_path ) == FALSE )
        {
            osi_print_stderr(
                        "unable to create notification filter database in (%s)",
                         notify_filter_db_path );

            halt( EXIT_CODE_ERROR );
        }
    }

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

/******************************************************************************
**
**    Function: verify_host_directories
**
**    Purpose:  we go through the host directories that we have, and do any
**	        	verification we need to on their file(s).
**
******************************************************************************/

void verify_host_directories()
{
    osi_bool result = TRUE;

    /* for now, we just verify the permissions on the host.conf file, */
    /* this file should not have any world permissions.               */

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

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

    if( root_path != NULL )
    {
        /* first, we construct path to the hosts directory. */

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

        log_info( LOG_HIGH, NULL, "permissions on host config files." );

        /* now traverse the directory, looking at each host.conf file */

        if( osi_open_directory( path, &directory ) )
        {
            do
            {
                char config_path[MAX_PATH_LENGTH];
                struct stat file_stats;

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

                osi_strlcpy( config_path, path, sizeof( config_path ) );

                osi_strlcat( config_path, path_separator,
                             sizeof( config_path ) );

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

                osi_strlcat( config_path, path_separator,
                             sizeof( config_path ) );

                osi_strlcat( config_path, HOST_CONFIG_FILE_NAME,
                             sizeof( config_path ) );

                if( stat( config_path, &file_stats ) == 0 )
                {
#ifdef WIN32
                    /* WIN32 - todo: add check for non-admin privs. */
#else
                    /* verify permissions. */

                    if( ( file_stats.st_mode & S_IROTH ) ||
                        ( file_stats.st_mode & S_IWOTH ) ||
                        ( file_stats.st_mode & S_IXOTH ) )
                    {
                        log_error( LOG_MEDIUM, NULL,
                       "world read permissions on config file: %s, please fix.",
                                   directory.filename );

                        osi_print_stderr( "host: %s has a host.conf file with world permissions, please fix.", directory.filename );

                        result = FALSE;
                    }
#endif
                }

            } while( osi_get_next_file( &directory ) );

            osi_close_directory( &directory );
        }
    }

    if( result == FALSE )
    {
        halt( EXIT_CODE_ERROR );
    }
}



/******************************************************************************
**
**    Function: parse_arguments
**
**    Purpose:  read command line arguments.
**
******************************************************************************/

void parse_arguments( int argument_count, char *argument_list[] )
{
    int count;
    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    for( count = 1; count < argument_count; count++ )
    {
        /* read in root path directory. */
        
        if( ( strcmp( argument_list[count], "--root-path" ) == 0 ||
              strcmp( argument_list[count], "-r" ) == 0 ) &&
            ( count < ( argument_count - 1 ) ) )
        {
            count++;

            if( osi_file_is_directory( argument_list[count], NULL ) )
            {
                /* save paths to root, and create a new path to the config  */
                /* assumed in root.                                         */
                
                osi_strlcpy( root_path, argument_list[count],
                             sizeof( root_path ) );

                osi_strlcpy( config_file_path, root_path,
                             sizeof( config_file_path ) );

                osi_strlcat( config_file_path, path_separator,
                             sizeof( config_file_path ) );

                osi_strlcat( config_file_path, MANAGEMENT_CONFIG_FILE_NAME,
                             sizeof( config_file_path ) );
            }

            else
            {
                osi_print_stderr(
                    "osirismd: error, unable to read root directory (%s).",
                    argument_list[count] );

                exit( EXIT_CODE_ERROR );
            }
        }
        
        /* read in path to configuration file to use. */
        
        else if( ( strcmp( argument_list[count], "--file" ) == 0 ||
                   strcmp( argument_list[count], "-f" ) == 0 ) &&
               ( count < ( argument_count - 1 ) ) )
        {
            count++;

            if( osi_file_exists( argument_list[count] ) )
            {
                osi_strlcpy( config_file_path, argument_list[count],
                             sizeof( config_file_path ) );
            }

            else
            {
                osi_print_stderr(
                "osirismd: error, unable to read configuration file (%s).",
                argument_list[count] );

                exit( EXIT_CODE_ERROR );
            }
        }

        else if( strcmp( argument_list[count], "--help" ) == 0 ||
                 strcmp( argument_list[count], "-h" ) == 0 )
        {
            print_usage();
            exit( EXIT_CODE_NORMAL );
        }

 #ifdef WIN32

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

            install_windows_service();
            exit( EXIT_CODE_NORMAL );
        }

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

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

        else
        {
            print_usage();
            exit( EXIT_CODE_ERROR );
        }
    }
}

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

void become_windows_service()
{
#ifdef WIN32

    /* if the user specified that we run this as a normal daemon, we */
    /* don't start the service registration and loop.                */

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

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

#endif
}

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

void halt( int code )
{
    log_info( LOG_LOW, NULL, "halting." );

    shutdown_servers();
    shutdown_ssl();
    shutdown_scheduler();

    unload_auth_database();

#ifdef WIN32
    WSACleanup();
    DeregisterEventSource( event_source );
#else
    closelog();
#endif

    close( user_pipe[0] );
    close( user_pipe[1] );

    close( schedule_pipe[0] );
    close( schedule_pipe[1] );

    close( restart_pipe[0] );
    close( restart_pipe[1] );

    exit( code );
}

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

void print_usage()
{
    osi_print_stdout( "" );
#ifdef WIN32
    osi_print_stdout( "usage: osirismd [-r <directory>] [-f <file>] [-d] [-h] | [-u|-i]\n" );
#else
    osi_print_stdout( "usage: osirismd [-r <directory>] [-f <file>] [-h]\n" );
#endif
    osi_print_stdout( "  -r directory   : specify alternate directory for certs and hosts." );
    osi_print_stdout( "  -f file        : specify alternate configuration file." );
    osi_print_stdout( "  -h             : this usage statement." );

#ifdef WIN32
    osi_print_stdout( "  -i             : install as a windows service on local machine." );
    osi_print_stdout( "  -u             : uninstall windows service from local machine." );
    osi_print_stdout( "  -d             : run this application as a normal daemon, not a service." );
#endif

   	osi_print_stdout( "" );
}

/******************************************************************************
**
**    Function: initialize_signals
**
**    Purpose:  add various common signals to the signal handler.
**
******************************************************************************/

void initialize_signals()
{
    signal( SIGINT, handle_sigint );
    signal( SIGTERM, handle_sigterm );

    /* windows doesn't know about these. */

#ifndef WIN32
    signal( SIGHUP, handle_sighup );
    signal( SIGCHLD, handle_sigchld );
    signal( SIGPIPE, handle_sigpipe );
    signal( SIGKILL, handle_sigkill );
#endif
}


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

void initialize_ssl()
{
    int result;
    char path_separator[2] = { PATH_SEPARATOR, '\0' };

    SSL_METHOD *method = NULL;

    bio_error = BIO_new_fp( stderr, BIO_NOCLOSE );

    SSL_load_error_strings();
    SSL_library_init();

    method = SSLv23_server_method();
    ssl_context = SSL_CTX_new( method );

    /* create our global SSL context for this server. */

    if( ssl_context == NULL )
    {
        ERR_print_errors( bio_error );
        log_error( LOG_LOW, NULL, "unable to initialize SSL." );

        halt( EXIT_CODE_ERROR );
    }

    /* load the certificate and key.  if the certificate isn't there, we */
    /* create a keypair, and store them locally, exit if this fails -we  */
    /* are useless.                                                      */

    result = SSL_CTX_use_certificate_file( ssl_context, cert_file,
                                           SSL_FILETYPE_PEM );
    if( result <= 0 )
    {
        log_error( LOG_MEDIUM,
                   NULL, "%s: unable to load server certificate: %s.",
                   cert_file );

        osi_print_stderr(
                        "unable to load server certificate (%s)\n  ==> creating one.",
                        cert_file );

        /* if there is a cert there, we have to shut down, something    */
        /* happened, otherwise there isn't a cert so we try to generate */
        /* a default cert to use.                                       */

        if( osi_file_exists( cert_file ) )
        {
            log_error( LOG_LOW, NULL,
                   "cert exists, but could not be loaded, verify this cert." );

            halt( EXIT_CODE_ERROR );
        }

        log_warning( LOG_MEDIUM, NULL,
                     "no cert exists, attempting to create a default cert." );

        if( generate_keypair() )
        {
            if( SSL_CTX_use_certificate_file( ssl_context, cert_file,
                                              SSL_FILETYPE_PEM ) <= 0 )
            {
                log_error( LOG_LOW, NULL,
                           "unable to load server certificate: %s.",cert_file );

                osi_print_stderr( "unable to load server certificate (%s).",
                                  cert_file );

                halt( EXIT_CODE_ERROR );
            }
        }

        else
        {
            log_error( LOG_LOW, NULL, "saving root certificate and/or keys." );

            osi_print_stderr(
                "error: unable to create or save certificate and keys." );
            
            halt( EXIT_CODE_ERROR );
        }
    }

    /* if the public key isn't there, or isn't valid, we have to just   */
    /* shut down.                                                       */

    if( SSL_CTX_use_PrivateKey_file( ssl_context, private_key_file,
                                     SSL_FILETYPE_PEM ) <= 0 )
    {
        log_error( LOG_LOW, NULL, "unable to load server key: %s.",
                   private_key_file );

        osi_print_stderr( "unable to load server certificate (%s).",
                          private_key_file );

        halt( EXIT_CODE_ERROR );
    }

    if( !SSL_CTX_check_private_key( ssl_context ) )
    {
        log_error( LOG_LOW, NULL,
                   "key does not match the certificate public key!" );

        osi_print_stderr(
                    "private key does not match the certificate public key!" );

        halt( EXIT_CODE_ERROR );
    }

    SSL_CTX_set_verify( ssl_context, SSL_VERIFY_PEER, ssl_verify_callback );
    SSL_CTX_set_verify_depth( ssl_context, OSIRISMD_SERVER_CERT_CHAIN_DEPTH );
    SSL_CTX_set_options( ssl_context, OSIRISMD_SSL_OPTIONS );

    if( SSL_CTX_set_cipher_list( ssl_context, OSIRISMD_CIPHER_LIST ) != 1 )
    {
        osi_print_stderr( "unable to set cipher list." );
        halt( EXIT_CODE_ERROR );
    }

    log_info( LOG_MEDIUM, NULL,"SSL server initialized, using certificate: %s.",
              cert_file );
}

/******************************************************************************
**
**    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 )
    {
        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 );

        osi_print_stderr( "error with certificate at depth: %i", depth );
        X509_NAME_oneline( X509_get_issuer_name( cert ), data, sizeof( data ) );
        osi_print_stderr( "  issuer = %s", data );

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

        osi_print_stderr( " subject = %s", data );

        osi_print_stderr( "error %i:%s", err,
                          X509_verify_cert_error_string( err ) );
    }

    return ok;
}

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

void start_servers()
{
    control_socket = osi_listen_on_local_port( config->control_port,
                                               LISTEN_QUEUE_MAX );

    if( control_socket <= 0 )
    {
        log_error( LOG_LOW, NULL, "with control server binding to port: %d.",
                   config->control_port );

        osi_close_socket( control_socket );
        halt( EXIT_CODE_ERROR );
    }

    /* only if we have a valid port. */

    if( config->http_port > 0 )
    {

        http_socket = osi_listen_on_local_port( config->http_port,
                                                LISTEN_QUEUE_MAX );

        if( http_socket <= 0 )
        {
            log_error( LOG_LOW, NULL, "with http server binding to port: %d.",
                       config->http_port );

            osi_close_socket( http_socket );
            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.       */
    /* note: this is only for the control server.                      */

     osi_set_socket_non_blocking( control_socket );

     log_info( LOG_LOW, NULL, "control server started on port: %d.",
               config->control_port );

     if( http_socket > 0 )
     {
        log_info( LOG_LOW, NULL, "http server started on port: %d.",
                  config->http_port );
     }
}

/******************************************************************************
**
**    Function: shutdown_servers
**
**    Purpose:  perform necessary cleanup for server.
**
******************************************************************************/
void shutdown_servers()
{
    osi_close_socket( control_socket );
    osi_close_socket( http_socket );

    control_socket = 0;
    http_socket = 0;

    log_info( LOG_LOW, NULL, "osirismd servers shut down." );
}

/******************************************************************************
**
**    Function: shutdown_scheduler
**
**    Purpose:  perform necessary cleanup for schedluing process, if exists.
**
******************************************************************************/

void shutdown_scheduler()
{
#ifndef WIN32
    if( scheduler_pid > 0 )
    {
        log_info( LOG_LOW, NULL, "osirismd scheduler process shut down." );
        kill( scheduler_pid, SIGTERM );
    }
#endif
}

/******************************************************************************
**
**    Function: handle_restart
**
**    Purpose:  perform all tasks related to a restart.  This includes
**              re-reading our config file, checking for server port changes,
**              and log changes.
**
******************************************************************************/

void handle_restart()
{
    /* save current port and syslog settings. */
    
    int control_port = config->control_port;
    int http_port    = config->http_port;
    
    /* re-read our config file. */
    
    parse_configuration_file();
    
    /* check is the server and control ports for the server.         */
    
    if( ( control_port != config->control_port ) || 
        ( http_port != config->http_port ) )
    {
        shutdown_servers();
        start_servers();
    }
    
    /* set our logging aggression. */

    log_intensity = config->log_intensity;

    /* reload our user database. */

    load_auth_database();

    /* if the logging changed, our logging handler will simply start */
    /* using the new one, same with the allow list.                  */
    
    log_warning( LOG_LOW, NULL, "server has been restarted." );
}

/*****************************************************************************
**
**    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_new_client
**
**    Purpose:  based upon type, utilize this message.
**
******************************************************************************/

void process_new_client( int client_socket )
{
    int result = 0;

#ifdef WIN32
    HANDLE control_handle;
    unsigned int control_thread_id;
#endif

    /* if we have support for threads, we create a new thread     */
    /* otherwise, we do the nasty and spawn a new process for it. */
    /* windows always will use threads.                           */

#ifdef HAVE_THREADS

    /* thread is responsible for freeing this socket copy. */

    int *socketcopy = osi_malloc( sizeof( int ) );
    
    if( socketcopy == NULL )
    {
        return;
    }

    (*socketcopy) = client_socket;

    control_handle = (HANDLE)_beginthreadex( NULL, 0, &process_client,
                                             socketcopy, 0,
                                             &control_thread_id );

    if( control_handle != 0 )
    {
        log_info( LOG_HIGH, NULL, "control thread spawned." );
        return;
    }

    else
    {
        log_error( LOG_MEDIUM, NULL, "unable to spawn control thread." );
        goto exit_gracefully;
    }

#else

    result = osi_create_process();

    if( result == OSI_ERROR_UNABLE_TO_CREATE_PROCESS )
    {
        /* restore socket and SSL, so server loop can close it. */

        log_error( LOG_MEDIUM, NULL,
                   "unable to spawn process to handle request." );

        goto exit_gracefully;
    }

    /* if we are the server/parent process, leave here. */

    if( result > 0 )
    {
        return;
    }

    /* enter the control handler for this client which will continue */
    /* to serve requests until the client disconnects.               */

    process_client( &client_socket );
    exit(0);

#endif /* not HAVE_THREADS */

exit_gracefully:

    osi_close_socket( client_socket );
    return;
}


/******************************************************************************
**
**    Function: process_new_http_client
**
**    Purpose:  handle any HTTP requests to the http server of this daemon.
**
******************************************************************************/

void process_new_http_client( int client_socket )
{
    int result;

    MESSAGE message;
    SSL *client_ssl;

    if( client_socket <= 0 )
    {
        return;
    }

    client_ssl = osi_ssl_accept_with_socket( client_socket,
                                             ssl_context, FALSE );

    if( client_ssl == NULL )
    {
        return;
    }

    result = osi_ssl_read_http_message( &message, client_ssl );

    if( result != MESSAGE_OK )
    {
        if( result == OSI_ERROR_SOCKET_CLOSED )
        {
            log_info( LOG_MEDIUM, NULL, "remote peer closed connection." );
        }

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

        goto exit_gracefully;
    }

    log_info( LOG_MEDIUM, NULL, "received http request." );
    process_http_message( client_ssl, message.data );

exit_gracefully:

    osi_close_socket( client_socket );
    osi_ssl_destroy( &client_ssl );

}

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

void wait_for_data()
{
    int max_descriptors;
    int sresult;

wait_start:

    FD_ZERO( &read_set );

    /* add the control socket to the set. */

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

    /* add the http socket to the set if it is valid. */

    if( http_socket > 0 )
    {
        FD_SET( http_socket, &read_set );
        max_descriptors = MAXIMUM( max_descriptors, http_socket );
    }

    /* add the user auth db pipe. */

    FD_SET( user_pipe[0], &read_set );
    max_descriptors = MAXIMUM( max_descriptors, user_pipe[0] );

    /* add the restart detection pipe. */

    FD_SET( restart_pipe[0], &read_set );
    max_descriptors = MAXIMUM( max_descriptors, restart_pipe[0] );

    /* select with null time value, so we can block. */

    sresult = select( ( max_descriptors + 1 ), &read_set, NULL, NULL, NULL );

    if( ( sresult < 0 ) && ( errno == EINTR ) )
    {
        check_for_signals();
        goto wait_start;
    }
}

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

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

}

/******************************************************************************
**
**    Function: fork_daemon_process
**
**    Purpose:  start a child process for the daemon, let the parent die.
**              this is only for non-windows as windows has a service.
**
******************************************************************************/

void fork_daemon_process()
{
#ifndef WIN32

    pid = fork();

    if( pid < 0 )
    {
        log_error( LOG_LOW, NULL, "unable to spawn child process." );
        exit( EXIT_CODE_ERROR );
    }

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

    if( pid == 0 )
    {
        log_info(  LOG_HIGH, NULL, "daemon process spawned." );
    }
#endif
}

/******************************************************************************
**
**    Function: initialize_pipes
**
**    Purpose:  create some anonymous pipes that will be used to communicate
**              between the various subsystems back up to the main process
**              or thread.
**
******************************************************************************/

void initialize_pipes()
{
#ifdef HAVE_PIPE
    if( pipe( user_pipe ) != 0 )
    {
        log_error( LOG_LOW, NULL, "creating auth pipe!" );
    }

    if( pipe( schedule_pipe ) != 0 )
    {
        log_error( LOG_LOW, NULL, "creating scheduler pipe!" );
    }

    if( pipe( restart_pipe ) != 0 )
    {
        log_error( LOG_LOW, NULL, "creating restart pipe!" );
    }
#else
    if( socketpair( AF_INET, SOCK_STREAM, 0, user_pipe ) != 0 )
    {
        log_error( LOG_LOW, NULL, "create auth pipe!" );
        halt( EXIT_CODE_ERROR );
    }
    if( socketpair( AF_INET, SOCK_STREAM, 0, schedule_pipe ) != 0 )
    {
        log_error( LOG_LOW, NULL, "creating scheduler pipe!" );
        halt( EXIT_CODE_ERROR );
    }
    if( socketpair( AF_INET, SOCK_STREAM, 0, restart_pipe ) != 0 )
    {
        log_error( LOG_LOW, NULL, "creating restart pipe!" );
        halt( EXIT_CODE_ERROR );
    }

#endif
}

/******************************************************************************
**
**    Function: start_scheduler
**
**    Purpose:  start a child process or thread to manage
**              scheduling of scans.
**
******************************************************************************/

void start_scheduler()
{
#ifdef HAVE_THREADS

    HANDLE scheduler_handle;
    unsigned int scheduler_thread_id;

    scheduler_handle = (HANDLE)_beginthreadex( NULL, 0, &run_scheduler, NULL,
                                               0, &scheduler_thread_id );

    if( scheduler_handle != 0 )
    {
        log_info( LOG_HIGH, NULL, "scheduling thread spawned." );
    }

    else
    {
        log_error( LOG_MEDIUM, NULL, "creating scheduling thread." );
    }


#else

    scheduler_pid = fork();

    if( scheduler_pid < 0 )
    {
        log_error( LOG_LOW, NULL, "creating scheduling process." );
        exit( EXIT_CODE_ERROR );
    }

    /* child process. */

    if( scheduler_pid == 0 )
    {
        log_info( LOG_HIGH, NULL, "scheduling process spawned." );
        run_scheduler( NULL );

        exit( EXIT_CODE_NORMAL );
    }

    /* parent continues on. */

#endif
}

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

    else
    {
        daemon_terminated = 1;
    }
#endif

    received_sigint = 1;
}

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

    else
    {
        daemon_terminated = 1;
    }
#endif

    received_sigterm = 1;
}

void handle_sighup( int signal )
{
#ifndef WIN32
    received_sighup = 1;
#endif
}

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

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

    received_sigpipe = 1;

#endif
}

void handle_sigkill( int signal )
{
#ifndef WIN32
    if( getpid() == scheduler_pid )
    {
        scheduler_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( scheduler_terminated )
        {
            scheduler_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "scheduler 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( scheduler_terminated )
        {
            scheduler_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "scheduler process was killed." );
        }

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

#ifndef WIN32

    if( received_sighup )
    {
        received_sighup = 0;
        handle_restart();
    }

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

        /* wait for child to finish. */

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

        if( pid == scheduler_pid )
        {
            log_error( LOG_LOW, NULL, "SIGCHLD, scheduler has terminated!" );
        }

        else
        {
            log_info( LOG_MEDIUM, NULL,
                      "SIGCHLD, client process has terminated." );
        }
    }

    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( scheduler_terminated )
        {
            scheduler_terminated = 0;
            log_warning( LOG_MEDIUM, NULL, "scheduler 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 )
    {
        halt( EXIT_CODE_NORMAL );
    }

#endif
}

void signal_restart_needed()
{
    osi_write( restart_pipe[1], "x", 1 );
}


/******************************************************************************
**
**    Function: startup_winsock
**
**    Purpose:  for windows only, initialize the windows socket library.
**
******************************************************************************/

void startup_winsock()
{
#ifdef WIN32

    WORD wVersionRequested;
    WSADATA wsaData;
    int err;

    wVersionRequested = MAKEWORD( 2, 2 );

    err = WSAStartup( wVersionRequested, &wsaData );

    if ( err != 0 )
    {
        /* Tell the user that we could not find a usable */
        /* WinSock DLL.                                  */

        log_error( LOG_LOW, NULL, "WSAStartup failed, unable to use winsock." );
        halt(-1);
    }

    /* 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 )
    {
        log_error( LOG_LOW, NULL,
        "version of winsock is not usable, version 2.2 at least is required." );

        WSACleanup( );
        halt(-1);;
    }
#endif
}

/******************************************************************************
**
**    Function: make_certificate
**
**    Purpose:  generate an X509 certificate and private key for use with
**		        osirismd, this will be just default values for the fields
**		        so that this daemon can start and serve requests.
**
******************************************************************************/

osi_bool make_certificate( X509 **x509p, EVP_PKEY **pkeyp, int bits,
                           int serial, int days )
{
    X509 *x;
    EVP_PKEY *pk;
    RSA *rsa;
    X509_NAME *name = NULL;

    if( ( pkeyp == NULL ) ||( (*pkeyp) == NULL ) )
    {
        if( ( pk = EVP_PKEY_new() ) == NULL )
        {
            return FALSE;
        }
    }

    else
    {
        pk= (*pkeyp);
    }

    if ((x509p == NULL) || (*x509p == NULL))
    {
        if( ( x = X509_new() ) == NULL )
        {
            goto error;
        }
    }

    else
    {
        x = (*x509p);
    }

    log_info( LOG_LOW, NULL, "Generating RSA key, 2048 bit long modulus." );

    fprintf( stdout, "Generating RSA key, 2048 bit long modulus.\n" );

    rsa = RSA_generate_key( bits, RSA_F4, genrsa_cb, NULL );

    if( !EVP_PKEY_assign_RSA( pk, rsa ) )
    {
        goto error;
    }

    rsa = NULL;

    X509_set_version( x, 3 );

    ASN1_INTEGER_set( X509_get_serialNumber(x), serial);
    X509_gmtime_adj( X509_get_notBefore(x), 0 );
    X509_gmtime_adj( X509_get_notAfter(x), (long)( 60*60*24*days ) );

    X509_set_pubkey( x, pk);
    name = X509_get_subject_name( x );

    /* setup default values for cert fields. */

    X509_NAME_add_entry_by_txt( name,"C", MBSTRING_ASC, DEFAULT_CERT_C,
                                -1, -1, 0 );

    X509_NAME_add_entry_by_txt( name,"CN", MBSTRING_ASC, DEFAULT_CERT_CN,
                                -1, -1, 0 );

    X509_NAME_add_entry_by_txt( name,"OU", MBSTRING_ASC, DEFAULT_CERT_OU,
                                -1, -1, 0 );

    /* self signed, so we set issuer to be the same as the subject. */

    X509_set_issuer_name( x, name );

    /* TODO: add extension here. */

    /* sign this certificate. */

    if( !X509_sign( x, pk, EVP_md5() ) )
    {
        goto error;
    }

    /* pass values back from this function. */

    (*x509p) = x;
    (*pkeyp) = pk;

    return TRUE;

error:

    return FALSE;
}

/******************************************************************************
**
**    Function: client_address_is_allowed
**
**    Purpose:  translate the passed in address structure to a string
**              representation and see if it is in our list of allowed clients.
**
******************************************************************************/

osi_bool client_address_is_allowed( const char *ip )
{
    osi_bool result = FALSE;
    
    if( ip != NULL )
    {
        log_info( LOG_HIGH, NULL, "authenticating %s...", ip );

        if( string_list_contains_item( config->authorized_hosts, ip, TRUE ) ||
            string_list_contains_item( config->authorized_hosts, "*", TRUE ) )
        {
            result = TRUE;
        }
    }
    
    return result;
}

/******************************************************************************
**
**    Function: generate_keypair
**
**    Purpose:  generate an RSA key and self signed cert, and store them on the
**		local filesystem, with the correct permissions.
**
******************************************************************************/

osi_bool generate_keypair()
{
    osi_bool result    = FALSE;

    X509 *x509     = NULL;
    EVP_PKEY *pkey = NULL;

    if( make_certificate( &x509, &pkey, OSIRISMD_RSA_KEY_SIZE, 0, 365 ) )
    {
        log_info( LOG_LOW, NULL, "self signed certificate created." );

        if( osi_write_x509_to_path( x509, cert_file,
                                    DEFAULT_ROOT_CERT_PERMISSIONS ) == OSI_OK )
        {
            if( osi_write_pkey_to_path( pkey, private_key_file,
                                        DEFAULT_CERT_PERMISSIONS ) == OSI_OK )
            {
                log_info( LOG_LOW, NULL, "saved certificate: %s.",
                          cert_file );

                log_info( LOG_LOW, NULL, "saved private key: %s.",
                          private_key_file );

                result = TRUE;
            }

            /* remove the cert we just wrote, in case we can't write the key. */

            else
            {
                osi_remove_file( cert_file );
            }
        }
    }

    X509_free( x509 );
    EVP_PKEY_free( pkey );

    return result;
}

static void genrsa_cb( int p, int n, void *arg )
{
    char c = 'B';

    if( p == 0 ) c = '.';
    if( p == 1 ) c = '+';
    if( p == 2 ) c = '*';
    if( p == 3 ) c = '\n';

    fprintf( stdout, "%c", c );
    fflush( stdout );
}

