aboutsummaryrefslogtreecommitdiffstats
path: root/daemon
diff options
context:
space:
mode:
Diffstat (limited to 'daemon')
-rw-r--r--daemon/conf.c730
-rw-r--r--daemon/fifo.c105
-rw-r--r--daemon/listener.c335
-rw-r--r--daemon/logrotate.c264
-rw-r--r--daemon/main.c660
-rw-r--r--daemon/names.c63
-rw-r--r--daemon/pathnames.c139
-rw-r--r--daemon/purger.c155
-rw-r--r--daemon/syslogd.c600
-rw-r--r--daemon/syslogd.h204
-rw-r--r--daemon/writer.c498
11 files changed, 3753 insertions, 0 deletions
diff --git a/daemon/conf.c b/daemon/conf.c
new file mode 100644
index 0000000..654e46b
--- /dev/null
+++ b/daemon/conf.c
@@ -0,0 +1,730 @@
+/*
+ * conf.c - syslogd implementation for windows, configuration reader
+ *
+ * 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.
+ *
+ */
+
+/* define SYSLOG_CONF_DIR where syslog.host should be
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <winsock2.h>
+
+#include <glib.h>
+
+#include <syslog.h>
+#include <syslogd.h>
+
+#ifndef SYSLOG_CONF_DIR
+static char syslog_conf_dir[] = ".";
+#else
+static char syslog_conf_dir[] = SYSLOG_CONF_DIR;
+#endif
+
+/* options and their default values */
+gboolean use_dns = TRUE;
+gchar *source_encoding = NULL;
+gchar *destination_encoding = NULL;
+int mark_interval = 0;
+gchar *mark_message = "-- MARK --";
+int hold = 3;
+gchar *logdir = NULL;
+
+/* sources, destinations, filters and logpaths */
+struct logpath_names
+{
+ gchar *source;
+ gchar *filter;
+ gchar *destination;
+};
+
+GList *sources = NULL;
+GList *destinations = NULL;
+GList *filters = NULL;
+GList *logpaths = NULL;
+
+GList *purger_dirs = NULL;
+
+/* Glib markup wrapper data */
+static gchar *encoding = NULL;
+static gboolean prolog_expected = TRUE;
+/* parser data */
+static struct filter *current_filter = NULL;
+
+/******************************************************************************
+ * xml_start_element
+ *
+ * parse configuration elements
+ */
+static void xml_start_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ const gchar **attribute_names,
+ const gchar **attribute_values,
+ gpointer user_data,
+ GError **error)
+{
+ const gchar *aname, *aval;
+ int line_number;
+
+ prolog_expected = FALSE;
+
+ g_markup_parse_context_get_position( context, &line_number, NULL );
+
+ /* top-level elements */
+ if( strcmp( element_name, "source" ) == 0 )
+ {
+ struct source *source = g_malloc( sizeof(struct source) );
+
+ source->name = NULL;
+ source->type = ST_UNDEFINED;
+ memset( &source->udp, 0, sizeof(source->udp) );
+ source->udp.sin_family = AF_INET;
+ source->udp.sin_port = htons( SYSLOG_PORT );
+
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ aval = *attribute_values;
+ if( strcmp( aname, "name" ) == 0 )
+ source->name = g_strdup( aval );
+ else if( strcmp( aname, "type" ) == 0 )
+ {
+ if( strcmp( aval, "internal" ) == 0 )
+ source->type = ST_INTERNAL;
+ else if( strcmp( aval, "udp" ) == 0 )
+ source->type = ST_UDP;
+ }
+ else if( strcmp( aname, "interface" ) == 0 )
+ {
+ struct hostent *he = gethostbyname( aval );
+ if( !he )
+ {
+ ERR( "Cannot resolve hostname %s; error %lu\n", aval, WSAGetLastError() );
+ g_free( source );
+ return;
+ }
+ memcpy( &source->udp.sin_addr.s_addr, he->h_addr, he->h_length );
+ }
+ else if( strcmp( aname, "port" ) == 0 )
+ source->udp.sin_port = htons( strtoul( aval, NULL, 0 ) );
+ }
+
+ if( !source->name )
+ ERR( "Undefined source name at line %d\n", line_number );
+ if( ST_UNDEFINED == source->type )
+ ERR( "Undefined source type at line %d\n", line_number );
+ if( (!source->name) || ST_UNDEFINED == source->type )
+ {
+ g_free( source );
+ return;
+ }
+ sources = g_list_append( sources, source );
+ }
+ else if( strcmp( element_name, "destination" ) == 0 )
+ {
+ struct destination *dest = g_malloc0( sizeof(struct destination) );
+
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ aval = *attribute_values;
+ if( strcmp( aname, "name" ) == 0 )
+ dest->name = g_strdup( aval );
+ else if( strcmp( aname, "file" ) == 0 )
+ dest->file = normalize_pathname( aval );
+ else if( strcmp( aname, "rotate" ) == 0 )
+ {
+ if( strcmp( aval, "daily" ) == 0 )
+ dest->rotate = RP_DAILY;
+ else if( strcmp( aval, "weekly" ) == 0 )
+ dest->rotate = RP_WEEKLY;
+ else if( strcmp( aval, "monthly" ) == 0 )
+ dest->rotate = RP_MONTHLY;
+ else
+ {
+ ERR( "Invalid rotation period at line %d\n", line_number );
+ dest->rotate = RP_INVALID;
+ }
+ }
+ else if( strcmp( aname, "size" ) == 0 )
+ {
+ char *endptr;
+ dest->size = strtoul( aval, &endptr, 0 );
+ if( 'k' == *endptr )
+ dest->size *= 1024;
+ else if( 'M' == *endptr )
+ dest->size *= 1024 * 1024;
+ }
+ else if( strcmp( aname, "backlogs" ) == 0 )
+ dest->backlogs = strtoul( aval, NULL, 0 );
+ else if( strcmp( aname, "ifempty" ) == 0 )
+ {
+ if( strcmp( aval, "yes" ) == 0 )
+ dest->ifempty = TRUE;
+ else if( strcmp( aval, "no" ) == 0 )
+ dest->ifempty = FALSE;
+ else
+ {
+ dest->ifempty = TRUE;
+ ERR( "Invalid value \"%s\" of attribute \"%s\" at line %d; assumed \"yes\"\n",
+ aval, aname, line_number );
+ }
+ }
+ else if( strcmp( aname, "olddir" ) == 0 )
+ dest->olddir = normalize_pathname( aval );
+ else if( strcmp( aname, "compresscmd" ) == 0 )
+ dest->compresscmd = g_strdup( aval );
+ else if( strcmp( aname, "compressoptions" ) == 0 )
+ dest->compressoptions = g_strdup( aval );
+ }
+ if( !dest->name )
+ ERR( "Undefined destination name at line %d\n", line_number );
+ if( !dest->file )
+ ERR( "Undefined destination file at line %d\n", line_number );
+ if( (!dest->name) || (!dest->file) || RP_INVALID == dest->rotate )
+ {
+ if( dest->name ) g_free( dest->name );
+ if( dest->file ) g_free( dest->file );
+ if( dest->olddir ) g_free( dest->olddir );
+ if( dest->compresscmd ) g_free( dest->compresscmd );
+ if( dest->compressoptions ) g_free( dest->compressoptions );
+ g_free( dest );
+ return;
+ }
+ if( dest->compresscmd && !dest->compressoptions )
+ dest->compressoptions = g_strdup( "$PATHNAME" );
+
+ dest->file_writers = NULL;
+ InitializeCriticalSection( &dest->cs_file_writers );
+
+ destinations = g_list_append( destinations, dest );
+ }
+ else if( strcmp( element_name, "filter" ) == 0 )
+ {
+ current_filter = g_malloc0( sizeof(struct filter) );
+
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ if( strcmp( aname, "name" ) == 0 )
+ current_filter->name = g_strdup( *attribute_values );
+ }
+ if( !current_filter->name )
+ {
+ ERR( "Undefined filter name at line %d\n", line_number );
+ g_free( current_filter );
+ current_filter = NULL;
+ return;
+ }
+ }
+ else if( strcmp( element_name, "logpath" ) == 0 )
+ {
+ /* at first, fill logpaths list with logpath_names structures
+ and replace them later with logpath structures after configuration has been read
+ */
+ struct logpath_names *logpath = g_malloc0( sizeof(struct logpath_names) );
+
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ aval = *attribute_values;
+ if( strcmp( aname, "source" ) == 0 )
+ logpath->source = g_strdup( aval );
+ else if( strcmp( aname, "filter" ) == 0 )
+ logpath->filter = g_strdup( aval );
+ else if( strcmp( aname, "destination" ) == 0 )
+ logpath->destination = g_strdup( aval );
+ }
+ if( !logpath->source )
+ ERR( "Undefined log path source at line %d\n", line_number );
+ if( !logpath->destination )
+ ERR( "Undefined log path destination at line %d\n", line_number );
+ if( (!logpath->source) || (!logpath->destination) )
+ {
+ if( logpath->source ) g_free( logpath->source );
+ if( logpath->filter ) g_free( logpath->filter );
+ if( logpath->destination ) g_free( logpath->destination );
+ g_free( logpath );
+ return;
+ }
+ logpaths = g_list_append( logpaths, logpath );
+ }
+ else if( strcmp( element_name, "options" ) == 0 )
+ {
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ aval = *attribute_values;
+ if( strcmp( aname, "dns" ) == 0 )
+ {
+ if( strcmp( aval, "yes" ) == 0 )
+ use_dns = TRUE;
+ else if( strcmp( aval, "no" ) == 0 )
+ use_dns = FALSE;
+ else
+ ERR( "Invalid value \"%s\" of attribute \"%s\" at line %d\n", aval, aname, line_number );
+ }
+ else if( strcmp( aname, "source_encoding" ) == 0 )
+ source_encoding = g_strdup( aval );
+ else if( strcmp( aname, "destination_encoding" ) == 0 )
+ destination_encoding = g_strdup( aval );
+ else if( strcmp( aname, "mark_interval" ) == 0 )
+ mark_interval = strtoul( aval, NULL, 0 );
+ else if( strcmp( aname, "mark_message" ) == 0 )
+ mark_message = g_strdup( aval );
+ else if( strcmp( aname, "hold" ) == 0 )
+ {
+ hold = strtoul( aval, NULL, 0 );
+ if( hold < 1 )
+ hold = 1;
+ }
+ else if( strcmp( aname, "logdir" ) == 0 )
+ logdir = normalize_pathname( aval );
+ }
+ }
+ else if( strcmp( element_name, "purge" ) == 0 )
+ {
+ struct purger_dir *pdir = g_malloc0( sizeof(struct purger_dir) );
+
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ aval = *attribute_values;
+ if( strcmp( aname, "directory" ) == 0 )
+ pdir->directory = normalize_pathname( aval );
+ else if( strcmp( aname, "keep_days" ) == 0 )
+ pdir->keep_days = strtoul( aval, NULL, 0 );
+ }
+ if( !pdir->directory )
+ ERR( "Undefined purge directory at line %d\n", line_number );
+ if( !pdir->keep_days )
+ ERR( "Undefined keep_days parameter at line %d\n", line_number );
+ if( (!pdir->directory) || (!pdir->keep_days) )
+ {
+ if( pdir->directory ) g_free( pdir->directory );
+ g_free( pdir );
+ return;
+ }
+ purger_dirs = g_list_append( purger_dirs, pdir );
+ }
+ else if( current_filter )
+ {
+ /* sub-elements of filter */
+ int val = -2;
+
+ if( strcmp( element_name, "facility" ) == 0 )
+ {
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ aval = *attribute_values;
+ if( strcmp( aname, "value" ) == 0 )
+ {
+ val = strtol( aval, NULL, 0 );
+ if( val < 0 || val >= LOG_NFACILITIES )
+ {
+ val = -1;
+ break;
+ }
+ }
+ else if( strcmp( aname, "name" ) == 0 )
+ {
+ CODE *c;
+ for( c = facilitynames; c->c_name; c++ )
+ if( strcmp( aval, c->c_name ) == 0 )
+ {
+ val = LOG_FAC( c->c_val );
+ break;
+ }
+ if( !c->c_name )
+ {
+ val = -1;
+ break;
+ }
+ }
+ }
+ if( -2 == val )
+ ERR( "Undefined facility at line %d\n", line_number );
+ else if( val != -1 )
+ current_filter->facilities[ val ] = TRUE;
+ }
+ else if( strcmp( element_name, "priority" ) == 0 )
+ {
+ for( ; (aname = *attribute_names) != NULL; attribute_names++, attribute_values++ )
+ {
+ aval = *attribute_values;
+ if( strcmp( aname, "value" ) == 0 )
+ {
+ val = strtol( aval, NULL, 0 );
+ if( val < 0 || val >= 8 )
+ {
+ val = -1;
+ break;
+ }
+ }
+ else if( strcmp( aname, "name" ) == 0 )
+ {
+ CODE *c;
+ for( c = prioritynames; c->c_name; c++ )
+ if( strcmp( aval, c->c_name ) == 0 )
+ {
+ val = LOG_PRI( c->c_val );
+ break;
+ }
+ if( !c->c_name )
+ {
+ val = -1;
+ break;
+ }
+ }
+ }
+ if( -2 == val )
+ ERR( "Undefined priority at line %d\n", line_number );
+ else if( val != -1 )
+ current_filter->priorities[ val ] = TRUE;
+ }
+
+ if( -1 == val )
+ ERR( "Invalid value \"%s\" of attribute \"%s\" at line %d\n", aval, aname, line_number );
+ }
+}
+
+/******************************************************************************
+ * xml_end_element
+ */
+static void xml_end_element (GMarkupParseContext *context,
+ const gchar *element_name,
+ gpointer user_data,
+ GError **error)
+{
+ if( strcmp( element_name, "filter" ) == 0 )
+ {
+ if( current_filter )
+ {
+ /* append filter to the list */
+ filters = g_list_append( filters, current_filter );
+ current_filter = NULL;
+ }
+ }
+}
+
+/******************************************************************************
+ * xml_passthrough
+ *
+ * look for encoding name
+ */
+static void xml_passthrough (GMarkupParseContext *context,
+ const gchar *passthrough_text,
+ gsize text_len,
+ gpointer user_data,
+ GError **error)
+{
+ const gchar *startptr, *endptr;
+
+ if( !prolog_expected )
+ return;
+
+ startptr = g_strstr_len( passthrough_text, text_len, "<?xml " );
+ if( !startptr )
+ return;
+
+ startptr += 6;
+ text_len -= startptr - passthrough_text;
+
+ endptr = g_strstr_len( startptr, text_len, "?>" );
+ if( !endptr )
+ goto parsed;
+
+ text_len = endptr - startptr;
+ startptr = g_strstr_len( startptr, text_len, "encoding=\"" );
+ if( !startptr )
+ goto parsed;
+
+ startptr += 10;
+
+ endptr = strchr( startptr, '"' );
+ if( !endptr )
+ goto parsed;
+
+ if( strncmp( startptr, "windows-", 8 ) == 0 )
+ {
+ gchar *p;
+
+ startptr += 8;
+ p = g_strndup( startptr, endptr - startptr );
+ if( !p )
+ goto parsed;
+ encoding = g_strdup_printf( "CP%s", p );
+ g_free( p );
+ }
+ else
+ encoding = g_strndup( startptr, endptr - startptr );
+
+parsed:
+ prolog_expected = FALSE;
+}
+
+/******************************************************************************
+ * resolve_logpaths
+ *
+ * replace logpath_names structures
+ */
+static void resolve_logpaths()
+{
+ GList *paths = NULL;
+ GList *path_item;
+ struct logpath *logpath = NULL;
+
+ for( path_item = logpaths; path_item; path_item = path_item->next )
+ {
+ struct logpath_names *names = path_item->data;
+ GList *item;
+
+ g_free( logpath );
+ logpath = g_malloc( sizeof(struct logpath) );
+
+ /* find source */
+ for( item = sources; item; item = item->next )
+ {
+ struct source *s = item->data;
+ if( strcmp( s->name, names->source ) == 0 )
+ break;
+ }
+ if( !item )
+ {
+ ERR( "Undefined source \"%s\" in log path\n", names->source );
+ continue;
+ }
+ logpath->source = item->data;
+
+ /* find destination */
+ for( item = destinations; item; item = item->next )
+ {
+ struct destination *d = item->data;
+ if( strcmp( d->name, names->destination ) == 0 )
+ break;
+ }
+ if( !item )
+ {
+ ERR( "Undefined destination \"%s\" in log path\n", names->destination );
+ continue;
+ }
+ logpath->destination = item->data;
+
+ /* find filter */
+ if( !names->filter )
+ logpath->filter = NULL;
+ else
+ {
+ for( item = filters; item; item = item->next )
+ {
+ struct filter *f = item->data;
+ if( strcmp( f->name, names->filter ) == 0 )
+ break;
+ }
+ if( item )
+ logpath->filter = item->data;
+ else
+ logpath->filter = NULL;
+ }
+ /* add item to paths */
+ paths = g_list_append( paths, logpath );
+ logpath = NULL;
+ }
+
+ /* free list */
+ for( path_item = logpaths; path_item; path_item = path_item->next )
+ {
+ struct logpath_names *names = path_item->data;
+ g_free( names->source );
+ g_free( names->destination );
+ if( names->filter ) g_free( names->filter );
+ g_free( names );
+ }
+ g_list_free( logpaths );
+
+ /* set new list */
+ logpaths = paths;
+}
+
+/******************************************************************************
+ * dump_configuration
+ */
+static void dump_configuration()
+{
+# ifdef HAVE_DEBUG
+
+ GList *item;
+
+ TRACE( "Sources:\n" );
+ for( item = sources; item; item = item->next )
+ {
+ struct source *s = item->data;
+ TRACE( "\tname=%s\ttype=%s\tinterface=%d:%d:%d:%d\tport=%d\n",
+ s->name,
+ (s->type == ST_INTERNAL)? "internal" : ((s->type == ST_UDP)? "udp" : "undefined"),
+ s->udp.sin_addr.S_un.S_un_b.s_b1, s->udp.sin_addr.S_un.S_un_b.s_b2,
+ s->udp.sin_addr.S_un.S_un_b.s_b3, s->udp.sin_addr.S_un.S_un_b.s_b4,
+ ntohs( s->udp.sin_port ) );
+ }
+ TRACE( "Destinations:\n" );
+ for( item = destinations; item; item = item->next )
+ {
+ struct destination *d = item->data;
+ TRACE( "\tname=%s\tfile=%s\n"
+ "\t\trotate=%s size=%d backlogs=%d ifempty=%s\n"
+ "\t\tolddir=%s compresscmd=%s\n",
+ d->name, d->file,
+ (d->rotate == RP_DAILY)? "daily"
+ : (d->rotate == RP_WEEKLY)? "weekly"
+ : (d->rotate == RP_MONTHLY)? "monthly"
+ : "undefined",
+ d->size, d->backlogs, d->ifempty? "yes" : "no",
+ d->olddir? d->olddir : "NULL",
+ d->compresscmd? d->compresscmd : "NULL" );
+ }
+ TRACE( "Filters:\n" );
+ for( item = filters; item; item = item->next )
+ {
+ struct filter *f = item->data;
+ int i;
+ TRACE( "\tname=%s\n", f->name );
+ TRACE( "\tfacilities:\n" );
+ for( i = 0; i < LOG_NFACILITIES; i++ )
+ if( f->facilities[i] )
+ TRACE( "\t\t%s\n", get_facility_name( i ) );
+ TRACE( "\tpriorities:\n" );
+ for( i = 0; i < 8; i++ )
+ if( f->priorities[i] )
+ TRACE( "\t\t%s\n", get_priority_name( i ) );
+ }
+ TRACE( "Log paths:\n" );
+ for( item = logpaths; item; item = item->next )
+ {
+ struct logpath *p = item->data;
+ TRACE( "\tsource=%s\tfilter=%s\tdestination=%s\n",
+ p->source->name, p->filter? p->filter->name : "NULL", p->destination->name );
+ }
+ TRACE( "Purge directories:\n" );
+ for( item = purger_dirs; item; item = item->next )
+ {
+ struct purger_dir *p = item->data;
+ TRACE( "\tdirectory=%s\tkeep_days=%d\n", p->directory, p->keep_days );
+ }
+ TRACE( "Options:\n" );
+ TRACE( "\tuse_dns=%d\n", (int) use_dns );
+ TRACE( "\tsource_encoding=%s\n", source_encoding? source_encoding : "NULL" );
+ TRACE( "\tdestination_encoding=%s\n", destination_encoding? destination_encoding : "NULL" );
+ TRACE( "\tmark_interval=%d\n", mark_interval );
+ TRACE( "\tmark_message=%s\n", mark_message );
+ TRACE( "\thold=%d\n", hold );
+ TRACE( "\tlogdir=%s\n", logdir? logdir : "NULL" );
+
+# endif /* HAVE_DEBUG */
+}
+
+/******************************************************************************
+ * read_configuration
+ */
+gboolean read_configuration()
+{
+ gboolean ret = FALSE;
+ GMarkupParser parser = {
+ xml_start_element,
+ xml_end_element,
+ NULL,
+ xml_passthrough,
+ NULL
+ };
+ gchar *pathname;
+ FILE *fd = NULL;
+ GMarkupParseContext *ctx = NULL;
+ char buffer[256];
+ GError *error = NULL;
+
+ TRACE_ENTER( "\n" );
+
+ if( '\\' == syslog_conf_dir[0] || '/' == syslog_conf_dir[0] || ':' == syslog_conf_dir[1] )
+ /* absolute path */
+ pathname = g_build_filename( syslog_conf_dir, "syslog.conf", NULL );
+ else
+ /* relative path */
+ pathname = g_build_filename( g_path_get_dirname( __argv[0] ),
+ syslog_conf_dir, "syslog.conf", NULL );
+ fd = fopen( pathname, "r" );
+ if( !fd )
+ {
+ ERR( "Cannot open configuration file %s: %s\n", pathname, strerror(errno) );
+ goto done;
+ }
+
+ ctx = g_markup_parse_context_new( &parser, 0, NULL, NULL );
+ if( !ctx )
+ {
+ ERR( "Failed g_markup_parse_context_new\n" );
+ goto done;
+ }
+
+ while( fgets( buffer, sizeof(buffer), fd ) )
+ {
+ gchar *encoded = NULL;
+ gchar *parser_input;
+ gboolean r;
+
+ /* wrapper for Glib's XML parser:
+ determine encoding in xml_passthrough and convert data fed to the parser to UTF-8 */
+ if( encoding )
+ {
+ encoded = g_convert( buffer, -1, "UTF-8", encoding, NULL, NULL, &error );
+ if( !encoded )
+ goto done;
+
+ parser_input = encoded;
+ }
+ else
+ parser_input = buffer;
+
+ r = g_markup_parse_context_parse( ctx, parser_input, strlen( parser_input ), &error );
+ if( encoded ) g_free( encoded );
+ if( !r )
+ goto done;
+ }
+ if( !feof( fd ) )
+ {
+ ERR( "Cannot read configuration file %s: %s\n", pathname, strerror(errno) );
+ goto done;
+ }
+
+ resolve_logpaths();
+ dump_configuration();
+ if( !logdir )
+ {
+ ERR( "logdir is not defined\n" );
+ goto done;
+ }
+ ret = TRUE;
+
+done:
+ if( error )
+ {
+ gchar *locale_msg = g_locale_from_utf8( error->message, -1, NULL, NULL, NULL );
+ if( locale_msg )
+ {
+ ERR( "%s\n", locale_msg );
+ g_free( locale_msg );
+ }
+ g_error_free( error );
+ }
+ if( ctx ) g_markup_parse_context_free( ctx );
+ if( fd ) fclose( fd );
+ g_free( pathname );
+
+ TRACE_LEAVE( "done; ret=%d\n", (int) ret );
+ return ret;
+}
diff --git a/daemon/fifo.c b/daemon/fifo.c
new file mode 100644
index 0000000..2754885
--- /dev/null
+++ b/daemon/fifo.c
@@ -0,0 +1,105 @@
+/*
+ * fifo.c - syslogd implementation for windows, simple and fast queue
+ *
+ * Created by Alexander Yaworsky
+ *
+ * This replacement eliminates strange page faults which were
+ * with glib's 2.6.3 queues.
+ *
+ * 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 <stdio.h>
+#include <windows.h>
+#include <glib.h>
+#include <syslog.h>
+#include <syslogd.h>
+
+/******************************************************************************
+ * fifo_create
+ *
+ * Allocate and initialize fifo structure. Add an empty item to the fifo.
+ */
+struct fifo* fifo_create()
+{
+ struct fifo *ret = g_malloc( sizeof(struct fifo) );
+ struct fifo_item *guard_item= g_malloc( sizeof(struct fifo_item) );
+ ret->first = guard_item;
+ ret->last = guard_item;
+ guard_item->next = NULL;
+ guard_item->payload = NULL;
+ return ret;
+}
+
+/******************************************************************************
+ * fifo_destroy
+ *
+ * Delete all items and free fifo structure.
+ */
+void fifo_destroy( struct fifo* queue )
+{
+ struct fifo_item *item;
+
+ while( (item = queue->first) != NULL )
+ {
+ queue->first = item->next;
+ g_free( item );
+ }
+ g_free( queue );
+}
+
+/******************************************************************************
+ * fifo_push
+ *
+ * Add item to queue.
+ */
+void fifo_push( struct fifo* queue, void* data )
+{
+ struct fifo_item *item = g_malloc( sizeof(struct fifo_item) );
+ item->next = NULL;
+ item->payload = data;
+ queue->last->next = item;
+ queue->last = item;
+}
+
+/******************************************************************************
+ * fifo_pop
+ *
+ * Extract item from queue.
+ * Consider four possible conditions:
+ * 1) next == NULL, payload == NULL: there is the only one empty item
+ * in the queue; leave it and return NULL
+ * 2) next == NULL, payload != NULL: leave item and return its payload;
+ * set item's payload NULL
+ * 3) next != NULL, payload == NULL: queue was empty when a new item was
+ * added; free this item and go to the next one
+ * 4) next != NULL, payload != NULL: free item and return its payload
+ */
+void* fifo_pop( struct fifo* queue )
+{
+ for(;;)
+ {
+ struct fifo_item *item = queue->first;
+ struct fifo_item *next = InterlockedExchangePointer( &item->next, NULL );
+ void *data = item->payload;
+ item->payload = NULL;
+ if( next )
+ {
+ queue->first = next;
+ g_free( item );
+ }
+ if( data )
+ return data;
+ if( !next )
+ return NULL;
+ }
+}
diff --git a/daemon/listener.c b/daemon/listener.c
new file mode 100644
index 0000000..32c6297
--- /dev/null
+++ b/daemon/listener.c
@@ -0,0 +1,335 @@
+/*
+ * listener.c - syslogd implementation for windows, listener for UDP
+ * and "internal" sources
+ *
+ * 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 <stdio.h>
+#include <winsock2.h>
+
+#include <glib.h>
+
+#include <syslog.h>
+#include <syslogd.h>
+
+static SOCKET *socket_array = NULL;
+static int socket_count = 0;
+
+static struct source **source_references = NULL;
+
+static HANDLE *event_array = NULL;
+static int event_count = 0;
+
+/* message data */
+static unsigned max_datagram_size = 1024;
+static gchar *datagram_buffer = NULL;
+static struct raw_message message;
+
+static CRITICAL_SECTION cs_internal_message;
+static HANDLE internal_message_accepted = NULL;
+static gchar internal_message_buffer[ 1024 ];
+
+/******************************************************************************
+ * init_listener
+ *
+ * create sockets and synchronization objects including ones for "internal"
+ * source
+ */
+gboolean init_listener()
+{
+ gboolean ret = FALSE;
+ unsigned n;
+ GList *item;
+ int i;
+ struct source *internal_src;
+
+ TRACE_ENTER( "\n" );
+
+ /* create critical section and event for the access serialization to internal message buffer
+ */
+ InitializeCriticalSection( &cs_internal_message );
+ internal_message_accepted = CreateEvent( NULL, FALSE, FALSE, NULL );
+ if( !internal_message_accepted )
+ {
+ ERR( "Cannot create event; error %lu\n", GetLastError() );
+ goto done;
+ }
+
+ /* allocate memory for sockets and events;
+ * the number of sockets is not greater than number of sources
+ */
+ n = g_list_length( sources );
+ socket_array = g_malloc( n * sizeof(SOCKET) );
+
+ /* number of source references is greater by one because of inclusion the event
+ * for "internal" source
+ * FIXME: how about multiple internal sources?
+ */
+ source_references = g_malloc( (n + 1) * sizeof(struct source*) );
+
+ /* number of events is greater by two because of inclusion the event
+ * for "internal" source and the service_stop_event
+ */
+ event_array = g_malloc( (n + 2) * sizeof(HANDLE) );
+
+ /* create sockets */
+ for( item = sources; item; item = item->next )
+ {
+ struct source *src = item->data;
+ SOCKET sock;
+ unsigned dgram_size;
+ int size;
+
+ if( src->type == ST_INTERNAL )
+ {
+ internal_src = src;
+ continue;
+ }
+
+ if( src->type != ST_UDP )
+ continue;
+
+ sock = socket( AF_INET, SOCK_DGRAM, 0 );
+ if( INVALID_SOCKET == sock )
+ {
+ ERR( "socket() error %lu\n", WSAGetLastError() );
+ goto done;
+ }
+
+ if( bind( sock, (struct sockaddr*) &src->udp, sizeof(src->udp) ) )
+ {
+ ERR( "bind() error %lu\n", WSAGetLastError() );
+ closesocket( sock );
+ goto done;
+ }
+
+ size = sizeof(dgram_size);
+ if( getsockopt( sock, SOL_SOCKET, SO_MAX_MSG_SIZE, (char*) &dgram_size, &size ) )
+ {
+ ERR( "getsockopt( SO_MAX_MSG_SIZE ) error %lu\n", WSAGetLastError() );
+ closesocket( sock );
+ goto done;
+ }
+ TRACE( "datagram size for %d.%d.%d.%d:%d is %u\n",
+ src->udp.sin_addr.S_un.S_un_b.s_b1, src->udp.sin_addr.S_un.S_un_b.s_b2,
+ src->udp.sin_addr.S_un.S_un_b.s_b3, src->udp.sin_addr.S_un.S_un_b.s_b4,
+ ntohs( src->udp.sin_port ), dgram_size );
+ if( dgram_size > max_datagram_size )
+ max_datagram_size = dgram_size;
+
+ source_references[ socket_count ] = src;
+ socket_array[ socket_count++ ] = sock;
+ }
+ source_references[ socket_count ] = internal_src;
+
+ /* create events;
+ * service_stop_event is added to the array
+ */
+ while( event_count <= socket_count )
+ {
+ HANDLE evt = CreateEvent( NULL, FALSE, FALSE, NULL );
+ if( !evt )
+ {
+ ERR( "Cannot create event; error %lu\n", GetLastError() );
+ goto done;
+ }
+ event_array[ event_count++ ] = evt;
+ }
+ event_array[ event_count++ ] = service_stop_event;
+
+ /* bind events to sockets */
+ for( i = 0; i < socket_count; i++ )
+ {
+ if( WSAEventSelect( socket_array[ i ], event_array[ i ], FD_READ ) )
+ {
+ ERR( "WSAEventSelect() error %lu\n", WSAGetLastError() );
+ goto done;
+ }
+ }
+
+ /* allocate datagram buffer */
+ datagram_buffer = g_malloc( max_datagram_size );
+
+ ret = TRUE;
+
+done:
+ if( !ret )
+ fini_listener();
+
+ TRACE_LEAVE( "done; socket_count=%d, event_count=%d, max_datagram_size=%d, ret=%d\n",
+ socket_count, event_count, max_datagram_size, (int) ret );
+ return ret;
+}
+
+/******************************************************************************
+ * fini_listener
+ */
+void fini_listener()
+{
+ int i;
+
+ TRACE_ENTER( "\n" );
+
+ for( i = 0; i < socket_count; i++ )
+ closesocket( socket_array[ i ] );
+ g_free( socket_array );
+ socket_array = NULL;
+ socket_count = 0;
+
+ g_free( source_references );
+ source_references = NULL;
+
+ /* note that the last event is the service_stop_event
+ * and should not be destroyed
+ */
+ for( i = 0; i < event_count - 1; i++ )
+ CloseHandle( event_array[ i ] );
+ g_free( event_array );
+ event_array = NULL;
+ event_count = 0;
+
+ g_free( datagram_buffer );
+ datagram_buffer = NULL;
+
+ if( internal_message_accepted )
+ {
+ CloseHandle( internal_message_accepted );
+ internal_message_accepted = NULL;
+ }
+ DeleteCriticalSection( &cs_internal_message );
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * listener
+ *
+ * wait for a message; generate mark message;
+ * allocates a new raw_message structure and assigns its pointer to *msg
+ */
+enum listener_status listener( struct raw_message** msg )
+{
+ enum listener_status ret = LSNR_ERROR;
+ DWORD t, w;
+ int r;
+ int addrlen;
+
+ TRACE_ENTER( "\n" );
+
+ for(;;)
+ {
+ if( !mark_interval )
+ t = INFINITE;
+ else
+ t = mark_interval * 1000;
+ w = WaitForMultipleObjects( event_count, event_array, FALSE, t );
+ if( WAIT_TIMEOUT == w )
+ {
+ /* issue mark message */
+ log_internal( LOG_NOTICE, "%s", mark_message );
+ continue;
+ }
+ if( WAIT_FAILED == w )
+ {
+ ERR( "Wait error %lu\n", GetLastError() );
+ goto done;
+ }
+ if( w >= event_count )
+ {
+ ERR( "Unknown wait error\n" );
+ goto done;
+ }
+ if( w == event_count - 1 )
+ {
+ /* shut down */
+ ret = LSNR_SHUTDOWN;
+ goto done;
+ }
+ if( w == event_count - 2 )
+ {
+ /* got "internal" message */
+ message.source = source_references[ socket_count ];
+ if( !message.source )
+ {
+ /* internal source is not defined, cannot handle message */
+ SetEvent( internal_message_accepted );
+ continue;
+ }
+ message.msg = g_strdup( internal_message_buffer );
+ SetEvent( internal_message_accepted );
+ memset( &message.sender_addr, 0, sizeof(message.sender_addr) );
+ goto alloc_msg;
+ }
+ /* got UDP message, read it */
+ addrlen = sizeof(message.sender_addr);
+ r = recvfrom( socket_array[ w ], datagram_buffer, max_datagram_size,
+ 0, (struct sockaddr*) &message.sender_addr, &addrlen );
+ if( r < 0 )
+ {
+ ERR( "recvfrom() error %lu\n", WSAGetLastError() );
+ goto done;
+ }
+ if( !r )
+ continue;
+
+ message.msg = g_strndup( datagram_buffer, r );
+ message.source = source_references[ w ];
+
+ alloc_msg:
+ *msg = g_malloc( sizeof(struct raw_message) );
+ memcpy( *msg, &message, sizeof(struct raw_message) );
+
+ ret = LSNR_GOT_MESSAGE;
+ goto done;
+ }
+
+done:
+ TRACE_LEAVE( "done; ret=%d\n", (int) ret );
+ return ret;
+}
+
+/******************************************************************************
+ * log_internal
+ *
+ * generate internal log message
+ */
+void log_internal( int pri, char* fmt, ... )
+{
+ va_list args;
+ SYSTEMTIME stm;
+ int len;
+ char *p;
+
+ TRACE_ENTER( "\n" );
+ EnterCriticalSection( &cs_internal_message );
+
+ GetLocalTime( &stm );
+ len = sprintf( internal_message_buffer, "<%d>%s %2d %02d:%02d:%02d %s syslog: ",
+ LOG_SYSLOG | pri,
+ str_month[ stm.wMonth - 1 ], stm.wDay, stm.wHour, stm.wMinute, stm.wSecond,
+ local_hostname );
+ va_start( args, fmt );
+ vsnprintf( internal_message_buffer + len, sizeof(internal_message_buffer) - len, fmt, args );
+ va_end( args );
+ p = strchr( internal_message_buffer, '\n' );
+ if( p )
+ *p = 0;
+ p = strchr( internal_message_buffer, '\r' );
+ if( p )
+ *p = 0;
+
+ SetEvent( event_array[ event_count - 2 ] );
+ LeaveCriticalSection( &cs_internal_message );
+ TRACE_LEAVE( "done\n" );
+}
diff --git a/daemon/logrotate.c b/daemon/logrotate.c
new file mode 100644
index 0000000..5b0932a
--- /dev/null
+++ b/daemon/logrotate.c
@@ -0,0 +1,264 @@
+/*
+ * logrotate.c - syslogd implementation for windows, log rotation
+ *
+ * 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 <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <windows.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+
+#include <syslog.h>
+#include <syslogd.h>
+
+/******************************************************************************
+ * expand_options
+ *
+ * Substitute $PATHNAME and $FILENAME
+ */
+static gchar* expand_options( const gchar* compressoptions, const gchar* pathname )
+{
+ gchar *filename = g_path_get_basename( pathname );
+ GString *str = g_string_new( compressoptions );
+ gchar *substr_pathname;
+ gchar *substr_filename;
+
+ TRACE_ENTER( "\n" );
+ do {
+ substr_pathname = strstr( str->str, "$PATHNAME" );
+ substr_filename = strstr( str->str, "$FILENAME" );
+
+ if( substr_pathname )
+ {
+ int pos = substr_pathname - str->str;
+ g_string_erase( str, pos, 9 );
+ g_string_insert( str, pos, pathname );
+ }
+
+ if( substr_filename )
+ {
+ int pos = substr_filename - str->str;
+ g_string_erase( str, pos, 9 );
+ g_string_insert( str, pos, filename );
+ }
+ } while( substr_pathname || substr_filename );
+ g_free( filename );
+ TRACE_LEAVE( "return %s\n", str->str );
+ return g_string_free( str, FALSE );
+}
+
+/******************************************************************************
+ * compress_backlog
+ *
+ * Run external compression program.
+ * Return TRUE if all right.
+ */
+static gboolean compress_backlog( const gchar* compresscmd, const gchar* compressoptions,
+ const gchar* backlog )
+{
+ gboolean ret = FALSE;
+ char command_pathname[ MAX_PATH ];
+ LPTSTR command_filename;
+ gchar *options;
+ gchar *command_line;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+ DWORD exit_code;
+
+ TRACE_ENTER( "\n" );
+
+ if( !SearchPath( NULL, compresscmd, ".exe",
+ sizeof(command_pathname), command_pathname, &command_filename ) )
+ {
+ ERR( "Command %s not found\n", compresscmd );
+ TRACE_LEAVE( "error\n" );
+ return FALSE;
+ }
+
+ options = expand_options( compressoptions, backlog );
+ command_line = g_strconcat( command_pathname, " ", options, NULL );
+
+ memset( &si, 0, sizeof(si ) );
+ si.cb = sizeof(si);
+ TRACE_2( "command_line=%s\n", command_line );
+ if( !CreateProcess( NULL, command_line, NULL, NULL, FALSE,
+ DETACHED_PROCESS, NULL, NULL, &si, &pi ) )
+ ERR( "Cannot create process %s; error %lu\n", command_line, GetLastError() );
+ else
+ {
+ CloseHandle( pi.hThread );
+ TRACE_2( "waiting for %s\n", command_line );
+ WaitForSingleObject( pi.hProcess, INFINITE );
+ if( !GetExitCodeProcess( pi.hProcess, &exit_code ) )
+ exit_code = 1;
+ CloseHandle( pi.hProcess );
+ ret = 0 == exit_code;
+ }
+
+ g_free( command_line );
+ g_free( options );
+
+ TRACE_LEAVE( "ret=%d\n", ret );
+ return ret;
+}
+
+/******************************************************************************
+ * do_rotate
+ *
+ * All criteria for rotation are met, just do it.
+ */
+static void do_rotate( const gchar* pathname, struct destination* destination )
+{
+ int i;
+ gchar *dest_pathname;
+ gchar *backlog;
+
+ TRACE_ENTER( "\n" );
+
+ /* construct destination pathname for backlogs */
+ if( destination->olddir )
+ {
+ gchar *filename = g_path_get_basename( pathname );
+
+ if( g_path_is_absolute( destination->olddir ) )
+ dest_pathname = g_build_filename( destination->olddir, filename, NULL );
+ else
+ {
+ gchar *prefix = g_path_get_dirname( __argv[0] );
+ dest_pathname = g_build_filename( prefix, destination->olddir, filename, NULL );
+ g_free( prefix );
+ }
+ g_free( filename );
+ }
+ else
+ dest_pathname = g_strdup( pathname );
+
+ /* remove earliest backlog */
+ backlog = g_strdup_printf( "%s.%d", dest_pathname, destination->backlogs );
+ DeleteFile( backlog );
+ g_free( backlog );
+
+ /* rotate backlogs */
+ for( i = destination->backlogs; i > 1; i-- )
+ {
+ gchar *old_backlog = g_strdup_printf( "%s.%d", dest_pathname, i - 1 );
+ gchar *new_backlog = g_strdup_printf( "%s.%d", dest_pathname, i );
+ TRACE_2( "Move %s to %s\n", old_backlog, new_backlog );
+ if( !MoveFile( old_backlog, new_backlog ) )
+ {
+ /* most possible that old backlog file does not exist */
+ TRACE_2( "Can't move %s to %s; error %lu\n", old_backlog, new_backlog, GetLastError() );
+ }
+ g_free( old_backlog );
+ g_free( new_backlog );
+ }
+
+ backlog = g_strconcat( dest_pathname, ".1", NULL );
+
+ /* move current log */
+ TRACE_2( "Move %s to %s\n", pathname, backlog );
+ if( !MoveFile( pathname, backlog ) )
+ ERR( "Can't move %s to %s; error %lu\n", pathname, backlog, GetLastError() );
+
+ /* compress new backlog */
+ if( destination->compresscmd )
+ {
+ if( compress_backlog( destination->compresscmd, destination->compressoptions, backlog ) )
+ /* remove original uncompressed file */
+ DeleteFile( backlog );
+ }
+
+ g_free( backlog );
+ g_free( dest_pathname );
+
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * rotate_logfile
+ */
+void rotate_logfile( const gchar* pathname, struct destination* destination )
+{
+ struct stat fst;
+ gboolean rotated = FALSE;
+ time_t current_time;
+ struct tm *tm;
+
+ TRACE_ENTER( "pathname=%s\n", pathname );
+
+ if( 0 == destination->backlogs
+ || (RP_UNDEFINED == destination->rotate && 0 == destination->size) )
+ {
+ TRACE_LEAVE( "no conditions for rotation\n" );
+ return;
+ }
+
+ if( stat( pathname, &fst ) )
+ {
+ /* most possible that file does not exist */
+ TRACE_2( "stat(%s) error %s\n", pathname, strerror( errno ) );
+ goto done;
+ }
+
+ if( destination->size )
+ {
+ if( fst.st_size > destination->size )
+ {
+ do_rotate( pathname, destination );
+ rotated = TRUE;
+ goto done;
+ }
+ TRACE_2( "checked size: file=%d, max=%d\n", fst.st_size, destination->size );
+ }
+
+ current_time = time(NULL);
+ tm = gmtime( &current_time );
+ TRACE_2( "checking time: creation=%d, modification=%d\n", fst.st_ctime, fst.st_mtime );
+ switch( destination->rotate )
+ {
+ case RP_DAILY:
+ if( fst.st_mtime - (fst.st_ctime - fst.st_ctime % (24 * 3600)) > 24 * 3600 )
+ break;
+ goto done;
+ case RP_WEEKLY:
+ if( 0 == tm->tm_wday && fst.st_mtime - fst.st_ctime > 24 * 3600 )
+ break;
+ goto done;
+ case RP_MONTHLY:
+ if( 1 == tm->tm_mday && fst.st_mtime - fst.st_ctime > 24 * 3600 )
+ break;
+ goto done;
+ default:
+ goto done;
+ }
+
+ if( fst.st_size || destination->ifempty )
+ {
+ do_rotate( pathname, destination );
+ rotated = TRUE;
+ }
+ else
+ {
+ TRACE_2( "do not rotate empty file\n" );
+ }
+
+done:
+ TRACE_LEAVE( "done; %s\n", rotated? "rotated" : "not rotated" );
+}
diff --git a/daemon/main.c b/daemon/main.c
new file mode 100644
index 0000000..eb77bb7
--- /dev/null
+++ b/daemon/main.c
@@ -0,0 +1,660 @@
+/*
+ * main.c - syslogd implementation for windows, main function
+ *
+ * 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 <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <winsock2.h>
+
+#include <glib.h>
+
+#include <syslog.h>
+#include <syslogd.h>
+
+HANDLE service_stop_event;
+
+char local_hostname[ MAX_COMPUTERNAME_LENGTH + 1 ];
+
+int verbosity_level = 0;
+
+static char *service_name = "syslogd";
+static char *service_display_name = "syslogd";
+static char *service_stop_event_name = "syslogd-stop";
+
+static OSVERSIONINFO vi;
+
+static SERVICE_STATUS_HANDLE hss;
+static SERVICE_STATUS sstatus;
+
+/******************************************************************************
+ * display message
+ */
+void display_message( FILE* fd, char* file, int line, const char* func, char* fmt, ... )
+{
+ va_list args;
+ char formatstr[512];
+
+ snprintf( formatstr, sizeof(formatstr), "%08lX:%s:%d:%s: %s",
+ GetCurrentThreadId(), file, line, func, fmt );
+ va_start( args, fmt );
+ vfprintf( fd, formatstr, args );
+ va_end( args );
+ fflush( fd );
+}
+
+/******************************************************************************
+ * shutdown_service
+ *
+ * set service stop event
+ * wait until event exists
+ */
+static void shutdown_service( gboolean quiet )
+{
+ HANDLE he;
+ BOOL ret;
+
+ for(;;)
+ {
+ he = OpenEvent( EVENT_MODIFY_STATE, FALSE, service_stop_event_name );
+ if( !he )
+ {
+ if( !quiet )
+ ERR( "cannot open event; error %lu\n", GetLastError() );
+ return;
+ }
+ TRACE( "setting stop event\n" );
+ ret = SetEvent( he );
+ CloseHandle( he );
+ if( !ret )
+ {
+ if( !quiet )
+ {
+ ERR( "cannot set event; error %lu\n", GetLastError() );
+ return;
+ }
+ }
+ quiet = TRUE;
+ Sleep(0);
+ }
+}
+
+/******************************************************************************
+ * service control handler
+ */
+static void WINAPI ServiceControlHandler( DWORD Control )
+{
+ switch( Control )
+ {
+ case SERVICE_CONTROL_STOP:
+ sstatus.dwCurrentState = SERVICE_STOP_PENDING;
+ sstatus.dwCheckPoint = 0;
+ SetEvent( service_stop_event );
+ break;
+ case SERVICE_CONTROL_INTERROGATE: /* return status immediately */
+ break;
+ }
+ SetServiceStatus( hss, &sstatus );
+}
+
+/******************************************************************************
+ * service main
+ */
+static void WINAPI winnt_ServiceMain( DWORD Argc, LPTSTR* Argv )
+{
+ hss = RegisterServiceCtrlHandler( PACKAGE_NAME,
+ (LPHANDLER_FUNCTION)ServiceControlHandler );
+ if( !hss )
+ return;
+
+ service_stop_event = CreateEvent( NULL, TRUE, FALSE, NULL );
+ if( !service_stop_event )
+ return;
+
+ sstatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
+ sstatus.dwCurrentState = SERVICE_RUNNING;
+ sstatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;
+ sstatus.dwWin32ExitCode = NO_ERROR;
+ sstatus.dwCheckPoint = 0;
+ sstatus.dwWaitHint = 1000;
+ SetServiceStatus( hss, &sstatus );
+
+ syslogd_main();
+
+ sstatus.dwWin32ExitCode = 0;
+ sstatus.dwServiceSpecificExitCode = 0;
+ sstatus.dwCurrentState = SERVICE_STOPPED;
+ sstatus.dwCheckPoint = 0;
+ SetServiceStatus( hss, &sstatus );
+
+ CloseHandle( service_stop_event );
+}
+
+static int win9x_ServiceMain()
+{
+ service_stop_event = CreateEvent( NULL, TRUE, FALSE, service_stop_event_name );
+ if( !service_stop_event )
+ {
+ ERR( "Cannot create event %s; error %lu\n", service_stop_event_name, GetLastError() );
+ return 1;
+ }
+ if( ERROR_ALREADY_EXISTS == GetLastError() )
+ {
+ CloseHandle( service_stop_event );
+ ERR( "Service is already running\n" );
+ return 1;
+ }
+
+ syslogd_main();
+
+ CloseHandle( service_stop_event );
+ return 0;
+}
+
+/******************************************************************************
+ * open system Run key
+ */
+static BOOL open_run_key( PHKEY hk )
+{
+ static char runkey[] = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run";
+
+ TRACE_ENTER( "\n" );
+ if( RegOpenKey( HKEY_LOCAL_MACHINE, runkey, hk ) )
+ {
+ ERR( "Cannot open registry key %s; error %lu\n", runkey, GetLastError() );
+ return FALSE;
+ }
+ TRACE_LEAVE( "done\n" );
+ return TRUE;
+}
+
+/******************************************************************************
+ * service installer
+ */
+static BOOL win9x_InstallService( char* command_line )
+{
+ BOOL ret;
+ HKEY hk;
+ STARTUPINFO si;
+ PROCESS_INFORMATION pi;
+
+ TRACE_ENTER( "command_line=%s\n", command_line );
+ ret = open_run_key( &hk );
+ if( !ret )
+ {
+ TRACE_LEAVE( "done\n" );
+ return FALSE;
+ }
+
+ if( RegSetValueEx( hk, service_name, 0, REG_SZ, command_line, strlen( command_line ) + 1 ) )
+ {
+ ERR( "Cannot set registry value; error %lu\n", GetLastError() );
+ ret = FALSE;
+ }
+ RegCloseKey( hk );
+ if( !ret )
+ return FALSE;
+
+ memset( &si, 0, sizeof(si ) );
+ si.cb = sizeof(si);
+ ret = CreateProcess( NULL, command_line, NULL, NULL, FALSE, DETACHED_PROCESS,
+ NULL, NULL, &si, &pi );
+ if( !ret )
+ {
+ ERR( "Cannot create process; error %lu\n", GetLastError() );
+ return FALSE;
+ }
+
+ CloseHandle( pi.hThread );
+ CloseHandle( pi.hProcess );
+ TRACE_LEAVE( "done\n" );
+ return TRUE;
+}
+
+static BOOL winnt_InstallService( char* command_line )
+{
+ SC_HANDLE hscm, hsvc;
+ BOOL ret;
+
+ TRACE_ENTER( "command_line=%s\n", command_line );
+ hscm = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
+ if( !hscm )
+ {
+ ERR( "Cannot open service control manager; error %lu\n", GetLastError() );
+ return FALSE;
+ }
+ hsvc = CreateService( hscm, service_name, service_display_name,
+ SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
+ SERVICE_AUTO_START, SERVICE_ERROR_IGNORE,
+ command_line, NULL, NULL, NULL, NULL, NULL );
+ if( !hsvc )
+ {
+ ERR( "Cannot create service; error %lu\n", GetLastError() );
+ ret = FALSE;
+ }
+ else
+ {
+ ret = StartService( hsvc, 0, NULL );
+ if( !ret )
+ ERR( "Cannot start service; error %lu\n", GetLastError() );
+ CloseServiceHandle( hsvc );
+ }
+ CloseServiceHandle( hscm );
+ TRACE_LEAVE( "done\n" );
+ return ret;
+}
+
+static BOOL install_service( char* priority )
+{
+ BOOL ret;
+ char command_line[ MAX_PATH + 64 ];
+
+ TRACE_ENTER( "\n" );
+ if( __argv[0][1] == ':' )
+ command_line[0] = 0;
+ else
+ {
+ TRACE( "argv[0] contains no absolute path\n" );
+ GetCurrentDirectory( MAX_PATH, command_line );
+ strcat( command_line, "\\" );
+ }
+ strcat( command_line, __argv[0] );
+ strcat( command_line, " --service" );
+ if( priority )
+ {
+ strcat( command_line, " --priority " );
+ strcat( command_line, priority );
+ }
+
+ if( VER_PLATFORM_WIN32_NT == vi.dwPlatformId )
+ ret = winnt_InstallService( command_line );
+ else
+ ret = win9x_InstallService( command_line );
+
+ TRACE_LEAVE( "done; ret=%d\n", ret );
+ return ret;
+}
+
+/******************************************************************************
+ * service uninstaller
+ */
+static void win9x_RemoveService()
+{
+ HKEY hk;
+
+ TRACE_ENTER( "\n" );
+ if( !open_run_key( &hk ) )
+ return;
+ RegDeleteValue( hk, service_name );
+ RegCloseKey( hk );
+ TRACE_LEAVE( "done\n" );
+}
+
+static void winnt_RemoveService()
+{
+ SC_HANDLE hscm, hsvc;
+ int i;
+
+ TRACE_ENTER( "\n" );
+ hscm = OpenSCManager( NULL, NULL, SC_MANAGER_ALL_ACCESS );
+ if( !hscm )
+ {
+ ERR( "Cannot open service control manager; error %lu\n", GetLastError() );
+ return;
+ }
+
+ hsvc = OpenService( hscm, service_name, SERVICE_ALL_ACCESS );
+ if( !hsvc )
+ {
+ ERR( "Cannot open service %s; error %lu\n", service_name, GetLastError() );
+ CloseServiceHandle( hscm );
+ return;
+ }
+
+ ControlService( hsvc, SERVICE_CONTROL_STOP, &sstatus );
+
+ for( i = 0; i < 10; i++ )
+ {
+ Sleep( 100 );
+ if( !QueryServiceStatus( hsvc, &sstatus ) )
+ {
+ TRACE( "Cannot query service status; error %lu\n", GetLastError() );
+ break;
+ }
+ if( SERVICE_STOPPED == sstatus.dwCurrentState )
+ break;
+ }
+ if( !DeleteService( hsvc ) )
+ TRACE( "Cannot delete service; error %lu\n", GetLastError() );
+
+ CloseServiceHandle( hsvc );
+ CloseServiceHandle( hscm );
+ TRACE_LEAVE( "done\n" );
+}
+
+static void remove_service()
+{
+ TRACE_ENTER( "\n" );
+ if( VER_PLATFORM_WIN32_NT == vi.dwPlatformId )
+ winnt_RemoveService();
+ else
+ win9x_RemoveService();
+
+ /* always try to shutdown because it's possible that daemon
+ is running not as service under winNT */
+ shutdown_service( TRUE );
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * exception_handler
+ */
+LONG WINAPI exception_handler( PEXCEPTION_POINTERS ei )
+{
+ DWORD i;
+ BYTE *addr;
+
+ fprintf( stderr,
+ "*********************************\n"
+ "thread id:\t\t%lX\n"
+ "ExceptionCode:\t\t%lX\n"
+ "ExceptionFlags:\t\t%lX\n"
+ "ExceptionRecord:\t%p\n"
+ "ExceptionAddress:\t%p\n"
+ "NumberParameters:\t%lx\n"
+ "ExceptionInformation:\n",
+ GetCurrentThreadId(),
+ ei->ExceptionRecord->ExceptionCode,
+ ei->ExceptionRecord->ExceptionFlags,
+ ei->ExceptionRecord->ExceptionRecord,
+ ei->ExceptionRecord->ExceptionAddress,
+ ei->ExceptionRecord->NumberParameters );
+
+ for( i = 0; i < ei->ExceptionRecord->NumberParameters; i++ )
+ fprintf( stderr, "\t%lX\n", ei->ExceptionRecord->ExceptionInformation[i] );
+
+ fprintf( stderr,
+ "ContextFlags=%lX\n"
+ "CS=%lX DS=%lX ES=%lX SS=%lX FS=%lX GS=%lX\n"
+ "EAX=%lX EBX=%lX ECX=%lX EDX=%lX ESI=%lX EDI=%lX\n"
+ "EBP=%lX ESP=%lX EIP=%lX EFLAGS=%lX\n"
+ "Stack Dump:\n",
+ ei->ContextRecord->ContextFlags,
+ ei->ContextRecord->SegCs,
+ ei->ContextRecord->SegDs,
+ ei->ContextRecord->SegEs,
+ ei->ContextRecord->SegSs,
+ ei->ContextRecord->SegFs,
+ ei->ContextRecord->SegGs,
+ ei->ContextRecord->Eax,
+ ei->ContextRecord->Ebx,
+ ei->ContextRecord->Ecx,
+ ei->ContextRecord->Edx,
+ ei->ContextRecord->Esi,
+ ei->ContextRecord->Edi,
+ ei->ContextRecord->Ebp,
+ ei->ContextRecord->Esp,
+ ei->ContextRecord->Eip,
+ ei->ContextRecord->EFlags );
+
+ addr = (LPBYTE) (ei->ContextRecord->Esp);
+ while( !IsBadReadPtr( addr, 16 ) )
+ {
+ int skip = ((DWORD) addr) & 15;
+ BYTE *keep_addr = addr;
+
+ fprintf( stderr, "%08lX", ((DWORD) addr) & ~15 );
+ for( i = 0; i < skip; i++ )
+ fprintf( stderr, " " );
+ for( ; i < 8; i++ )
+ fprintf( stderr, " %02X", *addr++ );
+ if( i == 8 )
+ fputc( '-', stderr );
+ for( ; i < 16; i++ )
+ fprintf( stderr, "%02X ", *addr++ );
+ fputc( ' ', stderr );
+ addr = keep_addr;
+ for( i = 0; i < skip; i++ )
+ fputc( ' ', stderr );
+ for( ; i < 16; i++ )
+ {
+ BYTE b = *addr++;
+
+ if( b < 32 ) b = ' ';
+ fputc( b, stderr );
+ }
+ fputc( '\n', stderr );
+ }
+ fprintf( stderr, "*********************************\n" );
+ fflush( stderr );
+ ExitProcess(2);
+}
+
+/******************************************************************************
+ * main
+ *
+ * Parse command line
+ */
+int main( int argc, char* argv[] )
+{
+ static int help_flag = 0;
+ static int version_flag = 0;
+ static int install_flag = 0;
+ static int remove_flag = 0;
+ static int service_flag = 0;
+ static int shutdown_flag = 0;
+ char *instance_name = NULL;
+ char *priority = NULL;
+ int getopt_failure = 0;
+ WSADATA wsd;
+ DWORD size;
+
+ SetUnhandledExceptionFilter( exception_handler );
+
+ if( WSAStartup( MAKEWORD( 2, 2 ), &wsd ) )
+ {
+ ERR( "Cannot initialize winsock; error %lu\n", WSAGetLastError() );
+ return 1;
+ }
+
+ /* get local host name */
+ size = sizeof(local_hostname);
+ if( !GetComputerName( local_hostname, &size ) )
+ {
+ ERR( "Cannot get computer name; error %lu\n", GetLastError() );
+ return 1;
+ }
+ TRACE( "local host name=%s\n", local_hostname );
+
+ /* get windows version */
+ vi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
+ if( ! GetVersionEx( &vi ) )
+ {
+ ERR( "Cannot get windows version; error %lu\n", GetLastError() );
+ return 1;
+ }
+
+ for(;;)
+ {
+ static struct option long_options[] =
+ {
+ { "verbose", no_argument, NULL, 'v'},
+ { "help", no_argument, &help_flag, 1 },
+ { "version", no_argument, &version_flag, 1 },
+ { "install", no_argument, &install_flag, 1 },
+ { "remove", no_argument, &remove_flag, 1 },
+ { "service", no_argument, &service_flag, 1 },
+ { "shutdown", no_argument, &shutdown_flag,1 },
+ { "instance", required_argument, NULL, 'I'},
+ { "priority", required_argument, NULL, 'p'},
+ { 0, 0, 0, 0 }
+ };
+ int option_char;
+ int option_index;
+
+ option_char = getopt_long( argc, argv, "vhirsI:p:",
+ long_options, &option_index );
+ if( -1 == option_char )
+ break;
+
+ switch( option_char )
+ {
+ case 0:
+ break;
+
+ case 'v':
+ verbosity_level++;
+ break;
+
+ case 'h':
+ help_flag = 1;
+ break;
+
+ case 'i':
+ install_flag = 1;
+ break;
+
+ case 'r':
+ remove_flag = 1;
+ break;
+
+ case 'I':
+ instance_name = optarg;
+ break;
+
+ case 'p':
+ {
+ DWORD pclass;
+ if( strcmpi( optarg, "normal" ) == 0 )
+ {
+ priority = optarg;
+ pclass = NORMAL_PRIORITY_CLASS;
+ }
+ else if( strcmpi( optarg, "high" ) == 0 )
+ {
+ priority = optarg;
+ pclass = ABOVE_NORMAL_PRIORITY_CLASS;
+ }
+ else if( strcmpi( optarg, "highest" ) == 0 )
+ {
+ priority = optarg;
+ pclass = HIGH_PRIORITY_CLASS;
+ }
+ else
+ {
+ ERR( "Invalid priority %s; must be 'normal', 'high' or 'highest'\n", optarg );
+ break;
+ }
+ SetPriorityClass( GetCurrentProcess(), pclass );
+ break;
+ }
+ case '?':
+ /* getopt_long already printed an error message. */
+ getopt_failure++;
+ break;
+
+ default:
+ abort();
+ }
+ }
+ if( getopt_failure )
+ return 1;
+
+ /* handle flags in order of priority */
+ /* at first, check instance name */
+ if( instance_name )
+ {
+ service_name = g_strconcat( service_name, "_", instance_name, NULL );
+ service_display_name = g_strconcat( service_display_name, "_", instance_name, NULL );
+ service_stop_event_name = g_strconcat( service_stop_event_name, "_", instance_name, NULL );
+ }
+ /* check service flag */
+ if( service_flag )
+ {
+ if( VER_PLATFORM_WIN32_NT == vi.dwPlatformId )
+ {
+ /* run as service under windows NT */
+ static SERVICE_TABLE_ENTRY service_table[] = {
+ { "", (LPSERVICE_MAIN_FUNCTION) winnt_ServiceMain },
+ { NULL, NULL }
+ };
+
+ if( !StartServiceCtrlDispatcher( service_table ) )
+ {
+ ERR( "Cannot start service control dispatcher; error %lu\n", GetLastError() );
+ return 1;
+ }
+ return 0;
+ }
+ else
+ {
+ /* run as service under windows 9x */
+ FreeConsole();
+ return win9x_ServiceMain();
+ }
+ }
+
+ /* print version */
+ if( version_flag )
+ {
+ printf( "%s %s\n", PACKAGE_NAME, PACKAGE_VERSION );
+ return 0;
+ }
+
+ /* print help */
+ if( help_flag )
+ {
+ printf( "Usage: syslogd [OPTION]\n" );
+ printf( "This is syslog daemon for windows.\n" );
+ printf( "\n" );
+ printf( "-v, --verbose\tbe verbose; each occurence of this parameter\n"
+ "\t\tincreases verbosity\n" );
+ printf( "-i, --install\tinstall and start service\n" );
+ printf( "-r, --remove\tstop and remove service\n" );
+ printf( "-s, --shutdown\tsend shutdown signal to the daemon\n" );
+ printf( "-I, --instance\tset instance name in the case of multiple daemons\n" );
+ printf( "-p, --priority\tset priority class; value may be 'normal' (default),\n"
+ "\t\t\t'high', or 'highest'\n" );
+ printf( "-h, --help\tdisplay this message\n" );
+ printf( "--version\tdisplay version information\n" );
+ return 0;
+ }
+
+ /* install/remove/shutdown */
+ if( remove_flag )
+ {
+ if( install_flag || shutdown_flag )
+ ERR( "Remove option has priority over install/shutdown\n" );
+ remove_service();
+ return 0;
+ }
+ if( install_flag )
+ {
+ if( shutdown_flag )
+ ERR( "Install option has priority over shutdown\n" );
+ install_service( priority );
+ return 0;
+ }
+ if( shutdown_flag )
+ {
+ shutdown_service( FALSE );
+ return 0;
+ }
+
+ /* run as ordinary console application */
+ return win9x_ServiceMain();
+}
diff --git a/daemon/names.c b/daemon/names.c
new file mode 100644
index 0000000..7c31d21
--- /dev/null
+++ b/daemon/names.c
@@ -0,0 +1,63 @@
+/*
+ * names.c - syslogd implementation for windows, syslog priority
+ * and facility names
+ *
+ * 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 <stdio.h>
+#define SYSLOG_NAMES
+#include <syslog.h>
+
+char* get_priority_name( int pri )
+{
+ static char *names[] = {
+ "emerg",
+ "alert",
+ "crit",
+ "error",
+ "warning",
+ "notice",
+ "info",
+ "debug"
+ };
+ return names[ pri ];
+}
+
+char* get_facility_name( int fac )
+{
+ static char *names[] = {
+ "kern",
+ "user",
+ "mail",
+ "daemon",
+ "auth",
+ "syslog",
+ "lpr",
+ "news",
+ "uucp",
+ "cron",
+ "authpriv",
+ "ftp",
+ "local0",
+ "local1",
+ "local2",
+ "local3",
+ "local4",
+ "local5",
+ "local6",
+ "local7"
+ };
+ return names[ fac ];
+}
diff --git a/daemon/pathnames.c b/daemon/pathnames.c
new file mode 100644
index 0000000..932c854
--- /dev/null
+++ b/daemon/pathnames.c
@@ -0,0 +1,139 @@
+/*
+ * pathnames.c - syslogd implementation for windows, miscellaneous functions
+ * operating with file names and paths
+ *
+ * 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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <windows.h>
+
+#include <glib.h>
+
+#include <syslog.h>
+#include <syslogd.h>
+
+/******************************************************************************
+ * make_absolute_log_pathname
+ *
+ * Make absolute pathname inside logdir.
+ */
+gchar* make_absolute_log_pathname( char* path_appendix )
+{
+ gchar *path, *ret;
+
+ if( logdir && g_path_is_absolute( logdir ) )
+ path = g_strdup( logdir );
+ else
+ {
+ gchar *dir = g_path_get_dirname( __argv[0] );
+ path = g_build_filename( dir, logdir, NULL );
+ g_free( dir );
+ }
+ ret = g_build_filename( path, path_appendix, NULL );
+ g_free( path );
+ return ret;
+}
+
+/******************************************************************************
+ * create_directories
+ *
+ * Create all the directories in pathname.
+ */
+void create_directories( gchar* pathname )
+{
+ gchar *p;
+ gchar saved;
+
+ TRACE_ENTER( "%s\n", pathname );
+ p = (gchar*) g_path_skip_root( pathname );
+ if( !p )
+ p = pathname;
+ for(;;)
+ {
+ while( *p != '\0' && *p != '/' && *p != '\\' )
+ p++;
+ if( *p == '\0' )
+ break;
+ saved = *p;
+ *p = '\0';
+ CreateDirectory( pathname, NULL );
+ *p++ = saved;
+ }
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * normalize_pathname
+ *
+ * Remove . and .. from pathname.
+ * Return NULL if pathname is invalid.
+ */
+gchar* normalize_pathname( const gchar* pathname )
+{
+ gchar *ret = g_strdup( pathname );
+ gchar *first_element, *current_element, *next_element;
+
+ TRACE_ENTER( "%s\n", pathname );
+
+ first_element = (gchar*) g_path_skip_root( ret );
+ if( !first_element )
+ first_element = ret;
+
+ for( current_element = first_element; *current_element != '\0'; )
+ {
+ next_element = current_element;
+ while( *next_element != '\0' && *next_element != '/' && *next_element != '\\' )
+ next_element++;
+ if( *next_element != '\0' )
+ next_element++; /* skip separator */
+
+ if( current_element[0] != '.' )
+ current_element = next_element;
+ else
+ {
+ if( current_element[1] == '/'
+ || current_element[1] == '\\' || current_element[1] == '\0' )
+ /* /./ remove current element */
+ memmove( current_element, next_element, strlen( next_element ) + 1 );
+ else if( current_element[1] == '.'
+ && (current_element[2] == '/'
+ || current_element[2] == '\\' || current_element[2] == '\0') )
+ {
+ /* /../ find and remove previous element */
+ gchar *prev_element = current_element - 2;
+ if( prev_element < first_element )
+ {
+ ERR( "Invalid pathname: %s\n", pathname );
+ g_free( ret );
+ ret = NULL;
+ break;
+ }
+ while( prev_element > first_element && *prev_element != '/' && *prev_element != '\\' )
+ prev_element--;
+ if( prev_element > first_element )
+ prev_element++; /* skip separator */
+ memmove( prev_element, next_element, strlen( next_element ) + 1 );
+ current_element = prev_element;
+ }
+ else
+ current_element = next_element;
+ }
+ }
+ TRACE_LEAVE( "ret=%s\n", ret? ret : "NULL" );
+ return ret;
+}
diff --git a/daemon/purger.c b/daemon/purger.c
new file mode 100644
index 0000000..43be168
--- /dev/null
+++ b/daemon/purger.c
@@ -0,0 +1,155 @@
+/*
+ * purger.c - syslogd implementation for windows, log dir purger
+ *
+ * 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 <stdio.h>
+#include <windows.h>
+
+#include <glib.h>
+
+#include <syslog.h>
+#include <syslogd.h>
+
+static HANDLE purger_semaphore = NULL;
+
+/******************************************************************************
+ * recursive_purge
+ *
+ * auxiliary function for purge_log_dirs
+ */
+static void recursive_purge( gchar* pathname, LPFILETIME min_time )
+{
+ gchar *new_pathname;
+ HANDLE find_handle;
+ WIN32_FIND_DATA find_data;
+
+ TRACE_ENTER( "%s\n", pathname );
+
+ new_pathname = g_build_filename( pathname, "*", NULL );
+ find_handle = FindFirstFile( new_pathname, &find_data );
+ if( INVALID_HANDLE_VALUE == find_handle )
+ {
+ TRACE_2( "FindFirstFile(%s) error %lu\n", new_pathname, GetLastError() );
+ g_free( new_pathname );
+ TRACE_LEAVE( "it seems the path does not exist\n" );
+ return;
+ }
+ g_free( new_pathname );
+ do {
+ if( (find_data.cFileName[0] == '.' && find_data.cFileName[1] == '\0')
+ ||
+ (find_data.cFileName[0] == '.'
+ && find_data.cFileName[1] == '.' && find_data.cFileName[2] == '\0') )
+ continue;
+
+ new_pathname = g_build_filename( pathname, find_data.cFileName, NULL );
+ if( find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY )
+ {
+ recursive_purge( new_pathname, min_time );
+ if( RemoveDirectory( new_pathname ) )
+ {
+ TRACE( "removed %s\n", new_pathname );
+ }
+ else
+ {
+ TRACE( "failed to remove %s\n", new_pathname );
+ }
+ }
+ else
+ {
+ if( CompareFileTime( min_time, &find_data.ftLastWriteTime ) > 0 )
+ {
+ if( DeleteFile( new_pathname ) )
+ {
+ TRACE( "deleted %s\n", new_pathname );
+ }
+ else
+ {
+ TRACE( "failed to delete %s\n", new_pathname );
+ }
+ }
+ }
+ g_free( new_pathname );
+ } while( FindNextFile( find_handle, &find_data ) );
+ FindClose( find_handle );
+
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * purge_log_dirs
+ */
+void purge_log_dirs()
+{
+ GList *item;
+
+ TRACE_ENTER( "\n" );
+ if( WaitForSingleObject( purger_semaphore, 0 ) != WAIT_OBJECT_0 )
+ {
+ TRACE_LEAVE( "done; another thread is active\n" );
+ return;
+ }
+ for( item = purger_dirs; item; item = item->next )
+ {
+ struct purger_dir *p = item->data;
+ gchar *pathname = make_absolute_log_pathname( p->directory );
+ FILETIME min_time;
+ long long min_time_64;
+
+ GetSystemTimeAsFileTime( &min_time );
+ min_time_64 = (((long long) min_time.dwHighDateTime) << 32) + (long long) min_time.dwLowDateTime;
+ min_time_64 -= ((long long) p->keep_days) * ((long long) (24 * 3600)) * (long long) 10000000;
+ min_time.dwHighDateTime = min_time_64 >> 32;
+ min_time.dwLowDateTime = min_time_64;
+
+ recursive_purge( pathname, &min_time );
+ g_free( pathname );
+ }
+ ReleaseSemaphore( purger_semaphore, 1, NULL );
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * init_purger
+ */
+gboolean init_purger()
+{
+ gboolean ret = FALSE;
+
+ TRACE_ENTER( "\n" );
+
+ purger_semaphore = CreateSemaphore( NULL, 1, 1, NULL );
+ if( !purger_semaphore )
+ {
+ ERR( "Cannot create semaphore; error %lu\n", GetLastError() );
+ goto done;
+ }
+ ret = TRUE;
+
+done:
+ TRACE_LEAVE( "done; ret=%d\n", ret );
+ return ret;
+}
+
+/******************************************************************************
+ * fini_purger
+ */
+void fini_purger()
+{
+ TRACE_ENTER( "\n" );
+ if( purger_semaphore ) CloseHandle( purger_semaphore );
+ TRACE_LEAVE( "done\n" );
+}
diff --git a/daemon/syslogd.c b/daemon/syslogd.c
new file mode 100644
index 0000000..59ede6d
--- /dev/null
+++ b/daemon/syslogd.c
@@ -0,0 +1,600 @@
+/*
+ * 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;
+
+ write_message( msg, logpath->destination );
+ }
+
+ 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_USER;
+ *priority = 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_USER;
+ *priority = 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;
+}
+
+/******************************************************************************
+ * 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_writer();
+ 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" );
+}
diff --git a/daemon/syslogd.h b/daemon/syslogd.h
new file mode 100644
index 0000000..65dc042
--- /dev/null
+++ b/daemon/syslogd.h
@@ -0,0 +1,204 @@
+/*
+ * syslogd.h - syslogd implementation for windows, common definitions
+ *
+ * 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.
+ *
+ */
+
+extern int verbosity_level;
+extern void display_message( FILE* fd, char* file, int line, const char* func, char* fmt, ... );
+
+#ifdef HAVE_DEBUG
+# define DO_TRACE( verbosity, fmt... ) \
+ do { \
+ if( verbosity <= verbosity_level ) \
+ display_message( stderr, __FILE__, __LINE__, __FUNCTION__, fmt ); \
+ } while(0)
+# define TRACE_2( fmt... ) DO_TRACE( 2, fmt )
+# define TRACE( fmt... ) DO_TRACE( 1, fmt )
+#else
+# define TRACE_2( fmt... )
+# define TRACE( fmt... )
+#endif
+#define TRACE_ENTER TRACE_2
+#define TRACE_LEAVE TRACE_2
+#define ERR( fmt... ) display_message( stderr, __FILE__, __LINE__, __FUNCTION__, fmt )
+
+extern HANDLE service_stop_event;
+
+extern char local_hostname[];
+
+extern char *str_month[];
+
+extern void syslogd_main();
+
+/* options and their default values */
+extern gboolean use_dns;
+extern gchar *source_encoding;
+extern gchar *destination_encoding;
+extern int mark_interval;
+extern gchar *mark_message;
+extern int hold;
+extern gchar *logdir;
+
+/* sources, destinations, filters and logpaths */
+enum source_type
+{
+ ST_UNDEFINED,
+ ST_INTERNAL,
+ ST_UDP
+};
+
+struct source
+{
+ gchar *name;
+ enum source_type type;
+ struct sockaddr_in udp;
+};
+
+enum rotation_period
+{
+ RP_UNDEFINED = 0,
+ RP_INVALID,
+ RP_DAILY,
+ RP_WEEKLY,
+ RP_MONTHLY
+};
+
+struct destination
+{
+ gchar *name;
+ gchar *file;
+ GList *file_writers;
+ CRITICAL_SECTION cs_file_writers;
+ enum rotation_period rotate;
+ int size;
+ int backlogs;
+ gboolean ifempty;
+ gchar *olddir;
+ gchar *compresscmd;
+ gchar *compressoptions;
+};
+
+struct filter
+{
+ gchar *name;
+ gboolean facilities[ LOG_NFACILITIES ];
+ gboolean priorities[ 8 ];
+};
+
+struct logpath
+{
+ struct source *source;
+ struct filter *filter;
+ struct destination *destination;
+};
+
+extern GList *sources;
+extern GList *destinations;
+extern GList *filters;
+extern GList *logpaths;
+
+extern gboolean read_configuration();
+
+/* queue */
+struct fifo_item
+{
+ struct fifo_item *next; /* queue is a single-linked list */
+ void *payload;
+};
+
+struct fifo
+{
+ struct fifo_item *first; /* first pushed item */
+ struct fifo_item *last; /* last pushed item */
+};
+
+extern struct fifo* fifo_create();
+extern void fifo_destroy( struct fifo* queue );
+extern void fifo_push( struct fifo* queue, void* data );
+extern void* fifo_pop( struct fifo* queue );
+
+/* listener */
+enum listener_status
+{
+ LSNR_ERROR,
+ LSNR_SHUTDOWN,
+ LSNR_GOT_MESSAGE
+};
+
+struct raw_message
+{
+ gchar *msg;
+ struct sockaddr_in sender_addr;
+ struct source *source;
+};
+
+extern gboolean init_listener();
+extern void fini_listener();
+extern enum listener_status listener( struct raw_message** msg );
+extern void log_internal( int pri, char* fmt, ... );
+
+/* message */
+struct message
+{
+ LONG refcount;
+ struct source *source;
+ gchar *sender;
+ int facility;
+ int priority;
+ gchar *timestamp;
+ gchar *hostname;
+ gchar *program;
+ gchar *message;
+};
+
+extern void reference_message( struct message* msg );
+extern void release_message( struct message* msg );
+
+/* writer */
+extern void fini_writer();
+extern void write_message( struct message* msg, struct destination* destination );
+
+/* logrotate */
+extern void rotate_logfile( const gchar* pathname, struct destination* destination );
+
+/* purger */
+struct purger_dir
+{
+ gchar *directory;
+ int keep_days;
+};
+
+extern GList *purger_dirs;
+
+extern gboolean init_purger();
+extern void fini_purger();
+extern void purge_log_dirs();
+
+/* pathnames */
+extern gchar* make_absolute_log_pathname( char* path_appendix );
+extern void create_directories( gchar* pathname );
+extern gchar* normalize_pathname( const gchar* pathname );
+
+/* workaround for syslog.h: included with SYSLOG_NAMES in names.c */
+typedef struct _code {
+ char *c_name;
+ int c_val;
+} CODE;
+
+extern CODE prioritynames[];
+extern CODE facilitynames[];
+
+extern char* get_priority_name( int pri );
+extern char* get_facility_name( int pri );
diff --git a/daemon/writer.c b/daemon/writer.c
new file mode 100644
index 0000000..ea4f915
--- /dev/null
+++ b/daemon/writer.c
@@ -0,0 +1,498 @@
+/*
+ * writer.c - syslogd implementation for windows, message writer
+ *
+ * Created by Alexander Yaworsky
+ *
+ * Asynchronous i/o is not supported under win9x so we have to use
+ * a separate thread for each file.
+ *
+ * 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 <errno.h>
+#include <process.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <windows.h>
+
+#include <glib.h>
+
+#include <syslog.h>
+#include <syslogd.h>
+
+#define WRITER_KEEPALIVE_TIME 10000
+
+struct file_writer
+{
+ struct destination *destination;
+ gchar *file_name;
+ struct fifo *message_queue;
+ HANDLE fifo_semaphore;
+ HANDLE shutdown_event; /* manual-reset event */
+ HANDLE fd;
+ struct message *first_msg, *second_msg, *current_msg;
+ int coalesce_count;
+ time_t max_hold;
+};
+
+/* forward declarations */
+static unsigned __stdcall writer_thread_proc( void* arg );
+
+/******************************************************************************
+ * compare_current_and_first_messages
+ *
+ * Auxiliary function for coalescer.
+ * If messages aren't identical, reset coalescer.
+ */
+static gboolean compare_current_and_first_messages( struct file_writer* writer )
+{
+ TRACE_ENTER( "%p\n", writer );
+
+ if( strcmp( writer->current_msg->hostname, writer->first_msg->hostname )
+ || strcmp( writer->current_msg->message, writer->first_msg->message ) )
+ {
+ release_message( writer->first_msg );
+ writer->first_msg = NULL;
+ TRACE_LEAVE( "%p done; messages aren't identical\n", writer );
+ return FALSE;
+ }
+
+ TRACE_LEAVE( "%p ok\n" );
+ return TRUE;
+}
+
+/******************************************************************************
+ * init_coalescer
+ *
+ * Should be called when we've got the first unique message.
+ * Save message for subsequent comparsions and set max hold time.
+ */
+static void init_coalescer( struct file_writer* writer )
+{
+ writer->first_msg = writer->current_msg;
+ reference_message( writer->first_msg );
+ writer->max_hold = time(NULL) + hold;
+ writer->coalesce_count = 1;
+ TRACE_2( "%p max_hold=%ld\n", writer, writer->max_hold );
+}
+
+/******************************************************************************
+ * coalesce
+ *
+ * coalesce messages;
+ * If there are more than two sequential identical messages then we
+ * should write first of them followed by message with the number of
+ * subsequent messages.
+ *
+ * The caller always must process current_msg unless it is set NULL
+ * when coalesced;
+ * return: TRUE if messages are coalesced; FALSE if flushing is required
+ */
+static gboolean coalesce( struct file_writer* writer )
+{
+ time_t current_time;
+
+ TRACE_ENTER( "%p\n", writer );
+
+ switch( writer->coalesce_count )
+ {
+ case 0:
+ /* the first message */
+ init_coalescer( writer );
+ return TRUE;
+
+ case 1:
+ /* the second message */
+ TRACE_2( "%p second message\n", writer );
+ if( !compare_current_and_first_messages( writer ) )
+ return FALSE;
+ writer->second_msg = writer->current_msg;
+ writer->current_msg = NULL;
+ writer->coalesce_count = 2;
+ goto check_hold_time;
+
+ case 2:
+ /* the third message */
+ TRACE_2( "%p third message\n", writer );
+ if( !compare_current_and_first_messages( writer ) )
+ /* leave the second message; it will be written by flush_coalescer */
+ return FALSE;
+ release_message( writer->second_msg );
+ writer->second_msg = NULL;
+ release_message( writer->current_msg );
+ writer->current_msg = NULL;
+ writer->coalesce_count = 3;
+ goto check_hold_time;
+
+ default:
+ /* the fourth and subsequent messages */
+ TRACE_2( "%p fourth+ message\n", writer );
+ if( !compare_current_and_first_messages( writer ) )
+ return FALSE;
+ release_message( writer->current_msg );
+ writer->current_msg = NULL;
+ writer->coalesce_count++;
+ TRACE_2( "%p coalesce_count=%d\n", writer, writer->coalesce_count );
+ goto check_hold_time;
+ }
+
+check_hold_time:
+ current_time = time(NULL);
+ TRACE_2( "%p current_time=%ld\n", writer, current_time );
+ if( writer->max_hold < current_time )
+ {
+ TRACE_LEAVE( "%p done; elapsed hold time\n", writer );
+ return FALSE;
+ }
+ TRACE_LEAVE( "%p done\n", writer );
+ return TRUE;
+}
+
+/******************************************************************************
+ * write_message_to_logfile
+ */
+static void write_message_to_logfile( struct file_writer* writer, struct message** msg )
+{
+ gchar *buffer;
+ DWORD written;
+
+ if( INVALID_HANDLE_VALUE == writer->fd )
+ return;
+ TRACE_2( "%p: %s\n", writer, (*msg)->message );
+ buffer = g_strconcat( (*msg)->timestamp, " ", (*msg)->hostname, " ", (*msg)->message, "\n", NULL );
+ WriteFile( writer->fd, buffer, strlen( buffer ), &written, NULL );
+ g_free( buffer );
+ release_message( *msg );
+ *msg = NULL;
+}
+
+/******************************************************************************
+ * flush_coalescer
+ */
+static void flush_coalescer( struct file_writer* writer )
+{
+ if( writer->second_msg )
+ {
+ write_message_to_logfile( writer, &writer->second_msg );
+ TRACE_2( "%p written second message\n", writer );
+ }
+
+ if( writer->coalesce_count > 2 )
+ {
+ SYSTEMTIME stm;
+ char buffer[ 1024 ];
+ int size;
+ DWORD written;
+
+ GetLocalTime( &stm );
+
+ /* make informational message */
+ size = snprintf( buffer, sizeof(buffer),
+ "%s %2d %02d:%02d:%02d %s syslog: last message repeated %d times\n",
+ str_month[ stm.wMonth - 1 ], stm.wDay, stm.wHour, stm.wMinute, stm.wSecond,
+ local_hostname,
+ writer->coalesce_count - 1 );
+ WriteFile( writer->fd, buffer, size, &written, NULL );
+ TRACE_2( "%p made informational message\n", writer );
+ }
+
+ writer->coalesce_count = 0;
+ if( writer->first_msg )
+ {
+ release_message( writer->first_msg );
+ writer->first_msg = NULL;
+ }
+ if( writer->current_msg )
+ {
+ /* we just got the first message and should initialize coalescer */
+ init_coalescer( writer );
+ write_message_to_logfile( writer, &writer->current_msg );
+ TRACE_2( "%p written current message\n", writer );
+ }
+}
+
+/******************************************************************************
+ * destroy_file_writer
+ */
+static void destroy_file_writer( struct file_writer* writer )
+{
+ TRACE_ENTER( "%p\n", writer );
+ if( writer->fifo_semaphore ) CloseHandle( writer->fifo_semaphore );
+ if( writer->shutdown_event ) CloseHandle( writer->shutdown_event );
+ fifo_destroy( writer->message_queue );
+ g_free( writer->file_name );
+ g_free( writer );
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * create_file_writer
+ */
+static struct file_writer* create_file_writer( gchar* file_name )
+{
+ struct file_writer *ret;
+ unsigned writer_thread_id;
+ HANDLE *writer_thread;
+
+ TRACE_ENTER( "file_name=%s\n", file_name );
+ ret = g_malloc0( sizeof(struct file_writer) );
+ ret->file_name = g_strdup( file_name );
+ ret->message_queue = fifo_create();
+ ret->fifo_semaphore = CreateSemaphore( NULL, 0, LONG_MAX, NULL );
+ if( !ret->fifo_semaphore )
+ {
+ ERR( "Cannot create semaphore; error %lu\n", GetLastError() );
+ goto error;
+ }
+ ret->shutdown_event = CreateEvent( NULL, TRUE, FALSE, NULL );
+ if( !ret->shutdown_event )
+ {
+ ERR( "Cannot create event; error %lu\n", GetLastError() );
+ goto error;
+ }
+ writer_thread = (HANDLE) _beginthreadex( NULL, 0, writer_thread_proc, ret,
+ 0, &writer_thread_id );
+ if( !writer_thread )
+ {
+ ERR( "Cannot create thread; error %lu\n", GetLastError() );
+ goto error;
+ }
+ CloseHandle( writer_thread );
+ TRACE_LEAVE( "done; ret=%p\n", ret );
+ return ret;
+
+error:
+ destroy_file_writer( ret );
+ TRACE_LEAVE( "error\n" );
+ return NULL;
+}
+
+/******************************************************************************
+ * detach_writer_from_destination
+ */
+static void detach_writer_from_destination( struct file_writer* writer )
+{
+ TRACE_ENTER( "%p\n", writer );
+ if( !writer->destination )
+ {
+ TRACE_LEAVE( "done; already detached\n" );
+ return;
+ }
+ EnterCriticalSection( &writer->destination->cs_file_writers );
+ writer->destination->file_writers = g_list_remove( writer->destination->file_writers, writer );
+ LeaveCriticalSection( &writer->destination->cs_file_writers );
+ writer->destination = NULL;
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * writer_thread_proc
+ *
+ * Open file, extract messages from queue and write them to file.
+ * If queue is empty long enough, close file and destroy itself.
+ */
+static unsigned __stdcall writer_thread_proc( void* arg )
+{
+ struct file_writer *writer = arg;
+ HANDLE wait_objects[2] = { writer->fifo_semaphore, writer->shutdown_event };
+ gchar *pathname;
+ FILETIME systime;
+
+ TRACE_ENTER( "writer=%p\n", writer );
+
+ pathname = make_absolute_log_pathname( writer->file_name );
+ create_directories( pathname );
+
+ rotate_logfile( pathname, writer->destination );
+
+ writer->fd = CreateFile( pathname, GENERIC_WRITE, FILE_SHARE_READ, NULL,
+ OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );
+ g_free( pathname );
+ if( INVALID_HANDLE_VALUE == writer->fd )
+ {
+ ERR( "CreateFile(%s) error %lu\n", pathname, GetLastError() );
+ goto done;
+ }
+ SetFilePointer( writer->fd, 0, NULL, FILE_END );
+
+ /* there's a strange bug or feature in windows: if there was a file with the same
+ name in the directory, a new file will inherit its creation time;
+ because of this logs will be rotate each time;
+ here is a workaround:
+ */
+ GetSystemTimeAsFileTime( &systime );
+ if( !SetFileTime( writer->fd, &systime, &systime, &systime ) )
+ ERR( "SetFileTime error %lu\n", GetLastError() );
+
+ for(;;)
+ {
+ DWORD w = WaitForMultipleObjects( 2, wait_objects, FALSE, WRITER_KEEPALIVE_TIME );
+ if( WAIT_TIMEOUT == w )
+ {
+ /* prepare to suicide; at this point a new message may be put into queue;
+ detach writer from destination and continue to write any pending messages */
+ detach_writer_from_destination( writer );
+ /* from now no new messages will be put into queue */
+ SetEvent( writer->shutdown_event );
+ }
+
+ writer->current_msg = fifo_pop( writer->message_queue );
+ if( !writer->current_msg )
+ {
+ /* shutdown */
+ goto done;
+ }
+
+ if( coalesce( writer ) )
+ {
+ if( writer->current_msg )
+ {
+ write_message_to_logfile( writer, &writer->current_msg );
+ TRACE_2( "%p written current message\n", writer );
+ }
+ }
+ else
+ {
+ flush_coalescer( writer );
+ }
+ }
+done:
+ detach_writer_from_destination( writer );
+ flush_coalescer( writer );
+ if( writer->fd != INVALID_HANDLE_VALUE ) CloseHandle( writer->fd );
+ destroy_file_writer( writer );
+
+ purge_log_dirs();
+
+ TRACE_LEAVE( "done\n" );
+ return 0;
+}
+
+/******************************************************************************
+ * make_file_name
+ *
+ * expand filename pattern (message->file)
+ */
+static void make_file_name( char* pattern, struct message* message, char* buffer )
+{
+ char *dest = buffer;
+ SYSTEMTIME stm;
+
+ GetLocalTime( &stm );
+ for(;;)
+ {
+ char c = *pattern++;
+ if( c != '%' )
+ {
+ *dest++ = c;
+ if( '\0' == c )
+ break;
+ continue;
+ }
+ c = *pattern++;
+ switch( c )
+ {
+ case 'Y': dest += sprintf( dest, "%u", stm.wYear ); break;
+ case 'M': dest += sprintf( dest, "%02u", stm.wMonth ); break;
+ case 'm': dest += sprintf( dest, "%u", stm.wMonth ); break;
+ case 'D': dest += sprintf( dest, "%02u", stm.wDay ); break;
+ case 'd': dest += sprintf( dest, "%u", stm.wDay ); break;
+ case 'W': dest += sprintf( dest, "%u", stm.wDayOfWeek + 1 ); break;
+ case 'F': dest += sprintf( dest, "%s", get_facility_name( message->facility ) ); break;
+ case 'f': dest += sprintf( dest, "%d", message->facility ); break;
+ case 'L': dest += sprintf( dest, "%s", get_priority_name( message->priority ) ); break;
+ case 'l': dest += sprintf( dest, "%d", message->priority ); break;
+ case 'H': dest += sprintf( dest, "%s", message->hostname ); break;
+ case 'h': dest += sprintf( dest, "%s", message->sender ); break;
+ case 'P': dest += sprintf( dest, "%s", message->program ); break;
+ default: *dest++ = c; break;
+ }
+ }
+ strlwr( buffer );
+}
+
+/******************************************************************************
+ * write_message
+ */
+void write_message( struct message* msg, struct destination* destination )
+{
+ char file_name[ MAX_PATH ];
+ GList *item;
+ struct file_writer *writer;
+
+ TRACE_ENTER( "msg=%p, destination=%s\n", msg, destination->name );
+ make_file_name( destination->file, msg, file_name );
+ EnterCriticalSection( &destination->cs_file_writers );
+ /* find existing writer */
+ for( writer = NULL, item = destination->file_writers; item; item = item->next )
+ if( strcmp( ((struct file_writer*) (item->data))->file_name, file_name ) == 0 )
+ {
+ writer = item->data;
+ break;
+ }
+ if( !writer )
+ {
+ /* create new writer */
+ writer = create_file_writer( file_name );
+ if( !writer )
+ goto done;
+ /* add writer to destination */
+ destination->file_writers = g_list_append( destination->file_writers, writer );
+ writer->destination = destination;
+ }
+ /* put message into queue */
+ reference_message( msg );
+ fifo_push( writer->message_queue, msg );
+ ReleaseSemaphore( writer->fifo_semaphore, 1, NULL );
+
+done:
+ LeaveCriticalSection( &destination->cs_file_writers );
+ TRACE_LEAVE( "done\n" );
+}
+
+/******************************************************************************
+ * fini_writer
+ */
+void fini_writer()
+{
+ GList *dest_item;
+
+ TRACE_ENTER( "\n" );
+ /* setting shutdown event */
+ for( dest_item = destinations; dest_item; dest_item = dest_item->next )
+ {
+ struct destination *destination = dest_item->data;
+ GList *wr_item;
+
+ EnterCriticalSection( &destination->cs_file_writers );
+ for( wr_item = destination->file_writers; wr_item; wr_item = wr_item->next )
+ {
+ struct file_writer *writer = wr_item->data;
+ SetEvent( writer->shutdown_event );
+ }
+ LeaveCriticalSection( &destination->cs_file_writers );
+ }
+ TRACE_2( "waiting for shutdown\n" );
+ for(;;)
+ {
+ for( dest_item = destinations; dest_item; dest_item = dest_item->next )
+ if( ((struct destination*) (dest_item->data))->file_writers )
+ break;
+ if( !dest_item )
+ break;
+ Sleep( 60 );
+ }
+ TRACE_LEAVE( "done\n" );
+}