
/******************************************************************************
**
**  This program is free software; you can redistribute it and/or
**  modify it, however, you cannot sell it.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
**
**  You should have received a copy of the license attached to the
**  use of this software.  If not, visit www.shmoo.com/osiris for
**  details.
**
******************************************************************************/

/*****************************************************************************
**
**  File:    md_schedule.c
**  Date:    June 3, 2002
**
**  Author:  Brian Wotring
**  Purpose: deal with sheduling tasks for the management daemon.
**
******************************************************************************/

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

#include "common.h"

#include "md_hosts.h"
#include "md_log.h"
#include "md_config.h"
#include "md_compare.h"
#include "md_scan.h"
#include "md_schedule.h"
#include "md_utilities.h"
#include "md_notify.h"

#include "logging.h"
#include <signal.h>

extern char root_path[MAX_PATH_LENGTH];
extern int schedule_pipe[2];

extern void parse_configuration_file();

THREAD_FUNCTION_TYPE run_scheduler( void *unused )
{
    osi_list schedules = read_schedules();
    initialize_scheduling_signals();

    while( 1 )
    {
        scheduler_wait();

        /* check for stale schedules. */

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

            log_info( LOG_MEDIUM, NULL,
                      "scheduler is stale, reloading schedules." );

            list_destroy_with_function( schedules,(void (*)(void *))&osi_free );

            schedules = read_schedules();
            osi_read( schedule_pipe[0], x, 1 );

            /* reload our mhost config. */

            parse_configuration_file();
        }

        schedule_check( schedules );
    }

#ifdef WIN32
    _endthreadex(0);
#endif

    return THREAD_RETURN_VALUE;
}

void schedule_check( osi_list schedules )
{
    static unsigned long last = 0;
    int result;

    unsigned long base    = 0;
    unsigned long current_seconds = 0;
    unsigned long current_minutes = 0;

    OSI_HOST_BRIEF *host_brief;
    OSI_ERROR *error;

    char *failure_message;
    osi_bool schedule_failed = FALSE;

    osi_list temp;
    MESSAGE res;

    if( schedules == NULL )
    {
        return;
    }

    temp = list_get_first_node( schedules );

    /* get current time and convert to minutes. */

    current_seconds = osi_get_time();
    current_minutes = (unsigned long )( current_seconds / 60 );

    /* still the same minute. */

    if( current_minutes == last )
    {
        return;
    }

    /* loop through each schedule and check for a match. */

    while( temp )
    {
        OSI_SCHEDULE *s = (OSI_SCHEDULE *)temp->data;

        /* if we have a schedule and it isn't zeroed out. */

        if( ( s != NULL ) && ( s->start > 0 ) && ( s->period > 0 ) )
        {
            base = (unsigned long )( s->start / 60 );

            /* we haven't reached the start yet. */

            if( base  > current_minutes )
            {
                return;
            }

            last = current_minutes;

            /* we've reached a new minute. check our period value. */

            if( ( ( current_minutes - base ) % ( s->period ) ) == 0 )
            {
                time_t value = current_seconds;
                osi_uint16 type;

                /* if we push base db's config, then scan this host. */

                result = osi_host_push_base_db_config( s->host );

                if( result != OSI_OK )
                {
                    schedule_failed = TRUE;
                    failure_message = osi_get_name_for_error( result );

                    goto exit_gracefully;
                }

                scan_host( s->host, &res );
                type = GET_MESSAGE_TYPE( ((MESSAGE *)&res) );

                if( type == MESSAGE_TYPE_SUCCESS )
                {
                    log_info( LOG_MEDIUM, s->host, "scheduled scan started." );
                }

                /* error starting the scan, log the error. */

                else if( type == MESSAGE_TYPE_ERROR )
                {
                    error = MESSAGE_TO_TYPE( ((MESSAGE *)&res ), OSI_ERROR * );

                    if( error != NULL )
                    {
                        unwrap_error( error );

                        log_error( LOG_MEDIUM, s->host,
                                   "scheduled scan failed: %s",
                                   error->message );

                        host_brief = osi_read_host_brief( s->host );

                        schedule_failed = TRUE;
                        failure_message = error->message;
                    }
                }

                else
                {
                    log_error( LOG_MEDIUM, s->host,
                               "scheduled scan failed to start." );

                    schedule_failed = TRUE;
                    failure_message = "unable to contact host.";
                }

exit_gracefully:

                /* if we had an error, we might need to notify the admin. */

                if( schedule_failed )
                {
                    host_brief = osi_read_host_brief( s->host );

                    if( ( host_brief == NULL ) ||
                        ( host_brief->notify_enabled ) )
                    {

                        osi_notify_admin_scan_failed( s->host, current_seconds,
                                                      failure_message );
                    }

                    if( host_brief )
                    {
                        osi_free( host_brief );
                    }
                }
            }
        }

        temp = list_get_next_node( temp );

    } /* end while loop. */
}

