aboutsummaryrefslogblamecommitdiffstats
path: root/daemon/syslogd.c
blob: 4c64a83aa841394e116172d6a440aec672357604 (plain) (tree)






































































































































































                                                                                    
                                                               



































































































































                                                                                          

                                          











                                                                            

                                      




















































































































































































                                                                                                  











                                                                               
























































































                                                                                                    
                        












                                                              
/*
 * syslogd.c - syslogd implementation for windows
 *
 * Created by Alexander Yaworsky
 *
 * THIS SOFTWARE IS NOT COPYRIGHTED
 *
 * This source code is offered for use in the public domain. You may
 * use, modify or distribute it freely.
 *
 * This code is distributed in the hope that it will be useful but
 * WITHOUT ANY WARRANTY. ALL WARRANTIES, EXPRESS OR IMPLIED ARE HEREBY
 * DISCLAIMED. This includes but is not limited to warranties of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *
 */

#include <ctype.h>
#include <process.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <winsock2.h>

#include <glib.h>

#include <syslog.h>
#include <syslogd.h>

static struct fifo *raw_message_queue = NULL;
static HANDLE fifo_semaphore = NULL;

/* cache for source hostnames */
struct hostname
{
    struct sockaddr_in addr;
    gchar *host;
    time_t top_age;  /* zero prevents aging */
};
static GList *hostnames = NULL;

#define HOSTNAME_LIFETIME 60 /* seconds */
/* FIXME: is this value correct? maybe we should make it configurable? */

static GIConv conversion_descriptor = (GIConv) -1;

char *str_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
                      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };

/******************************************************************************
 * refrence_message
 *
 * increment reference count
 */
void reference_message( struct message* msg )
{
    TRACE_ENTER( "message=%p\n", msg );
    InterlockedIncrement( &msg->refcount );
}

/******************************************************************************
 * release_message
 *
 * decrement reference count and destroy message structure if it becomes zero
 */
void release_message( struct message* msg )
{
    TRACE_ENTER( "message=%p\n", msg );
    if( InterlockedDecrement( &msg->refcount ) )
    {
        TRACE_LEAVE( "done; still referenced\n" );
        return;
    }
    g_free( msg->sender );
    g_free( msg->timestamp );
    g_free( msg->hostname );
    g_free( msg->program );
    g_free( msg->message );
    g_free( msg );
    TRACE_LEAVE( "done\n" );
}

/******************************************************************************
 * convert_message_encoding
 */
static void convert_message_encoding( struct message* msg )
{
    gchar *converted_msg;

    TRACE_ENTER( "message=%p\n", msg );

    if( conversion_descriptor == (GIConv) -1 )
    {
        TRACE_LEAVE( "nothing to do\n" );
        return;
    }

    converted_msg = g_convert_with_iconv( msg->message, -1,
                                          conversion_descriptor, NULL, NULL, NULL );
    if( !converted_msg )
    {
        TRACE_LEAVE( "conversion error\n" );
        return;
    }

    g_free( msg->message );
    msg->message = converted_msg;

    TRACE_LEAVE( "done; %s\n", msg->message );
}

/******************************************************************************
 * filter_message
 *
 * return: TRUE - accepted message, FALSE - rejected message
 */
static gboolean filter_message( struct message* msg, struct filter* filter )
{
    gboolean ret = FALSE;

    TRACE_ENTER( "message=%p, filter=%s\n", msg, filter? filter->name : "NULL" );

    if( !filter )
        goto passed;

    /* check facility */
    if( !filter->facilities[ msg->facility ] )
        goto done;

    /* check priority */
    if( !filter->priorities[ msg->priority ] )
        goto done;

passed:
    /* message is passed through filter */
    ret = TRUE;

done:
    TRACE_LEAVE( "done; ret=%d\n", ret );
    return ret;
}

/******************************************************************************
 * mux_message
 *
 * filter and multiplex message to destinations;
 * release message
 */
static void mux_message( struct message* msg )
{
    GList *item;

    TRACE_ENTER( "message=%p\n", msg );

    convert_message_encoding( msg );

    for( item = logpaths; item; item = item->next )
    {
        struct logpath *logpath = item->data;

        if( logpath->source != msg->source )
            continue;

        if( !filter_message( msg, logpath->filter ) )
            continue;

        logpath->destination->put( logpath->destination, msg );
    }

    release_message( msg );

    TRACE_LEAVE( "done\n" );
}

