/* * 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 #include #include #include #include #include #include #include #include #include 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" ); }