osi_list read_schedules()
{
    osi_list temp;
    osi_list hosts;
    osi_list schedules;

    hosts = osi_read_host_briefs( root_path );

    if( hosts == NULL )
    {
        return NULL;
    }

    schedules = list_new();
    temp = list_get_first_node( hosts );

    while( temp )
    {
        OSI_HOST_BRIEF *host = (OSI_HOST_BRIEF *)temp->data;

        if( ( host != NULL ) && ( host->enabled ) )
        {
            OSI_SCHEDULE *s = osi_malloc( sizeof( OSI_SCHEDULE ) );        

            osi_strlcpy( s->host, host->name, sizeof( s->host ) );
            s->start = (unsigned long )host->schedule_start;
            s->period = (unsigned long)host->schedule_period;

            list_add( schedules, s );
        }

        temp = list_get_next_node( temp );
    }

    list_destroy_with_function( hosts, (void (*)(void *))&osi_free );

    return schedules;
}

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

void initialize_scheduling_signals()
{
    signal( SIGINT, scheduling_signal_handler );
    signal( SIGTERM, scheduling_signal_handler );

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

#ifndef WIN32
    signal( SIGHUP, scheduling_signal_handler );
    signal( SIGCHLD, scheduling_signal_handler );
    signal( SIGPIPE, scheduling_signal_handler );
    signal( SIGKILL, scheduling_signal_handler );
#endif
}

/****************************************************************************
**
**    Function: signal_handler
**
**    Purpose:  handle common signals, this is mostly for unix clients.
**
******************************************************************************/

void scheduling_signal_handler( int signal )
{
    int status = 0;

    switch( signal )
    {
        case SIGINT:

            exit(-1);
            break;

        case SIGTERM:

            exit(-1);
            break;

#ifndef WIN32

        case SIGCHLD:
            log_info( LOG_HIGH, NULL, "scheduler: SIGCHLD." );
            wait( &status );
            break;

        case SIGHUP:
        case SIGPIPE:
            break;

        case SIGKILL:
            exit(-1);
            break;
#endif
        default:
            break;
    }
}


void signal_schedules_updated()
{
    osi_write( schedule_pipe[1], "x", 1 );
}



/******************************************************************************
**
**    Function: scheduler_wait
**
**    Purpose:  waits for data to arrive on the scheduler pipe, or on 
**              the periodic timeout period.
**
******************************************************************************/

void scheduler_wait()
{
    int result;

    int max_descriptors;
    struct timeval timer;

    timer.tv_sec  = SCHEDULE_PERIOD_SECONDS;
    timer.tv_usec = SCHEDULE_PERIOD_USECONDS;

    FD_ZERO( &read_set );

    /* add the scheduler pipe. */

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

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

wait_for_data:

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

    /* in case select was whacked by a signal, we keep selecting. */

    if( result == -1 )
    {
        if( errno == EINTR )
        {
            goto wait_for_data;
        }
    }
}