/******************************************************************************
 * get_hostname
 *
 * convert addr to string and return it
 */
static gchar* get_hostname( struct sockaddr_in* addr )
{
    gchar *ret;
    time_t current_time;
    GList *item;
    struct hostname *new_hostname;

    TRACE_ENTER( "%d.%d.%d.%d\n",
                 addr->sin_addr.S_un.S_un_b.s_b1, addr->sin_addr.S_un.S_un_b.s_b2,
                 addr->sin_addr.S_un.S_un_b.s_b3, addr->sin_addr.S_un.S_un_b.s_b4 );

    current_time = time(NULL);

    /* at first, try to find a cached entry */
    item = hostnames;
    while( item )
    {
        struct hostname *h = item->data;
        if( h->top_age && h->top_age < current_time )
        {
            GList *next_item = item->next;

            TRACE_2( "delete old entry %s\n", h->host );
            g_free( h->host );
            g_free( h );
            hostnames = g_list_delete_link( hostnames, item );
            item = next_item;
            continue;
        }
        if( h->addr.sin_addr.S_un.S_addr == addr->sin_addr.S_un.S_addr )
        {
            /* found in cache */
            ret = g_strdup( h->host );
            /* move entry to the beginning of the list */
            item->data = hostnames->data;
            hostnames->data = h;
            TRACE_LEAVE( "done; found cached entry: %s\n", ret );
            return ret;
        }
        item = item->next;
    }
    /* add new entry */
    new_hostname = g_malloc( sizeof(struct hostname) );
    memcpy( &new_hostname->addr, addr, sizeof(struct sockaddr_in) );
    if( use_dns )
    {
        struct hostent *he = gethostbyaddr( (char*) &addr->sin_addr.S_un.S_addr,
                                            sizeof(addr->sin_addr.S_un.S_addr), AF_INET );
        new_hostname->top_age = time(NULL) + HOSTNAME_LIFETIME;
        if( !he )
            goto use_addr;
        new_hostname->host = g_strdup( he->h_name );
    }
    else
    {
        new_hostname->top_age = 0;
use_addr:
        new_hostname->host = g_malloc( 16 );
        sprintf( new_hostname->host, "%d.%d.%d.%d",
                 addr->sin_addr.S_un.S_un_b.s_b1, addr->sin_addr.S_un.S_un_b.s_b2,
                 addr->sin_addr.S_un.S_un_b.s_b3, addr->sin_addr.S_un.S_un_b.s_b4 );
    }
    hostnames = g_list_prepend( hostnames, new_hostname );
    ret = g_strdup( new_hostname->host );
    TRACE_LEAVE( "done; ret=%s\n", ret );
    return ret;
}

/******************************************************************************
 * free_hostnames
 */
static void free_hostnames()
{
    GList *item;
    for( item = hostnames; item; item = item->next )
    {
        struct hostname *h = item->data;
        g_free( h->host );
        g_free( h );
    }
    g_list_free( hostnames );
    hostnames = NULL;
}

/******************************************************************************
 * parse_PRI
 *
 * parse PRI part of message;
 * set facility and priority;
 * return pointer to the next char after PRI
 */
static gchar* parse_PRI( gchar* msg, int* facility, int* priority )
{
    int i, pri;

    TRACE_ENTER( "\n" );

    if( *msg != '<' )
        goto no_pri;

    pri = 0;
    for( i = 1; i < 5; i++ )
    {
        if( msg[ i ] == '>' )
            break;
        if( !isdigit( msg[ i ] ) )
            goto no_pri;
        pri = (pri * 10) + (msg[ i ] - '0');
    }
    if( i == 5 )
        goto no_pri;
    if( i == 1 )
        /* FIXME: is this right? or "<>" is an unidentifiable PRI? */
        goto no_pri;

    msg += i + 1;

    if( pri > LOG_NFACILITIES * 8 + 7 )
    {
        TRACE_2( "Unidentifiable PRI %d\n", pri );
        *facility = LOG_FAC( LOG_USER );
        *priority = LOG_PRI( LOG_NOTICE );
    }
    else
    {
        TRACE_2( "PRI=%d\n", pri );
        *facility = LOG_FAC( pri );
        *priority = LOG_PRI( pri );
    }

    TRACE_LEAVE( "done; facility=%d, priority=%d\n", *facility, *priority );
    return msg;

no_pri:
    *facility = LOG_FAC( LOG_USER );
    *priority = LOG_PRI( LOG_NOTICE );
    TRACE_LEAVE( "done; message contains no PRI\n" );
    return msg;
}

/******************************************************************************
 * parse_timestamp
 *
 * parse TIMESTAMP part of message;
 * allocate string;
 * return pointer to the next char after TIMESTAMP
 */
static gchar* parse_timestamp( gchar* msg, gchar** timestamp )
{
    int i;
    SYSTEMTIME stm;

    TRACE_ENTER( "\n" );

    for( i = 0; i < 12; i++ )
        if( strncasecmp( msg, str_month[ i ], 3 ) == 0 )
            break;
    if( i == 12 )
        goto no_timestamp;
    stm.wMonth = i + 1;

    if( msg[3] != ' ' )
        goto no_timestamp;

    if( msg[4] != ' ' )
    {
        if( (!isdigit( msg[4] )) || (!isdigit( msg[5] )) )
            goto no_timestamp;
        stm.wDay = (msg[4] - '0') * 10 + (msg[5] - '0');
    }
    else
    {
        if( !isdigit( msg[5] ) )
            goto no_timestamp;
        stm.wDay = msg[5] - '0';
    }

    if( msg[6] != ' ' )
        goto no_timestamp;

    if( (!isdigit( msg[7] )) || (!isdigit( msg[8] )) || msg[9] != ':' )
        goto no_timestamp;
    stm.wHour = (msg[7] - '0') * 10 + (msg[8] - '0');

    if( (!isdigit( msg[10] )) || (!isdigit( msg[11] )) || msg[12] != ':' )
        goto no_timestamp;
    stm.wMinute = (msg[10] - '0') * 10 + (msg[11] - '0');

    if( (!isdigit( msg[13] )) || (!isdigit( msg[14] )) || msg[15] != ' ' )
        goto no_timestamp;
    stm.wSecond = (msg[13] - '0') * 10 + (msg[14] - '0');
    msg += 16;
    goto done;

no_timestamp:
    TRACE_2( "no timestamp\n" );
    GetLocalTime( &stm );

done:
    *timestamp = g_strdup_printf( "%s %2d %02d:%02d:%02d",
                                  str_month[ stm.wMonth - 1 ],
                                  stm.wDay, stm.wHour, stm.wMinute, stm.wSecond );
    TRACE_LEAVE( "done\n" );
    return msg;
}

/******************************************************************************
 * parse_raw_message
 *
 * parse raw message and make a new message;
 * destroy raw message
 */
static struct message* parse_raw_message( struct raw_message* raw_msg )
{
    struct message* msg;
    gchar *current_part, *next_part;

    TRACE_ENTER( "raw message=%p\n", raw_msg );

    /* allocate and initialize message structure */
    msg = g_malloc( sizeof(struct message) );
    msg->refcount = 1;
    msg->source = raw_msg->source;

    /* get sender's hostname */
    msg->sender = get_hostname( &raw_msg->sender_addr );

    current_part = raw_msg->msg;
    next_part = parse_PRI( current_part, &msg->facility, &msg->priority );

    current_part = next_part;
    next_part = parse_timestamp( current_part, &msg->timestamp );
    if( next_part == current_part )
    {
        /* no valid timestamp */
        TRACE_2( "no valid timestamp: msg=%s\n", current_part );
        msg->hostname = g_strdup( msg->sender );
        msg->message = g_strdup( current_part );
    }
    else
    {
        /* have valid timestamp, go ahead */
        current_part = next_part;
        while( isalnum( *next_part ) || *next_part == '-' || *next_part == '.' )
            next_part++;
        if( *next_part != ' ' )
        {
            /* invalid hostname */
            msg->hostname = g_strdup( msg->sender );
            msg->message = g_strdup( current_part );
            TRACE_2( "invalid hostname; set sender (%s); msg=%s\n", msg->hostname, msg->message );
        }
        else
        {
            msg->hostname = g_strndup( current_part, next_part - current_part );
            while( *next_part == ' ' && *next_part != 0 )
                next_part++;
            msg->message = g_strdup( next_part );
            TRACE_2( "hostname=%s; msg=%s\n", msg->hostname, msg->message );
        }
    }

    /* try to find program name */
    current_part = msg->message;
    next_part = current_part;
    while( *next_part != ' ' && *next_part != ':' && *next_part != '[' && *next_part != 0 )
        next_part++;
    if( *next_part == ' ' || *next_part == 0 )
        msg->program = g_strdup("");
    else
        msg->program = g_strndup( current_part, next_part - current_part );

    /* destroy raw message */
    g_free( raw_msg->msg );
    g_free( raw_msg );

    TRACE_LEAVE( "done; message=%p\n", msg );
    return msg;
}

/******************************************************************************
 * message_processor
 *
 * main function; extract raw messages from queue and parse them
 */
static unsigned __stdcall message_processor( void* arg )
{
    HANDLE wait_handles[2] = { fifo_semaphore, service_stop_event };

    TRACE_ENTER( "\n" );
    for(;;)
    {
        DWORD w;
        struct raw_message *raw_msg;

        w = WaitForMultipleObjects( 2, wait_handles, FALSE, INFINITE );
        if( WAIT_OBJECT_0 + 1 == w )
            /* shutdown */
            break;
        if( w != WAIT_OBJECT_0 )
        {
            ERR( "WaitForMultipleObjects() error %lu\n", GetLastError() );
            SetEvent( service_stop_event );
            break;
        }
        /* extract raw message from queue */
        raw_msg = fifo_pop( raw_message_queue );

        TRACE_2( "got message %p from queue\n", raw_msg );

        mux_message( parse_raw_message( raw_msg ) );
    }
    TRACE_LEAVE( "done\n" );
    return 0;
}

/******************************************************************************
 * fini_destinations
 *
 * for each destination call fini method
 */
static void fini_destinations()
{
    GList *dest;
    for( dest = destinations; dest; dest = dest->next )
        ((struct destination*) dest)->fini( (struct destination*) dest );
}

/******************************************************************************
 * main syslogd function
 *
 * global initialization; invoke listener
 */
void syslogd_main()
{
    HANDLE message_processor_thread = NULL;
    unsigned tid;

    TRACE_ENTER( "\n" );

    SetThreadPriority( GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL );

    if( !read_configuration() )
        goto done;

    if( !init_purger() )
        goto done;

    if( !init_listener() )
        goto done;

    purge_log_dirs();

    if( source_encoding && destination_encoding )
    {
        conversion_descriptor = g_iconv_open( destination_encoding, source_encoding );
        if( conversion_descriptor == (GIConv) -1 )
        {
            ERR( "Cannot convert messages from %s to %s\n",
                 source_encoding, destination_encoding );
        }
    }

    /* create message queue and semaphore;
     * avoid using Glib's asynchronous queues and threading support
     * because synchronization capabilities are very limited;
     */
    raw_message_queue = fifo_create();
    fifo_semaphore = CreateSemaphore( NULL, 0, LONG_MAX, NULL );
    if( !fifo_semaphore )
    {
        ERR( "Cannot create semaphore; error %lu\n", GetLastError() );
        goto done;
    }

    message_processor_thread = (HANDLE) _beginthreadex( NULL, 0, message_processor, NULL, 0, &tid );
    if( !message_processor_thread )
    {
        ERR( "Cannot create thread; error %lu\n", GetLastError() );
        goto done;
    }

    /* get messages from the listener */
    for(;;)
    {
        struct raw_message *raw_msg;

        switch( listener( &raw_msg ) )
        {
        case LSNR_ERROR:
        case LSNR_SHUTDOWN:
            goto done;

        case LSNR_GOT_MESSAGE:
            TRACE_2( "got message from %d.%d.%d.%d; source name %s; ptr=%p: %s\n",
                     raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b1,
                     raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b2,
                     raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b3,
                     raw_msg->sender_addr.sin_addr.S_un.S_un_b.s_b4,
                     raw_msg->source->name, raw_msg, raw_msg->msg );
            /* add raw message to queue */
            fifo_push( raw_message_queue, raw_msg );
            ReleaseSemaphore( fifo_semaphore, 1, NULL );
            break;
        }
    }

done:
    /* signal to all possibly running threads */
    SetEvent( service_stop_event );

    if( message_processor_thread )
    {
        /* wait for message processor shutdown */
        WaitForSingleObject( message_processor_thread, INFINITE );
        CloseHandle( message_processor_thread );
    }

    fini_destinations();
    fini_purger();
    free_hostnames();

    if( raw_message_queue ) fifo_destroy( raw_message_queue );
    if( conversion_descriptor != (GIConv) -1 )
        g_iconv_close( conversion_descriptor );

    fini_listener();

    if( fifo_semaphore ) CloseHandle( fifo_semaphore );

    TRACE_LEAVE( "done\n" );
}