summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
Diffstat (limited to 'runtime')
-rw-r--r--runtime/Makefile.am209
-rw-r--r--runtime/atomic.h229
-rw-r--r--runtime/batch.h197
-rw-r--r--runtime/cfsysline.c1057
-rw-r--r--runtime/cfsysline.h60
-rw-r--r--runtime/conf.c741
-rw-r--r--runtime/conf.h72
-rw-r--r--runtime/cryprov.h50
-rw-r--r--runtime/datetime.c1023
-rw-r--r--runtime/datetime.h66
-rw-r--r--runtime/debug.c1509
-rw-r--r--runtime/debug.h172
-rw-r--r--runtime/dnscache.c465
-rw-r--r--runtime/dnscache.h29
-rw-r--r--runtime/errmsg.c144
-rw-r--r--runtime/errmsg.h45
-rw-r--r--runtime/glbl.c696
-rw-r--r--runtime/glbl.h101
-rw-r--r--runtime/hashtable.c323
-rw-r--r--runtime/hashtable.h202
-rw-r--r--runtime/hashtable/Makefile26
-rw-r--r--runtime/hashtable/README11
-rw-r--r--runtime/hashtable/hashtable_utility.c71
-rw-r--r--runtime/hashtable/hashtable_utility.h55
-rw-r--r--runtime/hashtable/tester.c270
-rw-r--r--runtime/hashtable_itr.c190
-rw-r--r--runtime/hashtable_itr.h112
-rw-r--r--runtime/hashtable_private.h86
-rw-r--r--runtime/im-helper.h65
-rw-r--r--runtime/libgcry.c716
-rw-r--r--runtime/libgcry.h121
-rw-r--r--runtime/libgcry_common.c206
-rw-r--r--runtime/librsgt.c845
-rw-r--r--runtime/librsgt.h388
-rw-r--r--runtime/librsgt_read.c1092
-rw-r--r--runtime/linkedlist.c412
-rw-r--r--runtime/linkedlist.h71
-rw-r--r--runtime/lmcry_gcry.c337
-rw-r--r--runtime/lmcry_gcry.h39
-rw-r--r--runtime/lmsig_gt.c227
-rw-r--r--runtime/lmsig_gt.h40
-rw-r--r--runtime/module-template.h1019
-rw-r--r--runtime/modules.c1395
-rw-r--r--runtime/modules.h201
-rw-r--r--runtime/msg.c4085
-rw-r--r--runtime/msg.h247
-rw-r--r--runtime/net.c1545
-rw-r--r--runtime/net.h169
-rw-r--r--runtime/netstrm.c381
-rw-r--r--runtime/netstrm.h88
-rw-r--r--runtime/netstrms.c331
-rw-r--r--runtime/netstrms.h64
-rw-r--r--runtime/nsd.h109
-rw-r--r--runtime/nsd_gtls.c1750
-rw-r--r--runtime/nsd_gtls.h90
-rw-r--r--runtime/nsd_ptcp.c823
-rw-r--r--runtime/nsd_ptcp.h48
-rw-r--r--runtime/nsdpoll_ptcp.c316
-rw-r--r--runtime/nsdpoll_ptcp.h61
-rw-r--r--runtime/nsdsel_gtls.c276
-rw-r--r--runtime/nsdsel_gtls.h41
-rw-r--r--runtime/nsdsel_ptcp.c227
-rw-r--r--runtime/nsdsel_ptcp.h49
-rw-r--r--runtime/nspoll.c195
-rw-r--r--runtime/nspoll.h66
-rw-r--r--runtime/nssel.c226
-rw-r--r--runtime/nssel.h54
-rw-r--r--runtime/obj-types.h417
-rw-r--r--runtime/obj.c1481
-rw-r--r--runtime/obj.h129
-rw-r--r--runtime/objomsr.c156
-rw-r--r--runtime/objomsr.h51
-rw-r--r--runtime/parser.c728
-rw-r--r--runtime/parser.h71
-rw-r--r--runtime/prop.c247
-rw-r--r--runtime/prop.h65
-rw-r--r--runtime/queue.c2938
-rw-r--r--runtime/queue.h234
-rw-r--r--runtime/ratelimit.c385
-rw-r--r--runtime/ratelimit.h55
-rw-r--r--runtime/regexp.c101
-rw-r--r--runtime/regexp.h44
-rw-r--r--runtime/rsconf.c1386
-rw-r--r--runtime/rsconf.h182
-rw-r--r--runtime/rsyslog.c249
-rw-r--r--runtime/rsyslog.h559
-rw-r--r--runtime/ruleset.c1051
-rw-r--r--runtime/ruleset.h106
-rw-r--r--runtime/sd-daemon.c533
-rw-r--r--runtime/sd-daemon.h282
-rw-r--r--runtime/sigprov.h37
-rw-r--r--runtime/srUtils.h112
-rw-r--r--runtime/srutils.c659
-rw-r--r--runtime/statsobj.c379
-rw-r--r--runtime/statsobj.h158
-rw-r--r--runtime/stream.c2036
-rw-r--r--runtime/stream.h212
-rw-r--r--runtime/strgen.c279
-rw-r--r--runtime/strgen.h60
-rw-r--r--runtime/stringbuf.c1055
-rw-r--r--runtime/stringbuf.h230
-rw-r--r--runtime/strms_sess.c303
-rw-r--r--runtime/strms_sess.h74
-rw-r--r--runtime/strmsrv.c972
-rw-r--r--runtime/strmsrv.h110
-rw-r--r--runtime/syslogd-types.h111
-rw-r--r--runtime/typedefs.h203
-rw-r--r--runtime/unicode-helper.h67
-rw-r--r--runtime/unlimited_select.h45
-rw-r--r--runtime/var.c128
-rw-r--r--runtime/var.h63
-rw-r--r--runtime/wti.c399
-rw-r--r--runtime/wti.h59
-rw-r--r--runtime/wtp.c553
-rw-r--r--runtime/wtp.h100
-rw-r--r--runtime/zlibw.c124
-rw-r--r--runtime/zlibw.h44
117 files changed, 46648 insertions, 0 deletions
diff --git a/runtime/Makefile.am b/runtime/Makefile.am
new file mode 100644
index 00000000..dea06fe0
--- /dev/null
+++ b/runtime/Makefile.am
@@ -0,0 +1,209 @@
+sbin_PROGRAMS =
+man_MANS =
+noinst_LTLIBRARIES = librsyslog.la
+pkglib_LTLIBRARIES =
+#pkglib_LTLIBRARIES = librsyslog.la
+
+librsyslog_la_SOURCES = \
+ rsyslog.c \
+ rsyslog.h \
+ typedefs.h \
+ dnscache.c \
+ dnscache.h \
+ unicode-helper.h \
+ atomic.h \
+ batch.h \
+ syslogd-types.h \
+ module-template.h \
+ im-helper.h \
+ obj-types.h \
+ sigprov.h \
+ cryprov.h \
+ nsd.h \
+ glbl.h \
+ glbl.c \
+ unlimited_select.h \
+ conf.c \
+ conf.h \
+ rsconf.c \
+ rsconf.h \
+ parser.h \
+ parser.c \
+ strgen.h \
+ strgen.c \
+ msg.c \
+ msg.h \
+ linkedlist.c \
+ linkedlist.h \
+ objomsr.c \
+ objomsr.h \
+ stringbuf.c \
+ stringbuf.h \
+ datetime.c \
+ datetime.h \
+ srutils.c \
+ srUtils.h \
+ errmsg.c \
+ errmsg.h \
+ debug.c \
+ debug.h \
+ obj.c \
+ obj.h \
+ modules.c \
+ modules.h \
+ statsobj.c \
+ statsobj.h \
+ stream.c \
+ stream.h \
+ var.c \
+ var.h \
+ wtp.c \
+ wtp.h \
+ wti.c \
+ wti.h \
+ queue.c \
+ queue.h \
+ ruleset.c \
+ ruleset.h \
+ prop.c \
+ prop.h \
+ ratelimit.c \
+ ratelimit.h \
+ cfsysline.c \
+ cfsysline.h \
+ sd-daemon.c \
+ sd-daemon.h \
+ \
+ ../action.h \
+ ../action.c \
+ ../threads.c \
+ ../threads.h \
+ \
+ ../parse.c \
+ ../parse.h \
+ \
+ hashtable.c \
+ hashtable.h \
+ hashtable_itr.c \
+ hashtable_itr.h \
+ hashtable_private.h \
+ \
+ ../outchannel.c \
+ ../outchannel.h \
+ ../template.c \
+ ../template.h
+# the files with ../ we need to work on - so that they either become part of the
+# runtime or will no longer be needed. -- rgerhards, 2008-06-13
+#
+
+if WITH_MODDIRS
+librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -D_PATH_MODDIR=\"$(pkglibdir)/:$(moddirs)\" $(PTHREADS_CFLAGS) -I\$(top_srcdir)/tools
+else
+librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -D_PATH_MODDIR=\"$(pkglibdir)/\" -I$(top_srcdir) $(PTHREADS_CFLAGS) -I\$(top_srcdir)/tools -I\$(top_srcdir)/grammar
+endif
+#librsyslog_la_LDFLAGS = -module -avoid-version
+librsyslog_la_LIBADD = $(DL_LIBS) $(RT_LIBS)
+
+#
+# regular expression support
+#
+if ENABLE_REGEXP
+pkglib_LTLIBRARIES += lmregexp.la
+lmregexp_la_SOURCES = regexp.c regexp.h
+lmregexp_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmregexp_la_LDFLAGS = -module -avoid-version
+lmregexp_la_LIBADD =
+endif
+
+#
+# zlib support
+#
+if ENABLE_ZLIB
+pkglib_LTLIBRARIES += lmzlibw.la
+lmzlibw_la_SOURCES = zlibw.c zlibw.h
+lmzlibw_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmzlibw_la_LDFLAGS = -module -avoid-version
+lmzlibw_la_LIBADD =
+endif
+
+if ENABLE_INET
+pkglib_LTLIBRARIES += lmnet.la lmnetstrms.la
+#
+# network support
+#
+lmnet_la_SOURCES = net.c net.h
+lmnet_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmnet_la_LDFLAGS = -module -avoid-version ../compat/compat_la-getifaddrs.lo
+lmnet_la_LIBADD =
+
+# network stream master class and stream factory
+lmnetstrms_la_SOURCES = netstrms.c netstrms.h \
+ netstrm.c netstrm.h \
+ nssel.c nssel.h \
+ nspoll.c nspoll.h
+lmnetstrms_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmnetstrms_la_LDFLAGS = -module -avoid-version
+lmnetstrms_la_LIBADD =
+
+# generic stream server framework
+pkglib_LTLIBRARIES += lmstrmsrv.la
+lmstrmsrv_la_SOURCES = strmsrv.c strmsrv.h strms_sess.c strms_sess.h
+lmstrmsrv_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmstrmsrv_la_LDFLAGS = -module -avoid-version
+lmstrmsrv_la_LIBADD =
+
+# netstream drivers
+
+# plain tcp driver - main driver
+pkglib_LTLIBRARIES += lmnsd_ptcp.la
+lmnsd_ptcp_la_SOURCES = nsd_ptcp.c nsd_ptcp.h \
+ nsdsel_ptcp.c nsdsel_ptcp.h \
+ nsdpoll_ptcp.c nsdpoll_ptcp.h
+lmnsd_ptcp_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmnsd_ptcp_la_LDFLAGS = -module -avoid-version
+lmnsd_ptcp_la_LIBADD =
+endif # if ENABLE_INET
+
+#
+# GnuTLS netstream driver
+#
+if ENABLE_GNUTLS
+pkglib_LTLIBRARIES += lmnsd_gtls.la
+lmnsd_gtls_la_SOURCES = nsd_gtls.c nsd_gtls.h nsdsel_gtls.c nsdsel_gtls.h
+lmnsd_gtls_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(GNUTLS_CFLAGS)
+lmnsd_gtls_la_LDFLAGS = -module -avoid-version
+lmnsd_gtls_la_LIBADD = $(GNUTLS_LIBS)
+endif
+
+#
+# support library for libgcrypt
+#
+if ENABLE_LIBGCRYPT
+ noinst_LTLIBRARIES += libgcry.la
+ libgcry_la_SOURCES = libgcry.c libgcry_common.c libgcry.h
+ libgcry_la_CPPFLAGS = $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS)
+ pkglib_LTLIBRARIES += lmcry_gcry.la
+ lmcry_gcry_la_SOURCES = lmcry_gcry.c lmcry_gcry.h
+ lmcry_gcry_la_CPPFLAGS = $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS)
+ lmcry_gcry_la_LDFLAGS = -module -avoid-version
+ lmcry_gcry_la_LIBADD = libgcry.la $(LIBGCRYPT_LIBS)
+endif
+
+
+#
+# support library for guardtime
+#
+if ENABLE_GUARDTIME
+ noinst_LTLIBRARIES += librsgt.la
+ librsgt_la_SOURCES = librsgt.c librsgt_read.c librsgt.h
+ pkglib_LTLIBRARIES += lmsig_gt.la
+ lmsig_gt_la_SOURCES = lmsig_gt.c lmsig_gt.h
+ lmsig_gt_la_CPPFLAGS = $(RSRT_CFLAGS) $(GUARDTIME_CFLAGS)
+ lmsig_gt_la_LDFLAGS = -module -avoid-version
+ lmsig_gt_la_LIBADD = librsgt.la $(GUARDTIME_LIBS)
+endif
+
+
+update-systemd:
+ curl http://cgit.freedesktop.org/systemd/systemd/plain/src/libsystemd-daemon/sd-daemon.c > sd-daemon.c
+ curl http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h > sd-daemon.h
diff --git a/runtime/atomic.h b/runtime/atomic.h
new file mode 100644
index 00000000..2a895581
--- /dev/null
+++ b/runtime/atomic.h
@@ -0,0 +1,229 @@
+/* This header supplies atomic operations. So far, we rely on GCC's
+ * atomic builtins. During configure, we check if atomic operatons are
+ * available. If they are not, I am making the necessary provisioning to live without them if
+ * they are not available. Please note that you should only use the macros
+ * here if you think you can actually live WITHOUT an explicit atomic operation,
+ * because in the non-presence of them, we simply do it without atomicitiy.
+ * Which, for word-aligned data types, usually (but only usually!) should work.
+ *
+ * We are using the functions described in
+ * http:/gcc.gnu.org/onlinedocs/gcc/Atomic-Builtins.html
+ *
+ * THESE MACROS MUST ONLY BE USED WITH WORD-SIZED DATA TYPES!
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_ATOMIC_H
+#define INCLUDED_ATOMIC_H
+#include <time.h>
+#include "typedefs.h"
+
+/* for this release, we disable atomic calls because there seem to be some
+ * portability problems and we can not fix that without destabilizing the build.
+ * They simply came in too late. -- rgerhards, 2008-04-02
+ */
+#ifdef HAVE_ATOMIC_BUILTINS
+# define ATOMIC_SUB(data, val, phlpmut) __sync_fetch_and_sub(data, val)
+# define ATOMIC_ADD(data, val) __sync_fetch_and_add(&(data), val)
+# define ATOMIC_INC(data, phlpmut) ((void) __sync_fetch_and_add(data, 1))
+# define ATOMIC_INC_AND_FETCH_int(data, phlpmut) __sync_fetch_and_add(data, 1)
+# define ATOMIC_INC_AND_FETCH_unsigned(data, phlpmut) __sync_fetch_and_add(data, 1)
+# define ATOMIC_DEC(data, phlpmut) ((void) __sync_sub_and_fetch(data, 1))
+# define ATOMIC_DEC_AND_FETCH(data, phlpmut) __sync_sub_and_fetch(data, 1)
+# define ATOMIC_FETCH_32BIT(data, phlpmut) ((unsigned) __sync_fetch_and_and(data, 0xffffffff))
+# define ATOMIC_STORE_1_TO_32BIT(data) __sync_lock_test_and_set(&(data), 1)
+# define ATOMIC_STORE_0_TO_INT(data, phlpmut) __sync_fetch_and_and(data, 0)
+# define ATOMIC_STORE_1_TO_INT(data, phlpmut) __sync_fetch_and_or(data, 1)
+# define ATOMIC_STORE_INT_TO_INT(data, val) __sync_fetch_and_or(&(data), (val))
+# define ATOMIC_CAS(data, oldVal, newVal, phlpmut) __sync_bool_compare_and_swap(data, (oldVal), (newVal))
+# define ATOMIC_CAS_time_t(data, oldVal, newVal, phlpmut) __sync_bool_compare_and_swap(data, (oldVal), (newVal))
+# define ATOMIC_CAS_VAL(data, oldVal, newVal, phlpmut) __sync_val_compare_and_swap(data, (oldVal), (newVal));
+
+ /* functions below are not needed if we have atomics */
+# define DEF_ATOMIC_HELPER_MUT(x)
+# define INIT_ATOMIC_HELPER_MUT(x)
+# define DESTROY_ATOMIC_HELPER_MUT(x)
+
+ /* the following operations should preferrably be done atomic, but it is
+ * not fatal if not -- that means we can live with some missed updates. So be
+ * sure to use these macros only if that really does not matter!
+ */
+# define PREFER_ATOMIC_INC(data) ((void) __sync_fetch_and_add(&(data), 1))
+#else
+ /* note that we gained parctical proof that theoretical problems DO occur
+ * if we do not properly address them. See this blog post for details:
+ * http://blog.gerhards.net/2009/01/rsyslog-data-race-analysis.html
+ * The bottom line is that if there are no atomics available, we should NOT
+ * simply go ahead and do without them - use mutexes or other things. The
+ * code needs to be checked against all those cases. -- rgerhards, 2009-01-30
+ */
+ #include <pthread.h>
+# define ATOMIC_INC(data, phlpmut) { \
+ pthread_mutex_lock(phlpmut); \
+ ++(*(data)); \
+ pthread_mutex_unlock(phlpmut); \
+ }
+
+# define ATOMIC_STORE_0_TO_INT(data, hlpmut) { \
+ pthread_mutex_lock(hlpmut); \
+ *(data) = 0; \
+ pthread_mutex_unlock(hlpmut); \
+ }
+
+# define ATOMIC_STORE_1_TO_INT(data, hlpmut) { \
+ pthread_mutex_lock(hlpmut); \
+ *(data) = 1; \
+ pthread_mutex_unlock(hlpmut); \
+ }
+
+ static inline int
+ ATOMIC_CAS(int *data, int oldVal, int newVal, pthread_mutex_t *phlpmut) {
+ int bSuccess;
+ pthread_mutex_lock(phlpmut);
+ if(*data == oldVal) {
+ *data = newVal;
+ bSuccess = 1;
+ } else {
+ bSuccess = 0;
+ }
+ pthread_mutex_unlock(phlpmut);
+ return(bSuccess);
+ }
+
+ static inline int
+ ATOMIC_CAS_time_t(time_t *data, time_t oldVal, time_t newVal, pthread_mutex_t *phlpmut) {
+ int bSuccess;
+ pthread_mutex_lock(phlpmut);
+ if(*data == oldVal) {
+ *data = newVal;
+ bSuccess = 1;
+ } else {
+ bSuccess = 0;
+ }
+ pthread_mutex_unlock(phlpmut);
+ return(bSuccess);
+ }
+
+
+ static inline int
+ ATOMIC_CAS_VAL(int *data, int oldVal, int newVal, pthread_mutex_t *phlpmut) {
+ int val;
+ pthread_mutex_lock(phlpmut);
+ if(*data == oldVal) {
+ *data = newVal;
+ }
+ val = *data;
+ pthread_mutex_unlock(phlpmut);
+ return(val);
+ }
+
+# define ATOMIC_DEC(data, phlpmut) { \
+ pthread_mutex_lock(phlpmut); \
+ --(*(data)); \
+ pthread_mutex_unlock(phlpmut); \
+ }
+
+ static inline int
+ ATOMIC_INC_AND_FETCH_int(int *data, pthread_mutex_t *phlpmut) {
+ int val;
+ pthread_mutex_lock(phlpmut);
+ val = ++(*data);
+ pthread_mutex_unlock(phlpmut);
+ return(val);
+ }
+
+ static inline unsigned
+ ATOMIC_INC_AND_FETCH_unsigned(unsigned *data, pthread_mutex_t *phlpmut) {
+ unsigned val;
+ pthread_mutex_lock(phlpmut);
+ val = ++(*data);
+ pthread_mutex_unlock(phlpmut);
+ return(val);
+ }
+
+ static inline int
+ ATOMIC_DEC_AND_FETCH(int *data, pthread_mutex_t *phlpmut) {
+ int val;
+ pthread_mutex_lock(phlpmut);
+ val = --(*data);
+ pthread_mutex_unlock(phlpmut);
+ return(val);
+ }
+
+ static inline int
+ ATOMIC_FETCH_32BIT(int *data, pthread_mutex_t *phlpmut) {
+ int val;
+ pthread_mutex_lock(phlpmut);
+ val = (*data);
+ pthread_mutex_unlock(phlpmut);
+ return(val);
+ }
+
+ static inline void
+ ATOMIC_SUB(int *data, int val, pthread_mutex_t *phlpmut) {
+ pthread_mutex_lock(phlpmut);
+ (*data) -= val;
+ pthread_mutex_unlock(phlpmut);
+ }
+# define DEF_ATOMIC_HELPER_MUT(x) pthread_mutex_t x
+# define INIT_ATOMIC_HELPER_MUT(x) pthread_mutex_init(&(x), NULL)
+# define DESTROY_ATOMIC_HELPER_MUT(x) pthread_mutex_destroy(&(x))
+
+# define PREFER_ATOMIC_INC(data) ((void) ++data)
+
+#endif
+
+/* we need to handle 64bit atomics seperately as some platforms have
+ * 32 bit atomics, but not 64 biot ones... -- rgerhards, 2010-12-01
+ */
+#ifdef HAVE_ATOMIC_BUILTINS_64BIT
+# define ATOMIC_INC_uint64(data, phlpmut) ((void) __sync_fetch_and_add(data, 1))
+# define ATOMIC_DEC_unit64(data, phlpmut) ((void) __sync_sub_and_fetch(data, 1))
+# define ATOMIC_INC_AND_FETCH_uint64(data, phlpmut) __sync_fetch_and_add(data, 1)
+
+# define DEF_ATOMIC_HELPER_MUT64(x)
+# define INIT_ATOMIC_HELPER_MUT64(x)
+# define DESTROY_ATOMIC_HELPER_MUT64(x)
+#else
+# define ATOMIC_INC_uint64(data, phlpmut) { \
+ pthread_mutex_lock(phlpmut); \
+ ++(*(data)); \
+ pthread_mutex_unlock(phlpmut); \
+ }
+# define ATOMIC_DEC_uint64(data, phlpmut) { \
+ pthread_mutex_lock(phlpmut); \
+ --(*(data)); \
+ pthread_mutex_unlock(phlpmut); \
+ }
+
+ static inline unsigned
+ ATOMIC_INC_AND_FETCH_uint64(uint64 *data, pthread_mutex_t *phlpmut) {
+ uint64 val;
+ pthread_mutex_lock(phlpmut);
+ val = ++(*data);
+ pthread_mutex_unlock(phlpmut);
+ return(val);
+ }
+
+# define DEF_ATOMIC_HELPER_MUT64(x) pthread_mutex_t x
+# define INIT_ATOMIC_HELPER_MUT64(x) pthread_mutex_init(&(x), NULL)
+# define DESTROY_ATOMIC_HELPER_MUT64(x) pthread_mutex_destroy(&(x))
+#endif /* #ifdef HAVE_ATOMIC_BUILTINS_64BIT */
+
+#endif /* #ifndef INCLUDED_ATOMIC_H */
diff --git a/runtime/batch.h b/runtime/batch.h
new file mode 100644
index 00000000..2ec07670
--- /dev/null
+++ b/runtime/batch.h
@@ -0,0 +1,197 @@
+/* Definition of the batch_t data structure.
+ * I am not sure yet if this will become a full-blown object. For now, this header just
+ * includes the object definition and is not accompanied by code.
+ *
+ * Copyright 2009 by Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef BATCH_H_INCLUDED
+#define BATCH_H_INCLUDED
+
+#include <string.h>
+#include "msg.h"
+
+/* enum for batch states. Actually, we violate a layer here, in that we assume that a batch is used
+ * for action processing. So far, this seems acceptable, the status is simply ignored inside the
+ * main message queue. But over time, it could potentially be useful to split the two.
+ * rgerhad, 2009-05-12
+ */
+#define BATCH_STATE_RDY 0 /* object ready for processing */
+#define BATCH_STATE_BAD 1 /* unrecoverable failure while processing, do NOT resubmit to same action */
+#define BATCH_STATE_SUB 2 /* message submitted for processing, outcome yet unknown */
+#define BATCH_STATE_COMM 3 /* message successfully commited */
+#define BATCH_STATE_DISC 4 /* discarded - processed OK, but do not submit to any other action */
+typedef unsigned char batch_state_t;
+
+
+/* an object inside a batch, including any information (state!) needed for it to "life".
+ */
+struct batch_obj_s {
+ msg_t *pMsg;
+ /* work variables for action processing; these are reused for each action (or block of
+ * actions)
+ */
+ sbool bPrevWasSuspended;
+ /* following are caches to save allocs if not absolutely necessary */
+ uchar *staticActStrings[CONF_OMOD_NUMSTRINGS_MAXSIZE]; /**< for strings */
+ /* a cache to save malloc(), if not absolutely necessary */
+ void *staticActParams[CONF_OMOD_NUMSTRINGS_MAXSIZE]; /**< for anything else */
+ size_t staticLenStrings[CONF_OMOD_NUMSTRINGS_MAXSIZE];
+ /* and the same for the message length (if used) */
+ /* end action work variables */
+};
+
+/* the batch
+ * This object is used to dequeue multiple user pointers which are than handed over
+ * to processing. The size of elements is fixed after queue creation, but may be
+ * modified by config variables (better said: queue properties).
+ * Note that a "user pointer" in rsyslog context so far always is a message
+ * object. We stick to the more generic term because queues may potentially hold
+ * other types of objects, too.
+ * rgerhards, 2009-05-12
+ * Note that nElem is not necessarily equal to nElemDeq. This is the case when we
+ * discard some elements (because of configuration) during dequeue processing. As
+ * all Elements are only deleted when the batch is processed, we can not immediately
+ * delete them. So we need to keep their number that we can delete them when the batch
+ * is completed (else, the whole process does not work correctly).
+ */
+struct batch_s {
+ int maxElem; /* maximum number of elements that this batch supports */
+ int nElem; /* actual number of element in this entry */
+ int nElemDeq; /* actual number of elements dequeued (and thus to be deleted) - see comment above! */
+ int iDoneUpTo; /* all messages below this index have state other than RDY */
+ qDeqID deqID; /* ID of dequeue operation that generated this batch */
+ int *pbShutdownImmediate;/* end processing of this batch immediately if set to 1 */
+ sbool *active; /* which messages are active for processing, NULL=all */
+ sbool bSingleRuleset; /* do all msgs of this batch use a single ruleset? */
+ batch_obj_t *pElem; /* batch elements */
+ batch_state_t *eltState;/* state (array!) for individual objects.
+ NOTE: we have moved this out of batch_obj_t because we
+ get a *much* better cache hit ratio this way. So do not
+ move it back into this structure! Note that this is really
+ a HUGE saving, even if it doesn't look so (both profiler
+ data as well as practical tests indicate that!).
+ */
+};
+
+
+/* some inline functions (we may move this off to an object .. or not) */
+static inline void
+batchSetSingleRuleset(batch_t *pBatch, sbool val) {
+ pBatch->bSingleRuleset = val;
+}
+
+/* get the batches ruleset (if we have a single ruleset) */
+static inline ruleset_t*
+batchGetRuleset(batch_t *pBatch) {
+ return (pBatch->nElem > 0) ? pBatch->pElem[0].pMsg->pRuleset : NULL;
+}
+
+/* get the ruleset of a specifc element of the batch (index not verified!) */
+static inline ruleset_t*
+batchElemGetRuleset(batch_t *pBatch, int i) {
+ return pBatch->pElem[i].pMsg->pRuleset;
+}
+
+/* get number of msgs for this batch */
+static inline int
+batchNumMsgs(batch_t *pBatch) {
+ return pBatch->nElem;
+}
+
+
+/* set the status of the i-th batch element. Note that once the status is
+ * DISC, it will never be reset. So this function can NOT be used to initialize
+ * the state table. -- rgerhards, 2010-06-10
+ */
+static inline void
+batchSetElemState(batch_t *pBatch, int i, batch_state_t newState) {
+ if(pBatch->eltState[i] != BATCH_STATE_DISC)
+ pBatch->eltState[i] = newState;
+}
+
+
+/* check if an element is a valid entry. We do NOT verify if the
+ * element index is valid. -- rgerhards, 2010-06-10
+ */
+static inline int
+batchIsValidElem(batch_t *pBatch, int i) {
+ return( (pBatch->eltState[i] != BATCH_STATE_DISC)
+ && (pBatch->active == NULL || pBatch->active[i]));
+}
+
+
+/* free members of a batch "object". Note that we can not do the usual
+ * destruction as the object typically is allocated on the stack and so the
+ * object itself cannot be freed! -- rgerhards, 2010-06-15
+ */
+static inline void
+batchFree(batch_t *pBatch) {
+ int i;
+ int j;
+ for(i = 0 ; i < pBatch->maxElem ; ++i) {
+ for(j = 0 ; j < CONF_OMOD_NUMSTRINGS_MAXSIZE ; ++j) {
+ /* staticActParams MUST be freed immediately (if required),
+ * so we do not need to do that!
+ */
+ free(pBatch->pElem[i].staticActStrings[j]);
+ }
+ }
+ free(pBatch->pElem);
+ free(pBatch->eltState);
+}
+
+
+/* initialiaze a batch "object". The record must already exist,
+ * we "just" initialize it. The max number of elements must be
+ * provided. -- rgerhards, 2010-06-15
+ */
+static inline rsRetVal
+batchInit(batch_t *pBatch, int maxElem) {
+ DEFiRet;
+ pBatch->iDoneUpTo = 0;
+ pBatch->maxElem = maxElem;
+ CHKmalloc(pBatch->pElem = calloc((size_t)maxElem, sizeof(batch_obj_t)));
+ CHKmalloc(pBatch->eltState = calloc((size_t)maxElem, sizeof(batch_state_t)));
+ // TODO: replace calloc by inidividual writes?
+finalize_it:
+ RETiRet;
+}
+
+
+/* primarily a helper for debug purposes, get human-readble name of state */
+static inline char *
+batchState2String(batch_state_t state) {
+ switch(state) {
+ case BATCH_STATE_RDY:
+ return "BATCH_STATE_RDY";
+ case BATCH_STATE_BAD:
+ return "BATCH_STATE_BAD";
+ case BATCH_STATE_SUB:
+ return "BATCH_STATE_SUB";
+ case BATCH_STATE_COMM:
+ return "BATCH_STATE_COMM";
+ case BATCH_STATE_DISC:
+ return "BATCH_STATE_DISC";
+ }
+ return "ERROR, batch state not known!";
+}
+#endif /* #ifndef BATCH_H_INCLUDED */
diff --git a/runtime/cfsysline.c b/runtime/cfsysline.c
new file mode 100644
index 00000000..a437b7f8
--- /dev/null
+++ b/runtime/cfsysline.c
@@ -0,0 +1,1057 @@
+/* cfsysline.c
+ * Implementation of the configuration system line object.
+ *
+ * File begun on 2007-07-30 by RGerhards
+ *
+ * Copyright (C) 2007-2012 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "cfsysline.h"
+#include "obj.h"
+#include "conf.h"
+#include "errmsg.h"
+#include "srUtils.h"
+#include "unicode-helper.h"
+
+
+/* static data */
+DEFobjCurrIf(obj)
+DEFobjCurrIf(errmsg)
+
+linkedList_t llCmdList; /* this is NOT a pointer - no typo here ;) */
+
+/* --------------- START functions for handling canned syntaxes --------------- */
+
+
+/* parse a character from the config line
+ * added 2007-07-17 by rgerhards
+ * TODO: enhance this function to handle different classes of characters
+ * HINT: check if char is ' and, if so, use 'c' where c may also be things
+ * like \t etc.
+ */
+static rsRetVal doGetChar(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
+{
+ DEFiRet;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+ /* if we are not at a '\0', we have our new char - no validity checks here... */
+ if(**pp == '\0') {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "No character available");
+ iRet = RS_RET_NOT_FOUND;
+ } else {
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((uchar*)pVal) = **pp;
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, **pp));
+ }
+ ++(*pp); /* eat processed char */
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse a number from the configuration line. This is more or less
+ * a shell to call the custom handler.
+ * rgerhards, 2007-07-31
+ */
+static rsRetVal doCustomHdlr(uchar **pp, rsRetVal (*pSetHdlr)(uchar**, void*), void *pVal)
+{
+ DEFiRet;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ CHKiRet(pSetHdlr(pp, pVal));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse a number from the configuration line. This functions just parses
+ * the number and does NOT call any handlers or set any values. It is just
+ * for INTERNAL USE by other parse functions!
+ * rgerhards, 2008-01-08
+ */
+static rsRetVal parseIntVal(uchar **pp, int64 *pVal)
+{
+ DEFiRet;
+ uchar *p;
+ int64 i;
+ int bWasNegative;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+ assert(pVal != NULL);
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+ p = *pp;
+
+ if(*p == '-') {
+ bWasNegative = 1;
+ ++p; /* eat it */
+ } else {
+ bWasNegative = 0;
+ }
+
+ if(!isdigit((int) *p)) {
+ errno = 0;
+ errmsg.LogError(0, RS_RET_INVALID_INT, "invalid number");
+ ABORT_FINALIZE(RS_RET_INVALID_INT);
+ }
+
+ /* pull value */
+ for(i = 0 ; *p && (isdigit((int) *p) || *p == '.' || *p == ',') ; ++p) {
+ if(isdigit((int) *p)) {
+ i = i * 10 + *p - '0';
+ }
+ }
+
+ if(bWasNegative)
+ i *= -1;
+
+ *pVal = i;
+ *pp = p;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse a size from the configuration line. This is basically an integer
+ * syntax, but modifiers may be added after the integer (e.g. 1k to mean
+ * 1024). The size must immediately follow the number. Note that the
+ * param value must be int64!
+ * rgerhards, 2008-01-09
+ */
+static rsRetVal doGetSize(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
+{
+ DEFiRet;
+ int64 i;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ CHKiRet(parseIntVal(pp, &i));
+
+ /* we now check if the next character is one of our known modifiers.
+ * If so, we accept it as such. If not, we leave it alone. tera and
+ * above does not make any sense as that is above a 32-bit int value.
+ */
+ switch(**pp) {
+ /* traditional binary-based definitions */
+ case 'k': i *= 1024; ++(*pp); break;
+ case 'm': i *= 1024 * 1024; ++(*pp); break;
+ case 'g': i *= 1024 * 1024 * 1024; ++(*pp); break;
+ case 't': i *= (int64) 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* tera */
+ case 'p': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* peta */
+ case 'e': i *= (int64) 1024 * 1024 * 1024 * 1024 * 1024 * 1024; ++(*pp); break; /* exa */
+ /* and now the "new" 1000-based definitions */
+ case 'K': i *= 1000; ++(*pp); break;
+ case 'M': i *= 1000000; ++(*pp); break;
+ case 'G': i *= 1000000000; ++(*pp); break;
+ /* we need to use the multiplication below because otherwise
+ * the compiler gets an error during constant parsing */
+ case 'T': i *= (int64) 1000 * 1000000000; ++(*pp); break; /* tera */
+ case 'P': i *= (int64) 1000000 * 1000000000; ++(*pp); break; /* peta */
+ case 'E': i *= (int64) 1000000000 * 1000000000; ++(*pp); break; /* exa */
+ }
+
+ /* done */
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((int64*)pVal) = i;
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, i));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse a number from the configuration line.
+ * rgerhards, 2007-07-31
+ */
+static rsRetVal doGetInt(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
+{
+ uchar *p;
+ DEFiRet;
+ int64 i;
+ uchar errMsg[256]; /* for dynamic error messages */
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ CHKiRet(doGetSize(pp, NULL,&i));
+ p = *pp;
+ if(i > 2147483648ll) { /*2^31*/
+ snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar),
+ "value %lld too large for integer argument.", i);
+ errmsg.LogError(0, RS_RET_INVALID_VALUE, "%s", errMsg);
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+ }
+
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((int*)pVal) = (int) i;
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, (int) i));
+ }
+
+ *pp = p;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse and interpret a $FileCreateMode and $umask line. This function
+ * pulls the creation mode and, if successful, stores it
+ * into the global variable so that the rest of rsyslogd
+ * opens files with that mode. Any previous value will be
+ * overwritten.
+ * HINT: if we store the creation mode in selector_t, we
+ * can even specify multiple modes simply be virtue of
+ * being placed in the right section of rsyslog.conf
+ * rgerhards, 2007-07-4 (happy independence day to my US friends!)
+ * Parameter **pp has a pointer to the current config line.
+ * On exit, it will be updated to the processed position.
+ */
+static rsRetVal doFileCreateMode(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
+{
+ uchar *p;
+ DEFiRet;
+ uchar errMsg[128]; /* for dynamic error messages */
+ int iVal;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+ p = *pp;
+
+ /* for now, we parse and accept only octal numbers
+ * Sequence of tests is important, we are using boolean shortcuts
+ * to avoid addressing invalid memory!
+ */
+ if(!( (*p == '0')
+ && (*(p+1) && *(p+1) >= '0' && *(p+1) <= '7')
+ && (*(p+2) && *(p+2) >= '0' && *(p+2) <= '7')
+ && (*(p+3) && *(p+3) >= '0' && *(p+3) <= '7') ) ) {
+ snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar),
+ "value must be octal (e.g 0644).");
+ errno = 0;
+ errmsg.LogError(0, RS_RET_INVALID_VALUE, "%s", errMsg);
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+ }
+
+ /* we reach this code only if the octal number is ok - so we can now
+ * compute the value.
+ */
+ iVal = (*(p+1)-'0') * 64 + (*(p+2)-'0') * 8 + (*(p+3)-'0');
+
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((int*)pVal) = iVal;
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, iVal));
+ }
+
+ p += 4; /* eat the octal number */
+ *pp = p;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse and interpret an on/off inside a config file line. This is most
+ * often used for boolean options, but of course it may also be used
+ * for other things. The passed-in pointer is updated to point to
+ * the first unparsed character on exit. Function emits error messages
+ * if the value is neither on or off. It returns 0 if the option is off,
+ * 1 if it is on and another value if there was an error.
+ * rgerhards, 2007-07-15
+ */
+static int doParseOnOffOption(uchar **pp)
+{
+ uchar *pOptStart;
+ uchar szOpt[32];
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ pOptStart = *pp;
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+ if(getSubString(pp, (char*) szOpt, sizeof(szOpt) / sizeof(uchar), ' ') != 0) {
+ errmsg.LogError(0, NO_ERRCODE, "Invalid $-configline - could not extract on/off option");
+ return -1;
+ }
+
+ if(!strcmp((char*)szOpt, "on")) {
+ return 1;
+ } else if(!strcmp((char*)szOpt, "off")) {
+ return 0;
+ } else {
+ errmsg.LogError(0, NO_ERRCODE, "Option value must be on or off, but is '%s'", (char*)pOptStart);
+ return -1;
+ }
+}
+
+
+/* extract a groupname and return its gid.
+ * rgerhards, 2007-07-17
+ */
+static rsRetVal doGetGID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
+{
+ struct group *pgBuf = NULL;
+ struct group gBuf;
+ DEFiRet;
+ uchar szName[256];
+ int bufSize = 1024;
+ char * stringBuf = NULL;
+ int err;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract group name");
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+ do {
+ /* Increase bufsize and try again.*/
+ bufSize *= 2;
+ CHKmalloc(stringBuf = realloc(stringBuf, bufSize));
+ err = getgrnam_r((char*)szName, &gBuf, stringBuf, bufSize, &pgBuf);
+ } while((pgBuf == NULL) && (err == ERANGE));
+
+ if(pgBuf == NULL) {
+ if (err != 0) {
+ rs_strerror_r(err, stringBuf, bufSize);
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "Query for group '%s' resulted in an error: %s\n",
+ (char*)szName, stringBuf);
+ } else {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "ID for group '%s' could not be found", (char*)szName);
+ }
+ iRet = RS_RET_NOT_FOUND;
+ } else {
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((gid_t*)pVal) = pgBuf->gr_gid;
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, pgBuf->gr_gid));
+ }
+ dbgprintf("gid %d obtained for group '%s'\n", (int) pgBuf->gr_gid, szName);
+ }
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+finalize_it:
+ free(stringBuf);
+ RETiRet;
+}
+
+
+/* extract a username and return its uid.
+ * rgerhards, 2007-07-17
+ */
+static rsRetVal doGetUID(uchar **pp, rsRetVal (*pSetHdlr)(void*, uid_t), void *pVal)
+{
+ struct passwd *ppwBuf;
+ struct passwd pwBuf;
+ DEFiRet;
+ uchar szName[256];
+ char stringBuf[2048]; /* I hope this is large enough... */
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract user name");
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+ getpwnam_r((char*)szName, &pwBuf, stringBuf, sizeof(stringBuf), &ppwBuf);
+
+ if(ppwBuf == NULL) {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "ID for user '%s' could not be found or error", (char*)szName);
+ iRet = RS_RET_NOT_FOUND;
+ } else {
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((uid_t*)pVal) = ppwBuf->pw_uid;
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, ppwBuf->pw_uid));
+ }
+ dbgprintf("uid %d obtained for user '%s'\n", (int) ppwBuf->pw_uid, szName);
+ }
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse and process an binary cofig option. pVal must be
+ * a pointer to an integer which is to receive the option
+ * value.
+ * rgerhards, 2007-07-15
+ */
+static rsRetVal doBinaryOptionLine(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal)
+{
+ int iOption;
+ DEFiRet;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ if((iOption = doParseOnOffOption(pp)) == -1)
+ return RS_RET_ERR; /* nothing left to do */
+
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((int*)pVal) = iOption;
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, iOption));
+ }
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* parse a whitespace-delimited word from the provided string. This is a
+ * helper function for a number of syntaxes. The parsed value is returned
+ * in ppStrB (which must be provided by caller).
+ * rgerhards, 2008-02-14
+ */
+static rsRetVal
+getWord(uchar **pp, cstr_t **ppStrB)
+{
+ DEFiRet;
+ uchar *p;
+
+ ASSERT(pp != NULL);
+ ASSERT(*pp != NULL);
+ ASSERT(ppStrB != NULL);
+
+ CHKiRet(cstrConstruct(ppStrB));
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+ /* parse out the word */
+ p = *pp;
+
+ while(*p && !isspace((int) *p)) {
+ CHKiRet(cstrAppendChar(*ppStrB, *p++));
+ }
+ CHKiRet(cstrFinalize(*ppStrB));
+
+ *pp = p;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse and a word config line option. A word is a consequtive
+ * sequence of non-whitespace characters. pVal must be
+ * a pointer to a string which is to receive the option
+ * value. The returned string must be freed by the caller.
+ * rgerhards, 2007-09-07
+ * To facilitate multiple instances of the same command line
+ * directive, doGetWord() now checks if pVal is already a
+ * non-NULL pointer. If so, we assume it was created by a previous
+ * incarnation and is automatically freed. This happens only when
+ * no custom handler is defined. If it is, the customer handler
+ * must do the cleanup. I have checked and this was al also memory
+ * leak with some code. Obviously, not a large one. -- rgerhards, 2007-12-20
+ * Just to clarify: if pVal is parsed to a custom handler, this handler
+ * is responsible for freeing pVal. -- rgerhards, 2008-03-20
+ */
+static rsRetVal doGetWord(uchar **pp, rsRetVal (*pSetHdlr)(void*, uchar*), void *pVal)
+{
+ DEFiRet;
+ cstr_t *pStrB;
+ uchar *pNewVal;
+
+ ASSERT(pp != NULL);
+ ASSERT(*pp != NULL);
+
+ CHKiRet(getWord(pp, &pStrB));
+ CHKiRet(cstrConvSzStrAndDestruct(pStrB, &pNewVal, 0));
+ pStrB = NULL;
+
+ DBGPRINTF("doGetWord: get newval '%s' (len %d), hdlr %p\n",
+ pNewVal, (int) ustrlen(pNewVal), pSetHdlr);
+ /* we got the word, now set it */
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ if(*((uchar**)pVal) != NULL)
+ free(*((uchar**)pVal)); /* free previous entry */
+ *((uchar**)pVal) = pNewVal; /* set new one */
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, pNewVal));
+ }
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pStrB != NULL)
+ cstrDestruct(&pStrB);
+ }
+
+ RETiRet;
+}
+
+
+/* parse a syslog name from the string. This is the generic code that is
+ * called by the facility/severity functions. Note that we do not check the
+ * validity of numerical values, something that should probably change over
+ * time (TODO). -- rgerhards, 2008-02-14
+ */
+static rsRetVal
+doSyslogName(uchar **pp, rsRetVal (*pSetHdlr)(void*, int),
+ void *pVal, syslogName_t *pNameTable)
+{
+ DEFiRet;
+ cstr_t *pStrB;
+ int iNewVal;
+
+ ASSERT(pp != NULL);
+ ASSERT(*pp != NULL);
+
+ CHKiRet(getWord(pp, &pStrB)); /* get word */
+ iNewVal = decodeSyslogName(cstrGetSzStr(pStrB), pNameTable);
+
+ if(pSetHdlr == NULL) {
+ /* we should set value directly to var */
+ *((int*)pVal) = iNewVal; /* set new one */
+ } else {
+ /* we set value via a set function */
+ CHKiRet(pSetHdlr(pVal, iNewVal));
+ }
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+finalize_it:
+ if(pStrB != NULL)
+ rsCStrDestruct(&pStrB);
+
+ RETiRet;
+}
+
+
+/* Implements the facility syntax.
+ * rgerhards, 2008-02-14
+ */
+static rsRetVal
+doFacility(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal)
+{
+ DEFiRet;
+ iRet = doSyslogName(pp, pSetHdlr, pVal, syslogFacNames);
+ RETiRet;
+}
+
+
+static rsRetVal
+doGoneAway(__attribute__((unused)) uchar **pp,
+ __attribute__((unused)) rsRetVal (*pSetHdlr)(void*, int),
+ __attribute__((unused)) void *pVal)
+{
+ errmsg.LogError(0, RS_RET_CMD_GONE_AWAY, "config directive is no longer supported -- ignored");
+ return RS_RET_CMD_GONE_AWAY;
+}
+
+/* Implements the severity syntax.
+ * rgerhards, 2008-02-14
+ */
+static rsRetVal
+doSeverity(uchar **pp, rsRetVal (*pSetHdlr)(void*, int), void *pVal)
+{
+ DEFiRet;
+ iRet = doSyslogName(pp, pSetHdlr, pVal, syslogPriNames);
+ RETiRet;
+}
+
+
+/* --------------- END functions for handling canned syntaxes --------------- */
+
+/* destructor for cslCmdHdlr
+ * pThis is actually a cslCmdHdlr_t, but we do not cast it as all we currently
+ * need to do is free it.
+ */
+static rsRetVal cslchDestruct(void *pThis)
+{
+ ASSERT(pThis != NULL);
+ free(pThis);
+
+ return RS_RET_OK;
+}
+
+
+/* constructor for cslCmdHdlr
+ */
+static rsRetVal cslchConstruct(cslCmdHdlr_t **ppThis)
+{
+ cslCmdHdlr_t *pThis;
+ DEFiRet;
+
+ assert(ppThis != NULL);
+ if((pThis = calloc(1, sizeof(cslCmdHdlr_t))) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+finalize_it:
+ *ppThis = pThis;
+ RETiRet;
+}
+
+/* destructor for linked list keys. As we do not use any dynamic memory,
+ * we simply return. However, this entry point must be defined for the
+ * linkedList class to make sure we have not forgotten a destructor.
+ * rgerhards, 2007-11-21
+ */
+static rsRetVal cslchKeyDestruct(void __attribute__((unused)) *pData)
+{
+ return RS_RET_OK;
+}
+
+
+/* Key compare operation for linked list class. This compares two
+ * owner cookies (void *).
+ * rgerhards, 2007-11-21
+ */
+static int cslchKeyCompare(void *pKey1, void *pKey2)
+{
+ if(pKey1 == pKey2)
+ return 0;
+ else
+ if(pKey1 < pKey2)
+ return -1;
+ else
+ return 1;
+}
+
+
+/* set data members for this object
+ */
+rsRetVal cslchSetEntry(cslCmdHdlr_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, int *permitted)
+{
+ assert(pThis != NULL);
+ assert(eType != eCmdHdlrInvalid);
+
+ pThis->eType = eType;
+ pThis->cslCmdHdlr = pHdlr;
+ pThis->pData = pData;
+ pThis->permitted = permitted;
+
+ return RS_RET_OK;
+}
+
+
+/* call the specified handler
+ */
+static rsRetVal cslchCallHdlr(cslCmdHdlr_t *pThis, uchar **ppConfLine)
+{
+ DEFiRet;
+ rsRetVal (*pHdlr)() = NULL;
+ assert(pThis != NULL);
+ assert(ppConfLine != NULL);
+
+ switch(pThis->eType) {
+ case eCmdHdlrCustomHandler:
+ pHdlr = doCustomHdlr;
+ break;
+ case eCmdHdlrUID:
+ pHdlr = doGetUID;
+ break;
+ case eCmdHdlrGID:
+ pHdlr = doGetGID;
+ break;
+ case eCmdHdlrBinary:
+ pHdlr = doBinaryOptionLine;
+ break;
+ case eCmdHdlrFileCreateMode:
+ pHdlr = doFileCreateMode;
+ break;
+ case eCmdHdlrInt:
+ pHdlr = doGetInt;
+ break;
+ case eCmdHdlrSize:
+ pHdlr = doGetSize;
+ break;
+ case eCmdHdlrGetChar:
+ pHdlr = doGetChar;
+ break;
+ case eCmdHdlrFacility:
+ pHdlr = doFacility;
+ break;
+ case eCmdHdlrSeverity:
+ pHdlr = doSeverity;
+ break;
+ case eCmdHdlrGetWord:
+ pHdlr = doGetWord;
+ break;
+ case eCmdHdlrGoneAway:
+ pHdlr = doGoneAway;
+ break;
+ default:
+ iRet = RS_RET_NOT_IMPLEMENTED;
+ goto finalize_it;
+ }
+
+ /* we got a pointer to the handler, so let's call it */
+ assert(pHdlr != NULL);
+ CHKiRet(pHdlr(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* ---------------------------------------------------------------------- *
+ * now come the handlers for cslCmd_t
+ * ---------------------------------------------------------------------- */
+
+/* destructor for a cslCmd list key (a string as of now)
+ */
+static rsRetVal cslcKeyDestruct(void *pData)
+{
+ free(pData); /* we do not need to cast as all we do is free it anyway... */
+ return RS_RET_OK;
+}
+
+/* destructor for cslCmd
+ */
+static rsRetVal cslcDestruct(void *pData)
+{
+ cslCmd_t *pThis = (cslCmd_t*) pData;
+
+ assert(pThis != NULL);
+
+ llDestroy(&pThis->llCmdHdlrs);
+ free(pThis);
+
+ return RS_RET_OK;
+}
+
+
+/* constructor for cslCmd
+ */
+static rsRetVal cslcConstruct(cslCmd_t **ppThis, int bChainingPermitted)
+{
+ cslCmd_t *pThis;
+ DEFiRet;
+
+ assert(ppThis != NULL);
+ if((pThis = calloc(1, sizeof(cslCmd_t))) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ pThis->bChainingPermitted = bChainingPermitted;
+
+ CHKiRet(llInit(&pThis->llCmdHdlrs, cslchDestruct, cslchKeyDestruct, cslchKeyCompare));
+
+finalize_it:
+ *ppThis = pThis;
+ RETiRet;
+}
+
+
+/* add a handler entry to a known command
+ */
+static rsRetVal cslcAddHdlr(cslCmd_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie, int *permitted)
+{
+ DEFiRet;
+ cslCmdHdlr_t *pCmdHdlr = NULL;
+
+ assert(pThis != NULL);
+
+ CHKiRet(cslchConstruct(&pCmdHdlr));
+ CHKiRet(cslchSetEntry(pCmdHdlr, eType, pHdlr, pData, permitted));
+ CHKiRet(llAppend(&pThis->llCmdHdlrs, pOwnerCookie, pCmdHdlr));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pHdlr != NULL)
+ cslchDestruct(pCmdHdlr);
+ }
+
+ RETiRet;
+}
+
+
+/* function that registers cfsysline handlers.
+ * The supplied pCmdName is copied and a new buffer is allocated. This
+ * buffer is automatically destroyed when the element is freed, the
+ * caller does not need to take care of that. The caller must, however,
+ * free pCmdName if he allocated it dynamically! -- rgerhards, 2007-08-09
+ * Parameter permitted has been added to support the v2 config system. With it,
+ * we can tell the legacy system (us here!) to check if a config directive is
+ * still permitted. For example, the v2 system will disable module global
+ * paramters if the are supplied via the native v2 callbacks. In order not
+ * to break exisiting modules, we have renamed the rgCfSysLinHdlr routine to
+ * version 2 and added a new one with the original name. It just calls the
+ * v2 function and supplies a "don't care (NULL)" pointer as this argument.
+ * rgerhards, 2012-06-26
+ */
+rsRetVal regCfSysLineHdlr2(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie, int *permitted)
+{
+ DEFiRet;
+ cslCmd_t *pThis;
+ uchar *pMyCmdName;
+
+ iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pThis);
+ if(iRet == RS_RET_NOT_FOUND) {
+ /* new command */
+ CHKiRet(cslcConstruct(&pThis, bChainingPermitted));
+ CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) {
+ cslcDestruct(pThis);
+ FINALIZE;
+ }
+ /* important: add to list, AFTER everything else is OK. Else
+ * we mess up things in the error case.
+ */
+ if((pMyCmdName = (uchar*) strdup((char*)pCmdName)) == NULL) {
+ cslcDestruct(pThis);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ CHKiRet_Hdlr(llAppend(&llCmdList, pMyCmdName, (void*) pThis)) {
+ cslcDestruct(pThis);
+ FINALIZE;
+ }
+ } else {
+ /* command already exists, are we allowed to chain? */
+ if(pThis->bChainingPermitted == 0 || bChainingPermitted == 0) {
+ ABORT_FINALIZE(RS_RET_CHAIN_NOT_PERMITTED);
+ }
+ CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) {
+ cslcDestruct(pThis);
+ FINALIZE;
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie)
+{
+ DEFiRet;
+ iRet = regCfSysLineHdlr2(pCmdName, bChainingPermitted, eType, pHdlr, pData, pOwnerCookie, NULL);
+ RETiRet;
+}
+
+
+rsRetVal unregCfSysLineHdlrs(void)
+{
+ return llDestroy(&llCmdList);
+}
+
+
+/* helper function for unregCfSysLineHdlrs4Owner(). This is used to see if there is
+ * a handler of this owner inside the element and, if so, remove it. Please note that
+ * it keeps track of a pointer to the last linked list entry, as this is needed to
+ * remove an entry from the list.
+ * rgerhards, 2007-11-21
+ */
+DEFFUNC_llExecFunc(unregHdlrsHeadExec)
+{
+ DEFiRet;
+ cslCmd_t *pListHdr = (cslCmd_t*) pData;
+ int iNumElts;
+
+ /* first find element */
+ iRet = llFindAndDelete(&(pListHdr->llCmdHdlrs), pParam);
+
+ /* now go back and check how many elements are left */
+ CHKiRet(llGetNumElts(&(pListHdr->llCmdHdlrs), &iNumElts));
+
+ if(iNumElts == 0) {
+ /* nothing left in header, so request to delete it */
+ iRet = RS_RET_OK_DELETE_LISTENTRY;
+ }
+
+finalize_it:
+ RETiRet;
+}
+/* unregister and destroy cfSysLineHandlers for a specific owner. This method is
+ * most importantly used before unloading a loadable module providing some handlers.
+ * The full list of handlers is searched. If the to-be removed handler was the only
+ * handler for a directive name, the directive header, too, is deleted.
+ * rgerhards, 2007-11-21
+ */
+rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie)
+{
+ DEFiRet;
+ /* we need to walk through all directive names, as the linked list
+ * class does not provide a way to just search the lower-level handlers.
+ */
+ iRet = llExecFunc(&llCmdList, unregHdlrsHeadExec, pOwnerCookie);
+
+ RETiRet;
+}
+
+
+/* process a cfsysline command (based on handler structure)
+ * param "p" is a pointer to the command line after the command. Should be
+ * updated.
+ */
+rsRetVal processCfSysLineCommand(uchar *pCmdName, uchar **p)
+{
+ DEFiRet;
+ rsRetVal iRetLL; /* for linked list handling */
+ cslCmd_t *pCmd;
+ cslCmdHdlr_t *pCmdHdlr;
+ linkedListCookie_t llCookieCmdHdlr;
+ uchar *pHdlrP; /* the handler's private p (else we could only call one handler) */
+ int bWasOnceOK; /* was the result of an handler at least once RS_RET_OK? */
+ uchar *pOKp = NULL; /* returned conf line pointer when it was OK */
+ int bHadScopingErr = 0; /* set if a scoping error occured */
+
+ iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pCmd);
+
+ if(iRet == RS_RET_NOT_FOUND) {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "invalid or yet-unknown config file command '%s' - "
+ "have you forgotten to load a module?", pCmdName);
+ }
+
+ if(iRet != RS_RET_OK)
+ goto finalize_it;
+
+ llCookieCmdHdlr = NULL;
+ bWasOnceOK = 0;
+ while((iRetLL = llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr)) == RS_RET_OK) {
+ /* for the time being, we ignore errors during handlers. The
+ * reason is that handlers are independent. An error in one
+ * handler does not necessarily mean that another one will
+ * fail, too. Later, we might add a config variable to control
+ * this behaviour (but I am not sure if that is really
+ * necessary). -- rgerhards, 2007-07-31
+ */
+ pHdlrP = *p;
+ if(pCmdHdlr->permitted != NULL && !*(pCmdHdlr->permitted)) {
+ errmsg.LogError(0, RS_RET_PARAM_NOT_PERMITTED, "command '%s' is currently not "
+ "permitted - did you already set it via a RainerScript command (v6+ config)?",
+ pCmdName);
+ ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
+ } else if((iRet = cslchCallHdlr(pCmdHdlr, &pHdlrP)) == RS_RET_OK) {
+ bWasOnceOK = 1;
+ pOKp = pHdlrP;
+ }
+ }
+
+ if(bWasOnceOK == 1) {
+ *p = pOKp;
+ iRet = RS_RET_OK;
+ }
+
+ if(iRetLL != RS_RET_END_OF_LINKEDLIST)
+ iRet = iRetLL;
+
+ if(bHadScopingErr) {
+ iRet = RS_RET_CONF_INVLD_SCOPE;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* debug print the command handler structure
+ */
+void dbgPrintCfSysLineHandlers(void)
+{
+ cslCmd_t *pCmd;
+ cslCmdHdlr_t *pCmdHdlr;
+ linkedListCookie_t llCookieCmd;
+ linkedListCookie_t llCookieCmdHdlr;
+ uchar *pKey;
+
+ dbgprintf("Sytem Line Configuration Commands:\n");
+ llCookieCmd = NULL;
+ while(llGetNextElt(&llCmdList, &llCookieCmd, (void*)&pCmd) == RS_RET_OK) {
+ llGetKey(llCookieCmd, (void*) &pKey); /* TODO: using the cookie is NOT clean! */
+ dbgprintf("\tCommand '%s':\n", pKey);
+ llCookieCmdHdlr = NULL;
+ while(llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void*)&pCmdHdlr) == RS_RET_OK) {
+ dbgprintf("\t\ttype : %d\n", pCmdHdlr->eType);
+ dbgprintf("\t\tpData: 0x%lx\n", (unsigned long) pCmdHdlr->pData);
+ dbgprintf("\t\tHdlr : 0x%lx\n", (unsigned long) pCmdHdlr->cslCmdHdlr);
+ dbgprintf("\t\tOwner: 0x%lx\n", (unsigned long) llCookieCmdHdlr->pKey);
+ dbgprintf("\n");
+ }
+ }
+ dbgprintf("\n");
+}
+
+
+/* our init function. TODO: remove once converted to a class
+ */
+rsRetVal cfsyslineInit()
+{
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+
+ CHKiRet(llInit(&llCmdList, cslcDestruct, cslcKeyDestruct, strcasecmp));
+
+finalize_it:
+ RETiRet;
+}
+
+/* vim:set ai:
+ */
diff --git a/runtime/cfsysline.h b/runtime/cfsysline.h
new file mode 100644
index 00000000..69389f84
--- /dev/null
+++ b/runtime/cfsysline.h
@@ -0,0 +1,60 @@
+/* Definition of the cfsysline (config file system line) object.
+ *
+ * Copyright 2007-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CFSYSLINE_H_INCLUDED
+#define CFSYSLINE_H_INCLUDED
+
+#include "linkedlist.h"
+
+/* this is a single entry for a parse routine. It describes exactly
+ * one entry point/handler.
+ * The short name is cslch (Configfile SysLine CommandHandler)
+ */
+struct cslCmdHdlr_s { /* config file sysline parse entry */
+ ecslConfObjType __attribute__((deprecated)) eConfObjType; /* which config object is this for? */
+ ecslCmdHdrlType eType; /* which type of handler is this? */
+ rsRetVal (*cslCmdHdlr)(); /* function pointer to use with handler (params depending on eType) */
+ void *pData; /* user-supplied data pointer */
+ int *permitted; /* is this parameter currently permitted? (NULL=don't check) */
+};
+typedef struct cslCmdHdlr_s cslCmdHdlr_t;
+
+
+/* this is the list of known configuration commands with pointers to
+ * their handlers.
+ * The short name is cslc (Configfile SysLine Command)
+ */
+struct cslCmd_s { /* config file sysline parse entry */
+ int bChainingPermitted; /* may multiple handlers be chained for this command? */
+ linkedList_t llCmdHdlrs; /* linked list of command handlers */
+};
+typedef struct cslCmd_s cslCmd_t;
+
+/* prototypes */
+rsRetVal regCfSysLineHdlr(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie);
+rsRetVal regCfSysLineHdlr2(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie, int *permitted);
+rsRetVal unregCfSysLineHdlrs(void);
+rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie);
+rsRetVal processCfSysLineCommand(uchar *pCmd, uchar **p);
+rsRetVal cfsyslineInit(void);
+void dbgPrintCfSysLineHandlers(void);
+
+#endif /* #ifndef CFSYSLINE_H_INCLUDED */
diff --git a/runtime/conf.c b/runtime/conf.c
new file mode 100644
index 00000000..c3c7e447
--- /dev/null
+++ b/runtime/conf.c
@@ -0,0 +1,741 @@
+/* The config file handler (not yet a real object)
+ *
+ * This file is based on an excerpt from syslogd.c, which dates back
+ * much later. I began the file on 2008-02-19 as part of the modularization
+ * effort. Over time, a clean abstration will become even more important
+ * because the config file handler will by dynamically be loaded and be
+ * kept in memory only as long as the config file is actually being
+ * processed. Thereafter, it shall be unloaded. -- rgerhards
+ * Please note that the original syslogd.c source was under BSD license
+ * at the time of the rsyslog fork from sysklogd.
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define CFGLNSIZ 64*1024 /* the maximum size of a configuraton file line, after re-combination */
+#include "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+#include <dirent.h>
+#include <glob.h>
+#include <sys/types.h>
+#ifdef HAVE_LIBGEN_H
+# ifndef OS_SOLARIS
+# include <libgen.h>
+# endif
+#endif
+
+#include "rsyslog.h"
+#include "dirty.h"
+#include "parse.h"
+#include "action.h"
+#include "template.h"
+#include "cfsysline.h"
+#include "modules.h"
+#include "outchannel.h"
+#include "stringbuf.h"
+#include "conf.h"
+#include "stringbuf.h"
+#include "srUtils.h"
+#include "errmsg.h"
+#include "net.h"
+#include "ruleset.h"
+#include "rsconf.h"
+#include "unicode-helper.h"
+#include "rainerscript.h"
+
+#ifdef OS_SOLARIS
+# define NAME_MAX MAXNAMELEN
+#endif
+
+/* forward definitions */
+
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(module)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(net)
+DEFobjCurrIf(ruleset)
+
+int bConfStrictScoping = 0; /* force strict scoping during config processing? */
+
+
+/* The following module-global variables are used for building
+ * tag and host selector lines during startup and config reload.
+ * This is stored as a global variable pool because of its ease. It is
+ * also fairly compatible with multi-threading as the stratup code must
+ * be run in a single thread anyways. So there can be no race conditions.
+ * rgerhards 2005-10-18
+ */
+EHostnameCmpMode eDfltHostnameCmpMode = HN_NO_COMP;
+cstr_t *pDfltHostnameCmp = NULL;
+cstr_t *pDfltProgNameCmp = NULL;
+
+
+/* process a $ModLoad config line. */
+rsRetVal
+doModLoad(uchar **pp, __attribute__((unused)) void* pVal)
+{
+ DEFiRet;
+ uchar szName[512];
+ uchar *pModName;
+
+ ASSERT(pp != NULL);
+ ASSERT(*pp != NULL);
+
+ skipWhiteSpace(pp); /* skip over any whitespace */
+ if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "could not extract module name");
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+ skipWhiteSpace(pp); /* skip over any whitespace */
+
+ /* this below is a quick and dirty hack to provide compatibility with the
+ * $ModLoad MySQL forward compatibility statement. This needs to be supported
+ * for legacy format.
+ */
+ if(!strcmp((char*) szName, "MySQL"))
+ pModName = (uchar*) "ommysql.so";
+ else
+ pModName = szName;
+
+ CHKiRet(module.Load(pModName, 1, NULL));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* remove leading spaces from name; this "fixes" some anomalies in
+ * getSubString(), but I was not brave enough to fix the former as
+ * it has many other callers... -- rgerhards, 2013-05-27
+ */
+static inline void
+ltrim(char *src)
+{
+ char *dst = src;
+ while(isspace(*src))
+ ++src; /*SKIP*/;
+ if(dst != src) {
+ while(*src != '\0')
+ *dst++ = *src++;
+ *dst = '\0';
+ }
+}
+
+/* parse and interpret a $-config line that starts with
+ * a name (this is common code). It is parsed to the name
+ * and then the proper sub-function is called to handle
+ * the actual directive.
+ * rgerhards 2004-11-17
+ * rgerhards 2005-06-21: previously only for templates, now
+ * generalized.
+ */
+rsRetVal
+doNameLine(uchar **pp, void* pVal)
+{
+ DEFiRet;
+ uchar *p;
+ enum eDirective eDir;
+ char szName[128];
+
+ ASSERT(pp != NULL);
+ p = *pp;
+ ASSERT(p != NULL);
+
+ eDir = (enum eDirective) pVal; /* this time, it actually is NOT a pointer! */
+
+ if(getSubString(&p, szName, sizeof(szName) / sizeof(char), ',') != 0) {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "Invalid config line: could not extract name - line ignored");
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+ ltrim(szName);
+ if(*p == ',')
+ ++p; /* comma was eaten */
+
+ /* we got the name - now we pass name & the rest of the string
+ * to the subfunction. It makes no sense to do further
+ * parsing here, as this is in close interaction with the
+ * respective subsystem. rgerhards 2004-11-17
+ */
+
+ switch(eDir) {
+ case DIR_TEMPLATE:
+ tplAddLine(loadConf, szName, &p);
+ break;
+ case DIR_OUTCHANNEL:
+ ochAddLine(szName, &p);
+ break;
+ case DIR_ALLOWEDSENDER:
+ net.addAllowedSenderLine(szName, &p);
+ break;
+ default:/* we do this to avoid compiler warning - not all
+ * enum values call this function, so an incomplete list
+ * is quite ok (but then we should not run into this code,
+ * so at least we log a debug warning).
+ */
+ dbgprintf("INTERNAL ERROR: doNameLine() called with invalid eDir %d.\n",
+ eDir);
+ break;
+ }
+
+ *pp = p;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse and interpret a system-directive in the config line
+ * A system directive is one that starts with a "$" sign. It offers
+ * extended configuration parameters.
+ * 2004-11-17 rgerhards
+ */
+rsRetVal
+cfsysline(uchar *p)
+{
+ DEFiRet;
+ uchar szCmd[64];
+
+ ASSERT(p != NULL);
+ errno = 0;
+ if(getSubString(&p, (char*) szCmd, sizeof(szCmd) / sizeof(uchar), ' ') != 0) {
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "Invalid $-configline - could not extract command - line ignored\n");
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+ /* we now try and see if we can find the command in the registered
+ * list of cfsysline handlers. -- rgerhards, 2007-07-31
+ */
+ CHKiRet(processCfSysLineCommand(szCmd, &p));
+
+ /* now check if we have some extra characters left on the line - that
+ * should not be the case. Whitespace is OK, but everything else should
+ * trigger a warning (that may be an indication of undesired behaviour).
+ * An exception, of course, are comments (starting with '#').
+ * rgerhards, 2007-07-04
+ */
+ skipWhiteSpace(&p);
+
+ if(*p && *p != '#') { /* we have a non-whitespace, so let's complain */
+ errmsg.LogError(0, NO_ERRCODE,
+ "error: extra characters in config line ignored: '%s'", p);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Helper to cfline() and its helpers. Parses a template name
+ * from an "action" line. Must be called with the Line pointer
+ * pointing to the first character after the semicolon.
+ * rgerhards 2004-11-19
+ * changed function to work with OMSR. -- rgerhards, 2007-07-27
+ * the default template is to be used when no template is specified.
+ */
+rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName)
+{
+ uchar *p;
+ uchar *tplName = NULL;
+ cstr_t *pStrB;
+ DEFiRet;
+
+ ASSERT(pp != NULL);
+ ASSERT(*pp != NULL);
+ ASSERT(pOMSR != NULL);
+
+ p =*pp;
+ /* a template must follow - search it and complain, if not found */
+ skipWhiteSpace(&p);
+ if(*p == ';')
+ ++p; /* eat it */
+ else if(*p != '\0' && *p != '#') {
+ errmsg.LogError(0, RS_RET_ERR, "invalid character in selector line - ';template' expected");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ skipWhiteSpace(&p); /* go to begin of template name */
+
+ if(*p == '\0' || *p == '#') {
+ /* no template specified, use the default */
+ /* TODO: check NULL ptr */
+ tplName = (uchar*) strdup((char*)dfltTplName);
+ } else {
+ /* template specified, pick it up */
+ CHKiRet(cstrConstruct(&pStrB));
+
+ /* now copy the string */
+ while(*p && *p != '#' && !isspace((int) *p)) {
+ CHKiRet(cstrAppendChar(pStrB, *p));
+ ++p;
+ }
+ CHKiRet(cstrFinalize(pStrB));
+ CHKiRet(cstrConvSzStrAndDestruct(pStrB, &tplName, 0));
+ }
+
+ CHKiRet(OMSRsetEntry(pOMSR, iEntry, tplName, iTplOpts));
+
+finalize_it:
+ if(iRet != RS_RET_OK)
+ free(tplName);
+
+ *pp = p;
+
+ RETiRet;
+}
+
+/* Helper to cfline(). Parses a file name up until the first
+ * comma and then looks for the template specifier. Tries
+ * to find that template.
+ * rgerhards 2004-11-18
+ * parameter pFileName must point to a buffer large enough
+ * to hold the largest possible filename.
+ * rgerhards, 2007-07-25
+ * updated to include OMSR pointer -- rgerhards, 2007-07-27
+ * updated to include template name -- rgerhards, 2008-03-28
+ * rgerhards, 2010-01-19: file names end at the first space
+ */
+rsRetVal
+cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl)
+{
+ register uchar *pName;
+ int i;
+ DEFiRet;
+
+ ASSERT(pOMSR != NULL);
+
+ pName = pFileName;
+ i = 1; /* we start at 1 so that we reseve space for the '\0'! */
+ while(*p && *p != ';' && *p != ' ' && i < MAXFNAME) {
+ *pName++ = *p++;
+ ++i;
+ }
+ *pName = '\0';
+
+ iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, pszTpl);
+
+ RETiRet;
+}
+
+
+/* Decode a traditional PRI filter */
+/* GPLv3 - stems back to sysklogd */
+rsRetVal DecodePRIFilter(uchar *pline, uchar pmask[])
+{
+ uchar *p;
+ register uchar *q;
+ register int i, i2;
+ uchar *bp;
+ int pri;
+ int singlpri = 0;
+ int ignorepri = 0;
+ uchar buf[2048]; /* buffer for facility and priority names */
+ uchar xbuf[200];
+ DEFiRet;
+
+ ASSERT(pline != NULL);
+
+ dbgprintf("Decoding traditional PRI filter '%s'\n", pline);
+
+ for (i = 0; i <= LOG_NFACILITIES; i++) {
+ pmask[i] = TABLE_NOPRI;
+ }
+
+ /* scan through the list of selectors */
+ for (p = pline; *p && *p != '\t' && *p != ' ';) {
+ /* find the end of this facility name list */
+ for (q = p; *q && *q != '\t' && *q++ != '.'; )
+ continue;
+
+ /* collect priority name */
+ for (bp = buf; *q && !strchr("\t ,;", *q) && bp < buf+sizeof(buf)-1 ; )
+ *bp++ = *q++;
+ *bp = '\0';
+
+ /* skip cruft */
+ if(*q) {
+ while (strchr(",;", *q))
+ q++;
+ }
+
+ /* decode priority name */
+ if ( *buf == '!' ) {
+ ignorepri = 1;
+ /* copy below is ok, we can NOT go off the allocated area */
+ for (bp=buf; *(bp+1); bp++)
+ *bp=*(bp+1);
+ *bp='\0';
+ } else {
+ ignorepri = 0;
+ }
+ if ( *buf == '=' ) {
+ singlpri = 1;
+ pri = decodeSyslogName(&buf[1], syslogPriNames);
+ }
+ else { singlpri = 0;
+ pri = decodeSyslogName(buf, syslogPriNames);
+ }
+
+ if (pri < 0) {
+ snprintf((char*) xbuf, sizeof(xbuf), "unknown priority name \"%s\"", buf);
+ errmsg.LogError(0, RS_RET_ERR, "%s", xbuf);
+ return RS_RET_ERR;
+ }
+
+ /* scan facilities */
+ while (*p && !strchr("\t .;", *p)) {
+ for (bp = buf; *p && !strchr("\t ,;.", *p) && bp < buf+sizeof(buf)-1 ; )
+ *bp++ = *p++;
+ *bp = '\0';
+ if (*buf == '*') {
+ for (i = 0; i <= LOG_NFACILITIES; i++) {
+ if ( pri == INTERNAL_NOPRI ) {
+ if ( ignorepri )
+ pmask[i] = TABLE_ALLPRI;
+ else
+ pmask[i] = TABLE_NOPRI;
+ }
+ else if ( singlpri ) {
+ if ( ignorepri )
+ pmask[i] &= ~(1<<pri);
+ else
+ pmask[i] |= (1<<pri);
+ } else {
+ if ( pri == TABLE_ALLPRI ) {
+ if ( ignorepri )
+ pmask[i] = TABLE_NOPRI;
+ else
+ pmask[i] = TABLE_ALLPRI;
+ } else {
+ if ( ignorepri )
+ for (i2= 0; i2 <= pri; ++i2)
+ pmask[i] &= ~(1<<i2);
+ else
+ for (i2= 0; i2 <= pri; ++i2)
+ pmask[i] |= (1<<i2);
+ }
+ }
+ }
+ } else {
+ i = decodeSyslogName(buf, syslogFacNames);
+ if (i < 0) {
+
+ snprintf((char*) xbuf, sizeof(xbuf), "unknown facility name \"%s\"", buf);
+ errmsg.LogError(0, RS_RET_ERR, "%s", xbuf);
+ return RS_RET_ERR;
+ }
+
+ if ( pri == INTERNAL_NOPRI ) {
+ if ( ignorepri )
+ pmask[i >> 3] = TABLE_ALLPRI;
+ else
+ pmask[i >> 3] = TABLE_NOPRI;
+ } else if ( singlpri ) {
+ if ( ignorepri )
+ pmask[i >> 3] &= ~(1<<pri);
+ else
+ pmask[i >> 3] |= (1<<pri);
+ } else {
+ if ( pri == TABLE_ALLPRI ) {
+ if ( ignorepri )
+ pmask[i >> 3] = TABLE_NOPRI;
+ else
+ pmask[i >> 3] = TABLE_ALLPRI;
+ } else {
+ if ( ignorepri )
+ for (i2= 0; i2 <= pri; ++i2)
+ pmask[i >> 3] &= ~(1<<i2);
+ else
+ for (i2= 0; i2 <= pri; ++i2)
+ pmask[i >> 3] |= (1<<i2);
+ }
+ }
+ }
+ while (*p == ',' || *p == ' ')
+ p++;
+ }
+
+ p = q;
+ }
+
+ RETiRet;
+}
+
+
+/* Helper to cfline(). This function takes the filter part of a property
+ * based filter and decodes it. It processes the line up to the beginning
+ * of the action part. A pointer to that beginnig is passed back to the caller.
+ * rgerhards 2005-09-15
+ */
+rsRetVal DecodePropFilter(uchar *pline, struct cnfstmt *stmt)
+{
+ rsParsObj *pPars;
+ cstr_t *pCSCompOp;
+ cstr_t *pCSPropName;
+ rsRetVal iRet;
+ int iOffset; /* for compare operations */
+
+ ASSERT(pline != NULL);
+
+ dbgprintf("Decoding property-based filter '%s'\n", pline);
+
+ /* create parser object starting with line string without leading colon */
+ if((iRet = rsParsConstructFromSz(&pPars, pline+1)) != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "Error %d constructing parser object - ignoring selector", iRet);
+ return(iRet);
+ }
+
+ /* read property */
+ iRet = parsDelimCStr(pPars, &pCSPropName, ',', 1, 1, 1);
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "error %d parsing filter property - ignoring selector", iRet);
+ rsParsDestruct(pPars);
+ return(iRet);
+ }
+ iRet = propNameToID(pCSPropName, &stmt->d.s_propfilt.propID);
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "error %d parsing filter property - ignoring selector", iRet);
+ rsParsDestruct(pPars);
+ return(iRet);
+ }
+ if(stmt->d.s_propfilt.propID == PROP_CEE) {
+ /* in CEE case, we need to preserve the actual property name */
+ if((stmt->d.s_propfilt.propName =
+ es_newStrFromBuf((char*)cstrGetSzStrNoNULL(pCSPropName)+2, cstrLen(pCSPropName)-2)) == NULL) {
+ cstrDestruct(&pCSPropName);
+ return(RS_RET_ERR);
+ }
+ }
+ cstrDestruct(&pCSPropName);
+
+ /* read operation */
+ iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1, 1);
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "error %d compare operation property - ignoring selector", iRet);
+ rsParsDestruct(pPars);
+ return(iRet);
+ }
+
+ /* we now first check if the condition is to be negated. To do so, we first
+ * must make sure we have at least one char in the param and then check the
+ * first one.
+ * rgerhards, 2005-09-26
+ */
+ if(rsCStrLen(pCSCompOp) > 0) {
+ if(*rsCStrGetBufBeg(pCSCompOp) == '!') {
+ stmt->d.s_propfilt.isNegated = 1;
+ iOffset = 1; /* ignore '!' */
+ } else {
+ stmt->d.s_propfilt.isNegated = 0;
+ iOffset = 0;
+ }
+ } else {
+ stmt->d.s_propfilt.isNegated = 0;
+ iOffset = 0;
+ }
+
+ if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "contains", 8)) {
+ stmt->d.s_propfilt.operation = FIOP_CONTAINS;
+ } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isequal", 7)) {
+ stmt->d.s_propfilt.operation = FIOP_ISEQUAL;
+ } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isempty", 7)) {
+ stmt->d.s_propfilt.operation = FIOP_ISEMPTY;
+ } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "startswith", 10)) {
+ stmt->d.s_propfilt.operation = FIOP_STARTSWITH;
+ } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "regex", 5)) {
+ stmt->d.s_propfilt.operation = FIOP_REGEX;
+ } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "ereregex", 8)) {
+ stmt->d.s_propfilt.operation = FIOP_EREREGEX;
+ } else {
+ errmsg.LogError(0, NO_ERRCODE, "error: invalid compare operation '%s' - ignoring selector",
+ (char*) rsCStrGetSzStrNoNULL(pCSCompOp));
+ }
+ rsCStrDestruct(&pCSCompOp); /* no longer needed */
+
+ if(stmt->d.s_propfilt.operation != FIOP_ISEMPTY) {
+ /* read compare value */
+ iRet = parsQuotedCStr(pPars, &stmt->d.s_propfilt.pCSCompValue);
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "error %d compare value property - ignoring selector", iRet);
+ rsParsDestruct(pPars);
+ return(iRet);
+ }
+ }
+
+ return rsParsDestruct(pPars);
+}
+
+
+/* process the action part of a selector line
+ * rgerhards, 2007-08-01
+ */
+rsRetVal cflineDoAction(rsconf_t *conf, uchar **p, action_t **ppAction)
+{
+ modInfo_t *pMod;
+ cfgmodules_etry_t *node;
+ omodStringRequest_t *pOMSR;
+ int bHadWarning = 0;
+ action_t *pAction = NULL;
+ void *pModData;
+ DEFiRet;
+
+ ASSERT(p != NULL);
+ ASSERT(ppAction != NULL);
+
+ /* loop through all modules and see if one picks up the line */
+ node = module.GetNxtCnfType(conf, NULL, eMOD_OUT);
+ /* Note: clang static analyzer reports that node maybe == NULL. However, this is
+ * not possible, because we have the built-in output modules which are always
+ * present. Anyhow, we guard this by an assert. -- rgerhards, 2010-12-16
+ */
+ assert(node != NULL);
+ while(node != NULL) {
+ pOMSR = NULL;
+ pMod = node->pMod;
+ iRet = pMod->mod.om.parseSelectorAct(p, &pModData, &pOMSR);
+ dbgprintf("tried selector action for %s: %d\n", module.GetName(pMod), iRet);
+ if(iRet == RS_RET_OK_WARN) {
+ bHadWarning = 1;
+ iRet = RS_RET_OK;
+ }
+ if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) {
+ if((iRet = addAction(&pAction, pMod, pModData, pOMSR, NULL, NULL,
+ (iRet == RS_RET_SUSPENDED)? 1 : 0)) == RS_RET_OK) {
+ /* here check if the module is compatible with select features
+ * (currently, we have no such features!) */
+ pAction->eState = ACT_STATE_RDY; /* action is enabled */
+ conf->actions.nbrActions++; /* one more active action! */
+ }
+ break;
+ } else if(iRet != RS_RET_CONFLINE_UNPROCESSED) {
+ /* In this case, the module would have handled the config
+ * line, but some error occured while doing so. This error should
+ * already by reported by the module. We do not try any other
+ * modules on this line, because we found the right one.
+ * rgerhards, 2007-07-24
+ */
+ dbgprintf("error %d parsing config line\n", (int) iRet);
+ break;
+ }
+ node = module.GetNxtCnfType(conf, node, eMOD_OUT);
+ }
+
+ *ppAction = pAction;
+ if(iRet == RS_RET_OK && bHadWarning)
+ iRet = RS_RET_OK_WARN;
+ RETiRet;
+}
+
+
+/* return the current number of active actions
+ * rgerhards, 2008-07-28
+ */
+static rsRetVal
+GetNbrActActions(rsconf_t *conf, int *piNbrActions)
+{
+ DEFiRet;
+ assert(piNbrActions != NULL);
+ *piNbrActions = conf->actions.nbrActions;
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(conf)
+CODESTARTobjQueryInterface(conf)
+ if(pIf->ifVersion != confCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->doNameLine = doNameLine;
+ pIf->cfsysline = cfsysline;
+ pIf->doModLoad = doModLoad;
+ pIf->GetNbrActActions = GetNbrActActions;
+
+finalize_it:
+ENDobjQueryInterface(conf)
+
+
+/* Reset config variables to default values.
+ * rgerhards, 2010-07-23
+ */
+static rsRetVal
+resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ bConfStrictScoping = 0;
+ return RS_RET_OK;
+}
+
+
+/* exit our class
+ * rgerhards, 2008-03-11
+ */
+BEGINObjClassExit(conf, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(conf)
+ /* free no-longer needed module-global variables */
+ if(pDfltHostnameCmp != NULL) {
+ rsCStrDestruct(&pDfltHostnameCmp);
+ }
+
+ if(pDfltProgNameCmp != NULL) {
+ rsCStrDestruct(&pDfltProgNameCmp);
+ }
+
+ /* release objects we no longer need */
+ objRelease(module, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(net, LM_NET_FILENAME);
+ objRelease(ruleset, CORE_COMPONENT);
+ENDObjClassExit(conf)
+
+
+/* Initialize our class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-29
+ */
+BEGINAbstractObjClassInit(conf, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ /* request objects we use */
+ CHKiRet(objUse(module, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(net, LM_NET_FILENAME)); /* TODO: make this dependcy go away! */
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+
+ /* These commands will NOT be supported -- the new v6.3 config system provides
+ * far better methods. We will remove the related code soon. -- rgerhards, 2012-01-09
+ */
+ CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL));
+ENDObjClassInit(conf)
+
+/* vi:set ai:
+ */
diff --git a/runtime/conf.h b/runtime/conf.h
new file mode 100644
index 00000000..a1bb51ad
--- /dev/null
+++ b/runtime/conf.h
@@ -0,0 +1,72 @@
+/* Definitions for config file handling (not yet an object).
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_CONF_H
+#define INCLUDED_CONF_H
+#include "action.h"
+
+/* definitions used for doNameLine to differentiate between different command types
+ * (with otherwise identical code). This is a left-over from the previous config
+ * system. It stays, because it is still useful. So do not wonder why it looks
+ * somewhat strange (at least its name). -- rgerhards, 2007-08-01
+ */
+enum eDirective { DIR_TEMPLATE = 0, DIR_OUTCHANNEL = 1, DIR_ALLOWEDSENDER = 2};
+extern ecslConfObjType currConfObj;
+extern int bConfStrictScoping; /* force strict scoping during config processing? */
+
+/* interfaces */
+BEGINinterface(conf) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*doNameLine)(uchar **pp, void* pVal);
+ rsRetVal (*cfsysline)(uchar *p);
+ rsRetVal (*doModLoad)(uchar **pp, __attribute__((unused)) void* pVal);
+ rsRetVal (*GetNbrActActions)(rsconf_t *conf, int *);
+ /* version 4 -- 2010-07-23 rgerhards */
+ /* "just" added global variables
+ * FYI: we reconsider repacking as a non-object, as only the core currently
+ * accesses this module. The current object structure complicates things without
+ * any real benefit.
+ */
+ /* version 5 -- 2011-04-19 rgerhards */
+ /* complete revamp, we now use the rsconf object */
+ /* version 6 -- 2011-07-06 rgerhards */
+ /* again a complete revamp, using flex/bison based parser now */
+ENDinterface(conf)
+#define confCURR_IF_VERSION 6 /* increment whenever you change the interface structure! */
+/* in Version 3, entry point "ReInitConf()" was removed, as we do not longer need
+ * to support restart-type HUP -- rgerhards, 2009-07-15
+ */
+
+
+/* prototypes */
+PROTOTYPEObj(conf);
+
+
+/* TODO: the following 2 need to go in conf obj interface... */
+rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName);
+rsRetVal cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *pszTpl);
+
+rsRetVal DecodePRIFilter(uchar *pline, uchar pmask[]);
+rsRetVal DecodePropFilter(uchar *pline, struct cnfstmt *stmt);
+rsRetVal cflineDoAction(rsconf_t *conf, uchar **p, action_t **ppAction);
+extern EHostnameCmpMode eDfltHostnameCmpMode;
+extern cstr_t *pDfltHostnameCmp;
+extern cstr_t *pDfltProgNameCmp;
+
+#endif /* #ifndef INCLUDED_CONF_H */
diff --git a/runtime/cryprov.h b/runtime/cryprov.h
new file mode 100644
index 00000000..5690904d
--- /dev/null
+++ b/runtime/cryprov.h
@@ -0,0 +1,50 @@
+/* The interface definition for (file) crypto providers.
+ *
+ * This is just an abstract driver interface, which needs to be
+ * implemented by concrete classes.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_CRYPROV_H
+#define INCLUDED_CRYPROV_H
+
+#include <gcrypt.h>
+
+/* we unfortunately need to have two different param names depending on the
+ * context in which parameters are set. Other than (re/over)engineering the core
+ * interface, we just define some values to keep track of that.
+ */
+#define CRYPROV_PARAMTYPE_REGULAR 0
+#define CRYPROV_PARAMTYPE_DISK 1
+
+/* interface */
+BEGINinterface(cryprov) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(void *ppThis);
+ rsRetVal (*SetCnfParam)(void *ppThis, struct nvlst *lst, int paramType);
+ rsRetVal (*Destruct)(void *ppThis);
+ rsRetVal (*OnFileOpen)(void *pThis, uchar *fn, void *pFileInstData, char openMode);
+ rsRetVal (*Encrypt)(void *pFileInstData, uchar *buf, size_t *lenBuf);
+ rsRetVal (*Decrypt)(void *pFileInstData, uchar *buf, size_t *lenBuf);
+ rsRetVal (*OnFileClose)(void *pFileInstData, off64_t offsLogfile);
+ rsRetVal (*DeleteStateFiles)(uchar *logfn);
+ rsRetVal (*GetBytesLeftInBlock)(void *pFileInstData, ssize_t *left);
+ void (*SetDeleteOnClose)(void *pFileInstData, int val);
+ENDinterface(cryprov)
+#define cryprovCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */
+#endif /* #ifndef INCLUDED_CRYPROV_H */
diff --git a/runtime/datetime.c b/runtime/datetime.c
new file mode 100644
index 00000000..841ff625
--- /dev/null
+++ b/runtime/datetime.c
@@ -0,0 +1,1023 @@
+/* The datetime object. It contains date and time related functions.
+ *
+ * Module begun 2008-03-05 by Rainer Gerhards, based on some code
+ * from syslogd.c. The main intension was to move code out of syslogd.c
+ * in a useful manner. It is still undecided if all functions will continue
+ * to stay here or some will be moved into parser modules (once we have them).
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <assert.h>
+#ifdef HAVE_SYS_TIME_H
+# include <sys/time.h>
+#endif
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "modules.h"
+#include "datetime.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+#include "errmsg.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+
+/* the following table of ten powers saves us some computation */
+static const int tenPowers[6] = { 1, 10, 100, 1000, 10000, 100000 };
+
+/* ------------------------------ methods ------------------------------ */
+
+
+/**
+ * Convert struct timeval to syslog_time
+ */
+void
+timeval2syslogTime(struct timeval *tp, struct syslogTime *t)
+{
+ struct tm *tm;
+ struct tm tmBuf;
+ long lBias;
+ time_t secs;
+
+ secs = tp->tv_sec;
+ tm = localtime_r(&secs, &tmBuf);
+
+ t->year = tm->tm_year + 1900;
+ t->month = tm->tm_mon + 1;
+ t->day = tm->tm_mday;
+ t->hour = tm->tm_hour;
+ t->minute = tm->tm_min;
+ t->second = tm->tm_sec;
+ t->secfrac = tp->tv_usec;
+ t->secfracPrecision = 6;
+
+# if __sun
+ /* Solaris uses a different method of exporting the time zone.
+ * It is UTC - localtime, which is the opposite sign of mins east of GMT.
+ */
+ lBias = -(tm->tm_isdst ? altzone : timezone);
+# elif defined(__hpux)
+ lBias = tz.tz_dsttime ? - tz.tz_minuteswest : 0;
+# else
+ lBias = tm->tm_gmtoff;
+# endif
+ if(lBias < 0) {
+ t->OffsetMode = '-';
+ lBias *= -1;
+ } else
+ t->OffsetMode = '+';
+ t->OffsetHour = lBias / 3600;
+ t->OffsetMinute = (lBias % 3600) / 60;
+ t->timeType = TIME_TYPE_RFC5424; /* we have a high precision timestamp */
+}
+
+/**
+ * Get the current date/time in the best resolution the operating
+ * system has to offer (well, actually at most down to the milli-
+ * second level.
+ *
+ * The date and time is returned in separate fields as this is
+ * most portable and removes the need for additional structures
+ * (but I have to admit it is somewhat "bulky";)).
+ *
+ * Obviously, *t must not be NULL...
+ *
+ * rgerhards, 2008-10-07: added ttSeconds to provide a way to
+ * obtain the second-resolution UNIX timestamp. This is needed
+ * in some situations to minimize time() calls (namely when doing
+ * output processing). This can be left NULL if not needed.
+ */
+static void getCurrTime(struct syslogTime *t, time_t *ttSeconds)
+{
+ struct timeval tp;
+# if defined(__hpux)
+ struct timezone tz;
+# endif
+
+ assert(t != NULL);
+# if defined(__hpux)
+ /* TODO: check this: under HP UX, the tz information is actually valid
+ * data. So we need to obtain and process it there.
+ */
+ gettimeofday(&tp, &tz);
+# else
+ gettimeofday(&tp, NULL);
+# endif
+ if(ttSeconds != NULL)
+ *ttSeconds = tp.tv_sec;
+
+ timeval2syslogTime(&tp, t);
+}
+
+
+/* A fast alternative to getCurrTime() and time() that only obtains
+ * a timestamp like time() does. I was told that gettimeofday(), at
+ * least under Linux, is much faster than time() and I could confirm
+ * this testing. So I created that function as a replacement.
+ * rgerhards, 2009-11-12
+ */
+static time_t
+getTime(time_t *ttSeconds)
+{
+ struct timeval tp;
+
+ if(gettimeofday(&tp, NULL) == -1)
+ return -1;
+
+ if(ttSeconds != NULL)
+ *ttSeconds = tp.tv_sec;
+ return tp.tv_sec;
+}
+
+
+/*******************************************************************
+ * BEGIN CODE-LIBLOGGING *
+ *******************************************************************
+ * Code in this section is borrowed from liblogging. This is an
+ * interim solution. Once liblogging is fully integrated, this is
+ * to be removed (see http://www.monitorware.com/liblogging for
+ * more details. 2004-11-16 rgerhards
+ *
+ * Please note that the orginal liblogging code is modified so that
+ * it fits into the context of the current version of syslogd.c.
+ *
+ * DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!!
+ */
+
+
+/**
+ * Parse a 32 bit integer number from a string.
+ *
+ * \param ppsz Pointer to the Pointer to the string being parsed. It
+ * must be positioned at the first digit. Will be updated
+ * so that on return it points to the first character AFTER
+ * the integer parsed.
+ * \param pLenStr pointer to string length, decremented on exit by
+ * characters processed
+ * Note that if an empty string (len < 1) is passed in,
+ * the method always returns zero.
+ * \retval The number parsed.
+ */
+static inline int
+srSLMGParseInt32(uchar** ppsz, int *pLenStr)
+{
+ register int i;
+
+ i = 0;
+ while(*pLenStr > 0 && **ppsz >= '0' && **ppsz <= '9') {
+ i = i * 10 + **ppsz - '0';
+ ++(*ppsz);
+ --(*pLenStr);
+ }
+
+ return i;
+}
+
+
+/**
+ * Parse a TIMESTAMP-3339.
+ * updates the parse pointer position. The pTime parameter
+ * is guranteed to be updated only if a new valid timestamp
+ * could be obtained (restriction added 2008-09-16 by rgerhards).
+ * This method now also checks the maximum string length it is passed.
+ * If a *valid* timestamp is found, the string length is decremented
+ * by the number of characters processed. If it is not a valid timestamp,
+ * the length is kept unmodified. -- rgerhards, 2009-09-23
+ */
+static rsRetVal
+ParseTIMESTAMP3339(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr)
+{
+ uchar *pszTS = *ppszTS;
+ /* variables to temporarily hold time information while we parse */
+ int year;
+ int month;
+ int day;
+ int hour; /* 24 hour clock */
+ int minute;
+ int second;
+ int secfrac; /* fractional seconds (must be 32 bit!) */
+ int secfracPrecision;
+ char OffsetMode; /* UTC offset + or - */
+ char OffsetHour; /* UTC offset in hours */
+ int OffsetMinute; /* UTC offset in minutes */
+ int lenStr;
+ /* end variables to temporarily hold time information while we parse */
+ DEFiRet;
+
+ assert(pTime != NULL);
+ assert(ppszTS != NULL);
+ assert(pszTS != NULL);
+
+ lenStr = *pLenStr;
+ year = srSLMGParseInt32(&pszTS, &lenStr);
+
+ /* We take the liberty to accept slightly malformed timestamps e.g. in
+ * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course,
+ * with the current state of affairs, we would never run into this code
+ * here because at postion 11, there is no "T" in such cases ;)
+ */
+ if(lenStr == 0 || *pszTS++ != '-')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ month = srSLMGParseInt32(&pszTS, &lenStr);
+ if(month < 1 || month > 12)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS++ != '-')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ day = srSLMGParseInt32(&pszTS, &lenStr);
+ if(day < 1 || day > 31)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS++ != 'T')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+
+ hour = srSLMGParseInt32(&pszTS, &lenStr);
+ if(hour < 0 || hour > 23)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS++ != ':')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ minute = srSLMGParseInt32(&pszTS, &lenStr);
+ if(minute < 0 || minute > 59)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS++ != ':')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ second = srSLMGParseInt32(&pszTS, &lenStr);
+ if(second < 0 || second > 60)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ /* Now let's see if we have secfrac */
+ if(lenStr > 0 && *pszTS == '.') {
+ --lenStr;
+ uchar *pszStart = ++pszTS;
+ secfrac = srSLMGParseInt32(&pszTS, &lenStr);
+ secfracPrecision = (int) (pszTS - pszStart);
+ } else {
+ secfracPrecision = 0;
+ secfrac = 0;
+ }
+
+ /* check the timezone */
+ if(lenStr == 0)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(*pszTS == 'Z') {
+ --lenStr;
+ pszTS++; /* eat Z */
+ OffsetMode = 'Z';
+ OffsetHour = 0;
+ OffsetMinute = 0;
+ } else if((*pszTS == '+') || (*pszTS == '-')) {
+ OffsetMode = *pszTS;
+ --lenStr;
+ pszTS++;
+
+ OffsetHour = srSLMGParseInt32(&pszTS, &lenStr);
+ if(OffsetHour < 0 || OffsetHour > 23)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS != ':')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ pszTS++;
+ OffsetMinute = srSLMGParseInt32(&pszTS, &lenStr);
+ if(OffsetMinute < 0 || OffsetMinute > 59)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else {
+ /* there MUST be TZ information */
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ }
+
+ /* OK, we actually have a 3339 timestamp, so let's indicated this */
+ if(lenStr > 0) {
+ if(*pszTS != ' ') /* if it is not a space, it can not be a "good" time - 2010-02-22 rgerhards */
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ ++pszTS; /* just skip past it */
+ --lenStr;
+ }
+
+ /* we had success, so update parse pointer and caller-provided timestamp */
+ *ppszTS = pszTS;
+ pTime->timeType = 2;
+ pTime->year = year;
+ pTime->month = month;
+ pTime->day = day;
+ pTime->hour = hour;
+ pTime->minute = minute;
+ pTime->second = second;
+ pTime->secfrac = secfrac;
+ pTime->secfracPrecision = secfracPrecision;
+ pTime->OffsetMode = OffsetMode;
+ pTime->OffsetHour = OffsetHour;
+ pTime->OffsetMinute = OffsetMinute;
+ *pLenStr = lenStr;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/**
+ * Parse a TIMESTAMP-3164. The pTime parameter
+ * is guranteed to be updated only if a new valid timestamp
+ * could be obtained (restriction added 2008-09-16 by rgerhards). This
+ * also means the caller *must* provide a valid (probably current)
+ * timstamp in pTime when calling this function. a 3164 timestamp contains
+ * only partial information and only that partial information is updated.
+ * So the "output timestamp" is a valid timestamp only if the "input
+ * timestamp" was valid, too. The is actually an optimization, as it
+ * permits us to use a pre-aquired timestamp and thus avoids to do
+ * a (costly) time() call. Thanks to David Lang for insisting on
+ * time() call reduction ;).
+ * This method now also checks the maximum string length it is passed.
+ * If a *valid* timestamp is found, the string length is decremented
+ * by the number of characters processed. If it is not a valid timestamp,
+ * the length is kept unmodified. -- rgerhards, 2009-09-23
+ */
+static rsRetVal
+ParseTIMESTAMP3164(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr)
+{
+ /* variables to temporarily hold time information while we parse */
+ int month;
+ int day;
+ int year = 0; /* 0 means no year provided */
+ int hour; /* 24 hour clock */
+ int minute;
+ int second;
+ /* end variables to temporarily hold time information while we parse */
+ int lenStr;
+ uchar *pszTS;
+ DEFiRet;
+
+ assert(ppszTS != NULL);
+ pszTS = *ppszTS;
+ assert(pszTS != NULL);
+ assert(pTime != NULL);
+ assert(pLenStr != NULL);
+ lenStr = *pLenStr;
+
+ /* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec),
+ * we may see the following character sequences occur:
+ *
+ * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec
+ *
+ * We will use this for parsing, as it probably is the
+ * fastest way to parse it.
+ *
+ * 2009-08-17: we now do case-insensitive comparisons, as some devices obviously do not
+ * obey to the RFC-specified case. As we need to guess in any case, we can ignore case
+ * in the first place -- rgerhards
+ *
+ * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C...
+ * Fixed a bug that lead to invalid detection of the data. The issue was that
+ * we had an if(++pszTS == 'x') inside of some of the consturcts below. However,
+ * there were also some elseifs (doing the same ++), which than obviously did not
+ * check the orginal character but the next one. Now removed the ++ and put it
+ * into the statements below. Was a really nasty bug... I didn't detect it before
+ * june, when it first manifested. This also lead to invalid parsing of the rest
+ * of the message, as the time stamp was not detected to be correct. - rgerhards
+ */
+ if(lenStr < 3)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ switch(*pszTS++)
+ {
+ case 'j':
+ case 'J':
+ if(*pszTS == 'a' || *pszTS == 'A') {
+ ++pszTS;
+ if(*pszTS == 'n' || *pszTS == 'N') {
+ ++pszTS;
+ month = 1;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else if(*pszTS == 'u' || *pszTS == 'U') {
+ ++pszTS;
+ if(*pszTS == 'n' || *pszTS == 'N') {
+ ++pszTS;
+ month = 6;
+ } else if(*pszTS == 'l' || *pszTS == 'L') {
+ ++pszTS;
+ month = 7;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ case 'f':
+ case 'F':
+ if(*pszTS == 'e' || *pszTS == 'E') {
+ ++pszTS;
+ if(*pszTS == 'b' || *pszTS == 'B') {
+ ++pszTS;
+ month = 2;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ case 'm':
+ case 'M':
+ if(*pszTS == 'a' || *pszTS == 'A') {
+ ++pszTS;
+ if(*pszTS == 'r' || *pszTS == 'R') {
+ ++pszTS;
+ month = 3;
+ } else if(*pszTS == 'y' || *pszTS == 'Y') {
+ ++pszTS;
+ month = 5;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ case 'a':
+ case 'A':
+ if(*pszTS == 'p' || *pszTS == 'P') {
+ ++pszTS;
+ if(*pszTS == 'r' || *pszTS == 'R') {
+ ++pszTS;
+ month = 4;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else if(*pszTS == 'u' || *pszTS == 'U') {
+ ++pszTS;
+ if(*pszTS == 'g' || *pszTS == 'G') {
+ ++pszTS;
+ month = 8;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ case 's':
+ case 'S':
+ if(*pszTS == 'e' || *pszTS == 'E') {
+ ++pszTS;
+ if(*pszTS == 'p' || *pszTS == 'P') {
+ ++pszTS;
+ month = 9;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ case 'o':
+ case 'O':
+ if(*pszTS == 'c' || *pszTS == 'C') {
+ ++pszTS;
+ if(*pszTS == 't' || *pszTS == 'T') {
+ ++pszTS;
+ month = 10;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ case 'n':
+ case 'N':
+ if(*pszTS == 'o' || *pszTS == 'O') {
+ ++pszTS;
+ if(*pszTS == 'v' || *pszTS == 'V') {
+ ++pszTS;
+ month = 11;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ case 'd':
+ case 'D':
+ if(*pszTS == 'e' || *pszTS == 'E') {
+ ++pszTS;
+ if(*pszTS == 'c' || *pszTS == 'C') {
+ ++pszTS;
+ month = 12;
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ } else
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ break;
+ default:
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ }
+
+ lenStr -= 3;
+
+ /* done month */
+
+ if(lenStr == 0 || *pszTS++ != ' ')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+
+ /* we accept a slightly malformed timestamp when receiving. This is
+ * we accept one-digit days
+ */
+ if(*pszTS == ' ') {
+ --lenStr;
+ ++pszTS;
+ }
+
+ day = srSLMGParseInt32(&pszTS, &lenStr);
+ if(day < 1 || day > 31)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS++ != ' ')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+
+ /* time part */
+ hour = srSLMGParseInt32(&pszTS, &lenStr);
+ if(hour > 1970 && hour < 2100) {
+ /* if so, we assume this actually is a year. This is a format found
+ * e.g. in Cisco devices.
+ * (if you read this 2100+ trying to fix a bug, congratulate me
+ * to how long the code survived - me no longer ;)) -- rgerhards, 2008-11-18
+ */
+ year = hour;
+
+ /* re-query the hour, this time it must be valid */
+ if(lenStr == 0 || *pszTS++ != ' ')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ hour = srSLMGParseInt32(&pszTS, &lenStr);
+ }
+
+ if(hour < 0 || hour > 23)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS++ != ':')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ minute = srSLMGParseInt32(&pszTS, &lenStr);
+ if(minute < 0 || minute > 59)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ if(lenStr == 0 || *pszTS++ != ':')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ --lenStr;
+ second = srSLMGParseInt32(&pszTS, &lenStr);
+ if(second < 0 || second > 60)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ /* we provide support for an extra ":" after the date. While this is an
+ * invalid format, it occurs frequently enough (e.g. with Cisco devices)
+ * to permit it as a valid case. -- rgerhards, 2008-09-12
+ */
+ if(lenStr > 0 && *pszTS == ':') {
+ ++pszTS; /* just skip past it */
+ --lenStr;
+ }
+ if(lenStr > 0) {
+ if(*pszTS != ' ') /* if it is not a space, it can not be a "good" time - 2010-02-22 rgerhards */
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ ++pszTS; /* just skip past it */
+ --lenStr;
+ }
+
+ /* we had success, so update parse pointer and caller-provided timestamp
+ * fields we do not have are not updated in the caller's timestamp. This
+ * is the reason why the caller must pass in a correct timestamp.
+ */
+ *ppszTS = pszTS; /* provide updated parse position back to caller */
+ pTime->timeType = 1;
+ pTime->month = month;
+ if(year > 0)
+ pTime->year = year; /* persist year if detected */
+ pTime->day = day;
+ pTime->hour = hour;
+ pTime->minute = minute;
+ pTime->second = second;
+ pTime->secfracPrecision = 0;
+ pTime->secfrac = 0;
+ *pLenStr = lenStr;
+
+finalize_it:
+ RETiRet;
+}
+
+/*******************************************************************
+ * END CODE-LIBLOGGING *
+ *******************************************************************/
+
+/**
+ * Format a syslogTimestamp into format required by MySQL.
+ * We are using the 14 digits format. For example 20041111122600
+ * is interpreted as '2004-11-11 12:26:00'.
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string terminator). If 0 is returend, an error occured.
+ */
+int formatTimestampToMySQL(struct syslogTime *ts, char* pBuf)
+{
+ /* currently we do not consider localtime/utc. This may later be
+ * added. If so, I recommend using a property replacer option
+ * and/or a global configuration option. However, we should wait
+ * on user requests for this feature before doing anything.
+ * rgerhards, 2007-06-26
+ */
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ pBuf[0] = (ts->year / 1000) % 10 + '0';
+ pBuf[1] = (ts->year / 100) % 10 + '0';
+ pBuf[2] = (ts->year / 10) % 10 + '0';
+ pBuf[3] = ts->year % 10 + '0';
+ pBuf[4] = (ts->month / 10) % 10 + '0';
+ pBuf[5] = ts->month % 10 + '0';
+ pBuf[6] = (ts->day / 10) % 10 + '0';
+ pBuf[7] = ts->day % 10 + '0';
+ pBuf[8] = (ts->hour / 10) % 10 + '0';
+ pBuf[9] = ts->hour % 10 + '0';
+ pBuf[10] = (ts->minute / 10) % 10 + '0';
+ pBuf[11] = ts->minute % 10 + '0';
+ pBuf[12] = (ts->second / 10) % 10 + '0';
+ pBuf[13] = ts->second % 10 + '0';
+ pBuf[14] = '\0';
+ return 15;
+
+}
+
+int formatTimestampToPgSQL(struct syslogTime *ts, char *pBuf)
+{
+ /* see note in formatTimestampToMySQL, applies here as well */
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ pBuf[0] = (ts->year / 1000) % 10 + '0';
+ pBuf[1] = (ts->year / 100) % 10 + '0';
+ pBuf[2] = (ts->year / 10) % 10 + '0';
+ pBuf[3] = ts->year % 10 + '0';
+ pBuf[4] = '-';
+ pBuf[5] = (ts->month / 10) % 10 + '0';
+ pBuf[6] = ts->month % 10 + '0';
+ pBuf[7] = '-';
+ pBuf[8] = (ts->day / 10) % 10 + '0';
+ pBuf[9] = ts->day % 10 + '0';
+ pBuf[10] = ' ';
+ pBuf[11] = (ts->hour / 10) % 10 + '0';
+ pBuf[12] = ts->hour % 10 + '0';
+ pBuf[13] = ':';
+ pBuf[14] = (ts->minute / 10) % 10 + '0';
+ pBuf[15] = ts->minute % 10 + '0';
+ pBuf[16] = ':';
+ pBuf[17] = (ts->second / 10) % 10 + '0';
+ pBuf[18] = ts->second % 10 + '0';
+ pBuf[19] = '\0';
+ return 19;
+}
+
+
+/**
+ * Format a syslogTimestamp to just the fractional seconds.
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string terminator). If 0 is returend, an error occured.
+ * The buffer must be at least 7 bytes large.
+ * rgerhards, 2008-06-06
+ */
+int formatTimestampSecFrac(struct syslogTime *ts, char* pBuf)
+{
+ int iBuf;
+ int power;
+ int secfrac;
+ short digit;
+
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ iBuf = 0;
+ if(ts->secfracPrecision > 0)
+ {
+ power = tenPowers[(ts->secfracPrecision - 1) % 6];
+ secfrac = ts->secfrac;
+ while(power > 0) {
+ digit = secfrac / power;
+ secfrac -= digit * power;
+ power /= 10;
+ pBuf[iBuf++] = digit + '0';
+ }
+ } else {
+ pBuf[iBuf++] = '0';
+ }
+ pBuf[iBuf] = '\0';
+
+ return iBuf;
+}
+
+
+/**
+ * Format a syslogTimestamp to a RFC3339 timestamp string (as
+ * specified in syslog-protocol).
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string terminator). If 0 is returend, an error occured.
+ */
+int formatTimestamp3339(struct syslogTime *ts, char* pBuf)
+{
+ int iBuf;
+ int power;
+ int secfrac;
+ short digit;
+
+ BEGINfunc
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ /* start with fixed parts */
+ /* year yyyy */
+ pBuf[0] = (ts->year / 1000) % 10 + '0';
+ pBuf[1] = (ts->year / 100) % 10 + '0';
+ pBuf[2] = (ts->year / 10) % 10 + '0';
+ pBuf[3] = ts->year % 10 + '0';
+ pBuf[4] = '-';
+ /* month */
+ pBuf[5] = (ts->month / 10) % 10 + '0';
+ pBuf[6] = ts->month % 10 + '0';
+ pBuf[7] = '-';
+ /* day */
+ pBuf[8] = (ts->day / 10) % 10 + '0';
+ pBuf[9] = ts->day % 10 + '0';
+ pBuf[10] = 'T';
+ /* hour */
+ pBuf[11] = (ts->hour / 10) % 10 + '0';
+ pBuf[12] = ts->hour % 10 + '0';
+ pBuf[13] = ':';
+ /* minute */
+ pBuf[14] = (ts->minute / 10) % 10 + '0';
+ pBuf[15] = ts->minute % 10 + '0';
+ pBuf[16] = ':';
+ /* second */
+ pBuf[17] = (ts->second / 10) % 10 + '0';
+ pBuf[18] = ts->second % 10 + '0';
+
+ iBuf = 19; /* points to next free entry, now it becomes dynamic! */
+
+ if(ts->secfracPrecision > 0) {
+ pBuf[iBuf++] = '.';
+ power = tenPowers[(ts->secfracPrecision - 1) % 6];
+ secfrac = ts->secfrac;
+ while(power > 0) {
+ digit = secfrac / power;
+ secfrac -= digit * power;
+ power /= 10;
+ pBuf[iBuf++] = digit + '0';
+ }
+ }
+
+ if(ts->OffsetMode == 'Z') {
+ pBuf[iBuf++] = 'Z';
+ } else {
+ pBuf[iBuf++] = ts->OffsetMode;
+ pBuf[iBuf++] = (ts->OffsetHour / 10) % 10 + '0';
+ pBuf[iBuf++] = ts->OffsetHour % 10 + '0';
+ pBuf[iBuf++] = ':';
+ pBuf[iBuf++] = (ts->OffsetMinute / 10) % 10 + '0';
+ pBuf[iBuf++] = ts->OffsetMinute % 10 + '0';
+ }
+
+ pBuf[iBuf] = '\0';
+
+ ENDfunc
+ return iBuf;
+}
+
+/**
+ * Format a syslogTimestamp to a RFC3164 timestamp sring.
+ * The caller must provide the timestamp as well as a character
+ * buffer that will receive the resulting string. The function
+ * returns the size of the timestamp written in bytes (without
+ * the string termnator). If 0 is returend, an error occured.
+ * rgerhards, 2010-03-05: Added support to for buggy 3164 dates,
+ * where a zero-digit is written instead of a space for the first
+ * day character if day < 10. syslog-ng seems to do that, and some
+ * parsing scripts (in migration cases) rely on that.
+ */
+int formatTimestamp3164(struct syslogTime *ts, char* pBuf, int bBuggyDay)
+{
+ static char* monthNames[12] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+ int iDay;
+ assert(ts != NULL);
+ assert(pBuf != NULL);
+
+ pBuf[0] = monthNames[(ts->month - 1)% 12][0];
+ pBuf[1] = monthNames[(ts->month - 1) % 12][1];
+ pBuf[2] = monthNames[(ts->month - 1) % 12][2];
+ pBuf[3] = ' ';
+ iDay = (ts->day / 10) % 10; /* we need to write a space if the first digit is 0 */
+ pBuf[4] = (bBuggyDay || iDay > 0) ? iDay + '0' : ' ';
+ pBuf[5] = ts->day % 10 + '0';
+ pBuf[6] = ' ';
+ pBuf[7] = (ts->hour / 10) % 10 + '0';
+ pBuf[8] = ts->hour % 10 + '0';
+ pBuf[9] = ':';
+ pBuf[10] = (ts->minute / 10) % 10 + '0';
+ pBuf[11] = ts->minute % 10 + '0';
+ pBuf[12] = ':';
+ pBuf[13] = (ts->second / 10) % 10 + '0';
+ pBuf[14] = ts->second % 10 + '0';
+ pBuf[15] = '\0';
+ return 16; /* traditional: number of bytes written */
+}
+
+
+/**
+ * convert syslog timestamp to time_t
+ */
+time_t syslogTime2time_t(struct syslogTime *ts)
+{
+ long MonthInDays, NumberOfYears, NumberOfDays, i;
+ int utcOffset;
+ time_t TimeInUnixFormat;
+
+ /* Counting how many Days have passed since the 01.01 of the
+ * selected Year (Month level), according to the selected Month*/
+
+ switch(ts->month)
+ {
+ case 1:
+ MonthInDays = 0; //until 01 of January
+ break;
+ case 2:
+ MonthInDays = 31; //until 01 of February - leap year handling down below!
+ break;
+ case 3:
+ MonthInDays = 59; //until 01 of March
+ break;
+ case 4:
+ MonthInDays = 90; //until 01 of April
+ break;
+ case 5:
+ MonthInDays = 120; //until 01 of Mai
+ break;
+ case 6:
+ MonthInDays = 151; //until 01 of June
+ break;
+ case 7:
+ MonthInDays = 181; //until 01 of July
+ break;
+ case 8:
+ MonthInDays = 212; //until 01 of August
+ break;
+ case 9:
+ MonthInDays = 243; //until 01 of September
+ break;
+ case 10:
+ MonthInDays = 273; //until 01 of Oktober
+ break;
+ case 11:
+ MonthInDays = 304; //until 01 of November
+ break;
+ case 12:
+ MonthInDays = 334; //until 01 of December
+ break;
+ default: /* this cannot happen (and would be a program error)
+ * but we need the code to keep the compiler silent.
+ */
+ MonthInDays = 0; /* any value fits ;) */
+ break;
+ }
+
+
+ /* 1) Counting how many Years have passed since 1970
+ 2) Counting how many Days have passed since the 01.01 of the selected Year
+ (Day level) according to the Selected Month and Day. Last day doesn't count,
+ it should be until last day
+ 3) Calculating this period (NumberOfDays) in seconds*/
+
+ NumberOfYears = ts->year - 1970;
+ NumberOfDays = MonthInDays + ts->day - 1;
+ TimeInUnixFormat = NumberOfYears * 31536000 + NumberOfDays * 86400;
+
+ /* Now we need to adjust the number of years for leap
+ * year processing. If we are in Jan or Feb, this year
+ * will never be considered - because we haven't arrived
+ * at then end of Feb right now. [Feb, 29th in a leap year
+ * is handled correctly, because the day (29) is correctly
+ * added to the date serial]
+ */
+ if(ts->month < 3)
+ NumberOfYears--;
+
+ /*...AND ADDING ONE DAY FOR EACH YEAR WITH 366 DAYS
+ * note that we do not handle 2000 any special, as it was a
+ * leap year. The current code works OK until 2100, when it will
+ * break. As we do not process future dates, we accept that fate...
+ * the whole thing could be refactored by a table-based approach.
+ */
+ for(i = 1;i <= NumberOfYears; i++)
+ {
+ /* If i = 2 we have 1972, which was a Year with 366 Days
+ and if (i + 2) Mod (4) = 0 we have a Year after 1972
+ which is also a Year with 366 Days (repeated every 4 Years) */
+ if ((i == 2) || (((i + 2) % 4) == 0))
+ { /*Year with 366 Days!!!*/
+ TimeInUnixFormat += 86400;
+ }
+ }
+
+ /*Add Hours, minutes and seconds */
+ TimeInUnixFormat += ts->hour*60*60;
+ TimeInUnixFormat += ts->minute*60;
+ TimeInUnixFormat += ts->second;
+ /* do UTC offset */
+ utcOffset = ts->OffsetHour*3600 + ts->OffsetMinute*60;
+ if(ts->OffsetMode == '+')
+ utcOffset *= -1; /* if timestamp is ahead, we need to "go back" to UTC */
+ TimeInUnixFormat += utcOffset;
+ return TimeInUnixFormat;
+}
+
+
+/**
+ * format a timestamp as a UNIX timestamp; subsecond resolution is
+ * discarded.
+ * Note that this code can use some refactoring. I decided to use it
+ * because mktime() requires an upfront TZ update as it works on local
+ * time. In any case, it is worth reconsidering to move to mktime() or
+ * some other method.
+ * Important: pBuf must point to a buffer of at least 11 bytes.
+ * rgerhards, 2012-03-29
+ */
+int formatTimestampUnix(struct syslogTime *ts, char *pBuf)
+{
+ snprintf(pBuf, 11, "%u", (unsigned) syslogTime2time_t(ts));
+ return 11;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(datetime)
+CODESTARTobjQueryInterface(datetime)
+ if(pIf->ifVersion != datetimeCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->getCurrTime = getCurrTime;
+ pIf->GetTime = getTime;
+ pIf->timeval2syslogTime = timeval2syslogTime;
+ pIf->ParseTIMESTAMP3339 = ParseTIMESTAMP3339;
+ pIf->ParseTIMESTAMP3164 = ParseTIMESTAMP3164;
+ pIf->formatTimestampToMySQL = formatTimestampToMySQL;
+ pIf->formatTimestampToPgSQL = formatTimestampToPgSQL;
+ pIf->formatTimestampSecFrac = formatTimestampSecFrac;
+ pIf->formatTimestamp3339 = formatTimestamp3339;
+ pIf->formatTimestamp3164 = formatTimestamp3164;
+ pIf->formatTimestampUnix = formatTimestampUnix;
+ pIf->syslogTime2time_t = syslogTime2time_t;
+finalize_it:
+ENDobjQueryInterface(datetime)
+
+
+/* Initialize the datetime class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(datetime, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ENDObjClassInit(datetime)
+
+/* vi:set ai:
+ */
diff --git a/runtime/datetime.h b/runtime/datetime.h
new file mode 100644
index 00000000..9f3611e1
--- /dev/null
+++ b/runtime/datetime.h
@@ -0,0 +1,66 @@
+/* The datetime object. Contains time-related functions.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_DATETIME_H
+#define INCLUDED_DATETIME_H
+
+/* TODO: define error codes */
+#define NO_ERRCODE -1
+
+/* the datetime object */
+typedef struct datetime_s {
+ char dummy;
+} datetime_t;
+
+
+/* interfaces */
+BEGINinterface(datetime) /* name must also be changed in ENDinterface macro! */
+ void (*getCurrTime)(struct syslogTime *t, time_t *ttSeconds);
+ rsRetVal (*ParseTIMESTAMP3339)(struct syslogTime *pTime, uchar** ppszTS, int*);
+ rsRetVal (*ParseTIMESTAMP3164)(struct syslogTime *pTime, uchar** pszTS, int*);
+ int (*formatTimestampToMySQL)(struct syslogTime *ts, char* pDst);
+ int (*formatTimestampToPgSQL)(struct syslogTime *ts, char *pDst);
+ int (*formatTimestamp3339)(struct syslogTime *ts, char* pBuf);
+ int (*formatTimestamp3164)(struct syslogTime *ts, char* pBuf, int);
+ int (*formatTimestampSecFrac)(struct syslogTime *ts, char* pBuf);
+ /* v3, 2009-11-12 */
+ time_t (*GetTime)(time_t *ttSeconds);
+ /* v6, 2011-06-20 */
+ void (*timeval2syslogTime)(struct timeval *tp, struct syslogTime *t);
+ /* v7, 2012-03-29 */
+ int (*formatTimestampUnix)(struct syslogTime *ts, char*pBuf);
+ time_t (*syslogTime2time_t)(struct syslogTime *ts);
+ENDinterface(datetime)
+#define datetimeCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */
+/* interface changes:
+ * 1 - initial version
+ * 2 - not compatible to 1 - bugfix required ParseTIMESTAMP3164 to accept char ** as
+ * last parameter. Did not try to remain compatible as this is not something any
+ * third-party module should call. -- rgerhards, 2008.-09-12
+ * 3 - taken by v5 branch!
+ * 4 - formatTimestamp3164 takes a third int parameter
+ * 5 - merge of versions 3 + 4 (2010-03-09)
+ * 6 - see above
+ */
+
+/* prototypes */
+PROTOTYPEObj(datetime);
+
+#endif /* #ifndef INCLUDED_DATETIME_H */
diff --git a/runtime/debug.c b/runtime/debug.c
new file mode 100644
index 00000000..68474989
--- /dev/null
+++ b/runtime/debug.c
@@ -0,0 +1,1509 @@
+/* debug.c
+ *
+ * This file proides debug and run time error analysis support. Some of the
+ * settings are very performance intense and my be turned off during a release
+ * build.
+ *
+ * File begun on 2008-01-22 by RGerhards
+ *
+ * Some functions are controlled by environment variables:
+ *
+ * RSYSLOG_DEBUGLOG if set, a debug log file is written to that location
+ * RSYSLOG_DEBUG specific debug options
+ *
+ * For details, visit doc/debug.html
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h" /* autotools! */
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <signal.h>
+#include <errno.h>
+#include <pthread.h>
+#include <ctype.h>
+#include <assert.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SYSCALL_H
+# include <sys/syscall.h>
+#endif
+#if _POSIX_TIMERS <= 0
+#include <sys/time.h>
+#endif
+
+#include "rsyslog.h"
+#include "debug.h"
+#include "atomic.h"
+#include "cfsysline.h"
+#include "obj.h"
+
+
+/* static data (some time to be replaced) */
+DEFobjCurrIf(obj)
+int Debug; /* debug flag - read-only after startup */
+int debugging_on = 0; /* read-only, except on sig USR1 */
+static int bLogFuncFlow = 0; /* shall the function entry and exit be logged to the debug log? */
+static int bLogAllocFree = 0; /* shall calls to (m/c)alloc and free be logged to the debug log? */
+static int bPrintFuncDBOnExit = 0; /* shall the function entry and exit be logged to the debug log? */
+static int bPrintMutexAction = 0; /* shall mutex calls be printed to the debug log? */
+static int bPrintTime = 1; /* print a timestamp together with debug message */
+static int bPrintAllDebugOnExit = 0;
+static int bAbortTrace = 1; /* print a trace after SIGABRT or SIGSEGV */
+static int bOutputTidToStderr = 0;/* output TID to stderr on thread creation */
+static char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */
+static int altdbg = -1; /* and the handle for alternate debug output */
+int stddbg = 1; /* the handle for regular debug output, set to stdout if not forking, -1 otherwise */
+
+/* list of files/objects that should be printed */
+typedef struct dbgPrintName_s {
+ uchar *pName;
+ struct dbgPrintName_s *pNext;
+} dbgPrintName_t;
+
+
+/* forward definitions */
+static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID);
+static dbgThrdInfo_t *dbgGetThrdInfo(void);
+static int dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot);
+
+
+/* This lists are single-linked and members are added at the top */
+static dbgPrintName_t *printNameFileRoot = NULL;
+
+
+/* list of all known FuncDBs. We use a special list, because it must only be single-linked. As
+ * functions never disappear, we only need to add elements when we see a new one and never need
+ * to remove anything. For this, we simply add at the top, which saves us a Last pointer. The goal
+ * is to use as few memory as possible.
+ */
+typedef struct dbgFuncDBListEntry_s {
+ dbgFuncDB_t *pFuncDB;
+ struct dbgFuncDBListEntry_s *pNext;
+} dbgFuncDBListEntry_t;
+dbgFuncDBListEntry_t *pFuncDBListRoot;
+
+static pthread_mutex_t mutFuncDBList;
+
+typedef struct dbgMutLog_s {
+ struct dbgMutLog_s *pNext;
+ struct dbgMutLog_s *pPrev;
+ pthread_mutex_t *mut;
+ pthread_t thrd;
+ dbgFuncDB_t *pFuncDB;
+ int lockLn; /* the actual line where the mutex was locked */
+ short mutexOp;
+} dbgMutLog_t;
+static dbgMutLog_t *dbgMutLogListRoot = NULL;
+static dbgMutLog_t *dbgMutLogListLast = NULL;
+static pthread_mutex_t mutMutLog;
+
+
+static dbgThrdInfo_t *dbgCallStackListRoot = NULL;
+static dbgThrdInfo_t *dbgCallStackListLast = NULL;
+static pthread_mutex_t mutCallStack;
+
+static pthread_mutex_t mutdbgprint;
+
+static pthread_key_t keyCallStack;
+
+
+/* we do not have templates, so we use some macros to create linked list handlers
+ * for the several types
+ * DLL means "doubly linked list"
+ * rgerhards, 2008-01-23
+ */
+#define DLL_Del(type, pThis) \
+ if(pThis->pPrev != NULL) \
+ pThis->pPrev->pNext = pThis->pNext; \
+ if(pThis->pNext != NULL) \
+ pThis->pNext->pPrev = pThis->pPrev; \
+ if(pThis == dbg##type##ListRoot) \
+ dbg##type##ListRoot = pThis->pNext; \
+ if(pThis == dbg##type##ListLast) \
+ dbg##type##ListLast = pThis->pPrev; \
+ free(pThis);
+
+#define DLL_Add(type, pThis) \
+ if(dbg##type##ListRoot == NULL) { \
+ dbg##type##ListRoot = pThis; \
+ dbg##type##ListLast = pThis; \
+ } else { \
+ pThis->pPrev = dbg##type##ListLast; \
+ dbg##type##ListLast->pNext = pThis; \
+ dbg##type##ListLast = pThis; \
+ }
+
+/* we need to do our own mutex cancel cleanup handler as it shall not
+ * be subject to the debugging instrumentation (that would probably run us
+ * into an infinite loop
+ */
+static void dbgMutexCancelCleanupHdlr(void *pmut)
+{
+ pthread_mutex_unlock((pthread_mutex_t*) pmut);
+}
+
+
+/* handler to update the last execution location seen
+ * rgerhards, 2008-01-28
+ */
+static inline void
+dbgRecordExecLocation(int iStackPtr, int line)
+{
+ dbgThrdInfo_t *pThrd = dbgGetThrdInfo();
+ pThrd->lastLine[iStackPtr] = line;
+}
+
+
+/* ------------------------- mutex tracking code ------------------------- */
+
+/* ------------------------- FuncDB utility functions ------------------------- */
+
+#define SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ((int) (sizeof(pFuncDB->mutInfo) / sizeof(dbgFuncDBmutInfoEntry_t)))
+
+/* print a FuncDB
+ */
+static void dbgFuncDBPrint(dbgFuncDB_t *pFuncDB)
+{
+ assert(pFuncDB != NULL);
+ assert(pFuncDB->magic == dbgFUNCDB_MAGIC);
+ /* make output suitable for sorting on invocation count */
+ dbgprintf("%10.10ld times called: %s:%d:%s\n", pFuncDB->nTimesCalled, pFuncDB->file, pFuncDB->line, pFuncDB->func);
+}
+
+
+/* print all funcdb entries
+ */
+static void dbgFuncDBPrintAll(void)
+{
+ dbgFuncDBListEntry_t *pFuncDBList;
+ int nFuncs = 0;
+
+ for(pFuncDBList = pFuncDBListRoot ; pFuncDBList != NULL ; pFuncDBList = pFuncDBList->pNext) {
+ dbgFuncDBPrint(pFuncDBList->pFuncDB);
+ nFuncs++;
+ }
+
+ dbgprintf("%d unique functions called\n", nFuncs);
+}
+
+
+/* find a mutex inside the FuncDB mutex table. Returns NULL if not found. Only mutexes from the same thread
+ * are found.
+ */
+static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBGetMutexInfo(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut)
+{
+ int i;
+ int iFound = -1;
+ pthread_t ourThrd = pthread_self();
+
+ for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) {
+ if(pFuncDB->mutInfo[i].pmut == pmut && pFuncDB->mutInfo[i].lockLn != -1 && pFuncDB->mutInfo[i].thrd == ourThrd) {
+ iFound = i;
+ break;
+ }
+ }
+
+ return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i];
+}
+
+
+/* print any mutex that can be found in the FuncDB. Custom header is provided.
+ * "thrd" is the thread that is searched. If it is 0, mutexes for all threads
+ * shall be printed.
+ */
+static inline void
+dbgFuncDBPrintActiveMutexes(dbgFuncDB_t *pFuncDB, char *pszHdrText, pthread_t thrd)
+{
+ int i;
+ char pszThrdName[64];
+
+ for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) {
+ if(pFuncDB->mutInfo[i].lockLn != -1 && (thrd == 0 || thrd == pFuncDB->mutInfo[i].thrd)) {
+ dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pFuncDB->mutInfo[i].thrd, 1);
+ dbgprintf("%s:%d:%s:invocation %ld: %s %p[%d/%s]\n", pFuncDB->file, pFuncDB->line, pFuncDB->func,
+ pFuncDB->mutInfo[i].lInvocation, pszHdrText, (void*)pFuncDB->mutInfo[i].pmut, i,
+ pszThrdName);
+ }
+ }
+}
+
+/* find a free mutex info spot in FuncDB. NULL is returned if table is full.
+ */
+static inline dbgFuncDBmutInfoEntry_t *dbgFuncDBFindFreeMutexInfo(dbgFuncDB_t *pFuncDB)
+{
+ int i;
+ int iFound = -1;
+
+ for(i = 0 ; i < SIZE_FUNCDB_MUTEX_TABLE(pFuncDB) ; ++i) {
+ if(pFuncDB->mutInfo[i].lockLn == -1) {
+ iFound = i;
+ break;
+ }
+ }
+
+ if(iFound == -1) {
+ dbgprintf("%s:%d:%s: INFO: out of space in FuncDB for mutex info (max %d entries) - ignoring\n",
+ pFuncDB->file, pFuncDB->line, pFuncDB->func, SIZE_FUNCDB_MUTEX_TABLE(pFuncDB));
+ }
+
+ return (iFound == -1) ? NULL : &pFuncDB->mutInfo[i];
+}
+
+/* add a mutex lock to the FuncDB. If the size is exhausted, info is discarded.
+ */
+static inline void dbgFuncDBAddMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut, int lockLn)
+{
+ dbgFuncDBmutInfoEntry_t *pMutInfo;
+
+ if((pMutInfo = dbgFuncDBFindFreeMutexInfo(pFuncDB)) != NULL) {
+ pMutInfo->pmut = pmut;
+ pMutInfo->lockLn = lockLn;
+ pMutInfo->lInvocation = pFuncDB->nTimesCalled;
+ pMutInfo->thrd = pthread_self();
+ }
+}
+
+/* remove a locked mutex from the FuncDB (unlock case!).
+ */
+static inline void dbgFuncDBRemoveMutexLock(dbgFuncDB_t *pFuncDB, pthread_mutex_t *pmut)
+{
+ dbgFuncDBmutInfoEntry_t *pMutInfo;
+
+ if((pMutInfo = dbgFuncDBGetMutexInfo(pFuncDB, pmut)) != NULL) {
+ pMutInfo->lockLn = -1;
+ }
+}
+
+
+/* ------------------------- END FuncDB utility functions ------------------------- */
+
+/* output the current thread ID to "relevant" places
+ * (what "relevant" means is determinded by various ways)
+ */
+void
+dbgOutputTID(char* name)
+{
+# if defined(HAVE_SYSCALL) && defined(HAVE_SYS_gettid)
+ if(bOutputTidToStderr)
+ fprintf(stderr, "thread tid %u, name '%s'\n",
+ (unsigned)syscall(SYS_gettid), name);
+ DBGPRINTF("thread created, tid %u, name '%s'\n",
+ (unsigned)syscall(SYS_gettid), name);
+# endif
+}
+
+/* ###########################################################################
+ * IMPORTANT NOTE
+ * Mutex instrumentation reduces the code's concurrency and thus affects its
+ * order of execution. It is vital to test the code also with mutex
+ * instrumentation turned off! Some bugs may not show up while it on...
+ * ###########################################################################
+ */
+
+/* constructor & add new entry to list
+ */
+dbgMutLog_t *dbgMutLogAddEntry(pthread_mutex_t *pmut, short mutexOp, dbgFuncDB_t *pFuncDB, int lockLn)
+{
+ dbgMutLog_t *pLog;
+
+ pLog = calloc(1, sizeof(dbgMutLog_t));
+ assert(pLog != NULL);
+
+ /* fill data members */
+ pLog->mut = pmut;
+ pLog->thrd = pthread_self();
+ pLog->mutexOp = mutexOp;
+ pLog->lockLn = lockLn;
+ pLog->pFuncDB = pFuncDB;
+
+ DLL_Add(MutLog, pLog);
+
+ return pLog;
+}
+
+
+/* destruct log entry
+ */
+void dbgMutLogDelEntry(dbgMutLog_t *pLog)
+{
+ assert(pLog != NULL);
+ DLL_Del(MutLog, pLog);
+}
+
+
+/* print a single mutex log entry */
+static void dbgMutLogPrintOne(dbgMutLog_t *pLog)
+{
+ char *strmutop;
+ char buf[64];
+ char pszThrdName[64];
+
+ assert(pLog != NULL);
+ switch(pLog->mutexOp) {
+ case MUTOP_LOCKWAIT:
+ strmutop = "waited on";
+ break;
+ case MUTOP_LOCK:
+ strmutop = "owned";
+ break;
+ default:
+ snprintf(buf, sizeof(buf)/sizeof(char), "unknown state %d - should not happen!", pLog->mutexOp);
+ strmutop = buf;
+ break;
+ }
+
+ dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pLog->thrd, 1);
+ dbgprintf("mutex 0x%lx is being %s by code at %s:%d, thread %s\n", (unsigned long) pLog->mut,
+ strmutop, pLog->pFuncDB->file,
+ (pLog->mutexOp == MUTOP_LOCK) ? pLog->lockLn : pLog->pFuncDB->line,
+ pszThrdName);
+}
+
+/* print the complete mutex log */
+static void dbgMutLogPrintAll(void)
+{
+ dbgMutLog_t *pLog;
+
+ dbgprintf("Mutex log for all known mutex operations:\n");
+ for(pLog = dbgMutLogListRoot ; pLog != NULL ; pLog = pLog->pNext)
+ dbgMutLogPrintOne(pLog);
+
+}
+
+
+/* find the last log entry for that specific mutex object. Is used to delete
+ * a thread's own requests. Searches occur from the back.
+ * The pFuncDB is optional and may be NULL to indicate no specific funciont is
+ * reqested (aka "it is ignored" ;)). This is important for the unlock case.
+ */
+dbgMutLog_t *dbgMutLogFindSpecific(pthread_mutex_t *pmut, short mutop, dbgFuncDB_t *pFuncDB)
+{
+ dbgMutLog_t *pLog;
+ pthread_t mythrd = pthread_self();
+
+ pLog = dbgMutLogListLast;
+ while(pLog != NULL) {
+ if( pLog->mut == pmut && pLog->thrd == mythrd && pLog->mutexOp == mutop
+ && (pFuncDB == NULL || pLog->pFuncDB == pFuncDB))
+ break;
+ pLog = pLog->pPrev;
+ }
+
+ return pLog;
+}
+
+
+/* find mutex object from the back of the list */
+dbgMutLog_t *dbgMutLogFindFromBack(pthread_mutex_t *pmut, dbgMutLog_t *pLast)
+{
+ dbgMutLog_t *pLog;
+
+ if(pLast == NULL)
+ pLog = dbgMutLogListLast;
+ else
+ pLog = pLast->pPrev; /* if we get the last processed one, we need to go one before it, else its an endless loop */
+
+ while(pLog != NULL) {
+ if(pLog->mut == pmut) {
+ break;
+ }
+ pLog = pLog->pPrev;
+ }
+
+ return pLog;
+}
+
+
+/* find lock aquire for mutex from back of list */
+dbgMutLog_t *dbgMutLogFindHolder(pthread_mutex_t *pmut)
+{
+ dbgMutLog_t *pLog;
+
+ pLog = dbgMutLogFindFromBack(pmut, NULL);
+ while(pLog != NULL) {
+ if(pLog->mutexOp == MUTOP_LOCK)
+ break;
+ pLog = dbgMutLogFindFromBack(pmut, pLog);
+ }
+
+ return pLog;
+}
+
+/* report wait on a mutex and add it to the mutex log */
+static inline void dbgMutexPreLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln)
+{
+ dbgMutLog_t *pHolder;
+ char pszBuf[128];
+ char pszHolderThrdName[64];
+ char *pszHolder;
+
+ pthread_mutex_lock(&mutMutLog);
+ pHolder = dbgMutLogFindHolder(pmut);
+ dbgMutLogAddEntry(pmut, MUTOP_LOCKWAIT, pFuncDB, ln);
+
+ if(pHolder == NULL)
+ pszHolder = "[NONE]";
+ else {
+ dbgGetThrdName(pszHolderThrdName, sizeof(pszHolderThrdName), pHolder->thrd, 1);
+ snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d [%s]", pHolder->pFuncDB->file, pHolder->lockLn, pszHolderThrdName);
+ pszHolder = pszBuf;
+ }
+
+ if(bPrintMutexAction)
+ dbgprintf("%s:%d:%s: mutex %p waiting on lock, held by %s\n", pFuncDB->file, ln, pFuncDB->func, (void*)pmut, pszHolder);
+ pthread_mutex_unlock(&mutMutLog);
+}
+
+
+/* report aquired mutex */
+static inline void dbgMutexLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int lockLn)
+{
+ dbgMutLog_t *pLog;
+
+ pthread_mutex_lock(&mutMutLog);
+
+ /* find and delete "waiting" entry */
+ pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCKWAIT, pFuncDB);
+ assert(pLog != NULL);
+ dbgMutLogDelEntry(pLog);
+
+ /* add "lock" entry */
+ dbgMutLogAddEntry(pmut, MUTOP_LOCK, pFuncDB, lockLn);
+ dbgFuncDBAddMutexLock(pFuncDB, pmut, lockLn);
+ pthread_mutex_unlock(&mutMutLog);
+ if(bPrintMutexAction)
+ dbgprintf("%s:%d:%s: mutex %p aquired\n", pFuncDB->file, lockLn, pFuncDB->func, (void*)pmut);
+}
+
+
+/* report trylock on a mutex and add it to the mutex log */
+static inline void dbgMutexPreTryLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln)
+{
+ dbgMutLog_t *pHolder;
+ char pszBuf[128];
+ char pszHolderThrdName[64];
+ char *pszHolder;
+
+ pthread_mutex_lock(&mutMutLog);
+ pHolder = dbgMutLogFindHolder(pmut);
+ dbgMutLogAddEntry(pmut, MUTOP_TRYLOCK, pFuncDB, ln);
+
+ if(pHolder == NULL)
+ pszHolder = "[NONE]";
+ else {
+ dbgGetThrdName(pszHolderThrdName, sizeof(pszHolderThrdName), pHolder->thrd, 1);
+ snprintf(pszBuf, sizeof(pszBuf)/sizeof(char), "%s:%d [%s]", pHolder->pFuncDB->file, pHolder->lockLn, pszHolderThrdName);
+ pszHolder = pszBuf;
+ }
+
+ if(bPrintMutexAction)
+ dbgprintf("%s:%d:%s: mutex %p trying to get lock, held by %s\n", pFuncDB->file, ln, pFuncDB->func, (void*)pmut, pszHolder);
+ pthread_mutex_unlock(&mutMutLog);
+}
+
+
+/* report attempted mutex lock */
+static inline void dbgMutexTryLockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int lockLn)
+{
+ dbgMutLog_t *pLog;
+
+ pthread_mutex_lock(&mutMutLog);
+
+ /* find and delete "trylock" entry */
+ pLog = dbgMutLogFindSpecific(pmut, MUTOP_TRYLOCK, pFuncDB);
+ assert(pLog != NULL);
+ dbgMutLogDelEntry(pLog);
+
+ /* add "lock" entry */
+ dbgMutLogAddEntry(pmut, MUTOP_LOCK, pFuncDB, lockLn);
+ dbgFuncDBAddMutexLock(pFuncDB, pmut, lockLn);
+ pthread_mutex_unlock(&mutMutLog);
+ if(bPrintMutexAction)
+ dbgprintf("%s:%d:%s: mutex %p aquired\n", pFuncDB->file, lockLn, pFuncDB->func, (void*)pmut);
+}
+
+
+/* if we unlock, we just remove the lock aquired entry from the log list */
+static inline void dbgMutexUnlockLog(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int unlockLn)
+{
+ dbgMutLog_t *pLog;
+
+ pthread_mutex_lock(&mutMutLog);
+ pLog = dbgMutLogFindSpecific(pmut, MUTOP_LOCK, NULL);
+#if 0 /* toggle for testing */
+ assert(pLog != NULL);
+#else
+/* the change below seems not to work - the problem seems to be a real race... I keep this code in just in case
+ * I need to re-use it. It should be removed once we are finished analyzing this problem. -- rgerhards, 2008-09-17
+ */
+if(pLog == NULL) {
+ /* this may happen due to some races. We do not try to avoid
+ * this, as it would complicate the "real" code. This is not justified
+ * just to keep the debug info system up. -- rgerhards, 2008-09-17
+ */
+ pthread_mutex_unlock(&mutMutLog);
+ dbgprintf("%s:%d:%s: mutex %p UNlocked [but we did not yet know this mutex!]\n",
+ pFuncDB->file, unlockLn, pFuncDB->func, (void*)pmut);
+ return; /* if we don't know it yet, we can not clean up... */
+}
+#endif
+#include <sys/syscall.h>
+
+ /* we found the last lock entry. We now need to see from which FuncDB we need to
+ * remove it. This is recorded inside the mutex log entry.
+ */
+ dbgFuncDBRemoveMutexLock(pLog->pFuncDB, pmut);
+
+ /* donw with the log entry, get rid of it... */
+ dbgMutLogDelEntry(pLog);
+
+ pthread_mutex_unlock(&mutMutLog);
+ if(bPrintMutexAction)
+ dbgprintf("%s:%d:%s: mutex %p UNlocked\n", pFuncDB->file, unlockLn, pFuncDB->func, (void*)pmut);
+}
+
+
+/* wrapper for pthread_mutex_lock() */
+int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr)
+{
+ int ret;
+
+ dbgRecordExecLocation(iStackPtr, ln);
+ dbgMutexPreLockLog(pmut, pFuncDB, ln);
+ ret = pthread_mutex_lock(pmut);
+ if(ret == 0) {
+ dbgMutexLockLog(pmut, pFuncDB, ln);
+ } else {
+ dbgprintf("%s:%d:%s: ERROR: pthread_mutex_lock() for mutex %p failed with error %d\n",
+ pFuncDB->file, ln, pFuncDB->func, (void*)pmut, ret);
+ }
+
+ return ret;
+}
+
+
+/* wrapper for pthread_mutex_trylock() */
+int dbgMutexTryLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr)
+{
+ int ret;
+
+ dbgRecordExecLocation(iStackPtr, ln);
+ dbgMutexPreLockLog(pmut, pFuncDB, ln); // TODO : update this
+ ret = pthread_mutex_trylock(pmut);
+ if(ret == 0 || ret == EBUSY) {
+ // TODO : update this
+ dbgMutexLockLog(pmut, pFuncDB, ln);
+ } else {
+ dbgprintf("%s:%d:%s: ERROR: pthread_mutex_trylock() for mutex %p failed with error %d\n",
+ pFuncDB->file, ln, pFuncDB->func, (void*)pmut, ret);
+ }
+
+ return ret;
+}
+
+
+/* wrapper for pthread_mutex_unlock() */
+int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr)
+{
+ int ret;
+ dbgRecordExecLocation(iStackPtr, ln);
+ dbgMutexUnlockLog(pmut, pFuncDB, ln);
+ ret = pthread_mutex_unlock(pmut);
+ return ret;
+}
+
+
+/* wrapper for pthread_cond_wait() */
+int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr)
+{
+ int ret;
+ dbgRecordExecLocation(iStackPtr, ln);
+ dbgMutexUnlockLog(pmut, pFuncDB, ln);
+ if(bPrintMutexAction) {
+ dbgprintf("%s:%d:%s: mutex %p waiting on condition %p\n", pFuncDB->file, pFuncDB->line,
+ pFuncDB->func, (void*)pmut, (void*)cond);
+ }
+ dbgMutexPreLockLog(pmut, pFuncDB, ln);
+ ret = pthread_cond_wait(cond, pmut);
+ return ret;
+}
+
+
+/* wrapper for pthread_cond_timedwait() */
+int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr)
+{
+ int ret;
+ dbgRecordExecLocation(iStackPtr, ln);
+ dbgMutexUnlockLog(pmut, pFuncDB, ln);
+ dbgMutexPreLockLog(pmut, pFuncDB, ln);
+ if(bPrintMutexAction) {
+ dbgprintf("%s:%d:%s: mutex %p waiting on condition %p (with timeout)\n", pFuncDB->file,
+ pFuncDB->line, pFuncDB->func, (void*)pmut, (void*)cond);
+ }
+ ret = pthread_cond_timedwait(cond, pmut, abstime);
+ dbgMutexLockLog(pmut, pFuncDB, ln);
+ return ret;
+}
+
+
+/* ------------------------- end mutex tracking code ------------------------- */
+
+
+/* ------------------------- malloc/free tracking code ------------------------- */
+
+/* wrapper for free() */
+void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr)
+{
+ dbgRecordExecLocation(iStackPtr, ln);
+ if(bLogAllocFree) {
+ dbgprintf("%s:%d:%s: free %p\n", pFuncDB->file, ln, pFuncDB->func, (void*) pMem);
+ }
+ free(pMem);
+}
+
+
+/* ------------------------- end malloc/free tracking code ------------------------- */
+
+/* ------------------------- thread tracking code ------------------------- */
+
+/* get ptr to call stack - if none exists, create a new stack
+ */
+static dbgThrdInfo_t *dbgGetThrdInfo(void)
+{
+ dbgThrdInfo_t *pThrd;
+
+ pthread_mutex_lock(&mutCallStack);
+ if((pThrd = pthread_getspecific(keyCallStack)) == NULL) {
+ /* construct object */
+ pThrd = calloc(1, sizeof(dbgThrdInfo_t));
+ pThrd->thrd = pthread_self();
+ (void) pthread_setspecific(keyCallStack, pThrd);
+ DLL_Add(CallStack, pThrd);
+ }
+ pthread_mutex_unlock(&mutCallStack);
+ return pThrd;
+}
+
+
+
+/* find a specific thread ID. It must be present, else something is wrong
+ */
+static inline dbgThrdInfo_t *dbgFindThrd(pthread_t thrd)
+{
+ dbgThrdInfo_t *pThrd;
+
+ for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) {
+ if(pThrd->thrd == thrd)
+ break;
+ }
+ return pThrd;
+}
+
+
+/* build a string with the thread name. If none is set, the thread ID is
+ * used instead. Caller must provide buffer space. If bIncludeNumID is set
+ * to 1, the numerical ID is always included.
+ * rgerhards 2008-01-23
+ */
+static void dbgGetThrdName(char *pszBuf, size_t lenBuf, pthread_t thrd, int bIncludeNumID)
+{
+ dbgThrdInfo_t *pThrd;
+
+ assert(pszBuf != NULL);
+
+ pThrd = dbgFindThrd(thrd);
+
+ if(pThrd == 0 || pThrd->pszThrdName == NULL) {
+ /* no thread name, use numeric value */
+ snprintf(pszBuf, lenBuf, "%lx", (long) thrd);
+ } else {
+ if(bIncludeNumID) {
+ snprintf(pszBuf, lenBuf, "%s (%lx)", pThrd->pszThrdName, (long) thrd);
+ } else {
+ snprintf(pszBuf, lenBuf, "%s", pThrd->pszThrdName);
+ }
+ }
+
+}
+
+
+/* set a name for the current thread. The caller provided string is duplicated.
+ */
+void dbgSetThrdName(uchar *pszName)
+{
+return;
+
+ dbgThrdInfo_t *pThrd = dbgGetThrdInfo();
+ if(pThrd->pszThrdName != NULL)
+ free(pThrd->pszThrdName);
+ pThrd->pszThrdName = strdup((char*)pszName);
+}
+
+
+/* destructor for a call stack object */
+static void dbgCallStackDestruct(void *arg)
+{
+ dbgThrdInfo_t *pThrd = (dbgThrdInfo_t*) arg;
+
+ dbgprintf("destructor for debug call stack %p called\n", pThrd);
+ if(pThrd->pszThrdName != NULL) {
+ free(pThrd->pszThrdName);
+ }
+
+ pthread_mutex_lock(&mutCallStack);
+ DLL_Del(CallStack, pThrd);
+ pthread_mutex_unlock(&mutCallStack);
+}
+
+
+/* print a thread's call stack
+ */
+static void dbgCallStackPrint(dbgThrdInfo_t *pThrd)
+{
+ int i;
+ char pszThrdName[64];
+
+ pthread_mutex_lock(&mutCallStack);
+ dbgGetThrdName(pszThrdName, sizeof(pszThrdName), pThrd->thrd, 1);
+ dbgprintf("\n");
+ dbgprintf("Recorded Call Order for Thread '%s':\n", pszThrdName);
+ for(i = 0 ; i < pThrd->stackPtr ; i++) {
+ dbgprintf("%d: %s:%d:%s:\n", i, pThrd->callStack[i]->file, pThrd->lastLine[i], pThrd->callStack[i]->func);
+ }
+ dbgprintf("maximum number of nested calls for this thread: %d.\n", pThrd->stackPtrMax);
+ dbgprintf("NOTE: not all calls may have been recorded, code does not currently guarantee that!\n");
+ pthread_mutex_unlock(&mutCallStack);
+}
+
+/* print all threads call stacks
+ */
+void dbgCallStackPrintAll(void)
+{
+ dbgThrdInfo_t *pThrd;
+ /* stack info */
+ for(pThrd = dbgCallStackListRoot ; pThrd != NULL ; pThrd = pThrd->pNext) {
+ dbgCallStackPrint(pThrd);
+ }
+}
+
+
+/* handler for SIGSEGV - MUST terminiate the app, but does so in a somewhat
+ * more meaningful way.
+ * rgerhards, 2008-01-22
+ */
+void
+sigsegvHdlr(int signum)
+{
+ char *signame;
+ struct sigaction sigAct;
+
+ /* first, restore the default abort handler */
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = SIG_DFL;
+ sigaction(SIGABRT, &sigAct, NULL);
+
+ /* then do our actual processing */
+ if(signum == SIGSEGV) {
+ signame = " (SIGSEGV)";
+ } else if(signum == SIGABRT) {
+ signame = " (SIGABRT)";
+ } else {
+ signame = "";
+ }
+
+ dbgprintf("\n\n\n\nSignal %d%s occured, execution must be terminated.\n\n\n\n", signum, signame);
+
+ if(bAbortTrace) {
+ dbgPrintAllDebugInfo();
+ dbgprintf("If the call trace is empty, you may want to ./configure --enable-rtinst\n");
+ dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n");
+ }
+
+ dbgprintf("\n\nTo submit bug reports, visit http://www.rsyslog.com/bugs\n\n");
+
+ /* and finally abort... */
+ /* TODO: think about restarting rsyslog in this case: may be a good idea,
+ * but may also be a very bad one (restart loops!)
+ */
+ abort();
+}
+
+/* actually write the debug message. This is a separate fuction because the cleanup_push/_pop
+ * interface otherwise is unsafe to use (generates compiler warnings at least).
+ * 2009-05-20 rgerhards
+ */
+static inline void
+do_dbgprint(uchar *pszObjName, char *pszMsg, size_t lenMsg)
+{
+ static pthread_t ptLastThrdID = 0;
+ static int bWasNL = 0;
+ char pszThrdName[64]; /* 64 is to be on the safe side, anything over 20 is bad... */
+ char pszWriteBuf[32*1024];
+ size_t lenCopy;
+ size_t offsWriteBuf = 0;
+ size_t lenWriteBuf;
+ struct timespec t;
+# if _POSIX_TIMERS <= 0
+ struct timeval tv;
+# endif
+
+#if 1
+ /* The bWasNL handler does not really work. It works if no thread
+ * switching occurs during non-NL messages. Else, things are messed
+ * up. Anyhow, it works well enough to provide useful help during
+ * getting this up and running. It is questionable if the extra effort
+ * is worth fixing it, giving the limited appliability. -- rgerhards, 2005-10-25
+ * I have decided that it is not worth fixing it - especially as it works
+ * pretty well. -- rgerhards, 2007-06-15
+ */
+ if(ptLastThrdID != pthread_self()) {
+ if(!bWasNL) {
+ pszWriteBuf[0] = '\n';
+ offsWriteBuf = 1;
+ bWasNL = 1;
+ }
+ ptLastThrdID = pthread_self();
+ }
+
+ /* do not cache the thread name, as the caller might have changed it
+ * TODO: optimized, invalidate cache when new name is set
+ */
+ dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID, 0);
+
+ if(bWasNL) {
+ if(bPrintTime) {
+# if _POSIX_TIMERS > 0
+ /* this is the "regular" code */
+ clock_gettime(CLOCK_REALTIME, &t);
+# else
+ gettimeofday(&tv, NULL);
+ t.tv_sec = tv.tv_sec;
+ t.tv_nsec = tv.tv_usec * 1000;
+# endif
+ lenWriteBuf = snprintf(pszWriteBuf+offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf,
+ "%4.4ld.%9.9ld:", (long) (t.tv_sec % 10000), t.tv_nsec);
+ offsWriteBuf += lenWriteBuf;
+ }
+
+ lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, "%s: ", pszThrdName);
+ offsWriteBuf += lenWriteBuf;
+ /* print object name header if we have an object */
+ if(pszObjName != NULL) {
+ lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf, "%s: ", pszObjName);
+ offsWriteBuf += lenWriteBuf;
+ }
+ }
+#endif
+ if(lenMsg > sizeof(pszWriteBuf) - offsWriteBuf)
+ lenCopy = sizeof(pszWriteBuf) - offsWriteBuf;
+ else
+ lenCopy = lenMsg;
+ memcpy(pszWriteBuf + offsWriteBuf, pszMsg, lenCopy);
+ offsWriteBuf += lenCopy;
+ /* the write is included in an "if" just to silence compiler
+ * warnings. Here, we really don't care if the write fails, we
+ * have no good response to that in any case... -- rgerhards, 2012-11-28
+ */
+ if(stddbg != -1) if(write(stddbg, pszWriteBuf, offsWriteBuf)){};
+ if(altdbg != -1) if(write(altdbg, pszWriteBuf, offsWriteBuf)){};
+
+ bWasNL = (pszMsg[lenMsg - 1] == '\n') ? 1 : 0;
+}
+
+#pragma GCC diagnostic ignored "-Wempty-body"
+/* write the debug message. This is a helper to dbgprintf and dbgoprint which
+ * contains common code. added 2008-09-26 rgerhards
+ */
+static void
+dbgprint(obj_t *pObj, char *pszMsg, size_t lenMsg)
+{
+ uchar *pszObjName = NULL;
+
+ /* we must get the object name before we lock the mutex, because the object
+ * potentially calls back into us. If we locked the mutex, we would deadlock
+ * ourselfs. On the other hand, the GetName call needs not to be protected, as
+ * this thread has a valid reference. If such an object is deleted by another
+ * thread, we are in much more trouble than just for dbgprint(). -- rgerhards, 2008-09-26
+ */
+ if(pObj != NULL) {
+ pszObjName = obj.GetName(pObj);
+ }
+
+ pthread_mutex_lock(&mutdbgprint);
+ pthread_cleanup_push(dbgMutexCancelCleanupHdlr, &mutdbgprint);
+
+ do_dbgprint(pszObjName, pszMsg, lenMsg);
+
+ pthread_cleanup_pop(1);
+}
+#pragma GCC diagnostic warning "-Wempty-body"
+
+/* print some debug output when an object is given
+ * This is mostly a copy of dbgprintf, but I do not know how to combine it
+ * into a single function as we have variable arguments and I don't know how to call
+ * from one vararg function into another. I don't dig in this, it is OK for the
+ * time being. -- rgerhards, 2008-01-29
+ */
+void
+dbgoprint(obj_t *pObj, char *fmt, ...)
+{
+ va_list ap;
+ char pszWriteBuf[32*1024];
+ size_t lenWriteBuf;
+
+ if(!(Debug && debugging_on))
+ return;
+
+ /* a quick and very dirty hack to enable us to display just from those objects
+ * that we are interested in. So far, this must be changed at compile time (and
+ * chances are great it is commented out while you read it. Later, this shall
+ * be selectable via the environment. -- rgerhards, 2008-02-20
+ */
+#if 0
+ if(objGetObjID(pObj) != OBJexpr)
+ return;
+#endif
+
+ va_start(ap, fmt);
+ lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap);
+ va_end(ap);
+ if(lenWriteBuf >= sizeof(pszWriteBuf)) {
+ /* prevent buffer overrruns and garbagge display */
+ pszWriteBuf[sizeof(pszWriteBuf) - 5] = '.';
+ pszWriteBuf[sizeof(pszWriteBuf) - 4] = '.';
+ pszWriteBuf[sizeof(pszWriteBuf) - 3] = '.';
+ pszWriteBuf[sizeof(pszWriteBuf) - 2] = '\n';
+ pszWriteBuf[sizeof(pszWriteBuf) - 1] = '\0';
+ lenWriteBuf = sizeof(pszWriteBuf);
+ }
+ dbgprint(pObj, pszWriteBuf, lenWriteBuf);
+}
+
+
+/* print some debug output when no object is given
+ * WARNING: duplicate code, see dbgoprin above!
+ */
+void
+dbgprintf(char *fmt, ...)
+{
+ va_list ap;
+ char pszWriteBuf[32*1024];
+ size_t lenWriteBuf;
+
+ if(!(Debug && debugging_on))
+ return;
+
+ va_start(ap, fmt);
+ lenWriteBuf = vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap);
+ va_end(ap);
+ if(lenWriteBuf >= sizeof(pszWriteBuf)) {
+ /* prevent buffer overrruns and garbagge display */
+ pszWriteBuf[sizeof(pszWriteBuf) - 5] = '.';
+ pszWriteBuf[sizeof(pszWriteBuf) - 4] = '.';
+ pszWriteBuf[sizeof(pszWriteBuf) - 3] = '.';
+ pszWriteBuf[sizeof(pszWriteBuf) - 2] = '\n';
+ pszWriteBuf[sizeof(pszWriteBuf) - 1] = '\0';
+ lenWriteBuf = sizeof(pszWriteBuf);
+ }
+ dbgprint(NULL, pszWriteBuf, lenWriteBuf);
+}
+
+void tester(void)
+{
+BEGINfunc
+ENDfunc
+}
+
+/* handler called when a function is entered. This function creates a new
+ * funcDB on the heap if the passed-in pointer is NULL.
+ */
+int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line)
+{
+ int iStackPtr = 0; /* TODO: find some better default, this one hurts the least, but it is not clean */
+ dbgThrdInfo_t *pThrd;
+ dbgFuncDBListEntry_t *pFuncDBListEntry;
+ unsigned int i;
+ dbgFuncDB_t *pFuncDB;
+
+ assert(ppFuncDB != NULL);
+ assert(file != NULL);
+ assert(func != NULL);
+ pFuncDB = *ppFuncDB;
+ assert((pFuncDB == NULL) || (pFuncDB->magic == dbgFUNCDB_MAGIC));
+
+ pThrd = dbgGetThrdInfo(); /* we must do this AFTER the mutexes are initialized! */
+
+ if(pFuncDB == NULL) {
+ /* we do not yet have a funcDB and need to create a new one. We also add it
+ * to the linked list of funcDBs. Please note that when a module is unloaded and
+ * then reloaded again, we currently do not try to find its previous funcDB but
+ * instead create a duplicate. While finding the past one is straightforward, it
+ * opens up the question what to do with e.g. mutex data left in it. We do not
+ * yet see any need to handle these questions, so duplicaton seems to be the right
+ * thing to do. -- rgerhards, 2008-03-10
+ */
+ /* dbgprintf("%s:%d:%s: called first time, initializing FuncDB\n", pFuncDB->file, pFuncDB->line, pFuncDB->func); */
+ /* get a new funcDB and add it to the list (all of this is protected by the mutex) */
+ pthread_mutex_lock(&mutFuncDBList);
+ if((pFuncDBListEntry = calloc(1, sizeof(dbgFuncDBListEntry_t))) == NULL) {
+ dbgprintf("Error %d allocating memory for FuncDB List entry, not adding\n", errno);
+ pthread_mutex_unlock(&mutFuncDBList);
+ goto exit_it;
+ } else {
+ if((pFuncDB = calloc(1, sizeof(dbgFuncDB_t))) == NULL) {
+ dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno);
+ free(pFuncDBListEntry);
+ pthread_mutex_unlock(&mutFuncDBList);
+ goto exit_it;
+ } else {
+ pFuncDBListEntry->pFuncDB = pFuncDB;
+ pFuncDBListEntry->pNext = pFuncDBListRoot;
+ pFuncDBListRoot = pFuncDBListEntry;
+ }
+ }
+ /* now intialize the funcDB
+ * note that we duplicate the strings, because the address provided may go away
+ * if a loadable module is unloaded!
+ */
+ pFuncDB->magic = dbgFUNCDB_MAGIC;
+ pFuncDB->file = strdup(file);
+ pFuncDB->func = strdup(func);
+ pFuncDB->line = line;
+ pFuncDB->nTimesCalled = 0;
+ for(i = 0 ; i < sizeof(pFuncDB->mutInfo)/sizeof(dbgFuncDBmutInfoEntry_t) ; ++i) {
+ pFuncDB->mutInfo[i].lockLn = -1; /* set to not Locked */
+ }
+
+ /* a round of safety checks... */
+ if(pFuncDB->file == NULL || pFuncDB->func == NULL) {
+ dbgprintf("Error %d allocating memory for FuncDB, not adding\n", errno);
+ /* do a little bit of cleanup */
+ if(pFuncDB->file != NULL)
+ free(pFuncDB->file);
+ if(pFuncDB->func != NULL)
+ free(pFuncDB->func);
+ free(pFuncDB);
+ free(pFuncDBListEntry);
+ pthread_mutex_unlock(&mutFuncDBList);
+ goto exit_it;
+ }
+
+ /* done mutex-protected operations */
+ pthread_mutex_unlock(&mutFuncDBList);
+
+ *ppFuncDB = pFuncDB; /* all went well, so we can update the caller */
+ }
+
+ /* when we reach this point, we have a fully-initialized FuncDB! */
+ PREFER_ATOMIC_INC(pFuncDB->nTimesCalled);
+ if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot))
+ if(strcmp(pFuncDB->file, "stringbuf.c")) { /* TODO: make configurable */
+ dbgprintf("%s:%d: %s: enter\n", pFuncDB->file, pFuncDB->line, pFuncDB->func);
+ }
+ if(pThrd->stackPtr >= (int) (sizeof(pThrd->callStack) / sizeof(dbgFuncDB_t*))) {
+ dbgprintf("%s:%d: %s: debug module: call stack for this thread full, suspending call tracking\n",
+ pFuncDB->file, pFuncDB->line, pFuncDB->func);
+ iStackPtr = pThrd->stackPtr;
+ } else {
+ iStackPtr = pThrd->stackPtr++;
+ if(pThrd->stackPtr > pThrd->stackPtrMax)
+ pThrd->stackPtrMax = pThrd->stackPtr;
+ pThrd->callStack[iStackPtr] = pFuncDB;
+ pThrd->lastLine[iStackPtr] = line;
+ }
+
+exit_it:
+ return iStackPtr;
+}
+
+
+/* handler called when a function is exited
+ */
+void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet)
+{
+ dbgThrdInfo_t *pThrd = dbgGetThrdInfo();
+
+ assert(iStackPtrRestore >= 0);
+ assert(pFuncDB != NULL);
+ assert(pFuncDB->magic == dbgFUNCDB_MAGIC);
+
+ dbgFuncDBPrintActiveMutexes(pFuncDB, "WARNING: mutex still owned by us as we exit function, mutex: ", pthread_self());
+ if(bLogFuncFlow && dbgPrintNameIsInList((const uchar*)pFuncDB->file, printNameFileRoot)) {
+ if(strcmp(pFuncDB->file, "stringbuf.c")) { /* TODO: make configurable */
+ if(iRet == RS_RET_NO_IRET)
+ dbgprintf("%s:%d: %s: exit: (no iRet)\n", pFuncDB->file, pFuncDB->line, pFuncDB->func);
+ else
+ dbgprintf("%s:%d: %s: exit: %d\n", pFuncDB->file, pFuncDB->line, pFuncDB->func, iRet);
+ }
+ }
+ pThrd->stackPtr = iStackPtrRestore;
+ if(pThrd->stackPtr < 0) {
+ dbgprintf("Stack pointer for thread %lx below 0 - resetting (some RETiRet still wrong!)\n", (long) pthread_self());
+ pThrd->stackPtr = 0;
+ }
+}
+
+
+/* externally-callable handler to record the last exec location. We use a different function
+ * so that the internal one can be inline.
+ */
+void
+dbgSetExecLocation(int iStackPtr, int line)
+{
+ dbgRecordExecLocation(iStackPtr, line);
+}
+
+
+void dbgPrintAllDebugInfo(void)
+{
+ dbgCallStackPrintAll();
+ dbgMutLogPrintAll();
+ if(bPrintFuncDBOnExit)
+ dbgFuncDBPrintAll();
+}
+
+
+/* Handler for SIGUSR2. Dumps all available debug output
+ */
+static void sigusr2Hdlr(int __attribute__((unused)) signum)
+{
+ dbgprintf("SIGUSR2 received, dumping debug information\n");
+ dbgPrintAllDebugInfo();
+}
+
+/* support system to set debug options at runtime */
+
+
+/* parse a param/value pair from the current location of the
+ * option string. Returns 1 if an option was found, 0
+ * otherwise. 0 means there are NO MORE options to be
+ * processed. -- rgerhards, 2008-02-28
+ */
+static int
+dbgGetRTOptNamVal(uchar **ppszOpt, uchar **ppOptName, uchar **ppOptVal)
+{
+ int bRet = 0;
+ uchar *p;
+ size_t i;
+ static uchar optname[128]; /* not thread- or reentrant-safe, but that */
+ static uchar optval[1024]; /* doesn't matter (called only once at startup) */
+
+ assert(ppszOpt != NULL);
+ assert(*ppszOpt != NULL);
+
+ /* make sure we have some initial values */
+ optname[0] = '\0';
+ optval[0] = '\0';
+
+ p = *ppszOpt;
+ /* skip whitespace */
+ while(*p && isspace(*p))
+ ++p;
+
+ /* name - up until '=' or whitespace */
+ i = 0;
+ while(i < (sizeof(optname)/sizeof(uchar) - 1) && *p && *p != '=' && !isspace(*p)) {
+ optname[i++] = *p++;
+ }
+
+ if(i > 0) {
+ bRet = 1;
+ optname[i] = '\0';
+ if(*p == '=') {
+ /* we have a value, get it */
+ ++p;
+ i = 0;
+ while(i < (sizeof(optval)/sizeof(uchar) - 1) && *p && !isspace(*p)) {
+ optval[i++] = *p++;
+ }
+ optval[i] = '\0';
+ }
+ }
+
+ /* done */
+ *ppszOpt = p;
+ *ppOptName = optname;
+ *ppOptVal = optval;
+ return bRet;
+}
+
+
+/* create new PrintName list entry and add it to list (they will never
+ * be removed. -- rgerhards, 2008-02-28
+ */
+static void
+dbgPrintNameAdd(uchar *pName, dbgPrintName_t **ppRoot)
+{
+ dbgPrintName_t *pEntry;
+
+ if((pEntry = calloc(1, sizeof(dbgPrintName_t))) == NULL) {
+ fprintf(stderr, "ERROR: out of memory during debug setup\n");
+ exit(1);
+ }
+
+ if((pEntry->pName = (uchar*) strdup((char*) pName)) == NULL) {
+ fprintf(stderr, "ERROR: out of memory during debug setup\n");
+ exit(1);
+ }
+
+ if(*ppRoot != NULL) {
+ pEntry->pNext = *ppRoot; /* we enqueue at the front */
+ }
+ *ppRoot = pEntry;
+}
+
+
+/* check if name is in a printName list - returns 1 if so, 0 otherwise.
+ * There is one special handling: if the root pointer is NULL, the function
+ * always returns 1. This is because when no name is set, output shall be
+ * unrestricted.
+ * rgerhards, 2008-02-28
+ */
+static int
+dbgPrintNameIsInList(const uchar *pName, dbgPrintName_t *pRoot)
+{
+ int bFound = 0;
+ dbgPrintName_t *pEntry = pRoot;
+
+ if(pRoot == NULL)
+ bFound = 1;
+
+ while(pEntry != NULL && !bFound) {
+ if(!strcasecmp((char*)pEntry->pName, (char*)pName)) {
+ bFound = 1;
+ } else {
+ pEntry = pEntry->pNext;
+ }
+ }
+
+ return bFound;
+}
+
+
+/* this is a special version of malloc that fills the alloced memory with
+ * HIGHVALUE, as this helps to identify bugs. -- rgerhards, 2009-10-22
+ */
+void *
+dbgmalloc(size_t size)
+{
+ void *pRet;
+ pRet = malloc(size);
+ if(pRet != NULL)
+ memset(pRet, 0xff, size);
+ return pRet;
+}
+
+
+/* report fd used for debug log. This is needed in case of
+ * auto-backgrounding, where the debug log shall not be closed.
+ */
+int
+dbgGetDbglogFd(void)
+{
+ return altdbg;
+}
+
+/* read in the runtime options
+ * rgerhards, 2008-02-28
+ */
+static void
+dbgGetRuntimeOptions(void)
+{
+ uchar *pszOpts;
+ uchar *optval;
+ uchar *optname;
+
+ /* set some defaults */
+ if((pszOpts = (uchar*) getenv("RSYSLOG_DEBUG")) != NULL) {
+ /* we have options set, so let's process them */
+ while(dbgGetRTOptNamVal(&pszOpts, &optname, &optval)) {
+ if(!strcasecmp((char*)optname, "help")) {
+ fprintf(stderr,
+ "rsyslogd " VERSION " runtime debug support - help requested, rsyslog terminates\n\n"
+ "environment variables:\n"
+ "addional logfile: export RSYSLOG_DEBUGFILE=\"/path/to/file\"\n"
+ "to set: export RSYSLOG_DEBUG=\"cmd cmd cmd\"\n\n"
+ "Commands are (all case-insensitive):\n"
+ "help (this list, terminates rsyslogd\n"
+ "LogFuncFlow\n"
+ "LogAllocFree (very partly implemented)\n"
+ "PrintFuncDB\n"
+ "PrintMutexAction\n"
+ "PrintAllDebugInfoOnExit (not yet implemented)\n"
+ "NoLogTimestamp\n"
+ "Nostdoout\n"
+ "OutputTidToStderr\n"
+ "filetrace=file (may be provided multiple times)\n"
+ "DebugOnDemand - enables debugging on USR1, but does not turn on output\n"
+ "\nSee debug.html in your doc set or http://www.rsyslog.com for details\n");
+ exit(1);
+ } else if(!strcasecmp((char*)optname, "debug")) {
+ /* this is earlier in the process than the -d option, as such it
+ * allows us to spit out debug messages from the very beginning.
+ */
+ Debug = DEBUG_FULL;
+ debugging_on = 1;
+ } else if(!strcasecmp((char*)optname, "debugondemand")) {
+ /* Enables debugging, but turns off debug output */
+ Debug = DEBUG_ONDEMAND;
+ debugging_on = 1;
+ dbgprintf("Note: debug on demand turned on via configuraton file, "
+ "use USR1 signal to activate.\n");
+ debugging_on = 0;
+ } else if(!strcasecmp((char*)optname, "logfuncflow")) {
+ bLogFuncFlow = 1;
+ } else if(!strcasecmp((char*)optname, "logallocfree")) {
+ bLogAllocFree = 1;
+ } else if(!strcasecmp((char*)optname, "printfuncdb")) {
+ bPrintFuncDBOnExit = 1;
+ } else if(!strcasecmp((char*)optname, "printmutexaction")) {
+ bPrintMutexAction = 1;
+ } else if(!strcasecmp((char*)optname, "printalldebuginfoonexit")) {
+ bPrintAllDebugOnExit = 1;
+ } else if(!strcasecmp((char*)optname, "nologtimestamp")) {
+ bPrintTime = 0;
+ } else if(!strcasecmp((char*)optname, "nostdout")) {
+ stddbg = -1;
+ } else if(!strcasecmp((char*)optname, "noaborttrace")) {
+ bAbortTrace = 0;
+ } else if(!strcasecmp((char*)optname, "outputtidtostderr")) {
+ bOutputTidToStderr = 1;
+ } else if(!strcasecmp((char*)optname, "filetrace")) {
+ if(*optval == '\0') {
+ fprintf(stderr, "rsyslogd " VERSION " error: logfile debug option requires filename, "
+ "e.g. \"logfile=debug.c\"\n");
+ exit(1);
+ } else {
+ /* create new entry and add it to list */
+ dbgPrintNameAdd(optval, &printNameFileRoot);
+ }
+ } else {
+ fprintf(stderr, "rsyslogd " VERSION " error: invalid debug option '%s', value '%s' - ignored\n",
+ optval, optname);
+ }
+ }
+ }
+}
+
+
+void
+dbgSetDebugLevel(int level)
+{
+ Debug = level;
+ debugging_on = (level == DEBUG_FULL) ? 1 : 0;
+}
+
+void
+dbgSetDebugFile(uchar *fn)
+{
+ if(altdbg != -1) {
+ dbgprintf("switching to debug file %s\n", fn);
+ close(altdbg);
+ }
+ if((altdbg = open((char*)fn, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, S_IRUSR|S_IWUSR)) == -1) {
+ fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno));
+ }
+}
+
+/* end support system to set debug options at runtime */
+
+rsRetVal dbgClassInit(void)
+{
+ pthread_mutexattr_t mutAttr;
+ rsRetVal iRet; /* do not use DEFiRet, as this makes calls into the debug system! */
+
+ struct sigaction sigAct;
+ sigset_t sigSet;
+
+ (void) pthread_key_create(&keyCallStack, dbgCallStackDestruct); /* MUST be the first action done! */
+
+ /* the mutexes must be recursive, because it may be called from within
+ * signal handlers, which can lead to a hang if the signal interrupted dbgprintf
+ * (yes, we have really seen that situation in practice!). -- rgerhards, 2013-05-17
+ */
+ pthread_mutexattr_init(&mutAttr);
+ pthread_mutexattr_settype(&mutAttr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mutFuncDBList, &mutAttr);
+ pthread_mutex_init(&mutMutLog, &mutAttr);
+ pthread_mutex_init(&mutCallStack, &mutAttr);
+ pthread_mutex_init(&mutdbgprint, &mutAttr);
+
+ /* while we try not to use any of the real rsyslog code (to avoid infinite loops), we
+ * need to have the ability to query object names. Thus, we need to obtain a pointer to
+ * the object interface. -- rgerhards, 2008-02-29
+ */
+ CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */
+
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = sigusr2Hdlr;
+ sigaction(SIGUSR2, &sigAct, NULL);
+
+ sigemptyset(&sigSet);
+ sigaddset(&sigSet, SIGUSR2);
+ pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL);
+
+ dbgGetRuntimeOptions(); /* init debug system from environment */
+ pszAltDbgFileName = getenv("RSYSLOG_DEBUGLOG");
+
+ if(pszAltDbgFileName != NULL) {
+ /* we have a secondary file, so let's open it) */
+ if((altdbg = open(pszAltDbgFileName, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, S_IRUSR|S_IWUSR)) == -1) {
+ fprintf(stderr, "alternate debug file could not be opened, ignoring. Error: %s\n", strerror(errno));
+ }
+ }
+
+ dbgSetThrdName((uchar*)"main thread");
+
+finalize_it:
+ return(iRet);
+}
+
+
+rsRetVal dbgClassExit(void)
+{
+ dbgFuncDBListEntry_t *pFuncDBListEtry, *pToDel;
+ pthread_key_delete(keyCallStack);
+
+ if(bPrintAllDebugOnExit)
+ dbgPrintAllDebugInfo();
+
+ if(altdbg != -1)
+ close(altdbg);
+
+ /* now free all of our memory to make the memory debugger happy... */
+ pFuncDBListEtry = pFuncDBListRoot;
+ while(pFuncDBListEtry != NULL) {
+ pToDel = pFuncDBListEtry;
+ pFuncDBListEtry = pFuncDBListEtry->pNext;
+ free(pToDel->pFuncDB->file);
+ free(pToDel->pFuncDB->func);
+ free(pToDel->pFuncDB);
+ free(pToDel);
+ }
+
+ return RS_RET_OK;
+}
+/* vi:set ai:
+ */
diff --git a/runtime/debug.h b/runtime/debug.h
new file mode 100644
index 00000000..f3226098
--- /dev/null
+++ b/runtime/debug.h
@@ -0,0 +1,172 @@
+/* debug.h
+ *
+ * Definitions for the debug and run-time analysis support module.
+ * Contains a lot of macros.
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DEBUG_H_INCLUDED
+#define DEBUG_H_INCLUDED
+
+#include <pthread.h>
+#include "obj-types.h"
+
+/* some settings for various debug modes */
+#define DEBUG_OFF 0
+#define DEBUG_ONDEMAND 1
+#define DEBUG_FULL 2
+
+/* external static data elements (some time to be replaced) */
+extern int Debug; /* debug flag - read-only after startup */
+extern int debugging_on; /* read-only, except on sig USR1 */
+extern int stddbg; /* the handle for regular debug output, set to stdout if not forking, -1 otherwise */
+
+/* data types */
+
+/* the function database. It is used as a static var inside each function. That provides
+ * us the fast access to it that we need to make the instrumentation work. It's address
+ * also serves as a unique function identifier and can be used inside other structures
+ * to refer to the function (e.g. for pretty-printing names).
+ * rgerhards, 2008-01-24
+ */
+typedef struct dbgFuncDBmutInfoEntry_s {
+ pthread_mutex_t *pmut;
+ int lockLn; /* line where it was locked (inside our func): -1 means mutex is not locked */
+ pthread_t thrd; /* thrd where the mutex was locked */
+ unsigned long lInvocation; /* invocation (unique during program run!) of this function that locked the mutex */
+} dbgFuncDBmutInfoEntry_t;
+typedef struct dbgFuncDB_s {
+ unsigned magic;
+ unsigned long nTimesCalled;
+ char *func;
+ char *file;
+ int line;
+ dbgFuncDBmutInfoEntry_t mutInfo[5];
+ /* remember to update the initializer if you add anything or change the order! */
+} dbgFuncDB_t;
+#define dbgFUNCDB_MAGIC 0xA1B2C3D4
+#define dbgFuncDB_t_INITIALIZER \
+ { \
+ .magic = dbgFUNCDB_MAGIC,\
+ .nTimesCalled = 0,\
+ .func = __func__, \
+ .file = __FILE__, \
+ .line = __LINE__ \
+ }
+
+/* the structure below was originally just the thread's call stack, but it has
+ * a bit evolved over time. So we have now ended up with the fact that it
+ * all debug info we know about the thread.
+ */
+typedef struct dbgCallStack_s {
+ pthread_t thrd;
+ dbgFuncDB_t *callStack[500];
+ int lastLine[500]; /* last line where code execution was seen */
+ int stackPtr;
+ int stackPtrMax;
+ char *pszThrdName;
+ struct dbgCallStack_s *pNext;
+ struct dbgCallStack_s *pPrev;
+} dbgThrdInfo_t;
+
+
+/* prototypes */
+rsRetVal dbgClassInit(void);
+rsRetVal dbgClassExit(void);
+void dbgSetDebugFile(uchar *fn);
+void dbgSetDebugLevel(int level);
+void sigsegvHdlr(int signum);
+void dbgoprint(obj_t *pObj, char *fmt, ...) __attribute__((format(printf, 2, 3)));
+void dbgprintf(char *fmt, ...) __attribute__((format(printf, 1, 2)));
+int dbgMutexLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr);
+int dbgMutexTryLock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr);
+int dbgMutexUnlock(pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr);
+int dbgCondWait(pthread_cond_t *cond, pthread_mutex_t *pmut, dbgFuncDB_t *pFuncD, int ln, int iStackPtr);
+int dbgCondTimedWait(pthread_cond_t *cond, pthread_mutex_t *pmut, const struct timespec *abstime, dbgFuncDB_t *pFuncD, int ln, int iStackPtr);
+void dbgFree(void *pMem, dbgFuncDB_t *pFuncDB, int ln, int iStackPtr);
+int dbgEntrFunc(dbgFuncDB_t **ppFuncDB, const char *file, const char *func, int line);
+void dbgExitFunc(dbgFuncDB_t *pFuncDB, int iStackPtrRestore, int iRet);
+void dbgSetExecLocation(int iStackPtr, int line);
+void dbgSetThrdName(uchar *pszName);
+void dbgPrintAllDebugInfo(void);
+void *dbgmalloc(size_t size);
+void dbgOutputTID(char* name);
+int dbgGetDbglogFd(void);
+
+/* macros */
+#ifdef DEBUGLESS
+# define DBGPRINTF(...) {}
+# define DBGOPRINT(...) {}
+#else
+# define DBGPRINTF(...) if(Debug) { dbgprintf(__VA_ARGS__); }
+# define DBGOPRINT(...) if(Debug) { dbgoprint(__VA_ARGS__); }
+#endif
+#ifdef RTINST
+# define BEGINfunc static dbgFuncDB_t *pdbgFuncDB; int dbgCALLStaCK_POP_POINT = dbgEntrFunc(&pdbgFuncDB, __FILE__, __func__, __LINE__);
+# define ENDfunc dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, RS_RET_NO_IRET);
+# define ENDfuncIRet dbgExitFunc(pdbgFuncDB, dbgCALLStaCK_POP_POINT, iRet);
+# define ASSERT(x) assert(x)
+#else
+# define BEGINfunc
+# define ENDfunc
+# define ENDfuncIRet
+# define ASSERT(x)
+#endif
+#ifdef RTINST
+# define RUNLOG dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__); dbgprintf("%s:%d: %s: log point\n", __FILE__, __LINE__, __func__)
+# define RUNLOG_VAR(fmt, x) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\
+ dbgprintf("%s:%d: %s: var '%s'[%s]: " fmt "\n", __FILE__, __LINE__, __func__, #x, fmt, x)
+# define RUNLOG_STR(str) dbgSetExecLocation(dbgCALLStaCK_POP_POINT, __LINE__);\
+ dbgprintf("%s:%d: %s: %s\n", __FILE__, __LINE__, __func__, str)
+#else
+# define RUNLOG
+# define RUNLOG_VAR(fmt, x)
+# define RUNLOG_STR(str)
+#endif
+
+#ifdef MEMCHECK
+# define MALLOC(x) dbgmalloc(x)
+#else
+# define MALLOC(x) malloc(x)
+#endif
+
+/* mutex operations */
+#define MUTOP_LOCKWAIT 1
+#define MUTOP_LOCK 2
+#define MUTOP_UNLOCK 3
+#define MUTOP_TRYLOCK 4
+
+
+/* debug aides */
+#ifdef RTINST
+#define d_pthread_mutex_lock(x) dbgMutexLock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT )
+#define d_pthread_mutex_trylock(x) dbgMutexTryLock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT )
+#define d_pthread_mutex_unlock(x) dbgMutexUnlock(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT )
+#define d_pthread_cond_wait(cond, mut) dbgCondWait(cond, mut, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT )
+#define d_pthread_cond_timedwait(cond, mut, to) dbgCondTimedWait(cond, mut, to, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT )
+#define d_free(x) dbgFree(x, pdbgFuncDB, __LINE__, dbgCALLStaCK_POP_POINT )
+#else
+#define d_pthread_mutex_lock(x) pthread_mutex_lock(x)
+#define d_pthread_mutex_trylock(x) pthread_mutex_trylock(x)
+#define d_pthread_mutex_unlock(x) pthread_mutex_unlock(x)
+#define d_pthread_cond_wait(cond, mut) pthread_cond_wait(cond, mut)
+#define d_pthread_cond_timedwait(cond, mut, to) pthread_cond_timedwait(cond, mut, to)
+#define d_free(x) free(x)
+#endif
+#endif /* #ifndef DEBUG_H_INCLUDED */
diff --git a/runtime/dnscache.c b/runtime/dnscache.c
new file mode 100644
index 00000000..2096aa36
--- /dev/null
+++ b/runtime/dnscache.c
@@ -0,0 +1,465 @@
+/* dnscache.c
+ * Implementation of a real DNS cache
+ *
+ * File begun on 2011-06-06 by RGerhards
+ * The initial implementation is far from being optimal. The idea is to
+ * first get somethting that'S functionally OK, and then evolve the algorithm.
+ * In any case, even the initial implementaton is far faster than what we had
+ * before. -- rgerhards, 2011-06-06
+ *
+ * Copyright 2011-2013 by Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <netdb.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "syslogd-types.h"
+#include "glbl.h"
+#include "errmsg.h"
+#include "obj.h"
+#include "unicode-helper.h"
+#include "net.h"
+#include "hashtable.h"
+#include "prop.h"
+#include "dnscache.h"
+
+/* module data structures */
+struct dnscache_entry_s {
+ struct sockaddr_storage addr;
+ prop_t *fqdn;
+ prop_t *fqdnLowerCase;
+ prop_t *localName; /* only local name, without domain part (if configured so) */
+ prop_t *ip;
+ struct dnscache_entry_s *next;
+ unsigned nUsed;
+};
+typedef struct dnscache_entry_s dnscache_entry_t;
+struct dnscache_s {
+ pthread_rwlock_t rwlock;
+ struct hashtable *ht;
+ unsigned nEntries;
+};
+typedef struct dnscache_s dnscache_t;
+
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(prop)
+static dnscache_t dnsCache;
+static prop_t *staticErrValue;
+
+
+/* Our hash function.
+ * TODO: check how well it performs on socket addresses!
+ */
+unsigned int
+hash_from_key_fn(void *k)
+{
+ int len;
+ uchar *rkey = (uchar*) k; /* we treat this as opaque bytes */
+ unsigned hashval = 1;
+
+ len = SALEN((struct sockaddr*)k);
+ while(len--)
+ hashval = hashval * 33 + *rkey++;
+
+ return hashval;
+}
+
+static int
+key_equals_fn(void *key1, void *key2)
+{
+ return (SALEN((struct sockaddr*)key1) == SALEN((struct sockaddr*) key2)
+ && !memcmp(key1, key2, SALEN((struct sockaddr*) key1)));
+}
+
+/* destruct a cache entry.
+ * Precondition: entry must already be unlinked from list
+ */
+static void
+entryDestruct(dnscache_entry_t *etry)
+{
+ if(etry->fqdn != NULL)
+ prop.Destruct(&etry->fqdn);
+ if(etry->fqdnLowerCase != NULL)
+ prop.Destruct(&etry->fqdnLowerCase);
+ if(etry->localName != NULL)
+ prop.Destruct(&etry->localName);
+ if(etry->ip != NULL)
+ prop.Destruct(&etry->ip);
+ free(etry);
+}
+
+/* init function (must be called once) */
+rsRetVal
+dnscacheInit(void)
+{
+ DEFiRet;
+ if((dnsCache.ht = create_hashtable(100, hash_from_key_fn, key_equals_fn,
+ (void(*)(void*))entryDestruct)) == NULL) {
+ DBGPRINTF("dnscache: error creating hash table!\n");
+ ABORT_FINALIZE(RS_RET_ERR); // TODO: make this degrade, but run!
+ }
+ dnsCache.nEntries = 0;
+ pthread_rwlock_init(&dnsCache.rwlock, NULL);
+ CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+
+ prop.Construct(&staticErrValue);
+ prop.SetString(staticErrValue, (uchar*)"???", 3);
+ prop.ConstructFinalize(staticErrValue);
+finalize_it:
+ RETiRet;
+}
+
+/* deinit function (must be called once) */
+rsRetVal
+dnscacheDeinit(void)
+{
+ DEFiRet;
+ prop.Destruct(&staticErrValue);
+ hashtable_destroy(dnsCache.ht, 1); /* 1 => free all values automatically */
+ pthread_rwlock_destroy(&dnsCache.rwlock);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ RETiRet;
+}
+
+
+static inline dnscache_entry_t*
+findEntry(struct sockaddr_storage *addr)
+{
+ return((dnscache_entry_t*) hashtable_search(dnsCache.ht, addr));
+}
+
+
+/* This is a cancel-safe getnameinfo() version, because we learned
+ * (via drd/valgrind) that getnameinfo() seems to have some issues
+ * when being cancelled, at least if the module was dlloaded.
+ * rgerhards, 2008-09-30
+ */
+static inline int
+mygetnameinfo(const struct sockaddr *sa, socklen_t salen,
+ char *host, size_t hostlen,
+ char *serv, size_t servlen, int flags)
+{
+ int iCancelStateSave;
+ int i;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ i = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
+ pthread_setcancelstate(iCancelStateSave, NULL);
+ return i;
+}
+
+
+/* get only the local part of the hostname and set it in cache entry */
+static inline void
+setLocalHostName(dnscache_entry_t *etry)
+{
+ uchar *fqdnLower;
+ uchar *p;
+ int count;
+ int i;
+ uchar hostbuf[NI_MAXHOST];
+
+ if(glbl.GetPreserveFQDN()) {
+ prop.AddRef(etry->fqdnLowerCase);
+ etry->localName = etry->fqdnLowerCase;
+ goto done;
+ }
+
+ /* strip domain, if configured for this entry */
+ fqdnLower = propGetSzStr(etry->fqdnLowerCase);
+ p = (uchar*)strchr((char*)fqdnLower, '.'); /* find start of domain name "machine.example.com" */
+ if(p == NULL) { /* do we have a domain part? */
+ prop.AddRef(etry->fqdnLowerCase); /* no! */
+ etry->localName = etry->fqdnLowerCase;
+ goto done;
+ }
+
+ i = p - fqdnLower; /* length of hostname */
+ memcpy(hostbuf, fqdnLower, i);
+ /* now check if we belong to any of the domain names that were specified
+ * in the -s command line option. If so, remove and we are done.
+ */
+ if(glbl.GetStripDomains() != NULL) {
+ count=0;
+ while(glbl.GetStripDomains()[count]) {
+ if(strcmp((char*)(p + 1), glbl.GetStripDomains()[count]) == 0) {
+ prop.CreateStringProp(&etry->localName, hostbuf, i);
+ goto done;
+ }
+ count++;
+ }
+ }
+ /* if we reach this point, we have not found any domain we should strip. Now
+ * we try and see if the host itself is listed in the -l command line option
+ * and so should be stripped also. If so, we do it and return. Please note that
+ * -l list FQDNs, not just the hostname part. If it did just list the hostname, the
+ * door would be wide-open for all kinds of mixing up of hosts. Because of this,
+ * you'll see comparison against the full string (pszHostFQDN) below.
+ */
+ if(glbl.GetLocalHosts() != NULL) {
+ count=0;
+ while(glbl.GetLocalHosts()[count]) {
+ if(!strcmp((char*)fqdnLower, (char*)glbl.GetLocalHosts()[count])) {
+ prop.CreateStringProp(&etry->localName, hostbuf, i);
+ goto done;
+ }
+ count++;
+ }
+ }
+
+ /* at this point, we have not found anything, so we again use the
+ * already-created complete full name property.
+ */
+ prop.AddRef(etry->fqdnLowerCase);
+ etry->localName = etry->fqdnLowerCase;
+done: return;
+}
+
+
+/* resolve an address.
+ *
+ * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
+ * for some explanation of the code found below. We do by default not
+ * discard message where we detected malicouos DNS PTR records. However,
+ * there is a user-configurabel option that will tell us if
+ * we should abort. For this, the return value tells the caller if the
+ * message should be processed (1) or discarded (0).
+ */
+static rsRetVal
+resolveAddr(struct sockaddr_storage *addr, dnscache_entry_t *etry)
+{
+ DEFiRet;
+ int error;
+ sigset_t omask, nmask;
+ struct addrinfo hints, *res;
+ char szIP[80]; /* large enough for IPv6 */
+ char fqdnBuf[NI_MAXHOST];
+ rs_size_t fqdnLen;
+ rs_size_t i;
+
+ error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *)addr),
+ (char*) szIP, sizeof(szIP), NULL, 0, NI_NUMERICHOST);
+ if(error) {
+ dbgprintf("Malformed from address %s\n", gai_strerror(error));
+ ABORT_FINALIZE(RS_RET_INVALID_SOURCE);
+ }
+
+ if(!glbl.GetDisableDNS()) {
+ sigemptyset(&nmask);
+ sigaddset(&nmask, SIGHUP);
+ pthread_sigmask(SIG_BLOCK, &nmask, &omask);
+
+ error = mygetnameinfo((struct sockaddr *)addr, SALEN((struct sockaddr *) addr),
+ fqdnBuf, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
+
+ if(error == 0) {
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_flags = AI_NUMERICHOST;
+
+ /* we now do a lookup once again. This one should fail,
+ * because we should not have obtained a non-numeric address. If
+ * we got a numeric one, someone messed with DNS!
+ */
+ if(getaddrinfo (fqdnBuf, NULL, &hints, &res) == 0) {
+ uchar szErrMsg[1024];
+ freeaddrinfo (res);
+ /* OK, we know we have evil. The question now is what to do about
+ * it. One the one hand, the message might probably be intended
+ * to harm us. On the other hand, losing the message may also harm us.
+ * Thus, the behaviour is controlled by the $DropMsgsWithMaliciousDnsPTRRecords
+ * option. If it tells us we should discard, we do so, else we proceed,
+ * but log an error message together with it.
+ * time being, we simply drop the name we obtained and use the IP - that one
+ * is OK in any way. We do also log the error message. rgerhards, 2007-07-16
+ */
+ if(glbl.GetDropMalPTRMsgs() == 1) {
+ snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
+ "Malicious PTR record, message dropped "
+ "IP = \"%s\" HOST = \"%s\"",
+ szIP, fqdnBuf);
+ errmsg.LogError(0, RS_RET_MALICIOUS_ENTITY, "%s", szErrMsg);
+ pthread_sigmask(SIG_SETMASK, &omask, NULL);
+ ABORT_FINALIZE(RS_RET_MALICIOUS_ENTITY);
+ }
+
+ /* Please note: we deal with a malicous entry. Thus, we have crafted
+ * the snprintf() below so that all text is in front of the entry - maybe
+ * it contains characters that make the message unreadable
+ * (OK, I admit this is more or less impossible, but I am paranoid...)
+ * rgerhards, 2007-07-16
+ */
+ snprintf((char*)szErrMsg, sizeof(szErrMsg) / sizeof(uchar),
+ "Malicious PTR record (message accepted, but used IP "
+ "instead of PTR name: IP = \"%s\" HOST = \"%s\"",
+ szIP, fqdnBuf);
+ errmsg.LogError(0, NO_ERRCODE, "%s", szErrMsg);
+
+ error = 1; /* that will trigger using IP address below. */
+ } else {/* we have a valid entry, so let's create the respective properties */
+ fqdnLen = strlen(fqdnBuf);
+ prop.CreateStringProp(&etry->fqdn, (uchar*)fqdnBuf, fqdnLen);
+ for(i = 0 ; i < fqdnLen ; ++i)
+ fqdnBuf[i] = tolower(fqdnBuf[i]);
+ prop.CreateStringProp(&etry->fqdnLowerCase, (uchar*)fqdnBuf, fqdnLen);
+ }
+ }
+ pthread_sigmask(SIG_SETMASK, &omask, NULL);
+ }
+
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ strcpy(szIP, "?error.obtaining.ip?");
+ error = 1; /* trigger hostname copies below! */
+ }
+
+ /* we need to create the inputName property (only once during our lifetime) */
+ prop.CreateStringProp(&etry->ip, (uchar*)szIP, strlen(szIP));
+
+ if(error || glbl.GetDisableDNS()) {
+ dbgprintf("Host name for your address (%s) unknown\n", szIP);
+ prop.AddRef(etry->ip);
+ etry->fqdn = etry->ip;
+ prop.AddRef(etry->ip);
+ etry->fqdnLowerCase = etry->ip;
+ }
+
+ setLocalHostName(etry);
+
+ RETiRet;
+}
+
+
+static inline rsRetVal
+addEntry(struct sockaddr_storage *addr, dnscache_entry_t **pEtry)
+{
+ int r;
+ struct sockaddr_storage *keybuf;
+ dnscache_entry_t *etry = NULL;
+ DEFiRet;
+
+ CHKmalloc(etry = MALLOC(sizeof(dnscache_entry_t)));
+ CHKiRet(resolveAddr(addr, etry));
+ memcpy(&etry->addr, addr, SALEN((struct sockaddr*) addr));
+ etry->nUsed = 0;
+ *pEtry = etry;
+
+ CHKmalloc(keybuf = malloc(sizeof(struct sockaddr_storage)));
+ memcpy(keybuf, addr, sizeof(struct sockaddr_storage));
+
+ pthread_rwlock_unlock(&dnsCache.rwlock); /* release read lock */
+ pthread_rwlock_wrlock(&dnsCache.rwlock); /* and re-aquire for writing */
+ r = hashtable_insert(dnsCache.ht, keybuf, *pEtry);
+ if(r == 0) {
+ DBGPRINTF("dnscache: inserting element failed\n");
+ }
+ pthread_rwlock_unlock(&dnsCache.rwlock);
+ pthread_rwlock_rdlock(&dnsCache.rwlock); /* we need this again */
+
+finalize_it:
+ if(iRet != RS_RET_OK && etry != NULL) {
+ /* Note: sub-fields cannot be populated in this case */
+ free(etry);
+ }
+ RETiRet;
+}
+
+
+/* validate if an entry is still valid and, if not, re-query it.
+ * In the initial implementation, this is a dummy!
+ * TODO: implement!
+ */
+static inline rsRetVal
+validateEntry(dnscache_entry_t __attribute__((unused)) *etry, struct sockaddr_storage __attribute__((unused)) *addr)
+{
+ return RS_RET_OK;
+}
+
+
+/* This is the main function: it looks up an entry and returns it's name
+ * and IP address. If the entry is not yet inside the cache, it is added.
+ * If the entry can not be resolved, an error is reported back. If fqdn
+ * or fqdnLowerCase are NULL, they are not set.
+ */
+rsRetVal
+dnscacheLookup(struct sockaddr_storage *addr, prop_t **fqdn, prop_t **fqdnLowerCase,
+ prop_t **localName, prop_t **ip)
+{
+ dnscache_entry_t *etry;
+ DEFiRet;
+
+ pthread_rwlock_rdlock(&dnsCache.rwlock); /* TODO: optimize this! */
+ etry = findEntry(addr);
+ dbgprintf("dnscache: entry %p found\n", etry);
+ if(etry == NULL) {
+ CHKiRet(addEntry(addr, &etry));
+ } else {
+ CHKiRet(validateEntry(etry, addr));
+ }
+ prop.AddRef(etry->ip);
+ *ip = etry->ip;
+ if(fqdn != NULL) {
+ prop.AddRef(etry->fqdn);
+ *fqdn = etry->fqdn;
+ }
+ if(fqdnLowerCase != NULL) {
+ prop.AddRef(etry->fqdnLowerCase);
+ *fqdnLowerCase = etry->fqdnLowerCase;
+ }
+ if(localName != NULL) {
+ prop.AddRef(etry->localName);
+ *localName = etry->localName;
+ }
+
+finalize_it:
+ pthread_rwlock_unlock(&dnsCache.rwlock);
+ if(iRet != RS_RET_OK && iRet != RS_RET_ADDRESS_UNKNOWN) {
+ DBGPRINTF("dnscacheLookup failed with iRet %d\n", iRet);
+ prop.AddRef(staticErrValue);
+ *ip = staticErrValue;
+ if(fqdn != NULL) {
+ prop.AddRef(staticErrValue);
+ *fqdn = staticErrValue;
+ }
+ if(fqdnLowerCase != NULL) {
+ prop.AddRef(staticErrValue);
+ *fqdnLowerCase = staticErrValue;
+ }
+ if(localName != NULL) {
+ prop.AddRef(staticErrValue);
+ *localName = staticErrValue;
+ }
+ }
+ RETiRet;
+}
diff --git a/runtime/dnscache.h b/runtime/dnscache.h
new file mode 100644
index 00000000..9c21a645
--- /dev/null
+++ b/runtime/dnscache.h
@@ -0,0 +1,29 @@
+/* Definitions for dnscache module.
+ *
+ * Copyright 2011-2013 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDED_DNSCACHE_H
+#define INCLUDED_DNSCACHE_H
+
+rsRetVal dnscacheInit(void);
+rsRetVal dnscacheDeinit(void);
+rsRetVal dnscacheLookup(struct sockaddr_storage *addr, prop_t **fqdn, prop_t **fqdnLowerCase, prop_t **localName, prop_t **ip);
+
+#endif /* #ifndef INCLUDED_DNSCACHE_H */
diff --git a/runtime/errmsg.c b/runtime/errmsg.c
new file mode 100644
index 00000000..dcb5b185
--- /dev/null
+++ b/runtime/errmsg.c
@@ -0,0 +1,144 @@
+/* The errmsg object.
+ *
+ * Module begun 2008-03-05 by Rainer Gerhards, based on some code
+ * from syslogd.c. I converted this module to lgpl and have checked that
+ * all contributors agreed to that step.
+ * Now moving to ASL 2.0, and contributor checks tell that there is no need
+ * to take further case, as the code now boils to be either my own or, a few lines,
+ * of the original BSD-licenses sysklogd code. rgerhards, 2012-01-16
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+
+/* static data */
+DEFobjStaticHelpers
+
+
+/* ------------------------------ methods ------------------------------ */
+
+
+/* We now receive three parameters: one is the internal error code
+ * which will also become the error message number, the second is
+ * errno - if it is non-zero, the corresponding error message is included
+ * in the text and finally the message text itself. Note that it is not
+ * 100% clean to use the internal errcode, as it may be reached from
+ * multiple actual error causes. However, it is much better than having
+ * no error code at all (and in most cases, a single internal error code
+ * maps to a specific error event).
+ * rgerhards, 2008-06-27
+ */
+static void __attribute__((format(printf, 3, 4)))
+LogError(int iErrno, int iErrCode, char *fmt, ... )
+{
+ va_list ap;
+ char buf[1024];
+ char msg[1024];
+ char errStr[1024];
+ size_t lenBuf;
+
+ BEGINfunc
+ assert(fmt != NULL);
+ /* Format parameters */
+ va_start(ap, fmt);
+ lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap);
+ if(lenBuf >= sizeof(buf)) {
+ /* if our buffer was too small, we simply truncate. */
+ lenBuf--;
+ }
+ va_end(ap);
+
+ /* Log the error now */
+ buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */
+
+ dbgprintf("Called LogError, msg: %s\n", buf);
+
+ if(iErrno != 0) {
+ rs_strerror_r(iErrno, errStr, sizeof(errStr));
+ if(iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) {
+ snprintf(msg, sizeof(msg), "%s: %s", buf, errStr);
+ } else {
+ snprintf(msg, sizeof(msg), "%s: %s [try http://www.rsyslog.com/e/%d ]", buf, errStr, iErrCode * -1);
+ }
+ } else {
+ if(iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) {
+ snprintf(msg, sizeof(msg), "%s", buf);
+ } else {
+ snprintf(msg, sizeof(msg), "%s [try http://www.rsyslog.com/e/%d ]", buf, iErrCode * -1);
+ }
+ }
+ msg[sizeof(msg)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */
+ errno = 0;
+
+ glblErrLogger(iErrCode, (uchar*)msg);
+
+ ENDfunc
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(errmsg)
+CODESTARTobjQueryInterface(errmsg)
+ if(pIf->ifVersion != errmsgCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->LogError = LogError;
+finalize_it:
+ENDobjQueryInterface(errmsg)
+
+
+/* Initialize the errmsg class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(errmsg, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+
+ /* set our own handlers */
+ENDObjClassInit(errmsg)
+
+/* Exit the class.
+ * rgerhards, 2008-04-17
+ */
+BEGINObjClassExit(errmsg, OBJ_IS_CORE_MODULE) /* class, version */
+ /* release objects we no longer need */
+ENDObjClassExit(errmsg)
+
+/* vi:set ai:
+ */
diff --git a/runtime/errmsg.h b/runtime/errmsg.h
new file mode 100644
index 00000000..dfa70c00
--- /dev/null
+++ b/runtime/errmsg.h
@@ -0,0 +1,45 @@
+/* The errmsg object. It is used to emit error message inside rsyslog.
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_ERRMSG_H
+#define INCLUDED_ERRMSG_H
+
+#include "errmsg.h"
+
+/* TODO: define error codes */
+#define NO_ERRCODE -1
+
+/* the errmsg object */
+typedef struct errmsg_s {
+ char dummy;
+} errmsg_t;
+
+
+/* interfaces */
+BEGINinterface(errmsg) /* name must also be changed in ENDinterface macro! */
+ void __attribute__((format(printf, 3, 4))) (*LogError)(int iErrno, int iErrCode, char *pszErrFmt, ... );
+ENDinterface(errmsg)
+#define errmsgCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+PROTOTYPEObj(errmsg);
+
+#endif /* #ifndef INCLUDED_ERRMSG_H */
diff --git a/runtime/glbl.c b/runtime/glbl.c
new file mode 100644
index 00000000..ccb978ba
--- /dev/null
+++ b/runtime/glbl.c
@@ -0,0 +1,696 @@
+/* glbl.c - this module holds global defintions and data items.
+ * These are shared among the runtime library. Their use should be
+ * limited to cases where it is actually needed. The main intension for
+ * implementing them was support for the transistion from v2 to v4
+ * (with fully modular design), but it turned out that there may also
+ * be some other good use cases besides backwards-compatibility.
+ *
+ * Module begun 2008-04-16 by Rainer Gerhards
+ *
+ * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "unicode-helper.h"
+#include "cfsysline.h"
+#include "glbl.h"
+#include "prop.h"
+#include "atomic.h"
+#include "errmsg.h"
+#include "rainerscript.h"
+#include "net.h"
+
+/* some defaults */
+#ifndef DFLT_NETSTRM_DRVR
+# define DFLT_NETSTRM_DRVR ((uchar*)"ptcp")
+#endif
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(prop)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(net)
+
+/* static data
+ * For this object, these variables are obviously what makes the "meat" of the
+ * class...
+ */
+static uchar *pszWorkDir = NULL;
+static int bOptimizeUniProc = 1; /* enable uniprocessor optimizations */
+static int bParseHOSTNAMEandTAG = 1; /* parser modification (based on startup params!) */
+static int bPreserveFQDN = 0; /* should FQDNs always be preserved? */
+static int iMaxLine = 8096; /* maximum length of a syslog message */
+static int iDefPFFamily = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both) */
+static int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */
+static int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */
+static int bDisableDNS = 0; /* don't look up IP addresses of remote messages */
+static prop_t *propLocalIPIF = NULL;/* IP address to report for the local host (default is 127.0.0.1) */
+static prop_t *propLocalHostName = NULL;/* our hostname as FQDN - read-only after startup */
+static uchar *LocalHostName = NULL;/* our hostname - read-only after startup, except HUP */
+static uchar *LocalHostNameOverride = NULL;/* user-overridden hostname - read-only after startup */
+static uchar *LocalFQDNName = NULL;/* our hostname as FQDN - read-only after startup, except HUP */
+static uchar *LocalDomain = NULL;/* our local domain name - read-only after startup, except HUP */
+static char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u., never touched by init */
+static char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup, never touched by init */
+static uchar *pszDfltNetstrmDrvr = NULL; /* module name of default netstream driver */
+static uchar *pszDfltNetstrmDrvrCAF = NULL; /* default CA file for the netstrm driver */
+static uchar *pszDfltNetstrmDrvrKeyFile = NULL; /* default key file for the netstrm driver (server) */
+static uchar *pszDfltNetstrmDrvrCertFile = NULL; /* default cert file for the netstrm driver (server) */
+static int bTerminateInputs = 0; /* global switch that inputs shall terminate ASAP (1=> terminate) */
+pid_t glbl_ourpid;
+#ifndef HAVE_ATOMIC_BUILTINS
+static DEF_ATOMIC_HELPER_MUT(mutTerminateInputs);
+#endif
+#ifdef USE_UNLIMITED_SELECT
+static int iFdSetSize = howmany(FD_SETSIZE, __NFDBITS) * sizeof (fd_mask); /* size of select() bitmask in bytes */
+#endif
+static uchar *SourceIPofLocalClient = NULL; /* [ar] Source IP for local client to be used on multihomed host */
+
+
+/* tables for interfacing with the v6 config system */
+static struct cnfparamdescr cnfparamdescr[] = {
+ { "workdirectory", eCmdHdlrString, 0 },
+ { "dropmsgswithmaliciousdnsptrrecords", eCmdHdlrBinary, 0 },
+ { "localhostname", eCmdHdlrGetWord, 0 },
+ { "preservefqdn", eCmdHdlrBinary, 0 },
+ { "defaultnetstreamdrivercafile", eCmdHdlrString, 0 },
+ { "defaultnetstreamdriverkeyfile", eCmdHdlrString, 0 },
+ { "defaultnetstreamdriver", eCmdHdlrString, 0 },
+ { "maxmessagesize", eCmdHdlrSize, 0 },
+};
+static struct cnfparamblk paramblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfparamdescr)/sizeof(struct cnfparamdescr),
+ cnfparamdescr
+ };
+
+static struct cnfparamvals *cnfparamvals = NULL;
+/* we need to support multiple calls into our param block, so we need
+ * to persist the current settings. Note that this must be re-set
+ * each time a new config load begins (TODO: create interface?)
+ */
+
+/* define a macro for the simple properties' set and get functions
+ * (which are always the same). This is only suitable for pretty
+ * simple cases which require neither checks nor memory allocation.
+ */
+#define SIMP_PROP(nameFunc, nameVar, dataType) \
+ SIMP_PROP_GET(nameFunc, nameVar, dataType) \
+ SIMP_PROP_SET(nameFunc, nameVar, dataType)
+#define SIMP_PROP_SET(nameFunc, nameVar, dataType) \
+static rsRetVal Set##nameFunc(dataType newVal) \
+{ \
+ nameVar = newVal; \
+ return RS_RET_OK; \
+}
+#define SIMP_PROP_GET(nameFunc, nameVar, dataType) \
+static dataType Get##nameFunc(void) \
+{ \
+ return(nameVar); \
+}
+
+SIMP_PROP(ParseHOSTNAMEandTAG, bParseHOSTNAMEandTAG, int)
+SIMP_PROP(OptimizeUniProc, bOptimizeUniProc, int)
+SIMP_PROP(PreserveFQDN, bPreserveFQDN, int)
+SIMP_PROP(MaxLine, iMaxLine, int)
+SIMP_PROP(DefPFFamily, iDefPFFamily, int) /* note that in the future we may check the family argument */
+SIMP_PROP(DropMalPTRMsgs, bDropMalPTRMsgs, int)
+SIMP_PROP(Option_DisallowWarning, option_DisallowWarning, int)
+SIMP_PROP(DisableDNS, bDisableDNS, int)
+SIMP_PROP(StripDomains, StripDomains, char**)
+SIMP_PROP(LocalHosts, LocalHosts, char**)
+#ifdef USE_UNLIMITED_SELECT
+SIMP_PROP(FdSetSize, iFdSetSize, int)
+#endif
+
+SIMP_PROP_SET(DfltNetstrmDrvr, pszDfltNetstrmDrvr, uchar*) /* TODO: use custom function which frees existing value */
+SIMP_PROP_SET(DfltNetstrmDrvrCAF, pszDfltNetstrmDrvrCAF, uchar*) /* TODO: use custom function which frees existing value */
+SIMP_PROP_SET(DfltNetstrmDrvrKeyFile, pszDfltNetstrmDrvrKeyFile, uchar*) /* TODO: use custom function which frees existing value */
+SIMP_PROP_SET(DfltNetstrmDrvrCertFile, pszDfltNetstrmDrvrCertFile, uchar*) /* TODO: use custom function which frees existing value */
+
+#undef SIMP_PROP
+#undef SIMP_PROP_SET
+#undef SIMP_PROP_GET
+
+
+/* return global input termination status
+ * rgerhards, 2009-07-20
+ */
+static int GetGlobalInputTermState(void)
+{
+ return ATOMIC_FETCH_32BIT(&bTerminateInputs, &mutTerminateInputs);
+}
+
+
+/* set global termination state to "terminate". Note that this is a
+ * "once in a lifetime" action which can not be undone. -- gerhards, 2009-07-20
+ */
+static void SetGlobalInputTermination(void)
+{
+ ATOMIC_STORE_1_TO_INT(&bTerminateInputs, &mutTerminateInputs);
+}
+
+
+/* set the local host IP address to a specific string. Helper to
+ * small set of functions. No checks done, caller must ensure it is
+ * ok to call. Most importantly, the IP address must not already have
+ * been set. -- rgerhards, 2012-03-21
+ */
+static inline rsRetVal
+storeLocalHostIPIF(uchar *myIP)
+{
+ DEFiRet;
+ CHKiRet(prop.Construct(&propLocalIPIF));
+ CHKiRet(prop.SetString(propLocalIPIF, myIP, ustrlen(myIP)));
+ CHKiRet(prop.ConstructFinalize(propLocalIPIF));
+ DBGPRINTF("rsyslog/glbl: using '%s' as localhost IP\n", myIP);
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function is used to set the IP address that is to be
+ * reported for the local host. Note that in order to ease things
+ * for the v6 config interface, we do not allow to set this more
+ * than once.
+ * rgerhards, 2012-03-21
+ */
+static rsRetVal
+setLocalHostIPIF(void __attribute__((unused)) *pVal, uchar *pNewVal)
+{
+ uchar myIP[128];
+ rsRetVal localRet;
+ DEFiRet;
+
+ CHKiRet(objUse(net, CORE_COMPONENT));
+
+ if(propLocalIPIF != NULL) {
+ errmsg.LogError(0, RS_RET_ERR, "$LocalHostIPIF is already set "
+ "and cannot be reset; place it at TOP OF rsyslog.conf!");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ localRet = net.GetIFIPAddr(pNewVal, AF_UNSPEC, myIP, (int) sizeof(myIP));
+ if(localRet != RS_RET_OK) {
+ errmsg.LogError(0, RS_RET_ERR, "$LocalHostIPIF: IP address for interface "
+ "'%s' cannnot be obtained - ignoring directive", pNewVal);
+ } else {
+ storeLocalHostIPIF(myIP);
+ }
+
+
+finalize_it:
+ free(pNewVal); /* no longer needed -> is in prop! */
+ RETiRet;
+}
+
+
+/* This function is used to set the global work directory name.
+ * It verifies that the provided directory actually exists and
+ * emits an error message if not.
+ * rgerhards, 2011-02-16
+ */
+static rsRetVal setWorkDir(void __attribute__((unused)) *pVal, uchar *pNewVal)
+{
+ size_t lenDir;
+ int i;
+ struct stat sb;
+ DEFiRet;
+
+ /* remove trailing slashes */
+ lenDir = ustrlen(pNewVal);
+ i = lenDir - 1;
+ while(i > 0 && pNewVal[i] == '/') {
+ --i;
+ }
+
+ if(i < 0) {
+ errmsg.LogError(0, RS_RET_ERR_WRKDIR, "$WorkDirectory: empty value "
+ "- directive ignored");
+ ABORT_FINALIZE(RS_RET_ERR_WRKDIR);
+ }
+
+ if(i != (int) lenDir - 1) {
+ pNewVal[i+1] = '\0';
+ errmsg.LogError(0, RS_RET_WRN_WRKDIR, "$WorkDirectory: trailing slashes "
+ "removed, new value is '%s'", pNewVal);
+ }
+
+ if(stat((char*) pNewVal, &sb) != 0) {
+ errmsg.LogError(0, RS_RET_ERR_WRKDIR, "$WorkDirectory: %s can not be "
+ "accessed, probably does not exist - directive ignored", pNewVal);
+ ABORT_FINALIZE(RS_RET_ERR_WRKDIR);
+ }
+
+ if(!S_ISDIR(sb.st_mode)) {
+ errmsg.LogError(0, RS_RET_ERR_WRKDIR, "$WorkDirectory: %s not a directory - directive ignored",
+ pNewVal);
+ ABORT_FINALIZE(RS_RET_ERR_WRKDIR);
+ }
+
+ free(pszWorkDir);
+ pszWorkDir = pNewVal;
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal
+setDebugFile(void __attribute__((unused)) *pVal, uchar *pNewVal)
+{
+ DEFiRet;
+ dbgSetDebugFile(pNewVal);
+ free(pNewVal);
+ RETiRet;
+}
+
+
+static rsRetVal
+setDebugLevel(void __attribute__((unused)) *pVal, int level)
+{
+ DEFiRet;
+ dbgSetDebugLevel(level);
+ dbgprintf("debug level %d set via config file\n", level);
+ dbgprintf("This is rsyslog version " VERSION "\n");
+ RETiRet;
+}
+
+
+/* return our local IP.
+ * If no local IP is set, "127.0.0.1" is selected *and* set. This
+ * is an intensional side effect that we do in order to keep things
+ * consistent and avoid config errors (this will make us not accept
+ * setting the local IP address once a module has obtained it - so
+ * it forces the $LocalHostIPIF directive high up in rsyslog.conf)
+ * rgerhards, 2012-03-21
+ */
+static prop_t*
+GetLocalHostIP(void)
+{
+ if(propLocalIPIF == NULL)
+ storeLocalHostIPIF((uchar*)"127.0.0.1");
+ return(propLocalIPIF);
+}
+
+
+/* set our local hostname. Free previous hostname, if it was already set.
+ * Note that we do now do this in a thread
+ * "once in a lifetime" action which can not be undone. -- gerhards, 2009-07-20
+ */
+static rsRetVal
+SetLocalHostName(uchar *newname)
+{
+ free(LocalHostName);
+ LocalHostName = newname;
+ return RS_RET_OK;
+}
+
+
+/* return our local hostname. if it is not set, "[localhost]" is returned
+ */
+static uchar*
+GetLocalHostName(void)
+{
+ uchar *pszRet;
+
+ if(LocalHostNameOverride != NULL) {
+ pszRet = LocalHostNameOverride;
+ goto done;
+ }
+
+ if(LocalHostName == NULL)
+ pszRet = (uchar*) "[localhost]";
+ else {
+ if(GetPreserveFQDN() == 1)
+ pszRet = LocalFQDNName;
+ else
+ pszRet = LocalHostName;
+ }
+done:
+ return(pszRet);
+}
+
+
+/* set our local domain name. Free previous domain, if it was already set.
+ */
+static rsRetVal
+SetLocalDomain(uchar *newname)
+{
+ free(LocalDomain);
+ LocalDomain = newname;
+ return RS_RET_OK;
+}
+
+
+/* return our local hostname. if it is not set, "[localhost]" is returned
+ */
+static uchar*
+GetLocalDomain(void)
+{
+ return LocalDomain;
+}
+
+
+/* generate the local hostname property. This must be done after the hostname info
+ * has been set as well as PreserveFQDN.
+ * rgerhards, 2009-06-30
+ */
+static rsRetVal
+GenerateLocalHostNameProperty(void)
+{
+ DEFiRet;
+ uchar *pszName;
+
+ if(propLocalHostName != NULL)
+ prop.Destruct(&propLocalHostName);
+
+ CHKiRet(prop.Construct(&propLocalHostName));
+ if(LocalHostNameOverride == NULL) {
+ if(LocalHostName == NULL)
+ pszName = (uchar*) "[localhost]";
+ else {
+ if(GetPreserveFQDN() == 1)
+ pszName = LocalFQDNName;
+ else
+ pszName = LocalHostName;
+ }
+ } else { /* local hostname is overriden via config */
+ pszName = LocalHostNameOverride;
+ }
+ DBGPRINTF("GenerateLocalHostName uses '%s'\n", pszName);
+ CHKiRet(prop.SetString(propLocalHostName, pszName, ustrlen(pszName)));
+ CHKiRet(prop.ConstructFinalize(propLocalHostName));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* return our local hostname as a string property
+ */
+static prop_t*
+GetLocalHostNameProp(void)
+{
+ return(propLocalHostName);
+}
+
+
+static rsRetVal
+SetLocalFQDNName(uchar *newname)
+{
+ free(LocalFQDNName);
+ LocalFQDNName = newname;
+ return RS_RET_OK;
+}
+
+/* return the current localhost name as FQDN (requires FQDN to be set)
+ * TODO: we should set the FQDN ourselfs in here!
+ */
+static uchar*
+GetLocalFQDNName(void)
+{
+ return(LocalFQDNName == NULL ? (uchar*) "[localhost]" : LocalFQDNName);
+}
+
+
+/* return the current working directory */
+static uchar*
+GetWorkDir(void)
+{
+ return(pszWorkDir == NULL ? (uchar*) "" : pszWorkDir);
+}
+
+
+/* return the current default netstream driver */
+static uchar*
+GetDfltNetstrmDrvr(void)
+{
+ return(pszDfltNetstrmDrvr == NULL ? DFLT_NETSTRM_DRVR : pszDfltNetstrmDrvr);
+}
+
+
+/* return the current default netstream driver CA File */
+static uchar*
+GetDfltNetstrmDrvrCAF(void)
+{
+ return(pszDfltNetstrmDrvrCAF);
+}
+
+
+/* return the current default netstream driver key File */
+static uchar*
+GetDfltNetstrmDrvrKeyFile(void)
+{
+ return(pszDfltNetstrmDrvrKeyFile);
+}
+
+
+/* return the current default netstream driver certificate File */
+static uchar*
+GetDfltNetstrmDrvrCertFile(void)
+{
+ return(pszDfltNetstrmDrvrCertFile);
+}
+
+
+/* [ar] Source IP for local client to be used on multihomed host */
+static rsRetVal
+SetSourceIPofLocalClient(uchar *newname)
+{
+ if(SourceIPofLocalClient != NULL) {
+ free(SourceIPofLocalClient); }
+ SourceIPofLocalClient = newname;
+ return RS_RET_OK;
+}
+
+static uchar*
+GetSourceIPofLocalClient(void)
+{
+ return(SourceIPofLocalClient);
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-21
+ */
+BEGINobjQueryInterface(glbl)
+CODESTARTobjQueryInterface(glbl)
+ if(pIf->ifVersion != glblCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->GetWorkDir = GetWorkDir;
+ pIf->GenerateLocalHostNameProperty = GenerateLocalHostNameProperty;
+ pIf->GetLocalHostNameProp = GetLocalHostNameProp;
+ pIf->GetLocalHostIP = GetLocalHostIP;
+ pIf->SetGlobalInputTermination = SetGlobalInputTermination;
+ pIf->GetGlobalInputTermState = GetGlobalInputTermState;
+ pIf->GetSourceIPofLocalClient = GetSourceIPofLocalClient; /* [ar] */
+ pIf->SetSourceIPofLocalClient = SetSourceIPofLocalClient; /* [ar] */
+#define SIMP_PROP(name) \
+ pIf->Get##name = Get##name; \
+ pIf->Set##name = Set##name;
+ SIMP_PROP(MaxLine);
+ SIMP_PROP(OptimizeUniProc);
+ SIMP_PROP(ParseHOSTNAMEandTAG);
+ SIMP_PROP(PreserveFQDN);
+ SIMP_PROP(DefPFFamily);
+ SIMP_PROP(DropMalPTRMsgs);
+ SIMP_PROP(Option_DisallowWarning);
+ SIMP_PROP(DisableDNS);
+ SIMP_PROP(LocalFQDNName)
+ SIMP_PROP(LocalHostName)
+ SIMP_PROP(LocalDomain)
+ SIMP_PROP(StripDomains)
+ SIMP_PROP(LocalHosts)
+ SIMP_PROP(DfltNetstrmDrvr)
+ SIMP_PROP(DfltNetstrmDrvrCAF)
+ SIMP_PROP(DfltNetstrmDrvrKeyFile)
+ SIMP_PROP(DfltNetstrmDrvrCertFile)
+#ifdef USE_UNLIMITED_SELECT
+ SIMP_PROP(FdSetSize)
+#endif
+#undef SIMP_PROP
+finalize_it:
+ENDobjQueryInterface(glbl)
+
+
+/* Reset config variables to default values.
+ * rgerhards, 2008-04-17
+ */
+static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ free(pszDfltNetstrmDrvr);
+ pszDfltNetstrmDrvr = NULL;
+ free(pszDfltNetstrmDrvrCAF);
+ pszDfltNetstrmDrvrCAF = NULL;
+ free(pszDfltNetstrmDrvrKeyFile);
+ pszDfltNetstrmDrvrKeyFile = NULL;
+ free(pszDfltNetstrmDrvrCertFile);
+ pszDfltNetstrmDrvrCertFile = NULL;
+ free(LocalHostNameOverride);
+ LocalHostNameOverride = NULL;
+ free(pszWorkDir);
+ pszWorkDir = NULL;
+ bDropMalPTRMsgs = 0;
+ bOptimizeUniProc = 1;
+ bPreserveFQDN = 0;
+ iMaxLine = 8192;
+#ifdef USE_UNLIMITED_SELECT
+ iFdSetSize = howmany(FD_SETSIZE, __NFDBITS) * sizeof (fd_mask);
+#endif
+ return RS_RET_OK;
+}
+
+
+/* Prepare for new config
+ */
+void
+glblPrepCnf(void)
+{
+ free(cnfparamvals);
+ cnfparamvals = NULL;
+}
+
+/* handle a global config object. Note that multiple global config statements
+ * are permitted (because of plugin support), so once we got a param block,
+ * we need to hold to it.
+ * rgerhards, 2011-07-19
+ */
+void
+glblProcessCnf(struct cnfobj *o)
+{
+ cnfparamvals = nvlstGetParams(o->nvlst, &paramblk, cnfparamvals);
+ dbgprintf("glbl param blk after glblProcessCnf:\n");
+ cnfparamsPrint(&paramblk, cnfparamvals);
+}
+
+void
+glblDoneLoadCnf(void)
+{
+ int i;
+ unsigned char *cstr;
+
+ if(cnfparamvals == NULL)
+ goto finalize_it;
+
+ for(i = 0 ; i < paramblk.nParams ; ++i) {
+ if(!cnfparamvals[i].bUsed)
+ continue;
+ if(!strcmp(paramblk.descr[i].name, "workdirectory")) {
+ cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setWorkDir(NULL, cstr);
+ } else if(!strcmp(paramblk.descr[i].name, "localhostname")) {
+ free(LocalHostNameOverride);
+ LocalHostNameOverride = (uchar*)
+ es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdriverkeyfile")) {
+ free(pszDfltNetstrmDrvrKeyFile);
+ pszDfltNetstrmDrvrKeyFile = (uchar*)
+ es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdrivercafile")) {
+ free(pszDfltNetstrmDrvrCAF);
+ pszDfltNetstrmDrvrCAF = (uchar*)
+ es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdriver")) {
+ free(pszDfltNetstrmDrvr);
+ pszDfltNetstrmDrvr = (uchar*)
+ es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ } else if(!strcmp(paramblk.descr[i].name, "preservefqdn")) {
+ bPreserveFQDN = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name,
+ "dropmsgswithmaliciousdnsptrrecords")) {
+ bDropMalPTRMsgs = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "maxmessagesize")) {
+ iMaxLine = (int) cnfparamvals[i].val.d.n;
+ } else {
+ dbgprintf("glblDoneLoadCnf: program error, non-handled "
+ "param '%s'\n", paramblk.descr[i].name);
+ }
+ }
+finalize_it: ;
+}
+
+
+/* Initialize the glbl class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(glbl, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+
+ /* config handlers are never unregistered and need not be - we are always loaded ;) */
+ CHKiRet(regCfSysLineHdlr((uchar *)"debugfile", 0, eCmdHdlrGetWord, setDebugFile, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"debuglevel", 0, eCmdHdlrInt, setDebugLevel, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"workdirectory", 0, eCmdHdlrGetWord, setWorkDir, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"dropmsgswithmaliciousdnsptrrecords", 0, eCmdHdlrBinary, NULL, &bDropMalPTRMsgs, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriver", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvr, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercafile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrCAF, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriverkeyfile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrKeyFile, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercertfile", 0, eCmdHdlrGetWord, NULL, &pszDfltNetstrmDrvrCertFile, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"localhostname", 0, eCmdHdlrGetWord, NULL, &LocalHostNameOverride, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"localhostipif", 0, eCmdHdlrGetWord, setLocalHostIPIF, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"optimizeforuniprocessor", 0, eCmdHdlrBinary, NULL, &bOptimizeUniProc, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"preservefqdn", 0, eCmdHdlrBinary, NULL, &bPreserveFQDN, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"maxmessagesize", 0, eCmdHdlrSize,
+ NULL, &iMaxLine, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL));
+
+ INIT_ATOMIC_HELPER_MUT(mutTerminateInputs);
+ENDObjClassInit(glbl)
+
+
+/* Exit the glbl class.
+ * rgerhards, 2008-04-17
+ */
+BEGINObjClassExit(glbl, OBJ_IS_CORE_MODULE) /* class, version */
+ free(pszDfltNetstrmDrvr);
+ free(pszDfltNetstrmDrvrCAF);
+ free(pszDfltNetstrmDrvrKeyFile);
+ free(pszDfltNetstrmDrvrCertFile);
+ free(pszWorkDir);
+ free(LocalDomain);
+ free(LocalHostName);
+ free(LocalHostNameOverride);
+ free(LocalFQDNName);
+ objRelease(prop, CORE_COMPONENT);
+ DESTROY_ATOMIC_HELPER_MUT(mutTerminateInputs);
+ENDObjClassExit(glbl)
+
+void glblProcessCnf(struct cnfobj *o);
+
+/* vi:set ai:
+ */
diff --git a/runtime/glbl.h b/runtime/glbl.h
new file mode 100644
index 00000000..2c7f3b31
--- /dev/null
+++ b/runtime/glbl.h
@@ -0,0 +1,101 @@
+/* Definition of globally-accessible data items.
+ *
+ * This module provides access methods to items of global scope. Most often,
+ * these globals serve as defaults to initialize local settings. Currently,
+ * many of them are either constants or global variable references. However,
+ * this module provides the necessary hooks to change that at any time.
+ *
+ * Please note that there currently is no glbl.c file as we do not yet
+ * have any implementations.
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GLBL_H_INCLUDED
+#define GLBL_H_INCLUDED
+
+#include <sys/types.h>
+#include "rainerscript.h"
+#include "prop.h"
+
+#define glblGetIOBufSize() 4096 /* size of the IO buffer, e.g. for strm class */
+
+extern pid_t glbl_ourpid;
+
+/* interfaces */
+BEGINinterface(glbl) /* name must also be changed in ENDinterface macro! */
+ uchar* (*GetWorkDir)(void);
+#define SIMP_PROP(name, dataType) \
+ dataType (*Get##name)(void); \
+ rsRetVal (*Set##name)(dataType);
+ SIMP_PROP(MaxLine, int)
+ SIMP_PROP(OptimizeUniProc, int)
+ SIMP_PROP(PreserveFQDN, int)
+ SIMP_PROP(DefPFFamily, int)
+ SIMP_PROP(DropMalPTRMsgs, int)
+ SIMP_PROP(Option_DisallowWarning, int)
+ SIMP_PROP(DisableDNS, int)
+ SIMP_PROP(LocalFQDNName, uchar*)
+ SIMP_PROP(LocalHostName, uchar*)
+ SIMP_PROP(LocalDomain, uchar*)
+ SIMP_PROP(StripDomains, char**)
+ SIMP_PROP(LocalHosts, char**)
+ SIMP_PROP(DfltNetstrmDrvr, uchar*)
+ SIMP_PROP(DfltNetstrmDrvrCAF, uchar*)
+ SIMP_PROP(DfltNetstrmDrvrKeyFile, uchar*)
+ SIMP_PROP(DfltNetstrmDrvrCertFile, uchar*)
+ /* added v3, 2009-06-30 */
+ rsRetVal (*GenerateLocalHostNameProperty)(void);
+ prop_t* (*GetLocalHostNameProp)(void);
+ /* added v4, 2009-07-20 */
+ int (*GetGlobalInputTermState)(void);
+ void (*SetGlobalInputTermination)(void);
+ /* added v5, 2009-11-03 */
+ SIMP_PROP(ParseHOSTNAMEandTAG, int)
+ /* note: v4, v5 are already used by more recent versions, so we need to skip them! */
+ /* added v6, 2009-11-16 as part of varmojfekoj's "unlimited select()" patch
+ * Note that it must be always present, otherwise the interface would have different
+ * versions depending on compile settings, what is not acceptable.
+ * Use this property with care, it is only truly available if UNLIMITED_SELECT is enabled
+ * (I did not yet further investigate the details, because that code hopefully can be removed
+ * at some later stage).
+ */
+ SIMP_PROP(FdSetSize, int)
+ /* v7: was neeeded to mean v5+v6 - do NOT add anything else for that version! */
+ /* next change is v9! */
+ /* v8 - 2012-03-21 */
+ prop_t* (*GetLocalHostIP)(void);
+ uchar* (*GetSourceIPofLocalClient)(void); /* [ar] */
+ rsRetVal (*SetSourceIPofLocalClient)(uchar*); /* [ar] */
+#undef SIMP_PROP
+ENDinterface(glbl)
+#define glblCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */
+/* version 2 had PreserveFQDN added - rgerhards, 2008-12-08 */
+
+/* the remaining prototypes */
+PROTOTYPEObj(glbl);
+
+static inline pid_t glblGetOurPid(void) { return glbl_ourpid; }
+static inline void glblSetOurPid(pid_t pid) { glbl_ourpid = pid; }
+
+void glblPrepCnf(void);
+void glblProcessCnf(struct cnfobj *o);
+void glblDoneLoadCnf(void);
+
+#endif /* #ifndef GLBL_H_INCLUDED */
diff --git a/runtime/hashtable.c b/runtime/hashtable.c
new file mode 100644
index 00000000..f718bd43
--- /dev/null
+++ b/runtime/hashtable.c
@@ -0,0 +1,323 @@
+/* Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+/* taken from http://www.cl.cam.ac.uk/~cwc22/hashtable/ */
+
+#include "hashtable.h"
+#include "hashtable_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+/*
+Credit for primes table: Aaron Krowne
+ http://br.endernet.org/~akrowne/
+ http://planetmath.org/encyclopedia/GoodHashTablePrimes.html
+*/
+static const unsigned int primes[] = {
+53, 97, 193, 389,
+769, 1543, 3079, 6151,
+12289, 24593, 49157, 98317,
+196613, 393241, 786433, 1572869,
+3145739, 6291469, 12582917, 25165843,
+50331653, 100663319, 201326611, 402653189,
+805306457, 1610612741
+};
+const unsigned int prime_table_length = sizeof(primes)/sizeof(primes[0]);
+
+#define MAX_LOAD_FACTOR 65 /* to get real factor, divide by 100! */
+
+/* compute max load. We use a constant factor of 0.65, but do
+ * everything times 100, so that we do not need floats.
+ */
+static inline unsigned
+getLoadLimit(unsigned size)
+{
+ return (unsigned int) ((unsigned long long) size * MAX_LOAD_FACTOR) / 100;
+}
+
+/*****************************************************************************/
+struct hashtable *
+create_hashtable(unsigned int minsize,
+ unsigned int (*hashf) (void*),
+ int (*eqf) (void*,void*), void (*dest)(void*))
+{
+ struct hashtable *h;
+ unsigned int pindex, size = primes[0];
+ /* Check requested hashtable isn't too large */
+ if (minsize > (1u << 30)) return NULL;
+ /* Enforce size as prime */
+ for (pindex=0; pindex < prime_table_length; pindex++) {
+ if (primes[pindex] > minsize) { size = primes[pindex]; break; }
+ }
+ h = (struct hashtable *)malloc(sizeof(struct hashtable));
+ if (NULL == h) return NULL; /*oom*/
+ h->table = (struct entry **)malloc(sizeof(struct entry*) * size);
+ if (NULL == h->table) { free(h); return NULL; } /*oom*/
+ memset(h->table, 0, size * sizeof(struct entry *));
+ h->tablelength = size;
+ h->primeindex = pindex;
+ h->entrycount = 0;
+ h->hashfn = hashf;
+ h->eqfn = eqf;
+ h->dest = dest;
+ h->loadlimit = getLoadLimit(size);
+ return h;
+}
+
+/*****************************************************************************/
+unsigned int
+hash(struct hashtable *h, void *k)
+{
+ /* Aim to protect against poor hash functions by adding logic here
+ * - logic taken from java 1.4 hashtable source */
+ unsigned int i = h->hashfn(k);
+ i += ~(i << 9);
+ i ^= ((i >> 14) | (i << 18)); /* >>> */
+ i += (i << 4);
+ i ^= ((i >> 10) | (i << 22)); /* >>> */
+ return i;
+}
+
+/*****************************************************************************/
+static int
+hashtable_expand(struct hashtable *h)
+{
+ /* Double the size of the table to accomodate more entries */
+ struct entry **newtable;
+ struct entry *e;
+ struct entry **pE;
+ unsigned int newsize, i, idx;
+ /* Check we're not hitting max capacity */
+ if (h->primeindex == (prime_table_length - 1)) return 0;
+ newsize = primes[++(h->primeindex)];
+
+ newtable = (struct entry **)malloc(sizeof(struct entry*) * newsize);
+ if (NULL != newtable)
+ {
+ memset(newtable, 0, newsize * sizeof(struct entry *));
+ /* This algorithm is not 'stable'. ie. it reverses the list
+ * when it transfers entries between the tables */
+ for (i = 0; i < h->tablelength; i++) {
+ while (NULL != (e = h->table[i])) {
+ h->table[i] = e->next;
+ idx = indexFor(newsize,e->h);
+ e->next = newtable[idx];
+ newtable[idx] = e;
+ }
+ }
+ free(h->table);
+ h->table = newtable;
+ }
+ /* Plan B: realloc instead */
+ else
+ {
+ newtable = (struct entry **)
+ realloc(h->table, newsize * sizeof(struct entry *));
+ if (NULL == newtable) { (h->primeindex)--; return 0; }
+ h->table = newtable;
+ memset(newtable[h->tablelength], 0, newsize - h->tablelength);
+ for (i = 0; i < h->tablelength; i++) {
+ for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) {
+ idx = indexFor(newsize,e->h);
+ if (idx == i)
+ {
+ pE = &(e->next);
+ }
+ else
+ {
+ *pE = e->next;
+ e->next = newtable[idx];
+ newtable[idx] = e;
+ }
+ }
+ }
+ }
+ h->tablelength = newsize;
+ h->loadlimit = getLoadLimit(newsize);
+ return -1;
+}
+
+/*****************************************************************************/
+unsigned int
+hashtable_count(struct hashtable *h)
+{
+ return h->entrycount;
+}
+
+/*****************************************************************************/
+int
+hashtable_insert(struct hashtable *h, void *k, void *v)
+{
+ /* This method allows duplicate keys - but they shouldn't be used */
+ unsigned int idx;
+ struct entry *e;
+ if (++(h->entrycount) > h->loadlimit)
+ {
+ /* Ignore the return value. If expand fails, we should
+ * still try cramming just this value into the existing table
+ * -- we may not have memory for a larger table, but one more
+ * element may be ok. Next time we insert, we'll try expanding again.*/
+ hashtable_expand(h);
+ }
+ e = (struct entry *)malloc(sizeof(struct entry));
+ if (NULL == e) { --(h->entrycount); return 0; } /*oom*/
+ e->h = hash(h,k);
+ idx = indexFor(h->tablelength,e->h);
+ e->k = k;
+ e->v = v;
+ e->next = h->table[idx];
+ h->table[idx] = e;
+ return -1;
+}
+
+/*****************************************************************************/
+void * /* returns value associated with key */
+hashtable_search(struct hashtable *h, void *k)
+{
+ struct entry *e;
+ unsigned int hashvalue, idx;
+ hashvalue = hash(h,k);
+ idx = indexFor(h->tablelength,hashvalue);
+ e = h->table[idx];
+ while (NULL != e)
+ {
+ /* Check hash value to short circuit heavier comparison */
+ if ((hashvalue == e->h) && (h->eqfn(k, e->k))) return e->v;
+ e = e->next;
+ }
+ return NULL;
+}
+
+/*****************************************************************************/
+void * /* returns value associated with key */
+hashtable_remove(struct hashtable *h, void *k)
+{
+ /* TODO: consider compacting the table when the load factor drops enough,
+ * or provide a 'compact' method. */
+
+ struct entry *e;
+ struct entry **pE;
+ void *v;
+ unsigned int hashvalue, idx;
+
+ hashvalue = hash(h,k);
+ idx = indexFor(h->tablelength,hash(h,k));
+ pE = &(h->table[idx]);
+ e = *pE;
+ while (NULL != e)
+ {
+ /* Check hash value to short circuit heavier comparison */
+ if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
+ {
+ *pE = e->next;
+ h->entrycount--;
+ v = e->v;
+ freekey(e->k);
+ free(e);
+ return v;
+ }
+ pE = &(e->next);
+ e = e->next;
+ }
+ return NULL;
+}
+
+/*****************************************************************************/
+/* destroy */
+void
+hashtable_destroy(struct hashtable *h, int free_values)
+{
+ unsigned int i;
+ struct entry *e, *f;
+ struct entry **table = h->table;
+ if (free_values)
+ {
+ for (i = 0; i < h->tablelength; i++)
+ {
+ e = table[i];
+ while (NULL != e)
+ {
+ f = e;
+ e = e->next;
+ freekey(f->k);
+ if(h->dest == NULL)
+ free(f->v);
+ else
+ h->dest(f->v);
+ free(f);
+ }
+ }
+ }
+ else
+ {
+ for (i = 0; i < h->tablelength; i++)
+ {
+ e = table[i];
+ while (NULL != e)
+ { f = e; e = e->next; freekey(f->k); free(f); }
+ }
+ }
+ free(h->table);
+ free(h);
+}
+
+/* some generic hash functions */
+
+/* one provided by Aaaron Wiebe based on perl's hashing algorithm
+ * (so probably pretty generic). Not for excessively large strings!
+ */
+unsigned int
+hash_from_string(void *k)
+{
+ int len;
+ char *rkey = (char*) k;
+ unsigned hashval = 1;
+
+ len = (int) strlen(rkey);
+ while (len--)
+ hashval = hashval * 33 + *rkey++;
+
+ return hashval;
+}
+
+
+int
+key_equals_string(void *key1, void *key2)
+{
+ /* we must return true IF the keys are equal! */
+ return !strcmp(key1, key2);
+}
+
+
+/*
+ * Copyright (c) 2002, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/runtime/hashtable.h b/runtime/hashtable.h
new file mode 100644
index 00000000..f777ad0b
--- /dev/null
+++ b/runtime/hashtable.h
@@ -0,0 +1,202 @@
+/* Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#ifndef __HASHTABLE_CWC22_H__
+#define __HASHTABLE_CWC22_H__
+
+struct hashtable;
+
+/* Example of use:
+ *
+ * struct hashtable *h;
+ * struct some_key *k;
+ * struct some_value *v;
+ *
+ * static unsigned int hash_from_key_fn( void *k );
+ * static int keys_equal_fn ( void *key1, void *key2 );
+ *
+ * h = create_hashtable(16, hash_from_key_fn, keys_equal_fn);
+ * k = (struct some_key *) malloc(sizeof(struct some_key));
+ * v = (struct some_value *) malloc(sizeof(struct some_value));
+ *
+ * (initialise k and v to suitable values)
+ *
+ * if (! hashtable_insert(h,k,v) )
+ * { exit(-1); }
+ *
+ * if (NULL == (found = hashtable_search(h,k) ))
+ * { printf("not found!"); }
+ *
+ * if (NULL == (found = hashtable_remove(h,k) ))
+ * { printf("Not found\n"); }
+ *
+ */
+
+/* Macros may be used to define type-safe(r) hashtable access functions, with
+ * methods specialized to take known key and value types as parameters.
+ *
+ * Example:
+ *
+ * Insert this at the start of your file:
+ *
+ * DEFINE_HASHTABLE_INSERT(insert_some, struct some_key, struct some_value);
+ * DEFINE_HASHTABLE_SEARCH(search_some, struct some_key, struct some_value);
+ * DEFINE_HASHTABLE_REMOVE(remove_some, struct some_key, struct some_value);
+ *
+ * This defines the functions 'insert_some', 'search_some' and 'remove_some'.
+ * These operate just like hashtable_insert etc., with the same parameters,
+ * but their function signatures have 'struct some_key *' rather than
+ * 'void *', and hence can generate compile time errors if your program is
+ * supplying incorrect data as a key (and similarly for value).
+ *
+ * Note that the hash and key equality functions passed to create_hashtable
+ * still take 'void *' parameters instead of 'some key *'. This shouldn't be
+ * a difficult issue as they're only defined and passed once, and the other
+ * functions will ensure that only valid keys are supplied to them.
+ *
+ * The cost for this checking is increased code size and runtime overhead
+ * - if performance is important, it may be worth switching back to the
+ * unsafe methods once your program has been debugged with the safe methods.
+ * This just requires switching to some simple alternative defines - eg:
+ * #define insert_some hashtable_insert
+ *
+ */
+
+/*****************************************************************************
+ * create_hashtable
+
+ * @name create_hashtable
+ * @param minsize minimum initial size of hashtable
+ * @param hashfunction function for hashing keys
+ * @param key_eq_fn function for determining key equality
+ * @param dest destructor for value entries (NULL -> use free())
+ * @return newly created hashtable or NULL on failure
+ */
+
+struct hashtable *
+create_hashtable(unsigned int minsize,
+ unsigned int (*hashfunction) (void*),
+ int (*key_eq_fn) (void*,void*), void (*dest) (void*));
+
+/*****************************************************************************
+ * hashtable_insert
+
+ * @name hashtable_insert
+ * @param h the hashtable to insert into
+ * @param k the key - hashtable claims ownership and will free on removal
+ * @param v the value - does not claim ownership
+ * @return non-zero for successful insertion
+ *
+ * This function will cause the table to expand if the insertion would take
+ * the ratio of entries to table size over the maximum load factor.
+ *
+ * This function does not check for repeated insertions with a duplicate key.
+ * The value returned when using a duplicate key is undefined -- when
+ * the hashtable changes size, the order of retrieval of duplicate key
+ * entries is reversed.
+ * If in doubt, remove before insert.
+ */
+
+int
+hashtable_insert(struct hashtable *h, void *k, void *v);
+
+#define DEFINE_HASHTABLE_INSERT(fnname, keytype, valuetype) \
+int fnname (struct hashtable *h, keytype *k, valuetype *v) \
+{ \
+ return hashtable_insert(h,k,v); \
+}
+
+/*****************************************************************************
+ * hashtable_search
+
+ * @name hashtable_search
+ * @param h the hashtable to search
+ * @param k the key to search for - does not claim ownership
+ * @return the value associated with the key, or NULL if none found
+ */
+
+void *
+hashtable_search(struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_SEARCH(fnname, keytype, valuetype) \
+valuetype * fnname (struct hashtable *h, keytype *k) \
+{ \
+ return (valuetype *) (hashtable_search(h,k)); \
+}
+
+/*****************************************************************************
+ * hashtable_remove
+
+ * @name hashtable_remove
+ * @param h the hashtable to remove the item from
+ * @param k the key to search for - does not claim ownership
+ * @return the value associated with the key, or NULL if none found
+ */
+
+void * /* returns value */
+hashtable_remove(struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_REMOVE(fnname, keytype, valuetype) \
+valuetype * fnname (struct hashtable *h, keytype *k) \
+{ \
+ return (valuetype *) (hashtable_remove(h,k)); \
+}
+
+
+/*****************************************************************************
+ * hashtable_count
+
+ * @name hashtable_count
+ * @param h the hashtable
+ * @return the number of items stored in the hashtable
+ */
+unsigned int
+hashtable_count(struct hashtable *h);
+
+
+/*****************************************************************************
+ * hashtable_destroy
+
+ * @name hashtable_destroy
+ * @param h the hashtable
+ * @param free_values whether to call 'free' on the remaining values
+ */
+
+void
+hashtable_destroy(struct hashtable *h, int free_values);
+
+#endif /* __HASHTABLE_CWC22_H__ */
+
+/*
+ * Copyright (c) 2002, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+unsigned int hash_from_string(void *k) ;
+int key_equals_string(void *key1, void *key2);
diff --git a/runtime/hashtable/Makefile b/runtime/hashtable/Makefile
new file mode 100644
index 00000000..3b7b5e9f
--- /dev/null
+++ b/runtime/hashtable/Makefile
@@ -0,0 +1,26 @@
+
+tester: hashtable.o tester.o hashtable_itr.o
+ gcc -g -Wall -O -lm -o tester hashtable.o hashtable_itr.o tester.o
+
+all: tester old_tester
+
+tester.o: tester.c
+ gcc -g -Wall -O -c tester.c -o tester.o
+
+old_tester: hashtable_powers.o tester.o hashtable_itr.o
+ gcc -g -Wall -O -o old_tester hashtable_powers.o hashtable_itr.o tester.o
+
+hashtable_powers.o: hashtable_powers.c
+ gcc -g -Wall -O -c hashtable_powers.c -o hashtable_powers.o
+
+hashtable.o: hashtable.c
+ gcc -g -Wall -O -c hashtable.c -o hashtable.o
+
+hashtable_itr.o: hashtable_itr.c
+ gcc -g -Wall -O -c hashtable_itr.c -o hashtable_itr.o
+
+tidy:
+ rm *.o
+
+clean: tidy
+ rm -f tester old_tester
diff --git a/runtime/hashtable/README b/runtime/hashtable/README
new file mode 100644
index 00000000..5cadde0c
--- /dev/null
+++ b/runtime/hashtable/README
@@ -0,0 +1,11 @@
+This is the hashtable code provided by
+Christopher Clark <firstname.lastname@cl.cam.ac.uk>
+available at http://www.cl.cam.ac.uk/~cwc22/hashtable/
+
+It may be slightly modified. The plan is to streamline
+the code based on our needs and "really" integrate it into
+the rsyslog runtime library. For the time being, we use it from
+inside this subdirectory. We do not need all files, but I thought
+I keep them together in case we later need something else.
+
+rgerhards, 2010-09-28
diff --git a/runtime/hashtable/hashtable_utility.c b/runtime/hashtable/hashtable_utility.c
new file mode 100644
index 00000000..c3176709
--- /dev/null
+++ b/runtime/hashtable/hashtable_utility.c
@@ -0,0 +1,71 @@
+/* Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#include "hashtable.h"
+#include "hashtable_private.h"
+#include "hashtable_utility.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+/*****************************************************************************/
+/* hashtable_change
+ *
+ * function to change the value associated with a key, where there already
+ * exists a value bound to the key in the hashtable.
+ * Source due to Holger Schemel.
+ *
+ * */
+int
+hashtable_change(struct hashtable *h, void *k, void *v)
+{
+ struct entry *e;
+ unsigned int hashvalue, index;
+ hashvalue = hash(h,k);
+ index = indexFor(h->tablelength,hashvalue);
+ e = h->table[index];
+ while (NULL != e)
+ {
+ /* Check hash value to short circuit heavier comparison */
+ if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
+ {
+ free(e->v);
+ e->v = v;
+ return -1;
+ }
+ e = e->next;
+ }
+ return 0;
+}
+
+/*
+ * Copyright (c) 2002, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/runtime/hashtable/hashtable_utility.h b/runtime/hashtable/hashtable_utility.h
new file mode 100644
index 00000000..56a0ffd1
--- /dev/null
+++ b/runtime/hashtable/hashtable_utility.h
@@ -0,0 +1,55 @@
+/* Copyright (C) 2002 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#ifndef __HASHTABLE_CWC22_UTILITY_H__
+#define __HASHTABLE_CWC22_UTILITY_H__
+
+/*****************************************************************************
+ * hashtable_change
+ *
+ * function to change the value associated with a key, where there already
+ * exists a value bound to the key in the hashtable.
+ * Source due to Holger Schemel.
+ *
+ * @name hashtable_change
+ * @param h the hashtable
+ * @param key
+ * @param value
+ *
+ */
+int
+hashtable_change(struct hashtable *h, void *k, void *v);
+
+#endif /* __HASHTABLE_CWC22_H__ */
+
+/*
+ * Copyright (c) 2002, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/runtime/hashtable/tester.c b/runtime/hashtable/tester.c
new file mode 100644
index 00000000..4678ffa8
--- /dev/null
+++ b/runtime/hashtable/tester.c
@@ -0,0 +1,270 @@
+/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#include "hashtable.h"
+#include "hashtable_itr.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h> /* for memcmp */
+
+static const int ITEM_COUNT = 4000;
+
+typedef unsigned int uint32_t;
+typedef unsigned short uint16_t;
+
+/*****************************************************************************/
+struct key
+{
+ uint32_t one_ip; uint32_t two_ip; uint16_t one_port; uint16_t two_port;
+};
+
+struct value
+{
+ char *id;
+};
+
+DEFINE_HASHTABLE_INSERT(insert_some, struct key, struct value);
+DEFINE_HASHTABLE_SEARCH(search_some, struct key, struct value);
+DEFINE_HASHTABLE_REMOVE(remove_some, struct key, struct value);
+DEFINE_HASHTABLE_ITERATOR_SEARCH(search_itr_some, struct key);
+
+
+/*****************************************************************************/
+static unsigned int
+hashfromkey(void *ky)
+{
+ struct key *k = (struct key *)ky;
+ return (((k->one_ip << 17) | (k->one_ip >> 15)) ^ k->two_ip) +
+ (k->one_port * 17) + (k->two_port * 13 * 29);
+}
+
+static int
+equalkeys(void *k1, void *k2)
+{
+ return (0 == memcmp(k1,k2,sizeof(struct key)));
+}
+
+/*****************************************************************************/
+int
+main(int argc, char **argv)
+{
+ struct key *k, *kk;
+ struct value *v, *found;
+ struct hashtable *h;
+ struct hashtable_itr *itr;
+ int i;
+
+ h = create_hashtable(16, hashfromkey, equalkeys);
+ if (NULL == h) exit(-1); /*oom*/
+
+
+/*****************************************************************************/
+/* Insertion */
+ for (i = 0; i < ITEM_COUNT; i++)
+ {
+ k = (struct key *)malloc(sizeof(struct key));
+ if (NULL == k) {
+ printf("ran out of memory allocating a key\n");
+ return 1;
+ }
+ k->one_ip = 0xcfccee40 + i;
+ k->two_ip = 0xcf0cee67 - (5 * i);
+ k->one_port = 22 + (7 * i);
+ k->two_port = 5522 - (3 * i);
+
+ v = (struct value *)malloc(sizeof(struct value));
+ v->id = "a value";
+
+ if (!insert_some(h,k,v)) exit(-1); /*oom*/
+ }
+ printf("After insertion, hashtable contains %u items.\n",
+ hashtable_count(h));
+
+/*****************************************************************************/
+/* Hashtable search */
+ k = (struct key *)malloc(sizeof(struct key));
+ if (NULL == k) {
+ printf("ran out of memory allocating a key\n");
+ return 1;
+ }
+
+ for (i = 0; i < ITEM_COUNT; i++)
+ {
+ k->one_ip = 0xcfccee40 + i;
+ k->two_ip = 0xcf0cee67 - (5 * i);
+ k->one_port = 22 + (7 * i);
+ k->two_port = 5522 - (3 * i);
+
+ if (NULL == (found = search_some(h,k))) {
+ printf("BUG: key not found\n");
+ }
+ }
+
+/*****************************************************************************/
+/* Hashtable iteration */
+ /* Iterator constructor only returns a valid iterator if
+ * the hashtable is not empty */
+ itr = hashtable_iterator(h);
+ i = 0;
+ if (hashtable_count(h) > 0)
+ {
+ do {
+ kk = hashtable_iterator_key(itr);
+ v = hashtable_iterator_value(itr);
+ /* here (kk,v) are a valid (key, value) pair */
+ /* We could call 'hashtable_remove(h,kk)' - and this operation
+ * 'free's kk. However, the iterator is then broken.
+ * This is why hashtable_iterator_remove exists - see below.
+ */
+ i++;
+
+ } while (hashtable_iterator_advance(itr));
+ }
+ printf("Iterated through %u entries.\n", i);
+
+/*****************************************************************************/
+/* Hashtable iterator search */
+
+ /* Try the search some method */
+ for (i = 0; i < ITEM_COUNT; i++)
+ {
+ k->one_ip = 0xcfccee40 + i;
+ k->two_ip = 0xcf0cee67 - (5 * i);
+ k->one_port = 22 + (7 * i);
+ k->two_port = 5522 - (3 * i);
+
+ if (0 == search_itr_some(itr,h,k)) {
+ printf("BUG: key not found searching with iterator");
+ }
+ }
+
+/*****************************************************************************/
+/* Hashtable removal */
+
+ for (i = 0; i < ITEM_COUNT; i++)
+ {
+ k->one_ip = 0xcfccee40 + i;
+ k->two_ip = 0xcf0cee67 - (5 * i);
+ k->one_port = 22 + (7 * i);
+ k->two_port = 5522 - (3 * i);
+
+ if (NULL == (found = remove_some(h,k))) {
+ printf("BUG: key not found for removal\n");
+ }
+ }
+ printf("After removal, hashtable contains %u items.\n",
+ hashtable_count(h));
+
+/*****************************************************************************/
+/* Hashtable destroy and create */
+
+ hashtable_destroy(h, 1);
+ h = NULL;
+ free(k);
+
+ h = create_hashtable(160, hashfromkey, equalkeys);
+ if (NULL == h) {
+ printf("out of memory allocating second hashtable\n");
+ return 1;
+ }
+
+/*****************************************************************************/
+/* Hashtable insertion */
+
+ for (i = 0; i < ITEM_COUNT; i++)
+ {
+ k = (struct key *)malloc(sizeof(struct key));
+ k->one_ip = 0xcfccee40 + i;
+ k->two_ip = 0xcf0cee67 - (5 * i);
+ k->one_port = 22 + (7 * i);
+ k->two_port = 5522 - (3 * i);
+
+ v = (struct value *)malloc(sizeof(struct value));
+ v->id = "a value";
+
+ if (!insert_some(h,k,v))
+ {
+ printf("out of memory inserting into second hashtable\n");
+ return 1;
+ }
+ }
+ printf("After insertion, hashtable contains %u items.\n",
+ hashtable_count(h));
+
+/*****************************************************************************/
+/* Hashtable iterator search and iterator remove */
+
+ k = (struct key *)malloc(sizeof(struct key));
+ if (NULL == k) {
+ printf("ran out of memory allocating a key\n");
+ return 1;
+ }
+
+ for (i = ITEM_COUNT - 1; i >= 0; i = i - 7)
+ {
+ k->one_ip = 0xcfccee40 + i;
+ k->two_ip = 0xcf0cee67 - (5 * i);
+ k->one_port = 22 + (7 * i);
+ k->two_port = 5522 - (3 * i);
+
+ if (0 == search_itr_some(itr, h, k)) {
+ printf("BUG: key %u not found for search preremoval using iterator\n", i);
+ return 1;
+ }
+ if (0 == hashtable_iterator_remove(itr)) {
+ printf("BUG: key not found for removal using iterator\n");
+ return 1;
+ }
+ }
+ free(itr);
+
+/*****************************************************************************/
+/* Hashtable iterator remove and advance */
+
+ for (itr = hashtable_iterator(h);
+ hashtable_iterator_remove(itr) != 0; ) {
+ ;
+ }
+ free(itr);
+ printf("After removal, hashtable contains %u items.\n",
+ hashtable_count(h));
+
+/*****************************************************************************/
+/* Hashtable destroy */
+
+ hashtable_destroy(h, 1);
+ free(k);
+ return 0;
+}
+
+/*
+ * Copyright (c) 2002, 2004, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/runtime/hashtable_itr.c b/runtime/hashtable_itr.c
new file mode 100644
index 00000000..967287f1
--- /dev/null
+++ b/runtime/hashtable_itr.c
@@ -0,0 +1,190 @@
+/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#include "hashtable.h"
+#include "hashtable_private.h"
+#include "hashtable_itr.h"
+#include <stdlib.h> /* defines NULL */
+
+/*****************************************************************************/
+/* hashtable_iterator - iterator constructor */
+
+struct hashtable_itr *
+hashtable_iterator(struct hashtable *h)
+{
+ unsigned int i, tablelength;
+ struct hashtable_itr *itr = (struct hashtable_itr *)
+ malloc(sizeof(struct hashtable_itr));
+ if (NULL == itr) return NULL;
+ itr->h = h;
+ itr->e = NULL;
+ itr->parent = NULL;
+ tablelength = h->tablelength;
+ itr->index = tablelength;
+ if (0 == h->entrycount) return itr;
+
+ for (i = 0; i < tablelength; i++)
+ {
+ if (NULL != h->table[i])
+ {
+ itr->e = h->table[i];
+ itr->index = i;
+ break;
+ }
+ }
+ return itr;
+}
+
+/*****************************************************************************/
+/* key - return the key of the (key,value) pair at the current position */
+/* value - return the value of the (key,value) pair at the current position */
+
+#if 0 /* these are now inline functions! */
+void *
+hashtable_iterator_key(struct hashtable_itr *i)
+{ return i->e->k; }
+
+void *
+hashtable_iterator_value(struct hashtable_itr *i)
+{ return i->e->v; }
+#endif
+
+/*****************************************************************************/
+/* advance - advance the iterator to the next element
+ * returns zero if advanced to end of table */
+
+int
+hashtable_iterator_advance(struct hashtable_itr *itr)
+{
+ unsigned int j,tablelength;
+ struct entry **table;
+ struct entry *next;
+ if (NULL == itr->e) return 0; /* stupidity check */
+
+ next = itr->e->next;
+ if (NULL != next)
+ {
+ itr->parent = itr->e;
+ itr->e = next;
+ return -1;
+ }
+ tablelength = itr->h->tablelength;
+ itr->parent = NULL;
+ if (tablelength <= (j = ++(itr->index)))
+ {
+ itr->e = NULL;
+ return 0;
+ }
+ table = itr->h->table;
+ while (NULL == (next = table[j]))
+ {
+ if (++j >= tablelength)
+ {
+ itr->index = tablelength;
+ itr->e = NULL;
+ return 0;
+ }
+ }
+ itr->index = j;
+ itr->e = next;
+ return -1;
+}
+
+/*****************************************************************************/
+/* remove - remove the entry at the current iterator position
+ * and advance the iterator, if there is a successive
+ * element.
+ * If you want the value, read it before you remove:
+ * beware memory leaks if you don't.
+ * Returns zero if end of iteration. */
+
+int
+hashtable_iterator_remove(struct hashtable_itr *itr)
+{
+ struct entry *remember_e, *remember_parent;
+ int ret;
+
+ /* Do the removal */
+ if (NULL == (itr->parent))
+ {
+ /* element is head of a chain */
+ itr->h->table[itr->index] = itr->e->next;
+ } else {
+ /* element is mid-chain */
+ itr->parent->next = itr->e->next;
+ }
+ /* itr->e is now outside the hashtable */
+ remember_e = itr->e;
+ itr->h->entrycount--;
+ freekey(remember_e->k);
+
+ /* Advance the iterator, correcting the parent */
+ remember_parent = itr->parent;
+ ret = hashtable_iterator_advance(itr);
+ if (itr->parent == remember_e) { itr->parent = remember_parent; }
+ free(remember_e);
+ return ret;
+}
+
+/*****************************************************************************/
+int /* returns zero if not found */
+hashtable_iterator_search(struct hashtable_itr *itr,
+ struct hashtable *h, void *k)
+{
+ struct entry *e, *parent;
+ unsigned int hashvalue, index;
+
+ hashvalue = hash(h,k);
+ index = indexFor(h->tablelength,hashvalue);
+
+ e = h->table[index];
+ parent = NULL;
+ while (NULL != e)
+ {
+ /* Check hash value to short circuit heavier comparison */
+ if ((hashvalue == e->h) && (h->eqfn(k, e->k)))
+ {
+ itr->index = index;
+ itr->e = e;
+ itr->parent = parent;
+ itr->h = h;
+ return -1;
+ }
+ parent = e;
+ e = e->next;
+ }
+ return 0;
+}
+
+
+/*
+ * Copyright (c) 2002, 2004, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/runtime/hashtable_itr.h b/runtime/hashtable_itr.h
new file mode 100644
index 00000000..1c206b6e
--- /dev/null
+++ b/runtime/hashtable_itr.h
@@ -0,0 +1,112 @@
+/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#ifndef __HASHTABLE_ITR_CWC22__
+#define __HASHTABLE_ITR_CWC22__
+#include "hashtable.h"
+#include "hashtable_private.h" /* needed to enable inlining */
+
+/*****************************************************************************/
+/* This struct is only concrete here to allow the inlining of two of the
+ * accessor functions. */
+struct hashtable_itr
+{
+ struct hashtable *h;
+ struct entry *e;
+ struct entry *parent;
+ unsigned int index;
+};
+
+
+/*****************************************************************************/
+/* hashtable_iterator
+ */
+
+struct hashtable_itr *
+hashtable_iterator(struct hashtable *h);
+
+/*****************************************************************************/
+/* hashtable_iterator_key
+ * - return the value of the (key,value) pair at the current position */
+
+static inline void *
+hashtable_iterator_key(struct hashtable_itr *i)
+{
+ return i->e->k;
+}
+
+/*****************************************************************************/
+/* value - return the value of the (key,value) pair at the current position */
+
+static inline void *
+hashtable_iterator_value(struct hashtable_itr *i)
+{
+ return i->e->v;
+}
+
+/*****************************************************************************/
+/* advance - advance the iterator to the next element
+ * returns zero if advanced to end of table */
+
+int
+hashtable_iterator_advance(struct hashtable_itr *itr);
+
+/*****************************************************************************/
+/* remove - remove current element and advance the iterator to the next element
+ * NB: if you need the value to free it, read it before
+ * removing. ie: beware memory leaks!
+ * returns zero if advanced to end of table */
+
+int
+hashtable_iterator_remove(struct hashtable_itr *itr);
+
+/*****************************************************************************/
+/* search - overwrite the supplied iterator, to point to the entry
+ * matching the supplied key.
+ h points to the hashtable to be searched.
+ * returns zero if not found. */
+int
+hashtable_iterator_search(struct hashtable_itr *itr,
+ struct hashtable *h, void *k);
+
+#define DEFINE_HASHTABLE_ITERATOR_SEARCH(fnname, keytype) \
+int fnname (struct hashtable_itr *i, struct hashtable *h, keytype *k) \
+{ \
+ return (hashtable_iterator_search(i,h,k)); \
+}
+
+
+
+#endif /* __HASHTABLE_ITR_CWC22__*/
+
+/*
+ * Copyright (c) 2002, 2004, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/runtime/hashtable_private.h b/runtime/hashtable_private.h
new file mode 100644
index 00000000..10b82da4
--- /dev/null
+++ b/runtime/hashtable_private.h
@@ -0,0 +1,86 @@
+/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#ifndef __HASHTABLE_PRIVATE_CWC22_H__
+#define __HASHTABLE_PRIVATE_CWC22_H__
+
+#include "hashtable.h"
+
+/*****************************************************************************/
+struct entry
+{
+ void *k, *v;
+ unsigned int h;
+ struct entry *next;
+};
+
+struct hashtable {
+ unsigned int tablelength;
+ struct entry **table;
+ unsigned int entrycount;
+ unsigned int loadlimit;
+ unsigned int primeindex;
+ unsigned int (*hashfn) (void *k);
+ int (*eqfn) (void *k1, void *k2);
+ void (*dest) (void *v); /* destructor for values, if NULL use free() */
+};
+
+/*****************************************************************************/
+unsigned int
+hash(struct hashtable *h, void *k);
+
+/*****************************************************************************/
+/* indexFor */
+static inline unsigned int
+indexFor(unsigned int tablelength, unsigned int hashvalue) {
+ return (hashvalue % tablelength);
+};
+
+/* Only works if tablelength == 2^N */
+/*static inline unsigned int
+indexFor(unsigned int tablelength, unsigned int hashvalue)
+{
+ return (hashvalue & (tablelength - 1u));
+}
+*/
+
+/*****************************************************************************/
+#define freekey(X) free(X)
+/*define freekey(X) ; */
+
+
+/*****************************************************************************/
+
+#endif /* __HASHTABLE_PRIVATE_CWC22_H__*/
+
+/*
+ * Copyright (c) 2002, Christopher Clark
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of the original author; nor the names of any contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
+ * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
diff --git a/runtime/im-helper.h b/runtime/im-helper.h
new file mode 100644
index 00000000..5c58dcd8
--- /dev/null
+++ b/runtime/im-helper.h
@@ -0,0 +1,65 @@
+/* im-helper.h
+ * This file contains helper constructs that save time writing input modules. It
+ * assumes some common field names and plumbing. It is intended to be used together
+ * with module-template.h
+ *
+ * File begun on 2011-05-04 by RGerhards
+ *
+ * Copyright 2011 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#ifndef IM_HELPER_H_INCLUDED
+#define IM_HELPER_H_INCLUDED 1
+
+
+/* The following function provides a complete implementation to check a
+ * ruleset and set the actual ruleset pointer. The macro assumes that
+ * standard field names are used. A functon std_checkRuleset_genErrMsg()
+ * must be defined to generate error messages in case the ruleset cannot
+ * be found.
+ */
+static inline void std_checkRuleset_genErrMsg(modConfData_t *modConf, instanceConf_t *inst);
+static inline rsRetVal
+std_checkRuleset(modConfData_t *modConf, instanceConf_t *inst)
+{
+ ruleset_t *pRuleset;
+ rsRetVal localRet;
+ DEFiRet;
+
+ inst->pBindRuleset = NULL; /* assume default ruleset */
+
+ if(inst->pszBindRuleset == NULL)
+ FINALIZE;
+
+ localRet = ruleset.GetRuleset(modConf->pConf, &pRuleset, inst->pszBindRuleset);
+ if(localRet == RS_RET_NOT_FOUND) {
+ std_checkRuleset_genErrMsg(modConf, inst);
+ }
+ CHKiRet(localRet);
+ inst->pBindRuleset = pRuleset;
+
+finalize_it:
+ RETiRet;
+}
+
+#endif /* #ifndef IM_HELPER_H_INCLUDED */
+
+/* vim:set ai:
+ */
diff --git a/runtime/libgcry.c b/runtime/libgcry.c
new file mode 100644
index 00000000..4772cf47
--- /dev/null
+++ b/runtime/libgcry.c
@@ -0,0 +1,716 @@
+/* gcry.c - rsyslog's libgcrypt based crypto provider
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * We need to store some additional information in support of encryption.
+ * For this, we create a side-file, which is named like the actual log
+ * file, but with the suffix ".encinfo" appended. It contains the following
+ * records:
+ * IV:<hex> The initial vector used at block start. Also indicates start
+ * start of block.
+ * END:<int> The end offset of the block, as uint64_t in decimal notation.
+ * This is used during encryption to know when the current
+ * encryption block ends.
+ * For the current implementation, there must always be an IV record
+ * followed by an END record. Each records is LF-terminated. Record
+ * types can simply be extended in the future by specifying new
+ * types (like "IV") before the colon.
+ * To identify a file as rsyslog encryption info file, it must start with
+ * the line "FILETYPE:rsyslog-enrcyption-info"
+ * There are some size constraints: the recordtype must be 31 bytes at
+ * most and the actual value (between : and LF) must be 1023 bytes at most.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdio.h>
+#include <gcrypt.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "rsyslog.h"
+#include "srUtils.h"
+#include "libgcry.h"
+
+#define READBUF_SIZE 4096 /* size of the read buffer */
+
+static rsRetVal rsgcryBlkBegin(gcryfile gf);
+
+static rsRetVal
+eiWriteRec(gcryfile gf, char *recHdr, size_t lenRecHdr, char *buf, size_t lenBuf)
+{
+ struct iovec iov[3];
+ ssize_t nwritten, towrite;
+ DEFiRet;
+
+ iov[0].iov_base = recHdr;
+ iov[0].iov_len = lenRecHdr;
+ iov[1].iov_base = buf;
+ iov[1].iov_len = lenBuf;
+ iov[2].iov_base = "\n";
+ iov[2].iov_len = 1;
+ towrite = iov[0].iov_len + iov[1].iov_len + iov[2].iov_len;
+ nwritten = writev(gf->fd, iov, sizeof(iov)/sizeof(struct iovec));
+ if(nwritten != towrite) {
+ DBGPRINTF("eiWrite%s: error writing file, towrite %d, "
+ "nwritten %d\n", recHdr, (int) towrite, (int) nwritten);
+ ABORT_FINALIZE(RS_RET_EI_WR_ERR);
+ }
+ DBGPRINTF("encryption info file %s: written %s, len %d\n",
+ recHdr, gf->eiName, (int) nwritten);
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+eiOpenRead(gcryfile gf)
+{
+ DEFiRet;
+ gf->fd = open((char*)gf->eiName, O_RDONLY|O_NOCTTY|O_CLOEXEC);
+ if(gf->fd == -1) {
+ ABORT_FINALIZE(errno == ENOENT ? RS_RET_EI_NO_EXISTS : RS_RET_EI_OPN_ERR);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+eiRead(gcryfile gf)
+{
+ ssize_t nRead;
+ DEFiRet;
+
+ if(gf->readBuf == NULL) {
+ CHKmalloc(gf->readBuf = malloc(READBUF_SIZE));
+ }
+
+ nRead = read(gf->fd, gf->readBuf, READBUF_SIZE);
+ if(nRead <= 0) { /* TODO: provide specific EOF case? */
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ gf->readBufMaxIdx = (int16_t) nRead;
+ gf->readBufIdx = 0;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* returns EOF on any kind of error */
+static int
+eiReadChar(gcryfile gf)
+{
+ int c;
+
+ if(gf->readBufIdx >= gf->readBufMaxIdx) {
+ if(eiRead(gf) != RS_RET_OK) {
+ c = EOF;
+ goto finalize_it;
+ }
+ }
+ c = gf->readBuf[gf->readBufIdx++];
+finalize_it:
+ return c;
+}
+
+
+static rsRetVal
+eiCheckFiletype(gcryfile gf)
+{
+ char hdrBuf[128];
+ size_t toRead, didRead;
+ sbool bNeedClose = 0;
+ DEFiRet;
+
+ if(gf->fd == -1) {
+ bNeedClose = 1;
+ CHKiRet(eiOpenRead(gf));
+ }
+
+ if(Debug) memset(hdrBuf, 0, sizeof(hdrBuf)); /* for dbgprintf below! */
+ toRead = sizeof("FILETYPE:")-1 + sizeof(RSGCRY_FILETYPE_NAME)-1 + 1;
+ didRead = read(gf->fd, hdrBuf, toRead);
+ if(bNeedClose) {
+ close(gf->fd);
+ gf->fd = -1;
+ }
+ DBGPRINTF("eiCheckFiletype read %d bytes: '%s'\n", didRead, hdrBuf);
+ if( didRead != toRead
+ || strncmp(hdrBuf, "FILETYPE:" RSGCRY_FILETYPE_NAME "\n", toRead))
+ iRet = RS_RET_EI_INVLD_FILE;
+finalize_it:
+ RETiRet;
+}
+
+/* rectype/value must be EIF_MAX_*_LEN+1 long!
+ * returns 0 on success or something else on error/EOF
+ */
+static rsRetVal
+eiGetRecord(gcryfile gf, char *rectype, char *value)
+{
+ unsigned short i, j;
+ int c;
+ DEFiRet;
+
+ c = eiReadChar(gf);
+ if(c == EOF) { ABORT_FINALIZE(RS_RET_NO_DATA); }
+ for(i = 0 ; i < EIF_MAX_RECTYPE_LEN ; ++i) {
+ if(c == ':' || c == EOF)
+ break;
+ rectype[i] = c;
+ c = eiReadChar(gf);
+ }
+ if(c != ':') { ABORT_FINALIZE(RS_RET_ERR); }
+ rectype[i] = '\0';
+ j = 0;
+ for(++i ; i < EIF_MAX_VALUE_LEN ; ++i, ++j) {
+ c = eiReadChar(gf);
+ if(c == '\n' || c == EOF)
+ break;
+ value[j] = c;
+ }
+ if(c != '\n') { ABORT_FINALIZE(RS_RET_ERR); }
+ value[j] = '\0';
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+eiGetIV(gcryfile gf, uchar *iv, size_t leniv)
+{
+ char rectype[EIF_MAX_RECTYPE_LEN+1];
+ char value[EIF_MAX_VALUE_LEN+1];
+ size_t valueLen;
+ unsigned short i, j;
+ unsigned char nibble;
+ DEFiRet;
+
+ CHKiRet(eiGetRecord(gf, rectype, value));
+ if(strcmp(rectype, "IV")) {
+ DBGPRINTF("no IV record found when expected, record type "
+ "seen is '%s'\n", rectype);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ valueLen = strlen(value);
+ if(valueLen/2 != leniv) {
+ DBGPRINTF("length of IV is %d, expected %d\n",
+ valueLen/2, leniv);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ for(i = j = 0 ; i < valueLen ; ++i) {
+ if(value[i] >= '0' && value[i] <= '9')
+ nibble = value[i] - '0';
+ else if(value[i] >= 'a' && value[i] <= 'f')
+ nibble = value[i] - 'a' + 10;
+ else {
+ DBGPRINTF("invalid IV '%s'\n", value);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ if(i % 2 == 0)
+ iv[j] = nibble << 4;
+ else
+ iv[j++] |= nibble;
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+eiGetEND(gcryfile gf, off64_t *offs)
+{
+ char rectype[EIF_MAX_RECTYPE_LEN+1];
+ char value[EIF_MAX_VALUE_LEN+1];
+ DEFiRet;
+
+ CHKiRet(eiGetRecord(gf, rectype, value));
+ if(strcmp(rectype, "END")) {
+ DBGPRINTF("no END record found when expected, record type "
+ "seen is '%s'\n", rectype);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ *offs = atoll(value);
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+eiOpenAppend(gcryfile gf)
+{
+ rsRetVal localRet;
+ DEFiRet;
+ localRet = eiCheckFiletype(gf);
+ if(localRet == RS_RET_OK) {
+ gf->fd = open((char*)gf->eiName,
+ O_WRONLY|O_APPEND|O_NOCTTY|O_CLOEXEC, 0600);
+ if(gf->fd == -1) {
+ ABORT_FINALIZE(RS_RET_EI_OPN_ERR);
+ }
+ } else if(localRet == RS_RET_EI_NO_EXISTS) {
+ /* looks like we need to create a new file */
+ gf->fd = open((char*)gf->eiName,
+ O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0600);
+ if(gf->fd == -1) {
+ ABORT_FINALIZE(RS_RET_EI_OPN_ERR);
+ }
+ CHKiRet(eiWriteRec(gf, "FILETYPE:", 9, RSGCRY_FILETYPE_NAME,
+ sizeof(RSGCRY_FILETYPE_NAME)-1));
+ } else {
+ gf->fd = -1;
+ ABORT_FINALIZE(localRet);
+ }
+ DBGPRINTF("encryption info file %s: opened as #%d\n",
+ gf->eiName, gf->fd);
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+eiWriteIV(gcryfile gf, uchar *iv)
+{
+ static const char hexchars[16] =
+ {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
+ unsigned iSrc, iDst;
+ char hex[4096];
+ DEFiRet;
+
+ if(gf->blkLength > sizeof(hex)/2) {
+ DBGPRINTF("eiWriteIV: crypto block len way too large, aborting "
+ "write");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ for(iSrc = iDst = 0 ; iSrc < gf->blkLength ; ++iSrc) {
+ hex[iDst++] = hexchars[iv[iSrc]>>4];
+ hex[iDst++] = hexchars[iv[iSrc]&0x0f];
+ }
+
+ iRet = eiWriteRec(gf, "IV:", 3, hex, gf->blkLength*2);
+finalize_it:
+ RETiRet;
+}
+
+/* we do not return an error state, as we MUST close the file,
+ * no matter what happens.
+ */
+static void
+eiClose(gcryfile gf, off64_t offsLogfile)
+{
+ char offs[21];
+ size_t len;
+ if(gf->fd == -1)
+ return;
+ if(gf->openMode == 'w') {
+ /* 2^64 is 20 digits, so the snprintf buffer is large enough */
+ len = snprintf(offs, sizeof(offs), "%lld", offsLogfile);
+ eiWriteRec(gf, "END:", 4, offs, len);
+ }
+ gcry_cipher_close(gf->chd);
+ free(gf->readBuf);
+ close(gf->fd);
+ gf->fd = -1;
+ DBGPRINTF("encryption info file %s: closed\n", gf->eiName);
+}
+
+/* this returns the number of bytes left inside the block or -1, if the block
+ * size is unbounded. The function automatically handles end-of-block and begins
+ * to read the next block in this case.
+ */
+rsRetVal
+gcryfileGetBytesLeftInBlock(gcryfile gf, ssize_t *left)
+{
+ DEFiRet;
+ if(gf->bytesToBlkEnd == 0) {
+ DBGPRINTF("libgcry: end of current crypto block\n");
+ gcry_cipher_close(gf->chd);
+ CHKiRet(rsgcryBlkBegin(gf));
+ }
+ *left = gf->bytesToBlkEnd;
+finalize_it:
+ // TODO: remove once this code is sufficiently well-proven
+ DBGPRINTF("gcryfileGetBytesLeftInBlock returns %lld, iRet %d\n", (long long) *left, iRet);
+ RETiRet;
+}
+
+/* this is a special functon for use by the rsyslog disk queue subsystem. It
+ * needs to have the capability to delete state when a queue file is rolled
+ * over. This simply generates the file name and deletes it. It must take care
+ * of "all" state files, which currently happens to be a single one.
+ */
+rsRetVal
+gcryfileDeleteState(uchar *logfn)
+{
+ char fn[MAXFNAME+1];
+ DEFiRet;
+ snprintf(fn, sizeof(fn), "%s%s", logfn, ENCINFO_SUFFIX);
+ fn[MAXFNAME] = '\0'; /* be on save side */
+ DBGPRINTF("crypto provider deletes state file '%s' on request\n", fn);
+ unlink(fn);
+ RETiRet;
+}
+
+static rsRetVal
+gcryfileConstruct(gcryctx ctx, gcryfile *pgf, uchar *logfn)
+{
+ char fn[MAXFNAME+1];
+ gcryfile gf;
+ DEFiRet;
+
+ CHKmalloc(gf = calloc(1, sizeof(struct gcryfile_s)));
+ gf->ctx = ctx;
+ gf->fd = -1;
+ snprintf(fn, sizeof(fn), "%s%s", logfn, ENCINFO_SUFFIX);
+ fn[MAXFNAME] = '\0'; /* be on save side */
+ gf->eiName = (uchar*) strdup(fn);
+ *pgf = gf;
+finalize_it:
+ RETiRet;
+}
+
+
+gcryctx
+gcryCtxNew(void)
+{
+ gcryctx ctx;
+ ctx = calloc(1, sizeof(struct gcryctx_s));
+ ctx->algo = GCRY_CIPHER_AES128;
+ ctx->mode = GCRY_CIPHER_MODE_CBC;
+ return ctx;
+}
+
+int
+gcryfileDestruct(gcryfile gf, off64_t offsLogfile)
+{
+ int r = 0;
+ if(gf == NULL)
+ goto done;
+
+ DBGPRINTF("libgcry: close file %s\n", gf->eiName);
+ eiClose(gf, offsLogfile);
+ if(gf->bDeleteOnClose) {
+ DBGPRINTF("unlink file '%s' due to bDeleteOnClose set\n", gf->eiName);
+ unlink((char*)gf->eiName);
+ }
+ free(gf->eiName);
+ free(gf);
+done: return r;
+}
+void
+rsgcryCtxDel(gcryctx ctx)
+{
+ if(ctx != NULL) {
+ free(ctx);
+ }
+}
+
+static inline void
+addPadding(gcryfile pF, uchar *buf, size_t *plen)
+{
+ unsigned i;
+ size_t nPad;
+ nPad = (pF->blkLength - *plen % pF->blkLength) % pF->blkLength;
+ DBGPRINTF("libgcry: addPadding %d chars, blkLength %d, mod %d, pad %d\n",
+ *plen, pF->blkLength, *plen % pF->blkLength, nPad);
+ for(i = 0 ; i < nPad ; ++i)
+ buf[(*plen)+i] = 0x00;
+ (*plen)+= nPad;
+}
+
+static inline void
+removePadding(uchar *buf, size_t *plen)
+{
+ unsigned len = (unsigned) *plen;
+ unsigned iSrc, iDst;
+ uchar *frstNUL;
+
+ frstNUL = (uchar*)strchr((char*)buf, 0x00);
+ if(frstNUL == NULL)
+ goto done;
+ iDst = iSrc = frstNUL - buf;
+
+ while(iSrc < len) {
+ if(buf[iSrc] != 0x00)
+ buf[iDst++] = buf[iSrc];
+ ++iSrc;
+ }
+
+ *plen = iDst;
+done: return;
+}
+
+/* returns 0 on succes, positive if key length does not match and key
+ * of return value size is required.
+ */
+int
+rsgcrySetKey(gcryctx ctx, unsigned char *key, uint16_t keyLen)
+{
+ uint16_t reqKeyLen;
+ int r;
+
+ reqKeyLen = gcry_cipher_get_algo_keylen(ctx->algo);
+ if(keyLen != reqKeyLen) {
+ r = reqKeyLen;
+ goto done;
+ }
+ ctx->keyLen = keyLen;
+ ctx->key = malloc(keyLen);
+ memcpy(ctx->key, key, keyLen);
+ r = 0;
+done: return r;
+}
+
+rsRetVal
+rsgcrySetMode(gcryctx ctx, uchar *modename)
+{
+ int mode;
+ DEFiRet;
+
+ mode = rsgcryModename2Mode((char *)modename);
+ if(mode == GCRY_CIPHER_MODE_NONE) {
+ ABORT_FINALIZE(RS_RET_CRY_INVLD_MODE);
+ }
+ ctx->mode = mode;
+finalize_it:
+ RETiRet;
+}
+
+rsRetVal
+rsgcrySetAlgo(gcryctx ctx, uchar *algoname)
+{
+ int algo;
+ DEFiRet;
+
+ algo = rsgcryAlgoname2Algo((char *)algoname);
+ if(algo == GCRY_CIPHER_NONE) {
+ ABORT_FINALIZE(RS_RET_CRY_INVLD_ALGO);
+ }
+ ctx->algo = algo;
+finalize_it:
+ RETiRet;
+}
+
+/* As of some Linux and security expert I spoke to, /dev/urandom
+ * provides very strong random numbers, even if it runs out of
+ * entropy. As far as he knew, this is save for all applications
+ * (and he had good proof that I currently am not permitted to
+ * reproduce). -- rgerhards, 2013-03-04
+ */
+void
+seedIV(gcryfile gf, uchar **iv)
+{
+ int fd;
+
+ *iv = malloc(gf->blkLength); /* do NOT zero-out! */
+ /* if we cannot obtain data from /dev/urandom, we use whatever
+ * is present at the current memory location as random data. Of
+ * course, this is very weak and we should consider a different
+ * option, especially when not running under Linux (for Linux,
+ * unavailability of /dev/urandom is just a theoretic thing, it
+ * will always work...). -- TODO -- rgerhards, 2013-03-06
+ */
+ if((fd = open("/dev/urandom", O_RDONLY)) > 0) {
+ if(read(fd, *iv, gf->blkLength)) {}; /* keep compiler happy */
+ close(fd);
+ }
+}
+
+static inline rsRetVal
+readIV(gcryfile gf, uchar **iv)
+{
+ rsRetVal localRet;
+ DEFiRet;
+
+ if(gf->fd == -1) {
+ while(gf->fd == -1) {
+ localRet = eiOpenRead(gf);
+ if(localRet == RS_RET_EI_NO_EXISTS) {
+ /* wait until it is created */
+ srSleep(0, 10000);
+ } else {
+ CHKiRet(localRet);
+ }
+ }
+ CHKiRet(eiCheckFiletype(gf));
+ }
+ *iv = malloc(gf->blkLength); /* do NOT zero-out! */
+ CHKiRet(eiGetIV(gf, *iv, (size_t) gf->blkLength));
+finalize_it:
+ RETiRet;
+}
+
+/* this tries to read the END record. HOWEVER, no such record may be
+ * present, which is the case if we handle a currently-written to queue
+ * file. On the other hand, the queue file may contain multiple blocks. So
+ * what we do is try to see if there is a block end or not - and set the
+ * status accordingly. Note that once we found no end-of-block, we will never
+ * retry. This is because that case can never happen under current queue
+ * implementations. -- gerhards, 2013-05-16
+ */
+static inline rsRetVal
+readBlkEnd(gcryfile gf)
+{
+ off64_t blkEnd;
+ DEFiRet;
+
+ iRet = eiGetEND(gf, &blkEnd);
+ if(iRet == RS_RET_OK) {
+ gf->bytesToBlkEnd = (ssize_t) blkEnd;
+ } else if(iRet == RS_RET_NO_DATA) {
+ gf->bytesToBlkEnd = -1;
+ } else {
+ FINALIZE;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Read the block begin metadata and set our state variables accordingly. Can also
+ * be used to init the first block in write case.
+ */
+static rsRetVal
+rsgcryBlkBegin(gcryfile gf)
+{
+ gcry_error_t gcryError;
+ uchar *iv = NULL;
+ DEFiRet;
+
+ gcryError = gcry_cipher_open(&gf->chd, gf->ctx->algo, gf->ctx->mode, 0);
+ if (gcryError) {
+ DBGPRINTF("gcry_cipher_open failed: %s/%s\n",
+ gcry_strsource(gcryError), gcry_strerror(gcryError));
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ gcryError = gcry_cipher_setkey(gf->chd, gf->ctx->key, gf->ctx->keyLen);
+ if (gcryError) {
+ DBGPRINTF("gcry_cipher_setkey failed: %s/%s\n",
+ gcry_strsource(gcryError), gcry_strerror(gcryError));
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ if(gf->openMode == 'r') {
+ readIV(gf, &iv);
+ readBlkEnd(gf);
+ } else {
+ seedIV(gf, &iv);
+ }
+
+ gcryError = gcry_cipher_setiv(gf->chd, iv, gf->blkLength);
+ if (gcryError) {
+ DBGPRINTF("gcry_cipher_setiv failed: %s/%s\n",
+ gcry_strsource(gcryError), gcry_strerror(gcryError));
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ if(gf->openMode == 'w') {
+ CHKiRet(eiOpenAppend(gf));
+ CHKiRet(eiWriteIV(gf, iv));
+ }
+finalize_it:
+ free(iv);
+ RETiRet;
+}
+
+rsRetVal
+rsgcryInitCrypt(gcryctx ctx, gcryfile *pgf, uchar *fname, char openMode)
+{
+ gcryfile gf = NULL;
+ DEFiRet;
+
+ CHKiRet(gcryfileConstruct(ctx, &gf, fname));
+ gf->openMode = openMode;
+ gf->blkLength = gcry_cipher_get_algo_blklen(ctx->algo);
+ CHKiRet(rsgcryBlkBegin(gf));
+ *pgf = gf;
+finalize_it:
+ if(iRet != RS_RET_OK && gf != NULL)
+ gcryfileDestruct(gf, -1);
+ RETiRet;
+}
+
+rsRetVal
+rsgcryEncrypt(gcryfile pF, uchar *buf, size_t *len)
+{
+ int gcryError;
+ DEFiRet;
+
+ if(*len == 0)
+ FINALIZE;
+
+ addPadding(pF, buf, len);
+ gcryError = gcry_cipher_encrypt(pF->chd, buf, *len, NULL, 0);
+ if(gcryError) {
+ dbgprintf("gcry_cipher_encrypt failed: %s/%s\n",
+ gcry_strsource(gcryError),
+ gcry_strerror(gcryError));
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* TODO: handle multiple blocks
+ * test-read END record; if present, store offset, else unbounded (current active block)
+ * when decrypting, check if bound is reached. If yes, split into two blocks, get new IV for
+ * second one.
+ */
+rsRetVal
+rsgcryDecrypt(gcryfile pF, uchar *buf, size_t *len)
+{
+ gcry_error_t gcryError;
+ DEFiRet;
+
+ if(pF->bytesToBlkEnd != -1)
+ pF->bytesToBlkEnd -= *len;
+ gcryError = gcry_cipher_decrypt(pF->chd, buf, *len, NULL, 0);
+ if(gcryError) {
+ DBGPRINTF("gcry_cipher_decrypt failed: %s/%s\n",
+ gcry_strsource(gcryError),
+ gcry_strerror(gcryError));
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ removePadding(buf, len);
+ // TODO: remove dbgprintf once things are sufficently stable -- rgerhards, 2013-05-16
+ dbgprintf("libgcry: decrypted, bytesToBlkEnd %lld, buffer is now '%50.50s'\n", (long long) pF->bytesToBlkEnd, buf);
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* module-init dummy for potential later use */
+int
+rsgcryInit(void)
+{
+ return 0;
+}
+
+/* module-deinit dummy for potential later use */
+void
+rsgcryExit(void)
+{
+ return;
+}
diff --git a/runtime/libgcry.h b/runtime/libgcry.h
new file mode 100644
index 00000000..2f700554
--- /dev/null
+++ b/runtime/libgcry.h
@@ -0,0 +1,121 @@
+/* libgcry.h - rsyslog's guardtime support library
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_LIBGCRY_H
+#define INCLUDED_LIBGCRY_H
+#include <stdint.h>
+
+
+struct gcryctx_s {
+ uchar *key;
+ size_t keyLen;
+ int algo;
+ int mode;
+};
+typedef struct gcryctx_s *gcryctx;
+typedef struct gcryfile_s *gcryfile;
+
+/* this describes a file, as far as libgcry is concerned */
+struct gcryfile_s {
+ gcry_cipher_hd_t chd; /* cypher handle */
+ size_t blkLength; /* size of low-level crypto block */
+ uchar *eiName; /* name of .encinfo file */
+ int fd; /* descriptor of .encinfo file (-1 if not open) */
+ char openMode; /* 'r': read, 'w': write */
+ gcryctx ctx;
+ uchar *readBuf;
+ int16_t readBufIdx;
+ int16_t readBufMaxIdx;
+ int8_t bDeleteOnClose; /* for queue support, similar to stream subsys */
+ ssize_t bytesToBlkEnd; /* number of bytes remaining in current crypto block
+ -1 means -> no end (still being writen to, queue files),
+ 0 means -> end of block, new one must be started. */
+};
+
+int gcryGetKeyFromFile(char *fn, char **key, unsigned *keylen);
+int rsgcryInit(void);
+void rsgcryExit(void);
+int rsgcrySetKey(gcryctx ctx, unsigned char *key, uint16_t keyLen);
+rsRetVal rsgcrySetMode(gcryctx ctx, uchar *algoname);
+rsRetVal rsgcrySetAlgo(gcryctx ctx, uchar *modename);
+gcryctx gcryCtxNew(void);
+void rsgcryCtxDel(gcryctx ctx);
+int gcryfileDestruct(gcryfile gf, off64_t offsLogfile);
+rsRetVal rsgcryInitCrypt(gcryctx ctx, gcryfile *pgf, uchar *fname, char openMode);
+rsRetVal rsgcryEncrypt(gcryfile pF, uchar *buf, size_t *len);
+rsRetVal rsgcryDecrypt(gcryfile pF, uchar *buf, size_t *len);
+int gcryGetKeyFromProg(char *cmd, char **key, unsigned *keylen);
+rsRetVal gcryfileDeleteState(uchar *fn);
+rsRetVal gcryfileGetBytesLeftInBlock(gcryfile gf, ssize_t *left);
+
+/* error states */
+#define RSGCRYE_EI_OPEN 1 /* error opening .encinfo file */
+#define RSGCRYE_OOM 4 /* ran out of memory */
+
+#define EIF_MAX_RECTYPE_LEN 31 /* max length of record types */
+#define EIF_MAX_VALUE_LEN 1023 /* max length of value types */
+#define RSGCRY_FILETYPE_NAME "rsyslog-enrcyption-info"
+#define ENCINFO_SUFFIX ".encinfo"
+
+/* Note: gf may validly be NULL, e.g. if file has not yet been opened! */
+static inline void
+gcryfileSetDeleteOnClose(gcryfile gf, int val)
+{
+ if(gf != NULL)
+ gf->bDeleteOnClose = val;
+}
+
+static inline int
+rsgcryAlgoname2Algo(char *algoname) {
+ if(!strcmp((char*)algoname, "3DES")) return GCRY_CIPHER_3DES;
+ if(!strcmp((char*)algoname, "CAST5")) return GCRY_CIPHER_CAST5;
+ if(!strcmp((char*)algoname, "BLOWFISH")) return GCRY_CIPHER_BLOWFISH;
+ if(!strcmp((char*)algoname, "AES128")) return GCRY_CIPHER_AES128;
+ if(!strcmp((char*)algoname, "AES192")) return GCRY_CIPHER_AES192;
+ if(!strcmp((char*)algoname, "AES256")) return GCRY_CIPHER_AES256;
+ if(!strcmp((char*)algoname, "TWOFISH")) return GCRY_CIPHER_TWOFISH;
+ if(!strcmp((char*)algoname, "TWOFISH128")) return GCRY_CIPHER_TWOFISH128;
+ if(!strcmp((char*)algoname, "ARCFOUR")) return GCRY_CIPHER_ARCFOUR;
+ if(!strcmp((char*)algoname, "DES")) return GCRY_CIPHER_DES;
+ if(!strcmp((char*)algoname, "SERPENT128")) return GCRY_CIPHER_SERPENT128;
+ if(!strcmp((char*)algoname, "SERPENT192")) return GCRY_CIPHER_SERPENT192;
+ if(!strcmp((char*)algoname, "SERPENT256")) return GCRY_CIPHER_SERPENT256;
+ if(!strcmp((char*)algoname, "RFC2268_40")) return GCRY_CIPHER_RFC2268_40;
+ if(!strcmp((char*)algoname, "SEED")) return GCRY_CIPHER_SEED;
+ if(!strcmp((char*)algoname, "CAMELLIA128")) return GCRY_CIPHER_CAMELLIA128;
+ if(!strcmp((char*)algoname, "CAMELLIA192")) return GCRY_CIPHER_CAMELLIA192;
+ if(!strcmp((char*)algoname, "CAMELLIA256")) return GCRY_CIPHER_CAMELLIA256;
+ return GCRY_CIPHER_NONE;
+}
+
+static inline int
+rsgcryModename2Mode(char *modename) {
+ if(!strcmp((char*)modename, "ECB")) return GCRY_CIPHER_MODE_ECB;
+ if(!strcmp((char*)modename, "CFB")) return GCRY_CIPHER_MODE_CFB;
+ if(!strcmp((char*)modename, "CBC")) return GCRY_CIPHER_MODE_CBC;
+ if(!strcmp((char*)modename, "STREAM")) return GCRY_CIPHER_MODE_STREAM;
+ if(!strcmp((char*)modename, "OFB")) return GCRY_CIPHER_MODE_OFB;
+ if(!strcmp((char*)modename, "CTR")) return GCRY_CIPHER_MODE_CTR;
+# ifdef GCRY_CIPHER_MODE_AESWRAP
+ if(!strcmp((char*)modename, "AESWRAP")) return GCRY_CIPHER_MODE_AESWRAP;
+# endif
+ return GCRY_CIPHER_MODE_NONE;
+}
+#endif /* #ifndef INCLUDED_LIBGCRY_H */
diff --git a/runtime/libgcry_common.c b/runtime/libgcry_common.c
new file mode 100644
index 00000000..07a524dc
--- /dev/null
+++ b/runtime/libgcry_common.c
@@ -0,0 +1,206 @@
+/* libgcry_common.c
+ * This file hosts functions both being used by the rsyslog runtime as
+ * well as tools who do not use the runtime (so we can maintain the
+ * code at a single place).
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#if HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdio.h>
+#include <gcrypt.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "rsyslog.h" /* we need data typedefs */
+#include "libgcry.h"
+
+
+/* read a key from a key file
+ * @param[out] key - key buffer, must be freed by caller
+ * @param[out] keylen - length of buffer
+ * @returns 0 if OK, something else otherwise (we do not use
+ * iRet as this is also called from non-rsyslog w/o runtime)
+ * The key length is limited to 64KiB to prevent DoS.
+ * Note well: key is a blob, not a C string (NUL may be present!)
+ */
+int
+gcryGetKeyFromFile(char *fn, char **key, unsigned *keylen)
+{
+ struct stat sb;
+ int fd;
+ int r;
+
+ if(stat(fn, &sb) == -1) {
+ r = 1; goto done;
+ }
+ if((sb.st_mode & S_IFMT) != S_IFREG) {
+ r = 2; goto done;
+ }
+ if(sb.st_size > 64*1024) {
+ r = 3; goto done;
+ }
+ if((*key = malloc(sb.st_size)) == NULL) {
+ r = -1; goto done;
+ }
+ if((fd = open(fn, O_RDONLY)) < 0) {
+ r = 4; goto done;
+ }
+ if(read(fd, *key, sb.st_size) != sb.st_size) {
+ r = 5; goto done;
+ }
+ *keylen = sb.st_size;
+ close(fd);
+ r = 0;
+done: return r;
+}
+
+
+/* execute the child process (must be called in child context
+ * after fork).
+ */
+
+static void
+execKeyScript(char *cmd, int pipefd[])
+{
+ char *newargv[] = { NULL };
+ char *newenviron[] = { NULL };
+
+ dup2(pipefd[0], STDIN_FILENO);
+ dup2(pipefd[1], STDOUT_FILENO);
+
+ /* finally exec child */
+fprintf(stderr, "pre execve: %s\n", cmd);
+ execve(cmd, newargv, newenviron);
+ /* switch to?
+ execlp((char*)program, (char*) program, (char*)arg, NULL);
+ */
+
+ /* we should never reach this point, but if we do, we terminate */
+ return;
+}
+
+
+static int
+openPipe(char *cmd, int *fd)
+{
+ int pipefd[2];
+ pid_t cpid;
+ int r;
+
+ if(pipe(pipefd) == -1) {
+ r = 1; goto done;
+ }
+
+ cpid = fork();
+ if(cpid == -1) {
+ r = 1; goto done;
+ }
+
+ if(cpid == 0) {
+ /* we are the child */
+ execKeyScript(cmd, pipefd);
+ exit(1);
+ }
+
+ close(pipefd[1]);
+ *fd = pipefd[0];
+ r = 0;
+done: return r;
+}
+
+
+/* Read a character from the program's output. */
+// TODO: highly unoptimized version, should be used in buffered
+// mode
+static int
+readProgChar(int fd, char *c)
+{
+ int r;
+ if(read(fd, c, 1) != 1) {
+ r = 1; goto done;
+ }
+ r = 0;
+done: return r;
+}
+
+/* Read a line from the script. Line is terminated by LF, which
+ * is NOT put into the buffer.
+ * buf must be 64KiB
+ */
+static int
+readProgLine(int fd, char *buf)
+{
+ char c;
+ int r;
+ unsigned i;
+
+ for(i = 0 ; i < 64*1024 ; ++i) {
+ if((r = readProgChar(fd, &c)) != 0) goto done;
+ if(c == '\n')
+ break;
+ buf[i] = c;
+ };
+ if(i >= 64*1024) {
+ r = 1; goto done;
+ }
+ buf[i] = '\0';
+ r = 0;
+done: return r;
+}
+static int
+readProgKey(int fd, char *buf, unsigned keylen)
+{
+ char c;
+ int r;
+ unsigned i;
+
+ for(i = 0 ; i < keylen ; ++i) {
+ if((r = readProgChar(fd, &c)) != 0) goto done;
+ buf[i] = c;
+ };
+ r = 0;
+done: return r;
+}
+
+int
+gcryGetKeyFromProg(char *cmd, char **key, unsigned *keylen)
+{
+ int r;
+ int fd;
+ char rcvBuf[64*1024];
+
+ if((r = openPipe(cmd, &fd)) != 0) goto done;
+ if((r = readProgLine(fd, rcvBuf)) != 0) goto done;
+ if(strcmp(rcvBuf, "RSYSLOG-KEY-PROVIDER:0")) {
+ r = 2; goto done;
+ }
+ if((r = readProgLine(fd, rcvBuf)) != 0) goto done;
+ *keylen = atoi(rcvBuf);
+ if((*key = malloc(*keylen)) == NULL) {
+ r = -1; goto done;
+ }
+ if((r = readProgKey(fd, *key, *keylen)) != 0) goto done;
+done: return r;
+}
diff --git a/runtime/librsgt.c b/runtime/librsgt.c
new file mode 100644
index 00000000..85fc7742
--- /dev/null
+++ b/runtime/librsgt.c
@@ -0,0 +1,845 @@
+/* librsgt.c - rsyslog's guardtime support library
+ *
+ * Regarding the online algorithm for Merkle tree signing. Expected
+ * calling sequence is:
+ *
+ * sigblkConstruct
+ * for each signature block:
+ * sigblkInit
+ * for each record:
+ * sigblkAddRecord
+ * sigblkFinish
+ * sigblkDestruct
+ *
+ * Obviously, the next call after sigblkFinsh must either be to
+ * sigblkInit or sigblkDestruct (if no more signature blocks are
+ * to be emitted, e.g. on file close). sigblkDestruct saves state
+ * information (most importantly last block hash) and sigblkConstruct
+ * reads (or initilizes if not present) it.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#define MAXFNAME 1024
+
+#include <gt_http.h>
+
+#include "librsgt.h"
+
+typedef unsigned char uchar;
+#ifndef VERSION
+#define VERSION "no-version"
+#endif
+
+
+static void
+reportErr(gtctx ctx, char *errmsg)
+{
+ if(ctx->errFunc == NULL)
+ goto done;
+ ctx->errFunc(ctx->usrptr, (uchar*)errmsg);
+done: return;
+}
+
+static void
+reportGTAPIErr(gtctx ctx, gtfile gf, char *apiname, int ecode)
+{
+ char errbuf[4096];
+ snprintf(errbuf, sizeof(errbuf), "%s[%s:%d]: %s",
+ (gf == NULL) ? (uchar*)"" : gf->sigfilename,
+ apiname, ecode, GT_getErrorString(ecode));
+ errbuf[sizeof(errbuf)-1] = '\0';
+ reportErr(ctx, errbuf);
+}
+
+void
+rsgtsetErrFunc(gtctx ctx, void (*func)(void*, uchar *), void *usrptr)
+{
+ ctx->usrptr = usrptr;
+ ctx->errFunc = func;
+}
+
+imprint_t *
+rsgtImprintFromGTDataHash(GTDataHash *hash)
+{
+ imprint_t *imp;
+
+ if((imp = calloc(1, sizeof(imprint_t))) == NULL) {
+ goto done;
+ }
+ imp->hashID = hashIdentifier(hash->algorithm),
+ imp->len = hash->digest_length;
+ if((imp->data = (uint8_t*)malloc(imp->len)) == NULL) {
+ free(imp); imp = NULL; goto done;
+ }
+ memcpy(imp->data, hash->digest, imp->len);
+done: return imp;
+}
+
+void
+rsgtimprintDel(imprint_t *imp)
+{
+ if(imp != NULL) {
+ free(imp->data),
+ free(imp);
+ }
+}
+
+int
+rsgtInit(char *usragent)
+{
+ int r = 0;
+ int ret = GT_OK;
+
+ ret = GT_init();
+ if(ret != GT_OK) {
+ r = 1;
+ goto done;
+ }
+ ret = GTHTTP_init(usragent, 1);
+ if(ret != GT_OK) {
+ r = 1;
+ goto done;
+ }
+done: return r;
+}
+
+void
+rsgtExit(void)
+{
+ GTHTTP_finalize();
+ GT_finalize();
+}
+
+
+static inline gtfile
+rsgtfileConstruct(gtctx ctx)
+{
+ gtfile gf;
+ if((gf = calloc(1, sizeof(struct gtfile_s))) == NULL)
+ goto done;
+ gf->ctx = ctx;
+ gf->hashAlg = ctx->hashAlg;
+ gf->blockSizeLimit = ctx->blockSizeLimit;
+ gf->bKeepRecordHashes = ctx->bKeepRecordHashes;
+ gf->bKeepTreeHashes = ctx->bKeepTreeHashes;
+ gf->x_prev = NULL;
+
+done: return gf;
+}
+
+static inline int
+tlvbufPhysWrite(gtfile gf)
+{
+ ssize_t lenBuf;
+ ssize_t iTotalWritten;
+ ssize_t iWritten;
+ char *pWriteBuf;
+ int r = 0;
+
+ lenBuf = gf->tlvIdx;
+ pWriteBuf = gf->tlvBuf;
+ iTotalWritten = 0;
+ do {
+ iWritten = write(gf->fd, pWriteBuf, lenBuf);
+ if(iWritten < 0) {
+ iWritten = 0; /* we have written NO bytes! */
+ if(errno == EINTR) {
+ /*NO ERROR, just continue */;
+ } else {
+ reportErr(gf->ctx, "signature file write error");
+ r = RSGTE_IO;
+ goto finalize_it;
+ }
+ }
+ /* advance buffer to next write position */
+ iTotalWritten += iWritten;
+ lenBuf -= iWritten;
+ pWriteBuf += iWritten;
+ } while(lenBuf > 0); /* Warning: do..while()! */
+
+finalize_it:
+ gf->tlvIdx = 0;
+ return r;
+}
+
+static inline int
+tlvbufChkWrite(gtfile gf)
+{
+ if(gf->tlvIdx == sizeof(gf->tlvBuf)) {
+ return tlvbufPhysWrite(gf);
+ }
+ return 0;
+}
+
+
+/* write to TLV file buffer. If buffer is full, an actual call occurs. Else
+ * output is written only on flush or close.
+ */
+static inline int
+tlvbufAddOctet(gtfile gf, int8_t octet)
+{
+ int r;
+ r = tlvbufChkWrite(gf);
+ if(r != 0) goto done;
+ gf->tlvBuf[gf->tlvIdx++] = octet;
+done: return r;
+}
+static inline int
+tlvbufAddOctetString(gtfile gf, uint8_t *octet, int size)
+{
+ int i, r = 0;
+ for(i = 0 ; i < size ; ++i) {
+ r = tlvbufAddOctet(gf, octet[i]);
+ if(r != 0) goto done;
+ }
+done: return r;
+}
+/* return the actual length in to-be-written octets of an integer */
+static inline uint8_t
+tlvbufGetInt64OctetSize(uint64_t val)
+{
+ if(val >> 56)
+ return 8;
+ if((val >> 48) & 0xff)
+ return 7;
+ if((val >> 40) & 0xff)
+ return 6;
+ if((val >> 32) & 0xff)
+ return 5;
+ if((val >> 24) & 0xff)
+ return 4;
+ if((val >> 16) & 0xff)
+ return 3;
+ if((val >> 8) & 0xff)
+ return 2;
+ return 1;
+}
+static inline int
+tlvbufAddInt64(gtfile gf, uint64_t val)
+{
+ uint8_t doWrite = 0;
+ int r;
+ if(val >> 56) {
+ r = tlvbufAddOctet(gf, (val >> 56) & 0xff), doWrite = 1;
+ if(r != 0) goto done;
+ }
+ if(doWrite || ((val >> 48) & 0xff)) {
+ r = tlvbufAddOctet(gf, (val >> 48) & 0xff), doWrite = 1;
+ if(r != 0) goto done;
+ }
+ if(doWrite || ((val >> 40) & 0xff)) {
+ r = tlvbufAddOctet(gf, (val >> 40) & 0xff), doWrite = 1;
+ if(r != 0) goto done;
+ }
+ if(doWrite || ((val >> 32) & 0xff)) {
+ r = tlvbufAddOctet(gf, (val >> 32) & 0xff), doWrite = 1;
+ if(r != 0) goto done;
+ }
+ if(doWrite || ((val >> 24) & 0xff)) {
+ r = tlvbufAddOctet(gf, (val >> 24) & 0xff), doWrite = 1;
+ if(r != 0) goto done;
+ }
+ if(doWrite || ((val >> 16) & 0xff)) {
+ r = tlvbufAddOctet(gf, (val >> 16) & 0xff), doWrite = 1;
+ if(r != 0) goto done;
+ }
+ if(doWrite || ((val >> 8) & 0xff)) {
+ r = tlvbufAddOctet(gf, (val >> 8) & 0xff), doWrite = 1;
+ if(r != 0) goto done;
+ }
+ r = tlvbufAddOctet(gf, val & 0xff);
+done: return r;
+}
+
+
+int
+tlv8Write(gtfile gf, int flags, int tlvtype, int len)
+{
+ int r;
+ r = tlvbufAddOctet(gf, (flags << 5)|tlvtype);
+ if(r != 0) goto done;
+ r = tlvbufAddOctet(gf, len & 0xff);
+done: return r;
+}
+
+int
+tlv16Write(gtfile gf, int flags, int tlvtype, uint16_t len)
+{
+ uint16_t typ;
+ int r;
+ typ = ((flags|1) << 15)|tlvtype;
+ r = tlvbufAddOctet(gf, typ >> 8);
+ if(r != 0) goto done;
+ r = tlvbufAddOctet(gf, typ & 0xff);
+ if(r != 0) goto done;
+ r = tlvbufAddOctet(gf, (len >> 8) & 0xff);
+ if(r != 0) goto done;
+ r = tlvbufAddOctet(gf, len & 0xff);
+done: return r;
+}
+
+int
+tlvFlush(gtfile gf)
+{
+ return (gf->tlvIdx == 0) ? 0 : tlvbufPhysWrite(gf);
+}
+
+int
+tlvWriteHash(gtfile gf, uint16_t tlvtype, GTDataHash *rec)
+{
+ unsigned tlvlen;
+ int r;
+ tlvlen = 1 + rec->digest_length;
+ r = tlv16Write(gf, 0x00, tlvtype, tlvlen);
+ if(r != 0) goto done;
+ r = tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg));
+ if(r != 0) goto done;
+ r = tlvbufAddOctetString(gf, rec->digest, rec->digest_length);
+done: return r;
+}
+
+int
+tlvWriteBlockSig(gtfile gf, uchar *der, uint16_t lenDer)
+{
+ unsigned tlvlen;
+ uint8_t tlvlenRecords;
+ int r;
+
+ tlvlenRecords = tlvbufGetInt64OctetSize(gf->nRecords);
+ tlvlen = 2 + 1 /* hash algo TLV */ +
+ 2 + hashOutputLengthOctets(gf->hashAlg) /* iv */ +
+ 2 + 1 + gf->lenBlkStrtHash /* last hash */ +
+ 2 + tlvlenRecords /* rec-count */ +
+ 4 + lenDer /* rfc-3161 */;
+ /* write top-level TLV object (block-sig */
+ r = tlv16Write(gf, 0x00, 0x0902, tlvlen);
+ if(r != 0) goto done;
+ /* and now write the children */
+ //FIXME: flags???
+ /* hash-algo */
+ r = tlv8Write(gf, 0x00, 0x00, 1);
+ if(r != 0) goto done;
+ r = tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg));
+ if(r != 0) goto done;
+ /* block-iv */
+ r = tlv8Write(gf, 0x00, 0x01, hashOutputLengthOctets(gf->hashAlg));
+ if(r != 0) goto done;
+ r = tlvbufAddOctetString(gf, gf->IV, hashOutputLengthOctets(gf->hashAlg));
+ if(r != 0) goto done;
+ /* last-hash */
+ r = tlv8Write(gf, 0x00, 0x02, gf->lenBlkStrtHash+1);
+ if(r != 0) goto done;
+ r = tlvbufAddOctet(gf, hashIdentifier(gf->hashAlg));
+ if(r != 0) goto done;
+ r = tlvbufAddOctetString(gf, gf->blkStrtHash, gf->lenBlkStrtHash);
+ if(r != 0) goto done;
+ /* rec-count */
+ r = tlv8Write(gf, 0x00, 0x03, tlvlenRecords);
+ if(r != 0) goto done;
+ r = tlvbufAddInt64(gf, gf->nRecords);
+ if(r != 0) goto done;
+ /* rfc-3161 */
+ r = tlv16Write(gf, 0x00, 0x906, lenDer);
+ if(r != 0) goto done;
+ r = tlvbufAddOctetString(gf, der, lenDer);
+done: return r;
+}
+
+/* support for old platforms - graceful degrade */
+#ifndef O_CLOEXEC
+#define O_CLOEXEC 0
+#endif
+/* read rsyslog log state file; if we cannot access it or the
+ * contents looks invalid, we flag it as non-present (and thus
+ * begin a new hash chain).
+ * The context is initialized accordingly.
+ */
+static void
+readStateFile(gtfile gf)
+{
+ int fd;
+ struct rsgtstatefile sf;
+
+ fd = open((char*)gf->statefilename, O_RDONLY|O_NOCTTY|O_CLOEXEC, 0600);
+ if(fd == -1) goto err;
+
+ if(read(fd, &sf, sizeof(sf)) != sizeof(sf)) goto err;
+ if(strncmp(sf.hdr, "GTSTAT10", 8)) goto err;
+
+ gf->lenBlkStrtHash = sf.lenHash;
+ gf->blkStrtHash = calloc(1, gf->lenBlkStrtHash);
+ if(read(fd, gf->blkStrtHash, gf->lenBlkStrtHash)
+ != gf->lenBlkStrtHash) {
+ free(gf->blkStrtHash);
+ goto err;
+ }
+return;
+
+err:
+ gf->lenBlkStrtHash = hashOutputLengthOctets(gf->hashAlg);
+ gf->blkStrtHash = calloc(1, gf->lenBlkStrtHash);
+}
+
+/* persist all information that we need to re-open and append
+ * to a log signature file.
+ */
+static void
+writeStateFile(gtfile gf)
+{
+ int fd;
+ struct rsgtstatefile sf;
+
+ fd = open((char*)gf->statefilename,
+ O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0600);
+ if(fd == -1)
+ goto done;
+
+ memcpy(sf.hdr, "GTSTAT10", 8);
+ sf.hashID = hashIdentifier(gf->hashAlg);
+ sf.lenHash = gf->x_prev->len;
+ /* if the write fails, we cannot do anything against that. We check
+ * the condition just to keep the compiler happy.
+ */
+ if(write(fd, &sf, sizeof(sf))){};
+ if(write(fd, gf->x_prev->data, gf->x_prev->len)){};
+ close(fd);
+done: return;
+}
+
+
+int
+tlvClose(gtfile gf)
+{
+ int r;
+ r = tlvFlush(gf);
+ close(gf->fd);
+ gf->fd = -1;
+ writeStateFile(gf);
+ return r;
+}
+
+
+/* note: if file exists, the last hash for chaining must
+ * be read from file.
+ */
+int
+tlvOpen(gtfile gf, char *hdr, unsigned lenHdr)
+{
+ int r = 0;
+ gf->fd = open((char*)gf->sigfilename,
+ O_WRONLY|O_APPEND|O_NOCTTY|O_CLOEXEC, 0600);
+ if(gf->fd == -1) {
+ /* looks like we need to create a new file */
+ gf->fd = open((char*)gf->sigfilename,
+ O_WRONLY|O_CREAT|O_NOCTTY|O_CLOEXEC, 0600);
+ if(gf->fd == -1) {
+ r = RSGTE_IO;
+ goto done;
+ }
+ memcpy(gf->tlvBuf, hdr, lenHdr);
+ gf->tlvIdx = lenHdr;
+ } else {
+ gf->tlvIdx = 0; /* header already present! */
+ }
+ /* we now need to obtain the last previous hash, so that
+ * we can continue the hash chain. We do not check for error
+ * as a state file error can be recovered by graceful degredation.
+ */
+ readStateFile(gf);
+done: return r;
+}
+
+/*
+ * As of some Linux and security expert I spoke to, /dev/urandom
+ * provides very strong random numbers, even if it runs out of
+ * entropy. As far as he knew, this is save for all applications
+ * (and he had good proof that I currently am not permitted to
+ * reproduce). -- rgerhards, 2013-03-04
+ */
+void
+seedIV(gtfile gf)
+{
+ int hashlen;
+ int fd;
+
+ hashlen = hashOutputLengthOctets(gf->hashAlg);
+ gf->IV = malloc(hashlen); /* do NOT zero-out! */
+ /* if we cannot obtain data from /dev/urandom, we use whatever
+ * is present at the current memory location as random data. Of
+ * course, this is very weak and we should consider a different
+ * option, especially when not running under Linux (for Linux,
+ * unavailability of /dev/urandom is just a theoretic thing, it
+ * will always work...). -- TODO -- rgerhards, 2013-03-06
+ */
+ if((fd = open("/dev/urandom", O_RDONLY)) > 0) {
+ if(read(fd, gf->IV, hashlen)) {}; /* keep compiler happy */
+ close(fd);
+ }
+}
+
+gtctx
+rsgtCtxNew(void)
+{
+ gtctx ctx;
+ ctx = calloc(1, sizeof(struct gtctx_s));
+ ctx->hashAlg = GT_HASHALG_SHA256;
+ ctx->errFunc = NULL;
+ ctx->usrptr = NULL;
+ ctx->timestamper = strdup(
+ "http://stamper.guardtime.net/gt-signingservice");
+ return ctx;
+}
+
+/* either returns gtfile object or NULL if something went wrong */
+gtfile
+rsgtCtxOpenFile(gtctx ctx, unsigned char *logfn)
+{
+ gtfile gf;
+ char fn[MAXFNAME+1];
+
+ if((gf = rsgtfileConstruct(ctx)) == NULL)
+ goto done;
+
+ snprintf(fn, sizeof(fn), "%s.gtsig", logfn);
+ fn[MAXFNAME] = '\0'; /* be on save side */
+ gf->sigfilename = (uchar*) strdup(fn);
+ snprintf(fn, sizeof(fn), "%s.gtstate", logfn);
+ fn[MAXFNAME] = '\0'; /* be on save side */
+ gf->statefilename = (uchar*) strdup(fn);
+ if(tlvOpen(gf, LOGSIGHDR, sizeof(LOGSIGHDR)-1) != 0) {
+ reportErr(ctx, "signature file open failed");
+ gf = NULL;
+ }
+done: return gf;
+}
+
+
+/* returns 0 on succes, 1 if algo is unknown */
+int
+rsgtSetHashFunction(gtctx ctx, char *algName)
+{
+ int r = 0;
+ if(!strcmp(algName, "SHA2-256"))
+ ctx->hashAlg = GT_HASHALG_SHA256;
+ else if(!strcmp(algName, "SHA2-384"))
+ ctx->hashAlg = GT_HASHALG_SHA384;
+ else if(!strcmp(algName, "SHA2-512"))
+ ctx->hashAlg = GT_HASHALG_SHA512;
+ else if(!strcmp(algName, "SHA1"))
+ ctx->hashAlg = GT_HASHALG_SHA1;
+ else if(!strcmp(algName, "RIPEMD-160"))
+ ctx->hashAlg = GT_HASHALG_RIPEMD160;
+ else if(!strcmp(algName, "SHA2-224"))
+ ctx->hashAlg = GT_HASHALG_SHA224;
+ else
+ r = 1;
+ return r;
+}
+
+int
+rsgtfileDestruct(gtfile gf)
+{
+ int r = 0;
+ if(gf == NULL)
+ goto done;
+
+ if(!gf->disabled && gf->bInBlk) {
+ r = sigblkFinish(gf);
+ if(r != 0) gf->disabled = 1;
+ }
+ if(!gf->disabled)
+ r = tlvClose(gf);
+ free(gf->sigfilename);
+ free(gf->statefilename);
+ free(gf->IV);
+ free(gf->blkStrtHash);
+ rsgtimprintDel(gf->x_prev);
+ free(gf);
+done: return r;
+}
+
+void
+rsgtCtxDel(gtctx ctx)
+{
+ if(ctx != NULL) {
+ free(ctx->timestamper);
+ free(ctx);
+ }
+}
+
+/* new sigblk is initialized, but maybe in existing ctx */
+void
+sigblkInit(gtfile gf)
+{
+ if(gf == NULL) goto done;
+ seedIV(gf);
+ memset(gf->roots_valid, 0, sizeof(gf->roots_valid)/sizeof(char));
+ gf->nRoots = 0;
+ gf->nRecords = 0;
+ gf->bInBlk = 1;
+done: return;
+}
+
+
+/* concat: add IV to buffer */
+static inline void
+bufAddIV(gtfile gf, uchar *buf, size_t *len)
+{
+ memcpy(buf+*len, gf->IV, hashOutputLengthOctets(gf->hashAlg));
+ *len += sizeof(gf->IV);
+}
+
+
+/* concat: add imprint to buffer */
+static inline void
+bufAddImprint(gtfile gf, uchar *buf, size_t *len, imprint_t *imp)
+{
+ if(imp == NULL) {
+ /* TODO: how to get the REAL HASH ID? --> add field? */
+ buf[*len] = hashIdentifier(gf->hashAlg);
+ ++(*len);
+ memcpy(buf+*len, gf->blkStrtHash, gf->lenBlkStrtHash);
+ *len += gf->lenBlkStrtHash;
+ } else {
+ buf[*len] = imp->hashID;
+ ++(*len);
+ memcpy(buf+*len, imp->data, imp->len);
+ *len += imp->len;
+ }
+}
+/* concat: add hash to buffer */
+static inline void
+bufAddHash(gtfile gf, uchar *buf, size_t *len, GTDataHash *hash)
+{
+ buf[*len] = hashIdentifier(gf->hashAlg);
+ ++(*len);
+ memcpy(buf+*len, hash->digest, hash->digest_length);
+ *len += hash->digest_length;
+}
+/* concat: add tree level to buffer */
+static inline void
+bufAddLevel(uchar *buf, size_t *len, uint8_t level)
+{
+ memcpy(buf+*len, &level, sizeof(level));
+ *len += sizeof(level);
+}
+
+
+int
+hash_m(gtfile gf, GTDataHash **m)
+{
+ int rgt;
+ uchar concatBuf[16*1024];
+ size_t len = 0;
+ int r = 0;
+
+ bufAddImprint(gf, concatBuf, &len, gf->x_prev);
+ bufAddIV(gf, concatBuf, &len);
+ rgt = GTDataHash_create(gf->hashAlg, concatBuf, len, m);
+ if(rgt != GT_OK) {
+ reportGTAPIErr(gf->ctx, gf, "GTDataHash_create", rgt);
+ r = RSGTE_HASH_CREATE;
+ goto done;
+ }
+done: return r;
+}
+
+int
+hash_r(gtfile gf, GTDataHash **r, const uchar *rec, const size_t len)
+{
+ int ret = 0, rgt;
+ rgt = GTDataHash_create(gf->hashAlg, rec, len, r);
+ if(rgt != GT_OK) {
+ reportGTAPIErr(gf->ctx, gf, "GTDataHash_create", rgt);
+ ret = RSGTE_HASH_CREATE;
+ goto done;
+ }
+done: return ret;
+}
+
+
+int
+hash_node(gtfile gf, GTDataHash **node, GTDataHash *m, GTDataHash *rec,
+ uint8_t level)
+{
+ int r = 0, rgt;
+ uchar concatBuf[16*1024];
+ size_t len = 0;
+
+ bufAddHash(gf, concatBuf, &len, m);
+ bufAddHash(gf, concatBuf, &len, rec);
+ bufAddLevel(concatBuf, &len, level);
+ rgt = GTDataHash_create(gf->hashAlg, concatBuf, len, node);
+ if(rgt != GT_OK) {
+ reportGTAPIErr(gf->ctx, gf, "GTDataHash_create", rgt);
+ r = RSGTE_HASH_CREATE;
+ goto done;
+ }
+done: return r;
+}
+
+
+int
+sigblkAddRecord(gtfile gf, const uchar *rec, const size_t len)
+{
+ GTDataHash *x; /* current hash */
+ GTDataHash *m, *r, *t, *t_del;
+ uint8_t j;
+ int ret = 0;
+
+ if(gf == NULL || gf->disabled) goto done;
+ if((ret = hash_m(gf, &m)) != 0) goto done;
+ if((ret = hash_r(gf, &r, rec, len)) != 0) goto done;
+ if(gf->bKeepRecordHashes)
+ tlvWriteHash(gf, 0x0900, r);
+ if((ret = hash_node(gf, &x, m, r, 1)) != 0) goto done; /* hash leaf */
+ /* persists x here if Merkle tree needs to be persisted! */
+ if(gf->bKeepTreeHashes)
+ tlvWriteHash(gf, 0x0901, x);
+ rsgtimprintDel(gf->x_prev);
+ gf->x_prev = rsgtImprintFromGTDataHash(x);
+ /* add x to the forest as new leaf, update roots list */
+ t = x;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(gf->roots_valid[j] == 0) {
+ gf->roots_hash[j] = t;
+ gf->roots_valid[j] = 1;
+ t = NULL;
+ break;
+ } else if(t != NULL) {
+ /* hash interim node */
+ t_del = t;
+ ret = hash_node(gf, &t, gf->roots_hash[j], t_del, j+2);
+ gf->roots_valid[j] = 0;
+ GTDataHash_free(gf->roots_hash[j]);
+ GTDataHash_free(t_del);
+ if(ret != 0) goto done;
+ if(gf->bKeepTreeHashes)
+ tlvWriteHash(gf, 0x0901, t);
+ }
+ }
+ if(t != NULL) {
+ /* new level, append "at the top" */
+ gf->roots_hash[gf->nRoots] = t;
+ gf->roots_valid[gf->nRoots] = 1;
+ ++gf->nRoots;
+ assert(gf->nRoots < MAX_ROOTS);
+ t = NULL;
+ }
+ ++gf->nRecords;
+
+ /* cleanup (x is cleared as part of the roots array) */
+ GTDataHash_free(m);
+ GTDataHash_free(r);
+
+ if(gf->nRecords == gf->blockSizeLimit) {
+ ret = sigblkFinish(gf);
+ if(ret != 0) goto done;
+ sigblkInit(gf);
+ }
+done:
+ if(ret != 0) {
+ gf->disabled = 1;
+ }
+ return ret;
+}
+
+static int
+timestampIt(gtfile gf, GTDataHash *hash)
+{
+ unsigned char *der = NULL;
+ size_t lenDer;
+ int r = GT_OK;
+ int ret = 0;
+ GTTimestamp *timestamp = NULL;
+
+ /* Get the timestamp. */
+ r = GTHTTP_createTimestampHash(hash, gf->ctx->timestamper, &timestamp);
+
+ if(r != GT_OK) {
+ reportGTAPIErr(gf->ctx, gf, "GTHTTP_createTimestampHash", r);
+ ret = 1;
+ goto done;
+ }
+
+ /* Encode timestamp. */
+ r = GTTimestamp_getDEREncoded(timestamp, &der, &lenDer);
+ if(r != GT_OK) {
+ reportGTAPIErr(gf->ctx, gf, "GTTimestamp_getDEREncoded", r);
+ ret = 1;
+ goto done;
+ }
+
+ tlvWriteBlockSig(gf, der, lenDer);
+
+done:
+ GT_free(der);
+ GTTimestamp_free(timestamp);
+ return ret;
+}
+
+
+int
+sigblkFinish(gtfile gf)
+{
+ GTDataHash *root, *rootDel;
+ int8_t j;
+ int ret = 0;
+
+ if(gf->nRecords == 0)
+ goto done;
+
+ root = NULL;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(root == NULL) {
+ root = gf->roots_valid[j] ? gf->roots_hash[j] : NULL;
+ gf->roots_valid[j] = 0;
+ } else if(gf->roots_valid[j]) {
+ rootDel = root;
+ ret = hash_node(gf, &root, gf->roots_hash[j], rootDel, j+2);
+ gf->roots_valid[j] = 0;
+ GTDataHash_free(gf->roots_hash[j]);
+ GTDataHash_free(rootDel);
+ if(ret != 0) goto done; /* checks hash_node() result! */
+ }
+ }
+ if((ret = timestampIt(gf, root)) != 0) goto done;
+
+ GTDataHash_free(root);
+ free(gf->blkStrtHash);
+ gf->lenBlkStrtHash = gf->x_prev->len;
+ gf->blkStrtHash = malloc(gf->lenBlkStrtHash);
+ memcpy(gf->blkStrtHash, gf->x_prev->data, gf->x_prev->len);
+done:
+ gf->bInBlk = 0;
+ return ret;
+}
diff --git a/runtime/librsgt.h b/runtime/librsgt.h
new file mode 100644
index 00000000..bfcc4628
--- /dev/null
+++ b/runtime/librsgt.h
@@ -0,0 +1,388 @@
+/* librsgt.h - rsyslog's guardtime support library
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_LIBRSGT_H
+#define INCLUDED_LIBRSGT_H
+#include <gt_base.h>
+
+/* Max number of roots inside the forest. This permits blocks of up to
+ * 2^MAX_ROOTS records. We assume that 64 is sufficient for all use
+ * cases ;) [and 64 is not really a waste of memory, so we do not even
+ * try to work with reallocs and such...]
+ */
+#define MAX_ROOTS 64
+#define LOGSIGHDR "LOGSIG10"
+
+/* context for gt calls. This primarily serves as a container for the
+ * config settings. The actual file-specific data is kept in gtfile.
+ */
+struct gtctx_s {
+ enum GTHashAlgorithm hashAlg;
+ uint8_t bKeepRecordHashes;
+ uint8_t bKeepTreeHashes;
+ uint64_t blockSizeLimit;
+ char *timestamper;
+ void (*errFunc)(void *, unsigned char*);
+ void *usrptr; /* for error function */
+};
+typedef struct gtctx_s *gtctx;
+typedef struct gtfile_s *gtfile;
+typedef struct gterrctx_s gterrctx_t;
+typedef struct imprint_s imprint_t;
+typedef struct block_sig_s block_sig_t;
+typedef struct tlvrecord_s tlvrecord_t;
+
+/* this describes a file, as far as librsgt is concerned */
+struct gtfile_s {
+ /* the following data items are mirrored from gtctx to
+ * increase cache hit ratio (they are frequently accesed).
+ */
+ enum GTHashAlgorithm hashAlg;
+ uint8_t bKeepRecordHashes;
+ uint8_t bKeepTreeHashes;
+ /* end mirrored properties */
+ uint8_t disabled; /* permits to disable this file --> set to 1 */
+ uint64_t blockSizeLimit;
+ uint8_t *IV; /* initial value for blinding masks */
+ imprint_t *x_prev; /* last leaf hash (maybe of previous block) --> preserve on term */
+ unsigned char *sigfilename;
+ unsigned char *statefilename;
+ int fd;
+ unsigned char *blkStrtHash; /* last hash from previous block */
+ uint16_t lenBlkStrtHash;
+ uint64_t nRecords; /* current number of records in current block */
+ uint64_t bInBlk; /* are we currently inside a blk --> need to finish on close */
+ int8_t nRoots;
+ /* algo engineering: roots structure is split into two arrays
+ * in order to improve cache hits.
+ */
+ int8_t roots_valid[MAX_ROOTS];
+ GTDataHash *roots_hash[MAX_ROOTS];
+ /* data members for the associated TLV file */
+ char tlvBuf[4096];
+ int tlvIdx; /* current index into tlvBuf */
+ gtctx ctx;
+};
+
+struct tlvrecord_s {
+ uint16_t tlvtype;
+ uint16_t tlvlen;
+ uint8_t hdr[4]; /* the raw header (as persisted to file) */
+ uint8_t lenHdr; /* length of raw header */
+ uint8_t data[64*1024]; /* the actual data part (of length tlvlen) */
+};
+
+/* The following structure describes the "error context" to be used
+ * for verification and similiar reader functions. While verifying,
+ * we need some information (like filenames or block numbers) that
+ * is not readily available from the other objects (or not even known
+ * to librsgt). In order to provide meaningful error messages, this
+ * information must be passed in from the external callers. In order
+ * to centralize information (and make it more manageable), we use
+ * ths error context here, which contains everything needed to
+ * generate good error messages. Members of this structure are
+ * maintained both by library users (the callers) as well as
+ * the library itself. Who does what simply depends on who has
+ * the relevant information.
+ */
+struct gterrctx_s {
+ FILE *fp; /**< file for error messages */
+ char *filename;
+ uint8_t verbose;
+ uint64_t recNumInFile;
+ uint64_t recNum;
+ uint64_t blkNum;
+ uint8_t treeLevel;
+ GTDataHash *computedHash;
+ GTDataHash *lefthash, *righthash; /* hashes to display if tree hash fails */
+ imprint_t *fileHash;
+ int gtstate; /* status from last relevant GT.*() function call */
+ char *errRec;
+ char *frstRecInBlk; /* This holds the first message seen inside the current block */
+};
+
+struct imprint_s {
+ uint8_t hashID;
+ int len;
+ uint8_t *data;
+};
+
+#define SIGID_RFC3161 0
+struct block_sig_s {
+ uint8_t hashID;
+ uint8_t sigID; /* what type of *signature*? */
+ uint8_t *iv;
+ imprint_t lastHash;
+ uint64_t recCount;
+ struct {
+ struct {
+ uint8_t *data;
+ size_t len; /* must be size_t due to GT API! */
+ } der;
+ } sig;
+};
+
+
+/* the following defines the gtstate file record. Currently, this record
+ * is fixed, we may change that over time.
+ */
+struct rsgtstatefile {
+ char hdr[8]; /* must be "GTSTAT10" */
+ uint8_t hashID;
+ uint8_t lenHash;
+ /* after that, the hash value is contained within the file */
+};
+
+/* Flags and record types for TLV handling */
+#define RSGT_FLAG_TLV16 0x20
+
+/* error states */
+#define RSGTE_IO 1 /* any kind of io error */
+#define RSGTE_FMT 2 /* data fromat error */
+#define RSGTE_INVLTYP 3 /* invalid TLV type record (unexcpected at this point) */
+#define RSGTE_OOM 4 /* ran out of memory */
+#define RSGTE_LEN 5 /* error related to length records */
+#define RSGTE_TS_EXTEND 6/* error extending timestamp */
+#define RSGTE_INVLD_RECCNT 7/* mismatch between actual records and records
+ given in block-sig record */
+#define RSGTE_INVLHDR 8/* invalid file header */
+#define RSGTE_EOF 9 /* specific EOF */
+#define RSGTE_MISS_REC_HASH 10 /* record hash missing when expected */
+#define RSGTE_MISS_TREE_HASH 11 /* tree hash missing when expected */
+#define RSGTE_INVLD_REC_HASH 12 /* invalid record hash (failed verification) */
+#define RSGTE_INVLD_TREE_HASH 13 /* invalid tree hash (failed verification) */
+#define RSGTE_INVLD_REC_HASHID 14 /* invalid record hash ID (failed verification) */
+#define RSGTE_INVLD_TREE_HASHID 15 /* invalid tree hash ID (failed verification) */
+#define RSGTE_MISS_BLOCKSIG 16 /* block signature record missing when expected */
+#define RSGTE_INVLD_TIMESTAMP 17 /* RFC3161 timestamp is invalid */
+#define RSGTE_TS_DERDECODE 18 /* error DER-Decoding a timestamp */
+#define RSGTE_TS_DERENCODE 19 /* error DER-Encoding a timestamp */
+#define RSGTE_HASH_CREATE 20 /* error creating a hash */
+
+/* the following function maps RSGTE_* state to a string - must be updated
+ * whenever a new state is added.
+ * Note: it is thread-safe to call this function, as it returns a pointer
+ * into constant memory pool.
+ */
+static inline char *
+RSGTE2String(int err)
+{
+ switch(err) {
+ case 0:
+ return "success";
+ case RSGTE_IO:
+ return "i/o error";
+ case RSGTE_FMT:
+ return "data format error";
+ case RSGTE_INVLTYP:
+ return "invalid/unexpected tlv record type";
+ case RSGTE_OOM:
+ return "out of memory";
+ case RSGTE_LEN:
+ return "length record problem";
+ case RSGTE_TS_EXTEND:
+ return "error extending timestamp";
+ case RSGTE_INVLD_RECCNT:
+ return "mismatch between actual record count and number in block signature record";
+ case RSGTE_INVLHDR:
+ return "invalid file header";
+ case RSGTE_EOF:
+ return "EOF";
+ case RSGTE_MISS_REC_HASH:
+ return "record hash missing";
+ case RSGTE_MISS_TREE_HASH:
+ return "tree hash missing";
+ case RSGTE_INVLD_REC_HASH:
+ return "record hash mismatch";
+ case RSGTE_INVLD_TREE_HASH:
+ return "tree hash mismatch";
+ case RSGTE_INVLD_REC_HASHID:
+ return "invalid record hash ID";
+ case RSGTE_INVLD_TREE_HASHID:
+ return "invalid tree hash ID";
+ case RSGTE_MISS_BLOCKSIG:
+ return "missing block signature record";
+ case RSGTE_INVLD_TIMESTAMP:
+ return "RFC3161 timestamp invalid";
+ case RSGTE_TS_DERDECODE:
+ return "error DER-decoding RFC3161 timestamp";
+ case RSGTE_TS_DERENCODE:
+ return "error DER-encoding RFC3161 timestamp";
+ case RSGTE_HASH_CREATE:
+ return "error creating hash";
+ default:
+ return "unknown error";
+ }
+}
+
+
+static inline uint16_t
+hashOutputLengthOctets(uint8_t hashID)
+{
+ switch(hashID) {
+ case GT_HASHALG_SHA1: /* paper: SHA1 */
+ return 20;
+ case GT_HASHALG_RIPEMD160: /* paper: RIPEMD-160 */
+ return 20;
+ case GT_HASHALG_SHA224: /* paper: SHA2-224 */
+ return 28;
+ case GT_HASHALG_SHA256: /* paper: SHA2-256 */
+ return 32;
+ case GT_HASHALG_SHA384: /* paper: SHA2-384 */
+ return 48;
+ case GT_HASHALG_SHA512: /* paper: SHA2-512 */
+ return 64;
+ default:return 32;
+ }
+}
+
+static inline uint8_t
+hashIdentifier(enum GTHashAlgorithm hashID)
+{
+ switch(hashID) {
+ case GT_HASHALG_SHA1: /* paper: SHA1 */
+ return 0x00;
+ case GT_HASHALG_RIPEMD160: /* paper: RIPEMD-160 */
+ return 0x02;
+ case GT_HASHALG_SHA224: /* paper: SHA2-224 */
+ return 0x03;
+ case GT_HASHALG_SHA256: /* paper: SHA2-256 */
+ return 0x01;
+ case GT_HASHALG_SHA384: /* paper: SHA2-384 */
+ return 0x04;
+ case GT_HASHALG_SHA512: /* paper: SHA2-512 */
+ return 0x05;
+ default:return 0xff;
+ }
+}
+static inline char *
+hashAlgName(uint8_t hashID)
+{
+ switch(hashID) {
+ case GT_HASHALG_SHA1:
+ return "SHA1";
+ case GT_HASHALG_RIPEMD160:
+ return "RIPEMD-160";
+ case GT_HASHALG_SHA224:
+ return "SHA2-224";
+ case GT_HASHALG_SHA256:
+ return "SHA2-256";
+ case GT_HASHALG_SHA384:
+ return "SHA2-384";
+ case GT_HASHALG_SHA512:
+ return "SHA2-512";
+ default:return "[unknown]";
+ }
+}
+static inline enum GTHashAlgorithm
+hashID2Alg(uint8_t hashID)
+{
+ switch(hashID) {
+ case 0x00:
+ return GT_HASHALG_SHA1;
+ case 0x02:
+ return GT_HASHALG_RIPEMD160;
+ case 0x03:
+ return GT_HASHALG_SHA224;
+ case 0x01:
+ return GT_HASHALG_SHA256;
+ case 0x04:
+ return GT_HASHALG_SHA384;
+ case 0x05:
+ return GT_HASHALG_SHA512;
+ default:
+ return 0xff;
+ }
+}
+static inline char *
+sigTypeName(uint8_t sigID)
+{
+ switch(sigID) {
+ case SIGID_RFC3161:
+ return "RFC3161";
+ default:return "[unknown]";
+ }
+}
+static inline uint16_t
+getIVLen(block_sig_t *bs)
+{
+ return hashOutputLengthOctets(bs->hashID);
+}
+static inline void
+rsgtSetTimestamper(gtctx ctx, char *timestamper)
+{
+ free(ctx->timestamper);
+ ctx->timestamper = strdup(timestamper);
+}
+static inline void
+rsgtSetBlockSizeLimit(gtctx ctx, uint64_t limit)
+{
+ ctx->blockSizeLimit = limit;
+}
+static inline void
+rsgtSetKeepRecordHashes(gtctx ctx, int val)
+{
+ ctx->bKeepRecordHashes = val;
+}
+static inline void
+rsgtSetKeepTreeHashes(gtctx ctx, int val)
+{
+ ctx->bKeepTreeHashes = val;
+}
+
+int rsgtSetHashFunction(gtctx ctx, char *algName);
+int rsgtInit(char *usragent);
+void rsgtExit(void);
+gtctx rsgtCtxNew(void);
+void rsgtsetErrFunc(gtctx ctx, void (*func)(void*, unsigned char *), void *usrptr);
+gtfile rsgtCtxOpenFile(gtctx ctx, unsigned char *logfn);
+int rsgtfileDestruct(gtfile gf);
+void rsgtCtxDel(gtctx ctx);
+void sigblkInit(gtfile gf);
+int sigblkAddRecord(gtfile gf, const unsigned char *rec, const size_t len);
+int sigblkFinish(gtfile gf);
+imprint_t * rsgtImprintFromGTDataHash(GTDataHash *hash);
+void rsgtimprintDel(imprint_t *imp);
+/* reader functions */
+int rsgt_tlvrdHeader(FILE *fp, unsigned char *hdr);
+int rsgt_tlvrd(FILE *fp, tlvrecord_t *rec, void *obj);
+void rsgt_tlvprint(FILE *fp, uint16_t tlvtype, void *obj, uint8_t verbose);
+void rsgt_printBLOCK_SIG(FILE *fp, block_sig_t *bs, uint8_t verbose);
+int rsgt_getBlockParams(FILE *fp, uint8_t bRewind, block_sig_t **bs, uint8_t *bHasRecHashes, uint8_t *bHasIntermedHashes);
+int rsgt_chkFileHdr(FILE *fp, char *expect);
+gtfile rsgt_vrfyConstruct_gf(void);
+void rsgt_vrfyBlkInit(gtfile gf, block_sig_t *bs, uint8_t bHasRecHashes, uint8_t bHasIntermedHashes);
+int rsgt_vrfy_nextRec(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp, unsigned char *rec, size_t len, gterrctx_t *ectx);
+int verifyBLOCK_SIG(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp, uint8_t bExtend, gterrctx_t *ectx);
+void rsgt_errctxInit(gterrctx_t *ectx);
+void rsgt_errctxExit(gterrctx_t *ectx);
+void rsgt_errctxSetErrRec(gterrctx_t *ectx, char *rec);
+void rsgt_errctxFrstRecInBlk(gterrctx_t *ectx, char *rec);
+void rsgt_objfree(uint16_t tlvtype, void *obj);
+
+
+/* TODO: replace these? */
+int hash_m(gtfile gf, GTDataHash **m);
+int hash_r(gtfile gf, GTDataHash **r, const unsigned char *rec, const size_t len);
+int hash_node(gtfile gf, GTDataHash **node, GTDataHash *m, GTDataHash *r, uint8_t level);
+extern char *rsgt_read_puburl; /**< url of publication server */
+extern uint8_t rsgt_read_showVerified;
+
+#endif /* #ifndef INCLUDED_LIBRSGT_H */
diff --git a/runtime/librsgt_read.c b/runtime/librsgt_read.c
new file mode 100644
index 00000000..a6e33160
--- /dev/null
+++ b/runtime/librsgt_read.c
@@ -0,0 +1,1092 @@
+/* librsgt_read.c - rsyslog's guardtime support library
+ * This includes functions used for reading signature (and
+ * other related) files. Well, actually it also contains
+ * some writing functionality, but only as far as rsyslog
+ * itself is not concerned, but "just" the utility programs.
+ *
+ * This part of the library uses C stdio and expects that the
+ * caller will open and close the file to be read itself.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <gt_http.h>
+
+#include "librsgt.h"
+
+typedef unsigned char uchar;
+#ifndef VERSION
+#define VERSION "no-version"
+#endif
+#define MAXFNAME 1024
+
+static int rsgt_read_debug = 0;
+char *rsgt_read_puburl = "http://verify.guardtime.com/gt-controlpublications.bin";
+char *rsgt_extend_puburl = "http://verifier.guardtime.net/gt-extendingservice";
+uint8_t rsgt_read_showVerified = 0;
+
+/* macro to obtain next char from file including error tracking */
+#define NEXTC if((c = fgetc(fp)) == EOF) { \
+ r = feof(fp) ? RSGTE_EOF : RSGTE_IO; \
+ goto done; \
+ }
+
+/* check return state of operation and abort, if non-OK */
+#define CHKr(code) if((r = code) != 0) goto done
+
+
+/* if verbose==0, only the first and last two octets are shown,
+ * otherwise everything.
+ */
+static void
+outputHexBlob(FILE *fp, uint8_t *blob, uint16_t len, uint8_t verbose)
+{
+ unsigned i;
+ if(verbose || len <= 8) {
+ for(i = 0 ; i < len ; ++i)
+ fprintf(fp, "%2.2x", blob[i]);
+ } else {
+ fprintf(fp, "%2.2x%2.2x%2.2x[...]%2.2x%2.2x%2.2x",
+ blob[0], blob[1], blob[2],
+ blob[len-3], blob[len-2], blob[len-1]);
+ }
+}
+
+static inline void
+outputHash(FILE *fp, char *hdr, uint8_t *data, uint16_t len, uint8_t verbose)
+{
+ fprintf(fp, "%s", hdr);
+ outputHexBlob(fp, data, len, verbose);
+ fputc('\n', fp);
+}
+
+void
+rsgt_errctxInit(gterrctx_t *ectx)
+{
+ ectx->fp = NULL;
+ ectx->filename = NULL;
+ ectx->recNum = 0;
+ ectx->gtstate = 0;
+ ectx->recNumInFile = 0;
+ ectx->blkNum = 0;
+ ectx->verbose = 0;
+ ectx->errRec = NULL;
+ ectx->frstRecInBlk = NULL;
+ ectx->fileHash = NULL;
+ ectx->lefthash = ectx->righthash = ectx->computedHash = NULL;
+}
+void
+rsgt_errctxExit(gterrctx_t *ectx)
+{
+ free(ectx->filename);
+ free(ectx->frstRecInBlk);
+}
+
+/* note: we do not copy the record, so the caller MUST not destruct
+ * it before processing of the record is completed. To remove the
+ * current record without setting a new one, call this function
+ * with rec==NULL.
+ */
+void
+rsgt_errctxSetErrRec(gterrctx_t *ectx, char *rec)
+{
+ ectx->errRec = strdup(rec);
+}
+/* This stores the block's first record. Here we copy the data,
+ * as the caller will usually not preserve it long enough.
+ */
+void
+rsgt_errctxFrstRecInBlk(gterrctx_t *ectx, char *rec)
+{
+ free(ectx->frstRecInBlk);
+ ectx->frstRecInBlk = strdup(rec);
+}
+
+static void
+reportError(int errcode, gterrctx_t *ectx)
+{
+ if(ectx->fp != NULL) {
+ fprintf(ectx->fp, "%s[%llu:%llu:%llu]: error[%u]: %s\n",
+ ectx->filename,
+ (long long unsigned) ectx->blkNum, (long long unsigned) ectx->recNum,
+ (long long unsigned) ectx->recNumInFile,
+ errcode, RSGTE2String(errcode));
+ if(ectx->frstRecInBlk != NULL)
+ fprintf(ectx->fp, "\tBlock Start Record.: '%s'\n", ectx->frstRecInBlk);
+ if(ectx->errRec != NULL)
+ fprintf(ectx->fp, "\tRecord in Question.: '%s'\n", ectx->errRec);
+ if(ectx->computedHash != NULL) {
+ outputHash(ectx->fp, "\tComputed Hash......: ", ectx->computedHash->digest,
+ ectx->computedHash->digest_length, ectx->verbose);
+ }
+ if(ectx->fileHash != NULL) {
+ outputHash(ectx->fp, "\tSignature File Hash: ", ectx->fileHash->data,
+ ectx->fileHash->len, ectx->verbose);
+ }
+ if(errcode == RSGTE_INVLD_TREE_HASH ||
+ errcode == RSGTE_INVLD_TREE_HASHID) {
+ fprintf(ectx->fp, "\tTree Level.........: %d\n", (int) ectx->treeLevel);
+ outputHash(ectx->fp, "\tTree Left Hash.....: ", ectx->lefthash->digest,
+ ectx->lefthash->digest_length, ectx->verbose);
+ outputHash(ectx->fp, "\tTree Right Hash....: ", ectx->righthash->digest,
+ ectx->righthash->digest_length, ectx->verbose);
+ }
+ if(errcode == RSGTE_INVLD_TIMESTAMP ||
+ errcode == RSGTE_TS_DERDECODE) {
+ fprintf(ectx->fp, "\tPublication Server.: %s\n", rsgt_read_puburl);
+ fprintf(ectx->fp, "\tGT Verify Timestamp: [%u]%s\n",
+ ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate));
+ }
+ if(errcode == RSGTE_TS_EXTEND ||
+ errcode == RSGTE_TS_DERDECODE) {
+ fprintf(ectx->fp, "\tExtending Server...: %s\n", rsgt_extend_puburl);
+ fprintf(ectx->fp, "\tGT Extend Timestamp: [%u]%s\n",
+ ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate));
+ }
+ if(errcode == RSGTE_TS_DERENCODE) {
+ fprintf(ectx->fp, "\tAPI return state...: [%u]%s\n",
+ ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate));
+ }
+ }
+}
+
+/* obviously, this is not an error-reporting function. We still use
+ * ectx, as it has most information we need.
+ */
+static void
+reportVerifySuccess(gterrctx_t *ectx, GTVerificationInfo *vrfyInf)
+{
+ if(ectx->fp != NULL) {
+ fprintf(ectx->fp, "%s[%llu:%llu:%llu]: block signature successfully verified\n",
+ ectx->filename,
+ (long long unsigned) ectx->blkNum, (long long unsigned) ectx->recNum,
+ (long long unsigned) ectx->recNumInFile);
+ if(ectx->frstRecInBlk != NULL)
+ fprintf(ectx->fp, "\tBlock Start Record.: '%s'\n", ectx->frstRecInBlk);
+ if(ectx->errRec != NULL)
+ fprintf(ectx->fp, "\tBlock End Record...: '%s'\n", ectx->errRec);
+ fprintf(ectx->fp, "\tGT Verify Timestamp: [%u]%s\n",
+ ectx->gtstate, GTHTTP_getErrorString(ectx->gtstate));
+ GTVerificationInfo_print(ectx->fp, 0, vrfyInf);
+ }
+}
+
+/**
+ * Write the provided record to the current file position.
+ *
+ * @param[in] fp file pointer for writing
+ * @param[out] rec tlvrecord to write
+ *
+ * @returns 0 if ok, something else otherwise
+ */
+static int
+rsgt_tlvwrite(FILE *fp, tlvrecord_t *rec)
+{
+ int r = RSGTE_IO;
+ if(fwrite(rec->hdr, (size_t) rec->lenHdr, 1, fp) != 1) goto done;
+ if(fwrite(rec->data, (size_t) rec->tlvlen, 1, fp) != 1) goto done;
+ r = 0;
+done: return r;
+}
+
+/**
+ * Read a header from a binary file.
+ * @param[in] fp file pointer for processing
+ * @param[in] hdr buffer for the header. Must be 9 bytes
+ * (8 for header + NUL byte)
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_tlvrdHeader(FILE *fp, uchar *hdr)
+{
+ int r;
+ if(fread(hdr, 8, 1, fp) != 1) {
+ r = RSGTE_IO;
+ goto done;
+ }
+ hdr[8] = '\0';
+ r = 0;
+done: return r;
+}
+
+/* read type a complete tlv record
+ */
+static int
+rsgt_tlvRecRead(FILE *fp, tlvrecord_t *rec)
+{
+ int r = 1;
+ int c;
+
+ NEXTC;
+ rec->hdr[0] = c;
+ rec->tlvtype = c & 0x1f;
+ if(c & 0x80) { /* tlv16? */
+ rec->lenHdr = 4;
+ NEXTC;
+ rec->hdr[1] = c;
+ rec->tlvtype = (rec->tlvtype << 8) | c;
+ NEXTC;
+ rec->hdr[2] = c;
+ rec->tlvlen = c << 8;
+ NEXTC;
+ rec->hdr[3] = c;
+ rec->tlvlen |= c;
+ } else {
+ NEXTC;
+ rec->lenHdr = 2;
+ rec->hdr[1] = c;
+ rec->tlvlen = c;
+ }
+ if(fread(rec->data, (size_t) rec->tlvlen, 1, fp) != 1) {
+ r = RSGTE_IO;
+ goto done;
+ }
+
+ if(rsgt_read_debug)
+ printf("read tlvtype %4.4x, len %u\n", (unsigned) rec->tlvtype,
+ (unsigned) rec->tlvlen);
+ r = 0;
+done: return r;
+}
+
+/* decode a sub-tlv record from an existing record's memory buffer
+ */
+static int
+rsgt_tlvDecodeSUBREC(tlvrecord_t *rec, uint16_t *stridx, tlvrecord_t *newrec)
+{
+ int r = 1;
+ int c;
+
+ if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;}
+ c = rec->data[(*stridx)++];
+ newrec->hdr[0] = c;
+ newrec->tlvtype = c & 0x1f;
+ if(c & 0x80) { /* tlv16? */
+ newrec->lenHdr = 4;
+ if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;}
+ c = rec->data[(*stridx)++];
+ newrec->hdr[1] = c;
+ newrec->tlvtype = (newrec->tlvtype << 8) | c;
+ if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;}
+ c = rec->data[(*stridx)++];
+ newrec->hdr[2] = c;
+ newrec->tlvlen = c << 8;
+ if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;}
+ c = rec->data[(*stridx)++];
+ newrec->hdr[3] = c;
+ newrec->tlvlen |= c;
+ } else {
+ if(rec->tlvlen == *stridx) {r=RSGTE_LEN; goto done;}
+ c = rec->data[(*stridx)++];
+ newrec->lenHdr = 2;
+ newrec->hdr[1] = c;
+ newrec->tlvlen = c;
+ }
+ if(rec->tlvlen < *stridx + newrec->tlvlen) {r=RSGTE_LEN; goto done;}
+ memcpy(newrec->data, (rec->data)+(*stridx), newrec->tlvlen);
+ *stridx += newrec->tlvlen;
+
+ if(rsgt_read_debug)
+ printf("read sub-tlv: tlvtype %4.4x, len %u\n",
+ (unsigned) newrec->tlvtype,
+ (unsigned) newrec->tlvlen);
+ r = 0;
+done: return r;
+}
+
+
+static int
+rsgt_tlvDecodeIMPRINT(tlvrecord_t *rec, imprint_t **imprint)
+{
+ int r = 1;
+ imprint_t *imp;
+
+ if((imp = calloc(1, sizeof(imprint_t))) == NULL) {
+ r = RSGTE_OOM;
+ goto done;
+ }
+
+ imp->hashID = rec->data[0];
+ if(rec->tlvlen != 1 + hashOutputLengthOctets(imp->hashID)) {
+ r = RSGTE_LEN;
+ goto done;
+ }
+ imp->len = rec->tlvlen - 1;
+ if((imp->data = (uint8_t*)malloc(imp->len)) == NULL) {r=RSGTE_OOM;goto done;}
+ memcpy(imp->data, rec->data+1, imp->len);
+ *imprint = imp;
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvDecodeHASH_ALGO(tlvrecord_t *rec, uint16_t *strtidx, uint8_t *hashAlg)
+{
+ int r = 1;
+ tlvrecord_t subrec;
+
+ CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec));
+ if(!(subrec.tlvtype == 0x00 && subrec.tlvlen == 1)) {
+ r = RSGTE_FMT;
+ goto done;
+ }
+ *hashAlg = subrec.data[0];
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvDecodeBLOCK_IV(tlvrecord_t *rec, uint16_t *strtidx, uint8_t **iv)
+{
+ int r = 1;
+ tlvrecord_t subrec;
+
+ CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec));
+ if(!(subrec.tlvtype == 0x01)) {
+ r = RSGTE_INVLTYP;
+ goto done;
+ }
+ if((*iv = (uint8_t*)malloc(subrec.tlvlen)) == NULL) {r=RSGTE_OOM;goto done;}
+ memcpy(*iv, subrec.data, subrec.tlvlen);
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvDecodeLAST_HASH(tlvrecord_t *rec, uint16_t *strtidx, imprint_t *imp)
+{
+ int r = 1;
+ tlvrecord_t subrec;
+
+ CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec));
+ if(!(subrec.tlvtype == 0x02)) { r = RSGTE_INVLTYP; goto done; }
+ imp->hashID = subrec.data[0];
+ if(subrec.tlvlen != 1 + hashOutputLengthOctets(imp->hashID)) {
+ r = RSGTE_LEN;
+ goto done;
+ }
+ imp->len = subrec.tlvlen - 1;
+ if((imp->data = (uint8_t*)malloc(imp->len)) == NULL) {r=RSGTE_OOM;goto done;}
+ memcpy(imp->data, subrec.data+1, subrec.tlvlen-1);
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvDecodeREC_COUNT(tlvrecord_t *rec, uint16_t *strtidx, uint64_t *cnt)
+{
+ int r = 1;
+ int i;
+ uint64_t val;
+ tlvrecord_t subrec;
+
+ CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec));
+ if(!(subrec.tlvtype == 0x03 && subrec.tlvlen <= 8)) { r = RSGTE_INVLTYP; goto done; }
+ val = 0;
+ for(i = 0 ; i < subrec.tlvlen ; ++i) {
+ val = (val << 8) + subrec.data[i];
+ }
+ *cnt = val;
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvDecodeSIG(tlvrecord_t *rec, uint16_t *strtidx, block_sig_t *bs)
+{
+ int r = 1;
+ tlvrecord_t subrec;
+
+ CHKr(rsgt_tlvDecodeSUBREC(rec, strtidx, &subrec));
+ if(!(subrec.tlvtype == 0x0906)) { r = RSGTE_INVLTYP; goto done; }
+ bs->sig.der.len = subrec.tlvlen;
+ bs->sigID = SIGID_RFC3161;
+ if((bs->sig.der.data = (uint8_t*)malloc(bs->sig.der.len)) == NULL) {r=RSGTE_OOM;goto done;}
+ memcpy(bs->sig.der.data, subrec.data, bs->sig.der.len);
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvDecodeBLOCK_SIG(tlvrecord_t *rec, block_sig_t **blocksig)
+{
+ int r = 1;
+ uint16_t strtidx = 0;
+ block_sig_t *bs;
+ if((bs = calloc(1, sizeof(block_sig_t))) == NULL) {
+ r = RSGTE_OOM;
+ goto done;
+ }
+ CHKr(rsgt_tlvDecodeHASH_ALGO(rec, &strtidx, &(bs->hashID)));
+ CHKr(rsgt_tlvDecodeBLOCK_IV(rec, &strtidx, &(bs->iv)));
+ CHKr(rsgt_tlvDecodeLAST_HASH(rec, &strtidx, &(bs->lastHash)));
+ CHKr(rsgt_tlvDecodeREC_COUNT(rec, &strtidx, &(bs->recCount)));
+ CHKr(rsgt_tlvDecodeSIG(rec, &strtidx, bs));
+ if(strtidx != rec->tlvlen) {
+ r = RSGTE_LEN;
+ goto done;
+ }
+ *blocksig = bs;
+ r = 0;
+done: return r;
+}
+static int
+rsgt_tlvRecDecode(tlvrecord_t *rec, void *obj)
+{
+ int r = 1;
+ switch(rec->tlvtype) {
+ case 0x0900:
+ case 0x0901:
+ r = rsgt_tlvDecodeIMPRINT(rec, obj);
+ if(r != 0) goto done;
+ break;
+ case 0x0902:
+ r = rsgt_tlvDecodeBLOCK_SIG(rec, obj);
+ if(r != 0) goto done;
+ break;
+ }
+done:
+ return r;
+}
+
+static int
+rsgt_tlvrdRecHash(FILE *fp, FILE *outfp, imprint_t **imp)
+{
+ int r;
+ tlvrecord_t rec;
+
+ if((r = rsgt_tlvrd(fp, &rec, imp)) != 0) goto done;
+ if(rec.tlvtype != 0x0900) {
+ r = RSGTE_MISS_REC_HASH;
+ rsgt_objfree(rec.tlvtype, *imp);
+ goto done;
+ }
+ if(outfp != NULL)
+ if((r = rsgt_tlvwrite(outfp, &rec)) != 0) goto done;
+ r = 0;
+done: return r;
+}
+
+static int
+rsgt_tlvrdTreeHash(FILE *fp, FILE *outfp, imprint_t **imp)
+{
+ int r;
+ tlvrecord_t rec;
+
+ if((r = rsgt_tlvrd(fp, &rec, imp)) != 0) goto done;
+ if(rec.tlvtype != 0x0901) {
+ r = RSGTE_MISS_TREE_HASH;
+ rsgt_objfree(rec.tlvtype, *imp);
+ goto done;
+ }
+ if(outfp != NULL)
+ if((r = rsgt_tlvwrite(outfp, &rec)) != 0) goto done;
+ r = 0;
+done: return r;
+}
+
+/* read BLOCK_SIG during verification phase */
+static int
+rsgt_tlvrdVrfyBlockSig(FILE *fp, block_sig_t **bs, tlvrecord_t *rec)
+{
+ int r;
+
+ if((r = rsgt_tlvrd(fp, rec, bs)) != 0) goto done;
+ if(rec->tlvtype != 0x0902) {
+ r = RSGTE_MISS_BLOCKSIG;
+ rsgt_objfree(rec->tlvtype, *bs);
+ goto done;
+ }
+ r = 0;
+done: return r;
+}
+
+/**
+ * Read the next "object" from file. This usually is
+ * a single TLV, but may be something larger, for
+ * example in case of a block-sig TLV record.
+ * Unknown type records are ignored (or run aborted
+ * if we are not permitted to skip).
+ *
+ * @param[in] fp file pointer for processing
+ * @param[out] tlvtype type of tlv record (top-level for
+ * structured objects.
+ * @param[out] tlvlen length of the tlv record value
+ * @param[out] obj pointer to object; This is a proper
+ * tlv record structure, which must be casted
+ * by the caller according to the reported type.
+ * The object must be freed by the caller (TODO: better way?)
+ *
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_tlvrd(FILE *fp, tlvrecord_t *rec, void *obj)
+{
+ int r;
+ if((r = rsgt_tlvRecRead(fp, rec)) != 0) goto done;
+ r = rsgt_tlvRecDecode(rec, obj);
+done: return r;
+}
+
+
+/* return if a blob is all zero */
+static inline int
+blobIsZero(uint8_t *blob, uint16_t len)
+{
+ int i;
+ for(i = 0 ; i < len ; ++i)
+ if(blob[i] != 0)
+ return 0;
+ return 1;
+}
+
+static void
+rsgt_printIMPRINT(FILE *fp, char *name, imprint_t *imp, uint8_t verbose)
+{
+ fprintf(fp, "%s", name);
+ outputHexBlob(fp, imp->data, imp->len, verbose);
+ fputc('\n', fp);
+}
+
+static void
+rsgt_printREC_HASH(FILE *fp, imprint_t *imp, uint8_t verbose)
+{
+ rsgt_printIMPRINT(fp, "[0x0900]Record hash: ",
+ imp, verbose);
+}
+
+static void
+rsgt_printINT_HASH(FILE *fp, imprint_t *imp, uint8_t verbose)
+{
+ rsgt_printIMPRINT(fp, "[0x0901]Tree hash..: ",
+ imp, verbose);
+}
+
+/**
+ * Output a human-readable representation of a block_sig_t
+ * to proviced file pointer. This function is mainly inteded for
+ * debugging purposes or dumping tlv files.
+ *
+ * @param[in] fp file pointer to send output to
+ * @param[in] bsig ponter to block_sig_t to output
+ * @param[in] verbose if 0, abbreviate blob hexdump, else complete
+ */
+void
+rsgt_printBLOCK_SIG(FILE *fp, block_sig_t *bs, uint8_t verbose)
+{
+ fprintf(fp, "[0x0902]Block Signature Record:\n");
+ fprintf(fp, "\tPrevious Block Hash:\n");
+ fprintf(fp, "\t Algorithm..: %s\n", hashAlgName(bs->lastHash.hashID));
+ fprintf(fp, "\t Hash.......: ");
+ outputHexBlob(fp, bs->lastHash.data, bs->lastHash.len, verbose);
+ fputc('\n', fp);
+ if(blobIsZero(bs->lastHash.data, bs->lastHash.len))
+ fprintf(fp, "\t NOTE: New Hash Chain Start!\n");
+ fprintf(fp, "\tHash Algorithm: %s\n", hashAlgName(bs->hashID));
+ fprintf(fp, "\tIV............: ");
+ outputHexBlob(fp, bs->iv, getIVLen(bs), verbose);
+ fputc('\n', fp);
+ fprintf(fp, "\tRecord Count..: %llu\n", bs->recCount);
+ fprintf(fp, "\tSignature Type: %s\n", sigTypeName(bs->sigID));
+ fprintf(fp, "\tSignature Len.: %u\n", bs->sig.der.len);
+ fprintf(fp, "\tSignature.....: ");
+ outputHexBlob(fp, bs->sig.der.data, bs->sig.der.len, verbose);
+ fputc('\n', fp);
+}
+
+
+/**
+ * Output a human-readable representation of a tlv object.
+ *
+ * @param[in] fp file pointer to send output to
+ * @param[in] tlvtype type of tlv object (record)
+ * @param[in] verbose if 0, abbreviate blob hexdump, else complete
+ */
+void
+rsgt_tlvprint(FILE *fp, uint16_t tlvtype, void *obj, uint8_t verbose)
+{
+ switch(tlvtype) {
+ case 0x0900:
+ rsgt_printREC_HASH(fp, obj, verbose);
+ break;
+ case 0x0901:
+ rsgt_printINT_HASH(fp, obj, verbose);
+ break;
+ case 0x0902:
+ rsgt_printBLOCK_SIG(fp, obj, verbose);
+ break;
+ default:fprintf(fp, "unknown tlv record %4.4x\n", tlvtype);
+ break;
+ }
+}
+
+/**
+ * Free the provided object.
+ *
+ * @param[in] tlvtype type of tlv object (record)
+ * @param[in] obj the object to be destructed
+ */
+void
+rsgt_objfree(uint16_t tlvtype, void *obj)
+{
+ switch(tlvtype) {
+ case 0x0900:
+ case 0x0901:
+ free(((imprint_t*)obj)->data);
+ break;
+ case 0x0902:
+ free(((block_sig_t*)obj)->iv);
+ free(((block_sig_t*)obj)->lastHash.data);
+ free(((block_sig_t*)obj)->sig.der.data);
+ break;
+ default:fprintf(stderr, "rsgt_objfree: unknown tlv record %4.4x\n",
+ tlvtype);
+ break;
+ }
+ free(obj);
+}
+
+/**
+ * Read block parameters. This detects if the block contains the
+ * individual log hashes, the intermediate hashes and the overall
+ * block paramters (from the signature block). As we do not have any
+ * begin of block record, we do not know e.g. the hash algorithm or IV
+ * until reading the block signature record. And because the file is
+ * purely sequential and variable size, we need to read all records up to
+ * the next signature record.
+ * If a caller intends to verify a log file based on the parameters,
+ * he must re-read the file from the begining (we could keep things
+ * in memory, but this is impractical for large blocks). In order
+ * to facitate this, the function permits to rewind to the original
+ * read location when it is done.
+ *
+ * @param[in] fp file pointer of tlv file
+ * @param[in] bRewind 0 - do not rewind at end of procesing, 1 - do so
+ * @param[out] bs block signature record
+ * @param[out] bHasRecHashes 0 if record hashes are present, 1 otherwise
+ * @param[out] bHasIntermedHashes 0 if intermediate hashes are present,
+ * 1 otherwise
+ *
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_getBlockParams(FILE *fp, uint8_t bRewind, block_sig_t **bs,
+ uint8_t *bHasRecHashes, uint8_t *bHasIntermedHashes)
+{
+ int r;
+ uint64_t nRecs = 0;
+ uint8_t bDone = 0;
+ off_t rewindPos = 0;
+ void *obj;
+ tlvrecord_t rec;
+
+ if(bRewind)
+ rewindPos = ftello(fp);
+ *bHasRecHashes = 0;
+ *bHasIntermedHashes = 0;
+ *bs = NULL;
+
+ while(!bDone) { /* we will err out on EOF */
+ if((r = rsgt_tlvrd(fp, &rec, &obj)) != 0) goto done;
+ switch(rec.tlvtype) {
+ case 0x0900:
+ ++nRecs;
+ *bHasRecHashes = 1;
+ break;
+ case 0x0901:
+ *bHasIntermedHashes = 1;
+ break;
+ case 0x0902:
+ *bs = (block_sig_t*) obj;
+ bDone = 1;
+ break;
+ default:fprintf(fp, "unknown tlv record %4.4x\n", rec.tlvtype);
+ break;
+ }
+ if(!bDone)
+ rsgt_objfree(rec.tlvtype, obj);
+ }
+
+ if(*bHasRecHashes && (nRecs != (*bs)->recCount)) {
+ r = RSGTE_INVLD_RECCNT;
+ goto done;
+ }
+
+ if(bRewind) {
+ if(fseeko(fp, rewindPos, SEEK_SET) != 0) {
+ r = RSGTE_IO;
+ goto done;
+ }
+ }
+done:
+ return r;
+}
+
+
+/**
+ * Read the file header and compare it to the expected value.
+ * The file pointer is placed right after the header.
+ * @param[in] fp file pointer of tlv file
+ * @param[in] excpect expected header (e.g. "LOGSIG10")
+ * @returns 0 if ok, something else otherwise
+ */
+int
+rsgt_chkFileHdr(FILE *fp, char *expect)
+{
+ int r;
+ char hdr[9];
+
+ if((r = rsgt_tlvrdHeader(fp, (uchar*)hdr)) != 0) goto done;
+ if(strcmp(hdr, expect))
+ r = RSGTE_INVLHDR;
+ else
+ r = 0;
+done:
+ return r;
+}
+
+gtfile
+rsgt_vrfyConstruct_gf(void)
+{
+ gtfile gf;
+ if((gf = calloc(1, sizeof(struct gtfile_s))) == NULL)
+ goto done;
+ gf->x_prev = NULL;
+
+done: return gf;
+}
+
+void
+rsgt_vrfyBlkInit(gtfile gf, block_sig_t *bs, uint8_t bHasRecHashes, uint8_t bHasIntermedHashes)
+{
+ gf->hashAlg = hashID2Alg(bs->hashID);
+ gf->bKeepRecordHashes = bHasRecHashes;
+ gf->bKeepTreeHashes = bHasIntermedHashes;
+ free(gf->IV);
+ gf->IV = malloc(getIVLen(bs));
+ memcpy(gf->IV, bs->iv, getIVLen(bs));
+ free(gf->blkStrtHash);
+ gf->lenBlkStrtHash = bs->lastHash.len;
+ gf->blkStrtHash = malloc(gf->lenBlkStrtHash);
+ memcpy(gf->blkStrtHash, bs->lastHash.data, gf->lenBlkStrtHash);
+}
+
+static int
+rsgt_vrfy_chkRecHash(gtfile gf, FILE *sigfp, FILE *nsigfp,
+ GTDataHash *recHash, gterrctx_t *ectx)
+{
+ int r = 0;
+ imprint_t *imp = NULL;
+
+ if((r = rsgt_tlvrdRecHash(sigfp, nsigfp, &imp)) != 0)
+ reportError(r, ectx);
+ goto done;
+ if(imp->hashID != hashIdentifier(gf->hashAlg)) {
+ reportError(r, ectx);
+ r = RSGTE_INVLD_REC_HASHID;
+ goto done;
+ }
+ if(memcmp(imp->data, recHash->digest,
+ hashOutputLengthOctets(imp->hashID))) {
+ r = RSGTE_INVLD_REC_HASH;
+ ectx->computedHash = recHash;
+ ectx->fileHash = imp;
+ reportError(r, ectx);
+ ectx->computedHash = NULL, ectx->fileHash = NULL;
+ goto done;
+ }
+ r = 0;
+done:
+ if(imp != NULL)
+ rsgt_objfree(0x0900, imp);
+ return r;
+}
+
+static int
+rsgt_vrfy_chkTreeHash(gtfile gf, FILE *sigfp, FILE *nsigfp,
+ GTDataHash *hash, gterrctx_t *ectx)
+{
+ int r = 0;
+ imprint_t *imp = NULL;
+
+ if((r = rsgt_tlvrdTreeHash(sigfp, nsigfp, &imp)) != 0) {
+ reportError(r, ectx);
+ goto done;
+ }
+ if(imp->hashID != hashIdentifier(gf->hashAlg)) {
+ reportError(r, ectx);
+ r = RSGTE_INVLD_TREE_HASHID;
+ goto done;
+ }
+ if(memcmp(imp->data, hash->digest,
+ hashOutputLengthOctets(imp->hashID))) {
+ r = RSGTE_INVLD_TREE_HASH;
+ ectx->computedHash = hash;
+ ectx->fileHash = imp;
+ reportError(r, ectx);
+ ectx->computedHash = NULL, ectx->fileHash = NULL;
+ goto done;
+ }
+ r = 0;
+done:
+ if(imp != NULL)
+ rsgt_objfree(0x0901, imp);
+ return r;
+}
+
+int
+rsgt_vrfy_nextRec(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp,
+ unsigned char *rec, size_t len, gterrctx_t *ectx)
+{
+ int r = 0;
+ GTDataHash *x; /* current hash */
+ GTDataHash *m, *recHash = NULL, *t, *t_del;
+ uint8_t j;
+
+ hash_m(gf, &m);
+ hash_r(gf, &recHash, rec, len);
+ if(gf->bKeepRecordHashes) {
+ r = rsgt_vrfy_chkRecHash(gf, sigfp, nsigfp, recHash, ectx);
+ if(r != 0) goto done;
+ }
+ hash_node(gf, &x, m, recHash, 1); /* hash leaf */
+ if(gf->bKeepTreeHashes) {
+ ectx->treeLevel = 0;
+ ectx->lefthash = m;
+ ectx->righthash = recHash;
+ r = rsgt_vrfy_chkTreeHash(gf, sigfp, nsigfp, x, ectx);
+ if(r != 0) goto done;
+ }
+ rsgtimprintDel(gf->x_prev);
+ gf->x_prev = rsgtImprintFromGTDataHash(x);
+ /* add x to the forest as new leaf, update roots list */
+ t = x;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(gf->roots_valid[j] == 0) {
+ gf->roots_hash[j] = t;
+ gf->roots_valid[j] = 1;
+ t = NULL;
+ break;
+ } else if(t != NULL) {
+ /* hash interim node */
+ ectx->treeLevel = j+1;
+ ectx->righthash = t;
+ t_del = t;
+ hash_node(gf, &t, gf->roots_hash[j], t_del, j+2);
+ gf->roots_valid[j] = 0;
+ if(gf->bKeepTreeHashes) {
+ ectx->lefthash = gf->roots_hash[j];
+ r = rsgt_vrfy_chkTreeHash(gf, sigfp, nsigfp, t, ectx);
+ if(r != 0) goto done; /* mem leak ok, we terminate! */
+ }
+ GTDataHash_free(gf->roots_hash[j]);
+ GTDataHash_free(t_del);
+ }
+ }
+ if(t != NULL) {
+ /* new level, append "at the top" */
+ gf->roots_hash[gf->nRoots] = t;
+ gf->roots_valid[gf->nRoots] = 1;
+ ++gf->nRoots;
+ assert(gf->nRoots < MAX_ROOTS);
+ t = NULL;
+ }
+ ++gf->nRecords;
+
+ /* cleanup */
+ GTDataHash_free(m);
+done:
+ if(recHash != NULL)
+ GTDataHash_free(recHash);
+ return r;
+}
+
+
+/* TODO: think about merging this with the writer. The
+ * same applies to the other computation algos.
+ */
+static int
+verifySigblkFinish(gtfile gf, GTDataHash **pRoot)
+{
+ GTDataHash *root, *rootDel;
+ int8_t j;
+ int r;
+
+ if(gf->nRecords == 0)
+ goto done;
+
+ root = NULL;
+ for(j = 0 ; j < gf->nRoots ; ++j) {
+ if(root == NULL) {
+ root = gf->roots_valid[j] ? gf->roots_hash[j] : NULL;
+ gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */
+ } else if(gf->roots_valid[j]) {
+ rootDel = root;
+ hash_node(gf, &root, gf->roots_hash[j], root, j+2);
+ gf->roots_valid[j] = 0; /* guess this is redundant with init, maybe del */
+ GTDataHash_free(rootDel);
+ }
+ }
+
+ free(gf->blkStrtHash);
+ gf->blkStrtHash = NULL;
+ *pRoot = root;
+ r = 0;
+done:
+ gf->bInBlk = 0;
+ return r;
+}
+
+
+/* helper for rsgt_extendSig: */
+#define COPY_SUBREC_TO_NEWREC \
+ memcpy(newrec.data+iWr, subrec.hdr, subrec.lenHdr); \
+ iWr += subrec.lenHdr; \
+ memcpy(newrec.data+iWr, subrec.data, subrec.tlvlen); \
+ iWr += subrec.tlvlen;
+static inline int
+rsgt_extendSig(GTTimestamp *timestamp, tlvrecord_t *rec, gterrctx_t *ectx)
+{
+ GTTimestamp *out_timestamp;
+ uint8_t *der;
+ size_t lenDer;
+ int r, rgt;
+ tlvrecord_t newrec, subrec;
+ uint16_t iRd, iWr;
+
+ rgt = GTHTTP_extendTimestamp(timestamp, rsgt_extend_puburl, &out_timestamp);
+ if(rgt != GT_OK) {
+ ectx->gtstate = rgt;
+ r = RSGTE_TS_EXTEND;
+ goto done;
+ }
+ r = GTTimestamp_getDEREncoded(out_timestamp, &der, &lenDer);
+ if(r != GT_OK) {
+ r = RSGTE_TS_DERENCODE;
+ ectx->gtstate = rgt;
+ goto done;
+ }
+ /* update block_sig tlv record with new extended timestamp */
+ /* we now need to copy all tlv records before the actual der
+ * encoded part.
+ */
+ iRd = iWr = 0;
+ // TODO; check tlvtypes at comment places below!
+ if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done;
+ /* HASH_ALGO */
+ COPY_SUBREC_TO_NEWREC
+ if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done;
+ /* BLOCK_IV */
+ COPY_SUBREC_TO_NEWREC
+ if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done;
+ /* LAST_HASH */
+ COPY_SUBREC_TO_NEWREC
+ if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done;
+ /* REC_COUNT */
+ COPY_SUBREC_TO_NEWREC
+ if ((r = rsgt_tlvDecodeSUBREC(rec, &iRd, &subrec)) != 0) goto done;
+ /* actual sig! */
+ newrec.data[iWr++] = 0x09 | RSGT_FLAG_TLV16;
+ newrec.data[iWr++] = 0x06;
+ newrec.data[iWr++] = (lenDer >> 8) & 0xff;
+ newrec.data[iWr++] = lenDer & 0xff;
+ /* now we know how large the new main record is */
+ newrec.tlvlen = (uint16_t) iWr+lenDer;
+ newrec.tlvtype = rec->tlvtype;
+ newrec.hdr[0] = rec->hdr[0];
+ newrec.hdr[1] = rec->hdr[1];
+ newrec.hdr[2] = (newrec.tlvlen >> 8) & 0xff;
+ newrec.hdr[3] = newrec.tlvlen & 0xff;
+ newrec.lenHdr = 4;
+ memcpy(newrec.data+iWr, der, lenDer);
+ /* and finally copy back new record to existing one */
+ memcpy(rec, &newrec, sizeof(newrec)-sizeof(newrec.data)+newrec.tlvlen+4);
+ r = 0;
+done:
+ return r;
+}
+
+
+/* verify the root hash. This also means we need to compute the
+ * Merkle tree root for the current block.
+ */
+int
+verifyBLOCK_SIG(block_sig_t *bs, gtfile gf, FILE *sigfp, FILE *nsigfp,
+ uint8_t bExtend, gterrctx_t *ectx)
+{
+ int r;
+ int gtstate;
+ block_sig_t *file_bs = NULL;
+ GTTimestamp *timestamp = NULL;
+ GTVerificationInfo *vrfyInf;
+ GTDataHash *root = NULL;
+ tlvrecord_t rec;
+
+ if((r = verifySigblkFinish(gf, &root)) != 0)
+ goto done;
+ if((r = rsgt_tlvrdVrfyBlockSig(sigfp, &file_bs, &rec)) != 0)
+ goto done;
+ if(ectx->recNum != bs->recCount) {
+ r = RSGTE_INVLD_RECCNT;
+ goto done;
+ }
+
+ gtstate = GTTimestamp_DERDecode(file_bs->sig.der.data,
+ file_bs->sig.der.len, &timestamp);
+ if(gtstate != GT_OK) {
+ r = RSGTE_TS_DERDECODE;
+ ectx->gtstate = gtstate;
+ goto done;
+ }
+
+ gtstate = GTHTTP_verifyTimestampHash(timestamp, root, NULL,
+ NULL, NULL, rsgt_read_puburl, 0, &vrfyInf);
+ if(! (gtstate == GT_OK
+ && vrfyInf->verification_errors == GT_NO_FAILURES) ) {
+ r = RSGTE_INVLD_TIMESTAMP;
+ ectx->gtstate = gtstate;
+ goto done;
+ }
+
+ if(rsgt_read_showVerified)
+ reportVerifySuccess(ectx, vrfyInf);
+ if(bExtend)
+ if((r = rsgt_extendSig(timestamp, &rec, ectx)) != 0) goto done;
+
+ if(nsigfp != NULL)
+ if((r = rsgt_tlvwrite(nsigfp, &rec)) != 0) goto done;
+ r = 0;
+done:
+ if(file_bs != NULL)
+ rsgt_objfree(0x0902, file_bs);
+ if(r != 0)
+ reportError(r, ectx);
+ if(timestamp != NULL)
+ GTTimestamp_free(timestamp);
+ return r;
+}
diff --git a/runtime/linkedlist.c b/runtime/linkedlist.c
new file mode 100644
index 00000000..53aace47
--- /dev/null
+++ b/runtime/linkedlist.c
@@ -0,0 +1,412 @@
+/* linkedlist.c
+ * This file set implements a generic linked list object. It can be used
+ * wherever a linke list is required.
+ *
+ * NOTE: we do not currently provide a constructor and destructor for the
+ * object itself as we assume it will always be part of another strucuture.
+ * Having a pointer to it, I think, does not really make sense but costs
+ * performance. Consequently, there is is llInit() and llDestroy() and they
+ * do what a constructor and destructur do, except for creating the
+ * linkedList_t structure itself.
+ *
+ * File begun on 2007-07-31 by RGerhards
+ *
+ * Copyright (C) 2007-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "linkedlist.h"
+
+
+/* Initialize an existing linkedList_t structure
+ * pKey destructor may be zero to take care of non-keyed lists.
+ */
+rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)())
+{
+ assert(pThis != NULL);
+ assert(pEltDestructor != NULL);
+
+ pThis->pEltDestruct = pEltDestructor;
+ pThis->pKeyDestruct = pKeyDestructor;
+ pThis->cmpOp = pCmpOp;
+ pThis->pKey = NULL;
+ pThis->iNumElts = 0;
+ pThis->pRoot = NULL;
+ pThis->pLast = NULL;
+
+ return RS_RET_OK;
+};
+
+
+/* llDestroyEltData - destroys a list element
+ * It is a separate function as the
+ * functionality is needed in multiple code-pathes.
+ */
+static rsRetVal llDestroyElt(linkedList_t *pList, llElt_t *pElt)
+{
+ DEFiRet;
+
+ assert(pList != NULL);
+ assert(pElt != NULL);
+
+ /* we ignore errors during destruction, as we need to try
+ * free the element in any case.
+ */
+ if(pElt->pData != NULL)
+ pList->pEltDestruct(pElt->pData);
+ if(pElt->pKey != NULL)
+ pList->pKeyDestruct(pElt->pKey);
+ free(pElt);
+ pList->iNumElts--; /* one less */
+
+ RETiRet;
+}
+
+
+/* llDestroy - destroys a COMPLETE linkedList
+ */
+rsRetVal llDestroy(linkedList_t *pThis)
+{
+ DEFiRet;
+ llElt_t *pElt;
+ llElt_t *pEltPrev;
+
+ assert(pThis != NULL);
+
+ pElt = pThis->pRoot;
+ while(pElt != NULL) {
+ pEltPrev = pElt;
+ pElt = pElt->pNext;
+ /* we ignore errors during destruction, as we need to try
+ * finish the linked list in any case.
+ */
+ llDestroyElt(pThis, pEltPrev);
+ }
+ /* now clean up the pointers */
+ pThis->pRoot = NULL;
+ pThis->pLast = NULL;
+
+ RETiRet;
+}
+
+/* llDestroyRootElt - destroy the root element but otherwise
+ * keeps this list intact. -- rgerhards, 2007-08-03
+ */
+rsRetVal llDestroyRootElt(linkedList_t *pThis)
+{
+ DEFiRet;
+ llElt_t *pPrev;
+
+ if(pThis->pRoot == NULL) {
+ ABORT_FINALIZE(RS_RET_EMPTY_LIST);
+ }
+
+ pPrev = pThis->pRoot;
+ if(pPrev->pNext == NULL) {
+ /* it was the only list element */
+ pThis->pLast = NULL;
+ pThis->pRoot = NULL;
+ } else {
+ /* there are other list elements */
+ pThis->pRoot = pPrev->pNext;
+ }
+
+ CHKiRet(llDestroyElt(pThis, pPrev));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* get next user data element of a linked list. The caller must also
+ * provide a "cookie" to the function. On initial call, it must be
+ * NULL. Other than that, the caller is not allowed to to modify the
+ * cookie. In the current implementation, the cookie is an actual
+ * pointer to the current list element, but this is nothing that the
+ * caller should rely on.
+ */
+rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr)
+{
+ llElt_t *pElt;
+ DEFiRet;
+
+ assert(pThis != NULL);
+ assert(ppElt != NULL);
+ assert(ppUsr != NULL);
+
+ pElt = *ppElt;
+
+ pElt = (pElt == NULL) ? pThis->pRoot : pElt->pNext;
+
+ if(pElt == NULL) {
+ iRet = RS_RET_END_OF_LINKEDLIST;
+ } else {
+ *ppUsr = pElt->pData;
+ }
+
+ *ppElt = pElt;
+
+ RETiRet;
+}
+
+
+/* return the key of an Elt
+ * rgerhards, 2007-09-11: note that ppDatea is actually a void**,
+ * but I need to make it a void* to avoid lots of compiler warnings.
+ * It will be converted later down in the code.
+ */
+rsRetVal llGetKey(llElt_t *pThis, void *ppData)
+{
+ assert(pThis != NULL);
+ assert(ppData != NULL);
+
+ *(void**) ppData = pThis->pKey;
+
+ return RS_RET_OK;
+}
+
+
+/* construct a new llElt_t
+ */
+static rsRetVal llEltConstruct(llElt_t **ppThis, void *pKey, void *pData)
+{
+ DEFiRet;
+ llElt_t *pThis;
+
+ assert(ppThis != NULL);
+
+ if((pThis = (llElt_t*) calloc(1, sizeof(llElt_t))) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ pThis->pKey = pKey;
+ pThis->pData = pData;
+
+finalize_it:
+ *ppThis = pThis;
+ RETiRet;
+}
+
+
+/* append a user element to the end of the linked list. This includes setting a key. If no
+ * key is desired, simply pass in a NULL pointer for it.
+ */
+rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData)
+{
+ llElt_t *pElt;
+ DEFiRet;
+
+ CHKiRet(llEltConstruct(&pElt, pKey, pData));
+
+ pThis->iNumElts++; /* one more */
+ if(pThis->pLast == NULL) {
+ pThis->pRoot = pElt;
+ } else {
+ pThis->pLast->pNext = pElt;
+ }
+ pThis->pLast = pElt;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* unlink a requested element. As we have singly-linked lists, the
+ * caller also needs to pass in the previous element (or NULL, if it is the
+ * root element).
+ * rgerhards, 2007-11-21
+ */
+static rsRetVal llUnlinkElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev)
+{
+ assert(pElt != NULL);
+
+ if(pEltPrev == NULL) { /* root element? */
+ pThis->pRoot = pElt->pNext;
+ } else { /* regular element */
+ pEltPrev->pNext = pElt->pNext;
+ }
+
+ if(pElt == pThis->pLast)
+ pThis->pLast = pEltPrev;
+
+ return RS_RET_OK;
+}
+
+
+/* unlinks and immediately deletes an element. Previous element must
+ * be given (or zero if the root element is to be deleted).
+ * rgerhards, 2007-11-21
+ */
+static rsRetVal llUnlinkAndDelteElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev)
+{
+ DEFiRet;
+
+ assert(pElt != NULL);
+
+ CHKiRet(llUnlinkElt(pThis, pElt, pEltPrev));
+ CHKiRet(llDestroyElt(pThis, pElt));
+
+finalize_it:
+ RETiRet;
+}
+
+/* find a user element based on the provided key - this is the
+ * internal variant, which also tracks the last element pointer
+ * before the found element. This is necessary to delete elements.
+ * NULL means there is no element in front of it, aka the found elt
+ * is the root elt.
+ * rgerhards, 2007-11-21
+ */
+static rsRetVal llFindElt(linkedList_t *pThis, void *pKey, llElt_t **ppElt, llElt_t **ppEltPrev)
+{
+ DEFiRet;
+ llElt_t *pElt;
+ llElt_t *pEltPrev = NULL;
+ int bFound = 0;
+
+ assert(pThis != NULL);
+ assert(pKey != NULL);
+ assert(ppElt != NULL);
+ assert(ppEltPrev != NULL);
+
+ pElt = pThis->pRoot;
+ while(pElt != NULL && bFound == 0) {
+ if(pThis->cmpOp(pKey, pElt->pKey) == 0)
+ bFound = 1;
+ else {
+ pEltPrev = pElt;
+ pElt = pElt->pNext;
+ }
+ }
+
+ if(bFound == 1) {
+ *ppElt = pElt;
+ *ppEltPrev = pEltPrev;
+ } else
+ iRet = RS_RET_NOT_FOUND;
+
+ RETiRet;
+}
+
+
+/* find a user element based on the provided key
+ */
+rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData)
+{
+ DEFiRet;
+ llElt_t *pElt;
+ llElt_t *pEltPrev;
+
+ CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev));
+
+ /* if we reach this point, we have found the element */
+ *ppData = pElt->pData;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* find a delete an element based on user-provided key. The element is
+ * delete, the caller does not receive anything. If we need to receive
+ * the element before destruction, we may implement an llFindAndUnlink()
+ * at that time.
+ * rgerhards, 2007-11-21
+ */
+rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey)
+{
+ DEFiRet;
+ llElt_t *pElt;
+ llElt_t *pEltPrev;
+
+ CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev));
+
+ /* if we reach this point, we have found an element */
+ CHKiRet(llUnlinkAndDelteElt(pThis, pElt, pEltPrev));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* provide the count of linked list elements
+ */
+rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt)
+{
+ DEFiRet;
+
+ assert(pThis != NULL);
+ assert(piCnt != NULL);
+
+ *piCnt = pThis->iNumElts;
+
+ RETiRet;
+}
+
+
+/* execute a function on all list members. The functions receives a
+ * user-supplied parameter, which may be either a simple value
+ * or a pointer to a structure with more data. If the user-supplied
+ * function does not return RS_RET_OK, this function here terminates.
+ * rgerhards, 2007-08-02
+ * rgerhards, 2007-11-21: added functionality to delete a list element.
+ * If the called user function returns RS_RET_OK_DELETE_LISTENTRY the current element
+ * is deleted.
+ */
+rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam)
+{
+ DEFiRet;
+ rsRetVal iRetLL;
+ void *pData;
+ linkedListCookie_t llCookie = NULL;
+ linkedListCookie_t llCookiePrev = NULL; /* previous list element (needed for deletion, NULL = at root) */
+
+ assert(pThis != NULL);
+ assert(pFunc != NULL);
+
+ while((iRetLL = llGetNextElt(pThis, &llCookie, (void**)&pData)) == RS_RET_OK) {
+ iRet = pFunc(pData, pParam);
+ if(iRet == RS_RET_OK_DELETE_LISTENTRY) {
+ /* delete element */
+ CHKiRet(llUnlinkAndDelteElt(pThis, llCookie, llCookiePrev));
+ /* we need to revert back, as we have just deleted the current element.
+ * So the actual current element is the one before it, which happens to be
+ * stored in llCookiePrev. -- rgerhards, 2007-11-21
+ */
+ llCookie = llCookiePrev;
+ } else if (iRet != RS_RET_OK) {
+ FINALIZE;
+ }
+ llCookiePrev = llCookie;
+ }
+
+ if(iRetLL != RS_RET_END_OF_LINKEDLIST)
+ iRet = iRetLL;
+
+finalize_it:
+ RETiRet;
+}
+
+/* vim:set ai:
+ */
diff --git a/runtime/linkedlist.h b/runtime/linkedlist.h
new file mode 100644
index 00000000..eb829af9
--- /dev/null
+++ b/runtime/linkedlist.h
@@ -0,0 +1,71 @@
+/* Definition of the linkedlist object.
+ *
+ * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef LINKEDLIST_H_INCLUDED
+#define LINKEDLIST_H_INCLUDED
+
+/* this is a single entry for a parse routine. It describes exactly
+ * one entry point/handler.
+ * The short name is cslch (Configfile SysLine CommandHandler)
+ */
+struct llElt_s { /* config file sysline parse entry */
+ struct llElt_s *pNext;
+ void *pKey; /* key for this element */
+ void *pData; /* user-supplied data pointer */
+};
+typedef struct llElt_s llElt_t;
+
+
+/* this is the list of known configuration commands with pointers to
+ * their handlers.
+ * The short name is cslc (Configfile SysLine Command)
+ */
+struct linkedList_s { /* config file sysline parse entry */
+ int iNumElts; /* number of elements in list */
+ rsRetVal (*pEltDestruct)(void*pData); /* destructor for user pointer in llElt_t's */
+ rsRetVal (*pKeyDestruct)(void*pKey); /* destructor for key pointer in llElt_t's */
+ int (*cmpOp)(void*, void*); /* pointer to key compare operation function, retval like strcmp */
+ void *pKey; /* the list key (searchable, if set) */
+ llElt_t *pRoot; /* list root */
+ llElt_t *pLast; /* list tail */
+};
+typedef struct linkedList_s linkedList_t;
+
+typedef llElt_t* linkedListCookie_t; /* this type avoids exposing internals and keeps us flexible */
+
+/* prototypes */
+rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)());
+rsRetVal llDestroy(linkedList_t *pThis);
+rsRetVal llDestroyRootElt(linkedList_t *pThis);
+rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr);
+rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData);
+rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData);
+rsRetVal llGetKey(llElt_t *pThis, void *ppData);
+rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt);
+rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam);
+rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey);
+/* use the macro below to define a function that will be executed by
+ * llExecFunc()
+ */
+#define DEFFUNC_llExecFunc(funcName)\
+ static rsRetVal funcName(void __attribute__((unused)) *pData, void __attribute__((unused)) *pParam)
+
+#endif /* #ifndef LINKEDLIST_H_INCLUDED */
diff --git a/runtime/lmcry_gcry.c b/runtime/lmcry_gcry.c
new file mode 100644
index 00000000..9a0c0072
--- /dev/null
+++ b/runtime/lmcry_gcry.c
@@ -0,0 +1,337 @@
+/* lmcry_gcry.c
+ *
+ * An implementation of the cryprov interface for libgcrypt.
+ *
+ * Copyright 2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "module-template.h"
+#include "glbl.h"
+#include "errmsg.h"
+#include "cryprov.h"
+#include "libgcry.h"
+#include "lmcry_gcry.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+
+/* tables for interfacing with the v6 config system */
+static struct cnfparamdescr cnfpdescrRegular[] = {
+ { "cry.key", eCmdHdlrGetWord, 0 },
+ { "cry.keyfile", eCmdHdlrGetWord, 0 },
+ { "cry.keyprogram", eCmdHdlrGetWord, 0 },
+ { "cry.mode", eCmdHdlrGetWord, 0 }, /* CBC, ECB, etc */
+ { "cry.algo", eCmdHdlrGetWord, 0 }
+};
+static struct cnfparamblk pblkRegular =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfpdescrRegular)/sizeof(struct cnfparamdescr),
+ cnfpdescrRegular
+ };
+
+static struct cnfparamdescr cnfpdescrQueue[] = {
+ { "queue.cry.key", eCmdHdlrGetWord, 0 },
+ { "queue.cry.keyfile", eCmdHdlrGetWord, 0 },
+ { "queue.cry.keyprogram", eCmdHdlrGetWord, 0 },
+ { "queue.cry.mode", eCmdHdlrGetWord, 0 }, /* CBC, ECB, etc */
+ { "queue.cry.algo", eCmdHdlrGetWord, 0 }
+};
+static struct cnfparamblk pblkQueue =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfpdescrQueue)/sizeof(struct cnfparamdescr),
+ cnfpdescrQueue
+ };
+
+
+#if 0
+static void
+errfunc(__attribute__((unused)) void *usrptr, uchar *emsg)
+{
+ errmsg.LogError(0, RS_RET_CRYPROV_ERR, "Crypto Provider"
+ "Error: %s - disabling encryption", emsg);
+}
+#endif
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(lmcry_gcry)
+ pThis->ctx = gcryCtxNew();
+ENDobjConstruct(lmcry_gcry)
+
+
+/* destructor for the lmcry_gcry object */
+BEGINobjDestruct(lmcry_gcry) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(lmcry_gcry)
+ rsgcryCtxDel(pThis->ctx);
+ENDobjDestruct(lmcry_gcry)
+
+
+/* apply all params from param block to us. This must be called
+ * after construction, but before the OnFileOpen() entry point.
+ * Defaults are expected to have been set during construction.
+ */
+static rsRetVal
+SetCnfParam(void *pT, struct nvlst *lst, int paramType)
+{
+ lmcry_gcry_t *pThis = (lmcry_gcry_t*) pT;
+ int i, r;
+ unsigned keylen;
+ uchar *key = NULL;
+ uchar *keyfile = NULL;
+ uchar *keyprogram = NULL;
+ uchar *algo = NULL;
+ uchar *mode = NULL;
+ int nKeys; /* number of keys (actually methods) specified */
+ struct cnfparamvals *pvals;
+ struct cnfparamblk *pblk;
+ DEFiRet;
+
+ pblk = (paramType == CRYPROV_PARAMTYPE_REGULAR ) ? &pblkRegular : &pblkQueue;
+ nKeys = 0;
+ pvals = nvlstGetParams(lst, pblk, NULL);
+ if(Debug) {
+ dbgprintf("param blk in lmcry_gcry:\n");
+ cnfparamsPrint(pblk, pvals);
+ }
+
+ for(i = 0 ; i < pblk->nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(pblk->descr[i].name, "cry.key") ||
+ !strcmp(pblk->descr[i].name, "queue.cry.key")) {
+ key = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ ++nKeys;
+ } else if(!strcmp(pblk->descr[i].name, "cry.keyfile") ||
+ !strcmp(pblk->descr[i].name, "queue.cry.keyfile")) {
+ keyfile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ ++nKeys;
+ } else if(!strcmp(pblk->descr[i].name, "cry.keyprogram") ||
+ !strcmp(pblk->descr[i].name, "queue.cry.keyprogram")) {
+ keyprogram = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ ++nKeys;
+ } else if(!strcmp(pblk->descr[i].name, "cry.mode") ||
+ !strcmp(pblk->descr[i].name, "queue.cry.mode")) {
+ mode = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(pblk->descr[i].name, "cry.algo") ||
+ !strcmp(pblk->descr[i].name, "queue.cry.algo")) {
+ algo = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else {
+ DBGPRINTF("lmcry_gcry: program error, non-handled "
+ "param '%s'\n", pblk->descr[i].name);
+ }
+ }
+ if(algo != NULL) {
+ iRet = rsgcrySetAlgo(pThis->ctx, algo);
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "cry.algo '%s' is not know/supported", algo);
+ FINALIZE;
+ }
+ }
+ if(mode != NULL) {
+ iRet = rsgcrySetMode(pThis->ctx, mode);
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "cry.mode '%s' is not know/supported", mode);
+ FINALIZE;
+ }
+ }
+ /* note: key must be set AFTER algo/mode is set (as it depends on them) */
+ if(nKeys != 1) {
+ errmsg.LogError(0, RS_RET_INVALID_PARAMS, "excactly one of the following "
+ "parameters can be specified: cry.key, cry.keyfile, cry.keyprogram\n");
+ ABORT_FINALIZE(RS_RET_INVALID_PARAMS);
+ }
+ if(key != NULL) {
+ errmsg.LogError(0, RS_RET_ERR, "Note: specifying an actual key directly from the "
+ "config file is highly insecure - DO NOT USE FOR PRODUCTION");
+ keylen = strlen((char*)key);
+ }
+ if(keyfile != NULL) {
+ r = gcryGetKeyFromFile((char*)keyfile, (char**)&key, &keylen);
+ if(r != 0) {
+ errmsg.LogError(0, RS_RET_ERR, "error %d reading keyfile %s\n",
+ r, keyfile);
+ ABORT_FINALIZE(RS_RET_INVALID_PARAMS);
+ }
+ }
+ if(keyprogram != NULL) {
+ r = gcryGetKeyFromProg((char*)keyprogram, (char**)&key, &keylen);
+ if(r != 0) {
+ errmsg.LogError(0, RS_RET_ERR, "error %d obtaining key from program %s\n",
+ r, keyprogram);
+ ABORT_FINALIZE(RS_RET_INVALID_PARAMS);
+ }
+ }
+
+ /* if we reach this point, we have a valid key */
+ r = rsgcrySetKey(pThis->ctx, key, keylen);
+ if(r > 0) {
+ errmsg.LogError(0, RS_RET_INVALID_PARAMS, "Key length %d expected, but "
+ "key of length %d given", r, keylen);
+ ABORT_FINALIZE(RS_RET_INVALID_PARAMS);
+ }
+
+ cnfparamvalsDestruct(pvals, pblk);
+ if(key != NULL) {
+ memset(key, 0, strlen((char*)key));
+ free(key);
+ }
+ free(keyfile);
+ free(algo);
+ free(mode);
+finalize_it:
+ RETiRet;
+}
+
+static void
+SetDeleteOnClose(void *pF, int val)
+{
+ gcryfileSetDeleteOnClose(pF, val);
+}
+
+static rsRetVal
+GetBytesLeftInBlock(void *pF, ssize_t *left)
+{
+ return gcryfileGetBytesLeftInBlock((gcryfile) pF, left);
+}
+
+static rsRetVal
+DeleteStateFiles(uchar *logfn)
+{
+ return gcryfileDeleteState(logfn);
+}
+
+static rsRetVal
+OnFileOpen(void *pT, uchar *fn, void *pGF, char openMode)
+{
+ lmcry_gcry_t *pThis = (lmcry_gcry_t*) pT;
+ gcryfile *pgf = (gcryfile*) pGF;
+ DEFiRet;
+ DBGPRINTF("lmcry_gcry: open file '%s', mode '%c'\n", fn, openMode);
+
+ CHKiRet(rsgcryInitCrypt(pThis->ctx, pgf, fn, openMode));
+finalize_it:
+ /* TODO: enable this error message (need to cleanup loop first ;))
+ errmsg.LogError(0, iRet, "Encryption Provider"
+ "Error: cannot open .encinfo file - disabling log file");
+ */
+ RETiRet;
+}
+
+static rsRetVal
+Decrypt(void *pF, uchar *rec, size_t *lenRec)
+{
+ DEFiRet;
+ iRet = rsgcryDecrypt(pF, rec, lenRec);
+
+ RETiRet;
+}
+
+
+static rsRetVal
+Encrypt(void *pF, uchar *rec, size_t *lenRec)
+{
+ DEFiRet;
+ iRet = rsgcryEncrypt(pF, rec, lenRec);
+
+ RETiRet;
+}
+
+static rsRetVal
+OnFileClose(void *pF, off64_t offsLogfile)
+{
+ DEFiRet;
+ gcryfileDestruct(pF, offsLogfile);
+
+ RETiRet;
+}
+
+BEGINobjQueryInterface(lmcry_gcry)
+CODESTARTobjQueryInterface(lmcry_gcry)
+ if(pIf->ifVersion != cryprovCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+ pIf->Construct = (rsRetVal(*)(void*)) lmcry_gcryConstruct;
+ pIf->SetCnfParam = SetCnfParam;
+ pIf->SetDeleteOnClose = SetDeleteOnClose;
+ pIf->Destruct = (rsRetVal(*)(void*)) lmcry_gcryDestruct;
+ pIf->OnFileOpen = OnFileOpen;
+ pIf->Encrypt = Encrypt;
+ pIf->Decrypt = Decrypt;
+ pIf->OnFileClose = OnFileClose;
+ pIf->DeleteStateFiles = DeleteStateFiles;
+ pIf->GetBytesLeftInBlock = GetBytesLeftInBlock;
+finalize_it:
+ENDobjQueryInterface(lmcry_gcry)
+
+
+BEGINObjClassExit(lmcry_gcry, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(lmcry_gcry)
+ /* release objects we no longer need */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+
+ rsgcryExit();
+ENDObjClassExit(lmcry_gcry)
+
+
+BEGINObjClassInit(lmcry_gcry, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ if(rsgcryInit() != 0) {
+ errmsg.LogError(0, RS_RET_CRYPROV_ERR, "error initializing "
+ "crypto provider - cannot encrypt");
+ ABORT_FINALIZE(RS_RET_CRYPROV_ERR);
+ }
+ENDObjClassInit(lmcry_gcry)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ lmcry_gcryClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(lmcry_gcryClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
diff --git a/runtime/lmcry_gcry.h b/runtime/lmcry_gcry.h
new file mode 100644
index 00000000..c0205ab9
--- /dev/null
+++ b/runtime/lmcry_gcry.h
@@ -0,0 +1,39 @@
+/* An implementation of the cryprov interface for libgcrypt.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_LMCRY_GCRY_H
+#define INCLUDED_LMCRY_GCRY_H
+#include "cryprov.h"
+
+/* interface is defined in cryprov.h, we just implement it! */
+#define lmcry_gcryCURR_IF_VERSION cryprovCURR_IF_VERSION
+typedef cryprov_if_t lmcry_gcry_if_t;
+
+/* the lmcry_gcry object */
+struct lmcry_gcry_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ gcryctx ctx;
+};
+typedef struct lmcry_gcry_s lmcry_gcry_t;
+
+/* prototypes */
+PROTOTYPEObj(lmcry_gcry);
+
+#endif /* #ifndef INCLUDED_LMCRY_GCRY_H */
diff --git a/runtime/lmsig_gt.c b/runtime/lmsig_gt.c
new file mode 100644
index 00000000..e9194c76
--- /dev/null
+++ b/runtime/lmsig_gt.c
@@ -0,0 +1,227 @@
+/* lmsig_gt.c
+ *
+ * An implementation of the sigprov interface for GuardTime.
+ *
+ * Copyright 2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "module-template.h"
+#include "glbl.h"
+#include "errmsg.h"
+#include "sigprov.h"
+#include "lmsig_gt.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+
+/* tables for interfacing with the v6 config system */
+static struct cnfparamdescr cnfpdescr[] = {
+ { "sig.hashfunction", eCmdHdlrGetWord, 0 },
+ { "sig.timestampservice", eCmdHdlrGetWord, 0 },
+ { "sig.block.sizelimit", eCmdHdlrSize, 0 },
+ { "sig.keeprecordhashes", eCmdHdlrBinary, 0 },
+ { "sig.keeptreehashes", eCmdHdlrBinary, 0 }
+};
+static struct cnfparamblk pblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfpdescr)/sizeof(struct cnfparamdescr),
+ cnfpdescr
+ };
+
+
+static void
+errfunc(__attribute__((unused)) void *usrptr, uchar *emsg)
+{
+ errmsg.LogError(0, RS_RET_SIGPROV_ERR, "Signature Provider"
+ "Error: %s - disabling signatures", emsg);
+}
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(lmsig_gt)
+ pThis->ctx = rsgtCtxNew();
+ rsgtsetErrFunc(pThis->ctx, errfunc, NULL);
+ENDobjConstruct(lmsig_gt)
+
+
+/* destructor for the lmsig_gt object */
+BEGINobjDestruct(lmsig_gt) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(lmsig_gt)
+ rsgtCtxDel(pThis->ctx);
+ENDobjDestruct(lmsig_gt)
+
+
+/* apply all params from param block to us. This must be called
+ * after construction, but before the OnFileOpen() entry point.
+ * Defaults are expected to have been set during construction.
+ */
+rsRetVal
+SetCnfParam(void *pT, struct nvlst *lst)
+{
+ lmsig_gt_t *pThis = (lmsig_gt_t*) pT;
+ int i;
+ uchar *cstr;
+ struct cnfparamvals *pvals;
+ pvals = nvlstGetParams(lst, &pblk, NULL);
+ if(Debug) {
+ dbgprintf("sig param blk in lmsig_gt:\n");
+ cnfparamsPrint(&pblk, pvals);
+ }
+
+ for(i = 0 ; i < pblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(pblk.descr[i].name, "sig.hashfunction")) {
+ cstr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ if(rsgtSetHashFunction(pThis->ctx, (char*)cstr) != 0) {
+ errmsg.LogError(0, RS_RET_ERR, "Hash function "
+ "'%s' unknown - using default", cstr);
+ }
+ free(cstr);
+ } else if(!strcmp(pblk.descr[i].name, "sig.timestampservice")) {
+ cstr = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
+ rsgtSetTimestamper(pThis->ctx, (char*) cstr);
+ free(cstr);
+ } else if(!strcmp(pblk.descr[i].name, "sig.block.sizelimit")) {
+ rsgtSetBlockSizeLimit(pThis->ctx, pvals[i].val.d.n);
+ } else if(!strcmp(pblk.descr[i].name, "sig.keeprecordhashes")) {
+ rsgtSetKeepRecordHashes(pThis->ctx, pvals[i].val.d.n);
+ } else if(!strcmp(pblk.descr[i].name, "sig.keeptreehashes")) {
+ rsgtSetKeepTreeHashes(pThis->ctx, pvals[i].val.d.n);
+ } else {
+ DBGPRINTF("lmsig_gt: program error, non-handled "
+ "param '%s'\n", pblk.descr[i].name);
+ }
+ }
+ cnfparamvalsDestruct(pvals, &pblk);
+ return RS_RET_OK;
+}
+
+
+static rsRetVal
+OnFileOpen(void *pT, uchar *fn, void *pGF)
+{
+ lmsig_gt_t *pThis = (lmsig_gt_t*) pT;
+ gtfile *pgf = (gtfile*) pGF;
+ DEFiRet;
+ DBGPRINTF("lmsig_gt: onFileOpen: %s\n", fn);
+ /* note: if *pgf is set to NULL, this auto-disables GT functions */
+ *pgf = rsgtCtxOpenFile(pThis->ctx, fn);
+ sigblkInit(*pgf);
+ RETiRet;
+}
+
+/* Note: we assume that the record is terminated by a \n.
+ * As of the GuardTime paper, \n is not part of the signed
+ * message, so we subtract one from the record size. This
+ * may cause issues with non-standard formats, but let's
+ * see how things evolve (the verifier will not work in
+ * any case when the records are not \n delimited...).
+ * rgerhards, 2013-03-17
+ */
+static rsRetVal
+OnRecordWrite(void *pF, uchar *rec, rs_size_t lenRec)
+{
+ DEFiRet;
+ DBGPRINTF("lmsig_gt: onRecordWrite (%d): %s\n", lenRec-1, rec);
+ sigblkAddRecord(pF, rec, lenRec-1);
+
+ RETiRet;
+}
+
+static rsRetVal
+OnFileClose(void *pF)
+{
+ DEFiRet;
+ DBGPRINTF("lmsig_gt: onFileClose\n");
+ rsgtfileDestruct(pF);
+
+ RETiRet;
+}
+
+BEGINobjQueryInterface(lmsig_gt)
+CODESTARTobjQueryInterface(lmsig_gt)
+ if(pIf->ifVersion != sigprovCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+ pIf->Construct = (rsRetVal(*)(void*)) lmsig_gtConstruct;
+ pIf->SetCnfParam = SetCnfParam;
+ pIf->Destruct = (rsRetVal(*)(void*)) lmsig_gtDestruct;
+ pIf->OnFileOpen = OnFileOpen;
+ pIf->OnRecordWrite = OnRecordWrite;
+ pIf->OnFileClose = OnFileClose;
+finalize_it:
+ENDobjQueryInterface(lmsig_gt)
+
+
+BEGINObjClassExit(lmsig_gt, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(lmsig_gt)
+ /* release objects we no longer need */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+
+ rsgtExit();
+ENDObjClassExit(lmsig_gt)
+
+
+BEGINObjClassInit(lmsig_gt, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ if(rsgtInit("rsyslogd " VERSION) != 0) {
+ errmsg.LogError(0, RS_RET_SIGPROV_ERR, "error initializing "
+ "signature provider - cannot sign");
+ ABORT_FINALIZE(RS_RET_SIGPROV_ERR);
+ }
+ENDObjClassInit(lmsig_gt)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ lmsig_gtClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(lmsig_gtClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
diff --git a/runtime/lmsig_gt.h b/runtime/lmsig_gt.h
new file mode 100644
index 00000000..665e6a8e
--- /dev/null
+++ b/runtime/lmsig_gt.h
@@ -0,0 +1,40 @@
+/* An implementation of the sigprov interface for GuardTime.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_LMSIG_GT_H
+#define INCLUDED_LMSIG_GT_H
+#include "sigprov.h"
+#include "librsgt.h"
+
+/* interface is defined in sigprov.h, we just implement it! */
+#define lmsig_gtCURR_IF_VERSION sigprovCURR_IF_VERSION
+typedef sigprov_if_t lmsig_gt_if_t;
+
+/* the lmsig_gt object */
+struct lmsig_gt_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ gtctx ctx; /* librsgt context - contains all we need */
+};
+typedef struct lmsig_gt_s lmsig_gt_t;
+
+/* prototypes */
+PROTOTYPEObj(lmsig_gt);
+
+#endif /* #ifndef INCLUDED_LMSIG_GT_H */
diff --git a/runtime/module-template.h b/runtime/module-template.h
new file mode 100644
index 00000000..8a958f90
--- /dev/null
+++ b/runtime/module-template.h
@@ -0,0 +1,1019 @@
+/* module-template.h
+ * This header contains macros that can be used to implement the
+ * plumbing of modules.
+ *
+ * File begun on 2007-07-25 by RGerhards
+ *
+ * Copyright 2007-2012 Adiscon GmbH. This is Adiscon-exclusive code without any other
+ * contributions. *** GPLv3 ***
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Rsyslog is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Rsyslog is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Rsyslog. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ */
+#ifndef MODULE_TEMPLATE_H_INCLUDED
+#define MODULE_TEMPLATE_H_INCLUDED 1
+
+#include "modules.h"
+#include "obj.h"
+#include "objomsr.h"
+#include "threads.h"
+
+/* macro to define standard output-module static data members
+ */
+#define DEF_MOD_STATIC_DATA \
+ static __attribute__((unused)) rsRetVal (*omsdRegCFSLineHdlr)(uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie);
+
+#define DEF_OMOD_STATIC_DATA \
+ DEF_MOD_STATIC_DATA \
+ DEFobjCurrIf(obj) \
+ static __attribute__((unused)) int bCoreSupportsBatching;
+#define DEF_IMOD_STATIC_DATA \
+ DEF_MOD_STATIC_DATA \
+ DEFobjCurrIf(obj)
+#define DEF_LMOD_STATIC_DATA \
+ DEF_MOD_STATIC_DATA
+#define DEF_PMOD_STATIC_DATA \
+ DEFobjCurrIf(obj) \
+ DEF_MOD_STATIC_DATA
+#define DEF_SMOD_STATIC_DATA \
+ DEFobjCurrIf(obj) \
+ DEF_MOD_STATIC_DATA
+
+
+/* Macro to define the module type. Each module can only have a single type. If
+ * a module provides multiple types, several separate modules must be created which
+ * then should share a single library containing the majority of code. This macro
+ * must be present in each module. -- rgerhards, 2007-12-14
+ * Note that MODULE_TYPE_TESTBENCH is reserved for testbenches, but
+ * declared in their own header files (because the rest does not need these
+ * defines). -- rgerhards, 2008-06-13
+ */
+#define MODULE_TYPE(x)\
+static rsRetVal modGetType(eModType_t *modType) \
+ { \
+ *modType = x; \
+ return RS_RET_OK;\
+ }
+
+#define MODULE_TYPE_INPUT MODULE_TYPE(eMOD_IN)
+#define MODULE_TYPE_OUTPUT MODULE_TYPE(eMOD_OUT)
+#define MODULE_TYPE_PARSER MODULE_TYPE(eMOD_PARSER)
+#define MODULE_TYPE_STRGEN MODULE_TYPE(eMOD_STRGEN)
+#define MODULE_TYPE_LIB \
+ DEF_LMOD_STATIC_DATA \
+ MODULE_TYPE(eMOD_LIB)
+
+/* Macro to define whether the module should be kept dynamically linked.
+ */
+#define MODULE_KEEP_TYPE(x)\
+static rsRetVal modGetKeepType(eModKeepType_t *modKeepType) \
+ { \
+ *modKeepType = x; \
+ return RS_RET_OK;\
+ }
+#define MODULE_TYPE_NOKEEP MODULE_KEEP_TYPE(eMOD_NOKEEP)
+#define MODULE_TYPE_KEEP MODULE_KEEP_TYPE(eMOD_KEEP)
+
+/* macro to define a unique module id. This must be able to fit in a void*. The
+ * module id must be unique inside a running rsyslogd application. It is used to
+ * track ownership of several objects. Most importantly, when the module is
+ * unloaded the module id value is used to find what needs to be destroyed.
+ * We currently use a pointer to modExit() as the module id. This sounds to be
+ * reasonable save, as each module must have this entry point AND there is no valid
+ * reason for twice this entry point being in memory.
+ * rgerhards, 2007-11-21
+ */
+#define STD_LOADABLE_MODULE_ID ((void*) modExit)
+
+
+/* macro to implement the "modGetID()" interface function
+ * rgerhards 2007-11-21
+ */
+#define DEFmodGetID \
+static rsRetVal modGetID(void **pID) \
+ { \
+ *pID = STD_LOADABLE_MODULE_ID;\
+ return RS_RET_OK;\
+ }
+
+/* macro to provide the v6 config system module name
+ */
+#define MODULE_CNFNAME(name) \
+static rsRetVal modGetCnfName(uchar **cnfName) \
+ { \
+ *cnfName = (uchar*) name; \
+ return RS_RET_OK;\
+ }
+
+
+/* to following macros are used to generate function headers and standard
+ * functionality. It works as follows (described on the sample case of
+ * createInstance()):
+ *
+ * BEGINcreateInstance
+ * ... custom variable definitions (on stack) ... (if any)
+ * CODESTARTcreateInstance
+ * ... custom code ... (if any)
+ * ENDcreateInstance
+ */
+
+/* createInstance()
+ */
+#define BEGINcreateInstance \
+static rsRetVal createInstance(instanceData **ppData)\
+ {\
+ DEFiRet; /* store error code here */\
+ instanceData *pData; /* use this to point to data elements */
+
+#define CODESTARTcreateInstance \
+ if((pData = calloc(1, sizeof(instanceData))) == NULL) {\
+ *ppData = NULL;\
+ ENDfunc \
+ return RS_RET_OUT_OF_MEMORY;\
+ }
+
+#define ENDcreateInstance \
+ *ppData = pData;\
+ RETiRet;\
+}
+
+/* freeInstance()
+ * This is the cleanup function for the module instance. It is called immediately before
+ * the module instance is destroyed (unloaded). The module should do any cleanup
+ * here, e.g. close file, free instantance heap memory and the like. Control will
+ * not be passed back to the module once this function is finished. Keep in mind,
+ * however, that other instances may still be loaded and used. So do not destroy
+ * anything that may be used by another instance. If you have such a ressource, you
+ * currently need to do the instance counting yourself.
+ */
+#define BEGINfreeInstance \
+static rsRetVal freeInstance(void* pModData)\
+{\
+ DEFiRet;\
+ instanceData *pData;
+
+#define CODESTARTfreeInstance \
+ pData = (instanceData*) pModData;
+
+#define ENDfreeInstance \
+ if(pData != NULL)\
+ free(pData); /* we need to free this in any case */\
+ RETiRet;\
+}
+
+/* isCompatibleWithFeature()
+ */
+#define BEGINisCompatibleWithFeature \
+static rsRetVal isCompatibleWithFeature(syslogFeature __attribute__((unused)) eFeat)\
+{\
+ rsRetVal iRet = RS_RET_INCOMPATIBLE; \
+ BEGINfunc
+
+#define CODESTARTisCompatibleWithFeature
+
+#define ENDisCompatibleWithFeature \
+ RETiRet;\
+}
+
+
+/* beginTransaction()
+ * introduced in v4.3.3 -- rgerhards, 2009-04-27
+ */
+#define BEGINbeginTransaction \
+static rsRetVal beginTransaction(instanceData __attribute__((unused)) *pData)\
+{\
+ DEFiRet;
+
+#define CODESTARTbeginTransaction /* currently empty, but may be extended */
+
+#define ENDbeginTransaction \
+ RETiRet;\
+}
+
+
+/* endTransaction()
+ * introduced in v4.3.3 -- rgerhards, 2009-04-27
+ */
+#define BEGINendTransaction \
+static rsRetVal endTransaction(instanceData __attribute__((unused)) *pData)\
+{\
+ DEFiRet;
+
+#define CODESTARTendTransaction /* currently empty, but may be extended */
+
+#define ENDendTransaction \
+ RETiRet;\
+}
+
+
+/* doAction()
+ */
+#define BEGINdoAction \
+static rsRetVal doAction(uchar __attribute__((unused)) **ppString, unsigned __attribute__((unused)) iMsgOpts, instanceData __attribute__((unused)) *pData)\
+{\
+ DEFiRet;
+
+#define CODESTARTdoAction \
+ /* ppString may be NULL if the output module requested no strings */
+
+#define ENDdoAction \
+ RETiRet;\
+}
+
+
+/* dbgPrintInstInfo()
+ * Extra comments:
+ * Print debug information about this instance.
+ */
+#define BEGINdbgPrintInstInfo \
+static rsRetVal dbgPrintInstInfo(void *pModData)\
+{\
+ DEFiRet;\
+ instanceData *pData = NULL;
+
+#define CODESTARTdbgPrintInstInfo \
+ pData = (instanceData*) pModData; \
+ (void)pData; /* prevent compiler warning if unused! */
+
+#define ENDdbgPrintInstInfo \
+ RETiRet;\
+}
+
+
+/* parseSelectorAct()
+ * Extra comments:
+ * try to process a selector action line. Checks if the action
+ * applies to this module and, if so, processed it. If not, it
+ * is left untouched. The driver will then call another module.
+ * On exit, ppModData must point to instance data. Also, a string
+ * request object must be created and filled. A macro is defined
+ * for that.
+ * For the most usual case, we have defined a macro below.
+ * If more than one string is requested, the macro can be used together
+ * with own code that overwrites the entry count. In this case, the
+ * macro must come before the own code. It is recommended to be
+ * placed right after CODESTARTparseSelectorAct.
+ */
+#define BEGINparseSelectorAct \
+static rsRetVal parseSelectorAct(uchar **pp, void **ppModData, omodStringRequest_t **ppOMSR)\
+{\
+ DEFiRet;\
+ uchar *p;\
+ instanceData *pData = NULL;
+
+#define CODESTARTparseSelectorAct \
+ assert(pp != NULL);\
+ assert(ppModData != NULL);\
+ assert(ppOMSR != NULL);\
+ p = *pp;
+
+#define CODE_STD_STRING_REQUESTparseSelectorAct(NumStrReqEntries) \
+ CHKiRet(OMSRconstruct(ppOMSR, NumStrReqEntries));
+
+#define CODE_STD_FINALIZERparseSelectorAct \
+finalize_it:\
+ if(iRet == RS_RET_OK || iRet == RS_RET_OK_WARN || iRet == RS_RET_SUSPENDED) {\
+ *ppModData = pData;\
+ *pp = p;\
+ } else {\
+ /* cleanup, we failed */\
+ if(*ppOMSR != NULL) {\
+ OMSRdestruct(*ppOMSR);\
+ *ppOMSR = NULL;\
+ }\
+ if(pData != NULL) {\
+ freeInstance(pData);\
+ } \
+ }
+
+#define ENDparseSelectorAct \
+ RETiRet;\
+}
+
+
+/* newActInst()
+ * Extra comments:
+ * This creates a new instance of a the action that implements the call.
+ * This is part of the conf2 (rsyslog v6) config system. It is called by
+ * the core when an action object has been obtained. The output module
+ * must then verify parameters and create a new action instance (if
+ * parameters are acceptable) or return an error code.
+ * On exit, ppModData must point to instance data. Also, a string
+ * request object must be created and filled. A macro is defined
+ * for that.
+ * For the most usual case, we have defined a macro below.
+ * If more than one string is requested, the macro can be used together
+ * with own code that overwrites the entry count. In this case, the
+ * macro must come before the own code. It is recommended to be
+ * placed right after CODESTARTnewActInst.
+ */
+#define BEGINnewActInst \
+static rsRetVal newActInst(uchar __attribute__((unused)) *modName, \
+ struct nvlst *lst, void **ppModData, omodStringRequest_t **ppOMSR)\
+{\
+ DEFiRet;\
+ instanceData *pData = NULL; \
+ *ppOMSR = NULL;
+
+#define CODESTARTnewActInst \
+
+#define CODE_STD_STRING_REQUESTnewActInst(NumStrReqEntries) \
+ CHKiRet(OMSRconstruct(ppOMSR, NumStrReqEntries));
+
+#define CODE_STD_FINALIZERnewActInst \
+finalize_it:\
+ if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) {\
+ *ppModData = pData;\
+ } else {\
+ /* cleanup, we failed */\
+ if(*ppOMSR != NULL) {\
+ OMSRdestruct(*ppOMSR);\
+ *ppOMSR = NULL;\
+ }\
+ if(pData != NULL) {\
+ freeInstance(pData);\
+ } \
+ }
+
+#define ENDnewActInst \
+ RETiRet;\
+}
+
+
+/* newInpInst()
+ * This is basically the equivalent to newActInst() for creating input
+ * module (listener) instances.
+ */
+#define BEGINnewInpInst \
+static rsRetVal newInpInst(struct nvlst *lst)\
+{\
+ DEFiRet;
+
+#define CODESTARTnewInpInst \
+
+#define CODE_STD_FINALIZERnewInpInst
+
+#define ENDnewInpInst \
+ RETiRet;\
+}
+
+
+/* tryResume()
+ * This entry point is called to check if a module can resume operations. This
+ * happens when a module requested that it be suspended. In suspended state,
+ * the engine periodically tries to resume the module. If that succeeds, normal
+ * processing continues. If not, the module will not be called unless a
+ * tryResume() call succeeds.
+ * Returns RS_RET_OK, if resumption succeeded, RS_RET_SUSPENDED otherwise
+ * rgerhard, 2007-08-02
+ */
+#define BEGINtryResume \
+static rsRetVal tryResume(instanceData __attribute__((unused)) *pData)\
+{\
+ DEFiRet;
+
+#define CODESTARTtryResume \
+ assert(pData != NULL);
+
+#define ENDtryResume \
+ RETiRet;\
+}
+
+
+/* initConfVars() - initialize pre-v6.3-config variables
+ */
+#define BEGINinitConfVars \
+static rsRetVal initConfVars(void)\
+{\
+ DEFiRet;
+
+#define CODESTARTinitConfVars
+
+#define ENDinitConfVars \
+ RETiRet;\
+}
+
+
+/* queryEtryPt()
+ */
+#define BEGINqueryEtryPt \
+DEFmodGetID \
+static rsRetVal queryEtryPt(uchar *name, rsRetVal (**pEtryPoint)())\
+{\
+ DEFiRet;
+
+#define CODESTARTqueryEtryPt \
+ if((name == NULL) || (pEtryPoint == NULL)) {\
+ ENDfunc \
+ return RS_RET_PARAM_ERROR;\
+ } \
+ *pEtryPoint = NULL;
+
+#define ENDqueryEtryPt \
+ if(iRet == RS_RET_OK)\
+ if(*pEtryPoint == NULL) { \
+ dbgprintf("entry point '%s' not present in module\n", name); \
+ iRet = RS_RET_MODULE_ENTRY_POINT_NOT_FOUND;\
+ } \
+ RETiRet;\
+}
+
+/* the following definition is the standard block for queryEtryPt for all types
+ * of modules. It should be included in any module, and typically is so by calling
+ * the module-type specific macros.
+ */
+#define CODEqueryEtryPt_STD_MOD_QUERIES \
+ if(!strcmp((char*) name, "modExit")) {\
+ *pEtryPoint = modExit;\
+ } else if(!strcmp((char*) name, "modGetID")) {\
+ *pEtryPoint = modGetID;\
+ } else if(!strcmp((char*) name, "getType")) {\
+ *pEtryPoint = modGetType;\
+ } else if(!strcmp((char*) name, "getKeepType")) {\
+ *pEtryPoint = modGetKeepType;\
+ }
+
+/* the following definition is the standard block for queryEtryPt for output
+ * modules. This can be used if no specific handling (e.g. to cover version
+ * differences) is needed.
+ */
+#define CODEqueryEtryPt_STD_OMOD_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES \
+ else if(!strcmp((char*) name, "doAction")) {\
+ *pEtryPoint = doAction;\
+ } else if(!strcmp((char*) name, "dbgPrintInstInfo")) {\
+ *pEtryPoint = dbgPrintInstInfo;\
+ } else if(!strcmp((char*) name, "freeInstance")) {\
+ *pEtryPoint = freeInstance;\
+ } else if(!strcmp((char*) name, "parseSelectorAct")) {\
+ *pEtryPoint = parseSelectorAct;\
+ } else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\
+ *pEtryPoint = isCompatibleWithFeature;\
+ } else if(!strcmp((char*) name, "tryResume")) {\
+ *pEtryPoint = tryResume;\
+ }
+
+
+/* the following definition is queryEtryPt block that must be added
+ * if an output module supports the transactional interface.
+ * rgerhards, 2009-04-27
+ */
+#define CODEqueryEtryPt_TXIF_OMOD_QUERIES \
+ else if(!strcmp((char*) name, "beginTransaction")) {\
+ *pEtryPoint = beginTransaction;\
+ } else if(!strcmp((char*) name, "endTransaction")) {\
+ *pEtryPoint = endTransaction;\
+ }
+
+
+/* the following definition is a queryEtryPt block that must be added
+ * if a non-output module supports "isCompatibleWithFeature".
+ * rgerhards, 2009-07-20
+ */
+#define CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES \
+ else if(!strcmp((char*) name, "isCompatibleWithFeature")) {\
+ *pEtryPoint = isCompatibleWithFeature;\
+ }
+
+
+/* the following definition is the standard block for queryEtryPt for INPUT
+ * modules. This can be used if no specific handling (e.g. to cover version
+ * differences) is needed.
+ */
+#define CODEqueryEtryPt_STD_IMOD_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES \
+ else if(!strcmp((char*) name, "runInput")) {\
+ *pEtryPoint = runInput;\
+ } else if(!strcmp((char*) name, "willRun")) {\
+ *pEtryPoint = willRun;\
+ } else if(!strcmp((char*) name, "afterRun")) {\
+ *pEtryPoint = afterRun;\
+ }
+
+
+/* the following block is to be added for modules that support the v2
+ * config system. The config name is also provided.
+ */
+#define CODEqueryEtryPt_STD_CONF2_QUERIES \
+ else if(!strcmp((char*) name, "beginCnfLoad")) {\
+ *pEtryPoint = beginCnfLoad;\
+ } else if(!strcmp((char*) name, "endCnfLoad")) {\
+ *pEtryPoint = endCnfLoad;\
+ } else if(!strcmp((char*) name, "checkCnf")) {\
+ *pEtryPoint = checkCnf;\
+ } else if(!strcmp((char*) name, "activateCnf")) {\
+ *pEtryPoint = activateCnf;\
+ } else if(!strcmp((char*) name, "freeCnf")) {\
+ *pEtryPoint = freeCnf;\
+ } \
+ CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES
+
+/* the following block is to be added for modules that support v2
+ * module global parameters [module(...)]
+ */
+#define CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES \
+ else if(!strcmp((char*) name, "setModCnf")) {\
+ *pEtryPoint = setModCnf;\
+ } \
+
+/* the following block is to be added for output modules that support the v2
+ * config system. The config name is also provided.
+ */
+#define CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES \
+ else if(!strcmp((char*) name, "newActInst")) {\
+ *pEtryPoint = newActInst;\
+ } \
+ CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES
+
+
+/* the following block is to be added for input modules that support the v2
+ * config system. The config name is also provided.
+ */
+#define CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES \
+ else if(!strcmp((char*) name, "newInpInst")) {\
+ *pEtryPoint = newInpInst;\
+ } \
+ CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES
+
+
+/* the following block is to be added for modules that require
+ * pre priv drop activation support.
+ */
+#define CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES \
+ else if(!strcmp((char*) name, "activateCnfPrePrivDrop")) {\
+ *pEtryPoint = activateCnfPrePrivDrop;\
+ }
+
+/* the following block is to be added for modules that support
+ * their config name. This is required for the rsyslog v6 config
+ * system, especially for outout modules which do not require
+ * the new set of begin/end config settings.
+ */
+#define CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES \
+ else if(!strcmp((char*) name, "getModCnfName")) {\
+ *pEtryPoint = modGetCnfName;\
+ }
+
+/* the following definition is the standard block for queryEtryPt for LIBRARY
+ * modules. This can be used if no specific handling (e.g. to cover version
+ * differences) is needed.
+ */
+#define CODEqueryEtryPt_STD_LIB_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES
+
+/* the following definition is the standard block for queryEtryPt for PARSER
+ * modules. This can be used if no specific handling (e.g. to cover version
+ * differences) is needed.
+ */
+#define CODEqueryEtryPt_STD_PMOD_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES \
+ else if(!strcmp((char*) name, "parse")) {\
+ *pEtryPoint = parse;\
+ } else if(!strcmp((char*) name, "GetParserName")) {\
+ *pEtryPoint = GetParserName;\
+ }
+
+/* the following definition is the standard block for queryEtryPt for Strgen
+ * modules. This can be used if no specific handling (e.g. to cover version
+ * differences) is needed.
+ */
+#define CODEqueryEtryPt_STD_SMOD_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES \
+ else if(!strcmp((char*) name, "strgen")) {\
+ *pEtryPoint = strgen;\
+ } else if(!strcmp((char*) name, "GetName")) {\
+ *pEtryPoint = GetStrgenName;\
+ }
+
+/* modInit()
+ * This has an extra parameter, which is the specific name of the modInit
+ * function. That is needed for built-in modules, which must have unique
+ * names in order to link statically. Please note that this is always only
+ * the case with modInit() and NO other entry point. The reason is that only
+ * modInit() is visible form a linker/loader point of view. All other entry
+ * points are passed via rsyslog-internal query functions and are defined
+ * static inside the modules source. This is an important concept, as it allows
+ * us to support different interface versions within a single module. (Granted,
+ * we do not currently have different interface versions, so we can not put
+ * it to a test - but our firm believe is that we can do all abstraction needed...)
+ *
+ * Extra Comments:
+ * initialize the module
+ *
+ * Later, much more must be done. So far, we only return a pointer
+ * to the queryEtryPt() function
+ * TODO: do interface version checking & handshaking
+ * iIfVersRequetsed is the version of the interface specification that the
+ * caller would like to see being used. ipIFVersProvided is what we
+ * decide to provide.
+ * rgerhards, 2007-11-21: see modExit() comment below for important information
+ * on the need to initialize static data with code. modInit() may be called on a
+ * cached, left-in-memory copy of a previous incarnation.
+ */
+#define BEGINmodInit(uniqName) \
+rsRetVal modInit##uniqName(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t __attribute__((unused)) *pModInfo)\
+{\
+ DEFiRet; \
+ rsRetVal (*pObjGetObjInterface)(obj_if_t *pIf);
+
+#define CODESTARTmodInit \
+ assert(pHostQueryEtryPt != NULL);\
+ iRet = pHostQueryEtryPt((uchar*)"objGetObjInterface", &pObjGetObjInterface); \
+ if((iRet != RS_RET_OK) || (pQueryEtryPt == NULL) || (ipIFVersProvided == NULL) || (pObjGetObjInterface == NULL)) { \
+ ENDfunc \
+ return (iRet == RS_RET_OK) ? RS_RET_PARAM_ERROR : iRet; \
+ } \
+ /* now get the obj interface so that we can access other objects */ \
+ CHKiRet(pObjGetObjInterface(&obj));
+
+/* do those initializations necessary for legacy config variables */
+#define INITLegCnfVars \
+ initConfVars();
+
+#define ENDmodInit \
+finalize_it:\
+ *pQueryEtryPt = queryEtryPt;\
+ RETiRet;\
+}
+
+
+/* now come some check functions, which enable a standard way of obtaining feature
+ * information from the core. feat is the to-be-tested feature and featVar is a
+ * variable that receives the result (0-not support, 1-supported).
+ * This must be a macro, so that it is put into the output's code. Otherwise, we
+ * would need to rely on a library entry point, which is what we intend to avoid ;)
+ * rgerhards, 2009-04-27
+ */
+#define INITChkCoreFeature(featVar, feat) \
+{ \
+ rsRetVal MACRO_Ret; \
+ rsRetVal (*pQueryCoreFeatureSupport)(int*, unsigned); \
+ int bSupportsIt; \
+ featVar = 0; \
+ MACRO_Ret = pHostQueryEtryPt((uchar*)"queryCoreFeatureSupport", &pQueryCoreFeatureSupport); \
+ if(MACRO_Ret == RS_RET_OK) { \
+ /* found entry point, so let's see if core supports it */ \
+ CHKiRet((*pQueryCoreFeatureSupport)(&bSupportsIt, feat)); \
+ if(bSupportsIt) \
+ featVar = 1; \
+ } else if(MACRO_Ret != RS_RET_ENTRY_POINT_NOT_FOUND) { \
+ ABORT_FINALIZE(MACRO_Ret); /* Something else went wrong, what is not acceptable */ \
+ } \
+}
+
+
+
+/* definitions for host API queries */
+#define CODEmodInit_QueryRegCFSLineHdlr \
+ CHKiRet(pHostQueryEtryPt((uchar*)"regCfSysLineHdlr", &omsdRegCFSLineHdlr));
+
+
+/* modExit()
+ * This is the counterpart to modInit(). It destroys a module and makes it ready for
+ * unloading. It is similiar to freeInstance() for the instance data. Please note that
+ * this entry point needs to free any module-global data structures and registrations.
+ * For example, the CfSysLineHandlers a module has registered need to be unregistered
+ * here. This entry point is only called immediately before unloading of the module. So
+ * it is likely to be destroyed. HOWEVER, the caller may decide to keep the module cached.
+ * So a module must never assume that it is actually destroyed. A call to modInit() may
+ * happen immediately after modExit(). So a module can NOT assume that static data elements
+ * are being re-initialized by the loader - this must always be done by module code itself.
+ * It is suggested to do this in modInit(). - rgerhards, 2007-11-21
+ */
+#define BEGINmodExit \
+static rsRetVal modExit(void)\
+{\
+ DEFiRet;
+
+#define CODESTARTmodExit
+
+#define ENDmodExit \
+ RETiRet;\
+}
+
+
+/* beginCnfLoad()
+ * This is a function tells an input module that a new config load begins.
+ * The core passes in a handle to the new module-specific module conf to
+ * the module. -- rgerards, 2011-05-03
+ */
+#define BEGINbeginCnfLoad \
+static rsRetVal beginCnfLoad(modConfData_t **ptr, __attribute__((unused)) rsconf_t *pConf)\
+{\
+ modConfData_t *pModConf; \
+ DEFiRet;
+
+#define CODESTARTbeginCnfLoad \
+ if((pModConf = calloc(1, sizeof(modConfData_t))) == NULL) {\
+ *ptr = NULL;\
+ ENDfunc \
+ return RS_RET_OUT_OF_MEMORY;\
+ }
+
+#define ENDbeginCnfLoad \
+ *ptr = pModConf;\
+ RETiRet;\
+}
+
+
+/* setModCnf()
+ * This function permits to set module global parameters via the v2 config
+ * interface. It may be called multiple times, but parameters must not be
+ * set in a conflicting way. The module must use its current config load
+ * context when processing the directives.
+ * Note that lst may be NULL, especially if the module is loaded via the
+ * legacy config system. The module must check for this.
+ * NOTE: This entry point must only be implemented if module global
+ * parameters are actually required.
+ */
+#define BEGINsetModCnf \
+static rsRetVal setModCnf(struct nvlst *lst)\
+{\
+ DEFiRet;
+
+#define CODESTARTsetModCnf
+
+#define ENDsetModCnf \
+ RETiRet;\
+}
+
+
+/* endCnfLoad()
+ * This is a function tells an input module that the current config load ended.
+ * It gets a last chance to make changes to its in-memory config object. After
+ * this call, the config object must no longer be changed.
+ * The pModConf pointer passed into the module must no longer be used.
+ * rgerards, 2011-05-03
+ */
+#define BEGINendCnfLoad \
+static rsRetVal endCnfLoad(modConfData_t *ptr)\
+{\
+ modConfData_t __attribute__((unused)) *pModConf = (modConfData_t*) ptr; \
+ DEFiRet;
+
+#define CODESTARTendCnfLoad
+
+#define ENDendCnfLoad \
+ RETiRet;\
+}
+
+
+/* checkCnf()
+ * Check the provided config object for errors, inconsistencies and other things
+ * that do not work out.
+ * NOTE: no part of the config must be activated, so some checks that require
+ * activation can not be done in this entry point. They must be done in the
+ * activateConf() stage, where the caller must also be prepared for error
+ * returns.
+ * rgerhards, 2011-05-03
+ */
+#define BEGINcheckCnf \
+static rsRetVal checkCnf(modConfData_t *ptr)\
+{\
+ modConfData_t __attribute__((unused)) *pModConf = (modConfData_t*) ptr; \
+ DEFiRet;
+
+#define CODESTARTcheckCnf
+
+#define ENDcheckCnf \
+ RETiRet;\
+}
+
+
+/* activateCnfPrePrivDrop()
+ * Initial config activation, before dropping privileges. This is an optional
+ * entry points that should only be implemented by those module that really need
+ * it. Processing should be limited to the minimum possible. Main activation
+ * should happen in the normal activateCnf() call.
+ * rgerhards, 2011-05-06
+ */
+#define BEGINactivateCnfPrePrivDrop \
+static rsRetVal activateCnfPrePrivDrop(modConfData_t *ptr)\
+{\
+ modConfData_t *pModConf = (modConfData_t*) ptr; \
+ DEFiRet;
+
+#define CODESTARTactivateCnfPrePrivDrop
+
+#define ENDactivateCnfPrePrivDrop \
+ RETiRet;\
+}
+
+
+/* activateCnf()
+ * This activates the provided config, and may report errors if they are detected
+ * during activation.
+ * rgerhards, 2011-05-03
+ */
+#define BEGINactivateCnf \
+static rsRetVal activateCnf(modConfData_t *ptr)\
+{\
+ modConfData_t __attribute__((unused)) *pModConf = (modConfData_t*) ptr; \
+ DEFiRet;
+
+#define CODESTARTactivateCnf
+
+#define ENDactivateCnf \
+ RETiRet;\
+}
+
+
+/* freeCnf()
+ * This is a function tells an input module that it must free all data
+ * associated with the passed-in module config.
+ * rgerhards, 2011-05-03
+ */
+#define BEGINfreeCnf \
+static rsRetVal freeCnf(void *ptr)\
+{\
+ modConfData_t *pModConf = (modConfData_t*) ptr; \
+ DEFiRet;
+
+#define CODESTARTfreeCnf
+
+#define ENDfreeCnf \
+ if(pModConf != NULL)\
+ free(pModConf); /* we need to free this in any case */\
+ RETiRet;\
+}
+
+
+/* runInput()
+ * This is the main function for input modules. It is used to gather data from the
+ * input source and submit it to the message queue. Each runInput() instance has its own
+ * thread. This is handled by the rsyslog engine. It needs to spawn off new threads only
+ * if there is a module-internal need to do so.
+ */
+#define BEGINrunInput \
+static rsRetVal runInput(thrdInfo_t __attribute__((unused)) *pThrd)\
+{\
+ DEFiRet;
+
+#define CODESTARTrunInput \
+ dbgSetThrdName((uchar*)__FILE__); /* we need to provide something better later */
+
+#define ENDrunInput \
+ RETiRet;\
+}
+
+
+/* willRun()
+ * This is a function that will be replaced in the longer term. It is used so
+ * that a module can tell the caller if it will run or not. This is to be replaced
+ * when we introduce input module instances. However, these require config syntax
+ * changes and I may (or may not... ;)) hold that until another config file
+ * format is available. -- rgerhards, 2007-12-17
+ * returns RS_RET_NO_RUN if it will not run (RS_RET_OK or error otherwise)
+ */
+#define BEGINwillRun \
+static rsRetVal willRun(void)\
+{\
+ DEFiRet;
+
+#define CODESTARTwillRun
+
+#define ENDwillRun \
+ RETiRet;\
+}
+
+
+/* afterRun()
+ * This function is called after an input module has been run and its thread has
+ * been terminated. It shall do any necessary cleanup.
+ * This is expected to evolve into a freeInstance type of call once the input module
+ * interface evolves to support multiple instances.
+ * rgerhards, 2007-12-17
+ */
+#define BEGINafterRun \
+static rsRetVal afterRun(void)\
+{\
+ DEFiRet;
+
+#define CODESTARTafterRun
+
+#define ENDafterRun \
+ RETiRet;\
+}
+
+
+/* doHUP()
+ * This function is optional. Currently, it is available to output plugins
+ * only, but may be made available to other types of plugins in the future.
+ * A plugin does not need to define this entry point. If if does, it gets
+ * called when a non-restart type of HUP is done. A plugin should register
+ * this function so that it can close files, connection or other ressources
+ * on HUP - if it can be assume the user wanted to do this as a part of HUP
+ * processing. Note that the name "HUP" has historical reasons, it stems back
+ * to the infamous SIGHUP which was sent to restart a syslogd. We still retain
+ * that legacy, but may move this to a different signal.
+ * rgerhards, 2008-10-22
+ */
+#define CODEqueryEtryPt_doHUP \
+ else if(!strcmp((char*) name, "doHUP")) {\
+ *pEtryPoint = doHUP;\
+ }
+#define BEGINdoHUP \
+static rsRetVal doHUP(instanceData __attribute__((unused)) *pData)\
+{\
+ DEFiRet;
+
+#define CODESTARTdoHUP
+
+#define ENDdoHUP \
+ RETiRet;\
+}
+
+
+/* SetShutdownImmdtPtr()
+ * This function is optional. If defined by an output plugin, it is called
+ * each time the action is invoked to set the "ShutdownImmediate" pointer,
+ * which is used during termination to indicate the action should shutdown
+ * as quickly as possible.
+ */
+#define CODEqueryEtryPt_SetShutdownImmdtPtr \
+ else if(!strcmp((char*) name, "SetShutdownImmdtPtr")) {\
+ *pEtryPoint = SetShutdownImmdtPtr;\
+ }
+#define BEGINSetShutdownImmdtPtr \
+static rsRetVal SetShutdownImmdtPtr(instanceData __attribute__((unused)) *pData, int *pPtr)\
+{\
+ DEFiRet;
+
+#define CODESTARTSetShutdownImmdtPtr
+
+#define ENDSetShutdownImmdtPtr \
+ RETiRet;\
+}
+
+
+/* parse() - main entry point of parser modules
+ */
+#define BEGINparse \
+static rsRetVal parse(msg_t *pMsg)\
+{\
+ DEFiRet;
+
+#define CODESTARTparse \
+ assert(pMsg != NULL);
+
+#define ENDparse \
+ RETiRet;\
+}
+
+
+/* strgen() - main entry point of parser modules
+ */
+#define BEGINstrgen \
+static rsRetVal strgen(msg_t *pMsg, uchar **ppBuf, size_t *pLenBuf) \
+{\
+ DEFiRet;
+
+#define CODESTARTstrgen \
+ assert(pMsg != NULL);
+
+#define ENDstrgen \
+ RETiRet;\
+}
+
+
+/* function to specify the parser name. This is done via a single command which
+ * receives a ANSI string as parameter.
+ */
+#define PARSER_NAME(x) \
+static rsRetVal GetParserName(uchar **ppSz)\
+{\
+ *ppSz = UCHAR_CONSTANT(x);\
+ return RS_RET_OK;\
+}
+
+
+
+/* function to specify the strgen name. This is done via a single command which
+ * receives a ANSI string as parameter.
+ */
+#define STRGEN_NAME(x) \
+static rsRetVal GetStrgenName(uchar **ppSz)\
+{\
+ *ppSz = UCHAR_CONSTANT(x);\
+ return RS_RET_OK;\
+}
+
+
+#endif /* #ifndef MODULE_TEMPLATE_H_INCLUDED */
+
+/* vim:set ai:
+ */
diff --git a/runtime/modules.c b/runtime/modules.c
new file mode 100644
index 00000000..56606306
--- /dev/null
+++ b/runtime/modules.c
@@ -0,0 +1,1395 @@
+/* modules.c
+ * This is the implementation of syslogd modules object.
+ * This object handles plug-ins and build-in modules of all kind.
+ *
+ * Modules are reference-counted. Anyone who access a module must call
+ * Use() before any function is accessed and Release() when he is done.
+ * When the reference count reaches 0, rsyslog unloads the module (that
+ * may be changed in the future to cache modules). Rsyslog does NOT
+ * unload modules with a reference count > 0, even if the unload
+ * method is called!
+ *
+ * File begun on 2007-07-22 by RGerhards
+ *
+ * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <assert.h>
+#include <errno.h>
+#include <pthread.h>
+#ifdef OS_BSD
+# include "libgen.h"
+#endif
+
+#include <dlfcn.h> /* TODO: replace this with the libtools equivalent! */
+
+#include <unistd.h>
+#include <sys/file.h>
+
+#ifdef OS_SOLARIS
+# define PATH_MAX MAXPATHLEN
+#endif
+
+#include "cfsysline.h"
+#include "rsconf.h"
+#include "modules.h"
+#include "errmsg.h"
+#include "parser.h"
+#include "strgen.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(parser)
+DEFobjCurrIf(strgen)
+
+static modInfo_t *pLoadedModules = NULL; /* list of currently-loaded modules */
+static modInfo_t *pLoadedModulesLast = NULL; /* tail-pointer */
+
+/* already dlopen()-ed libs */
+static struct dlhandle_s *pHandles = NULL;
+
+static uchar *pModDir; /* directory where loadable modules are found */
+
+/* tables for interfacing with the v6 config system */
+/* action (instance) parameters */
+static struct cnfparamdescr actpdescr[] = {
+ { "load", eCmdHdlrGetWord, 1 }
+};
+static struct cnfparamblk pblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(actpdescr)/sizeof(struct cnfparamdescr),
+ actpdescr
+ };
+
+
+/* we provide a set of dummy functions for modules that do not support the
+ * some interfaces.
+ * On the commit feature: As the modules do not support it, they commit each message they
+ * receive, and as such the dummies can always return RS_RET_OK without causing
+ * harm. This simplifies things as in action processing we do not need to check
+ * if the transactional entry points exist.
+ */
+static rsRetVal
+dummyBeginTransaction()
+{
+ return RS_RET_OK;
+}
+static rsRetVal
+dummyEndTransaction()
+{
+ return RS_RET_OK;
+}
+static rsRetVal
+dummyIsCompatibleWithFeature()
+{
+ return RS_RET_INCOMPATIBLE;
+}
+static rsRetVal
+dummynewActInst(uchar *modName, struct nvlst __attribute__((unused)) *dummy1,
+ void __attribute__((unused)) **dummy2, omodStringRequest_t __attribute__((unused)) **dummy3)
+{
+ errmsg.LogError(0, RS_RET_CONFOBJ_UNSUPPORTED, "config objects are not "
+ "supported by module '%s' -- legacy config options "
+ "MUST be used instead", modName);
+ return RS_RET_CONFOBJ_UNSUPPORTED;
+}
+
+#ifdef DEBUG
+/* we add some home-grown support to track our users (and detect who does not free us). In
+ * the long term, this should probably be migrated into debug.c (TODO). -- rgerhards, 2008-03-11
+ */
+
+/* add a user to the current list of users (always at the root) */
+static void
+modUsrAdd(modInfo_t *pThis, char *pszUsr)
+{
+ modUsr_t *pUsr;
+
+ BEGINfunc
+ if((pUsr = calloc(1, sizeof(modUsr_t))) == NULL)
+ goto finalize_it;
+
+ if((pUsr->pszFile = strdup(pszUsr)) == NULL) {
+ free(pUsr);
+ goto finalize_it;
+ }
+
+ if(pThis->pModUsrRoot != NULL) {
+ pUsr->pNext = pThis->pModUsrRoot;
+ }
+ pThis->pModUsrRoot = pUsr;
+
+finalize_it:
+ ENDfunc;
+}
+
+
+/* remove a user from the current user list
+ * rgerhards, 2008-03-11
+ */
+static void
+modUsrDel(modInfo_t *pThis, char *pszUsr)
+{
+ modUsr_t *pUsr;
+ modUsr_t *pPrev = NULL;
+
+ for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) {
+ if(!strcmp(pUsr->pszFile, pszUsr))
+ break;
+ else
+ pPrev = pUsr;
+ }
+
+ if(pUsr == NULL) {
+ dbgprintf("oops - tried to delete user %s from module %s and it wasn't registered as one...\n",
+ pszUsr, pThis->pszName);
+ } else {
+ if(pPrev == NULL) {
+ /* This was at the root! */
+ pThis->pModUsrRoot = pUsr->pNext;
+ } else {
+ pPrev->pNext = pUsr->pNext;
+ }
+ /* free ressources */
+ free(pUsr->pszFile);
+ free(pUsr);
+ pUsr = NULL; /* just to make sure... */
+ }
+}
+
+
+/* print a short list all all source files using the module in question
+ * rgerhards, 2008-03-11
+ */
+static void
+modUsrPrint(modInfo_t *pThis)
+{
+ modUsr_t *pUsr;
+
+ for(pUsr = pThis->pModUsrRoot ; pUsr != NULL ; pUsr = pUsr->pNext) {
+ dbgprintf("\tmodule %s is currently in use by file %s\n",
+ pThis->pszName, pUsr->pszFile);
+ }
+}
+
+
+/* print all loaded modules and who is accessing them. This is primarily intended
+ * to be called at end of run to detect "module leaks" and who is causing them.
+ * rgerhards, 2008-03-11
+ */
+//static void
+void
+modUsrPrintAll(void)
+{
+ modInfo_t *pMod;
+
+ BEGINfunc
+ for(pMod = pLoadedModules ; pMod != NULL ; pMod = pMod->pNext) {
+ dbgprintf("printing users of loadable module %s, refcount %u, ptr %p, type %d\n", pMod->pszName, pMod->uRefCnt, pMod, pMod->eType);
+ modUsrPrint(pMod);
+ }
+ ENDfunc
+}
+
+#endif /* #ifdef DEBUG */
+
+
+/* Construct a new module object
+ */
+static rsRetVal moduleConstruct(modInfo_t **pThis)
+{
+ modInfo_t *pNew;
+
+ if((pNew = calloc(1, sizeof(modInfo_t))) == NULL)
+ return RS_RET_OUT_OF_MEMORY;
+
+ /* OK, we got the element, now initialize members that should
+ * not be zero-filled.
+ */
+
+ *pThis = pNew;
+ return RS_RET_OK;
+}
+
+
+/* Destructs a module object. The object must not be linked to the
+ * linked list of modules. Please note that all other dependencies on this
+ * modules must have been removed before (e.g. CfSysLineHandlers!)
+ */
+static void moduleDestruct(modInfo_t *pThis)
+{
+ assert(pThis != NULL);
+ free(pThis->pszName);
+ free(pThis->cnfName);
+ if(pThis->pModHdlr != NULL) {
+# ifdef VALGRIND
+# warning "dlclose disabled for valgrind"
+# else
+ if (pThis->eKeepType == eMOD_NOKEEP) {
+ dlclose(pThis->pModHdlr);
+ }
+# endif
+ }
+
+ free(pThis);
+}
+
+
+/* This enables a module to query the core for specific features.
+ * rgerhards, 2009-04-22
+ */
+static rsRetVal queryCoreFeatureSupport(int *pBool, unsigned uFeat)
+{
+ DEFiRet;
+
+ if((pBool == NULL))
+ ABORT_FINALIZE(RS_RET_PARAM_ERROR);
+
+ *pBool = (uFeat & CORE_FEATURE_BATCHING) ? 1 : 0;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* The following function is the queryEntryPoint for host-based entry points.
+ * Modules may call it to get access to core interface functions. Please note
+ * that utility functions can be accessed via shared libraries - at least this
+ * is my current shool of thinking.
+ * Please note that the implementation as a query interface allows to take
+ * care of plug-in interface version differences. -- rgerhards, 2007-07-31
+ * ... but often it better not to use a new interface. So we now add core
+ * functions here that a plugin may request. -- rgerhards, 2009-04-22
+ */
+static rsRetVal queryHostEtryPt(uchar *name, rsRetVal (**pEtryPoint)())
+{
+ DEFiRet;
+
+ if((name == NULL) || (pEtryPoint == NULL))
+ ABORT_FINALIZE(RS_RET_PARAM_ERROR);
+
+ if(!strcmp((char*) name, "regCfSysLineHdlr")) {
+ *pEtryPoint = regCfSysLineHdlr;
+ } else if(!strcmp((char*) name, "objGetObjInterface")) {
+ *pEtryPoint = objGetObjInterface;
+ } else if(!strcmp((char*) name, "OMSRgetSupportedTplOpts")) {
+ *pEtryPoint = OMSRgetSupportedTplOpts;
+ } else if(!strcmp((char*) name, "queryCoreFeatureSupport")) {
+ *pEtryPoint = queryCoreFeatureSupport;
+ } else {
+ *pEtryPoint = NULL; /* to be on the safe side */
+ ABORT_FINALIZE(RS_RET_ENTRY_POINT_NOT_FOUND);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* get the name of a module
+ */
+uchar *
+modGetName(modInfo_t *pThis)
+{
+ return((pThis->pszName == NULL) ? (uchar*) "" : pThis->pszName);
+}
+
+
+/* get the state-name of a module. The state name is its name
+ * together with a short description of the module state (which
+ * is pulled from the module itself.
+ * rgerhards, 2007-07-24
+ * TODO: the actual state name is not yet pulled
+ */
+static uchar *modGetStateName(modInfo_t *pThis)
+{
+ return(modGetName(pThis));
+}
+
+
+/* Add a module to the loaded module linked list
+ */
+static inline void
+addModToGlblList(modInfo_t *pThis)
+{
+ assert(pThis != NULL);
+
+ if(pLoadedModules == NULL) {
+ pLoadedModules = pLoadedModulesLast = pThis;
+ } else {
+ /* there already exist entries */
+ pThis->pPrev = pLoadedModulesLast;
+ pLoadedModulesLast->pNext = pThis;
+ pLoadedModulesLast = pThis;
+ }
+}
+
+
+/* ready module for config processing. this includes checking if the module
+ * is already in the config, so this function may return errors. Returns a
+ * pointer to the last module inthe current config. That pointer needs to
+ * be passed to addModToCnfLst() when it is called later in the process.
+ */
+rsRetVal
+readyModForCnf(modInfo_t *pThis, cfgmodules_etry_t **ppNew, cfgmodules_etry_t **ppLast)
+{
+ cfgmodules_etry_t *pNew;
+ cfgmodules_etry_t *pLast;
+ DEFiRet;
+ assert(pThis != NULL);
+
+ if(loadConf == NULL) {
+ FINALIZE; /* we are in an early init state */
+ }
+
+ /* check for duplicates and, as a side-activity, identify last node */
+ pLast = loadConf->modules.root;
+ if(pLast != NULL) {
+ while(1) { /* loop broken inside */
+ if(pLast->pMod == pThis) {
+ DBGPRINTF("module '%s' already in this config\n", modGetName(pThis));
+ if(strncmp((char*)modGetName(pThis), "builtin:", sizeof("builtin:")-1)) {
+ errmsg.LogError(0, RS_RET_MODULE_ALREADY_IN_CONF,
+ "module '%s' already in this config, cannot be added\n", modGetName(pThis));
+ ABORT_FINALIZE(RS_RET_MODULE_ALREADY_IN_CONF);
+ }
+ FINALIZE;
+ }
+ if(pLast->next == NULL)
+ break;
+ pLast = pLast->next;
+ }
+ }
+
+ /* if we reach this point, pLast is the tail pointer and this module is new
+ * inside the currently loaded config. So, iff it is an input module, let's
+ * pass it a pointer which it can populate with a pointer to its module conf.
+ */
+
+ CHKmalloc(pNew = MALLOC(sizeof(cfgmodules_etry_t)));
+ pNew->canActivate = 1;
+ pNew->next = NULL;
+ pNew->pMod = pThis;
+
+ if(pThis->beginCnfLoad != NULL) {
+ CHKiRet(pThis->beginCnfLoad(&pNew->modCnf, loadConf));
+ }
+
+ *ppLast = pLast;
+ *ppNew = pNew;
+finalize_it:
+ RETiRet;
+}
+
+
+/* abort the creation of a module entry without adding it to the
+ * module list. Needed to prevent mem leaks.
+ */
+static inline void
+abortCnfUse(cfgmodules_etry_t *pNew)
+{
+ free(pNew);
+}
+
+
+/* Add a module to the config module list for current loadConf.
+ * Requires last pointer obtained by readyModForCnf().
+ */
+rsRetVal
+addModToCnfList(cfgmodules_etry_t *pNew, cfgmodules_etry_t *pLast)
+{
+ DEFiRet;
+ assert(pNew != NULL);
+
+ if(loadConf == NULL) {
+ FINALIZE; /* we are in an early init state */
+ }
+
+ if(pLast == NULL) {
+ loadConf->modules.root = pNew;
+ } else {
+ /* there already exist entries */
+ pLast->next = pNew;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Get the next module pointer - this is used to traverse the list.
+ * The function returns the next pointer or NULL, if there is no next one.
+ * The last object must be provided to the function. If NULL is provided,
+ * it starts at the root of the list. Even in this case, NULL may be
+ * returned - then, the list is empty.
+ * rgerhards, 2007-07-23
+ */
+static modInfo_t *GetNxt(modInfo_t *pThis)
+{
+ modInfo_t *pNew;
+
+ if(pThis == NULL)
+ pNew = pLoadedModules;
+ else
+ pNew = pThis->pNext;
+
+ return(pNew);
+}
+
+
+/* this function is like GetNxt(), but it returns pointers to
+ * the configmodules entry, which than can be used to obtain the
+ * actual module pointer. Note that it returns those for
+ * modules of specific type only. Only modules from the provided
+ * config are returned. Note that processing speed could be improved,
+ * but this is really not relevant, as config file loading is not really
+ * something we are concerned about in regard to runtime.
+ */
+static cfgmodules_etry_t
+*GetNxtCnfType(rsconf_t *cnf, cfgmodules_etry_t *node, eModType_t rqtdType)
+{
+ if(node == NULL) { /* start at beginning of module list */
+ node = cnf->modules.root;
+ } else {
+ node = node->next;
+ }
+
+ if(rqtdType != eMOD_ANY) { /* if any, we already have the right one! */
+ while(node != NULL && node->pMod->eType != rqtdType) {
+ node = node->next;
+ }
+ }
+
+ return node;
+}
+
+
+/* Find a module with the given conf name and type. Returns NULL if none
+ * can be found, otherwise module found.
+ */
+static modInfo_t *
+FindWithCnfName(rsconf_t *cnf, uchar *name, eModType_t rqtdType)
+{
+ cfgmodules_etry_t *node;
+
+ ;
+ for( node = cnf->modules.root
+ ; node != NULL
+ ; node = node->next) {
+ if(node->pMod->eType != rqtdType || node->pMod->cnfName == NULL)
+ continue;
+ if(!strcasecmp((char*)node->pMod->cnfName, (char*)name))
+ break;
+ }
+
+ return node == NULL ? NULL : node->pMod;
+}
+
+
+/* Prepare a module for unloading.
+ * This is currently a dummy, to be filled when we have a plug-in
+ * interface - rgerhards, 2007-08-09
+ * rgerhards, 2007-11-21:
+ * When this function is called, all instance-data must already have
+ * been destroyed. In the case of output modules, this happens when the
+ * rule set is being destroyed. When we implement other module types, we
+ * need to think how we handle it there (and if we have any instance data).
+ * rgerhards, 2008-03-10: reject unload request if the module has a reference
+ * count > 0.
+ */
+static rsRetVal
+modPrepareUnload(modInfo_t *pThis)
+{
+ DEFiRet;
+ void *pModCookie;
+
+ assert(pThis != NULL);
+
+ if(pThis->uRefCnt > 0) {
+ dbgprintf("rejecting unload of module '%s' because it has a refcount of %d\n",
+ pThis->pszName, pThis->uRefCnt);
+ ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED);
+ }
+
+ CHKiRet(pThis->modGetID(&pModCookie));
+ pThis->modExit(); /* tell the module to get ready for unload */
+ CHKiRet(unregCfSysLineHdlrs4Owner(pModCookie));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Add an already-loaded module to the module linked list. This function does
+ * everything needed to fully initialize the module.
+ */
+static rsRetVal
+doModInit(rsRetVal (*modInit)(int, int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*),
+ uchar *name, void *pModHdlr, modInfo_t **pNewModule)
+{
+ rsRetVal localRet;
+ modInfo_t *pNew = NULL;
+ uchar *pName;
+ parser_t *pParser; /* used for parser modules */
+ strgen_t *pStrgen; /* used for strgen modules */
+ rsRetVal (*GetName)(uchar**);
+ rsRetVal (*modGetType)(eModType_t *pType);
+ rsRetVal (*modGetKeepType)(eModKeepType_t *pKeepType);
+ struct dlhandle_s *pHandle = NULL;
+ rsRetVal (*getModCnfName)(uchar **cnfName);
+ uchar *cnfName;
+ DEFiRet;
+
+ assert(modInit != NULL);
+
+ if((iRet = moduleConstruct(&pNew)) != RS_RET_OK) {
+ pNew = NULL;
+ ABORT_FINALIZE(iRet);
+ }
+
+ CHKiRet((*modInit)(CURR_MOD_IF_VERSION, &pNew->iIFVers, &pNew->modQueryEtryPt, queryHostEtryPt, pNew));
+
+ if(pNew->iIFVers != CURR_MOD_IF_VERSION) {
+ ABORT_FINALIZE(RS_RET_MISSING_INTERFACE);
+ }
+
+ /* We now poll the module to see what type it is. We do this only once as this
+ * can never change in the lifetime of an module. -- rgerhards, 2007-12-14
+ */
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getType", &modGetType));
+ CHKiRet((*modGetType)(&pNew->eType));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getKeepType", &modGetKeepType));
+ CHKiRet((*modGetKeepType)(&pNew->eKeepType));
+ dbgprintf("module %s of type %d being loaded (keepType=%d).\n", name, pNew->eType, pNew->eKeepType);
+
+ /* OK, we know we can successfully work with the module. So we now fill the
+ * rest of the data elements. First we load the interfaces common to all
+ * module types.
+ */
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modGetID", &pNew->modGetID));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"modExit", &pNew->modExit));
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"isCompatibleWithFeature", &pNew->isCompatibleWithFeature);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND)
+ pNew->isCompatibleWithFeature = dummyIsCompatibleWithFeature;
+ else if(localRet != RS_RET_OK)
+ ABORT_FINALIZE(localRet);
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"setModCnf", &pNew->setModCnf);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND)
+ pNew->setModCnf = NULL;
+ else if(localRet != RS_RET_OK)
+ ABORT_FINALIZE(localRet);
+
+ /* optional calls for new config system */
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"getModCnfName", &getModCnfName);
+ if(localRet == RS_RET_OK) {
+ if(getModCnfName(&cnfName) == RS_RET_OK)
+ pNew->cnfName = (uchar*) strdup((char*)cnfName);
+ /**< we do not care if strdup() fails, we can accept that */
+ else
+ pNew->cnfName = NULL;
+ dbgprintf("module config name is '%s'\n", cnfName);
+ }
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"beginCnfLoad", &pNew->beginCnfLoad);
+ if(localRet == RS_RET_OK) {
+ dbgprintf("module %s supports rsyslog v6 config interface\n", name);
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"endCnfLoad", &pNew->endCnfLoad));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeCnf", &pNew->freeCnf));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"checkCnf", &pNew->checkCnf));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"activateCnf", &pNew->activateCnf));
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"activateCnfPrePrivDrop", &pNew->activateCnfPrePrivDrop);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->activateCnfPrePrivDrop = NULL;
+ } else {
+ CHKiRet(localRet);
+ }
+ } else if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->beginCnfLoad = NULL; /* flag as non-present */
+ } else {
+ ABORT_FINALIZE(localRet);
+ }
+ /* ... and now the module-specific interfaces */
+ switch(pNew->eType) {
+ case eMOD_IN:
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"runInput", &pNew->mod.im.runInput));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"willRun", &pNew->mod.im.willRun));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"afterRun", &pNew->mod.im.afterRun));
+ pNew->mod.im.bCanRun = 0;
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"newInpInst", &pNew->mod.im.newInpInst);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->mod.om.newActInst = NULL;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+ break;
+ case eMOD_OUT:
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeInstance", &pNew->freeInstance));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"dbgPrintInstInfo", &pNew->dbgPrintInstInfo));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"doAction", &pNew->mod.om.doAction));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume));
+ /* try load optional interfaces */
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"doHUP", &pNew->doHUP);
+ if(localRet != RS_RET_OK && localRet != RS_RET_MODULE_ENTRY_POINT_NOT_FOUND)
+ ABORT_FINALIZE(localRet);
+
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"SetShutdownImmdtPtr", &pNew->mod.om.SetShutdownImmdtPtr);
+ if(localRet != RS_RET_OK && localRet != RS_RET_MODULE_ENTRY_POINT_NOT_FOUND)
+ ABORT_FINALIZE(localRet);
+
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"beginTransaction", &pNew->mod.om.beginTransaction);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND)
+ pNew->mod.om.beginTransaction = dummyBeginTransaction;
+ else if(localRet != RS_RET_OK)
+ ABORT_FINALIZE(localRet);
+
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"endTransaction",
+ &pNew->mod.om.endTransaction);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->mod.om.endTransaction = dummyEndTransaction;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"newActInst", &pNew->mod.om.newActInst);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->mod.om.newActInst = dummynewActInst;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+ break;
+ case eMOD_LIB:
+ break;
+ case eMOD_PARSER:
+ /* first, we need to obtain the parser object. We could not do that during
+ * init as that would have caused class bootstrap issues which are not
+ * absolutely necessary. Note that we can call objUse() multiple times, it
+ * handles that.
+ */
+ CHKiRet(objUse(parser, CORE_COMPONENT));
+ /* here, we create a new parser object */
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parse", &pNew->mod.pm.parse));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"GetParserName", &GetName));
+ CHKiRet(GetName(&pName));
+ CHKiRet(parser.Construct(&pParser));
+
+ /* check some features */
+ localRet = pNew->isCompatibleWithFeature(sFEATUREAutomaticSanitazion);
+ if(localRet == RS_RET_OK){
+ CHKiRet(parser.SetDoSanitazion(pParser, RSTRUE));
+ }
+ localRet = pNew->isCompatibleWithFeature(sFEATUREAutomaticPRIParsing);
+ if(localRet == RS_RET_OK){
+ CHKiRet(parser.SetDoPRIParsing(pParser, RSTRUE));
+ }
+
+ CHKiRet(parser.SetName(pParser, pName));
+ CHKiRet(parser.SetModPtr(pParser, pNew));
+ CHKiRet(parser.ConstructFinalize(pParser));
+ break;
+ case eMOD_STRGEN:
+ /* first, we need to obtain the strgen object. We could not do that during
+ * init as that would have caused class bootstrap issues which are not
+ * absolutely necessary. Note that we can call objUse() multiple times, it
+ * handles that.
+ */
+ CHKiRet(objUse(strgen, CORE_COMPONENT));
+ /* here, we create a new parser object */
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"strgen", &pNew->mod.sm.strgen));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"GetName", &GetName));
+ CHKiRet(GetName(&pName));
+ CHKiRet(strgen.Construct(&pStrgen));
+ CHKiRet(strgen.SetName(pStrgen, pName));
+ CHKiRet(strgen.SetModPtr(pStrgen, pNew));
+ CHKiRet(strgen.ConstructFinalize(pStrgen));
+ break;
+ case eMOD_ANY: /* this is mostly to keep the compiler happy! */
+ DBGPRINTF("PROGRAM ERROR: eMOD_ANY set as module type\n");
+ assert(0);
+ break;
+ }
+
+ pNew->pszName = (uchar*) strdup((char*)name); /* we do not care if strdup() fails, we can accept that */
+ pNew->pModHdlr = pModHdlr;
+ if(pModHdlr == NULL) {
+ pNew->eLinkType = eMOD_LINK_STATIC;
+ } else {
+ pNew->eLinkType = eMOD_LINK_DYNAMIC_LOADED;
+
+ /* if we need to keep the linked module, save it */
+ if (pNew->eKeepType == eMOD_KEEP) {
+ /* see if we have this one already */
+ for (pHandle = pHandles; pHandle; pHandle = pHandle->next) {
+ if (!strcmp((char *)name, (char *)pHandle->pszName))
+ break;
+ }
+
+ /* not found, create it */
+ if (!pHandle) {
+ if((pHandle = malloc(sizeof (*pHandle))) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ if((pHandle->pszName = (uchar*) strdup((char*)name)) == NULL) {
+ free(pHandle);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ pHandle->pModHdlr = pModHdlr;
+ pHandle->next = pHandles;
+
+ pHandles = pHandle;
+ }
+ }
+ }
+
+ /* we initialized the structure, now let's add it to the linked list of modules */
+ addModToGlblList(pNew);
+ *pNewModule = pNew;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL)
+ moduleDestruct(pNew);
+ *pNewModule = NULL;
+ }
+
+ RETiRet;
+}
+
+/* Print loaded modules. This is more or less a
+ * debug or test aid, but anyhow I think it's worth it...
+ * This only works if the dbgprintf() subsystem is initialized.
+ * TODO: update for new input modules!
+ */
+static void modPrintList(void)
+{
+ modInfo_t *pMod;
+
+ pMod = GetNxt(NULL);
+ while(pMod != NULL) {
+ dbgprintf("Loaded Module: Name='%s', IFVersion=%d, ",
+ (char*) modGetName(pMod), pMod->iIFVers);
+ dbgprintf("type=");
+ switch(pMod->eType) {
+ case eMOD_OUT:
+ dbgprintf("output");
+ break;
+ case eMOD_IN:
+ dbgprintf("input");
+ break;
+ case eMOD_LIB:
+ dbgprintf("library");
+ break;
+ case eMOD_PARSER:
+ dbgprintf("parser");
+ break;
+ case eMOD_STRGEN:
+ dbgprintf("strgen");
+ break;
+ case eMOD_ANY: /* this is mostly to keep the compiler happy! */
+ DBGPRINTF("PROGRAM ERROR: eMOD_ANY set as module type\n");
+ assert(0);
+ break;
+ }
+ dbgprintf(" module.\n");
+ dbgprintf("Entry points:\n");
+ dbgprintf("\tqueryEtryPt: 0x%lx\n", (unsigned long) pMod->modQueryEtryPt);
+ dbgprintf("\tdbgPrintInstInfo: 0x%lx\n", (unsigned long) pMod->dbgPrintInstInfo);
+ dbgprintf("\tfreeInstance: 0x%lx\n", (unsigned long) pMod->freeInstance);
+ dbgprintf("\tbeginCnfLoad: 0x%lx\n", (unsigned long) pMod->beginCnfLoad);
+ dbgprintf("\tSetModCnf: 0x%lx\n", (unsigned long) pMod->setModCnf);
+ dbgprintf("\tcheckCnf: 0x%lx\n", (unsigned long) pMod->checkCnf);
+ dbgprintf("\tactivateCnfPrePrivDrop: 0x%lx\n", (unsigned long) pMod->activateCnfPrePrivDrop);
+ dbgprintf("\tactivateCnf: 0x%lx\n", (unsigned long) pMod->activateCnf);
+ dbgprintf("\tfreeCnf: 0x%lx\n", (unsigned long) pMod->freeCnf);
+ switch(pMod->eType) {
+ case eMOD_OUT:
+ dbgprintf("Output Module Entry Points:\n");
+ dbgprintf("\tdoAction: %p\n", pMod->mod.om.doAction);
+ dbgprintf("\tparseSelectorAct: %p\n", pMod->mod.om.parseSelectorAct);
+ dbgprintf("\tnewActInst: %p\n", (pMod->mod.om.newActInst == dummynewActInst) ?
+ NULL : pMod->mod.om.newActInst);
+ dbgprintf("\ttryResume: %p\n", pMod->tryResume);
+ dbgprintf("\tdoHUP: %p\n", pMod->doHUP);
+ dbgprintf("\tBeginTransaction: %p\n", ((pMod->mod.om.beginTransaction == dummyBeginTransaction) ?
+ NULL : pMod->mod.om.beginTransaction));
+ dbgprintf("\tEndTransaction: %p\n", ((pMod->mod.om.endTransaction == dummyEndTransaction) ?
+ NULL : pMod->mod.om.endTransaction));
+ break;
+ case eMOD_IN:
+ dbgprintf("Input Module Entry Points\n");
+ dbgprintf("\trunInput: 0x%lx\n", (unsigned long) pMod->mod.im.runInput);
+ dbgprintf("\twillRun: 0x%lx\n", (unsigned long) pMod->mod.im.willRun);
+ dbgprintf("\tafterRun: 0x%lx\n", (unsigned long) pMod->mod.im.afterRun);
+ break;
+ case eMOD_LIB:
+ break;
+ case eMOD_PARSER:
+ dbgprintf("Parser Module Entry Points\n");
+ dbgprintf("\tparse: 0x%lx\n", (unsigned long) pMod->mod.pm.parse);
+ break;
+ case eMOD_STRGEN:
+ dbgprintf("Strgen Module Entry Points\n");
+ dbgprintf("\tstrgen: 0x%lx\n", (unsigned long) pMod->mod.sm.strgen);
+ break;
+ case eMOD_ANY: /* this is mostly to keep the compiler happy! */
+ break;
+ }
+ dbgprintf("\n");
+ pMod = GetNxt(pMod); /* done, go next */
+ }
+}
+
+
+/* unlink and destroy a module. The caller must provide a pointer to the module
+ * itself as well as one to its immediate predecessor.
+ * rgerhards, 2008-02-26
+ */
+static rsRetVal
+modUnlinkAndDestroy(modInfo_t **ppThis)
+{
+ DEFiRet;
+ modInfo_t *pThis;
+
+ assert(ppThis != NULL);
+ pThis = *ppThis;
+ assert(pThis != NULL);
+
+ pthread_mutex_lock(&mutObjGlobalOp);
+
+ /* first check if we are permitted to unload */
+ if(pThis->eType == eMOD_LIB) {
+ if(pThis->uRefCnt > 0) {
+ dbgprintf("module %s NOT unloaded because it still has a refcount of %u\n",
+ pThis->pszName, pThis->uRefCnt);
+# ifdef DEBUG
+ //modUsrPrintAll();
+# endif
+ ABORT_FINALIZE(RS_RET_MODULE_STILL_REFERENCED);
+ }
+ }
+
+ /* we need to unlink the module before we can destruct it -- rgerhards, 2008-02-26 */
+ if(pThis->pPrev == NULL) {
+ /* module is root, so we need to set a new root */
+ pLoadedModules = pThis->pNext;
+ } else {
+ pThis->pPrev->pNext = pThis->pNext;
+ }
+
+ if(pThis->pNext == NULL) {
+ pLoadedModulesLast = pThis->pPrev;
+ } else {
+ pThis->pNext->pPrev = pThis->pPrev;
+ }
+
+ /* finally, we are ready for the module to go away... */
+ dbgprintf("Unloading module %s\n", modGetName(pThis));
+ CHKiRet(modPrepareUnload(pThis));
+ *ppThis = pThis->pNext;
+
+ moduleDestruct(pThis);
+
+finalize_it:
+ pthread_mutex_unlock(&mutObjGlobalOp);
+ RETiRet;
+}
+
+
+/* unload all loaded modules of a specific type (use eMOD_ALL if you want to
+ * unload all module types). The unload happens only if the module is no longer
+ * referenced. So some modules may survive this call.
+ * rgerhards, 2008-03-11
+ */
+static rsRetVal
+modUnloadAndDestructAll(eModLinkType_t modLinkTypesToUnload)
+{
+ DEFiRet;
+ modInfo_t *pModCurr; /* module currently being processed */
+
+ pModCurr = GetNxt(NULL);
+ while(pModCurr != NULL) {
+ if(modLinkTypesToUnload == eMOD_LINK_ALL || pModCurr->eLinkType == modLinkTypesToUnload) {
+ if(modUnlinkAndDestroy(&pModCurr) == RS_RET_MODULE_STILL_REFERENCED) {
+ pModCurr = GetNxt(pModCurr);
+ } else {
+ /* Note: if the module was successfully unloaded, it has updated the
+ * pModCurr pointer to the next module. However, the unload process may
+ * still have indirectly referenced the pointer list in a way that the
+ * unloaded module is not aware of. So we restart the unload process
+ * to make sure we do not fall into a trap (what we did ;)). The
+ * performance toll is minimal. -- rgerhards, 2008-04-28
+ */
+ pModCurr = GetNxt(NULL);
+ }
+ } else {
+ pModCurr = GetNxt(pModCurr);
+ }
+ }
+
+# ifdef DEBUG
+ /* DEV DEBUG only!
+ if(pLoadedModules != NULL) {
+ dbgprintf("modules still loaded after module.UnloadAndDestructAll:\n");
+ modUsrPrintAll();
+ }
+ */
+# endif
+
+ RETiRet;
+}
+
+/* find module with given name in global list */
+static inline rsRetVal
+findModule(uchar *pModName, int iModNameLen, modInfo_t **pMod)
+{
+ modInfo_t *pModInfo;
+ uchar *pModNameCmp;
+ DEFiRet;
+
+ pModInfo = GetNxt(NULL);
+ while(pModInfo != NULL) {
+ if(!strncmp((char *) pModName, (char *) (pModNameCmp = modGetName(pModInfo)), iModNameLen) &&
+ (!*(pModNameCmp + iModNameLen) || !strcmp((char *) pModNameCmp + iModNameLen, ".so"))) {
+ dbgprintf("Module '%s' found\n", pModName);
+ break;
+ }
+ pModInfo = GetNxt(pModInfo);
+ }
+ *pMod = pModInfo;
+ RETiRet;
+}
+
+
+/* load a module and initialize it, based on doModLoad() from conf.c
+ * rgerhards, 2008-03-05
+ * varmojfekoj added support for dynamically loadable modules on 2007-08-13
+ * rgerhards, 2007-09-25: please note that the non-threadsafe function dlerror() is
+ * called below. This is ok because modules are currently only loaded during
+ * configuration file processing, which is executed on a single thread. Should we
+ * change that design at any stage (what is unlikely), we need to find a
+ * replacement.
+ * rgerhards, 2011-04-27:
+ * Parameter "bConfLoad" tells us if the load was triggered by a config handler, in
+ * which case we need to tie the loaded module to the current config. If bConfLoad == 0,
+ * the system loads a module for internal reasons, this is not directly tied to a
+ * configuration. We could also think if it would be useful to add only certain types
+ * of modules, but the current implementation at least looks simpler.
+ * Note: pvals = NULL means legacy config system
+ */
+static rsRetVal
+Load(uchar *pModName, sbool bConfLoad, struct nvlst *lst)
+{
+ size_t iPathLen, iModNameLen;
+ int bHasExtension;
+ void *pModHdlr, *pModInit;
+ modInfo_t *pModInfo;
+ cfgmodules_etry_t *pNew;
+ cfgmodules_etry_t *pLast;
+ uchar *pModDirCurr, *pModDirNext;
+ int iLoadCnt;
+ struct dlhandle_s *pHandle = NULL;
+# ifdef PATH_MAX
+ uchar pathBuf[PATH_MAX+1];
+# else
+ uchar pathBuf[4096];
+# endif
+ uchar *pPathBuf = pathBuf;
+ size_t lenPathBuf = sizeof(pathBuf);
+ rsRetVal localRet;
+ DEFiRet;
+
+ assert(pModName != NULL);
+ DBGPRINTF("Requested to load module '%s'\n", pModName);
+
+ iModNameLen = strlen((char*)pModName);
+ /* overhead for a full path is potentially 1 byte for a slash,
+ * three bytes for ".so" and one byte for '\0'.
+ */
+# define PATHBUF_OVERHEAD 1 + iModNameLen + 3 + 1
+
+ pthread_mutex_lock(&mutObjGlobalOp);
+
+ if(iModNameLen > 3 && !strcmp((char *) pModName + iModNameLen - 3, ".so")) {
+ iModNameLen -= 3;
+ bHasExtension = RSTRUE;
+ } else
+ bHasExtension = RSFALSE;
+
+ CHKiRet(findModule(pModName, iModNameLen, &pModInfo));
+ if(pModInfo != NULL) {
+ DBGPRINTF("Module '%s' already loaded\n", pModName);
+ if(bConfLoad) {
+ localRet = readyModForCnf(pModInfo, &pNew, &pLast);
+ if(pModInfo->setModCnf != NULL && localRet == RS_RET_OK) {
+ if(!strncmp((char*)pModName, "builtin:", sizeof("builtin:")-1)) {
+ if(pModInfo->bSetModCnfCalled) {
+ errmsg.LogError(0, RS_RET_DUP_PARAM,
+ "parameters for built-in module %s already set - ignored\n",
+ pModName);
+ ABORT_FINALIZE(RS_RET_DUP_PARAM);
+ } else {
+ /* for built-in moules, we need to call setModConf,
+ * because there is no way to set parameters at load
+ * time for obvious reasons...
+ */
+ if(lst != NULL)
+ pModInfo->setModCnf(lst);
+ pModInfo->bSetModCnfCalled = 1;
+ }
+ } else {
+ /* regular modules need to be added to conf list (for
+ * builtins, this happend during initial load).
+ */
+ addModToCnfList(pNew, pLast);
+ }
+ }
+ }
+ FINALIZE;
+ }
+
+ pModDirCurr = (uchar *)((pModDir == NULL) ?
+ _PATH_MODDIR : (char *)pModDir);
+ pModDirNext = NULL;
+ pModHdlr = NULL;
+ iLoadCnt = 0;
+ do { /* now build our load module name */
+ if(*pModName == '/' || *pModName == '.') {
+ if(lenPathBuf < PATHBUF_OVERHEAD) {
+ if(pPathBuf != pathBuf) /* already malloc()ed memory? */
+ free(pPathBuf);
+ /* we always alloc enough memory for everything we potentiall need to add */
+ lenPathBuf = PATHBUF_OVERHEAD;
+ CHKmalloc(pPathBuf = malloc(sizeof(char)*lenPathBuf));
+ }
+ *pPathBuf = '\0'; /* we do not need to append the path - its already in the module name */
+ iPathLen = 0;
+ } else {
+ *pPathBuf = '\0';
+
+ iPathLen = strlen((char *)pModDirCurr);
+ pModDirNext = (uchar *)strchr((char *)pModDirCurr, ':');
+ if(pModDirNext)
+ iPathLen = (size_t)(pModDirNext - pModDirCurr);
+
+ if(iPathLen == 0) {
+ if(pModDirNext) {
+ pModDirCurr = pModDirNext + 1;
+ continue;
+ }
+ break;
+ } else if(iPathLen > lenPathBuf - PATHBUF_OVERHEAD) {
+ if(pPathBuf != pathBuf) /* already malloc()ed memory? */
+ free(pPathBuf);
+ /* we always alloc enough memory for everything we potentiall need to add */
+ lenPathBuf = iPathLen + PATHBUF_OVERHEAD;
+ CHKmalloc(pPathBuf = malloc(sizeof(char)*lenPathBuf));
+ }
+
+ memcpy((char *) pPathBuf, (char *)pModDirCurr, iPathLen);
+ if((pPathBuf[iPathLen - 1] != '/')) {
+ /* we have space, made sure in previous check */
+ pPathBuf[iPathLen++] = '/';
+ }
+ pPathBuf[iPathLen] = '\0';
+
+ if(pModDirNext)
+ pModDirCurr = pModDirNext + 1;
+ }
+
+ /* ... add actual name ... */
+ strncat((char *) pPathBuf, (char *) pModName, lenPathBuf - iPathLen - 1);
+
+ /* now see if we have an extension and, if not, append ".so" */
+ if(!bHasExtension) {
+ /* we do not have an extension and so need to add ".so"
+ * TODO: I guess this is highly importable, so we should change the
+ * algo over time... -- rgerhards, 2008-03-05
+ */
+ strncat((char *) pPathBuf, ".so", lenPathBuf - strlen((char*) pPathBuf) - 1);
+ iPathLen += 3;
+ }
+
+ /* complete load path constructed, so ... GO! */
+ dbgprintf("loading module '%s'\n", pPathBuf);
+
+ /* see if we have this one already */
+ for (pHandle = pHandles; pHandle; pHandle = pHandle->next) {
+ if (!strcmp((char *)pModName, (char *)pHandle->pszName)) {
+ pModHdlr = pHandle->pModHdlr;
+ break;
+ }
+ }
+
+ /* not found, try to dynamically link it */
+ if (!pModHdlr) {
+ pModHdlr = dlopen((char *) pPathBuf, RTLD_NOW);
+ }
+
+ iLoadCnt++;
+
+ } while(pModHdlr == NULL && *pModName != '/' && pModDirNext);
+
+ if(!pModHdlr) {
+ if(iLoadCnt) {
+ errmsg.LogError(0, RS_RET_MODULE_LOAD_ERR_DLOPEN, "could not load module '%s', dlopen: %s\n",
+ pPathBuf, dlerror());
+ } else {
+ errmsg.LogError(0, NO_ERRCODE, "could not load module '%s', ModDir was '%s'\n", pPathBuf,
+ ((pModDir == NULL) ? _PATH_MODDIR : (char *)pModDir));
+ }
+ ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_DLOPEN);
+ }
+ if(!(pModInit = dlsym(pModHdlr, "modInit"))) {
+ errmsg.LogError(0, RS_RET_MODULE_LOAD_ERR_NO_INIT,
+ "could not load module '%s', dlsym: %s\n", pPathBuf, dlerror());
+ dlclose(pModHdlr);
+ ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_NO_INIT);
+ }
+ if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr, &pModInfo)) != RS_RET_OK) {
+ errmsg.LogError(0, RS_RET_MODULE_LOAD_ERR_INIT_FAILED,
+ "could not load module '%s', rsyslog error %d\n", pPathBuf, iRet);
+ dlclose(pModHdlr);
+ ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_INIT_FAILED);
+ }
+
+ if(bConfLoad) {
+ readyModForCnf(pModInfo, &pNew, &pLast);
+ if(pModInfo->setModCnf != NULL) {
+ if(lst != NULL) {
+ localRet = pModInfo->setModCnf(lst);
+ if(localRet != RS_RET_OK) {
+ errmsg.LogError(0, localRet,
+ "module '%s', failed processing config parameters",
+ pPathBuf);
+ abortCnfUse(pNew);
+ ABORT_FINALIZE(localRet);
+ }
+ }
+ pModInfo->bSetModCnfCalled = 1;
+ }
+ addModToCnfList(pNew, pLast);
+ }
+
+finalize_it:
+ if(pPathBuf != pathBuf) /* used malloc()ed memory? */
+ free(pPathBuf);
+ pthread_mutex_unlock(&mutObjGlobalOp);
+ RETiRet;
+}
+
+
+/* the v6+ way of loading modules: process a "module(...)" directive.
+ * rgerhards, 2012-06-20
+ */
+rsRetVal
+modulesProcessCnf(struct cnfobj *o)
+{
+ struct cnfparamvals *pvals;
+ uchar *cnfModName = NULL;
+ int typeIdx;
+ DEFiRet;
+
+ pvals = nvlstGetParams(o->nvlst, &pblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ DBGPRINTF("modulesProcessCnf params:\n");
+ cnfparamsPrint(&pblk, pvals);
+ typeIdx = cnfparamGetIdx(&pblk, "load");
+ if(pvals[typeIdx].bUsed == 0) {
+ errmsg.LogError(0, RS_RET_CONF_RQRD_PARAM_MISSING, "module type missing");
+ ABORT_FINALIZE(RS_RET_CONF_RQRD_PARAM_MISSING);
+ }
+
+ cnfModName = (uchar*)es_str2cstr(pvals[typeIdx].val.d.estr, NULL);
+ iRet = Load(cnfModName, 1, o->nvlst);
+
+finalize_it:
+ free(cnfModName);
+ cnfparamvalsDestruct(pvals, &pblk);
+ RETiRet;
+}
+
+
+/* set the default module load directory. A NULL value may be provided, in
+ * which case any previous value is deleted but no new one set. The caller-provided
+ * string is duplicated. If it needs to be freed, that's the caller's duty.
+ * rgerhards, 2008-03-07
+ */
+static rsRetVal
+SetModDir(uchar *pszModDir)
+{
+ DEFiRet;
+
+ dbgprintf("setting default module load directory '%s'\n", pszModDir);
+ if(pModDir != NULL) {
+ free(pModDir);
+ }
+
+ pModDir = (uchar*) strdup((char*)pszModDir);
+
+ RETiRet;
+}
+
+
+/* Reference-Counting object access: add 1 to the current reference count. Must be
+ * called by anyone interested in using a module. -- rgerhards, 20080-03-10
+ */
+static rsRetVal
+Use(char *srcFile, modInfo_t *pThis)
+{
+ DEFiRet;
+
+ assert(pThis != NULL);
+ pThis->uRefCnt++;
+ dbgprintf("source file %s requested reference for module '%s', reference count now %u\n",
+ srcFile, pThis->pszName, pThis->uRefCnt);
+
+# ifdef DEBUG
+ modUsrAdd(pThis, srcFile);
+# endif
+
+ RETiRet;
+
+}
+
+
+/* Reference-Counting object access: subract one from the current refcount. Must
+ * by called by anyone who no longer needs a module. If count reaches 0, the
+ * module is unloaded. -- rgerhards, 20080-03-10
+ */
+static rsRetVal
+Release(char *srcFile, modInfo_t **ppThis)
+{
+ DEFiRet;
+ modInfo_t *pThis;
+
+ assert(ppThis != NULL);
+ pThis = *ppThis;
+ assert(pThis != NULL);
+ if(pThis->uRefCnt == 0) {
+ /* oops, we are already at 0? */
+ dbgprintf("internal error: module '%s' already has a refcount of 0 (released by %s)!\n",
+ pThis->pszName, srcFile);
+ } else {
+ --pThis->uRefCnt;
+ dbgprintf("file %s released module '%s', reference count now %u\n",
+ srcFile, pThis->pszName, pThis->uRefCnt);
+# ifdef DEBUG
+ modUsrDel(pThis, srcFile);
+ modUsrPrint(pThis);
+# endif
+ }
+
+ if(pThis->uRefCnt == 0) {
+ /* we have a zero refcount, so we must unload the module */
+ dbgprintf("module '%s' has zero reference count, unloading...\n", pThis->pszName);
+ modUnlinkAndDestroy(&pThis);
+ /* we must NOT do a *ppThis = NULL, because ppThis now points into freed memory!
+ * If in doubt, see obj.c::ReleaseObj() for how we are called.
+ */
+ }
+
+ RETiRet;
+
+}
+
+
+/* exit our class
+ * rgerhards, 2008-03-11
+ */
+BEGINObjClassExit(module, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(module)
+ /* release objects we no longer need */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(parser, CORE_COMPONENT);
+ free(pModDir);
+# ifdef DEBUG
+ modUsrPrintAll(); /* debug aid - TODO: integrate with debug.c, at least the settings! */
+# endif
+ENDObjClassExit(module)
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(module)
+CODESTARTobjQueryInterface(module)
+ if(pIf->ifVersion != moduleCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->GetNxt = GetNxt;
+ pIf->GetNxtCnfType = GetNxtCnfType;
+ pIf->GetName = modGetName;
+ pIf->GetStateName = modGetStateName;
+ pIf->PrintList = modPrintList;
+ pIf->FindWithCnfName = FindWithCnfName;
+ pIf->UnloadAndDestructAll = modUnloadAndDestructAll;
+ pIf->doModInit = doModInit;
+ pIf->SetModDir = SetModDir;
+ pIf->Load = Load;
+ pIf->Use = Use;
+ pIf->Release = Release;
+finalize_it:
+ENDobjQueryInterface(module)
+
+
+/* Initialize our class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-03-05
+ */
+BEGINAbstractObjClassInit(module, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ uchar *pModPath;
+
+ /* use any module load path specified in the environment */
+ if((pModPath = (uchar*) getenv("RSYSLOG_MODDIR")) != NULL) {
+ SetModDir(pModPath);
+ }
+
+ /* now check if another module path was set via the command line (-M)
+ * if so, that overrides the environment. Please note that we must use
+ * a global setting here because the command line parser can NOT call
+ * into the module object, because it is not initialized at that point. So
+ * instead a global setting is changed and we pick it up as soon as we
+ * initialize -- rgerhards, 2008-04-04
+ */
+ if(glblModPath != NULL) {
+ SetModDir(glblModPath);
+ }
+
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ENDObjClassInit(module)
+
+/* vi:set ai:
+ */
diff --git a/runtime/modules.h b/runtime/modules.h
new file mode 100644
index 00000000..64644be2
--- /dev/null
+++ b/runtime/modules.h
@@ -0,0 +1,201 @@
+/* modules.h
+ *
+ * Definition for build-in and plug-ins module handler. This file is the base
+ * for all dynamically loadable module support. In theory, in v3 all modules
+ * are dynamically loaded, in practice we currently do have a few build-in
+ * once. This may become removed.
+ *
+ * The loader keeps track of what is loaded. For library modules, it is also
+ * used to find objects (libraries) and to obtain the queryInterface function
+ * for them. A reference count is maintened for libraries, so that they are
+ * unloaded only when nobody still accesses them.
+ *
+ * File begun on 2007-07-22 by RGerhards
+ *
+ * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#ifndef MODULES_H_INCLUDED
+#define MODULES_H_INCLUDED 1
+
+#include "objomsr.h"
+#include "rainerscript.h"
+
+/* the following define defines the current version of the module interface.
+ * It can be used by any module which want's to simply prevent version conflicts
+ * and does not intend to do specific old-version emulations.
+ * rgerhards, 2008-03-04
+ * version 3 adds modInfo_t ptr to call of modInit -- rgerhards, 2008-03-10
+ * version 4 removes needUDPSocket OM callback -- rgerhards, 2008-03-22
+ * version 5 changes the way parsing works for input modules. This is
+ * an important change, parseAndSubmitMessage() goes away. Other
+ * module types are not affected. -- rgerhards, 2008-10-09
+ * version 6 introduces scoping support (starting with the output
+ * modules) -- rgerhards, 2010-07-27
+ */
+#define CURR_MOD_IF_VERSION 6
+
+typedef enum eModType_ {
+ eMOD_IN = 0, /* input module */
+ eMOD_OUT = 1, /* output module */
+ eMOD_LIB = 2, /* library module */
+ eMOD_PARSER = 3,/* parser module */
+ eMOD_STRGEN = 4,/* strgen module */
+ eMOD_ANY = 5 /* meta-name for "any type of module" -- to be used in function calls */
+} eModType_t;
+
+
+#ifdef DEBUG
+typedef struct modUsr_s {
+ struct modUsr_s *pNext;
+ char *pszFile;
+} modUsr_t;
+#endif
+
+
+/* how is this module linked? */
+typedef enum eModLinkType_ {
+ eMOD_LINK_STATIC,
+ eMOD_LINK_DYNAMIC_UNLOADED, /* dynalink module, currently not loaded */
+ eMOD_LINK_DYNAMIC_LOADED, /* dynalink module, currently loaded */
+ eMOD_LINK_ALL /* special: all linkage types, e.g. for unload */
+} eModLinkType_t;
+
+/* remember which shared libs we dlopen()-ed */
+struct dlhandle_s {
+ uchar *pszName;
+ void *pModHdlr;
+ struct dlhandle_s *next;
+};
+
+/* should this module be kept linked? */
+typedef enum eModKeepType_ {
+ eMOD_NOKEEP,
+ eMOD_KEEP
+} eModKeepType_t;
+
+struct modInfo_s {
+ struct modInfo_s *pPrev; /* support for creating a double linked module list */
+ struct modInfo_s *pNext; /* support for creating a linked module list */
+ int iIFVers; /* Interface version of module */
+ eModType_t eType; /* type of this module */
+ eModLinkType_t eLinkType;
+ eModKeepType_t eKeepType; /* keep the module dynamically linked on unload */
+ uchar* pszName; /* printable module name, e.g. for dbgprintf */
+ uchar* cnfName; /* name to be used in config statements (e.g. 'name="omusrmsg"') */
+ unsigned uRefCnt; /* reference count for this module; 0 -> may be unloaded */
+ sbool bSetModCnfCalled;/* is setModCnf already called? Needed for built-in modules */
+ /* functions supported by all types of modules */
+ rsRetVal (*modInit)(int, int*, rsRetVal(**)()); /* initialize the module */
+ /* be sure to support version handshake! */
+ rsRetVal (*modQueryEtryPt)(uchar *name, rsRetVal (**EtryPoint)()); /* query entry point addresses */
+ rsRetVal (*isCompatibleWithFeature)(syslogFeature);
+ rsRetVal (*freeInstance)(void*);/* called before termination or module unload */
+ rsRetVal (*dbgPrintInstInfo)(void*);/* called before termination or module unload */
+ rsRetVal (*tryResume)(void*);/* called to see if module actin can be resumed now */
+ rsRetVal (*modExit)(void); /* called before termination or module unload */
+ rsRetVal (*modGetID)(void **); /* get its unique ID from module */
+ rsRetVal (*doHUP)(void *); /* non-restart type HUP handler */
+ /* v2 config system specific */
+ rsRetVal (*beginCnfLoad)(void*newCnf, rsconf_t *pConf);
+ rsRetVal (*setModCnf)(struct nvlst *lst);
+ rsRetVal (*endCnfLoad)(void*Cnf);
+ rsRetVal (*checkCnf)(void*Cnf);
+ rsRetVal (*activateCnfPrePrivDrop)(void*Cnf);
+ rsRetVal (*activateCnf)(void*Cnf); /* make provided config the running conf */
+ rsRetVal (*freeCnf)(void*Cnf);
+ /* end v2 config system specific */
+ /* below: create an instance of this module. Most importantly the module
+ * can allocate instance memory in this call.
+ */
+ rsRetVal (*createInstance)();
+ union {
+ struct {/* data for input modules */
+/* TODO: remove? */rsRetVal (*willRun)(void); /* check if the current config will be able to run*/
+ rsRetVal (*runInput)(thrdInfo_t*); /* function to gather input and submit to queue */
+ rsRetVal (*afterRun)(thrdInfo_t*); /* function to gather input and submit to queue */
+ rsRetVal (*newInpInst)(struct nvlst *lst);
+ int bCanRun; /* cached value of whether willRun() succeeded */
+ } im;
+ struct {/* data for output modules */
+ /* below: perform the configured action
+ */
+ rsRetVal (*beginTransaction)(void*);
+ rsRetVal (*doAction)(uchar**, unsigned, void*);
+ rsRetVal (*endTransaction)(void*);
+ rsRetVal (*parseSelectorAct)(uchar**, void**,omodStringRequest_t**);
+ rsRetVal (*newActInst)(uchar *modName, struct nvlst *lst, void **, omodStringRequest_t **);
+ rsRetVal (*SetShutdownImmdtPtr)(void *pData, void *pPtr);
+ } om;
+ struct { /* data for library modules */
+ char dummy;
+ } lm;
+ struct { /* data for parser modules */
+ rsRetVal (*parse)(msg_t*);
+ } pm;
+ struct { /* data for strgen modules */
+ rsRetVal (*strgen)(msg_t*, uchar**, size_t *);
+ } sm;
+ } mod;
+ void *pModHdlr; /* handler to the dynamic library holding the module */
+# ifdef DEBUG
+ /* we add some home-grown support to track our users (and detect who does not free us). */
+ modUsr_t *pModUsrRoot;
+# endif
+};
+
+
+/* interfaces */
+BEGINinterface(module) /* name must also be changed in ENDinterface macro! */
+ modInfo_t *(*GetNxt)(modInfo_t *pThis);
+ cfgmodules_etry_t *(*GetNxtCnfType)(rsconf_t *cnf, cfgmodules_etry_t *pThis, eModType_t rqtdType);
+ uchar *(*GetName)(modInfo_t *pThis);
+ uchar *(*GetStateName)(modInfo_t *pThis);
+ rsRetVal (*Use)(char *srcFile, modInfo_t *pThis); /**< must be called before a module is used (ref counting) */
+ rsRetVal (*Release)(char *srcFile, modInfo_t **ppThis); /**< release a module (ref counting) */
+ void (*PrintList)(void);
+ rsRetVal (*UnloadAndDestructAll)(eModLinkType_t modLinkTypesToUnload);
+ rsRetVal (*doModInit)(rsRetVal (*modInit)(), uchar *name, void *pModHdlr, modInfo_t **pNew);
+ rsRetVal (*Load)(uchar *name, sbool bConfLoad, struct nvlst *lst);
+ rsRetVal (*SetModDir)(uchar *name);
+ modInfo_t *(*FindWithCnfName)(rsconf_t *cnf, uchar *name, eModType_t rqtdType); /* added v3, 2011-07-19 */
+ENDinterface(module)
+#define moduleCURR_IF_VERSION 4 /* increment whenever you change the interface structure! */
+/* Changes:
+ * v2
+ * - added param bCondLoad to Load call - 2011-04-27
+ * - removed GetNxtType, added GetNxtCnfType - 2011-04-27
+ * v3 (see above)
+ * v4
+ * - added third parameter to Load() - 2012-06-20
+ */
+
+/* prototypes */
+PROTOTYPEObj(module);
+/* in v6, we go back to in-core static link for core objects, at least those
+ * that are not called from plugins.
+ * ... and we need to know that none of the module functions are called from plugins!
+ * rgerhards, 2012-09-24
+ */
+rsRetVal modulesProcessCnf(struct cnfobj *o);
+uchar *modGetName(modInfo_t *pThis);
+rsRetVal addModToCnfList(cfgmodules_etry_t *pNew, cfgmodules_etry_t *pLast);
+rsRetVal readyModForCnf(modInfo_t *pThis, cfgmodules_etry_t **ppNew, cfgmodules_etry_t **ppLast);
+#endif /* #ifndef MODULES_H_INCLUDED */
diff --git a/runtime/msg.c b/runtime/msg.c
new file mode 100644
index 00000000..a5c52810
--- /dev/null
+++ b/runtime/msg.c
@@ -0,0 +1,4085 @@
+/* msg.c
+ * The msg object. Implementation of all msg-related functions
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ * This file is under development and has not yet arrived at being fully
+ * self-contained and a real object. So far, it is mostly an excerpt
+ * of the "old" message code without any modifications. However, it
+ * helps to have things at the right place one we go to the meat of it.
+ *
+ * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#define SYSLOG_NAMES
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#if HAVE_SYSINFO_UPTIME
+#include <sys/sysinfo.h>
+#endif
+#include <netdb.h>
+#include <libestr.h>
+#include <json/json.h>
+/* For struct json_object_iter, should not be necessary in future versions */
+#include <json/json_object_private.h>
+#if HAVE_MALLOC_H
+# include <malloc.h>
+#endif
+#ifdef USE_LIBUUID
+ #include <uuid/uuid.h>
+#endif
+#include "rsyslog.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+#include "template.h"
+#include "msg.h"
+#include "datetime.h"
+#include "glbl.h"
+#include "regexp.h"
+#include "atomic.h"
+#include "unicode-helper.h"
+#include "ruleset.h"
+#include "prop.h"
+#include "net.h"
+#include "var.h"
+#include "rsconf.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(regexp)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(net)
+DEFobjCurrIf(var)
+
+static char *two_digits[100] = {
+ "00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
+ "10", "11", "12", "13", "14", "15", "16", "17", "18", "19",
+ "20", "21", "22", "23", "24", "25", "26", "27", "28", "29",
+ "30", "31", "32", "33", "34", "35", "36", "37", "38", "39",
+ "40", "41", "42", "43", "44", "45", "46", "47", "48", "49",
+ "50", "51", "52", "53", "54", "55", "56", "57", "58", "59",
+ "60", "61", "62", "63", "64", "65", "66", "67", "68", "69",
+ "70", "71", "72", "73", "74", "75", "76", "77", "78", "79",
+ "80", "81", "82", "83", "84", "85", "86", "87", "88", "89",
+ "90", "91", "92", "93", "94", "95", "96", "97", "98", "99"};
+
+static struct {
+ uchar *pszName;
+ short lenName;
+} syslog_pri_names[192] = {
+ { UCHAR_CONSTANT("0"), 3},
+ { UCHAR_CONSTANT("1"), 3},
+ { UCHAR_CONSTANT("2"), 3},
+ { UCHAR_CONSTANT("3"), 3},
+ { UCHAR_CONSTANT("4"), 3},
+ { UCHAR_CONSTANT("5"), 3},
+ { UCHAR_CONSTANT("6"), 3},
+ { UCHAR_CONSTANT("7"), 3},
+ { UCHAR_CONSTANT("8"), 3},
+ { UCHAR_CONSTANT("9"), 3},
+ { UCHAR_CONSTANT("10"), 4},
+ { UCHAR_CONSTANT("11"), 4},
+ { UCHAR_CONSTANT("12"), 4},
+ { UCHAR_CONSTANT("13"), 4},
+ { UCHAR_CONSTANT("14"), 4},
+ { UCHAR_CONSTANT("15"), 4},
+ { UCHAR_CONSTANT("16"), 4},
+ { UCHAR_CONSTANT("17"), 4},
+ { UCHAR_CONSTANT("18"), 4},
+ { UCHAR_CONSTANT("19"), 4},
+ { UCHAR_CONSTANT("20"), 4},
+ { UCHAR_CONSTANT("21"), 4},
+ { UCHAR_CONSTANT("22"), 4},
+ { UCHAR_CONSTANT("23"), 4},
+ { UCHAR_CONSTANT("24"), 4},
+ { UCHAR_CONSTANT("25"), 4},
+ { UCHAR_CONSTANT("26"), 4},
+ { UCHAR_CONSTANT("27"), 4},
+ { UCHAR_CONSTANT("28"), 4},
+ { UCHAR_CONSTANT("29"), 4},
+ { UCHAR_CONSTANT("30"), 4},
+ { UCHAR_CONSTANT("31"), 4},
+ { UCHAR_CONSTANT("32"), 4},
+ { UCHAR_CONSTANT("33"), 4},
+ { UCHAR_CONSTANT("34"), 4},
+ { UCHAR_CONSTANT("35"), 4},
+ { UCHAR_CONSTANT("36"), 4},
+ { UCHAR_CONSTANT("37"), 4},
+ { UCHAR_CONSTANT("38"), 4},
+ { UCHAR_CONSTANT("39"), 4},
+ { UCHAR_CONSTANT("40"), 4},
+ { UCHAR_CONSTANT("41"), 4},
+ { UCHAR_CONSTANT("42"), 4},
+ { UCHAR_CONSTANT("43"), 4},
+ { UCHAR_CONSTANT("44"), 4},
+ { UCHAR_CONSTANT("45"), 4},
+ { UCHAR_CONSTANT("46"), 4},
+ { UCHAR_CONSTANT("47"), 4},
+ { UCHAR_CONSTANT("48"), 4},
+ { UCHAR_CONSTANT("49"), 4},
+ { UCHAR_CONSTANT("50"), 4},
+ { UCHAR_CONSTANT("51"), 4},
+ { UCHAR_CONSTANT("52"), 4},
+ { UCHAR_CONSTANT("53"), 4},
+ { UCHAR_CONSTANT("54"), 4},
+ { UCHAR_CONSTANT("55"), 4},
+ { UCHAR_CONSTANT("56"), 4},
+ { UCHAR_CONSTANT("57"), 4},
+ { UCHAR_CONSTANT("58"), 4},
+ { UCHAR_CONSTANT("59"), 4},
+ { UCHAR_CONSTANT("60"), 4},
+ { UCHAR_CONSTANT("61"), 4},
+ { UCHAR_CONSTANT("62"), 4},
+ { UCHAR_CONSTANT("63"), 4},
+ { UCHAR_CONSTANT("64"), 4},
+ { UCHAR_CONSTANT("65"), 4},
+ { UCHAR_CONSTANT("66"), 4},
+ { UCHAR_CONSTANT("67"), 4},
+ { UCHAR_CONSTANT("68"), 4},
+ { UCHAR_CONSTANT("69"), 4},
+ { UCHAR_CONSTANT("70"), 4},
+ { UCHAR_CONSTANT("71"), 4},
+ { UCHAR_CONSTANT("72"), 4},
+ { UCHAR_CONSTANT("73"), 4},
+ { UCHAR_CONSTANT("74"), 4},
+ { UCHAR_CONSTANT("75"), 4},
+ { UCHAR_CONSTANT("76"), 4},
+ { UCHAR_CONSTANT("77"), 4},
+ { UCHAR_CONSTANT("78"), 4},
+ { UCHAR_CONSTANT("79"), 4},
+ { UCHAR_CONSTANT("80"), 4},
+ { UCHAR_CONSTANT("81"), 4},
+ { UCHAR_CONSTANT("82"), 4},
+ { UCHAR_CONSTANT("83"), 4},
+ { UCHAR_CONSTANT("84"), 4},
+ { UCHAR_CONSTANT("85"), 4},
+ { UCHAR_CONSTANT("86"), 4},
+ { UCHAR_CONSTANT("87"), 4},
+ { UCHAR_CONSTANT("88"), 4},
+ { UCHAR_CONSTANT("89"), 4},
+ { UCHAR_CONSTANT("90"), 4},
+ { UCHAR_CONSTANT("91"), 4},
+ { UCHAR_CONSTANT("92"), 4},
+ { UCHAR_CONSTANT("93"), 4},
+ { UCHAR_CONSTANT("94"), 4},
+ { UCHAR_CONSTANT("95"), 4},
+ { UCHAR_CONSTANT("96"), 4},
+ { UCHAR_CONSTANT("97"), 4},
+ { UCHAR_CONSTANT("98"), 4},
+ { UCHAR_CONSTANT("99"), 4},
+ { UCHAR_CONSTANT("100"), 5},
+ { UCHAR_CONSTANT("101"), 5},
+ { UCHAR_CONSTANT("102"), 5},
+ { UCHAR_CONSTANT("103"), 5},
+ { UCHAR_CONSTANT("104"), 5},
+ { UCHAR_CONSTANT("105"), 5},
+ { UCHAR_CONSTANT("106"), 5},
+ { UCHAR_CONSTANT("107"), 5},
+ { UCHAR_CONSTANT("108"), 5},
+ { UCHAR_CONSTANT("109"), 5},
+ { UCHAR_CONSTANT("110"), 5},
+ { UCHAR_CONSTANT("111"), 5},
+ { UCHAR_CONSTANT("112"), 5},
+ { UCHAR_CONSTANT("113"), 5},
+ { UCHAR_CONSTANT("114"), 5},
+ { UCHAR_CONSTANT("115"), 5},
+ { UCHAR_CONSTANT("116"), 5},
+ { UCHAR_CONSTANT("117"), 5},
+ { UCHAR_CONSTANT("118"), 5},
+ { UCHAR_CONSTANT("119"), 5},
+ { UCHAR_CONSTANT("120"), 5},
+ { UCHAR_CONSTANT("121"), 5},
+ { UCHAR_CONSTANT("122"), 5},
+ { UCHAR_CONSTANT("123"), 5},
+ { UCHAR_CONSTANT("124"), 5},
+ { UCHAR_CONSTANT("125"), 5},
+ { UCHAR_CONSTANT("126"), 5},
+ { UCHAR_CONSTANT("127"), 5},
+ { UCHAR_CONSTANT("128"), 5},
+ { UCHAR_CONSTANT("129"), 5},
+ { UCHAR_CONSTANT("130"), 5},
+ { UCHAR_CONSTANT("131"), 5},
+ { UCHAR_CONSTANT("132"), 5},
+ { UCHAR_CONSTANT("133"), 5},
+ { UCHAR_CONSTANT("134"), 5},
+ { UCHAR_CONSTANT("135"), 5},
+ { UCHAR_CONSTANT("136"), 5},
+ { UCHAR_CONSTANT("137"), 5},
+ { UCHAR_CONSTANT("138"), 5},
+ { UCHAR_CONSTANT("139"), 5},
+ { UCHAR_CONSTANT("140"), 5},
+ { UCHAR_CONSTANT("141"), 5},
+ { UCHAR_CONSTANT("142"), 5},
+ { UCHAR_CONSTANT("143"), 5},
+ { UCHAR_CONSTANT("144"), 5},
+ { UCHAR_CONSTANT("145"), 5},
+ { UCHAR_CONSTANT("146"), 5},
+ { UCHAR_CONSTANT("147"), 5},
+ { UCHAR_CONSTANT("148"), 5},
+ { UCHAR_CONSTANT("149"), 5},
+ { UCHAR_CONSTANT("150"), 5},
+ { UCHAR_CONSTANT("151"), 5},
+ { UCHAR_CONSTANT("152"), 5},
+ { UCHAR_CONSTANT("153"), 5},
+ { UCHAR_CONSTANT("154"), 5},
+ { UCHAR_CONSTANT("155"), 5},
+ { UCHAR_CONSTANT("156"), 5},
+ { UCHAR_CONSTANT("157"), 5},
+ { UCHAR_CONSTANT("158"), 5},
+ { UCHAR_CONSTANT("159"), 5},
+ { UCHAR_CONSTANT("160"), 5},
+ { UCHAR_CONSTANT("161"), 5},
+ { UCHAR_CONSTANT("162"), 5},
+ { UCHAR_CONSTANT("163"), 5},
+ { UCHAR_CONSTANT("164"), 5},
+ { UCHAR_CONSTANT("165"), 5},
+ { UCHAR_CONSTANT("166"), 5},
+ { UCHAR_CONSTANT("167"), 5},
+ { UCHAR_CONSTANT("168"), 5},
+ { UCHAR_CONSTANT("169"), 5},
+ { UCHAR_CONSTANT("170"), 5},
+ { UCHAR_CONSTANT("171"), 5},
+ { UCHAR_CONSTANT("172"), 5},
+ { UCHAR_CONSTANT("173"), 5},
+ { UCHAR_CONSTANT("174"), 5},
+ { UCHAR_CONSTANT("175"), 5},
+ { UCHAR_CONSTANT("176"), 5},
+ { UCHAR_CONSTANT("177"), 5},
+ { UCHAR_CONSTANT("178"), 5},
+ { UCHAR_CONSTANT("179"), 5},
+ { UCHAR_CONSTANT("180"), 5},
+ { UCHAR_CONSTANT("181"), 5},
+ { UCHAR_CONSTANT("182"), 5},
+ { UCHAR_CONSTANT("183"), 5},
+ { UCHAR_CONSTANT("184"), 5},
+ { UCHAR_CONSTANT("185"), 5},
+ { UCHAR_CONSTANT("186"), 5},
+ { UCHAR_CONSTANT("187"), 5},
+ { UCHAR_CONSTANT("188"), 5},
+ { UCHAR_CONSTANT("189"), 5},
+ { UCHAR_CONSTANT("190"), 5},
+ { UCHAR_CONSTANT("191"), 5}
+ };
+static char hexdigit[16] =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8',
+ '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+/*syslog facility names (as of RFC5424) */
+static char *syslog_fac_names[24] = { "kern", "user", "mail", "daemon", "auth", "syslog", "lpr",
+ "news", "uucp", "cron", "authpriv", "ftp", "ntp", "audit",
+ "alert", "clock", "local0", "local1", "local2", "local3",
+ "local4", "local5", "local6", "local7" };
+/* length of the facility names string (for optimizatiions) */
+static short len_syslog_fac_names[24] = { 4, 4, 4, 6, 4, 6, 3,
+ 4, 4, 4, 8, 3, 3, 5,
+ 5, 5, 6, 6, 6, 6,
+ 6, 6, 6, 6 };
+
+/* table of severity names (in numerical order)*/
+static char *syslog_severity_names[8] = { "emerg", "alert", "crit", "err", "warning", "notice", "info", "debug" };
+static short len_syslog_severity_names[8] = { 5, 5, 4, 3, 7, 6, 4, 5 };
+
+/* numerical values as string - this is the most efficient approach to convert severity
+ * and facility values to a numerical string... -- rgerhars, 2009-06-17
+ */
+
+static char *syslog_number_names[24] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14",
+ "15", "16", "17", "18", "19", "20", "21", "22", "23" };
+
+/* global variables */
+#if defined(HAVE_MALLOC_TRIM) && !defined(HAVE_ATOMIC_BUILTINS)
+static pthread_mutex_t mutTrimCtr; /* mutex to handle malloc trim */
+#endif
+
+/* some forward declarations */
+static int getAPPNAMELen(msg_t *pM, sbool bLockMutex);
+static rsRetVal jsonPathFindParent(msg_t *pM, uchar *name, uchar *leaf, struct json_object **parent, int bCreate);
+static uchar * jsonPathGetLeaf(uchar *name, int lenName);
+static struct json_object *jsonDeepCopy(struct json_object *src);
+
+
+/* the locking and unlocking implementations: */
+static inline void
+MsgLock(msg_t *pThis)
+{
+ /* DEV debug only! dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis); */
+ pthread_mutex_lock(&pThis->mut);
+}
+static inline void
+MsgUnlock(msg_t *pThis)
+{
+ /* DEV debug only! dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis); */
+ pthread_mutex_unlock(&pThis->mut);
+}
+
+
+/* set RcvFromIP name in msg object WITHOUT calling AddRef.
+ * rgerhards, 2013-01-22
+ */
+static inline void
+MsgSetRcvFromIPWithoutAddRef(msg_t *pThis, prop_t *new)
+{
+ if(pThis->pRcvFromIP != NULL)
+ prop.Destruct(&pThis->pRcvFromIP);
+ pThis->pRcvFromIP = new;
+}
+
+
+/* set RcvFrom name in msg object WITHOUT calling AddRef.
+ * rgerhards, 2013-01-22
+ */
+void MsgSetRcvFromWithoutAddRef(msg_t *pThis, prop_t *new)
+{
+ assert(pThis != NULL);
+
+ if(pThis->msgFlags & NEEDS_DNSRESOL) {
+ if(pThis->rcvFrom.pfrominet != NULL)
+ free(pThis->rcvFrom.pfrominet);
+ pThis->msgFlags &= ~NEEDS_DNSRESOL;
+ } else {
+ if(pThis->rcvFrom.pRcvFrom != NULL)
+ prop.Destruct(&pThis->rcvFrom.pRcvFrom);
+ }
+ pThis->rcvFrom.pRcvFrom = new;
+}
+
+
+/* rgerhards 2012-04-18: set associated ruleset (by ruleset name)
+ * If ruleset cannot be found, no update is done.
+ */
+static void
+MsgSetRulesetByName(msg_t *pMsg, cstr_t *rulesetName)
+{
+ rulesetGetRuleset(runConf, &(pMsg->pRuleset), rsCStrGetSzStrNoNULL(rulesetName));
+}
+
+
+static inline int getProtocolVersion(msg_t *pM)
+{
+ return(pM->iProtocolVersion);
+}
+
+
+/* do a DNS reverse resolution, if not already done, reflect status
+ * rgerhards, 2009-11-16
+ */
+static inline rsRetVal
+resolveDNS(msg_t *pMsg) {
+ rsRetVal localRet;
+ prop_t *propFromHost = NULL;
+ prop_t *ip;
+ prop_t *localName;
+ DEFiRet;
+
+ MsgLock(pMsg);
+ CHKiRet(objUse(net, CORE_COMPONENT));
+ if(pMsg->msgFlags & NEEDS_DNSRESOL) {
+ localRet = net.cvthname(pMsg->rcvFrom.pfrominet, &localName, NULL, &ip);
+ if(localRet == RS_RET_OK) {
+ /* we pass down the props, so no need for AddRef */
+ MsgSetRcvFromWithoutAddRef(pMsg, localName);
+ MsgSetRcvFromIPWithoutAddRef(pMsg, ip);
+ }
+ }
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ /* best we can do: remove property */
+ MsgSetRcvFromStr(pMsg, UCHAR_CONSTANT(""), 0, &propFromHost);
+ prop.Destruct(&propFromHost);
+ }
+ MsgUnlock(pMsg);
+ if(propFromHost != NULL)
+ prop.Destruct(&propFromHost);
+ RETiRet;
+}
+
+
+static inline void
+getInputName(msg_t *pM, uchar **ppsz, int *plen)
+{
+ BEGINfunc
+ if(pM == NULL || pM->pInputName == NULL) {
+ *ppsz = UCHAR_CONSTANT("");
+ *plen = 0;
+ } else {
+ prop.GetString(pM->pInputName, ppsz, plen);
+ }
+ ENDfunc
+}
+
+
+static inline uchar*
+getRcvFromIP(msg_t *pM)
+{
+ uchar *psz;
+ int len;
+ BEGINfunc
+ if(pM == NULL) {
+ psz = UCHAR_CONSTANT("");
+ } else {
+ resolveDNS(pM); /* make sure we have a resolved entry */
+ if(pM->pRcvFromIP == NULL)
+ psz = UCHAR_CONSTANT("");
+ else
+ prop.GetString(pM->pRcvFromIP, &psz, &len);
+ }
+ ENDfunc
+ return psz;
+}
+
+
+/* map a property name (C string) to a property ID */
+rsRetVal
+propNameStrToID(uchar *pName, propid_t *pPropID)
+{
+ DEFiRet;
+
+ assert(pName != NULL);
+
+ /* sometimes there are aliases to the original MonitoWare
+ * property names. These come after || in the ifs below. */
+ if(!strcmp((char*) pName, "msg")) {
+ *pPropID = PROP_MSG;
+ } else if(!strcmp((char*) pName, "timestamp")
+ || !strcmp((char*) pName, "timereported")) {
+ *pPropID = PROP_TIMESTAMP;
+ } else if(!strcmp((char*) pName, "hostname") || !strcmp((char*) pName, "source")) {
+ *pPropID = PROP_HOSTNAME;
+ } else if(!strcmp((char*) pName, "syslogtag")) {
+ *pPropID = PROP_SYSLOGTAG;
+ } else if(!strcmp((char*) pName, "rawmsg")) {
+ *pPropID = PROP_RAWMSG;
+ } else if(!strcmp((char*) pName, "inputname")) {
+ *pPropID = PROP_INPUTNAME;
+ } else if(!strcmp((char*) pName, "fromhost")) {
+ *pPropID = PROP_FROMHOST;
+ } else if(!strcmp((char*) pName, "fromhost-ip")) {
+ *pPropID = PROP_FROMHOST_IP;
+ } else if(!strcmp((char*) pName, "pri")) {
+ *pPropID = PROP_PRI;
+ } else if(!strcmp((char*) pName, "pri-text")) {
+ *pPropID = PROP_PRI_TEXT;
+ } else if(!strcmp((char*) pName, "iut")) {
+ *pPropID = PROP_IUT;
+ } else if(!strcmp((char*) pName, "syslogfacility")) {
+ *pPropID = PROP_SYSLOGFACILITY;
+ } else if(!strcmp((char*) pName, "syslogfacility-text")) {
+ *pPropID = PROP_SYSLOGFACILITY_TEXT;
+ } else if(!strcmp((char*) pName, "syslogseverity") || !strcmp((char*) pName, "syslogpriority")) {
+ *pPropID = PROP_SYSLOGSEVERITY;
+ } else if(!strcmp((char*) pName, "syslogseverity-text") || !strcmp((char*) pName, "syslogpriority-text")) {
+ *pPropID = PROP_SYSLOGSEVERITY_TEXT;
+ } else if(!strcmp((char*) pName, "timegenerated")) {
+ *pPropID = PROP_TIMEGENERATED;
+ } else if(!strcmp((char*) pName, "programname")) {
+ *pPropID = PROP_PROGRAMNAME;
+ } else if(!strcmp((char*) pName, "protocol-version")) {
+ *pPropID = PROP_PROTOCOL_VERSION;
+ } else if(!strcmp((char*) pName, "structured-data")) {
+ *pPropID = PROP_STRUCTURED_DATA;
+ } else if(!strcmp((char*) pName, "app-name")) {
+ *pPropID = PROP_APP_NAME;
+ } else if(!strcmp((char*) pName, "procid")) {
+ *pPropID = PROP_PROCID;
+ } else if(!strcmp((char*) pName, "msgid")) {
+ *pPropID = PROP_MSGID;
+ } else if(!strcmp((char*) pName, "parsesuccess")) {
+ *pPropID = PROP_PARSESUCCESS;
+#ifdef USE_LIBUUID
+ } else if(!strcmp((char*) pName, "uuid")) {
+ *pPropID = PROP_UUID;
+#endif
+ /* here start system properties (those, that do not relate to the message itself */
+ } else if(!strcmp((char*) pName, "$now")) {
+ *pPropID = PROP_SYS_NOW;
+ } else if(!strcmp((char*) pName, "$year")) {
+ *pPropID = PROP_SYS_YEAR;
+ } else if(!strcmp((char*) pName, "$month")) {
+ *pPropID = PROP_SYS_MONTH;
+ } else if(!strcmp((char*) pName, "$day")) {
+ *pPropID = PROP_SYS_DAY;
+ } else if(!strcmp((char*) pName, "$hour")) {
+ *pPropID = PROP_SYS_HOUR;
+ } else if(!strcmp((char*) pName, "$hhour")) {
+ *pPropID = PROP_SYS_HHOUR;
+ } else if(!strcmp((char*) pName, "$qhour")) {
+ *pPropID = PROP_SYS_QHOUR;
+ } else if(!strcmp((char*) pName, "$minute")) {
+ *pPropID = PROP_SYS_MINUTE;
+ } else if(!strcmp((char*) pName, "$myhostname")) {
+ *pPropID = PROP_SYS_MYHOSTNAME;
+ } else if(!strcmp((char*) pName, "$!all-json")) {
+ *pPropID = PROP_CEE_ALL_JSON;
+ } else if(!strncmp((char*) pName, "$!", 2)) {
+ *pPropID = PROP_CEE;
+ } else if(!strcmp((char*) pName, "$bom")) {
+ *pPropID = PROP_SYS_BOM;
+ } else if(!strcmp((char*) pName, "$uptime")) {
+ *pPropID = PROP_SYS_UPTIME;
+ } else {
+ *pPropID = PROP_INVALID;
+ iRet = RS_RET_VAR_NOT_FOUND;
+ }
+
+ RETiRet;
+}
+
+
+/* map a property name (string) to a property ID */
+rsRetVal
+propNameToID(cstr_t *pCSPropName, propid_t *pPropID)
+{
+ uchar *pName;
+ DEFiRet;
+
+ assert(pCSPropName != NULL);
+ assert(pPropID != NULL);
+ pName = rsCStrGetSzStrNoNULL(pCSPropName);
+ iRet = propNameStrToID(pName, pPropID);
+ RETiRet;
+}
+
+
+/* map a property ID to a name string (useful for displaying) */
+uchar *propIDToName(propid_t propID)
+{
+ switch(propID) {
+ case PROP_MSG:
+ return UCHAR_CONSTANT("msg");
+ case PROP_TIMESTAMP:
+ return UCHAR_CONSTANT("timestamp");
+ case PROP_HOSTNAME:
+ return UCHAR_CONSTANT("hostname");
+ case PROP_SYSLOGTAG:
+ return UCHAR_CONSTANT("syslogtag");
+ case PROP_RAWMSG:
+ return UCHAR_CONSTANT("rawmsg");
+ case PROP_INPUTNAME:
+ return UCHAR_CONSTANT("inputname");
+ case PROP_FROMHOST:
+ return UCHAR_CONSTANT("fromhost");
+ case PROP_FROMHOST_IP:
+ return UCHAR_CONSTANT("fromhost-ip");
+ case PROP_PRI:
+ return UCHAR_CONSTANT("pri");
+ case PROP_PRI_TEXT:
+ return UCHAR_CONSTANT("pri-text");
+ case PROP_IUT:
+ return UCHAR_CONSTANT("iut");
+ case PROP_SYSLOGFACILITY:
+ return UCHAR_CONSTANT("syslogfacility");
+ case PROP_SYSLOGFACILITY_TEXT:
+ return UCHAR_CONSTANT("syslogfacility-text");
+ case PROP_SYSLOGSEVERITY:
+ return UCHAR_CONSTANT("syslogseverity");
+ case PROP_SYSLOGSEVERITY_TEXT:
+ return UCHAR_CONSTANT("syslogseverity-text");
+ case PROP_TIMEGENERATED:
+ return UCHAR_CONSTANT("timegenerated");
+ case PROP_PROGRAMNAME:
+ return UCHAR_CONSTANT("programname");
+ case PROP_PROTOCOL_VERSION:
+ return UCHAR_CONSTANT("protocol-version");
+ case PROP_STRUCTURED_DATA:
+ return UCHAR_CONSTANT("structured-data");
+ case PROP_APP_NAME:
+ return UCHAR_CONSTANT("app-name");
+ case PROP_PROCID:
+ return UCHAR_CONSTANT("procid");
+ case PROP_MSGID:
+ return UCHAR_CONSTANT("msgid");
+ case PROP_PARSESUCCESS:
+ return UCHAR_CONSTANT("parsesuccess");
+ case PROP_SYS_NOW:
+ return UCHAR_CONSTANT("$NOW");
+ case PROP_SYS_YEAR:
+ return UCHAR_CONSTANT("$YEAR");
+ case PROP_SYS_MONTH:
+ return UCHAR_CONSTANT("$MONTH");
+ case PROP_SYS_DAY:
+ return UCHAR_CONSTANT("$DAY");
+ case PROP_SYS_HOUR:
+ return UCHAR_CONSTANT("$HOUR");
+ case PROP_SYS_HHOUR:
+ return UCHAR_CONSTANT("$HHOUR");
+ case PROP_SYS_QHOUR:
+ return UCHAR_CONSTANT("$QHOUR");
+ case PROP_SYS_MINUTE:
+ return UCHAR_CONSTANT("$MINUTE");
+ case PROP_SYS_MYHOSTNAME:
+ return UCHAR_CONSTANT("$MYHOSTNAME");
+ case PROP_CEE:
+ return UCHAR_CONSTANT("*CEE-based property*");
+ case PROP_CEE_ALL_JSON:
+ return UCHAR_CONSTANT("$!all-json");
+ case PROP_SYS_BOM:
+ return UCHAR_CONSTANT("$BOM");
+ case PROP_UUID:
+ return UCHAR_CONSTANT("uuid");
+ default:
+ return UCHAR_CONSTANT("*invalid property id*");
+ }
+}
+
+
+/* This is common code for all Constructors. It is defined in an
+ * inline'able function so that we can save a function call in the
+ * actual constructors (otherwise, the msgConstruct would need
+ * to call msgConstructWithTime(), which would require a
+ * function call). Now, both can use this inline function. This
+ * enables us to be optimal, but still have the code just once.
+ * the new object or NULL if no such object could be allocated.
+ * An object constructed via this function should only be destroyed
+ * via "msgDestruct()". This constructor does not query system time
+ * itself but rather uses a user-supplied value. This enables the caller
+ * to do some tricks to save processing time (done, for example, in the
+ * udp input).
+ * NOTE: this constructor does NOT call calloc(), as we have many bytes
+ * inside the structure which do not need to be cleared. bzero() will
+ * heavily thrash the cache, so we do the init manually (which also
+ * is the right thing to do with pointers, as they are not neccessarily
+ * a binary 0 on all machines [but today almost always...]).
+ * rgerhards, 2008-10-06
+ */
+static inline rsRetVal msgBaseConstruct(msg_t **ppThis)
+{
+ DEFiRet;
+ msg_t *pM;
+
+ assert(ppThis != NULL);
+ CHKmalloc(pM = MALLOC(sizeof(msg_t)));
+ objConstructSetObjInfo(pM); /* intialize object helper entities */
+
+ /* initialize members in ORDER they appear in structure (think "cache line"!) */
+ pM->flowCtlType = 0;
+ pM->bParseSuccess = 0;
+ pM->iRefCount = 1;
+ pM->iSeverity = -1;
+ pM->iFacility = -1;
+ pM->iLenPROGNAME = -1;
+ pM->offAfterPRI = 0;
+ pM->offMSG = -1;
+ pM->iProtocolVersion = 0;
+ pM->msgFlags = 0;
+ pM->iLenRawMsg = 0;
+ pM->iLenMSG = 0;
+ pM->iLenTAG = 0;
+ pM->iLenHOSTNAME = 0;
+ pM->pszRawMsg = NULL;
+ pM->pszHOSTNAME = NULL;
+ pM->pszRcvdAt3164 = NULL;
+ pM->pszRcvdAt3339 = NULL;
+ pM->pszRcvdAt_MySQL = NULL;
+ pM->pszRcvdAt_PgSQL = NULL;
+ pM->pszTIMESTAMP3164 = NULL;
+ pM->pszTIMESTAMP3339 = NULL;
+ pM->pszTIMESTAMP_MySQL = NULL;
+ pM->pszTIMESTAMP_PgSQL = NULL;
+ pM->pCSStrucData = NULL;
+ pM->pCSAPPNAME = NULL;
+ pM->pCSPROCID = NULL;
+ pM->pCSMSGID = NULL;
+ pM->pInputName = NULL;
+ pM->pRcvFromIP = NULL;
+ pM->rcvFrom.pRcvFrom = NULL;
+ pM->pRuleset = NULL;
+ pM->json = NULL;
+ memset(&pM->tRcvdAt, 0, sizeof(pM->tRcvdAt));
+ memset(&pM->tTIMESTAMP, 0, sizeof(pM->tTIMESTAMP));
+ pM->TAG.pszTAG = NULL;
+ pM->pszTimestamp3164[0] = '\0';
+ pM->pszTimestamp3339[0] = '\0';
+ pM->pszTIMESTAMP_SecFrac[0] = '\0';
+ pM->pszRcvdAt_SecFrac[0] = '\0';
+ pM->pszTIMESTAMP_Unix[0] = '\0';
+ pM->pszRcvdAt_Unix[0] = '\0';
+ pM->pszUUID = NULL;
+ pthread_mutex_init(&pM->mut, NULL);
+
+ /* DEV debugging only! dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM);*/
+
+ *ppThis = pM;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* "Constructor" for a msg "object". Returns a pointer to
+ * the new object or NULL if no such object could be allocated.
+ * An object constructed via this function should only be destroyed
+ * via "msgDestruct()". This constructor does not query system time
+ * itself but rather uses a user-supplied value. This enables the caller
+ * to do some tricks to save processing time (done, for example, in the
+ * udp input).
+ * rgerhards, 2008-10-06
+ */
+rsRetVal msgConstructWithTime(msg_t **ppThis, struct syslogTime *stTime, time_t ttGenTime)
+{
+ DEFiRet;
+
+ CHKiRet(msgBaseConstruct(ppThis));
+ (*ppThis)->ttGenTime = ttGenTime;
+ memcpy(&(*ppThis)->tRcvdAt, stTime, sizeof(struct syslogTime));
+ memcpy(&(*ppThis)->tTIMESTAMP, stTime, sizeof(struct syslogTime));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* "Constructor" for a msg "object". Returns a pointer to
+ * the new object or NULL if no such object could be allocated.
+ * An object constructed via this function should only be destroyed
+ * via "msgDestruct()". This constructor, for historical reasons,
+ * also sets the two timestamps to the current time.
+ */
+rsRetVal msgConstruct(msg_t **ppThis)
+{
+ DEFiRet;
+
+ CHKiRet(msgBaseConstruct(ppThis));
+ /* we initialize both timestamps to contain the current time, so that they
+ * are consistent. Also, this saves us from doing any further time calls just
+ * to obtain a timestamp. The memcpy() should not really make a difference,
+ * especially as I think there is no codepath currently where it would not be
+ * required (after I have cleaned up the pathes ;)). -- rgerhards, 2008-10-02
+ */
+ datetime.getCurrTime(&((*ppThis)->tRcvdAt), &((*ppThis)->ttGenTime));
+ memcpy(&(*ppThis)->tTIMESTAMP, &(*ppThis)->tRcvdAt, sizeof(struct syslogTime));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Special msg constructor, to be used when an object is deserialized.
+ * we do only the base init as we know the properties will be set in
+ * any case by the deserializer. We still do the "inexpensive" inits
+ * just to be on the safe side. The whole process needs to be
+ * refactored together with the msg serialization subsystem.
+ */
+rsRetVal
+msgConstructForDeserializer(msg_t **ppThis)
+{
+ return msgBaseConstruct(ppThis);
+}
+
+
+/* some free handlers for (slightly) complicated cases... All of them may be called
+ * with an empty element.
+ */
+static inline void freeTAG(msg_t *pThis)
+{
+ if(pThis->iLenTAG >= CONF_TAG_BUFSIZE)
+ free(pThis->TAG.pszTAG);
+}
+static inline void freeHOSTNAME(msg_t *pThis)
+{
+ if(pThis->iLenHOSTNAME >= CONF_HOSTNAME_BUFSIZE)
+ free(pThis->pszHOSTNAME);
+}
+
+
+BEGINobjDestruct(msg) /* be sure to specify the object type also in END and CODESTART macros! */
+ int currRefCount;
+# if HAVE_MALLOC_TRIM
+ int currCnt;
+# endif
+CODESTARTobjDestruct(msg)
+ /* DEV Debugging only ! dbgprintf("msgDestruct\t0x%lx, Ref now: %d\n", (unsigned long)pThis, pThis->iRefCount - 1); */
+# ifdef HAVE_ATOMIC_BUILTINS
+ currRefCount = ATOMIC_DEC_AND_FETCH(&pThis->iRefCount, NULL);
+# else
+ MsgLock(pThis);
+ currRefCount = --pThis->iRefCount;
+# endif
+ if(currRefCount == 0)
+ {
+ /* DEV Debugging Only! dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n", (unsigned long)pThis); */
+ if(pThis->pszRawMsg != pThis->szRawMsg)
+ free(pThis->pszRawMsg);
+ freeTAG(pThis);
+ freeHOSTNAME(pThis);
+ if(pThis->pInputName != NULL)
+ prop.Destruct(&pThis->pInputName);
+ if((pThis->msgFlags & NEEDS_DNSRESOL) == 0) {
+ if(pThis->rcvFrom.pRcvFrom != NULL)
+ prop.Destruct(&pThis->rcvFrom.pRcvFrom);
+ } else {
+ free(pThis->rcvFrom.pfrominet);
+ }
+ if(pThis->pRcvFromIP != NULL)
+ prop.Destruct(&pThis->pRcvFromIP);
+ free(pThis->pszRcvdAt3164);
+ free(pThis->pszRcvdAt3339);
+ free(pThis->pszRcvdAt_MySQL);
+ free(pThis->pszRcvdAt_PgSQL);
+ free(pThis->pszTIMESTAMP_MySQL);
+ free(pThis->pszTIMESTAMP_PgSQL);
+ if(pThis->iLenPROGNAME >= CONF_PROGNAME_BUFSIZE)
+ free(pThis->PROGNAME.ptr);
+ if(pThis->pCSStrucData != NULL)
+ rsCStrDestruct(&pThis->pCSStrucData);
+ if(pThis->pCSAPPNAME != NULL)
+ rsCStrDestruct(&pThis->pCSAPPNAME);
+ if(pThis->pCSPROCID != NULL)
+ rsCStrDestruct(&pThis->pCSPROCID);
+ if(pThis->pCSMSGID != NULL)
+ rsCStrDestruct(&pThis->pCSMSGID);
+ if(pThis->json != NULL)
+ json_object_put(pThis->json);
+ if(pThis->pszUUID != NULL)
+ free(pThis->pszUUID);
+# ifndef HAVE_ATOMIC_BUILTINS
+ MsgUnlock(pThis);
+# endif
+ pthread_mutex_destroy(&pThis->mut);
+ /* now we need to do our own optimization. Testing has shown that at least the glibc
+ * malloc() subsystem returns memory to the OS far too late in our case. So we need
+ * to help it a bit, by calling malloc_trim(), which will tell the alloc subsystem
+ * to consolidate and return to the OS. We keep 128K for our use, as a safeguard
+ * to too-frequent reallocs. But more importantly, we call this hook only every
+ * 100,000 messages (which is an approximation, as we do not work with atomic
+ * operations on the counter. --- rgerhards, 2009-06-22.
+ */
+# if HAVE_MALLOC_TRIM
+ { /* standard C requires a new block for a new variable definition!
+ * To simplify matters, we use modulo arithmetic and live with the fact
+ * that we trim too often when the counter wraps.
+ */
+ static unsigned iTrimCtr = 1;
+ currCnt = ATOMIC_INC_AND_FETCH_unsigned(&iTrimCtr, &mutTrimCtr);
+ if(currCnt % 100000 == 0) {
+ malloc_trim(128*1024);
+ }
+ }
+# endif
+ } else {
+# ifndef HAVE_ATOMIC_BUILTINS
+ MsgUnlock(pThis);
+# endif
+ pThis = NULL; /* tell framework not to destructing the object! */
+ }
+ENDobjDestruct(msg)
+
+
+/* The macros below are used in MsgDup(). I use macros
+ * to keep the fuction code somewhat more readyble. It is my
+ * replacement for inline functions in CPP
+ */
+#define tmpCOPYSZ(name) \
+ if(pOld->psz##name != NULL) { \
+ if((pNew->psz##name = srUtilStrDup(pOld->psz##name, pOld->iLen##name)) == NULL) {\
+ msgDestruct(&pNew);\
+ return NULL;\
+ }\
+ pNew->iLen##name = pOld->iLen##name;\
+ }
+
+/* copy the CStr objects.
+ * if the old value is NULL, we do not need to do anything because we
+ * initialized the new value to NULL via calloc().
+ */
+#define tmpCOPYCSTR(name) \
+ if(pOld->pCS##name != NULL) {\
+ if(rsCStrConstructFromCStr(&(pNew->pCS##name), pOld->pCS##name) != RS_RET_OK) {\
+ msgDestruct(&pNew);\
+ return NULL;\
+ }\
+ }
+/* Constructs a message object by duplicating another one.
+ * Returns NULL if duplication failed. We do not need to lock the
+ * message object here, because a fully-created msg object is never
+ * allowed to be manipulated. For this, MsgDup() must be used, so MsgDup()
+ * can never run into a situation where the message object is being
+ * modified while its content is copied - it's forbidden by definition.
+ * rgerhards, 2007-07-10
+ */
+msg_t* MsgDup(msg_t* pOld)
+{
+ msg_t* pNew;
+ rsRetVal localRet;
+
+ assert(pOld != NULL);
+
+ BEGINfunc
+ if(msgConstructWithTime(&pNew, &pOld->tTIMESTAMP, pOld->ttGenTime) != RS_RET_OK) {
+ return NULL;
+ }
+
+ /* now copy the message properties */
+ pNew->iRefCount = 1;
+ pNew->iSeverity = pOld->iSeverity;
+ pNew->iFacility = pOld->iFacility;
+ pNew->msgFlags = pOld->msgFlags;
+ pNew->iProtocolVersion = pOld->iProtocolVersion;
+ pNew->ttGenTime = pOld->ttGenTime;
+ pNew->offMSG = pOld->offMSG;
+ pNew->iLenRawMsg = pOld->iLenRawMsg;
+ pNew->iLenMSG = pOld->iLenMSG;
+ pNew->iLenTAG = pOld->iLenTAG;
+ pNew->iLenHOSTNAME = pOld->iLenHOSTNAME;
+ if((pOld->msgFlags & NEEDS_DNSRESOL)) {
+ localRet = msgSetFromSockinfo(pNew, pOld->rcvFrom.pfrominet);
+ if(localRet != RS_RET_OK) {
+ /* if something fails, we accept loss of this property, it is
+ * better than losing the whole message.
+ */
+ pNew->msgFlags &= ~NEEDS_DNSRESOL;
+ pNew->rcvFrom.pRcvFrom = NULL; /* make sure no dangling values */
+ }
+ } else {
+ if(pOld->rcvFrom.pRcvFrom != NULL) {
+ pNew->rcvFrom.pRcvFrom = pOld->rcvFrom.pRcvFrom;
+ prop.AddRef(pNew->rcvFrom.pRcvFrom);
+ }
+ }
+ if(pOld->pRcvFromIP != NULL) {
+ pNew->pRcvFromIP = pOld->pRcvFromIP;
+ prop.AddRef(pNew->pRcvFromIP);
+ }
+ if(pOld->pInputName != NULL) {
+ pNew->pInputName = pOld->pInputName;
+ prop.AddRef(pNew->pInputName);
+ }
+ if(pOld->iLenTAG > 0) {
+ if(pOld->iLenTAG < CONF_TAG_BUFSIZE) {
+ memcpy(pNew->TAG.szBuf, pOld->TAG.szBuf, pOld->iLenTAG + 1);
+ } else {
+ if((pNew->TAG.pszTAG = srUtilStrDup(pOld->TAG.pszTAG, pOld->iLenTAG)) == NULL) {
+ msgDestruct(&pNew);
+ return NULL;
+ }
+ pNew->iLenTAG = pOld->iLenTAG;
+ }
+ }
+ if(pOld->iLenRawMsg < CONF_RAWMSG_BUFSIZE) {
+ memcpy(pNew->szRawMsg, pOld->szRawMsg, pOld->iLenRawMsg + 1);
+ pNew->pszRawMsg = pNew->szRawMsg;
+ } else {
+ tmpCOPYSZ(RawMsg);
+ }
+ if(pOld->pszHOSTNAME == NULL) {
+ pNew->pszHOSTNAME = NULL;
+ } else {
+ if(pOld->iLenHOSTNAME < CONF_HOSTNAME_BUFSIZE) {
+ memcpy(pNew->szHOSTNAME, pOld->szHOSTNAME, pOld->iLenHOSTNAME + 1);
+ pNew->pszHOSTNAME = pNew->szHOSTNAME;
+ } else {
+ tmpCOPYSZ(HOSTNAME);
+ }
+ }
+
+ tmpCOPYCSTR(StrucData);
+ tmpCOPYCSTR(APPNAME);
+ tmpCOPYCSTR(PROCID);
+ tmpCOPYCSTR(MSGID);
+
+ if(pOld->json != NULL)
+ pNew->json = jsonDeepCopy(pOld->json);
+
+ /* we do not copy all other cache properties, as we do not even know
+ * if they are needed once again. So we let them re-create if needed.
+ */
+
+ ENDfunc
+ return pNew;
+}
+#undef tmpCOPYSZ
+#undef tmpCOPYCSTR
+
+
+/* This method serializes a message object. That means the whole
+ * object is modified into text form. That text form is suitable for
+ * later reconstruction of the object by calling MsgDeSerialize().
+ * The most common use case for this method is the creation of an
+ * on-disk representation of the message object.
+ * We do not serialize the cache properties. We re-create them when needed.
+ * This saves us a lot of memory. Performance is no concern, as serializing
+ * is a so slow operation that recration of the caches does not count. Also,
+ * we do not serialize --currently none--, as this is only a helper variable
+ * during msg construction - and never again used later.
+ * rgerhards, 2008-01-03
+ */
+static rsRetVal MsgSerialize(msg_t *pThis, strm_t *pStrm)
+{
+ uchar *psz;
+ int len;
+ DEFiRet;
+
+ assert(pThis != NULL);
+ assert(pStrm != NULL);
+
+ /* then serialize elements */
+ CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis));
+ objSerializeSCALAR(pStrm, iProtocolVersion, SHORT);
+ objSerializeSCALAR(pStrm, iSeverity, SHORT);
+ objSerializeSCALAR(pStrm, iFacility, SHORT);
+ objSerializeSCALAR(pStrm, msgFlags, INT);
+ objSerializeSCALAR(pStrm, ttGenTime, INT);
+ objSerializeSCALAR(pStrm, tRcvdAt, SYSLOGTIME);
+ objSerializeSCALAR(pStrm, tTIMESTAMP, SYSLOGTIME);
+
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszTAG"), PROPTYPE_PSZ, (void*)
+ ((pThis->iLenTAG < CONF_TAG_BUFSIZE) ? pThis->TAG.szBuf : pThis->TAG.pszTAG)));
+
+ objSerializePTR(pStrm, pszRawMsg, PSZ);
+ objSerializePTR(pStrm, pszHOSTNAME, PSZ);
+ getInputName(pThis, &psz, &len);
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszInputName"), PROPTYPE_PSZ, (void*) psz));
+ psz = getRcvFrom(pThis);
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRcvFrom"), PROPTYPE_PSZ, (void*) psz));
+ psz = getRcvFromIP(pThis);
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRcvFromIP"), PROPTYPE_PSZ, (void*) psz));
+ if(pThis->json != NULL) {
+ psz = (uchar*) json_object_get_string(pThis->json);
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("json"), PROPTYPE_PSZ, (void*) psz));
+ }
+
+ objSerializePTR(pStrm, pCSStrucData, CSTR);
+ objSerializePTR(pStrm, pCSAPPNAME, CSTR);
+ objSerializePTR(pStrm, pCSPROCID, CSTR);
+ objSerializePTR(pStrm, pCSMSGID, CSTR);
+
+ objSerializePTR(pStrm, pszUUID, PSZ);
+
+ if(pThis->pRuleset != NULL) {
+ rulesetGetName(pThis->pRuleset);
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszRuleset"), PROPTYPE_PSZ,
+ rulesetGetName(pThis->pRuleset)));
+ }
+
+ /* offset must be serialized after pszRawMsg, because we need that to obtain the correct
+ * MSG size.
+ */
+ objSerializeSCALAR(pStrm, offMSG, SHORT);
+
+ CHKiRet(obj.EndSerialize(pStrm));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This is a helper for MsgDeserialize that re-inits the var object. This
+ * whole construct should be replaced, var is really ready to be retired.
+ * But as an interim help during refactoring let's introduce this function
+ * here (and thus NOT as method of var object!). -- rgerhads, 2012-11-06
+ */
+static inline void
+reinitVar(var_t *pVar)
+{
+ rsCStrDestruct(&pVar->pcsName); /* no longer needed */
+ if(pVar->varType == VARTYPE_STR) {
+ if(pVar->val.pStr != NULL)
+ rsCStrDestruct(&pVar->val.pStr);
+ }
+}
+/* deserialize the message again
+ * we deserialize the properties in the same order that we serialized them. Except
+ * for some checks to cover downlevel version, we do not need to do all these
+ * CPU intense name checkings.
+ */
+#define isProp(name) !rsCStrSzStrCmp(pVar->pcsName, (uchar*) name, sizeof(name) - 1)
+rsRetVal
+MsgDeserialize(msg_t *pMsg, strm_t *pStrm)
+{
+ prop_t *myProp;
+ prop_t *propRcvFrom = NULL;
+ prop_t *propRcvFromIP = NULL;
+ struct json_tokener *tokener;
+ struct json_object *json;
+ var_t *pVar = NULL;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pStrm, strm);
+
+ CHKiRet(var.Construct(&pVar));
+ CHKiRet(var.ConstructFinalize(pVar));
+
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ if(isProp("iProtocolVersion")) {
+ setProtocolVersion(pMsg, pVar->val.num);
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("iSeverity")) {
+ pMsg->iSeverity = pVar->val.num;
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("iFacility")) {
+ pMsg->iFacility = pVar->val.num;
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("msgFlags")) {
+ pMsg->msgFlags = pVar->val.num;
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("ttGenTime")) {
+ pMsg->ttGenTime = pVar->val.num;
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("tRcvdAt")) {
+ memcpy(&pMsg->tRcvdAt, &pVar->val.vSyslogTime, sizeof(struct syslogTime));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("tTIMESTAMP")) {
+ memcpy(&pMsg->tTIMESTAMP, &pVar->val.vSyslogTime, sizeof(struct syslogTime));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszTAG")) {
+ MsgSetTAG(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), cstrLen(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszRawMsg")) {
+ MsgSetRawMsg(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr), cstrLen(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszHOSTNAME")) {
+ MsgSetHOSTNAME(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszInputName")) {
+ /* we need to create a property */
+ CHKiRet(prop.Construct(&myProp));
+ CHKiRet(prop.SetString(myProp, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr)));
+ CHKiRet(prop.ConstructFinalize(myProp));
+ MsgSetInputName(pMsg, myProp);
+ prop.Destruct(&myProp);
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszRcvFrom")) {
+ MsgSetRcvFromStr(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr), &propRcvFrom);
+ prop.Destruct(&propRcvFrom);
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszRcvFromIP")) {
+ MsgSetRcvFromIPStr(pMsg, rsCStrGetSzStrNoNULL(pVar->val.pStr), rsCStrLen(pVar->val.pStr), &propRcvFromIP);
+ prop.Destruct(&propRcvFromIP);
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("json")) {
+ tokener = json_tokener_new();
+ json = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pVar->val.pStr),
+ cstrLen(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pCSStrucData")) {
+ MsgSetStructuredData(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pCSAPPNAME")) {
+ MsgSetAPPNAME(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pCSPROCID")) {
+ MsgSetPROCID(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pCSMSGID")) {
+ MsgSetMSGID(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszUUID")) {
+ pMsg->pszUUID = ustrdup(rsCStrGetSzStrNoNULL(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("pszRuleset")) {
+ MsgSetRulesetByName(pMsg, pVar->val.pStr);
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ /* "offMSG" must always be our last field, so we use this as an
+ * indicator if the sequence is correct. This is a bit questionable,
+ * but on the other hand it works decently AND we will probably replace
+ * the whole persisted format soon in any case. -- rgerhards, 2012-11-06
+ */
+ if(!isProp("offMSG"))
+ ABORT_FINALIZE(RS_RET_DS_PROP_SEQ_ERR);
+ MsgSetMSGoffs(pMsg, pVar->val.num);
+finalize_it:
+ if(pVar != NULL)
+ var.Destruct(&pVar);
+ RETiRet;
+}
+#undef isProp
+
+
+/* Increment reference count - see description of the "msg"
+ * structure for details. As a convenience to developers,
+ * this method returns the msg pointer that is passed to it.
+ * It is recommended that it is called as follows:
+ *
+ * pSecondMsgPointer = MsgAddRef(pOrgMsgPointer);
+ */
+msg_t *MsgAddRef(msg_t *pM)
+{
+ assert(pM != NULL);
+# ifdef HAVE_ATOMIC_BUILTINS
+ ATOMIC_INC(&pM->iRefCount, NULL);
+# else
+ MsgLock(pM);
+ pM->iRefCount++;
+ MsgUnlock(pM);
+# endif
+ /* DEV debugging only! dbgprintf("MsgAddRef\t0x%x done, Ref now: %d\n", (int)pM, pM->iRefCount);*/
+ return(pM);
+}
+
+
+/* This functions tries to aquire the PROCID from TAG. Its primary use is
+ * when a legacy syslog message has been received and should be forwarded as
+ * syslog-protocol (or the PROCID is requested for any other reason).
+ * In legacy syslog, the PROCID is considered to be the character sequence
+ * between the first [ and the first ]. This usually are digits only, but we
+ * do not check that. However, if there is no closing ], we do not assume we
+ * can obtain a PROCID. Take in mind that not every legacy syslog message
+ * actually has a PROCID.
+ * rgerhards, 2005-11-24
+ * THIS MUST be called with the message lock locked.
+ */
+static rsRetVal aquirePROCIDFromTAG(msg_t *pM)
+{
+ register int i;
+ uchar *pszTag;
+ DEFiRet;
+
+ assert(pM != NULL);
+
+ if(pM->pCSPROCID != NULL)
+ return RS_RET_OK; /* we are already done ;) */
+
+ if(getProtocolVersion(pM) != 0)
+ return RS_RET_OK; /* we can only emulate if we have legacy format */
+
+ pszTag = (uchar*) ((pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG);
+
+ /* find first '['... */
+ i = 0;
+ while((i < pM->iLenTAG) && (pszTag[i] != '['))
+ ++i;
+ if(!(i < pM->iLenTAG))
+ return RS_RET_OK; /* no [, so can not emulate... */
+
+ ++i; /* skip '[' */
+
+ /* now obtain the PROCID string... */
+ CHKiRet(cstrConstruct(&pM->pCSPROCID));
+ while((i < pM->iLenTAG) && (pszTag[i] != ']')) {
+ CHKiRet(cstrAppendChar(pM->pCSPROCID, pszTag[i]));
+ ++i;
+ }
+
+ if(!(i < pM->iLenTAG)) {
+ /* oops... it looked like we had a PROCID, but now it has
+ * turned out this is not true. In this case, we need to free
+ * the buffer and simply return. Note that this is NOT an error
+ * case!
+ */
+ cstrDestruct(&pM->pCSPROCID);
+ FINALIZE;
+ }
+
+ /* OK, finaally we could obtain a PROCID. So let's use it ;) */
+ CHKiRet(cstrFinalize(pM->pCSPROCID));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Parse and set the "programname" for a given MSG object. Programname
+ * is a BSD concept, it is the tag without any instance-specific information.
+ * Precisely, the programname is terminated by either (whichever occurs first):
+ * - end of tag
+ * - nonprintable character
+ * - ':'
+ * - '['
+ * - '/'
+ * The above definition has been taken from the FreeBSD syslogd sources.
+ *
+ * The program name is not parsed by default, because it is infrequently-used.
+ * IMPORTANT: A locked message object must be provided, else a crash will occur.
+ * rgerhards, 2005-10-19
+ */
+static inline rsRetVal
+aquireProgramName(msg_t *pM)
+{
+ int i;
+ uchar *pszTag, *pszProgName;
+ DEFiRet;
+
+ assert(pM != NULL);
+ pszTag = (uchar*) ((pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG);
+ for( i = 0
+ ; (i < pM->iLenTAG) && isprint((int) pszTag[i])
+ && (pszTag[i] != '\0') && (pszTag[i] != ':')
+ && (pszTag[i] != '[') && (pszTag[i] != '/')
+ ; ++i)
+ ; /* just search end of PROGNAME */
+ if(i < CONF_PROGNAME_BUFSIZE) {
+ pszProgName = pM->PROGNAME.szBuf;
+ } else {
+ CHKmalloc(pM->PROGNAME.ptr = malloc(i+1));
+ pszProgName = pM->PROGNAME.ptr;
+ }
+ memcpy((char*)pszProgName, (char*)pszTag, i);
+ pszProgName[i] = '\0';
+ pM->iLenPROGNAME = i;
+finalize_it:
+ RETiRet;
+}
+
+
+/* Access methods - dumb & easy, not a comment for each ;)
+ */
+void setProtocolVersion(msg_t *pM, int iNewVersion)
+{
+ assert(pM != NULL);
+ if(iNewVersion != 0 && iNewVersion != 1) {
+ dbgprintf("Tried to set unsupported protocol version %d - changed to 0.\n", iNewVersion);
+ iNewVersion = 0;
+ }
+ pM->iProtocolVersion = iNewVersion;
+}
+
+/* note: string is taken from constant pool, do NOT free */
+char *getProtocolVersionString(msg_t *pM)
+{
+ assert(pM != NULL);
+ return(pM->iProtocolVersion ? "1" : "0");
+}
+
+#ifdef USE_LIBUUID
+/* note: libuuid seems not to be thread-safe, so we need
+ * to get some safeguards in place.
+ */
+static void msgSetUUID(msg_t *pM)
+{
+ size_t lenRes = sizeof(uuid_t) * 2 + 1;
+ char hex_char [] = "0123456789ABCDEF";
+ unsigned int byte_nbr;
+ uuid_t uuid;
+ static pthread_mutex_t mutUUID = PTHREAD_MUTEX_INITIALIZER;
+
+ dbgprintf("[MsgSetUUID] START\n");
+ assert(pM != NULL);
+
+ if((pM->pszUUID = (uchar*) MALLOC(lenRes)) == NULL) {
+ pM->pszUUID = (uchar *)"";
+ } else {
+ pthread_mutex_lock(&mutUUID);
+ uuid_generate(uuid);
+ pthread_mutex_unlock(&mutUUID);
+ for (byte_nbr = 0; byte_nbr < sizeof (uuid_t); byte_nbr++) {
+ pM->pszUUID[byte_nbr * 2 + 0] = hex_char[uuid [byte_nbr] >> 4];
+ pM->pszUUID[byte_nbr * 2 + 1] = hex_char[uuid [byte_nbr] & 15];
+ }
+
+ dbgprintf("[MsgSetUUID] UUID : %s LEN: %d \n", pM->pszUUID, (int)lenRes);
+ pM->pszUUID[lenRes] = '\0';
+ }
+ dbgprintf("[MsgSetUUID] END\n");
+}
+
+void getUUID(msg_t *pM, uchar **pBuf, int *piLen)
+{
+ dbgprintf("[getUUID] START\n");
+ if(pM == NULL) {
+ dbgprintf("[getUUID] pM is NULL\n");
+ *pBuf= UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ if(pM->pszUUID == NULL) {
+ dbgprintf("[getUUID] pM->pszUUID is NULL\n");
+ MsgLock(pM);
+ /* re-query, things may have changed in the mean time... */
+ if(pM->pszUUID == NULL)
+ msgSetUUID(pM);
+ MsgUnlock(pM);
+ } else { /* UUID already there we reuse it */
+ dbgprintf("[getUUID] pM->pszUUID already exists\n");
+ }
+ *pBuf = pM->pszUUID;
+ *piLen = sizeof(uuid_t) * 2;
+ }
+ dbgprintf("[getUUID] END\n");
+}
+#endif
+
+void
+getRawMsg(msg_t *pM, uchar **pBuf, int *piLen)
+{
+ if(pM == NULL) {
+ *pBuf= UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ if(pM->pszRawMsg == NULL) {
+ *pBuf= UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ *pBuf = pM->pszRawMsg;
+ *piLen = pM->iLenRawMsg;
+ }
+ }
+}
+
+
+/* note: setMSGLen() is only for friends who really know what they
+ * do. Setting an invalid length can be desasterous!
+ */
+void setMSGLen(msg_t *pM, int lenMsg)
+{
+ pM->iLenMSG = lenMsg;
+}
+
+int getMSGLen(msg_t *pM)
+{
+ return((pM == NULL) ? 0 : pM->iLenMSG);
+}
+
+uchar *getMSG(msg_t *pM)
+{
+ uchar *ret;
+ if(pM == NULL)
+ ret = UCHAR_CONSTANT("");
+ else {
+ if(pM->iLenMSG == 0)
+ ret = UCHAR_CONSTANT("");
+ else
+ ret = pM->pszRawMsg + pM->offMSG;
+ }
+ return ret;
+}
+
+
+/* Get PRI value as integer */
+static int getPRIi(msg_t *pM)
+{
+ return (pM->iFacility << 3) + (pM->iSeverity);
+}
+
+
+/* Get PRI value in text form
+ */
+char *
+getPRI(msg_t *pM)
+{
+ /* PRI is a number in the range 0..191. Thus, we use a simple lookup table to obtain the
+ * string value. It looks a bit clumpsy here in code ;)
+ */
+ int iPRI;
+
+ if(pM == NULL)
+ return "";
+
+ iPRI = getPRIi(pM);
+ return (iPRI > 191) ? "invld" : (char*)syslog_pri_names[iPRI].pszName;
+}
+
+
+char *
+getTimeReported(msg_t *pM, enum tplFormatTypes eFmt)
+{
+ BEGINfunc
+ if(pM == NULL)
+ return "";
+
+ switch(eFmt) {
+ case tplFmtDefault:
+ case tplFmtRFC3164Date:
+ case tplFmtRFC3164BuggyDate:
+ MsgLock(pM);
+ if(pM->pszTIMESTAMP3164 == NULL) {
+ pM->pszTIMESTAMP3164 = pM->pszTimestamp3164;
+ datetime.formatTimestamp3164(&pM->tTIMESTAMP, pM->pszTIMESTAMP3164,
+ (eFmt == tplFmtRFC3164BuggyDate));
+ }
+ MsgUnlock(pM);
+ return(pM->pszTIMESTAMP3164);
+ case tplFmtMySQLDate:
+ MsgLock(pM);
+ if(pM->pszTIMESTAMP_MySQL == NULL) {
+ if((pM->pszTIMESTAMP_MySQL = MALLOC(15)) == NULL) {
+ MsgUnlock(pM);
+ return "";
+ }
+ datetime.formatTimestampToMySQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_MySQL);
+ }
+ MsgUnlock(pM);
+ return(pM->pszTIMESTAMP_MySQL);
+ case tplFmtPgSQLDate:
+ MsgLock(pM);
+ if(pM->pszTIMESTAMP_PgSQL == NULL) {
+ if((pM->pszTIMESTAMP_PgSQL = MALLOC(21)) == NULL) {
+ MsgUnlock(pM);
+ return "";
+ }
+ datetime.formatTimestampToPgSQL(&pM->tTIMESTAMP, pM->pszTIMESTAMP_PgSQL);
+ }
+ MsgUnlock(pM);
+ return(pM->pszTIMESTAMP_PgSQL);
+ case tplFmtRFC3339Date:
+ MsgLock(pM);
+ if(pM->pszTIMESTAMP3339 == NULL) {
+ pM->pszTIMESTAMP3339 = pM->pszTimestamp3339;
+ datetime.formatTimestamp3339(&pM->tTIMESTAMP, pM->pszTIMESTAMP3339);
+ }
+ MsgUnlock(pM);
+ return(pM->pszTIMESTAMP3339);
+ case tplFmtUnixDate:
+ MsgLock(pM);
+ if(pM->pszTIMESTAMP_Unix[0] == '\0') {
+ datetime.formatTimestampUnix(&pM->tTIMESTAMP, pM->pszTIMESTAMP_Unix);
+ }
+ MsgUnlock(pM);
+ return(pM->pszTIMESTAMP_Unix);
+ case tplFmtSecFrac:
+ if(pM->pszTIMESTAMP_SecFrac[0] == '\0') {
+ MsgLock(pM);
+ /* re-check, may have changed while we did not hold lock */
+ if(pM->pszTIMESTAMP_SecFrac[0] == '\0') {
+ datetime.formatTimestampSecFrac(&pM->tTIMESTAMP, pM->pszTIMESTAMP_SecFrac);
+ }
+ MsgUnlock(pM);
+ }
+ return(pM->pszTIMESTAMP_SecFrac);
+ }
+ ENDfunc
+ return "INVALID eFmt OPTION!";
+}
+
+static inline char *getTimeGenerated(msg_t *pM, enum tplFormatTypes eFmt)
+{
+ BEGINfunc
+ if(pM == NULL)
+ return "";
+
+ switch(eFmt) {
+ case tplFmtDefault:
+ MsgLock(pM);
+ if(pM->pszRcvdAt3164 == NULL) {
+ if((pM->pszRcvdAt3164 = MALLOC(16)) == NULL) {
+ MsgUnlock(pM);
+ return "";
+ }
+ datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164, 0);
+ }
+ MsgUnlock(pM);
+ return(pM->pszRcvdAt3164);
+ case tplFmtMySQLDate:
+ MsgLock(pM);
+ if(pM->pszRcvdAt_MySQL == NULL) {
+ if((pM->pszRcvdAt_MySQL = MALLOC(15)) == NULL) {
+ MsgUnlock(pM);
+ return "";
+ }
+ datetime.formatTimestampToMySQL(&pM->tRcvdAt, pM->pszRcvdAt_MySQL);
+ }
+ MsgUnlock(pM);
+ return(pM->pszRcvdAt_MySQL);
+ case tplFmtPgSQLDate:
+ MsgLock(pM);
+ if(pM->pszRcvdAt_PgSQL == NULL) {
+ if((pM->pszRcvdAt_PgSQL = MALLOC(21)) == NULL) {
+ MsgUnlock(pM);
+ return "";
+ }
+ datetime.formatTimestampToPgSQL(&pM->tRcvdAt, pM->pszRcvdAt_PgSQL);
+ }
+ MsgUnlock(pM);
+ return(pM->pszRcvdAt_PgSQL);
+ case tplFmtRFC3164Date:
+ case tplFmtRFC3164BuggyDate:
+ MsgLock(pM);
+ if(pM->pszRcvdAt3164 == NULL) {
+ if((pM->pszRcvdAt3164 = MALLOC(16)) == NULL) {
+ MsgUnlock(pM);
+ return "";
+ }
+ datetime.formatTimestamp3164(&pM->tRcvdAt, pM->pszRcvdAt3164,
+ (eFmt == tplFmtRFC3164BuggyDate));
+ }
+ MsgUnlock(pM);
+ return(pM->pszRcvdAt3164);
+ case tplFmtRFC3339Date:
+ MsgLock(pM);
+ if(pM->pszRcvdAt3339 == NULL) {
+ if((pM->pszRcvdAt3339 = MALLOC(33)) == NULL) {
+ MsgUnlock(pM);
+ return "";
+ }
+ datetime.formatTimestamp3339(&pM->tRcvdAt, pM->pszRcvdAt3339);
+ }
+ MsgUnlock(pM);
+ return(pM->pszRcvdAt3339);
+ case tplFmtUnixDate:
+ MsgLock(pM);
+ if(pM->pszRcvdAt_Unix[0] == '\0') {
+ datetime.formatTimestampUnix(&pM->tRcvdAt, pM->pszRcvdAt_Unix);
+ }
+ MsgUnlock(pM);
+ return(pM->pszRcvdAt_Unix);
+ case tplFmtSecFrac:
+ if(pM->pszRcvdAt_SecFrac[0] == '\0') {
+ MsgLock(pM);
+ /* re-check, may have changed while we did not hold lock */
+ if(pM->pszRcvdAt_SecFrac[0] == '\0') {
+ datetime.formatTimestampSecFrac(&pM->tRcvdAt, pM->pszRcvdAt_SecFrac);
+ }
+ MsgUnlock(pM);
+ }
+ return(pM->pszRcvdAt_SecFrac);
+ }
+ ENDfunc
+ return "INVALID eFmt OPTION!";
+}
+
+
+static inline char *getSeverity(msg_t *pM)
+{
+ char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(pM->iSeverity < 0 || pM->iSeverity > 7) {
+ name = "invld";
+ } else {
+ name = syslog_number_names[pM->iSeverity];
+ }
+
+ return name;
+}
+
+
+static inline char *getSeverityStr(msg_t *pM)
+{
+ char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(pM->iSeverity < 0 || pM->iSeverity > 7) {
+ name = "invld";
+ } else {
+ name = syslog_severity_names[pM->iSeverity];
+ }
+
+ return name;
+}
+
+static inline char *getFacility(msg_t *pM)
+{
+ char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(pM->iFacility < 0 || pM->iFacility > 23) {
+ name = "invld";
+ } else {
+ name = syslog_number_names[pM->iFacility];
+ }
+
+ return name;
+}
+
+static inline char *getFacilityStr(msg_t *pM)
+{
+ char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(pM->iFacility < 0 || pM->iFacility > 23) {
+ name = "invld";
+ } else {
+ name = syslog_fac_names[pM->iFacility];
+ }
+
+ return name;
+}
+
+
+/* set flow control state (if not called, the default - NO_DELAY - is used)
+ * This needs no locking because it is only done while the object is
+ * not fully constructed (which also means you must not call this
+ * method after the msg has been handed over to a queue).
+ * rgerhards, 2008-03-14
+ */
+rsRetVal
+MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl)
+{
+ DEFiRet;
+ assert(pMsg != NULL);
+ assert(eFlowCtl == eFLOWCTL_NO_DELAY || eFlowCtl == eFLOWCTL_LIGHT_DELAY || eFlowCtl == eFLOWCTL_FULL_DELAY);
+
+ pMsg->flowCtlType = eFlowCtl;
+
+ RETiRet;
+}
+
+/* set offset after which PRI in raw msg starts
+ * rgerhards, 2009-06-16
+ */
+rsRetVal
+MsgSetAfterPRIOffs(msg_t *pMsg, short offs)
+{
+ assert(pMsg != NULL);
+ pMsg->offAfterPRI = offs;
+ return RS_RET_OK;
+}
+
+
+/* rgerhards 2004-11-24: set APP-NAME in msg object
+ * This is not locked, because it either is called during message
+ * construction (where we need no locking) or later as part of a function
+ * which already obtained the lock. So in general, this function here must
+ * only be called when it it safe to do so without it aquiring a lock.
+ */
+rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME)
+{
+ DEFiRet;
+ assert(pMsg != NULL);
+ if(pMsg->pCSAPPNAME == NULL) {
+ /* we need to obtain the object first */
+ CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME));
+ }
+ /* if we reach this point, we have the object */
+ iRet = rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* rgerhards 2004-11-24: set PROCID in msg object
+ */
+rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pMsg, msg);
+ if(pMsg->pCSPROCID == NULL) {
+ /* we need to obtain the object first */
+ CHKiRet(cstrConstruct(&pMsg->pCSPROCID));
+ }
+ /* if we reach this point, we have the object */
+ CHKiRet(rsCStrSetSzStr(pMsg->pCSPROCID, (uchar*) pszPROCID));
+ CHKiRet(cstrFinalize(pMsg->pCSPROCID));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* check if we have a procid, and, if not, try to aquire/emulate it.
+ * This must be called WITHOUT the message lock being held.
+ * rgerhards, 2009-06-26
+ */
+static inline void preparePROCID(msg_t *pM, sbool bLockMutex)
+{
+ if(pM->pCSPROCID == NULL) {
+ if(bLockMutex == LOCK_MUTEX)
+ MsgLock(pM);
+ /* re-query, things may have changed in the mean time... */
+ if(pM->pCSPROCID == NULL)
+ aquirePROCIDFromTAG(pM);
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+ }
+}
+
+
+#if 0
+/* rgerhards, 2005-11-24
+ */
+static inline int getPROCIDLen(msg_t *pM, sbool bLockMutex)
+{
+ assert(pM != NULL);
+ preparePROCID(pM, bLockMutex);
+ return (pM->pCSPROCID == NULL) ? 1 : rsCStrLen(pM->pCSPROCID);
+}
+#endif
+
+
+/* rgerhards, 2005-11-24
+ */
+char *getPROCID(msg_t *pM, sbool bLockMutex)
+{
+ uchar *pszRet;
+
+ ISOBJ_TYPE_assert(pM, msg);
+ if(bLockMutex == LOCK_MUTEX)
+ MsgLock(pM);
+ preparePROCID(pM, MUTEX_ALREADY_LOCKED);
+ if(pM->pCSPROCID == NULL)
+ pszRet = UCHAR_CONSTANT("-");
+ else
+ pszRet = rsCStrGetSzStrNoNULL(pM->pCSPROCID);
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+ return (char*) pszRet;
+}
+
+
+/* rgerhards 2004-11-24: set MSGID in msg object
+ */
+rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pMsg, msg);
+ if(pMsg->pCSMSGID == NULL) {
+ /* we need to obtain the object first */
+ CHKiRet(rsCStrConstruct(&pMsg->pCSMSGID));
+ }
+ /* if we reach this point, we have the object */
+ iRet = rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Return state of last parser. If it had success, "OK" is returned, else
+ * "FAIL". All from the constant pool.
+ */
+static inline char *getParseSuccess(msg_t *pM)
+{
+ return (pM->bParseSuccess) ? "OK" : "FAIL";
+}
+
+
+/* al, 2011-07-26: LockMsg to avoid race conditions
+ */
+static inline char *getMSGID(msg_t *pM)
+{
+ if (pM->pCSMSGID == NULL) {
+ return "-";
+ }
+ else {
+ MsgLock(pM);
+ char* pszreturn = (char*) rsCStrGetSzStrNoNULL(pM->pCSMSGID);
+ MsgUnlock(pM);
+ return pszreturn;
+ }
+}
+
+/* rgerhards 2012-03-15: set parser success (an integer, acutally bool)
+ */
+void MsgSetParseSuccess(msg_t *pMsg, int bSuccess)
+{
+ assert(pMsg != NULL);
+ pMsg->bParseSuccess = bSuccess;
+}
+
+/* rgerhards 2009-06-12: set associated ruleset
+ */
+void MsgSetRuleset(msg_t *pMsg, ruleset_t *pRuleset)
+{
+ assert(pMsg != NULL);
+ pMsg->pRuleset = pRuleset;
+}
+
+
+/* set TAG in msg object
+ * (rewritten 2009-06-18 rgerhards)
+ */
+void MsgSetTAG(msg_t *pMsg, uchar* pszBuf, size_t lenBuf)
+{
+ uchar *pBuf;
+ assert(pMsg != NULL);
+
+ freeTAG(pMsg);
+
+ pMsg->iLenTAG = lenBuf;
+ if(pMsg->iLenTAG < CONF_TAG_BUFSIZE) {
+ /* small enough: use fixed buffer (faster!) */
+ pBuf = pMsg->TAG.szBuf;
+ } else {
+ if((pBuf = (uchar*) MALLOC(pMsg->iLenTAG + 1)) == NULL) {
+ /* truncate message, better than completely loosing it... */
+ pBuf = pMsg->TAG.szBuf;
+ pMsg->iLenTAG = CONF_TAG_BUFSIZE - 1;
+ } else {
+ pMsg->TAG.pszTAG = pBuf;
+ }
+ }
+
+ memcpy(pBuf, pszBuf, pMsg->iLenTAG);
+ pBuf[pMsg->iLenTAG] = '\0'; /* this also works with truncation! */
+}
+
+
+/* This function tries to emulate the TAG if none is
+ * set. Its primary purpose is to provide an old-style TAG
+ * when a syslog-protocol message has been received. Then,
+ * the tag is APP-NAME "[" PROCID "]". The function first checks
+ * if there is a TAG and, if not, if it can emulate it.
+ * rgerhards, 2005-11-24
+ */
+static inline void tryEmulateTAG(msg_t *pM, sbool bLockMutex)
+{
+ size_t lenTAG;
+ uchar bufTAG[CONF_TAG_MAXSIZE];
+ assert(pM != NULL);
+
+ if(bLockMutex == LOCK_MUTEX)
+ MsgLock(pM);
+ if(pM->iLenTAG > 0) {
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+ return; /* done, no need to emulate */
+ }
+
+ if(getProtocolVersion(pM) == 1) {
+ if(!strcmp(getPROCID(pM, MUTEX_ALREADY_LOCKED), "-")) {
+ /* no process ID, use APP-NAME only */
+ MsgSetTAG(pM, (uchar*) getAPPNAME(pM, MUTEX_ALREADY_LOCKED), getAPPNAMELen(pM, MUTEX_ALREADY_LOCKED));
+ } else {
+ /* now we can try to emulate */
+ lenTAG = snprintf((char*)bufTAG, CONF_TAG_MAXSIZE, "%s[%s]",
+ getAPPNAME(pM, MUTEX_ALREADY_LOCKED), getPROCID(pM, MUTEX_ALREADY_LOCKED));
+ bufTAG[sizeof(bufTAG)-1] = '\0'; /* just to make sure... */
+ MsgSetTAG(pM, bufTAG, lenTAG);
+ }
+ }
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+}
+
+
+void
+getTAG(msg_t *pM, uchar **ppBuf, int *piLen)
+{
+ if(pM == NULL) {
+ *ppBuf = UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ if(pM->iLenTAG == 0)
+ tryEmulateTAG(pM, LOCK_MUTEX);
+ if(pM->iLenTAG == 0) {
+ *ppBuf = UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ *ppBuf = (pM->iLenTAG < CONF_TAG_BUFSIZE) ? pM->TAG.szBuf : pM->TAG.pszTAG;
+ *piLen = pM->iLenTAG;
+ }
+ }
+}
+
+
+int getHOSTNAMELen(msg_t *pM)
+{
+ if(pM == NULL)
+ return 0;
+ else
+ if(pM->pszHOSTNAME == NULL) {
+ resolveDNS(pM);
+ if(pM->rcvFrom.pRcvFrom == NULL)
+ return 0;
+ else
+ return prop.GetStringLen(pM->rcvFrom.pRcvFrom);
+ } else
+ return pM->iLenHOSTNAME;
+}
+
+
+char *getHOSTNAME(msg_t *pM)
+{
+ if(pM == NULL)
+ return "";
+ else
+ if(pM->pszHOSTNAME == NULL) {
+ resolveDNS(pM);
+ if(pM->rcvFrom.pRcvFrom == NULL) {
+ return "";
+ } else {
+ uchar *psz;
+ int len;
+ prop.GetString(pM->rcvFrom.pRcvFrom, &psz, &len);
+ return (char*) psz;
+ }
+ } else {
+ return (char*) pM->pszHOSTNAME;
+ }
+}
+
+
+uchar *getRcvFrom(msg_t *pM)
+{
+ uchar *psz;
+ int len;
+ BEGINfunc
+
+ if(pM == NULL) {
+ psz = UCHAR_CONSTANT("");
+ } else {
+ resolveDNS(pM);
+ if(pM->rcvFrom.pRcvFrom == NULL)
+ psz = UCHAR_CONSTANT("");
+ else
+ prop.GetString(pM->rcvFrom.pRcvFrom, &psz, &len);
+ }
+ ENDfunc
+ return psz;
+}
+
+
+/* rgerhards 2004-11-24: set STRUCTURED DATA in msg object
+ */
+rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pMsg, msg);
+ if(pMsg->pCSStrucData == NULL) {
+ /* we need to obtain the object first */
+ CHKiRet(rsCStrConstruct(&pMsg->pCSStrucData));
+ }
+ /* if we reach this point, we have the object */
+ iRet = rsCStrSetSzStr(pMsg->pCSStrucData, (uchar*) pszStrucData);
+
+finalize_it:
+ RETiRet;
+}
+
+/* get the length of the "STRUCTURED-DATA" sz string
+ * rgerhards, 2005-11-24
+ */
+#if 0 /* This method is currently not called, be we like to preserve it */
+static int getStructuredDataLen(msg_t *pM)
+{
+ return (pM->pCSStrucData == NULL) ? 1 : rsCStrLen(pM->pCSStrucData);
+}
+#endif
+
+
+/* get the "STRUCTURED-DATA" as sz string
+ * rgerhards, 2005-11-24
+ */
+static inline char *getStructuredData(msg_t *pM)
+{
+ uchar *pszRet;
+
+ MsgLock(pM);
+ if(pM->pCSStrucData == NULL)
+ pszRet = UCHAR_CONSTANT("-");
+ else
+ pszRet = rsCStrGetSzStrNoNULL(pM->pCSStrucData);
+ MsgUnlock(pM);
+ return (char*) pszRet;
+}
+
+/* get the "programname" as sz string
+ * rgerhards, 2005-10-19
+ */
+uchar *getProgramName(msg_t *pM, sbool bLockMutex)
+{
+ if(pM->iLenPROGNAME == -1) {
+ if(bLockMutex == LOCK_MUTEX) {
+ MsgLock(pM);
+ /* need to re-check, things may have change in between! */
+ if(pM->iLenPROGNAME == -1)
+ aquireProgramName(pM);
+ MsgUnlock(pM);
+ } else {
+ aquireProgramName(pM);
+ }
+ }
+ return (pM->iLenPROGNAME < CONF_PROGNAME_BUFSIZE) ? pM->PROGNAME.szBuf
+ : pM->PROGNAME.ptr;
+}
+
+
+/* This function tries to emulate APPNAME if it is not present. Its
+ * main use is when we have received a log record via legacy syslog and
+ * now would like to send out the same one via syslog-protocol.
+ * MUST be called with the Msg Lock locked!
+ */
+static void tryEmulateAPPNAME(msg_t *pM)
+{
+ assert(pM != NULL);
+ if(pM->pCSAPPNAME != NULL)
+ return; /* we are already done */
+
+ if(getProtocolVersion(pM) == 0) {
+ /* only then it makes sense to emulate */
+ MsgSetAPPNAME(pM, (char*)getProgramName(pM, MUTEX_ALREADY_LOCKED));
+ }
+}
+
+
+
+/* check if we have a APPNAME, and, if not, try to aquire/emulate it.
+ * This must be called WITHOUT the message lock being held.
+ * rgerhards, 2009-06-26
+ */
+static inline void prepareAPPNAME(msg_t *pM, sbool bLockMutex)
+{
+ if(pM->pCSAPPNAME == NULL) {
+ if(bLockMutex == LOCK_MUTEX)
+ MsgLock(pM);
+
+ /* re-query as things might have changed during locking */
+ if(pM->pCSAPPNAME == NULL)
+ tryEmulateAPPNAME(pM);
+
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+ }
+}
+
+/* rgerhards, 2005-11-24
+ */
+char *getAPPNAME(msg_t *pM, sbool bLockMutex)
+{
+ uchar *pszRet;
+
+ assert(pM != NULL);
+ if(bLockMutex == LOCK_MUTEX)
+ MsgLock(pM);
+ prepareAPPNAME(pM, MUTEX_ALREADY_LOCKED);
+ if(pM->pCSAPPNAME == NULL)
+ pszRet = UCHAR_CONSTANT("");
+ else
+ pszRet = rsCStrGetSzStrNoNULL(pM->pCSAPPNAME);
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+ return (char*)pszRet;
+}
+
+/* rgerhards, 2005-11-24
+ */
+static int getAPPNAMELen(msg_t *pM, sbool bLockMutex)
+{
+ assert(pM != NULL);
+ prepareAPPNAME(pM, bLockMutex);
+ return (pM->pCSAPPNAME == NULL) ? 0 : rsCStrLen(pM->pCSAPPNAME);
+}
+
+/* rgerhards 2008-09-10: set pszInputName in msg object. This calls AddRef()
+ * on the property, because this must be done in all current cases and there
+ * is no case expected where this may not be necessary.
+ * rgerhards, 2009-06-16
+ */
+void MsgSetInputName(msg_t *pThis, prop_t *inputName)
+{
+ assert(pThis != NULL);
+
+ prop.AddRef(inputName);
+ if(pThis->pInputName != NULL)
+ prop.Destruct(&pThis->pInputName);
+ pThis->pInputName = inputName;
+}
+
+
+/* Set the pfrominet socket store, so that we can obtain the peer at some
+ * later time. Note that we do not check if pRcvFrom is already set, so this
+ * function must only be called during message creation.
+ * NOTE: msgFlags is NOT set. While this is somewhat a violation of layers,
+ * it is done because it gains us some performance. So the caller must make
+ * sure the message flags are properly maintained. For all current callers,
+ * this is always the case and without extra effort required.
+ * rgerhards, 2009-11-17
+ */
+rsRetVal
+msgSetFromSockinfo(msg_t *pThis, struct sockaddr_storage *sa){
+ DEFiRet;
+ assert(pThis->rcvFrom.pRcvFrom == NULL);
+
+ CHKmalloc(pThis->rcvFrom.pfrominet = malloc(sizeof(struct sockaddr_storage)));
+ memcpy(pThis->rcvFrom.pfrominet, sa, sizeof(struct sockaddr_storage));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* rgerhards 2008-09-10: set RcvFrom name in msg object. This calls AddRef()
+ * on the property, because this must be done in all current cases and there
+ * is no case expected where this may not be necessary.
+ * rgerhards, 2009-06-30
+ */
+void MsgSetRcvFrom(msg_t *pThis, prop_t *new)
+{
+ prop.AddRef(new);
+ MsgSetRcvFromWithoutAddRef(pThis, new);
+}
+
+
+/* This is used to set the property via a string. This function should not be
+ * called if there is a reliable way for a caller to make sure that the
+ * same name can be used across multiple messages. However, if it can not
+ * ensure that, calling this function is the second best thing, because it
+ * will re-use the previously created property if it contained the same
+ * name (but it works only for the immediate previous).
+ * rgerhards, 2009-06-31
+ */
+void MsgSetRcvFromStr(msg_t *pThis, uchar *psz, int len, prop_t **ppProp)
+{
+ assert(pThis != NULL);
+ assert(ppProp != NULL);
+
+ prop.CreateOrReuseStringProp(ppProp, psz, len);
+ MsgSetRcvFrom(pThis, *ppProp);
+}
+
+
+/* set RcvFromIP name in msg object. This calls AddRef()
+ * on the property, because this must be done in all current cases and there
+ * is no case expected where this may not be necessary.
+ * rgerhards, 2009-06-30
+ */
+rsRetVal MsgSetRcvFromIP(msg_t *pThis, prop_t *new)
+{
+ assert(pThis != NULL);
+
+ BEGINfunc
+ prop.AddRef(new);
+ MsgSetRcvFromIPWithoutAddRef(pThis, new);
+ ENDfunc
+ return RS_RET_OK;
+}
+
+
+/* This is used to set the property via a string. This function should not be
+ * called if there is a reliable way for a caller to make sure that the
+ * same name can be used across multiple messages. However, if it can not
+ * ensure that, calling this function is the second best thing, because it
+ * will re-use the previously created property if it contained the same
+ * name (but it works only for the immediate previous).
+ * rgerhards, 2009-06-31
+ */
+rsRetVal MsgSetRcvFromIPStr(msg_t *pThis, uchar *psz, int len, prop_t **ppProp)
+{
+ DEFiRet;
+ assert(pThis != NULL);
+
+ CHKiRet(prop.CreateOrReuseStringProp(ppProp, psz, len));
+ MsgSetRcvFromIP(pThis, *ppProp);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* rgerhards 2004-11-09: set HOSTNAME in msg object
+ * rgerhards, 2007-06-21:
+ * Does not return anything. If an error occurs, the hostname is
+ * simply not set. I have changed this behaviour. The only problem
+ * we can run into is memory shortage. If we have such, it is better
+ * to loose the hostname than the full message. So we silently ignore
+ * that problem and hope that memory will be available the next time
+ * we need it. The rest of the code already knows how to handle an
+ * unset HOSTNAME.
+ */
+void MsgSetHOSTNAME(msg_t *pThis, uchar* pszHOSTNAME, int lenHOSTNAME)
+{
+ assert(pThis != NULL);
+
+ freeHOSTNAME(pThis);
+
+ pThis->iLenHOSTNAME = lenHOSTNAME;
+ if(pThis->iLenHOSTNAME < CONF_HOSTNAME_BUFSIZE) {
+ /* small enough: use fixed buffer (faster!) */
+ pThis->pszHOSTNAME = pThis->szHOSTNAME;
+ } else if((pThis->pszHOSTNAME = (uchar*) MALLOC(pThis->iLenHOSTNAME + 1)) == NULL) {
+ /* truncate message, better than completely loosing it... */
+ pThis->pszHOSTNAME = pThis->szHOSTNAME;
+ pThis->iLenHOSTNAME = CONF_HOSTNAME_BUFSIZE - 1;
+ }
+
+ memcpy(pThis->pszHOSTNAME, pszHOSTNAME, pThis->iLenHOSTNAME);
+ pThis->pszHOSTNAME[pThis->iLenHOSTNAME] = '\0'; /* this also works with truncation! */
+}
+
+
+/* set the offset of the MSG part into the raw msg buffer
+ * Note that the offset may be higher than the length of the raw message
+ * (exactly by one). This can happen if we have a message that does not
+ * contain any MSG part.
+ */
+void MsgSetMSGoffs(msg_t *pMsg, short offs)
+{
+ ISOBJ_TYPE_assert(pMsg, msg);
+ pMsg->offMSG = offs;
+ if(offs > pMsg->iLenRawMsg) {
+ assert(offs - 1 == pMsg->iLenRawMsg);
+ pMsg->iLenMSG = 0;
+ } else {
+ pMsg->iLenMSG = pMsg->iLenRawMsg - offs;
+ }
+}
+
+
+/* replace the MSG part of a message. The update actually takes place inside
+ * rawmsg.
+ * There are two cases: either the new message will be larger than the new msg
+ * or it will be less than or equal. If it is less than or equal, we can utilize
+ * the previous message buffer. If it is larger, we can utilize the msg_t-included
+ * message buffer if it fits in there. If this is not the case, we need to alloc
+ * a new, larger, chunk and copy over the data to it. Note that this function is
+ * (hopefully) relatively seldom being called, so some performance impact is
+ * uncritical. In any case, pszMSG is copied, so if it was dynamically allocated,
+ * the caller is responsible for freeing it.
+ * rgerhards, 2009-06-23
+ */
+rsRetVal MsgReplaceMSG(msg_t *pThis, uchar* pszMSG, int lenMSG)
+{
+ int lenNew;
+ uchar *bufNew;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, msg);
+ assert(pszMSG != NULL);
+
+ lenNew = pThis->iLenRawMsg + lenMSG - pThis->iLenMSG;
+ if(lenMSG > pThis->iLenMSG && lenNew >= CONF_RAWMSG_BUFSIZE) {
+ /* we have lost our "bet" and need to alloc a new buffer ;) */
+ CHKmalloc(bufNew = MALLOC(lenNew + 1));
+ memcpy(bufNew, pThis->pszRawMsg, pThis->offMSG);
+ if(pThis->pszRawMsg != pThis->szRawMsg)
+ free(pThis->pszRawMsg);
+ pThis->pszRawMsg = bufNew;
+ }
+
+ if(lenMSG > 0)
+ memcpy(pThis->pszRawMsg + pThis->offMSG, pszMSG, lenMSG);
+ pThis->pszRawMsg[lenNew] = '\0'; /* this also works with truncation! */
+ pThis->iLenRawMsg = lenNew;
+ pThis->iLenMSG = lenMSG;
+
+finalize_it:
+ RETiRet;
+}
+
+/* set raw message in message object. Size of message is provided.
+ * The function makes sure that the stored rawmsg is properly
+ * terminated by '\0'.
+ * rgerhards, 2009-06-16
+ */
+void MsgSetRawMsg(msg_t *pThis, char* pszRawMsg, size_t lenMsg)
+{
+ assert(pThis != NULL);
+ if(pThis->pszRawMsg != pThis->szRawMsg)
+ free(pThis->pszRawMsg);
+
+ pThis->iLenRawMsg = lenMsg;
+ if(pThis->iLenRawMsg < CONF_RAWMSG_BUFSIZE) {
+ /* small enough: use fixed buffer (faster!) */
+ pThis->pszRawMsg = pThis->szRawMsg;
+ } else if((pThis->pszRawMsg = (uchar*) MALLOC(pThis->iLenRawMsg + 1)) == NULL) {
+ /* truncate message, better than completely loosing it... */
+ pThis->pszRawMsg = pThis->szRawMsg;
+ pThis->iLenRawMsg = CONF_RAWMSG_BUFSIZE - 1;
+ }
+
+ memcpy(pThis->pszRawMsg, pszRawMsg, pThis->iLenRawMsg);
+ pThis->pszRawMsg[pThis->iLenRawMsg] = '\0'; /* this also works with truncation! */
+}
+
+
+/* set raw message in message object. Size of message is not provided. This
+ * function should only be used when it is unavoidable (and over time we should
+ * try to remove it altogether).
+ * rgerhards, 2009-06-16
+ */
+void MsgSetRawMsgWOSize(msg_t *pMsg, char* pszRawMsg)
+{
+ MsgSetRawMsg(pMsg, pszRawMsg, strlen(pszRawMsg));
+}
+
+
+/* Decode a priority into textual information like auth.emerg.
+ * The variable pRes must point to a user-supplied buffer.
+ * The pointer to the buffer
+ * is also returned, what makes this functiona suitable for
+ * use in printf-like functions.
+ * Note: a buffer size of 20 characters is always sufficient.
+ */
+char *textpri(char *pRes, int pri)
+{
+ assert(pRes != NULL);
+ memcpy(pRes, syslog_fac_names[LOG_FAC(pri)], len_syslog_fac_names[LOG_FAC(pri)]);
+ pRes[len_syslog_fac_names[LOG_FAC(pri)]] = '.';
+ memcpy(pRes+len_syslog_fac_names[LOG_FAC(pri)]+1,
+ syslog_severity_names[LOG_PRI(pri)],
+ len_syslog_severity_names[LOG_PRI(pri)]+1 /* for \0! */);
+ return pRes;
+}
+
+
+/* This function returns the current date in different
+ * variants. It is used to construct the $NOW series of
+ * system properties. The returned buffer must be freed
+ * by the caller when no longer needed. If the function
+ * can not allocate memory, it returns a NULL pointer.
+ * Added 2007-07-10 rgerhards
+ */
+typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_HHOUR, NOW_QHOUR, NOW_MINUTE } eNOWType;
+#define tmpBUFSIZE 16 /* size of formatting buffer */
+static uchar *getNOW(eNOWType eNow, struct syslogTime *t)
+{
+ uchar *pBuf;
+
+ if((pBuf = (uchar*) MALLOC(sizeof(uchar) * tmpBUFSIZE)) == NULL) {
+ return NULL;
+ }
+
+ if(t->year == 0) { /* not yet set! */
+ datetime.getCurrTime(t, NULL);
+ }
+
+ switch(eNow) {
+ case NOW_NOW:
+ memcpy(pBuf, two_digits[t->year/100], 2);
+ memcpy(pBuf+2, two_digits[t->year%100], 2);
+ pBuf[4] = '-';
+ memcpy(pBuf+5, two_digits[(int)t->month], 2);
+ pBuf[7] = '-';
+ memcpy(pBuf+8, two_digits[(int)t->day], 3);
+ break;
+ case NOW_YEAR:
+ memcpy(pBuf, two_digits[t->year/100], 2);
+ memcpy(pBuf+2, two_digits[t->year%100], 3);
+ break;
+ case NOW_MONTH:
+ memcpy(pBuf, two_digits[(int)t->month], 3);
+ break;
+ case NOW_DAY:
+ memcpy(pBuf, two_digits[(int)t->day], 3);
+ break;
+ case NOW_HOUR:
+ memcpy(pBuf, two_digits[(int)t->hour], 3);
+ break;
+ case NOW_HHOUR:
+ memcpy(pBuf, two_digits[t->hour/30], 3);
+ break;
+ case NOW_QHOUR:
+ memcpy(pBuf, two_digits[t->hour/15], 3);
+ break;
+ case NOW_MINUTE:
+ memcpy(pBuf, two_digits[(int)t->minute], 3);
+ break;
+ }
+
+ return(pBuf);
+}
+#undef tmpBUFSIZE /* clean up */
+
+
+/* Get a CEE-Property as string value*/
+rsRetVal
+getCEEPropVal(msg_t *pM, es_str_t *propName, uchar **pRes, rs_size_t *buflen, unsigned short *pbMustBeFreed)
+{
+ uchar *name = NULL;
+ uchar *leaf;
+ struct json_object *parent;
+ struct json_object *field;
+ DEFiRet;
+
+ if(*pbMustBeFreed)
+ free(*pRes);
+ *pRes = NULL;
+ // TODO: mutex?
+ if(pM->json == NULL) goto finalize_it;
+
+ if(!es_strbufcmp(propName, (uchar*)"!", 1)) {
+ field = pM->json;
+ } else {
+ name = (uchar*)es_str2cstr(propName, NULL);
+ leaf = jsonPathGetLeaf(name, ustrlen(name));
+ CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1));
+ field = json_object_object_get(parent, (char*)leaf);
+ }
+ if(field != NULL) {
+ *pRes = (uchar*) strdup(json_object_get_string(field));
+ *buflen = (int) ustrlen(*pRes);
+ *pbMustBeFreed = 1;
+ }
+
+finalize_it:
+ free(name);
+ if(*pRes == NULL) {
+ /* could not find any value, so set it to empty */
+ *pRes = (unsigned char*)"";
+ *pbMustBeFreed = 0;
+ }
+ RETiRet;
+}
+
+
+/* Get a CEE-Property as native json object
+ */
+rsRetVal
+msgGetCEEPropJSON(msg_t *pM, es_str_t *propName, struct json_object **pjson)
+{
+ uchar *name = NULL;
+ uchar *leaf;
+ struct json_object *parent;
+ DEFiRet;
+
+ // TODO: mutex?
+ if(pM->json == NULL) {
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+ if(!es_strbufcmp(propName, (uchar*)"!", 1)) {
+ *pjson = pM->json;
+ FINALIZE;
+ }
+ name = (uchar*)es_str2cstr(propName, NULL);
+ leaf = jsonPathGetLeaf(name, ustrlen(name));
+ CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1));
+ *pjson = json_object_object_get(parent, (char*)leaf);
+ if(*pjson == NULL) {
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+finalize_it:
+ free(name);
+ RETiRet;
+}
+
+
+/* Encode a JSON value and add it to provided string. Note that
+ * the string object may be NULL. In this case, it is created
+ * if and only if escaping is needed.
+ */
+static rsRetVal
+jsonAddVal(uchar *pSrc, unsigned buflen, es_str_t **dst)
+{
+ unsigned char c;
+ es_size_t i;
+ char numbuf[4];
+ int j;
+ DEFiRet;
+
+ for(i = 0 ; i < buflen ; ++i) {
+ c = pSrc[i];
+ if( (c >= 0x23 && c <= 0x5b)
+ || (c >= 0x5d /* && c <= 0x10FFFF*/)
+ || c == 0x20 || c == 0x21) {
+ /* no need to escape */
+ if(*dst != NULL)
+ es_addChar(dst, c);
+ } else {
+ if(*dst == NULL) {
+ if(i == 0) {
+ /* we hope we have only few escapes... */
+ *dst = es_newStr(buflen+10);
+ } else {
+ *dst = es_newStrFromBuf((char*)pSrc, i);
+ }
+ if(*dst == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ }
+ /* we must escape, try RFC4627-defined special sequences first */
+ switch(c) {
+ case '\0':
+ es_addBuf(dst, "\\u0000", 6);
+ break;
+ case '\"':
+ es_addBuf(dst, "\\\"", 2);
+ break;
+ case '/':
+ es_addBuf(dst, "\\/", 2);
+ break;
+ case '\\':
+ es_addBuf(dst, "\\\\", 2);
+ break;
+ case '\010':
+ es_addBuf(dst, "\\b", 2);
+ break;
+ case '\014':
+ es_addBuf(dst, "\\f", 2);
+ break;
+ case '\n':
+ es_addBuf(dst, "\\n", 2);
+ break;
+ case '\r':
+ es_addBuf(dst, "\\r", 2);
+ break;
+ case '\t':
+ es_addBuf(dst, "\\t", 2);
+ break;
+ default:
+ /* TODO : proper Unicode encoding (see header comment) */
+ for(j = 0 ; j < 4 ; ++j) {
+ numbuf[3-j] = hexdigit[c % 16];
+ c = c / 16;
+ }
+ es_addBuf(dst, "\\u", 2);
+ es_addBuf(dst, numbuf, 4);
+ break;
+ }
+ }
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* encode a property in JSON escaped format. This is a helper
+ * to MsgGetProp. It needs to update all provided parameters.
+ * Note: Code is borrowed from libee (my own code, so ASL 2.0
+ * is fine with it); this function may later be replaced by
+ * some "better" and more complete implementation (maybe from
+ * libee or its helpers).
+ * For performance reasons, we begin to copy the string only
+ * when we recognice that we actually need to do some escaping.
+ * rgerhards, 2012-03-16
+ */
+static rsRetVal
+jsonEncode(uchar **ppRes, unsigned short *pbMustBeFreed, int *pBufLen)
+{
+ unsigned buflen;
+ uchar *pSrc;
+ es_str_t *dst = NULL;
+ DEFiRet;
+
+ pSrc = *ppRes;
+ buflen = (*pBufLen == -1) ? ustrlen(pSrc) : *pBufLen;
+ CHKiRet(jsonAddVal(pSrc, buflen, &dst));
+
+ if(dst != NULL) {
+ /* we updated the string and need to replace the
+ * previous data.
+ */
+ if(*pbMustBeFreed)
+ free(*ppRes);
+ *ppRes = (uchar*)es_str2cstr(dst, NULL);
+ *pbMustBeFreed = 1;
+ *pBufLen = -1;
+ es_deleteStr(dst);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Format a property as JSON field, that means
+ * "name"="value"
+ * where value is JSON-escaped (here we assume that the name
+ * only contains characters from the valid character set).
+ * Note: this function duplicates code from jsonEncode().
+ * TODO: these two functions should be combined, at least if
+ * that makes any sense from a performance PoV - definitely
+ * something to consider at a later stage. rgerhards, 2012-04-19
+ */
+static rsRetVal
+jsonField(struct templateEntry *pTpe, uchar **ppRes, unsigned short *pbMustBeFreed, int *pBufLen)
+{
+ unsigned buflen;
+ uchar *pSrc;
+ es_str_t *dst = NULL;
+ DEFiRet;
+
+ pSrc = *ppRes;
+ buflen = (*pBufLen == -1) ? ustrlen(pSrc) : *pBufLen;
+ /* we hope we have only few escapes... */
+ dst = es_newStr(buflen+pTpe->lenFieldName+15);
+ es_addChar(&dst, '"');
+ es_addBuf(&dst, (char*)pTpe->fieldName, pTpe->lenFieldName);
+ es_addBufConstcstr(&dst, "\":\"");
+ CHKiRet(jsonAddVal(pSrc, buflen, &dst));
+ es_addChar(&dst, '"');
+
+ if(*pbMustBeFreed)
+ free(*ppRes);
+ /* we know we do not have \0 chars - so the size does not change */
+ *pBufLen = es_strlen(dst);
+ *ppRes = (uchar*)es_str2cstr(dst, NULL);
+ *pbMustBeFreed = 1;
+ es_deleteStr(dst);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function returns a string-representation of the
+ * requested message property. This is a generic function used
+ * to abstract properties so that these can be easier
+ * queried. Returns NULL if property could not be found.
+ * Actually, this function is a big if..elseif. What it does
+ * is simply to map property names (from MonitorWare) to the
+ * message object data fields.
+ *
+ * In case we need string forms of propertis we do not
+ * yet have in string form, we do a memory allocation that
+ * is sufficiently large (in all cases). Once the string
+ * form has been obtained, it is saved until the Msg object
+ * is finally destroyed. This is so that we save the processing
+ * time in the (likely) case that this property is requested
+ * again. It also saves us a lot of dynamic memory management
+ * issues in the upper layers, because we so can guarantee that
+ * the buffer will remain static AND available during the lifetime
+ * of the object. Please note that both the max size allocation as
+ * well as keeping things in memory might like look like a
+ * waste of memory (some might say it actually is...) - we
+ * deliberately accept this because performance is more important
+ * to us ;)
+ * rgerhards 2004-11-18
+ * Parameter "bMustBeFreed" is set by this function. It tells the
+ * caller whether or not the string returned must be freed by the
+ * caller itself. It is is 0, the caller MUST NOT free it. If it is
+ * 1, the caller MUST free it. Handling this wrongly leads to either
+ * a memory leak of a program abort (do to double-frees or frees on
+ * the constant memory pool). So be careful to do it right.
+ * rgerhards 2004-11-23
+ * regular expression support contributed by Andres Riancho merged
+ * on 2005-09-13
+ * changed so that it now an be called without a template entry (NULL).
+ * In this case, only the (unmodified) property is returned. This will
+ * be used in selector line processing.
+ * rgerhards 2005-09-15
+ */
+/* a quick helper to save some writing: */
+#define RET_OUT_OF_MEMORY { *pbMustBeFreed = 0;\
+ *pPropLen = sizeof("**OUT OF MEMORY**") - 1; \
+ return(UCHAR_CONSTANT("**OUT OF MEMORY**"));}
+uchar *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe,
+ propid_t propid, es_str_t *propName, rs_size_t *pPropLen,
+ unsigned short *pbMustBeFreed, struct syslogTime *ttNow)
+{
+ uchar *pRes; /* result pointer */
+ rs_size_t bufLen = -1; /* length of string or -1, if not known */
+ uchar *pBufStart;
+ uchar *pBuf;
+ int iLen;
+ short iOffs;
+ enum tplFormatTypes datefmt;
+
+ BEGINfunc
+ assert(pMsg != NULL);
+ assert(pbMustBeFreed != NULL);
+
+#ifdef FEATURE_REGEXP
+ /* Variables necessary for regular expression matching */
+ size_t nmatch = 10;
+ regmatch_t pmatch[10];
+#endif
+
+ *pbMustBeFreed = 0;
+
+ switch(propid) {
+ case PROP_MSG:
+ pRes = getMSG(pMsg);
+ bufLen = getMSGLen(pMsg);
+ break;
+ case PROP_TIMESTAMP:
+ if (pTpe != NULL)
+ datefmt = pTpe->data.field.eDateFormat;
+ else
+ datefmt = tplFmtDefault;
+ pRes = (uchar*)getTimeReported(pMsg, datefmt);
+ break;
+ case PROP_HOSTNAME:
+ pRes = (uchar*)getHOSTNAME(pMsg);
+ bufLen = getHOSTNAMELen(pMsg);
+ break;
+ case PROP_SYSLOGTAG:
+ getTAG(pMsg, &pRes, &bufLen);
+ break;
+ case PROP_RAWMSG:
+ getRawMsg(pMsg, &pRes, &bufLen);
+ break;
+ case PROP_INPUTNAME:
+ getInputName(pMsg, &pRes, &bufLen);
+ break;
+ case PROP_FROMHOST:
+ pRes = getRcvFrom(pMsg);
+ break;
+ case PROP_FROMHOST_IP:
+ pRes = getRcvFromIP(pMsg);
+ break;
+ case PROP_PRI:
+ pRes = (uchar*)getPRI(pMsg);
+ break;
+ case PROP_PRI_TEXT:
+ pBuf = MALLOC(20 * sizeof(uchar));
+ if(pBuf == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ pRes = (uchar*)textpri((char*)pBuf, getPRIi(pMsg));
+ }
+ break;
+ case PROP_IUT:
+ pRes = UCHAR_CONSTANT("1"); /* always 1 for syslog messages (a MonitorWare thing;)) */
+ bufLen = 1;
+ break;
+ case PROP_SYSLOGFACILITY:
+ pRes = (uchar*)getFacility(pMsg);
+ break;
+ case PROP_SYSLOGFACILITY_TEXT:
+ pRes = (uchar*)getFacilityStr(pMsg);
+ break;
+ case PROP_SYSLOGSEVERITY:
+ pRes = (uchar*)getSeverity(pMsg);
+ break;
+ case PROP_SYSLOGSEVERITY_TEXT:
+ pRes = (uchar*)getSeverityStr(pMsg);
+ break;
+ case PROP_TIMEGENERATED:
+ if (pTpe != NULL)
+ datefmt = pTpe->data.field.eDateFormat;
+ else
+ datefmt = tplFmtDefault;
+ pRes = (uchar*)getTimeGenerated(pMsg, datefmt);
+ break;
+ case PROP_PROGRAMNAME:
+ pRes = getProgramName(pMsg, LOCK_MUTEX);
+ break;
+ case PROP_PROTOCOL_VERSION:
+ pRes = (uchar*)getProtocolVersionString(pMsg);
+ break;
+ case PROP_STRUCTURED_DATA:
+ pRes = (uchar*)getStructuredData(pMsg);
+ break;
+ case PROP_APP_NAME:
+ pRes = (uchar*)getAPPNAME(pMsg, LOCK_MUTEX);
+ break;
+ case PROP_PROCID:
+ pRes = (uchar*)getPROCID(pMsg, LOCK_MUTEX);
+ break;
+ case PROP_MSGID:
+ pRes = (uchar*)getMSGID(pMsg);
+ break;
+#ifdef USE_LIBUUID
+ case PROP_UUID:
+ getUUID(pMsg, &pRes, &bufLen);
+ break;
+#endif
+ case PROP_PARSESUCCESS:
+ pRes = (uchar*)getParseSuccess(pMsg);
+ break;
+ case PROP_SYS_NOW:
+ if((pRes = getNOW(NOW_NOW, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 10;
+ }
+ break;
+ case PROP_SYS_YEAR:
+ if((pRes = getNOW(NOW_YEAR, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 4;
+ }
+ break;
+ case PROP_SYS_MONTH:
+ if((pRes = getNOW(NOW_MONTH, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_DAY:
+ if((pRes = getNOW(NOW_DAY, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_HOUR:
+ if((pRes = getNOW(NOW_HOUR, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_HHOUR:
+ if((pRes = getNOW(NOW_HHOUR, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_QHOUR:
+ if((pRes = getNOW(NOW_QHOUR, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_MINUTE:
+ if((pRes = getNOW(NOW_MINUTE, ttNow)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_MYHOSTNAME:
+ pRes = glbl.GetLocalHostName();
+ break;
+ case PROP_CEE_ALL_JSON:
+ if(pMsg->json == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = (uchar*) "{}";
+ bufLen = 2;
+ *pbMustBeFreed = 0;
+ } else {
+ pRes = (uchar*)strdup(json_object_get_string(pMsg->json));
+ *pbMustBeFreed = 1;
+ }
+ break;
+ case PROP_CEE:
+ getCEEPropVal(pMsg, propName, &pRes, &bufLen, pbMustBeFreed);
+ break;
+ case PROP_SYS_BOM:
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = (uchar*) "\xEF\xBB\xBF";
+ *pbMustBeFreed = 0;
+ break;
+ case PROP_SYS_UPTIME:
+# ifndef HAVE_SYSINFO_UPTIME
+ /* An alternative on some systems (eg Solaris) is to scan
+ * /var/adm/utmpx for last boot time.
+ */
+ pRes = (uchar*) "UPTIME NOT available on this system";
+ *pbMustBeFreed = 0;
+# else
+ {
+ struct sysinfo s_info;
+
+ if((pRes = (uchar*) MALLOC(sizeof(uchar) * 32)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ }
+ *pbMustBeFreed = 1;
+
+ if(sysinfo(&s_info) < 0) {
+ *pPropLen = sizeof("**SYSCALL FAILED**") - 1;
+ return(UCHAR_CONSTANT("**SYSCALL FAILED**"));
+ }
+
+ snprintf((char*) pRes, sizeof(uchar) * 32, "%ld", s_info.uptime);
+ }
+# endif
+ break;
+ default:
+ /* there is no point in continuing, we may even otherwise render the
+ * error message unreadable. rgerhards, 2007-07-10
+ */
+ dbgprintf("invalid property id: '%d'\n", propid);
+ *pbMustBeFreed = 0;
+ *pPropLen = sizeof("**INVALID PROPERTY NAME**") - 1;
+ return UCHAR_CONSTANT("**INVALID PROPERTY NAME**");
+ }
+
+ /* If we did not receive a template pointer, we are already done... */
+ if(pTpe == NULL || !pTpe->bComplexProcessing) {
+ *pPropLen = (bufLen == -1) ? ustrlen(pRes) : bufLen;
+ return pRes;
+ }
+
+ /* Now check if we need to make "temporary" transformations (these
+ * are transformations that do not go back into the message -
+ * memory must be allocated for them!).
+ */
+
+ /* substring extraction */
+ /* first we check if we need to extract by field number
+ * rgerhards, 2005-12-22
+ */
+ if(pTpe->data.field.has_fields == 1) {
+ size_t iCurrFld;
+ uchar *pFld;
+ uchar *pFldEnd;
+ /* first, skip to the field in question. The field separator
+ * is always one character and is stored in the template entry.
+ */
+ iCurrFld = 1;
+ pFld = pRes;
+ while(*pFld && iCurrFld < pTpe->data.field.iFieldNr) {
+ /* skip fields until the requested field or end of string is found */
+ while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim)
+ ++pFld; /* skip to field terminator */
+ if(*pFld == pTpe->data.field.field_delim) {
+ ++pFld; /* eat it */
+#ifdef STRICT_GPLV3
+ if (pTpe->data.field.field_expand != 0) {
+ while (*pFld == pTpe->data.field.field_delim) {
+ ++pFld;
+ }
+ }
+#endif
+ ++iCurrFld;
+ }
+ }
+ dbgprintf("field requested %d, field found %d\n", pTpe->data.field.iFieldNr, (int) iCurrFld);
+
+ if(iCurrFld == pTpe->data.field.iFieldNr) {
+ /* field found, now extract it */
+ /* first of all, we need to find the end */
+ pFldEnd = pFld;
+ while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim)
+ ++pFldEnd;
+ --pFldEnd; /* we are already at the delimiter - so we need to
+ * step back a little not to copy it as part of the field. */
+ /* we got our end pointer, now do the copy */
+ /* TODO: code copied from below, this is a candidate for a separate function */
+ iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */
+ pBufStart = pBuf = MALLOC((iLen + 1) * sizeof(char));
+ if(pBuf == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ /* now copy */
+ memcpy(pBuf, pFld, iLen);
+ bufLen = iLen;
+ pBuf[iLen] = '\0'; /* terminate it */
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pBufStart;
+ *pbMustBeFreed = 1;
+ if(*(pFldEnd+1) != '\0')
+ ++pFldEnd; /* OK, skip again over delimiter char */
+ } else {
+ /* field not found, return error */
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ *pbMustBeFreed = 0;
+ *pPropLen = sizeof("**FIELD NOT FOUND**") - 1;
+ return UCHAR_CONSTANT("**FIELD NOT FOUND**");
+ }
+#ifdef FEATURE_REGEXP
+ } else {
+ /* Check for regular expressions */
+ if (pTpe->data.field.has_regex != 0) {
+ if (pTpe->data.field.has_regex == 2) {
+ /* Could not compile regex before! */
+ if (*pbMustBeFreed == 1) {
+ free(pRes);
+ *pbMustBeFreed = 0;
+ }
+ *pPropLen = sizeof("**NO MATCH** **BAD REGULAR EXPRESSION**") - 1;
+ return UCHAR_CONSTANT("**NO MATCH** **BAD REGULAR EXPRESSION**");
+ }
+
+ dbgprintf("string to match for regex is: %s\n", pRes);
+
+ if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) {
+ short iTry = 0;
+ uchar bFound = 0;
+ iOffs = 0;
+ /* first see if we find a match, iterating through the series of
+ * potential matches over the string.
+ */
+ while(!bFound) {
+ int iREstat;
+ iREstat = regexp.regexec(&pTpe->data.field.re, (char*)(pRes + iOffs), nmatch, pmatch, 0);
+ dbgprintf("regexec return is %d\n", iREstat);
+ if(iREstat == 0) {
+ if(pmatch[0].rm_so == -1) {
+ dbgprintf("oops ... start offset of successful regexec is -1\n");
+ break;
+ }
+ if(iTry == pTpe->data.field.iMatchToUse) {
+ bFound = 1;
+ } else {
+ dbgprintf("regex found at offset %d, new offset %d, tries %d\n",
+ iOffs, (int) (iOffs + pmatch[0].rm_eo), iTry);
+ iOffs += pmatch[0].rm_eo;
+ ++iTry;
+ }
+ } else {
+ break;
+ }
+ }
+ dbgprintf("regex: end search, found %d\n", bFound);
+ if(!bFound) {
+ /* we got no match! */
+ if(pTpe->data.field.nomatchAction != TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) {
+ if (*pbMustBeFreed == 1) {
+ free(pRes);
+ *pbMustBeFreed = 0;
+ }
+ if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_DFLTSTR) {
+ bufLen = sizeof("**NO MATCH**") - 1;
+ pRes = UCHAR_CONSTANT("**NO MATCH**");
+ } else if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_ZERO) {
+ bufLen = 1;
+ pRes = UCHAR_CONSTANT("0");
+ } else {
+ bufLen = 0;
+ pRes = UCHAR_CONSTANT("");
+ }
+ }
+ } else {
+ /* Match- but did it match the one we wanted? */
+ /* we got no match! */
+ if(pmatch[pTpe->data.field.iSubMatchToUse].rm_so == -1) {
+ if(pTpe->data.field.nomatchAction != TPL_REGEX_NOMATCH_USE_WHOLE_FIELD) {
+ if (*pbMustBeFreed == 1) {
+ free(pRes);
+ *pbMustBeFreed = 0;
+ }
+ if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_DFLTSTR) {
+ bufLen = sizeof("**NO MATCH**") - 1;
+ pRes = UCHAR_CONSTANT("**NO MATCH**");
+ } else if(pTpe->data.field.nomatchAction == TPL_REGEX_NOMATCH_USE_ZERO) {
+ bufLen = 1;
+ pRes = UCHAR_CONSTANT("0");
+ } else {
+ bufLen = 0;
+ pRes = UCHAR_CONSTANT("");
+ }
+ }
+ }
+ /* OK, we have a usable match - we now need to malloc pB */
+ int iLenBuf;
+ uchar *pB;
+
+ iLenBuf = pmatch[pTpe->data.field.iSubMatchToUse].rm_eo
+ - pmatch[pTpe->data.field.iSubMatchToUse].rm_so;
+ pB = MALLOC((iLenBuf + 1) * sizeof(uchar));
+
+ if (pB == NULL) {
+ if (*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+
+ /* Lets copy the matched substring to the buffer */
+ memcpy(pB, pRes + iOffs + pmatch[pTpe->data.field.iSubMatchToUse].rm_so, iLenBuf);
+ bufLen = iLenBuf;
+ pB[iLenBuf] = '\0';/* terminate string, did not happen before */
+
+ if (*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pB;
+ *pbMustBeFreed = 1;
+ }
+ } else {
+ /* we could not load regular expression support. This is quite unexpected at
+ * this stage of processing (after all, the config parser found it), but so
+ * it is. We return an error in that case. -- rgerhards, 2008-03-07
+ */
+ dbgprintf("could not get regexp object pointer, so regexp can not be evaluated\n");
+ if (*pbMustBeFreed == 1) {
+ free(pRes);
+ *pbMustBeFreed = 0;
+ }
+ *pPropLen = sizeof("***REGEXP NOT AVAILABLE***") - 1;
+ return UCHAR_CONSTANT("***REGEXP NOT AVAILABLE***");
+ }
+ }
+#endif /* #ifdef FEATURE_REGEXP */
+ }
+
+ if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) {
+ /* we need to obtain a private copy */
+ int iFrom, iTo;
+ uchar *pSb;
+ iFrom = pTpe->data.field.iFromPos;
+ iTo = pTpe->data.field.iToPos;
+ if(bufLen == -1)
+ bufLen = ustrlen(pRes);
+ if(pTpe->data.field.options.bFromPosEndRelative) {
+ iFrom = (bufLen < iFrom) ? 0 : bufLen - iFrom;
+ iTo = (bufLen < iTo)? 0 : bufLen - iTo;
+ } else {
+ /* need to zero-base to and from (they are 1-based!) */
+ if(iFrom > 0)
+ --iFrom;
+ if(iTo > 0)
+ --iTo;
+ }
+ if(iFrom == 0 && iTo >= bufLen) {
+ /* in this case, the requested string is a superset of what we already have,
+ * so there is no need to do any processing. This is a frequent case for size-limited
+ * fields like TAG in the default forwarding template (so it is a useful optimization
+ * to check for this condition ;)). -- rgerhards, 2009-07-09
+ */
+ ; /*DO NOTHING*/
+ } else {
+ if(iTo > bufLen) /* iTo is very large, if no to-position is set in the template! */
+ iTo = bufLen;
+ iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */
+ pBufStart = pBuf = MALLOC((iLen + 1) * sizeof(char));
+ if(pBuf == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ pSb = pRes;
+ if(iFrom) {
+ /* skip to the start of the substring (can't do pointer arithmetic
+ * because the whole string might be smaller!!)
+ */
+ while(*pSb && iFrom) {
+ --iFrom;
+ ++pSb;
+ }
+ }
+ /* OK, we are at the begin - now let's copy... */
+ bufLen = iLen;
+ while(*pSb && iLen) {
+ *pBuf++ = *pSb;
+ ++pSb;
+ --iLen;
+ }
+ *pBuf = '\0';
+ bufLen -= iLen; /* subtract remaining length if the string was smaller! */
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pBufStart;
+ *pbMustBeFreed = 1;
+ }
+ }
+
+ /* now check if we need to do our "SP if first char is non-space" hack logic */
+ if(*pRes && pTpe->data.field.options.bSPIffNo1stSP) {
+ /* here, we always destruct the buffer and return a new one */
+ uchar cFirst = *pRes; /* save first char */
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = (cFirst == ' ') ? UCHAR_CONSTANT("") : UCHAR_CONSTANT(" ");
+ bufLen = (cFirst == ' ') ? 0 : 1;
+ *pbMustBeFreed = 0;
+ }
+
+ if(*pRes) {
+ /* case conversations (should go after substring, because so we are able to
+ * work on the smallest possible buffer).
+ */
+ if(pTpe->data.field.eCaseConv != tplCaseConvNo) {
+ /* we need to obtain a private copy */
+ if(bufLen == -1)
+ bufLen = ustrlen(pRes);
+ uchar *pBStart;
+ uchar *pB;
+ uchar *pSrc;
+ pBStart = pB = MALLOC((bufLen + 1) * sizeof(char));
+ if(pB == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ pSrc = pRes;
+ while(*pSrc) {
+ *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ?
+ (uchar)toupper((int)*pSrc) : (uchar)tolower((int)*pSrc);
+ /* currently only these two exist */
+ ++pSrc;
+ }
+ *pB = '\0';
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pBStart;
+ *pbMustBeFreed = 1;
+ }
+
+ /* now do control character dropping/escaping/replacement
+ * Only one of these can be used. If multiple options are given, the
+ * result is random (though currently there obviously is an order of
+ * preferrence, see code below. But this is NOT guaranteed.
+ * RGerhards, 2006-11-17
+ * We must copy the strings if we modify them, because they may either
+ * point to static memory or may point into the message object, in which
+ * case we would actually modify the original property (which of course
+ * is wrong).
+ * This was found and fixed by varmojefkoj on 2007-09-11
+ */
+ if(pTpe->data.field.options.bDropCC) {
+ int iLenBuf = 0;
+ uchar *pSrc = pRes;
+ uchar *pDstStart;
+ uchar *pDst;
+ uchar bDropped = 0;
+
+ while(*pSrc) {
+ if(!iscntrl((int) *pSrc++))
+ iLenBuf++;
+ else
+ bDropped = 1;
+ }
+
+ if(bDropped) {
+ pDst = pDstStart = MALLOC(iLenBuf + 1);
+ if(pDst == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ for(pSrc = pRes; *pSrc; pSrc++) {
+ if(!iscntrl((int) *pSrc))
+ *pDst++ = *pSrc;
+ }
+ *pDst = '\0';
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pDstStart;
+ bufLen = iLenBuf;
+ *pbMustBeFreed = 1;
+ }
+ } else if(pTpe->data.field.options.bSpaceCC) {
+ uchar *pSrc;
+ uchar *pDstStart;
+ uchar *pDst;
+
+ if(*pbMustBeFreed == 1) {
+ /* in this case, we already work on dynamic
+ * memory, so there is no need to copy it - we can
+ * modify it in-place without any harm. This is a
+ * performance optiomization.
+ */
+ for(pDst = pRes; *pDst; pDst++) {
+ if(iscntrl((int) *pDst))
+ *pDst = ' ';
+ }
+ } else {
+ if(bufLen == -1)
+ bufLen = ustrlen(pRes);
+ pDst = pDstStart = MALLOC(bufLen + 1);
+ if(pDst == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ for(pSrc = pRes; *pSrc; pSrc++) {
+ if(iscntrl((int) *pSrc))
+ *pDst++ = ' ';
+ else
+ *pDst++ = *pSrc;
+ }
+ *pDst = '\0';
+ pRes = pDstStart;
+ *pbMustBeFreed = 1;
+ }
+ } else if(pTpe->data.field.options.bEscapeCC) {
+ /* we must first count how many control charactes are
+ * present, because we need this to compute the new string
+ * buffer length. While doing so, we also compute the string
+ * length.
+ */
+ int iNumCC = 0;
+ int iLenBuf = 0;
+ uchar *pB;
+
+ for(pB = pRes ; *pB ; ++pB) {
+ ++iLenBuf;
+ if(iscntrl((int) *pB))
+ ++iNumCC;
+ }
+
+ if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */
+ /* OK, let's do the escaping... */
+ uchar *pBStart;
+ uchar szCCEsc[8]; /* buffer for escape sequence */
+ int i;
+
+ iLenBuf += iNumCC * 4;
+ pBStart = pB = MALLOC((iLenBuf + 1) * sizeof(uchar));
+ if(pB == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ while(*pRes) {
+ if(iscntrl((int) *pRes)) {
+ snprintf((char*)szCCEsc, sizeof(szCCEsc), "#%3.3d", *pRes);
+ for(i = 0 ; i < 4 ; ++i)
+ *pB++ = szCCEsc[i];
+ } else {
+ *pB++ = *pRes;
+ }
+ ++pRes;
+ }
+ *pB = '\0';
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pBStart;
+ bufLen = -1;
+ *pbMustBeFreed = 1;
+ }
+ }
+ }
+
+ /* Take care of spurious characters to make the property safe
+ * for a path definition
+ */
+ if(pTpe->data.field.options.bSecPathDrop || pTpe->data.field.options.bSecPathReplace) {
+ if(pTpe->data.field.options.bSecPathDrop) {
+ int iLenBuf = 0;
+ uchar *pSrc = pRes;
+ uchar *pDstStart;
+ uchar *pDst;
+ uchar bDropped = 0;
+
+ while(*pSrc) {
+ if(*pSrc++ != '/')
+ iLenBuf++;
+ else
+ bDropped = 1;
+ }
+
+ if(bDropped) {
+ pDst = pDstStart = MALLOC(iLenBuf + 1);
+ if(pDst == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ for(pSrc = pRes; *pSrc; pSrc++) {
+ if(*pSrc != '/')
+ *pDst++ = *pSrc;
+ }
+ *pDst = '\0';
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pDstStart;
+ bufLen = -1; /* TODO: can we do better? */
+ *pbMustBeFreed = 1;
+ }
+ } else {
+ uchar *pSrc;
+ uchar *pDstStart;
+ uchar *pDst;
+
+ if(*pbMustBeFreed == 1) {
+ /* here, again, we can modify the string as we already obtained
+ * a private buffer. As we do not change the size of that buffer,
+ * in-place modification is possible. This is a performance
+ * enhancement.
+ */
+ for(pDst = pRes; *pDst; pDst++) {
+ if(*pDst == '/')
+ *pDst++ = '_';
+ }
+ } else {
+ if(bufLen == -1)
+ bufLen = ustrlen(pRes);
+ pDst = pDstStart = MALLOC(bufLen + 1);
+ if(pDst == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ for(pSrc = pRes; *pSrc; pSrc++) {
+ if(*pSrc == '/')
+ *pDst++ = '_';
+ else
+ *pDst++ = *pSrc;
+ }
+ *pDst = '\0';
+ /* we must NOT check if it needs to be freed, because we have done
+ * this in the if above. So if we come to hear, the pSrc string needs
+ * not to be freed (and we do not need to care about it).
+ */
+ pRes = pDstStart;
+ *pbMustBeFreed = 1;
+ }
+ }
+
+ /* check for "." and ".." (note the parenthesis in the if condition!) */
+ if(*pRes == '\0') {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = UCHAR_CONSTANT("_");
+ bufLen = 1;
+ *pbMustBeFreed = 0;
+ } else if((*pRes == '.') && (*(pRes + 1) == '\0' || (*(pRes + 1) == '.' && *(pRes + 2) == '\0'))) {
+ uchar *pTmp = pRes;
+
+ if(*(pRes + 1) == '\0')
+ pRes = UCHAR_CONSTANT("_");
+ else
+ pRes = UCHAR_CONSTANT("_.");;
+ if(*pbMustBeFreed == 1)
+ free(pTmp);
+ *pbMustBeFreed = 0;
+ }
+ }
+
+ /* Now drop last LF if present (pls note that this must not be done
+ * if bEscapeCC was set)!
+ */
+ if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) {
+ int iLn;
+ uchar *pB;
+ if(bufLen == -1)
+ bufLen = ustrlen(pRes);
+ iLn = bufLen;
+ if(iLn > 0 && *(pRes + iLn - 1) == '\n') {
+ /* we have a LF! */
+ /* check if we need to obtain a private copy */
+ if(*pbMustBeFreed == 0) {
+ /* ok, original copy, need a private one */
+ pB = MALLOC((iLn + 1) * sizeof(uchar));
+ if(pB == NULL) {
+ RET_OUT_OF_MEMORY;
+ }
+ memcpy(pB, pRes, iLn - 1);
+ pRes = pB;
+ *pbMustBeFreed = 1;
+ }
+ *(pRes + iLn - 1) = '\0'; /* drop LF ;) */
+ --bufLen;
+ }
+ }
+
+ /* finally, we need to check if the property should be formatted in CSV or JSON.
+ * For CSV we use RFC 4180, and always use double quotes. As of this writing,
+ * this should be the last action carried out on the property, but in the
+ * future there may be reasons to change that. -- rgerhards, 2009-04-02
+ */
+ if(pTpe->data.field.options.bCSV) {
+ /* we need to obtain a private copy, as we need to at least add the double quotes */
+ int iBufLen;
+ uchar *pBStart;
+ uchar *pDst;
+ uchar *pSrc;
+ if(bufLen == -1)
+ bufLen = ustrlen(pRes);
+ iBufLen = bufLen;
+ /* the malloc may be optimized, we currently use the worst case... */
+ pBStart = pDst = MALLOC((2 * iBufLen + 3) * sizeof(uchar));
+ if(pDst == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ pSrc = pRes;
+ *pDst++ = '"'; /* starting quote */
+ while(*pSrc) {
+ if(*pSrc == '"')
+ *pDst++ = '"'; /* need to add double double quote (see RFC4180) */
+ *pDst++ = *pSrc++;
+ }
+ *pDst++ = '"'; /* ending quote */
+ *pDst = '\0';
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = pBStart;
+ bufLen = -1;
+ *pbMustBeFreed = 1;
+ } else if(pTpe->data.field.options.bJSON) {
+ jsonEncode(&pRes, pbMustBeFreed, &bufLen);
+ } else if(pTpe->data.field.options.bJSONf) {
+ jsonField(pTpe, &pRes, pbMustBeFreed, &bufLen);
+ }
+
+ *pPropLen = (bufLen == -1) ? ustrlen(pRes) : bufLen;
+
+ ENDfunc
+ return(pRes);
+}
+
+
+/* The function returns a cee variable suitable for use with RainerScript.
+ * Note: caller must free the returned string.
+ * Note that we need to do a lot of conversions between es_str_t and cstr -- this will go away once
+ * we have moved larger parts of rsyslog to es_str_t. Acceptable for the moment, especially as we intend
+ * to rewrite the script engine as well!
+ * rgerhards, 2010-12-03
+ */
+es_str_t*
+msgGetCEEVarNew(msg_t *pMsg, char *name)
+{
+ uchar *leaf;
+ char *val;
+ es_str_t *estr = NULL;
+ struct json_object *json, *parent;
+
+ ISOBJ_TYPE_assert(pMsg, msg);
+
+ if(pMsg->json == NULL) {
+ estr = es_newStr(1);
+ goto done;
+ }
+ leaf = jsonPathGetLeaf((uchar*)name, strlen(name));
+ if(jsonPathFindParent(pMsg, (uchar*)name, leaf, &parent, 1) != RS_RET_OK) {
+ estr = es_newStr(1);
+ goto done;
+ }
+ json = json_object_object_get(parent, (char*)leaf);
+ val = (char*)json_object_get_string(json);
+ estr = es_newStrFromCStr(val, strlen(val));
+done:
+ return estr;
+}
+
+
+/* Return an es_str_t for given message property.
+ */
+es_str_t*
+msgGetMsgVarNew(msg_t *pThis, uchar *name)
+{
+ rs_size_t propLen;
+ uchar *pszProp = NULL;
+ propid_t propid;
+ unsigned short bMustBeFreed = 0;
+ es_str_t *estr;
+
+ ISOBJ_TYPE_assert(pThis, msg);
+
+ /* always call MsgGetProp() without a template specifier */
+ /* TODO: optimize propNameToID() call -- rgerhards, 2009-06-26 */
+ propNameStrToID(name, &propid);
+ pszProp = (uchar*) MsgGetProp(pThis, NULL, propid, NULL, &propLen, &bMustBeFreed, NULL);
+
+ estr = es_newStrFromCStr((char*)pszProp, propLen);
+ if(bMustBeFreed)
+ free(pszProp);
+
+ return estr;
+}
+
+
+/* This function can be used as a generic way to set properties.
+ * We have to handle a lot of legacy, so our return value is not always
+ * 100% correct (called functions do not always provide one, should
+ * change over time).
+ * rgerhards, 2008-01-07
+ */
+#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1)
+rsRetVal MsgSetProperty(msg_t *pThis, var_t *pProp)
+{
+ prop_t *myProp;
+ prop_t *propRcvFrom = NULL;
+ prop_t *propRcvFromIP = NULL;
+ struct json_tokener *tokener;
+ struct json_object *json;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, msg);
+ assert(pProp != NULL);
+
+ if(isProp("iProtocolVersion")) {
+ setProtocolVersion(pThis, pProp->val.num);
+ } else if(isProp("iSeverity")) {
+ pThis->iSeverity = pProp->val.num;
+ } else if(isProp("iFacility")) {
+ pThis->iFacility = pProp->val.num;
+ } else if(isProp("msgFlags")) {
+ pThis->msgFlags = pProp->val.num;
+ } else if(isProp("offMSG")) {
+ MsgSetMSGoffs(pThis, pProp->val.num);
+ } else if(isProp("pszRawMsg")) {
+ MsgSetRawMsg(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr), cstrLen(pProp->val.pStr));
+ } else if(isProp("pszUxTradMsg")) {
+ /*IGNORE*/; /* this *was* a property, but does no longer exist */
+ } else if(isProp("pszTAG")) {
+ MsgSetTAG(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), cstrLen(pProp->val.pStr));
+ } else if(isProp("pszInputName")) {
+ /* we need to create a property */
+ CHKiRet(prop.Construct(&myProp));
+ CHKiRet(prop.SetString(myProp, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr)));
+ CHKiRet(prop.ConstructFinalize(myProp));
+ MsgSetInputName(pThis, myProp);
+ prop.Destruct(&myProp);
+ } else if(isProp("pszRcvFromIP")) {
+ MsgSetRcvFromIPStr(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr), &propRcvFromIP);
+ prop.Destruct(&propRcvFromIP);
+ } else if(isProp("pszRcvFrom")) {
+ MsgSetRcvFromStr(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr), &propRcvFrom);
+ prop.Destruct(&propRcvFrom);
+ } else if(isProp("pszHOSTNAME")) {
+ MsgSetHOSTNAME(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr));
+ } else if(isProp("pCSStrucData")) {
+ MsgSetStructuredData(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr));
+ } else if(isProp("pCSAPPNAME")) {
+ MsgSetAPPNAME(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr));
+ } else if(isProp("pCSPROCID")) {
+ MsgSetPROCID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr));
+ } else if(isProp("pCSMSGID")) {
+ MsgSetMSGID(pThis, (char*) rsCStrGetSzStrNoNULL(pProp->val.pStr));
+ } else if(isProp("ttGenTime")) {
+ pThis->ttGenTime = pProp->val.num;
+ } else if(isProp("tRcvdAt")) {
+ memcpy(&pThis->tRcvdAt, &pProp->val.vSyslogTime, sizeof(struct syslogTime));
+ } else if(isProp("tTIMESTAMP")) {
+ memcpy(&pThis->tTIMESTAMP, &pProp->val.vSyslogTime, sizeof(struct syslogTime));
+ } else if(isProp("pszRuleset")) {
+ MsgSetRulesetByName(pThis, pProp->val.pStr);
+ } else if(isProp("pszMSG")) {
+ dbgprintf("no longer supported property pszMSG silently ignored\n");
+ } else if(isProp("json")) {
+ tokener = json_tokener_new();
+ json = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pProp->val.pStr),
+ cstrLen(pProp->val.pStr));
+ json_tokener_free(tokener);
+ msgAddJSON(pThis, (uchar*)"!", json);
+ } else {
+ dbgprintf("unknown supported property '%s' silently ignored\n",
+ rsCStrGetSzStrNoNULL(pProp->pcsName));
+ }
+
+finalize_it:
+ RETiRet;
+}
+#undef isProp
+
+
+/* get the severity - this is an entry point that
+ * satisfies the base object class getSeverity semantics.
+ * rgerhards, 2008-01-14
+ */
+rsRetVal
+MsgGetSeverity(msg_t *pMsg, int *piSeverity)
+{
+ *piSeverity = pMsg->iSeverity;
+ return RS_RET_OK;
+}
+
+
+static uchar *
+jsonPathGetLeaf(uchar *name, int lenName)
+{
+ int i;
+ for(i = lenName ; name[i] != '!' && i >= 0 ; --i)
+ /* just skip */;
+ if(name[i] == '!')
+ ++i;
+ return name + i;
+}
+
+
+static rsRetVal
+jsonPathFindNext(struct json_object *root, uchar **name, uchar *leaf,
+ struct json_object **found, int bCreate)
+{
+ uchar namebuf[1024];
+ struct json_object *json;
+ size_t i;
+ uchar *p = *name;
+ DEFiRet;
+
+ if(*p == '!')
+ ++p;
+ for(i = 0 ; *p && *p != '!' && p != leaf && i < sizeof(namebuf)-1 ; ++i, ++p)
+ namebuf[i] = *p;
+ if(i > 0) {
+ namebuf[i] = '\0';
+ dbgprintf("AAAA: next JSONPath elt: '%s'\n", namebuf);
+ json = json_object_object_get(root, (char*)namebuf);
+ } else
+ json = root;
+ if(json == NULL) {
+ if(!bCreate) {
+ ABORT_FINALIZE(RS_RET_JNAME_INVALID);
+ } else {
+ json = json_object_new_object();
+ json_object_object_add(root, (char*)namebuf, json);
+ }
+ }
+
+ *name = p;
+ *found = json;
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+jsonPathFindParent(msg_t *pM, uchar *name, uchar *leaf, struct json_object **parent, int bCreate)
+{
+ DEFiRet;
+ *parent = pM->json;
+ while(name < leaf-1) {
+ jsonPathFindNext(*parent, &name, leaf, parent, bCreate);
+ }
+ RETiRet;
+}
+
+static rsRetVal
+jsonMerge(struct json_object *existing, struct json_object *json)
+{
+ /* TODO: check & handle duplicate names */
+ DEFiRet;
+ struct json_object_iter it;
+
+ json_object_object_foreachC(json, it) {
+DBGPRINTF("AAAA jsonMerge adds '%s'\n", it.key);
+ json_object_object_add(existing, it.key,
+ json_object_get(it.val));
+ }
+ /* note: json-c does ref counting. We added all descandants refcounts
+ * in the loop above. So when we now free(_put) the root object, only
+ * root gets freed().
+ */
+ json_object_put(json);
+ RETiRet;
+}
+
+/* find a JSON structure element (field or container doesn't matter). */
+rsRetVal
+jsonFind(msg_t *pM, es_str_t *propName, struct json_object **jsonres)
+{
+ uchar *name = NULL;
+ uchar *leaf;
+ struct json_object *parent;
+ struct json_object *field;
+ DEFiRet;
+
+ if(pM->json == NULL) {
+ field = NULL;
+ goto finalize_it;
+ }
+
+ if(!es_strbufcmp(propName, (uchar*)"!", 1)) {
+ field = pM->json;
+ } else {
+ name = (uchar*)es_str2cstr(propName, NULL);
+ leaf = jsonPathGetLeaf(name, ustrlen(name));
+ CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 0));
+ field = json_object_object_get(parent, (char*)leaf);
+ }
+ *jsonres = field;
+
+finalize_it:
+ free(name);
+ RETiRet;
+}
+
+rsRetVal
+msgAddJSON(msg_t *pM, uchar *name, struct json_object *json)
+{
+ /* TODO: error checks! This is a quick&dirty PoC! */
+ struct json_object *parent, *leafnode;
+ uchar *leaf;
+ DEFiRet;
+
+ MsgLock(pM);
+ if(name[0] == '!' && name[1] == '\0') {
+ if(pM->json == NULL)
+ pM->json = json;
+ else
+ CHKiRet(jsonMerge(pM->json, json));
+ } else {
+ if(pM->json == NULL) {
+ /* now we need a root obj */
+ pM->json = json_object_new_object();
+ }
+ leaf = jsonPathGetLeaf(name, ustrlen(name));
+ CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1));
+ leafnode = json_object_object_get(parent, (char*)leaf);
+ if(leafnode == NULL) {
+ json_object_object_add(parent, (char*)leaf, json);
+ } else {
+ if(json_object_get_type(json) == json_type_object) {
+ CHKiRet(jsonMerge(pM->json, json));
+ } else {
+//dbgprintf("AAAA: leafnode already exists, type is %d, update with %d\n", (int)json_object_get_type(leafnode), (int)json_object_get_type(json));
+ /* TODO: improve the code below, however, the current
+ * state is not really bad */
+ if(json_object_get_type(leafnode) == json_type_object) {
+ DBGPRINTF("msgAddJSON: trying to update a container "
+ "node with a leaf, name is '%s' - "
+ "forbidden\n", name);
+ json_object_put(json);
+ ABORT_FINALIZE(RS_RET_INVLD_SETOP);
+ }
+ /* json-c code indicates we can simply replace a
+ * json type. Unfortunaltely, this is not documented
+ * as part of the interface spec. We still use it,
+ * because it speeds up processing. If it does not work
+ * at some point, use
+ * json_object_object_del(parent, (char*)leaf);
+ * before adding. rgerhards, 2012-09-17
+ */
+ json_object_object_add(parent, (char*)leaf, json);
+ }
+ }
+ }
+
+finalize_it:
+ MsgUnlock(pM);
+ RETiRet;
+}
+
+rsRetVal
+msgDelJSON(msg_t *pM, uchar *name)
+{
+ struct json_object *parent, *leafnode;
+ uchar *leaf;
+ DEFiRet;
+
+dbgprintf("AAAA: unset variable '%s'\n", name);
+ MsgLock(pM);
+ if(name[0] == '!' && name[1] == '\0') {
+ /* strange, but I think we should permit this. After all,
+ * we trust rsyslog.conf to be written by the admin.
+ */
+ DBGPRINTF("unsetting JSON root object\n");
+ json_object_put(pM->json);
+ pM->json = NULL;
+ } else {
+ if(pM->json == NULL) {
+ /* now we need a root obj */
+ pM->json = json_object_new_object();
+ }
+ leaf = jsonPathGetLeaf(name, ustrlen(name));
+ CHKiRet(jsonPathFindParent(pM, name, leaf, &parent, 1));
+ leafnode = json_object_object_get(parent, (char*)leaf);
+DBGPRINTF("AAAA: unset found JSON value path '%s', " "leaf '%s', leafnode %p\n", name, leaf, leafnode);
+ if(leafnode == NULL) {
+ DBGPRINTF("unset JSON: could not find '%s'\n", name);
+ ABORT_FINALIZE(RS_RET_JNAME_NOTFOUND);
+ } else {
+ DBGPRINTF("deleting JSON value path '%s', "
+ "leaf '%s', type %d\n",
+ name, leaf, json_object_get_type(leafnode));
+ json_object_object_del(parent, (char*)leaf);
+ }
+ }
+
+finalize_it:
+ MsgUnlock(pM);
+ RETiRet;
+}
+
+static struct json_object *
+jsonDeepCopy(struct json_object *src)
+{
+ struct json_object *dst = NULL, *json;
+ struct json_object_iter it;
+ int arrayLen, i;
+
+ if(src == NULL) goto done;
+
+ switch(json_object_get_type(src)) {
+ case json_type_boolean:
+ dst = json_object_new_boolean(json_object_get_boolean(src));
+ break;
+ case json_type_double:
+ dst = json_object_new_double(json_object_get_double(src));
+ break;
+ case json_type_int:
+ dst = json_object_new_int(json_object_get_int(src));
+ break;
+ case json_type_string:
+ dst = json_object_new_string(json_object_get_string(src));
+ break;
+ case json_type_object:
+ dst = json_object_new_object();
+ json_object_object_foreachC(src, it) {
+ json = jsonDeepCopy(it.val);
+ json_object_object_add(dst, it.key, json);
+ }
+ break;
+ case json_type_array:
+ arrayLen = json_object_array_length(src);
+ dst = json_object_new_array();
+ for(i = 0 ; i < arrayLen ; ++i) {
+ json = json_object_array_get_idx(src, i);
+ json = jsonDeepCopy(json);
+ json_object_array_add(dst, json);
+ }
+ break;
+ default:DBGPRINTF("jsonDeepCopy(): error unknown type %d\n",
+ json_object_get_type(src));
+ dst = NULL;
+ break;
+ }
+done: return dst;
+}
+
+
+rsRetVal
+msgSetJSONFromVar(msg_t *pMsg, uchar *varname, struct var *v)
+{
+ struct json_object *json = NULL;
+ char *cstr;
+ DEFiRet;
+ switch(v->datatype) {
+ case 'S':/* string */
+ cstr = es_str2cstr(v->d.estr, NULL);
+ json = json_object_new_string(cstr);
+ free(cstr);
+ break;
+ case 'N':/* number (integer) */
+ json = json_object_new_int((int) v->d.n);
+ break;
+ case 'J':/* native JSON */
+ json = jsonDeepCopy(v->d.json);
+ break;
+ default:DBGPRINTF("msgSetJSONFromVar: unsupported datatype %c\n",
+ v->datatype);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ msgAddJSON(pMsg, varname+1, json);
+finalize_it:
+ RETiRet;
+}
+
+/* dummy */
+rsRetVal msgQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; }
+
+/* Initialize the message class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-01-04
+ */
+BEGINObjClassInit(msg, 1, OBJ_IS_CORE_MODULE)
+ /* request objects we use */
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+ CHKiRet(objUse(var, CORE_COMPONENT));
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_SERIALIZE, MsgSerialize);
+ /* some more inits */
+# if HAVE_MALLOC_TRIM
+ INIT_ATOMIC_HELPER_MUT(mutTrimCtr);
+# endif
+ENDObjClassInit(msg)
+/* vim:set ai:
+ */
diff --git a/runtime/msg.h b/runtime/msg.h
new file mode 100644
index 00000000..6faf066a
--- /dev/null
+++ b/runtime/msg.h
@@ -0,0 +1,247 @@
+/* msg.h
+ * Header file for all msg-related functions.
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "template.h" /* this is a quirk, but these two are too interdependant... */
+
+#ifndef MSG_H_INCLUDED
+#define MSG_H_INCLUDED 1
+
+#include <pthread.h>
+#include <libestr.h>
+#include <json/json.h>
+#include "obj.h"
+#include "syslogd-types.h"
+#include "template.h"
+#include "atomic.h"
+
+/* rgerhards 2004-11-08: The following structure represents a
+ * syslog message.
+ *
+ * Important Note:
+ * The message object is used for multiple purposes (once it
+ * has been created). Once created, it actully is a read-only
+ * object (though we do not specifically express this). In order
+ * to avoid multiple copies of the same object, we use a
+ * reference counter. This counter is set to 1 by the constructer
+ * and increased by 1 with a call to MsgAddRef(). The destructor
+ * checks the reference count. If it is more than 1, only the counter
+ * will be decremented. If it is 1, however, the object is actually
+ * destroyed. To make this work, it is vital that MsgAddRef() is
+ * called each time a "copy" is stored somewhere.
+ *
+ * WARNING: this structure is not calloc()ed, so be careful when
+ * adding new fields. You need to initialize them in
+ * msgBaseConstruct(). That function header comment also describes
+ * why this is the case.
+ */
+struct msg {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ flowControl_t flowCtlType; /**< type of flow control we can apply, for enqueueing, needs not to be persisted because
+ once data has entered the queue, this property is no longer needed. */
+ pthread_mutex_t mut;
+ int iRefCount; /* reference counter (0 = unused) */
+ sbool bAlreadyFreed; /* aid to help detect a well-hidden bad bug -- TODO: remove when no longer needed */
+ sbool bParseSuccess; /* set to reflect state of last executed higher level parser */
+ short iSeverity; /* the severity 0..7 */
+ short iFacility; /* Facility code 0 .. 23*/
+ short offAfterPRI; /* offset, at which raw message WITHOUT PRI part starts in pszRawMsg */
+ short offMSG; /* offset at which the MSG part starts in pszRawMsg */
+ short iProtocolVersion;/* protocol version of message received 0 - legacy, 1 syslog-protocol) */
+ int msgFlags; /* flags associated with this message */
+ int iLenRawMsg; /* length of raw message */
+ int iLenMSG; /* Length of the MSG part */
+ int iLenTAG; /* Length of the TAG part */
+ int iLenHOSTNAME; /* Length of HOSTNAME */
+ int iLenPROGNAME; /* Length of PROGNAME (-1 = not yet set) */
+ uchar *pszRawMsg; /* message as it was received on the wire. This is important in case we
+ * need to preserve cryptographic verifiers. */
+ uchar *pszHOSTNAME; /* HOSTNAME from syslog message */
+ char *pszRcvdAt3164; /* time as RFC3164 formatted string (always 15 charcters) */
+ char *pszRcvdAt3339; /* time as RFC3164 formatted string (32 charcters at most) */
+ char *pszRcvdAt_MySQL; /* rcvdAt as MySQL formatted string (always 14 charcters) */
+ char *pszRcvdAt_PgSQL; /* rcvdAt as PgSQL formatted string (always 21 characters) */
+ char *pszTIMESTAMP3164; /* TIMESTAMP as RFC3164 formatted string (always 15 charcters) */
+ char *pszTIMESTAMP3339; /* TIMESTAMP as RFC3339 formatted string (32 charcters at most) */
+ char *pszTIMESTAMP_MySQL;/* TIMESTAMP as MySQL formatted string (always 14 charcters) */
+ char *pszTIMESTAMP_PgSQL;/* TIMESTAMP as PgSQL formatted string (always 21 characters) */
+ cstr_t *pCSStrucData; /* STRUCTURED-DATA */
+ cstr_t *pCSAPPNAME; /* APP-NAME */
+ cstr_t *pCSPROCID; /* PROCID */
+ cstr_t *pCSMSGID; /* MSGID */
+ prop_t *pInputName; /* input name property */
+ prop_t *pRcvFromIP; /* IP of system message was received from */
+ union {
+ prop_t *pRcvFrom;/* name of system message was received from */
+ struct sockaddr_storage *pfrominet; /* unresolved name */
+ } rcvFrom;
+
+ ruleset_t *pRuleset; /* ruleset to be used for processing this message */
+ time_t ttGenTime; /* time msg object was generated, same as tRcvdAt, but a Unix timestamp.
+ While this field looks redundant, it is required because a Unix timestamp
+ is used at later processing stages (namely in the output arena). Thanks to
+ the subleties of how time is defined, there is no reliable way to reconstruct
+ the Unix timestamp from the syslogTime fields (in practice, we may be close
+ enough to reliable, but I prefer to leave the subtle things to the OS, where
+ it obviously is solved in way or another...). */
+ struct syslogTime tRcvdAt;/* time the message entered this program */
+ struct syslogTime tTIMESTAMP;/* (parsed) value of the timestamp */
+ struct json_object *json;
+ /* some fixed-size buffers to save malloc()/free() for frequently used fields (from the default templates) */
+ uchar szRawMsg[CONF_RAWMSG_BUFSIZE]; /* most messages are small, and these are stored here (without malloc/free!) */
+ uchar szHOSTNAME[CONF_HOSTNAME_BUFSIZE];
+ union {
+ uchar *ptr; /* pointer to progname value */
+ uchar szBuf[CONF_PROGNAME_BUFSIZE];
+ } PROGNAME;
+ union {
+ uchar *pszTAG; /* pointer to tag value */
+ uchar szBuf[CONF_TAG_BUFSIZE];
+ } TAG;
+ char pszTimestamp3164[CONST_LEN_TIMESTAMP_3164 + 1];
+ char pszTimestamp3339[CONST_LEN_TIMESTAMP_3339 + 1];
+ char pszTIMESTAMP_SecFrac[7]; /* Note: a pointer is 64 bits/8 char, so this is actually fewer than a pointer! */
+ char pszRcvdAt_SecFrac[7]; /* same as above. Both are fractional seconds for their respective timestamp */
+ char pszTIMESTAMP_Unix[12]; /* almost as small as a pointer! */
+ char pszRcvdAt_Unix[12];
+ uchar *pszUUID; /* The message's UUID */
+};
+
+
+/* message flags (msgFlags), not an enum for historical reasons
+ */
+#define NOFLAG 0x000 /* no flag is set (to be used when a flag must be specified and none is required) */
+#define INTERNAL_MSG 0x001 /* msg generated by logmsgInternal() --> special handling */
+/* 0x002 not used because it was previously a known value - rgerhards, 2008-10-09 */
+#define IGNDATE 0x004 /* ignore, if given, date in message and use date of reception as msg date */
+#define MARK 0x008 /* this message is a mark */
+#define NEEDS_PARSING 0x010 /* raw message, must be parsed before processing can be done */
+#define PARSE_HOSTNAME 0x020 /* parse the hostname during message parsing */
+#define NEEDS_DNSRESOL 0x040 /* fromhost address is unresolved and must be locked up via DNS reverse lookup first */
+#define NEEDS_ACLCHK_U 0x080 /* check UDP ACLs after DNS resolution has been done in main queue consumer */
+#define NO_PRI_IN_RAW 0x100 /* rawmsg does not include a PRI (Solaris!), but PRI is already set correctly in the msg object */
+
+
+/* function prototypes
+ */
+PROTOTYPEObjClassInit(msg);
+rsRetVal msgConstruct(msg_t **ppThis);
+rsRetVal msgConstructWithTime(msg_t **ppThis, struct syslogTime *stTime, time_t ttGenTime);
+rsRetVal msgConstructForDeserializer(msg_t **ppThis);
+rsRetVal msgConstructFinalizer(msg_t *pThis);
+rsRetVal msgDestruct(msg_t **ppM);
+msg_t* MsgDup(msg_t* pOld);
+msg_t *MsgAddRef(msg_t *pM);
+void setProtocolVersion(msg_t *pM, int iNewVersion);
+void MsgSetInputName(msg_t *pMsg, prop_t*);
+rsRetVal MsgSetAPPNAME(msg_t *pMsg, char* pszAPPNAME);
+rsRetVal MsgSetPROCID(msg_t *pMsg, char* pszPROCID);
+rsRetVal MsgSetMSGID(msg_t *pMsg, char* pszMSGID);
+void MsgSetParseSuccess(msg_t *pMsg, int bSuccess);
+void MsgSetTAG(msg_t *pMsg, uchar* pszBuf, size_t lenBuf);
+void MsgSetRuleset(msg_t *pMsg, ruleset_t*);
+rsRetVal MsgSetFlowControlType(msg_t *pMsg, flowControl_t eFlowCtl);
+rsRetVal MsgSetStructuredData(msg_t *pMsg, char* pszStrucData);
+rsRetVal msgSetFromSockinfo(msg_t *pThis, struct sockaddr_storage *sa);
+void MsgSetRcvFrom(msg_t *pMsg, prop_t*);
+void MsgSetRcvFromStr(msg_t *pMsg, uchar* pszRcvFrom, int, prop_t **);
+rsRetVal MsgSetRcvFromIP(msg_t *pMsg, prop_t*);
+rsRetVal MsgSetRcvFromIPStr(msg_t *pThis, uchar *psz, int len, prop_t **ppProp);
+void MsgSetHOSTNAME(msg_t *pMsg, uchar* pszHOSTNAME, int lenHOSTNAME);
+rsRetVal MsgSetAfterPRIOffs(msg_t *pMsg, short offs);
+void MsgSetMSGoffs(msg_t *pMsg, short offs);
+void MsgSetRawMsgWOSize(msg_t *pMsg, char* pszRawMsg);
+void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg, size_t lenMsg);
+rsRetVal MsgReplaceMSG(msg_t *pThis, uchar* pszMSG, int lenMSG);
+uchar *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe,
+ propid_t propid, es_str_t *propName,
+ rs_size_t *pPropLen, unsigned short *pbMustBeFreed, struct syslogTime *ttNow);
+rsRetVal msgGetMsgVar(msg_t *pThis, cstr_t *pstrPropName, var_t **ppVar);
+es_str_t* msgGetMsgVarNew(msg_t *pThis, uchar *name);
+uchar *getRcvFrom(msg_t *pM);
+void getTAG(msg_t *pM, uchar **ppBuf, int *piLen);
+char *getTimeReported(msg_t *pM, enum tplFormatTypes eFmt);
+char *getPRI(msg_t *pMsg);
+void getRawMsg(msg_t *pM, uchar **pBuf, int *piLen);
+rsRetVal msgGetCEEVar(msg_t *pThis, cstr_t *propName, var_t **ppVar);
+es_str_t* msgGetCEEVarNew(msg_t *pMsg, char *name);
+rsRetVal msgAddJSON(msg_t *pM, uchar *name, struct json_object *json);
+rsRetVal getCEEPropVal(msg_t *pM, es_str_t *propName, uchar **pRes, rs_size_t *buflen, unsigned short *pbMustBeFreed);
+rsRetVal MsgGetSeverity(msg_t *pThis, int *piSeverity);
+rsRetVal MsgDeserialize(msg_t *pMsg, strm_t *pStrm);
+
+/* TODO: remove these five (so far used in action.c) */
+uchar *getMSG(msg_t *pM);
+char *getHOSTNAME(msg_t *pM);
+char *getPROCID(msg_t *pM, sbool bLockMutex);
+char *getAPPNAME(msg_t *pM, sbool bLockMutex);
+void setMSGLen(msg_t *pM, int lenMsg);
+int getMSGLen(msg_t *pM);
+
+char *getHOSTNAME(msg_t *pM);
+int getHOSTNAMELen(msg_t *pM);
+uchar *getProgramName(msg_t *pM, sbool bLockMutex);
+uchar *getRcvFrom(msg_t *pM);
+rsRetVal propNameToID(cstr_t *pCSPropName, propid_t *pPropID);
+uchar *propIDToName(propid_t propID);
+rsRetVal msgGetCEEPropJSON(msg_t *pM, es_str_t *propName, struct json_object **pjson);
+rsRetVal msgSetJSONFromVar(msg_t *pMsg, uchar *varname, struct var *var);
+rsRetVal msgDelJSON(msg_t *pMsg, uchar *varname);
+rsRetVal jsonFind(msg_t *pM, es_str_t *propName, struct json_object **jsonres);
+
+static inline rsRetVal
+msgUnsetJSON(msg_t *pMsg, uchar *varname) {
+ return msgDelJSON(pMsg, varname+1);
+}
+
+
+/* ------------------------------ some inline functions ------------------------------ */
+
+/* set raw message size. This is needed in some cases where a trunctation is necessary
+ * but the raw message must not be newly set. The most important (and currently only)
+ * use case is if we remove trailing LF or NUL characters. Note that the size can NOT
+ * be extended, only shrunk!
+ * rgerhards, 2009-08-26
+ */
+static inline void
+MsgSetRawMsgSize(msg_t *pMsg, size_t newLen)
+{
+ assert(newLen <= (size_t) pMsg->iLenRawMsg);
+ pMsg->iLenRawMsg = newLen;
+}
+
+
+/* get the ruleset that is associated with the ruleset.
+ * May be NULL. -- rgerhards, 2009-10-27
+ */
+static inline ruleset_t*
+MsgGetRuleset(msg_t *pMsg)
+{
+ return pMsg->pRuleset;
+}
+
+
+#endif /* #ifndef MSG_H_INCLUDED */
+/* vim:set ai:
+ */
diff --git a/runtime/net.c b/runtime/net.c
new file mode 100644
index 00000000..b291213e
--- /dev/null
+++ b/runtime/net.c
@@ -0,0 +1,1545 @@
+/* net.c
+ * Implementation of network-related stuff.
+ *
+ * File begun on 2007-07-20 by RGerhards (extracted from syslogd.c)
+ * This file is under development and has not yet arrived at being fully
+ * self-contained and a real object. So far, it is mostly an excerpt
+ * of the "old" networking code without any modifications. However, it
+ * helps to have things at the right place one we go to the meat of it.
+ *
+ * Starting 2007-12-24, I have begun to shuffle more network-related code
+ * from syslogd.c to over here. I am not sure if it will stay here in the
+ * long term, but it is good to have it out of syslogd.c. Maybe this here is
+ * an interim location ;)
+ *
+ * Copyright 2007-2011 Rainer Gerhards and Adiscon GmbH.
+ *
+ * rgerhards, 2008-04-16: I changed this code to LGPL today. I carefully analyzed
+ * that it does not borrow code from the original sysklogd and that I have
+ * permission to do so from all other contributors. My analysis found that all
+ * code from sysklogd has been superseeded by our own functionality, so it
+ * is OK to move this file to LGPL. Some variable sysklogd variable names
+ * remain, but even this will change as the net object evolves.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <fnmatch.h>
+#include <fcntl.h>
+#include <unistd.h>
+#if HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#else
+#include "compat/ifaddrs.h"
+#endif /* HAVE_GETIFADDRS */
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include "syslogd-types.h"
+#include "module-template.h"
+#include "parse.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "net.h"
+#include "dnscache.h"
+#include "prop.h"
+
+#ifdef OS_SOLARIS
+# define s6_addr32 _S6_un._S6_u32
+ typedef unsigned int u_int32_t;
+#endif
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(prop)
+
+/* support for defining allowed TCP and UDP senders. We use the same
+ * structure to implement this (a linked list), but we define two different
+ * list roots, one for UDP and one for TCP.
+ * rgerhards, 2005-09-26
+ */
+/* All of the five below are read-only after startup */
+struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */
+struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */
+static struct AllowedSenders *pLastAllowedSenders_UDP = NULL; /* and now the pointers to the last */
+static struct AllowedSenders *pLastAllowedSenders_TCP = NULL; /* element in the respective list */
+#ifdef USE_GSSAPI
+struct AllowedSenders *pAllowedSenders_GSS = NULL;
+static struct AllowedSenders *pLastAllowedSenders_GSS = NULL;
+#endif
+
+int ACLAddHostnameOnFail = 0; /* add hostname to acl when DNS resolving has failed */
+int ACLDontResolve = 0; /* add hostname to acl instead of resolving it to IP(s) */
+
+
+/* ------------------------------ begin permitted peers code ------------------------------ */
+
+
+/* sets the correct allow root pointer based on provided type
+ * rgerhards, 2008-12-01
+ */
+static inline rsRetVal
+setAllowRoot(struct AllowedSenders **ppAllowRoot, uchar *pszType)
+{
+ DEFiRet;
+
+ if(!strcmp((char*)pszType, "UDP"))
+ *ppAllowRoot = pAllowedSenders_UDP;
+ else if(!strcmp((char*)pszType, "TCP"))
+ *ppAllowRoot = pAllowedSenders_TCP;
+#ifdef USE_GSSAPI
+ else if(!strcmp((char*)pszType, "GSS"))
+ *ppAllowRoot = pAllowedSenders_GSS;
+#endif
+ else {
+ dbgprintf("program error: invalid allowed sender ID '%s', denying...\n", pszType);
+ ABORT_FINALIZE(RS_RET_CODE_ERR); /* everything is invalid for an invalid type */
+ }
+
+finalize_it:
+ RETiRet;
+}
+/* re-initializes (sets to NULL) the correct allow root pointer
+ * rgerhards, 2009-01-12
+ */
+static inline rsRetVal
+reinitAllowRoot(uchar *pszType)
+{
+ DEFiRet;
+
+ if(!strcmp((char*)pszType, "UDP"))
+ pAllowedSenders_UDP = NULL;
+ else if(!strcmp((char*)pszType, "TCP"))
+ pAllowedSenders_TCP = NULL;
+#ifdef USE_GSSAPI
+ else if(!strcmp((char*)pszType, "GSS"))
+ pAllowedSenders_GSS = NULL;
+#endif
+ else {
+ dbgprintf("program error: invalid allowed sender ID '%s', denying...\n", pszType);
+ ABORT_FINALIZE(RS_RET_CODE_ERR); /* everything is invalid for an invalid type */
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* add a wildcard entry to this permitted peer. Entries are always
+ * added at the tail of the list. pszStr and lenStr identify the wildcard
+ * entry to be added. Note that the string is NOT \0 terminated, so
+ * we must rely on lenStr for when it is finished.
+ * rgerhards, 2008-05-27
+ */
+static rsRetVal
+AddPermittedPeerWildcard(permittedPeers_t *pPeer, uchar* pszStr, size_t lenStr)
+{
+ permittedPeerWildcard_t *pNew = NULL;
+ size_t iSrc;
+ size_t iDst;
+ DEFiRet;
+
+ assert(pPeer != NULL);
+ assert(pszStr != NULL);
+
+ CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t)));
+
+ if(lenStr == 0) { /* empty domain components are permitted */
+ pNew->wildcardType = PEER_WILDCARD_EMPTY_COMPONENT;
+ FINALIZE;
+ } else {
+ /* alloc memory for the domain component. We may waste a byte or
+ * two, but that's ok.
+ */
+ CHKmalloc(pNew->pszDomainPart = MALLOC(lenStr +1 ));
+ }
+
+ if(pszStr[0] == '*') {
+ pNew->wildcardType = PEER_WILDCARD_AT_START;
+ iSrc = 1; /* skip '*' */
+ } else {
+ iSrc = 0;
+ }
+
+ for(iDst = 0 ; iSrc < lenStr && pszStr[iSrc] != '*' ; ++iSrc, ++iDst) {
+ pNew->pszDomainPart[iDst] = pszStr[iSrc];
+ }
+
+ if(iSrc < lenStr) {
+ if(iSrc + 1 == lenStr && pszStr[iSrc] == '*') {
+ if(pNew->wildcardType == PEER_WILDCARD_AT_START) {
+ ABORT_FINALIZE(RS_RET_INVALID_WILDCARD);
+ } else {
+ pNew->wildcardType = PEER_WILDCARD_AT_END;
+ }
+ } else {
+ /* we have an invalid wildcard, something follows the asterisk! */
+ ABORT_FINALIZE(RS_RET_INVALID_WILDCARD);
+ }
+ }
+
+ if(lenStr == 1 && pNew->wildcardType == PEER_WILDCARD_AT_START) {
+ pNew->wildcardType = PEER_WILDCARD_MATCH_ALL;
+ }
+
+ /* if we reach this point, we had a valid wildcard. We now need to
+ * properly terminate the domain component string.
+ */
+ pNew->pszDomainPart[iDst] = '\0';
+ pNew->lenDomainPart = strlen((char*)pNew->pszDomainPart);
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL) {
+ if(pNew->pszDomainPart != NULL)
+ free(pNew->pszDomainPart);
+ free(pNew);
+ }
+ } else {
+ /* enqueue the element */
+ if(pPeer->pWildcardRoot == NULL) {
+ pPeer->pWildcardRoot = pNew;
+ } else {
+ pPeer->pWildcardLast->pNext = pNew;
+ }
+ pPeer->pWildcardLast = pNew;
+ }
+
+ RETiRet;
+}
+
+
+/* Destruct a permitted peer's wildcard list -- rgerhards, 2008-05-27 */
+static rsRetVal
+DestructPermittedPeerWildcards(permittedPeers_t *pPeer)
+{
+ permittedPeerWildcard_t *pCurr;
+ permittedPeerWildcard_t *pDel;
+ DEFiRet;
+
+ assert(pPeer != NULL);
+
+ for(pCurr = pPeer->pWildcardRoot ; pCurr != NULL ; /*EMPTY*/) {
+ pDel = pCurr;
+ pCurr = pCurr->pNext;
+ free(pDel->pszDomainPart);
+ free(pDel);
+ }
+
+ pPeer->pWildcardRoot = NULL;
+ pPeer->pWildcardLast = NULL;
+
+ RETiRet;
+}
+
+
+/* add a permitted peer. PermittedPeers is an interim solution until we can provide
+ * access control via enhanced RainerScript methods.
+ * Note: the provided string is handed over to this function, caller must
+ * no longer access it. -- rgerhards, 2008-05-19
+ */
+static rsRetVal
+AddPermittedPeer(permittedPeers_t **ppRootPeer, uchar* pszID)
+{
+ permittedPeers_t *pNew = NULL;
+ DEFiRet;
+
+ assert(ppRootPeer != NULL);
+ assert(pszID != NULL);
+
+ CHKmalloc(pNew = calloc(1, sizeof(permittedPeers_t))); /* we use calloc() for consistency with "real" objects */
+ CHKmalloc(pNew->pszID = (uchar*)strdup((char*)pszID));
+
+ if(*ppRootPeer != NULL) {
+ pNew->pNext = *ppRootPeer;
+ }
+ *ppRootPeer = pNew;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL)
+ free(pNew);
+ }
+ RETiRet;
+}
+
+
+/* Destruct a permitted peers list -- rgerhards, 2008-05-19 */
+static rsRetVal
+DestructPermittedPeers(permittedPeers_t **ppRootPeer)
+{
+ permittedPeers_t *pCurr;
+ permittedPeers_t *pDel;
+ DEFiRet;
+
+ assert(ppRootPeer != NULL);
+
+ for(pCurr = *ppRootPeer ; pCurr != NULL ; /*EMPTY*/) {
+ pDel = pCurr;
+ pCurr = pCurr->pNext;
+ DestructPermittedPeerWildcards(pDel);
+ free(pDel->pszID);
+ free(pDel);
+ }
+
+ *ppRootPeer = NULL;
+
+ RETiRet;
+}
+
+
+/* Compile a wildcard. The function first checks if there is a wildcard
+ * present and compiles it only if so ;) It sets the etryType status
+ * accordingly.
+ * rgerhards, 2008-05-27
+ */
+static rsRetVal
+PermittedPeerWildcardCompile(permittedPeers_t *pPeer)
+{
+ uchar *pC;
+ uchar *pStart;
+ DEFiRet;
+
+ assert(pPeer != NULL);
+ assert(pPeer->pszID != NULL);
+
+ /* first check if we have a wildcard */
+ for(pC = pPeer->pszID ; *pC != '\0' && *pC != '*' ; ++pC)
+ /*EMPTY, just skip*/;
+
+ if(*pC == '\0') {
+ /* no wildcard found, we are mostly done */
+ pPeer->etryType = PERM_PEER_TYPE_PLAIN;
+ FINALIZE;
+ }
+
+ /* if we reach this point, the string contains wildcards. So let's
+ * compile the structure. To do so, we must parse from dot to dot
+ * and create a wildcard entry for each domain component we find.
+ * We must also flag problems if we have an asterisk in the middle
+ * of the text (it is supported at the start or end only).
+ */
+ pPeer->etryType = PERM_PEER_TYPE_WILDCARD;
+ pC = pPeer->pszID;
+ while(*pC != '\0') {
+ pStart = pC;
+ /* find end of domain component */
+ for( ; *pC != '\0' && *pC != '.' ; ++pC)
+ /*EMPTY, just skip*/;
+ CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, pC - pStart));
+ /* now check if we have an empty component at end of string */
+ if(*pC == '.' && *(pC + 1) == '\0') {
+ /* pStart is a dummy, it is not used if length is 0 */
+ CHKiRet(AddPermittedPeerWildcard(pPeer, pStart, 0));
+ }
+ if(*pC != '\0')
+ ++pC;
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "error compiling wildcard expression '%s'",
+ pPeer->pszID);
+ }
+ RETiRet;
+}
+
+
+/* Do a (potential) wildcard match. The function first checks if the wildcard
+ * has already been compiled and, if not, compiles it. If the peer entry in
+ * question does NOT contain a wildcard, a simple strcmp() is done.
+ * *pbIsMatching is set to 0 if there is no match and something else otherwise.
+ * rgerhards, 2008-05-27 */
+static rsRetVal
+PermittedPeerWildcardMatch(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching)
+{
+ permittedPeerWildcard_t *pWildcard;
+ uchar *pC;
+ uchar *pStart; /* start of current domain component */
+ size_t iWildcard, iName; /* work indexes for backward comparisons */
+ DEFiRet;
+
+ assert(pPeer != NULL);
+ assert(pszNameToMatch != NULL);
+ assert(pbIsMatching != NULL);
+
+ if(pPeer->etryType == PERM_PEER_TYPE_UNDECIDED) {
+ PermittedPeerWildcardCompile(pPeer);
+ }
+
+ if(pPeer->etryType == PERM_PEER_TYPE_PLAIN) {
+ *pbIsMatching = !strcmp((char*)pPeer->pszID, (char*)pszNameToMatch);
+ FINALIZE;
+ }
+
+ /* we have a wildcard, so we need to extract the domain components and
+ * check then against the provided wildcards.
+ */
+ pWildcard = pPeer->pWildcardRoot;
+ pC = pszNameToMatch;
+ while(*pC != '\0') {
+ if(pWildcard == NULL) {
+ /* we have more domain components than we have wildcards --> no match */
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ pStart = pC;
+ while(*pC != '\0' && *pC != '.') {
+ ++pC;
+ }
+
+ /* got the component, now do the match */
+ switch(pWildcard->wildcardType) {
+ case PEER_WILDCARD_NONE:
+ if( pWildcard->lenDomainPart != (size_t) (pC - pStart)
+ || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pC - pStart)) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ break;
+ case PEER_WILDCARD_AT_START:
+ /* we need to do the backwards-matching manually */
+ if(pWildcard->lenDomainPart > (size_t) (pC - pStart)) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ iName = (size_t) (pC - pStart) - pWildcard->lenDomainPart;
+ iWildcard = 0;
+ while(iWildcard < pWildcard->lenDomainPart) {
+ if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ ++iName;
+ ++iWildcard;
+ }
+ break;
+ case PEER_WILDCARD_AT_END:
+ if( pWildcard->lenDomainPart > (size_t) (pC - pStart)
+ || strncmp((char*)pStart, (char*)pWildcard->pszDomainPart, pWildcard->lenDomainPart)) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ break;
+ case PEER_WILDCARD_MATCH_ALL:
+ /* everything is OK, just continue */
+ break;
+ case PEER_WILDCARD_EMPTY_COMPONENT:
+ if(pC - pStart > 0) {
+ /* if it is not empty, it is no match... */
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ break;
+ }
+ pWildcard = pWildcard->pNext; /* we processed this entry */
+
+ /* skip '.' if we had it and so prepare for next iteration */
+ if(*pC == '.')
+ ++pC;
+ }
+
+ if(pWildcard != NULL) {
+ /* we have more domain components than in the name to be
+ * checked. So this is no match.
+ */
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+
+ *pbIsMatching = 1; /* finally... it matches ;) */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* ------------------------------ end permitted peers code ------------------------------ */
+
+
+/* Code for handling allowed/disallowed senders
+ */
+static inline void MaskIP6 (struct in6_addr *addr, uint8_t bits) {
+ register uint8_t i;
+
+ assert (addr != NULL);
+ assert (bits <= 128);
+
+ i = bits/32;
+ if (bits%32)
+ addr->s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32)));
+ for (; i < (sizeof addr->s6_addr32)/4; i++)
+ addr->s6_addr32[i] = 0;
+}
+
+static inline void MaskIP4 (struct in_addr *addr, uint8_t bits) {
+
+ assert (addr != NULL);
+ assert (bits <=32 );
+
+ addr->s_addr &= htonl(0xffffffff << (32 - bits));
+}
+
+#define SIN(sa) ((struct sockaddr_in *)(void*)(sa))
+#define SIN6(sa) ((struct sockaddr_in6 *)(void*)(sa))
+
+
+/* This is a cancel-safe getnameinfo() version, because we learned
+ * (via drd/valgrind) that getnameinfo() seems to have some issues
+ * when being cancelled, at least if the module was dlloaded.
+ * rgerhards, 2008-09-30
+ */
+static inline int
+mygetnameinfo(const struct sockaddr *sa, socklen_t salen,
+ char *host, size_t hostlen,
+ char *serv, size_t servlen, int flags)
+{
+ int iCancelStateSave;
+ int i;
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ i = getnameinfo(sa, salen, host, hostlen, serv, servlen, flags);
+ pthread_setcancelstate(iCancelStateSave, NULL);
+ return i;
+}
+
+
+/* This function adds an allowed sender entry to the ACL linked list.
+ * In any case, a single entry is added. If an error occurs, the
+ * function does its error reporting itself. All validity checks
+ * must already have been done by the caller.
+ * This is a helper to AddAllowedSender().
+ * rgerhards, 2007-07-17
+ */
+static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast,
+ struct NetAddr *iAllow, uint8_t iSignificantBits)
+{
+ struct AllowedSenders *pEntry = NULL;
+
+ assert(ppRoot != NULL);
+ assert(ppLast != NULL);
+ assert(iAllow != NULL);
+
+ if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) {
+ return RS_RET_OUT_OF_MEMORY; /* no options left :( */
+ }
+
+ memcpy(&(pEntry->allowedSender), iAllow, sizeof (struct NetAddr));
+ pEntry->pNext = NULL;
+ pEntry->SignificantBits = iSignificantBits;
+
+ /* enqueue */
+ if(*ppRoot == NULL) {
+ *ppRoot = pEntry;
+ } else {
+ (*ppLast)->pNext = pEntry;
+ }
+ *ppLast = pEntry;
+
+ return RS_RET_OK;
+}
+
+/* function to clear the allowed sender structure in cases where
+ * it must be freed (occurs most often when HUPed).
+ * rgerhards, 2008-12-02: revamped this code when we fixed the interface
+ * definition. Now an iterative algorithm is used.
+ */
+static void
+clearAllowedSenders(uchar *pszType)
+{
+ struct AllowedSenders *pPrev;
+ struct AllowedSenders *pCurr = NULL;
+
+ if(setAllowRoot(&pCurr, pszType) != RS_RET_OK)
+ return; /* if something went wrong, so let's leave */
+
+ while(pCurr != NULL) {
+ pPrev = pCurr;
+ pCurr = pCurr->pNext;
+ /* now delete the entry we are right now processing */
+ if(F_ISSET(pPrev->allowedSender.flags, ADDR_NAME))
+ free(pPrev->allowedSender.addr.HostWildcard);
+ else
+ free(pPrev->allowedSender.addr.NetAddr);
+ free(pPrev);
+ }
+
+ /* indicate root pointer is de-init (was forgotten previously, resulting in
+ * all kinds of interesting things) -- rgerhards, 2009-01-12
+ */
+ reinitAllowRoot(pszType);
+}
+
+
+/* function to add an allowed sender to the allowed sender list. The
+ * root of the list is caller-provided, so it can be used for all
+ * supported lists. The caller must provide a pointer to the root,
+ * as it eventually needs to be updated. Also, a pointer to the
+ * pointer to the last element must be provided (to speed up adding
+ * list elements).
+ * rgerhards, 2005-09-26
+ * If a hostname is given there are possible multiple entries
+ * added (all addresses from that host).
+ */
+static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast,
+ struct NetAddr *iAllow, uint8_t iSignificantBits)
+{
+ DEFiRet;
+
+ assert(ppRoot != NULL);
+ assert(ppLast != NULL);
+ assert(iAllow != NULL);
+
+ if (!F_ISSET(iAllow->flags, ADDR_NAME)) {
+ if(iSignificantBits == 0)
+ /* we handle this seperatly just to provide a better
+ * error message.
+ */
+ errmsg.LogError(0, NO_ERRCODE, "You can not specify 0 bits of the netmask, this would "
+ "match ALL systems. If you really intend to do that, "
+ "remove all $AllowedSender directives.");
+
+ switch (iAllow->addr.NetAddr->sa_family) {
+ case AF_INET:
+ if((iSignificantBits < 1) || (iSignificantBits > 32)) {
+ errmsg.LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv4 address - adjusted to 32",
+ (int)iSignificantBits);
+ iSignificantBits = 32;
+ }
+
+ MaskIP4 (&(SIN(iAllow->addr.NetAddr)->sin_addr), iSignificantBits);
+ break;
+ case AF_INET6:
+ if((iSignificantBits < 1) || (iSignificantBits > 128)) {
+ errmsg.LogError(0, NO_ERRCODE, "Invalid number of bits (%d) in IPv6 address - adjusted to 128",
+ iSignificantBits);
+ iSignificantBits = 128;
+ }
+
+ MaskIP6 (&(SIN6(iAllow->addr.NetAddr)->sin6_addr), iSignificantBits);
+ break;
+ default:
+ /* rgerhards, 2007-07-16: We have an internal program error in this
+ * case. However, there is not much we can do against it right now. Of
+ * course, we could abort, but that would probably cause more harm
+ * than good. So we continue to run. We simply do not add this line - the
+ * worst thing that happens is that one host will not be allowed to
+ * log.
+ */
+ errmsg.LogError(0, NO_ERRCODE, "Internal error caused AllowedSender to be ignored, AF = %d",
+ iAllow->addr.NetAddr->sa_family);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ /* OK, entry constructed, now lets add it to the ACL list */
+ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits);
+ } else {
+ /* we need to process a hostname ACL */
+ if(glbl.GetDisableDNS()) {
+ errmsg.LogError(0, NO_ERRCODE, "Ignoring hostname based ACLs because DNS is disabled.");
+ ABORT_FINALIZE(RS_RET_OK);
+ }
+
+ if (!strchr (iAllow->addr.HostWildcard, '*') &&
+ !strchr (iAllow->addr.HostWildcard, '?') &&
+ ACLDontResolve == 0) {
+ /* single host - in this case, we pull its IP addresses from DNS
+ * and add IP-based ACLs.
+ */
+ struct addrinfo hints, *res, *restmp;
+ struct NetAddr allowIP;
+
+ memset (&hints, 0, sizeof (struct addrinfo));
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_DGRAM;
+# ifdef AI_ADDRCONFIG /* seems not to be present on all systems */
+ hints.ai_flags = AI_ADDRCONFIG;
+# endif
+
+ if (getaddrinfo (iAllow->addr.HostWildcard, NULL, &hints, &res) != 0) {
+ errmsg.LogError(0, NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard);
+
+ if (ACLAddHostnameOnFail) {
+ errmsg.LogError(0, NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard);
+ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits);
+ FINALIZE;
+ } else {
+ errmsg.LogError(0, NO_ERRCODE, "Hostname \"%s\" WON\'T be added to ACL.", iAllow->addr.HostWildcard);
+ ABORT_FINALIZE(RS_RET_NOENTRY);
+ }
+ }
+
+ for (restmp = res ; res != NULL ; res = res->ai_next) {
+ switch (res->ai_family) {
+ case AF_INET: /* add IPv4 */
+ iSignificantBits = 32;
+ allowIP.flags = 0;
+ if((allowIP.addr.NetAddr = MALLOC(res->ai_addrlen)) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen);
+
+ if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits))
+ != RS_RET_OK)
+ FINALIZE;
+ break;
+ case AF_INET6: /* IPv6 - but need to check if it is a v6-mapped IPv4 */
+ if(IN6_IS_ADDR_V4MAPPED (&SIN6(res->ai_addr)->sin6_addr)) {
+ /* extract & add IPv4 */
+
+ iSignificantBits = 32;
+ allowIP.flags = 0;
+ if((allowIP.addr.NetAddr = MALLOC(sizeof(struct sockaddr_in)))
+ == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ SIN(allowIP.addr.NetAddr)->sin_family = AF_INET;
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+ SIN(allowIP.addr.NetAddr)->sin_len = sizeof (struct sockaddr_in);
+#endif
+ SIN(allowIP.addr.NetAddr)->sin_port = 0;
+ memcpy(&(SIN(allowIP.addr.NetAddr)->sin_addr.s_addr),
+ &(SIN6(res->ai_addr)->sin6_addr.s6_addr32[3]),
+ sizeof (in_addr_t));
+
+ if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP,
+ iSignificantBits))
+ != RS_RET_OK)
+ FINALIZE;
+ } else {
+ /* finally add IPv6 */
+
+ iSignificantBits = 128;
+ allowIP.flags = 0;
+ if((allowIP.addr.NetAddr = MALLOC(res->ai_addrlen)) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen);
+
+ if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP,
+ iSignificantBits))
+ != RS_RET_OK)
+ FINALIZE;
+ }
+ break;
+ }
+ }
+ freeaddrinfo (restmp);
+ } else {
+ /* wildcards in hostname - we need to add a text-based ACL.
+ * For this, we already have everything ready and just need
+ * to pass it along...
+ */
+ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits);
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Print an allowed sender list. The caller must tell us which one.
+ * iListToPrint = 1 means UDP, 2 means TCP
+ * rgerhards, 2005-09-27
+ */
+void PrintAllowedSenders(int iListToPrint)
+{
+ struct AllowedSenders *pSender;
+ uchar szIP[64];
+
+ assert((iListToPrint == 1) || (iListToPrint == 2)
+#ifdef USE_GSSAPI
+ || (iListToPrint == 3)
+#endif
+ );
+
+ dbgprintf("Allowed %s Senders:\n",
+ (iListToPrint == 1) ? "UDP" :
+#ifdef USE_GSSAPI
+ (iListToPrint == 3) ? "GSS" :
+#endif
+ "TCP");
+
+ pSender = (iListToPrint == 1) ? pAllowedSenders_UDP :
+#ifdef USE_GSSAPI
+ (iListToPrint == 3) ? pAllowedSenders_GSS :
+#endif
+ pAllowedSenders_TCP;
+ if(pSender == NULL) {
+ dbgprintf("\tNo restrictions set.\n");
+ } else {
+ while(pSender != NULL) {
+ if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME))
+ dbgprintf ("\t%s\n", pSender->allowedSender.addr.HostWildcard);
+ else {
+ if(mygetnameinfo (pSender->allowedSender.addr.NetAddr,
+ SALEN(pSender->allowedSender.addr.NetAddr),
+ (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) {
+ dbgprintf ("\t%s/%u\n", szIP, pSender->SignificantBits);
+ } else {
+ /* getnameinfo() failed - but as this is only a
+ * debug function, we simply spit out an error and do
+ * not care much about it.
+ */
+ dbgprintf("\tERROR in getnameinfo() - something may be wrong "
+ "- ignored for now\n");
+ }
+ }
+ pSender = pSender->pNext;
+ }
+ }
+}
+
+
+/* parse an allowed sender config line and add the allowed senders
+ * (if the line is correct).
+ * rgerhards, 2005-09-27
+ */
+rsRetVal addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine)
+{
+ struct AllowedSenders **ppRoot;
+ struct AllowedSenders **ppLast;
+ rsParsObj *pPars;
+ rsRetVal iRet;
+ struct NetAddr *uIP = NULL;
+ int iBits;
+
+ assert(pName != NULL);
+ assert(ppRestOfConfLine != NULL);
+ assert(*ppRestOfConfLine != NULL);
+
+ if(!strcasecmp(pName, "udp")) {
+ ppRoot = &pAllowedSenders_UDP;
+ ppLast = &pLastAllowedSenders_UDP;
+ } else if(!strcasecmp(pName, "tcp")) {
+ ppRoot = &pAllowedSenders_TCP;
+ ppLast = &pLastAllowedSenders_TCP;
+#ifdef USE_GSSAPI
+ } else if(!strcasecmp(pName, "gss")) {
+ ppRoot = &pAllowedSenders_GSS;
+ ppLast = &pLastAllowedSenders_GSS;
+#endif
+ } else {
+ errmsg.LogError(0, RS_RET_ERR, "Invalid protocol '%s' in allowed sender "
+ "list, line ignored", pName);
+ return RS_RET_ERR;
+ }
+
+ /* OK, we now know the protocol and have valid list pointers.
+ * So let's process the entries. We are using the parse class
+ * for this.
+ */
+ /* create parser object starting with line string without leading colon */
+ if((iRet = rsParsConstructFromSz(&pPars, (uchar*) *ppRestOfConfLine) != RS_RET_OK)) {
+ errmsg.LogError(0, iRet, "Error %d constructing parser object - ignoring allowed sender list", iRet);
+ return(iRet);
+ }
+
+ while(!parsIsAtEndOfParseString(pPars)) {
+ if(parsPeekAtCharAtParsPtr(pPars) == '#')
+ break; /* a comment-sign stops processing of line */
+ /* now parse a single IP address */
+ if((iRet = parsAddrWithBits(pPars, &uIP, &iBits)) != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "Error %d parsing address in allowed sender"
+ "list - ignoring.", iRet);
+ rsParsDestruct(pPars);
+ return(iRet);
+ }
+ if((iRet = AddAllowedSender(ppRoot, ppLast, uIP, iBits)) != RS_RET_OK) {
+ if(iRet == RS_RET_NOENTRY) {
+ errmsg.LogError(0, iRet, "Error %d adding allowed sender entry "
+ "- ignoring.", iRet);
+ } else {
+ errmsg.LogError(0, iRet, "Error %d adding allowed sender entry "
+ "- terminating, nothing more will be added.", iRet);
+ rsParsDestruct(pPars);
+ return(iRet);
+ }
+ }
+ free (uIP); /* copy stored in AllowedSenders list */
+ }
+
+ /* cleanup */
+ *ppRestOfConfLine += parsGetCurrentPosition(pPars);
+ return rsParsDestruct(pPars);
+}
+
+
+
+/* compares a host to an allowed sender list entry. Handles all subleties
+ * including IPv4/v6 as well as domain name wildcards.
+ * This is a helper to isAllowedSender. As it is only called once, it is
+ * declared inline.
+ * Returns 0 if they do not match, 1 if they match and 2 if a DNS name would have been required.
+ * contributed 2007-07-16 by mildew@gmail.com
+ */
+static inline int
+MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS)
+{
+ assert(pAllow != NULL);
+ assert(pFrom != NULL);
+
+ if(F_ISSET(pAllow->flags, ADDR_NAME)) {
+ if(bChkDNS == 0)
+ return 2;
+ dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard);
+
+# if !defined(FNM_CASEFOLD)
+ /* TODO: I don't know if that then works, seen on HP UX, what I have not in lab... ;) */
+ return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE) == 0);
+# else
+ return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0);
+# endif
+ } else {/* We need to compare an IP address */
+ switch (pFrom->sa_family) {
+ case AF_INET:
+ if (AF_INET == pAllow->addr.NetAddr->sa_family)
+ return(( SIN(pFrom)->sin_addr.s_addr & htonl(0xffffffff << (32 - bits)) )
+ == SIN(pAllow->addr.NetAddr)->sin_addr.s_addr);
+ else
+ return 0;
+ break;
+ case AF_INET6:
+ switch (pAllow->addr.NetAddr->sa_family) {
+ case AF_INET6: {
+ struct in6_addr ip, net;
+ register uint8_t i;
+
+ memcpy (&ip, &(SIN6(pFrom))->sin6_addr, sizeof (struct in6_addr));
+ memcpy (&net, &(SIN6(pAllow->addr.NetAddr))->sin6_addr, sizeof (struct in6_addr));
+
+ i = bits/32;
+ if (bits % 32)
+ ip.s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32)));
+ for (; i < (sizeof ip.s6_addr32)/4; i++)
+ ip.s6_addr32[i] = 0;
+
+ return (memcmp (ip.s6_addr, net.s6_addr, sizeof ip.s6_addr) == 0 &&
+ (SIN6(pAllow->addr.NetAddr)->sin6_scope_id != 0 ?
+ SIN6(pFrom)->sin6_scope_id == SIN6(pAllow->addr.NetAddr)->sin6_scope_id : 1));
+ }
+ case AF_INET: {
+ struct in6_addr *ip6 = &(SIN6(pFrom))->sin6_addr;
+ struct in_addr *net = &(SIN(pAllow->addr.NetAddr))->sin_addr;
+
+ if ((ip6->s6_addr32[3] & (u_int32_t) htonl((0xffffffff << (32 - bits)))) == net->s_addr &&
+#if BYTE_ORDER == LITTLE_ENDIAN
+ (ip6->s6_addr32[2] == (u_int32_t)0xffff0000) &&
+#else
+ (ip6->s6_addr32[2] == (u_int32_t)0x0000ffff) &&
+#endif
+ (ip6->s6_addr32[1] == 0) && (ip6->s6_addr32[0] == 0))
+ return 1;
+ else
+ return 0;
+ }
+ default:
+ /* Unsupported AF */
+ return 0;
+ }
+ default:
+ /* Unsupported AF */
+ return 0;
+ }
+ }
+}
+
+
+/* check if a sender is allowed. The root of the the allowed sender.
+ * list must be proveded by the caller. As such, this function can be
+ * used to check both UDP and TCP allowed sender lists.
+ * returns 1, if the sender is allowed, 0 if not and 2 if we could not
+ * obtain a result because we would need a dns name, which we don't have
+ * (2 was added rgerhards, 2009-11-16).
+ * rgerhards, 2005-09-26
+ */
+static int isAllowedSender2(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS)
+{
+ struct AllowedSenders *pAllow;
+ struct AllowedSenders *pAllowRoot = NULL;
+ int bNeededDNS = 0; /* partial check because we could not resolve DNS? */
+ int ret;
+
+ assert(pFrom != NULL);
+
+ if(setAllowRoot(&pAllowRoot, pszType) != RS_RET_OK)
+ return 0; /* if something went wrong, we deny access - that's the better choice... */
+
+ if(pAllowRoot == NULL)
+ return 1; /* checking disabled, everything is valid! */
+
+ /* now we loop through the list of allowed senders. As soon as
+ * we find a match, we return back (indicating allowed). We loop
+ * until we are out of allowed senders. If so, we fall through the
+ * loop and the function's terminal return statement will indicate
+ * that the sender is disallowed.
+ */
+ for(pAllow = pAllowRoot ; pAllow != NULL ; pAllow = pAllow->pNext) {
+ ret = MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost, bChkDNS);
+ if(ret == 1)
+ return 1;
+ else if(ret == 2)
+ bNeededDNS = 2;
+ }
+ return bNeededDNS;
+}
+
+
+/* legacy API, not to be used any longer */
+static int
+isAllowedSender(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost) {
+ return isAllowedSender2(pszType, pFrom, pszFromHost, 1);
+}
+
+
+/* The following #ifdef sequence is a small compatibility
+ * layer. It tries to work around the different availality
+ * levels of SO_BSDCOMPAT on linuxes...
+ * I borrowed this code from
+ * http://www.erlang.org/ml-archive/erlang-questions/200307/msg00037.html
+ * It still needs to be a bit better adapted to rsyslog.
+ * rgerhards 2005-09-19
+ */
+#include <sys/utsname.h>
+static int
+should_use_so_bsdcompat(void)
+{
+#ifndef OS_BSD
+ static int init_done = 0;
+ static int so_bsdcompat_is_obsolete = 0;
+
+ if (!init_done) {
+ struct utsname myutsname;
+ unsigned int version, patchlevel;
+
+ init_done = 1;
+ if (uname(&myutsname) < 0) {
+ char errStr[1024];
+ dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr)));
+ return 1;
+ }
+ /* Format is <version>.<patchlevel>.<sublevel><extraversion>
+ where the first three are unsigned integers and the last
+ is an arbitrary string. We only care about the first two. */
+ if (sscanf(myutsname.release, "%u.%u", &version, &patchlevel) != 2) {
+ dbgprintf("uname: unexpected release '%s'\r\n",
+ myutsname.release);
+ return 1;
+ }
+ /* SO_BSCOMPAT is deprecated and triggers warnings in 2.5
+ kernels. It is a no-op in 2.4 but not in 2.2 kernels. */
+ if (version > 2 || (version == 2 && patchlevel >= 5))
+ so_bsdcompat_is_obsolete = 1;
+ }
+ return !so_bsdcompat_is_obsolete;
+#else /* #ifndef OS_BSD */
+ return 1;
+#endif /* #ifndef OS_BSD */
+}
+#ifndef SO_BSDCOMPAT
+/* this shall prevent compiler errors due to undfined name */
+#define SO_BSDCOMPAT 0
+#endif
+
+
+/* print out which socket we are listening on. This is only
+ * a debug aid. rgerhards, 2007-07-02
+ */
+void debugListenInfo(int fd, char *type)
+{
+ char *szFamily;
+ int port;
+ struct sockaddr sa;
+ struct sockaddr_in *ipv4;
+ struct sockaddr_in6 *ipv6;
+ socklen_t saLen = sizeof(sa);
+
+ if(getsockname(fd, &sa, &saLen) == 0) {
+ switch(sa.sa_family) {
+ case PF_INET:
+ szFamily = "IPv4";
+ ipv4 = (struct sockaddr_in*)(void*) &sa;
+ port = ntohs(ipv4->sin_port);
+ break;
+ case PF_INET6:
+ szFamily = "IPv6";
+ ipv6 = (struct sockaddr_in6*)(void*) &sa;
+ port = ntohs(ipv6->sin6_port);
+ break;
+ default:
+ szFamily = "other";
+ port = -1;
+ break;
+ }
+ dbgprintf("Listening on %s syslogd socket %d (%s/port %d).\n",
+ type, fd, szFamily, port);
+ return;
+ }
+
+ /* we can not obtain peer info. We are just providing
+ * debug info, so this is no reason to break the program
+ * or do any serious error reporting.
+ */
+ dbgprintf("Listening on syslogd socket %d - could not obtain peer info.\n", fd);
+}
+
+
+/* Return a printable representation of a host addresses. If
+ * a parameter is NULL, it is not set. rgerhards, 2013-01-22
+ */
+rsRetVal
+cvthname(struct sockaddr_storage *f, prop_t **localName, prop_t **fqdn, prop_t **ip)
+{
+ DEFiRet;
+ assert(f != NULL);
+ iRet = dnscacheLookup(f, NULL, fqdn, localName, ip);
+ RETiRet;
+}
+
+
+/* get the name of the local host. A pointer to a character pointer is passed
+ * in, which on exit points to the local hostname. This buffer is dynamically
+ * allocated and must be free()ed by the caller. If the functions returns an
+ * error, the pointer is NULL. This function is based on GNU/Hurd's localhostname
+ * function.
+ * rgerhards, 20080-04-10
+ */
+static rsRetVal
+getLocalHostname(uchar **ppName)
+{
+ DEFiRet;
+ uchar *buf = NULL;
+ size_t buf_len = 0;
+
+ assert(ppName != NULL);
+
+ do {
+ if(buf == NULL) {
+ buf_len = 128; /* Initial guess */
+ CHKmalloc(buf = MALLOC(buf_len));
+ } else {
+ buf_len += buf_len;
+ CHKmalloc(buf = realloc (buf, buf_len));
+ }
+ } while((gethostname((char*)buf, buf_len) == 0 && !memchr (buf, '\0', buf_len)) || errno == ENAMETOOLONG);
+
+ *ppName = buf;
+ buf = NULL;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(buf != NULL)
+ free(buf);
+ }
+ RETiRet;
+}
+
+
+/* closes the UDP listen sockets (if they exist) and frees
+ * all dynamically assigned memory.
+ */
+void closeUDPListenSockets(int *pSockArr)
+{
+ register int i;
+
+ assert(pSockArr != NULL);
+ if(pSockArr != NULL) {
+ for (i = 0; i < *pSockArr; i++)
+ close(pSockArr[i+1]);
+ free(pSockArr);
+ }
+}
+
+
+/* creates the UDP listen sockets
+ * hostname and/or pszPort may be NULL, but not both!
+ * bIsServer indicates if a server socket should be created
+ * 1 - server, 0 - client
+ */
+int *create_udp_socket(uchar *hostname, uchar *pszPort, int bIsServer)
+{
+ struct addrinfo hints, *res, *r;
+ int error, maxs, *s, *socks, on = 1;
+ int sockflags;
+
+ assert(!((pszPort == NULL) && (hostname == NULL)));
+ memset(&hints, 0, sizeof(hints));
+ if(bIsServer)
+ hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+ else
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = glbl.GetDefPFFamily();
+ hints.ai_socktype = SOCK_DGRAM;
+ error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res);
+ if(error) {
+ errmsg.LogError(0, NO_ERRCODE, "%s", gai_strerror(error));
+ errmsg.LogError(0, NO_ERRCODE, "UDP message reception disabled due to error logged in last message.\n");
+ return NULL;
+ }
+
+ /* Count max number of sockets we may open */
+ for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++)
+ /* EMPTY */;
+ socks = MALLOC((maxs+1) * sizeof(int));
+ if (socks == NULL) {
+ errmsg.LogError(0, NO_ERRCODE, "couldn't allocate memory for UDP sockets, suspending UDP message reception");
+ freeaddrinfo(res);
+ return NULL;
+ }
+
+ *socks = 0; /* num of sockets counter at start of array */
+ s = socks + 1;
+ for (r = res; r != NULL ; r = r->ai_next) {
+ *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+ if (*s < 0) {
+ if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT))
+ errmsg.LogError(errno, NO_ERRCODE, "create_udp_socket(), socket");
+ /* it is debateble if PF_INET with EAFNOSUPPORT should
+ * also be ignored...
+ */
+ continue;
+ }
+
+# ifdef IPV6_V6ONLY
+ if (r->ai_family == AF_INET6) {
+ int ion = 1;
+ if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&ion, sizeof (ion)) < 0) {
+ errmsg.LogError(errno, NO_ERRCODE, "setsockopt");
+ close(*s);
+ *s = -1;
+ continue;
+ }
+ }
+# endif
+
+ /* if we have an error, we "just" suspend that socket. Eventually
+ * other sockets will work. At the end of this function, we check
+ * if we managed to open at least one socket. If not, we'll write
+ * a "inet suspended" message and declare failure. Else we use
+ * what we could obtain.
+ * rgerhards, 2007-06-22
+ */
+ if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR,
+ (char *) &on, sizeof(on)) < 0 ) {
+ errmsg.LogError(errno, NO_ERRCODE, "setsockopt(REUSEADDR)");
+ close(*s);
+ *s = -1;
+ continue;
+ }
+
+ /* We need to enable BSD compatibility. Otherwise an attacker
+ * could flood our log files by sending us tons of ICMP errors.
+ */
+#if !defined(OS_BSD) && !defined(__hpux)
+ if (should_use_so_bsdcompat()) {
+ if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT,
+ (char *) &on, sizeof(on)) < 0) {
+ errmsg.LogError(errno, NO_ERRCODE, "setsockopt(BSDCOMPAT)");
+ close(*s);
+ *s = -1;
+ continue;
+ }
+ }
+#endif
+ /* We must not block on the network socket, in case a packet
+ * gets lost between select and recv, otherwise the process
+ * will stall until the timeout, and other processes trying to
+ * log will also stall.
+ * Patch vom Colin Phipps <cph@cph.demon.co.uk> to the original
+ * sysklogd source. Applied to rsyslogd on 2005-10-19.
+ */
+ if ((sockflags = fcntl(*s, F_GETFL)) != -1) {
+ sockflags |= O_NONBLOCK;
+ /* SETFL could fail too, so get it caught by the subsequent
+ * error check.
+ */
+ sockflags = fcntl(*s, F_SETFL, sockflags);
+ }
+ if (sockflags == -1) {
+ errmsg.LogError(errno, NO_ERRCODE, "fcntl(O_NONBLOCK)");
+ close(*s);
+ *s = -1;
+ continue;
+ }
+
+ if(bIsServer) {
+ /* rgerhards, 2007-06-22: if we run on a kernel that does not support
+ * the IPV6_V6ONLY socket option, we need to use a work-around. On such
+ * systems the IPv6 socket does also accept IPv4 sockets. So an IPv4
+ * socket can not listen on the same port as an IPv6 socket. The only
+ * workaround is to ignore the "socket in use" error. This is what we
+ * do if we have to.
+ */
+ if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0)
+ # ifndef IPV6_V6ONLY
+ && (errno != EADDRINUSE)
+ # endif
+ ) {
+ errmsg.LogError(errno, NO_ERRCODE, "bind");
+ close(*s);
+ *s = -1;
+ continue;
+ }
+ }
+
+ (*socks)++;
+ s++;
+ }
+
+ if(res != NULL)
+ freeaddrinfo(res);
+
+ if(Debug && *socks != maxs)
+ dbgprintf("We could initialize %d UDP listen sockets out of %d we received "
+ "- this may or may not be an error indication.\n", *socks, maxs);
+
+ if(*socks == 0) {
+ errmsg.LogError(0, NO_ERRCODE, "No UDP listen socket could successfully be initialized, "
+ "message reception via UDP disabled.\n");
+ /* we do NOT need to free any sockets, because there were none... */
+ free(socks);
+ return(NULL);
+ }
+
+ return(socks);
+}
+
+
+/* check if two provided socket addresses point to the same host. Note that the
+ * length of the sockets must be provided as third parameter. This is necessary to
+ * compare non IPv4/v6 hosts, in which case we do a simple memory compare of the
+ * address structure (in that case, the same host may not reliably be detected).
+ * Note that we need to do the comparison not on the full structure, because it contains things
+ * like the port, which we do not need to look at when thinking about hostnames. So we look
+ * at the relevant fields, what means a somewhat more complicated processing.
+ * Also note that we use a non-standard calling interface, as this is much more natural and
+ * it looks extremely unlikely that we get an exception of any kind here. What we
+ * return is mimiced after memcmp(), and as such useful for building binary trees
+ * (the order relation may be a bit arbritrary, but at least it is consistent).
+ * rgerhards, 2009-09-03
+ */
+static int CmpHost(struct sockaddr_storage *s1, struct sockaddr_storage* s2, size_t socklen)
+{
+ int ret;
+
+ if(((struct sockaddr*) s1)->sa_family != ((struct sockaddr*) s2)->sa_family) {
+ ret = memcmp(s1, s2, socklen);
+ goto finalize_it;
+ }
+
+ if(((struct sockaddr*) s1)->sa_family == AF_INET) {
+ if(((struct sockaddr_in *) s1)->sin_addr.s_addr == ((struct sockaddr_in*)s2)->sin_addr.s_addr) {
+ ret = 0;
+ } else if(((struct sockaddr_in *) s1)->sin_addr.s_addr < ((struct sockaddr_in*)s2)->sin_addr.s_addr) {
+ ret = -1;
+ } else {
+ ret = 1;
+ }
+ } else if(((struct sockaddr*) s1)->sa_family == AF_INET6) {
+ /* IPv6 addresses are always 16 octets long */
+ ret = memcmp(((struct sockaddr_in6 *)s1)->sin6_addr.s6_addr, ((struct sockaddr_in6*)s2)->sin6_addr.s6_addr, 16);
+ } else {
+ ret = memcmp(s1, s2, socklen);
+ }
+
+finalize_it:
+ return ret;
+}
+
+
+
+/* check if restrictions (ALCs) exists. The goal of this function is to disable the
+ * somewhat time-consuming ACL checks if no restrictions are defined (the usual case).
+ * This also permits to gain some speedup by using firewall-based ACLs instead of
+ * rsyslog ACLs (the recommended method.
+ * rgerhards, 2009-11-16
+ */
+static rsRetVal
+HasRestrictions(uchar *pszType, int *bHasRestrictions) {
+ struct AllowedSenders *pAllowRoot = NULL;
+ DEFiRet;
+
+ CHKiRet(setAllowRoot(&pAllowRoot, pszType));
+
+ *bHasRestrictions = (pAllowRoot == NULL) ? 0 : 1;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ *bHasRestrictions = 1; /* in this case it is better to check individually */
+ DBGPRINTF("Error %d trying to obtain ACL restriction state of '%s'\n", iRet, pszType);
+ }
+ RETiRet;
+}
+
+
+/* return the IP address (IPv4/6) for the provided interface. Returns
+ * RS_RET_NOT_FOUND if interface can not be found in interface list.
+ * The family must be correct (AF_INET vs. AF_INET6, AF_UNSPEC means
+ * either of *these two*).
+ * The function re-queries the interface list (at least in theory).
+ * However, it caches entries in order to avoid too-frequent requery.
+ * rgerhards, 2012-03-06
+ */
+static rsRetVal
+getIFIPAddr(uchar *szif, int family, uchar *pszbuf, int lenBuf)
+{
+ struct ifaddrs * ifaddrs = NULL;
+ struct ifaddrs * ifa;
+ void * pAddr;
+ DEFiRet;
+
+ if(getifaddrs(&ifaddrs) != 0) {
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) {
+ if(strcmp(ifa->ifa_name, (char*)szif))
+ continue;
+ if( (family == AF_INET6 || family == AF_UNSPEC)
+ && ifa->ifa_addr->sa_family == AF_INET6) {
+ pAddr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+ inet_ntop(AF_INET6, pAddr, (char*)pszbuf, lenBuf);
+ break;
+ } else if(/* (family == AF_INET || family == AF_UNSPEC)
+ &&*/ ifa->ifa_addr->sa_family == AF_INET) {
+ pAddr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
+ inet_ntop(AF_INET, pAddr, (char*)pszbuf, lenBuf);
+ break;
+ }
+ }
+
+ if(ifaddrs != NULL)
+ freeifaddrs(ifaddrs);
+
+ if(ifa == NULL)
+ iRet = RS_RET_NOT_FOUND;
+
+finalize_it:
+ RETiRet;
+
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(net)
+CODESTARTobjQueryInterface(net)
+ if(pIf->ifVersion != netCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->cvthname = cvthname;
+ /* things to go away after proper modularization */
+ pIf->addAllowedSenderLine = addAllowedSenderLine;
+ pIf->PrintAllowedSenders = PrintAllowedSenders;
+ pIf->clearAllowedSenders = clearAllowedSenders;
+ pIf->debugListenInfo = debugListenInfo;
+ pIf->create_udp_socket = create_udp_socket;
+ pIf->closeUDPListenSockets = closeUDPListenSockets;
+ pIf->isAllowedSender = isAllowedSender;
+ pIf->isAllowedSender2 = isAllowedSender2;
+ pIf->should_use_so_bsdcompat = should_use_so_bsdcompat;
+ pIf->getLocalHostname = getLocalHostname;
+ pIf->AddPermittedPeer = AddPermittedPeer;
+ pIf->DestructPermittedPeers = DestructPermittedPeers;
+ pIf->PermittedPeerWildcardMatch = PermittedPeerWildcardMatch;
+ pIf->CmpHost = CmpHost;
+ pIf->HasRestrictions = HasRestrictions;
+ pIf->GetIFIPAddr = getIFIPAddr;
+ /* data members */
+ pIf->pACLAddHostnameOnFail = &ACLAddHostnameOnFail;
+ pIf->pACLDontResolve = &ACLDontResolve;
+finalize_it:
+ENDobjQueryInterface(net)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(net, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(net)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ENDObjClassExit(net)
+
+
+/* Initialize the net class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(net, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(net)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ netClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(netClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
+/* vi:set ai:
+ */
diff --git a/runtime/net.h b/runtime/net.h
new file mode 100644
index 00000000..b196116b
--- /dev/null
+++ b/runtime/net.h
@@ -0,0 +1,169 @@
+/* Definitions for network-related stuff.
+ *
+ * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef INCLUDED_NET_H
+#define INCLUDED_NET_H
+
+#include <netinet/in.h>
+#include <sys/socket.h> /* this is needed on HP UX -- rgerhards, 2008-03-04 */
+
+typedef enum _TCPFRAMINGMODE {
+ TCP_FRAMING_OCTET_STUFFING = 0, /* traditional LF-delimited */
+ TCP_FRAMING_OCTET_COUNTING = 1 /* -transport-tls like octet count */
+ } TCPFRAMINGMODE;
+
+#define F_SET(where, flag) (where)|=(flag)
+#define F_ISSET(where, flag) ((where)&(flag))==(flag)
+#define F_UNSET(where, flag) (where)&=~(flag)
+
+#define ADDR_NAME 0x01 /* address is hostname wildcard) */
+#define ADDR_PRI6 0x02 /* use IPv6 address prior to IPv4 when resolving */
+
+#ifdef OS_BSD
+# ifndef _KERNEL
+# define s6_addr32 __u6_addr.__u6_addr32
+# endif
+#endif
+
+struct NetAddr {
+ uint8_t flags;
+ union {
+ struct sockaddr *NetAddr;
+ char *HostWildcard;
+ } addr;
+};
+
+#ifndef SO_BSDCOMPAT
+ /* this shall prevent compiler errors due to undefined name */
+# define SO_BSDCOMPAT 0
+#endif
+
+
+/* IPv6 compatibility layer for older platforms
+ * We need to handle a few things different if we are running
+ * on an older platform which does not support all the glory
+ * of IPv6. We try to limit toll on features and reliability,
+ * but obviously it is better to run rsyslog on a platform that
+ * supports everything...
+ * rgerhards, 2007-06-22
+ */
+#ifndef AI_NUMERICSERV
+# define AI_NUMERICSERV 0
+#endif
+
+
+#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
+#define SALEN(sa) ((sa)->sa_len)
+#else
+static inline size_t SALEN(struct sockaddr *sa) {
+ switch (sa->sa_family) {
+ case AF_INET: return (sizeof (struct sockaddr_in));
+ case AF_INET6: return (sizeof (struct sockaddr_in6));
+ default: return 0;
+ }
+}
+#endif
+
+struct AllowedSenders {
+ struct NetAddr allowedSender; /* ip address allowed */
+ uint8_t SignificantBits; /* defines how many bits should be discarded (eqiv to mask) */
+ struct AllowedSenders *pNext;
+};
+
+
+/* this structure is a helper to implement wildcards in permittedPeers_t. It specifies
+ * the domain component and the matching mode.
+ * rgerhards, 2008-05-27
+ */
+struct permittedPeerWildcard_s {
+ uchar *pszDomainPart;
+ size_t lenDomainPart;
+ enum {
+ PEER_WILDCARD_NONE = 0, /**< no wildcard in this entry */
+ PEER_WILDCARD_AT_START = 1, /**< wildcard at start of entry (*name) */
+ PEER_WILDCARD_AT_END = 2, /**< wildcard at end of entry (name*) */
+ PEER_WILDCARD_MATCH_ALL = 3, /**< only * wildcard, matches all values */
+ PEER_WILDCARD_EMPTY_COMPONENT = 4/**< special case: domain component empty (e.g. "..") */
+ } wildcardType;
+ permittedPeerWildcard_t *pNext;
+};
+
+/* for fingerprints and hostnames, we need to have a temporary linked list of
+ * permitted values. Unforutnately, we must also duplicate this in the netstream
+ * drivers. However, this is the best interim solution (with the least effort).
+ * A clean implementation requires that we have more capable variables and the
+ * full-fledged scripting engine available. So we have opted to do the interim
+ * solution so that our users can begin to enjoy authenticated TLS. The next step
+ * (hopefully) is to enhance RainerScript. -- rgerhards, 2008-05-19
+ */
+struct permittedPeers_s {
+ uchar *pszID;
+ enum {
+ PERM_PEER_TYPE_UNDECIDED = 0, /**< we have not yet decided the type (fine in some auth modes) */
+ PERM_PEER_TYPE_PLAIN = 1, /**< just plain text contained */
+ PERM_PEER_TYPE_WILDCARD = 2, /**< wildcards are contained, wildcard struture is filled */
+ } etryType;
+ permittedPeers_t *pNext;
+ permittedPeerWildcard_t *pWildcardRoot; /**< root of the wildcard, NULL if not initialized */
+ permittedPeerWildcard_t *pWildcardLast; /**< end of the wildcard list, NULL if not initialized */
+};
+
+
+/* interfaces */
+BEGINinterface(net) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*cvthname)(struct sockaddr_storage *f, prop_t **localName, prop_t **fqdn, prop_t **ip);
+ /* things to go away after proper modularization */
+ rsRetVal (*addAllowedSenderLine)(char* pName, uchar** ppRestOfConfLine);
+ void (*PrintAllowedSenders)(int iListToPrint);
+ void (*clearAllowedSenders)(uchar*);
+ void (*debugListenInfo)(int fd, char *type);
+ int *(*create_udp_socket)(uchar *hostname, uchar *LogPort, int bIsServer);
+ void (*closeUDPListenSockets)(int *finet);
+ int (*isAllowedSender)(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost); /* deprecated! */
+ rsRetVal (*getLocalHostname)(uchar**);
+ int (*should_use_so_bsdcompat)(void);
+ /* permitted peer handling should be replaced by something better (see comments above) */
+ rsRetVal (*AddPermittedPeer)(permittedPeers_t **ppRootPeer, uchar *pszID);
+ rsRetVal (*DestructPermittedPeers)(permittedPeers_t **ppRootPeer);
+ rsRetVal (*PermittedPeerWildcardMatch)(permittedPeers_t *pPeer, uchar *pszNameToMatch, int *pbIsMatching);
+ /* v5 interface additions */
+ int (*CmpHost)(struct sockaddr_storage *, struct sockaddr_storage*, size_t);
+ /* v6 interface additions - 2009-11-16 */
+ rsRetVal (*HasRestrictions)(uchar *, int *bHasRestrictions);
+ int (*isAllowedSender2)(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost, int bChkDNS);
+ /* v7 interface additions - 2012-03-06 */
+ rsRetVal (*GetIFIPAddr)(uchar *szif, int family, uchar *pszbuf, int lenBuf);
+ /* data members - these should go away over time... TODO */
+ int *pACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */
+ int *pACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */
+ /* v8 cvthname() signature change -- rgerhards, 2013-01-18 */
+ENDinterface(net)
+#define netCURR_IF_VERSION 8 /* increment whenever you change the interface structure! */
+
+/* prototypes */
+PROTOTYPEObj(net);
+
+/* the name of our library binary */
+#define LM_NET_FILENAME "lmnet"
+
+#endif /* #ifndef INCLUDED_NET_H */
diff --git a/runtime/netstrm.c b/runtime/netstrm.c
new file mode 100644
index 00000000..c046cf52
--- /dev/null
+++ b/runtime/netstrm.c
@@ -0,0 +1,381 @@
+/* netstrm.c
+ *
+ * This class implements a generic netstrmwork stream class. It supports
+ * sending and receiving data streams over a netstrmwork. The class abstracts
+ * the transport, though it is a safe assumption that TCP is being used.
+ * The class has a number of properties, among which are also ones to
+ * select privacy settings, eg by enabling TLS and/or GSSAPI. In the
+ * long run, this class shall provide all stream-oriented netstrmwork
+ * functionality inside rsyslog.
+ *
+ * It is a high-level class, which uses a number of helper objects
+ * to carry out its work (including, and most importantly, transport
+ * drivers).
+ *
+ * Work on this module begun 2008-04-17 by Rainer Gerhards. This code
+ * borrows from librelp's tcp.c/.h code. librelp is dual licensed and
+ * Rainer Gerhards and Adiscon GmbH have agreed to permit using the code
+ * under the terms of the GNU Lesser General Public License.
+ *
+ * Copyright 2007-2009 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "rsyslog.h"
+#include "net.h"
+#include "module-template.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "netstrms.h"
+#include "netstrm.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(netstrms)
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(netstrm) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(netstrm)
+
+
+/* destructor for the netstrm object */
+BEGINobjDestruct(netstrm) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(netstrm)
+//printf("destruct driver data %p\n", pThis->pDrvrData);
+ if(pThis->pDrvrData != NULL)
+ iRet = pThis->Drvr.Destruct(&pThis->pDrvrData);
+ENDobjDestruct(netstrm)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+netstrmConstructFinalize(netstrm_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData));
+finalize_it:
+ RETiRet;
+}
+
+/* abort a connection. This is much like Destruct(), but tries
+ * to discard any unsent data. -- rgerhards, 2008-03-24
+ */
+static rsRetVal
+AbortDestruct(netstrm_t **ppThis)
+{
+ DEFiRet;
+ assert(ppThis != NULL);
+ ISOBJ_TYPE_assert((*ppThis), netstrm);
+
+ /* we do NOT exit on error, because that would make things worse */
+ (*ppThis)->Drvr.Abort((*ppThis)->pDrvrData);
+ iRet = netstrmDestruct(ppThis);
+
+ RETiRet;
+}
+
+
+/* accept an incoming connection request
+ * The netstrm instance that had the incoming request must be provided. If
+ * the connection request succeeds, a new netstrm object is created and
+ * passed back to the caller. The caller is responsible for destructing it.
+ * pReq is the nsd_t obj that has the accept request.
+ * rgerhards, 2008-04-21
+ */
+static rsRetVal
+AcceptConnReq(netstrm_t *pThis, netstrm_t **ppNew)
+{
+ nsd_t *pNewNsd = NULL;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ assert(ppNew != NULL);
+
+ /* accept the new connection */
+ CHKiRet(pThis->Drvr.AcceptConnReq(pThis->pDrvrData, &pNewNsd));
+ /* construct our object so that we can use it... */
+ CHKiRet(objUse(netstrms, DONT_LOAD_LIB)); /* use netstrms obj if not already done so */
+ CHKiRet(netstrms.CreateStrm(pThis->pNS, ppNew));
+ (*ppNew)->pDrvrData = pNewNsd;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ /* the close may be redundant, but that doesn't hurt... */
+ if(pNewNsd != NULL)
+ pThis->Drvr.Destruct(&pNewNsd);
+ }
+
+ RETiRet;
+}
+
+
+/* make the netstrm listen to specified port and IP.
+ * pLstnIP points to the port to listen to (NULL means "all"),
+ * iMaxSess has the maximum number of sessions permitted (this ist just a hint).
+ * pLstnPort must point to a port name or number. NULL is NOT permitted.
+ * rgerhards, 2008-04-22
+ */
+static rsRetVal
+LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ uchar *pLstnPort, uchar *pLstnIP, int iSessMax)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pNS, netstrms);
+ assert(fAddLstn != NULL);
+ assert(pLstnPort != NULL);
+
+ CHKiRet(pNS->Drvr.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* receive data from a tcp socket
+ * The lenBuf parameter must contain the max buffer size on entry and contains
+ * the number of octets read (or -1 in case of error) on exit. This function
+ * never blocks, not even when called on a blocking socket. That is important
+ * for client sockets, which are set to block during send, but should not
+ * block when trying to read data. If *pLenBuf is -1, an error occured and
+ * errno holds the exact error cause.
+ * rgerhards, 2008-03-17
+ */
+static rsRetVal
+Rcv(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+//printf("Rcv %p\n", pThis);
+ iRet = pThis->Drvr.Rcv(pThis->pDrvrData, pBuf, pLenBuf);
+ RETiRet;
+}
+
+/* here follows a number of methods that shuffle authentication settings down
+ * to the drivers. Drivers not supporting these settings may return an error
+ * state.
+ * -------------------------------------------------------------------------- */
+
+/* set the driver mode
+ * rgerhards, 2008-04-28
+ */
+static rsRetVal
+SetDrvrMode(netstrm_t *pThis, int iMode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetMode(pThis->pDrvrData, iMode);
+ RETiRet;
+}
+
+
+/* set the driver authentication mode -- rgerhards, 2008-05-16
+ */
+static rsRetVal
+SetDrvrAuthMode(netstrm_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetAuthMode(pThis->pDrvrData, mode);
+ RETiRet;
+}
+
+
+/* set the driver's permitted peers -- rgerhards, 2008-05-19 */
+static rsRetVal
+SetDrvrPermPeers(netstrm_t *pThis, permittedPeers_t *pPermPeers)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetPermPeers(pThis->pDrvrData, pPermPeers);
+ RETiRet;
+}
+
+
+/* End of methods to shuffle autentication settings to the driver.
+ * -------------------------------------------------------------------------- */
+
+
+/* send a buffer. On entry, pLenBuf contains the number of octets to
+ * write. On exit, it contains the number of octets actually written.
+ * If this number is lower than on entry, only a partial buffer has
+ * been written.
+ * rgerhards, 2008-03-19
+ */
+static rsRetVal
+Send(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.Send(pThis->pDrvrData, pBuf, pLenBuf);
+ RETiRet;
+}
+
+/* Enable Keep-Alive handling for those drivers that support it.
+ * rgerhards, 2009-06-02
+ */
+static rsRetVal
+EnableKeepAlive(netstrm_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.EnableKeepAlive(pThis->pDrvrData);
+ RETiRet;
+}
+
+
+
+/* check connection - slim wrapper for NSD driver function */
+static rsRetVal
+CheckConnection(netstrm_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ return pThis->Drvr.CheckConnection(pThis->pDrvrData);
+}
+
+
+/* get remote hname - slim wrapper for NSD driver function */
+static rsRetVal
+GetRemoteHName(netstrm_t *pThis, uchar **ppsz)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.GetRemoteHName(pThis->pDrvrData, ppsz);
+ RETiRet;
+}
+
+
+/* get remote IP - slim wrapper for NSD driver function */
+static rsRetVal
+GetRemoteIP(netstrm_t *pThis, prop_t **ip)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.GetRemoteIP(pThis->pDrvrData, ip);
+ RETiRet;
+}
+
+
+/* get remote addr - slim wrapper for NSD driver function */
+static rsRetVal
+GetRemAddr(netstrm_t *pThis, struct sockaddr_storage **ppAddr)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.GetRemAddr(pThis->pDrvrData, ppAddr);
+ RETiRet;
+}
+
+
+/* open a connection to a remote host (server).
+ * rgerhards, 2008-03-19
+ */
+static rsRetVal
+Connect(netstrm_t *pThis, int family, uchar *port, uchar *host)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ assert(port != NULL);
+ assert(host != NULL);
+ iRet = pThis->Drvr.Connect(pThis->pDrvrData, family, port, host);
+ RETiRet;
+}
+
+
+/* Provide access to the underlying OS socket. This is dirty
+ * and scheduled to be removed. Does not work with all nsd drivers.
+ * See comment in netstrm interface for details.
+ * rgerhards, 2008-05-05
+ */
+static rsRetVal
+GetSock(netstrm_t *pThis, int *pSock)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ assert(pSock != NULL);
+ iRet = pThis->Drvr.GetSock(pThis->pDrvrData, pSock);
+ RETiRet;
+}
+
+
+/* queryInterface function
+ */
+BEGINobjQueryInterface(netstrm)
+CODESTARTobjQueryInterface(netstrm)
+ if(pIf->ifVersion != netstrmCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = netstrmConstruct;
+ pIf->ConstructFinalize = netstrmConstructFinalize;
+ pIf->Destruct = netstrmDestruct;
+ pIf->AbortDestruct = AbortDestruct;
+ pIf->Rcv = Rcv;
+ pIf->Send = Send;
+ pIf->Connect = Connect;
+ pIf->LstnInit = LstnInit;
+ pIf->AcceptConnReq = AcceptConnReq;
+ pIf->GetRemoteHName = GetRemoteHName;
+ pIf->GetRemoteIP = GetRemoteIP;
+ pIf->GetRemAddr = GetRemAddr;
+ pIf->SetDrvrMode = SetDrvrMode;
+ pIf->SetDrvrAuthMode = SetDrvrAuthMode;
+ pIf->SetDrvrPermPeers = SetDrvrPermPeers;
+ pIf->CheckConnection = CheckConnection;
+ pIf->GetSock = GetSock;
+ pIf->EnableKeepAlive = EnableKeepAlive;
+finalize_it:
+ENDobjQueryInterface(netstrm)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(netstrm, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(netstrm)
+ /* release objects we no longer need */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(netstrms, DONT_LOAD_LIB);
+ENDObjClassExit(netstrm)
+
+
+/* Initialize the netstrm class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(netstrm, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(netstrm)
+/* vi:set ai:
+ */
diff --git a/runtime/netstrm.h b/runtime/netstrm.h
new file mode 100644
index 00000000..4ef24229
--- /dev/null
+++ b/runtime/netstrm.h
@@ -0,0 +1,88 @@
+/* Definitions for the stream-based netstrmworking class.
+ *
+ * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef INCLUDED_NETSTRM_H
+#define INCLUDED_NETSTRM_H
+
+#include "netstrms.h"
+
+/* the netstrm object */
+struct netstrm_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ nsd_t *pDrvrData; /**< the driver's data elements (at most other places, this is called pNsd) */
+ nsd_if_t Drvr; /**< our stream driver */
+ void *pUsr; /**< pointer to user-provided data structure */
+ netstrms_t *pNS; /**< pointer to our netstream subsystem object */
+};
+
+
+/* interface */
+BEGINinterface(netstrm) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(netstrm_t **ppThis);
+ rsRetVal (*ConstructFinalize)(netstrm_t *pThis);
+ rsRetVal (*Destruct)(netstrm_t **ppThis);
+ rsRetVal (*AbortDestruct)(netstrm_t **ppThis);
+ rsRetVal (*LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*)(void*,netstrm_t*),
+ uchar *pLstnPort, uchar *pLstnIP, int iSessMax);
+ rsRetVal (*AcceptConnReq)(netstrm_t *pThis, netstrm_t **ppNew);
+ rsRetVal (*Rcv)(netstrm_t *pThis, uchar *pRcvBuf, ssize_t *pLenBuf);
+ rsRetVal (*Send)(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf);
+ rsRetVal (*Connect)(netstrm_t *pThis, int family, unsigned char *port, unsigned char *host);
+ rsRetVal (*GetRemoteHName)(netstrm_t *pThis, uchar **pszName);
+ rsRetVal (*GetRemoteIP)(netstrm_t *pThis, prop_t **ip);
+ rsRetVal (*SetDrvrMode)(netstrm_t *pThis, int iMode);
+ rsRetVal (*SetDrvrAuthMode)(netstrm_t *pThis, uchar*);
+ rsRetVal (*SetDrvrPermPeers)(netstrm_t *pThis, permittedPeers_t*);
+ rsRetVal (*CheckConnection)(netstrm_t *pThis); /* This is a trick mostly for plain tcp syslog */
+ /* the GetSock() below is a hack to make imgssapi work. In the long term,
+ * we should migrate imgssapi to a stream driver, which will relieve us of
+ * this problem. Please note that nobody else should use GetSock(). Using it
+ * will also tie the caller to nsd_ptcp, because other drivers may not support
+ * it at all. Once the imgssapi problem is solved, GetSock should be removed from
+ * this interface. -- rgerhards, 2008-05-05
+ */
+ rsRetVal (*GetSock)(netstrm_t *pThis, int *pSock);
+ rsRetVal (*GetRemAddr)(netstrm_t *pThis, struct sockaddr_storage **ppAddr);
+ /* getRemAddr() is an aid needed by the legacy ACL system. It exposes the remote
+ * peer's socket addr structure, so that the legacy matching functions can work on
+ * it. Note that this ties netstream drivers to things that can be implemented over
+ * sockets - not really desirable, but not the end of the world... TODO: should be
+ * reconsidered when a new ACL system is build. -- rgerhards, 2008-12-01
+ */
+ /* v4 */
+ rsRetVal (*EnableKeepAlive)(netstrm_t *pThis);
+ENDinterface(netstrm)
+#define netstrmCURR_IF_VERSION 6 /* increment whenever you change the interface structure! */
+/* interface version 3 added GetRemAddr()
+ * interface version 4 added EnableKeepAlive() -- rgerhards, 2009-06-02
+ * interface version 5 changed return of CheckConnection from void to rsRetVal -- alorbach, 2012-09-06
+ * interface version 6 changed signature of GetRemoteIP() -- rgerhards, 2013-01-21
+ * */
+
+/* prototypes */
+PROTOTYPEObj(netstrm);
+
+/* the name of our library binary */
+#define LM_NETSTRM_FILENAME LM_NETSTRMS_FILENAME
+
+#endif /* #ifndef INCLUDED_NETSTRM_H */
diff --git a/runtime/netstrms.c b/runtime/netstrms.c
new file mode 100644
index 00000000..0122064d
--- /dev/null
+++ b/runtime/netstrms.c
@@ -0,0 +1,331 @@
+/* netstrms.c
+ *
+ * Work on this module begung 2008-04-23 by Rainer Gerhards.
+ *
+ * Copyright 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+
+#include "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "nsd.h"
+#include "netstrm.h"
+#include "nssel.h"
+#include "nspoll.h"
+#include "netstrms.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+//DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(netstrm)
+
+
+/* load our low-level driver. This must be done before any
+ * driver-specific functions (allmost all...) can be carried
+ * out. Note that the driver's .ifIsLoaded is correctly
+ * initialized by calloc() and we depend on that.
+ * WARNING: this code is mostly identical to similar code in
+ * nssel.c - TODO: abstract it and move it to some common place.
+ * rgerhards, 2008-04-18
+ */
+static rsRetVal
+loadDrvr(netstrms_t *pThis)
+{
+ DEFiRet;
+ uchar *pBaseDrvrName;
+ uchar szDrvrName[48]; /* 48 shall be large enough */
+
+ pBaseDrvrName = pThis->pBaseDrvrName;
+ if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */
+ pBaseDrvrName = glbl.GetDfltNetstrmDrvr();
+ if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsd_%s", pBaseDrvrName) == sizeof(szDrvrName))
+ ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG);
+ CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName));
+
+ pThis->Drvr.ifVersion = nsdCURR_IF_VERSION;
+ /* The pDrvrName+2 below is a hack to obtain the object name. It
+ * safes us to have yet another variable with the name without "lm" in
+ * front of it. If we change the module load interface, we may re-think
+ * about this hack, but for the time being it is efficient and clean
+ * enough. -- rgerhards, 2008-04-18
+ */
+ CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, szDrvrName, (void*) &pThis->Drvr));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->pDrvrName != NULL)
+ free(pThis->pDrvrName);
+ pThis->pDrvrName = NULL;
+ }
+ RETiRet;
+}
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(netstrms) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(netstrms)
+
+
+/* destructor for the netstrms object */
+BEGINobjDestruct(netstrms) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(netstrms)
+ /* and now we must release our driver, if we got one. We use the presence of
+ * a driver name string as load indicator (because we also need that string
+ * to release the driver
+ */
+ if(pThis->pDrvrName != NULL) {
+ obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, pThis->pDrvrName, (void*) &pThis->Drvr);
+ free(pThis->pDrvrName);
+ }
+ if(pThis->pszDrvrAuthMode != NULL) {
+ free(pThis->pszDrvrAuthMode);
+ pThis->pszDrvrAuthMode = NULL;
+ }
+ if(pThis->pBaseDrvrName != NULL) {
+ free(pThis->pBaseDrvrName);
+ pThis->pBaseDrvrName = NULL;
+ }
+ENDobjDestruct(netstrms)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+netstrmsConstructFinalize(netstrms_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ CHKiRet(loadDrvr(pThis));
+finalize_it:
+ RETiRet;
+}
+
+
+/* set the base driver name. If the driver name
+ * is set to NULL, the previously set name is deleted but
+ * no name set again (which results in the system default being
+ * used)-- rgerhards, 2008-05-05
+ */
+static rsRetVal
+SetDrvrName(netstrms_t *pThis, uchar *pszName)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ if(pThis->pBaseDrvrName != NULL) {
+ free(pThis->pBaseDrvrName);
+ pThis->pBaseDrvrName = NULL;
+ }
+
+ if(pszName != NULL) {
+ CHKmalloc(pThis->pBaseDrvrName = (uchar*) strdup((char*) pszName));
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* set the driver's permitted peers -- rgerhards, 2008-05-19 */
+static rsRetVal
+SetDrvrPermPeers(netstrms_t *pThis, permittedPeers_t *pPermPeers)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ pThis->pPermPeers = pPermPeers;
+ RETiRet;
+}
+/* return the driver's permitted peers
+ * We use non-standard calling conventions because it makes an awful lot
+ * of sense here.
+ * rgerhards, 2008-05-19
+ */
+static permittedPeers_t*
+GetDrvrPermPeers(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->pPermPeers;
+}
+
+
+/* set the driver auth mode -- rgerhards, 2008-05-19 */
+static rsRetVal
+SetDrvrAuthMode(netstrms_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ CHKmalloc(pThis->pszDrvrAuthMode = (uchar*)strdup((char*)mode));
+finalize_it:
+ RETiRet;
+}
+/* return the driver auth mode
+ * We use non-standard calling conventions because it makes an awful lot
+ * of sense here.
+ * rgerhards, 2008-05-19
+ */
+static uchar*
+GetDrvrAuthMode(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->pszDrvrAuthMode;
+}
+
+
+/* set the driver mode -- rgerhards, 2008-04-30 */
+static rsRetVal
+SetDrvrMode(netstrms_t *pThis, int iMode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ pThis->iDrvrMode = iMode;
+ RETiRet;
+}
+
+
+/* return the driver mode
+ * We use non-standard calling conventions because it makes an awful lot
+ * of sense here.
+ * rgerhards, 2008-04-30
+ */
+static int
+GetDrvrMode(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->iDrvrMode;
+}
+
+
+/* create an instance of a netstrm object. It is initialized with default
+ * values. The current driver is used. The caller may set netstrm properties
+ * and must call ConstructFinalize().
+ */
+static rsRetVal
+CreateStrm(netstrms_t *pThis, netstrm_t **ppStrm)
+{
+ netstrm_t *pStrm = NULL;
+ DEFiRet;
+
+ CHKiRet(objUse(netstrm, DONT_LOAD_LIB));
+ CHKiRet(netstrm.Construct(&pStrm));
+ /* we copy over our driver structure. We could provide a pointer to
+ * ourselves, but that costs some performance on each driver invocation.
+ * As we already have hefty indirection (and thus performance toll), I
+ * prefer to copy over the function pointers here. -- rgerhards, 2008-04-23
+ */
+ memcpy(&pStrm->Drvr, &pThis->Drvr, sizeof(pThis->Drvr));
+ pStrm->pNS = pThis;
+
+ *ppStrm = pStrm;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pStrm != NULL)
+ netstrm.Destruct(&pStrm);
+ }
+ RETiRet;
+}
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(netstrms)
+CODESTARTobjQueryInterface(netstrms)
+ if(pIf->ifVersion != netstrmsCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = netstrmsConstruct;
+ pIf->ConstructFinalize = netstrmsConstructFinalize;
+ pIf->Destruct = netstrmsDestruct;
+ pIf->CreateStrm = CreateStrm;
+ pIf->SetDrvrName = SetDrvrName;
+ pIf->SetDrvrMode = SetDrvrMode;
+ pIf->GetDrvrMode = GetDrvrMode;
+ pIf->SetDrvrAuthMode = SetDrvrAuthMode;
+ pIf->GetDrvrAuthMode = GetDrvrAuthMode;
+ pIf->SetDrvrPermPeers = SetDrvrPermPeers;
+ pIf->GetDrvrPermPeers = GetDrvrPermPeers;
+finalize_it:
+ENDobjQueryInterface(netstrms)
+
+
+/* exit our class */
+BEGINObjClassExit(netstrms, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(netstrms)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(netstrm, DONT_LOAD_LIB);
+ENDObjClassExit(netstrms)
+
+
+/* Initialize the netstrms class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(netstrms, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(netstrms)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ nsselClassExit();
+ nspollClassExit();
+ netstrmsClassExit();
+ netstrmClassExit(); /* we use this object, so we must exit it after we are finished */
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(netstrmClassInit(pModInfo));
+ CHKiRet(nsselClassInit(pModInfo));
+ CHKiRet(nspollClassInit(pModInfo));
+ CHKiRet(netstrmsClassInit(pModInfo));
+ENDmodInit
+/* vi:set ai:
+ */
diff --git a/runtime/netstrms.h b/runtime/netstrms.h
new file mode 100644
index 00000000..3f686af6
--- /dev/null
+++ b/runtime/netstrms.h
@@ -0,0 +1,64 @@
+/* Definitions for the stream-based netstrmsworking class.
+ *
+ * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef INCLUDED_NETSTRMS_H
+#define INCLUDED_NETSTRMS_H
+
+#include "nsd.h" /* we need our driver interface to be defined */
+
+/* the netstrms object */
+struct netstrms_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */
+ uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */
+ int iDrvrMode; /**< current default driver mode */
+ uchar *pszDrvrAuthMode; /**< current driver authentication mode */
+ permittedPeers_t *pPermPeers;/**< current driver's permitted peers */
+
+ nsd_if_t Drvr; /**< our stream driver */
+};
+
+
+/* interface */
+BEGINinterface(netstrms) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(netstrms_t **ppThis);
+ rsRetVal (*ConstructFinalize)(netstrms_t *pThis);
+ rsRetVal (*Destruct)(netstrms_t **ppThis);
+ rsRetVal (*CreateStrm)(netstrms_t *pThis, netstrm_t **ppStrm);
+ rsRetVal (*SetDrvrName)(netstrms_t *pThis, uchar *pszName);
+ rsRetVal (*SetDrvrMode)(netstrms_t *pThis, int iMode);
+ rsRetVal (*SetDrvrAuthMode)(netstrms_t *pThis, uchar*);
+ rsRetVal (*SetDrvrPermPeers)(netstrms_t *pThis, permittedPeers_t*);
+ int (*GetDrvrMode)(netstrms_t *pThis);
+ uchar* (*GetDrvrAuthMode)(netstrms_t *pThis);
+ permittedPeers_t* (*GetDrvrPermPeers)(netstrms_t *pThis);
+ENDinterface(netstrms)
+#define netstrmsCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+/* prototypes */
+PROTOTYPEObj(netstrms);
+
+/* the name of our library binary */
+#define LM_NETSTRMS_FILENAME "lmnetstrms"
+
+#endif /* #ifndef INCLUDED_NETSTRMS_H */
diff --git a/runtime/nsd.h b/runtime/nsd.h
new file mode 100644
index 00000000..aa3662a4
--- /dev/null
+++ b/runtime/nsd.h
@@ -0,0 +1,109 @@
+/* The interface definition for "NetStream Drivers" (nsd).
+ *
+ * This is just an abstract driver interface, which needs to be
+ * implemented by concrete classes. As such, no nsd data type itself
+ * is defined.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_NSD_H
+#define INCLUDED_NSD_H
+
+#include <sys/socket.h>
+
+/**
+ * The following structure is a set of descriptors that need to be processed.
+ * This set will be the result of the epoll call and be used
+ * in the actual request processing stage. -- rgerhards, 2011-01-24
+ */
+struct nsd_epworkset_s {
+ int id;
+ void *pUsr;
+};
+
+enum nsdsel_waitOp_e {
+ NSDSEL_RD = 1,
+ NSDSEL_WR = 2,
+ NSDSEL_RDWR = 3
+}; /**< the operation we wait for */
+
+/* nsd_t is actually obj_t (which is somewhat better than void* but in essence
+ * much the same).
+ */
+
+/* interface */
+BEGINinterface(nsd) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(nsd_t **ppThis);
+ rsRetVal (*Destruct)(nsd_t **ppThis);
+ rsRetVal (*Abort)(nsd_t *pThis);
+ rsRetVal (*Rcv)(nsd_t *pThis, uchar *pRcvBuf, ssize_t *pLenBuf);
+ rsRetVal (*Send)(nsd_t *pThis, uchar *pBuf, ssize_t *pLenBuf);
+ rsRetVal (*Connect)(nsd_t *pThis, int family, unsigned char *port, unsigned char *host);
+ rsRetVal (*LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ uchar *pLstnPort, uchar *pLstnIP, int iSessMax);
+ rsRetVal (*AcceptConnReq)(nsd_t *pThis, nsd_t **ppThis);
+ rsRetVal (*GetRemoteHName)(nsd_t *pThis, uchar **pszName);
+ rsRetVal (*GetRemoteIP)(nsd_t *pThis, prop_t **ip);
+ rsRetVal (*SetMode)(nsd_t *pThis, int mode); /* sets a driver specific mode - see driver doc for details */
+ rsRetVal (*SetAuthMode)(nsd_t *pThis, uchar*); /* sets a driver specific mode - see driver doc for details */
+ rsRetVal (*SetPermPeers)(nsd_t *pThis, permittedPeers_t*); /* sets driver permitted peers for auth needs */
+ rsRetVal (*CheckConnection)(nsd_t *pThis); /* This is a trick mostly for plain tcp syslog */
+ rsRetVal (*GetSock)(nsd_t *pThis, int *pSock);
+ rsRetVal (*SetSock)(nsd_t *pThis, int sock);
+ /* GetSock() and SetSock() return an error if the driver does not use plain
+ * OS sockets. This interface is primarily meant as an internal aid for
+ * those drivers that utilize the nsd_ptcp to do some of their work.
+ */
+ rsRetVal (*GetRemAddr)(nsd_t *pThis, struct sockaddr_storage **ppAddr);
+ /* getRemAddr() is an aid needed by the legacy ACL system. It exposes the remote
+ * peer's socket addr structure, so that the legacy matching functions can work on
+ * it. Note that this ties netstream drivers to things that can be implemented over
+ * sockets - not really desirable, but not the end of the world... TODO: should be
+ * reconsidered when a new ACL system is build. -- rgerhards, 2008-12-01
+ */
+ /* v5 */
+ rsRetVal (*EnableKeepAlive)(nsd_t *pThis);
+ENDinterface(nsd)
+#define nsdCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */
+/* interface version 4 added GetRemAddr()
+ * interface version 5 added EnableKeepAlive() -- rgerhards, 2009-06-02
+ * interface version 6 changed return of CheckConnection from void to rsRetVal -- alorbach, 2012-09-06
+ * interface version 7 changed signature ofGetRempoteIP() -- rgerhards, 2013-01-21
+ */
+
+/* interface for the select call */
+BEGINinterface(nsdsel) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(nsdsel_t **ppThis);
+ rsRetVal (*Destruct)(nsdsel_t **ppThis);
+ rsRetVal (*Add)(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp);
+ rsRetVal (*Select)(nsdsel_t *pNsdsel, int *piNumReady);
+ rsRetVal (*IsReady)(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady);
+ENDinterface(nsdsel)
+#define nsdselCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+/* interface for the epoll call */
+BEGINinterface(nsdpoll) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(nsdpoll_t **ppThis);
+ rsRetVal (*Destruct)(nsdpoll_t **ppThis);
+ rsRetVal (*Ctl)(nsdpoll_t *pNsdpoll, nsd_t *pNsd, int id, void *pUsr, int mode, int op);
+ rsRetVal (*Wait)(nsdpoll_t *pNsdpoll, int timeout, int *numReady, nsd_epworkset_t workset[]);
+ENDinterface(nsdpoll)
+#define nsdpollCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+#endif /* #ifndef INCLUDED_NSD_H */
diff --git a/runtime/nsd_gtls.c b/runtime/nsd_gtls.c
new file mode 100644
index 00000000..6ef4feba
--- /dev/null
+++ b/runtime/nsd_gtls.c
@@ -0,0 +1,1750 @@
+/* nsd_gtls.c
+ *
+ * An implementation of the nsd interface for GnuTLS.
+ *
+ * Copyright (C) 2007, 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
+# include <gcrypt.h>
+#endif
+#include <errno.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pthread.h>
+
+#include "rsyslog.h"
+#include "syslogd-types.h"
+#include "module-template.h"
+#include "cfsysline.h"
+#include "obj.h"
+#include "stringbuf.h"
+#include "errmsg.h"
+#include "net.h"
+#include "datetime.h"
+#include "nsd_ptcp.h"
+#include "nsdsel_gtls.h"
+#include "nsd_gtls.h"
+
+/* things to move to some better place/functionality - TODO */
+#define CRLFILE "crl.pem"
+
+
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif
+MODULE_TYPE_LIB
+MODULE_TYPE_KEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(net)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(nsd_ptcp)
+
+static int bGlblSrvrInitDone = 0; /**< 0 - server global init not yet done, 1 - already done */
+
+static pthread_mutex_t mutGtlsStrerror; /**< a mutex protecting the potentially non-reentrant gtlStrerror() function */
+
+/* a macro to check GnuTLS calls against unexpected errors */
+#define CHKgnutls(x) \
+ if((gnuRet = (x)) != 0) { \
+ uchar *pErr = gtlsStrerror(gnuRet); \
+ dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr); \
+ free(pErr); \
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \
+ }
+
+
+/* ------------------------------ GnuTLS specifics ------------------------------ */
+static gnutls_certificate_credentials xcred;
+
+#ifdef DEBUG
+#if 0 /* uncomment, if needed some time again -- DEV Debug only */
+/* This defines a log function to be provided to GnuTLS. It hopefully
+ * helps us track down hard to find problems.
+ * rgerhards, 2008-06-20
+ */
+static void logFunction(int level, const char *msg)
+{
+ dbgprintf("GnuTLS log msg, level %d: %s\n", level, msg);
+}
+#endif
+#endif /* #ifdef DEBUG */
+
+
+/* read in the whole content of a file. The caller is responsible for
+ * freeing the buffer. To prevent DOS, this function can NOT read
+ * files larger than 1MB (which still is *very* large).
+ * rgerhards, 2008-05-26
+ */
+static rsRetVal
+readFile(uchar *pszFile, gnutls_datum_t *pBuf)
+{
+ int fd;
+ struct stat stat_st;
+ DEFiRet;
+
+ assert(pszFile != NULL);
+ assert(pBuf != NULL);
+
+ pBuf->data = NULL;
+
+ if((fd = open((char*)pszFile, O_RDONLY)) == -1) {
+ errmsg.LogError(0, RS_RET_FILE_NOT_FOUND, "can not read file '%s'", pszFile);
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+
+ }
+
+ if(fstat(fd, &stat_st) == -1) {
+ errmsg.LogError(0, RS_RET_FILE_NO_STAT, "can not stat file '%s'", pszFile);
+ ABORT_FINALIZE(RS_RET_FILE_NO_STAT);
+ }
+
+ /* 1MB limit */
+ if(stat_st.st_size > 1024 * 1024) {
+ errmsg.LogError(0, RS_RET_FILE_TOO_LARGE, "file '%s' too large, max 1MB", pszFile);
+ ABORT_FINALIZE(RS_RET_FILE_TOO_LARGE);
+ }
+
+ CHKmalloc(pBuf->data = MALLOC(stat_st.st_size));
+ pBuf->size = stat_st.st_size;
+ if(read(fd, pBuf->data, stat_st.st_size) != stat_st.st_size) {
+ errmsg.LogError(0, RS_RET_IO_ERROR, "error or incomplete read of file '%s'", pszFile);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ close(fd);
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pBuf->data != NULL) {
+ free(pBuf->data);
+ pBuf->data = NULL;
+ pBuf->size = 0;
+ }
+ }
+ RETiRet;
+}
+
+
+/* Load the certificate and the private key into our own store. We need to do
+ * this in the client case, to support fingerprint authentication. In that case,
+ * we may be presented no matching root certificate, but we must provide ours.
+ * The only way to do that is via the cert callback interface, but for it we
+ * need to load certificates into our private store.
+ * rgerhards, 2008-05-26
+ */
+static rsRetVal
+gtlsLoadOurCertKey(nsd_gtls_t *pThis)
+{
+ DEFiRet;
+ int gnuRet;
+ gnutls_datum_t data = { NULL, 0 };
+ uchar *keyFile;
+ uchar *certFile;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ certFile = glbl.GetDfltNetstrmDrvrCertFile();
+ keyFile = glbl.GetDfltNetstrmDrvrKeyFile();
+
+ if(certFile == NULL || keyFile == NULL) {
+ /* in this case, we can not set our certificate. If we are
+ * a client and the server is running in "anon" auth mode, this
+ * may be well acceptable. In other cases, we will see some
+ * more error messages down the road. -- rgerhards, 2008-07-02
+ */
+ dbgprintf("our certificate is not set, file name values are cert: '%s', key: '%s'\n",
+ certFile, keyFile);
+ ABORT_FINALIZE(RS_RET_CERTLESS);
+ }
+
+ /* try load certificate */
+ CHKiRet(readFile(certFile, &data));
+ CHKgnutls(gnutls_x509_crt_init(&pThis->ourCert));
+ pThis->bOurCertIsInit = 1;
+ CHKgnutls(gnutls_x509_crt_import(pThis->ourCert, &data, GNUTLS_X509_FMT_PEM));
+ free(data.data);
+ data.data = NULL;
+
+ /* try load private key */
+ CHKiRet(readFile(keyFile, &data));
+ CHKgnutls(gnutls_x509_privkey_init(&pThis->ourKey));
+ pThis->bOurKeyIsInit = 1;
+ CHKgnutls(gnutls_x509_privkey_import(pThis->ourKey, &data, GNUTLS_X509_FMT_PEM));
+ free(data.data);
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(data.data != NULL)
+ free(data.data);
+ if(pThis->bOurCertIsInit) {
+ gnutls_x509_crt_deinit(pThis->ourCert);
+ pThis->bOurCertIsInit = 0;
+ }
+ if(pThis->bOurKeyIsInit) {
+ gnutls_x509_privkey_deinit(pThis->ourKey);
+ pThis->bOurKeyIsInit = 0;
+ }
+ }
+ RETiRet;
+}
+
+
+/* This callback must be associated with a session by calling
+ * gnutls_certificate_client_set_retrieve_function(session, cert_callback),
+ * before a handshake. We will always return the configured certificate,
+ * even if it does not match the peer's trusted CAs. This is necessary
+ * to use self-signed certs in fingerprint mode. And, yes, this usage
+ * of the callback is quite a hack. But it seems the only way to
+ * obey to the IETF -transport-tls I-D.
+ * Note: GnuTLS requires the function to return 0 on success and
+ * -1 on failure.
+ * rgerhards, 2008-05-27
+ */
+static int
+gtlsClientCertCallback(gnutls_session session,
+ __attribute__((unused)) const gnutls_datum* req_ca_rdn, int __attribute__((unused)) nreqs,
+ __attribute__((unused)) const gnutls_pk_algorithm* sign_algos, int __attribute__((unused)) sign_algos_length,
+ gnutls_retr_st *st)
+{
+ nsd_gtls_t *pThis;
+
+ pThis = (nsd_gtls_t*) gnutls_session_get_ptr(session);
+
+ st->type = GNUTLS_CRT_X509;
+ st->ncerts = 1;
+ st->cert.x509 = &pThis->ourCert;
+ st->key.x509 = pThis->ourKey;
+ st->deinit_all = 0;
+
+ return 0;
+}
+
+
+/* This function extracts some information about this session's peer
+ * certificate. Works for X.509 certificates only. Adds all
+ * of the info to a cstr_t, which is handed over to the caller.
+ * Caller must destruct it when no longer needed.
+ * rgerhards, 2008-05-21
+ */
+static rsRetVal
+gtlsGetCertInfo(nsd_gtls_t *pThis, cstr_t **ppStr)
+{
+ uchar szBufA[1024];
+ uchar *szBuf = szBufA;
+ size_t szBufLen = sizeof(szBufA), tmp;
+ unsigned int algo, bits;
+ time_t expiration_time, activation_time;
+ const gnutls_datum *cert_list;
+ unsigned cert_list_size = 0;
+ gnutls_x509_crt cert;
+ cstr_t *pStr = NULL;
+ int gnuRet;
+ DEFiRet;
+ unsigned iAltName;
+
+ assert(ppStr != NULL);
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509)
+ return RS_RET_TLS_CERT_ERR;
+
+ cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size);
+ CHKiRet(rsCStrConstructFromszStrf(&pStr, "peer provided %d certificate(s). ", cert_list_size));
+
+ if(cert_list_size > 0) {
+ /* we only print information about the first certificate */
+ CHKgnutls(gnutls_x509_crt_init(&cert));
+ CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER));
+
+ expiration_time = gnutls_x509_crt_get_expiration_time(cert);
+ activation_time = gnutls_x509_crt_get_activation_time(cert);
+ ctime_r(&activation_time, szBuf);
+ szBuf[strlen(szBuf) - 1] = '\0'; /* strip linefeed */
+ CHKiRet(rsCStrAppendStrf(pStr, (uchar*)"Certificate 1 info: "
+ "certificate valid from %s ", szBuf));
+ ctime_r(&expiration_time, szBuf);
+ szBuf[strlen(szBuf) - 1] = '\0'; /* strip linefeed */
+ CHKiRet(rsCStrAppendStrf(pStr, "to %s; ", szBuf));
+
+ /* Extract some of the public key algorithm's parameters */
+ algo = gnutls_x509_crt_get_pk_algorithm(cert, &bits);
+ CHKiRet(rsCStrAppendStrf(pStr, "Certificate public key: %s; ",
+ gnutls_pk_algorithm_get_name(algo)));
+
+ /* names */
+ tmp = szBufLen;
+ if(gnutls_x509_crt_get_dn(cert, szBuf, &tmp)
+ == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ szBufLen = tmp;
+ szBuf = malloc(tmp);
+ gnutls_x509_crt_get_dn(cert, szBuf, &tmp);
+ }
+ CHKiRet(rsCStrAppendStrf(pStr, "DN: %s; ", szBuf));
+
+ tmp = szBufLen;
+ if(gnutls_x509_crt_get_issuer_dn(cert, szBuf, &tmp)
+ == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ szBufLen = tmp;
+ szBuf = realloc((szBuf == szBufA) ? NULL : szBuf, tmp);
+ gnutls_x509_crt_get_issuer_dn(cert, szBuf, &tmp);
+ }
+ CHKiRet(rsCStrAppendStrf(pStr, "Issuer DN: %s; ", szBuf));
+
+ /* dNSName alt name */
+ iAltName = 0;
+ while(1) { /* loop broken below */
+ tmp = szBufLen;
+ gnuRet = gnutls_x509_crt_get_subject_alt_name(cert, iAltName,
+ szBuf, &tmp, NULL);
+ if(gnuRet == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ szBufLen = tmp;
+ szBuf = realloc((szBuf == szBufA) ? NULL : szBuf, tmp);
+ continue;
+ } else if(gnuRet < 0)
+ break;
+ else if(gnuRet == GNUTLS_SAN_DNSNAME) {
+ /* we found it! */
+ CHKiRet(rsCStrAppendStrf(pStr, "SAN:DNSname: %s; ", szBuf));
+ /* do NOT break, because there may be multiple dNSName's! */
+ }
+ ++iAltName;
+ }
+
+ gnutls_x509_crt_deinit(cert);
+ }
+
+ CHKiRet(cstrFinalize(pStr));
+ *ppStr = pStr;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pStr != NULL)
+ rsCStrDestruct(&pStr);
+ }
+ if(szBuf != szBufA)
+ free(szBuf);
+
+ RETiRet;
+}
+
+
+
+#if 0 /* we may need this in the future - code needs to be looked at then! */
+/* This function will print some details of the
+ * given pThis->sess.
+ */
+static rsRetVal
+print_info(nsd_gtls_t *pThis)
+{
+ const char *tmp;
+ gnutls_credentials_type cred;
+ gnutls_kx_algorithm kx;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ /* print the key exchange's algorithm name
+ */
+ kx = gnutls_kx_get(pThis->sess);
+ tmp = gnutls_kx_get_name(kx);
+ dbgprintf("- Key Exchange: %s\n", tmp);
+
+ /* Check the authentication type used and switch
+ * to the appropriate.
+ */
+ cred = gnutls_auth_get_type(pThis->sess);
+ switch (cred) {
+ case GNUTLS_CRD_ANON: /* anonymous authentication */
+ dbgprintf("- Anonymous DH using prime of %d bits\n",
+ gnutls_dh_get_prime_bits(pThis->sess));
+ break;
+ case GNUTLS_CRD_CERTIFICATE: /* certificate authentication */
+ /* Check if we have been using ephemeral Diffie Hellman.
+ */
+ if (kx == GNUTLS_KX_DHE_RSA || kx == GNUTLS_KX_DHE_DSS) {
+ dbgprintf("\n- Ephemeral DH using prime of %d bits\n",
+ gnutls_dh_get_prime_bits(pThis->sess));
+ }
+
+ /* if the certificate list is available, then
+ * print some information about it.
+ */
+ gtlsPrintCert(pThis);
+ break;
+ case GNUTLS_CRD_SRP: /* certificate authentication */
+ dbgprintf("GNUTLS_CRD_SRP/IA");
+ break;
+ case GNUTLS_CRD_PSK: /* certificate authentication */
+ dbgprintf("GNUTLS_CRD_PSK");
+ break;
+ case GNUTLS_CRD_IA: /* certificate authentication */
+ dbgprintf("GNUTLS_CRD_IA");
+ break;
+ } /* switch */
+
+ /* print the protocol's name (ie TLS 1.0) */
+ tmp = gnutls_protocol_get_name(gnutls_protocol_get_version(pThis->sess));
+ dbgprintf("- Protocol: %s\n", tmp);
+
+ /* print the certificate type of the peer.
+ * ie X.509
+ */
+ tmp = gnutls_certificate_type_get_name(
+ gnutls_certificate_type_get(pThis->sess));
+
+ dbgprintf("- Certificate Type: %s\n", tmp);
+
+ /* print the compression algorithm (if any)
+ */
+ tmp = gnutls_compression_get_name( gnutls_compression_get(pThis->sess));
+ dbgprintf("- Compression: %s\n", tmp);
+
+ /* print the name of the cipher used.
+ * ie 3DES.
+ */
+ tmp = gnutls_cipher_get_name(gnutls_cipher_get(pThis->sess));
+ dbgprintf("- Cipher: %s\n", tmp);
+
+ /* Print the MAC algorithms name.
+ * ie SHA1
+ */
+ tmp = gnutls_mac_get_name(gnutls_mac_get(pThis->sess));
+ dbgprintf("- MAC: %s\n", tmp);
+
+ RETiRet;
+}
+#endif
+
+
+/* Convert a fingerprint to printable data. The conversion is carried out
+ * according IETF I-D syslog-transport-tls-12. The fingerprint string is
+ * returned in a new cstr object. It is the caller's responsibility to
+ * destruct that object.
+ * rgerhards, 2008-05-08
+ */
+static rsRetVal
+GenFingerprintStr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr)
+{
+ cstr_t *pStr = NULL;
+ uchar buf[4];
+ size_t i;
+ DEFiRet;
+
+ CHKiRet(rsCStrConstruct(&pStr));
+ CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*)"SHA1", 4));
+ for(i = 0 ; i < sizeFingerprint ; ++i) {
+ snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]);
+ CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3));
+ }
+ CHKiRet(cstrFinalize(pStr));
+
+ *ppStr = pStr;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pStr != NULL)
+ rsCStrDestruct(&pStr);
+ }
+ RETiRet;
+}
+
+
+/* a thread-safe variant of gnutls_strerror
+ * The caller must free the returned string.
+ * rgerhards, 2008-04-30
+ */
+uchar *gtlsStrerror(int error)
+{
+ uchar *pErr;
+
+ pthread_mutex_lock(&mutGtlsStrerror);
+ pErr = (uchar*) strdup(gnutls_strerror(error));
+ pthread_mutex_unlock(&mutGtlsStrerror);
+
+ return pErr;
+}
+
+
+/* try to receive a record from the remote peer. This works with
+ * our own abstraction and handles local buffering and EAGAIN.
+ * See details on local buffering in Rcv(9 header-comment.
+ * This function MUST only be called when the local buffer is
+ * empty. Calling it otherwise will cause losss of current buffer
+ * data.
+ * rgerhards, 2008-06-24
+ */
+rsRetVal
+gtlsRecordRecv(nsd_gtls_t *pThis)
+{
+ ssize_t lenRcvd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF);
+ if(lenRcvd >= 0) {
+ pThis->lenRcvBuf = lenRcvd;
+ pThis->ptrRcvBuf = 0;
+ } else if(lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) {
+ pThis->rtryCall = gtlsRtry_recv;
+ dbgprintf("GnuTLS receive requires a retry (this most probably is OK and no error condition)\n");
+ ABORT_FINALIZE(RS_RET_RETRY);
+ } else {
+ int gnuRet; /* TODO: build a specific function for GnuTLS error reporting */
+ CHKgnutls(lenRcvd); /* this will abort the function */
+ }
+
+finalize_it:
+ dbgprintf("gtlsRecordRecv return. nsd %p, iRet %d, lenRcvd %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, (int) lenRcvd, pThis->lenRcvBuf, pThis->ptrRcvBuf);
+ RETiRet;
+}
+
+
+/* add our own certificate to the certificate set, so that the peer
+ * can identify us. Please note that we try to use mutual authentication,
+ * so we always add a cert, even if we are in the client role (later,
+ * this may be controlled by a config setting).
+ * rgerhards, 2008-05-15
+ */
+static rsRetVal
+gtlsAddOurCert(void)
+{
+ int gnuRet;
+ uchar *keyFile;
+ uchar *certFile;
+ uchar *pGnuErr; /* for GnuTLS error reporting */
+ DEFiRet;
+
+ certFile = glbl.GetDfltNetstrmDrvrCertFile();
+ keyFile = glbl.GetDfltNetstrmDrvrKeyFile();
+ dbgprintf("GTLS certificate file: '%s'\n", certFile);
+ dbgprintf("GTLS key file: '%s'\n", keyFile);
+ CHKgnutls(gnutls_certificate_set_x509_key_file(xcred, (char*)certFile, (char*)keyFile, GNUTLS_X509_FMT_PEM));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ pGnuErr = gtlsStrerror(gnuRet);
+ errno = 0;
+ errmsg.LogError(0, iRet, "error adding our certificate. GnuTLS error %d, message: '%s', "
+ "key: '%s', cert: '%s'", gnuRet, pGnuErr, keyFile, certFile);
+ free(pGnuErr);
+ }
+ RETiRet;
+}
+
+
+/* globally initialize GnuTLS */
+static rsRetVal
+gtlsGlblInit(void)
+{
+ int gnuRet;
+ uchar *cafile;
+ DEFiRet;
+
+ /* gcry_control must be called first, so that the thread system is correctly set up */
+ #if GNUTLS_VERSION_NUMBER <= 0x020b00
+ gcry_control (GCRYCTL_SET_THREAD_CBS, &gcry_threads_pthread);
+ #endif
+ CHKgnutls(gnutls_global_init());
+
+ /* X509 stuff */
+ CHKgnutls(gnutls_certificate_allocate_credentials(&xcred));
+
+ /* sets the trusted cas file */
+ cafile = glbl.GetDfltNetstrmDrvrCAF();
+ dbgprintf("GTLS CA file: '%s'\n", cafile);
+ gnuRet = gnutls_certificate_set_x509_trust_file(xcred, (char*)cafile, GNUTLS_X509_FMT_PEM);
+ if(gnuRet < 0) {
+ /* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */
+ uchar *pErr = gtlsStrerror(gnuRet);
+ dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr);
+ free(pErr);
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ }
+
+# ifdef DEBUG
+#if 0 /* do this in special cases only. WARNING: if active, it may reveal sensitive information! */
+ /* intialize log function - set a level only for hard-to-find bugs */
+ gnutls_global_set_log_function(logFunction);
+ gnutls_global_set_log_level(10); /* 0 (no) to 9 (most), 10 everything */
+# endif
+# endif
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+gtlsInitSession(nsd_gtls_t *pThis)
+{
+ DEFiRet;
+ int gnuRet;
+ gnutls_session session;
+
+ gnutls_init(&session, GNUTLS_SERVER);
+ pThis->bHaveSess = 1;
+ pThis->bIsInitiator = 0;
+
+ /* avoid calling all the priority functions, since the defaults are adequate. */
+ CHKgnutls(gnutls_set_default_priority(session));
+ CHKgnutls(gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, xcred));
+
+ /* request client certificate if any. */
+ gnutls_certificate_server_set_request( session, GNUTLS_CERT_REQUEST);
+
+ pThis->sess = session;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* set up all global things that are needed for server operations
+ * rgerhards, 2008-04-30
+ */
+static rsRetVal
+gtlsGlblInitLstn(void)
+{
+ DEFiRet;
+
+ if(bGlblSrvrInitDone == 0) {
+ /* we do not use CRLs right now, and I doubt we'll ever do. This functionality is
+ * considered legacy. -- rgerhards, 2008-05-05
+ */
+ /*CHKgnutls(gnutls_certificate_set_x509_crl_file(xcred, CRLFILE, GNUTLS_X509_FMT_PEM));*/
+ bGlblSrvrInitDone = 1; /* we are all set now */
+
+ /* now we need to add our certificate */
+ CHKiRet(gtlsAddOurCert());
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Obtain the CN from the DN field and hand it back to the caller
+ * (which is responsible for destructing it). We try to follow
+ * RFC2253 as far as it makes sense for our use-case. This function
+ * is considered a compromise providing good-enough correctness while
+ * limiting code size and complexity. If a problem occurs, we may enhance
+ * this function. A (pointer to a) certificate must be caller-provided.
+ * If no CN is contained in the cert, no string is returned
+ * (*ppstrCN remains NULL). *ppstrCN MUST be NULL on entry!
+ * rgerhards, 2008-05-22
+ */
+static rsRetVal
+gtlsGetCN(nsd_gtls_t *pThis, gnutls_x509_crt *pCert, cstr_t **ppstrCN)
+{
+ DEFiRet;
+ int gnuRet;
+ int i;
+ int bFound;
+ cstr_t *pstrCN = NULL;
+ size_t size;
+ /* big var the last, so we hope to have all we usually neeed within one mem cache line */
+ uchar szDN[1024]; /* this should really be large enough for any non-malicious case... */
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ assert(pCert != NULL);
+ assert(ppstrCN != NULL);
+ assert(*ppstrCN == NULL);
+
+ size = sizeof(szDN);
+ CHKgnutls(gnutls_x509_crt_get_dn(*pCert, (char*)szDN, &size));
+
+ /* now search for the CN part */
+ i = 0;
+ bFound = 0;
+ while(!bFound && szDN[i] != '\0') {
+ /* note that we do not overrun our string due to boolean shortcut
+ * operations. If we have '\0', the if does not match and evaluation
+ * stops. Order of checks is obviously important!
+ */
+ if(szDN[i] == 'C' && szDN[i+1] == 'N' && szDN[i+2] == '=') {
+ bFound = 1;
+ i += 2;
+ }
+ i++;
+
+ }
+
+ if(!bFound) {
+ FINALIZE; /* we are done */
+ }
+
+ /* we found a common name, now extract it */
+ CHKiRet(cstrConstruct(&pstrCN));
+ while(szDN[i] != '\0' && szDN[i] != ',') {
+ if(szDN[i] == '\\') {
+ /* hex escapes are not implemented */
+ ++i; /* escape char processed */
+ if(szDN[i] == '\0')
+ ABORT_FINALIZE(RS_RET_CERT_INVALID_DN);
+ CHKiRet(cstrAppendChar(pstrCN, szDN[i]));
+ } else {
+ CHKiRet(cstrAppendChar(pstrCN, szDN[i]));
+ }
+ ++i; /* char processed */
+ }
+ CHKiRet(cstrFinalize(pstrCN));
+
+ /* we got it - we ignore the rest of the DN string (if any). So we may
+ * not detect if it contains more than one CN
+ */
+
+ *ppstrCN = pstrCN;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pstrCN != NULL)
+ cstrDestruct(&pstrCN);
+ }
+
+ RETiRet;
+}
+
+
+/* Check the peer's ID in fingerprint auth mode.
+ * rgerhards, 2008-05-22
+ */
+static rsRetVal
+gtlsChkPeerFingerprint(nsd_gtls_t *pThis, gnutls_x509_crt *pCert)
+{
+ uchar fingerprint[20];
+ size_t size;
+ cstr_t *pstrFingerprint = NULL;
+ int bFoundPositiveMatch;
+ permittedPeers_t *pPeer;
+ int gnuRet;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ /* obtain the SHA1 fingerprint */
+ size = sizeof(fingerprint);
+ CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA1, fingerprint, &size));
+ CHKiRet(GenFingerprintStr(fingerprint, size, &pstrFingerprint));
+ dbgprintf("peer's certificate SHA1 fingerprint: %s\n", cstrGetSzStr(pstrFingerprint));
+
+ /* now search through the permitted peers to see if we can find a permitted one */
+ bFoundPositiveMatch = 0;
+ pPeer = pThis->pPermPeers;
+ while(pPeer != NULL && !bFoundPositiveMatch) {
+ if(!rsCStrSzStrCmp(pstrFingerprint, pPeer->pszID, strlen((char*) pPeer->pszID))) {
+ bFoundPositiveMatch = 1;
+ } else {
+ pPeer = pPeer->pNext;
+ }
+ }
+
+ if(!bFoundPositiveMatch) {
+ dbgprintf("invalid peer fingerprint, not permitted to talk to it\n");
+ if(pThis->bReportAuthErr == 1) {
+ errno = 0;
+ errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer fingerprint '%s' unknown - we are "
+ "not permitted to talk to it", cstrGetSzStr(pstrFingerprint));
+ pThis->bReportAuthErr = 0;
+ }
+ ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
+ }
+
+finalize_it:
+ if(pstrFingerprint != NULL)
+ cstrDestruct(&pstrFingerprint);
+ RETiRet;
+}
+
+
+/* Perform a match on ONE peer name obtained from the certificate. This name
+ * is checked against the set of configured credentials. *pbFoundPositiveMatch is
+ * set to 1 if the ID matches. *pbFoundPositiveMatch must have been initialized
+ * to 0 by the caller (this is a performance enhancement as we expect to be
+ * called multiple times).
+ * TODO: implemet wildcards?
+ * rgerhards, 2008-05-26
+ */
+static rsRetVal
+gtlsChkOnePeerName(nsd_gtls_t *pThis, uchar *pszPeerID, int *pbFoundPositiveMatch)
+{
+ permittedPeers_t *pPeer;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ assert(pszPeerID != NULL);
+ assert(pbFoundPositiveMatch != NULL);
+
+ if(pThis->pPermPeers) { /* do we have configured peer IDs? */
+ pPeer = pThis->pPermPeers;
+ while(pPeer != NULL) {
+ CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch));
+ if(*pbFoundPositiveMatch)
+ break;
+ pPeer = pPeer->pNext;
+ }
+ } else {
+ /* we do not have configured peer IDs, so we use defaults */
+ if( pThis->pszConnectHost
+ && !strcmp((char*)pszPeerID, (char*)pThis->pszConnectHost)) {
+ *pbFoundPositiveMatch = 1;
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Check the peer's ID in name auth mode.
+ * rgerhards, 2008-05-22
+ */
+static rsRetVal
+gtlsChkPeerName(nsd_gtls_t *pThis, gnutls_x509_crt *pCert)
+{
+ uchar lnBuf[256];
+ char szAltName[1024]; /* this is sufficient for the DNSNAME... */
+ int iAltName;
+ size_t szAltNameLen;
+ int bFoundPositiveMatch;
+ cstr_t *pStr = NULL;
+ cstr_t *pstrCN = NULL;
+ int gnuRet;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ bFoundPositiveMatch = 0;
+ CHKiRet(rsCStrConstruct(&pStr));
+
+ /* first search through the dNSName subject alt names */
+ iAltName = 0;
+ while(!bFoundPositiveMatch) { /* loop broken below */
+ szAltNameLen = sizeof(szAltName);
+ gnuRet = gnutls_x509_crt_get_subject_alt_name(*pCert, iAltName,
+ szAltName, &szAltNameLen, NULL);
+ if(gnuRet < 0)
+ break;
+ else if(gnuRet == GNUTLS_SAN_DNSNAME) {
+ dbgprintf("subject alt dnsName: '%s'\n", szAltName);
+ snprintf((char*)lnBuf, sizeof(lnBuf), "DNSname: %s; ", szAltName);
+ CHKiRet(rsCStrAppendStr(pStr, lnBuf));
+ CHKiRet(gtlsChkOnePeerName(pThis, (uchar*)szAltName, &bFoundPositiveMatch));
+ /* do NOT break, because there may be multiple dNSName's! */
+ }
+ ++iAltName;
+ }
+
+ if(!bFoundPositiveMatch) {
+ /* if we did not succeed so far, we try the CN part of the DN... */
+ CHKiRet(gtlsGetCN(pThis, pCert, &pstrCN));
+ if(pstrCN != NULL) { /* NULL if there was no CN present */
+ dbgprintf("gtls now checking auth for CN '%s'\n", cstrGetSzStr(pstrCN));
+ snprintf((char*)lnBuf, sizeof(lnBuf), "CN: %s; ", cstrGetSzStr(pstrCN));
+ CHKiRet(rsCStrAppendStr(pStr, lnBuf));
+ CHKiRet(gtlsChkOnePeerName(pThis, cstrGetSzStr(pstrCN), &bFoundPositiveMatch));
+ }
+ }
+
+ if(!bFoundPositiveMatch) {
+ dbgprintf("invalid peer name, not permitted to talk to it\n");
+ if(pThis->bReportAuthErr == 1) {
+ CHKiRet(cstrFinalize(pStr));
+ errno = 0;
+ errmsg.LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer name not authorized - "
+ "not permitted to talk to it. Names: %s",
+ cstrGetSzStr(pStr));
+ pThis->bReportAuthErr = 0;
+ }
+ ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
+ }
+
+finalize_it:
+ if(pStr != NULL)
+ rsCStrDestruct(&pStr);
+ if(pstrCN != NULL)
+ rsCStrDestruct(&pstrCN);
+ RETiRet;
+}
+
+
+/* check the ID of the remote peer - used for both fingerprint and
+ * name authentication. This is common code. Will call into specific
+ * drivers once the certificate has been obtained.
+ * rgerhards, 2008-05-08
+ */
+static rsRetVal
+gtlsChkPeerID(nsd_gtls_t *pThis)
+{
+ const gnutls_datum *cert_list;
+ unsigned int list_size = 0;
+ gnutls_x509_crt cert;
+ int bMustDeinitCert = 0;
+ int gnuRet;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ /* This function only works for X.509 certificates. */
+ if(gnutls_certificate_type_get(pThis->sess) != GNUTLS_CRT_X509)
+ return RS_RET_TLS_CERT_ERR;
+
+ cert_list = gnutls_certificate_get_peers(pThis->sess, &list_size);
+
+ if(list_size < 1) {
+ if(pThis->bReportAuthErr == 1) {
+ errno = 0;
+ errmsg.LogError(0, RS_RET_TLS_NO_CERT, "error: peer did not provide a certificate, "
+ "not permitted to talk to it");
+ pThis->bReportAuthErr = 0;
+ }
+ ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
+ }
+
+ /* If we reach this point, we have at least one valid certificate.
+ * We always use only the first certificate. As of GnuTLS documentation, the
+ * first certificate always contains the remote peer's own certificate. All other
+ * certificates are issuer's certificates (up the chain). We are only interested
+ * in the first certificate, which is our peer. -- rgerhards, 2008-05-08
+ */
+ CHKgnutls(gnutls_x509_crt_init(&cert));
+ bMustDeinitCert = 1; /* indicate cert is initialized and must be freed on exit */
+ CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER));
+
+ /* Now we see which actual authentication code we must call. */
+ if(pThis->authMode == GTLS_AUTH_CERTFINGERPRINT) {
+ CHKiRet(gtlsChkPeerFingerprint(pThis, &cert));
+ } else {
+ assert(pThis->authMode == GTLS_AUTH_CERTNAME);
+ CHKiRet(gtlsChkPeerName(pThis, &cert));
+ }
+
+finalize_it:
+ if(bMustDeinitCert)
+ gnutls_x509_crt_deinit(cert);
+
+ RETiRet;
+}
+
+
+/* Verify the validity of the remote peer's certificate.
+ * rgerhards, 2008-05-21
+ */
+static rsRetVal
+gtlsChkPeerCertValidity(nsd_gtls_t *pThis)
+{
+ DEFiRet;
+ char *pszErrCause;
+ int gnuRet;
+ cstr_t *pStr;
+ unsigned stateCert;
+ const gnutls_datum *cert_list;
+ unsigned cert_list_size = 0;
+ gnutls_x509_crt cert;
+ unsigned i;
+ time_t ttCert;
+ time_t ttNow;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ /* check if we have at least one cert */
+ cert_list = gnutls_certificate_get_peers(pThis->sess, &cert_list_size);
+ if(cert_list_size < 1) {
+ errno = 0;
+ errmsg.LogError(0, RS_RET_TLS_NO_CERT, "peer did not provide a certificate, not permitted to talk to it");
+ ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
+ }
+
+ CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert));
+
+ if(stateCert & GNUTLS_CERT_INVALID) {
+ /* provide error details if we have them */
+ if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) {
+ pszErrCause = "signer not found";
+ } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) {
+ pszErrCause = "signer is not a CA";
+ } else if(stateCert & GNUTLS_CERT_INSECURE_ALGORITHM) {
+ pszErrCause = "insecure algorithm";
+ } else if(stateCert & GNUTLS_CERT_REVOKED) {
+ pszErrCause = "certificate revoked";
+ } else {
+ pszErrCause = "GnuTLS returned no specific reason";
+ dbgprintf("GnuTLS returned no specific reason for GNUTLS_CERT_INVALID, certificate "
+ "status is %d\n", stateCert);
+ }
+ errmsg.LogError(0, NO_ERRCODE, "not permitted to talk to peer, certificate invalid: %s",
+ pszErrCause);
+ gtlsGetCertInfo(pThis, &pStr);
+ errmsg.LogError(0, NO_ERRCODE, "invalid cert info: %s", cstrGetSzStr(pStr));
+ cstrDestruct(&pStr);
+ ABORT_FINALIZE(RS_RET_CERT_INVALID);
+ }
+
+ /* get current time for certificate validation */
+ if(datetime.GetTime(&ttNow) == -1)
+ ABORT_FINALIZE(RS_RET_SYS_ERR);
+
+ /* as it looks, we need to validate the expiration dates ourselves...
+ * We need to loop through all certificates as we need to make sure the
+ * interim certificates are also not expired.
+ */
+ for(i = 0 ; i < cert_list_size ; ++i) {
+ CHKgnutls(gnutls_x509_crt_init(&cert));
+ CHKgnutls(gnutls_x509_crt_import(cert, &cert_list[i], GNUTLS_X509_FMT_DER));
+ ttCert = gnutls_x509_crt_get_activation_time(cert);
+ if(ttCert == -1)
+ ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
+ else if(ttCert > ttNow) {
+ errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "not permitted to talk to peer: certificate %d not yet active", i);
+ gtlsGetCertInfo(pThis, &pStr);
+ errmsg.LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "invalid cert info: %s", cstrGetSzStr(pStr));
+ cstrDestruct(&pStr);
+ ABORT_FINALIZE(RS_RET_CERT_NOT_YET_ACTIVE);
+ }
+
+ ttCert = gnutls_x509_crt_get_expiration_time(cert);
+ if(ttCert == -1)
+ ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
+ else if(ttCert < ttNow) {
+ errmsg.LogError(0, RS_RET_CERT_EXPIRED, "not permitted to talk to peer: certificate %d expired", i);
+ gtlsGetCertInfo(pThis, &pStr);
+ errmsg.LogError(0, RS_RET_CERT_EXPIRED, "invalid cert info: %s", cstrGetSzStr(pStr));
+ cstrDestruct(&pStr);
+ ABORT_FINALIZE(RS_RET_CERT_EXPIRED);
+ }
+ gnutls_x509_crt_deinit(cert);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* check if it is OK to talk to the remote peer
+ * rgerhards, 2008-05-21
+ */
+rsRetVal
+gtlsChkPeerAuth(nsd_gtls_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ /* call the actual function based on current auth mode */
+ switch(pThis->authMode) {
+ case GTLS_AUTH_CERTNAME:
+ /* if we check the name, we must ensure the cert is valid */
+ CHKiRet(gtlsChkPeerCertValidity(pThis));
+ CHKiRet(gtlsChkPeerID(pThis));
+ break;
+ case GTLS_AUTH_CERTFINGERPRINT:
+ CHKiRet(gtlsChkPeerID(pThis));
+ break;
+ case GTLS_AUTH_CERTVALID:
+ CHKiRet(gtlsChkPeerCertValidity(pThis));
+ break;
+ case GTLS_AUTH_CERTANON:
+ FINALIZE;
+ break;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* globally de-initialize GnuTLS */
+static rsRetVal
+gtlsGlblExit(void)
+{
+ DEFiRet;
+ /* X509 stuff */
+ gnutls_certificate_free_credentials(xcred);
+ gnutls_global_deinit(); /* we are done... */
+ RETiRet;
+}
+
+
+/* end a GnuTLS session
+ * The function checks if we have a session and ends it only if so. So it can
+ * always be called, even if there currently is no session.
+ */
+static rsRetVal
+gtlsEndSess(nsd_gtls_t *pThis)
+{
+ int gnuRet;
+ DEFiRet;
+
+ if(pThis->bHaveSess) {
+ if(pThis->bIsInitiator) {
+ gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR);
+ while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) {
+ gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_RDWR);
+ }
+ }
+ gnutls_deinit(pThis->sess);
+ pThis->bHaveSess = 0;
+ }
+ RETiRet;
+}
+
+
+/* a small wrapper for gnutls_transport_set_ptr(). The main intension for
+ * creating this wrapper is to get the annoying "cast to pointer from different
+ * size" compiler warning just once. There seems to be no way around it, see:
+ * http://lists.gnu.org/archive/html/help-gnutls/2008-05/msg00000.html
+ * rgerhards, 2008.05-07
+ */
+#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
+static inline void
+gtlsSetTransportPtr(nsd_gtls_t *pThis, int sock)
+{
+ /* Note: the compiler warning for the next line is OK - see header comment! */
+ gnutls_transport_set_ptr(pThis->sess, (gnutls_transport_ptr_t) sock);
+}
+#pragma GCC diagnostic warning "-Wint-to-pointer-cast"
+
+/* ---------------------------- end GnuTLS specifics ---------------------------- */
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(nsd_gtls) /* be sure to specify the object type also in END macro! */
+ iRet = nsd_ptcp.Construct(&pThis->pTcp);
+ pThis->bReportAuthErr = 1;
+ENDobjConstruct(nsd_gtls)
+
+
+/* destructor for the nsd_gtls object */
+BEGINobjDestruct(nsd_gtls) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nsd_gtls)
+ if(pThis->iMode == 1) {
+ gtlsEndSess(pThis);
+ }
+
+ if(pThis->pTcp != NULL) {
+ nsd_ptcp.Destruct(&pThis->pTcp);
+ }
+
+ if(pThis->pszConnectHost != NULL) {
+ free(pThis->pszConnectHost);
+ }
+
+ if(pThis->pszRcvBuf == NULL) {
+ free(pThis->pszRcvBuf);
+ }
+
+ if(pThis->bOurCertIsInit)
+ gnutls_x509_crt_deinit(pThis->ourCert);
+ if(pThis->bOurKeyIsInit)
+ gnutls_x509_privkey_deinit(pThis->ourKey);
+ if(pThis->bHaveSess)
+ gnutls_deinit(pThis->sess);
+ENDobjDestruct(nsd_gtls)
+
+
+/* Set the driver mode. For us, this has the following meaning:
+ * 0 - work in plain tcp mode, without tls (e.g. before a STARTTLS)
+ * 1 - work in TLS mode
+ * rgerhards, 2008-04-28
+ */
+static rsRetVal
+SetMode(nsd_t *pNsd, int mode)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(mode != 0 && mode != 1) {
+ errmsg.LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by "
+ "gtls netstream driver", mode);
+ ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE);
+ }
+
+ pThis->iMode = mode;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set the authentication mode. For us, the following is supported:
+ * anon - no certificate checks whatsoever (discouraged, but supported)
+ * x509/certvalid - (just) check certificate validity
+ * x509/fingerprint - certificate fingerprint
+ * x509/name - cerfificate name check
+ * mode == NULL is valid and defaults to x509/name
+ * rgerhards, 2008-05-16
+ */
+static rsRetVal
+SetAuthMode(nsd_t *pNsd, uchar *mode)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(mode == NULL || !strcasecmp((char*)mode, "x509/name")) {
+ pThis->authMode = GTLS_AUTH_CERTNAME;
+ } else if(!strcasecmp((char*) mode, "x509/fingerprint")) {
+ pThis->authMode = GTLS_AUTH_CERTFINGERPRINT;
+ } else if(!strcasecmp((char*) mode, "x509/certvalid")) {
+ pThis->authMode = GTLS_AUTH_CERTVALID;
+ } else if(!strcasecmp((char*) mode, "anon")) {
+ pThis->authMode = GTLS_AUTH_CERTANON;
+ } else {
+ errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by "
+ "gtls netstream driver", mode);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+/* TODO: clear stored IDs! */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set permitted peers. It is depending on the auth mode if this are
+ * fingerprints or names. -- rgerhards, 2008-05-19
+ */
+static rsRetVal
+SetPermPeers(nsd_t *pNsd, permittedPeers_t *pPermPeers)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(pPermPeers == NULL)
+ FINALIZE;
+
+ if(pThis->authMode != GTLS_AUTH_CERTFINGERPRINT && pThis->authMode != GTLS_AUTH_CERTNAME) {
+ errmsg.LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by "
+ "gtls netstream driver in the configured authentication mode - ignored");
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE);
+ }
+
+ pThis->pPermPeers = pPermPeers;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Provide access to the underlying OS socket. This is primarily
+ * useful for other drivers (like nsd_gtls) who utilize ourselfs
+ * for some of their functionality. -- rgerhards, 2008-04-18
+ */
+static rsRetVal
+SetSock(nsd_t *pNsd, int sock)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ assert(sock >= 0);
+
+ nsd_ptcp.SetSock(pThis->pTcp, sock);
+
+ RETiRet;
+}
+
+
+/* abort a connection. This is meant to be called immediately
+ * before the Destruct call. -- rgerhards, 2008-03-24
+ */
+static rsRetVal
+Abort(nsd_t *pNsd)
+{
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+
+ if(pThis->iMode == 0) {
+ nsd_ptcp.Abort(pThis->pTcp);
+ }
+
+ RETiRet;
+}
+
+
+
+/* initialize the tcp socket for a listner
+ * Here, we use the ptcp driver - because there is nothing special
+ * at this point with GnuTLS. Things become special once we accept
+ * a session, but not during listener setup.
+ * gerhards, 2008-04-25
+ */
+static rsRetVal
+LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ uchar *pLstnPort, uchar *pLstnIP, int iSessMax)
+{
+ DEFiRet;
+ CHKiRet(gtlsGlblInitLstn());
+ iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, pLstnPort, pLstnIP, iSessMax);
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function checks if the connection is still alive - well, kind of...
+ * This is a dummy here. For details, check function common in ptcp driver.
+ * rgerhards, 2008-06-09
+ */
+static rsRetVal
+CheckConnection(nsd_t __attribute__((unused)) *pNsd)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ CHKiRet(nsd_ptcp.CheckConnection(pThis->pTcp));
+finalize_it:
+ RETiRet;
+}
+
+
+/* get the remote hostname. The returned hostname must be freed by the caller.
+ * rgerhards, 2008-04-25
+ */
+static rsRetVal
+GetRemoteHName(nsd_t *pNsd, uchar **ppszHName)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ iRet = nsd_ptcp.GetRemoteHName(pThis->pTcp, ppszHName);
+ RETiRet;
+}
+
+
+/* Provide access to the sockaddr_storage of the remote peer. This
+ * is needed by the legacy ACL system. --- gerhards, 2008-12-01
+ */
+static rsRetVal
+GetRemAddr(nsd_t *pNsd, struct sockaddr_storage **ppAddr)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ iRet = nsd_ptcp.GetRemAddr(pThis->pTcp, ppAddr);
+ RETiRet;
+}
+
+
+/* get the remote host's IP address. Caller must Destruct the object. */
+static rsRetVal
+GetRemoteIP(nsd_t *pNsd, prop_t **ip)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ip);
+ RETiRet;
+}
+
+
+/* accept an incoming connection request - here, we do the usual accept
+ * handling. TLS specific handling is done thereafter (and if we run in TLS
+ * mode at this time).
+ * rgerhards, 2008-04-25
+ */
+static rsRetVal
+AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew)
+{
+ DEFiRet;
+ int gnuRet;
+ nsd_gtls_t *pNew = NULL;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ CHKiRet(nsd_gtlsConstruct(&pNew)); // TODO: prevent construct/destruct!
+ CHKiRet(nsd_ptcp.Destruct(&pNew->pTcp));
+ CHKiRet(nsd_ptcp.AcceptConnReq(pThis->pTcp, &pNew->pTcp));
+
+ if(pThis->iMode == 0) {
+ /* we are in non-TLS mode, so we are done */
+ *ppNew = (nsd_t*) pNew;
+ FINALIZE;
+ }
+
+ /* if we reach this point, we are in TLS mode */
+ CHKiRet(gtlsInitSession(pNew));
+ gtlsSetTransportPtr(pNew, ((nsd_ptcp_t*) (pNew->pTcp))->sock);
+ pNew->authMode = pThis->authMode;
+ pNew->pPermPeers = pThis->pPermPeers;
+
+ /* we now do the handshake. This is a bit complicated, because we are
+ * on non-blocking sockets. Usually, the handshake will not complete
+ * immediately, so that we need to retry it some time later.
+ */
+ gnuRet = gnutls_handshake(pNew->sess);
+ if(gnuRet == GNUTLS_E_AGAIN || gnuRet == GNUTLS_E_INTERRUPTED) {
+ pNew->rtryCall = gtlsRtry_handshake;
+ dbgprintf("GnuTLS handshake does not complete immediately - setting to retry (this is OK and normal)\n");
+ } else if(gnuRet == 0) {
+ /* we got a handshake, now check authorization */
+ CHKiRet(gtlsChkPeerAuth(pNew));
+ } else {
+ uchar *pGnuErr = gtlsStrerror(gnuRet);
+ errmsg.LogError(0, RS_RET_TLS_HANDSHAKE_ERR,
+ "gnutls returned error on handshake: %s\n", pGnuErr);
+ free(pGnuErr);
+ ABORT_FINALIZE(RS_RET_TLS_HANDSHAKE_ERR);
+ }
+
+ pNew->iMode = 1; /* this session is now in TLS mode! */
+
+ *ppNew = (nsd_t*) pNew;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL)
+ nsd_gtlsDestruct(&pNew);
+ }
+ RETiRet;
+}
+
+
+/* receive data from a tcp socket
+ * The lenBuf parameter must contain the max buffer size on entry and contains
+ * the number of octets read on exit. This function
+ * never blocks, not even when called on a blocking socket. That is important
+ * for client sockets, which are set to block during send, but should not
+ * block when trying to read data. -- rgerhards, 2008-03-17
+ * The function now follows the usual iRet calling sequence.
+ * With GnuTLS, we may need to restart a recv() system call. If so, we need
+ * to supply the SAME buffer on the retry. We can not assure this, as the
+ * caller is free to call us with any buffer location (and in current
+ * implementation, it is on the stack and extremely likely to change). To
+ * work-around this problem, we allocate a buffer ourselfs and always receive
+ * into that buffer. We pass data on to the caller only after we have received it.
+ * To save some space, we allocate that internal buffer only when it is actually
+ * needed, which means when we reach this function for the first time. To keep
+ * the algorithm simple, we always supply data only from the internal buffer,
+ * even if it is a single byte. As we have a stream, the caller must be prepared
+ * to accept messages in any order, so we do not need to take care about this.
+ * Please note that the logic also forces us to do some "faking" in select(), as
+ * we must provide a fake "is ready for readign" status if we have data inside our
+ * buffer. -- rgerhards, 2008-06-23
+ */
+static rsRetVal
+Rcv(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf)
+{
+ DEFiRet;
+ ssize_t iBytesCopy; /* how many bytes are to be copied to the client buffer? */
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ if(pThis->bAbortConn)
+ ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ);
+
+ if(pThis->iMode == 0) {
+ CHKiRet(nsd_ptcp.Rcv(pThis->pTcp, pBuf, pLenBuf));
+ FINALIZE;
+ }
+
+ /* --- in TLS mode now --- */
+
+ /* Buffer logic applies only if we are in TLS mode. Here we
+ * assume that we will switch from plain to TLS, but never back. This
+ * assumption may be unsafe, but it is the model for the time being and I
+ * do not see any valid reason why we should switch back to plain TCP after
+ * we were in TLS mode. However, in that case we may lose something that
+ * is already in the receive buffer ... risk accepted. -- rgerhards, 2008-06-23
+ */
+
+ if(pThis->pszRcvBuf == NULL) {
+ /* we have no buffer, so we need to malloc one */
+ CHKmalloc(pThis->pszRcvBuf = MALLOC(NSD_GTLS_MAX_RCVBUF));
+ pThis->lenRcvBuf = -1;
+ }
+
+ /* now check if we have something in our buffer. If so, we satisfy
+ * the request from buffer contents.
+ */
+ if(pThis->lenRcvBuf == -1) { /* no data present, must read */
+ CHKiRet(gtlsRecordRecv(pThis));
+ }
+
+ if(pThis->lenRcvBuf == 0) { /* EOS */
+ *pLenBuf = 0;
+ /* in this case, we also need to free the receive buffer, if we
+ * allocated one. -- rgerhards, 2008-12-03
+ */
+ if(pThis->pszRcvBuf != NULL) {
+ free(pThis->pszRcvBuf);
+ pThis->pszRcvBuf = NULL;
+ }
+ ABORT_FINALIZE(RS_RET_CLOSED);
+ }
+
+ /* if we reach this point, data is present in the buffer and must be copied */
+ iBytesCopy = pThis->lenRcvBuf - pThis->ptrRcvBuf;
+ if(iBytesCopy > *pLenBuf) {
+ iBytesCopy = *pLenBuf;
+ } else {
+ pThis->lenRcvBuf = -1; /* buffer will be emptied below */
+ }
+
+ memcpy(pBuf, pThis->pszRcvBuf + pThis->ptrRcvBuf, iBytesCopy);
+ pThis->ptrRcvBuf += iBytesCopy;
+ *pLenBuf = iBytesCopy;
+
+finalize_it:
+ dbgprintf("gtlsRcv return. nsd %p, iRet %d, lenRcvBuf %d, ptrRcvBuf %d\n", pThis, iRet, pThis->lenRcvBuf, pThis->ptrRcvBuf);
+ RETiRet;
+}
+
+
+/* send a buffer. On entry, pLenBuf contains the number of octets to
+ * write. On exit, it contains the number of octets actually written.
+ * If this number is lower than on entry, only a partial buffer has
+ * been written.
+ * rgerhards, 2008-03-19
+ */
+static rsRetVal
+Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf)
+{
+ int iSent;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ if(pThis->bAbortConn)
+ ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ);
+
+ if(pThis->iMode == 0) {
+ CHKiRet(nsd_ptcp.Send(pThis->pTcp, pBuf, pLenBuf));
+ FINALIZE;
+ }
+
+ /* in TLS mode now */
+ while(1) { /* loop broken inside */
+ iSent = gnutls_record_send(pThis->sess, pBuf, *pLenBuf);
+ if(iSent >= 0) {
+ *pLenBuf = iSent;
+ break;
+ }
+ if(iSent != GNUTLS_E_INTERRUPTED && iSent != GNUTLS_E_AGAIN) {
+ dbgprintf("unexpected GnuTLS error %d in %s:%d\n", iSent, __FILE__, __LINE__);
+ gnutls_perror(iSent); /* TODO: can we do better? */
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+/* Enable KEEPALIVE handling on the socket.
+ * rgerhards, 2009-06-02
+ */
+static rsRetVal
+EnableKeepAlive(nsd_t *pNsd)
+{
+ return nsd_ptcp.EnableKeepAlive(pNsd);
+}
+
+
+
+/* open a connection to a remote host (server). With GnuTLS, we always
+ * open a plain tcp socket and then, if in TLS mode, do a handshake on it.
+ * rgerhards, 2008-03-19
+ */
+static rsRetVal
+Connect(nsd_t *pNsd, int family, uchar *port, uchar *host)
+{
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ int sock;
+ int gnuRet;
+ /* TODO: later? static const int cert_type_priority[3] = { GNUTLS_CRT_X509, GNUTLS_CRT_OPENPGP, 0 };*/
+ static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 };
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ assert(port != NULL);
+ assert(host != NULL);
+
+ CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host));
+
+ if(pThis->iMode == 0)
+ FINALIZE;
+
+ /* we reach this point if in TLS mode */
+ CHKgnutls(gnutls_init(&pThis->sess, GNUTLS_CLIENT));
+ pThis->bHaveSess = 1;
+ pThis->bIsInitiator = 1;
+
+ /* in the client case, we need to set a callback that ensures our certificate
+ * will be presented to the server even if it is not signed by one of the server's
+ * trusted roots. This is necessary to support fingerprint authentication.
+ */
+ /* store a pointer to ourselfs (needed by callback) */
+ gnutls_session_set_ptr(pThis->sess, (void*)pThis);
+ iRet = gtlsLoadOurCertKey(pThis); /* first load .pem files */
+ if(iRet == RS_RET_OK) {
+ gnutls_certificate_client_set_retrieve_function(xcred, gtlsClientCertCallback);
+ } else if(iRet != RS_RET_CERTLESS) {
+ FINALIZE; /* we have an error case! */
+ }
+
+ /* Use default priorities */
+ CHKgnutls(gnutls_set_default_priority(pThis->sess));
+ CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority));
+
+ /* put the x509 credentials to the current session */
+ CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, xcred));
+
+ /* assign the socket to GnuTls */
+ CHKiRet(nsd_ptcp.GetSock(pThis->pTcp, &sock));
+ gtlsSetTransportPtr(pThis, sock);
+
+ /* we need to store the hostname as an alternate mean of authentication if no
+ * permitted peer names are given. Using the hostname is quite useful. It permits
+ * auto-configuration of security if a commen root cert is present. -- rgerhards, 2008-05-26
+ */
+ CHKmalloc(pThis->pszConnectHost = (uchar*)strdup((char*)host));
+
+ /* and perform the handshake */
+ CHKgnutls(gnutls_handshake(pThis->sess));
+ dbgprintf("GnuTLS handshake succeeded\n");
+
+ /* now check if the remote peer is permitted to talk to us - ideally, we
+ * should do this during the handshake, but GnuTLS does not yet provide
+ * the necessary callbacks -- rgerhards, 2008-05-26
+ */
+ CHKiRet(gtlsChkPeerAuth(pThis));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->bHaveSess) {
+ gnutls_deinit(pThis->sess);
+ pThis->bHaveSess = 0;
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nsd_gtls)
+CODESTARTobjQueryInterface(nsd_gtls)
+ if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = (rsRetVal(*)(nsd_t**)) nsd_gtlsConstruct;
+ pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_gtlsDestruct;
+ pIf->Abort = Abort;
+ pIf->LstnInit = LstnInit;
+ pIf->AcceptConnReq = AcceptConnReq;
+ pIf->Rcv = Rcv;
+ pIf->Send = Send;
+ pIf->Connect = Connect;
+ pIf->SetSock = SetSock;
+ pIf->SetMode = SetMode;
+ pIf->SetAuthMode = SetAuthMode;
+ pIf->SetPermPeers =SetPermPeers;
+ pIf->CheckConnection = CheckConnection;
+ pIf->GetRemoteHName = GetRemoteHName;
+ pIf->GetRemoteIP = GetRemoteIP;
+ pIf->GetRemAddr = GetRemAddr;
+ pIf->EnableKeepAlive = EnableKeepAlive;
+finalize_it:
+ENDobjQueryInterface(nsd_gtls)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nsd_gtls, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsd_gtls)
+ gtlsGlblExit(); /* shut down GnuTLS */
+
+ /* release objects we no longer need */
+ objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME);
+ objRelease(net, LM_NET_FILENAME);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ENDObjClassExit(nsd_gtls)
+
+
+/* Initialize the nsd_gtls class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nsd_gtls, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(net, LM_NET_FILENAME));
+ CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME));
+
+ /* now do global TLS init stuff */
+ CHKiRet(gtlsGlblInit());
+ENDObjClassInit(nsd_gtls)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ nsdsel_gtlsClassExit();
+ nsd_gtlsClassExit();
+ pthread_mutex_destroy(&mutGtlsStrerror);
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(nsd_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ CHKiRet(nsdsel_gtlsClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+
+ pthread_mutex_init(&mutGtlsStrerror, NULL);
+ENDmodInit
+/* vi:set ai:
+ */
diff --git a/runtime/nsd_gtls.h b/runtime/nsd_gtls.h
new file mode 100644
index 00000000..eb92ff2a
--- /dev/null
+++ b/runtime/nsd_gtls.h
@@ -0,0 +1,90 @@
+/* An implementation of the nsd interface for GnuTLS.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDED_NSD_GTLS_H
+#define INCLUDED_NSD_GTLS_H
+
+#include "nsd.h"
+
+#define NSD_GTLS_MAX_RCVBUF 8 * 1024 /* max size of buffer for message reception */
+
+typedef enum {
+ gtlsRtry_None = 0, /**< no call needs to be retried */
+ gtlsRtry_handshake = 1,
+ gtlsRtry_recv = 2
+} gtlsRtryCall_t; /**< IDs of calls that needs to be retried */
+
+typedef nsd_if_t nsd_gtls_if_t; /* we just *implement* this interface */
+
+/* the nsd_gtls object */
+struct nsd_gtls_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ nsd_t *pTcp; /**< our aggregated nsd_ptcp data */
+ uchar *pszConnectHost; /**< hostname used for connect - may be used to authenticate peer if no other name given */
+ int iMode; /* 0 - plain tcp, 1 - TLS */
+ int bAbortConn; /* if set, abort conncection (fatal error had happened) */
+ enum {
+ GTLS_AUTH_CERTNAME = 0,
+ GTLS_AUTH_CERTFINGERPRINT = 1,
+ GTLS_AUTH_CERTVALID = 2,
+ GTLS_AUTH_CERTANON = 3
+ } authMode;
+ gtlsRtryCall_t rtryCall;/**< what must we retry? */
+ int bIsInitiator; /**< 0 if socket is the server end (listener), 1 if it is the initiator */
+ gnutls_session sess;
+ int bHaveSess; /* as we don't know exactly which gnutls_session values are invalid, we use this one
+ to flag whether or not we are in a session (same as -1 for a socket meaning no sess) */
+ int bReportAuthErr; /* only the first auth error is to be reported, this var triggers it. Initially, it is
+ * set to 1 and changed to 0 after the first report. It is changed back to 1 after
+ * one successful authentication. */
+ permittedPeers_t *pPermPeers; /* permitted peers */
+ gnutls_x509_crt ourCert; /**< our certificate, if in client mode (unused in server mode) */
+ gnutls_x509_privkey ourKey; /**< our private key, if in client mode (unused in server mode) */
+ short bOurCertIsInit; /**< 1 if our certificate is initialized and must be deinit on destruction */
+ short bOurKeyIsInit; /**< 1 if our private key is initialized and must be deinit on destruction */
+ char *pszRcvBuf;
+ int lenRcvBuf; /**< -1: empty, 0: connection closed, 1..NSD_GTLS_MAX_RCVBUF-1: data of that size present */
+ int ptrRcvBuf; /**< offset for next recv operation if 0 < lenRcvBuf < NSD_GTLS_MAX_RCVBUF */
+};
+
+/* interface is defined in nsd.h, we just implement it! */
+#define nsd_gtlsCURR_IF_VERSION nsdCURR_IF_VERSION
+
+/* prototypes */
+PROTOTYPEObj(nsd_gtls);
+/* some prototypes for things used by our nsdsel_gtls helper class */
+uchar *gtlsStrerror(int error);
+rsRetVal gtlsChkPeerAuth(nsd_gtls_t *pThis);
+rsRetVal gtlsRecordRecv(nsd_gtls_t *pThis);
+static inline rsRetVal gtlsHasRcvInBuffer(nsd_gtls_t *pThis) {
+ /* we have a valid receive buffer one such is allocated and
+ * NOT exhausted!
+ */
+ dbgprintf("hasRcvInBuffer on nsd %p: pszRcvBuf %p, lenRcvBuf %d\n", pThis,
+ pThis->pszRcvBuf, pThis->lenRcvBuf);
+ return(pThis->pszRcvBuf != NULL && pThis->lenRcvBuf != -1);
+ }
+
+
+/* the name of our library binary */
+#define LM_NSD_GTLS_FILENAME "lmnsd_gtls"
+
+#endif /* #ifndef INCLUDED_NSD_GTLS_H */
diff --git a/runtime/nsd_ptcp.c b/runtime/nsd_ptcp.c
new file mode 100644
index 00000000..f889a00e
--- /dev/null
+++ b/runtime/nsd_ptcp.c
@@ -0,0 +1,823 @@
+/* nsd_ptcp.c
+ *
+ * An implementation of the nsd interface for plain tcp sockets.
+ *
+ * Copyright 2007-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <ctype.h>
+#include <netdb.h>
+#include <fnmatch.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "syslogd-types.h"
+#include "module-template.h"
+#include "parse.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "net.h"
+#include "netstrms.h"
+#include "netstrm.h"
+#include "nsdsel_ptcp.h"
+#include "nsdpoll_ptcp.h"
+#include "nsd_ptcp.h"
+#include "prop.h"
+#include "dnscache.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(net)
+DEFobjCurrIf(netstrms)
+DEFobjCurrIf(netstrm)
+DEFobjCurrIf(prop)
+
+
+/* a few deinit helpers */
+
+/* close socket if open (may always be called) */
+static void
+sockClose(int *pSock)
+{
+ if(*pSock >= 0) {
+ close(*pSock);
+ *pSock = -1;
+ }
+}
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(nsd_ptcp) /* be sure to specify the object type also in END macro! */
+ pThis->sock = -1;
+ENDobjConstruct(nsd_ptcp)
+
+
+/* destructor for the nsd_ptcp object */
+BEGINobjDestruct(nsd_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nsd_ptcp)
+ sockClose(&pThis->sock);
+ if(pThis->remoteIP != NULL)
+ prop.Destruct(&pThis->remoteIP);
+ free(pThis->pRemHostName);
+ENDobjDestruct(nsd_ptcp)
+
+
+/* Provide access to the sockaddr_storage of the remote peer. This
+ * is needed by the legacy ACL system. --- gerhards, 2008-12-01
+ */
+static rsRetVal
+GetRemAddr(nsd_t *pNsd, struct sockaddr_storage **ppAddr)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+ assert(ppAddr != NULL);
+
+ *ppAddr = &(pThis->remAddr);
+
+ RETiRet;
+}
+
+
+/* Provide access to the underlying OS socket. This is primarily
+ * useful for other drivers (like nsd_gtls) who utilize ourselfs
+ * for some of their functionality. -- rgerhards, 2008-04-18
+ */
+static rsRetVal
+GetSock(nsd_t *pNsd, int *pSock)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+ assert(pSock != NULL);
+
+ *pSock = pThis->sock;
+
+ RETiRet;
+}
+
+
+/* Set the driver mode. We support no different modes, but allow mode
+ * 0 to be set to be compatible with config file defaults and the other
+ * drivers.
+ * rgerhards, 2008-04-28
+ */
+static rsRetVal
+SetMode(nsd_t __attribute__((unused)) *pNsd, int mode)
+{
+ DEFiRet;
+ if(mode != 0) {
+ errmsg.LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by "
+ "ptcp netstream driver", mode);
+ ABORT_FINALIZE(RS_RET_INVALID_DRVR_MODE);
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set the authentication mode. For us, the following is supported:
+ * anon - no certificate checks whatsoever (discouraged, but supported)
+ * mode == NULL is valid and defaults to anon
+ * Actually, we do not even record the mode right now, because we can
+ * always work in anon mode, only. So there is no point in recording
+ * something if that's the only choice. What the function does is
+ * return an error if something is requested that we can not support.
+ * rgerhards, 2008-05-17
+ */
+static rsRetVal
+SetAuthMode(nsd_t __attribute__((unused)) *pNsd, uchar *mode)
+{
+ DEFiRet;
+ if(mode != NULL && strcasecmp((char*)mode, "anon")) {
+ errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by "
+ "ptcp netstream driver", mode);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set the permitted peers. This is a dummy, always returning an
+ * error because we do not support fingerprint authentication.
+ * rgerhards, 2008-05-17
+ */
+static rsRetVal
+SetPermPeers(nsd_t __attribute__((unused)) *pNsd, permittedPeers_t __attribute__((unused)) *pPermPeers)
+{
+ DEFiRet;
+
+ if(pPermPeers != NULL) {
+ errmsg.LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by ptcp netstream driver");
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+
+/* Provide access to the underlying OS socket. This is primarily
+ * useful for other drivers (like nsd_gtls) who utilize ourselfs
+ * for some of their functionality.
+ * This function sets the socket -- rgerhards, 2008-04-25
+ */
+static rsRetVal
+SetSock(nsd_t *pNsd, int sock)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+ assert(sock >= 0);
+
+ pThis->sock = sock;
+
+ RETiRet;
+}
+
+
+/* abort a connection. This is meant to be called immediately
+ * before the Destruct call. -- rgerhards, 2008-03-24
+ */
+static rsRetVal
+Abort(nsd_t *pNsd)
+{
+ struct linger ling;
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+
+ DEFiRet;
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+
+ if((pThis)->sock != -1) {
+ ling.l_onoff = 1;
+ ling.l_linger = 0;
+ if(setsockopt((pThis)->sock, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling)) < 0 ) {
+ dbgprintf("could not set SO_LINGER, errno %d\n", errno);
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* Set pRemHost based on the address provided. This is to be called upon accept()ing
+ * a connection request. It must be provided by the socket we received the
+ * message on as well as a NI_MAXHOST size large character buffer for the FQDN.
+ * Please see http://www.hmug.org/man/3/getnameinfo.php (under Caveats)
+ * for some explanation of the code found below. If we detect a malicious
+ * hostname, we return RS_RET_MALICIOUS_HNAME and let the caller decide
+ * on how to deal with that.
+ * rgerhards, 2008-03-31
+ */
+static rsRetVal
+FillRemHost(nsd_ptcp_t *pThis, struct sockaddr_storage *pAddr)
+{
+ prop_t *fqdn;
+
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+ assert(pAddr != NULL);
+
+ CHKiRet(dnscacheLookup(pAddr, &fqdn, NULL, NULL, &pThis->remoteIP));
+
+ /* We now have the names, so now let's allocate memory and store them permanently.
+ * (side note: we may hold on to these values for quite a while, thus we trim their
+ * memory consumption)
+ */
+ if((pThis->pRemHostName = MALLOC(prop.GetStringLen(fqdn)+1)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ memcpy(pThis->pRemHostName, propGetSzStr(fqdn), prop.GetStringLen(fqdn)+1);
+ prop.Destruct(&fqdn);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* accept an incoming connection request
+ * rgerhards, 2008-04-22
+ */
+static rsRetVal
+AcceptConnReq(nsd_t *pNsd, nsd_t **ppNew)
+{
+ int sockflags;
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ struct sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ nsd_ptcp_t *pNew = NULL;
+ int iNewSock = -1;
+
+ DEFiRet;
+ assert(ppNew != NULL);
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+
+ iNewSock = accept(pThis->sock, (struct sockaddr*) &addr, &addrlen);
+ if(iNewSock < 0) {
+ if(Debug) {
+ char errStr[1024];
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ dbgprintf("nds_ptcp: error accepting connection on socket %d, errno %d: %s\n",
+ pThis->sock, errno, errStr);
+ }
+ ABORT_FINALIZE(RS_RET_ACCEPT_ERR);
+ }
+
+ /* construct our object so that we can use it... */
+ CHKiRet(nsd_ptcpConstruct(&pNew));
+
+ /* for the legacy ACL code, we need to preserve addr. While this is far from
+ * begin perfect (from an abstract design perspective), we need this to prevent
+ * breaking everything. TODO: we need to implement a new ACL module to get rid
+ * of this function. -- rgerhards, 2008-12-01
+ */
+ memcpy(&pNew->remAddr, &addr, sizeof(struct sockaddr_storage));
+ CHKiRet(FillRemHost(pNew, &addr));
+
+ /* set the new socket to non-blocking IO -TODO:do we really need to do this here? Do we always want it? */
+ if((sockflags = fcntl(iNewSock, F_GETFL)) != -1) {
+ sockflags |= O_NONBLOCK;
+ /* SETFL could fail too, so get it caught by the subsequent
+ * error check.
+ */
+ sockflags = fcntl(iNewSock, F_SETFL, sockflags);
+ }
+ if(sockflags == -1) {
+ dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket %d", errno, iNewSock);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ pNew->sock = iNewSock;
+ *ppNew = (nsd_t*) pNew;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL)
+ nsd_ptcpDestruct(&pNew);
+ /* the close may be redundant, but that doesn't hurt... */
+ sockClose(&iNewSock);
+ }
+
+ RETiRet;
+}
+
+
+/* initialize tcp sockets for a listner. The initialized sockets are passed to the
+ * app-level caller via a callback.
+ * pLstnPort must point to a port name or number. NULL is NOT permitted. pLstnIP
+ * points to the port to listen to (NULL means "all"), iMaxSess has the maximum
+ * number of sessions permitted.
+ * rgerhards, 2008-04-22
+ */
+static rsRetVal
+LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ uchar *pLstnPort, uchar *pLstnIP, int iSessMax)
+{
+ DEFiRet;
+ netstrm_t *pNewStrm = NULL;
+ nsd_t *pNewNsd = NULL;
+ int error, maxs, on = 1;
+ int sock = -1;
+ int numSocks;
+ int sockflags;
+ struct addrinfo hints, *res = NULL, *r;
+
+ ISOBJ_TYPE_assert(pNS, netstrms);
+ assert(fAddLstn != NULL);
+ assert(pLstnPort != NULL);
+ assert(iSessMax >= 0);
+
+ dbgprintf("creating tcp listen socket on port %s\n", pLstnPort);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = glbl.GetDefPFFamily();
+ hints.ai_socktype = SOCK_STREAM;
+
+ error = getaddrinfo((char*)pLstnIP, (char*) pLstnPort, &hints, &res);
+ if(error) {
+ dbgprintf("error %d querying port '%s'\n", error, pLstnPort);
+ ABORT_FINALIZE(RS_RET_INVALID_PORT);
+ }
+
+ /* Count max number of sockets we may open */
+ for(maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++)
+ /* EMPTY */;
+
+ numSocks = 0; /* num of sockets counter at start of array */
+ for(r = res; r != NULL ; r = r->ai_next) {
+ sock = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+ if(sock < 0) {
+ if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT))
+ dbgprintf("error %d creating tcp listen socket", errno);
+ /* it is debatable if PF_INET with EAFNOSUPPORT should
+ * also be ignored...
+ */
+ continue;
+ }
+
+#ifdef IPV6_V6ONLY
+ if(r->ai_family == AF_INET6) {
+ int iOn = 1;
+ if(setsockopt(sock, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&iOn, sizeof (iOn)) < 0) {
+ close(sock);
+ sock = -1;
+ continue;
+ }
+ }
+#endif
+ if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) {
+ dbgprintf("error %d setting tcp socket option\n", errno);
+ close(sock);
+ sock = -1;
+ continue;
+ }
+
+ /* We use non-blocking IO! */
+ if((sockflags = fcntl(sock, F_GETFL)) != -1) {
+ sockflags |= O_NONBLOCK;
+ /* SETFL could fail too, so get it caught by the subsequent
+ * error check.
+ */
+ sockflags = fcntl(sock, F_SETFL, sockflags);
+ }
+ if(sockflags == -1) {
+ dbgprintf("error %d setting fcntl(O_NONBLOCK) on tcp socket", errno);
+ close(sock);
+ sock = -1;
+ continue;
+ }
+
+
+
+ /* We need to enable BSD compatibility. Otherwise an attacker
+ * could flood our log files by sending us tons of ICMP errors.
+ */
+#ifndef BSD
+ if(net.should_use_so_bsdcompat()) {
+ if (setsockopt(sock, SOL_SOCKET, SO_BSDCOMPAT,
+ (char *) &on, sizeof(on)) < 0) {
+ errmsg.LogError(errno, NO_ERRCODE, "TCP setsockopt(BSDCOMPAT)");
+ close(sock);
+ sock = -1;
+ continue;
+ }
+ }
+#endif
+
+ if( (bind(sock, r->ai_addr, r->ai_addrlen) < 0)
+#ifndef IPV6_V6ONLY
+ && (errno != EADDRINUSE)
+#endif
+ ) {
+ /* TODO: check if *we* bound the socket - else we *have* an error! */
+ char errStr[1024];
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ dbgprintf("error %d while binding tcp socket: %s\n", errno, errStr);
+ close(sock);
+ sock = -1;
+ continue;
+ }
+
+ if(listen(sock, iSessMax / 10 + 5) < 0) {
+ /* If the listen fails, it most probably fails because we ask
+ * for a too-large backlog. So in this case we first set back
+ * to a fixed, reasonable, limit that should work. Only if
+ * that fails, too, we give up.
+ */
+ dbgprintf("listen with a backlog of %d failed - retrying with default of 32.\n",
+ iSessMax / 10 + 5);
+ if(listen(sock, 32) < 0) {
+ dbgprintf("tcp listen error %d, suspending\n", errno);
+ close(sock);
+ sock = -1;
+ continue;
+ }
+ }
+
+ /* if we reach this point, we were able to obtain a valid socket, so we can
+ * construct a new netstrm obj and hand it over to the upper layers for inclusion
+ * into their socket array. -- rgerhards, 2008-04-23
+ */
+ CHKiRet(pNS->Drvr.Construct(&pNewNsd));
+ CHKiRet(pNS->Drvr.SetSock(pNewNsd, sock));
+ sock = -1;
+ CHKiRet(pNS->Drvr.SetMode(pNewNsd, netstrms.GetDrvrMode(pNS)));
+ CHKiRet(pNS->Drvr.SetAuthMode(pNewNsd, netstrms.GetDrvrAuthMode(pNS)));
+ CHKiRet(pNS->Drvr.SetPermPeers(pNewNsd, netstrms.GetDrvrPermPeers(pNS)));
+ CHKiRet(netstrms.CreateStrm(pNS, &pNewStrm));
+ pNewStrm->pDrvrData = (nsd_t*) pNewNsd;
+ pNewNsd = NULL;
+ CHKiRet(fAddLstn(pUsr, pNewStrm));
+ pNewStrm = NULL;
+ ++numSocks;
+ }
+
+ if(numSocks != maxs)
+ dbgprintf("We could initialize %d TCP listen sockets out of %d we received "
+ "- this may or may not be an error indication.\n", numSocks, maxs);
+
+ if(numSocks == 0) {
+ dbgprintf("No TCP listen sockets could successfully be initialized\n");
+ ABORT_FINALIZE(RS_RET_COULD_NOT_BIND);
+ }
+
+finalize_it:
+ if(res != NULL)
+ freeaddrinfo(res);
+
+ if(iRet != RS_RET_OK) {
+ if(sock != -1)
+ close(sock);
+ if(pNewStrm != NULL)
+ netstrm.Destruct(&pNewStrm);
+ if(pNewNsd != NULL)
+ pNS->Drvr.Destruct(&pNewNsd);
+ }
+
+ RETiRet;
+}
+
+
+/* receive data from a tcp socket
+ * The lenBuf parameter must contain the max buffer size on entry and contains
+ * the number of octets read (or -1 in case of error) on exit. This function
+ * never blocks, not even when called on a blocking socket. That is important
+ * for client sockets, which are set to block during send, but should not
+ * block when trying to read data. If *pLenBuf is -1, an error occured and
+ * errno holds the exact error cause.
+ * rgerhards, 2008-03-17
+ */
+static rsRetVal
+Rcv(nsd_t *pNsd, uchar *pRcvBuf, ssize_t *pLenBuf)
+{
+ char errStr[1024];
+ DEFiRet;
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+
+ *pLenBuf = recv(pThis->sock, pRcvBuf, *pLenBuf, MSG_DONTWAIT);
+
+ if(*pLenBuf == 0) {
+ ABORT_FINALIZE(RS_RET_CLOSED);
+ } else if (*pLenBuf < 0) {
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ dbgprintf("error during recv on NSD %p: %s\n", pNsd, errStr);
+ ABORT_FINALIZE(RS_RET_RCV_ERR);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* send a buffer. On entry, pLenBuf contains the number of octets to
+ * write. On exit, it contains the number of octets actually written.
+ * If this number is lower than on entry, only a partial buffer has
+ * been written.
+ * rgerhards, 2008-03-19
+ */
+static rsRetVal
+Send(nsd_t *pNsd, uchar *pBuf, ssize_t *pLenBuf)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ ssize_t written;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+
+ written = send(pThis->sock, pBuf, *pLenBuf, 0);
+
+ if(written == -1) {
+ switch(errno) {
+ case EAGAIN:
+ case EINTR:
+ /* this is fine, just retry... */
+ written = 0;
+ break;
+ default:
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ break;
+ }
+ }
+
+ *pLenBuf = written;
+finalize_it:
+ RETiRet;
+}
+
+
+/* Enable KEEPALIVE handling on the socket.
+ * rgerhards, 2009-06-02
+ */
+static rsRetVal
+EnableKeepAlive(nsd_t *pNsd)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ int ret;
+ int optval;
+ socklen_t optlen;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+
+ optval = 1;
+ optlen = sizeof(optval);
+ ret = setsockopt(pThis->sock, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen);
+ if(ret < 0) {
+ dbgprintf("EnableKeepAlive socket call returns error %d\n", ret);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ dbgprintf("KEEPALIVE enabled for nsd %p\n", pThis);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* open a connection to a remote host (server).
+ * rgerhards, 2008-03-19
+ */
+static rsRetVal
+Connect(nsd_t *pNsd, int family, uchar *port, uchar *host)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ struct addrinfo *res = NULL;
+ struct addrinfo hints;
+
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+ assert(port != NULL);
+ assert(host != NULL);
+ assert(pThis->sock == -1);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = family;
+ hints.ai_socktype = SOCK_STREAM;
+ if(getaddrinfo((char*)host, (char*)port, &hints, &res) != 0) {
+ dbgprintf("error %d in getaddrinfo\n", errno);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ if((pThis->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ if(connect(pThis->sock, res->ai_addr, res->ai_addrlen) != 0) {
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+finalize_it:
+ if(res != NULL)
+ freeaddrinfo(res);
+
+ if(iRet != RS_RET_OK) {
+ sockClose(&pThis->sock);
+ }
+
+ RETiRet;
+}
+
+
+/* get the remote hostname. The returned hostname must be freed by the
+ * caller.
+ * rgerhards, 2008-04-24
+ */
+static rsRetVal
+GetRemoteHName(nsd_t *pNsd, uchar **ppszHName)
+{
+ DEFiRet;
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+ assert(ppszHName != NULL);
+
+ // TODO: how can the RemHost be empty?
+ CHKmalloc(*ppszHName = (uchar*)strdup(pThis->pRemHostName == NULL ? "" : (char*) pThis->pRemHostName));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function checks if the connection is still alive - well, kind of... It
+ * is primarily being used for plain TCP syslog and it is quite a hack. However,
+ * as it seems to work, it is worth supporting it. The bottom line is that it
+ * should not be called by anything else but a plain tcp syslog sender.
+ * In order for it to work, it must be called *immediately* *before* the send()
+ * call. For details about what is done, see here:
+ * http://blog.gerhards.net/2008/06/getting-bit-more-reliability-from-plain.html
+ * rgerhards, 2008-06-09
+ */
+static rsRetVal
+CheckConnection(nsd_t *pNsd)
+{
+ DEFiRet;
+ int rc;
+ char msgbuf[1]; /* dummy */
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+
+ rc = recv(pThis->sock, msgbuf, 1, MSG_DONTWAIT | MSG_PEEK);
+ if(rc == 0) {
+ dbgprintf("CheckConnection detected broken connection - closing it\n");
+ /* in this case, the remote peer had shut down the connection and we
+ * need to close our side, too.
+ */
+ sockClose(&pThis->sock);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* get the remote host's IP address. Caller must Destruct the object.
+ */
+static rsRetVal
+GetRemoteIP(nsd_t *pNsd, prop_t **ip)
+{
+ DEFiRet;
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+ prop.AddRef(pThis->remoteIP);
+ *ip = pThis->remoteIP;
+ RETiRet;
+}
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nsd_ptcp)
+CODESTARTobjQueryInterface(nsd_ptcp)
+ if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = (rsRetVal(*)(nsd_t**)) nsd_ptcpConstruct;
+ pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_ptcpDestruct;
+ pIf->Abort = Abort;
+ pIf->GetRemAddr = GetRemAddr;
+ pIf->GetSock = GetSock;
+ pIf->SetSock = SetSock;
+ pIf->SetMode = SetMode;
+ pIf->SetAuthMode = SetAuthMode;
+ pIf->SetPermPeers = SetPermPeers;
+ pIf->Rcv = Rcv;
+ pIf->Send = Send;
+ pIf->LstnInit = LstnInit;
+ pIf->AcceptConnReq = AcceptConnReq;
+ pIf->Connect = Connect;
+ pIf->GetRemoteHName = GetRemoteHName;
+ pIf->GetRemoteIP = GetRemoteIP;
+ pIf->CheckConnection = CheckConnection;
+ pIf->EnableKeepAlive = EnableKeepAlive;
+finalize_it:
+ENDobjQueryInterface(nsd_ptcp)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nsd_ptcp, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsd_ptcp)
+ /* release objects we no longer need */
+ objRelease(net, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(netstrm, DONT_LOAD_LIB);
+ objRelease(netstrms, LM_NETSTRMS_FILENAME);
+ENDObjClassExit(nsd_ptcp)
+
+
+/* Initialize the nsd_ptcp class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nsd_ptcp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+ CHKiRet(objUse(net, CORE_COMPONENT));
+ CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME));
+ CHKiRet(objUse(netstrm, DONT_LOAD_LIB));
+
+ /* set our own handlers */
+ENDObjClassInit(nsd_ptcp)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+# ifdef HAVE_EPOLL_CREATE /* module only available if epoll() is supported! */
+ nsdpoll_ptcpClassExit();
+# endif
+ nsdsel_ptcpClassExit();
+ nsd_ptcpClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(nsd_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ CHKiRet(nsdsel_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+# ifdef HAVE_EPOLL_CREATE /* module only available if epoll() is supported! */
+ CHKiRet(nsdpoll_ptcpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+# endif
+ENDmodInit
+/* vi:set ai:
+ */
diff --git a/runtime/nsd_ptcp.h b/runtime/nsd_ptcp.h
new file mode 100644
index 00000000..ed6b8565
--- /dev/null
+++ b/runtime/nsd_ptcp.h
@@ -0,0 +1,48 @@
+/* An implementation of the nsd interface for plain tcp sockets.
+ *
+ * Copyright 2007-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDED_NSD_PTCP_H
+#define INCLUDED_NSD_PTCP_H
+
+#include <sys/socket.h>
+
+#include "nsd.h"
+typedef nsd_if_t nsd_ptcp_if_t; /* we just *implement* this interface */
+
+/* the nsd_ptcp object */
+struct nsd_ptcp_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ prop_t *remoteIP; /**< IP address of remote peer (currently used in server mode, only) */
+ uchar *pRemHostName; /**< host name of remote peer (currently used in server mode, only) */
+ struct sockaddr_storage remAddr; /**< remote addr as sockaddr - used for legacy ACL code */
+ int sock; /**< the socket we use for regular, single-socket, operations */
+};
+
+/* interface is defined in nsd.h, we just implement it! */
+#define nsd_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION
+
+/* prototypes */
+PROTOTYPEObj(nsd_ptcp);
+
+/* the name of our library binary */
+#define LM_NSD_PTCP_FILENAME "lmnsd_ptcp"
+
+#endif /* #ifndef INCLUDED_NSD_PTCP_H */
diff --git a/runtime/nsdpoll_ptcp.c b/runtime/nsdpoll_ptcp.c
new file mode 100644
index 00000000..8d95811a
--- /dev/null
+++ b/runtime/nsdpoll_ptcp.c
@@ -0,0 +1,316 @@
+/* nsdpoll_ptcp.c
+ *
+ * An implementation of the nsd epoll() interface for plain tcp sockets.
+ *
+ * Copyright 2009 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#ifdef HAVE_EPOLL_CREATE /* this module requires epoll! */
+
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#if HAVE_SYS_EPOLL_H
+# include <sys/epoll.h>
+#endif
+
+#include "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "srUtils.h"
+#include "nspoll.h"
+#include "nsd_ptcp.h"
+#include "nsdpoll_ptcp.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+
+
+/* -START------------------------- helpers for event list ------------------------------------ */
+
+/* add new entry to list. We assume that the fd is not already present and DO NOT check this!
+ * Returns newly created entry in pEvtLst.
+ * Note that we currently need to use level-triggered mode, because the upper layers do not work
+ * in parallel. As such, in edge-triggered mode we may not get notified, because new data comes
+ * in after we have read everything that was present. To use ET mode, we need to change the upper
+ * peers so that they immediately start a new wait before processing the data read. That obviously
+ * requires more elaborate redesign and we postpone this until the current more simplictic mode has
+ * been proven OK in practice.
+ * rgerhards, 2009-11-18
+ */
+static inline rsRetVal
+addEvent(nsdpoll_ptcp_t *pThis, int id, void *pUsr, int mode, nsd_ptcp_t *pSock, nsdpoll_epollevt_lst_t **pEvtLst) {
+ nsdpoll_epollevt_lst_t *pNew;
+ DEFiRet;
+
+ CHKmalloc(pNew = (nsdpoll_epollevt_lst_t*) calloc(1, sizeof(nsdpoll_epollevt_lst_t)));
+ pNew->id = id;
+ pNew->pUsr = pUsr;
+ pNew->pSock = pSock;
+ pNew->event.events = 0; /* TODO: at some time we should be able to use EPOLLET */
+ //pNew->event.events = EPOLLET;
+ if(mode & NSDPOLL_IN)
+ pNew->event.events |= EPOLLIN;
+ if(mode & NSDPOLL_OUT)
+ pNew->event.events |= EPOLLOUT;
+ pNew->event.data.ptr = pNew;
+ pthread_mutex_lock(&pThis->mutEvtLst);
+ pNew->pNext = pThis->pRoot;
+ pThis->pRoot = pNew;
+ pthread_mutex_unlock(&pThis->mutEvtLst);
+ *pEvtLst = pNew;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* find and unlink the entry identified by id/pUsr from the list.
+ * rgerhards, 2009-11-23
+ */
+static inline rsRetVal
+unlinkEvent(nsdpoll_ptcp_t *pThis, int id, void *pUsr, nsdpoll_epollevt_lst_t **ppEvtLst) {
+ nsdpoll_epollevt_lst_t *pEvtLst;
+ nsdpoll_epollevt_lst_t *pPrev = NULL;
+ DEFiRet;
+
+ pthread_mutex_lock(&pThis->mutEvtLst);
+ pEvtLst = pThis->pRoot;
+ while(pEvtLst != NULL && !(pEvtLst->id == id && pEvtLst->pUsr == pUsr)) {
+ pPrev = pEvtLst;
+ pEvtLst = pEvtLst->pNext;
+ }
+ if(pEvtLst == NULL)
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+
+ *ppEvtLst = pEvtLst;
+
+ /* unlink */
+ if(pPrev == NULL)
+ pThis->pRoot = pEvtLst->pNext;
+ else
+ pPrev->pNext = pEvtLst->pNext;
+
+finalize_it:
+ pthread_mutex_unlock(&pThis->mutEvtLst);
+ RETiRet;
+}
+
+
+/* destruct the provided element. It must already be unlinked from the list.
+ * rgerhards, 2009-11-23
+ */
+static inline rsRetVal
+delEvent(nsdpoll_epollevt_lst_t **ppEvtLst) {
+ DEFiRet;
+ free(*ppEvtLst);
+ *ppEvtLst = NULL;
+ RETiRet;
+}
+
+
+/* -END--------------------------- helpers for event list ------------------------------------ */
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(nsdpoll_ptcp) /* be sure to specify the object type also in END macro! */
+#if defined(EPOLL_CLOEXEC) && defined(HAVE_EPOLL_CREATE1)
+ DBGPRINTF("nsdpoll_ptcp uses epoll_create1()\n");
+ pThis->efd = epoll_create1(EPOLL_CLOEXEC);
+ if(pThis->efd < 0 && errno == ENOSYS)
+#endif
+ {
+ DBGPRINTF("nsdpoll_ptcp uses epoll_create()\n");
+ pThis->efd = epoll_create(100); /* size is ignored in newer kernels, but 100 is not bad... */
+ }
+
+ if(pThis->efd < 0) {
+ DBGPRINTF("epoll_create1() could not create fd\n");
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ pthread_mutex_init(&pThis->mutEvtLst, NULL);
+finalize_it:
+ENDobjConstruct(nsdpoll_ptcp)
+
+
+/* destructor for the nsdpoll_ptcp object */
+BEGINobjDestruct(nsdpoll_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */
+ nsdpoll_epollevt_lst_t *node;
+ nsdpoll_epollevt_lst_t *nextnode;
+CODESTARTobjDestruct(nsdpoll_ptcp)
+ /* we check if the epoll list still holds entries. This may happen, but
+ * is a bit unusual.
+ */
+ if(pThis->pRoot != NULL) {
+ for(node = pThis->pRoot ; node != NULL ; node = nextnode) {
+ nextnode = node->pNext;
+ dbgprintf("nsdpoll_ptcp destruct, need to destruct node %p\n", node);
+ delEvent(&node);
+ }
+ }
+ pthread_mutex_destroy(&pThis->mutEvtLst);
+ENDobjDestruct(nsdpoll_ptcp)
+
+
+/* Modify socket set */
+static rsRetVal
+Ctl(nsdpoll_t *pNsdpoll, nsd_t *pNsd, int id, void *pUsr, int mode, int op) {
+ nsdpoll_ptcp_t *pThis = (nsdpoll_ptcp_t*) pNsdpoll;
+ nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd;
+ nsdpoll_epollevt_lst_t *pEventLst;
+ int errSave;
+ char errStr[512];
+ DEFiRet;
+
+ if(op == NSDPOLL_ADD) {
+ dbgprintf("adding nsdpoll entry %d/%p, sock %d\n", id, pUsr, pSock->sock);
+ CHKiRet(addEvent(pThis, id, pUsr, mode, pSock, &pEventLst));
+ if(epoll_ctl(pThis->efd, EPOLL_CTL_ADD, pSock->sock, &pEventLst->event) < 0) {
+ errSave = errno;
+ rs_strerror_r(errSave, errStr, sizeof(errStr));
+ errmsg.LogError(errSave, RS_RET_ERR_EPOLL_CTL,
+ "epoll_ctl failed on fd %d, id %d/%p, op %d with %s\n",
+ pSock->sock, id, pUsr, mode, errStr);
+ }
+ } else if(op == NSDPOLL_DEL) {
+ dbgprintf("removing nsdpoll entry %d/%p, sock %d\n", id, pUsr, pSock->sock);
+ CHKiRet(unlinkEvent(pThis, id, pUsr, &pEventLst));
+ if(epoll_ctl(pThis->efd, EPOLL_CTL_DEL, pSock->sock, &pEventLst->event) < 0) {
+ errSave = errno;
+ rs_strerror_r(errSave, errStr, sizeof(errStr));
+ errmsg.LogError(errSave, RS_RET_ERR_EPOLL_CTL,
+ "epoll_ctl failed on fd %d, id %d/%p, op %d with %s\n",
+ pSock->sock, id, pUsr, mode, errStr);
+ ABORT_FINALIZE(RS_RET_ERR_EPOLL_CTL);
+ }
+ CHKiRet(delEvent(&pEventLst));
+ } else {
+ dbgprintf("program error: invalid NSDPOLL_mode %d - ignoring request\n", op);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Wait for io to become ready. After the successful call, idRdy contains the
+ * id set by the caller for that i/o event, ppUsr is a pointer to a location
+ * where the user pointer shall be stored.
+ * numEntries contains the maximum number of entries on entry and the actual
+ * number of entries actually read on exit.
+ * rgerhards, 2009-11-18
+ */
+static rsRetVal
+Wait(nsdpoll_t *pNsdpoll, int timeout, int *numEntries, nsd_epworkset_t workset[]) {
+ nsdpoll_ptcp_t *pThis = (nsdpoll_ptcp_t*) pNsdpoll;
+ nsdpoll_epollevt_lst_t *pOurEvt;
+ struct epoll_event event[128];
+ int nfds;
+ int i;
+ DEFiRet;
+
+ assert(workset != NULL);
+
+ if(*numEntries > 128)
+ *numEntries = 128;
+ DBGPRINTF("doing epoll_wait for max %d events\n", *numEntries);
+ nfds = epoll_wait(pThis->efd, event, *numEntries, timeout);
+ if(nfds == -1) {
+ if(errno == EINTR) {
+ ABORT_FINALIZE(RS_RET_EINTR);
+ } else {
+ DBGPRINTF("epoll() returned with error code %d\n", errno);
+ ABORT_FINALIZE(RS_RET_ERR_EPOLL);
+ }
+ } else if(nfds == 0) {
+ ABORT_FINALIZE(RS_RET_TIMEOUT);
+ }
+
+ /* we got valid events, so tell the caller... */
+dbgprintf("epoll returned %d entries\n", nfds);
+ for(i = 0 ; i < nfds ; ++i) {
+ pOurEvt = (nsdpoll_epollevt_lst_t*) event[i].data.ptr;
+ workset[i].id = pOurEvt->id;
+ workset[i].pUsr = pOurEvt->pUsr;
+dbgprintf("epoll push ppusr[%d]: %p\n", i, pOurEvt->pUsr);
+ }
+ *numEntries = nfds;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* ------------------------------ end support for the epoll() interface ------------------------------ */
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nsdpoll_ptcp)
+CODESTARTobjQueryInterface(nsdpoll_ptcp)
+ if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = (rsRetVal(*)(nsdpoll_t**)) nsdpoll_ptcpConstruct;
+ pIf->Destruct = (rsRetVal(*)(nsdpoll_t**)) nsdpoll_ptcpDestruct;
+ pIf->Ctl = Ctl;
+ pIf->Wait = Wait;
+finalize_it:
+ENDobjQueryInterface(nsdpoll_ptcp)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nsdpoll_ptcp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsdpoll_ptcp)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ENDObjClassExit(nsdpoll_ptcp)
+
+
+/* Initialize the nsdpoll_ptcp class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nsdpoll_ptcp, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(nsdpoll_ptcp)
+#endif /* #ifdef HAVE_EPOLL_CREATE this module requires epoll! */
+
+/* vi:set ai:
+ */
diff --git a/runtime/nsdpoll_ptcp.h b/runtime/nsdpoll_ptcp.h
new file mode 100644
index 00000000..dfefad1b
--- /dev/null
+++ b/runtime/nsdpoll_ptcp.h
@@ -0,0 +1,61 @@
+/* An implementation of the nsd poll interface for plain tcp sockets.
+ *
+ * Copyright 2009 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef INCLUDED_NSDPOLL_PTCP_H
+#define INCLUDED_NSDPOLL_PTCP_H
+
+#include "nsd.h"
+#if HAVE_SYS_EPOLL_H
+# include <sys/epoll.h>
+#endif
+typedef nsdpoll_if_t nsdpoll_ptcp_if_t; /* we just *implement* this interface */
+/* a helper object to keep track of the epoll event records
+ * Note that we need to keep track of that list because we need to
+ * free the events when they are no longer needed.
+ */
+typedef struct nsdpoll_epollevt_lst_s nsdpoll_epollevt_lst_t;
+struct nsdpoll_epollevt_lst_s {
+#if HAVE_SYS_EPOLL_H
+ epoll_event_t event;
+#endif
+ int id;
+ void *pUsr;
+ nsd_ptcp_t *pSock; /* our associated netstream driver data */
+ nsdpoll_epollevt_lst_t *pNext;
+};
+
+/* the nsdpoll_ptcp object */
+struct nsdpoll_ptcp_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ int efd; /* file descriptor used by epoll */
+ nsdpoll_epollevt_lst_t *pRoot; /* Root of the epoll event list */
+ pthread_mutex_t mutEvtLst;
+};
+
+/* interface is defined in nsd.h, we just implement it! */
+#define nsdpoll_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION
+
+/* prototypes */
+PROTOTYPEObj(nsdpoll_ptcp);
+
+#endif /* #ifndef INCLUDED_NSDPOLL_PTCP_H */
diff --git a/runtime/nsdsel_gtls.c b/runtime/nsdsel_gtls.c
new file mode 100644
index 00000000..b086add8
--- /dev/null
+++ b/runtime/nsdsel_gtls.c
@@ -0,0 +1,276 @@
+/* nsdsel_gtls.c
+ *
+ * An implementation of the nsd select() interface for GnuTLS.
+ *
+ * Copyright (C) 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/select.h>
+#include <gnutls/gnutls.h>
+
+#include "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "nsd.h"
+#include "nsd_gtls.h"
+#include "nsd_ptcp.h"
+#include "nsdsel_ptcp.h"
+#include "nsdsel_gtls.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(nsdsel_ptcp)
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(nsdsel_gtls) /* be sure to specify the object type also in END macro! */
+ iRet = nsdsel_ptcp.Construct(&pThis->pTcp);
+ENDobjConstruct(nsdsel_gtls)
+
+
+/* destructor for the nsdsel_gtls object */
+BEGINobjDestruct(nsdsel_gtls) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nsdsel_gtls)
+ if(pThis->pTcp != NULL)
+ nsdsel_ptcp.Destruct(&pThis->pTcp);
+ENDobjDestruct(nsdsel_gtls)
+
+
+/* Add a socket to the select set */
+static rsRetVal
+Add(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp)
+{
+ DEFiRet;
+ nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel;
+ nsd_gtls_t *pNsdGTLS = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_gtls);
+ ISOBJ_TYPE_assert(pNsdGTLS, nsd_gtls);
+ if(pNsdGTLS->iMode == 1) {
+ if(waitOp == NSDSEL_RD && gtlsHasRcvInBuffer(pNsdGTLS)) {
+ ++pThis->iBufferRcvReady;
+ dbgprintf("nsdsel_gtls: data already present in buffer, initiating "
+ "dummy select %p->iBufferRcvReady=%d\n",
+ pThis, pThis->iBufferRcvReady);
+ FINALIZE;
+ }
+ if(pNsdGTLS->rtryCall != gtlsRtry_None) {
+ if(gnutls_record_get_direction(pNsdGTLS->sess) == 0) {
+ CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, NSDSEL_RD));
+ } else {
+ CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, NSDSEL_WR));
+ }
+ FINALIZE;
+ }
+ }
+
+ /* if we reach this point, we need no special handling */
+ CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdGTLS->pTcp, waitOp));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* perform the select() piNumReady returns how many descriptors are ready for IO
+ * TODO: add timeout!
+ */
+static rsRetVal
+Select(nsdsel_t *pNsdsel, int *piNumReady)
+{
+ DEFiRet;
+ nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel;
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_gtls);
+ if(pThis->iBufferRcvReady > 0) {
+ /* we still have data ready! */
+ *piNumReady = pThis->iBufferRcvReady;
+ dbgprintf("nsdsel_gtls: doing dummy select, data present\n");
+ } else {
+ iRet = nsdsel_ptcp.Select(pThis->pTcp, piNumReady);
+ }
+
+ RETiRet;
+}
+
+
+/* retry an interrupted GTLS operation
+ * rgerhards, 2008-04-30
+ */
+static rsRetVal
+doRetry(nsd_gtls_t *pNsd)
+{
+ DEFiRet;
+ int gnuRet;
+
+ dbgprintf("GnuTLS requested retry of %d operation - executing\n", pNsd->rtryCall);
+
+ /* We follow a common scheme here: first, we do the systen call and
+ * then we check the result. So far, the result is checked after the
+ * switch, because the result check is the same for all calls. Note that
+ * this may change once we deal with the read and write calls (but
+ * probably this becomes an issue only when we begin to work on TLS
+ * for relp). -- rgerhards, 2008-04-30
+ */
+ switch(pNsd->rtryCall) {
+ case gtlsRtry_handshake:
+ gnuRet = gnutls_handshake(pNsd->sess);
+ if(gnuRet == 0) {
+ pNsd->rtryCall = gtlsRtry_None; /* we are done */
+ /* we got a handshake, now check authorization */
+ CHKiRet(gtlsChkPeerAuth(pNsd));
+ }
+ break;
+ case gtlsRtry_recv:
+ dbgprintf("retrying gtls recv, nsd: %p\n", pNsd);
+ CHKiRet(gtlsRecordRecv(pNsd));
+ pNsd->rtryCall = gtlsRtry_None; /* we are done */
+ gnuRet = 0;
+ break;
+ default:
+ assert(0); /* this shall not happen! */
+ dbgprintf("ERROR: pNsd->rtryCall invalid in nsdsel_gtls.c:%d\n", __LINE__);
+ gnuRet = 0; /* if it happens, we have at least a defined behaviour... ;) */
+ break;
+ }
+
+ if(gnuRet == 0) {
+ pNsd->rtryCall = gtlsRtry_None; /* we are done */
+ } else if(gnuRet != GNUTLS_E_AGAIN && gnuRet != GNUTLS_E_INTERRUPTED) {
+ uchar *pErr = gtlsStrerror(gnuRet);
+ dbgprintf("unexpected GnuTLS error %d in %s:%d: %s\n", gnuRet, __FILE__, __LINE__, pErr);
+ free(pErr);
+ pNsd->rtryCall = gtlsRtry_None; /* we are also done... ;) */
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ }
+ /* if we are interrupted once again (else case), we do not need to
+ * change our status because we are already setup for retries.
+ */
+
+finalize_it:
+ if(iRet != RS_RET_OK && iRet != RS_RET_CLOSED && iRet != RS_RET_RETRY)
+ pNsd->bAbortConn = 1; /* request abort */
+dbgprintf("XXXXXX: doRetry: iRet %d, pNsd->bAbortConn %d\n", iRet, pNsd->bAbortConn);
+ RETiRet;
+}
+
+
+/* check if a socket is ready for IO */
+static rsRetVal
+IsReady(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady)
+{
+ DEFiRet;
+ nsdsel_gtls_t *pThis = (nsdsel_gtls_t*) pNsdsel;
+ nsd_gtls_t *pNsdGTLS = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_gtls);
+ ISOBJ_TYPE_assert(pNsdGTLS, nsd_gtls);
+ if(pNsdGTLS->iMode == 1) {
+ if(waitOp == NSDSEL_RD && gtlsHasRcvInBuffer(pNsdGTLS)) {
+ *pbIsReady = 1;
+ --pThis->iBufferRcvReady; /* one "pseudo-read" less */
+ dbgprintf("nsdl_gtls: dummy read, decermenting %p->iBufRcvReady, now %d\n",
+ pThis, pThis->iBufferRcvReady);
+ FINALIZE;
+ }
+ if(pNsdGTLS->rtryCall != gtlsRtry_None) {
+ CHKiRet(doRetry(pNsdGTLS));
+ /* we used this up for our own internal processing, so the socket
+ * is not ready from the upper layer point of view.
+ */
+ *pbIsReady = 0;
+ FINALIZE;
+ }
+ /* now we must ensure that we do not fall back to PTCP if we have
+ * done a "dummy" select. In that case, we know when the predicate
+ * is not matched here, we do not have data available for this
+ * socket. -- rgerhards, 2010-11-20
+ */
+ if(pThis->iBufferRcvReady) {
+ dbgprintf("nsd_gtls: dummy read, buffer not available for this FD\n");
+ *pbIsReady = 0;
+ FINALIZE;
+ }
+ }
+
+ CHKiRet(nsdsel_ptcp.IsReady(pThis->pTcp, pNsdGTLS->pTcp, waitOp, pbIsReady));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* ------------------------------ end support for the select() interface ------------------------------ */
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nsdsel_gtls)
+CODESTARTobjQueryInterface(nsdsel_gtls)
+ if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = (rsRetVal(*)(nsdsel_t**)) nsdsel_gtlsConstruct;
+ pIf->Destruct = (rsRetVal(*)(nsdsel_t**)) nsdsel_gtlsDestruct;
+ pIf->Add = Add;
+ pIf->Select = Select;
+ pIf->IsReady = IsReady;
+finalize_it:
+ENDobjQueryInterface(nsdsel_gtls)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nsdsel_gtls, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsdsel_gtls)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(nsdsel_ptcp, LM_NSD_PTCP_FILENAME);
+ENDObjClassExit(nsdsel_gtls)
+
+
+/* Initialize the nsdsel_gtls class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nsdsel_gtls, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(nsdsel_ptcp, LM_NSD_PTCP_FILENAME));
+
+ /* set our own handlers */
+ENDObjClassInit(nsdsel_gtls)
+/* vi:set ai:
+ */
diff --git a/runtime/nsdsel_gtls.h b/runtime/nsdsel_gtls.h
new file mode 100644
index 00000000..eb96f6eb
--- /dev/null
+++ b/runtime/nsdsel_gtls.h
@@ -0,0 +1,41 @@
+/* An implementation of the nsd select interface for GnuTLS.
+ *
+ * Copyright (C) 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDED_NSDSEL_GTLS_H
+#define INCLUDED_NSDSEL_GTLS_H
+
+#include "nsd.h"
+typedef nsdsel_if_t nsdsel_gtls_if_t; /* we just *implement* this interface */
+
+/* the nsdsel_gtls object */
+struct nsdsel_gtls_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ nsdsel_t *pTcp; /* our aggregated ptcp sel handler (which does almost everything) */
+ int iBufferRcvReady; /* number of descriptiors where no RD select is needed because we have data in buf */
+};
+
+/* interface is defined in nsd.h, we just implement it! */
+#define nsdsel_gtlsCURR_IF_VERSION nsdCURR_IF_VERSION
+
+/* prototypes */
+PROTOTYPEObj(nsdsel_gtls);
+
+#endif /* #ifndef INCLUDED_NSDSEL_GTLS_H */
diff --git a/runtime/nsdsel_ptcp.c b/runtime/nsdsel_ptcp.c
new file mode 100644
index 00000000..e2cfca7c
--- /dev/null
+++ b/runtime/nsdsel_ptcp.c
@@ -0,0 +1,227 @@
+/* nsdsel_ptcp.c
+ *
+ * An implementation of the nsd select() interface for plain tcp sockets.
+ *
+ * Copyright 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+#include <sys/select.h>
+
+#include "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "nsd_ptcp.h"
+#include "nsdsel_ptcp.h"
+#include "unlimited_select.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(nsdsel_ptcp) /* be sure to specify the object type also in END macro! */
+ pThis->maxfds = 0;
+#ifdef USE_UNLIMITED_SELECT
+ pThis->pReadfds = calloc(1, glbl.GetFdSetSize());
+ pThis->pWritefds = calloc(1, glbl.GetFdSetSize());
+#else
+ FD_ZERO(&pThis->readfds);
+ FD_ZERO(&pThis->writefds);
+#endif
+ENDobjConstruct(nsdsel_ptcp)
+
+
+/* destructor for the nsdsel_ptcp object */
+BEGINobjDestruct(nsdsel_ptcp) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nsdsel_ptcp)
+#ifdef USE_UNLIMITED_SELECT
+ freeFdSet(pThis->pReadfds);
+ freeFdSet(pThis->pWritefds);
+#endif
+ENDobjDestruct(nsdsel_ptcp)
+
+
+/* Add a socket to the select set */
+static rsRetVal
+Add(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp)
+{
+ DEFiRet;
+ nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel;
+ nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd;
+#ifdef USE_UNLIMITED_SELECT
+ fd_set *pReadfds = pThis->pReadfds;
+ fd_set *pWritefds = pThis->pWritefds;
+#else
+ fd_set *pReadfds = &pThis->readfds;
+ fd_set *pWritefds = &pThis->writefds;
+#endif
+
+ ISOBJ_TYPE_assert(pSock, nsd_ptcp);
+ ISOBJ_TYPE_assert(pThis, nsdsel_ptcp);
+
+ switch(waitOp) {
+ case NSDSEL_RD:
+ FD_SET(pSock->sock, pReadfds);
+ break;
+ case NSDSEL_WR:
+ FD_SET(pSock->sock, pWritefds);
+ break;
+ case NSDSEL_RDWR:
+ FD_SET(pSock->sock, pReadfds);
+ FD_SET(pSock->sock, pWritefds);
+ break;
+ }
+
+ if(pSock->sock > pThis->maxfds)
+ pThis->maxfds = pSock->sock;
+
+ RETiRet;
+}
+
+
+/* perform the select() piNumReady returns how many descriptors are ready for IO
+ * TODO: add timeout!
+ */
+static rsRetVal
+Select(nsdsel_t *pNsdsel, int *piNumReady)
+{
+ DEFiRet;
+ int i;
+ nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel;
+#ifdef USE_UNLIMITED_SELECT
+ fd_set *pReadfds = pThis->pReadfds;
+ fd_set *pWritefds = pThis->pWritefds;
+#else
+ fd_set *pReadfds = &pThis->readfds;
+ fd_set *pWritefds = &pThis->writefds;
+#endif
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_ptcp);
+ assert(piNumReady != NULL);
+
+ if(Debug) { // TODO: debug setting!
+ // TODO: name in dbgprintf!
+ dbgprintf("--------<NSDSEL_PTCP> calling select, active fds (max %d): ", pThis->maxfds);
+ for(i = 0; i <= pThis->maxfds; ++i)
+ if(FD_ISSET(i, pReadfds) || FD_ISSET(i, pWritefds))
+ dbgprintf("%d ", i);
+ dbgprintf("\n");
+ }
+
+ /* now do the select */
+ *piNumReady = select(pThis->maxfds+1, pReadfds, pWritefds, NULL, NULL);
+
+ RETiRet;
+}
+
+
+/* check if a socket is ready for IO */
+static rsRetVal
+IsReady(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp, int *pbIsReady)
+{
+ DEFiRet;
+ nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel;
+ nsd_ptcp_t *pSock = (nsd_ptcp_t*) pNsd;
+#ifdef USE_UNLIMITED_SELECT
+ fd_set *pReadfds = pThis->pReadfds;
+ fd_set *pWritefds = pThis->pWritefds;
+#else
+ fd_set *pReadfds = &pThis->readfds;
+ fd_set *pWritefds = &pThis->writefds;
+#endif
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_ptcp);
+ ISOBJ_TYPE_assert(pSock, nsd_ptcp);
+ assert(pbIsReady != NULL);
+
+ switch(waitOp) {
+ case NSDSEL_RD:
+ *pbIsReady = FD_ISSET(pSock->sock, pReadfds);
+ break;
+ case NSDSEL_WR:
+ *pbIsReady = FD_ISSET(pSock->sock, pWritefds);
+ break;
+ case NSDSEL_RDWR:
+ *pbIsReady = FD_ISSET(pSock->sock, pReadfds)
+ | FD_ISSET(pSock->sock, pWritefds);
+ break;
+ }
+
+ RETiRet;
+}
+
+
+/* ------------------------------ end support for the select() interface ------------------------------ */
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nsdsel_ptcp)
+CODESTARTobjQueryInterface(nsdsel_ptcp)
+ if(pIf->ifVersion != nsdCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = (rsRetVal(*)(nsdsel_t**)) nsdsel_ptcpConstruct;
+ pIf->Destruct = (rsRetVal(*)(nsdsel_t**)) nsdsel_ptcpDestruct;
+ pIf->Add = Add;
+ pIf->Select = Select;
+ pIf->IsReady = IsReady;
+finalize_it:
+ENDobjQueryInterface(nsdsel_ptcp)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nsdsel_ptcp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsdsel_ptcp)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ENDObjClassExit(nsdsel_ptcp)
+
+
+/* Initialize the nsdsel_ptcp class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nsdsel_ptcp, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(nsdsel_ptcp)
+/* vi:set ai:
+ */
diff --git a/runtime/nsdsel_ptcp.h b/runtime/nsdsel_ptcp.h
new file mode 100644
index 00000000..f9ec8210
--- /dev/null
+++ b/runtime/nsdsel_ptcp.h
@@ -0,0 +1,49 @@
+/* An implementation of the nsd select interface for plain tcp sockets.
+ *
+ * Copyright 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef INCLUDED_NSDSEL_PTCP_H
+#define INCLUDED_NSDSEL_PTCP_H
+
+#include "nsd.h"
+typedef nsdsel_if_t nsdsel_ptcp_if_t; /* we just *implement* this interface */
+
+/* the nsdsel_ptcp object */
+struct nsdsel_ptcp_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ int maxfds;
+#ifdef USE_UNLIMITED_SELECT
+ fd_set *pReadfds;
+ fd_set *pWritefds;
+#else
+ fd_set readfds;
+ fd_set writefds;
+#endif
+};
+
+/* interface is defined in nsd.h, we just implement it! */
+#define nsdsel_ptcpCURR_IF_VERSION nsdCURR_IF_VERSION
+
+/* prototypes */
+PROTOTYPEObj(nsdsel_ptcp);
+
+#endif /* #ifndef INCLUDED_NSDSEL_PTCP_H */
diff --git a/runtime/nspoll.c b/runtime/nspoll.c
new file mode 100644
index 00000000..a936b255
--- /dev/null
+++ b/runtime/nspoll.c
@@ -0,0 +1,195 @@
+/* nspoll.c
+ *
+ * This is an io waiter interface utilizing the much-more-efficient poll/epoll API.
+ * Note that it may not always be available for a given driver. If so, that is reported
+ * back to the upper peer which then should consult a nssel-based io waiter.
+ *
+ * Work on this module begun 2009-11-18 by Rainer Gerhards.
+ *
+ * Copyright 2009 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "module-template.h"
+#include "netstrm.h"
+#include "nspoll.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+
+/* load our low-level driver. This must be done before any
+ * driver-specific functions (allmost all...) can be carried
+ * out. Note that the driver's .ifIsLoaded is correctly
+ * initialized by calloc() and we depend on that. Please note that
+ * we do some name-mangeling. We know that each nsd driver also needs
+ * a nspoll driver. So we simply append "sel" to the nsd driver name: This,
+ * of course, means that the driver name must match these rules, but that
+ * shouldn't be a real problem.
+ * WARNING: this code is mostly identical to similar code in
+ * netstrms.c - TODO: abstract it and move it to some common place.
+ * rgerhards, 2008-04-28
+ */
+static rsRetVal
+loadDrvr(nspoll_t *pThis)
+{
+ DEFiRet;
+ uchar *pBaseDrvrName;
+ uchar szDrvrName[48]; /* 48 shall be large enough */
+
+ pBaseDrvrName = pThis->pBaseDrvrName;
+ if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */
+ pBaseDrvrName = glbl.GetDfltNetstrmDrvr();
+ if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsdpoll_%s", pBaseDrvrName) == sizeof(szDrvrName))
+ ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG);
+ CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName));
+
+ pThis->Drvr.ifVersion = nsdCURR_IF_VERSION;
+ /* The pDrvrName+2 below is a hack to obtain the object name. It
+ * safes us to have yet another variable with the name without "lm" in
+ * front of it. If we change the module load interface, we may re-think
+ * about this hack, but for the time being it is efficient and clean
+ * enough. -- rgerhards, 2008-04-18
+ */
+ CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->pDrvrName != NULL)
+ free(pThis->pDrvrName);
+ pThis->pDrvrName = NULL;
+ }
+ RETiRet;
+}
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(nspoll) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(nspoll)
+
+
+/* destructor for the nspoll object */
+BEGINobjDestruct(nspoll) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nspoll)
+ if(pThis->pDrvrData != NULL)
+ pThis->Drvr.Destruct(&pThis->pDrvrData);
+
+ /* and now we must release our driver, if we got one. We use the presence of
+ * a driver name string as load indicator (because we also need that string
+ * to release the driver
+ */
+ if(pThis->pDrvrName != NULL) {
+ obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr);
+ free(pThis->pDrvrName);
+ }
+ENDobjDestruct(nspoll)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+ConstructFinalize(nspoll_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nspoll);
+ CHKiRet(loadDrvr(pThis));
+ CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData));
+finalize_it:
+ RETiRet;
+}
+
+
+/* Carries out the actual wait (all done in lower layers)
+ */
+static rsRetVal
+Wait(nspoll_t *pThis, int timeout, int *numEntries, nsd_epworkset_t workset[]) {
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nspoll);
+ assert(workset != NULL);
+ iRet = pThis->Drvr.Wait(pThis->pDrvrData, timeout, numEntries, workset);
+ RETiRet;
+}
+
+
+/* semantics like the epoll_ctl() function, does the same thing.
+ * rgerhards, 2009-11-18
+ */
+static rsRetVal
+Ctl(nspoll_t *pThis, netstrm_t *pStrm, int id, void *pUsr, int mode, int op) {
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nspoll);
+ iRet = pThis->Drvr.Ctl(pThis->pDrvrData, pStrm->pDrvrData, id, pUsr, mode, op);
+ RETiRet;
+}
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nspoll)
+CODESTARTobjQueryInterface(nspoll)
+ if(pIf->ifVersion != nspollCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = nspollConstruct;
+ pIf->ConstructFinalize = ConstructFinalize;
+ pIf->Destruct = nspollDestruct;
+ pIf->Wait = Wait;
+ pIf->Ctl = Ctl;
+finalize_it:
+ENDobjQueryInterface(nspoll)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nspoll, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nspoll)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ENDObjClassExit(nspoll)
+
+
+/* Initialize the nspoll class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nspoll, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ DBGPRINTF("doing nspollClassInit\n");
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(nspoll)
+/* vi:set ai:
+ */
diff --git a/runtime/nspoll.h b/runtime/nspoll.h
new file mode 100644
index 00000000..037f6c38
--- /dev/null
+++ b/runtime/nspoll.h
@@ -0,0 +1,66 @@
+/* Definitions for the nspoll io activity waiter
+ *
+ * Copyright 2009 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef INCLUDED_NSPOLL_H
+#define INCLUDED_NSPOLL_H
+
+#include "netstrms.h"
+
+/* some operations to be portable when we do not have epoll() available */
+#define NSDPOLL_ADD 1
+#define NSDPOLL_DEL 2
+
+/* and some mode specifiers for waiting on input/output */
+#define NSDPOLL_IN 1 /* EPOLLIN */
+#define NSDPOLL_OUT 2 /* EPOLLOUT */
+/* next is 4, 8, 16, ... - must be bit values, as they are ored! */
+
+/* the nspoll object */
+struct nspoll_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ nsd_t *pDrvrData; /**< the driver's data elements */
+ uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */
+ uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */
+ nsdpoll_if_t Drvr; /**< our stream driver */
+};
+
+
+/* interface */
+BEGINinterface(nspoll) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(nspoll_t **ppThis);
+ rsRetVal (*ConstructFinalize)(nspoll_t *pThis);
+ rsRetVal (*Destruct)(nspoll_t **ppThis);
+ rsRetVal (*Wait)(nspoll_t *pNsdpoll, int timeout, int *numEntries, nsd_epworkset_t workset[]);
+ rsRetVal (*Ctl)(nspoll_t *pNsdpoll, netstrm_t *pStrm, int id, void *pUsr, int mode, int op);
+ rsRetVal (*IsEPollSupported)(void); /* static method */
+ENDinterface(nspoll)
+#define nspollCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */
+/* interface change in v2 is that wait supports multiple return objects */
+
+/* prototypes */
+PROTOTYPEObj(nspoll);
+
+/* the name of our library binary */
+#define LM_NSPOLL_FILENAME LM_NETSTRMS_FILENAME
+
+#endif /* #ifndef INCLUDED_NSPOLL_H */
diff --git a/runtime/nssel.c b/runtime/nssel.c
new file mode 100644
index 00000000..751dae9b
--- /dev/null
+++ b/runtime/nssel.c
@@ -0,0 +1,226 @@
+/* nssel.c
+ *
+ * The io waiter is a helper object enabling us to wait on a set of streams to become
+ * ready for IO - this is modelled after select(). We need this, because
+ * stream drivers may have different concepts. Consequently,
+ * the structure must contain nsd_t's from the same stream driver type
+ * only. This is implemented as a singly-linked list where every
+ * new element is added at the top of the list.
+ *
+ * Work on this module begun 2008-04-22 by Rainer Gerhards.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <errno.h>
+#include <string.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "module-template.h"
+#include "netstrm.h"
+#include "nssel.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+
+/* load our low-level driver. This must be done before any
+ * driver-specific functions (allmost all...) can be carried
+ * out. Note that the driver's .ifIsLoaded is correctly
+ * initialized by calloc() and we depend on that. Please note that
+ * we do some name-mangeling. We know that each nsd driver also needs
+ * a nssel driver. So we simply append "sel" to the nsd driver name: This,
+ * of course, means that the driver name must match these rules, but that
+ * shouldn't be a real problem.
+ * WARNING: this code is mostly identical to similar code in
+ * netstrms.c - TODO: abstract it and move it to some common place.
+ * rgerhards, 2008-04-28
+ */
+static rsRetVal
+loadDrvr(nssel_t *pThis)
+{
+ DEFiRet;
+ uchar *pBaseDrvrName;
+ uchar szDrvrName[48]; /* 48 shall be large enough */
+
+ pBaseDrvrName = pThis->pBaseDrvrName;
+ if(pBaseDrvrName == NULL) /* if no drvr name is set, use system default */
+ pBaseDrvrName = glbl.GetDfltNetstrmDrvr();
+ if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmnsdsel_%s", pBaseDrvrName) == sizeof(szDrvrName))
+ ABORT_FINALIZE(RS_RET_DRVRNAME_TOO_LONG);
+ CHKmalloc(pThis->pDrvrName = (uchar*) strdup((char*)szDrvrName));
+
+ pThis->Drvr.ifVersion = nsdCURR_IF_VERSION;
+ /* The pDrvrName+2 below is a hack to obtain the object name. It
+ * safes us to have yet another variable with the name without "lm" in
+ * front of it. If we change the module load interface, we may re-think
+ * about this hack, but for the time being it is efficient and clean
+ * enough. -- rgerhards, 2008-04-18
+ */
+ CHKiRet(obj.UseObj(__FILE__, szDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->pDrvrName != NULL)
+ free(pThis->pDrvrName);
+ pThis->pDrvrName = NULL;
+ }
+ RETiRet;
+}
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(nssel) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(nssel)
+
+
+/* destructor for the nssel object */
+BEGINobjDestruct(nssel) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nssel)
+ if(pThis->pDrvrData != NULL)
+ pThis->Drvr.Destruct(&pThis->pDrvrData);
+
+ /* and now we must release our driver, if we got one. We use the presence of
+ * a driver name string as load indicator (because we also need that string
+ * to release the driver
+ */
+ if(pThis->pDrvrName != NULL) {
+ obj.ReleaseObj(__FILE__, pThis->pDrvrName+2, DONT_LOAD_LIB, (void*) &pThis->Drvr);
+ free(pThis->pDrvrName);
+ }
+ENDobjDestruct(nssel)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+ConstructFinalize(nssel_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nssel);
+ CHKiRet(loadDrvr(pThis));
+ CHKiRet(pThis->Drvr.Construct(&pThis->pDrvrData));
+finalize_it:
+ RETiRet;
+}
+
+
+/* Add a stream object to the current select() set.
+ * Note that a single stream may have multiple "sockets" if
+ * it is a listener. If so, all of them are begin added.
+ */
+static rsRetVal
+Add(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nssel);
+ ISOBJ_TYPE_assert(pStrm, netstrm);
+
+ CHKiRet(pThis->Drvr.Add(pThis->pDrvrData, pStrm->pDrvrData, waitOp));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* wait for IO to happen on one of our netstreams. iNumReady has
+ * the number of ready "sockets" after the call. This function blocks
+ * until some are ready. EAGAIN is retried.
+ */
+static rsRetVal
+Wait(nssel_t *pThis, int *piNumReady)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nssel);
+ assert(piNumReady != NULL);
+ iRet = pThis->Drvr.Select(pThis->pDrvrData, piNumReady);
+ RETiRet;
+}
+
+
+/* Check if a stream is ready for IO. *piNumReady contains the remaining number
+ * of ready streams. Note that this function may say the stream is not ready
+ * but still decrement *piNumReady. This can happen when (e.g. with TLS) the low
+ * level driver requires some IO which is hidden from the upper layer point of view.
+ * rgerhards, 2008-04-23
+ */
+static rsRetVal
+IsReady(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp, int *pbIsReady, int *piNumReady)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nssel);
+ ISOBJ_TYPE_assert(pStrm, netstrm);
+ assert(pbIsReady != NULL);
+ assert(piNumReady != NULL);
+ iRet = pThis->Drvr.IsReady(pThis->pDrvrData, pStrm->pDrvrData, waitOp, pbIsReady);
+ RETiRet;
+}
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nssel)
+CODESTARTobjQueryInterface(nssel)
+ if(pIf->ifVersion != nsselCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = nsselConstruct;
+ pIf->ConstructFinalize = ConstructFinalize;
+ pIf->Destruct = nsselDestruct;
+ pIf->Add = Add;
+ pIf->Wait = Wait;
+ pIf->IsReady = IsReady;
+finalize_it:
+ENDobjQueryInterface(nssel)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nssel, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nssel)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ENDObjClassExit(nssel)
+
+
+/* Initialize the nssel class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nssel, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ DBGPRINTF("doing nsselClassInit\n");
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(nssel)
+/* vi:set ai:
+ */
diff --git a/runtime/nssel.h b/runtime/nssel.h
new file mode 100644
index 00000000..d7f4fcd3
--- /dev/null
+++ b/runtime/nssel.h
@@ -0,0 +1,54 @@
+/* Definitions for the nssel IO waiter.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDED_NSSEL_H
+#define INCLUDED_NSSEL_H
+
+#include "netstrms.h"
+
+/* the nssel object */
+struct nssel_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ nsd_t *pDrvrData; /**< the driver's data elements */
+ uchar *pBaseDrvrName; /**< nsd base driver name to use, or NULL if system default */
+ uchar *pDrvrName; /**< full base driver name (set when driver is loaded) */
+ nsdsel_if_t Drvr; /**< our stream driver */
+};
+
+
+/* interface */
+BEGINinterface(nssel) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(nssel_t **ppThis);
+ rsRetVal (*ConstructFinalize)(nssel_t *pThis);
+ rsRetVal (*Destruct)(nssel_t **ppThis);
+ rsRetVal (*Add)(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp);
+ rsRetVal (*Wait)(nssel_t *pThis, int *pNumReady);
+ rsRetVal (*IsReady)(nssel_t *pThis, netstrm_t *pStrm, nsdsel_waitOp_t waitOp, int *pbIsReady, int *piNumReady);
+ENDinterface(nssel)
+#define nsselCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+/* prototypes */
+PROTOTYPEObj(nssel);
+
+/* the name of our library binary */
+#define LM_NSSEL_FILENAME LM_NETSTRMS_FILENAME
+
+#endif /* #ifndef INCLUDED_NSSEL_H */
diff --git a/runtime/obj-types.h b/runtime/obj-types.h
new file mode 100644
index 00000000..30a6a2c0
--- /dev/null
+++ b/runtime/obj-types.h
@@ -0,0 +1,417 @@
+/* Some type definitions and macros for the obj object.
+ * I needed to move them out of the main obj.h, because obj.h's
+ * prototypes use other data types. However, their .h's rely
+ * on some of the obj.h data types and macros. So I needed to break
+ * that loop somehow and I've done that by moving the typedefs
+ * into this file here.
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OBJ_TYPES_H_INCLUDED
+#define OBJ_TYPES_H_INCLUDED
+
+#include "stringbuf.h"
+#include "syslogd-types.h"
+
+/* property types for obj[De]Serialize() */
+typedef enum {
+ PROPTYPE_NONE = 0, /* currently no value set */
+ PROPTYPE_PSZ = 1,
+ PROPTYPE_SHORT = 2,
+ PROPTYPE_INT = 3,
+ PROPTYPE_LONG = 4,
+ PROPTYPE_INT64 = 5,
+ PROPTYPE_CSTR = 6,
+ PROPTYPE_SYSLOGTIME = 7
+} propType_t;
+
+typedef unsigned objID_t;
+
+typedef enum { /* IDs of base methods supported by all objects - used for jump table, so
+ * they must start at zero and be incremented. -- rgerhards, 2008-01-04
+ */
+ objMethod_CONSTRUCT = 0,
+ objMethod_DESTRUCT = 1,
+ objMethod_SERIALIZE = 2,
+ objMethod_DESERIALIZE = 3,
+ objMethod_SETPROPERTY = 4,
+ objMethod_CONSTRUCTION_FINALIZER = 5,
+ objMethod_GETSEVERITY = 6,
+ objMethod_DEBUGPRINT = 7
+} objMethod_t;
+#define OBJ_NUM_METHODS 8 /* must be updated to contain the max number of methods supported */
+
+
+/* the base data type for interfaces
+ * This MUST be in sync with the ifBEGIN macro
+ */
+struct interface_s {
+ int ifVersion; /* must be set to version requested */
+ int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes, 2-load failed; if not 1, functions can NOT be called! */
+};
+
+
+struct objInfo_s {
+ uchar *pszID; /* the object ID as a string */
+ size_t lenID; /* length of the ID string */
+ int iObjVers;
+ uchar *pszName;
+ rsRetVal (*objMethods[OBJ_NUM_METHODS])();
+ rsRetVal (*QueryIF)(interface_t*);
+ struct modInfo_s *pModInfo;
+};
+
+
+struct obj_s { /* the dummy struct that each derived class can be casted to */
+ objInfo_t *pObjInfo;
+#ifndef NDEBUG /* this means if debug... */
+ unsigned int iObjCooCKiE; /* must always be 0xBADEFEE for a valid object */
+#endif
+ uchar *pszName; /* the name of *this* specific object instance */
+};
+
+
+/* macros which must be gloablly-visible (because they are used during definition of
+ * other objects.
+ */
+#ifndef NDEBUG /* this means if debug... */
+#include <string.h>
+# define BEGINobjInstance \
+ obj_t objData
+# define ISOBJ_assert(pObj) \
+ do { \
+ ASSERT((pObj) != NULL); \
+ ASSERT((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \
+ } while(0);
+# define ISOBJ_TYPE_assert(pObj, objType) \
+ do { \
+ ASSERT(pObj != NULL); \
+ if(strcmp((char*)(((obj_t*)pObj)->pObjInfo->pszID), #objType)) { \
+ dbgprintf("%s:%d ISOBJ assert failure: invalid object type, expected '%s' " \
+ "actual '%s', cookie: %X\n", __FILE__, __LINE__, #objType, \
+ (((obj_t*)pObj)->pObjInfo->pszID), ((obj_t*)(pObj))->iObjCooCKiE); \
+ assert(0); /* trigger assertion, messge we already have */ \
+ } \
+ ASSERT((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \
+ } while(0)
+#else /* non-debug mode, no checks but much faster */
+# define BEGINobjInstance obj_t objData
+# define ISOBJ_TYPE_assert(pObj, objType)
+# define ISOBJ_assert(pObj)
+#endif
+
+/* a set method for *very simple* object accesses. Note that this does
+ * NOT conform to the standard calling conventions and should be
+ * used only if actually nothing can go wrong! -- rgerhards, 2008-04-17
+ */
+#define DEFpropGetMeth(obj, prop, dataType)\
+ dataType obj##Get##prop(void)\
+ { \
+ return pThis->prop = pVal; \
+ }
+
+#define DEFpropSetMethPTR(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType *pVal)\
+ { \
+ /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\
+ pThis->prop = pVal; \
+ return RS_RET_OK; \
+ }
+#define PROTOTYPEpropSetMethPTR(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType*)
+#define DEFpropSetMeth(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\
+ { \
+ /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\
+ pThis->prop = pVal; \
+ return RS_RET_OK; \
+ }
+#define DEFpropSetMethFP(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType)\
+ { \
+ /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\
+ pThis->prop = pVal; \
+ return RS_RET_OK; \
+ }
+#define PROTOTYPEpropSetMethFP(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType)
+#define DEFpropSetMeth(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\
+ { \
+ /* DEV debug: dbgprintf("%sSet%s()\n", #obj, #prop); */\
+ pThis->prop = pVal; \
+ return RS_RET_OK; \
+ }
+#define PROTOTYPEpropSetMeth(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)
+#define INTERFACEpropSetMeth(obj, prop, dataType)\
+ rsRetVal (*Set##prop)(obj##_t *pThis, dataType)
+/* class initializer */
+#define PROTOTYPEObjClassInit(objName) rsRetVal objName##ClassInit(struct modInfo_s*)
+/* below: objName must be the object name (e.g. vm, strm, ...) and ISCORE must be
+ * 1 if the module is a statically linked core module and 0 if it is a
+ * dynamically loaded one. -- rgerhards, 2008-02-29
+ */
+#define OBJ_IS_CORE_MODULE 1 /* This should better be renamed to something like "OBJ_IS_NOT_LIBHEAD" or so... ;) */
+#define OBJ_IS_LOADABLE_MODULE 0
+#define BEGINObjClassInit(objName, objVers, objType) \
+rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \
+{ \
+ DEFiRet; \
+ if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \
+ CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \
+ } \
+ CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \
+ (rsRetVal (*)(void*))objName##Construct,\
+ (rsRetVal (*)(void*))objName##Destruct,\
+ (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo)); \
+
+#define ENDObjClassInit(objName) \
+ iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \
+finalize_it: \
+ RETiRet; \
+}
+
+/* ... and now the same for abstract classes.
+ * TODO: consolidate the two -- rgerhards, 2008-02-29
+ */
+#define BEGINAbstractObjClassInit(objName, objVers, objType) \
+rsRetVal objName##ClassInit(struct modInfo_s *pModInfo) \
+{ \
+ DEFiRet; \
+ if(objType == OBJ_IS_CORE_MODULE) { /* are we a core module? */ \
+ CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */ \
+ } \
+ CHKiRet(obj.InfoConstruct(&pObjInfoOBJ, (uchar*) #objName, objVers, \
+ NULL,\
+ NULL,\
+ (rsRetVal (*)(interface_t*))objName##QueryInterface, pModInfo));
+
+#define ENDObjClassInit(objName) \
+ iRet = obj.RegisterObj((uchar*)#objName, pObjInfoOBJ); \
+finalize_it: \
+ RETiRet; \
+}
+
+
+/* now come the class exit. This is to be called immediately before the class is
+ * unloaded (actual unload for plugins, program termination for core modules)
+ * gerhards, 2008-03-10
+ */
+#define PROTOTYPEObjClassExit(objName) rsRetVal objName##ClassExit(void)
+#define BEGINObjClassExit(objName, objType) \
+rsRetVal objName##ClassExit(void) \
+{ \
+ DEFiRet;
+
+#define CODESTARTObjClassExit(objName)
+
+#define ENDObjClassExit(objName) \
+ iRet = obj.UnregisterObj((uchar*)#objName); \
+ RETiRet; \
+}
+
+/* this defines both the constructor and initializer
+ * rgerhards, 2008-01-10
+ */
+#define BEGINobjConstruct(obj) \
+ rsRetVal obj##Initialize(obj##_t __attribute__((unused)) *pThis) \
+ { \
+ DEFiRet;
+
+#define ENDobjConstruct(obj) \
+ /* use finalize_it: before calling the macro (if you need it)! */ \
+ RETiRet; \
+ } \
+ rsRetVal obj##Construct(obj##_t **ppThis) \
+ { \
+ DEFiRet; \
+ obj##_t *pThis; \
+ \
+ ASSERT(ppThis != NULL); \
+ \
+ if((pThis = (obj##_t *)calloc(1, sizeof(obj##_t))) == NULL) { \
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); \
+ } \
+ objConstructSetObjInfo(pThis); \
+ \
+ obj##Initialize(pThis); \
+ \
+ finalize_it: \
+ OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \
+ RETiRet; \
+ }
+
+
+/* this defines the destructor. The important point is that the base object
+ * destructor is called. The upper-level class shall destruct all of its
+ * properties, but not the instance itself. This is freed here by the
+ * framework (we need an intact pointer because we need to free the
+ * obj_t structures inside it). A pointer to the object pointer must be
+ * parse, because it is re-set to NULL (this, for example, is important in
+ * cancellation handlers). The object pointer is always named pThis.
+ * The object is always freed, even if there is some error while
+ * Cancellation is blocked during destructors, as this could have fatal
+ * side-effects. However, this also means the upper-level object should
+ * not perform any lenghty processing.
+ * IMPORTANT: if the upper level object requires some situations where the
+ * object shall not be destructed (e.g. via reference counting), then
+ * it shall set pThis to NULL, which prevents destruction of the
+ * object.
+ * processing.
+ * rgerhards, 2008-01-30
+ */
+#define BEGINobjDestruct(OBJ) \
+ rsRetVal OBJ##Destruct(OBJ##_t __attribute__((unused)) **ppThis) \
+ { \
+ DEFiRet; \
+ OBJ##_t *pThis;
+
+#define CODESTARTobjDestruct(OBJ) \
+ ASSERT(ppThis != NULL); \
+ pThis = *ppThis; \
+ ISOBJ_TYPE_assert(pThis, OBJ);
+
+/* note: there was a long-time bug in the macro below that lead to *ppThis = NULL
+ * only when the object was actually destructed. I discovered this issue during
+ * introduction of the pRcvFrom property in msg_t, but it potentially had other
+ * effects, too. I am not sure if some experienced instability resulted from this
+ * bug OR if its fix will cause harm to so-far "correctly" running code. The later
+ * may very well be. Thus I will change it only for the current branch and also
+ * the beta, but not in all old builds. Let's see how things evolve.
+ * rgerhards, 2009-06-30
+ */
+#define ENDobjDestruct(OBJ) \
+ goto finalize_it; /* prevent compiler warning ;) */ \
+ /* no more code here! */ \
+ finalize_it: \
+ if(pThis != NULL) { \
+ obj.DestructObjSelf((obj_t*) pThis); \
+ free(pThis); \
+ } \
+ *ppThis = NULL; \
+ RETiRet; \
+ }
+
+
+/* this defines the debug print entry point. DebugPrint is optional. If
+ * it is provided, the object should output some meaningful information
+ * via the debug system.
+ * rgerhards, 2008-02-20
+ */
+#define PROTOTYPEObjDebugPrint(obj) rsRetVal obj##DebugPrint(obj##_t *pThis)
+#define INTERFACEObjDebugPrint(obj) rsRetVal (*DebugPrint)(obj##_t *pThis)
+#define BEGINobjDebugPrint(obj) \
+ rsRetVal obj##DebugPrint(obj##_t __attribute__((unused)) *pThis) \
+ { \
+ DEFiRet; \
+
+#define CODESTARTobjDebugPrint(obj) \
+ ASSERT(pThis != NULL); \
+ ISOBJ_TYPE_assert(pThis, obj); \
+
+#define ENDobjDebugPrint(obj) \
+ RETiRet; \
+ }
+
+/* ------------------------------ object loader system ------------------------------ *
+ * The following code builds a dynamic object loader system. The
+ * root idea is that all objects are dynamically loadable,
+ * which is necessary to get a clean plug-in interface where every plugin can access
+ * rsyslog's rich object model via simple and quite portable methods.
+ *
+ * To do so, each object defines one or more interfaces. They are essentially structures
+ * with function (method) pointers. Anyone interested in calling an object must first
+ * obtain the interface and can then call through it.
+ *
+ * The interface data type must always be called <obj>_if_t, as this is expected
+ * by the macros. Having consitent naming is also easier for the programmer. By default,
+ * macros create a static variable named like the object in each calling objects
+ * static data block.
+ *
+ * rgerhards, 2008-02-21 (initial implementation), 2008-04-17 (update of this note)
+ */
+
+/* this defines the QueryInterface print entry point. Over time, it should be
+ * present in all objects.
+ */
+#define BEGINobjQueryInterface(obj) \
+ rsRetVal obj##QueryInterface(obj##_if_t *pIf) \
+ { \
+ DEFiRet; \
+
+#define CODESTARTobjQueryInterface(obj) \
+ ASSERT(pIf != NULL);
+
+#define ENDobjQueryInterface(obj) \
+ RETiRet; \
+ }
+
+
+/* the following macros should be used to define interfaces inside the
+ * header files.
+ */
+#define BEGINinterface(obj) \
+ typedef struct obj##_if_s {\
+ ifBEGIN /* This MUST always be the first interface member */
+#define ENDinterface(obj) \
+ } obj##_if_t;
+
+/* the following macro is used to get access to an object (not an instance,
+ * just the class itself!). It must be called before any of the object's
+ * methods can be accessed. The MYLIB part is the name of my library, or NULL if
+ * the caller is a core module. Using the right value here is important to get
+ * the reference counting correct (object accesses from the same library must
+ * not be counted because that would cause a library plugin to never unload, as
+ * its ClassExit() entry points are only called if no object is referenced, which
+ * would never happen as the library references itself.
+ * rgerhards, 2008-03-11
+ */
+#define CORE_COMPONENT NULL /* use this to indicate this is a core component */
+#define DONT_LOAD_LIB NULL /* do not load a library to obtain object interface (currently same as CORE_COMPONENT) */
+#define objUse(objName, FILENAME) \
+ obj.UseObj(__FILE__, (uchar*)#objName, (uchar*)FILENAME, (void*) &objName)
+#define objRelease(objName, FILENAME) \
+ obj.ReleaseObj(__FILE__, (uchar*)#objName, (uchar*) FILENAME, (void*) &objName)
+
+/* defines data that must always be present at the very begin of the interface structure */
+#define ifBEGIN \
+ int ifVersion; /* must be set to version requested */ \
+ int ifIsLoaded; /* is the interface loaded? (0-no, 1-yes; if no, functions can NOT be called! */
+
+
+/* use the following define some place in your static data (suggested right at
+ * the beginning
+ */
+#define DEFobjCurrIf(obj) \
+ static obj##_if_t obj = { .ifVersion = obj##CURR_IF_VERSION, .ifIsLoaded = 0 };
+
+/* define the prototypes for a class - when we use interfaces, we just have few
+ * functions that actually need to be non-static.
+ */
+#define PROTOTYPEObj(obj) \
+ PROTOTYPEObjClassInit(obj); \
+ PROTOTYPEObjClassExit(obj)
+
+/* ------------------------------ end object loader system ------------------------------ */
+
+
+#include "modules.h"
+#endif /* #ifndef OBJ_TYPES_H_INCLUDED */
diff --git a/runtime/obj.c b/runtime/obj.c
new file mode 100644
index 00000000..63f1f38c
--- /dev/null
+++ b/runtime/obj.c
@@ -0,0 +1,1481 @@
+/* obj.c
+ *
+ * This file implements a generic object "class". All other classes can
+ * use the service of this base class here to include auto-destruction and
+ * other capabilities in a generic manner.
+ *
+ * As of 2008-02-29, I (rgerhards) am adding support for dynamically loadable
+ * objects. In essence, each object will soon be available via its interface,
+ * only. Before any object's code is accessed (including global static methods),
+ * the caller needs to obtain an object interface. To do so, it needs to provide
+ * the object name and the file where the object is expected to reside in. A
+ * file may not be given, in which case the object is expected to reside in
+ * the rsyslog core. The caller than receives an interface pointer which can
+ * be utilized to access all the object's methods. This method enables rsyslog
+ * to load library modules on demand. In order to keep overhead low, callers
+ * should request object interface only once in the object Init function and
+ * free them when they exit. The only exception is when a caller needs to
+ * access an object only conditional, in which case a pointer to its interface
+ * shall be aquired as need first arises but still be released only on exit
+ * or when there definitely is no further need. The whole idea is to limit
+ * the very performance-intense act of dynamically loading an objects library.
+ * Of course, it is possible to violate this suggestion, but than you should
+ * have very good reasoning to do so.
+ *
+ * Please note that there is one trick we need to do. Each object queries
+ * the object interfaces and it does so via objUse(). objUse, however, is
+ * part of the obj object's interface (implemented via the file you are
+ * just reading). So in order to obtain a pointer to objUse, we need to
+ * call it - obviously not possible. One solution would be that objUse is
+ * hardcoded into all callers. That, however, would bring us into slight
+ * trouble with actually dynamically loaded modules, as we should NOT
+ * rely on the OS loader to resolve symbols back to the caller (this
+ * is a feature not universally available and highly importable). Of course,
+ * we can solve this with a pHostQueryEtryPoint() call. It still sounds
+ * somewhat unnatural to call a regular interface function via a special
+ * method. So what we do instead is define a special function called
+ * objGetObjInterface() which delivers our own interface. That function
+ * than will be defined global and be queriable via pHostQueryEtryPoint().
+ * I agree, technically this is much the same, but from an architecture
+ * point of view it looks cleaner (at least to me).
+ *
+ * Please note that there is another egg-hen problem: we use a linked list,
+ * which is provided by the linkedList object. However, we need to
+ * initialize the linked list before we can provide the UseObj()
+ * functionality. That, in turn, would probably be required by the
+ * linkedList object. So the solution is to use a backdoor just to
+ * init the linked list and from then on use the usual interfaces.
+ *
+ * File begun on 2008-01-04 by RGerhards
+ *
+ * Copyright 2008-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+#include <pthread.h>
+
+/* how many objects are supported by rsyslogd? */
+#define OBJ_NUM_IDS 100 /* TODO change to a linked list? info: 16 were currently in use 2008-02-29 */
+
+#include "rsyslog.h"
+#include "syslogd-types.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "stream.h"
+#include "modules.h"
+#include "errmsg.h"
+#include "cfsysline.h"
+#include "unicode-helper.h"
+#include "datetime.h"
+
+/* static data */
+DEFobjCurrIf(obj) /* we define our own interface, as this is expected by some macros! */
+DEFobjCurrIf(var)
+DEFobjCurrIf(module)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(strm)
+static objInfo_t *arrObjInfo[OBJ_NUM_IDS]; /* array with object information pointers */
+pthread_mutex_t mutObjGlobalOp; /* mutex to guard global operations of the object system */
+
+
+/* cookies for serialized lines */
+#define COOKIE_OBJLINE '<'
+#define COOKIE_PROPLINE '+'
+#define COOKIE_ENDLINE '>'
+#define COOKIE_BLANKLINE '.'
+
+/* forward definitions */
+static rsRetVal FindObjInfo(cstr_t *pszObjName, objInfo_t **ppInfo);
+
+/* methods */
+
+/* This is a dummy method to be used when a standard method has not been
+ * implemented by an object. Having it allows us to simply call via the
+ * jump table without any NULL pointer checks - which gains quite
+ * some performance. -- rgerhards, 2008-01-04
+ */
+static rsRetVal objInfoNotImplementedDummy(void __attribute__((unused)) *pThis)
+{
+ return RS_RET_NOT_IMPLEMENTED;
+}
+
+/* and now the macro to check if something is not implemented
+ * must be provided an objInfo_t pointer.
+ */
+#define objInfoIsImplemented(pThis, method) \
+ (pThis->objMethods[method] != objInfoNotImplementedDummy)
+
+/* construct an object Info object. Each class shall do this on init. The
+ * resulting object shall be cached during the lifetime of the class and each
+ * object shall receive a reference. A constructor and destructor MUST be provided for all
+ * objects, thus they are in the parameter list.
+ * pszID is the identifying object name and must point to constant pool memory. It is never freed.
+ */
+static rsRetVal
+InfoConstruct(objInfo_t **ppThis, uchar *pszID, int iObjVers,
+ rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *),
+ rsRetVal (*pQueryIF)(interface_t*), modInfo_t *pModInfo)
+{
+ DEFiRet;
+ int i;
+ objInfo_t *pThis;
+
+ assert(ppThis != NULL);
+
+ if((pThis = calloc(1, sizeof(objInfo_t))) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ pThis->pszID = pszID;
+ pThis->lenID = ustrlen(pszID);
+ pThis->pszName = ustrdup(pszID); /* it's OK if we have NULL ptr, GetName() will deal with that! */
+ pThis->iObjVers = iObjVers;
+ pThis->QueryIF = pQueryIF;
+ pThis->pModInfo = pModInfo;
+
+ pThis->objMethods[0] = pConstruct;
+ pThis->objMethods[1] = pDestruct;
+ for(i = 2 ; i < OBJ_NUM_METHODS ; ++i) {
+ pThis->objMethods[i] = objInfoNotImplementedDummy;
+ }
+
+ *ppThis = pThis;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* destruct the objInfo object - must be done only when no more instances exist.
+ * rgerhards, 2008-03-10
+ */
+static rsRetVal
+InfoDestruct(objInfo_t **ppThis)
+{
+ DEFiRet;
+ objInfo_t *pThis;
+
+ assert(ppThis != NULL);
+ pThis = *ppThis;
+ assert(pThis != NULL);
+
+ free(pThis->pszName);
+ free(pThis);
+ *ppThis = NULL;
+
+ RETiRet;
+}
+
+
+/* set a method handler */
+static rsRetVal
+InfoSetMethod(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*))
+{
+ assert(pThis != NULL);
+ assert(objMethod > 0 && objMethod < OBJ_NUM_METHODS);
+ pThis->objMethods[objMethod] = pHandler;
+
+ return RS_RET_OK;
+}
+
+/* destruct the base object properties.
+ * rgerhards, 2008-01-29
+ */
+static rsRetVal
+DestructObjSelf(obj_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_assert(pThis);
+ free(pThis->pszName);
+
+ RETiRet;
+}
+
+
+/* --------------- object serializiation / deserialization support --------------- */
+
+
+/* serialize the header of an object
+ * pszRecType must be either "Obj" (Object) or "OPB" (Object Property Bag)
+ */
+static rsRetVal objSerializeHeader(strm_t *pStrm, obj_t *pObj, uchar *pszRecType)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pStrm, strm);
+ ISOBJ_assert(pObj);
+ assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB"));
+
+ /* object cookie and serializer version (so far always 1) */
+ CHKiRet(strm.WriteChar(pStrm, COOKIE_OBJLINE));
+ CHKiRet(strm.Write(pStrm, (uchar*) pszRecType, 3)); /* record types are always 3 octets */
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+ CHKiRet(strm.WriteChar(pStrm, '1'));
+
+ /* object type, version and string length */
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+ CHKiRet(strm.Write(pStrm, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID));
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+ CHKiRet(strm.WriteLong(pStrm, objGetVersion(pObj)));
+
+ /* record trailer */
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+ CHKiRet(strm.WriteChar(pStrm, '\n'));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* begin serialization of an object
+ * rgerhards, 2008-01-06
+ */
+static rsRetVal
+BeginSerialize(strm_t *pStrm, obj_t *pObj)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pStrm, strm);
+ ISOBJ_assert(pObj);
+
+ CHKiRet(strm.RecordBegin(pStrm));
+ CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "Obj"));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* begin serialization of an object's property bag
+ * Note: a property bag is used to serialize some of an objects
+ * properties, but not necessarily all. A good example is the queue
+ * object, which at some stage needs to serialize a number of its
+ * properties, but not the queue data itself. From the object point
+ * of view, a property bag can not be used to re-instantiate an object.
+ * Otherwise, the serialization is exactly the same.
+ * rgerhards, 2008-01-11
+ */
+static rsRetVal
+BeginSerializePropBag(strm_t *pStrm, obj_t *pObj)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pStrm, strm);
+ ISOBJ_assert(pObj);
+
+ CHKiRet(strm.RecordBegin(pStrm));
+ CHKiRet(objSerializeHeader(pStrm, pObj, (uchar*) "OPB"));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* append a property
+ */
+static rsRetVal
+SerializeProp(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr)
+{
+ DEFiRet;
+ uchar *pszBuf = NULL;
+ size_t lenBuf = 0;
+ uchar szBuf[64];
+ varType_t vType = VARTYPE_NONE;
+
+ ISOBJ_TYPE_assert(pStrm, strm);
+ assert(pszPropName != NULL);
+
+ /*dbgprintf("objSerializeProp: strm %p, propName '%s', type %d, pUsr %p\n", pStrm, pszPropName, propType, pUsr);*/
+ /* if we have no user pointer, there is no need to write this property.
+ * TODO: think if that's the righ point of view
+ * rgerhards, 2008-01-06
+ */
+ if(pUsr == NULL) {
+ ABORT_FINALIZE(RS_RET_OK);
+ }
+
+ /* TODO: use the stream functions for data conversion here - should be quicker */
+
+ switch(propType) {
+ case PROPTYPE_PSZ:
+ pszBuf = (uchar*) pUsr;
+ lenBuf = ustrlen(pszBuf);
+ vType = VARTYPE_STR;
+ break;
+ case PROPTYPE_SHORT:
+ CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((short*) pUsr)));
+ pszBuf = szBuf;
+ lenBuf = ustrlen(szBuf);
+ vType = VARTYPE_NUMBER;
+ break;
+ case PROPTYPE_INT:
+ CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), (long) *((int*) pUsr)));
+ pszBuf = szBuf;
+ lenBuf = ustrlen(szBuf);
+ vType = VARTYPE_NUMBER;
+ break;
+ case PROPTYPE_LONG:
+ CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((long*) pUsr)));
+ pszBuf = szBuf;
+ lenBuf = ustrlen(szBuf);
+ vType = VARTYPE_NUMBER;
+ break;
+ case PROPTYPE_INT64:
+ CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), *((int64*) pUsr)));
+ pszBuf = szBuf;
+ lenBuf = ustrlen(szBuf);
+ vType = VARTYPE_NUMBER;
+ break;
+ case PROPTYPE_CSTR:
+ pszBuf = rsCStrGetSzStrNoNULL((cstr_t *) pUsr);
+ lenBuf = rsCStrLen((cstr_t*) pUsr);
+ vType = VARTYPE_STR;
+ break;
+ case PROPTYPE_SYSLOGTIME:
+ lenBuf = snprintf((char*) szBuf, sizeof(szBuf), "%d:%d:%d:%d:%d:%d:%d:%d:%d:%c:%d:%d",
+ ((syslogTime_t*)pUsr)->timeType,
+ ((syslogTime_t*)pUsr)->year,
+ ((syslogTime_t*)pUsr)->month,
+ ((syslogTime_t*)pUsr)->day,
+ ((syslogTime_t*)pUsr)->hour,
+ ((syslogTime_t*)pUsr)->minute,
+ ((syslogTime_t*)pUsr)->second,
+ ((syslogTime_t*)pUsr)->secfrac,
+ ((syslogTime_t*)pUsr)->secfracPrecision,
+ ((syslogTime_t*)pUsr)->OffsetMode,
+ ((syslogTime_t*)pUsr)->OffsetHour,
+ ((syslogTime_t*)pUsr)->OffsetMinute);
+ if(lenBuf > sizeof(szBuf) - 1)
+ ABORT_FINALIZE(RS_RET_PROVIDED_BUFFER_TOO_SMALL);
+ vType = VARTYPE_SYSLOGTIME;
+ pszBuf = szBuf;
+ break;
+ default:
+ dbgprintf("invalid PROPTYPE %d\n", propType);
+ break;
+ }
+
+ /* cookie */
+ CHKiRet(strm.WriteChar(pStrm, COOKIE_PROPLINE));
+ /* name */
+ CHKiRet(strm.Write(pStrm, pszPropName, ustrlen(pszPropName)));
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+ /* type */
+ CHKiRet(strm.WriteLong(pStrm, (int) vType));
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+ /* length */
+ CHKiRet(strm.WriteLong(pStrm, lenBuf));
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+
+ /* data */
+ CHKiRet(strm.Write(pStrm, (uchar*) pszBuf, lenBuf));
+
+ /* trailer */
+ CHKiRet(strm.WriteChar(pStrm, ':'));
+ CHKiRet(strm.WriteChar(pStrm, '\n'));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* end serialization of an object. The caller receives a
+ * standard C string, which he must free when no longer needed.
+ */
+static rsRetVal
+EndSerialize(strm_t *pStrm)
+{
+ DEFiRet;
+
+ assert(pStrm != NULL);
+
+ CHKiRet(strm.WriteChar(pStrm, COOKIE_ENDLINE));
+ CHKiRet(strm.Write(pStrm, (uchar*) "End\n", sizeof("END\n") - 1));
+ CHKiRet(strm.WriteChar(pStrm, COOKIE_BLANKLINE));
+ CHKiRet(strm.WriteChar(pStrm, '\n'));
+
+ CHKiRet(strm.RecordEnd(pStrm));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* define a helper to make code below a bit cleaner (and quicker to write) */
+#define NEXTC CHKiRet(strm.ReadChar(pStrm, &c))/*;dbgprintf("c: %c\n", c)*/
+
+
+/* de-serialize an embedded, non-octect-counted string. This is useful
+ * for deserializing the object name inside the header. The string is
+ * terminated by the first occurence of the ':' character.
+ * rgerhards, 2008-02-29
+ */
+static rsRetVal
+objDeserializeEmbedStr(cstr_t **ppStr, strm_t *pStrm)
+{
+ DEFiRet;
+ uchar c;
+ cstr_t *pStr = NULL;
+
+ assert(ppStr != NULL);
+
+ CHKiRet(cstrConstruct(&pStr));
+
+ NEXTC;
+ while(c != ':') {
+ CHKiRet(cstrAppendChar(pStr, c));
+ NEXTC;
+ }
+ CHKiRet(cstrFinalize(pStr));
+
+ *ppStr = pStr;
+
+finalize_it:
+ if(iRet != RS_RET_OK && pStr != NULL)
+ cstrDestruct(&pStr);
+
+ RETiRet;
+}
+
+
+/* de-serialize a number */
+static rsRetVal objDeserializeNumber(number_t *pNum, strm_t *pStrm)
+{
+ DEFiRet;
+ number_t i;
+ int bIsNegative;
+ uchar c;
+
+ assert(pNum != NULL);
+
+ NEXTC;
+ if(c == '-') {
+ bIsNegative = 1;
+ NEXTC;
+ } else {
+ bIsNegative = 0;
+ }
+
+ /* we check this so that we get more meaningful error codes */
+ if(!isdigit(c)) ABORT_FINALIZE(RS_RET_INVALID_NUMBER);
+
+ i = 0;
+ while(isdigit(c)) {
+ i = i * 10 + c - '0';
+ NEXTC;
+ }
+
+ if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER);
+
+ if(bIsNegative)
+ i *= -1;
+
+ *pNum = i;
+finalize_it:
+ RETiRet;
+}
+
+
+/* de-serialize a string, length must be provided but may be 0 */
+static rsRetVal objDeserializeStr(cstr_t **ppCStr, int iLen, strm_t *pStrm)
+{
+ DEFiRet;
+ int i;
+ uchar c;
+ cstr_t *pCStr = NULL;
+
+ assert(ppCStr != NULL);
+ assert(iLen >= 0);
+
+ CHKiRet(cstrConstruct(&pCStr));
+
+ NEXTC;
+ for(i = 0 ; i < iLen ; ++i) {
+ CHKiRet(cstrAppendChar(pCStr, c));
+ NEXTC;
+ }
+ CHKiRet(cstrFinalize(pCStr));
+
+ /* check terminator */
+ if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER);
+
+ *ppCStr = pCStr;
+
+finalize_it:
+ if(iRet != RS_RET_OK && pCStr != NULL)
+ cstrDestruct(&pCStr);
+
+ RETiRet;
+}
+
+
+/* de-serialize a syslogTime -- rgerhards,2008-01-08 */
+#define GETVAL(var) \
+ CHKiRet(objDeserializeNumber(&l, pStrm)); \
+ pTime->var = l;
+static rsRetVal objDeserializeSyslogTime(syslogTime_t *pTime, strm_t *pStrm)
+{
+ DEFiRet;
+ number_t l;
+ uchar c;
+
+ assert(pTime != NULL);
+
+ GETVAL(timeType);
+ GETVAL(year);
+ GETVAL(month);
+ GETVAL(day);
+ GETVAL(hour);
+ GETVAL(minute);
+ GETVAL(second);
+ GETVAL(secfrac);
+ GETVAL(secfracPrecision);
+ /* OffsetMode is a single character! */
+ NEXTC; pTime->OffsetMode = c;
+ NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_DELIMITER);
+ GETVAL(OffsetHour);
+ GETVAL(OffsetMinute);
+
+finalize_it:
+ RETiRet;
+}
+#undef GETVAL
+
+/* de-serialize an object header
+ * rgerhards, 2008-01-07
+ */
+static rsRetVal objDeserializeHeader(uchar *pszRecType, cstr_t **ppstrID, int* poVers, strm_t *pStrm)
+{
+ DEFiRet;
+ number_t oVers;
+ uchar c;
+
+ assert(ppstrID != NULL);
+ assert(poVers != NULL);
+ assert(!strcmp((char*) pszRecType, "Obj") || !strcmp((char*) pszRecType, "OPB"));
+
+ /* check header cookie */
+ NEXTC; if(c != COOKIE_OBJLINE) ABORT_FINALIZE(RS_RET_INVALID_HEADER);
+ NEXTC; if(c != pszRecType[0]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE);
+ NEXTC; if(c != pszRecType[1]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE);
+ NEXTC; if(c != pszRecType[2]) ABORT_FINALIZE(RS_RET_INVALID_HEADER_RECTYPE);
+ NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER);
+ NEXTC; if(c != '1') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS);
+ NEXTC; if(c != ':') ABORT_FINALIZE(RS_RET_INVALID_HEADER_VERS);
+
+ /* object type and version */
+ CHKiRet(objDeserializeEmbedStr(ppstrID, pStrm));
+ CHKiRet(objDeserializeNumber(&oVers, pStrm));
+
+ /* and now we skip over the rest until the delemiting \n */
+ NEXTC;
+ while(c != '\n') {
+ NEXTC;
+ }
+
+ *poVers = oVers;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Deserialize a single property. Pointer must be positioned at begin of line. Whole line
+ * up until the \n is read.
+ */
+rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm)
+{
+ DEFiRet;
+ number_t i;
+ number_t iLen;
+ uchar c;
+ int step = 0; /* which step was successful? */
+ int64 offs;
+
+ assert(pProp != NULL);
+
+ /* check cookie */
+ NEXTC;
+ if(c != COOKIE_PROPLINE) {
+ /* oops, we've read one char that does not belong to use - unget it first */
+ CHKiRet(strm.UnreadChar(pStrm, c));
+ ABORT_FINALIZE(RS_RET_NO_PROPLINE);
+ }
+
+ /* get the property name first */
+ CHKiRet(cstrConstruct(&pProp->pcsName));
+
+ NEXTC;
+ while(c != ':') {
+ CHKiRet(cstrAppendChar(pProp->pcsName, c));
+ NEXTC;
+ }
+ CHKiRet(cstrFinalize(pProp->pcsName));
+ step = 1;
+
+ /* property type */
+ CHKiRet(objDeserializeNumber(&i, pStrm));
+ pProp->varType = i;
+ step = 2;
+
+ /* size (needed for strings) */
+ CHKiRet(objDeserializeNumber(&iLen, pStrm));
+ step = 3;
+
+ /* we now need to deserialize the value */
+ switch(pProp->varType) {
+ case VARTYPE_STR:
+ CHKiRet(objDeserializeStr(&pProp->val.pStr, iLen, pStrm));
+ break;
+ case VARTYPE_NUMBER:
+ CHKiRet(objDeserializeNumber(&pProp->val.num, pStrm));
+ break;
+ case VARTYPE_SYSLOGTIME:
+ CHKiRet(objDeserializeSyslogTime(&pProp->val.vSyslogTime, pStrm));
+ break;
+ default:
+ dbgprintf("invalid VARTYPE %d\n", pProp->varType);
+ break;
+ }
+ step = 4;
+
+ /* we should now be at the end of the line. So the next char must be \n */
+ NEXTC;
+ if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_PROPFRAME);
+
+finalize_it:
+ if(Debug && iRet != RS_RET_OK && iRet != RS_RET_NO_PROPLINE) {
+ strm.GetCurrOffset(pStrm, &offs);
+ dbgprintf("error %d deserializing property name, offset %lld, step %d\n",
+ iRet, offs, step);
+ if(step >= 1) {
+ dbgprintf("error property name: '%s'\n", rsCStrGetSzStrNoNULL(pProp->pcsName));
+ }
+ if(step >= 2) {
+ dbgprintf("error var type: '%d'\n", pProp->varType);
+ }
+ if(step >= 3) {
+ dbgprintf("error len: '%d'\n", (int) iLen);
+ }
+ if(step >= 4) {
+ switch(pProp->varType) {
+ case VARTYPE_STR:
+ dbgprintf("error data string: '%s'\n",
+ rsCStrGetSzStrNoNULL(pProp->val.pStr));
+ break;
+ case VARTYPE_NUMBER:
+ dbgprintf("error number: %d\n", (int) pProp->val.num);
+ break;
+ case VARTYPE_SYSLOGTIME:
+ dbgprintf("syslog time was successfully parsed (but "
+ "is not displayed\n");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ RETiRet;
+}
+
+
+/* de-serialize an object trailer. This does not get any data but checks if the
+ * format is ok.
+ * rgerhards, 2008-01-07
+ */
+static rsRetVal objDeserializeTrailer(strm_t *pStrm)
+{
+ DEFiRet;
+ uchar c;
+
+ /* check header cookie */
+ NEXTC; if(c != COOKIE_ENDLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
+ NEXTC; if(c != 'E') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
+ NEXTC; if(c != 'n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
+ NEXTC; if(c != 'd') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
+ NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
+ NEXTC; if(c != COOKIE_BLANKLINE) ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
+ NEXTC; if(c != '\n') ABORT_FINALIZE(RS_RET_INVALID_TRAILER);
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* This method tries to recover a serial store if it got out of sync.
+ * To do so, it scans the line beginning cookies and waits for the object
+ * cookie. If that is found, control is returned. If the store is exhausted,
+ * we will receive an RS_RET_EOF error as part of NEXTC, which will also
+ * terminate this function. So we may either return with somehting that
+ * looks like a valid object or end of store.
+ * rgerhards, 2008-01-07
+ */
+static rsRetVal objDeserializeTryRecover(strm_t *pStrm)
+{
+ DEFiRet;
+ uchar c;
+ int bWasNL;
+ int bRun;
+
+ assert(pStrm != NULL);
+ bRun = 1;
+ bWasNL = 0;
+
+ while(bRun) {
+ NEXTC;
+ if(c == '\n')
+ bWasNL = 1;
+ else {
+ if(bWasNL == 1 && c == COOKIE_OBJLINE)
+ bRun = 0; /* we found it! */
+ else
+ bWasNL = 0;
+ }
+ }
+
+ CHKiRet(strm.UnreadChar(pStrm, c));
+
+finalize_it:
+ dbgprintf("deserializer has possibly been able to re-sync and recover, state %d\n", iRet);
+ RETiRet;
+}
+
+
+/* De-serialize the properties of an object. This includes processing
+ * of the trailer. Header must already have been processed.
+ * rgerhards, 2008-01-11
+ */
+static rsRetVal objDeserializeProperties(obj_t *pObj, rsRetVal (*objSetProperty)(), strm_t *pStrm)
+{
+ DEFiRet;
+ var_t *pVar = NULL;
+
+ ISOBJ_assert(pObj);
+ ISOBJ_TYPE_assert(pStrm, strm);
+
+ CHKiRet(var.Construct(&pVar));
+ CHKiRet(var.ConstructFinalize(pVar));
+
+ iRet = objDeserializeProperty(pVar, pStrm);
+ while(iRet == RS_RET_OK) {
+ CHKiRet(objSetProperty(pObj, pVar));
+ /* re-init var object - TODO: method of var! */
+ rsCStrDestruct(&pVar->pcsName); /* no longer needed */
+ if(pVar->varType == VARTYPE_STR) {
+ if(pVar->val.pStr != NULL)
+ rsCStrDestruct(&pVar->val.pStr);
+ }
+ iRet = objDeserializeProperty(pVar, pStrm);
+ }
+
+ if(iRet != RS_RET_NO_PROPLINE)
+ FINALIZE;
+
+ CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */
+finalize_it:
+ if(pVar != NULL)
+ var.Destruct(&pVar);
+
+ RETiRet;
+}
+
+
+/* De-Serialize an object.
+ * Params: Pointer to object Pointer (pObj) (like a obj_t**, but can not do that due to compiler warning)
+ * expected object ID (to check against), a fixup function that can modify the object before it is finalized
+ * and a user pointer that is to be passed to that function in addition to the object. The fixup function
+ * pointer may be NULL, in which case none is called.
+ * The caller must destruct the created object.
+ * rgerhards, 2008-01-07
+ */
+static rsRetVal
+Deserialize(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr)
+{
+ DEFiRet;
+ rsRetVal iRetLocal;
+ obj_t *pObj = NULL;
+ int oVers = 0; /* keep compiler happy, but it is totally useless but takes up some execution time... */
+ cstr_t *pstrID = NULL;
+ objInfo_t *pObjInfo;
+
+ assert(ppObj != NULL);
+ assert(pszTypeExpected != NULL);
+ ISOBJ_TYPE_assert(pStrm, strm);
+
+ /* we de-serialize the header. if all goes well, we are happy. However, if
+ * we experience a problem, we try to recover. We do this by skipping to
+ * the next object header. This is defined via the line-start cookies. In
+ * worst case, we exhaust the queue, but then we receive EOF return state,
+ * from objDeserializeTryRecover(), what will cause us to ultimately give up.
+ * rgerhards, 2008-07-08
+ */
+ do {
+ iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm);
+ if(iRetLocal != RS_RET_OK) {
+ dbgprintf("objDeserialize error %d during header processing - trying to recover\n", iRetLocal);
+ CHKiRet(objDeserializeTryRecover(pStrm));
+ }
+ } while(iRetLocal != RS_RET_OK);
+
+ if(rsCStrSzStrCmp(pstrID, pszTypeExpected, ustrlen(pszTypeExpected))) /* TODO: optimize strlen() - caller shall provide */
+ ABORT_FINALIZE(RS_RET_INVALID_OID);
+
+ CHKiRet(FindObjInfo(pstrID, &pObjInfo));
+
+ CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCT](&pObj));
+
+ /* we got the object, now we need to fill the properties */
+ CHKiRet(objDeserializeProperties(pObj, pObjInfo->objMethods[objMethod_SETPROPERTY], pStrm));
+
+ /* check if we need to call a fixup function that modifies the object
+ * before it is finalized. -- rgerhards, 2008-01-13
+ */
+ if(fFixup != NULL)
+ CHKiRet(fFixup(pObj, pUsr));
+
+ /* we have a valid object, let's finalize our work and return */
+ if(objInfoIsImplemented(pObjInfo, objMethod_CONSTRUCTION_FINALIZER))
+ CHKiRet(pObjInfo->objMethods[objMethod_CONSTRUCTION_FINALIZER](pObj));
+
+ *((obj_t**) ppObj) = pObj;
+
+finalize_it:
+ if(iRet != RS_RET_OK && pObj != NULL)
+ free(pObj); /* TODO: check if we can call destructor 2008-01-13 rger */
+
+ if(pstrID != NULL)
+ rsCStrDestruct(&pstrID);
+
+ RETiRet;
+}
+
+
+/* De-Serialize an object, with known constructur and destructor. Params like Deserialize().
+ * rgerhards, 2012-11-03
+ */
+rsRetVal
+objDeserializeWithMethods(void *ppObj, uchar *pszTypeExpected, int lenTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr, rsRetVal (*objConstruct)(), rsRetVal (*objConstructFinalize)(), rsRetVal (*objDeserialize)())
+{
+ DEFiRet;
+ rsRetVal iRetLocal;
+ obj_t *pObj = NULL;
+ int oVers = 0; /* keep compiler happy, but it is totally useless but takes up some execution time... */
+ cstr_t *pstrID = NULL;
+
+ assert(ppObj != NULL);
+ assert(pszTypeExpected != NULL);
+ ISOBJ_TYPE_assert(pStrm, strm);
+
+ /* we de-serialize the header. if all goes well, we are happy. However, if
+ * we experience a problem, we try to recover. We do this by skipping to
+ * the next object header. This is defined via the line-start cookies. In
+ * worst case, we exhaust the queue, but then we receive EOF return state,
+ * from objDeserializeTryRecover(), what will cause us to ultimately give up.
+ * rgerhards, 2008-07-08
+ */
+ do {
+ iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm);
+ if(iRetLocal != RS_RET_OK) {
+ dbgprintf("objDeserialize error %d during header processing - "
+ "trying to recover\n", iRetLocal);
+ CHKiRet(objDeserializeTryRecover(pStrm));
+ }
+ } while(iRetLocal != RS_RET_OK);
+
+ if(rsCStrSzStrCmp(pstrID, pszTypeExpected, lenTypeExpected))
+ ABORT_FINALIZE(RS_RET_INVALID_OID);
+
+ CHKiRet(objConstruct(&pObj));
+
+ /* we got the object, now we need to fill the properties */
+ CHKiRet(objDeserialize(pObj, pStrm));
+ CHKiRet(objDeserializeTrailer(pStrm)); /* do trailer checks */
+
+ /* check if we need to call a fixup function that modifies the object
+ * before it is finalized. -- rgerhards, 2008-01-13
+ */
+ if(fFixup != NULL)
+ CHKiRet(fFixup(pObj, pUsr));
+
+ /* we have a valid object, let's finalize our work and return */
+ if(objConstructFinalize != NULL) {
+ CHKiRet(objConstructFinalize(pObj));
+ }
+
+ *((obj_t**) ppObj) = pObj;
+
+finalize_it:
+ if(iRet != RS_RET_OK && pObj != NULL)
+ free(pObj); /* TODO: check if we can call destructor 2008-01-13 rger */
+
+ if(pstrID != NULL)
+ rsCStrDestruct(&pstrID);
+
+ RETiRet;
+}
+
+/* This is a dummy deserializer, to be used for the delete queue reader
+ * specifically. This is kind of a hack, but also to be replace (hopefully) soon
+ * by totally different code. So let's make it as simple as possible...
+ * rgerhards, 2012-11-06
+ */
+rsRetVal
+objDeserializeDummy(obj_t __attribute__((unused)) *pObj, strm_t *pStrm)
+{
+ DEFiRet;
+ var_t *pVar = NULL;
+
+ CHKiRet(var.Construct(&pVar));
+ CHKiRet(var.ConstructFinalize(pVar));
+
+ iRet = objDeserializeProperty(pVar, pStrm);
+ while(iRet == RS_RET_OK) {
+ /* this loop does actually NOGHTING but read the file... */
+ /* re-init var object - TODO: method of var! */
+ rsCStrDestruct(&pVar->pcsName); /* no longer needed */
+ if(pVar->varType == VARTYPE_STR) {
+ if(pVar->val.pStr != NULL)
+ rsCStrDestruct(&pVar->val.pStr);
+ }
+ iRet = objDeserializeProperty(pVar, pStrm);
+ }
+finalize_it:
+ if(iRet == RS_RET_NO_PROPLINE)
+ iRet = RS_RET_OK; /* NO_PROPLINE is OK and a kind of EOF! */
+ if(pVar != NULL)
+ var.Destruct(&pVar);
+ RETiRet;
+}
+
+
+/* De-Serialize an object, but treat it as property bag.
+ * rgerhards, 2008-01-11
+ */
+rsRetVal
+objDeserializeObjAsPropBag(obj_t *pObj, strm_t *pStrm)
+{
+ DEFiRet;
+ rsRetVal iRetLocal;
+ cstr_t *pstrID = NULL;
+ int oVers = 0; /* after all, it is totally useless but takes up some execution time... */
+ objInfo_t *pObjInfo;
+
+ ISOBJ_assert(pObj);
+ ISOBJ_TYPE_assert(pStrm, strm);
+
+ /* we de-serialize the header. if all goes well, we are happy. However, if
+ * we experience a problem, we try to recover. We do this by skipping to
+ * the next object header. This is defined via the line-start cookies. In
+ * worst case, we exhaust the queue, but then we receive EOF return state
+ * from objDeserializeTryRecover(), what will cause us to ultimately give up.
+ * rgerhards, 2008-07-08
+ */
+ do {
+ iRetLocal = objDeserializeHeader((uchar*) "Obj", &pstrID, &oVers, pStrm);
+ if(iRetLocal != RS_RET_OK) {
+ dbgprintf("objDeserializeObjAsPropBag error %d during header - trying to recover\n", iRetLocal);
+ CHKiRet(objDeserializeTryRecover(pStrm));
+ }
+ } while(iRetLocal != RS_RET_OK);
+
+ if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID))
+ ABORT_FINALIZE(RS_RET_INVALID_OID);
+
+ CHKiRet(FindObjInfo(pstrID, &pObjInfo));
+
+ /* we got the object, now we need to fill the properties */
+ CHKiRet(objDeserializeProperties(pObj, pObjInfo->objMethods[objMethod_SETPROPERTY], pStrm));
+
+finalize_it:
+ if(pstrID != NULL)
+ rsCStrDestruct(&pstrID);
+
+ RETiRet;
+}
+
+
+
+/* De-Serialize an object property bag. As a property bag contains only partial properties,
+ * it is not instanciable. Thus, the caller must provide a pointer of an already-instanciated
+ * object of the correct type.
+ * Params: Pointer to object (pObj)
+ * Pointer to be passed to the function
+ * The caller must destruct the created object.
+ * rgerhards, 2008-01-07
+ */
+static rsRetVal
+DeserializePropBag(obj_t *pObj, strm_t *pStrm)
+{
+ DEFiRet;
+ rsRetVal iRetLocal;
+ cstr_t *pstrID = NULL;
+ int oVers;
+ objInfo_t *pObjInfo;
+
+ ISOBJ_assert(pObj);
+ ISOBJ_TYPE_assert(pStrm, strm);
+
+ /* we de-serialize the header. if all goes well, we are happy. However, if
+ * we experience a problem, we try to recover. We do this by skipping to
+ * the next object header. This is defined via the line-start cookies. In
+ * worst case, we exhaust the queue, but then we receive EOF return state
+ * from objDeserializeTryRecover(), what will cause us to ultimately give up.
+ * rgerhards, 2008-07-08
+ */
+ do {
+ iRetLocal = objDeserializeHeader((uchar*) "OPB", &pstrID, &oVers, pStrm);
+ if(iRetLocal != RS_RET_OK) {
+ dbgprintf("objDeserializePropBag error %d during header - trying to recover\n", iRetLocal);
+ CHKiRet(objDeserializeTryRecover(pStrm));
+ }
+ } while(iRetLocal != RS_RET_OK);
+
+ if(rsCStrSzStrCmp(pstrID, pObj->pObjInfo->pszID, pObj->pObjInfo->lenID))
+ ABORT_FINALIZE(RS_RET_INVALID_OID);
+
+ CHKiRet(FindObjInfo(pstrID, &pObjInfo));
+
+ /* we got the object, now we need to fill the properties */
+ CHKiRet(objDeserializeProperties(pObj, pObjInfo->objMethods[objMethod_SETPROPERTY], pStrm));
+
+finalize_it:
+ if(pstrID != NULL)
+ rsCStrDestruct(&pstrID);
+
+ RETiRet;
+}
+
+#undef NEXTC /* undef helper macro */
+
+
+/* --------------- end object serializiation / deserialization support --------------- */
+
+
+/* set the object (instance) name
+ * rgerhards, 2008-01-29
+ * TODO: change the naming to a rsCStr obj! (faster)
+ */
+static rsRetVal
+SetName(obj_t *pThis, uchar *pszName)
+{
+ DEFiRet;
+
+ free(pThis->pszName);
+ CHKmalloc(pThis->pszName = ustrdup(pszName));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* get the object (instance) name
+ * Note that we use a non-standard calling convention. Thus function must never
+ * fail, else we run into real big problems. So it must make sure that at least someting
+ * is returned.
+ * rgerhards, 2008-01-30
+ */
+static uchar *
+GetName(obj_t *pThis)
+{
+ uchar *ret;
+ uchar szName[128];
+
+ BEGINfunc
+ ISOBJ_assert(pThis);
+
+ if(pThis->pszName == NULL) {
+ snprintf((char*)szName, sizeof(szName)/sizeof(uchar), "%s %p", objGetClassName(pThis), pThis);
+ SetName(pThis, szName);
+ /* looks strange, but we NEED to re-check because if there was an
+ * error in objSetName(), the pointer may still be NULL
+ */
+ if(pThis->pszName == NULL) {
+ ret = objGetClassName(pThis);
+ } else {
+ ret = pThis->pszName;
+ }
+ } else {
+ ret = pThis->pszName;
+ }
+
+ ENDfunc
+ return ret;
+}
+
+
+/* Find the objInfo object for the current object
+ * rgerhards, 2008-02-29
+ */
+static rsRetVal
+FindObjInfo(cstr_t *pstrOID, objInfo_t **ppInfo)
+{
+ DEFiRet;
+ int bFound;
+ int i;
+
+ assert(pstrOID != NULL);
+ assert(ppInfo != NULL);
+
+ bFound = 0;
+ i = 0;
+ while(!bFound && i < OBJ_NUM_IDS) {
+ if(arrObjInfo[i] != NULL && !rsCStrSzStrCmp(pstrOID, arrObjInfo[i]->pszID, arrObjInfo[i]->lenID)) {
+ bFound = 1;
+ break;
+ }
+ ++i;
+ }
+
+ if(!bFound)
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+
+ *ppInfo = arrObjInfo[i];
+
+finalize_it:
+ if(iRet == RS_RET_OK) {
+ /* DEV DEBUG ONLY dbgprintf("caller requested object '%s', found at index %d\n", (*ppInfo)->pszID, i);*/
+ /*EMPTY BY INTENSION*/;
+ } else {
+ dbgprintf("caller requested object '%s', not found (iRet %d)\n", rsCStrGetSzStr(pstrOID), iRet);
+ }
+
+ RETiRet;
+}
+
+
+/* register a classes' info pointer, so that we can reference it later, if needed to
+ * (e.g. for de-serialization support).
+ * rgerhards, 2008-01-07
+ * In this function, we look for a free space in the object table. While we do so, we
+ * also detect if the same object has already been registered, which is not valid.
+ * rgerhards, 2008-02-29
+ */
+static rsRetVal
+RegisterObj(uchar *pszObjName, objInfo_t *pInfo)
+{
+ DEFiRet;
+ int bFound;
+ int i;
+
+ assert(pszObjName != NULL);
+ assert(pInfo != NULL);
+
+ bFound = 0;
+ i = 0;
+ while(!bFound && i < OBJ_NUM_IDS && arrObjInfo[i] != NULL) {
+ if( arrObjInfo[i] != NULL
+ && !ustrcmp(arrObjInfo[i]->pszID, pszObjName)) {
+ bFound = 1;
+ break;
+ }
+ ++i;
+ }
+
+ if(bFound) ABORT_FINALIZE(RS_RET_OBJ_ALREADY_REGISTERED);
+ if(i >= OBJ_NUM_IDS) ABORT_FINALIZE(RS_RET_OBJ_REGISTRY_OUT_OF_SPACE);
+
+ arrObjInfo[i] = pInfo;
+ /* DEV debug only: dbgprintf("object '%s' successfully registered with index %d, qIF %p\n", pszObjName, i, pInfo->QueryIF); */
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, NO_ERRCODE, "registering object '%s' failed with error code %d", pszObjName, iRet);
+ }
+
+ RETiRet;
+}
+
+
+/* deregister a classes' info pointer, usually called because the class is unloaded.
+ * After deregistration, the class can no longer be accessed, except if it is reloaded.
+ * rgerhards, 2008-03-10
+ */
+static rsRetVal
+UnregisterObj(uchar *pszObjName)
+{
+ DEFiRet;
+ int bFound;
+ int i;
+
+ assert(pszObjName != NULL);
+
+ bFound = 0;
+ i = 0;
+ while(!bFound && i < OBJ_NUM_IDS) {
+ if( arrObjInfo[i] != NULL
+ && !ustrcmp(arrObjInfo[i]->pszID, pszObjName)) {
+ bFound = 1;
+ break;
+ }
+ ++i;
+ }
+
+ if(!bFound)
+ ABORT_FINALIZE(RS_RET_OBJ_NOT_REGISTERED);
+
+ InfoDestruct(&arrObjInfo[i]);
+ /* DEV debug only: dbgprintf("object '%s' successfully unregistered with index %d\n", pszObjName, i); */
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ dbgprintf("unregistering object '%s' failed with error code %d\n", pszObjName, iRet);
+ }
+
+ RETiRet;
+}
+
+
+/* This function shall be called by anyone who would like to use an object. It will
+ * try to locate the object, load it into memory if not already present and return
+ * a pointer to the objects interface.
+ * rgerhards, 2008-02-29
+ */
+static rsRetVal
+UseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf)
+{
+ DEFiRet;
+ cstr_t *pStr = NULL;
+ objInfo_t *pObjInfo;
+
+
+ /* DEV debug only: dbgprintf("source file %s requests object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */
+ pthread_mutex_lock(&mutObjGlobalOp);
+
+ if(pIf->ifIsLoaded == 1) {
+ ABORT_FINALIZE(RS_RET_OK); /* we are already set */
+ }
+ if(pIf->ifIsLoaded == 2) {
+ ABORT_FINALIZE(RS_RET_LOAD_ERROR); /* we had a load error and can not continue */
+ }
+
+ /* we must be careful that we do not enter in infinite loop if an error occurs during
+ * loading a module. ModLoad emits an error message in such cases and that potentially
+ * can trigger the same code here. So we initially set the module state to "load error"
+ * and set it to "fully initialized" when the load succeeded. It's a bit hackish, but
+ * looks like a good solution. -- rgerhards, 2008-03-07
+ */
+ pIf->ifIsLoaded = 2;
+
+ CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName));
+ iRet = FindObjInfo(pStr, &pObjInfo);
+ if(iRet == RS_RET_NOT_FOUND) {
+ /* in this case, we need to see if we can dynamically load the object */
+ if(pObjFile == NULL) {
+ FINALIZE; /* no chance, we have lost... */
+ } else {
+ CHKiRet(module.Load(pObjFile, 0, NULL));
+ /* NOW, we must find it or we have a problem... */
+ CHKiRet(FindObjInfo(pStr, &pObjInfo));
+ }
+ } else if(iRet != RS_RET_OK) {
+ FINALIZE; /* give up */
+ }
+
+ /* if we reach this point, we have a valid pObjInfo */
+ if(pObjFile != NULL) { /* NULL means core module */
+ module.Use(srcFile, pObjInfo->pModInfo); /* increase refcount */
+ }
+
+ CHKiRet(pObjInfo->QueryIF(pIf));
+ pIf->ifIsLoaded = 1; /* we are happy */
+
+finalize_it:
+ pthread_mutex_unlock(&mutObjGlobalOp);
+
+ if(pStr != NULL)
+ rsCStrDestruct(&pStr);
+
+ RETiRet;
+}
+
+
+/* This function shall be called when a caller is done with an object. Its primary
+ * purpose is to keep the reference count correct, which is highly important for
+ * modules residing in loadable modules.
+ * rgerhards, 2008-03-10
+ */
+static rsRetVal
+ReleaseObj(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf)
+{
+ DEFiRet;
+ cstr_t *pStr = NULL;
+ objInfo_t *pObjInfo;
+
+
+ /* dev debug only dbgprintf("source file %s releasing object '%s', ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded); */
+ pthread_mutex_lock(&mutObjGlobalOp);
+
+ if(pObjFile == NULL)
+ FINALIZE; /* if it is not a lodable module, we do not need to do anything... */
+
+ if(pIf->ifIsLoaded == 0) {
+ FINALIZE; /* we are not loaded - this is perfectly OK... */
+ } else if(pIf->ifIsLoaded == 2) {
+ pIf->ifIsLoaded = 0; /* clean up */
+ FINALIZE; /* we had a load error and can not/must not continue */
+ }
+
+ CHKiRet(rsCStrConstructFromszStr(&pStr, pObjName));
+ CHKiRet(FindObjInfo(pStr, &pObjInfo));
+
+ /* if we reach this point, we have a valid pObjInfo */
+ module.Release(srcFile, &pObjInfo->pModInfo); /* decrease refcount */
+
+ pIf->ifIsLoaded = 0; /* indicated "no longer valid" */
+
+finalize_it:
+ pthread_mutex_unlock(&mutObjGlobalOp);
+
+ if(pStr != NULL)
+ rsCStrDestruct(&pStr);
+
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(obj)
+CODESTARTobjQueryInterface(obj)
+ if(pIf->ifVersion != objCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->UseObj = UseObj;
+ pIf->ReleaseObj = ReleaseObj;
+ pIf->InfoConstruct = InfoConstruct;
+ pIf->DestructObjSelf = DestructObjSelf;
+ pIf->BeginSerializePropBag = BeginSerializePropBag;
+ pIf->InfoSetMethod = InfoSetMethod;
+ pIf->BeginSerialize = BeginSerialize;
+ pIf->SerializeProp = SerializeProp;
+ pIf->EndSerialize = EndSerialize;
+ pIf->RegisterObj = RegisterObj;
+ pIf->UnregisterObj = UnregisterObj;
+ pIf->Deserialize = Deserialize;
+ pIf->DeserializePropBag = DeserializePropBag;
+ pIf->SetName = SetName;
+ pIf->GetName = GetName;
+finalize_it:
+ENDobjQueryInterface(obj)
+
+
+/* This function returns a pointer to our own interface. It is used as the
+ * hook that every object (including dynamically loaded ones) can use to
+ * obtain a pointer to our interface which than can be used to obtain
+ * pointers to any other interface in the system. This function must be
+ * externally visible because of its special nature.
+ * rgerhards, 2008-02-29 [nice - will have that date the next time in 4 years ;)]
+ */
+rsRetVal
+objGetObjInterface(obj_if_t *pIf)
+{
+ DEFiRet;
+ assert(pIf != NULL);
+ objQueryInterface(pIf);
+ RETiRet;
+}
+
+
+/* exit our class
+ * rgerhards, 2008-03-11
+ */
+rsRetVal
+objClassExit(void)
+{
+ DEFiRet;
+ /* release objects we no longer need */
+ objRelease(strm, CORE_COMPONENT);
+ objRelease(var, CORE_COMPONENT);
+ objRelease(module, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+
+ /* TODO: implement the class exits! */
+#if 0
+ cfsyslineExit(pModInfo);
+ varClassExit(pModInfo);
+#endif
+ errmsgClassExit();
+ moduleClassExit();
+ RETiRet;
+}
+
+
+/* initialize our own class
+ * Please note that this also initializes those classes that we rely on.
+ * Though this is a bit dirty, we need to do it - otherwise we can't get
+ * around that bootstrap problem. We need to face the fact the the obj
+ * class is a little different from the rest of the system, as it provides
+ * the core class loader functionality.
+ * rgerhards, 2008-02-29
+ */
+rsRetVal
+objClassInit(modInfo_t *pModInfo)
+{
+ pthread_mutexattr_t mutAttr;
+ int i;
+ DEFiRet;
+
+ /* first, initialize the object system itself. This must be done
+ * before any other object is created.
+ */
+ for(i = 0 ; i < OBJ_NUM_IDS ; ++i) {
+ arrObjInfo[i] = NULL;
+ }
+
+ /* the mutex must be recursive, because objects may call into other
+ * object identifiers recursively.
+ */
+ pthread_mutexattr_init(&mutAttr);
+ pthread_mutexattr_settype(&mutAttr, PTHREAD_MUTEX_RECURSIVE);
+ pthread_mutex_init(&mutObjGlobalOp, &mutAttr);
+
+ /* request objects we use */
+ CHKiRet(objGetObjInterface(&obj)); /* get ourselves ;) */
+
+ /* init classes we use (limit to as few as possible!) */
+ CHKiRet(errmsgClassInit(pModInfo));
+ CHKiRet(datetimeClassInit(pModInfo));
+ CHKiRet(cfsyslineInit());
+ CHKiRet(varClassInit(pModInfo));
+ CHKiRet(moduleClassInit(pModInfo));
+ CHKiRet(strmClassInit(pModInfo));
+ CHKiRet(objUse(var, CORE_COMPONENT));
+ CHKiRet(objUse(module, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(strm, CORE_COMPONENT));
+
+finalize_it:
+ RETiRet;
+}
+
+/* vi:set ai:
+ */
diff --git a/runtime/obj.h b/runtime/obj.h
new file mode 100644
index 00000000..27d32b7a
--- /dev/null
+++ b/runtime/obj.h
@@ -0,0 +1,129 @@
+/* Definition of the generic obj class module.
+ *
+ * This module relies heavily on preprocessor macros in order to
+ * provide fast execution time AND ease of use.
+ *
+ * Each object that uses this base class MUST provide a constructor with
+ * the following interface:
+ *
+ * Destruct(pThis);
+ *
+ * A constructor is not necessary (except for some features, e.g. de-serialization).
+ * If it is provided, it is a three-part constructor (to handle all cases with a
+ * generic interface):
+ *
+ * Construct(&pThis);
+ * SetProperty(pThis, property_t *);
+ * ConstructFinalize(pThis);
+ *
+ * SetProperty() and ConstructFinalize() may also be called on an object
+ * instance which has been Construct()'ed outside of this module.
+ *
+ * pThis always references to a pointer of the object.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OBJ_H_INCLUDED
+#define OBJ_H_INCLUDED
+
+#include "obj-types.h"
+#include "var.h"
+#include "stream.h"
+
+/* macros */
+/* the following one is a helper that prevents us from writing the
+ * ever-same code at the end of Construct()
+ */
+#define OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP \
+ if(iRet == RS_RET_OK) { \
+ *ppThis = pThis; \
+ } else { \
+ if(pThis != NULL) \
+ free(pThis); \
+ }
+
+#define objSerializeSCALAR_VAR(strm, propName, propType, var) \
+ CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &var));
+#define objSerializeSCALAR(strm, propName, propType) \
+ CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) &pThis->propName));
+#define objSerializePTR(strm, propName, propType) \
+ CHKiRet(obj.SerializeProp(strm, (uchar*) #propName, PROPTYPE_##propType, (void*) pThis->propName));
+#define DEFobjStaticHelpers \
+ static objInfo_t __attribute__((unused)) *pObjInfoOBJ = NULL; \
+ DEFobjCurrIf(obj)
+
+
+#define objGetClassName(pThis) (((obj_t*) (pThis))->pObjInfo->pszID)
+#define objGetVersion(pThis) (((obj_t*) (pThis))->pObjInfo->iObjVers)
+/* the next macro MUST be called in Constructors: */
+#ifndef NDEBUG /* this means if debug... */
+# define objConstructSetObjInfo(pThis) \
+ ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \
+ ((obj_t*) (pThis))->pszName = NULL; \
+ ((obj_t*) (pThis))->iObjCooCKiE = 0xBADEFEE
+#else
+# define objConstructSetObjInfo(pThis) \
+ ((obj_t*) (pThis))->pObjInfo = pObjInfoOBJ; \
+ ((obj_t*) (pThis))->pszName = NULL
+#endif
+#define objSerialize(pThis) (((obj_t*) (pThis))->pObjInfo->objMethods[objMethod_SERIALIZE])
+
+#define OBJSetMethodHandler(methodID, pHdlr) \
+ CHKiRet(obj.InfoSetMethod(pObjInfoOBJ, methodID, (rsRetVal (*)(void*)) pHdlr))
+
+/* interfaces */
+BEGINinterface(obj) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*UseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf);
+ rsRetVal (*ReleaseObj)(char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf);
+ rsRetVal (*InfoConstruct)(objInfo_t **ppThis, uchar *pszID, int iObjVers,
+ rsRetVal (*pConstruct)(void *), rsRetVal (*pDestruct)(void *),
+ rsRetVal (*pQueryIF)(interface_t*), modInfo_t*);
+ rsRetVal (*DestructObjSelf)(obj_t *pThis);
+ rsRetVal (*BeginSerializePropBag)(strm_t *pStrm, obj_t *pObj);
+ rsRetVal (*InfoSetMethod)(objInfo_t *pThis, objMethod_t objMethod, rsRetVal (*pHandler)(void*));
+ rsRetVal (*BeginSerialize)(strm_t *pStrm, obj_t *pObj);
+ rsRetVal (*SerializeProp)(strm_t *pStrm, uchar *pszPropName, propType_t propType, void *pUsr);
+ rsRetVal (*EndSerialize)(strm_t *pStrm);
+ rsRetVal (*RegisterObj)(uchar *pszObjName, objInfo_t *pInfo);
+ rsRetVal (*UnregisterObj)(uchar *pszObjName);
+ rsRetVal (*Deserialize)(void *ppObj, uchar *pszTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr);
+ rsRetVal (*DeserializePropBag)(obj_t *pObj, strm_t *pStrm);
+ rsRetVal (*SetName)(obj_t *pThis, uchar *pszName);
+ uchar * (*GetName)(obj_t *pThis);
+ENDinterface(obj)
+#define objCURR_IF_VERSION 2 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+/* the following define *is* necessary, because it provides the root way of obtaining
+ * interfaces (at some place we need to start our query...
+ */
+rsRetVal objGetObjInterface(obj_if_t *pIf);
+PROTOTYPEObjClassInit(obj);
+PROTOTYPEObjClassExit(obj);
+rsRetVal objDeserializeWithMethods(void *ppObj, uchar *pszTypeExpected, int lenTypeExpected, strm_t *pStrm, rsRetVal (*fFixup)(obj_t*,void*), void *pUsr, rsRetVal (*objConstruct)(), rsRetVal (*objConstructFinalize)(), rsRetVal (*objDeserialize)());
+rsRetVal objDeserializeProperty(var_t *pProp, strm_t *pStrm);
+rsRetVal objDeserializeDummy(obj_t *pObj, strm_t *pStrm);
+
+
+/* the following definition is only for "friends" */
+extern pthread_mutex_t mutObjGlobalOp; /* mutex to guard global operations of the object system */
+
+#endif /* #ifndef OBJ_H_INCLUDED */
diff --git a/runtime/objomsr.c b/runtime/objomsr.c
new file mode 100644
index 00000000..e63eb681
--- /dev/null
+++ b/runtime/objomsr.c
@@ -0,0 +1,156 @@
+/* objomsr.c
+ * Implementation of the omsr (omodStringRequest) object.
+ *
+ * File begun on 2007-07-27 by RGerhards
+ *
+ * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "rsyslog.h"
+#include "objomsr.h"
+
+
+/* destructor
+ */
+rsRetVal OMSRdestruct(omodStringRequest_t *pThis)
+{
+ int i;
+
+ assert(pThis != NULL);
+ /* free the strings */
+ if(pThis->ppTplName != NULL) {
+ for(i = 0 ; i < pThis->iNumEntries ; ++i) {
+ free(pThis->ppTplName[i]);
+ }
+ free(pThis->ppTplName);
+ }
+ if(pThis->piTplOpts != NULL)
+ free(pThis->piTplOpts);
+ free(pThis);
+
+ return RS_RET_OK;
+}
+
+
+/* constructor
+ */
+rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries)
+{
+ omodStringRequest_t *pThis = NULL;
+ DEFiRet;
+
+ assert(ppThis != NULL);
+ assert(iNumEntries >= 0);
+ if(iNumEntries > CONF_OMOD_NUMSTRINGS_MAXSIZE) {
+ ABORT_FINALIZE(RS_RET_MAX_OMSR_REACHED);
+ }
+ CHKmalloc(pThis = calloc(1, sizeof(omodStringRequest_t)));
+
+ /* got the structure, so fill it */
+ pThis->iNumEntries = iNumEntries;
+ /* allocate string for template name array. The individual strings will be
+ * allocated as the code progresses (we do not yet know the string sizes)
+ */
+ CHKmalloc(pThis->ppTplName = calloc(iNumEntries, sizeof(uchar*)));
+
+ /* allocate the template options array. */
+ CHKmalloc(pThis->piTplOpts = calloc(iNumEntries, sizeof(int)));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis != NULL) {
+ OMSRdestruct(pThis);
+ pThis = NULL;
+ }
+ }
+ *ppThis = pThis;
+ RETiRet;
+}
+
+/* set a template name and option to the object. Index must be given. The pTplName must be
+ * pointing to memory that can be freed. If in doubt, the caller must strdup() the value.
+ */
+rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts)
+{
+ assert(pThis != NULL);
+ assert(iEntry < pThis->iNumEntries);
+
+ if(pThis->ppTplName[iEntry] != NULL)
+ free(pThis->ppTplName[iEntry]);
+ pThis->ppTplName[iEntry] = pTplName;
+ pThis->piTplOpts[iEntry] = iTplOpts;
+
+ return RS_RET_OK;
+}
+
+
+/* get number of entries for this object
+ */
+int OMSRgetEntryCount(omodStringRequest_t *pThis)
+{
+ assert(pThis != NULL);
+ return pThis->iNumEntries;
+}
+
+
+/* return data for a specific entry. All data returned is
+ * read-only and lasts only as long as the object lives. If the caller
+ * needs it for an extended period of time, the caller must copy the
+ * strings. Please note that the string pointer may be NULL, which is the
+ * case when it was never set.
+ */
+int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts)
+{
+ assert(pThis != NULL);
+ assert(ppTplName != NULL);
+ assert(piTplOpts != NULL);
+ assert(iEntry < pThis->iNumEntries);
+
+ *ppTplName = pThis->ppTplName[iEntry];
+ *piTplOpts = pThis->piTplOpts[iEntry];
+
+ return RS_RET_OK;
+}
+
+
+/* return the full set of template options that are supported by this version of
+ * OMSR. They are returned in an unsigned long value. The caller can mask that
+ * value to check on the option he is interested in.
+ * Note that this interface was added in 4.1.6, so a plugin must obtain a pointer
+ * to this interface via queryHostEtryPt().
+ * rgerhards, 2009-04-03
+ */
+rsRetVal
+OMSRgetSupportedTplOpts(unsigned long *pOpts)
+{
+ DEFiRet;
+ assert(pOpts != NULL);
+ *pOpts = OMSR_RQD_TPL_OPT_SQL | OMSR_TPL_AS_ARRAY | OMSR_TPL_AS_MSG
+ | OMSR_TPL_AS_JSON;
+ RETiRet;
+}
+
+/* vim:set ai:
+ */
diff --git a/runtime/objomsr.h b/runtime/objomsr.h
new file mode 100644
index 00000000..3baccaa3
--- /dev/null
+++ b/runtime/objomsr.h
@@ -0,0 +1,51 @@
+/* Definition of the omsr (omodStringRequest) object.
+ *
+ * Copyright 2007-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef OBJOMSR_H_INCLUDED
+#define OBJOMSR_H_INCLUDED
+
+/* define flags for required template options */
+#define OMSR_NO_RQD_TPL_OPTS 0
+#define OMSR_RQD_TPL_OPT_SQL 1
+/* only one of OMSR_TPL_AS_ARRAY, _AS_MSG, or _AS_JSON must be specified,
+ * if all are given results are unpredictable.
+ */
+#define OMSR_TPL_AS_ARRAY 2 /* introduced in 4.1.6, 2009-04-03 */
+#define OMSR_TPL_AS_MSG 4 /* introduced in 5.3.4, 2009-11-02 */
+#define OMSR_TPL_AS_JSON 8 /* introduced in 6.5.1, 2012-09-02 */
+/* next option is 16, 32, 64, ... */
+
+struct omodStringRequest_s { /* strings requested by output module for doAction() */
+ int iNumEntries; /* number of array entries for data elements below */
+ uchar **ppTplName; /* pointer to array of template names */
+ int *piTplOpts;/* pointer to array of check-options when pulling template */
+};
+typedef struct omodStringRequest_s omodStringRequest_t;
+
+/* prototypes */
+rsRetVal OMSRdestruct(omodStringRequest_t *pThis);
+rsRetVal OMSRconstruct(omodStringRequest_t **ppThis, int iNumEntries);
+rsRetVal OMSRsetEntry(omodStringRequest_t *pThis, int iEntry, uchar *pTplName, int iTplOpts);
+rsRetVal OMSRgetSupportedTplOpts(unsigned long *pOpts);
+int OMSRgetEntryCount(omodStringRequest_t *pThis);
+int OMSRgetEntry(omodStringRequest_t *pThis, int iEntry, uchar **ppTplName, int *piTplOpts);
+
+#endif /* #ifndef OBJOMSR_H_INCLUDED */
diff --git a/runtime/parser.c b/runtime/parser.c
new file mode 100644
index 00000000..74b28f4c
--- /dev/null
+++ b/runtime/parser.c
@@ -0,0 +1,728 @@
+/* parser.c
+ * This module contains functions for message parsers. It still needs to be
+ * converted into an object (and much extended).
+ *
+ * Module begun 2008-10-09 by Rainer Gerhards (based on previous code from syslogd.c)
+ *
+ * Copyright 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+#ifdef USE_NETZIP
+#include <zlib.h>
+#endif
+
+#include "rsyslog.h"
+#include "dirty.h"
+#include "msg.h"
+#include "obj.h"
+#include "datetime.h"
+#include "errmsg.h"
+#include "parser.h"
+#include "ruleset.h"
+#include "unicode-helper.h"
+#include "dirty.h"
+#include "cfsysline.h"
+
+/* some defines */
+#define DEFUPRI (LOG_USER|LOG_NOTICE)
+
+/* definitions for objects we access */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(ruleset)
+
+/* static data */
+
+/* config data */
+static uchar cCCEscapeChar = '#';/* character to be used to start an escape sequence for control chars */
+static int bEscapeCCOnRcv = 1; /* escape control characters on reception: 0 - no, 1 - yes */
+static int bSpaceLFOnRcv = 0; /* replace newlines with spaces on reception: 0 - no, 1 - yes */
+static int bEscape8BitChars = 0; /* escape characters > 127 on reception: 0 - no, 1 - yes */
+static int bEscapeTab = 1; /* escape tab control character when doing CC escapes: 0 - no, 1 - yes */
+static int bDropTrailingLF = 1; /* drop trailing LF's on reception? */
+
+/* This is the list of all parsers known to us.
+ * This is also used to unload all modules on shutdown.
+ */
+parserList_t *pParsLstRoot = NULL;
+
+/* this is the list of the default parsers, to be used if no others
+ * are specified.
+ */
+parserList_t *pDfltParsLst = NULL;
+
+
+/* intialize (but NOT allocate) a parser list. Primarily meant as a hook
+ * which can be used to extend the list in the future. So far, just sets
+ * it to NULL.
+ */
+static rsRetVal
+InitParserList(parserList_t **pListRoot)
+{
+ *pListRoot = NULL;
+ return RS_RET_OK;
+}
+
+
+/* destruct a parser list. The list elements are destroyed, but the parser objects
+ * themselves are not modified. (That is done at a late stage during rsyslogd
+ * shutdown and need not be considered here.)
+ */
+static rsRetVal
+DestructParserList(parserList_t **ppListRoot)
+{
+ parserList_t *pParsLst;
+ parserList_t *pParsLstDel;
+
+ pParsLst = *ppListRoot;
+ while(pParsLst != NULL) {
+ pParsLstDel = pParsLst;
+ pParsLst = pParsLst->pNext;
+ free(pParsLstDel);
+ }
+ *ppListRoot = NULL;
+ return RS_RET_OK;
+}
+
+
+/* Add a parser to the list. We use a VERY simple and ineffcient algorithm,
+ * but it is employed only for a few milliseconds during config processing. So
+ * I prefer to keep it very simple and with simple data structures. Unfortunately,
+ * we need to preserve the order, but I don't like to add a tail pointer as that
+ * would require a container object. So I do the extra work to skip to the tail
+ * when adding elements...
+ * rgerhards, 2009-11-03
+ */
+static rsRetVal
+AddParserToList(parserList_t **ppListRoot, parser_t *pParser)
+{
+ parserList_t *pThis;
+ parserList_t *pTail;
+ DEFiRet;
+
+ CHKmalloc(pThis = MALLOC(sizeof(parserList_t)));
+ pThis->pParser = pParser;
+ pThis->pNext = NULL;
+
+ if(*ppListRoot == NULL) {
+ pThis->pNext = *ppListRoot;
+ *ppListRoot = pThis;
+ } else {
+ /* find tail first */
+ for(pTail = *ppListRoot ; pTail->pNext != NULL ; pTail = pTail->pNext)
+ /* just search, do nothing else */;
+ /* add at tail */
+ pTail->pNext = pThis;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+void
+printParserList(parserList_t *pList)
+{
+ while(pList != NULL) {
+ dbgprintf("parser: %s\n", pList->pParser->pName);
+ pList = pList->pNext;
+ }
+}
+
+/* find a parser based on the provided name */
+static rsRetVal
+FindParser(parser_t **ppParser, uchar *pName)
+{
+ parserList_t *pThis;
+ DEFiRet;
+
+ for(pThis = pParsLstRoot ; pThis != NULL ; pThis = pThis->pNext) {
+ if(ustrcmp(pThis->pParser->pName, pName) == 0) {
+ *ppParser = pThis->pParser;
+ FINALIZE; /* found it, iRet still eq. OK! */
+ }
+ }
+
+ iRet = RS_RET_PARSER_NOT_FOUND;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* --- END helper functions for parser list handling --- */
+
+/* Add a an already existing parser to the default list. As usual, order
+ * of calls is important (most importantly, that means the legacy parser,
+ * which can process everything, MUST be added last!).
+ * rgerhards, 2009-11-04
+ */
+static rsRetVal
+AddDfltParser(uchar *pName)
+{
+ parser_t *pParser;
+ DEFiRet;
+
+ CHKiRet(FindParser(&pParser, pName));
+ CHKiRet(AddParserToList(&pDfltParsLst, pParser));
+ DBGPRINTF("Parser '%s' added to default parser set.\n", pName);
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+BEGINobjConstruct(parser) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(parser)
+
+/* ConstructionFinalizer. The most important chore is to add the parser object
+ * to our global list of available parsers.
+ * rgerhards, 2009-11-03
+ */
+rsRetVal parserConstructFinalize(parser_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, parser);
+ CHKiRet(AddParserToList(&pParsLstRoot, pThis));
+ DBGPRINTF("Parser '%s' added to list of available parsers.\n", pThis->pName);
+
+finalize_it:
+ RETiRet;
+}
+
+BEGINobjDestruct(parser) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(parser)
+ DBGPRINTF("destructing parser '%s'\n", pThis->pName);
+ free(pThis->pName);
+ENDobjDestruct(parser)
+
+
+/* uncompress a received message if it is compressed.
+ * pMsg->pszRawMsg buffer is updated.
+ * rgerhards, 2008-10-09
+ */
+static inline rsRetVal uncompressMessage(msg_t *pMsg)
+{
+ DEFiRet;
+# ifdef USE_NETZIP
+ uchar *deflateBuf = NULL;
+ uLongf iLenDefBuf;
+ uchar *pszMsg;
+ size_t lenMsg;
+
+ assert(pMsg != NULL);
+ pszMsg = pMsg->pszRawMsg;
+ lenMsg = pMsg->iLenRawMsg;
+
+ /* we first need to check if we have a compressed record. If so,
+ * we must decompress it.
+ */
+ if(lenMsg > 0 && *pszMsg == 'z') { /* compressed data present? (do NOT change order if conditions!) */
+ /* we have compressed data, so let's deflate it. We support a maximum
+ * message size of iMaxLine. If it is larger, an error message is logged
+ * and the message is dropped. We do NOT try to decompress larger messages
+ * as such might be used for denial of service. It might happen to later
+ * builds that such functionality be added as an optional, operator-configurable
+ * feature.
+ */
+ int ret;
+ iLenDefBuf = glbl.GetMaxLine();
+ CHKmalloc(deflateBuf = MALLOC(sizeof(uchar) * (iLenDefBuf + 1)));
+ ret = uncompress((uchar *) deflateBuf, &iLenDefBuf, (uchar *) pszMsg+1, lenMsg-1);
+ DBGPRINTF("Compressed message uncompressed with status %d, length: new %ld, old %d.\n",
+ ret, (long) iLenDefBuf, (int) (lenMsg-1));
+ /* Now check if the uncompression worked. If not, there is not much we can do. In
+ * that case, we log an error message but ignore the message itself. Storing the
+ * compressed text is dangerous, as it contains control characters. So we do
+ * not do this. If someone would like to have a copy, this code here could be
+ * modified to do a hex-dump of the buffer in question. We do not include
+ * this functionality right now.
+ * rgerhards, 2006-12-07
+ */
+ if(ret != Z_OK) {
+ errmsg.LogError(0, NO_ERRCODE, "Uncompression of a message failed with return code %d "
+ "- enable debug logging if you need further information. "
+ "Message ignored.", ret);
+ FINALIZE; /* unconditional exit, nothing left to do... */
+ }
+ MsgSetRawMsg(pMsg, (char*)deflateBuf, iLenDefBuf);
+ }
+finalize_it:
+ if(deflateBuf != NULL)
+ free(deflateBuf);
+
+# else /* ifdef USE_NETZIP */
+
+ /* in this case, we still need to check if the message is compressed. If so, we must
+ * tell the user we can not accept it.
+ */
+ if(pMsg->iLenRawMsg > 0 && *pMsg->pszRawMsg == 'z') {
+ errmsg.LogError(0, NO_ERRCODE, "Received a compressed message, but rsyslogd does not have compression "
+ "support enabled. The message will be ignored.");
+ ABORT_FINALIZE(RS_RET_NO_ZIP);
+ }
+
+finalize_it:
+# endif /* ifdef USE_NETZIP */
+
+ RETiRet;
+}
+
+
+/* sanitize a received message
+ * if a message gets to large during sanitization, it is truncated. This is
+ * as specified in the upcoming syslog RFC series.
+ * rgerhards, 2008-10-09
+ * We check if we have a NUL character at the very end of the
+ * message. This seems to be a frequent problem with a number of senders.
+ * So I have now decided to drop these NULs. However, if they are intentional,
+ * that may cause us some problems, e.g. with syslog-sign. On the other hand,
+ * current code always has problems with intentional NULs (as it needs to escape
+ * them to prevent problems with the C string libraries), so that does not
+ * really matter. Just to be on the save side, we'll log destruction of such
+ * NULs in the debug log.
+ * rgerhards, 2007-09-14
+ */
+static inline rsRetVal
+SanitizeMsg(msg_t *pMsg)
+{
+ DEFiRet;
+ uchar *pszMsg;
+ uchar *pDst; /* destination for copy job */
+ size_t lenMsg;
+ size_t iSrc;
+ size_t iDst;
+ size_t iMaxLine;
+ size_t maxDest;
+ sbool bUpdatedLen = RSFALSE;
+ uchar szSanBuf[32*1024]; /* buffer used for sanitizing a string */
+
+ assert(pMsg != NULL);
+ assert(pMsg->iLenRawMsg > 0);
+
+ pszMsg = pMsg->pszRawMsg;
+ lenMsg = pMsg->iLenRawMsg;
+
+ /* remove NUL character at end of message (see comment in function header)
+ * Note that we do not need to add a NUL character in this case, because it
+ * is already present ;)
+ */
+ if(pszMsg[lenMsg-1] == '\0') {
+ DBGPRINTF("dropped NUL at very end of message\n");
+ bUpdatedLen = RSTRUE;
+ lenMsg--;
+ }
+
+ /* then we check if we need to drop trailing LFs, which often make
+ * their way into syslog messages unintentionally. In order to remain
+ * compatible to recent IETF developments, we allow the user to
+ * turn on/off this handling. rgerhards, 2007-07-23
+ */
+ if(bDropTrailingLF && pszMsg[lenMsg-1] == '\n') {
+ DBGPRINTF("dropped LF at very end of message (DropTrailingLF is set)\n");
+ lenMsg--;
+ pszMsg[lenMsg] = '\0';
+ bUpdatedLen = RSTRUE;
+ }
+
+ /* it is much quicker to sweep over the message and see if it actually
+ * needs sanitation than to do the sanitation in any case. So we first do
+ * this and terminate when it is not needed - which is expectedly the case
+ * for the vast majority of messages. -- rgerhards, 2009-06-15
+ * Note that we do NOT check here if tab characters are to be escaped or
+ * not. I expect this functionality to be seldomly used and thus I do not
+ * like to pay the performance penalty. So the penalty is only with those
+ * that actually use it, because we may call the sanitizer without actual
+ * need below (but it then still will work perfectly well!). -- rgerhards, 2009-11-27
+ */
+ int bNeedSanitize = 0;
+ for(iSrc = 0 ; iSrc < lenMsg ; iSrc++) {
+ if(pszMsg[iSrc] < 32) {
+ if(bSpaceLFOnRcv && pszMsg[iSrc] == '\n')
+ pszMsg[iSrc] = ' ';
+ else if(pszMsg[iSrc] == '\0' || bEscapeCCOnRcv) {
+ bNeedSanitize = 1;
+ if (!bSpaceLFOnRcv)
+ break;
+ }
+ } else if(pszMsg[iSrc] > 127 && bEscape8BitChars) {
+ bNeedSanitize = 1;
+ break;
+ }
+ }
+
+ if(!bNeedSanitize) {
+ if(bUpdatedLen == RSTRUE)
+ MsgSetRawMsgSize(pMsg, lenMsg);
+ FINALIZE;
+ }
+
+ /* now copy over the message and sanitize it. Note that up to iSrc-1 there was
+ * obviously no need to sanitize, so we can go over that quickly...
+ */
+ iMaxLine = glbl.GetMaxLine();
+ maxDest = lenMsg * 4; /* message can grow at most four-fold */
+ if(maxDest > iMaxLine)
+ maxDest = iMaxLine; /* but not more than the max size! */
+ if(maxDest < sizeof(szSanBuf))
+ pDst = szSanBuf;
+ else
+ CHKmalloc(pDst = MALLOC(sizeof(uchar) * (iMaxLine + 1)));
+ if(iSrc > 0) {
+ iSrc--; /* go back to where everything is OK */
+ memcpy(pDst, pszMsg, iSrc); /* fast copy known good */
+ }
+ iDst = iSrc;
+ while(iSrc < lenMsg && iDst < maxDest - 3) { /* leave some space if last char must be escaped */
+ if((pszMsg[iSrc] < 32) && (pszMsg[iSrc] != '\t' || bEscapeTab)) {
+ /* note: \0 must always be escaped, the rest of the code currently
+ * can not handle it! -- rgerhards, 2009-08-26
+ */
+ if(pszMsg[iSrc] == '\0' || bEscapeCCOnRcv) {
+ /* we are configured to escape control characters. Please note
+ * that this most probably break non-western character sets like
+ * Japanese, Korean or Chinese. rgerhards, 2007-07-17
+ */
+ pDst[iDst++] = cCCEscapeChar;
+ pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0300) >> 6);
+ pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0070) >> 3);
+ pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0007));
+ }
+ } else if(pszMsg[iSrc] > 127 && bEscape8BitChars) {
+ /* In this case, we also do the conversion. Note that this most
+ * probably breaks European languages. -- rgerhards, 2010-01-27
+ */
+ pDst[iDst++] = cCCEscapeChar;
+ pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0300) >> 6);
+ pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0070) >> 3);
+ pDst[iDst++] = '0' + ((pszMsg[iSrc] & 0007));
+ } else {
+ pDst[iDst++] = pszMsg[iSrc];
+ }
+ ++iSrc;
+ }
+ pDst[iDst] = '\0';
+
+ MsgSetRawMsg(pMsg, (char*)pDst, iDst); /* save sanitized string */
+
+ if(pDst != szSanBuf)
+ free(pDst);
+
+finalize_it:
+ RETiRet;
+}
+
+/* A standard parser to parse out the PRI. This is made available in
+ * this module as it is expected that allmost all parsers will need
+ * that functionality and so they do not need to implement it themsleves.
+ */
+static inline rsRetVal
+ParsePRI(msg_t *pMsg)
+{
+ int pri;
+ uchar *msg;
+ int lenMsg;
+ DEFiRet;
+
+ /* pull PRI */
+ lenMsg = pMsg->iLenRawMsg;
+ msg = pMsg->pszRawMsg;
+ pri = DEFUPRI;
+ if(pMsg->msgFlags & NO_PRI_IN_RAW) {
+ /* In this case, simply do so as if the pri would be right at top */
+ MsgSetAfterPRIOffs(pMsg, 0);
+ } else {
+ if(*msg == '<') {
+ /* while we process the PRI, we also fill the PRI textual representation
+ * inside the msg object. This may not be ideal from an OOP point of view,
+ * but it offers us performance...
+ */
+ pri = 0;
+ while(--lenMsg > 0 && isdigit((int) *++msg)) {
+ pri = 10 * pri + (*msg - '0');
+ }
+ if(*msg == '>')
+ ++msg;
+ if(pri & ~(LOG_FACMASK|LOG_PRIMASK))
+ pri = DEFUPRI;
+ }
+ pMsg->iFacility = LOG_FAC(pri);
+ pMsg->iSeverity = LOG_PRI(pri);
+ MsgSetAfterPRIOffs(pMsg, msg - pMsg->pszRawMsg);
+ }
+ RETiRet;
+}
+
+
+/* Parse a received message. The object's rawmsg property is taken and
+ * parsed according to the relevant standards. This can later be
+ * extended to support configured parsers.
+ * rgerhards, 2008-10-09
+ */
+static rsRetVal
+ParseMsg(msg_t *pMsg)
+{
+ rsRetVal localRet = RS_RET_ERR;
+ parserList_t *pParserList;
+ parser_t *pParser;
+ sbool bIsSanitized;
+ sbool bPRIisParsed;
+ static int iErrMsgRateLimiter = 0;
+ DEFiRet;
+
+ if(pMsg->iLenRawMsg == 0)
+ ABORT_FINALIZE(RS_RET_EMPTY_MSG);
+
+# ifdef USE_NETZIP
+ CHKiRet(uncompressMessage(pMsg));
+# endif
+
+ /* we take the risk to print a non-sanitized string, because this is the best we can get
+ * (and that functionality is too important for debugging to drop it...).
+ */
+ DBGPRINTF("msg parser: flags %x, from '%s', msg '%.60s'\n", pMsg->msgFlags,
+ (pMsg->msgFlags & NEEDS_DNSRESOL) ? UCHAR_CONSTANT("~NOTRESOLVED~") : getRcvFrom(pMsg),
+ pMsg->pszRawMsg);
+
+ /* we now need to go through our list of parsers and see which one is capable of
+ * parsing the message. Note that the first parser that requires message sanitization
+ * will cause it to happen. After that, access to the unsanitized message is no
+ * loger possible.
+ */
+ pParserList = ruleset.GetParserList(ourConf, pMsg);
+ if(pParserList == NULL) {
+ pParserList = pDfltParsLst;
+ }
+ DBGPRINTF("parse using parser list %p%s.\n", pParserList,
+ (pParserList == pDfltParsLst) ? " (the default list)" : "");
+
+ bIsSanitized = RSFALSE;
+ bPRIisParsed = RSFALSE;
+ while(pParserList != NULL) {
+ pParser = pParserList->pParser;
+ if(pParser->bDoSanitazion && bIsSanitized == RSFALSE) {
+ CHKiRet(SanitizeMsg(pMsg));
+ if(pParser->bDoPRIParsing && bPRIisParsed == RSFALSE) {
+ CHKiRet(ParsePRI(pMsg));
+ bPRIisParsed = RSTRUE;
+ }
+ bIsSanitized = RSTRUE;
+ }
+ localRet = pParser->pModule->mod.pm.parse(pMsg);
+ DBGPRINTF("Parser '%s' returned %d\n", pParser->pName, localRet);
+ if(localRet != RS_RET_COULD_NOT_PARSE)
+ break;
+ pParserList = pParserList->pNext;
+ }
+
+ /* We need to log a warning message and drop the message if we did not find a parser.
+ * Note that we log at most the first 1000 message, as this may very well be a problem
+ * that causes a message generation loop. We do not synchronize that counter, it doesn't
+ * matter if we log a handful messages more than we should...
+ */
+ if(localRet != RS_RET_OK) {
+ if(++iErrMsgRateLimiter > 1000) {
+ errmsg.LogError(0, localRet, "Error: one message could not be processed by "
+ "any parser, message is being discarded (start of raw msg: '%.50s')",
+ pMsg->pszRawMsg);
+ }
+ DBGPRINTF("No parser could process the message (state %d), we need to discard it.\n", localRet);
+ ABORT_FINALIZE(localRet);
+ }
+
+ /* "finalize" message object */
+ pMsg->msgFlags &= ~NEEDS_PARSING; /* this message is now parsed */
+
+finalize_it:
+ RETiRet;
+}
+
+/* set the parser name - string is copied over, call can continue to use it,
+ * but must free it if desired.
+ */
+static rsRetVal
+SetName(parser_t *pThis, uchar *name)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, parser);
+ assert(name != NULL);
+
+ if(pThis->pName != NULL) {
+ free(pThis->pName);
+ pThis->pName = NULL;
+ }
+
+ CHKmalloc(pThis->pName = ustrdup(name));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* set a pointer to "our" module. Note that no module
+ * pointer must already be set.
+ */
+static rsRetVal
+SetModPtr(parser_t *pThis, modInfo_t *pMod)
+{
+ ISOBJ_TYPE_assert(pThis, parser);
+ assert(pMod != NULL);
+ assert(pThis->pModule == NULL);
+ pThis->pModule = pMod;
+ return RS_RET_OK;
+}
+
+
+/* Specify if we should do standard message sanitazion before we pass the data
+ * down to the parser.
+ */
+static rsRetVal
+SetDoSanitazion(parser_t *pThis, int bDoIt)
+{
+ ISOBJ_TYPE_assert(pThis, parser);
+ pThis->bDoSanitazion = bDoIt;
+ return RS_RET_OK;
+}
+
+
+/* Specify if we should do standard PRI parsing before we pass the data
+ * down to the parser module.
+ */
+static rsRetVal
+SetDoPRIParsing(parser_t *pThis, int bDoIt)
+{
+ ISOBJ_TYPE_assert(pThis, parser);
+ pThis->bDoPRIParsing = bDoIt;
+ return RS_RET_OK;
+}
+
+
+/* queryInterface function-- rgerhards, 2009-11-03
+ */
+BEGINobjQueryInterface(parser)
+CODESTARTobjQueryInterface(parser)
+ if(pIf->ifVersion != parserCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = parserConstruct;
+ pIf->ConstructFinalize = parserConstructFinalize;
+ pIf->Destruct = parserDestruct;
+ pIf->SetName = SetName;
+ pIf->SetModPtr = SetModPtr;
+ pIf->SetDoSanitazion = SetDoSanitazion;
+ pIf->SetDoPRIParsing = SetDoPRIParsing;
+ pIf->ParseMsg = ParseMsg;
+ pIf->SanitizeMsg = SanitizeMsg;
+ pIf->InitParserList = InitParserList;
+ pIf->DestructParserList = DestructParserList;
+ pIf->AddParserToList = AddParserToList;
+ pIf->AddDfltParser = AddDfltParser;
+ pIf->FindParser = FindParser;
+finalize_it:
+ENDobjQueryInterface(parser)
+
+
+
+/* Reset config variables to default values.
+ * rgerhards, 2007-07-17
+ */
+static rsRetVal
+resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ cCCEscapeChar = '#';
+ bEscapeCCOnRcv = 1; /* default is to escape control characters */
+ bSpaceLFOnRcv = 0;
+ bEscape8BitChars = 0; /* default is to escape control characters */
+ bEscapeTab = 1; /* default is to escape control characters */
+ bDropTrailingLF = 1; /* default is to drop trailing LF's on reception */
+
+ return RS_RET_OK;
+}
+
+/* This destroys the master parserlist and all of its parser entries. MUST only be
+ * done when the module is shut down. Parser modules are NOT unloaded, rsyslog
+ * does that at a later stage for all dynamically loaded modules.
+ */
+static void
+destroyMasterParserList(void)
+{
+ parserList_t *pParsLst;
+ parserList_t *pParsLstDel;
+
+ pParsLst = pParsLstRoot;
+ while(pParsLst != NULL) {
+ parserDestruct(&pParsLst->pParser);
+ pParsLstDel = pParsLst;
+ pParsLst = pParsLst->pNext;
+ free(pParsLstDel);
+ }
+}
+
+/* Exit our class.
+ * rgerhards, 2009-11-04
+ */
+BEGINObjClassExit(parser, OBJ_IS_CORE_MODULE) /* class, version */
+ DestructParserList(&pDfltParsLst);
+ destroyMasterParserList();
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+ objRelease(ruleset, CORE_COMPONENT);
+ENDObjClassExit(parser)
+
+
+/* Initialize the parser class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2009-11-02
+ */
+BEGINObjClassInit(parser, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+
+ CHKiRet(regCfSysLineHdlr((uchar *)"controlcharacterescapeprefix", 0, eCmdHdlrGetChar, NULL, &cCCEscapeChar, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary, NULL, &bDropTrailingLF, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscapeCCOnRcv, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"spacelfonreceive", 0, eCmdHdlrBinary, NULL, &bSpaceLFOnRcv, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"escape8bitcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscape8BitChars, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactertab", 0, eCmdHdlrBinary, NULL, &bEscapeTab, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL));
+
+ InitParserList(&pParsLstRoot);
+ InitParserList(&pDfltParsLst);
+ENDObjClassInit(parser)
+
diff --git a/runtime/parser.h b/runtime/parser.h
new file mode 100644
index 00000000..87a6269e
--- /dev/null
+++ b/runtime/parser.h
@@ -0,0 +1,71 @@
+/* header for parser.c
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_PARSER_H
+#define INCLUDED_PARSER_H
+
+
+/* we create a small helper object, a list of parsers, that we can use to
+ * build a chain of them whereever this is needed (initially thought to be
+ * used in ruleset.c as well as ourselvs).
+ */
+struct parserList_s {
+ parser_t *pParser;
+ parserList_t *pNext;
+};
+
+
+/* the parser object, a dummy because we have only static methods */
+struct parser_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ uchar *pName; /* name of this parser */
+ modInfo_t *pModule; /* pointer to parser's module */
+ sbool bDoSanitazion; /* do standard message sanitazion before calling parser? */
+ sbool bDoPRIParsing; /* do standard PRI parsing before calling parser? */
+};
+
+/* interfaces */
+BEGINinterface(parser) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(var);
+ rsRetVal (*Construct)(parser_t **ppThis);
+ rsRetVal (*ConstructFinalize)(parser_t *pThis);
+ rsRetVal (*Destruct)(parser_t **ppThis);
+ rsRetVal (*SetName)(parser_t *pThis, uchar *name);
+ rsRetVal (*SetModPtr)(parser_t *pThis, modInfo_t *pMod);
+ rsRetVal (*SetDoSanitazion)(parser_t *pThis, int);
+ rsRetVal (*SetDoPRIParsing)(parser_t *pThis, int);
+ rsRetVal (*FindParser)(parser_t **ppThis, uchar*name);
+ rsRetVal (*InitParserList)(parserList_t **pListRoot);
+ rsRetVal (*DestructParserList)(parserList_t **pListRoot);
+ rsRetVal (*AddParserToList)(parserList_t **pListRoot, parser_t *pParser);
+ /* static functions */
+ rsRetVal (*ParseMsg)(msg_t *pMsg);
+ rsRetVal (*SanitizeMsg)(msg_t *pMsg);
+ rsRetVal (*AddDfltParser)(uchar *);
+ENDinterface(parser)
+#define parserCURR_IF_VERSION 1 /* increment whenever you change the interface above! */
+
+void printParserList(parserList_t *pList);
+
+/* prototypes */
+PROTOTYPEObj(parser);
+
+
+#endif /* #ifndef INCLUDED_PARSER_H */
diff --git a/runtime/prop.c b/runtime/prop.c
new file mode 100644
index 00000000..cb89fac0
--- /dev/null
+++ b/runtime/prop.c
@@ -0,0 +1,247 @@
+/* prop.c - rsyslog's prop object
+ *
+ * This object is meant to support message properties that are stored
+ * seperately from the message. The main intent is to support properties
+ * that are "constant" during a period of time, so that many messages may
+ * contain a reference to the same property. It is important, though, that
+ * properties are destroyed when they are no longer needed.
+ *
+ * Please note that this is a performance-critical part of the software and
+ * as such we may use some methods in here which do not look elegant, but
+ * which are fast...
+ *
+ * Module begun 2009-06-17 by Rainer Gerhards
+ *
+ * Copyright 2009-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "obj-types.h"
+#include "unicode-helper.h"
+#include "atomic.h"
+#include "prop.h"
+
+/* static data */
+DEFobjStaticHelpers
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(prop) /* be sure to specify the object type also in END macro! */
+ pThis->iRefCount = 1;
+ INIT_ATOMIC_HELPER_MUT(pThis->mutRefCount);
+ENDobjConstruct(prop)
+
+
+/* destructor for the prop object */
+BEGINobjDestruct(prop) /* be sure to specify the object type also in END and CODESTART macros! */
+ int currRefCount;
+CODESTARTobjDestruct(prop)
+ currRefCount = ATOMIC_DEC_AND_FETCH(&pThis->iRefCount, &pThis->mutRefCount);
+ if(currRefCount == 0) {
+ /* (only) in this case we need to actually destruct the object */
+ if(pThis->len >= CONF_PROP_BUFSIZE)
+ free(pThis->szVal.psz);
+ DESTROY_ATOMIC_HELPER_MUT(pThis->mutRefCount);
+ } else {
+ pThis = NULL; /* tell framework NOT to destructing the object! */
+ }
+ENDobjDestruct(prop)
+
+/* set string, we make our own private copy! This MUST only be called BEFORE
+ * ConstructFinalize()!
+ */
+static rsRetVal SetString(prop_t *pThis, uchar *psz, int len)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, prop);
+ if(pThis->len >= CONF_PROP_BUFSIZE)
+ free(pThis->szVal.psz);
+ pThis->len = len;
+ if(len < CONF_PROP_BUFSIZE) {
+ memcpy(pThis->szVal.sz, psz, len + 1);
+ } else {
+ CHKmalloc(pThis->szVal.psz = MALLOC(len + 1));
+ memcpy(pThis->szVal.psz, psz, len + 1);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* get string length */
+static int GetStringLen(prop_t *pThis)
+{
+ return pThis->len;
+}
+
+
+/* get string */
+rsRetVal GetString(prop_t *pThis, uchar **ppsz, int *plen)
+{
+ BEGINfunc
+ ISOBJ_TYPE_assert(pThis, prop);
+ if(pThis->len < CONF_PROP_BUFSIZE) {
+ *ppsz = pThis->szVal.sz;
+ } else {
+ *ppsz = pThis->szVal.psz;
+ }
+ *plen = pThis->len;
+ ENDfunc
+ return RS_RET_OK;
+}
+
+
+/* ConstructionFinalizer
+ * rgerhards, 2008-01-09
+ */
+static rsRetVal
+propConstructFinalize(prop_t __attribute__((unused)) *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, prop);
+ RETiRet;
+}
+
+
+/* add a new reference. It is VERY IMPORTANT to call this function whenever
+ * the property is handed over to some entitiy that later call Destruct() on it.
+ */
+static rsRetVal AddRef(prop_t *pThis)
+{
+ ATOMIC_INC(&pThis->iRefCount, &pThis->mutRefCount);
+ return RS_RET_OK;
+}
+
+
+/* this is a "do it all in one shot" function that creates a new property,
+ * assigns the provided string to it and finalizes the property. Among the
+ * convenience, it is alos (very, very) slightly faster.
+ * rgerhards, 2009-07-01
+ */
+static rsRetVal CreateStringProp(prop_t **ppThis, uchar* psz, int len)
+{
+ DEFiRet;
+ propConstruct(ppThis);
+ SetString(*ppThis, psz, len);
+ propConstructFinalize(*ppThis);
+ RETiRet;
+}
+
+/* another one-stop function, quite useful: it takes a property pointer and
+ * a string. If the string is already contained in the property, nothing happens.
+ * If the string is different (or the pointer NULL), the current property
+ * is destructed and a new one created. This can be used to get a specific
+ * name in those cases where there is a good chance that the property
+ * immediatly previously processed already contained the value we need - in
+ * which case we save us all the creation overhead by just reusing the already
+ * existing property).
+ * rgerhards, 2009-07-01
+ */
+rsRetVal CreateOrReuseStringProp(prop_t **ppThis, uchar *psz, int len)
+{
+ uchar *pszPrev;
+ int lenPrev;
+ DEFiRet;
+ assert(ppThis != NULL);
+
+ if(*ppThis == NULL) {
+ /* we need to create a property */
+ CHKiRet(CreateStringProp(ppThis, psz, len));
+ } else {
+ /* already exists, check if we can re-use it */
+ GetString(*ppThis, &pszPrev, &lenPrev);
+ if(len != lenPrev || ustrcmp(psz, pszPrev)) {
+ /* different, need to discard old & create new one */
+ propDestruct(ppThis);
+ CHKiRet(CreateStringProp(ppThis, psz, len));
+ } /* else we can re-use the existing one! */
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* debugprint for the prop object */
+BEGINobjDebugPrint(prop) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(prop)
+ dbgprintf("prop object %p - no further debug info implemented\n", pThis);
+ENDobjDebugPrint(prop)
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-21
+ */
+BEGINobjQueryInterface(prop)
+CODESTARTobjQueryInterface(prop)
+ if(pIf->ifVersion != propCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = propConstruct;
+ pIf->ConstructFinalize = propConstructFinalize;
+ pIf->Destruct = propDestruct;
+ pIf->DebugPrint = propDebugPrint;
+ pIf->SetString = SetString;
+ pIf->GetString = GetString;
+ pIf->GetStringLen = GetStringLen;
+ pIf->AddRef = AddRef;
+ pIf->CreateStringProp = CreateStringProp;
+ pIf->CreateOrReuseStringProp = CreateOrReuseStringProp;
+
+finalize_it:
+ENDobjQueryInterface(prop)
+
+
+/* Exit the prop class.
+ * rgerhards, 2009-04-06
+ */
+BEGINObjClassExit(prop, OBJ_IS_CORE_MODULE) /* class, version */
+// objRelease(errmsg, CORE_COMPONENT);
+ENDObjClassExit(prop)
+
+
+/* Initialize the prop class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(prop, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+// CHKiRet(objUse(errmsg, CORE_COMPONENT));
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, propDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, propConstructFinalize);
+ENDObjClassInit(prop)
+
+/* vi:set ai:
+ */
diff --git a/runtime/prop.h b/runtime/prop.h
new file mode 100644
index 00000000..c7564e6b
--- /dev/null
+++ b/runtime/prop.h
@@ -0,0 +1,65 @@
+/* The prop object.
+ *
+ * This implements props within rsyslog.
+ *
+ * Copyright 2009-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_PROP_H
+#define INCLUDED_PROP_H
+#include "atomic.h"
+
+/* the prop object */
+struct prop_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ int iRefCount; /* reference counter */
+ union {
+ uchar *psz; /* stored string */
+ uchar sz[CONF_PROP_BUFSIZE];
+ } szVal;
+ int len; /* we use int intentionally, otherwise we may get some troubles... */
+ DEF_ATOMIC_HELPER_MUT(mutRefCount);
+};
+
+/* interfaces */
+BEGINinterface(prop) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(prop);
+ rsRetVal (*Construct)(prop_t **ppThis);
+ rsRetVal (*ConstructFinalize)(prop_t *pThis);
+ rsRetVal (*Destruct)(prop_t **ppThis);
+ rsRetVal (*SetString)(prop_t *pThis, uchar* psz, int len);
+ rsRetVal (*GetString)(prop_t *pThis, uchar** ppsz, int *plen);
+ int (*GetStringLen)(prop_t *pThis);
+ rsRetVal (*AddRef)(prop_t *pThis);
+ rsRetVal (*CreateStringProp)(prop_t **ppThis, uchar* psz, int len);
+ rsRetVal (*CreateOrReuseStringProp)(prop_t **ppThis, uchar *psz, int len);
+ENDinterface(prop)
+#define propCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+
+/* get classic c-style string */
+static inline uchar *
+propGetSzStr(prop_t *pThis)
+{
+ return(pThis->len < CONF_PROP_BUFSIZE) ? pThis->szVal.sz : pThis->szVal.psz;
+}
+
+/* prototypes */
+PROTOTYPEObj(prop);
+
+#endif /* #ifndef INCLUDED_PROP_H */
diff --git a/runtime/queue.c b/runtime/queue.c
new file mode 100644
index 00000000..699e2a66
--- /dev/null
+++ b/runtime/queue.c
@@ -0,0 +1,2938 @@
+/* queue.c
+ *
+ * This file implements the queue object and its several queueing methods.
+ *
+ * File begun on 2008-01-03 by RGerhards
+ *
+ * There is some in-depth documentation available in doc/dev_queue.html
+ * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it
+ * if you are getting aquainted to the object.
+ *
+ * NOTE: as of 2009-04-22, I have begin to remove the qqueue* prefix from static
+ * function names - this makes it really hard to read and does not provide much
+ * benefit, at least I (now) think so...
+ *
+ * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/stat.h> /* required for HP UX */
+#include <time.h>
+#include <errno.h>
+
+#include "rsyslog.h"
+#include "queue.h"
+#include "stringbuf.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "wtp.h"
+#include "wti.h"
+#include "msg.h"
+#include "atomic.h"
+#include "errmsg.h"
+#include "datetime.h"
+#include "unicode-helper.h"
+#include "statsobj.h"
+
+#ifdef OS_SOLARIS
+# include <sched.h>
+#endif
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(strm)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(statsobj)
+
+/* forward-definitions */
+static inline rsRetVal doEnqSingleObj(qqueue_t *pThis, flowControl_t flowCtlType, msg_t *pMsg);
+static rsRetVal qqueueChkPersist(qqueue_t *pThis, int nUpdates);
+static rsRetVal RateLimiter(qqueue_t *pThis);
+static int qqueueChkStopWrkrDA(qqueue_t *pThis);
+static rsRetVal GetDeqBatchSize(qqueue_t *pThis, int *pVal);
+static rsRetVal ConsumerDA(qqueue_t *pThis, wti_t *pWti);
+static rsRetVal batchProcessed(qqueue_t *pThis, wti_t *pWti);
+static rsRetVal qqueueMultiEnqObjNonDirect(qqueue_t *pThis, multi_submit_t *pMultiSub);
+static rsRetVal qqueueMultiEnqObjDirect(qqueue_t *pThis, multi_submit_t *pMultiSub);
+static rsRetVal qAddDirect(qqueue_t *pThis, msg_t *pMsg);
+static rsRetVal qDestructDirect(qqueue_t __attribute__((unused)) *pThis);
+static rsRetVal qConstructDirect(qqueue_t __attribute__((unused)) *pThis);
+static rsRetVal qDelDirect(qqueue_t __attribute__((unused)) *pThis);
+static rsRetVal qDestructDisk(qqueue_t *pThis);
+
+/* some constants for queuePersist () */
+#define QUEUE_CHECKPOINT 1
+#define QUEUE_NO_CHECKPOINT 0
+
+/* tables for interfacing with the v6 config system */
+static struct cnfparamdescr cnfpdescr[] = {
+ { "queue.filename", eCmdHdlrGetWord, 0 },
+ { "queue.size", eCmdHdlrSize, 0 },
+ { "queue.dequeuebatchsize", eCmdHdlrInt, 0 },
+ { "queue.maxdiskspace", eCmdHdlrSize, 0 },
+ { "queue.highwatermark", eCmdHdlrInt, 0 },
+ { "queue.lowwatermark", eCmdHdlrInt, 0 },
+ { "queue.fulldelaymark", eCmdHdlrInt, 0 },
+ { "queue.lightdelaymark", eCmdHdlrInt, 0 },
+ { "queue.discardmark", eCmdHdlrInt, 0 },
+ { "queue.discardseverity", eCmdHdlrFacility, 0 },
+ { "queue.checkpointinterval", eCmdHdlrInt, 0 },
+ { "queue.syncqueuefiles", eCmdHdlrBinary, 0 },
+ { "queue.type", eCmdHdlrQueueType, 0 },
+ { "queue.workerthreads", eCmdHdlrInt, 0 },
+ { "queue.timeoutshutdown", eCmdHdlrInt, 0 },
+ { "queue.timeoutactioncompletion", eCmdHdlrInt, 0 },
+ { "queue.timeoutenqueue", eCmdHdlrInt, 0 },
+ { "queue.timeoutworkerthreadshutdown", eCmdHdlrInt, 0 },
+ { "queue.workerthreadminimummessages", eCmdHdlrInt, 0 },
+ { "queue.maxfilesize", eCmdHdlrSize, 0 },
+ { "queue.saveonshutdown", eCmdHdlrBinary, 0 },
+ { "queue.dequeueslowdown", eCmdHdlrInt, 0 },
+ { "queue.dequeuetimebegin", eCmdHdlrInt, 0 },
+ { "queue.dequeuetimeend", eCmdHdlrInt, 0 },
+ { "queue.cry.provider", eCmdHdlrGetWord, 0 }
+};
+static struct cnfparamblk pblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfpdescr)/sizeof(struct cnfparamdescr),
+ cnfpdescr
+ };
+
+/* debug aid */
+static inline void displayBatchState(batch_t *pBatch)
+{
+ int i;
+ for(i = 0 ; i < pBatch->nElem ; ++i) {
+ DBGPRINTF("displayBatchState %p[%d]: %d\n", pBatch, i, pBatch->eltState[i]);
+ }
+}
+
+/***********************************************************************
+ * we need a private data structure, the "to-delete" list. As C does
+ * not provide any partly private data structures, we implement this
+ * structure right here inside the module.
+ * Note that this list must always be kept sorted based on a unique
+ * dequeue ID (which is monotonically increasing).
+ * rgerhards, 2009-05-18
+ ***********************************************************************/
+
+/* generate next uniqueue dequeue ID. Note that uniqueness is only required
+ * on a per-queue basis and while this instance runs. So a stricly monotonically
+ * increasing counter is sufficient (if enough bits are used).
+ */
+static inline qDeqID getNextDeqID(qqueue_t *pQueue)
+{
+ ISOBJ_TYPE_assert(pQueue, qqueue);
+ return pQueue->deqIDAdd++;
+}
+
+
+/* return the top element of the to-delete list or NULL, if the
+ * list is empty.
+ */
+static inline toDeleteLst_t *tdlPeek(qqueue_t *pQueue)
+{
+ ISOBJ_TYPE_assert(pQueue, qqueue);
+ return pQueue->toDeleteLst;
+}
+
+
+/* remove the top element of the to-delete list. Nothing but the
+ * element itself is destroyed. Must not be called when the list
+ * is empty.
+ */
+static inline rsRetVal tdlPop(qqueue_t *pQueue)
+{
+ toDeleteLst_t *pRemove;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pQueue, qqueue);
+ assert(pQueue->toDeleteLst != NULL);
+
+ pRemove = pQueue->toDeleteLst;
+ pQueue->toDeleteLst = pQueue->toDeleteLst->pNext;
+ free(pRemove);
+
+ RETiRet;
+}
+
+
+/* Add a new to-delete list entry. The function allocates the data
+ * structure, populates it with the values provided and links the new
+ * element into the correct place inside the list.
+ */
+static inline rsRetVal tdlAdd(qqueue_t *pQueue, qDeqID deqID, int nElemDeq)
+{
+ toDeleteLst_t *pNew;
+ toDeleteLst_t *pPrev;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pQueue, qqueue);
+ assert(pQueue->toDeleteLst != NULL);
+
+ CHKmalloc(pNew = MALLOC(sizeof(toDeleteLst_t)));
+ pNew->deqID = deqID;
+ pNew->nElemDeq = nElemDeq;
+
+ /* now find right spot */
+ for( pPrev = pQueue->toDeleteLst
+ ; pPrev != NULL && deqID > pPrev->deqID
+ ; pPrev = pPrev->pNext) {
+ /*JUST SEARCH*/;
+ }
+
+ if(pPrev == NULL) {
+ pNew->pNext = pQueue->toDeleteLst;
+ pQueue->toDeleteLst = pNew;
+ } else {
+ pNew->pNext = pPrev->pNext;
+ pPrev->pNext = pNew;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* methods */
+
+static inline char *
+getQueueTypeName(queueType_t t)
+{
+ char *r;
+
+ switch(t) {
+ case QUEUETYPE_FIXED_ARRAY:
+ r = "FixedArray";
+ break;
+ case QUEUETYPE_LINKEDLIST:
+ r = "LinkedList";
+ break;
+ case QUEUETYPE_DISK:
+ r = "Disk";
+ break;
+ case QUEUETYPE_DIRECT:
+ r = "Direct";
+ break;
+ default:
+ r = "invalid/unknown queue mode";
+ break;
+ }
+ return r;
+}
+
+void
+qqueueDbgPrint(qqueue_t *pThis)
+{
+ dbgoprint((obj_t*) pThis, "parameter dump:\n");
+ dbgoprint((obj_t*) pThis, "queue.filename '%s'\n",
+ (pThis->pszFilePrefix == NULL) ? "[NONE]" : (char*)pThis->pszFilePrefix);
+ dbgoprint((obj_t*) pThis, "queue.size: %d\n", pThis->iMaxQueueSize);
+ dbgoprint((obj_t*) pThis, "queue.dequeuebatchsize: %d\n", pThis->iDeqBatchSize);
+ dbgoprint((obj_t*) pThis, "queue.maxdiskspace: %lld\n", pThis->iMaxFileSize);
+ dbgoprint((obj_t*) pThis, "queue.highwatermark: %d\n", pThis->iHighWtrMrk);
+ dbgoprint((obj_t*) pThis, "queue.lowwatermark: %d\n", pThis->iLowWtrMrk);
+ dbgoprint((obj_t*) pThis, "queue.fulldelaymark: %d\n", pThis->iFullDlyMrk);
+ dbgoprint((obj_t*) pThis, "queue.lightdelaymark: %d\n", pThis->iLightDlyMrk);
+ dbgoprint((obj_t*) pThis, "queue.discardmark: %d\n", pThis->iDiscardMrk);
+ dbgoprint((obj_t*) pThis, "queue.discardseverity: %d\n", pThis->iDiscardSeverity);
+ dbgoprint((obj_t*) pThis, "queue.checkpointinterval: %d\n", pThis->iPersistUpdCnt);
+ dbgoprint((obj_t*) pThis, "queue.syncqueuefiles: %d\n", pThis->bSyncQueueFiles);
+ dbgoprint((obj_t*) pThis, "queue.type: %d [%s]\n", pThis->qType, getQueueTypeName(pThis->qType));
+ dbgoprint((obj_t*) pThis, "queue.workerthreads: %d\n", pThis->iNumWorkerThreads);
+ dbgoprint((obj_t*) pThis, "queue.timeoutshutdown: %d\n", pThis->toQShutdown);
+ dbgoprint((obj_t*) pThis, "queue.timeoutactioncompletion: %d\n", pThis->toActShutdown);
+ dbgoprint((obj_t*) pThis, "queue.timeoutenqueue: %d\n", pThis->toEnq);
+ dbgoprint((obj_t*) pThis, "queue.timeoutworkerthreadshutdown: %d\n", pThis->toWrkShutdown);
+ dbgoprint((obj_t*) pThis, "queue.workerthreadminimummessages: %d\n", pThis->iMinMsgsPerWrkr);
+ dbgoprint((obj_t*) pThis, "queue.maxfilesize: %lld\n", pThis->iMaxFileSize);
+ dbgoprint((obj_t*) pThis, "queue.saveonshutdown: %d\n", pThis->bSaveOnShutdown);
+ dbgoprint((obj_t*) pThis, "queue.dequeueslowdown: %d\n", pThis->iDeqSlowdown);
+ dbgoprint((obj_t*) pThis, "queue.dequeuetimebegin: %d\n", pThis->iDeqtWinFromHr);
+ dbgoprint((obj_t*) pThis, "queuedequeuetimend.: %d\n", pThis->iDeqtWinToHr);
+}
+
+
+/* get the physical queue size. Must only be called
+ * while mutex is locked!
+ * rgerhards, 2008-01-29
+ */
+static inline int
+getPhysicalQueueSize(qqueue_t *pThis)
+{
+ return pThis->iQueueSize;
+}
+
+
+/* get the logical queue size (that is store size minus logically dequeued elements).
+ * Must only be called while mutex is locked!
+ * rgerhards, 2009-05-19
+ */
+static inline int
+getLogicalQueueSize(qqueue_t *pThis)
+{
+ return pThis->iQueueSize - pThis->nLogDeq;
+}
+
+
+
+/* This function drains the queue in cases where this needs to be done. The most probable
+ * reason is a HUP which needs to discard data (because the queue is configured to be lossy).
+ * During a shutdown, this is typically not needed, as the OS frees up ressources and does
+ * this much quicker than when we clean up ourselvs. -- rgerhards, 2008-10-21
+ * This function returns void, as it makes no sense to communicate an error back, even if
+ * it happens.
+ * This functions works "around" the regular deque mechanism, because it is only used to
+ * clean up (in cases where message loss is acceptable).
+ */
+static inline void queueDrain(qqueue_t *pThis)
+{
+ msg_t *pMsg;
+ ASSERT(pThis != NULL);
+
+ BEGINfunc
+ DBGOPRINT((obj_t*) pThis, "queue (type %d) will lose %d messages, destroying...\n", pThis->qType, pThis->iQueueSize);
+ /* iQueueSize is not decremented by qDel(), so we need to do it ourselves */
+ while(ATOMIC_DEC_AND_FETCH(&pThis->iQueueSize, &pThis->mutQueueSize) > 0) {
+ pThis->qDeq(pThis, &pMsg);
+ if(pMsg != NULL) {
+ msgDestruct(&pMsg);
+ }
+ pThis->qDel(pThis);
+ }
+ ENDfunc
+}
+
+
+/* --------------- code for disk-assisted (DA) queue modes -------------------- */
+
+
+/* returns the number of workers that should be advised at
+ * this point in time. The mutex must be locked when
+ * ths function is called. -- rgerhards, 2008-01-25
+ */
+static inline rsRetVal
+qqueueAdviseMaxWorkers(qqueue_t *pThis)
+{
+ DEFiRet;
+ int iMaxWorkers;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ if(!pThis->bEnqOnly) {
+ if(pThis->bIsDA && getLogicalQueueSize(pThis) >= pThis->iHighWtrMrk) {
+ DBGOPRINT((obj_t*) pThis, "(re)activating DA worker\n");
+ wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* disk queues have always one worker */
+ } else {
+ if(getLogicalQueueSize(pThis) == 0) {
+ iMaxWorkers = 0;
+ } else if(pThis->qType == QUEUETYPE_DISK || pThis->iMinMsgsPerWrkr == 0) {
+ iMaxWorkers = 1;
+ } else {
+ iMaxWorkers = getLogicalQueueSize(pThis) / pThis->iMinMsgsPerWrkr + 1;
+ }
+ wtpAdviseMaxWorkers(pThis->pWtpReg, iMaxWorkers);
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* check if we run in disk-assisted mode and record that
+ * setting for easy (and quick!) access in the future. This
+ * function must only be called from constructors and only
+ * from those that support disk-assisted modes (aka memory-
+ * based queue drivers).
+ * rgerhards, 2008-01-14
+ */
+static rsRetVal
+qqueueChkIsDA(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ if(pThis->pszFilePrefix != NULL) {
+ pThis->bIsDA = 1;
+ DBGOPRINT((obj_t*) pThis, "is disk-assisted, disk will be used on demand\n");
+ } else {
+ DBGOPRINT((obj_t*) pThis, "is NOT disk-assisted\n");
+ }
+
+ RETiRet;
+}
+
+
+/* Start disk-assisted queue mode.
+ * rgerhards, 2008-01-15
+ */
+static rsRetVal
+StartDA(qqueue_t *pThis)
+{
+ DEFiRet;
+ uchar pszDAQName[128];
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ /* create message queue */
+ CHKiRet(qqueueConstruct(&pThis->pqDA, QUEUETYPE_DISK , 1, 0, pThis->pConsumer));
+
+ /* give it a name */
+ snprintf((char*) pszDAQName, sizeof(pszDAQName)/sizeof(uchar), "%s[DA]", obj.GetName((obj_t*) pThis));
+ obj.SetName((obj_t*) pThis->pqDA, pszDAQName);
+
+ /* as the created queue is the same object class, we take the
+ * liberty to access its properties directly.
+ */
+ pThis->pqDA->pqParent = pThis;
+
+ CHKiRet(qqueueSetpAction(pThis->pqDA, pThis->pAction));
+ CHKiRet(qqueueSetsizeOnDiskMax(pThis->pqDA, pThis->sizeOnDiskMax));
+ CHKiRet(qqueueSetiDeqSlowdown(pThis->pqDA, pThis->iDeqSlowdown));
+ CHKiRet(qqueueSetMaxFileSize(pThis->pqDA, pThis->iMaxFileSize));
+ CHKiRet(qqueueSetFilePrefix(pThis->pqDA, pThis->pszFilePrefix, pThis->lenFilePrefix));
+ CHKiRet(qqueueSetiPersistUpdCnt(pThis->pqDA, pThis->iPersistUpdCnt));
+ CHKiRet(qqueueSetbSyncQueueFiles(pThis->pqDA, pThis->bSyncQueueFiles));
+ CHKiRet(qqueueSettoActShutdown(pThis->pqDA, pThis->toActShutdown));
+ CHKiRet(qqueueSettoEnq(pThis->pqDA, pThis->toEnq));
+ CHKiRet(qqueueSetiDeqtWinFromHr(pThis->pqDA, pThis->iDeqtWinFromHr));
+ CHKiRet(qqueueSetiDeqtWinToHr(pThis->pqDA, pThis->iDeqtWinToHr));
+ CHKiRet(qqueueSettoQShutdown(pThis->pqDA, pThis->toQShutdown));
+ CHKiRet(qqueueSetiHighWtrMrk(pThis->pqDA, 0));
+ CHKiRet(qqueueSetiDiscardMrk(pThis->pqDA, 0));
+
+ iRet = qqueueStart(pThis->pqDA);
+ /* file not found is expected, that means it is no previous QIF available */
+ if(iRet != RS_RET_OK && iRet != RS_RET_FILE_NOT_FOUND) {
+ errno = 0; /* else an errno is shown in errmsg! */
+ errmsg.LogError(errno, iRet, "error starting up disk queue, using pure in-memory mode");
+ pThis->bIsDA = 0; /* disable memory mode */
+ FINALIZE; /* something is wrong */
+ }
+
+ DBGOPRINT((obj_t*) pThis, "DA queue initialized, disk queue 0x%lx\n",
+ qqueueGetID(pThis->pqDA));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->pqDA != NULL) {
+ qqueueDestruct(&pThis->pqDA);
+ }
+ DBGOPRINT((obj_t*) pThis, "error %d creating disk queue - giving up.\n", iRet);
+ pThis->bIsDA = 0;
+ }
+
+ RETiRet;
+}
+
+
+/* initiate DA mode
+ * param bEnqOnly tells if the disk queue is to be run in enqueue-only mode. This may
+ * be needed during shutdown of memory queues which need to be persisted to disk.
+ * If this function fails (should not happen), DA mode is not turned on.
+ * rgerhards, 2008-01-16
+ */
+static rsRetVal
+InitDA(qqueue_t *pThis, int bLockMutex)
+{
+ DEFiRet;
+ DEFVARS_mutexProtection;
+ uchar pszBuf[64];
+ size_t lenBuf;
+
+ BEGIN_MTX_PROTECTED_OPERATIONS(pThis->mut, bLockMutex);
+ /* check if we already have a DA worker pool. If not, initiate one. Please note that the
+ * pool is created on first need but never again destructed (until the queue is). This
+ * is intentional. We assume that when we need it once, we may also need it on another
+ * occasion. Ressources used are quite minimal when no worker is running.
+ * rgerhards, 2008-01-24
+ * NOTE: this is the DA worker *pool*, not the DA queue!
+ */
+ lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:DAwpool", obj.GetName((obj_t*) pThis));
+ CHKiRet(wtpConstruct (&pThis->pWtpDA));
+ CHKiRet(wtpSetDbgHdr (pThis->pWtpDA, pszBuf, lenBuf));
+ CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int)) qqueueChkStopWrkrDA));
+ CHKiRet(wtpSetpfGetDeqBatchSize (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, int*)) GetDeqBatchSize));
+ CHKiRet(wtpSetpfDoWork (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, void *pWti)) ConsumerDA));
+ CHKiRet(wtpSetpfObjProcessed (pThis->pWtpDA, (rsRetVal (*)(void *pUsr, wti_t *pWti)) batchProcessed));
+ CHKiRet(wtpSetpmutUsr (pThis->pWtpDA, pThis->mut));
+ CHKiRet(wtpSetpcondBusy (pThis->pWtpDA, &pThis->notEmpty));
+ CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpDA, 1));
+ CHKiRet(wtpSettoWrkShutdown (pThis->pWtpDA, pThis->toWrkShutdown));
+ CHKiRet(wtpSetpUsr (pThis->pWtpDA, pThis));
+ CHKiRet(wtpConstructFinalize (pThis->pWtpDA));
+ /* if we reach this point, we have a "good" DA worker pool */
+
+ /* now construct the actual queue (if it does not already exist) */
+ if(pThis->pqDA == NULL) {
+ CHKiRet(StartDA(pThis));
+ }
+
+finalize_it:
+ END_MTX_PROTECTED_OPERATIONS(pThis->mut);
+ RETiRet;
+}
+
+
+/* --------------- end code for disk-assisted queue modes -------------------- */
+
+
+/* Now, we define type-specific handlers. The provide a generic functionality,
+ * but for this specific type of queue. The mapping to these handlers happens during
+ * queue construction. Later on, handlers are called by pointers present in the
+ * queue instance object.
+ */
+
+/* -------------------- fixed array -------------------- */
+static rsRetVal qConstructFixedArray(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ if(pThis->iMaxQueueSize == 0)
+ ABORT_FINALIZE(RS_RET_QSIZE_ZERO);
+
+ if((pThis->tVars.farray.pBuf = MALLOC(sizeof(void *) * pThis->iMaxQueueSize)) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ pThis->tVars.farray.deqhead = 0;
+ pThis->tVars.farray.head = 0;
+ pThis->tVars.farray.tail = 0;
+
+ qqueueChkIsDA(pThis);
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal qDestructFixedArray(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ queueDrain(pThis); /* discard any remaining queue entries */
+ free(pThis->tVars.farray.pBuf);
+
+ RETiRet;
+}
+
+
+static rsRetVal qAddFixedArray(qqueue_t *pThis, msg_t* in)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ pThis->tVars.farray.pBuf[pThis->tVars.farray.tail] = in;
+ pThis->tVars.farray.tail++;
+ if (pThis->tVars.farray.tail == pThis->iMaxQueueSize)
+ pThis->tVars.farray.tail = 0;
+
+ RETiRet;
+}
+
+
+static rsRetVal qDeqFixedArray(qqueue_t *pThis, msg_t **out)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ *out = (void*) pThis->tVars.farray.pBuf[pThis->tVars.farray.deqhead];
+
+ pThis->tVars.farray.deqhead++;
+ if (pThis->tVars.farray.deqhead == pThis->iMaxQueueSize)
+ pThis->tVars.farray.deqhead = 0;
+
+ RETiRet;
+}
+
+
+static rsRetVal qDelFixedArray(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ pThis->tVars.farray.head++;
+ if (pThis->tVars.farray.head == pThis->iMaxQueueSize)
+ pThis->tVars.farray.head = 0;
+
+ RETiRet;
+}
+
+
+/* -------------------- linked list -------------------- */
+
+
+static rsRetVal qConstructLinkedList(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ pThis->tVars.linklist.pDeqRoot = NULL;
+ pThis->tVars.linklist.pDelRoot = NULL;
+ pThis->tVars.linklist.pLast = NULL;
+
+ qqueueChkIsDA(pThis);
+
+ RETiRet;
+}
+
+
+static rsRetVal qDestructLinkedList(qqueue_t __attribute__((unused)) *pThis)
+{
+ DEFiRet;
+
+ queueDrain(pThis); /* discard any remaining queue entries */
+
+ /* with the linked list type, there is nothing left to do here. The
+ * reason is that there are no dynamic elements for the list itself.
+ */
+
+ RETiRet;
+}
+
+static rsRetVal qAddLinkedList(qqueue_t *pThis, msg_t* pMsg)
+{
+ qLinkedList_t *pEntry;
+ DEFiRet;
+
+ CHKmalloc((pEntry = (qLinkedList_t*) MALLOC(sizeof(qLinkedList_t))));
+
+ pEntry->pNext = NULL;
+ pEntry->pMsg = pMsg;
+
+ if(pThis->tVars.linklist.pDelRoot == NULL) {
+ pThis->tVars.linklist.pDelRoot = pThis->tVars.linklist.pDeqRoot = pThis->tVars.linklist.pLast = pEntry;
+ } else {
+ pThis->tVars.linklist.pLast->pNext = pEntry;
+ pThis->tVars.linklist.pLast = pEntry;
+ }
+
+ if(pThis->tVars.linklist.pDeqRoot == NULL) {
+ pThis->tVars.linklist.pDeqRoot = pEntry;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal qDeqLinkedList(qqueue_t *pThis, msg_t **ppMsg)
+{
+ qLinkedList_t *pEntry;
+ DEFiRet;
+
+ pEntry = pThis->tVars.linklist.pDeqRoot;
+ *ppMsg = pEntry->pMsg;
+ pThis->tVars.linklist.pDeqRoot = pEntry->pNext;
+
+ RETiRet;
+}
+
+
+static rsRetVal qDelLinkedList(qqueue_t *pThis)
+{
+ qLinkedList_t *pEntry;
+ DEFiRet;
+
+ pEntry = pThis->tVars.linklist.pDelRoot;
+
+ if(pThis->tVars.linklist.pDelRoot == pThis->tVars.linklist.pLast) {
+ pThis->tVars.linklist.pDelRoot = pThis->tVars.linklist.pDeqRoot = pThis->tVars.linklist.pLast = NULL;
+ } else {
+ pThis->tVars.linklist.pDelRoot = pEntry->pNext;
+ }
+
+ free(pEntry);
+
+ RETiRet;
+}
+
+
+/* -------------------- disk -------------------- */
+
+
+/* The following function is used to "save" ourself from being killed by
+ * a fatally failed disk queue. A fatal failure is, for example, if no
+ * data can be read or written. In that case, the disk support is disabled,
+ * with all on-disk structures kept as-is as much as possible. Instead, the
+ * queue is switched to direct mode, so that at least
+ * some processing can happen. Of course, this may still have lots of
+ * undesired side-effects, but is probably better than aborting the
+ * syslogd. Note that this function *must* succeed in one way or another, as
+ * we can not recover from failure here. But it may emit different return
+ * states, which can trigger different processing in the higher layers.
+ * rgerhards, 2011-05-03
+ */
+static inline rsRetVal
+queueSwitchToEmergencyMode(qqueue_t *pThis, rsRetVal initiatingError)
+{
+ pThis->iQueueSize = 0;
+ pThis->nLogDeq = 0;
+ qDestructDisk(pThis); /* free disk structures */
+
+ pThis->qType = QUEUETYPE_DIRECT;
+ pThis->qConstruct = qConstructDirect;
+ pThis->qDestruct = qDestructDirect;
+ pThis->qAdd = qAddDirect;
+ pThis->qDel = qDelDirect;
+ pThis->MultiEnq = qqueueMultiEnqObjDirect;
+ if(pThis->pqParent != NULL) {
+ DBGOPRINT((obj_t*) pThis, "DA queue is in emergency mode, disabling DA in parent\n");
+ pThis->pqParent->bIsDA = 0;
+ pThis->pqParent->pqDA = NULL;
+ /* This may have undesired side effects, not sure if I really evaluated
+ * all. So you know where to look at if you come to this point during
+ * troubleshooting ;) -- rgerhards, 2011-05-03
+ */
+ }
+
+ errmsg.LogError(0, initiatingError, "fatal error on disk queue '%s', emergency switch to direct mode",
+ obj.GetName((obj_t*) pThis));
+ return RS_RET_ERR_QUEUE_EMERGENCY;
+}
+
+
+static rsRetVal
+qqueueLoadPersStrmInfoFixup(strm_t *pStrm, qqueue_t __attribute__((unused)) *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pStrm, strm);
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ CHKiRet(strm.SetDir(pStrm, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir())));
+finalize_it:
+ RETiRet;
+}
+
+
+/* The method loads the persistent queue information.
+ * rgerhards, 2008-01-11
+ */
+static rsRetVal
+qqueueTryLoadPersistedInfo(qqueue_t *pThis)
+{
+ DEFiRet;
+ strm_t *psQIF = NULL;
+ struct stat stat_buf;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ /* check if the file exists */
+ if(stat((char*) pThis->pszQIFNam, &stat_buf) == -1) {
+ if(errno == ENOENT) {
+ DBGOPRINT((obj_t*) pThis, "clean startup, no .qi file found\n");
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ } else {
+ DBGOPRINT((obj_t*) pThis, "error %d trying to access .qi file\n", errno);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ }
+
+ /* If we reach this point, we have a .qi file */
+
+ CHKiRet(strm.Construct(&psQIF));
+ CHKiRet(strm.SettOperationsMode(psQIF, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(psQIF, STREAMTYPE_FILE_SINGLE));
+ CHKiRet(strm.SetFName(psQIF, pThis->pszQIFNam, pThis->lenQIFNam));
+ CHKiRet(strm.ConstructFinalize(psQIF));
+
+ /* first, we try to read the property bag for ourselfs */
+ CHKiRet(obj.DeserializePropBag((obj_t*) pThis, psQIF));
+
+ /* then the stream objects (same order as when persisted!) */
+ CHKiRet(obj.Deserialize(&pThis->tVars.disk.pWrite, (uchar*) "strm", psQIF,
+ (rsRetVal(*)(obj_t*,void*))qqueueLoadPersStrmInfoFixup, pThis));
+ CHKiRet(obj.Deserialize(&pThis->tVars.disk.pReadDel, (uchar*) "strm", psQIF,
+ (rsRetVal(*)(obj_t*,void*))qqueueLoadPersStrmInfoFixup, pThis));
+ /* create a duplicate for the read "pointer". */
+ CHKiRet(strm.Dup(pThis->tVars.disk.pReadDel, &pThis->tVars.disk.pReadDeq));
+ CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDeq, 0)); /* deq must NOT delete the files! */
+ CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pReadDeq));
+ /* if we use a crypto provider, we need to amend the objects with it's info */
+ if(pThis->useCryprov) {
+ CHKiRet(strm.Setcryprov(pThis->tVars.disk.pWrite, &pThis->cryprov));
+ CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pWrite, pThis->cryprovData));
+ CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDeq, &pThis->cryprov));
+ CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDeq, pThis->cryprovData));
+ CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDel, &pThis->cryprov));
+ CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDel, pThis->cryprovData));
+ }
+
+ CHKiRet(strm.SeekCurrOffs(pThis->tVars.disk.pWrite));
+ CHKiRet(strm.SeekCurrOffs(pThis->tVars.disk.pReadDel));
+ CHKiRet(strm.SeekCurrOffs(pThis->tVars.disk.pReadDeq));
+
+ /* OK, we could successfully read the file, so we now can request that it be
+ * deleted when we are done with the persisted information.
+ */
+ pThis->bNeedDelQIF = 1;
+
+finalize_it:
+ if(psQIF != NULL)
+ strm.Destruct(&psQIF);
+
+ if(iRet != RS_RET_OK) {
+ DBGOPRINT((obj_t*) pThis, "state %d reading .qi file - can not read persisted info (if any)\n",
+ iRet);
+ }
+
+ RETiRet;
+}
+
+
+/* disk queue constructor.
+ * Note that we use a file limit of 10,000,000 files. That number should never pose a
+ * problem. If so, I guess the user has a design issue... But of course, the code can
+ * always be changed (though it would probably be more appropriate to increase the
+ * allowed file size at this point - that should be a config setting...
+ * rgerhards, 2008-01-10
+ */
+static rsRetVal qConstructDisk(qqueue_t *pThis)
+{
+ DEFiRet;
+ int bRestarted = 0;
+
+ ASSERT(pThis != NULL);
+
+ /* and now check if there is some persistent information that needs to be read in */
+ iRet = qqueueTryLoadPersistedInfo(pThis);
+ if(iRet == RS_RET_OK)
+ bRestarted = 1;
+ else if(iRet != RS_RET_FILE_NOT_FOUND)
+ FINALIZE;
+
+ if(bRestarted == 1) {
+ ;
+ } else {
+ CHKiRet(strm.Construct(&pThis->tVars.disk.pWrite));
+ CHKiRet(strm.SetbSync(pThis->tVars.disk.pWrite, pThis->bSyncQueueFiles));
+ CHKiRet(strm.SetDir(pThis->tVars.disk.pWrite, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir())));
+ CHKiRet(strm.SetiMaxFiles(pThis->tVars.disk.pWrite, 10000000));
+ CHKiRet(strm.SettOperationsMode(pThis->tVars.disk.pWrite, STREAMMODE_WRITE));
+ CHKiRet(strm.SetsType(pThis->tVars.disk.pWrite, STREAMTYPE_FILE_CIRCULAR));
+ if(pThis->useCryprov) {
+ CHKiRet(strm.Setcryprov(pThis->tVars.disk.pWrite, &pThis->cryprov));
+ CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pWrite, pThis->cryprovData));
+ }
+ CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pWrite));
+
+ CHKiRet(strm.Construct(&pThis->tVars.disk.pReadDeq));
+ CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDeq, 0));
+ CHKiRet(strm.SetDir(pThis->tVars.disk.pReadDeq, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir())));
+ CHKiRet(strm.SetiMaxFiles(pThis->tVars.disk.pReadDeq, 10000000));
+ CHKiRet(strm.SettOperationsMode(pThis->tVars.disk.pReadDeq, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(pThis->tVars.disk.pReadDeq, STREAMTYPE_FILE_CIRCULAR));
+ if(pThis->useCryprov) {
+ CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDeq, &pThis->cryprov));
+ CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDeq, pThis->cryprovData));
+ }
+ CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pReadDeq));
+
+ CHKiRet(strm.Construct(&pThis->tVars.disk.pReadDel));
+ CHKiRet(strm.SetbSync(pThis->tVars.disk.pReadDel, pThis->bSyncQueueFiles));
+ CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDel, 1));
+ CHKiRet(strm.SetDir(pThis->tVars.disk.pReadDel, glbl.GetWorkDir(), strlen((char*)glbl.GetWorkDir())));
+ CHKiRet(strm.SetiMaxFiles(pThis->tVars.disk.pReadDel, 10000000));
+ CHKiRet(strm.SettOperationsMode(pThis->tVars.disk.pReadDel, STREAMMODE_READ));
+ CHKiRet(strm.SetsType(pThis->tVars.disk.pReadDel, STREAMTYPE_FILE_CIRCULAR));
+ if(pThis->useCryprov) {
+ CHKiRet(strm.Setcryprov(pThis->tVars.disk.pReadDel, &pThis->cryprov));
+ CHKiRet(strm.SetcryprovData(pThis->tVars.disk.pReadDel, pThis->cryprovData));
+ }
+ CHKiRet(strm.ConstructFinalize(pThis->tVars.disk.pReadDel));
+
+ CHKiRet(strm.SetFName(pThis->tVars.disk.pWrite, pThis->pszFilePrefix, pThis->lenFilePrefix));
+ CHKiRet(strm.SetFName(pThis->tVars.disk.pReadDeq, pThis->pszFilePrefix, pThis->lenFilePrefix));
+ CHKiRet(strm.SetFName(pThis->tVars.disk.pReadDel, pThis->pszFilePrefix, pThis->lenFilePrefix));
+ }
+
+ /* now we set (and overwrite in case of a persisted restart) some parameters which
+ * should always reflect the current configuration variables. Be careful by doing so,
+ * for example file name generation must not be changed as that would break the
+ * ability to read existing queue files. -- rgerhards, 2008-01-12
+ */
+ CHKiRet(strm.SetiMaxFileSize(pThis->tVars.disk.pWrite, pThis->iMaxFileSize));
+ CHKiRet(strm.SetiMaxFileSize(pThis->tVars.disk.pReadDeq, pThis->iMaxFileSize));
+ CHKiRet(strm.SetiMaxFileSize(pThis->tVars.disk.pReadDel, pThis->iMaxFileSize));
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal qDestructDisk(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ if(pThis->tVars.disk.pWrite != NULL)
+ strm.Destruct(&pThis->tVars.disk.pWrite);
+ if(pThis->tVars.disk.pReadDeq != NULL)
+ strm.Destruct(&pThis->tVars.disk.pReadDeq);
+ if(pThis->tVars.disk.pReadDel != NULL)
+ strm.Destruct(&pThis->tVars.disk.pReadDel);
+
+ RETiRet;
+}
+
+static rsRetVal qAddDisk(qqueue_t *pThis, msg_t* pMsg)
+{
+ DEFiRet;
+ number_t nWriteCount;
+
+ ASSERT(pThis != NULL);
+
+ CHKiRet(strm.SetWCntr(pThis->tVars.disk.pWrite, &nWriteCount));
+ CHKiRet((objSerialize(pMsg))(pMsg, pThis->tVars.disk.pWrite));
+ CHKiRet(strm.Flush(pThis->tVars.disk.pWrite));
+ CHKiRet(strm.SetWCntr(pThis->tVars.disk.pWrite, NULL)); /* no more counting for now... */
+
+ pThis->tVars.disk.sizeOnDisk += nWriteCount;
+
+ /* we have enqueued the user element to disk. So we now need to destruct
+ * the in-memory representation. The instance will be re-created upon
+ * dequeue. -- rgerhards, 2008-07-09
+ */
+ msgDestruct(&pMsg);
+
+ DBGOPRINT((obj_t*) pThis, "write wrote %lld octets to disk, queue disk size now %lld octets, EnqOnly:%d\n",
+ nWriteCount, pThis->tVars.disk.sizeOnDisk, pThis->bEnqOnly);
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal qDeqDisk(qqueue_t *pThis, msg_t **ppMsg)
+{
+ DEFiRet;
+ iRet = objDeserializeWithMethods(ppMsg, (uchar*) "msg", 3, pThis->tVars.disk.pReadDeq, NULL,
+ NULL, msgConstructForDeserializer, NULL, MsgDeserialize);
+ RETiRet;
+}
+
+
+/* -------------------- direct (no queueing) -------------------- */
+static rsRetVal qConstructDirect(qqueue_t __attribute__((unused)) *pThis)
+{
+ return RS_RET_OK;
+}
+
+
+static rsRetVal qDestructDirect(qqueue_t __attribute__((unused)) *pThis)
+{
+ return RS_RET_OK;
+}
+
+static rsRetVal qAddDirect(qqueue_t *pThis, msg_t* pMsg)
+{
+ batch_t singleBatch;
+ batch_obj_t batchObj;
+ batch_state_t batchState = BATCH_STATE_RDY;
+ sbool active = 1;
+ int i;
+ DEFiRet;
+
+ //TODO: init batchObj (states _OK and new fields -- CHECK)
+ ASSERT(pThis != NULL);
+
+ /* calling the consumer is quite different here than it is from a worker thread */
+ /* we need to provide the consumer's return value back to the caller because in direct
+ * mode the consumer probably has a lot to convey (which get's lost in the other modes
+ * because they are asynchronous. But direct mode is deliberately synchronous.
+ * rgerhards, 2008-02-12
+ * We use our knowledge about the batch_t structure below, but without that, we
+ * pay a too-large performance toll... -- rgerhards, 2009-04-22
+ */
+ memset(&batchObj, 0, sizeof(batch_obj_t));
+ memset(&singleBatch, 0, sizeof(batch_t));
+ batchObj.pMsg = pMsg;
+ singleBatch.nElem = 1; /* there always is only one in direct mode */
+ singleBatch.pElem = &batchObj;
+ singleBatch.eltState = &batchState;
+ singleBatch.active = &active;
+ iRet = pThis->pConsumer(pThis->pAction, &singleBatch, &pThis->bShutdownImmediate);
+ /* delete the batch string params: TODO: create its own "class" for this */
+ for(i = 0 ; i < CONF_OMOD_NUMSTRINGS_MAXSIZE ; ++i) {
+ free(batchObj.staticActStrings[i]);
+ }
+ msgDestruct(&pMsg);
+
+ RETiRet;
+}
+
+/* "enqueue" a batch in direct mode. This is a shortcut which saves all the overhead
+ * otherwise incured. -- rgerhards, ~2010-06-23
+ */
+rsRetVal qqueueEnqObjDirectBatch(qqueue_t *pThis, batch_t *pBatch)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ /* calling the consumer is quite different here than it is from a worker thread */
+ /* we need to provide the consumer's return value back to the caller because in direct
+ * mode the consumer probably has a lot to convey (which get's lost in the other modes
+ * because they are asynchronous. But direct mode is deliberately synchronous.
+ * rgerhards, 2008-02-12
+ * We use our knowledge about the batch_t structure below, but without that, we
+ * pay a too-large performance toll... -- rgerhards, 2009-04-22
+ */
+ iRet = pThis->pConsumer(pThis->pAction, pBatch, NULL);
+
+ RETiRet;
+}
+
+
+static rsRetVal qDelDirect(qqueue_t __attribute__((unused)) *pThis)
+{
+ return RS_RET_OK;
+}
+
+
+/* --------------- end type-specific handlers -------------------- */
+
+
+/* generic code to add a queue entry
+ * We use some specific code to most efficiently support direct mode
+ * queues. This is justified in spite of the gain and the need to do some
+ * things truely different. -- rgerhards, 2008-02-12
+ */
+static rsRetVal
+qqueueAdd(qqueue_t *pThis, msg_t *pMsg)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ CHKiRet(pThis->qAdd(pThis, pMsg));
+
+ if(pThis->qType != QUEUETYPE_DIRECT) {
+ ATOMIC_INC(&pThis->iQueueSize, &pThis->mutQueueSize);
+ DBGOPRINT((obj_t*) pThis, "entry added, size now log %d, phys %d entries\n",
+ getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* generic code to dequeue a queue entry
+ */
+static rsRetVal
+qqueueDeq(qqueue_t *pThis, msg_t **ppMsg)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ /* we do NOT abort if we encounter an error, because otherwise the queue
+ * will not be decremented, what will most probably result in an endless loop.
+ * If we decrement, however, we may lose a message. But that is better than
+ * losing the whole process because it loops... -- rgerhards, 2008-01-03
+ */
+ iRet = pThis->qDeq(pThis, ppMsg);
+ ATOMIC_INC(&pThis->nLogDeq, &pThis->mutLogDeq);
+
+// DBGOPRINT((obj_t*) pThis, "entry deleted, size now log %d, phys %d entries\n",
+// getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+
+ RETiRet;
+}
+
+
+/* Try to shut down regular and DA queue workers, within the queue timeout
+ * period. That means processing continues as usual. This is the expected
+ * usual case, where during shutdown those messages remaining are being
+ * processed. At this point, it is acceptable that the queue can not be
+ * fully depleted, that case is handled in the next step. During this phase,
+ * we first shut down the main queue DA worker to prevent new data to arrive
+ * at the DA queue, and then we ask the regular workers of both the Regular
+ * and DA queue to try complete processing.
+ * rgerhards, 2009-10-14
+ */
+static inline rsRetVal
+tryShutdownWorkersWithinQueueTimeout(qqueue_t *pThis)
+{
+ struct timespec tTimeout;
+ rsRetVal iRetLocal;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */
+
+ if(pThis->bIsDA) {
+ /* We need to lock the mutex, as otherwise we may have a race that prevents
+ * us from awaking the DA worker. */
+ d_pthread_mutex_lock(pThis->mut);
+
+ /* tell regular queue DA worker to stop shuffling messages to DA queue... */
+ DBGOPRINT((obj_t*) pThis, "setting EnqOnly mode for DA worker\n");
+ pThis->pqDA->bEnqOnly = 1;
+ wtpSetState(pThis->pWtpDA, wtpState_SHUTDOWN_IMMEDIATE);
+ wtpAdviseMaxWorkers(pThis->pWtpDA, 1);
+ DBGOPRINT((obj_t*) pThis, "awoke DA worker, told it to shut down.\n");
+
+ /* also tell the DA queue worker to shut down, so that it already knows... */
+ wtpSetState(pThis->pqDA->pWtpReg, wtpState_SHUTDOWN);
+ wtpAdviseMaxWorkers(pThis->pqDA->pWtpReg, 1); /* awake its lone worker */
+ DBGOPRINT((obj_t*) pThis, "awoke DA queue regular worker, told it to shut down when done.\n");
+
+ d_pthread_mutex_unlock(pThis->mut);
+ }
+
+
+ /* first calculate absolute timeout - we need the absolute value here, because we need to coordinate
+ * shutdown of both the regular and DA queue on *the same* timeout.
+ */
+ timeoutComp(&tTimeout, pThis->toQShutdown);
+ DBGOPRINT((obj_t*) pThis, "trying shutdown of regular workers\n");
+ iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN, &tTimeout);
+ if(iRetLocal == RS_RET_TIMED_OUT) {
+ DBGOPRINT((obj_t*) pThis, "regular shutdown timed out on primary queue (this is OK)\n");
+ } else {
+ DBGOPRINT((obj_t*) pThis, "regular queue workers shut down.\n");
+ }
+
+ /* OK, the worker for the regular queue is processed, on the the DA queue regular worker. */
+ if(pThis->pqDA != NULL) {
+ DBGOPRINT((obj_t*) pThis, "we have a DA queue (0x%lx), requesting its shutdown.\n",
+ qqueueGetID(pThis->pqDA));
+ /* we use the same absolute timeout as above, so we do not use more than the configured
+ * timeout interval!
+ */
+ DBGOPRINT((obj_t*) pThis, "trying shutdown of regular worker of DA queue\n");
+ iRetLocal = wtpShutdownAll(pThis->pqDA->pWtpReg, wtpState_SHUTDOWN, &tTimeout);
+ if(iRetLocal == RS_RET_TIMED_OUT) {
+ DBGOPRINT((obj_t*) pThis, "shutdown timed out on DA queue worker (this is OK)\n");
+ } else {
+ DBGOPRINT((obj_t*) pThis, "DA queue worker shut down.\n");
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* Try to shut down regular and DA queue workers, within the action timeout
+ * period. This aborts processing, but at the end of the current action, in
+ * a well-defined manner. During this phase, we terminate all three worker
+ * pools, including the regular queue DA worker if it not yet has terminated.
+ * Not finishing processing all messages is OK (and expected) at this stage
+ * (they may be preserved later, depending * on bSaveOnShutdown setting).
+ * rgerhards, 2009-10-14
+ */
+static rsRetVal
+tryShutdownWorkersWithinActionTimeout(qqueue_t *pThis)
+{
+ struct timespec tTimeout;
+ rsRetVal iRetLocal;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */
+
+ /* instruct workers to finish ASAP, even if still work exists */
+ DBGOPRINT((obj_t*) pThis, "trying to shutdown workers within Action Timeout");
+ DBGOPRINT((obj_t*) pThis, "setting EnqOnly mode\n");
+ pThis->bEnqOnly = 1;
+ pThis->bShutdownImmediate = 1;
+ /* now DA queue */
+ if(pThis->bIsDA) {
+ pThis->pqDA->bEnqOnly = 1;
+ pThis->pqDA->bShutdownImmediate = 1;
+ }
+
+// TODO: make sure we have at minimum a 10ms timeout - workers deserve a chance...
+ /* now give the queue workers a last chance to gracefully shut down (based on action timeout setting) */
+ timeoutComp(&tTimeout, pThis->toActShutdown);
+ DBGOPRINT((obj_t*) pThis, "trying immediate shutdown of regular workers (if any)\n");
+ iRetLocal = wtpShutdownAll(pThis->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout);
+ if(iRetLocal == RS_RET_TIMED_OUT) {
+ DBGOPRINT((obj_t*) pThis, "immediate shutdown timed out on primary queue (this is acceptable and "
+ "triggers cancellation)\n");
+ } else if(iRetLocal != RS_RET_OK) {
+ DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the primary queue "
+ "in disk save mode. Continuing, but results are unpredictable\n", iRetLocal);
+ }
+
+ if(pThis->pqDA != NULL) {
+ /* and now the same for the DA queue */
+ DBGOPRINT((obj_t*) pThis, "trying immediate shutdown of DA queue workers\n");
+ iRetLocal = wtpShutdownAll(pThis->pqDA->pWtpReg, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout);
+ if(iRetLocal == RS_RET_TIMED_OUT) {
+ DBGOPRINT((obj_t*) pThis, "immediate shutdown timed out on DA queue (this is acceptable "
+ "and triggers cancellation)\n");
+ } else if(iRetLocal != RS_RET_OK) {
+ DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d after trying immediate shutdown of the DA "
+ "queue in disk save mode. Continuing, but results are unpredictable\n", iRetLocal);
+ }
+
+ /* and now we need to terminate the DA worker itself. We always grant it a 100ms timeout,
+ * which should be sufficient and usually not be required (it is expected to have finished
+ * long before while we were processing the queue timeout in shutdown phase 1).
+ * rgerhards, 2009-10-14
+ */
+ timeoutComp(&tTimeout, 100);
+ DBGOPRINT((obj_t*) pThis, "trying regular shutdown of main queue DA worker pool\n");
+ iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN_IMMEDIATE, &tTimeout);
+ if(iRetLocal == RS_RET_TIMED_OUT) {
+ DBGOPRINT((obj_t*) pThis, "shutdown timed out on main queue DA worker pool "
+ "(this is not good, but probably OK)\n");
+ } else {
+ DBGOPRINT((obj_t*) pThis, "main queue DA worker pool shut down.\n");
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* This function cancels all remaining regular workers for both the main and the DA
+ * queue.
+ * rgerhards, 2009-05-29
+ */
+static rsRetVal
+cancelWorkers(qqueue_t *pThis)
+{
+ rsRetVal iRetLocal;
+ DEFiRet;
+
+ /* Now queue workers should have terminated. If not, we need to cancel them as we have applied
+ * all timeout setting. If any worker in any queue still executes, its consumer is possibly
+ * long-running and cancelling is the only way to get rid of it.
+ */
+ DBGOPRINT((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the primary queue\n");
+ iRetLocal = wtpCancelAll(pThis->pWtpReg); /* returns immediately if all threads already have terminated */
+ if(iRetLocal != RS_RET_OK) {
+ DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d trying to cancel primary queue worker "
+ "threads, continuing, but results are unpredictable\n", iRetLocal);
+ }
+
+ /* ... and now the DA queue, if it exists (should always be after the primary one) */
+ if(pThis->pqDA != NULL) {
+ DBGOPRINT((obj_t*) pThis, "checking to see if we need to cancel any worker threads of the DA queue\n");
+ iRetLocal = wtpCancelAll(pThis->pqDA->pWtpReg); /* returns immediately if all threads already have terminated */
+ if(iRetLocal != RS_RET_OK) {
+ DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d trying to cancel DA queue worker "
+ "threads, continuing, but results are unpredictable\n", iRetLocal);
+ }
+
+ /* finally, we cancel the main queue's DA worker pool, if it still is running. It may be
+ * restarted later to persist the queue. But we stop it, because otherwise we get into
+ * big trouble when resetting the logical dequeue pointer. This operation can only be
+ * done when *no* worker is running. So time for a shutdown... -- rgerhards, 2009-05-28
+ */
+ DBGOPRINT((obj_t*) pThis, "checking to see if main queue DA worker pool needs to be cancelled\n");
+ wtpCancelAll(pThis->pWtpDA); /* returns immediately if all threads already have terminated */
+ }
+
+ RETiRet;
+}
+
+
+/* This function shuts down all worker threads and waits until they
+ * have terminated. If they timeout, they are cancelled.
+ * rgerhards, 2008-01-24
+ * Please note that this function shuts down BOTH the parent AND the child queue
+ * in DA case. This is necessary because their timeouts are tightly coupled. Most
+ * importantly, the timeouts would be applied twice (or logic be extremely
+ * complex) if each would have its own shutdown. The function does not self check
+ * this condition - the caller must make sure it is not called with a parent.
+ * rgerhards, 2009-05-26: we do NO longer persist the queue here if bSaveOnShutdown
+ * is set. This must be handled by the caller. Not doing that cleans up the queue
+ * shutdown considerably. Also, older engines had a potential hang condition when
+ * the DA queue was already started and the DA worker configured for infinite
+ * retries and the action was during retry processing. This was a design issue,
+ * which is solved as of now. Note that the shutdown now may take a little bit
+ * longer, because we no longer can persist the queue in parallel to waiting
+ * on worker timeouts.
+ */
+static rsRetVal
+ShutdownWorkers(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ASSERT(pThis->pqParent == NULL); /* detect invalid calling sequence */
+
+ DBGOPRINT((obj_t*) pThis, "initiating worker thread shutdown sequence\n");
+
+ CHKiRet(tryShutdownWorkersWithinQueueTimeout(pThis));
+
+ if(getPhysicalQueueSize(pThis) > 0) {
+ CHKiRet(tryShutdownWorkersWithinActionTimeout(pThis));
+ }
+
+ CHKiRet(cancelWorkers(pThis));
+
+ /* ... finally ... all worker threads have terminated :-)
+ * Well, more precisely, they *are in termination*. Some cancel cleanup handlers
+ * may still be running. Note that the main queue's DA worker may still be running.
+ */
+ DBGOPRINT((obj_t*) pThis, "worker threads terminated, remaining queue size log %d, phys %d.\n",
+ getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+
+finalize_it:
+ RETiRet;
+}
+
+/* Constructor for the queue object
+ * This constructs the data structure, but does not yet start the queue. That
+ * is done by queueStart(). The reason is that we want to give the caller a chance
+ * to modify some parameters before the queue is actually started.
+ */
+rsRetVal qqueueConstruct(qqueue_t **ppThis, queueType_t qType, int iWorkerThreads,
+ int iMaxQueueSize, rsRetVal (*pConsumer)(void*, batch_t*,int*))
+{
+ DEFiRet;
+ qqueue_t *pThis;
+
+ ASSERT(ppThis != NULL);
+ ASSERT(pConsumer != NULL);
+ ASSERT(iWorkerThreads >= 0);
+
+ CHKmalloc(pThis = (qqueue_t *)calloc(1, sizeof(qqueue_t)));
+
+ /* we have an object, so let's fill the properties */
+ objConstructSetObjInfo(pThis);
+ if((pThis->pszSpoolDir = (uchar*) strdup((char*)glbl.GetWorkDir())) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ /* set some water marks so that we have useful defaults if none are set specifically */
+ pThis->iFullDlyMrk = -1;
+ pThis->iLightDlyMrk = -1;
+ pThis->lenSpoolDir = ustrlen(pThis->pszSpoolDir);
+ pThis->iMaxFileSize = 1024 * 1024; /* default is 1 MiB */
+ pThis->iQueueSize = 0;
+ pThis->nLogDeq = 0;
+ pThis->useCryprov = 0;
+ pThis->iMaxQueueSize = iMaxQueueSize;
+ pThis->pConsumer = pConsumer;
+ pThis->iNumWorkerThreads = iWorkerThreads;
+ pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */
+ pThis->iDeqBatchSize = 8; /* conservative default, should still provide good performance */
+
+ pThis->pszFilePrefix = NULL;
+ pThis->qType = qType;
+
+
+ INIT_ATOMIC_HELPER_MUT(pThis->mutQueueSize);
+ INIT_ATOMIC_HELPER_MUT(pThis->mutLogDeq);
+
+finalize_it:
+ OBJCONSTRUCT_CHECK_SUCCESS_AND_CLEANUP
+ RETiRet;
+}
+
+
+/* set default inside queue object suitable for action queues.
+ * This shall be called directly after queue construction. This functions has
+ * been added in support of the new v6 config system. It expect properly pre-initialized
+ * objects, but we need to differentiate between ruleset main and action queues.
+ * In order to avoid unnecessary complexity, we provide the necessary defaults
+ * via specific function calls.
+ */
+void
+qqueueSetDefaultsActionQueue(qqueue_t *pThis)
+{
+ pThis->qType = QUEUETYPE_DIRECT; /* type of the main message queue above */
+ pThis->iMaxQueueSize = 1000; /* size of the main message queue above */
+ pThis->iDeqBatchSize = 128; /* default batch size */
+ pThis->iHighWtrMrk = 800; /* high water mark for disk-assisted queues */
+ pThis->iLowWtrMrk = 200; /* low water mark for disk-assisted queues */
+ pThis->iDiscardMrk = 980; /* begin to discard messages */
+ pThis->iDiscardSeverity = 8; /* turn off */
+ pThis->iNumWorkerThreads = 1; /* number of worker threads for the mm queue above */
+ pThis->iMaxFileSize = 1024*1024;
+ pThis->iPersistUpdCnt = 0; /* persist queue info every n updates */
+ pThis->bSyncQueueFiles = 0;
+ pThis->toQShutdown = 0; /* queue shutdown */
+ pThis->toActShutdown = 1000; /* action shutdown (in phase 2) */
+ pThis->toEnq = 2000; /* timeout for queue enque */
+ pThis->toWrkShutdown = 60000; /* timeout for worker thread shutdown */
+ pThis->iMinMsgsPerWrkr = 100; /* minimum messages per worker needed to start a new one */
+ pThis->bSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */
+ pThis->sizeOnDiskMax = 0; /* unlimited */
+ pThis->iDeqSlowdown = 0;
+ pThis->iDeqtWinFromHr = 0;
+ pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */
+}
+
+
+/* set defaults inside queue object suitable for main/ruleset queues.
+ * See queueSetDefaultsActionQueue() for more details and background.
+ */
+void
+qqueueSetDefaultsRulesetQueue(qqueue_t *pThis)
+{
+ pThis->qType = QUEUETYPE_FIXED_ARRAY; /* type of the main message queue above */
+ pThis->iMaxQueueSize = 50000; /* size of the main message queue above */
+ pThis->iDeqBatchSize = 1024; /* default batch size */
+ pThis->iHighWtrMrk = 45000; /* high water mark for disk-assisted queues */
+ pThis->iLowWtrMrk = 20000; /* low water mark for disk-assisted queues */
+ pThis->iDiscardMrk = 49500; /* begin to discard messages */
+ pThis->iDiscardSeverity = 8; /* turn off */
+ pThis->iNumWorkerThreads = 1; /* number of worker threads for the mm queue above */
+ pThis->iMaxFileSize = 16*1024*1024;
+ pThis->iPersistUpdCnt = 0; /* persist queue info every n updates */
+ pThis->bSyncQueueFiles = 0;
+ pThis->toQShutdown = 1500; /* queue shutdown */
+ pThis->toActShutdown = 1000; /* action shutdown (in phase 2) */
+ pThis->toEnq = 2000; /* timeout for queue enque */
+ pThis->toWrkShutdown = 60000; /* timeout for worker thread shutdown */
+ pThis->iMinMsgsPerWrkr = 1000; /* minimum messages per worker needed to start a new one */
+ pThis->bSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */
+ pThis->sizeOnDiskMax = 0; /* unlimited */
+ pThis->iDeqSlowdown = 0;
+ pThis->iDeqtWinFromHr = 0;
+ pThis->iDeqtWinToHr = 25; /* disable time-windowed dequeuing by default */
+}
+
+
+/* This function checks if the provided message shall be discarded and does so, if needed.
+ * In DA mode, we do not discard any messages as we assume the disk subsystem is fast enough to
+ * provide real-time creation of spool files.
+ * Note: cached copies of iQueueSize is provided so that no mutex locks are required.
+ * The caller must have obtained them while the mutex was locked. Of course, these values may no
+ * longer be current, but that is OK for the discard check. At worst, the message is either processed
+ * or discarded when it should not have been. As discarding is in itself somewhat racy and erratic,
+ * that is no problems for us. This function MUST NOT lock the queue mutex, it could result in
+ * deadlocks!
+ * If the message is discarded, it can no longer be processed by the caller. So be sure to check
+ * the return state!
+ * rgerhards, 2008-01-24
+ */
+static int qqueueChkDiscardMsg(qqueue_t *pThis, int iQueueSize, msg_t *pMsg)
+{
+ DEFiRet;
+ rsRetVal iRetLocal;
+ int iSeverity;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ if(pThis->iDiscardMrk > 0 && iQueueSize >= pThis->iDiscardMrk) {
+ iRetLocal = MsgGetSeverity(pMsg, &iSeverity);
+ if(iRetLocal == RS_RET_OK && iSeverity >= pThis->iDiscardSeverity) {
+ DBGOPRINT((obj_t*) pThis, "queue nearly full (%d entries), discarded severity %d message\n",
+ iQueueSize, iSeverity);
+ STATSCOUNTER_INC(pThis->ctrNFDscrd, pThis->mutCtrNFDscrd);
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_QUEUE_FULL);
+ } else {
+ DBGOPRINT((obj_t*) pThis, "queue nearly full (%d entries), but could not drop msg "
+ "(iRet: %d, severity %d)\n", iQueueSize, iRetLocal, iSeverity);
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Finally remove n elements from the queue store.
+ */
+static inline rsRetVal
+DoDeleteBatchFromQStore(qqueue_t *pThis, int nElem)
+{
+ int i;
+ off64_t bytesDel;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ /* now send delete request to storage driver */
+ if(pThis->qType == QUEUETYPE_DISK) {
+ strmMultiFileSeek(pThis->tVars.disk.pReadDel, pThis->tVars.disk.deqFileNumOut,
+ pThis->tVars.disk.deqOffs, &bytesDel);
+ /* We need to correct the on-disk file size. This time it is a bit tricky:
+ * we free disk space only upon file deletion. So we need to keep track of what we
+ * have read until we get an out-offset that is lower than the in-offset (which
+ * indicates file change). Then, we can subtract the whole thing from the on-disk
+ * size. -- rgerhards, 2008-01-30
+ */
+ if(bytesDel != 0) {
+ pThis->tVars.disk.sizeOnDisk -= bytesDel;
+ DBGOPRINT((obj_t*) pThis, "a %lld octet file has been deleted, now %lld octets disk "
+ "space used\n", bytesDel, pThis->tVars.disk.sizeOnDisk);
+ /* awake possibly waiting enq process */
+ pthread_cond_signal(&pThis->notFull); /* we hold the mutex while we are in here! */
+ }
+ } else { /* memory queue */
+ for(i = 0 ; i < nElem ; ++i) {
+ pThis->qDel(pThis);
+ }
+ }
+
+ /* iQueueSize is not decremented by qDel(), so we need to do it ourselves */
+ ATOMIC_SUB(&pThis->iQueueSize, nElem, &pThis->mutQueueSize);
+ ATOMIC_SUB(&pThis->nLogDeq, nElem, &pThis->mutLogDeq);
+ DBGPRINTF("delete batch from store, new sizes: log %d, phys %d\n",
+ getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+ ++pThis->deqIDDel; /* one more batch dequeued */
+
+ RETiRet;
+}
+
+
+/* remove messages from the physical queue store that are fully processed. This is
+ * controlled via the to-delete list.
+ */
+static inline rsRetVal
+DeleteBatchFromQStore(qqueue_t *pThis, batch_t *pBatch)
+{
+ toDeleteLst_t *pTdl;
+ qDeqID deqIDDel;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ assert(pBatch != NULL);
+
+ pTdl = tdlPeek(pThis); /* get current head element */
+ if(pTdl == NULL) { /* to-delete list empty */
+ DoDeleteBatchFromQStore(pThis, pBatch->nElem);
+ } else if(pBatch->deqID == pThis->deqIDDel) {
+ deqIDDel = pThis->deqIDDel;
+ pTdl = tdlPeek(pThis);
+ while(pTdl != NULL && deqIDDel == pTdl->deqID) {
+ DoDeleteBatchFromQStore(pThis, pTdl->nElemDeq);
+ tdlPop(pThis);
+ ++deqIDDel;
+ pTdl = tdlPeek(pThis);
+ }
+ /* old entries deleted, now delete current ones... */
+ DoDeleteBatchFromQStore(pThis, pBatch->nElem);
+ } else {
+ /* can not delete, insert into to-delete list */
+ DBGPRINTF("not at head of to-delete list, enqueue %d\n", (int) pBatch->deqID);
+ CHKiRet(tdlAdd(pThis, pBatch->deqID, pBatch->nElem));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Delete a batch of processed user objects from the queue, which includes
+ * destructing the objects themself. Any entries not marked as finally
+ * processed are enqueued again. The new enqueue is necessary because we have a
+ * rgerhards, 2009-05-13
+ */
+static inline rsRetVal
+DeleteProcessedBatch(qqueue_t *pThis, batch_t *pBatch)
+{
+ int i;
+ msg_t *pMsg;
+ int nEnqueued = 0;
+ rsRetVal localRet;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ assert(pBatch != NULL);
+
+ for(i = 0 ; i < pBatch->nElem ; ++i) {
+ pMsg = pBatch->pElem[i].pMsg;
+ if( pBatch->eltState[i] == BATCH_STATE_RDY
+ || pBatch->eltState[i] == BATCH_STATE_SUB) {
+ localRet = doEnqSingleObj(pThis, eFLOWCTL_NO_DELAY, MsgAddRef(pMsg));
+ ++nEnqueued;
+ if(localRet != RS_RET_OK) {
+ DBGPRINTF("error %d re-enqueuing unprocessed data element - discarded\n", localRet);
+ }
+ }
+ msgDestruct(&pMsg);
+ }
+
+ DBGPRINTF("we deleted %d objects and enqueued %d objects\n", i-nEnqueued, nEnqueued);
+
+ if(nEnqueued > 0)
+ qqueueChkPersist(pThis, nEnqueued);
+
+ iRet = DeleteBatchFromQStore(pThis, pBatch);
+
+ pBatch->nElem = pBatch->nElemDeq = 0; /* reset batch */ // TODO: more fine init, new fields! 2010-06-14
+
+ RETiRet;
+}
+
+
+/* dequeue as many user pointers as are available, until we hit the configured
+ * upper limit of pointers. Note that this function also deletes all processed
+ * objects from the previous batch. However, it is perfectly valid that the
+ * previous batch contained NO objects at all. For example, this happens
+ * immediately after system startup or when a queue was exhausted and the queue
+ * worker needed to wait for new data.
+ * This must only be called when the queue mutex is LOOKED, otherwise serious
+ * malfunction will happen.
+ */
+static inline rsRetVal
+DequeueConsumableElements(qqueue_t *pThis, wti_t *pWti, int *piRemainingQueueSize)
+{
+ int nDequeued;
+ int nDiscarded;
+ int nDeleted;
+ int iQueueSize;
+ msg_t *pMsg;
+ rsRetVal localRet;
+ DEFiRet;
+
+ nDeleted = pWti->batch.nElemDeq;
+ DeleteProcessedBatch(pThis, &pWti->batch);
+
+ nDequeued = nDiscarded = 0;
+ if(pThis->qType == QUEUETYPE_DISK) {
+ pThis->tVars.disk.deqFileNumIn = strmGetCurrFileNum(pThis->tVars.disk.pReadDeq);
+ }
+ while((iQueueSize = getLogicalQueueSize(pThis)) > 0 && nDequeued < pThis->iDeqBatchSize) {
+ CHKiRet(qqueueDeq(pThis, &pMsg));
+
+ /* check if we should discard this element */
+ localRet = qqueueChkDiscardMsg(pThis, pThis->iQueueSize, pMsg);
+ if(localRet == RS_RET_QUEUE_FULL) {
+ ++nDiscarded;
+ continue;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+
+ /* all well, use this element */
+ pWti->batch.pElem[nDequeued].pMsg = pMsg;
+ pWti->batch.eltState[nDequeued] = BATCH_STATE_RDY;
+ ++nDequeued;
+ }
+
+ if(pThis->qType == QUEUETYPE_DISK) {
+ strm.GetCurrOffset(pThis->tVars.disk.pReadDeq, &pThis->tVars.disk.deqOffs);
+ pThis->tVars.disk.deqFileNumOut = strmGetCurrFileNum(pThis->tVars.disk.pReadDeq);
+ }
+
+ /* it is sufficient to persist only when the bulk of work is done */
+ qqueueChkPersist(pThis, nDequeued+nDiscarded+nDeleted);
+
+ pWti->batch.nElem = nDequeued;
+ pWti->batch.nElemDeq = nDequeued + nDiscarded;
+ pWti->batch.deqID = getNextDeqID(pThis);
+ *piRemainingQueueSize = iQueueSize;
+finalize_it:
+ RETiRet;
+}
+
+
+/* dequeue the queued object for the queue consumers.
+ * rgerhards, 2008-10-21
+ * I made a radical change - we now dequeue multiple elements, and store these objects in
+ * an array of user pointers. We expect that this increases performance.
+ * rgerhards, 2009-04-22
+ */
+static rsRetVal
+DequeueConsumable(qqueue_t *pThis, wti_t *pWti)
+{
+ DEFiRet;
+ int iQueueSize = 0; /* keep the compiler happy... */
+
+ /* dequeue element batch (still protected from mutex) */
+ iRet = DequeueConsumableElements(pThis, pWti, &iQueueSize);
+
+ /* awake some flow-controlled sources if we can do this right now */
+ /* TODO: this could be done better from a performance point of view -- do it only if
+ * we have someone waiting for the condition (or only when we hit the watermark right
+ * on the nail [exact value]) -- rgerhards, 2008-03-14
+ * now that we dequeue batches of pointers, this is much less an issue...
+ * rgerhards, 2009-04-22
+ */
+ if(iQueueSize < pThis->iFullDlyMrk / 2 || glbl.GetGlobalInputTermState() == 1) {
+ pthread_cond_broadcast(&pThis->belowFullDlyWtrMrk);
+ }
+
+ if(iQueueSize < pThis->iLightDlyMrk / 2) {
+ pthread_cond_broadcast(&pThis->belowLightDlyWtrMrk);
+ }
+
+ pthread_cond_signal(&pThis->notFull);
+ /* WE ARE NO LONGER PROTECTED BY THE MUTEX */
+
+ if(iRet != RS_RET_OK && iRet != RS_RET_DISCARDMSG) {
+ DBGOPRINT((obj_t*) pThis, "error %d dequeueing element - ignoring, but strange things "
+ "may happen\n", iRet);
+ }
+
+ RETiRet;
+}
+
+
+/* The rate limiter
+ *
+ * Here we may wait if a dequeue time window is defined or if we are
+ * rate-limited. TODO: If we do so, we should also look into the
+ * way new worker threads are spawned. Obviously, it doesn't make much
+ * sense to spawn additional worker threads when none of them can do any
+ * processing. However, it is deemed acceptable to allow this for an initial
+ * implementation of the timeframe/rate limiting feature.
+ * Please also note that these feature could also be implemented at the action
+ * level. However, that would limit them to be used together with actions. We have
+ * taken the broader approach, moving it right into the queue. This is even
+ * necessary if we want to prevent spawning of multiple unnecessary worker
+ * threads as described above. -- rgerhards, 2008-04-02
+ *
+ *
+ * time window: tCurr is current time; tFrom is start time, tTo is end time (in mil 24h format).
+ * We may have tFrom = 4, tTo = 10 --> run from 4 to 10 hrs. nice and happy
+ * we may also have tFrom= 22, tTo = 4 -> run from 10pm to 4am, which is actually two
+ * windows: 0-4; 22-23:59
+ * so when to run? Let's assume we have 3am
+ *
+ * if(tTo < tFrom) {
+ * if(tCurr < tTo [3 < 4] || tCurr > tFrom [3 > 22])
+ * do work
+ * else
+ * sleep for tFrom - tCurr "hours" [22 - 5 --> 17]
+ * } else {
+ * if(tCurr >= tFrom [3 >= 4] && tCurr < tTo [3 < 10])
+ * do work
+ * else
+ * sleep for tTo - tCurr "hours" [4 - 3 --> 1]
+ * }
+ *
+ * Bottom line: we need to check which type of window we have and need to adjust our
+ * logic accordingly. Of course, sleep calculations need to be done up to the minute,
+ * but you get the idea from the code above.
+ */
+static rsRetVal
+RateLimiter(qqueue_t *pThis)
+{
+ DEFiRet;
+ int iDelay;
+ int iHrCurr;
+ time_t tCurr;
+ struct tm m;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ iDelay = 0;
+ if(pThis->iDeqtWinToHr != 25) { /* 25 means disabled */
+ /* time calls are expensive, so only do them when needed */
+ datetime.GetTime(&tCurr);
+ localtime_r(&tCurr, &m);
+ iHrCurr = m.tm_hour;
+
+ if(pThis->iDeqtWinToHr < pThis->iDeqtWinFromHr) {
+ if(iHrCurr < pThis->iDeqtWinToHr || iHrCurr > pThis->iDeqtWinFromHr) {
+ ; /* do not delay */
+ } else {
+ iDelay = (pThis->iDeqtWinFromHr - iHrCurr) * 3600;
+ /* this time, we are already into the next hour, so we need
+ * to subtract our current minute and seconds.
+ */
+ iDelay -= m.tm_min * 60;
+ iDelay -= m.tm_sec;
+ }
+ } else {
+ if(iHrCurr >= pThis->iDeqtWinFromHr && iHrCurr < pThis->iDeqtWinToHr) {
+ ; /* do not delay */
+ } else {
+ if(iHrCurr < pThis->iDeqtWinFromHr) {
+ iDelay = (pThis->iDeqtWinFromHr - iHrCurr - 1) * 3600; /* -1 as we are already in the hour */
+ iDelay += (60 - m.tm_min) * 60;
+ iDelay += 60 - m.tm_sec;
+ } else {
+ iDelay = (24 - iHrCurr + pThis->iDeqtWinFromHr) * 3600;
+ /* this time, we are already into the next hour, so we need
+ * to subtract our current minute and seconds.
+ */
+ iDelay -= m.tm_min * 60;
+ iDelay -= m.tm_sec;
+ }
+ }
+ }
+ }
+
+ if(iDelay > 0) {
+ DBGOPRINT((obj_t*) pThis, "outside dequeue time window, delaying %d seconds\n", iDelay);
+ srSleep(iDelay, 0);
+ }
+
+ RETiRet;
+}
+
+
+/* This dequeues the next batch. Note that this function must not be
+ * cancelled, else it will leave back an inconsistent state.
+ * rgerhards, 2009-05-20
+ */
+static inline rsRetVal
+DequeueForConsumer(qqueue_t *pThis, wti_t *pWti)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pWti, wti);
+
+ CHKiRet(DequeueConsumable(pThis, pWti));
+
+ if(pWti->batch.nElem == 0)
+ ABORT_FINALIZE(RS_RET_IDLE);
+
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This is called when a batch is processed and the worker does not
+ * ask for another batch (e.g. because it is to be terminated)
+ * Note that we must not be terminated while we delete a processed
+ * batch. Otherwise, we may not complete it, and then the cancel
+ * handler also tries to delete the batch. But then it finds some of
+ * the messages already destructed. This was a bug we have seen, especially
+ * with disk mode, where a delete takes rather long. Anyhow, the coneptual
+ * problem exists in all queue modes.
+ * rgerhards, 2009-05-27
+ */
+static rsRetVal
+batchProcessed(qqueue_t *pThis, wti_t *pWti)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pWti, wti);
+
+ int iCancelStateSave;
+ /* at this spot, we must not be cancelled */
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ DeleteProcessedBatch(pThis, &pWti->batch);
+ qqueueChkPersist(pThis, pWti->batch.nElemDeq);
+ pthread_setcancelstate(iCancelStateSave, NULL);
+
+ RETiRet;
+}
+
+
+/* This is the queue consumer in the regular (non-DA) case. It is
+ * protected by the queue mutex, but MUST release it as soon as possible.
+ * rgerhards, 2008-01-21
+ */
+static rsRetVal
+ConsumerReg(qqueue_t *pThis, wti_t *pWti)
+{
+ int iCancelStateSave;
+ int bNeedReLock = 0; /**< do we need to lock the mutex again? */
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pWti, wti);
+
+ iRet = DequeueForConsumer(pThis, pWti);
+ if(iRet == RS_RET_FILE_NOT_FOUND) {
+ /* This is a fatal condition and means the queue is almost unusable */
+ d_pthread_mutex_unlock(pThis->mut);
+ DBGOPRINT((obj_t*) pThis, "got 'file not found' error %d, queue defunct\n", iRet);
+ iRet = queueSwitchToEmergencyMode(pThis, iRet);
+ // TODO: think about what to return as iRet -- keep RS_RET_FILE_NOT_FOUND?
+ d_pthread_mutex_lock(pThis->mut);
+ }
+ if (iRet != RS_RET_OK) {
+ FINALIZE;
+ }
+
+ /* we now have a non-idle batch of work, so we can release the queue mutex and process it */
+ d_pthread_mutex_unlock(pThis->mut);
+ bNeedReLock = 1;
+
+ /* at this spot, we may be cancelled */
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &iCancelStateSave);
+
+
+ CHKiRet(pThis->pConsumer(pThis->pAction, &pWti->batch, &pThis->bShutdownImmediate));
+
+ /* we now need to check if we should deliberately delay processing a bit
+ * and, if so, do that. -- rgerhards, 2008-01-30
+ */
+//TODO: MULTIQUEUE: the following setting is no longer correct - need to think about how to do that...
+ if(pThis->iDeqSlowdown) {
+ DBGOPRINT((obj_t*) pThis, "sleeping %d microseconds as requested by config params\n",
+ pThis->iDeqSlowdown);
+ srSleep(pThis->iDeqSlowdown / 1000000, pThis->iDeqSlowdown % 1000000);
+ }
+
+ /* but now cancellation is no longer permitted */
+ pthread_setcancelstate(iCancelStateSave, NULL);
+
+finalize_it:
+ DBGPRINTF("regular consumer finished, iret=%d, szlog %d sz phys %d\n", iRet,
+ getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+
+ /* now we are done, but potentially need to re-aquire the mutex */
+ if(bNeedReLock)
+ d_pthread_mutex_lock(pThis->mut);
+
+ RETiRet;
+}
+
+
+/* This is a special consumer to feed the disk-queue in disk-assisted mode.
+ * When active, our own queue more or less acts as a memory buffer to the disk.
+ * So this consumer just needs to drain the memory queue and submit entries
+ * to the disk queue. The disk queue will then call the actual consumer from
+ * the app point of view (we chain two queues here).
+ * When this method is entered, the mutex is always locked and needs to be unlocked
+ * as part of the processing.
+ * rgerhards, 2008-01-14
+ */
+static rsRetVal
+ConsumerDA(qqueue_t *pThis, wti_t *pWti)
+{
+ int i;
+ int iCancelStateSave;
+ int bNeedReLock = 0; /**< do we need to lock the mutex again? */
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pWti, wti);
+
+ CHKiRet(DequeueForConsumer(pThis, pWti));
+
+ /* we now have a non-idle batch of work, so we can release the queue mutex and process it */
+ d_pthread_mutex_unlock(pThis->mut);
+ bNeedReLock = 1;
+
+ /* at this spot, we may be cancelled */
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &iCancelStateSave);
+
+ /* iterate over returned results and enqueue them in DA queue */
+ for(i = 0 ; i < pWti->batch.nElem && !pThis->bShutdownImmediate ; i++) {
+ CHKiRet(qqueueEnqMsg(pThis->pqDA, eFLOWCTL_NO_DELAY,
+ MsgAddRef(pWti->batch.pElem[i].pMsg)));
+ pWti->batch.eltState[i] = BATCH_STATE_COMM; /* commited to other queue! */
+ }
+
+ /* but now cancellation is no longer permitted */
+ pthread_setcancelstate(iCancelStateSave, NULL);
+
+finalize_it:
+ /* now we are done, but potentially need to re-aquire the mutex */
+ if(bNeedReLock)
+ d_pthread_mutex_lock(pThis->mut);
+ DBGOPRINT((obj_t*) pThis, "DAConsumer returns with iRet %d\n", iRet);
+ RETiRet;
+}
+
+
+/* must only be called when the queue mutex is locked, else results
+ * are not stable!
+ */
+static rsRetVal
+qqueueChkStopWrkrDA(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ /*DBGPRINTF("XXXX: chkStopWrkrDA called, low watermark %d, log Size %d, phys Size %d, bEnqOnly %d\n",
+ pThis->iLowWtrMrk, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis), pThis->bEnqOnly);*/
+ if(pThis->bEnqOnly) {
+ iRet = RS_RET_TERMINATE_WHEN_IDLE;
+ }
+ if(getPhysicalQueueSize(pThis) <= pThis->iLowWtrMrk) {
+ iRet = RS_RET_TERMINATE_NOW;
+ }
+
+ RETiRet;
+}
+
+
+/* must only be called when the queue mutex is locked, else results
+ * are not stable!
+ * If we are a child, we have done our duty when the queue is empty. In that case,
+ * we can terminate. Version for the regular worker thread.
+ */
+static rsRetVal
+ChkStopWrkrReg(qqueue_t *pThis)
+{
+ DEFiRet;
+ /*DBGPRINTF("XXXX: chkStopWrkrReg called, low watermark %d, log Size %d, phys Size %d, bEnqOnly %d\n",
+ pThis->iLowWtrMrk, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis), pThis->bEnqOnly);*/
+ if(pThis->bEnqOnly) {
+ iRet = RS_RET_TERMINATE_NOW;
+ } else if(pThis->pqParent != NULL) {
+ iRet = RS_RET_TERMINATE_WHEN_IDLE;
+ }
+
+ RETiRet;
+}
+
+
+/* return the configured "deq max at once" interval
+ * rgerhards, 2009-04-22
+ */
+static rsRetVal
+GetDeqBatchSize(qqueue_t *pThis, int *pVal)
+{
+ DEFiRet;
+ assert(pVal != NULL);
+ *pVal = pThis->iDeqBatchSize;
+if(pThis->pqParent != NULL) // TODO: check why we actually do this!
+ *pVal = 16;
+ RETiRet;
+}
+
+
+/* start up the queue - it must have been constructed and parameters defined
+ * before.
+ */
+rsRetVal
+qqueueStart(qqueue_t *pThis) /* this is the ConstructionFinalizer */
+{
+ DEFiRet;
+ uchar pszBuf[64];
+ uchar pszQIFNam[MAXFNAME];
+ int wrk;
+ uchar *qName;
+ size_t lenBuf;
+
+ ASSERT(pThis != NULL);
+
+ /* set type-specific handlers and other very type-specific things
+ * (we can not totally hide it...)
+ */
+ switch(pThis->qType) {
+ case QUEUETYPE_FIXED_ARRAY:
+ pThis->qConstruct = qConstructFixedArray;
+ pThis->qDestruct = qDestructFixedArray;
+ pThis->qAdd = qAddFixedArray;
+ pThis->qDeq = qDeqFixedArray;
+ pThis->qDel = qDelFixedArray;
+ pThis->MultiEnq = qqueueMultiEnqObjNonDirect;
+ break;
+ case QUEUETYPE_LINKEDLIST:
+ pThis->qConstruct = qConstructLinkedList;
+ pThis->qDestruct = qDestructLinkedList;
+ pThis->qAdd = qAddLinkedList;
+ pThis->qDeq = qDeqLinkedList;
+ pThis->qDel = qDelLinkedList;
+ pThis->MultiEnq = qqueueMultiEnqObjNonDirect;
+ break;
+ case QUEUETYPE_DISK:
+ pThis->qConstruct = qConstructDisk;
+ pThis->qDestruct = qDestructDisk;
+ pThis->qAdd = qAddDisk;
+ pThis->qDeq = qDeqDisk;
+ pThis->qDel = NULL; /* delete for disk handled via special code! */
+ pThis->MultiEnq = qqueueMultiEnqObjNonDirect;
+ /* special handling */
+ pThis->iNumWorkerThreads = 1; /* we need exactly one worker */
+ /* pre-construct file name for .qi file */
+ pThis->lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam) / sizeof(uchar),
+ "%s/%s.qi", (char*) glbl.GetWorkDir(), (char*)pThis->pszFilePrefix);
+ pThis->pszQIFNam = ustrdup(pszQIFNam);
+ DBGOPRINT((obj_t*) pThis, ".qi file name is '%s', len %d\n", pThis->pszQIFNam,
+ (int) pThis->lenQIFNam);
+ break;
+ case QUEUETYPE_DIRECT:
+ pThis->qConstruct = qConstructDirect;
+ pThis->qDestruct = qDestructDirect;
+ pThis->qAdd = qAddDirect;
+ pThis->qDel = qDelDirect;
+ pThis->MultiEnq = qqueueMultiEnqObjDirect;
+ break;
+ }
+
+ if(pThis->iFullDlyMrk == -1)
+ pThis->iFullDlyMrk = pThis->iMaxQueueSize
+ - (pThis->iMaxQueueSize / 100) * 3; /* default 97% */
+ if(pThis->iLightDlyMrk == -1)
+ pThis->iLightDlyMrk = pThis->iMaxQueueSize
+ - (pThis->iMaxQueueSize / 100) * 30; /* default 70% */
+
+ /* we need to do a quick check if our water marks are set plausible. If not,
+ * we correct the most important shortcomings. TODO: do that!!!! -- rgerhards, 2008-03-14
+ */
+
+ /* finalize some initializations that could not yet be done because it is
+ * influenced by properties which might have been set after queueConstruct ()
+ */
+ if(pThis->pqParent == NULL) {
+ pThis->mut = (pthread_mutex_t *) MALLOC (sizeof (pthread_mutex_t));
+ pthread_mutex_init(pThis->mut, NULL);
+ } else {
+ /* child queue, we need to use parent's mutex */
+ DBGOPRINT((obj_t*) pThis, "I am a child\n");
+ pThis->mut = pThis->pqParent->mut;
+ }
+
+ pthread_mutex_init(&pThis->mutThrdMgmt, NULL);
+ pthread_cond_init (&pThis->notFull, NULL);
+ pthread_cond_init (&pThis->notEmpty, NULL);
+ pthread_cond_init (&pThis->belowFullDlyWtrMrk, NULL);
+ pthread_cond_init (&pThis->belowLightDlyWtrMrk, NULL);
+
+ /* call type-specific constructor */
+ CHKiRet(pThis->qConstruct(pThis)); /* this also sets bIsDA */
+
+ /* re-adjust some params if required */
+ if(pThis->bIsDA) {
+ /* if we are in DA mode, we must make sure full delayable messages do not
+ * initiate going to disk!
+ */
+ wrk = pThis->iHighWtrMrk - (pThis->iHighWtrMrk / 100) * 50; /* 50% of high water mark */
+ if(wrk < pThis->iFullDlyMrk)
+ pThis->iFullDlyMrk = wrk;
+ }
+
+ DBGOPRINT((obj_t*) pThis, "type %d, enq-only %d, disk assisted %d, maxFileSz %lld, lqsize %d, pqsize %d, child %d, "
+ "full delay %d, light delay %d, deq batch size %d starting\n",
+ pThis->qType, pThis->bEnqOnly, pThis->bIsDA, pThis->iMaxFileSize,
+ getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis),
+ pThis->pqParent == NULL ? 0 : 1, pThis->iFullDlyMrk, pThis->iLightDlyMrk,
+ pThis->iDeqBatchSize);
+
+ pThis->bQueueStarted = 1;
+ if(pThis->qType == QUEUETYPE_DIRECT)
+ FINALIZE; /* with direct queues, we are already finished... */
+
+ /* create worker thread pools for regular and DA operation.
+ */
+ lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s:Reg", obj.GetName((obj_t*) pThis));
+ CHKiRet(wtpConstruct (&pThis->pWtpReg));
+ CHKiRet(wtpSetDbgHdr (pThis->pWtpReg, pszBuf, lenBuf));
+ CHKiRet(wtpSetpfRateLimiter (pThis->pWtpReg, (rsRetVal (*)(void *pUsr)) RateLimiter));
+ CHKiRet(wtpSetpfChkStopWrkr (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int)) ChkStopWrkrReg));
+ CHKiRet(wtpSetpfGetDeqBatchSize (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, int*)) GetDeqBatchSize));
+ CHKiRet(wtpSetpfDoWork (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, void *pWti)) ConsumerReg));
+ CHKiRet(wtpSetpfObjProcessed (pThis->pWtpReg, (rsRetVal (*)(void *pUsr, wti_t *pWti)) batchProcessed));
+ CHKiRet(wtpSetpmutUsr (pThis->pWtpReg, pThis->mut));
+ CHKiRet(wtpSetpcondBusy (pThis->pWtpReg, &pThis->notEmpty));
+ CHKiRet(wtpSetiNumWorkerThreads (pThis->pWtpReg, pThis->iNumWorkerThreads));
+ CHKiRet(wtpSettoWrkShutdown (pThis->pWtpReg, pThis->toWrkShutdown));
+ CHKiRet(wtpSetpUsr (pThis->pWtpReg, pThis));
+ CHKiRet(wtpConstructFinalize (pThis->pWtpReg));
+
+ /* set up DA system if we have a disk-assisted queue */
+ if(pThis->bIsDA)
+ InitDA(pThis, LOCK_MUTEX); /* initiate DA mode */
+
+ DBGOPRINT((obj_t*) pThis, "queue finished initialization\n");
+
+ /* if the queue already contains data, we need to start the correct number of worker threads. This can be
+ * the case when a disk queue has been loaded. If we did not start it here, it would never start.
+ */
+ qqueueAdviseMaxWorkers(pThis);
+
+ /* support statistics gathering */
+ qName = obj.GetName((obj_t*)pThis);
+ CHKiRet(statsobj.Construct(&pThis->statsobj));
+ CHKiRet(statsobj.SetName(pThis->statsobj, qName));
+ /* we need to save the queue size, as the stats module initializes it to 0! */
+ /* iQueueSize is a dual-use counter: no init, no mutex! */
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("size"),
+ ctrType_Int, &pThis->iQueueSize));
+
+ STATSCOUNTER_INIT(pThis->ctrEnqueued, pThis->mutCtrEnqueued);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("enqueued"),
+ ctrType_IntCtr, &pThis->ctrEnqueued));
+
+ STATSCOUNTER_INIT(pThis->ctrFull, pThis->mutCtrFull);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("full"),
+ ctrType_IntCtr, &pThis->ctrFull));
+
+ STATSCOUNTER_INIT(pThis->ctrFDscrd, pThis->mutCtrFDscrd);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("discarded.full"),
+ ctrType_IntCtr, &pThis->ctrFDscrd));
+ STATSCOUNTER_INIT(pThis->ctrNFDscrd, pThis->mutCtrNFDscrd);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("discarded.nf"),
+ ctrType_IntCtr, &pThis->ctrNFDscrd));
+
+ pThis->ctrMaxqsize = 0; /* no mutex needed, thus no init call */
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("maxqsize"),
+ ctrType_Int, &pThis->ctrMaxqsize));
+
+ CHKiRet(statsobj.ConstructFinalize(pThis->statsobj));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* persist the queue to disk (write the .qi file). If we have something to persist, we first
+ * save the information on the queue properties itself and then we call
+ * the queue-type specific drivers.
+ * Variable bIsCheckpoint is set to 1 if the persist is for a checkpoint,
+ * and 0 otherwise.
+ * rgerhards, 2008-01-10
+ */
+static rsRetVal qqueuePersist(qqueue_t *pThis, int bIsCheckpoint)
+{
+ DEFiRet;
+ strm_t *psQIF = NULL; /* Queue Info File */
+
+ ASSERT(pThis != NULL);
+
+ if(pThis->qType != QUEUETYPE_DISK) {
+ if(getPhysicalQueueSize(pThis) > 0) {
+ /* This error code is OK, but we will probably not implement this any time
+ * The reason is that persistence happens via DA queues. But I would like to
+ * leave the code as is, as we so have a hook in case we need one.
+ * -- rgerhards, 2008-01-28
+ */
+ ABORT_FINALIZE(RS_RET_NOT_IMPLEMENTED);
+ } else
+ FINALIZE; /* if the queue is empty, we are happy and done... */
+ }
+
+ DBGOPRINT((obj_t*) pThis, "persisting queue to disk, %d entries...\n", getPhysicalQueueSize(pThis));
+
+ if((bIsCheckpoint != QUEUE_CHECKPOINT) && (getPhysicalQueueSize(pThis) == 0)) {
+ if(pThis->bNeedDelQIF) {
+ unlink((char*)pThis->pszQIFNam);
+ pThis->bNeedDelQIF = 0;
+ }
+ /* indicate spool file needs to be deleted */
+ if(pThis->tVars.disk.pReadDel != NULL) /* may be NULL if we had a startup failure! */
+ CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDel, 1));
+ FINALIZE; /* nothing left to do, so be happy */
+ }
+
+ CHKiRet(strm.Construct(&psQIF));
+ CHKiRet(strm.SettOperationsMode(psQIF, STREAMMODE_WRITE_TRUNC));
+ CHKiRet(strm.SetbSync(psQIF, pThis->bSyncQueueFiles));
+ CHKiRet(strm.SetsType(psQIF, STREAMTYPE_FILE_SINGLE));
+ CHKiRet(strm.SetFName(psQIF, pThis->pszQIFNam, pThis->lenQIFNam));
+ CHKiRet(strm.ConstructFinalize(psQIF));
+
+ /* first, write the property bag for ourselfs
+ * And, surprisingly enough, we currently need to persist only the size of the
+ * queue. All the rest is re-created with then-current config parameters when the
+ * queue is re-created. Well, we'll also save the current queue type, just so that
+ * we know when somebody has changed the queue type... -- rgerhards, 2008-01-11
+ */
+ CHKiRet(obj.BeginSerializePropBag(psQIF, (obj_t*) pThis));
+ objSerializeSCALAR(psQIF, iQueueSize, INT);
+ objSerializeSCALAR(psQIF, tVars.disk.sizeOnDisk, INT64);
+ CHKiRet(obj.EndSerialize(psQIF));
+
+ /* now persist the stream info */
+ if(pThis->tVars.disk.pWrite != NULL)
+ CHKiRet(strm.Serialize(pThis->tVars.disk.pWrite, psQIF));
+ if(pThis->tVars.disk.pReadDel != NULL)
+ CHKiRet(strm.Serialize(pThis->tVars.disk.pReadDel, psQIF));
+
+ /* tell the input file object that it must not delete the file on close if the queue
+ * is non-empty - but only if we are not during a simple checkpoint
+ */
+ if(bIsCheckpoint != QUEUE_CHECKPOINT
+ && pThis->tVars.disk.pReadDel != NULL) {
+ CHKiRet(strm.SetbDeleteOnClose(pThis->tVars.disk.pReadDel, 0));
+ }
+
+ /* we have persisted the queue object. So whenever it comes to an empty queue,
+ * we need to delete the QIF. Thus, we indicte that need.
+ */
+ pThis->bNeedDelQIF = 1;
+
+finalize_it:
+ if(psQIF != NULL)
+ strm.Destruct(&psQIF);
+
+ RETiRet;
+}
+
+
+/* check if we need to persist the current queue info. If an
+ * error occurs, this should be ignored by caller (but we still
+ * abide to our regular call interface)...
+ * rgerhards, 2008-01-13
+ * nUpdates is the number of updates since the last call to this function.
+ * It may be > 1 due to batches. -- rgerhards, 2009-05-12
+ */
+static rsRetVal qqueueChkPersist(qqueue_t *pThis, int nUpdates)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ assert(nUpdates >= 0);
+
+ if(nUpdates == 0)
+ FINALIZE;
+
+ pThis->iUpdsSincePersist += nUpdates;
+ if(pThis->iPersistUpdCnt && pThis->iUpdsSincePersist >= pThis->iPersistUpdCnt) {
+ qqueuePersist(pThis, QUEUE_CHECKPOINT);
+ pThis->iUpdsSincePersist = 0;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* persist a queue with all data elements to disk - this is used to handle
+ * bSaveOnShutdown. We utilize the DA worker to do this. This must only
+ * be called after all workers have been shut down and if bSaveOnShutdown
+ * is actually set. Note that this function may potentially run long,
+ * depending on the queue configuration (e.g. store on remote machine).
+ * rgerhards, 2009-05-26
+ */
+static inline rsRetVal
+DoSaveOnShutdown(qqueue_t *pThis)
+{
+ struct timespec tTimeout;
+ rsRetVal iRetLocal;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ /* we reduce the low water mark, otherwise the DA worker would terminate when
+ * it is reached.
+ */
+ DBGOPRINT((obj_t*) pThis, "bSaveOnShutdown set, restarting DA worker...\n");
+ pThis->bShutdownImmediate = 0; /* would termiante the DA worker! */
+ pThis->iLowWtrMrk = 0;
+ wtpSetState(pThis->pWtpDA, wtpState_SHUTDOWN); /* shutdown worker (only) when done (was _IMMEDIATE!) */
+ wtpAdviseMaxWorkers(pThis->pWtpDA, 1); /* restart DA worker */
+
+ DBGOPRINT((obj_t*) pThis, "waiting for DA worker to terminate...\n");
+ timeoutComp(&tTimeout, QUEUE_TIMEOUT_ETERNAL);
+ /* and run the primary queue's DA worker to drain the queue */
+ iRetLocal = wtpShutdownAll(pThis->pWtpDA, wtpState_SHUTDOWN, &tTimeout);
+ DBGOPRINT((obj_t*) pThis, "end queue persistence run, iRet %d, queue size log %d, phys %d\n",
+ iRetLocal, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+ if(iRetLocal != RS_RET_OK) {
+ DBGOPRINT((obj_t*) pThis, "unexpected iRet state %d after trying to shut down primary queue in disk save mode, "
+ "continuing, but results are unpredictable\n", iRetLocal);
+ }
+
+ RETiRet;
+}
+
+
+/* destructor for the queue object */
+BEGINobjDestruct(qqueue) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(qqueue)
+ if(pThis->bQueueStarted) {
+ /* shut down all workers
+ * We do not need to shutdown workers when we are in enqueue-only mode or we are a
+ * direct queue - because in both cases we have none... ;)
+ * with a child! -- rgerhards, 2008-01-28
+ */
+ if(pThis->qType != QUEUETYPE_DIRECT && !pThis->bEnqOnly && pThis->pqParent == NULL
+ && pThis->pWtpReg != NULL)
+ ShutdownWorkers(pThis);
+
+ if(pThis->bIsDA && getPhysicalQueueSize(pThis) > 0 && pThis->bSaveOnShutdown) {
+ CHKiRet(DoSaveOnShutdown(pThis));
+ }
+
+ /* finally destruct our (regular) worker thread pool
+ * Note: currently pWtpReg is never NULL, but if we optimize our logic, this may happen,
+ * e.g. when they are not created in enqueue-only mode. We already check the condition
+ * as this may otherwise be very hard to find once we optimize (and have long forgotten
+ * about this condition here ;)
+ * rgerhards, 2008-01-25
+ */
+ if(pThis->qType != QUEUETYPE_DIRECT && pThis->pWtpReg != NULL) {
+ wtpDestruct(&pThis->pWtpReg);
+ }
+
+ /* Now check if we actually have a DA queue and, if so, destruct it.
+ * Note that the wtp must be destructed first, it may be in cancel cleanup handler
+ * *right now* and actually *need* to access the queue object to persist some final
+ * data (re-queueing case). So we need to destruct the wtp first, which will make
+ * sure all workers have terminated. Please note that this also generates a situation
+ * where it is possible that the DA queue has a parent pointer but the parent has
+ * no WtpDA associated with it - which is perfectly legal thanks to this code here.
+ */
+ if(pThis->pWtpDA != NULL) {
+ wtpDestruct(&pThis->pWtpDA);
+ }
+ if(pThis->pqDA != NULL) {
+ qqueueDestruct(&pThis->pqDA);
+ }
+
+ /* persist the queue (we always do that - queuePersits() does cleanup if the queue is empty)
+ * This handler is most important for disk queues, it will finally persist the necessary
+ * on-disk structures. In theory, other queueing modes may implement their other (non-DA)
+ * methods of persisting a queue between runs, but in practice all of this is done via
+ * disk queues and DA mode. Anyhow, it doesn't hurt to know that we could extend it here
+ * if need arises (what I doubt...) -- rgerhards, 2008-01-25
+ */
+ CHKiRet_Hdlr(qqueuePersist(pThis, QUEUE_NO_CHECKPOINT)) {
+ DBGOPRINT((obj_t*) pThis, "error %d persisting queue - data lost!\n", iRet);
+ }
+
+ /* finally, clean up some simple things... */
+ if(pThis->pqParent == NULL) {
+ /* if we are not a child, we allocated our own mutex, which we now need to destroy */
+ pthread_mutex_destroy(pThis->mut);
+ free(pThis->mut);
+ }
+ pthread_mutex_destroy(&pThis->mutThrdMgmt);
+ pthread_cond_destroy(&pThis->notFull);
+ pthread_cond_destroy(&pThis->notEmpty);
+ pthread_cond_destroy(&pThis->belowFullDlyWtrMrk);
+ pthread_cond_destroy(&pThis->belowLightDlyWtrMrk);
+
+ DESTROY_ATOMIC_HELPER_MUT(pThis->mutQueueSize);
+ DESTROY_ATOMIC_HELPER_MUT(pThis->mutLogDeq);
+
+ /* type-specific destructor */
+ iRet = pThis->qDestruct(pThis);
+ }
+
+ free(pThis->pszFilePrefix);
+ free(pThis->pszSpoolDir);
+ if(pThis->useCryprov) {
+ pThis->cryprov.Destruct(&pThis->cryprovData);
+ obj.ReleaseObj(__FILE__, pThis->cryprovNameFull+2, pThis->cryprovNameFull,
+ (void*) &pThis->cryprov);
+ free(pThis->cryprovName);
+ free(pThis->cryprovNameFull);
+ }
+
+ /* some queues do not provide stats and thus have no statsobj! */
+ if(pThis->statsobj != NULL)
+ statsobj.Destruct(&pThis->statsobj);
+ENDobjDestruct(qqueue)
+
+
+/* set the queue's file prefix
+ * The passed-in string is duplicated. So if the caller does not need
+ * it any longer, it must free it.
+ * rgerhards, 2008-01-09
+ */
+rsRetVal
+qqueueSetFilePrefix(qqueue_t *pThis, uchar *pszPrefix, size_t iLenPrefix)
+{
+ DEFiRet;
+
+ free(pThis->pszFilePrefix);
+ pThis->pszFilePrefix = NULL;
+
+ if(pszPrefix == NULL) /* just unset the prefix! */
+ ABORT_FINALIZE(RS_RET_OK);
+
+ if((pThis->pszFilePrefix = MALLOC(sizeof(uchar) * iLenPrefix + 1)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ memcpy(pThis->pszFilePrefix, pszPrefix, iLenPrefix + 1);
+ pThis->lenFilePrefix = iLenPrefix;
+
+finalize_it:
+ RETiRet;
+}
+
+/* set the queue's maximum file size
+ * rgerhards, 2008-01-09
+ */
+rsRetVal
+qqueueSetMaxFileSize(qqueue_t *pThis, size_t iMaxFileSize)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ if(iMaxFileSize < 1024) {
+ ABORT_FINALIZE(RS_RET_VALUE_TOO_LOW);
+ }
+
+ pThis->iMaxFileSize = iMaxFileSize;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* enqueue a single data object.
+ * Note that the queue mutex MUST already be locked when this function is called.
+ * rgerhards, 2009-06-16
+ */
+static inline rsRetVal
+doEnqSingleObj(qqueue_t *pThis, flowControl_t flowCtlType, msg_t *pMsg)
+{
+ DEFiRet;
+ int err;
+ struct timespec t;
+
+ STATSCOUNTER_INC(pThis->ctrEnqueued, pThis->mutCtrEnqueued);
+ /* first check if we need to discard this message (which will cause CHKiRet() to exit)
+ */
+ CHKiRet(qqueueChkDiscardMsg(pThis, pThis->iQueueSize, pMsg));
+
+ /* handle flow control
+ * There are two different flow control mechanisms: basic and advanced flow control.
+ * Basic flow control has always been implemented and protects the queue structures
+ * in that it makes sure no more data is enqueued than the queue is configured to
+ * support. Enhanced flow control is being added today. There are some sources which
+ * can easily be stopped, e.g. a file reader. This is the case because it is unlikely
+ * that blocking those sources will have negative effects (after all, the file is
+ * continued to be written). Other sources can somewhat be blocked (e.g. the kernel
+ * log reader or the local log stream reader): in general, nothing is lost if messages
+ * from these sources are not picked up immediately. HOWEVER, they can not block for
+ * an extended period of time, as this either causes message loss or - even worse - some
+ * other bad effects (e.g. unresponsive system in respect to the main system log socket).
+ * Finally, there are some (few) sources which can not be blocked at all. UDP syslog is
+ * a prime example. If a UDP message is not received, it is simply lost. So we can't
+ * do anything against UDP sockets that come in too fast. The core idea of advanced
+ * flow control is that we take into account the different natures of the sources and
+ * select flow control mechanisms that fit these needs. This also means, in the end
+ * result, that non-blockable sources like UDP syslog receive priority in the system.
+ * It's a side effect, but a good one ;) -- rgerhards, 2008-03-14
+ */
+ if(flowCtlType == eFLOWCTL_FULL_DELAY) {
+ while(pThis->iQueueSize >= pThis->iFullDlyMrk&& ! glbl.GetGlobalInputTermState()) {
+ /* We have a problem during shutdown if we block eternally. In that
+ * case, the the input thread cannot be terminated. So we wake up
+ * from time to time to check for termination.
+ * TODO/v6(at earliest): check if we could signal the condition during
+ * shutdown. However, this requires new queue registries and thus is
+ * far to much change for a stable version (and I am still not sure it
+ * is worth the effort, given how seldom this situation occurs and how
+ * few resources the wakeups need). -- rgerhards, 2012-05-03
+ * In any case, this was the old code (if we do the TODO):
+ * pthread_cond_wait(&pThis->belowFullDlyWtrMrk, pThis->mut);
+ */
+ DBGOPRINT((obj_t*) pThis, "enqueueMsg: FullDelay mark reached for full delayable message "
+ "- blocking, queue size is %d.\n", pThis->iQueueSize);
+ timeoutComp(&t, 1000);
+ err = pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t);
+ if(err != 0 && err != ETIMEDOUT) {
+ /* Something is really wrong now. Report to debug log and abort the
+ * wait. That keeps us running, even though we may lose messages.
+ */
+ DBGOPRINT((obj_t*) pThis, "potential program bug: pthread_cond_timedwait()"
+ "/fulldelay returned %d\n", err);
+ break;
+
+ }
+ DBGPRINTF("wti worker in full delay timed out, checking termination...\n");
+ }
+ } else if(flowCtlType == eFLOWCTL_LIGHT_DELAY && !glbl.GetGlobalInputTermState()) {
+ if(pThis->iQueueSize >= pThis->iLightDlyMrk) {
+ DBGOPRINT((obj_t*) pThis, "enqueueMsg: LightDelay mark reached for light "
+ "delayable message - blocking a bit.\n");
+ timeoutComp(&t, 1000); /* 1000 millisconds = 1 second TODO: make configurable */
+ err = pthread_cond_timedwait(&pThis->belowLightDlyWtrMrk, pThis->mut, &t);
+ if(err != 0 && err != ETIMEDOUT) {
+ /* Something is really wrong now. Report to debug log */
+ DBGOPRINT((obj_t*) pThis, "potential program bug: pthread_cond_timedwait()"
+ "/lightdelay returned %d\n", err);
+
+ }
+ }
+ }
+
+ /* from our regular flow control settings, we are now ready to enqueue the object.
+ * However, we now need to do a check if the queue permits to add more data. If that
+ * is not the case, basic flow control enters the field, which means we wait for
+ * the queue to become ready or drop the new message. -- rgerhards, 2008-03-14
+ */
+ while( (pThis->iMaxQueueSize > 0 && pThis->iQueueSize >= pThis->iMaxQueueSize)
+ || (pThis->qType == QUEUETYPE_DISK && pThis->sizeOnDiskMax != 0
+ && pThis->tVars.disk.sizeOnDisk > pThis->sizeOnDiskMax)) {
+ STATSCOUNTER_INC(pThis->ctrFull, pThis->mutCtrFull);
+ if(pThis->toEnq == 0 || pThis->bEnqOnly) {
+ DBGOPRINT((obj_t*) pThis, "enqueueMsg: queue FULL - configured for immediate discarding.\n");
+ STATSCOUNTER_INC(pThis->ctrFDscrd, pThis->mutCtrFDscrd);
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_QUEUE_FULL);
+ } else {
+ DBGOPRINT((obj_t*) pThis, "enqueueMsg: queue FULL - waiting %dms to drain.\n", pThis->toEnq);
+ if(glbl.GetGlobalInputTermState()) {
+ DBGOPRINT((obj_t*) pThis, "enqueueMsg: queue FULL, discard due to FORCE_TERM.\n");
+ ABORT_FINALIZE(RS_RET_FORCE_TERM);
+ }
+ timeoutComp(&t, pThis->toEnq);
+ if(pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t) != 0) {
+ DBGOPRINT((obj_t*) pThis, "enqueueMsg: cond timeout, dropping message!\n");
+ STATSCOUNTER_INC(pThis->ctrFDscrd, pThis->mutCtrFDscrd);
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_QUEUE_FULL);
+ }
+ dbgoprint((obj_t*) pThis, "enqueueMsg: wait solved queue full condition, enqueing\n");
+ }
+ }
+
+ /* and finally enqueue the message */
+ CHKiRet(qqueueAdd(pThis, pMsg));
+ STATSCOUNTER_SETMAX_NOMUT(pThis->ctrMaxqsize, pThis->iQueueSize);
+
+finalize_it:
+ RETiRet;
+}
+
+/* ------------------------------ multi-enqueue functions ------------------------------ */
+/* enqueue multiple user data elements at once. The aim is to provide a faster interface
+ * for object submission. Uses the multi_submit_t helper object.
+ * Please note that this function is not cancel-safe and consequently
+ * sets the calling thread's cancelibility state to PTHREAD_CANCEL_DISABLE
+ * during its execution. If that is not done, race conditions occur if the
+ * thread is canceled (most important use case is input module termination).
+ * rgerhards, 2009-06-16
+ * Note: there now exists multiple different functions implementing specially
+ * optimized algorithms for different config cases. -- rgerhards, 2010-06-09
+ */
+/* now the function for all modes but direct */
+static rsRetVal
+qqueueMultiEnqObjNonDirect(qqueue_t *pThis, multi_submit_t *pMultiSub)
+{
+ int iCancelStateSave;
+ int i;
+ rsRetVal localRet;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ assert(pMultiSub != NULL);
+
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ d_pthread_mutex_lock(pThis->mut);
+ for(i = 0 ; i < pMultiSub->nElem ; ++i) {
+ localRet = doEnqSingleObj(pThis, pMultiSub->ppMsgs[i]->flowCtlType, (void*)pMultiSub->ppMsgs[i]);
+ if(localRet != RS_RET_OK && localRet != RS_RET_QUEUE_FULL)
+ ABORT_FINALIZE(localRet);
+ }
+ qqueueChkPersist(pThis, pMultiSub->nElem);
+
+finalize_it:
+ /* make sure at least one worker is running. */
+ qqueueAdviseMaxWorkers(pThis);
+ /* and release the mutex */
+ d_pthread_mutex_unlock(pThis->mut);
+ pthread_setcancelstate(iCancelStateSave, NULL);
+ DBGOPRINT((obj_t*) pThis, "MultiEnqObj advised worker start\n");
+
+ RETiRet;
+}
+
+/* now, the same function, but for direct mode */
+static rsRetVal
+qqueueMultiEnqObjDirect(qqueue_t *pThis, multi_submit_t *pMultiSub)
+{
+ int i;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ assert(pMultiSub != NULL);
+
+ for(i = 0 ; i < pMultiSub->nElem ; ++i) {
+ CHKiRet(qAddDirect(pThis, (void*)pMultiSub->ppMsgs[i]));
+ }
+
+finalize_it:
+ RETiRet;
+}
+/* ------------------------------ END multi-enqueue functions ------------------------------ */
+
+
+/* enqueue a new user data element in direct mode
+ * NOTE/TODO: This is a TESTER/EXPERIEMENTAL, to be changed to better
+ * code later on (like multi submit!) 2010-06-10
+ * Enqueues the new element and awakes worker thread.
+ */
+rsRetVal
+qqueueEnqMsgDirect(qqueue_t *pThis, msg_t *pMsg)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ iRet = qAddDirect(pThis, pMsg);
+ RETiRet;
+}
+
+
+/* enqueue a new user data element
+ * Enqueues the new element and awakes worker thread.
+ */
+rsRetVal
+qqueueEnqMsg(qqueue_t *pThis, flowControl_t flowCtlType, msg_t *pMsg)
+{
+ DEFiRet;
+ int iCancelStateSave;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ if(pThis->qType != QUEUETYPE_DIRECT) {
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ d_pthread_mutex_lock(pThis->mut);
+ }
+
+ CHKiRet(doEnqSingleObj(pThis, flowCtlType, pMsg));
+
+ qqueueChkPersist(pThis, 1);
+
+finalize_it:
+ if(pThis->qType != QUEUETYPE_DIRECT) {
+ /* make sure at least one worker is running. */
+ qqueueAdviseMaxWorkers(pThis);
+ /* and release the mutex */
+ d_pthread_mutex_unlock(pThis->mut);
+ pthread_setcancelstate(iCancelStateSave, NULL);
+ DBGOPRINT((obj_t*) pThis, "EnqueueMsg advised worker start\n");
+ }
+
+ RETiRet;
+}
+
+
+/* are any queue params set at all? 1 - yes, 0 - no
+ * We need to evaluate the param block for this function, which is somewhat
+ * inefficient. HOWEVER, this is only done during config load, so we really
+ * don't care... -- rgerhards, 2013-05-10
+ */
+int
+queueCnfParamsSet(struct nvlst *lst)
+{
+ int r;
+ struct cnfparamvals *pvals;
+
+ pvals = nvlstGetParams(lst, &pblk, NULL);
+ r = cnfparamvalsIsSet(&pblk, pvals);
+ cnfparamvalsDestruct(pvals, &pblk);
+ return r;
+}
+
+
+static inline rsRetVal
+initCryprov(qqueue_t *pThis, struct nvlst *lst)
+{
+ uchar szDrvrName[1024];
+ DEFiRet;
+
+ if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmcry_%s", pThis->cryprovName)
+ == sizeof(szDrvrName)) {
+ errmsg.LogError(0, RS_RET_ERR, "queue: crypto provider "
+ "name is too long: '%s' - encryption disabled",
+ pThis->cryprovName);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ pThis->cryprovNameFull = ustrdup(szDrvrName);
+
+ pThis->cryprov.ifVersion = cryprovCURR_IF_VERSION;
+ /* The pDrvrName+2 below is a hack to obtain the object name. It
+ * safes us to have yet another variable with the name without "lm" in
+ * front of it. If we change the module load interface, we may re-think
+ * about this hack, but for the time being it is efficient and clean enough.
+ */
+ if(obj.UseObj(__FILE__, szDrvrName, szDrvrName, (void*) &pThis->cryprov)
+ != RS_RET_OK) {
+ errmsg.LogError(0, RS_RET_LOAD_ERROR, "queue: could not load "
+ "crypto provider '%s' - encryption disabled",
+ szDrvrName);
+ ABORT_FINALIZE(RS_RET_CRYPROV_ERR);
+ }
+
+ if(pThis->cryprov.Construct(&pThis->cryprovData) != RS_RET_OK) {
+ errmsg.LogError(0, RS_RET_CRYPROV_ERR, "queue: error constructing "
+ "crypto provider %s dataset - encryption disabled",
+ szDrvrName);
+ ABORT_FINALIZE(RS_RET_CRYPROV_ERR);
+ }
+ CHKiRet(pThis->cryprov.SetCnfParam(pThis->cryprovData, lst, CRYPROV_PARAMTYPE_DISK));
+
+ dbgprintf("loaded crypto provider %s, data instance at %p\n",
+ szDrvrName, pThis->cryprovData);
+ pThis->useCryprov = 1;
+finalize_it:
+ RETiRet;
+}
+
+/* apply all params from param block to queue. Must be called before
+ * finalizing. This supports the v6 config system. Defaults were already
+ * set during queue creation. The pvals object is destructed by this
+ * function.
+ */
+rsRetVal
+qqueueApplyCnfParam(qqueue_t *pThis, struct nvlst *lst)
+{
+ int i;
+ struct cnfparamvals *pvals;
+
+ pvals = nvlstGetParams(lst, &pblk, NULL);
+ if(Debug) {
+ dbgprintf("queue param blk:\n");
+ cnfparamsPrint(&pblk, pvals);
+ }
+ for(i = 0 ; i < pblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(pblk.descr[i].name, "queue.filename")) {
+ pThis->pszFilePrefix = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ pThis->lenFilePrefix = es_strlen(pvals[i].val.d.estr);
+ } else if(!strcmp(pblk.descr[i].name, "queue.cry.provider")) {
+ pThis->cryprovName = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(pblk.descr[i].name, "queue.size")) {
+ pThis->iMaxQueueSize = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.dequeuebatchsize")) {
+ pThis->iDeqBatchSize = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.maxdiskspace")) {
+ pThis->iMaxFileSize = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.highwatermark")) {
+ pThis->iHighWtrMrk = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.lowwatermark")) {
+ pThis->iLowWtrMrk = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.fulldelaymark")) {
+ pThis->iFullDlyMrk = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.lightdelaymark")) {
+ pThis->iLightDlyMrk = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.discardmark")) {
+ pThis->iDiscardMrk = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.discardseverity")) {
+ pThis->iDiscardSeverity = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.checkpointinterval")) {
+ pThis->iPersistUpdCnt = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.syncqueuefiles")) {
+ pThis->bSyncQueueFiles = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.type")) {
+ pThis->qType = (queueType_t) pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.workerthreads")) {
+ pThis->iNumWorkerThreads = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.timeoutshutdown")) {
+ pThis->toQShutdown = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.timeoutactioncompletion")) {
+ pThis->toActShutdown = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.timeoutenqueue")) {
+ pThis->toEnq = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.timeoutworkerthreadshutdown")) {
+ pThis->toWrkShutdown = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.workerthreadminimummessages")) {
+ pThis->iMinMsgsPerWrkr = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.maxfilesize")) {
+ pThis->iMaxFileSize = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.saveonshutdown")) {
+ pThis->bSaveOnShutdown = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.dequeueslowdown")) {
+ pThis->iDeqSlowdown = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.dequeuetimebegin")) {
+ pThis->iDeqtWinFromHr = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queuedequeuetimend.")) {
+ pThis->iDeqtWinToHr = pvals[i].val.d.n;
+ } else {
+ DBGPRINTF("queue: program error, non-handled "
+ "param '%s'\n", pblk.descr[i].name);
+ }
+ }
+ if(pThis->qType == QUEUETYPE_DISK) {
+ if(pThis->pszFilePrefix == NULL) {
+ errmsg.LogError(0, RS_RET_QUEUE_DISK_NO_FN, "error on queue '%s', disk mode selected, but "
+ "no queue file name given; queue type changed to 'linkedList'",
+ obj.GetName((obj_t*) pThis));
+ pThis->qType = QUEUETYPE_LINKEDLIST;
+ }
+ }
+
+ if(pThis->pszFilePrefix == NULL && pThis->cryprovName != NULL) {
+ errmsg.LogError(0, RS_RET_QUEUE_CRY_DISK_ONLY, "error on queue '%s', crypto provider can "
+ "only be set for disk or disk assisted queue - ignored",
+ obj.GetName((obj_t*) pThis));
+ free(pThis->cryprovName);
+ pThis->cryprovName = NULL;
+ }
+
+ if(pThis->cryprovName != NULL) {
+ initCryprov(pThis, lst);
+ }
+
+ cnfparamvalsDestruct(pvals, &pblk);
+ return RS_RET_OK;
+}
+
+
+/* some simple object access methods */
+DEFpropSetMeth(qqueue, bSyncQueueFiles, int)
+DEFpropSetMeth(qqueue, iPersistUpdCnt, int)
+DEFpropSetMeth(qqueue, iDeqtWinFromHr, int)
+DEFpropSetMeth(qqueue, iDeqtWinToHr, int)
+DEFpropSetMeth(qqueue, toQShutdown, long)
+DEFpropSetMeth(qqueue, toActShutdown, long)
+DEFpropSetMeth(qqueue, toWrkShutdown, long)
+DEFpropSetMeth(qqueue, toEnq, long)
+DEFpropSetMeth(qqueue, iHighWtrMrk, int)
+DEFpropSetMeth(qqueue, iLowWtrMrk, int)
+DEFpropSetMeth(qqueue, iDiscardMrk, int)
+DEFpropSetMeth(qqueue, iFullDlyMrk, int)
+DEFpropSetMeth(qqueue, iDiscardSeverity, int)
+DEFpropSetMeth(qqueue, iLightDlyMrk, int)
+DEFpropSetMeth(qqueue, bIsDA, int)
+DEFpropSetMeth(qqueue, iMinMsgsPerWrkr, int)
+DEFpropSetMeth(qqueue, bSaveOnShutdown, int)
+DEFpropSetMeth(qqueue, pAction, action_t*)
+DEFpropSetMeth(qqueue, iDeqSlowdown, int)
+DEFpropSetMeth(qqueue, iDeqBatchSize, int)
+DEFpropSetMeth(qqueue, sizeOnDiskMax, int64)
+
+
+/* This function can be used as a generic way to set properties. Only the subset
+ * of properties required to read persisted property bags is supported. This
+ * functions shall only be called by the property bag reader, thus it is static.
+ * rgerhards, 2008-01-11
+ */
+#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, (uchar*) name, sizeof(name) - 1)
+static rsRetVal qqueueSetProperty(qqueue_t *pThis, var_t *pProp)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ASSERT(pProp != NULL);
+
+ if(isProp("iQueueSize")) {
+ pThis->iQueueSize = pProp->val.num;
+ } else if(isProp("tVars.disk.sizeOnDisk")) {
+ pThis->tVars.disk.sizeOnDisk = pProp->val.num;
+ } else if(isProp("qType")) {
+ if(pThis->qType != pProp->val.num)
+ ABORT_FINALIZE(RS_RET_QTYPE_MISMATCH);
+ }
+
+finalize_it:
+ RETiRet;
+}
+#undef isProp
+
+/* dummy */
+rsRetVal qqueueQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; }
+
+/* Initialize the stream class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-01-09
+ */
+BEGINObjClassInit(qqueue, 1, OBJ_IS_CORE_MODULE)
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(strm, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(statsobj, CORE_COMPONENT));
+
+ /* now set our own handlers */
+ OBJSetMethodHandler(objMethod_SETPROPERTY, qqueueSetProperty);
+ENDObjClassInit(qqueue)
+
+/* vi:set ai:
+ */
diff --git a/runtime/queue.h b/runtime/queue.h
new file mode 100644
index 00000000..844523ad
--- /dev/null
+++ b/runtime/queue.h
@@ -0,0 +1,234 @@
+/* Definition of the queue support module.
+ *
+ * Copyright 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef QUEUE_H_INCLUDED
+#define QUEUE_H_INCLUDED
+
+#include <pthread.h>
+#include "obj.h"
+#include "wtp.h"
+#include "batch.h"
+#include "stream.h"
+#include "statsobj.h"
+#include "cryprov.h"
+
+/* support for the toDelete list */
+typedef struct toDeleteLst_s toDeleteLst_t;
+struct toDeleteLst_s {
+ qDeqID deqID;
+ int nElemDeq; /* numbe of elements that were dequeued and as such must now be discarded */
+ struct toDeleteLst_s *pNext;
+};
+
+
+/* queue types */
+typedef enum {
+ QUEUETYPE_FIXED_ARRAY = 0,/* a simple queue made out of a fixed (initially malloced) array fast but memoryhog */
+ QUEUETYPE_LINKEDLIST = 1, /* linked list used as buffer, lower fixed memory overhead but slower */
+ QUEUETYPE_DISK = 2, /* disk files used as buffer */
+ QUEUETYPE_DIRECT = 3 /* no queuing happens, consumer is directly called */
+} queueType_t;
+
+/* list member definition for linked list types of queues: */
+typedef struct qLinkedList_S {
+ struct qLinkedList_S *pNext;
+ msg_t *pMsg;
+} qLinkedList_t;
+
+
+/* the queue object */
+struct queue_s {
+ BEGINobjInstance;
+ queueType_t qType;
+ int nLogDeq; /* number of elements currently logically dequeued */
+ int bShutdownImmediate; /* should all workers cease processing messages? */
+ sbool bEnqOnly; /* does queue run in enqueue-only mode (1) or not (0)? */
+ sbool bSaveOnShutdown;/* persists everthing on shutdown (if DA!)? 1-yes, 0-no */
+ sbool bQueueStarted; /* has queueStart() been called on this queue? 1-yes, 0-no */
+ int iQueueSize; /* Current number of elements in the queue */
+ int iMaxQueueSize; /* how large can the queue grow? */
+ int iNumWorkerThreads;/* number of worker threads to use */
+ int iCurNumWrkThrd;/* current number of active worker threads */
+ int iMinMsgsPerWrkr;/* minimum nbr of msgs per worker thread, if more, a new worker is started until max wrkrs */
+ wtp_t *pWtpDA;
+ wtp_t *pWtpReg;
+ action_t *pAction; /* for action queues, ptr to action object; for main queues unused */
+ int iUpdsSincePersist;/* nbr of queue updates since the last persist call */
+ int iPersistUpdCnt; /* persits queue info after this nbr of updates - 0 -> persist only on shutdown */
+ sbool bSyncQueueFiles;/* if working with files, sync them after each write? */
+ int iHighWtrMrk; /* high water mark for disk-assisted memory queues */
+ int iLowWtrMrk; /* low water mark for disk-assisted memory queues */
+ int iDiscardMrk; /* if the queue is above this mark, low-severity messages are discarded */
+ int iFullDlyMrk; /* if the queue is above this mark, FULL_DELAYable message are put on hold */
+ int iLightDlyMrk; /* if the queue is above this mark, LIGHT_DELAYable message are put on hold */
+ int iDiscardSeverity;/* messages of this severity above are discarded on too-full queue */
+ sbool bNeedDelQIF; /* does the QIF file need to be deleted when queue becomes empty? */
+ int toQShutdown; /* timeout for regular queue shutdown in ms */
+ int toActShutdown; /* timeout for long-running action shutdown in ms */
+ int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */
+ toDeleteLst_t *toDeleteLst;/* this queue's to-delete list */
+ int toEnq; /* enqueue timeout */
+ int iDeqBatchSize; /* max number of elements that shall be dequeued at once */
+ /* rate limiting settings (will be expanded) */
+ int iDeqSlowdown; /* slow down dequeue by specified nbr of microseconds */
+ /* end rate limiting */
+ /* dequeue time window settings (may also be expanded) */
+ int iDeqtWinFromHr; /* begin of dequeue time window (hour only) */
+ int iDeqtWinToHr; /* end of dequeue time window (hour only), set to 25 to disable deq window! */
+ /* note that begin and end have specific semantics. It is a big difference if we have
+ * begin 4, end 22 or begin 22, end 4. In the later case, dequeuing will run from 10p,
+ * throughout the night and stop at 4 in the morning. In the first case, it will start
+ * at 4am, run throughout the day, and stop at 10 in the evening! So far, not logic is
+ * applied to detect user configuration errors (and tell me how should we detect what
+ * the user really wanted...). -- rgerhards, 2008-04-02
+ */
+ /* end dequeue time window */
+ rsRetVal (*pConsumer)(void *,batch_t*,int*); /* user-supplied consumer function for dequeued messages */
+ /* calling interface for pConsumer: arg1 is the global user pointer from this structure, arg2 is the
+ * user pointer array that was dequeued (actual sample: for actions, arg1 is the pAction and arg2
+ * is pointer to an array of message message pointers), arg3 is a pointer to an interger which is zero
+ * during normal operations and one if the consumer must urgently shut down.
+ */
+ /* type-specific handlers (set during construction) */
+ rsRetVal (*qConstruct)(struct queue_s *pThis);
+ rsRetVal (*qDestruct)(struct queue_s *pThis);
+ rsRetVal (*qAdd)(struct queue_s *pThis, msg_t *pMsg);
+ rsRetVal (*qDeq)(struct queue_s *pThis, msg_t **ppMsg);
+ rsRetVal (*qDel)(struct queue_s *pThis);
+ /* end type-specific handler */
+ /* public entry points (set during construction, permit to set best algorithm for params selected) */
+ rsRetVal (*MultiEnq)(qqueue_t *pThis, multi_submit_t *pMultiSub);
+ /* end public entry points */
+ /* synchronization variables */
+ pthread_mutex_t mutThrdMgmt; /* mutex for the queue's thread management */
+ pthread_mutex_t *mut; /* mutex for enqueing and dequeueing messages */
+ pthread_cond_t notFull, notEmpty;
+ pthread_cond_t belowFullDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */
+ pthread_cond_t belowLightDlyWtrMrk; /* below eFLOWCTL_FULL_DELAY watermark */
+ int bThrdStateChanged; /* at least one thread state has changed if 1 */
+ /* end sync variables */
+ /* the following variables are always present, because they
+ * are not only used for the "disk" queueing mode but also for
+ * any other queueing mode if it is set to "disk assisted".
+ * rgerhards, 2008-01-09
+ */
+ uchar *pszSpoolDir;
+ size_t lenSpoolDir;
+ uchar *pszFilePrefix;
+ size_t lenFilePrefix;
+ uchar *pszQIFNam; /* full .qi file name, based on parts above */
+ size_t lenQIFNam;
+ int iNumberFiles; /* how many files make up the queue? */
+ int64 iMaxFileSize; /* max size for a single queue file */
+ int64 sizeOnDiskMax; /* maximum size on disk allowed */
+ qDeqID deqIDAdd; /* next dequeue ID to use during add to queue store */
+ qDeqID deqIDDel; /* queue store delete position */
+ int bIsDA; /* is this queue disk assisted? */
+ struct queue_s *pqDA; /* queue for disk-assisted modes */
+ struct queue_s *pqParent;/* pointer to the parent (if this is a child queue) */
+ int bDAEnqOnly; /* EnqOnly setting for DA queue */
+ /* now follow queueing mode specific data elements */
+ //union { /* different data elements based on queue type (qType) */
+ struct { /* different data elements based on queue type (qType) */
+ struct {
+ long deqhead, head, tail;
+ void** pBuf; /* the queued user data structure */
+ } farray;
+ struct {
+ qLinkedList_t *pDeqRoot;
+ qLinkedList_t *pDelRoot;
+ qLinkedList_t *pLast;
+ } linklist;
+ struct {
+ int64 sizeOnDisk; /* current amount of disk space used */
+ int64 deqOffs; /* offset after dequeue batch - used for file deleter */
+ int deqFileNumIn; /* same for the circular file numbers, mainly for */
+ int deqFileNumOut;/* deleting finished files */
+ strm_t *pWrite; /* current file to be written */
+ strm_t *pReadDeq; /* current file for dequeueing */
+ strm_t *pReadDel; /* current file for deleting */
+ } disk;
+ } tVars;
+ sbool useCryprov; /* quicker than checkig ptr (1 vs 8 bytes!) */
+ uchar *cryprovName; /* crypto provider to use */
+ cryprov_if_t cryprov; /* ptr to crypto provider interface */
+ void *cryprovData; /* opaque data ptr for provider use */
+ uchar *cryprovNameFull;/* full internal crypto provider name */
+ DEF_ATOMIC_HELPER_MUT(mutQueueSize);
+ DEF_ATOMIC_HELPER_MUT(mutLogDeq);
+ /* for statistics subsystem */
+ statsobj_t *statsobj;
+ STATSCOUNTER_DEF(ctrEnqueued, mutCtrEnqueued);
+ STATSCOUNTER_DEF(ctrFull, mutCtrFull);
+ STATSCOUNTER_DEF(ctrFDscrd, mutCtrFDscrd);
+ STATSCOUNTER_DEF(ctrNFDscrd, mutCtrNFDscrd);
+ int ctrMaxqsize; /* NOT guarded by a mutex */
+};
+
+
+/* the define below is an "eternal" timeout for the timeout settings which require a value.
+ * It is one day, which is not really eternal, but comes close to it if we think about
+ * rsyslog (e.g.: do you want to wait on shutdown for more than a day? ;))
+ * rgerhards, 2008-01-17
+ */
+#define QUEUE_TIMEOUT_ETERNAL 24 * 60 * 60 * 1000
+
+/* prototypes */
+rsRetVal qqueueDestruct(qqueue_t **ppThis);
+rsRetVal qqueueEnqMsgDirect(qqueue_t *pThis, msg_t *pMsg);
+rsRetVal qqueueEnqMsg(qqueue_t *pThis, flowControl_t flwCtlType, msg_t *pMsg);
+rsRetVal qqueueStart(qqueue_t *pThis);
+rsRetVal qqueueSetMaxFileSize(qqueue_t *pThis, size_t iMaxFileSize);
+rsRetVal qqueueSetFilePrefix(qqueue_t *pThis, uchar *pszPrefix, size_t iLenPrefix);
+rsRetVal qqueueConstruct(qqueue_t **ppThis, queueType_t qType, int iWorkerThreads,
+ int iMaxQueueSize, rsRetVal (*pConsumer)(void*,batch_t*, int*));
+rsRetVal qqueueEnqObjDirectBatch(qqueue_t *pThis, batch_t *pBatch);
+int queueCnfParamsSet(struct nvlst *lst);
+rsRetVal qqueueApplyCnfParam(qqueue_t *pThis, struct nvlst *lst);
+void qqueueSetDefaultsRulesetQueue(qqueue_t *pThis);
+void qqueueSetDefaultsActionQueue(qqueue_t *pThis);
+void qqueueDbgPrint(qqueue_t *pThis);
+
+PROTOTYPEObjClassInit(qqueue);
+PROTOTYPEpropSetMeth(qqueue, iPersistUpdCnt, int);
+PROTOTYPEpropSetMeth(qqueue, bSyncQueueFiles, int);
+PROTOTYPEpropSetMeth(qqueue, iDeqtWinFromHr, int);
+PROTOTYPEpropSetMeth(qqueue, iDeqtWinToHr, int);
+PROTOTYPEpropSetMeth(qqueue, toQShutdown, long);
+PROTOTYPEpropSetMeth(qqueue, toActShutdown, long);
+PROTOTYPEpropSetMeth(qqueue, toWrkShutdown, long);
+PROTOTYPEpropSetMeth(qqueue, toEnq, long);
+PROTOTYPEpropSetMeth(qqueue, iLightDlyMrk, int);
+PROTOTYPEpropSetMeth(qqueue, iHighWtrMrk, int);
+PROTOTYPEpropSetMeth(qqueue, iLowWtrMrk, int);
+PROTOTYPEpropSetMeth(qqueue, iDiscardMrk, int);
+PROTOTYPEpropSetMeth(qqueue, iDiscardSeverity, int);
+PROTOTYPEpropSetMeth(qqueue, iMinMsgsPerWrkr, int);
+PROTOTYPEpropSetMeth(qqueue, bSaveOnShutdown, int);
+PROTOTYPEpropSetMeth(qqueue, pAction, action_t*);
+PROTOTYPEpropSetMeth(qqueue, iDeqSlowdown, int);
+PROTOTYPEpropSetMeth(qqueue, sizeOnDiskMax, int64);
+PROTOTYPEpropSetMeth(qqueue, iDeqBatchSize, int);
+#define qqueueGetID(pThis) ((unsigned long) pThis)
+
+#endif /* #ifndef QUEUE_H_INCLUDED */
diff --git a/runtime/ratelimit.c b/runtime/ratelimit.c
new file mode 100644
index 00000000..a808e04a
--- /dev/null
+++ b/runtime/ratelimit.c
@@ -0,0 +1,385 @@
+/* ratelimit.c
+ * support for rate-limiting sources, including "last message
+ * repeated n times" processing.
+ *
+ * Copyright 2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "errmsg.h"
+#include "ratelimit.h"
+#include "datetime.h"
+#include "parser.h"
+#include "unicode-helper.h"
+#include "msg.h"
+#include "rsconf.h"
+#include "dirty.h"
+
+/* definitions for objects we access */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(parser)
+
+/* static data */
+
+/* generate a "repeated n times" message */
+static inline msg_t *
+ratelimitGenRepMsg(ratelimit_t *ratelimit)
+{
+ msg_t *repMsg;
+ size_t lenRepMsg;
+ uchar szRepMsg[1024];
+
+ if(ratelimit->nsupp == 1) { /* we simply use the original message! */
+ repMsg = MsgAddRef(ratelimit->pMsg);
+ } else {/* we need to duplicate, original message may still be in use in other
+ * parts of the system! */
+ if((repMsg = MsgDup(ratelimit->pMsg)) == NULL) {
+ DBGPRINTF("Message duplication failed, dropping repeat message.\n");
+ goto done;
+ }
+ lenRepMsg = snprintf((char*)szRepMsg, sizeof(szRepMsg),
+ " message repeated %d times: [%.800s]",
+ ratelimit->nsupp, getMSG(ratelimit->pMsg));
+ MsgReplaceMSG(repMsg, szRepMsg, lenRepMsg);
+ }
+
+done: return repMsg;
+}
+
+static inline rsRetVal
+doLastMessageRepeatedNTimes(ratelimit_t *ratelimit, msg_t *pMsg, msg_t **ppRepMsg)
+{
+ int bNeedUnlockMutex = 0;
+ rsRetVal localRet;
+ DEFiRet;
+
+ if((pMsg->msgFlags & NEEDS_PARSING) != 0) {
+ if((localRet = parser.ParseMsg(pMsg)) != RS_RET_OK) {
+ DBGPRINTF("Message discarded, parsing error %d\n", localRet);
+ ABORT_FINALIZE(RS_RET_DISCARDMSG);
+ }
+ }
+
+ if(ratelimit->bThreadSafe) {
+ pthread_mutex_lock(&ratelimit->mut);
+ bNeedUnlockMutex = 1;
+ }
+
+ if( ratelimit->pMsg != NULL &&
+ getMSGLen(pMsg) == getMSGLen(ratelimit->pMsg) &&
+ !ustrcmp(getMSG(pMsg), getMSG(ratelimit->pMsg)) &&
+ !strcmp(getHOSTNAME(pMsg), getHOSTNAME(ratelimit->pMsg)) &&
+ !strcmp(getPROCID(pMsg, LOCK_MUTEX), getPROCID(ratelimit->pMsg, LOCK_MUTEX)) &&
+ !strcmp(getAPPNAME(pMsg, LOCK_MUTEX), getAPPNAME(ratelimit->pMsg, LOCK_MUTEX))) {
+ ratelimit->nsupp++;
+ DBGPRINTF("msg repeated %d times\n", ratelimit->nsupp);
+ /* use current message, so we have the new timestamp
+ * (means we need to discard previous one) */
+ msgDestruct(&ratelimit->pMsg);
+ ratelimit->pMsg = pMsg;
+ ABORT_FINALIZE(RS_RET_DISCARDMSG);
+ } else {/* new message, do "repeat processing" & save it */
+ if(ratelimit->pMsg != NULL) {
+ if(ratelimit->nsupp > 0) {
+ *ppRepMsg = ratelimitGenRepMsg(ratelimit);
+ ratelimit->nsupp = 0;
+ }
+ msgDestruct(&ratelimit->pMsg);
+ }
+ ratelimit->pMsg = MsgAddRef(pMsg);
+ }
+
+finalize_it:
+ if(bNeedUnlockMutex)
+ pthread_mutex_unlock(&ratelimit->mut);
+ RETiRet;
+}
+
+
+/* helper: tell how many messages we lost due to linux-like ratelimiting */
+static inline void
+tellLostCnt(ratelimit_t *ratelimit)
+{
+ uchar msgbuf[1024];
+ if(ratelimit->missed) {
+ snprintf((char*)msgbuf, sizeof(msgbuf),
+ "%s: %u messages lost due to rate-limiting",
+ ratelimit->name, ratelimit->missed);
+ ratelimit->missed = 0;
+ logmsgInternal(RS_RET_RATE_LIMITED, LOG_SYSLOG|LOG_INFO, msgbuf, 0);
+ }
+}
+
+/* Linux-like ratelimiting, modelled after the linux kernel
+ * returns 1 if message is within rate limit and shall be
+ * processed, 0 otherwise.
+ * This implementation is NOT THREAD-SAFE and must not
+ * be called concurrently.
+ */
+static inline int
+withinRatelimit(ratelimit_t *ratelimit, time_t tt)
+{
+ int ret;
+ uchar msgbuf[1024];
+
+ if(ratelimit->interval == 0) {
+ ret = 1;
+ goto finalize_it;
+ }
+
+ /* we primarily need "NoTimeCache" mode for imjournal, as it
+ * sets the message generation time to the journal timestamp.
+ * As such, we do not get a proper indication of the actual
+ * message rate. To prevent this, we need to query local
+ * system time ourselvs.
+ */
+ if(ratelimit->bNoTimeCache)
+ tt = time(NULL);
+
+ assert(ratelimit->burst != 0);
+
+ if(ratelimit->begin == 0)
+ ratelimit->begin = tt;
+
+ /* resume if we go out of time window */
+ if(tt > ratelimit->begin + ratelimit->interval) {
+ ratelimit->begin = 0;
+ ratelimit->done = 0;
+ tellLostCnt(ratelimit);
+ }
+
+ /* do actual limit check */
+ if(ratelimit->burst > ratelimit->done) {
+ ratelimit->done++;
+ ret = 1;
+ } else {
+ ratelimit->missed++;
+ if(ratelimit->missed == 1) {
+ snprintf((char*)msgbuf, sizeof(msgbuf),
+ "%s: begin to drop messages due to rate-limiting",
+ ratelimit->name);
+ logmsgInternal(RS_RET_RATE_LIMITED, LOG_SYSLOG|LOG_INFO, msgbuf, 0);
+ }
+ ret = 0;
+ }
+
+finalize_it:
+ return ret;
+}
+
+
+/* ratelimit a message, that means:
+ * - handle "last message repeated n times" logic
+ * - handle actual (discarding) rate-limiting
+ * This function returns RS_RET_OK, if the caller shall process
+ * the message regularly and RS_RET_DISCARD if the caller must
+ * discard the message. The caller should also discard the message
+ * if another return status occurs. This places some burden on the
+ * caller logic, but provides best performance. Demanding this
+ * cooperative mode can enable a faulty caller to thrash up part
+ * of the system, but we accept that risk (a faulty caller can
+ * always do all sorts of evil, so...)
+ * If *ppRepMsg != NULL on return, the caller must enqueue that
+ * message before the original message.
+ */
+rsRetVal
+ratelimitMsg(ratelimit_t *ratelimit, msg_t *pMsg, msg_t **ppRepMsg)
+{
+ DEFiRet;
+
+ *ppRepMsg = NULL;
+ /* Only the messages having severity level at or below the
+ * treshold (the value is >=) are subject to ratelimiting. */
+ if(ratelimit->interval && (pMsg->iSeverity >= ratelimit->severity)) {
+ if(withinRatelimit(ratelimit, pMsg->ttGenTime) == 0) {
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_DISCARDMSG);
+ }
+ }
+ if(ratelimit->bReduceRepeatMsgs) {
+ CHKiRet(doLastMessageRepeatedNTimes(ratelimit, pMsg, ppRepMsg));
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* returns 1, if the ratelimiter performs any checks and 0 otherwise */
+int
+ratelimitChecked(ratelimit_t *ratelimit)
+{
+ return ratelimit->interval || ratelimit->bReduceRepeatMsgs;
+}
+
+
+/* add a message to a ratelimiter/multisubmit structure.
+ * ratelimiting is automatically handled according to the ratelimit
+ * settings.
+ * if pMultiSub == NULL, a single-message enqueue happens (under reconsideration)
+ */
+rsRetVal
+ratelimitAddMsg(ratelimit_t *ratelimit, multi_submit_t *pMultiSub, msg_t *pMsg)
+{
+ rsRetVal localRet;
+ msg_t *repMsg;
+ DEFiRet;
+
+ if(pMultiSub == NULL) {
+ localRet = ratelimitMsg(ratelimit, pMsg, &repMsg);
+ if(repMsg != NULL)
+ CHKiRet(submitMsg2(repMsg));
+ if(localRet == RS_RET_OK)
+ CHKiRet(submitMsg2(pMsg));
+ } else {
+ localRet = ratelimitMsg(ratelimit, pMsg, &repMsg);
+ if(repMsg != NULL) {
+ pMultiSub->ppMsgs[pMultiSub->nElem++] = repMsg;
+ if(pMultiSub->nElem == pMultiSub->maxElem)
+ CHKiRet(multiSubmitMsg2(pMultiSub));
+ }
+ if(localRet == RS_RET_OK) {
+ pMultiSub->ppMsgs[pMultiSub->nElem++] = pMsg;
+ if(pMultiSub->nElem == pMultiSub->maxElem)
+ CHKiRet(multiSubmitMsg2(pMultiSub));
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* modname must be a static name (usually expected to be the module
+ * name and MUST be present. dynname may be NULL and can be used for
+ * dynamic information, e.g. PID or listener IP, ...
+ * Both values should be kept brief.
+ */
+rsRetVal
+ratelimitNew(ratelimit_t **ppThis, char *modname, char *dynname)
+{
+ ratelimit_t *pThis;
+ char namebuf[256];
+ DEFiRet;
+
+ CHKmalloc(pThis = calloc(1, sizeof(ratelimit_t)));
+ if(modname == NULL)
+ modname ="*ERROR:MODULE NAME MISSING*";
+
+ if(dynname == NULL) {
+ pThis->name = strdup(modname);
+ } else {
+ snprintf(namebuf, sizeof(namebuf), "%s[%s]",
+ modname, dynname);
+ namebuf[sizeof(namebuf)-1] = '\0'; /* to be on safe side */
+ pThis->name = strdup(namebuf);
+ }
+ /* pThis->severity == 0 - all messages are ratelimited */
+ pThis->bReduceRepeatMsgs = loadConf->globals.bReduceRepeatMsgs;
+ *ppThis = pThis;
+finalize_it:
+ RETiRet;
+}
+
+
+/* enable linux-like ratelimiting */
+void
+ratelimitSetLinuxLike(ratelimit_t *ratelimit, unsigned short interval, unsigned short burst)
+{
+ ratelimit->interval = interval;
+ ratelimit->burst = burst;
+ ratelimit->done = 0;
+ ratelimit->missed = 0;
+ ratelimit->begin = 0;
+}
+
+
+/* enable thread-safe operations mode. This make sure that
+ * a single ratelimiter can be called from multiple threads. As
+ * this causes some overhead and is not always required, it needs
+ * to be explicitely enabled. This operation cannot be undone
+ * (think: why should one do that???)
+ */
+void
+ratelimitSetThreadSafe(ratelimit_t *ratelimit)
+{
+ ratelimit->bThreadSafe = 1;
+ pthread_mutex_init(&ratelimit->mut, NULL);
+}
+void
+ratelimitSetNoTimeCache(ratelimit_t *ratelimit)
+{
+ ratelimit->bNoTimeCache = 1;
+ pthread_mutex_init(&ratelimit->mut, NULL);
+}
+
+/* Severity level determines which messages are subject to
+ * ratelimiting. Default (no value set) is all messages.
+ */
+void
+ratelimitSetSeverity(ratelimit_t *ratelimit, intTiny severity)
+{
+ ratelimit->severity = severity;
+}
+
+void
+ratelimitDestruct(ratelimit_t *ratelimit)
+{
+ msg_t *pMsg;
+ if(ratelimit->pMsg != NULL) {
+ if(ratelimit->nsupp > 0) {
+ pMsg = ratelimitGenRepMsg(ratelimit);
+ if(pMsg != NULL)
+ submitMsg2(pMsg);
+ }
+ msgDestruct(&ratelimit->pMsg);
+ }
+ tellLostCnt(ratelimit);
+ if(ratelimit->bThreadSafe)
+ pthread_mutex_destroy(&ratelimit->mut);
+ free(ratelimit->name);
+ free(ratelimit);
+}
+
+void
+ratelimitModExit(void)
+{
+ objRelease(datetime, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(parser, CORE_COMPONENT);
+}
+
+rsRetVal
+ratelimitModInit(void)
+{
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(parser, CORE_COMPONENT));
+finalize_it:
+ RETiRet;
+}
diff --git a/runtime/ratelimit.h b/runtime/ratelimit.h
new file mode 100644
index 00000000..563777fd
--- /dev/null
+++ b/runtime/ratelimit.h
@@ -0,0 +1,55 @@
+/* header for ratelimit.c
+ *
+ * Copyright 2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_RATELIMIT_H
+#define INCLUDED_RATELIMIT_H
+
+struct ratelimit_s {
+ char *name; /**< rate limiter name, e.g. for user messages */
+ /* support for Linux kernel-type ratelimiting */
+ unsigned short interval;
+ unsigned short burst;
+ intTiny severity; /**< ratelimit only equal or lower severity levels (eq or higher values) */
+ unsigned done;
+ unsigned missed;
+ time_t begin;
+ /* support for "last message repeated n times */
+ int bReduceRepeatMsgs; /**< shall we do "last message repeated n times" processing? */
+ unsigned nsupp; /**< nbr of msgs suppressed */
+ msg_t *pMsg;
+ sbool bThreadSafe; /**< do we need to operate in Thread-Safe mode? */
+ sbool bNoTimeCache; /**< if we shall not used cached reception time */
+ pthread_mutex_t mut; /**< mutex if thread-safe operation desired */
+};
+
+/* prototypes */
+rsRetVal ratelimitNew(ratelimit_t **ppThis, char *modname, char *dynname);
+void ratelimitSetThreadSafe(ratelimit_t *ratelimit);
+void ratelimitSetLinuxLike(ratelimit_t *ratelimit, unsigned short interval, unsigned short burst);
+void ratelimitSetNoTimeCache(ratelimit_t *ratelimit);
+void ratelimitSetSeverity(ratelimit_t *ratelimit, intTiny severity);
+rsRetVal ratelimitMsg(ratelimit_t *ratelimit, msg_t *pMsg, msg_t **ppRep);
+rsRetVal ratelimitAddMsg(ratelimit_t *ratelimit, multi_submit_t *pMultiSub, msg_t *pMsg);
+void ratelimitDestruct(ratelimit_t *pThis);
+int ratelimitChecked(ratelimit_t *ratelimit);
+rsRetVal ratelimitModInit(void);
+void ratelimitModExit(void);
+
+#endif /* #ifndef INCLUDED_RATELIMIT_H */
diff --git a/runtime/regexp.c b/runtime/regexp.c
new file mode 100644
index 00000000..912db9c9
--- /dev/null
+++ b/runtime/regexp.c
@@ -0,0 +1,101 @@
+/* The regexp object.
+ *
+ * Module begun 2008-03-05 by Rainer Gerhards, based on some code
+ * from syslogd.c
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <regex.h>
+#include <string.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "regexp.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+
+
+/* ------------------------------ methods ------------------------------ */
+
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(regexp)
+CODESTARTobjQueryInterface(regexp)
+ if(pIf->ifVersion != regexpCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->regcomp = regcomp;
+ pIf->regexec = regexec;
+ pIf->regerror = regerror;
+ pIf->regfree = regfree;
+finalize_it:
+ENDobjQueryInterface(regexp)
+
+
+/* Initialize the regexp class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(regexp, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+
+ /* set our own handlers */
+ENDObjClassInit(regexp)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ CHKiRet(regexpClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ENDmodInit
+/* vi:set ai:
+ */
diff --git a/runtime/regexp.h b/runtime/regexp.h
new file mode 100644
index 00000000..16b0c4e6
--- /dev/null
+++ b/runtime/regexp.h
@@ -0,0 +1,44 @@
+/* The regexp object. It encapsulates the C regexp functionality. The primary
+ * purpose of this wrapper class is to enable rsyslogd core to be build without
+ * regexp libraries.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_REGEXP_H
+#define INCLUDED_REGEXP_H
+
+#include <regex.h>
+
+/* interfaces */
+BEGINinterface(regexp) /* name must also be changed in ENDinterface macro! */
+ int (*regcomp)(regex_t *preg, const char *regex, int cflags);
+ int (*regexec)(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags);
+ size_t (*regerror)(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size);
+ void (*regfree)(regex_t *preg);
+ENDinterface(regexp)
+#define regexpCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+PROTOTYPEObj(regexp);
+
+/* the name of our library binary */
+#define LM_REGEXP_FILENAME "lmregexp"
+
+#endif /* #ifndef INCLUDED_REGEXP_H */
diff --git a/runtime/rsconf.c b/runtime/rsconf.c
new file mode 100644
index 00000000..d8b81f1b
--- /dev/null
+++ b/runtime/rsconf.c
@@ -0,0 +1,1386 @@
+/* rsconf.c - the rsyslog configuration system.
+ *
+ * Module begun 2011-04-19 by Rainer Gerhards
+ *
+ * Copyright 2011-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <grp.h>
+#include <stdarg.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "srUtils.h"
+#include "ruleset.h"
+#include "modules.h"
+#include "conf.h"
+#include "queue.h"
+#include "rsconf.h"
+#include "cfsysline.h"
+#include "errmsg.h"
+#include "action.h"
+#include "glbl.h"
+#include "unicode-helper.h"
+#include "omshell.h"
+#include "omusrmsg.h"
+#include "omfwd.h"
+#include "omfile.h"
+#include "ompipe.h"
+#include "omdiscard.h"
+#include "pmrfc5424.h"
+#include "pmrfc3164.h"
+#include "smfile.h"
+#include "smtradfile.h"
+#include "smfwd.h"
+#include "smtradfwd.h"
+#include "parser.h"
+#include "outchannel.h"
+#include "threads.h"
+#include "datetime.h"
+#include "parserif.h"
+#include "modules.h"
+#include "dirty.h"
+#include "template.h"
+
+extern char* yytext;
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(ruleset)
+DEFobjCurrIf(module)
+DEFobjCurrIf(conf)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(parser)
+DEFobjCurrIf(datetime)
+
+/* exported static data */
+rsconf_t *runConf = NULL;/* the currently running config */
+rsconf_t *loadConf = NULL;/* the config currently being loaded (no concurrent config load supported!) */
+
+/* hardcoded standard templates (used for defaults) */
+static uchar template_DebugFormat[] = "\"Debug line with all properties:\nFROMHOST: '%FROMHOST%', fromhost-ip: '%fromhost-ip%', HOSTNAME: '%HOSTNAME%', PRI: %PRI%,\nsyslogtag '%syslogtag%', programname: '%programname%', APP-NAME: '%APP-NAME%', PROCID: '%PROCID%', MSGID: '%MSGID%',\nTIMESTAMP: '%TIMESTAMP%', STRUCTURED-DATA: '%STRUCTURED-DATA%',\nmsg: '%msg%'\nescaped msg: '%msg:::drop-cc%'\ninputname: %inputname% rawmsg: '%rawmsg%'\n\n\"";
+static uchar template_SyslogProtocol23Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n\"";
+static uchar template_TraditionalFileFormat[] = "=RSYSLOG_TraditionalFileFormat";
+static uchar template_FileFormat[] = "=RSYSLOG_FileFormat";
+static uchar template_ForwardFormat[] = "=RSYSLOG_ForwardFormat";
+static uchar template_TraditionalForwardFormat[] = "=RSYSLOG_TraditionalForwardFormat";
+static uchar template_WallFmt[] = "\"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r\"";
+static uchar template_StdUsrMsgFmt[] = "\" %syslogtag%%msg%\n\r\"";
+static uchar template_StdDBFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-mysql%', '%timegenerated:::date-mysql%', %iut%, '%syslogtag%')\",SQL";
+static uchar template_StdPgSQLFmt[] = "\"insert into SystemEvents (Message, Facility, FromHost, Priority, DeviceReportedTime, ReceivedAt, InfoUnitID, SysLogTag) values ('%msg%', %syslogfacility%, '%HOSTNAME%', %syslogpriority%, '%timereported:::date-pgsql%', '%timegenerated:::date-pgsql%', %iut%, '%syslogtag%')\",STDSQL";
+static uchar template_spoofadr[] = "\"%fromhost-ip%\"";
+static uchar template_SysklogdFileFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg%\n\"";
+static uchar template_StdJSONFmt[] = "\"{\\\"message\\\":\\\"%msg:::json%\\\",\\\"fromhost\\\":\\\"%HOSTNAME:::json%\\\",\\\"facility\\\":\\\"%syslogfacility-text%\\\",\\\"priority\\\":\\\"%syslogpriority-text%\\\",\\\"timereported\\\":\\\"%timereported:::date-rfc3339%\\\",\\\"timegenerated\\\":\\\"%timegenerated:::date-rfc3339%\\\"}\"";
+/* end templates */
+
+/* tables for interfacing with the v6 config system (as far as we need to) */
+static struct cnfparamdescr inppdescr[] = {
+ { "type", eCmdHdlrString, CNFPARAM_REQUIRED }
+};
+static struct cnfparamblk inppblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(inppdescr)/sizeof(struct cnfparamdescr),
+ inppdescr
+ };
+
+/* forward-definitions */
+void cnfDoCfsysline(char *ln);
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(rsconf) /* be sure to specify the object type also in END macro! */
+ pThis->globals.bDebugPrintTemplateList = 1;
+ pThis->globals.bDebugPrintModuleList = 0;
+ pThis->globals.bDebugPrintCfSysLineHandlerList = 0;
+ pThis->globals.bLogStatusMsgs = DFLT_bLogStatusMsgs;
+ pThis->globals.bErrMsgToStderr = 1;
+ pThis->globals.umask = -1;
+ pThis->templates.root = NULL;
+ pThis->templates.last = NULL;
+ pThis->templates.lastStatic = NULL;
+ pThis->actions.nbrActions = 0;
+ CHKiRet(llInit(&pThis->rulesets.llRulesets, rulesetDestructForLinkedList,
+ rulesetKeyDestruct, strcasecmp));
+ /* queue params */
+ pThis->globals.mainQ.iMainMsgQueueSize = 10000;
+ pThis->globals.mainQ.iMainMsgQHighWtrMark = 8000;
+ pThis->globals.mainQ.iMainMsgQLowWtrMark = 2000;
+ pThis->globals.mainQ.iMainMsgQDiscardMark = 9800;
+ pThis->globals.mainQ.iMainMsgQDiscardSeverity = 8;
+ pThis->globals.mainQ.iMainMsgQueueNumWorkers = 1;
+ pThis->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ pThis->globals.mainQ.pszMainMsgQFName = NULL;
+ pThis->globals.mainQ.iMainMsgQueMaxFileSize = 1024*1024;
+ pThis->globals.mainQ.iMainMsgQPersistUpdCnt = 0;
+ pThis->globals.mainQ.bMainMsgQSyncQeueFiles = 0;
+ pThis->globals.mainQ.iMainMsgQtoQShutdown = 1500;
+ pThis->globals.mainQ.iMainMsgQtoActShutdown = 1000;
+ pThis->globals.mainQ.iMainMsgQtoEnq = 2000;
+ pThis->globals.mainQ.iMainMsgQtoWrkShutdown = 60000;
+ pThis->globals.mainQ.iMainMsgQWrkMinMsgs = 100;
+ pThis->globals.mainQ.iMainMsgQDeqSlowdown = 0;
+ pThis->globals.mainQ.iMainMsgQueMaxDiskSpace = 0;
+ pThis->globals.mainQ.iMainMsgQueDeqBatchSize = 32;
+ pThis->globals.mainQ.bMainMsgQSaveOnShutdown = 1;
+ pThis->globals.mainQ.iMainMsgQueueDeqtWinFromHr = 0;
+ pThis->globals.mainQ.iMainMsgQueueDeqtWinToHr = 25;
+ /* end queue params */
+finalize_it:
+ENDobjConstruct(rsconf)
+
+
+/* ConstructionFinalizer
+ */
+rsRetVal rsconfConstructFinalize(rsconf_t __attribute__((unused)) *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, rsconf);
+ RETiRet;
+}
+
+
+/* call freeCnf() module entry points AND free the module entries themselfes.
+ */
+static inline void
+freeCnf(rsconf_t *pThis)
+{
+ cfgmodules_etry_t *etry, *del;
+ etry = pThis->modules.root;
+ while(etry != NULL) {
+ if(etry->pMod->beginCnfLoad != NULL) {
+ dbgprintf("calling freeCnf(%p) for module '%s'\n",
+ etry->modCnf, (char*) module.GetName(etry->pMod));
+ etry->pMod->freeCnf(etry->modCnf);
+ }
+ del = etry;
+ etry = etry->next;
+ free(del);
+ }
+}
+
+
+/* destructor for the rsconf object */
+BEGINobjDestruct(rsconf) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(rsconf)
+ freeCnf(pThis);
+ tplDeleteAll(pThis);
+ free(pThis->globals.mainQ.pszMainMsgQFName);
+ free(pThis->globals.pszConfDAGFile);
+ llDestroy(&(pThis->rulesets.llRulesets));
+ENDobjDestruct(rsconf)
+
+
+/* DebugPrint support for the rsconf object */
+BEGINobjDebugPrint(rsconf) /* be sure to specify the object type also in END and CODESTART macros! */
+ cfgmodules_etry_t *modNode;
+
+ dbgprintf("configuration object %p\n", pThis);
+ dbgprintf("Global Settings:\n");
+ dbgprintf(" bDebugPrintTemplateList.............: %d\n",
+ pThis->globals.bDebugPrintTemplateList);
+ dbgprintf(" bDebugPrintModuleList : %d\n",
+ pThis->globals.bDebugPrintModuleList);
+ dbgprintf(" bDebugPrintCfSysLineHandlerList.....: %d\n",
+ pThis->globals.bDebugPrintCfSysLineHandlerList);
+ dbgprintf(" bLogStatusMsgs : %d\n",
+ pThis->globals.bLogStatusMsgs);
+ dbgprintf(" bErrMsgToStderr.....................: %d\n",
+ pThis->globals.bErrMsgToStderr);
+ dbgprintf(" drop Msgs with malicious PTR Record : %d\n",
+ glbl.GetDropMalPTRMsgs());
+ ruleset.DebugPrintAll(pThis);
+ dbgprintf("\n");
+ if(pThis->globals.bDebugPrintTemplateList)
+ tplPrintList(pThis);
+ if(pThis->globals.bDebugPrintModuleList)
+ module.PrintList();
+ if(pThis->globals.bDebugPrintCfSysLineHandlerList)
+ dbgPrintCfSysLineHandlers();
+ // TODO: The following code needs to be "streamlined", so far just moved over...
+ dbgprintf("Main queue size %d messages.\n", pThis->globals.mainQ.iMainMsgQueueSize);
+ dbgprintf("Main queue worker threads: %d, wThread shutdown: %d, Perists every %d updates.\n",
+ pThis->globals.mainQ.iMainMsgQueueNumWorkers,
+ pThis->globals.mainQ.iMainMsgQtoWrkShutdown, pThis->globals.mainQ.iMainMsgQPersistUpdCnt);
+ dbgprintf("Main queue timeouts: shutdown: %d, action completion shutdown: %d, enq: %d\n",
+ pThis->globals.mainQ.iMainMsgQtoQShutdown,
+ pThis->globals.mainQ.iMainMsgQtoActShutdown, pThis->globals.mainQ.iMainMsgQtoEnq);
+ dbgprintf("Main queue watermarks: high: %d, low: %d, discard: %d, discard-severity: %d\n",
+ pThis->globals.mainQ.iMainMsgQHighWtrMark, pThis->globals.mainQ.iMainMsgQLowWtrMark,
+ pThis->globals.mainQ.iMainMsgQDiscardMark, pThis->globals.mainQ.iMainMsgQDiscardSeverity);
+ dbgprintf("Main queue save on shutdown %d, max disk space allowed %lld\n",
+ pThis->globals.mainQ.bMainMsgQSaveOnShutdown, pThis->globals.mainQ.iMainMsgQueMaxDiskSpace);
+ /* TODO: add
+ iActionRetryCount = 0;
+ iActionRetryInterval = 30000;
+ static int iMainMsgQtoWrkMinMsgs = 100;
+ static int iMainMsgQbSaveOnShutdown = 1;
+ iMainMsgQueMaxDiskSpace = 0;
+ setQPROP(qqueueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", 100);
+ setQPROP(qqueueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", 1);
+ */
+ dbgprintf("Work Directory: '%s'.\n", glbl.GetWorkDir());
+ ochPrintList();
+ dbgprintf("Modules used in this configuration:\n");
+ for(modNode = pThis->modules.root ; modNode != NULL ; modNode = modNode->next) {
+ dbgprintf(" %s\n", module.GetName(modNode->pMod));
+ }
+CODESTARTobjDebugPrint(rsconf)
+ENDobjDebugPrint(rsconf)
+
+
+/* This function returns the current date in different
+ * variants. It is used to construct the $NOW series of
+ * system properties. The returned buffer must be freed
+ * by the caller when no longer needed. If the function
+ * can not allocate memory, it returns a NULL pointer.
+ * TODO: this was taken from msg.c and we should consolidate it with the code
+ * there. This is especially important when we increase the number of system
+ * variables (what we definitely want to do).
+ */
+typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_MINUTE } eNOWType;
+static rsRetVal
+getNOW(eNOWType eNow, es_str_t **estr)
+{
+ DEFiRet;
+ uchar szBuf[16];
+ struct syslogTime t;
+ es_size_t len;
+
+ datetime.getCurrTime(&t, NULL);
+ switch(eNow) {
+ case NOW_NOW:
+ len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar),
+ "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day);
+ break;
+ case NOW_YEAR:
+ len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%4.4d", t.year);
+ break;
+ case NOW_MONTH:
+ len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.month);
+ break;
+ case NOW_DAY:
+ len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.day);
+ break;
+ case NOW_HOUR:
+ len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.hour);
+ break;
+ case NOW_MINUTE:
+ len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "%2.2d", t.minute);
+ break;
+ default:
+ len = snprintf((char*) szBuf, sizeof(szBuf)/sizeof(uchar), "*invld eNow*");
+ break;
+ }
+
+ /* now create a string object out of it and hand that over to the var */
+ *estr = es_newStrFromCStr((char*)szBuf, len);
+
+ RETiRet;
+}
+
+
+
+static inline es_str_t *
+getSysVar(char *name)
+{
+ es_str_t *estr = NULL;
+ rsRetVal iRet = RS_RET_OK;
+
+ if(!strcmp(name, "now")) {
+ CHKiRet(getNOW(NOW_NOW, &estr));
+ } else if(!strcmp(name, "year")) {
+ CHKiRet(getNOW(NOW_YEAR, &estr));
+ } else if(!strcmp(name, "month")) {
+ CHKiRet(getNOW(NOW_MONTH, &estr));
+ } else if(!strcmp(name, "day")) {
+ CHKiRet(getNOW(NOW_DAY, &estr));
+ } else if(!strcmp(name, "hour")) {
+ CHKiRet(getNOW(NOW_HOUR, &estr));
+ } else if(!strcmp(name, "minute")) {
+ CHKiRet(getNOW(NOW_MINUTE, &estr));
+ } else if(!strcmp(name, "myhostname")) {
+ char *hn = (char*)glbl.GetLocalHostName();
+ estr = es_newStrFromCStr(hn, strlen(hn));
+ } else {
+ ABORT_FINALIZE(RS_RET_SYSVAR_NOT_FOUND);
+ }
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ dbgprintf("getSysVar error iRet %d\n", iRet);
+ if(estr == NULL)
+ estr = es_newStrFromCStr("*ERROR*", sizeof("*ERROR*") - 1);
+ }
+ return estr;
+}
+
+
+/* Process input() objects */
+rsRetVal
+inputProcessCnf(struct cnfobj *o)
+{
+ struct cnfparamvals *pvals;
+ modInfo_t *pMod;
+ uchar *cnfModName = NULL;
+ int typeIdx;
+ DEFiRet;
+
+ pvals = nvlstGetParams(o->nvlst, &inppblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
+ }
+ DBGPRINTF("input param blk after inputProcessCnf:\n");
+ cnfparamsPrint(&inppblk, pvals);
+ typeIdx = cnfparamGetIdx(&inppblk, "type");
+ cnfModName = (uchar*)es_str2cstr(pvals[typeIdx].val.d.estr, NULL);
+ if((pMod = module.FindWithCnfName(loadConf, cnfModName, eMOD_IN)) == NULL) {
+ errmsg.LogError(0, RS_RET_MOD_UNKNOWN, "input module name '%s' is unknown", cnfModName);
+ ABORT_FINALIZE(RS_RET_MOD_UNKNOWN);
+ }
+ if(pMod->mod.im.newInpInst == NULL) {
+ errmsg.LogError(0, RS_RET_MOD_NO_INPUT_STMT,
+ "input module '%s' does not support input() statement", cnfModName);
+ ABORT_FINALIZE(RS_RET_MOD_NO_INPUT_STMT);
+ }
+ CHKiRet(pMod->mod.im.newInpInst(o->nvlst));
+finalize_it:
+ free(cnfModName);
+ cnfparamvalsDestruct(pvals, &inppblk);
+ RETiRet;
+}
+
+/*------------------------------ interface to flex/bison parser ------------------------------*/
+extern int yylineno;
+
+void
+parser_errmsg(char *fmt, ...)
+{
+ va_list ap;
+ char errBuf[1024];
+
+ va_start(ap, fmt);
+ if(vsnprintf(errBuf, sizeof(errBuf), fmt, ap) == sizeof(errBuf))
+ errBuf[sizeof(errBuf)-1] = '\0';
+ errmsg.LogError(0, RS_RET_CONF_PARSE_ERROR,
+ "error during parsing file %s, on or before line %d: %s",
+ cnfcurrfn, yylineno, errBuf);
+ va_end(ap);
+}
+
+int
+yyerror(char *s)
+{
+ parser_errmsg("%s on token '%s'", s, yytext);
+ return 0;
+}
+void cnfDoObj(struct cnfobj *o)
+{
+ int bChkUnuse = 1;
+
+ dbgprintf("cnf:global:obj: ");
+ cnfobjPrint(o);
+ switch(o->objType) {
+ case CNFOBJ_GLOBAL:
+ glblProcessCnf(o);
+ break;
+ case CNFOBJ_MODULE:
+ modulesProcessCnf(o);
+ break;
+ case CNFOBJ_INPUT:
+ inputProcessCnf(o);
+ break;
+ case CNFOBJ_TPL:
+ if(tplProcessCnf(o) != RS_RET_OK)
+ parser_errmsg("error processing template object");
+ break;
+ case CNFOBJ_RULESET:
+ rulesetProcessCnf(o);
+ break;
+ case CNFOBJ_PROPERTY:
+ case CNFOBJ_CONSTANT:
+ /* these types are processed at a later stage */
+ bChkUnuse = 0;
+ break;
+ default:
+ dbgprintf("cnfDoObj program error: unexpected object type %u\n",
+ o->objType);
+ break;
+ }
+ if(bChkUnuse)
+ nvlstChkUnused(o->nvlst);
+ cnfobjDestruct(o);
+}
+
+void cnfDoScript(struct cnfstmt *script)
+{
+ dbgprintf("cnf:global:script\n");
+ ruleset.AddScript(ruleset.GetCurrent(loadConf), script);
+}
+
+void cnfDoCfsysline(char *ln)
+{
+ DBGPRINTF("cnf:global:cfsysline: %s\n", ln);
+ /* the legacy system needs the "$" stripped */
+ conf.cfsysline((uchar*) ln+1);
+ free(ln);
+}
+
+void cnfDoBSDTag(char *ln)
+{
+ DBGPRINTF("cnf:global:BSD tag: %s\n", ln);
+ errmsg.LogError(0, RS_RET_BSD_BLOCKS_UNSUPPORTED,
+ "BSD-style blocks are no longer supported in rsyslog, "
+ "see http://www.rsyslog.com/g/BSD for details and a "
+ "solution (Block '%s')", ln);
+ free(ln);
+}
+
+void cnfDoBSDHost(char *ln)
+{
+ DBGPRINTF("cnf:global:BSD host: %s\n", ln);
+ errmsg.LogError(0, RS_RET_BSD_BLOCKS_UNSUPPORTED,
+ "BSD-style blocks are no longer supported in rsyslog, "
+ "see http://www.rsyslog.com/g/BSD for details and a "
+ "solution (Block '%s')", ln);
+ free(ln);
+}
+
+es_str_t*
+cnfGetVar(char *name, void *usrptr)
+{
+ es_str_t *estr;
+ if(name[0] == '$') {
+ if(name[1] == '$')
+ estr = getSysVar(name+2);
+ else if(name[1] == '!')
+ estr = msgGetCEEVarNew((msg_t*) usrptr, name+2);
+ else
+ estr = msgGetMsgVarNew((msg_t*) usrptr, (uchar*)name+1);
+ } else { /* if this happens, we have a program logic error */
+ estr = es_newStrFromCStr("err: var must start with $",
+ strlen("err: var must start with $"));
+ }
+ if(Debug) {
+ char *s;
+ s = es_str2cstr(estr, NULL);
+ dbgprintf("rainerscript: var '%s': '%s'\n", name, s);
+ free(s);
+ }
+ return estr;
+}
+/*------------------------------ end interface to flex/bison parser ------------------------------*/
+
+
+
+/* drop to specified group
+ * if something goes wrong, the function never returns
+ * Note that such an abort can cause damage to on-disk structures, so we should
+ * re-design the "interface" in the long term. -- rgerhards, 2008-11-26
+ */
+static void doDropPrivGid(int iGid)
+{
+ int res;
+ uchar szBuf[1024];
+
+ res = setgroups(0, NULL); /* remove all supplementary group IDs */
+ if(res) {
+ perror("could not remove supplemental group IDs");
+ exit(1);
+ }
+ DBGPRINTF("setgroups(0, NULL): %d\n", res);
+ res = setgid(iGid);
+ if(res) {
+ /* if we can not set the userid, this is fatal, so let's unconditionally abort */
+ perror("could not set requested group id");
+ exit(1);
+ }
+ DBGPRINTF("setgid(%d): %d\n", iGid, res);
+ snprintf((char*)szBuf, sizeof(szBuf)/sizeof(uchar), "rsyslogd's groupid changed to %d", iGid);
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, szBuf, 0);
+}
+
+
+/* drop to specified user
+ * if something goes wrong, the function never returns
+ * Note that such an abort can cause damage to on-disk structures, so we should
+ * re-design the "interface" in the long term. -- rgerhards, 2008-11-19
+ */
+static void doDropPrivUid(int iUid)
+{
+ int res;
+ uchar szBuf[1024];
+
+ res = setuid(iUid);
+ if(res) {
+ /* if we can not set the userid, this is fatal, so let's unconditionally abort */
+ perror("could not set requested userid");
+ exit(1);
+ }
+ DBGPRINTF("setuid(%d): %d\n", iUid, res);
+ snprintf((char*)szBuf, sizeof(szBuf)/sizeof(uchar), "rsyslogd's userid changed to %d", iUid);
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, szBuf, 0);
+}
+
+
+
+/* drop privileges. This will drop to the configured privileges, if
+ * set by the user. After this method has been executed, the previous
+ * privileges can no be re-gained.
+ */
+static inline rsRetVal
+dropPrivileges(rsconf_t *cnf)
+{
+ DEFiRet;
+
+ /* If instructed to do so, we now drop privileges. Note that this is not 100% secure,
+ * because outputs are already running at this time. However, we can implement
+ * dropping of privileges rather quickly and it will work in many cases. While it is not
+ * the ultimate solution, the current one is still much better than not being able to
+ * drop privileges at all. Doing it correctly, requires a change in architecture, which
+ * we should do over time. TODO -- rgerhards, 2008-11-19
+ */
+ if(cnf->globals.gidDropPriv != 0) {
+ doDropPrivGid(ourConf->globals.gidDropPriv);
+ DBGPRINTF("group privileges have been dropped to gid %u\n", (unsigned)
+ ourConf->globals.gidDropPriv);
+ }
+
+ if(cnf->globals.uidDropPriv != 0) {
+ doDropPrivUid(ourConf->globals.uidDropPriv);
+ DBGPRINTF("user privileges have been dropped to uid %u\n", (unsigned)
+ ourConf->globals.uidDropPriv);
+ }
+
+ RETiRet;
+}
+
+
+/* tell the rsysog core (including ourselfs) that the config load is done and
+ * we need to prepare to move over to activate mode.
+ */
+static inline void
+tellCoreConfigLoadDone(void)
+{
+ glblDoneLoadCnf();
+}
+
+
+/* Tell input modules that the config parsing stage is over. */
+static rsRetVal
+tellModulesConfigLoadDone(void)
+{
+ cfgmodules_etry_t *node;
+
+ BEGINfunc
+ DBGPRINTF("telling modules that config load for %p is done\n", loadConf);
+ node = module.GetNxtCnfType(loadConf, NULL, eMOD_ANY);
+ while(node != NULL) {
+ if(node->pMod->beginCnfLoad != NULL)
+ node->pMod->endCnfLoad(node->modCnf);
+ node = module.GetNxtCnfType(runConf, node, eMOD_IN);
+ }
+
+ ENDfunc
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+
+/* Tell input modules to verify config object */
+static rsRetVal
+tellModulesCheckConfig(void)
+{
+ cfgmodules_etry_t *node;
+ rsRetVal localRet;
+
+ BEGINfunc
+ DBGPRINTF("telling modules to check config %p\n", loadConf);
+ node = module.GetNxtCnfType(loadConf, NULL, eMOD_ANY);
+ while(node != NULL) {
+ if(node->pMod->beginCnfLoad != NULL) {
+ localRet = node->pMod->checkCnf(node->modCnf);
+ DBGPRINTF("module %s tells us config can %sbe activated\n",
+ node->pMod->pszName, (localRet == RS_RET_OK) ? "" : "NOT ");
+ if(localRet == RS_RET_OK) {
+ node->canActivate = 1;
+ } else {
+ node->canActivate = 0;
+ }
+ }
+ node = module.GetNxtCnfType(runConf, node, eMOD_IN);
+ }
+
+ ENDfunc
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+
+/* Tell modules to activate current running config (pre privilege drop) */
+static rsRetVal
+tellModulesActivateConfigPrePrivDrop(void)
+{
+ cfgmodules_etry_t *node;
+ rsRetVal localRet;
+
+ BEGINfunc
+ DBGPRINTF("telling modules to activate config (before dropping privs) %p\n", runConf);
+ node = module.GetNxtCnfType(runConf, NULL, eMOD_ANY);
+ while(node != NULL) {
+ if( node->pMod->beginCnfLoad != NULL
+ && node->pMod->activateCnfPrePrivDrop != NULL
+ && node->canActivate) {
+ DBGPRINTF("pre priv drop activating config %p for module %s\n",
+ runConf, node->pMod->pszName);
+ localRet = node->pMod->activateCnfPrePrivDrop(node->modCnf);
+ if(localRet != RS_RET_OK) {
+ errmsg.LogError(0, localRet, "activation of module %s failed",
+ node->pMod->pszName);
+ node->canActivate = 0; /* in a sense, could not activate... */
+ }
+ }
+ node = module.GetNxtCnfType(runConf, node, eMOD_IN);
+ }
+
+ ENDfunc
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+
+/* Tell modules to activate current running config */
+static rsRetVal
+tellModulesActivateConfig(void)
+{
+ cfgmodules_etry_t *node;
+ rsRetVal localRet;
+
+ BEGINfunc
+ DBGPRINTF("telling modules to activate config %p\n", runConf);
+ node = module.GetNxtCnfType(runConf, NULL, eMOD_ANY);
+ while(node != NULL) {
+ if(node->pMod->beginCnfLoad != NULL && node->canActivate) {
+ DBGPRINTF("activating config %p for module %s\n",
+ runConf, node->pMod->pszName);
+ localRet = node->pMod->activateCnf(node->modCnf);
+ if(localRet != RS_RET_OK) {
+ errmsg.LogError(0, localRet, "activation of module %s failed",
+ node->pMod->pszName);
+ node->canActivate = 0; /* in a sense, could not activate... */
+ }
+ }
+ node = module.GetNxtCnfType(runConf, node, eMOD_IN);
+ }
+
+ ENDfunc
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+
+/* Actually run the input modules. This happens after privileges are dropped,
+ * if that is requested.
+ */
+static rsRetVal
+runInputModules(void)
+{
+ cfgmodules_etry_t *node;
+ int bNeedsCancel;
+
+ BEGINfunc
+ node = module.GetNxtCnfType(runConf, NULL, eMOD_IN);
+ while(node != NULL) {
+ if(node->canRun) {
+ bNeedsCancel = (node->pMod->isCompatibleWithFeature(sFEATURENonCancelInputTermination) == RS_RET_OK) ?
+ 0 : 1;
+ DBGPRINTF("running module %s with config %p, term mode: %s\n", node->pMod->pszName, node,
+ bNeedsCancel ? "cancel" : "cooperative/SIGTTIN");
+ thrdCreate(node->pMod->mod.im.runInput, node->pMod->mod.im.afterRun, bNeedsCancel,
+ (node->pMod->cnfName == NULL) ? node->pMod->pszName : node->pMod->cnfName);
+ }
+ node = module.GetNxtCnfType(runConf, node, eMOD_IN);
+ }
+
+ ENDfunc
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+
+/* Make the modules check if they are ready to start.
+ */
+static rsRetVal
+startInputModules(void)
+{
+ DEFiRet;
+ cfgmodules_etry_t *node;
+
+ node = module.GetNxtCnfType(runConf, NULL, eMOD_IN);
+ while(node != NULL) {
+ if(node->canActivate) {
+ iRet = node->pMod->mod.im.willRun();
+ node->canRun = (iRet == RS_RET_OK);
+ if(!node->canRun) {
+ DBGPRINTF("module %s will not run, iRet %d\n", node->pMod->pszName, iRet);
+ }
+ } else {
+ node->canRun = 0;
+ }
+ node = module.GetNxtCnfType(runConf, node, eMOD_IN);
+ }
+
+ ENDfunc
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+
+/* activate the main queue */
+static inline rsRetVal
+activateMainQueue()
+{
+ DEFiRet;
+ /* create message queue */
+ CHKiRet_Hdlr(createMainQueue(&pMsgQueue, UCHAR_CONSTANT("main Q"), NULL)) {
+ /* no queue is fatal, we need to give up in that case... */
+ fprintf(stderr, "fatal error %d: could not create message queue - rsyslogd can not run!\n", iRet);
+ FINALIZE;
+ }
+
+ bHaveMainQueue = (ourConf->globals.mainQ.MainMsgQueType == QUEUETYPE_DIRECT) ? 0 : 1;
+ DBGPRINTF("Main processing queue is initialized and running\n");
+finalize_it:
+ RETiRet;
+}
+
+
+/* set the processes umask (upon configuration request) */
+static inline rsRetVal
+setUmask(int iUmask)
+{
+ if(iUmask != -1) {
+ umask(iUmask);
+ DBGPRINTF("umask set to 0%3.3o.\n", iUmask);
+ }
+
+ return RS_RET_OK;
+}
+
+
+/* Activate an already-loaded configuration. The configuration will become
+ * the new running conf (if successful). Note that in theory this method may
+ * be called when there already is a running conf. In practice, the current
+ * version of rsyslog does not support this. Future versions probably will.
+ * Begun 2011-04-20, rgerhards
+ */
+rsRetVal
+activate(rsconf_t *cnf)
+{
+ DEFiRet;
+
+ /* at this point, we "switch" over to the running conf */
+ runConf = cnf;
+# if 0 /* currently the DAG is not supported -- code missing! */
+ /* TODO: re-enable this functionality some time later! */
+ /* check if we need to generate a config DAG and, if so, do that */
+ if(ourConf->globals.pszConfDAGFile != NULL)
+ generateConfigDAG(ourConf->globals.pszConfDAGFile);
+# endif
+ setUmask(cnf->globals.umask);
+
+ /* the output part and the queue is now ready to run. So it is a good time
+ * to initialize the inputs. Please note that the net code above should be
+ * shuffled to down here once we have everything in input modules.
+ * rgerhards, 2007-12-14
+ * NOTE: as of 2009-06-29, the input modules are initialized, but not yet run.
+ * Keep in mind. though, that the outputs already run if the queue was
+ * persisted to disk. -- rgerhards
+ */
+ tellModulesActivateConfigPrePrivDrop();
+
+ CHKiRet(dropPrivileges(cnf));
+
+ tellModulesActivateConfig();
+ startInputModules();
+ CHKiRet(activateActions());
+ CHKiRet(activateMainQueue());
+ /* finally let the inputs run... */
+ runInputModules();
+
+ dbgprintf("configuration %p activated\n", cnf);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* -------------------- some legacy config handlers --------------------
+ * TODO: move to conf.c?
+ */
+
+/* legacy config system: set the action resume interval */
+static rsRetVal setActionResumeInterval(void __attribute__((unused)) *pVal, int iNewVal)
+{
+ return actionSetGlobalResumeInterval(iNewVal);
+}
+
+
+/* Switch the default ruleset (that, what servcies bind to if nothing specific
+ * is specified).
+ * rgerhards, 2009-06-12
+ */
+static rsRetVal
+setDefaultRuleset(void __attribute__((unused)) *pVal, uchar *pszName)
+{
+ DEFiRet;
+
+ CHKiRet(ruleset.SetDefaultRuleset(ourConf, pszName));
+
+finalize_it:
+ free(pszName); /* no longer needed */
+ RETiRet;
+}
+
+
+/* Switch to either an already existing rule set or start a new one. The
+ * named rule set becomes the new "current" rule set (what means that new
+ * actions are added to it).
+ * rgerhards, 2009-06-12
+ */
+static rsRetVal
+setCurrRuleset(void __attribute__((unused)) *pVal, uchar *pszName)
+{
+ ruleset_t *pRuleset;
+ rsRetVal localRet;
+ DEFiRet;
+
+ localRet = ruleset.SetCurrRuleset(ourConf, pszName);
+
+ if(localRet == RS_RET_NOT_FOUND) {
+ DBGPRINTF("begin new current rule set '%s'\n", pszName);
+ CHKiRet(ruleset.Construct(&pRuleset));
+ CHKiRet(ruleset.SetName(pRuleset, pszName));
+ CHKiRet(ruleset.ConstructFinalize(ourConf, pRuleset));
+ rulesetSetCurrRulesetPtr(pRuleset);
+ } else {
+ ABORT_FINALIZE(localRet);
+ }
+
+finalize_it:
+ free(pszName); /* no longer needed */
+ RETiRet;
+}
+
+
+/* set the main message queue mode
+ * rgerhards, 2008-01-03
+ */
+static rsRetVal setMainMsgQueType(void __attribute__((unused)) *pVal, uchar *pszType)
+{
+ DEFiRet;
+
+ if (!strcasecmp((char *) pszType, "fixedarray")) {
+ loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ DBGPRINTF("main message queue type set to FIXED_ARRAY\n");
+ } else if (!strcasecmp((char *) pszType, "linkedlist")) {
+ loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_LINKEDLIST;
+ DBGPRINTF("main message queue type set to LINKEDLIST\n");
+ } else if (!strcasecmp((char *) pszType, "disk")) {
+ loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_DISK;
+ DBGPRINTF("main message queue type set to DISK\n");
+ } else if (!strcasecmp((char *) pszType, "direct")) {
+ loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_DIRECT;
+ DBGPRINTF("main message queue type set to DIRECT (no queueing at all)\n");
+ } else {
+ errmsg.LogError(0, RS_RET_INVALID_PARAMS, "unknown mainmessagequeuetype parameter: %s", (char *) pszType);
+ iRet = RS_RET_INVALID_PARAMS;
+ }
+ free(pszType); /* no longer needed */
+
+ RETiRet;
+}
+
+
+/* -------------------- end legacy config handlers -------------------- */
+
+
+/* set the processes max number ob files (upon configuration request)
+ * 2009-04-14 rgerhards
+ */
+static rsRetVal setMaxFiles(void __attribute__((unused)) *pVal, int iFiles)
+{
+// TODO this must use a local var, then carry out action during activate!
+ struct rlimit maxFiles;
+ char errStr[1024];
+ DEFiRet;
+
+ maxFiles.rlim_cur = iFiles;
+ maxFiles.rlim_max = iFiles;
+
+ if(setrlimit(RLIMIT_NOFILE, &maxFiles) < 0) {
+ /* NOTE: under valgrind, we seem to be unable to extend the size! */
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ errmsg.LogError(0, RS_RET_ERR_RLIM_NOFILE, "could not set process file limit to %d: %s [kernel max %ld]",
+ iFiles, errStr, (long) maxFiles.rlim_max);
+ ABORT_FINALIZE(RS_RET_ERR_RLIM_NOFILE);
+ }
+#ifdef USE_UNLIMITED_SELECT
+ glbl.SetFdSetSize(howmany(iFiles, __NFDBITS) * sizeof (fd_mask));
+#endif
+ DBGPRINTF("Max number of files set to %d [kernel max %ld].\n", iFiles, (long) maxFiles.rlim_max);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* legacy config system: reset config variables to default values. */
+static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ loadConf->globals.bLogStatusMsgs = DFLT_bLogStatusMsgs;
+ loadConf->globals.bDebugPrintTemplateList = 1;
+ loadConf->globals.bDebugPrintCfSysLineHandlerList = 1;
+ loadConf->globals.bDebugPrintModuleList = 1;
+ loadConf->globals.bAbortOnUncleanConfig = 0;
+ loadConf->globals.bReduceRepeatMsgs = 0;
+ free(loadConf->globals.mainQ.pszMainMsgQFName);
+ loadConf->globals.mainQ.pszMainMsgQFName = NULL;
+ loadConf->globals.mainQ.iMainMsgQueueSize = 10000;
+ loadConf->globals.mainQ.iMainMsgQHighWtrMark = 8000;
+ loadConf->globals.mainQ.iMainMsgQLowWtrMark = 2000;
+ loadConf->globals.mainQ.iMainMsgQDiscardMark = 9800;
+ loadConf->globals.mainQ.iMainMsgQDiscardSeverity = 8;
+ loadConf->globals.mainQ.iMainMsgQueMaxFileSize = 1024 * 1024;
+ loadConf->globals.mainQ.iMainMsgQueueNumWorkers = 1;
+ loadConf->globals.mainQ.iMainMsgQPersistUpdCnt = 0;
+ loadConf->globals.mainQ.bMainMsgQSyncQeueFiles = 0;
+ loadConf->globals.mainQ.iMainMsgQtoQShutdown = 1500;
+ loadConf->globals.mainQ.iMainMsgQtoActShutdown = 1000;
+ loadConf->globals.mainQ.iMainMsgQtoEnq = 2000;
+ loadConf->globals.mainQ.iMainMsgQtoWrkShutdown = 60000;
+ loadConf->globals.mainQ.iMainMsgQWrkMinMsgs = 100;
+ loadConf->globals.mainQ.iMainMsgQDeqSlowdown = 0;
+ loadConf->globals.mainQ.bMainMsgQSaveOnShutdown = 1;
+ loadConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ loadConf->globals.mainQ.iMainMsgQueMaxDiskSpace = 0;
+ loadConf->globals.mainQ.iMainMsgQueDeqBatchSize = 32;
+
+ return RS_RET_OK;
+}
+
+
+/* legacy config system: set the action resume interval */
+static rsRetVal
+setModDir(void __attribute__((unused)) *pVal, uchar* pszNewVal)
+{
+ DEFiRet;
+ iRet = module.SetModDir(pszNewVal);
+ free(pszNewVal);
+ RETiRet;
+}
+
+
+/* "load" a build in module and register it for the current load config */
+static rsRetVal
+regBuildInModule(rsRetVal (*modInit)(), uchar *name, void *pModHdlr)
+{
+ cfgmodules_etry_t *pNew;
+ cfgmodules_etry_t *pLast;
+ modInfo_t *pMod;
+ DEFiRet;
+ CHKiRet(module.doModInit(modInit, name, pModHdlr, &pMod));
+ readyModForCnf(pMod, &pNew, &pLast);
+ addModToCnfList(pNew, pLast);
+finalize_it:
+ RETiRet;
+}
+
+
+/* load build-in modules
+ * very first version begun on 2007-07-23 by rgerhards
+ */
+static rsRetVal
+loadBuildInModules()
+{
+ DEFiRet;
+
+ CHKiRet(regBuildInModule(modInitFile, UCHAR_CONSTANT("builtin:omfile"), NULL));
+ CHKiRet(regBuildInModule(modInitPipe, UCHAR_CONSTANT("builtin:ompipe"), NULL));
+ CHKiRet(regBuildInModule(modInitShell, UCHAR_CONSTANT("builtin-shell"), NULL));
+ CHKiRet(regBuildInModule(modInitDiscard, UCHAR_CONSTANT("builtin:omdiscard"), NULL));
+# ifdef SYSLOG_INET
+ CHKiRet(regBuildInModule(modInitFwd, UCHAR_CONSTANT("builtin:omfwd"), NULL));
+# endif
+
+ /* dirty, but this must be for the time being: the usrmsg module must always be
+ * loaded as last module. This is because it processes any type of action selector.
+ * If we load it before other modules, these others will never have a chance of
+ * working with the config file. We may change that implementation so that a user name
+ * must start with an alnum, that would definitely help (but would it break backwards
+ * compatibility?). * rgerhards, 2007-07-23
+ * User names now must begin with:
+ * [a-zA-Z0-9_.]
+ */
+ CHKiRet(regBuildInModule(modInitUsrMsg, (uchar*) "builtin:omusrmsg", NULL));
+
+ /* load build-in parser modules */
+ CHKiRet(regBuildInModule(modInitpmrfc5424, UCHAR_CONSTANT("builtin:pmrfc5424"), NULL));
+ CHKiRet(regBuildInModule(modInitpmrfc3164, UCHAR_CONSTANT("builtin:pmrfc3164"), NULL));
+
+ /* and set default parser modules. Order is *very* important, legacy
+ * (3164) parser needs to go last! */
+ CHKiRet(parser.AddDfltParser(UCHAR_CONSTANT("rsyslog.rfc5424")));
+ CHKiRet(parser.AddDfltParser(UCHAR_CONSTANT("rsyslog.rfc3164")));
+
+ /* load build-in strgen modules */
+ CHKiRet(regBuildInModule(modInitsmfile, UCHAR_CONSTANT("builtin:smfile"), NULL));
+ CHKiRet(regBuildInModule(modInitsmtradfile, UCHAR_CONSTANT("builtin:smtradfile"), NULL));
+ CHKiRet(regBuildInModule(modInitsmfwd, UCHAR_CONSTANT("builtin:smfwd"), NULL));
+ CHKiRet(regBuildInModule(modInitsmtradfwd, UCHAR_CONSTANT("builtin:smtradfwd"), NULL));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ /* we need to do fprintf, as we do not yet have an error reporting system
+ * in place.
+ */
+ fprintf(stderr, "fatal error: could not activate built-in modules. Error code %d.\n",
+ iRet);
+ }
+ RETiRet;
+}
+
+
+/* intialize the legacy config system */
+static inline rsRetVal
+initLegacyConf(void)
+{
+ DEFiRet;
+ uchar *pTmp;
+ ruleset_t *pRuleset;
+
+ DBGPRINTF("doing legacy config system init\n");
+ /* construct the default ruleset */
+ ruleset.Construct(&pRuleset);
+ ruleset.SetName(pRuleset, UCHAR_CONSTANT("RSYSLOG_DefaultRuleset"));
+ ruleset.ConstructFinalize(loadConf, pRuleset);
+ rulesetSetCurrRulesetPtr(pRuleset);
+
+ /* now register config handlers */
+ CHKiRet(regCfSysLineHdlr((uchar *)"sleep", 0, eCmdHdlrGoneAway,
+ NULL, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"logrsyslogstatusmessages", 0, eCmdHdlrBinary,
+ NULL, &loadConf->globals.bLogStatusMsgs, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"errormessagestostderr", 0, eCmdHdlrBinary,
+ NULL, &loadConf->globals.bErrMsgToStderr, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"abortonuncleanconfig", 0, eCmdHdlrBinary,
+ NULL, &loadConf->globals.bAbortOnUncleanConfig, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgreduction", 0, eCmdHdlrBinary,
+ NULL, &loadConf->globals.bReduceRepeatMsgs, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"debugprinttemplatelist", 0, eCmdHdlrBinary,
+ NULL, &(loadConf->globals.bDebugPrintTemplateList), NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"debugprintmodulelist", 0, eCmdHdlrBinary,
+ NULL, &(loadConf->globals.bDebugPrintModuleList), NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"debugprintcfsyslinehandlerlist", 0, eCmdHdlrBinary,
+ NULL, &(loadConf->globals.bDebugPrintCfSysLineHandlerList), NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"privdroptouser", 0, eCmdHdlrUID,
+ NULL, &loadConf->globals.uidDropPriv, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"privdroptouserid", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.uidDropPriv, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"privdroptogroup", 0, eCmdHdlrGID,
+ NULL, &loadConf->globals.gidDropPriv, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"privdroptogroupid", 0, eCmdHdlrGID,
+ NULL, &loadConf->globals.gidDropPriv, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"generateconfiggraph", 0, eCmdHdlrGetWord,
+ NULL, &loadConf->globals.pszConfDAGFile, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"umask", 0, eCmdHdlrFileCreateMode,
+ NULL, &loadConf->globals.umask, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"maxopenfiles", 0, eCmdHdlrInt,
+ setMaxFiles, NULL, NULL));
+
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeinterval", 0, eCmdHdlrInt,
+ setActionResumeInterval, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler,
+ conf.doModLoad, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultruleset", 0, eCmdHdlrGetWord,
+ setDefaultRuleset, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"ruleset", 0, eCmdHdlrGetWord,
+ setCurrRuleset, NULL, NULL));
+
+ /* handler for "larger" config statements (tie into legacy conf system) */
+ CHKiRet(regCfSysLineHdlr((uchar *)"template", 0, eCmdHdlrCustomHandler,
+ conf.doNameLine, (void*)DIR_TEMPLATE, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"outchannel", 0, eCmdHdlrCustomHandler,
+ conf.doNameLine, (void*)DIR_OUTCHANNEL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"allowedsender", 0, eCmdHdlrCustomHandler,
+ conf.doNameLine, (void*)DIR_ALLOWEDSENDER, NULL));
+
+ /* the following are parameters for the main message queue. I have the
+ * strong feeling that this needs to go to a different space, but that
+ * feeling may be wrong - we'll see how things evolve.
+ * rgerhards, 2011-04-21
+ */
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuefilename", 0, eCmdHdlrGetWord,
+ NULL, &loadConf->globals.mainQ.pszMainMsgQFName, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesize", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQueueSize, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuehighwatermark", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQHighWtrMark, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuelowwatermark", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQLowWtrMark, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardmark", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQDiscardMark, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardseverity", 0, eCmdHdlrSeverity,
+ NULL, &loadConf->globals.mainQ.iMainMsgQDiscardSeverity, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuecheckpointinterval", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQPersistUpdCnt, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesyncqueuefiles", 0, eCmdHdlrBinary,
+ NULL, &loadConf->globals.mainQ.bMainMsgQSyncQeueFiles, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetype", 0, eCmdHdlrGetWord,
+ setMainMsgQueType, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreads", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQueueNumWorkers, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutshutdown", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQtoQShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutactioncompletion", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQtoActShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutenqueue", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQtoEnq, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkertimeoutthreadshutdown", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQtoWrkShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeueslowdown", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQDeqSlowdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreadminimummessages", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQWrkMinMsgs, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxfilesize", 0, eCmdHdlrSize,
+ NULL, &loadConf->globals.mainQ.iMainMsgQueMaxFileSize, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuebatchsize", 0, eCmdHdlrSize,
+ NULL, &loadConf->globals.mainQ.iMainMsgQueDeqBatchSize, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxdiskspace", 0, eCmdHdlrSize,
+ NULL, &loadConf->globals.mainQ.iMainMsgQueMaxDiskSpace, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesaveonshutdown", 0, eCmdHdlrBinary,
+ NULL, &loadConf->globals.mainQ.bMainMsgQSaveOnShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimebegin", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQueueDeqtWinFromHr, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimeend", 0, eCmdHdlrInt,
+ NULL, &loadConf->globals.mainQ.iMainMsgQueueDeqtWinToHr, NULL));
+ /* moddir is a bit hard problem -- because it actually needs to
+ * modify a setting that is specific to module.c. The important point
+ * is that this action MUST actually be carried out during config load,
+ * because we must load modules in order to get their config extensions
+ * (no way around).
+ * TODO: think about a clean solution
+ */
+ CHKiRet(regCfSysLineHdlr((uchar *)"moddir", 0, eCmdHdlrGetWord,
+ setModDir, NULL, NULL));
+
+ /* finally, the reset handler */
+ CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler,
+ resetConfigVariables, NULL, NULL));
+
+ /* initialize the build-in templates */
+ pTmp = template_DebugFormat;
+ tplAddLine(ourConf, "RSYSLOG_DebugFormat", &pTmp);
+ pTmp = template_SyslogProtocol23Format;
+ tplAddLine(ourConf, "RSYSLOG_SyslogProtocol23Format", &pTmp);
+ pTmp = template_FileFormat; /* new format for files with high-precision stamp */
+ tplAddLine(ourConf, "RSYSLOG_FileFormat", &pTmp);
+ pTmp = template_TraditionalFileFormat;
+ tplAddLine(ourConf, "RSYSLOG_TraditionalFileFormat", &pTmp);
+ pTmp = template_WallFmt;
+ tplAddLine(ourConf, " WallFmt", &pTmp);
+ pTmp = template_ForwardFormat;
+ tplAddLine(ourConf, "RSYSLOG_ForwardFormat", &pTmp);
+ pTmp = template_TraditionalForwardFormat;
+ tplAddLine(ourConf, "RSYSLOG_TraditionalForwardFormat", &pTmp);
+ pTmp = template_StdUsrMsgFmt;
+ tplAddLine(ourConf, " StdUsrMsgFmt", &pTmp);
+ pTmp = template_StdDBFmt;
+ tplAddLine(ourConf, " StdDBFmt", &pTmp);
+ pTmp = template_SysklogdFileFormat;
+ tplAddLine(ourConf, "RSYSLOG_SysklogdFileFormat", &pTmp);
+ pTmp = template_StdPgSQLFmt;
+ tplAddLine(ourConf, " StdPgSQLFmt", &pTmp);
+ pTmp = template_StdJSONFmt;
+ tplAddLine(ourConf, " StdJSONFmt", &pTmp);
+ pTmp = template_spoofadr;
+ tplLastStaticInit(ourConf, tplAddLine(ourConf, "RSYSLOG_omudpspoofDfltSourceTpl", &pTmp));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* validate the current configuration, generate error messages, do
+ * optimizations, etc, etc,...
+ */
+static inline rsRetVal
+validateConf(void)
+{
+ DEFiRet;
+
+ /* some checks */
+ if(ourConf->globals.mainQ.iMainMsgQueueNumWorkers < 1) {
+ errmsg.LogError(0, NO_ERRCODE, "$MainMsgQueueNumWorkers must be at least 1! Set to 1.\n");
+ ourConf->globals.mainQ.iMainMsgQueueNumWorkers = 1;
+ }
+
+ if(ourConf->globals.mainQ.MainMsgQueType == QUEUETYPE_DISK) {
+ errno = 0; /* for logerror! */
+ if(glbl.GetWorkDir() == NULL) {
+ errmsg.LogError(0, NO_ERRCODE, "No $WorkDirectory specified - can not run main message queue in 'disk' mode. "
+ "Using 'FixedArray' instead.\n");
+ ourConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ }
+ if(ourConf->globals.mainQ.pszMainMsgQFName == NULL) {
+ errmsg.LogError(0, NO_ERRCODE, "No $MainMsgQueueFileName specified - can not run main message queue in "
+ "'disk' mode. Using 'FixedArray' instead.\n");
+ ourConf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ }
+ }
+ RETiRet;
+}
+
+
+/* Load a configuration. This will do all necessary steps to create
+ * the in-memory representation of the configuration, including support
+ * for multiple configuration languages.
+ * Note that to support the legacy language we must provide some global
+ * object that holds the currently-being-loaded config ptr.
+ * Begun 2011-04-20, rgerhards
+ */
+rsRetVal
+load(rsconf_t **cnf, uchar *confFile)
+{
+ int iNbrActions;
+ int r;
+ DEFiRet;
+
+ CHKiRet(rsconfConstruct(&loadConf));
+ourConf = loadConf; // TODO: remove, once ourConf is gone!
+
+ CHKiRet(loadBuildInModules());
+ CHKiRet(initLegacyConf());
+
+ /* open the configuration file */
+ r = cnfSetLexFile((char*)confFile);
+ if(r == 0) {
+ r = yyparse();
+ conf.GetNbrActActions(loadConf, &iNbrActions);
+ }
+
+ if(r == 1) {
+ errmsg.LogError(0, RS_RET_CONF_PARSE_ERROR,
+ "CONFIG ERROR: could not interpret master "
+ "config file '%s'.", confFile);
+ ABORT_FINALIZE(RS_RET_CONF_PARSE_ERROR);
+ } else if(iNbrActions == 0) {
+ errmsg.LogError(0, RS_RET_NO_ACTIONS, "CONFIG ERROR: there are no "
+ "active actions configured. Inputs will "
+ "run, but no output whatsoever is created.");
+ ABORT_FINALIZE(RS_RET_NO_ACTIONS);
+ }
+ tellLexEndParsing();
+ rulesetOptimizeAll(loadConf);
+
+ tellCoreConfigLoadDone();
+ tellModulesConfigLoadDone();
+
+ tellModulesCheckConfig();
+ CHKiRet(validateConf());
+
+ /* we are done checking the config - now validate if we should actually run or not.
+ * If not, terminate. -- rgerhards, 2008-07-25
+ * TODO: iConfigVerify -- should it be pulled from the config, or leave as is (option)?
+ */
+ if(iConfigVerify) {
+ if(iRet == RS_RET_OK)
+ iRet = RS_RET_VALIDATION_RUN;
+ FINALIZE;
+ }
+
+ /* all OK, pass loaded conf to caller */
+ *cnf = loadConf;
+// TODO: enable this once all config code is moved to here! loadConf = NULL;
+
+ dbgprintf("rsyslog finished loading master config %p\n", loadConf);
+ rsconfDebugPrint(loadConf);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* queryInterface function
+ */
+BEGINobjQueryInterface(rsconf)
+CODESTARTobjQueryInterface(rsconf)
+ if(pIf->ifVersion != rsconfCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = rsconfConstruct;
+ pIf->ConstructFinalize = rsconfConstructFinalize;
+ pIf->Destruct = rsconfDestruct;
+ pIf->DebugPrint = rsconfDebugPrint;
+ pIf->Load = load;
+ pIf->Activate = activate;
+finalize_it:
+ENDobjQueryInterface(rsconf)
+
+
+/* Initialize the rsconf class. Must be called as the very first method
+ * before anything else is called inside this class.
+ */
+BEGINObjClassInit(rsconf, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+ CHKiRet(objUse(module, CORE_COMPONENT));
+ CHKiRet(objUse(conf, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(parser, CORE_COMPONENT));
+
+ /* now set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, rsconfDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, rsconfConstructFinalize);
+ENDObjClassInit(rsconf)
+
+
+/* De-initialize the rsconf class.
+ */
+BEGINObjClassExit(rsconf, OBJ_IS_CORE_MODULE) /* class, version */
+ objRelease(ruleset, CORE_COMPONENT);
+ objRelease(module, CORE_COMPONENT);
+ objRelease(conf, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+ objRelease(parser, CORE_COMPONENT);
+ENDObjClassExit(rsconf)
+
+/* vi:set ai:
+ */
diff --git a/runtime/rsconf.h b/runtime/rsconf.h
new file mode 100644
index 00000000..484fec8c
--- /dev/null
+++ b/runtime/rsconf.h
@@ -0,0 +1,182 @@
+/* The rsconf object. It models a complete rsyslog configuration.
+ *
+ * Copyright 2011 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#ifndef INCLUDED_RSCONF_H
+#define INCLUDED_RSCONF_H
+
+#include "linkedlist.h"
+#include "queue.h"
+
+/* --- configuration objects (the plan is to have ALL upper layers in this file) --- */
+
+/* queue config parameters. TODO: move to queue.c? */
+struct queuecnf_s {
+ int iMainMsgQueueSize; /* size of the main message queue above */
+ int iMainMsgQHighWtrMark; /* high water mark for disk-assisted queues */
+ int iMainMsgQLowWtrMark; /* low water mark for disk-assisted queues */
+ int iMainMsgQDiscardMark; /* begin to discard messages */
+ int iMainMsgQDiscardSeverity; /* by default, discard nothing to prevent unintentional loss */
+ int iMainMsgQueueNumWorkers; /* number of worker threads for the mm queue above */
+ queueType_t MainMsgQueType; /* type of the main message queue above */
+ uchar *pszMainMsgQFName; /* prefix for the main message queue file */
+ int64 iMainMsgQueMaxFileSize;
+ int iMainMsgQPersistUpdCnt; /* persist queue info every n updates */
+ int bMainMsgQSyncQeueFiles; /* sync queue files on every write? */
+ int iMainMsgQtoQShutdown; /* queue shutdown (ms) */
+ int iMainMsgQtoActShutdown; /* action shutdown (in phase 2) */
+ int iMainMsgQtoEnq; /* timeout for queue enque */
+ int iMainMsgQtoWrkShutdown; /* timeout for worker thread shutdown */
+ int iMainMsgQWrkMinMsgs; /* minimum messages per worker needed to start a new one */
+ int iMainMsgQDeqSlowdown; /* dequeue slowdown (simple rate limiting) */
+ int64 iMainMsgQueMaxDiskSpace; /* max disk space allocated 0 ==> unlimited */
+ int64 iMainMsgQueDeqBatchSize; /* dequeue batch size */
+ int bMainMsgQSaveOnShutdown; /* save queue on shutdown (when DA enabled)? */
+ int iMainMsgQueueDeqtWinFromHr; /* hour begin of time frame when queue is to be dequeued */
+ int iMainMsgQueueDeqtWinToHr; /* hour begin of time frame when queue is to be dequeued */
+};
+
+/* globals are data items that are really global, and can be set only
+ * once (at least in theory, because the legacy system permits them to
+ * be re-set as often as the user likes).
+ */
+struct globals_s {
+ int bDebugPrintTemplateList;
+ int bDebugPrintModuleList;
+ int bDebugPrintCfSysLineHandlerList;
+ int bLogStatusMsgs; /* log rsyslog start/stop/HUP messages? */
+ int bErrMsgToStderr; /* print error messages to stderr
+ (in addition to everything else)? */
+ int bAbortOnUncleanConfig; /* abort run (rather than starting with partial
+ config) if there was any issue in conf */
+ int uidDropPriv; /* user-id to which priveleges should be dropped to */
+ int gidDropPriv; /* group-id to which priveleges should be dropped to */
+ int umask; /* umask to use */
+ uchar *pszConfDAGFile; /* name of config DAG file, non-NULL means generate one */
+
+ // TODO are the following ones defaults?
+ int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */
+
+ //TODO: other representation for main queue? Or just load it differently?
+ queuecnf_t mainQ; /* main queue paramters */
+};
+
+/* (global) defaults are global in the sense that they are accessible
+ * to all code, but they can change value and other objects (like
+ * actions) actually copy the value a global had at the time the action
+ * was defined. In that sense, a global default is just that, a default,
+ * wich can (and will) be changed in the course of config file
+ * processing. Once the config file has been processed, defaults
+ * can be dropped. The current code does not do this for simplicity.
+ * That is not a problem, because the defaults do not take up much memory.
+ * At a later stage, we may think about dropping them. -- rgerhards, 2011-04-19
+ */
+struct defaults_s {
+};
+
+
+/* list of modules loaded in this configuration (config specific module list) */
+struct cfgmodules_etry_s {
+ cfgmodules_etry_t *next;
+ modInfo_t *pMod;
+ void *modCnf; /* pointer to the input module conf */
+ /* the following data is input module specific */
+ sbool canActivate; /* OK to activate this config? */
+ sbool canRun; /* OK to run this config? */
+};
+
+struct cfgmodules_s {
+ cfgmodules_etry_t *root;
+};
+
+/* outchannel-specific data */
+struct outchannels_s {
+ struct outchannel *ochRoot; /* the root of the outchannel list */
+ struct outchannel *ochLast; /* points to the last element of the outchannel list */
+};
+
+struct templates_s {
+ struct template *root; /* the root of the template list */
+ struct template *last; /* points to the last element of the template list */
+ struct template *lastStatic; /* last static element of the template list */
+};
+
+
+struct actions_s {
+ unsigned nbrActions; /* number of actions */
+};
+
+
+struct rulesets_s {
+ linkedList_t llRulesets; /* this is NOT a pointer - no typo here ;) */
+
+ /* support for legacy rsyslog.conf format */
+ ruleset_t *pCurr; /* currently "active" ruleset */
+ ruleset_t *pDflt; /* current default ruleset, e.g. for binding to actions which have no other */
+};
+
+
+/* --- end configuration objects --- */
+
+/* the rsconf object */
+struct rsconf_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ cfgmodules_t modules;
+ globals_t globals;
+ defaults_t defaults;
+ templates_t templates;
+ outchannels_t och;
+ actions_t actions;
+ rulesets_t rulesets;
+ /* note: rulesets include the complete output part:
+ * - rules
+ * - filter (as part of the action)
+ * - actions
+ * Of course, we need to debate if we shall change that some time...
+ */
+};
+
+
+/* interfaces */
+BEGINinterface(rsconf) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(rsconf);
+ rsRetVal (*Construct)(rsconf_t **ppThis);
+ rsRetVal (*ConstructFinalize)(rsconf_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(rsconf_t **ppThis);
+ rsRetVal (*Load)(rsconf_t **ppThis, uchar *confFile);
+ rsRetVal (*Activate)(rsconf_t *ppThis);
+ENDinterface(rsconf)
+// TODO: switch version to 1 for first "complete" version!!!! 2011-04-20
+#define rsconfCURR_IF_VERSION 0 /* increment whenever you change the interface above! */
+
+
+/* prototypes */
+PROTOTYPEObj(rsconf);
+
+/* globally-visible external data */
+extern rsconf_t *runConf;/* the currently running config */
+extern rsconf_t *loadConf;/* the config currently being loaded (no concurrent config load supported!) */
+
+
+/* some defaults (to be removed?) */
+#define DFLT_bLogStatusMsgs 1
+
+#endif /* #ifndef INCLUDED_RSCONF_H */
diff --git a/runtime/rsyslog.c b/runtime/rsyslog.c
new file mode 100644
index 00000000..047dfa9b
--- /dev/null
+++ b/runtime/rsyslog.c
@@ -0,0 +1,249 @@
+/* rsyslog.c - the main entry point into rsyslog's runtime library (RTL)
+ *
+ * This module contains all function which work on a RTL global level. It's
+ * name is abbreviated to "rsrt" (rsyslog runtime).
+ *
+ * Please note that the runtime library tends to be plugin-safe. That is, it must be
+ * initialized by calling a global initialization function. However, that
+ * function checks if the library is already initialized and, if so, does
+ * nothing except incrementing a refeence count. Similarly, the deinit
+ * function does nothing as long as there are still other users (which
+ * is tracked via the refcount). As such, it is safe to call init and
+ * exit multiple times, as long as this are always matching calls. This
+ * capability is needed for a plugin system, where one plugin never
+ * knows what the other did. HOWEVER, as of this writing, not all runtime
+ * library objects may work cleanly without static global data (the
+ * debug system is a very good example of this). So while we aim at the
+ * ability to work well in a plugin environment, things may not really work
+ * out. If you intend to use the rsyslog runtime library inside plugins,
+ * you should investigate the situation in detail. Please note that the
+ * rsyslog project itself does not yet need this functionality - thus you
+ * can safely assume it is totally untested ;).
+ *
+ * rgerhards, 2008-04-17: I have now once again checked on the plugin-safety.
+ * Unfortunately, there is currently no hook at all with which we could
+ * abstract a global data instance class. As such, we can NOT make the
+ * runtime plugin-safe in the above-described sense. As the rsyslog
+ * project itself does not need this functionality (and it is quesationable
+ * if someone else ever will), we do currently do not make an effort to
+ * support it. So if you intend to use rsyslog runtime inside a non-rsyslog
+ * plugin system, be careful!
+ *
+ * The rsyslog runtime library is in general reentrant and thread-safe. There
+ * are some intentional exceptions (e.g. inside the msg object). These are
+ * documented. Any other threading and reentrency issue can be considered a bug.
+ *
+ * Module begun 2008-04-16 by Rainer Gerhards
+ *
+ * Copyright 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "stringbuf.h"
+#include "wti.h"
+#include "wtp.h"
+#include "datetime.h"
+#include "queue.h"
+#include "conf.h"
+#include "rsconf.h"
+#include "glbl.h"
+#include "errmsg.h"
+#include "prop.h"
+#include "ruleset.h"
+#include "parser.h"
+#include "strgen.h"
+#include "statsobj.h"
+#include "atomic.h"
+
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+struct sched_param default_sched_param;
+pthread_attr_t default_thread_attr;
+int default_thr_sched_policy;
+#endif
+
+/* forward definitions */
+static rsRetVal dfltErrLogger(int, uchar *errMsg);
+
+/* globally visible static data - see comment in rsyslog.h for details */
+uchar *glblModPath; /* module load path */
+rsRetVal (*glblErrLogger)(int, uchar*) = dfltErrLogger; /* the error logger to use by the errmsg module */
+
+/* static data */
+static int iRefCount = 0; /* our refcount - it MUST exist only once inside a process (not thread)
+ thus it is perfectly OK to use a static. MUST be initialized to 0! */
+
+/* This is the default instance of the error logger. It simply writes the message
+ * to stderr. It is expected that this is replaced by the runtime user very early
+ * during startup (at least if the default is unsuitable). However, we provide a
+ * default so that we can log errors during the intial phase, most importantly
+ * during initialization. -- rgerhards. 2008-04-17
+ */
+static rsRetVal dfltErrLogger(int iErr, uchar *errMsg)
+{
+ DEFiRet;
+ fprintf(stderr, "rsyslog runtime error(%d): %s\n", iErr, errMsg);
+ RETiRet;
+}
+
+
+/* set the error log function
+ * rgerhards, 2008-04-18
+ */
+rsRetVal
+rsrtSetErrLogger(rsRetVal (*errLogger)(int, uchar*))
+{
+ DEFiRet;
+ assert(errLogger != NULL);
+ glblErrLogger = errLogger;
+ RETiRet;
+}
+
+
+/* globally initialze the runtime system
+ * NOTE: this is NOT thread safe and must not be called concurrently. If that
+ * ever poses a problem, we may use proper mutex calls - not considered needed yet.
+ * If ppErrObj is provided, it receives a char pointer to the name of the object that
+ * caused the problem (if one occured). The caller must never free this pointer. If
+ * ppErrObj is NULL, no such information will be provided. pObjIF is the pointer to
+ * the "obj" object interface, which may be used to query any other rsyslog objects.
+ * rgerhards, 2008-04-16
+ */
+rsRetVal
+rsrtInit(char **ppErrObj, obj_if_t *pObjIF)
+{
+ DEFiRet;
+
+ if(iRefCount == 0) {
+ /* init runtime only if not yet done */
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+ CHKiRet(pthread_getschedparam(pthread_self(),
+ &default_thr_sched_policy,
+ &default_sched_param));
+ CHKiRet(pthread_attr_init(&default_thread_attr));
+ CHKiRet(pthread_attr_setschedpolicy(&default_thread_attr,
+ default_thr_sched_policy));
+ CHKiRet(pthread_attr_setschedparam(&default_thread_attr,
+ &default_sched_param));
+ CHKiRet(pthread_attr_setinheritsched(&default_thread_attr,
+ PTHREAD_EXPLICIT_SCHED));
+#endif
+ if(ppErrObj != NULL) *ppErrObj = "obj";
+ CHKiRet(objClassInit(NULL)); /* *THIS* *MUST* always be the first class initilizer being called! */
+ CHKiRet(objGetObjInterface(pObjIF)); /* this provides the root pointer for all other queries */
+
+ /* initialize core classes. We must be very careful with the order of events. Some
+ * classes use others and if we do not initialize them in the right order, we may end
+ * up with an invalid call. The most important thing that can happen is that an error
+ * is detected and needs to be logged, wich in turn requires a broader number of classes
+ * to be available. The solution is that we take care in the order of calls AND use a
+ * class immediately after it is initialized. And, of course, we load those classes
+ * first that we use ourselfs... -- rgerhards, 2008-03-07
+ */
+ if(ppErrObj != NULL) *ppErrObj = "statsobj";
+ CHKiRet(statsobjClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "prop";
+ CHKiRet(propClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "glbl";
+ CHKiRet(glblClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "msg";
+ CHKiRet(msgClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "ruleset";
+ CHKiRet(rulesetClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "wti";
+ CHKiRet(wtiClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "wtp";
+ CHKiRet(wtpClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "queue";
+ CHKiRet(qqueueClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "conf";
+ CHKiRet(confClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "parser";
+ CHKiRet(parserClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "strgen";
+ CHKiRet(strgenClassInit(NULL));
+ if(ppErrObj != NULL) *ppErrObj = "rsconf";
+ CHKiRet(rsconfClassInit(NULL));
+
+ /* dummy "classes" */
+ if(ppErrObj != NULL) *ppErrObj = "str";
+ CHKiRet(strInit());
+ }
+
+ ++iRefCount;
+ dbgprintf("rsyslog runtime initialized, version %s, current users %d\n", VERSION, iRefCount);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* globally de-initialze the runtime system
+ * NOTE: this is NOT thread safe and must not be called concurrently. If that
+ * ever poses a problem, we may use proper mutex calls - not considered needed yet.
+ * This function must be provided with the caller's obj object pointer. This is
+ * automatically deinitialized by the runtime system.
+ * rgerhards, 2008-04-16
+ */
+rsRetVal
+rsrtExit(void)
+{
+ DEFiRet;
+
+ if(iRefCount == 1) {
+ /* do actual de-init only if we are the last runtime user */
+ confClassExit();
+ glblClassExit();
+ rulesetClassExit();
+
+ objClassExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */
+ }
+
+ --iRefCount;
+ /* TODO we must deinit this pointer! pObjIF = NULL; / * no longer exists for this caller */
+
+ dbgprintf("rsyslog runtime de-initialized, current users %d\n", iRefCount);
+
+ RETiRet;
+}
+
+
+/* returns 0 if the rsyslog runtime is not initialized and another value
+ * if it is. This function is primarily meant to be used by runtime functions
+ * itself. However, it is safe to call it before initializing the runtime.
+ * Plugins should NOT rely on this function. The reason is that another caller
+ * may have already initialized it but deinits it before this plugin is done.
+ * So for plugins and like architectures, the right course of action is to
+ * call rsrtInit() and rsrtExit(), which can be called by multiple callers.
+ * rgerhards, 2008-04-16
+ */
+int rsrtIsInit(void)
+{
+ return iRefCount;
+}
+
+
+/* vim:set ai:
+ */
diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h
new file mode 100644
index 00000000..179d93e6
--- /dev/null
+++ b/runtime/rsyslog.h
@@ -0,0 +1,559 @@
+/* This is the header file for the rsyslog runtime. It must be included
+ * if someone intends to use the runtime.
+ *
+ * Begun 2005-09-15 RGerhards
+ *
+ * Copyright (C) 2005-2008 by Rainer Gerhards and Adiscon GmbH
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#ifndef INCLUDED_RSYSLOG_H
+#define INCLUDED_RSYSLOG_H
+#include <pthread.h>
+#include "typedefs.h"
+
+/* ############################################################# *
+ * # Some constant values # *
+ * ############################################################# */
+#define CONST_LEN_TIMESTAMP_3164 15 /* number of chars (excluding \0!) in a RFC3164 timestamp */
+#define CONST_LEN_TIMESTAMP_3339 32 /* number of chars (excluding \0!) in a RFC3339 timestamp */
+
+/* ############################################################# *
+ * # Config Settings # *
+ * ############################################################# */
+#define RS_STRINGBUF_ALLOC_INCREMENT 128
+/* MAXSIZE are absolute maxima, while BUFSIZE are just values after which
+ * processing is more time-intense. The BUFSIZE params currently add their
+ * value to the fixed size of the message object.
+ */
+#define CONF_TAG_MAXSIZE 512 /* a value that is deemed far too large for any valid TAG */
+#define CONF_HOSTNAME_MAXSIZE 512 /* a value that is deemed far too large for any valid HOSTNAME */
+#define CONF_RAWMSG_BUFSIZE 101
+#define CONF_TAG_BUFSIZE 32
+#define CONF_PROGNAME_BUFSIZE 16
+#define CONF_HOSTNAME_BUFSIZE 32
+#define CONF_PROP_BUFSIZE 16 /* should be close to sizeof(ptr) or lighly above it */
+#define CONF_MIN_SIZE_FOR_COMPRESS 60 /* config param: minimum message size to try compression. The smaller
+ * the message, the less likely is any compression gain. We check for
+ * gain before we submit the message. But to do so we still need to
+ * do the (costly) compress() call. The following setting sets a size
+ * for which no call to compress() is done at all. This may result in
+ * a few more bytes being transmited but better overall performance.
+ * Note: I have not yet checked the minimum UDP packet size. It might be
+ * that we do not save anything by compressing very small messages, because
+ * UDP might need to pad ;)
+ * rgerhards, 2006-11-30
+ */
+
+#define CONF_OMOD_NUMSTRINGS_MAXSIZE 5 /* cache for pointers to output module buffer pointers. All
+ * rsyslog-provided plugins do NOT need more than five buffers. If
+ * more are needed (future developments, third-parties), rsyslog
+ * must be recompiled with a larger parameter. Hardcoding this
+ * saves us some overhead, both in runtime in code complexity. As
+ * it is doubtful if ever more than 3 parameters are needed, the
+ * approach taken here is considered appropriate.
+ * rgerhards, 2010-06-24
+ */
+#define CONF_NUM_MULTISUB 1024 /* default number of messages per multisub structure */
+
+/* ############################################################# *
+ * # End Config Settings # *
+ * ############################################################# */
+
+/* portability: not all platforms have these defines, so we
+ * define them here if they are missing. -- rgerhards, 2008-03-04
+ */
+#ifndef LOG_MAKEPRI
+# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri))
+#endif
+#ifndef LOG_PRI
+# define LOG_PRI(p) ((p) & LOG_PRIMASK)
+#endif
+#ifndef LOG_FAC
+# define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3)
+#endif
+
+
+/* the rsyslog core provides information about present feature to plugins
+ * asking it. Below are feature-test macros which must be used to query
+ * features. Note that this must be powers of two, so that multiple queries
+ * can be combined. -- rgerhards, 2009-04-27
+ */
+#define CORE_FEATURE_BATCHING 1
+/*#define CORE_FEATURE_whatever 2 ... and so on ... */
+
+#ifndef _PATH_CONSOLE
+#define _PATH_CONSOLE "/dev/console"
+#endif
+
+/* properties are now encoded as (tiny) integers. I do not use an enum as I would like
+ * to keep the memory footprint small (and thus cache hits high).
+ * rgerhards, 2009-06-26
+ */
+typedef uintTiny propid_t;
+#define PROP_INVALID 0
+#define PROP_MSG 1
+#define PROP_TIMESTAMP 2
+#define PROP_HOSTNAME 3
+#define PROP_SYSLOGTAG 4
+#define PROP_RAWMSG 5
+#define PROP_INPUTNAME 6
+#define PROP_FROMHOST 7
+#define PROP_FROMHOST_IP 8
+#define PROP_PRI 9
+#define PROP_PRI_TEXT 10
+#define PROP_IUT 11
+#define PROP_SYSLOGFACILITY 12
+#define PROP_SYSLOGFACILITY_TEXT 13
+#define PROP_SYSLOGSEVERITY 14
+#define PROP_SYSLOGSEVERITY_TEXT 15
+#define PROP_TIMEGENERATED 16
+#define PROP_PROGRAMNAME 17
+#define PROP_PROTOCOL_VERSION 18
+#define PROP_STRUCTURED_DATA 19
+#define PROP_APP_NAME 20
+#define PROP_PROCID 21
+#define PROP_MSGID 22
+#define PROP_PARSESUCCESS 23
+#define PROP_SYS_NOW 150
+#define PROP_SYS_YEAR 151
+#define PROP_SYS_MONTH 152
+#define PROP_SYS_DAY 153
+#define PROP_SYS_HOUR 154
+#define PROP_SYS_HHOUR 155
+#define PROP_SYS_QHOUR 156
+#define PROP_SYS_MINUTE 157
+#define PROP_SYS_MYHOSTNAME 158
+#define PROP_CEE 200
+#define PROP_CEE_ALL_JSON 201
+#define PROP_SYS_BOM 159
+#define PROP_SYS_UPTIME 160
+#define PROP_UUID 161
+
+
+/* The error codes below are orginally "borrowed" from
+ * liblogging. As such, we reserve values up to -2999
+ * just in case we need to borrow something more ;)
+*/
+enum rsRetVal_ /** return value. All methods return this if not specified otherwise */
+{
+ /* the first two define are for errmsg.logError(), so that we can use the rsRetVal
+ * as an rsyslog error code. -- rgerhards, 20080-06-27
+ */
+ RS_RET_NO_ERRCODE = -1, /**< RESERVED for NO_ERRCODE errmsg.logError status name */
+ RS_RET_INCLUDE_ERRNO = 1073741824, /* 2**30 - do NOT use error codes above this! */
+ /* begin regular error codes */
+ RS_RET_NOT_IMPLEMENTED = -7, /**< implementation is missing (probably internal error or lazyness ;)) */
+ RS_RET_OUT_OF_MEMORY = -6, /**< memory allocation failed */
+ RS_RET_PROVIDED_BUFFER_TOO_SMALL = -50,/**< the caller provided a buffer, but the called function sees the size of this buffer is too small - operation not carried out */
+ RS_RET_TRUE = -3, /**< to indicate a true state (can be used as TRUE, legacy) */
+ RS_RET_FALSE = -2, /**< to indicate a false state (can be used as FALSE, legacy) */
+ RS_RET_NO_IRET = -8, /**< This is a trick for the debuging system - it means no iRet is provided */
+ RS_RET_VALIDATION_RUN = -9, /**< indicates a (config) validation run, processing not carried out */
+ RS_RET_ERR = -3000, /**< generic failure */
+ RS_TRUNCAT_TOO_LARGE = -3001, /**< truncation operation where too many chars should be truncated */
+ RS_RET_FOUND_AT_STRING_END = -3002, /**< some value found, but at the last pos of string */
+ RS_RET_NOT_FOUND = -3003, /**< some requested value not found */
+ RS_RET_MISSING_TRAIL_QUOTE = -3004, /**< an expected trailing quote is missing */
+ RS_RET_NO_DIGIT = -3005, /**< an digit was expected, but none found (mostly parsing) */
+ RS_RET_NO_MORE_DATA = -3006, /**< insufficient data, e.g. end of string during parsing */
+ RS_RET_INVALID_IP = -3007, /**< invalid ip found where valid was expected */
+ RS_RET_OBJ_CREATION_FAILED = - 3008, /**< the creation of an object failed (no details available) */
+ RS_RET_PARAM_ERROR = -1000, /**< invalid parameter in call to function */
+ RS_RET_MISSING_INTERFACE = -1001,/**< interface version mismatch, required missing */
+ RS_RET_INVALID_CORE_INTERFACE = -1002,/**< interface provided by host invalid, can not be used */
+ RS_RET_ENTRY_POINT_NOT_FOUND = -1003,/**< a requested entry point was not found */
+ RS_RET_MODULE_ENTRY_POINT_NOT_FOUND = -1004,/**< a entry point requested from a module was not present in it */
+ RS_RET_OBJ_NOT_AVAILABLE = -1005,/**< something could not be completed because the required object is not available*/
+ RS_RET_LOAD_ERROR = -1006,/**< we had an error loading the object/interface and can not continue */
+ RS_RET_MODULE_STILL_REFERENCED = -1007,/**< module could not be unloaded because it still is referenced by someone */
+ RS_RET_OBJ_UNKNOWN = -1008,/**< object is unknown where required */
+ RS_RET_OBJ_NOT_REGISTERED = -1009,/**< tried to unregister an object that is not registered */
+ /* return states for config file processing */
+ RS_RET_NONE = -2000, /**< some value is not available - not necessarily an error */
+ RS_RET_CONFLINE_UNPROCESSED = -2001,/**< config line was not processed, pass to other module */
+ RS_RET_DISCARDMSG = -2002, /**< discard message (no error state, processing request!) */
+ RS_RET_INCOMPATIBLE = -2003, /**< function not compatible with requested feature */
+ RS_RET_NOENTRY = -2004, /**< do not create an entry for (whatever) - not necessary an error */
+ RS_RET_NO_SQL_STRING = -2005, /**< string is not suitable for use as SQL */
+ RS_RET_DISABLE_ACTION = -2006, /**< action requests that it be disabled */
+ RS_RET_SUSPENDED = -2007, /**< something was suspended, not neccesarily an error */
+ RS_RET_RQD_TPLOPT_MISSING = -2008,/**< a required template option is missing */
+ RS_RET_INVALID_VALUE = -2009,/**< some value is invalid (e.g. user-supplied data) */
+ RS_RET_INVALID_INT = -2010,/**< invalid integer */
+ RS_RET_INVALID_CMD = -2011,/**< invalid command */
+ RS_RET_VAL_OUT_OF_RANGE = -2012, /**< value out of range */
+ RS_RET_FOPEN_FAILURE = -2013, /**< failure during fopen, for example file not found - see errno */
+ RS_RET_END_OF_LINKEDLIST = -2014, /**< end of linked list, not an error, but a status */
+ RS_RET_CHAIN_NOT_PERMITTED = -2015, /**< chaining (e.g. of config command handlers) not permitted */
+ RS_RET_INVALID_PARAMS = -2016,/**< supplied parameters are invalid */
+ RS_RET_EMPTY_LIST = -2017, /**< linked list is empty */
+ RS_RET_FINISHED = -2018, /**< some opertion is finished, not an error state */
+ RS_RET_INVALID_SOURCE = -2019, /**< source (address) invalid for some reason */
+ RS_RET_ADDRESS_UNKNOWN = -2020, /**< an address is unknown - not necessarily an error */
+ RS_RET_MALICIOUS_ENTITY = -2021, /**< there is an malicious entity involved */
+ RS_RET_NO_KERNEL_LOGSRC = -2022, /**< no source for kernel logs can be obtained */
+ RS_RET_TCP_SEND_ERROR = -2023, /**< error during TCP send process */
+ RS_RET_GSS_SEND_ERROR = -2024, /**< error during GSS (via TCP) send process */
+ RS_RET_TCP_SOCKCREATE_ERR = -2025, /**< error during creation of TCP socket */
+ RS_RET_GSS_SENDINIT_ERROR = -2024, /**< error during GSS (via TCP) send initialization process */
+ RS_RET_EOF = -2026, /**< end of file reached, not necessarily an error */
+ RS_RET_IO_ERROR = -2027, /**< some kind of IO error happened */
+ RS_RET_INVALID_OID = -2028, /**< invalid object ID */
+ RS_RET_INVALID_HEADER = -2029, /**< invalid header */
+ RS_RET_INVALID_HEADER_VERS = -2030, /**< invalid header version */
+ RS_RET_INVALID_DELIMITER = -2031, /**< invalid delimiter, e.g. between params */
+ RS_RET_INVALID_PROPFRAME = -2032, /**< invalid framing in serialized property */
+ RS_RET_NO_PROPLINE = -2033, /**< line is not a property line */
+ RS_RET_INVALID_TRAILER = -2034, /**< invalid trailer */
+ RS_RET_VALUE_TOO_LOW = -2035, /**< a provided value is too low */
+ RS_RET_FILE_PREFIX_MISSING = -2036, /**< a required file prefix (parameter?) is missing */
+ RS_RET_INVALID_HEADER_RECTYPE = -2037, /**< invalid record type in header or invalid header */
+ RS_RET_QTYPE_MISMATCH = -2038, /**< different qType when reading back a property type */
+ RS_RET_NO_FILE_ACCESS = -2039, /**< covers EACCES error on file open() */
+ RS_RET_FILE_NOT_FOUND = -2040, /**< file not found */
+ RS_RET_TIMED_OUT = -2041, /**< timeout occured (not necessarily an error) */
+ RS_RET_QSIZE_ZERO = -2042, /**< queue size is zero where this is not supported */
+ RS_RET_ALREADY_STARTING = -2043, /**< something (a thread?) is already starting - not necessarily an error */
+ RS_RET_NO_MORE_THREADS = -2044, /**< no more threads available, not necessarily an error */
+ RS_RET_NO_FILEPREFIX = -2045, /**< file prefix is not specified where one is needed */
+ RS_RET_CONFIG_ERROR = -2046, /**< there is a problem with the user-provided config settigs */
+ RS_RET_OUT_OF_DESRIPTORS = -2047, /**< a descriptor table's space has been exhausted */
+ RS_RET_NO_DRIVERS = -2048, /**< a required drivers missing */
+ RS_RET_NO_DRIVERNAME = -2049, /**< driver name missing where one was required */
+ RS_RET_EOS = -2050, /**< end of stream (of whatever) */
+ RS_RET_SYNTAX_ERROR = -2051, /**< syntax error, eg. during parsing */
+ RS_RET_INVALID_OCTAL_DIGIT = -2052, /**< invalid octal digit during parsing */
+ RS_RET_INVALID_HEX_DIGIT = -2053, /**< invalid hex digit during parsing */
+ RS_RET_INTERFACE_NOT_SUPPORTED = -2054, /**< interface not supported */
+ RS_RET_OUT_OF_STACKSPACE = -2055, /**< a stack data structure is exhausted and can not be grown */
+ RS_RET_STACK_EMPTY = -2056, /**< a pop was requested on a stack, but the stack was already empty */
+ RS_RET_INVALID_VMOP = -2057, /**< invalid virtual machine instruction */
+ RS_RET_INVALID_VAR = -2058, /**< a var_t or its content is unsuitable, eg. VARTYPE_NONE */
+ RS_RET_INVALID_NUMBER = -2059, /**< number invalid during parsing */
+ RS_RET_NOT_A_NUMBER = -2060, /**< e.g. conversion impossible because the string is not a number */
+ RS_RET_OBJ_ALREADY_REGISTERED = -2061, /**< object (name) is already registered */
+ RS_RET_OBJ_REGISTRY_OUT_OF_SPACE = -2062, /**< the object registry has run out of space */
+ RS_RET_HOST_NOT_PERMITTED = -2063, /**< a host is not permitted to perform an action it requested */
+ RS_RET_MODULE_LOAD_ERR = -2064, /**< module could not be loaded */
+ RS_RET_MODULE_LOAD_ERR_PATHLEN = -2065, /**< module could not be loaded - path to long */
+ RS_RET_MODULE_LOAD_ERR_DLOPEN = -2066, /**< module could not be loaded - problem in dlopen() */
+ RS_RET_MODULE_LOAD_ERR_NO_INIT = -2067, /**< module could not be loaded - init() missing */
+ RS_RET_MODULE_LOAD_ERR_INIT_FAILED = -2068, /**< module could not be loaded - init() failed */
+ RS_RET_NO_SOCKET = -2069, /**< socket could not be obtained or was not provided */
+ RS_RET_SMTP_ERROR = -2070, /**< error during SMTP transation */
+ RS_RET_MAIL_NO_TO = -2071, /**< recipient for mail destination is missing */
+ RS_RET_MAIL_NO_FROM = -2072, /**< sender for mail destination is missing */
+ RS_RET_INVALID_PRI = -2073, /**< PRI value is invalid */
+ RS_RET_MALICIOUS_HNAME = -2074, /**< remote peer is trying malicious things with its hostname */
+ RS_RET_INVALID_HNAME = -2075, /**< remote peer's hostname invalid or unobtainable */
+ RS_RET_INVALID_PORT = -2076, /**< invalid port value */
+ RS_RET_COULD_NOT_BIND = -2077, /**< could not bind socket, defunct */
+ RS_RET_GNUTLS_ERR = -2078, /**< (unexpected) error in GnuTLS call */
+ RS_RET_MAX_SESS_REACHED = -2079, /**< max nbr of sessions reached, can not create more */
+ RS_RET_MAX_LSTN_REACHED = -2080, /**< max nbr of listeners reached, can not create more */
+ RS_RET_INVALID_DRVR_MODE = -2081, /**< tried to set mode not supported by driver */
+ RS_RET_DRVRNAME_TOO_LONG = -2082, /**< driver name too long - should never happen */
+ RS_RET_TLS_HANDSHAKE_ERR = -2083, /**< TLS handshake failed */
+ RS_RET_TLS_CERT_ERR = -2084, /**< generic TLS certificate error */
+ RS_RET_TLS_NO_CERT = -2085, /**< no TLS certificate available where one was expected */
+ RS_RET_VALUE_NOT_SUPPORTED = -2086, /**< a provided value is not supported */
+ RS_RET_VALUE_NOT_IN_THIS_MODE = -2087, /**< a provided value is invalid for the curret mode */
+ RS_RET_INVALID_FINGERPRINT = -2088, /**< a fingerprint is not valid for this use case */
+ RS_RET_CONNECTION_ABORTREQ = -2089, /**< connection was abort requested due to previous error */
+ RS_RET_CERT_INVALID = -2090, /**< a x509 certificate failed validation */
+ RS_RET_CERT_INVALID_DN = -2091, /**< distinguised name in x509 certificate is invalid (e.g. wrong escaping) */
+ RS_RET_CERT_EXPIRED = -2092, /**< we are past a x.509 cert's expiration time */
+ RS_RET_CERT_NOT_YET_ACTIVE = -2094, /**< x.509 cert's activation time not yet reached */
+ RS_RET_SYS_ERR = -2095, /**< system error occured (e.g. time() returned -1, quite unexpected) */
+ RS_RET_FILE_NO_STAT = -2096, /**< can not stat() a file */
+ RS_RET_FILE_TOO_LARGE = -2097, /**< a file is larger than permitted */
+ RS_RET_INVALID_WILDCARD = -2098, /**< a wildcard entry is invalid */
+ RS_RET_CLOSED = -2099, /**< connection was closed */
+ RS_RET_RETRY = -2100, /**< call should be retried (e.g. EGAIN on recv) */
+ RS_RET_GSS_ERR = -2101, /**< generic error occured in GSSAPI subsystem */
+ RS_RET_CERTLESS = -2102, /**< state: we run without machine cert (this may be OK) */
+ RS_RET_NO_ACTIONS = -2103, /**< no active actions are configured (no output will be created) */
+ RS_RET_CONF_FILE_NOT_FOUND = -2104, /**< config file or directory not found */
+ RS_RET_QUEUE_FULL = -2105, /**< queue is full, operation could not be completed */
+ RS_RET_ACCEPT_ERR = -2106, /**< error during accept() system call */
+ RS_RET_INVLD_TIME = -2107, /**< invalid timestamp (e.g. could not be parsed) */
+ RS_RET_NO_ZIP = -2108, /**< ZIP functionality is not present */
+ RS_RET_CODE_ERR = -2109, /**< program code (internal) error */
+ RS_RET_FUNC_NO_LPAREN = -2110, /**< left parenthesis missing after function call (rainerscript) */
+ RS_RET_FUNC_MISSING_EXPR = -2111, /**< no expression after comma in function call (rainerscript) */
+ RS_RET_INVLD_NBR_ARGUMENTS = -2112, /**< invalid number of arguments for function call (rainerscript) */
+ RS_RET_INVLD_FUNC = -2113, /**< invalid function name for function call (rainerscript) */
+ RS_RET_DUP_FUNC_NAME = -2114, /**< duplicate function name (rainerscript) */
+ RS_RET_UNKNW_FUNC = -2115, /**< unkown function name (rainerscript) */
+ RS_RET_ERR_RLIM_NOFILE = -2116, /**< error setting max. nbr open files process limit */
+ RS_RET_ERR_CREAT_PIPE = -2117, /**< error during pipe creation */
+ RS_RET_ERR_FORK = -2118, /**< error during fork() */
+ RS_RET_ERR_WRITE_PIPE = -2119, /**< error writing to pipe */
+ RS_RET_RSCORE_TOO_OLD = -2120, /**< rsyslog core is too old for ... (eg this plugin) */
+ RS_RET_DEFER_COMMIT = -2121, /**< output plugin status: not yet committed (an OK state!) */
+ RS_RET_PREVIOUS_COMMITTED = -2122, /**< output plugin status: previous record was committed (an OK state!) */
+ RS_RET_ACTION_FAILED = -2123, /**< action failed and is now suspended */
+ RS_RET_NONFATAL_CONFIG_ERR = -2124, /**< non-fatal error during config processing */
+ RS_RET_NON_SIZELIMITCMD = -2125, /**< size limit for file defined, but no size limit command given */
+ RS_RET_SIZELIMITCMD_DIDNT_RESOLVE = -2126, /**< size limit command did not resolve situation */
+ RS_RET_STREAM_DISABLED = -2127, /**< a file has been disabled (e.g. by size limit restriction) */
+ RS_RET_FILENAME_INVALID = -2140, /**< filename invalid, not found, no access, ... */
+ RS_RET_ZLIB_ERR = -2141, /**< error during zlib call */
+ RS_RET_VAR_NOT_FOUND = -2142, /**< variable not found */
+ RS_RET_EMPTY_MSG = -2143, /**< provided (raw) MSG is empty */
+ RS_RET_PEER_CLOSED_CONN = -2144, /**< remote peer closed connection (information, no error) */
+ RS_RET_ERR_OPEN_KLOG = -2145, /**< error opening the kernel log socket (primarily solaris) */
+ RS_RET_ERR_AQ_CONLOG = -2146, /**< error aquiring console log (on solaris) */
+ RS_RET_ERR_DOOR = -2147, /**< some problems with handling the Solaris door functionality */
+ RS_RET_NO_SRCNAME_TPL = -2150, /**< sourcename template was not specified where one was needed (omudpspoof spoof addr) */
+ RS_RET_HOST_NOT_SPECIFIED = -2151, /**< (target) host was not specified where it was needed */
+ RS_RET_ERR_LIBNET_INIT = -2152, /**< error initializing libnet, e.g. because not running as root */
+ RS_RET_FORCE_TERM = -2153, /**< thread was forced to terminate by bShallShutdown, a state, not an error */
+ RS_RET_RULES_QUEUE_EXISTS = -2154,/**< we were instructed to create a new ruleset queue, but one already exists */
+ RS_RET_NO_CURR_RULESET = -2155,/**< no current ruleset exists (but one is required) */
+ RS_RET_NO_MSG_PASSING = -2156,/**< output module interface parameter passing mode "MSG" is not available but required */
+ RS_RET_RULESET_NOT_FOUND = -2157,/**< a required ruleset could not be found */
+ RS_RET_NO_RULESET= -2158,/**< no ruleset name as specified where one was needed */
+ RS_RET_PARSER_NOT_FOUND = -2159,/**< parser with the specified name was not found */
+ RS_RET_COULD_NOT_PARSE = -2160,/**< (this) parser could not parse the message (no error, means try next one) */
+ RS_RET_EINTR = -2161, /**< EINTR occured during a system call (not necessarily an error) */
+ RS_RET_ERR_EPOLL = -2162, /**< epoll() returned with an unexpected error code */
+ RS_RET_ERR_EPOLL_CTL = -2163, /**< epol_ctll() returned with an unexpected error code */
+ RS_RET_TIMEOUT = -2164, /**< timeout occured during operation */
+ RS_RET_RCV_ERR = -2165, /**< error occured during socket rcv operation */
+ RS_RET_NO_SOCK_CONFIGURED = -2166, /**< no socket (name) was configured where one is required */
+ RS_RET_CONF_NOT_GLBL = -2167, /**< $Begin not in global scope */
+ RS_RET_CONF_IN_GLBL = -2168, /**< $End when in global scope */
+ RS_RET_CONF_INVLD_END = -2169, /**< $End for wrong conf object (probably nesting error) */
+ RS_RET_CONF_INVLD_SCOPE = -2170,/**< config statement not valid in current scope (e.g. global stmt in action block) */
+ RS_RET_CONF_END_NO_ACT = -2171, /**< end of action block, but no actual action specified */
+ RS_RET_NO_LSTN_DEFINED = -2172, /**< no listener defined (e.g. inside an input module) */
+ RS_RET_EPOLL_CR_FAILED = -2173, /**< epoll_create() failed */
+ RS_RET_EPOLL_CTL_FAILED = -2174, /**< epoll_ctl() failed */
+ RS_RET_INTERNAL_ERROR = -2175, /**< rsyslogd internal error, unexpected code path reached */
+ RS_RET_ERR_CRE_AFUX = -2176, /**< error creating AF_UNIX socket (and binding it) */
+ RS_RET_RATE_LIMITED = -2177, /**< some messages discarded due to exceeding a rate limit */
+ RS_RET_ERR_HDFS_WRITE = -2178, /**< error writing to HDFS */
+ RS_RET_ERR_HDFS_OPEN = -2179, /**< error during hdfsOpen (e.g. file does not exist) */
+ RS_RET_FILE_NOT_SPECIFIED = -2180, /**< file name not configured where this was required */
+ RS_RET_ERR_WRKDIR = -2181, /**< problems with the rsyslog working directory */
+ RS_RET_WRN_WRKDIR = -2182, /**< correctable problems with the rsyslog working directory */
+ RS_RET_ERR_QUEUE_EMERGENCY = -2183, /**< some fatal error caused queue to switch to emergency mode */
+ RS_RET_OUTDATED_STMT = -2184, /**< some outdated statement/functionality is being used in conf file */
+ RS_RET_MISSING_WHITESPACE = -2185, /**< whitespace is missing in some config construct */
+ RS_RET_OK_WARN = -2186, /**< config part: everything was OK, but a warning message was emitted */
+
+ RS_RET_INVLD_CONF_OBJ= -2200, /**< invalid config object (e.g. $Begin conf statement) */
+ RS_RET_ERR_LIBEE_INIT = -2201, /**< cannot obtain libee ctx */
+ RS_RET_ERR_LIBLOGNORM_INIT = -2202,/**< cannot obtain liblognorm ctx */
+ RS_RET_ERR_LIBLOGNORM_SAMPDB_LOAD = -2203,/**< liblognorm sampledb load failed */
+ RS_RET_CMD_GONE_AWAY = -2204,/**< config directive existed, but no longer supported */
+ RS_RET_ERR_SCHED_PARAMS = -2205,/**< there is a problem with configured thread scheduling params */
+ RS_RET_SOCKNAME_MISSING = -2206,/**< no socket name configured where one is required */
+ RS_RET_CONF_PARSE_ERROR = -2207,/**< (fatal) error parsing config file */
+ RS_RET_CONF_RQRD_PARAM_MISSING = -2208,/**< required parameter in config object is missing */
+ RS_RET_MOD_UNKNOWN = -2209,/**< module (config name) is unknown */
+ RS_RET_CONFOBJ_UNSUPPORTED = -2210,/**< config objects (v6 conf) are not supported here */
+ RS_RET_MISSING_CNFPARAMS = -2211, /**< missing configuration parameters */
+ RS_RET_NO_LISTNERS = -2212, /**< module loaded, but no listeners are defined */
+ RS_RET_INVLD_PROTOCOL = -2213, /**< invalid protocol specified in config file */
+ RS_RET_CNF_INVLD_FRAMING = -2214, /**< invalid framing specified in config file */
+ RS_RET_LEGA_ACT_NOT_SUPPORTED = -2215, /**< the module (no longer) supports legacy action syntax */
+ RS_RET_MAX_OMSR_REACHED = -2216, /**< max nbr of string requests reached, not supported by core */
+ RS_RET_UID_MISSING = -2217, /**< a user id is missing (but e.g. a password provided) */
+ RS_RET_DATAFAIL = -2218, /**< data passed to action caused failure */
+ /* reserved for pre-v6.5 */
+ RS_RET_DUP_PARAM = -2220, /**< config parameter is given more than once */
+ RS_RET_MODULE_ALREADY_IN_CONF = -2221, /**< module already in current configuration */
+ RS_RET_PARAM_NOT_PERMITTED = -2222, /**< legacy parameter no longer permitted (usally already set by v2) */
+ RS_RET_NO_JSON_PASSING = -2223, /**< rsyslog core does not support JSON-passing plugin API */
+ RS_RET_MOD_NO_INPUT_STMT = -2224, /**< (input) module does not support input() statement */
+ RS_RET_NO_CEE_MSG = -2225, /**< the message being processed is NOT CEE-enhanced */
+
+ /**** up to 2290 is reserved for v6 use ****/
+ RS_RET_RELP_ERR = -2291, /**<< error in RELP processing */
+ /**** up to 3000 is reserved for c7 use ****/
+ RS_RET_JNAME_NO_ROOT = -2301, /**< root element is missing in JSON path */
+ RS_RET_JNAME_INVALID = -2302, /**< JSON path is invalid */
+ RS_RET_JSON_PARSE_ERR = -2303, /**< we had a problem parsing JSON (or extra data) */
+ RS_RET_BSD_BLOCKS_UNSUPPORTED = -2304, /**< BSD-style config blocks are no longer supported */
+ RS_RET_JNAME_NOTFOUND = -2305, /**< JSON name not found (does not exist) */
+ RS_RET_INVLD_SETOP = -2305, /**< invalid variable set operation, incompatible type */
+ RS_RET_RULESET_EXISTS = -2306,/**< ruleset already exists */
+ RS_RET_DEPRECATED = -2307,/**< deprecated functionality is used */
+ RS_RET_DS_PROP_SEQ_ERR = -2308,/**< property sequence error deserializing object */
+ RS_RET_TPL_INVLD_PROP = -2309,/**< property name error in template (unknown name) */
+ RS_RET_NO_RULEBASE = -2310,/**< mmnormalize: rulebase can not be found or otherwise invalid */
+ RS_RET_INVLD_MODE = -2311,/**< invalid mode specified in configuration */
+ RS_RET_INVLD_ANON_BITS = -2312,/**< mmanon: invalid number of bits to anonymize specified */
+ RS_RET_REPLCHAR_IGNORED = -2313,/**< mmanon: replacementChar parameter is ignored */
+ RS_RET_SIGPROV_ERR = -2320,/**< error in signature provider */
+ RS_RET_CRYPROV_ERR = -2321,/**< error in cryptography encryption provider */
+ RS_RET_EI_OPN_ERR = -2322,/**< error opening an .encinfo file */
+ RS_RET_EI_NO_EXISTS = -2323,/**< .encinfo file does not exist (status, not necessarily error!)*/
+ RS_RET_EI_WR_ERR = -2324,/**< error writing an .encinfo file */
+ RS_RET_EI_INVLD_FILE = -2325,/**< header indicates the file is no .encinfo file */
+ RS_RET_CRY_INVLD_ALGO = -2326,/**< user specified invalid (unkonwn) crypto algorithm */
+ RS_RET_CRY_INVLD_MODE = -2327,/**< user specified invalid (unkonwn) crypto mode */
+ RS_RET_QUEUE_DISK_NO_FN = -2328,/**< disk queue configured, but filename not set */
+ /* up to 2350 reserved for 7.4 */
+ RS_RET_QUEUE_CRY_DISK_ONLY = -2351,/**< crypto provider only supported for disk-associated queues */
+ RS_RET_NO_DATA = -2352,/**< file has no data; more a state than a real error */
+
+ /* RainerScript error messages (range 1000.. 1999) */
+ RS_RET_SYSVAR_NOT_FOUND = 1001, /**< system variable could not be found (maybe misspelled) */
+ RS_RET_FIELD_NOT_FOUND = 1002, /**< field() function did not find requested field */
+
+ /* some generic error/status codes */
+ RS_RET_OK = 0, /**< operation successful */
+ RS_RET_OK_DELETE_LISTENTRY = 1, /**< operation successful, but callee requested the deletion of an entry (special state) */
+ RS_RET_TERMINATE_NOW = 2, /**< operation successful, function is requested to terminate (mostly used with threads) */
+ RS_RET_NO_RUN = 3, /**< operation successful, but function does not like to be executed */
+ RS_RET_IDLE = 4, /**< operation successful, but callee is idle (e.g. because queue is empty) */
+ RS_RET_TERMINATE_WHEN_IDLE = 5 /**< operation successful, function is requested to terminate when idle */
+};
+
+/* some helpful macros to work with srRetVals.
+ * Be sure to call the to-be-returned variable always "iRet" and
+ * the function finalizer always "finalize_it".
+ */
+#define CHKiRet(code) if((iRet = code) != RS_RET_OK) goto finalize_it
+/* macro below is to be used if we need our own handling, eg for cleanup */
+#define CHKiRet_Hdlr(code) if((iRet = code) != RS_RET_OK)
+/* macro below is to handle failing malloc/calloc/strdup... which we almost always handle in the same way... */
+#define CHKmalloc(operation) if((operation) == NULL) ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY)
+/* macro below is used in conjunction with CHKiRet_Hdlr, else use ABORT_FINALIZE */
+#define FINALIZE goto finalize_it;
+#define DEFiRet BEGINfunc rsRetVal iRet = RS_RET_OK
+#define RETiRet do{ ENDfuncIRet return iRet; }while(0)
+
+#define ABORT_FINALIZE(errCode) \
+ do { \
+ iRet = errCode; \
+ goto finalize_it; \
+ } while (0)
+
+/** Object ID. These are for internal checking. Each
+ * object is assigned a specific ID. This is contained in
+ * all Object structs (just like C++ RTTI). We can use
+ * this field to see if we have been passed a correct ID.
+ * Other than that, there is currently no other use for
+ * the object id.
+ */
+enum rsObjectID
+{
+ OIDrsFreed = -1, /**< assigned, when an object is freed. If this
+ * is seen during a method call, this is an
+ * invalid object pointer!
+ */
+ OIDrsInvalid = 0, /**< value created by calloc(), so do not use ;) */
+ /* The 0x3412 is a debug aid. It helps us find object IDs in memory
+ * dumps (on X86, this is 1234 in the dump ;)
+ * If you are on an embedded device and you would like to save space
+ * make them 1 byte only.
+ */
+ OIDrsCStr = 0x34120001,
+ OIDrsPars = 0x34120002
+};
+typedef enum rsObjectID rsObjID;
+
+/* support to set object types */
+#ifdef NDEBUG
+#define rsSETOBJTYPE(pObj, type)
+#define rsCHECKVALIDOBJECT(x, type)
+#else
+#define rsSETOBJTYPE(pObj, type) pObj->OID = type;
+#define rsCHECKVALIDOBJECT(x, type) {assert(x != NULL); assert(x->OID == type);}
+#endif
+
+/**
+ * This macro should be used to free objects.
+ * It aids in interpreting dumps during debugging.
+ */
+#ifdef NDEBUG
+#define RSFREEOBJ(x) free(x)
+#else
+#define RSFREEOBJ(x) {(x)->OID = OIDrsFreed; free(x);}
+#endif
+
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+extern struct sched_param default_sched_param;
+extern pthread_attr_t default_thread_attr;
+extern int default_thr_sched_policy;
+#endif
+
+
+/* for the time being, we do our own portability handling here. It
+ * looks like autotools either does not yet support checks for it, or
+ * I wasn't smart enough to find them ;) rgerhards, 2007-07-18
+ */
+#ifndef __GNUC__
+# define __attribute__(x) /*NOTHING*/
+#endif
+
+#ifndef O_CLOEXEC
+/* of course, this limits the functionality... */
+# define O_CLOEXEC 0
+#endif
+
+/* some constants */
+#define MUTEX_ALREADY_LOCKED 0
+#define LOCK_MUTEX 1
+
+/* The following prototype is convenient, even though it may not be the 100% correct place.. -- rgerhards 2008-01-07 */
+void dbgprintf(char *, ...) __attribute__((format(printf, 1, 2)));
+
+
+#include "debug.h"
+#include "obj.h"
+
+/* the variable below is a trick: before we can init the runtime, the caller
+ * may want to set a module load path. We can not do this via the glbl class
+ * because it needs an initialized runtime system (and may at some point in time
+ * even be loaded itself). So this is a no-go. What we do is use a single global
+ * variable which may be provided with a pointer by the caller. This variable
+ * resides in rsyslog.c, the main runtime file. We have not seen any realy valule
+ * in providing object access functions. If you don't like that, feel free to
+ * add them. -- rgerhards, 2008-04-17
+ */
+extern uchar *glblModPath; /* module load path */
+extern rsRetVal (*glblErrLogger)(int, uchar*);
+
+/* some runtime prototypes */
+rsRetVal rsrtInit(char **ppErrObj, obj_if_t *pObjIF);
+rsRetVal rsrtExit(void);
+int rsrtIsInit(void);
+rsRetVal rsrtSetErrLogger(rsRetVal (*errLogger)(int, uchar*));
+
+/* this define below is (later) intended to be used to implement empty
+ * structs. TODO: check if compilers supports this and, if not, define
+ * a dummy variable. This requires review of where in code empty structs
+ * are already defined. -- rgerhards, 2010-07-26
+ */
+#define EMPTY_STRUCT
+
+/* TODO: remove this -- this is only for transition of the config system */
+extern rsconf_t *ourConf; /* defined by syslogd.c, a hack for functions that do not
+ yet receive a copy, so that we can incrementially
+ compile and change... -- rgerhars, 2011-04-19 */
+
+#endif /* multi-include protection */
+/* vim:set ai:
+ */
diff --git a/runtime/ruleset.c b/runtime/ruleset.c
new file mode 100644
index 00000000..aacbdf57
--- /dev/null
+++ b/runtime/ruleset.c
@@ -0,0 +1,1051 @@
+/* ruleset.c - rsyslog's ruleset object
+ *
+ * We have a two-way structure of linked lists: one config-specifc linked list
+ * (conf->rulesets.llRulesets) hold alls rule sets that we know. Included in each
+ * list is a list of rules (which contain a list of actions, but that's
+ * a different story).
+ *
+ * Usually, only a single rule set is executed. However, there exist some
+ * situations where all rules must be iterated over, for example on HUP. Thus,
+ * we also provide interfaces to do that.
+ *
+ * Module begun 2009-06-10 by Rainer Gerhards
+ *
+ * Copyright 2009-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <ctype.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "cfsysline.h"
+#include "msg.h"
+#include "ruleset.h"
+#include "errmsg.h"
+#include "parser.h"
+#include "batch.h"
+#include "unicode-helper.h"
+#include "rsconf.h"
+#include "action.h"
+#include "rainerscript.h"
+#include "srUtils.h"
+#include "modules.h"
+#include "dirty.h" /* for main ruleset queue creation */
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(parser)
+
+/* tables for interfacing with the v6 config system (as far as we need to) */
+static struct cnfparamdescr rspdescr[] = {
+ { "name", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "parser", eCmdHdlrArray, 0 }
+};
+static struct cnfparamblk rspblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(rspdescr)/sizeof(struct cnfparamdescr),
+ rspdescr
+ };
+
+/* forward definitions */
+static rsRetVal processBatch(batch_t *pBatch);
+static rsRetVal scriptExec(struct cnfstmt *root, batch_t *pBatch, sbool *active);
+
+
+/* ---------- linked-list key handling functions (ruleset) ---------- */
+
+/* destructor for linked list keys.
+ */
+rsRetVal
+rulesetKeyDestruct(void __attribute__((unused)) *pData)
+{
+ free(pData);
+ return RS_RET_OK;
+}
+/* ---------- END linked-list key handling functions (ruleset) ---------- */
+
+
+/* iterate over all actions in a script (stmt subtree) */
+static void
+scriptIterateAllActions(struct cnfstmt *root, rsRetVal (*pFunc)(void*, void*), void* pParam)
+{
+ struct cnfstmt *stmt;
+ for(stmt = root ; stmt != NULL ; stmt = stmt->next) {
+ switch(stmt->nodetype) {
+ case S_NOP:
+ case S_STOP:
+ case S_CALL:/* call does not need to do anything - done in called ruleset! */
+ break;
+ case S_ACT:
+ DBGPRINTF("iterateAllActions calling into action %p\n", stmt->d.act);
+ pFunc(stmt->d.act, pParam);
+ break;
+ case S_IF:
+ if(stmt->d.s_if.t_then != NULL)
+ scriptIterateAllActions(stmt->d.s_if.t_then,
+ pFunc, pParam);
+ if(stmt->d.s_if.t_else != NULL)
+ scriptIterateAllActions(stmt->d.s_if.t_else,
+ pFunc, pParam);
+ break;
+ case S_PRIFILT:
+ if(stmt->d.s_prifilt.t_then != NULL)
+ scriptIterateAllActions(stmt->d.s_prifilt.t_then,
+ pFunc, pParam);
+ if(stmt->d.s_prifilt.t_else != NULL)
+ scriptIterateAllActions(stmt->d.s_prifilt.t_else,
+ pFunc, pParam);
+ break;
+ case S_PROPFILT:
+ scriptIterateAllActions(stmt->d.s_propfilt.t_then,
+ pFunc, pParam);
+ break;
+ default:
+ dbgprintf("error: unknown stmt type %u during iterateAll\n",
+ (unsigned) stmt->nodetype);
+ break;
+ }
+ }
+}
+
+/* driver to iterate over all of this ruleset actions */
+typedef struct iterateAllActions_s {
+ rsRetVal (*pFunc)(void*, void*);
+ void *pParam;
+} iterateAllActions_t;
+/* driver to iterate over all actions */
+DEFFUNC_llExecFunc(doIterateAllActions)
+{
+ DEFiRet;
+ ruleset_t* pThis = (ruleset_t*) pData;
+ iterateAllActions_t *pMyParam = (iterateAllActions_t*) pParam;
+ scriptIterateAllActions(pThis->root, pMyParam->pFunc, pMyParam->pParam);
+ RETiRet;
+}
+/* iterate over ALL actions present in the WHOLE system.
+ * this is often needed, for example when HUP processing
+ * must be done or a shutdown is pending.
+ */
+static rsRetVal
+iterateAllActions(rsconf_t *conf, rsRetVal (*pFunc)(void*, void*), void* pParam)
+{
+ iterateAllActions_t params;
+ DEFiRet;
+ assert(pFunc != NULL);
+
+ params.pFunc = pFunc;
+ params.pParam = pParam;
+ CHKiRet(llExecFunc(&(conf->rulesets.llRulesets), doIterateAllActions, &params));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function is similar to processBatch(), but works on a batch that
+ * contains rules from multiple rulesets. In this case, we can not push
+ * the whole batch through the ruleset. Instead, we examine it and
+ * partition it into sub-rulesets which we then push through the system.
+ * rgerhards, 2010-06-15
+ */
+static inline rsRetVal
+processBatchMultiRuleset(batch_t *pBatch)
+{
+ ruleset_t *currRuleset;
+ batch_t snglRuleBatch;
+ int i;
+ int iStart; /* start index of partial batch */
+ int iNew; /* index for new (temporary) batch */
+ int bHaveUnprocessed; /* do we (still) have unprocessed entries? (loop term predicate) */
+ DEFiRet;
+
+ do {
+ bHaveUnprocessed = 0;
+ /* search for first unprocessed element */
+ for(iStart = 0 ; iStart < pBatch->nElem && pBatch->eltState[iStart] == BATCH_STATE_DISC ; ++iStart)
+ /* just search, no action */;
+ if(iStart == pBatch->nElem)
+ break; /* everything processed */
+
+ /* prepare temporary batch */
+ CHKiRet(batchInit(&snglRuleBatch, pBatch->nElem));
+ snglRuleBatch.pbShutdownImmediate = pBatch->pbShutdownImmediate;
+ currRuleset = batchElemGetRuleset(pBatch, iStart);
+ iNew = 0;
+ for(i = iStart ; i < pBatch->nElem ; ++i) {
+ if(batchElemGetRuleset(pBatch, i) == currRuleset) {
+ /* for performance reasons, we copy only those members that we actually need */
+ snglRuleBatch.pElem[iNew].pMsg = pBatch->pElem[i].pMsg;
+ snglRuleBatch.eltState[iNew] = pBatch->eltState[i];
+ ++iNew;
+ /* We indicate the element also as done, so it will not be processed again */
+ pBatch->eltState[i] = BATCH_STATE_DISC;
+ } else {
+ bHaveUnprocessed = 1;
+ }
+ }
+ snglRuleBatch.nElem = iNew; /* was left just right by the for loop */
+ batchSetSingleRuleset(&snglRuleBatch, 1);
+ /* process temp batch */
+ processBatch(&snglRuleBatch);
+ batchFree(&snglRuleBatch);
+ } while(bHaveUnprocessed == 1);
+
+finalize_it:
+ RETiRet;
+}
+
+/* return a new "active" structure for the batch. Free with freeActive(). */
+static inline sbool *newActive(batch_t *pBatch)
+{
+ return malloc(sizeof(sbool) * batchNumMsgs(pBatch));
+
+}
+static inline void freeActive(sbool *active) { free(active); }
+
+
+/* for details, see scriptExec() header comment! */
+/* call action for all messages with filter on */
+static rsRetVal
+execAct(struct cnfstmt *stmt, batch_t *pBatch, sbool *active)
+{
+ DEFiRet;
+dbgprintf("RRRR: execAct [%s]: batch of %d elements, active %p\n", modGetName(stmt->d.act->pMod), batchNumMsgs(pBatch), active);
+ pBatch->active = active;
+ stmt->d.act->submitToActQ(stmt->d.act, pBatch);
+ RETiRet;
+}
+
+static rsRetVal
+execSet(struct cnfstmt *stmt, batch_t *pBatch, sbool *active)
+{
+ int i;
+ struct var result;
+ DEFiRet;
+ for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) {
+ if( pBatch->eltState[i] != BATCH_STATE_DISC
+ && (active == NULL || active[i])) {
+ cnfexprEval(stmt->d.s_set.expr, &result, pBatch->pElem[i].pMsg);
+ msgSetJSONFromVar(pBatch->pElem[i].pMsg, stmt->d.s_set.varname,
+ &result);
+ varDelete(&result);
+ }
+ }
+ RETiRet;
+}
+
+static rsRetVal
+execUnset(struct cnfstmt *stmt, batch_t *pBatch, sbool *active)
+{
+ int i;
+ DEFiRet;
+ for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) {
+ if( pBatch->eltState[i] != BATCH_STATE_DISC
+ && (active == NULL || active[i])) {
+ msgUnsetJSON(pBatch->pElem[i].pMsg, stmt->d.s_unset.varname);
+ }
+ }
+ RETiRet;
+}
+
+/* for details, see scriptExec() header comment! */
+/* "stop" simply discards the filtered items - it's just a (hopefully more intuitive
+ * shortcut for users.
+ */
+static rsRetVal
+execStop(batch_t *pBatch, sbool *active)
+{
+ int i;
+ DEFiRet;
+ for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) {
+ if( pBatch->eltState[i] != BATCH_STATE_DISC
+ && (active == NULL || active[i])) {
+ pBatch->eltState[i] = BATCH_STATE_DISC;
+ }
+ }
+ RETiRet;
+}
+
+/* for details, see scriptExec() header comment! */
+// save current filter, evaluate new one
+// perform then (if any message)
+// if ELSE given:
+// set new filter, inverted
+// perform else (if any messages)
+static rsRetVal
+execIf(struct cnfstmt *stmt, batch_t *pBatch, sbool *active)
+{
+ sbool *newAct;
+ int i;
+ sbool bRet;
+ sbool allInactive = 1;
+ DEFiRet;
+ newAct = newActive(pBatch);
+ for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) {
+ if(*(pBatch->pbShutdownImmediate))
+ FINALIZE;
+ if(pBatch->eltState[i] == BATCH_STATE_DISC)
+ continue; /* will be ignored in any case */
+ if(active == NULL || active[i]) {
+ bRet = cnfexprEvalBool(stmt->d.s_if.expr, pBatch->pElem[i].pMsg);
+ allInactive = 0;
+ } else
+ bRet = 0;
+ newAct[i] = bRet;
+ DBGPRINTF("batch: item %d: expr eval: %d\n", i, bRet);
+ }
+
+ if(allInactive) {
+ DBGPRINTF("execIf: all batch elements are inactive, holding execution\n");
+ freeActive(newAct);
+ FINALIZE;
+ }
+
+ if(stmt->d.s_if.t_then != NULL) {
+ scriptExec(stmt->d.s_if.t_then, pBatch, newAct);
+ }
+ if(stmt->d.s_if.t_else != NULL) {
+ for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) {
+ if(*(pBatch->pbShutdownImmediate))
+ FINALIZE;
+ if(pBatch->eltState[i] != BATCH_STATE_DISC
+ && (active == NULL || active[i]))
+ newAct[i] = !newAct[i];
+ }
+ scriptExec(stmt->d.s_if.t_else, pBatch, newAct);
+ }
+ freeActive(newAct);
+finalize_it:
+ RETiRet;
+}
+
+/* for details, see scriptExec() header comment! */
+static void
+execPRIFILT(struct cnfstmt *stmt, batch_t *pBatch, sbool *active)
+{
+ sbool *newAct;
+ msg_t *pMsg;
+ int bRet;
+ int i;
+ newAct = newActive(pBatch);
+ for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) {
+ if(*(pBatch->pbShutdownImmediate))
+ return;
+ if(pBatch->eltState[i] == BATCH_STATE_DISC)
+ continue; /* will be ignored in any case */
+ pMsg = pBatch->pElem[i].pMsg;
+ if(active == NULL || active[i]) {
+ if( (stmt->d.s_prifilt.pmask[pMsg->iFacility] == TABLE_NOPRI) ||
+ ((stmt->d.s_prifilt.pmask[pMsg->iFacility]
+ & (1<<pMsg->iSeverity)) == 0) )
+ bRet = 0;
+ else
+ bRet = 1;
+ } else
+ bRet = 0;
+ newAct[i] = bRet;
+ DBGPRINTF("batch: item %d PRIFILT %d\n", i, newAct[i]);
+ }
+
+ if(stmt->d.s_prifilt.t_then != NULL) {
+ scriptExec(stmt->d.s_prifilt.t_then, pBatch, newAct);
+ }
+ if(stmt->d.s_prifilt.t_else != NULL) {
+ for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) {
+ if(*(pBatch->pbShutdownImmediate))
+ return;
+ if(pBatch->eltState[i] != BATCH_STATE_DISC
+ && (active == NULL || active[i]))
+ newAct[i] = !newAct[i];
+ }
+ scriptExec(stmt->d.s_prifilt.t_else, pBatch, newAct);
+ }
+ freeActive(newAct);
+}
+
+
+/* helper to execPROPFILT(), as the evaluation itself is quite lengthy */
+static int
+evalPROPFILT(struct cnfstmt *stmt, msg_t *pMsg)
+{
+ unsigned short pbMustBeFreed;
+ uchar *pszPropVal;
+ int bRet = 0;
+ rs_size_t propLen;
+
+ if(stmt->d.s_propfilt.propID == PROP_INVALID)
+ goto done;
+
+ pszPropVal = MsgGetProp(pMsg, NULL, stmt->d.s_propfilt.propID,
+ stmt->d.s_propfilt.propName, &propLen,
+ &pbMustBeFreed, NULL);
+
+ /* Now do the compares (short list currently ;)) */
+ switch(stmt->d.s_propfilt.operation ) {
+ case FIOP_CONTAINS:
+ if(rsCStrLocateInSzStr(stmt->d.s_propfilt.pCSCompValue, (uchar*) pszPropVal) != -1)
+ bRet = 1;
+ break;
+ case FIOP_ISEMPTY:
+ if(propLen == 0)
+ bRet = 1; /* process message! */
+ break;
+ case FIOP_ISEQUAL:
+ if(rsCStrSzStrCmp(stmt->d.s_propfilt.pCSCompValue,
+ pszPropVal, propLen) == 0)
+ bRet = 1; /* process message! */
+ break;
+ case FIOP_STARTSWITH:
+ if(rsCStrSzStrStartsWithCStr(stmt->d.s_propfilt.pCSCompValue,
+ pszPropVal, propLen) == 0)
+ bRet = 1; /* process message! */
+ break;
+ case FIOP_REGEX:
+ if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue,
+ (unsigned char*) pszPropVal, 0, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK)
+ bRet = 1;
+ break;
+ case FIOP_EREREGEX:
+ if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue,
+ (unsigned char*) pszPropVal, 1, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK)
+ bRet = 1;
+ break;
+ default:
+ /* here, it handles NOP (for performance reasons) */
+ assert(stmt->d.s_propfilt.operation == FIOP_NOP);
+ bRet = 1; /* as good as any other default ;) */
+ break;
+ }
+
+ /* now check if the value must be negated */
+ if(stmt->d.s_propfilt.isNegated)
+ bRet = (bRet == 1) ? 0 : 1;
+
+ if(Debug) {
+ char *cstr;
+ if(stmt->d.s_propfilt.propID == PROP_CEE) {
+ cstr = es_str2cstr(stmt->d.s_propfilt.propName, NULL);
+ DBGPRINTF("Filter: check for CEE property '%s' (value '%s') ",
+ cstr, pszPropVal);
+ free(cstr);
+ } else {
+ DBGPRINTF("Filter: check for property '%s' (value '%s') ",
+ propIDToName(stmt->d.s_propfilt.propID), pszPropVal);
+ }
+ if(stmt->d.s_propfilt.isNegated)
+ DBGPRINTF("NOT ");
+ if(stmt->d.s_propfilt.operation == FIOP_ISEMPTY) {
+ DBGPRINTF("%s : %s\n",
+ getFIOPName(stmt->d.s_propfilt.operation),
+ bRet ? "TRUE" : "FALSE");
+ } else {
+ DBGPRINTF("%s '%s': %s\n",
+ getFIOPName(stmt->d.s_propfilt.operation),
+ rsCStrGetSzStrNoNULL(stmt->d.s_propfilt.pCSCompValue),
+ bRet ? "TRUE" : "FALSE");
+ }
+ }
+
+ /* cleanup */
+ if(pbMustBeFreed)
+ free(pszPropVal);
+done:
+ return bRet;
+}
+
+/* for details, see scriptExec() header comment! */
+static void
+execPROPFILT(struct cnfstmt *stmt, batch_t *pBatch, sbool *active)
+{
+ sbool *thenAct;
+ sbool bRet;
+ int i;
+ thenAct = newActive(pBatch);
+ for(i = 0 ; i < batchNumMsgs(pBatch) ; ++i) {
+ if(*(pBatch->pbShutdownImmediate))
+ return;
+ if(pBatch->eltState[i] == BATCH_STATE_DISC)
+ continue; /* will be ignored in any case */
+ if(active == NULL || active[i]) {
+ bRet = evalPROPFILT(stmt, pBatch->pElem[i].pMsg);
+ } else
+ bRet = 0;
+ thenAct[i] = bRet;
+ DBGPRINTF("batch: item %d PROPFILT %d\n", i, thenAct[i]);
+ }
+
+ scriptExec(stmt->d.s_propfilt.t_then, pBatch, thenAct);
+ freeActive(thenAct);
+}
+
+/* The rainerscript execution engine. It is debatable if that would be better
+ * contained in grammer/rainerscript.c, HOWEVER, that file focusses primarily
+ * on the parsing and object creation part. So as an actual executor, it is
+ * better suited here.
+ * param active: if NULL, all messages are active (to be processed), if non-null
+ * this is an array of the same size as the batch. If 1, the message
+ * is to be processed, otherwise not.
+ * NOTE: this function must receive batches which contain a single ruleset ONLY!
+ * rgerhards, 2012-09-04
+ */
+static rsRetVal
+scriptExec(struct cnfstmt *root, batch_t *pBatch, sbool *active)
+{
+ DEFiRet;
+ struct cnfstmt *stmt;
+
+ for(stmt = root ; stmt != NULL ; stmt = stmt->next) {
+ if(Debug) {
+ dbgprintf("scriptExec: batch of %d elements, active %p, active[0]:%d\n",
+ batchNumMsgs(pBatch), active, (active == NULL ? 1 : active[0]));
+ cnfstmtPrintOnly(stmt, 2, 0);
+ }
+ switch(stmt->nodetype) {
+ case S_NOP:
+ break;
+ case S_STOP:
+ execStop(pBatch, active);
+ break;
+ case S_ACT:
+ execAct(stmt, pBatch, active);
+ break;
+ case S_SET:
+ execSet(stmt, pBatch, active);
+ break;
+ case S_UNSET:
+ execUnset(stmt, pBatch, active);
+ break;
+ case S_CALL:
+ scriptExec(stmt->d.s_call.stmt, pBatch, active);
+ break;
+ case S_IF:
+ execIf(stmt, pBatch, active);
+ break;
+ case S_PRIFILT:
+ execPRIFILT(stmt, pBatch, active);
+ break;
+ case S_PROPFILT:
+ execPROPFILT(stmt, pBatch, active);
+ break;
+ default:
+ dbgprintf("error: unknown stmt type %u during exec\n",
+ (unsigned) stmt->nodetype);
+ break;
+ }
+ }
+ RETiRet;
+}
+
+
+/* Process (consume) a batch of messages. Calls the actions configured.
+ * If the whole batch uses a singel ruleset, we can process the batch as
+ * a whole. Otherwise, we need to process it slower, on a message-by-message
+ * basis (what can be optimized to a per-ruleset basis)
+ * rgerhards, 2005-10-13
+ */
+static rsRetVal
+processBatch(batch_t *pBatch)
+{
+ ruleset_t *pThis;
+ DEFiRet;
+ assert(pBatch != NULL);
+
+ DBGPRINTF("processBatch: batch of %d elements must be processed\n", pBatch->nElem);
+ if(pBatch->bSingleRuleset) {
+ pThis = batchGetRuleset(pBatch);
+ if(pThis == NULL)
+ pThis = ourConf->rulesets.pDflt;
+ ISOBJ_TYPE_assert(pThis, ruleset);
+ CHKiRet(scriptExec(pThis->root, pBatch, NULL));
+ } else {
+ CHKiRet(processBatchMultiRuleset(pBatch));
+ }
+
+finalize_it:
+ DBGPRINTF("ruleset.ProcessMsg() returns %d\n", iRet);
+ RETiRet;
+}
+
+
+/* return the ruleset-assigned parser list. NULL means use the default
+ * parser list.
+ * rgerhards, 2009-11-04
+ */
+static parserList_t*
+GetParserList(rsconf_t *conf, msg_t *pMsg)
+{
+ return (pMsg->pRuleset == NULL) ? conf->rulesets.pDflt->pParserLst : pMsg->pRuleset->pParserLst;
+}
+
+
+/* Add a script block to the current ruleset */
+static void
+addScript(ruleset_t *pThis, struct cnfstmt *script)
+{
+ if(pThis->last == NULL)
+ pThis->root = pThis->last = script;
+ else {
+ pThis->last->next = script;
+ pThis->last = script;
+ }
+}
+
+
+/* set name for ruleset */
+static rsRetVal rulesetSetName(ruleset_t *pThis, uchar *pszName)
+{
+ DEFiRet;
+ free(pThis->pszName);
+ CHKmalloc(pThis->pszName = ustrdup(pszName));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* get current ruleset
+ * We use a non-standard calling interface, as nothing can go wrong and it
+ * is really much more natural to return the pointer directly.
+ */
+static ruleset_t*
+GetCurrent(rsconf_t *conf)
+{
+ return conf->rulesets.pCurr;
+}
+
+
+/* get main queue associated with ruleset. If no ruleset-specifc main queue
+ * is set, the primary main message queue is returned.
+ * We use a non-standard calling interface, as nothing can go wrong and it
+ * is really much more natural to return the pointer directly.
+ */
+static qqueue_t*
+GetRulesetQueue(ruleset_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, ruleset);
+ return (pThis->pQueue == NULL) ? pMsgQueue : pThis->pQueue;
+}
+
+
+/* Find the ruleset with the given name and return a pointer to its object.
+ */
+rsRetVal
+rulesetGetRuleset(rsconf_t *conf, ruleset_t **ppRuleset, uchar *pszName)
+{
+ DEFiRet;
+ assert(ppRuleset != NULL);
+ assert(pszName != NULL);
+
+ CHKiRet(llFind(&(conf->rulesets.llRulesets), pszName, (void*) ppRuleset));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set a new default rule set. If the default can not be found, no change happens.
+ */
+static rsRetVal
+SetDefaultRuleset(rsconf_t *conf, uchar *pszName)
+{
+ ruleset_t *pRuleset;
+ DEFiRet;
+ assert(pszName != NULL);
+
+ CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName));
+ conf->rulesets.pDflt = pRuleset;
+ DBGPRINTF("default rule set changed to %p: '%s'\n", pRuleset, pszName);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set a new current rule set. If the ruleset can not be found, no change happens */
+static rsRetVal
+SetCurrRuleset(rsconf_t *conf, uchar *pszName)
+{
+ ruleset_t *pRuleset;
+ DEFiRet;
+ assert(pszName != NULL);
+
+ CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName));
+ conf->rulesets.pCurr = pRuleset;
+ DBGPRINTF("current rule set changed to %p: '%s'\n", pRuleset, pszName);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(ruleset) /* be sure to specify the object type also in END macro! */
+ pThis->root = NULL;
+ pThis->last = NULL;
+ENDobjConstruct(ruleset)
+
+
+/* ConstructionFinalizer
+ * This also adds the rule set to the list of all known rulesets.
+ */
+static rsRetVal
+rulesetConstructFinalize(rsconf_t *conf, ruleset_t *pThis)
+{
+ uchar *keyName;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, ruleset);
+
+ /* we must duplicate our name, as the key destructer would also
+ * free it, resulting in a double-free. It's also cleaner to have
+ * two separate copies.
+ */
+ CHKmalloc(keyName = ustrdup(pThis->pszName));
+ CHKiRet(llAppend(&(conf->rulesets.llRulesets), keyName, pThis));
+
+ /* and also the default, if so far none has been set */
+ if(conf->rulesets.pDflt == NULL)
+ conf->rulesets.pDflt = pThis;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* destructor for the ruleset object */
+BEGINobjDestruct(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(ruleset)
+ DBGPRINTF("destructing ruleset %p, name %p\n", pThis, pThis->pszName);
+ if(pThis->pQueue != NULL) {
+ qqueueDestruct(&pThis->pQueue);
+ }
+ if(pThis->pParserLst != NULL) {
+ parser.DestructParserList(&pThis->pParserLst);
+ }
+ free(pThis->pszName);
+ cnfstmtDestruct(pThis->root);
+ENDobjDestruct(ruleset)
+
+
+/* destruct ALL rule sets that reside in the system. This must
+ * be callable before unloading this module as the module may
+ * not be unloaded before unload of the actions is required. This is
+ * kind of a left-over from previous logic and may be optimized one
+ * everything runs stable again. -- rgerhards, 2009-06-10
+ */
+static rsRetVal
+destructAllActions(rsconf_t *conf)
+{
+ DEFiRet;
+
+ CHKiRet(llDestroy(&(conf->rulesets.llRulesets)));
+ CHKiRet(llInit(&(conf->rulesets.llRulesets), rulesetDestructForLinkedList, rulesetKeyDestruct, strcasecmp));
+ conf->rulesets.pDflt = NULL;
+
+finalize_it:
+ RETiRet;
+}
+
+/* this is a special destructor for the linkedList class. LinkedList does NOT
+ * provide a pointer to the pointer, but rather the raw pointer itself. So we
+ * must map this, otherwise the destructor will abort.
+ */
+rsRetVal
+rulesetDestructForLinkedList(void *pData)
+{
+ ruleset_t *pThis = (ruleset_t*) pData;
+ return rulesetDestruct(&pThis);
+}
+
+/* debugprint for the ruleset object */
+BEGINobjDebugPrint(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(ruleset)
+ dbgoprint((obj_t*) pThis, "rsyslog ruleset %s:\n", pThis->pszName);
+ cnfstmtPrint(pThis->root, 0);
+ dbgoprint((obj_t*) pThis, "ruleset %s assigned parser list:\n", pThis->pszName);
+ printParserList(pThis->pParserLst);
+ENDobjDebugPrint(ruleset)
+
+
+/* helper for debugPrintAll(), prints a single ruleset */
+DEFFUNC_llExecFunc(doDebugPrintAll)
+{
+ return rulesetDebugPrint((ruleset_t*) pData);
+}
+/* debug print all rulesets
+ */
+static rsRetVal
+debugPrintAll(rsconf_t *conf)
+{
+ DEFiRet;
+ dbgprintf("All Rulesets:\n");
+ llExecFunc(&(conf->rulesets.llRulesets), doDebugPrintAll, NULL);
+ dbgprintf("End of Rulesets.\n");
+ RETiRet;
+}
+
+static inline void
+rulesetOptimize(ruleset_t *pRuleset)
+{
+ if(Debug) {
+ dbgprintf("ruleset '%s' before optimization:\n",
+ pRuleset->pszName);
+ rulesetDebugPrint((ruleset_t*) pRuleset);
+ }
+ cnfstmtOptimize(pRuleset->root);
+ if(Debug) {
+ dbgprintf("ruleset '%s' after optimization:\n",
+ pRuleset->pszName);
+ rulesetDebugPrint((ruleset_t*) pRuleset);
+ }
+}
+
+/* helper for rulsetOptimizeAll(), optimizes a single ruleset */
+DEFFUNC_llExecFunc(doRulesetOptimizeAll)
+{
+ rulesetOptimize((ruleset_t*) pData);
+ return RS_RET_OK;
+}
+/* optimize all rulesets
+ */
+rsRetVal
+rulesetOptimizeAll(rsconf_t *conf)
+{
+ DEFiRet;
+ dbgprintf("begin ruleset optimization phase\n");
+ llExecFunc(&(conf->rulesets.llRulesets), doRulesetOptimizeAll, NULL);
+ dbgprintf("ruleset optimization phase finished.\n");
+ RETiRet;
+}
+
+
+/* Create a ruleset-specific "main" queue for this ruleset. If one is already
+ * defined, an error message is emitted but nothing else is done.
+ * Note: we use the main message queue parameters for queue creation and access
+ * syslogd.c directly to obtain these. This is far from being perfect, but
+ * considered acceptable for the time being.
+ * rgerhards, 2009-10-27
+ */
+static inline rsRetVal
+doRulesetCreateQueue(rsconf_t *conf, int *pNewVal)
+{
+ uchar *rsname;
+ DEFiRet;
+
+ if(conf->rulesets.pCurr == NULL) {
+ errmsg.LogError(0, RS_RET_NO_CURR_RULESET, "error: currently no specific ruleset specified, thus a "
+ "queue can not be added to it");
+ ABORT_FINALIZE(RS_RET_NO_CURR_RULESET);
+ }
+
+ if(conf->rulesets.pCurr->pQueue != NULL) {
+ errmsg.LogError(0, RS_RET_RULES_QUEUE_EXISTS, "error: ruleset already has a main queue, can not "
+ "add another one");
+ ABORT_FINALIZE(RS_RET_RULES_QUEUE_EXISTS);
+ }
+
+ if(pNewVal == 0)
+ FINALIZE; /* if it is turned off, we do not need to change anything ;) */
+
+ rsname = (conf->rulesets.pCurr->pszName == NULL) ? (uchar*) "[ruleset]" : conf->rulesets.pCurr->pszName;
+ DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s'\n", rsname);
+ CHKiRet(createMainQueue(&conf->rulesets.pCurr->pQueue, rsname, NULL));
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+rulesetCreateQueue(void __attribute__((unused)) *pVal, int *pNewVal)
+{
+ return doRulesetCreateQueue(ourConf, pNewVal);
+}
+
+/* Add a ruleset specific parser to the ruleset. Note that adding the first
+ * parser automatically disables the default parsers. If they are needed as well,
+ * the must be added via explicit config directives.
+ * Note: this is the only spot in the code that requires the parser object. In order
+ * to solve some class init bootstrap sequence problems, we get the object handle here
+ * instead of during module initialization. Note that objUse() is capable of being
+ * called multiple times.
+ * rgerhards, 2009-11-04
+ */
+static rsRetVal
+doRulesetAddParser(ruleset_t *pRuleset, uchar *pName)
+{
+ parser_t *pParser;
+ DEFiRet;
+
+ CHKiRet(objUse(parser, CORE_COMPONENT));
+ iRet = parser.FindParser(&pParser, pName);
+ if(iRet == RS_RET_PARSER_NOT_FOUND) {
+ errmsg.LogError(0, RS_RET_PARSER_NOT_FOUND, "error: parser '%s' unknown at this time "
+ "(maybe defined too late in rsyslog.conf?)", pName);
+ ABORT_FINALIZE(RS_RET_NO_CURR_RULESET);
+ } else if(iRet != RS_RET_OK) {
+ errmsg.LogError(0, iRet, "error trying to find parser '%s'\n", pName);
+ FINALIZE;
+ }
+
+ CHKiRet(parser.AddParserToList(&pRuleset->pParserLst, pParser));
+
+ DBGPRINTF("added parser '%s' to ruleset '%s'\n", pName, pRuleset->pszName);
+
+finalize_it:
+ d_free(pName); /* no longer needed */
+
+ RETiRet;
+}
+
+static rsRetVal
+rulesetAddParser(void __attribute__((unused)) *pVal, uchar *pName)
+{
+ return doRulesetAddParser(ourConf->rulesets.pCurr, pName);
+}
+
+
+/* Process ruleset() objects */
+rsRetVal
+rulesetProcessCnf(struct cnfobj *o)
+{
+ struct cnfparamvals *pvals;
+ rsRetVal localRet;
+ uchar *rsName = NULL;
+ uchar *parserName;
+ int nameIdx, parserIdx;
+ ruleset_t *pRuleset;
+ struct cnfarray *ar;
+ int i;
+ uchar *rsname;
+ DEFiRet;
+
+ pvals = nvlstGetParams(o->nvlst, &rspblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
+ }
+ DBGPRINTF("ruleset param blk after rulesetProcessCnf:\n");
+ cnfparamsPrint(&rspblk, pvals);
+ nameIdx = cnfparamGetIdx(&rspblk, "name");
+ rsName = (uchar*)es_str2cstr(pvals[nameIdx].val.d.estr, NULL);
+ localRet = rulesetGetRuleset(loadConf, &pRuleset, rsName);
+ if(localRet == RS_RET_OK) {
+ errmsg.LogError(0, RS_RET_RULESET_EXISTS,
+ "error: ruleset '%s' specified more than once",
+ rsName);
+ cnfstmtDestruct(o->script);
+ ABORT_FINALIZE(RS_RET_RULESET_EXISTS);
+ } else if(localRet != RS_RET_NOT_FOUND) {
+ ABORT_FINALIZE(localRet);
+ }
+ CHKiRet(rulesetConstruct(&pRuleset));
+ CHKiRet(rulesetSetName(pRuleset, rsName));
+ CHKiRet(rulesetConstructFinalize(loadConf, pRuleset));
+ addScript(pRuleset, o->script);
+
+ /* we have only two params, so we do NOT do the usual param loop */
+ parserIdx = cnfparamGetIdx(&rspblk, "parser");
+ if(parserIdx != -1 && pvals[parserIdx].bUsed) {
+ ar = pvals[parserIdx].val.d.ar;
+ for(i = 0 ; i < ar->nmemb ; ++i) {
+ parserName = (uchar*)es_str2cstr(ar->arr[i], NULL);
+ doRulesetAddParser(pRuleset, parserName);
+ free(parserName);
+ }
+ }
+
+ /* pick up ruleset queue parameters */
+ if(queueCnfParamsSet(o->nvlst)) {
+ rsname = (pRuleset->pszName == NULL) ? (uchar*) "[ruleset]" : pRuleset->pszName;
+ DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s'\n", rsname);
+ CHKiRet(createMainQueue(&pRuleset->pQueue, rsname, o->nvlst));
+ }
+
+finalize_it:
+ free(rsName);
+ cnfparamvalsDestruct(pvals, &rspblk);
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-21
+ */
+BEGINobjQueryInterface(ruleset)
+CODESTARTobjQueryInterface(ruleset)
+ if(pIf->ifVersion != rulesetCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = rulesetConstruct;
+ pIf->ConstructFinalize = rulesetConstructFinalize;
+ pIf->Destruct = rulesetDestruct;
+ pIf->DebugPrint = rulesetDebugPrint;
+
+ pIf->IterateAllActions = iterateAllActions;
+ pIf->DestructAllActions = destructAllActions;
+ pIf->AddScript = addScript;
+ pIf->ProcessBatch = processBatch;
+ pIf->SetName = rulesetSetName;
+ pIf->DebugPrintAll = debugPrintAll;
+ pIf->GetCurrent = GetCurrent;
+ pIf->GetRuleset = rulesetGetRuleset;
+ pIf->SetDefaultRuleset = SetDefaultRuleset;
+ pIf->SetCurrRuleset = SetCurrRuleset;
+ pIf->GetRulesetQueue = GetRulesetQueue;
+ pIf->GetParserList = GetParserList;
+finalize_it:
+ENDobjQueryInterface(ruleset)
+
+
+/* Exit the ruleset class.
+ * rgerhards, 2009-04-06
+ */
+BEGINObjClassExit(ruleset, OBJ_IS_CORE_MODULE) /* class, version */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(parser, CORE_COMPONENT);
+ENDObjClassExit(ruleset)
+
+
+/* Initialize the ruleset class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(ruleset, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, rulesetDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, rulesetConstructFinalize);
+
+ /* config file handlers */
+ CHKiRet(regCfSysLineHdlr((uchar *)"rulesetparser", 0, eCmdHdlrGetWord, rulesetAddParser, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"rulesetcreatemainqueue", 0, eCmdHdlrBinary, rulesetCreateQueue, NULL, NULL));
+ENDObjClassInit(ruleset)
+
+/* vi:set ai:
+ */
diff --git a/runtime/ruleset.h b/runtime/ruleset.h
new file mode 100644
index 00000000..cbf8243b
--- /dev/null
+++ b/runtime/ruleset.h
@@ -0,0 +1,106 @@
+/* The ruleset object.
+ *
+ * This implements rulesets within rsyslog.
+ *
+ * Copyright 2009-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_RULESET_H
+#define INCLUDED_RULESET_H
+
+#include "queue.h"
+#include "linkedlist.h"
+#include "rsconf.h"
+
+/* the ruleset object */
+struct ruleset_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ uchar *pszName; /* name of our ruleset */
+ qqueue_t *pQueue; /* "main" message queue, if the ruleset has its own (else NULL) */
+ struct cnfstmt *root;
+ struct cnfstmt *last;
+ parserList_t *pParserLst;/* list of parsers to use for this ruleset */
+};
+
+/* interfaces */
+BEGINinterface(ruleset) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(ruleset);
+ rsRetVal (*DebugPrintAll)(rsconf_t *conf);
+ rsRetVal (*Construct)(ruleset_t **ppThis);
+ rsRetVal (*ConstructFinalize)(rsconf_t *conf, ruleset_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(ruleset_t **ppThis);
+ rsRetVal (*DestructAllActions)(rsconf_t *conf);
+ rsRetVal (*SetName)(ruleset_t *pThis, uchar *pszName);
+ rsRetVal (*ProcessBatch)(batch_t*);
+ rsRetVal (*GetRuleset)(rsconf_t *conf, ruleset_t **ppThis, uchar*);
+ rsRetVal (*SetDefaultRuleset)(rsconf_t *conf, uchar*);
+ rsRetVal (*SetCurrRuleset)(rsconf_t *conf, uchar*);
+ ruleset_t* (*GetCurrent)(rsconf_t *conf);
+ qqueue_t* (*GetRulesetQueue)(ruleset_t*);
+ /* v3, 2009-11-04 */
+ parserList_t* (*GetParserList)(rsconf_t *conf, msg_t *);
+ /* v5, 2011-04-19
+ * added support for the rsconf object -- fundamental change
+ * v6, 2011-07-15
+ * removed conf ptr from SetName, AddRule as the flex/bison based
+ * system uses globals in any case.
+ */
+ /* v7, 2012-09-04 */
+ /* AddRule() removed */
+ /*TODO:REMOVE*/rsRetVal (*IterateAllActions)(rsconf_t *conf, rsRetVal (*pFunc)(void*, void*), void* pParam);
+ void (*AddScript)(ruleset_t *pThis, struct cnfstmt *script);
+ENDinterface(ruleset)
+#define rulesetCURR_IF_VERSION 7 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+PROTOTYPEObj(ruleset);
+
+/* TODO: remove these -- currently done dirty for config file
+ * redo -- rgerhards, 2011-04-19
+ * rgerhards, 2012-04-19: actually, it may be way cooler not to remove
+ * them and use plain c-style conventions at least inside core objects.
+ */
+rsRetVal rulesetDestructForLinkedList(void *pData);
+rsRetVal rulesetKeyDestruct(void __attribute__((unused)) *pData);
+
+/* Get name associated to ruleset. This function cannot fail (except,
+ * of course, if previously something went really wrong). Returned
+ * pointer is read-only.
+ * rgerhards, 2012-04-18
+ */
+static inline uchar*
+rulesetGetName(ruleset_t *pRuleset)
+{
+ return pRuleset->pszName;
+}
+
+
+/* we will most probably convert this module back to traditional C
+ * calling sequence, so here we go...
+ */
+rsRetVal rulesetGetRuleset(rsconf_t *conf, ruleset_t **ppRuleset, uchar *pszName);
+rsRetVal rulesetOptimizeAll(rsconf_t *conf);
+rsRetVal rulesetProcessCnf(struct cnfobj *o);
+
+/* Set a current rule set to already-known pointer */
+static inline void
+rulesetSetCurrRulesetPtr(ruleset_t *pRuleset) {
+ loadConf->rulesets.pCurr = pRuleset;
+}
+#endif /* #ifndef INCLUDED_RULESET_H */
diff --git a/runtime/sd-daemon.c b/runtime/sd-daemon.c
new file mode 100644
index 00000000..79d8ca37
--- /dev/null
+++ b/runtime/sd-daemon.c
@@ -0,0 +1,533 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#ifndef _GNU_SOURCE
+# define _GNU_SOURCE
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#ifdef __BIONIC__
+# include <linux/fcntl.h>
+#else
+# include <sys/fcntl.h>
+#endif
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <limits.h>
+
+#if defined(__linux__)
+# include <mqueue.h>
+#endif
+
+#include "sd-daemon.h"
+
+#if (__GNUC__ >= 4)
+# ifdef SD_EXPORT_SYMBOLS
+/* Export symbols */
+# define _sd_export_ __attribute__ ((visibility("default")))
+# else
+/* Don't export the symbols */
+# define _sd_export_ __attribute__ ((visibility("hidden")))
+# endif
+#else
+# define _sd_export_
+#endif
+
+_sd_export_ int sd_listen_fds(int unset_environment) {
+
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ int r, fd;
+ const char *e;
+ char *p = NULL;
+ unsigned long l;
+
+ e = getenv("LISTEN_PID");
+ if (!e) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || p == e || *p || l <= 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ /* Is this for us? */
+ if (getpid() != (pid_t) l) {
+ r = 0;
+ goto finish;
+ }
+
+ e = getenv("LISTEN_FDS");
+ if (!e) {
+ r = 0;
+ goto finish;
+ }
+
+ errno = 0;
+ l = strtoul(e, &p, 10);
+
+ if (errno != 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (!p || p == e || *p) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + (int) l; fd ++) {
+ int flags;
+
+ flags = fcntl(fd, F_GETFD);
+ if (flags < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ if (flags & FD_CLOEXEC)
+ continue;
+
+ if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) < 0) {
+ r = -errno;
+ goto finish;
+ }
+ }
+
+ r = (int) l;
+
+finish:
+ if (unset_environment) {
+ unsetenv("LISTEN_PID");
+ unsetenv("LISTEN_FDS");
+ }
+
+ return r;
+#endif
+}
+
+_sd_export_ int sd_is_fifo(int fd, const char *path) {
+ struct stat st_fd;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISFIFO(st_fd.st_mode))
+ return 0;
+
+ if (path) {
+ struct stat st_path;
+
+ if (stat(path, &st_path) < 0) {
+
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ return -errno;
+ }
+
+ return
+ st_path.st_dev == st_fd.st_dev &&
+ st_path.st_ino == st_fd.st_ino;
+ }
+
+ return 1;
+}
+
+_sd_export_ int sd_is_special(int fd, const char *path) {
+ struct stat st_fd;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISREG(st_fd.st_mode) && !S_ISCHR(st_fd.st_mode))
+ return 0;
+
+ if (path) {
+ struct stat st_path;
+
+ if (stat(path, &st_path) < 0) {
+
+ if (errno == ENOENT || errno == ENOTDIR)
+ return 0;
+
+ return -errno;
+ }
+
+ if (S_ISREG(st_fd.st_mode) && S_ISREG(st_path.st_mode))
+ return
+ st_path.st_dev == st_fd.st_dev &&
+ st_path.st_ino == st_fd.st_ino;
+ else if (S_ISCHR(st_fd.st_mode) && S_ISCHR(st_path.st_mode))
+ return st_path.st_rdev == st_fd.st_rdev;
+ else
+ return 0;
+ }
+
+ return 1;
+}
+
+static int sd_is_socket_internal(int fd, int type, int listening) {
+ struct stat st_fd;
+
+ if (fd < 0 || type < 0)
+ return -EINVAL;
+
+ if (fstat(fd, &st_fd) < 0)
+ return -errno;
+
+ if (!S_ISSOCK(st_fd.st_mode))
+ return 0;
+
+ if (type != 0) {
+ int other_type = 0;
+ socklen_t l = sizeof(other_type);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &other_type, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(other_type))
+ return -EINVAL;
+
+ if (other_type != type)
+ return 0;
+ }
+
+ if (listening >= 0) {
+ int accepting = 0;
+ socklen_t l = sizeof(accepting);
+
+ if (getsockopt(fd, SOL_SOCKET, SO_ACCEPTCONN, &accepting, &l) < 0)
+ return -errno;
+
+ if (l != sizeof(accepting))
+ return -EINVAL;
+
+ if (!accepting != !listening)
+ return 0;
+ }
+
+ return 1;
+}
+
+union sockaddr_union {
+ struct sockaddr sa;
+ struct sockaddr_in in4;
+ struct sockaddr_in6 in6;
+ struct sockaddr_un un;
+ struct sockaddr_storage storage;
+};
+
+_sd_export_ int sd_is_socket(int fd, int family, int type, int listening) {
+ int r;
+
+ if (family < 0)
+ return -EINVAL;
+
+ r = sd_is_socket_internal(fd, type, listening);
+ if (r <= 0)
+ return r;
+
+ if (family > 0) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ return sockaddr.sa.sa_family == family;
+ }
+
+ return 1;
+}
+
+_sd_export_ int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ if (family != 0 && family != AF_INET && family != AF_INET6)
+ return -EINVAL;
+
+ r = sd_is_socket_internal(fd, type, listening);
+ if (r <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_INET &&
+ sockaddr.sa.sa_family != AF_INET6)
+ return 0;
+
+ if (family > 0)
+ if (sockaddr.sa.sa_family != family)
+ return 0;
+
+ if (port > 0) {
+ if (sockaddr.sa.sa_family == AF_INET) {
+ if (l < sizeof(struct sockaddr_in))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in4.sin_port;
+ } else {
+ if (l < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+
+ return htons(port) == sockaddr.in6.sin6_port;
+ }
+ }
+
+ return 1;
+}
+
+_sd_export_ int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length) {
+ union sockaddr_union sockaddr;
+ socklen_t l;
+ int r;
+
+ r = sd_is_socket_internal(fd, type, listening);
+ if (r <= 0)
+ return r;
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ l = sizeof(sockaddr);
+
+ if (getsockname(fd, &sockaddr.sa, &l) < 0)
+ return -errno;
+
+ if (l < sizeof(sa_family_t))
+ return -EINVAL;
+
+ if (sockaddr.sa.sa_family != AF_UNIX)
+ return 0;
+
+ if (path) {
+ if (length == 0)
+ length = strlen(path);
+
+ if (length == 0)
+ /* Unnamed socket */
+ return l == offsetof(struct sockaddr_un, sun_path);
+
+ if (path[0])
+ /* Normal path socket */
+ return
+ (l >= offsetof(struct sockaddr_un, sun_path) + length + 1) &&
+ memcmp(path, sockaddr.un.sun_path, length+1) == 0;
+ else
+ /* Abstract namespace socket */
+ return
+ (l == offsetof(struct sockaddr_un, sun_path) + length) &&
+ memcmp(path, sockaddr.un.sun_path, length) == 0;
+ }
+
+ return 1;
+}
+
+_sd_export_ int sd_is_mq(int fd, const char *path) {
+#if !defined(__linux__)
+ return 0;
+#else
+ struct mq_attr attr;
+
+ if (fd < 0)
+ return -EINVAL;
+
+ if (mq_getattr(fd, &attr) < 0)
+ return -errno;
+
+ if (path) {
+ char fpath[PATH_MAX];
+ struct stat a, b;
+
+ if (path[0] != '/')
+ return -EINVAL;
+
+ if (fstat(fd, &a) < 0)
+ return -errno;
+
+ strncpy(stpcpy(fpath, "/dev/mqueue"), path, sizeof(fpath) - 12);
+ fpath[sizeof(fpath)-1] = 0;
+
+ if (stat(fpath, &b) < 0)
+ return -errno;
+
+ if (a.st_dev != b.st_dev ||
+ a.st_ino != b.st_ino)
+ return 0;
+ }
+
+ return 1;
+#endif
+}
+
+_sd_export_ int sd_notify(int unset_environment, const char *state) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__) || !defined(SOCK_CLOEXEC)
+ return 0;
+#else
+ int fd = -1, r;
+ struct msghdr msghdr;
+ struct iovec iovec;
+ union sockaddr_union sockaddr;
+ const char *e;
+
+ if (!state) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ e = getenv("NOTIFY_SOCKET");
+ if (!e)
+ return 0;
+
+ /* Must be an abstract socket, or an absolute path */
+ if ((e[0] != '@' && e[0] != '/') || e[1] == 0) {
+ r = -EINVAL;
+ goto finish;
+ }
+
+ fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0);
+ if (fd < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ memset(&sockaddr, 0, sizeof(sockaddr));
+ sockaddr.sa.sa_family = AF_UNIX;
+ strncpy(sockaddr.un.sun_path, e, sizeof(sockaddr.un.sun_path));
+
+ if (sockaddr.un.sun_path[0] == '@')
+ sockaddr.un.sun_path[0] = 0;
+
+ memset(&iovec, 0, sizeof(iovec));
+ iovec.iov_base = (char*) state;
+ iovec.iov_len = strlen(state);
+
+ memset(&msghdr, 0, sizeof(msghdr));
+ msghdr.msg_name = &sockaddr;
+ msghdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(e);
+
+ if (msghdr.msg_namelen > sizeof(struct sockaddr_un))
+ msghdr.msg_namelen = sizeof(struct sockaddr_un);
+
+ msghdr.msg_iov = &iovec;
+ msghdr.msg_iovlen = 1;
+
+ if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) {
+ r = -errno;
+ goto finish;
+ }
+
+ r = 1;
+
+finish:
+ if (unset_environment)
+ unsetenv("NOTIFY_SOCKET");
+
+ if (fd >= 0)
+ close(fd);
+
+ return r;
+#endif
+}
+
+_sd_export_ int sd_notifyf(int unset_environment, const char *format, ...) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ va_list ap;
+ char *p = NULL;
+ int r;
+
+ va_start(ap, format);
+ r = vasprintf(&p, format, ap);
+ va_end(ap);
+
+ if (r < 0 || !p)
+ return -ENOMEM;
+
+ r = sd_notify(unset_environment, p);
+ free(p);
+
+ return r;
+#endif
+}
+
+_sd_export_ int sd_booted(void) {
+#if defined(DISABLE_SYSTEMD) || !defined(__linux__)
+ return 0;
+#else
+ struct stat st;
+
+ /* We test whether the runtime unit file directory has been
+ * created. This takes place in mount-setup.c, so is
+ * guaranteed to happen very early during boot. */
+
+ if (lstat("/run/systemd/system/", &st) < 0)
+ return 0;
+
+ return !!S_ISDIR(st.st_mode);
+#endif
+}
diff --git a/runtime/sd-daemon.h b/runtime/sd-daemon.h
new file mode 100644
index 00000000..fb7456d5
--- /dev/null
+++ b/runtime/sd-daemon.h
@@ -0,0 +1,282 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddaemonhfoo
+#define foosddaemonhfoo
+
+/***
+ Copyright 2010 Lennart Poettering
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+***/
+
+#include <sys/types.h>
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ Reference implementation of a few systemd related interfaces for
+ writing daemons. These interfaces are trivial to implement. To
+ simplify porting we provide this reference implementation.
+ Applications are welcome to reimplement the algorithms described
+ here if they do not want to include these two source files.
+
+ The following functionality is provided:
+
+ - Support for logging with log levels on stderr
+ - File descriptor passing for socket-based activation
+ - Daemon startup and status notification
+ - Detection of systemd boots
+
+ You may compile this with -DDISABLE_SYSTEMD to disable systemd
+ support. This makes all those calls NOPs that are directly related to
+ systemd (i.e. only sd_is_xxx() will stay useful).
+
+ Since this is drop-in code we don't want any of our symbols to be
+ exported in any case. Hence we declare hidden visibility for all of
+ them.
+
+ You may find an up-to-date version of these source files online:
+
+ http://cgit.freedesktop.org/systemd/systemd/plain/src/systemd/sd-daemon.h
+ http://cgit.freedesktop.org/systemd/systemd/plain/src/libsystemd-daemon/sd-daemon.c
+
+ This should compile on non-Linux systems, too, but with the
+ exception of the sd_is_xxx() calls all functions will become NOPs.
+
+ See sd-daemon(3) for more information.
+*/
+
+#ifndef _sd_printf_attr_
+#if __GNUC__ >= 4
+#define _sd_printf_attr_(a,b) __attribute__ ((format (printf, a, b)))
+#else
+#define _sd_printf_attr_(a,b)
+#endif
+#endif
+
+/*
+ Log levels for usage on stderr:
+
+ fprintf(stderr, SD_NOTICE "Hello World!\n");
+
+ This is similar to printk() usage in the kernel.
+*/
+#define SD_EMERG "<0>" /* system is unusable */
+#define SD_ALERT "<1>" /* action must be taken immediately */
+#define SD_CRIT "<2>" /* critical conditions */
+#define SD_ERR "<3>" /* error conditions */
+#define SD_WARNING "<4>" /* warning conditions */
+#define SD_NOTICE "<5>" /* normal but significant condition */
+#define SD_INFO "<6>" /* informational */
+#define SD_DEBUG "<7>" /* debug-level messages */
+
+/* The first passed file descriptor is fd 3 */
+#define SD_LISTEN_FDS_START 3
+
+/*
+ Returns how many file descriptors have been passed, or a negative
+ errno code on failure. Optionally, removes the $LISTEN_FDS and
+ $LISTEN_PID file descriptors from the environment (recommended, but
+ problematic in threaded environments). If r is the return value of
+ this function you'll find the file descriptors passed as fds
+ SD_LISTEN_FDS_START to SD_LISTEN_FDS_START+r-1. Returns a negative
+ errno style error code on failure. This function call ensures that
+ the FD_CLOEXEC flag is set for the passed file descriptors, to make
+ sure they are not passed on to child processes. If FD_CLOEXEC shall
+ not be set, the caller needs to unset it after this call for all file
+ descriptors that are used.
+
+ See sd_listen_fds(3) for more information.
+*/
+int sd_listen_fds(int unset_environment);
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a FIFO in the file system stored under the
+ specified path, 0 otherwise. If path is NULL a path name check will
+ not be done and the call only verifies if the file descriptor
+ refers to a FIFO. Returns a negative errno style error code on
+ failure.
+
+ See sd_is_fifo(3) for more information.
+*/
+int sd_is_fifo(int fd, const char *path);
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a special character device on the file
+ system stored under the specified path, 0 otherwise.
+ If path is NULL a path name check will not be done and the call
+ only verifies if the file descriptor refers to a special character.
+ Returns a negative errno style error code on failure.
+
+ See sd_is_special(3) for more information.
+*/
+int sd_is_special(int fd, const char *path);
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a socket of the specified family (AF_INET,
+ ...) and type (SOCK_DGRAM, SOCK_STREAM, ...), 0 otherwise. If
+ family is 0 a socket family check will not be done. If type is 0 a
+ socket type check will not be done and the call only verifies if
+ the file descriptor refers to a socket. If listening is > 0 it is
+ verified that the socket is in listening mode. (i.e. listen() has
+ been called) If listening is == 0 it is verified that the socket is
+ not in listening mode. If listening is < 0 no listening mode check
+ is done. Returns a negative errno style error code on failure.
+
+ See sd_is_socket(3) for more information.
+*/
+int sd_is_socket(int fd, int family, int type, int listening);
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an Internet socket, of the specified family
+ (either AF_INET or AF_INET6) and the specified type (SOCK_DGRAM,
+ SOCK_STREAM, ...), 0 otherwise. If version is 0 a protocol version
+ check is not done. If type is 0 a socket type check will not be
+ done. If port is 0 a socket port check will not be done. The
+ listening flag is used the same way as in sd_is_socket(). Returns a
+ negative errno style error code on failure.
+
+ See sd_is_socket_inet(3) for more information.
+*/
+int sd_is_socket_inet(int fd, int family, int type, int listening, uint16_t port);
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is an AF_UNIX socket of the specified type
+ (SOCK_DGRAM, SOCK_STREAM, ...) and path, 0 otherwise. If type is 0
+ a socket type check will not be done. If path is NULL a socket path
+ check will not be done. For normal AF_UNIX sockets set length to
+ 0. For abstract namespace sockets set length to the length of the
+ socket name (including the initial 0 byte), and pass the full
+ socket path in path (including the initial 0 byte). The listening
+ flag is used the same way as in sd_is_socket(). Returns a negative
+ errno style error code on failure.
+
+ See sd_is_socket_unix(3) for more information.
+*/
+int sd_is_socket_unix(int fd, int type, int listening, const char *path, size_t length);
+
+/*
+ Helper call for identifying a passed file descriptor. Returns 1 if
+ the file descriptor is a POSIX Message Queue of the specified name,
+ 0 otherwise. If path is NULL a message queue name check is not
+ done. Returns a negative errno style error code on failure.
+*/
+int sd_is_mq(int fd, const char *path);
+
+/*
+ Informs systemd about changed daemon state. This takes a number of
+ newline separated environment-style variable assignments in a
+ string. The following variables are known:
+
+ READY=1 Tells systemd that daemon startup is finished (only
+ relevant for services of Type=notify). The passed
+ argument is a boolean "1" or "0". Since there is
+ little value in signaling non-readiness the only
+ value daemons should send is "READY=1".
+
+ STATUS=... Passes a single-line status string back to systemd
+ that describes the daemon state. This is free-from
+ and can be used for various purposes: general state
+ feedback, fsck-like programs could pass completion
+ percentages and failing programs could pass a human
+ readable error message. Example: "STATUS=Completed
+ 66% of file system check..."
+
+ ERRNO=... If a daemon fails, the errno-style error code,
+ formatted as string. Example: "ERRNO=2" for ENOENT.
+
+ BUSERROR=... If a daemon fails, the D-Bus error-style error
+ code. Example: "BUSERROR=org.freedesktop.DBus.Error.TimedOut"
+
+ MAINPID=... The main pid of a daemon, in case systemd did not
+ fork off the process itself. Example: "MAINPID=4711"
+
+ WATCHDOG=1 Tells systemd to update the watchdog timestamp.
+ Services using this feature should do this in
+ regular intervals. A watchdog framework can use the
+ timestamps to detect failed services.
+
+ Daemons can choose to send additional variables. However, it is
+ recommended to prefix variable names not listed above with X_.
+
+ Returns a negative errno-style error code on failure. Returns > 0
+ if systemd could be notified, 0 if it couldn't possibly because
+ systemd is not running.
+
+ Example: When a daemon finished starting up, it could issue this
+ call to notify systemd about it:
+
+ sd_notify(0, "READY=1");
+
+ See sd_notifyf() for more complete examples.
+
+ See sd_notify(3) for more information.
+*/
+int sd_notify(int unset_environment, const char *state);
+
+/*
+ Similar to sd_notify() but takes a format string.
+
+ Example 1: A daemon could send the following after initialization:
+
+ sd_notifyf(0, "READY=1\n"
+ "STATUS=Processing requests...\n"
+ "MAINPID=%lu",
+ (unsigned long) getpid());
+
+ Example 2: A daemon could send the following shortly before
+ exiting, on failure:
+
+ sd_notifyf(0, "STATUS=Failed to start up: %s\n"
+ "ERRNO=%i",
+ strerror(errno),
+ errno);
+
+ See sd_notifyf(3) for more information.
+*/
+int sd_notifyf(int unset_environment, const char *format, ...) _sd_printf_attr_(2,3);
+
+/*
+ Returns > 0 if the system was booted with systemd. Returns < 0 on
+ error. Returns 0 if the system was not booted with systemd. Note
+ that all of the functions above handle non-systemd boots just
+ fine. You should NOT protect them with a call to this function. Also
+ note that this function checks whether the system, not the user
+ session is controlled by systemd. However the functions above work
+ for both user and system services.
+
+ See sd_booted(3) for more information.
+*/
+int sd_booted(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/runtime/sigprov.h b/runtime/sigprov.h
new file mode 100644
index 00000000..82587b7d
--- /dev/null
+++ b/runtime/sigprov.h
@@ -0,0 +1,37 @@
+/* The interface definition for (file) signature providers.
+ *
+ * This is just an abstract driver interface, which needs to be
+ * implemented by concrete classes.
+ *
+ * Copyright 2013 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_SIGPROV_H
+#define INCLUDED_SIGPROV_H
+
+/* interface */
+BEGINinterface(sigprov) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(void *ppThis);
+ rsRetVal (*SetCnfParam)(void *ppThis, struct nvlst *lst);
+ rsRetVal (*Destruct)(void *ppThis);
+ rsRetVal (*OnFileOpen)(void *pThis, uchar *fn, void *pFileInstData);
+ rsRetVal (*OnRecordWrite)(void *pFileInstData, uchar *rec, rs_size_t lenRec);
+ rsRetVal (*OnFileClose)(void *pFileInstData);
+ENDinterface(sigprov)
+#define sigprovCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+#endif /* #ifndef INCLUDED_SIGPROV_H */
diff --git a/runtime/srUtils.h b/runtime/srUtils.h
new file mode 100644
index 00000000..8626a4bb
--- /dev/null
+++ b/runtime/srUtils.h
@@ -0,0 +1,112 @@
+/*! \file srUtils.h
+ * \brief General, small utilities that fit nowhere else.
+ *
+ * \author Rainer Gerhards <rgerhards@adiscon.com>
+ * \date 2003-09-09
+ * Coding begun.
+ *
+ * Copyright 2003-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef __SRUTILS_H_INCLUDED__
+#define __SRUTILS_H_INCLUDED__ 1
+
+
+/* syslog names */
+#ifndef LOG_MAKEPRI
+# define LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri))
+#endif
+#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */
+#define TABLE_NOPRI 0 /* Value to indicate no priority in f_pmask */
+#define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */
+#define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */
+
+typedef struct syslogName_s {
+ char *c_name;
+ int c_val;
+} syslogName_t;
+
+extern syslogName_t syslogPriNames[];
+extern syslogName_t syslogFacNames[];
+
+/**
+ * A reimplementation of itoa(), as this is not available
+ * on all platforms. We used the chance to make an interface
+ * that fits us well, so it is no longer plain itoa().
+ *
+ * This method works with the US-ASCII alphabet. If you port this
+ * to e.g. EBCDIC, you need to make a small adjustment. Keep in mind,
+ * that on the wire it MUST be US-ASCII, so basically all you need
+ * to do is replace the constant '0' with 0x30 ;).
+ *
+ * \param pBuf Caller-provided buffer that will receive the
+ * generated ASCII string.
+ *
+ * \param iLenBuf Length of the caller-provided buffer.
+ *
+ * \param iToConv The integer to be converted.
+ */
+rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv);
+
+/**
+ * A method to duplicate a string for which the length is known.
+ * Len must be the length in characters WITHOUT the trailing
+ * '\0' byte.
+ * rgerhards, 2007-07-10
+ */
+unsigned char *srUtilStrDup(unsigned char *pOld, size_t len);
+/**
+ * A method to create a directory and all its missing parents for
+ * a given file name. Please not that the rightmost element is
+ * considered to be a file name and thus NO directory is being created
+ * for it.
+ * added 2007-07-17 by rgerhards
+ */
+int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode, uid_t uid, gid_t gid, int bFailOnChown);
+int execProg(uchar *program, int bWait, uchar *arg);
+void skipWhiteSpace(uchar **pp);
+rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName,
+ size_t lenFName, long lNum, int lNumDigits);
+int getNumberDigits(long lNum);
+rsRetVal timeoutComp(struct timespec *pt, long iTimeout);
+long timeoutVal(struct timespec *pt);
+void mutexCancelCleanup(void *arg);
+void srSleep(int iSeconds, int iuSeconds);
+char *rs_strerror_r(int errnum, char *buf, size_t buflen);
+int decodeSyslogName(uchar *name, syslogName_t *codetab);
+int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep);
+rsRetVal getFileSize(uchar *pszName, off_t *pSize);
+int containsGlobWildcard(char *str);
+
+/* mutex operations */
+/* some useful constants */
+#define DEFVARS_mutexProtection\
+ int bLockedOpIsLocked=0
+#define BEGIN_MTX_PROTECTED_OPERATIONS(mut, bMustLock) \
+ if(bMustLock == LOCK_MUTEX) { \
+ d_pthread_mutex_lock(mut); \
+ assert(bLockedOpIsLocked == 0); \
+ bLockedOpIsLocked = 1; \
+ }
+#define END_MTX_PROTECTED_OPERATIONS(mut) \
+ if(bLockedOpIsLocked) { \
+ d_pthread_mutex_unlock(mut); \
+ bLockedOpIsLocked = 0; \
+ }
+
+#endif
diff --git a/runtime/srutils.c b/runtime/srutils.c
new file mode 100644
index 00000000..8eb2459c
--- /dev/null
+++ b/runtime/srutils.c
@@ -0,0 +1,659 @@
+/**\file srUtils.c
+ * \brief General utilties that fit nowhere else.
+ *
+ * The namespace for this file is "srUtil".
+ *
+ * \author Rainer Gerhards <rgerhards@adiscon.com>
+ * \date 2003-09-09
+ * Coding begun.
+ *
+ * Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <assert.h>
+#include <sys/wait.h>
+#include <ctype.h>
+#include "srUtils.h"
+#include "obj.h"
+
+#if _POSIX_TIMERS <= 0
+#include <sys/time.h>
+#endif
+
+/* here we host some syslog specific names. There currently is no better place
+ * to do it, but over here is also not ideal... -- rgerhards, 2008-02-14
+ * rgerhards, 2008-04-16: note in LGPL move: the code tables below exist in
+ * the same way in BSD, so it is not a problem to move them from GPLv3 to LGPL.
+ * And nobody modified them since it was under LGPL, so we can also move it
+ * to ASL 2.0.
+ */
+syslogName_t syslogPriNames[] = {
+ {"alert", LOG_ALERT},
+ {"crit", LOG_CRIT},
+ {"debug", LOG_DEBUG},
+ {"emerg", LOG_EMERG},
+ {"err", LOG_ERR},
+ {"error", LOG_ERR}, /* DEPRECATED */
+ {"info", LOG_INFO},
+ {"none", INTERNAL_NOPRI}, /* INTERNAL */
+ {"notice", LOG_NOTICE},
+ {"panic", LOG_EMERG}, /* DEPRECATED */
+ {"warn", LOG_WARNING}, /* DEPRECATED */
+ {"warning", LOG_WARNING},
+ {"*", TABLE_ALLPRI},
+ {NULL, -1}
+};
+
+#ifndef LOG_AUTHPRIV
+# define LOG_AUTHPRIV LOG_AUTH
+#endif
+syslogName_t syslogFacNames[] = {
+ {"auth", LOG_AUTH},
+ {"authpriv", LOG_AUTHPRIV},
+ {"cron", LOG_CRON},
+ {"daemon", LOG_DAEMON},
+ {"kern", LOG_KERN},
+ {"lpr", LOG_LPR},
+ {"mail", LOG_MAIL},
+ {"mark", LOG_MARK}, /* INTERNAL */
+ {"news", LOG_NEWS},
+ {"security", LOG_AUTH}, /* DEPRECATED */
+ {"bsd_security", (13<<3) }, /* BSD-specific, unfortunatly with duplicate name... */
+ {"syslog", LOG_SYSLOG},
+ {"user", LOG_USER},
+ {"uucp", LOG_UUCP},
+#if defined(LOG_FTP)
+ {"ftp", LOG_FTP},
+#endif
+#if defined(LOG_AUDIT)
+ {"audit", LOG_AUDIT},
+#endif
+ {"console", (14 << 3)}, /* BSD-specific priority */
+ {"local0", LOG_LOCAL0},
+ {"local1", LOG_LOCAL1},
+ {"local2", LOG_LOCAL2},
+ {"local3", LOG_LOCAL3},
+ {"local4", LOG_LOCAL4},
+ {"local5", LOG_LOCAL5},
+ {"local6", LOG_LOCAL6},
+ {"local7", LOG_LOCAL7},
+ {NULL, -1},
+};
+
+/* ################################################################# *
+ * private members *
+ * ################################################################# */
+
+/* As this is not a "real" object, there won't be any private
+ * members in this file.
+ */
+
+/* ################################################################# *
+ * public members *
+ * ################################################################# */
+
+rsRetVal srUtilItoA(char *pBuf, int iLenBuf, number_t iToConv)
+{
+ int i;
+ int bIsNegative;
+ char szBuf[64]; /* sufficiently large for my lifespan and those of my children... ;) */
+
+ assert(pBuf != NULL);
+ assert(iLenBuf > 1); /* This is actually an app error and as thus checked for... */
+
+ if(iToConv < 0)
+ {
+ bIsNegative = RSTRUE;
+ iToConv *= -1;
+ }
+ else
+ bIsNegative = RSFALSE;
+
+ /* first generate a string with the digits in the reverse direction */
+ i = 0;
+ do
+ {
+ szBuf[i++] = iToConv % 10 + '0';
+ iToConv /= 10;
+ } while(iToConv > 0); /* warning: do...while()! */
+ --i; /* undo last increment - we were pointing at NEXT location */
+
+ /* make sure we are within bounds... */
+ if(i + 2 > iLenBuf) /* +2 because: a) i starts at zero! b) the \0 byte */
+ return RS_RET_PROVIDED_BUFFER_TOO_SMALL;
+
+ /* then move it to the right direction... */
+ if(bIsNegative == RSTRUE)
+ *pBuf++ = '-';
+ while(i >= 0)
+ *pBuf++ = szBuf[i--];
+ *pBuf = '\0'; /* terminate it!!! */
+
+ return RS_RET_OK;
+}
+
+uchar *srUtilStrDup(uchar *pOld, size_t len)
+{
+ uchar *pNew;
+
+ assert(pOld != NULL);
+
+ if((pNew = MALLOC(len + 1)) != NULL)
+ memcpy(pNew, pOld, len + 1);
+
+ return pNew;
+}
+
+
+/* creates a path recursively
+ * Return 0 on success, -1 otherwise. On failure, errno * hold the last OS error.
+ * Param "mode" holds the mode that all non-existing directories are to be
+ * created with.
+ * Note that we have a potential race inside that code, a race that even exists
+ * outside of the rsyslog process (if multiple instances run, or other programs
+ * generate directories): If the directory does not exist, a context switch happens,
+ * at that moment another process creates it, then our creation on the context
+ * switch back fails. This actually happened in practice, and depending on the
+ * configuration it is even likely to happen. We can not solve this situation
+ * with a mutex, as that works only within out process space. So the solution
+ * is that we take the optimistic approach, try the creation, and if it fails
+ * with "already exists" we go back and do one retry of the check/create
+ * sequence. That should then succeed. If the directory is still not found but
+ * the creation fails in the similar way, we return an error on that second
+ * try because otherwise we would potentially run into an endless loop.
+ * loop. -- rgerhards, 2010-03-25
+ */
+int makeFileParentDirs(uchar *szFile, size_t lenFile, mode_t mode,
+ uid_t uid, gid_t gid, int bFailOnChownFail)
+{
+ uchar *p;
+ uchar *pszWork;
+ size_t len;
+ int err;
+ int iTry = 0;
+ int bErr = 0;
+
+ assert(szFile != NULL);
+ assert(lenFile > 0);
+
+ len = lenFile + 1; /* add one for '\0'-byte */
+ if((pszWork = MALLOC(sizeof(uchar) * len)) == NULL)
+ return -1;
+ memcpy(pszWork, szFile, len);
+ for(p = pszWork+1 ; *p ; p++)
+ if(*p == '/') {
+ /* temporarily terminate string, create dir and go on */
+ *p = '\0';
+again:
+ if(access((char*)pszWork, F_OK)) {
+ if((err = mkdir((char*)pszWork, mode)) == 0) {
+ if(uid != (uid_t) -1 || gid != (gid_t) -1) {
+ /* we need to set owner/group */
+ if(chown((char*)pszWork, uid, gid) != 0)
+ if(bFailOnChownFail)
+ bErr = 1;
+ /* silently ignore if configured
+ * to do so.
+ */
+ }
+ } else {
+ if(err == EEXIST && iTry == 0) {
+ iTry = 1;
+ goto again;
+ }
+ bErr = 1;
+ }
+ if(bErr) {
+ int eSave = errno;
+ free(pszWork);
+ errno = eSave;
+ return -1;
+ }
+ }
+ *p = '/';
+ }
+ free(pszWork);
+ return 0;
+}
+
+
+/* execute a program with a single argument
+ * returns child pid if everything ok, 0 on failure. if
+ * it fails, errno is set. if it fails after the fork(), the caller
+ * can not be notfied for obvious reasons. if bwait is set to 1,
+ * the code waits until the child terminates - that potentially takes
+ * a lot of time.
+ * implemented 2007-07-20 rgerhards
+ */
+int execProg(uchar *program, int bWait, uchar *arg)
+{
+ int pid;
+ int sig;
+ struct sigaction sigAct;
+
+ dbgprintf("exec program '%s' with param '%s'\n", program, arg);
+ pid = fork();
+ if (pid < 0) {
+ return 0;
+ }
+
+ if(pid) { /* Parent */
+ if(bWait)
+ if(waitpid(pid, NULL, 0) == -1)
+ if(errno != ECHILD) {
+ /* we do not use logerror(), because
+ * that might bring us into an endless
+ * loop. At some time, we may
+ * reconsider this behaviour.
+ */
+ dbgprintf("could not wait on child after executing '%s'",
+ (char*)program);
+ }
+ return pid;
+ }
+ /* Child */
+ alarm(0); /* create a clean environment before we exec the real child */
+
+ memset(&sigAct, 0, sizeof(sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = SIG_DFL;
+
+ for(sig = 1 ; sig < NSIG ; ++sig)
+ sigaction(sig, &sigAct, NULL);
+
+ execlp((char*)program, (char*) program, (char*)arg, NULL);
+ /* In the long term, it's a good idea to implement some enhanced error
+ * checking here. However, it can not easily be done. For starters, we
+ * may run into endless loops if we log to syslog. The next problem is
+ * that output is typically not seen by the user. For the time being,
+ * we use no error reporting, which is quite consitent with the old
+ * system() way of doing things. rgerhards, 2007-07-20
+ */
+ perror("exec");
+ exit(1); /* not much we can do in this case */
+}
+
+
+/* skip over whitespace in a standard C string. The
+ * provided pointer is advanced to the first non-whitespace
+ * charater or the \0 byte, if there is none. It is never
+ * moved past the \0.
+ */
+void skipWhiteSpace(uchar **pp)
+{
+ register uchar *p;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ p = *pp;
+ while(*p && isspace((int) *p))
+ ++p;
+ *pp = p;
+}
+
+
+/* generate a file name from four parts:
+ * <directory name>/<name>.<number>
+ * If number is negative, it is not used. If any of the strings is
+ * NULL, an empty string is used instead. Length must be provided.
+ * lNumDigits is the minimum number of digits that lNum should have. This
+ * is to pretty-print the file name, e.g. lNum = 3, lNumDigits= 4 will
+ * result in "0003" being used inside the file name. Set lNumDigits to 0
+ * to use as few space as possible.
+ * rgerhards, 2008-01-03
+ */
+rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName,
+ size_t lenFName, long lNum, int lNumDigits)
+{
+ DEFiRet;
+ uchar *pName;
+ uchar *pNameWork;
+ size_t lenName;
+ uchar szBuf[128]; /* buffer for number */
+ char szFmtBuf[32]; /* buffer for snprintf format */
+ size_t lenBuf;
+
+ if(lNum < 0) {
+ szBuf[0] = '\0';
+ lenBuf = 0;
+ } else {
+ if(lNumDigits > 0) {
+ snprintf(szFmtBuf, sizeof(szFmtBuf), ".%%0%dld", lNumDigits);
+ lenBuf = snprintf((char*)szBuf, sizeof(szBuf), szFmtBuf, lNum);
+ } else
+ lenBuf = snprintf((char*)szBuf, sizeof(szBuf), ".%ld", lNum);
+ }
+
+ lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */
+ if((pName = MALLOC(sizeof(uchar) * lenName)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ /* got memory, now construct string */
+ memcpy(pName, pDirName, lenDirName);
+ pNameWork = pName + lenDirName;
+ *pNameWork++ = '/';
+ memcpy(pNameWork, pFName, lenFName);
+ pNameWork += lenFName;
+ if(lenBuf > 0) {
+ memcpy(pNameWork, szBuf, lenBuf);
+ pNameWork += lenBuf;
+ }
+ *pNameWork = '\0';
+
+ *ppName = pName;
+
+finalize_it:
+ RETiRet;
+}
+
+/* get the number of digits required to represent a given number. We use an
+ * iterative approach as we do not like to draw in the floating point
+ * library just for log(). -- rgerhards, 2008-01-10
+ */
+int getNumberDigits(long lNum)
+{
+ int iDig;
+
+ if(lNum == 0)
+ iDig = 1;
+ else
+ for(iDig = 0 ; lNum != 0 ; ++iDig)
+ lNum /= 10;
+
+ return iDig;
+}
+
+
+/* compute an absolute time timeout suitable for calls to pthread_cond_timedwait()
+ * iTimeout is in milliseconds
+ * rgerhards, 2008-01-14
+ */
+rsRetVal
+timeoutComp(struct timespec *pt, long iTimeout)
+{
+# if _POSIX_TIMERS <= 0
+ struct timeval tv;
+# endif
+
+ BEGINfunc
+ assert(pt != NULL);
+ /* compute timeout */
+
+# if _POSIX_TIMERS > 0
+ /* this is the "regular" code */
+ clock_gettime(CLOCK_REALTIME, pt);
+# else
+ gettimeofday(&tv, NULL);
+ pt->tv_sec = tv.tv_sec;
+ pt->tv_nsec = tv.tv_usec * 1000;
+# endif
+ pt->tv_sec += iTimeout / 1000;
+ pt->tv_nsec += (iTimeout % 1000) * 1000000; /* think INTEGER arithmetic! */
+ if(pt->tv_nsec > 999999999) { /* overrun? */
+ pt->tv_nsec -= 1000000000;
+ ++pt->tv_sec;
+ }
+ ENDfunc
+ return RS_RET_OK; /* so far, this is static... */
+}
+
+
+/* This function is kind of the reverse of timeoutComp() - it takes an absolute
+ * timeout value and computes how far this is in the future. If the value is already
+ * in the past, 0 is returned. The return value is in ms.
+ * rgerhards, 2008-01-25
+ */
+long
+timeoutVal(struct timespec *pt)
+{
+ struct timespec t;
+ long iTimeout;
+# if _POSIX_TIMERS <= 0
+ struct timeval tv;
+# endif
+
+ BEGINfunc
+ assert(pt != NULL);
+ /* compute timeout */
+# if _POSIX_TIMERS > 0
+ /* this is the "regular" code */
+ clock_gettime(CLOCK_REALTIME, &t);
+# else
+ gettimeofday(&tv, NULL);
+ t.tv_sec = tv.tv_sec;
+ t.tv_nsec = tv.tv_usec * 1000;
+# endif
+ iTimeout = (pt->tv_nsec - t.tv_nsec) / 1000000;
+ iTimeout += (pt->tv_sec - t.tv_sec) * 1000;
+
+ if(iTimeout < 0)
+ iTimeout = 0;
+
+ ENDfunc
+ return iTimeout;
+}
+
+
+/* cancellation cleanup handler - frees provided mutex
+ * rgerhards, 2008-01-14
+ */
+void
+mutexCancelCleanup(void *arg)
+{
+ BEGINfunc
+ assert(arg != NULL);
+ d_pthread_mutex_unlock((pthread_mutex_t*) arg);
+ ENDfunc
+}
+
+
+/* rsSleep() - a fairly portable way to to sleep. It
+ * will wake up when
+ * a) the wake-time is over
+ * rgerhards, 2008-01-28
+ */
+void
+srSleep(int iSeconds, int iuSeconds)
+{
+ struct timeval tvSelectTimeout;
+
+ BEGINfunc
+ tvSelectTimeout.tv_sec = iSeconds;
+ tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */
+ select(0, NULL, NULL, NULL, &tvSelectTimeout);
+ ENDfunc
+}
+
+
+/* From varmojfekoj's mail on why he provided rs_strerror_r():
+ * There are two problems with strerror_r():
+ * I see you've rewritten some of the code which calls it to use only
+ * the supplied buffer; unfortunately the GNU implementation sometimes
+ * doesn't use the buffer at all and returns a pointer to some
+ * immutable string instead, as noted in the man page.
+ *
+ * The other problem is that on some systems strerror_r() has a return
+ * type of int.
+ *
+ * So I've written a wrapper function rs_strerror_r(), which should
+ * take care of all this and be used instead.
+ *
+ * Added 2008-01-30
+ */
+char *rs_strerror_r(int errnum, char *buf, size_t buflen) {
+#ifndef HAVE_STRERROR_R
+ char *pszErr;
+ pszErr = strerror(errnum);
+ snprintf(buf, buflen, "%s", pszErr);
+#else
+# ifdef STRERROR_R_CHAR_P
+ char *p = strerror_r(errnum, buf, buflen);
+ if (p != buf) {
+ strncpy(buf, p, buflen);
+ buf[buflen - 1] = '\0';
+ }
+# else
+ strerror_r(errnum, buf, buflen);
+# endif
+#endif /* #ifdef __hpux */
+ return buf;
+}
+
+
+/* Decode a symbolic name to a numeric value */
+int decodeSyslogName(uchar *name, syslogName_t *codetab)
+{
+ register syslogName_t *c;
+ register uchar *p;
+ uchar buf[80];
+
+ ASSERT(name != NULL);
+ ASSERT(codetab != NULL);
+
+ DBGPRINTF("symbolic name: %s", name);
+ if(isdigit((int) *name)) {
+ DBGPRINTF("\n");
+ return (atoi((char*) name));
+ }
+ strncpy((char*) buf, (char*) name, 79);
+ for(p = buf; *p; p++) {
+ if (isupper((int) *p))
+ *p = tolower((int) *p);
+ }
+ for(c = codetab; c->c_name; c++) {
+ if(!strcmp((char*) buf, (char*) c->c_name)) {
+ DBGPRINTF(" ==> %d\n", c->c_val);
+ return (c->c_val);
+ }
+ }
+ DBGPRINTF("\n");
+ return (-1);
+}
+
+
+/**
+ * getSubString
+ *
+ * Copy a string byte by byte until the occurrence
+ * of a given separator.
+ *
+ * \param ppSrc Pointer to a pointer of the source array of characters. If a
+ separator detected the Pointer points to the next char after the
+ separator. Except if the end of the string is dedected ('\n').
+ Then it points to the terminator char.
+ * \param pDst Pointer to the destination array of characters. Here the substing
+ will be stored.
+ * \param DstSize Maximum numbers of characters to store.
+ * \param cSep Separator char.
+ * \ret int Returns 0 if no error occured.
+ *
+ * rgerhards, 2008-02-12: some notes are due... I will once again fix this function, this time
+ * so that it treats ' ' as a request for whitespace. But in general, the function and its callers
+ * should be changed over time, this is not really very good code...
+ */
+int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep)
+{
+ uchar *pSrc = *ppSrc;
+ int iErr = 0; /* 0 = no error, >0 = error */
+ while((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) {
+ *pDst++ = *(pSrc)++;
+ DstSize--;
+ }
+ /* check if the Dst buffer was to small */
+ if ((cSep == ' ' ? !isspace(*pSrc) : *pSrc != cSep) && *pSrc != '\n' && *pSrc != '\0') {
+ dbgprintf("in getSubString, error Src buffer > Dst buffer\n");
+ iErr = 1;
+ }
+ if (*pSrc == '\0' || *pSrc == '\n')
+ /* this line was missing, causing ppSrc to be invalid when it
+ * was returned in case of end-of-string. rgerhards 2005-07-29
+ */
+ *ppSrc = pSrc;
+ else
+ *ppSrc = pSrc+1;
+ *pDst = '\0';
+ return iErr;
+}
+
+
+/* get the size of a file or return appropriate error code. If an error is returned,
+ * *pSize content is undefined.
+ * rgerhards, 2009-06-12
+ */
+rsRetVal
+getFileSize(uchar *pszName, off_t *pSize)
+{
+ int ret;
+ struct stat statBuf;
+ DEFiRet;
+
+ ret = stat((char*) pszName, &statBuf);
+ if(ret == -1) {
+ switch(errno) {
+ case EACCES: ABORT_FINALIZE(RS_RET_NO_FILE_ACCESS);
+ case ENOTDIR:
+ case ENOENT: ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ default: ABORT_FINALIZE(RS_RET_FILE_NO_STAT);
+ }
+ }
+
+ *pSize = statBuf.st_size;
+
+finalize_it:
+ RETiRet;
+}
+
+/* Returns 1 if the given string contains a non-escaped glob(3)
+ * wildcard character and 0 otherwise (or if the string is empty).
+ */
+int
+containsGlobWildcard(char *str)
+{
+ char *p;
+ if(!str) {
+ return 0;
+ }
+ /* From Linux Programmer's Guide:
+ * "A string is a wildcard pattern if it contains one of the characters '?', '*' or '['"
+ * "One can remove the special meaning of '?', '*' and '[' by preceding them by a backslash"
+ */
+ for(p = str; *p != '\0'; p++) {
+ if((*p == '?' || *p == '*' || *p == '[') &&
+ (p == str || *(p-1) != '\\')) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/* vim:set ai:
+ */
diff --git a/runtime/statsobj.c b/runtime/statsobj.c
new file mode 100644
index 00000000..25275616
--- /dev/null
+++ b/runtime/statsobj.c
@@ -0,0 +1,379 @@
+/* The statsobj object.
+ *
+ * This object provides a statistics-gathering facility inside rsyslog. This
+ * functionality will be pragmatically implemented and extended.
+ *
+ * Copyright 2010-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <pthread.h>
+#include <errno.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "unicode-helper.h"
+#include "obj.h"
+#include "statsobj.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+
+
+/* externally-visiable data (see statsobj.h for explanation) */
+int GatherStats = 0;
+
+/* static data */
+DEFobjStaticHelpers
+
+/* doubly linked list of stats objects. Object is automatically linked to it
+ * upon construction. Enqueue always happens at the front (simplifies logic).
+ */
+static statsobj_t *objRoot = NULL;
+static statsobj_t *objLast = NULL;
+
+static pthread_mutex_t mutStats;
+
+/* ------------------------------ statsobj linked list maintenance ------------------------------ */
+
+static inline void
+addToObjList(statsobj_t *pThis)
+{
+ pthread_mutex_lock(&mutStats);
+ pThis->prev = objLast;
+ if(objLast != NULL)
+ objLast->next = pThis;
+ objLast = pThis;
+ if(objRoot == NULL)
+ objRoot = pThis;
+ pthread_mutex_unlock(&mutStats);
+}
+
+
+static inline void
+removeFromObjList(statsobj_t *pThis)
+{
+ pthread_mutex_lock(&mutStats);
+ if(pThis->prev != NULL)
+ pThis->prev->next = pThis->next;
+ if(pThis->next != NULL)
+ pThis->next->prev = pThis->prev;
+ if(objLast == pThis)
+ objLast = pThis->prev;
+ if(objRoot == pThis)
+ objRoot = pThis->next;
+ pthread_mutex_unlock(&mutStats);
+}
+
+
+static inline void
+addCtrToList(statsobj_t *pThis, ctr_t *pCtr)
+{
+ pthread_mutex_lock(&pThis->mutCtr);
+ pCtr->prev = pThis->ctrLast;
+ if(pThis->ctrLast != NULL)
+ pThis->ctrLast->next = pCtr;
+ pThis->ctrLast = pCtr;
+ if(pThis->ctrRoot == NULL)
+ pThis->ctrRoot = pCtr;
+ pthread_mutex_unlock(&pThis->mutCtr);
+}
+
+/* ------------------------------ methods ------------------------------ */
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(statsobj) /* be sure to specify the object type also in END macro! */
+ pthread_mutex_init(&pThis->mutCtr, NULL);
+ pThis->ctrLast = NULL;
+ pThis->ctrRoot = NULL;
+ENDobjConstruct(statsobj)
+
+
+/* ConstructionFinalizer
+ */
+static rsRetVal
+statsobjConstructFinalize(statsobj_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, statsobj);
+ addToObjList(pThis);
+ RETiRet;
+}
+
+
+/* set name. Note that we make our own copy of the memory, caller is
+ * responsible to free up name it passes in (if required).
+ */
+static rsRetVal
+setName(statsobj_t *pThis, uchar *name)
+{
+ DEFiRet;
+ CHKmalloc(pThis->name = ustrdup(name));
+finalize_it:
+ RETiRet;
+}
+
+
+/* add a counter to an object
+ * ctrName is duplicated, caller must free it if requried
+ * NOTE: The counter is READ-ONLY and MUST NOT be modified (most
+ * importantly, it must not be initialized, so the caller must
+ * ensure the counter is properly initialized before AddCounter()
+ * is called.
+ */
+static rsRetVal
+addCounter(statsobj_t *pThis, uchar *ctrName, statsCtrType_t ctrType, void *pCtr)
+{
+ ctr_t *ctr;
+ DEFiRet;
+
+ CHKmalloc(ctr = malloc(sizeof(ctr_t)));
+ ctr->next = NULL;
+ ctr->prev = NULL;
+ CHKmalloc(ctr->name = ustrdup(ctrName));
+ ctr->ctrType = ctrType;
+ switch(ctrType) {
+ case ctrType_IntCtr:
+ ctr->val.pIntCtr = (intctr_t*) pCtr;
+ break;
+ case ctrType_Int:
+ ctr->val.pInt = (int*) pCtr;
+ break;
+ }
+ addCtrToList(pThis, ctr);
+
+finalize_it:
+ RETiRet;
+}
+
+/* get all the object's countes together as CEE. */
+static rsRetVal
+getStatsLineCEE(statsobj_t *pThis, cstr_t **ppcstr, int cee_cookie)
+{
+ cstr_t *pcstr;
+ ctr_t *pCtr;
+ DEFiRet;
+
+ CHKiRet(cstrConstruct(&pcstr));
+
+ if (cee_cookie == 1)
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("@cee: "), 6);
+
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("{"), 1);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("name"), 4);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(":"), 1);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1);
+ rsCStrAppendStr(pcstr, pThis->name);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(","), 1);
+
+ /* now add all counters to this line */
+ pthread_mutex_lock(&pThis->mutCtr);
+ for(pCtr = pThis->ctrRoot ; pCtr != NULL ; pCtr = pCtr->next) {
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1);
+ rsCStrAppendStr(pcstr, pCtr->name);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("\""), 1);
+ cstrAppendChar(pcstr, ':');
+ switch(pCtr->ctrType) {
+ case ctrType_IntCtr:
+ rsCStrAppendInt(pcstr, *(pCtr->val.pIntCtr)); // TODO: OK?????
+ break;
+ case ctrType_Int:
+ rsCStrAppendInt(pcstr, *(pCtr->val.pInt));
+ break;
+ }
+ if (pCtr->next != NULL) {
+ cstrAppendChar(pcstr, ',');
+ } else {
+ cstrAppendChar(pcstr, '}');
+ }
+
+ }
+ pthread_mutex_unlock(&pThis->mutCtr);
+
+ CHKiRet(cstrFinalize(pcstr));
+ *ppcstr = pcstr;
+
+finalize_it:
+ RETiRet;
+}
+
+/* get all the object's countes together with object name as one line.
+ */
+static rsRetVal
+getStatsLine(statsobj_t *pThis, cstr_t **ppcstr)
+{
+ cstr_t *pcstr;
+ ctr_t *pCtr;
+ DEFiRet;
+
+ CHKiRet(cstrConstruct(&pcstr));
+ rsCStrAppendStr(pcstr, pThis->name);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(": "), 2);
+
+ /* now add all counters to this line */
+ pthread_mutex_lock(&pThis->mutCtr);
+ for(pCtr = pThis->ctrRoot ; pCtr != NULL ; pCtr = pCtr->next) {
+ rsCStrAppendStr(pcstr, pCtr->name);
+ cstrAppendChar(pcstr, '=');
+ switch(pCtr->ctrType) {
+ case ctrType_IntCtr:
+ rsCStrAppendInt(pcstr, *(pCtr->val.pIntCtr)); // TODO: OK?????
+ break;
+ case ctrType_Int:
+ rsCStrAppendInt(pcstr, *(pCtr->val.pInt));
+ break;
+ }
+ cstrAppendChar(pcstr, ' ');
+ }
+ pthread_mutex_unlock(&pThis->mutCtr);
+
+ CHKiRet(cstrFinalize(pcstr));
+ *ppcstr = pcstr;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* this function can be used to obtain all stats lines. In this case,
+ * a callback must be provided. This module than iterates over all objects and
+ * submits each stats line to the callback. The callback has two parameters:
+ * the first one is a caller-provided void*, the second one the cstr_t with the
+ * line. If the callback reports an error, processing is stopped.
+ */
+static rsRetVal
+getAllStatsLines(rsRetVal(*cb)(void*, cstr_t*), void *usrptr, statsFmtType_t fmt)
+{
+ statsobj_t *o;
+ cstr_t *cstr;
+ DEFiRet;
+
+ for(o = objRoot ; o != NULL ; o = o->next) {
+ switch(fmt) {
+ case statsFmt_Legacy:
+ CHKiRet(getStatsLine(o, &cstr));
+ break;
+ case statsFmt_CEE:
+ CHKiRet(getStatsLineCEE(o, &cstr, 1));
+ break;
+ case statsFmt_JSON:
+ CHKiRet(getStatsLineCEE(o, &cstr, 0));
+ break;
+ }
+ CHKiRet(cb(usrptr, cstr));
+ rsCStrDestruct(&cstr);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Enable statistics gathering. currently there is no function to disable it
+ * again, as this is right now not needed.
+ */
+static rsRetVal
+enableStats()
+{
+ GatherStats = 1;
+ return RS_RET_OK;
+}
+
+
+/* destructor for the statsobj object */
+BEGINobjDestruct(statsobj) /* be sure to specify the object type also in END and CODESTART macros! */
+ ctr_t *ctr, *ctrToDel;
+CODESTARTobjDestruct(statsobj)
+ removeFromObjList(pThis);
+
+ /* destruct counters */
+ ctr = pThis->ctrRoot;
+ while(ctr != NULL) {
+ ctrToDel = ctr;
+ ctr = ctr->next;
+ free(ctrToDel->name);
+ free(ctrToDel);
+ }
+
+ pthread_mutex_destroy(&pThis->mutCtr);
+ free(pThis->name);
+ENDobjDestruct(statsobj)
+
+
+/* debugprint for the statsobj object */
+BEGINobjDebugPrint(statsobj) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(statsobj)
+ dbgoprint((obj_t*) pThis, "statsobj object, currently no state info available\n");
+ENDobjDebugPrint(statsobj)
+
+
+/* queryInterface function
+ */
+BEGINobjQueryInterface(statsobj)
+CODESTARTobjQueryInterface(statsobj)
+ if(pIf->ifVersion != statsobjCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = statsobjConstruct;
+ pIf->ConstructFinalize = statsobjConstructFinalize;
+ pIf->Destruct = statsobjDestruct;
+ pIf->DebugPrint = statsobjDebugPrint;
+ pIf->SetName = setName;
+ pIf->GetStatsLine = getStatsLine;
+ pIf->GetAllStatsLines = getAllStatsLines;
+ pIf->AddCounter = addCounter;
+ pIf->EnableStats = enableStats;
+finalize_it:
+ENDobjQueryInterface(statsobj)
+
+
+/* Initialize the statsobj class. Must be called as the very first method
+ * before anything else is called inside this class.
+ */
+BEGINAbstractObjClassInit(statsobj, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, statsobjDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, statsobjConstructFinalize);
+
+ /* init other data items */
+ pthread_mutex_init(&mutStats, NULL);
+
+ENDObjClassInit(statsobj)
+
+/* Exit the class.
+ */
+BEGINObjClassExit(statsobj, OBJ_IS_CORE_MODULE) /* class, version */
+ /* release objects we no longer need */
+ pthread_mutex_destroy(&mutStats);
+ENDObjClassExit(statsobj)
diff --git a/runtime/statsobj.h b/runtime/statsobj.h
new file mode 100644
index 00000000..14b33215
--- /dev/null
+++ b/runtime/statsobj.h
@@ -0,0 +1,158 @@
+/* The statsobj object.
+ *
+ * Copyright 2010-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_STATSOBJ_H
+#define INCLUDED_STATSOBJ_H
+
+#include "atomic.h"
+
+/* The following data item is somewhat dirty, in that it does not follow
+ * our usual object calling conventions. However, much like with "Debug", we
+ * do this to gain speed. If we finally come to a platform that does not
+ * provide resolution of names for dynamically loaded modules, we need to find
+ * a work-around, but until then, we use the direct access.
+ * If set to 0, statistics are not gathered, otherwise they are.
+ */
+extern int GatherStats;
+
+/* our basic counter type -- need 32 bit on 32 bit platform.
+ * IMPORTANT: this type *MUST* be supported by atomic instructions!
+ */
+typedef uint64 intctr_t;
+
+/* counter types */
+typedef enum statsCtrType_e {
+ ctrType_IntCtr,
+ ctrType_Int
+} statsCtrType_t;
+
+/* stats line format types */
+typedef enum statsFmtType_e {
+ statsFmt_Legacy,
+ statsFmt_JSON,
+ statsFmt_CEE
+} statsFmtType_t;
+
+
+/* helper entity, the counter */
+typedef struct ctr_s {
+ uchar *name;
+ statsCtrType_t ctrType;
+ union {
+ intctr_t *pIntCtr;
+ int *pInt;
+ } val;
+ struct ctr_s *next, *prev;
+} ctr_t;
+
+/* the statsobj object */
+struct statsobj_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ uchar *name;
+ pthread_mutex_t mutCtr; /* to guard counter linked-list ops */
+ ctr_t *ctrRoot; /* doubly-linked list of statsobj counters */
+ ctr_t *ctrLast;
+ /* used to link ourselves together */
+ statsobj_t *prev;
+ statsobj_t *next;
+};
+
+
+/* interfaces */
+BEGINinterface(statsobj) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(statsobj);
+ rsRetVal (*Construct)(statsobj_t **ppThis);
+ rsRetVal (*ConstructFinalize)(statsobj_t *pThis);
+ rsRetVal (*Destruct)(statsobj_t **ppThis);
+ rsRetVal (*SetName)(statsobj_t *pThis, uchar *name);
+ rsRetVal (*GetStatsLine)(statsobj_t *pThis, cstr_t **ppcstr);
+ rsRetVal (*GetAllStatsLines)(rsRetVal(*cb)(void*, cstr_t*), void *usrptr, statsFmtType_t fmt);
+ rsRetVal (*AddCounter)(statsobj_t *pThis, uchar *ctrName, statsCtrType_t ctrType, void *pCtr);
+ rsRetVal (*EnableStats)(void);
+ENDinterface(statsobj)
+#define statsobjCURR_IF_VERSION 10 /* increment whenever you change the interface structure! */
+/* Changes
+ * v2-v9 rserved for future use in "older" version branches
+ * v10, 2012-04-01: GetAllStatsLines got fmt parameter
+ */
+
+
+/* prototypes */
+PROTOTYPEObj(statsobj);
+
+
+/* macros to handle stats counters
+ * These are to be used by "counter providers". Note that we MUST
+ * specify the mutex name, even though at first it looks like it
+ * could be automatically be generated via e.g. "mut##ctr".
+ * Unfortunately, this does not work if counter is e.g. "pThis->ctr".
+ * So we decided, for clarity, to always insist on specifying the mutex
+ * name (after all, it's just a few more keystrokes...).
+ * --------------------------------------------------------------------
+ * NOTE WELL
+ * --------------------------------------------------------------------
+ * There are actually two types of stats counters: "regular" counters,
+ * which are only used for stats purposes and "dual" counters, which
+ * are primarily used for other purposes but can be included in stats
+ * as well. ALL regular counters MUST be initialized with
+ * STATSCOUNTER_INIT and only be modified by STATSCOUNTER_* functions.
+ * They MUST NOT be used for any other purpose (if this seems to make
+ * sense, consider changing it to a dual counter).
+ * Dual counters are somewhat dangerous in that a single variable is
+ * used for two purposes: the actual application need and stats
+ * counting. However, this is supported for performance reasons, as it
+ * provides insight into the inner engine workings without need for
+ * additional counters (and their maintenance code). Dual counters
+ * MUST NOT be modified by STATSCOUNTER_* functions. Most importantly,
+ * it is expected that the actua application code provides proper
+ * (enough) synchronized access to these counters. Most importantly,
+ * this means they have NO stats-system mutex associated to them.
+ *
+ * The interface function AddCounter() is a read-only function. It
+ * only provides the stats subsystem with a reference to a counter.
+ * It is irrelevant if the counter is a regular or dual one. For that
+ * reason, AddCounter() must not modify the counter contents, as in
+ * the case of a dual counter application code may be broken.
+ */
+#define STATSCOUNTER_DEF(ctr, mut) \
+ intctr_t ctr; \
+ DEF_ATOMIC_HELPER_MUT64(mut);
+
+#define STATSCOUNTER_INIT(ctr, mut) \
+ INIT_ATOMIC_HELPER_MUT64(mut); \
+ ctr = 0;
+
+#define STATSCOUNTER_INC(ctr, mut) \
+ if(GatherStats) \
+ ATOMIC_INC_uint64(&ctr, &mut);
+
+#define STATSCOUNTER_DEC(ctr, mut) \
+ if(GatherStats) \
+ ATOMIC_DEC_uint64(&ctr, mut);
+
+/* the next macro works only if the variable is already guarded
+ * by mutex (or the users risks a wrong result). It is assumed
+ * that there are not concurrent operations that modify the counter.
+ */
+#define STATSCOUNTER_SETMAX_NOMUT(ctr, newmax) \
+ if(GatherStats && ((newmax) > (ctr))) \
+ ctr = newmax;
+
+#endif /* #ifndef INCLUDED_STATSOBJ_H */
diff --git a/runtime/stream.c b/runtime/stream.c
new file mode 100644
index 00000000..53039056
--- /dev/null
+++ b/runtime/stream.c
@@ -0,0 +1,2036 @@
+/* The serial stream class.
+ *
+ * A serial stream provides serial data access. In theory, serial streams
+ * can be implemented via a number of methods (e.g. files or in-memory
+ * streams). In practice, there currently only exist the file type (aka
+ * "driver").
+ *
+ * File begun on 2008-01-09 by RGerhards
+ * Large modifications in 2009-06 to support using it with omfile, including zip writer.
+ * Note that this file obtains the zlib wrapper object is needed, but it never frees it
+ * again. While this sounds like a leak (and one may argue it actually is), there is no
+ * harm associated with that. The reason is that strm is a core object, so it is terminated
+ * only when rsyslogd exists. As we could only release on termination (or else bear more
+ * overhead for keeping track of how many users we have), not releasing zlibw is OK, because
+ * it will be released when rsyslogd terminates. We may want to revisit this decision if
+ * it turns out to be problematic. Then, we need to quasi-refcount the number of accesses
+ * to the object.
+ *
+ * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h> /* required for HP UX */
+#include <errno.h>
+#include <pthread.h>
+
+#include "rsyslog.h"
+#include "stringbuf.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "stream.h"
+#include "unicode-helper.h"
+#include "module-template.h"
+#include "cryprov.h"
+#if HAVE_SYS_PRCTL_H
+# include <sys/prctl.h>
+#endif
+
+/* some platforms do not have large file support :( */
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+#ifndef HAVE_LSEEK64
+ typedef off_t off64_t;
+# define lseek64(fd, offset, whence) lseek(fd, offset, whence)
+#endif
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(zlibw)
+
+/* forward definitions */
+static rsRetVal strmFlushInternal(strm_t *pThis, int bFlushZip);
+static rsRetVal strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf);
+static rsRetVal strmCloseFile(strm_t *pThis);
+static void *asyncWriterThread(void *pPtr);
+static rsRetVal doZipWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlush);
+static rsRetVal doZipFinish(strm_t *pThis);
+static rsRetVal strmPhysWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf);
+static rsRetVal strmSeekCurrOffs(strm_t *pThis);
+
+
+/* methods */
+
+/* Try to resolve a size limit situation. This is used to support custom-file size handlers
+ * for omfile. It first runs the command, and then checks if we are still above the size
+ * treshold. Note that this works only with single file names, NOT with circular names.
+ * Note that pszCurrFName can NOT be taken from pThis, because the stream is closed when
+ * we are called (and that destroys pszCurrFName, as there is NO CURRENT file name!). So
+ * we need to receive the name as a parameter.
+ * initially wirtten 2005-06-21, moved to this class & updates 2009-06-01, both rgerhards
+ */
+static rsRetVal
+resolveFileSizeLimit(strm_t *pThis, uchar *pszCurrFName)
+{
+ uchar *pParams;
+ uchar *pCmd;
+ uchar *p;
+ off_t actualFileSize;
+ rsRetVal localRet;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+ assert(pszCurrFName != NULL);
+
+ if(pThis->pszSizeLimitCmd == NULL) {
+ ABORT_FINALIZE(RS_RET_NON_SIZELIMITCMD); /* nothing we can do in this case... */
+ }
+
+ /* we first check if we have command line parameters. We assume this,
+ * when we have a space in the program name. If we find it, everything after
+ * the space is treated as a single argument.
+ */
+ CHKmalloc(pCmd = ustrdup(pThis->pszSizeLimitCmd));
+
+ for(p = pCmd ; *p && *p != ' ' ; ++p) {
+ /* JUST SKIP */
+ }
+
+ if(*p == ' ') {
+ *p = '\0'; /* pretend string-end */
+ pParams = p+1;
+ } else
+ pParams = NULL;
+
+ /* the execProg() below is probably not great, but at least is is
+ * fairly secure now. Once we change the way file size limits are
+ * handled, we should also revisit how this command is run (and
+ * with which parameters). rgerhards, 2007-07-20
+ */
+ execProg(pCmd, 1, pParams);
+
+ free(pCmd);
+
+ localRet = getFileSize(pszCurrFName, &actualFileSize);
+
+ if(localRet == RS_RET_OK && actualFileSize >= pThis->iSizeLimit) {
+ ABORT_FINALIZE(RS_RET_SIZELIMITCMD_DIDNT_RESOLVE); /* OK, it didn't work out... */
+ } else if(localRet != RS_RET_FILE_NOT_FOUND) {
+ /* file not found is OK, the command may have moved away the file */
+ ABORT_FINALIZE(localRet);
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(iRet == RS_RET_SIZELIMITCMD_DIDNT_RESOLVE) {
+ DBGPRINTF("file size limit cmd for file '%s' did no resolve situation\n", pszCurrFName);
+ } else {
+ DBGPRINTF("file size limit cmd for file '%s' failed with code %d.\n", pszCurrFName, iRet);
+ }
+ pThis->bDisabled = 1;
+ }
+
+ RETiRet;
+}
+
+
+/* Check if the file has grown beyond the configured omfile iSizeLimit
+ * and, if so, initiate processing.
+ */
+static rsRetVal
+doSizeLimitProcessing(strm_t *pThis)
+{
+ uchar *pszCurrFName = NULL;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ ASSERT(pThis->iSizeLimit != 0);
+ ASSERT(pThis->fd != -1);
+
+ if(pThis->iCurrOffs >= pThis->iSizeLimit) {
+ /* strmCloseFile() destroys the current file name, so we
+ * need to preserve it.
+ */
+ CHKmalloc(pszCurrFName = ustrdup(pThis->pszCurrFName));
+ CHKiRet(strmCloseFile(pThis));
+ CHKiRet(resolveFileSizeLimit(pThis, pszCurrFName));
+ }
+
+finalize_it:
+ free(pszCurrFName);
+ RETiRet;
+}
+
+
+/* now, we define type-specific handlers. The provide a generic functionality,
+ * but for this specific type of strm. The mapping to these handlers happens during
+ * strm construction. Later on, handlers are called by pointers present in the
+ * strm instance object.
+ */
+
+/* do the physical open() call on a file.
+ */
+static rsRetVal
+doPhysOpen(strm_t *pThis)
+{
+ int iFlags = 0;
+ struct stat statOpen;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ /* compute which flags we need to provide to open */
+ switch(pThis->tOperationsMode) {
+ case STREAMMODE_READ:
+ iFlags = O_CLOEXEC | O_NOCTTY | O_RDONLY;
+ break;
+ case STREAMMODE_WRITE: /* legacy mode used inside queue engine */
+ iFlags = O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT;
+ break;
+ case STREAMMODE_WRITE_TRUNC:
+ iFlags = O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT | O_TRUNC;
+ break;
+ case STREAMMODE_WRITE_APPEND:
+ iFlags = O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT | O_APPEND;
+ break;
+ default:assert(0);
+ break;
+ }
+ if(pThis->sType == STREAMTYPE_NAMED_PIPE) {
+ DBGPRINTF("Note: stream '%s' is a named pipe, open with O_NONBLOCK\n", pThis->pszCurrFName);
+ iFlags |= O_NONBLOCK;
+ }
+
+ pThis->fd = open((char*)pThis->pszCurrFName, iFlags | O_LARGEFILE, pThis->tOpenMode);
+ DBGPRINTF("file '%s' opened as #%d with mode %d\n", pThis->pszCurrFName,
+ pThis->fd, (int) pThis->tOpenMode);
+ if(pThis->fd == -1) {
+ char errStr[1024];
+ int err = errno;
+ rs_strerror_r(err, errStr, sizeof(errStr));
+ DBGOPRINT((obj_t*) pThis, "open error %d, file '%s': %s\n", errno, pThis->pszCurrFName, errStr);
+ if(err == ENOENT)
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ else
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ if(pThis->tOperationsMode == STREAMMODE_READ) {
+ if(fstat(pThis->fd, &statOpen) == -1) {
+ DBGPRINTF("Error: cannot obtain inode# for file %s\n", pThis->pszCurrFName);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ pThis->inode = statOpen.st_ino;
+ }
+
+ if(!ustrcmp(pThis->pszCurrFName, UCHAR_CONSTANT(_PATH_CONSOLE)) || isatty(pThis->fd)) {
+ DBGPRINTF("file %d is a tty-type file\n", pThis->fd);
+ pThis->bIsTTY = 1;
+ } else {
+ pThis->bIsTTY = 0;
+ }
+
+ if(pThis->cryprov != NULL) {
+ CHKiRet(pThis->cryprov->OnFileOpen(pThis->cryprovData,
+ pThis->pszCurrFName, &pThis->cryprovFileData,
+ (pThis->tOperationsMode == STREAMMODE_READ) ? 'r' : 'w'));
+ pThis->cryprov->SetDeleteOnClose(pThis->cryprovFileData, pThis->bDeleteOnClose);
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal
+strmSetCurrFName(strm_t *pThis)
+{
+ DEFiRet;
+
+ if(pThis->sType == STREAMTYPE_FILE_CIRCULAR) {
+ CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir,
+ pThis->pszFName, pThis->lenFName, pThis->iCurrFNum, pThis->iFileNumDigits));
+ } else {
+ if(pThis->pszDir == NULL) {
+ if((pThis->pszCurrFName = ustrdup(pThis->pszFName)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ } else {
+ CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir,
+ pThis->pszFName, pThis->lenFName, -1, 0));
+ }
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* This function checks if the actual file has changed and, if so, resets the
+ * offset. This is support for monitoring files. It should be called after
+ * deserializing the strm object and before doing any other operation on it
+ * (most importantly not an open or seek!).
+ */
+static rsRetVal
+CheckFileChange(strm_t *pThis)
+{
+ struct stat statName;
+ DEFiRet;
+
+ CHKiRet(strmSetCurrFName(pThis));
+ if(stat((char*) pThis->pszCurrFName, &statName) == -1)
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ DBGPRINTF("stream/after deserialize checking for file change on '%s', "
+ "inode %u/%u, size/currOffs %llu/%llu\n",
+ pThis->pszCurrFName, (unsigned) pThis->inode,
+ (unsigned) statName.st_ino, statName.st_size, pThis->iCurrOffs);
+ if(pThis->inode != statName.st_ino || statName.st_size < pThis->iCurrOffs) {
+ DBGPRINTF("stream: file %s has changed\n", pThis->pszCurrFName);
+ pThis->iCurrOffs = 0;
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* open a strm file
+ * It is OK to call this function when the stream is already open. In that
+ * case, it returns immediately with RS_RET_OK
+ */
+static rsRetVal strmOpenFile(strm_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ if(pThis->fd != -1)
+ ABORT_FINALIZE(RS_RET_OK);
+ pThis->pszCurrFName = NULL; /* used to prevent mem leak in case of error */
+
+ if(pThis->pszFName == NULL)
+ ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING);
+
+ CHKiRet(strmSetCurrFName(pThis));
+
+ CHKiRet(doPhysOpen(pThis));
+
+ pThis->iCurrOffs = 0;
+ if(pThis->tOperationsMode == STREAMMODE_WRITE_APPEND) {
+ /* we need to obtain the current offset */
+ off_t offset;
+ CHKiRet(getFileSize(pThis->pszCurrFName, &offset));
+ pThis->iCurrOffs = offset;
+ }
+
+ DBGOPRINT((obj_t*) pThis, "opened file '%s' for %s as %d\n", pThis->pszCurrFName,
+ (pThis->tOperationsMode == STREAMMODE_READ) ? "READ" : "WRITE", pThis->fd);
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->pszCurrFName != NULL) {
+ free(pThis->pszCurrFName);
+ pThis->pszCurrFName = NULL; /* just to prevent mis-adressing down the road... */
+ }
+ if(pThis->fd != -1) {
+ close(pThis->fd);
+ pThis->fd = -1;
+ }
+ }
+ RETiRet;
+}
+
+
+/* wait for the output writer thread to be done. This must be called before actions
+ * that require data to be persisted. May be called in non-async mode and is a null
+ * operation than. Must be called with the mutex locked.
+ */
+static inline void
+strmWaitAsyncWriterDone(strm_t *pThis)
+{
+ BEGINfunc
+ if(pThis->bAsyncWrite) {
+ /* awake writer thread and make it write out everything */
+ while(pThis->iCnt > 0) {
+ pthread_cond_signal(&pThis->notEmpty);
+ d_pthread_cond_wait(&pThis->isEmpty, &pThis->mut);
+ }
+ }
+ ENDfunc
+}
+
+
+/* close a strm file
+ * Note that the bDeleteOnClose flag is honored. If it is set, the file will be
+ * deleted after close. This is in support for the qRead thread.
+ * Note: it is valid to call this function when the physical file is closed. If so,
+ * strmCloseFile() will still check if there is any unwritten data inside buffers
+ * (this may be the case) and, if so, will open the file, write the data, and then
+ * close it again (this is done via strmFlushInternal and friends).
+ */
+static rsRetVal strmCloseFile(strm_t *pThis)
+{
+ off64_t currOffs;
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) closing\n", pThis->fd,
+ (pThis->pszFName == NULL) ? "N/A" : (char*)pThis->pszFName);
+
+ if(pThis->tOperationsMode != STREAMMODE_READ) {
+ strmFlushInternal(pThis, 0);
+ if(pThis->iZipLevel) {
+ doZipFinish(pThis);
+ }
+ if(pThis->bAsyncWrite) {
+ strmWaitAsyncWriterDone(pThis);
+ }
+ }
+
+ /* if we have a signature provider, we must make sure that the crypto
+ * state files are opened and proper close processing happens. */
+ if(pThis->cryprov != NULL && pThis->fd == -1) {
+ strmOpenFile(pThis);
+ }
+
+ /* the file may already be closed (or never have opened), so guard
+ * against this. -- rgerhards, 2010-03-19
+ */
+ if(pThis->fd != -1) {
+ currOffs = lseek64(pThis->fd, 0, SEEK_CUR);
+ close(pThis->fd);
+ pThis->fd = -1;
+ pThis->inode = 0;
+ if(pThis->cryprov != NULL) {
+ pThis->cryprov->OnFileClose(pThis->cryprovFileData, currOffs);
+ pThis->cryprovFileData = NULL;
+ }
+ }
+
+ if(pThis->fdDir != -1) {
+ /* close associated directory handle, if it is open */
+ close(pThis->fdDir);
+ pThis->fdDir = -1;
+ }
+
+ if(pThis->bDeleteOnClose) {
+ if(pThis->pszCurrFName == NULL) {
+ CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir,
+ pThis->pszFName, pThis->lenFName, pThis->iCurrFNum,
+ pThis->iFileNumDigits));
+ }
+ DBGPRINTF("strmCloseFile: deleting '%s'\n", pThis->pszCurrFName);
+ if(unlink((char*) pThis->pszCurrFName) == -1) {
+ char errStr[1024];
+ int err = errno;
+ rs_strerror_r(err, errStr, sizeof(errStr));
+ DBGPRINTF("error %d unlinking '%s' - ignored: %s\n",
+ errno, pThis->pszCurrFName, errStr);
+ }
+ free(pThis->pszCurrFName);
+ pThis->pszCurrFName = NULL;
+ }
+
+ pThis->iCurrOffs = 0; /* we are back at begin of file */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* switch to next strm file
+ * This method must only be called if we are in a multi-file mode!
+ */
+static rsRetVal
+strmNextFile(strm_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ ASSERT(pThis->iMaxFiles != 0);
+ ASSERT(pThis->fd != -1);
+
+ CHKiRet(strmCloseFile(pThis));
+
+ /* we do modulo operation to ensure we obey the iMaxFile property. This will always
+ * result in a file number lower than iMaxFile, so it if wraps, the name is back to
+ * 0, which results in the first file being overwritten. Not desired for queues, so
+ * make sure their iMaxFiles is large enough. But it is well-desired for other
+ * use cases, e.g. a circular output log file. -- rgerhards, 2008-01-10
+ */
+ pThis->iCurrFNum = (pThis->iCurrFNum + 1) % pThis->iMaxFiles;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* handle the eof case for monitored files.
+ * If we are monitoring a file, someone may have rotated it. In this case, we
+ * also need to close it and reopen it under the same name.
+ * rgerhards, 2008-02-13
+ * The previous code also did a check for file truncation, in which case the
+ * file was considered rewritten. However, this potential border case turned
+ * out to be a big trouble spot on busy systems. It caused massive message
+ * duplication (I guess stat() can return a too-low number under some
+ * circumstances). So starting as of now, we only check the inode number and
+ * a file change is detected only if the inode changes. -- rgerhards, 2011-01-10
+ */
+static rsRetVal
+strmHandleEOFMonitor(strm_t *pThis)
+{
+ DEFiRet;
+ struct stat statName;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ if(stat((char*) pThis->pszCurrFName, &statName) == -1)
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ DBGPRINTF("stream checking for file change on '%s', inode %u/%u\n",
+ pThis->pszCurrFName, (unsigned) pThis->inode,
+ (unsigned) statName.st_ino);
+ if(pThis->inode == statName.st_ino) {
+ ABORT_FINALIZE(RS_RET_EOF);
+ } else {
+ /* we had a file change! */
+ DBGPRINTF("we had a file change on '%s'\n", pThis->pszCurrFName);
+ CHKiRet(strmCloseFile(pThis));
+ CHKiRet(strmOpenFile(pThis));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* handle the EOF case of a stream
+ * The EOF case is somewhat complicated, as the proper action depends on the
+ * mode the stream is in. If there are multiple files (circular logs, most
+ * important use case is queue files!), we need to close the current file and
+ * try to open the next one.
+ * rgerhards, 2008-02-13
+ */
+static rsRetVal
+strmHandleEOF(strm_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ switch(pThis->sType) {
+ case STREAMTYPE_FILE_SINGLE:
+ case STREAMTYPE_NAMED_PIPE:
+ ABORT_FINALIZE(RS_RET_EOF);
+ break;
+ case STREAMTYPE_FILE_CIRCULAR:
+ /* we have multiple files and need to switch to the next one */
+ /* TODO: think about emulating EOF in this case (not yet needed) */
+ DBGOPRINT((obj_t*) pThis, "file %d EOF\n", pThis->fd);
+ CHKiRet(strmNextFile(pThis));
+ break;
+ case STREAMTYPE_FILE_MONITOR:
+ CHKiRet(strmHandleEOFMonitor(pThis));
+ break;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+/* read the next buffer from disk
+ * rgerhards, 2008-02-13
+ */
+static rsRetVal
+strmReadBuf(strm_t *pThis, int *padBytes)
+{
+ DEFiRet;
+ int bRun;
+ long iLenRead;
+ size_t actualDataLen;
+ size_t toRead;
+ ssize_t bytesLeft;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ /* We need to try read at least twice because we may run into EOF and need to switch files. */
+ bRun = 1;
+ while(bRun) {
+ /* first check if we need to (re)open the file. We may have switched to a new one in
+ * circular mode or it may have been rewritten (rotated) if we monitor a file
+ * rgerhards, 2008-02-13
+ */
+ CHKiRet(strmOpenFile(pThis));
+ if(pThis->cryprov == NULL) {
+ toRead = pThis->sIOBufSize;
+ } else {
+ CHKiRet(pThis->cryprov->GetBytesLeftInBlock(pThis->cryprovFileData, &bytesLeft));
+ if(bytesLeft == -1 || bytesLeft > (ssize_t) pThis->sIOBufSize) {
+ toRead = pThis->sIOBufSize;
+ } else {
+ toRead = (size_t) bytesLeft;
+ }
+ }
+ iLenRead = read(pThis->fd, pThis->pIOBuf, toRead);
+ DBGOPRINT((obj_t*) pThis, "file %d read %ld bytes\n", pThis->fd, iLenRead);
+ /* end crypto */
+ if(iLenRead == 0) {
+ CHKiRet(strmHandleEOF(pThis));
+ } else if(iLenRead < 0)
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ else { /* good read */
+ /* here we place our crypto interface */
+ if(pThis->cryprov != NULL) {
+ actualDataLen = iLenRead;
+ pThis->cryprov->Decrypt(pThis->cryprovFileData, pThis->pIOBuf, &actualDataLen);
+ *padBytes = iLenRead - actualDataLen;
+ iLenRead = actualDataLen;
+ DBGOPRINT((obj_t*) pThis, "encrypted file %d pad bytes %d, actual "
+ "data %ld\n", pThis->fd, *padBytes, iLenRead);
+ } else {
+ *padBytes = 0;
+ }
+ pThis->iBufPtrMax = iLenRead;
+ bRun = 0; /* exit loop */
+ }
+ }
+ /* if we reach this point, we had a good read */
+ pThis->iBufPtr = 0;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* logically "read" a character from a file. What actually happens is that
+ * data is taken from the buffer. Only if the buffer is full, data is read
+ * directly from file. In that case, a read is performed blockwise.
+ * rgerhards, 2008-01-07
+ * NOTE: needs to be enhanced to support sticking with a strm entry (if not
+ * deleted).
+ */
+static rsRetVal strmReadChar(strm_t *pThis, uchar *pC)
+{
+ int padBytes = 0; /* in crypto mode, we may have some padding (non-data) bytes */
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ ASSERT(pC != NULL);
+
+ /* DEV debug only: DBGOPRINT((obj_t*) pThis, "strmRead index %d, max %d\n", pThis->iBufPtr, pThis->iBufPtrMax); */
+ if(pThis->iUngetC != -1) { /* do we have an "unread" char that we need to provide? */
+ *pC = pThis->iUngetC;
+ ++pThis->iCurrOffs; /* one more octet read */
+ pThis->iUngetC = -1;
+ ABORT_FINALIZE(RS_RET_OK);
+ }
+
+ /* do we need to obtain a new buffer? */
+ if(pThis->iBufPtr >= pThis->iBufPtrMax) {
+ CHKiRet(strmReadBuf(pThis, &padBytes));
+ }
+ pThis->iCurrOffs += padBytes;
+
+ /* if we reach this point, we have data available in the buffer */
+
+ *pC = pThis->pIOBuf[pThis->iBufPtr++];
+ ++pThis->iCurrOffs; /* one more octet read */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* unget a single character just like ungetc(). As with that call, there is only a single
+ * character buffering capability.
+ * rgerhards, 2008-01-07
+ */
+static rsRetVal strmUnreadChar(strm_t *pThis, uchar c)
+{
+ ASSERT(pThis != NULL);
+ ASSERT(pThis->iUngetC == -1);
+ pThis->iUngetC = c;
+ --pThis->iCurrOffs; /* one less octet read - NOTE: this can cause problems if we got a file change
+ and immediately do an unread and the file is on a buffer boundary and the stream is then persisted.
+ With the queue, this can not happen as an Unread is only done on record begin, which is never split
+ accross files. For other cases we accept the very remote risk. -- rgerhards, 2008-01-12 */
+
+ return RS_RET_OK;
+}
+
+/* read a 'paragraph' from a strm file.
+ * A paragraph may be terminated by a LF, by a LFLF, or by LF<not whitespace> depending on the option set.
+ * The termination LF characters are read, but are
+ * not returned in the buffer (it is discared). The caller is responsible for
+ * destruction of the returned CStr object! -- dlang 2010-12-13
+ */
+static rsRetVal
+strmReadLine(strm_t *pThis, cstr_t **ppCStr, int mode)
+{
+ /* mode = 0 single line mode (equivalent to ReadLine)
+ * mode = 1 LFLF mode (paragraph, blank line between entries)
+ * mode = 2 LF <not whitespace> mode, a log line starts at the beginning of a line, but following lines that are indented are part of the same log entry
+ * This modal interface is not nearly as flexible as being able to define a regex for when a new record starts, but it's also not nearly as hard (or as slow) to implement
+ */
+ uchar c;
+ uchar finished;
+ rsRetVal readCharRet;
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ ASSERT(ppCStr != NULL);
+
+ CHKiRet(cstrConstruct(ppCStr));
+ CHKiRet(strmReadChar(pThis, &c));
+
+ if(mode == 0) {
+ /* append previous message to current message if necessary */
+ if(pThis->prevLineSegment != NULL) {
+ CHKiRet(cstrAppendCStr(*ppCStr, pThis->prevLineSegment));
+ cstrDestruct(&pThis->prevLineSegment);
+ }
+ while(c != '\n') {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ readCharRet = strmReadChar(pThis, &c);
+ if(readCharRet == RS_RET_EOF) {/* end of file reached without \n? */
+ CHKiRet(rsCStrConstructFromCStr(&pThis->prevLineSegment, *ppCStr));
+ }
+ CHKiRet(readCharRet);
+ }
+ CHKiRet(cstrFinalize(*ppCStr));
+ } else if(mode == 1) {
+ finished=0;
+ while(finished == 0){
+ if(c != '\n') {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ } else {
+ if ((((*ppCStr)->iStrLen) > 0) ){
+ if ((*ppCStr)->pBuf[(*ppCStr)->iStrLen -1 ] == '\n'){
+ rsCStrTruncate(*ppCStr,1); /* remove the prior newline */
+ finished=1;
+ } else {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ }
+ } else {
+ finished=1; /* this is a blank line, a \n with nothing since the last complete record */
+ }
+ }
+ }
+ CHKiRet(cstrFinalize(*ppCStr));
+ } else if(mode == 2) {
+ /* indented follow-up lines */
+ finished=0;
+ while(finished == 0){
+ if ((*ppCStr)->iStrLen == 0){
+ if(c != '\n') {
+ /* nothing in the buffer, and it's not a newline, add it to the buffer */
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ } else {
+ finished=1; /* this is a blank line, a \n with nothing since the last complete record */
+ }
+ } else {
+ if ((*ppCStr)->pBuf[(*ppCStr)->iStrLen -1 ] != '\n'){
+ /* not the first character after a newline, add it to the buffer */
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ } else {
+ if ((c == ' ') || (c == '\t')){
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ } else {
+ /* clean things up by putting the character we just read back into
+ * the input buffer and removing the LF character that is currently at the
+ * end of the output string */
+ CHKiRet(strmUnreadChar(pThis, c));
+ rsCStrTruncate(*ppCStr,1);
+ finished=1;
+ }
+ }
+ }
+ }
+ CHKiRet(cstrFinalize(*ppCStr));
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK && *ppCStr != NULL)
+ cstrDestruct(ppCStr);
+
+ RETiRet;
+}
+
+
+/* Standard-Constructor for the strm object
+ */
+BEGINobjConstruct(strm) /* be sure to specify the object type also in END macro! */
+ pThis->iCurrFNum = 1;
+ pThis->fd = -1;
+ pThis->fdDir = -1;
+ pThis->iUngetC = -1;
+ pThis->bVeryReliableZip = 0;
+ pThis->sType = STREAMTYPE_FILE_SINGLE;
+ pThis->sIOBufSize = glblGetIOBufSize();
+ pThis->tOpenMode = 0600;
+ pThis->prevLineSegment = NULL;
+ENDobjConstruct(strm)
+
+
+/* ConstructionFinalizer
+ * rgerhards, 2008-01-09
+ */
+static rsRetVal strmConstructFinalize(strm_t *pThis)
+{
+ rsRetVal localRet;
+ int i;
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ pThis->iBufPtrMax = 0; /* results in immediate read request */
+ if(pThis->iZipLevel) { /* do we need a zip buf? */
+ localRet = objUse(zlibw, LM_ZLIBW_FILENAME);
+ if(localRet != RS_RET_OK) {
+ pThis->iZipLevel = 0;
+ DBGPRINTF("stream was requested with zip mode, but zlibw module unavailable (%d) - using "
+ "without zip\n", localRet);
+ } else {
+ /* we use the same size as the original buf, as we would like
+ * to make sure we can write out everything with a SINGLE api call!
+ * We add another 128 bytes to take care of the gzip header and "all eventualities".
+ */
+ CHKmalloc(pThis->pZipBuf = (Bytef*) MALLOC(sizeof(uchar) * (pThis->sIOBufSize + 128)));
+ }
+ }
+
+ /* if we are set to sync, we must obtain a file handle to the directory for fsync() purposes */
+ if(pThis->bSync && !pThis->bIsTTY) {
+ pThis->fdDir = open((char*)pThis->pszDir, O_RDONLY | O_CLOEXEC | O_NOCTTY);
+ if(pThis->fdDir == -1) {
+ char errStr[1024];
+ int err = errno;
+ rs_strerror_r(err, errStr, sizeof(errStr));
+ DBGPRINTF("error %d opening directory file for fsync() use - fsync for directory disabled: %s\n",
+ errno, errStr);
+ }
+ }
+
+ DBGPRINTF("file stream %s params: flush interval %d, async write %d\n",
+ (pThis->pszFName == NULL) ? "N/A" : (char*)pThis->pszFName,
+ pThis->iFlushInterval, pThis->bAsyncWrite);
+ /* if we have a flush interval, we need to do async writes in any case */
+ if(pThis->iFlushInterval != 0) {
+ pThis->bAsyncWrite = 1;
+ }
+
+ /* if we work asynchronously, we need a couple of synchronization objects */
+ if(pThis->bAsyncWrite) {
+ pthread_mutex_init(&pThis->mut, 0);
+ pthread_cond_init(&pThis->notFull, 0);
+ pthread_cond_init(&pThis->notEmpty, 0);
+ pthread_cond_init(&pThis->isEmpty, 0);
+ pThis->iCnt = pThis->iEnq = pThis->iDeq = 0;
+ for(i = 0 ; i < STREAM_ASYNC_NUMBUFS ; ++i) {
+ CHKmalloc(pThis->asyncBuf[i].pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->sIOBufSize));
+ }
+ pThis->pIOBuf = pThis->asyncBuf[0].pBuf;
+ pThis->bStopWriter = 0;
+ if(pthread_create(&pThis->writerThreadID,
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+ &default_thread_attr,
+#else
+ NULL,
+#endif
+ asyncWriterThread, pThis) != 0)
+ DBGPRINTF("ERROR: stream %p cold not create writer thread\n", pThis);
+ } else {
+ /* we work synchronously, so we need to alloc a fixed pIOBuf */
+ CHKmalloc(pThis->pIOBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->sIOBufSize));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* stop the writer thread (we MUST be runnnig asynchronously when this method
+ * is called!). Note that the mutex must be locked! -- rgerhards, 2009-07-06
+ */
+static inline void
+stopWriter(strm_t *pThis)
+{
+ BEGINfunc
+ pThis->bStopWriter = 1;
+ pthread_cond_signal(&pThis->notEmpty);
+ d_pthread_mutex_unlock(&pThis->mut);
+ pthread_join(pThis->writerThreadID, NULL);
+ ENDfunc
+}
+
+
+/* destructor for the strm object */
+BEGINobjDestruct(strm) /* be sure to specify the object type also in END and CODESTART macros! */
+ int i;
+CODESTARTobjDestruct(strm)
+ /* we need to stop the ZIP writer */
+ if(pThis->bAsyncWrite)
+ /* Note: mutex will be unlocked in stopWriter! */
+ d_pthread_mutex_lock(&pThis->mut);
+
+ /* strmClose() will handle read-only files as well as need to open
+ * files that have unwritten buffers. -- rgerhards, 2010-03-09
+ */
+ strmCloseFile(pThis);
+
+ if(pThis->bAsyncWrite) {
+ stopWriter(pThis);
+ pthread_mutex_destroy(&pThis->mut);
+ pthread_cond_destroy(&pThis->notFull);
+ pthread_cond_destroy(&pThis->notEmpty);
+ pthread_cond_destroy(&pThis->isEmpty);
+ for(i = 0 ; i < STREAM_ASYNC_NUMBUFS ; ++i) {
+ free(pThis->asyncBuf[i].pBuf);
+ }
+ } else {
+ free(pThis->pIOBuf);
+ }
+
+ /* Finally, we can free the resources.
+ * IMPORTANT: we MUST free this only AFTER the ansyncWriter has been stopped, else
+ * we get random errors...
+ */
+ free(pThis->pszDir);
+ free(pThis->pZipBuf);
+ free(pThis->pszCurrFName);
+ free(pThis->pszFName);
+ pThis->bStopWriter = 2; /* RG: use as flag for destruction */
+ENDobjDestruct(strm)
+
+
+/* check if we need to open a new file (in output mode only).
+ * The decision is based on file size AND record delimition state.
+ * This method may also be called on a closed file, in which case
+ * it immediately returns.
+ */
+static rsRetVal strmCheckNextOutputFile(strm_t *pThis)
+{
+ DEFiRet;
+
+ if(pThis->fd == -1)
+ FINALIZE;
+
+ /* wait for output to be empty, so that our counts are correct */
+ strmWaitAsyncWriterDone(pThis);
+
+ if(pThis->iCurrOffs >= pThis->iMaxFileSize) {
+ DBGOPRINT((obj_t*) pThis, "max file size %ld reached for %d, now %ld - starting new file\n",
+ (long) pThis->iMaxFileSize, pThis->fd, (long) pThis->iCurrOffs);
+ CHKiRet(strmNextFile(pThis));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* try to recover a tty after a write error. This may have happend
+ * due to vhangup(), and, if so, we can simply re-open it.
+ */
+#ifdef linux
+# define ERR_TTYHUP EIO
+#else
+# define ERR_TTYHUP EBADF
+#endif
+static rsRetVal
+tryTTYRecover(strm_t *pThis, int err)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+ if(err == ERR_TTYHUP) {
+ close(pThis->fd);
+ CHKiRet(doPhysOpen(pThis));
+ }
+
+finalize_it:
+ RETiRet;
+}
+#undef ER_TTYHUP
+
+
+/* issue write() api calls until either the buffer is completely
+ * written or an error occured (it may happen that multiple writes
+ * are required, what is perfectly legal. On exit, *pLenBuf contains
+ * the number of bytes actually written.
+ * rgerhards, 2009-06-08
+ */
+static rsRetVal
+doWriteCall(strm_t *pThis, uchar *pBuf, size_t *pLenBuf)
+{
+ ssize_t lenBuf;
+ ssize_t iTotalWritten;
+ ssize_t iWritten;
+ char *pWriteBuf;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ lenBuf = *pLenBuf;
+ pWriteBuf = (char*) pBuf;
+ iTotalWritten = 0;
+ do {
+ iWritten = write(pThis->fd, pWriteBuf, lenBuf);
+ if(iWritten < 0) {
+ char errStr[1024];
+ int err = errno;
+ iWritten = 0; /* we have written NO bytes! */
+ rs_strerror_r(err, errStr, sizeof(errStr));
+ DBGPRINTF("log file (%d) write error %d: %s\n", pThis->fd, err, errStr);
+ if(err == EINTR) {
+ /*NO ERROR, just continue */;
+ } else {
+ if(pThis->bIsTTY) {
+ CHKiRet(tryTTYRecover(pThis, err));
+ } else {
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ /* Would it make sense to cover more error cases? So far, I
+ * do not see good reason to do so.
+ */
+ }
+ }
+ }
+ /* advance buffer to next write position */
+ iTotalWritten += iWritten;
+ lenBuf -= iWritten;
+ pWriteBuf += iWritten;
+ } while(lenBuf > 0); /* Warning: do..while()! */
+
+ DBGOPRINT((obj_t*) pThis, "file %d write wrote %d bytes\n", pThis->fd, (int) iWritten);
+
+finalize_it:
+ *pLenBuf = iTotalWritten;
+ RETiRet;
+}
+
+
+
+/* write memory buffer to a stream object.
+ */
+static inline rsRetVal
+doWriteInternal(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlush)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ if(pThis->iZipLevel) {
+ CHKiRet(doZipWrite(pThis, pBuf, lenBuf, bFlush));
+ } else {
+ /* write without zipping */
+ CHKiRet(strmPhysWrite(pThis, pBuf, lenBuf));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function is called to "do" an async write call, what primarily means that
+ * the data is handed over to the writer thread (which will then do the actual write
+ * in parallel). Note that the stream mutex has already been locked by the
+ * strmWrite...() calls. Also note that we always have only a single producer,
+ * so we can simply serially assign the next free buffer to it and be sure that
+ * the very some producer comes back in sequence to submit the then-filled buffers.
+ * This also enables us to timout on partially written buffers. -- rgerhards, 2009-07-06
+ */
+static inline rsRetVal
+doAsyncWriteInternal(strm_t *pThis, size_t lenBuf)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ /* the -1 below is important, because we need one buffer for the main thread! */
+ while(pThis->iCnt >= STREAM_ASYNC_NUMBUFS - 1)
+ d_pthread_cond_wait(&pThis->notFull, &pThis->mut);
+
+ pThis->asyncBuf[pThis->iEnq % STREAM_ASYNC_NUMBUFS].lenBuf = lenBuf;
+ pThis->pIOBuf = pThis->asyncBuf[++pThis->iEnq % STREAM_ASYNC_NUMBUFS].pBuf;
+
+ pThis->bDoTimedWait = 0; /* everything written, no need to timeout partial buffer writes */
+ if(++pThis->iCnt == 1)
+ pthread_cond_signal(&pThis->notEmpty);
+
+ RETiRet;
+}
+
+
+/* schedule writing to the stream. Depending on our concurrency settings,
+ * this either directly writes to the stream or schedules writing via
+ * the background thread. -- rgerhards, 2009-07-07
+ */
+static rsRetVal
+strmSchedWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlushZip)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ /* we need to reset the buffer pointer BEFORE calling the actual write
+ * function. Otherwise, in circular mode, the write function will
+ * potentially close the file, then close will flush and as the
+ * buffer pointer is nonzero, will re-call into this code here. In
+ * the end result, we than have a problem (and things are screwed
+ * up). So we reset the buffer pointer first, and all this can
+ * not happen. It is safe to do so, because that pointer is NOT
+ * used inside the write functions. -- rgerhads, 2010-03-10
+ */
+ pThis->iBufPtr = 0; /* we are at the begin of a new buffer */
+ if(pThis->bAsyncWrite) {
+ CHKiRet(doAsyncWriteInternal(pThis, lenBuf));
+ } else {
+ CHKiRet(doWriteInternal(pThis, pBuf, lenBuf, bFlushZip));
+ }
+
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* This is the writer thread for asynchronous mode.
+ * -- rgerhards, 2009-07-06
+ */
+static void*
+asyncWriterThread(void *pPtr)
+{
+ int iDeq;
+ struct timespec t;
+ sbool bTimedOut = 0;
+ strm_t *pThis = (strm_t*) pPtr;
+ int err;
+ uchar thrdName[256] = "rs:";
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ BEGINfunc
+ ustrncpy(thrdName+3, pThis->pszFName, sizeof(thrdName)-4);
+ dbgOutputTID((char*)thrdName);
+# if HAVE_PRCTL && defined PR_SET_NAME
+ if(prctl(PR_SET_NAME, (char*)thrdName, 0, 0, 0) != 0) {
+ DBGPRINTF("prctl failed, not setting thread name for '%s'\n", "stream writer");
+ }
+# endif
+
+ d_pthread_mutex_lock(&pThis->mut);
+ while(1) { /* loop broken inside */
+ while(pThis->iCnt == 0) {
+ if(pThis->bStopWriter) {
+ pthread_cond_broadcast(&pThis->isEmpty);
+ d_pthread_mutex_unlock(&pThis->mut);
+ goto finalize_it; /* break main loop */
+ }
+ if(bTimedOut && pThis->iBufPtr > 0) {
+ /* if we timed out, we need to flush pending data */
+ strmFlushInternal(pThis, 0);
+ bTimedOut = 0;
+ d_pthread_mutex_unlock(&pThis->mut);
+ continue;
+ }
+ bTimedOut = 0;
+ timeoutComp(&t, pThis->iFlushInterval * 1000); /* *1000 millisconds */
+ if(pThis->bDoTimedWait) {
+ if((err = pthread_cond_timedwait(&pThis->notEmpty, &pThis->mut, &t)) != 0) {
+ bTimedOut = 1; /* simulate in any case */
+ if(err != ETIMEDOUT) {
+ char errStr[1024];
+ rs_strerror_r(err, errStr, sizeof(errStr));
+ DBGPRINTF("stream async writer timeout with error (%d): %s - ignoring\n",
+ err, errStr);
+ }
+ }
+ } else {
+ d_pthread_cond_wait(&pThis->notEmpty, &pThis->mut);
+ }
+ }
+
+ bTimedOut = 0; /* we may have timed out, but there *is* work to do... */
+
+ iDeq = pThis->iDeq++ % STREAM_ASYNC_NUMBUFS;
+
+ /* now we can do the actual write in parallel */
+ d_pthread_mutex_unlock(&pThis->mut);
+ doWriteInternal(pThis, pThis->asyncBuf[iDeq].pBuf, pThis->asyncBuf[iDeq].lenBuf, 0); // TODO: flush state
+ // TODO: error check????? 2009-07-06
+ d_pthread_mutex_lock(&pThis->mut);
+
+ --pThis->iCnt;
+ if(pThis->iCnt < STREAM_ASYNC_NUMBUFS) {
+ pthread_cond_signal(&pThis->notFull);
+ if(pThis->iCnt == 0)
+ pthread_cond_broadcast(&pThis->isEmpty);
+ }
+ }
+ d_pthread_mutex_unlock(&pThis->mut);
+
+finalize_it:
+ ENDfunc
+ return NULL; /* to keep pthreads happy */
+}
+
+
+/* sync the file to disk, so that any unwritten data is persisted. This
+ * also syncs the directory and thus makes sure that the file survives
+ * fatal failure. Note that we do NOT return an error status if the
+ * sync fails. Doing so would probably cause more trouble than it
+ * is worth (read: data loss may occur where we otherwise might not
+ * have it). -- rgerhards, 2009-06-08
+ */
+#undef SYNCCALL
+#if HAVE_FDATASYNC
+# define SYNCCALL(x) fdatasync(x)
+#else
+# define SYNCCALL(x) fsync(x)
+#endif
+static rsRetVal
+syncFile(strm_t *pThis)
+{
+ int ret;
+ DEFiRet;
+
+ if(pThis->bIsTTY)
+ FINALIZE; /* TTYs can not be synced */
+
+ DBGPRINTF("syncing file %d\n", pThis->fd);
+ ret = SYNCCALL(pThis->fd);
+ if(ret != 0) {
+ char errStr[1024];
+ int err = errno;
+ rs_strerror_r(err, errStr, sizeof(errStr));
+ DBGPRINTF("sync failed for file %d with error (%d): %s - ignoring\n",
+ pThis->fd, err, errStr);
+ }
+
+ if(pThis->fdDir != -1) {
+ ret = fsync(pThis->fdDir);
+ }
+
+finalize_it:
+ RETiRet;
+}
+#undef SYNCCALL
+
+/* physically write to the output file. the provided data is ready for
+ * writing (e.g. zipped if we are requested to do that).
+ * Note that if the write() API fails, we do not reset any pointers, but return
+ * an error code. That means we may redo work in the next iteration.
+ * rgerhards, 2009-06-04
+ */
+static rsRetVal
+strmPhysWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf)
+{
+ size_t iWritten;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ DBGPRINTF("strmPhysWrite, stream %p, len %u\n", pThis, (unsigned)lenBuf);
+ if(pThis->fd == -1)
+ CHKiRet(strmOpenFile(pThis));
+
+ /* here we place our crypto interface */
+ if(pThis->cryprov != NULL) {
+ pThis->cryprov->Encrypt(pThis->cryprovFileData, pBuf, &lenBuf);
+ }
+ /* end crypto */
+
+ iWritten = lenBuf;
+ CHKiRet(doWriteCall(pThis, pBuf, &iWritten));
+
+ pThis->iCurrOffs += iWritten;
+ /* update user counter, if provided */
+ if(pThis->pUsrWCntr != NULL)
+ *pThis->pUsrWCntr += iWritten;
+
+ if(pThis->bSync) {
+ CHKiRet(syncFile(pThis));
+ }
+
+ if(pThis->sType == STREAMTYPE_FILE_CIRCULAR) {
+ CHKiRet(strmCheckNextOutputFile(pThis));
+ } else if(pThis->iSizeLimit != 0) {
+ CHKiRet(doSizeLimitProcessing(pThis));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* write the output buffer in zip mode
+ * This means we compress it first and then do a physical write.
+ * Note that we always do a full deflateInit ... deflate ... deflateEnd
+ * sequence. While this is not optimal, we need to do it because we need
+ * to ensure that the file is readable even when we are aborted. Doing the
+ * full sequence brings us as far towards this goal as possible (and not
+ * doing it would be a total failure). It may be worth considering to
+ * add a config switch so that the user can decide the risk he is ready
+ * to take, but so far this is not yet implemented (not even requested ;)).
+ * rgerhards, 2009-06-04
+ */
+static rsRetVal
+doZipWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf, int bFlush)
+{
+ int zRet; /* zlib return state */
+ DEFiRet;
+ unsigned outavail;
+ assert(pThis != NULL);
+ assert(pBuf != NULL);
+
+ if(!pThis->bzInitDone) {
+ /* allocate deflate state */
+ pThis->zstrm.zalloc = Z_NULL;
+ pThis->zstrm.zfree = Z_NULL;
+ pThis->zstrm.opaque = Z_NULL;
+ /* see note in file header for the params we use with deflateInit2() */
+ zRet = zlibw.DeflateInit2(&pThis->zstrm, pThis->iZipLevel, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY);
+ if(zRet != Z_OK) {
+ DBGPRINTF("error %d returned from zlib/deflateInit2()\n", zRet);
+ ABORT_FINALIZE(RS_RET_ZLIB_ERR);
+ }
+ pThis->bzInitDone = RSTRUE;
+ }
+
+ /* now doing the compression */
+ pThis->zstrm.next_in = (Bytef*) pBuf;
+ pThis->zstrm.avail_in = lenBuf;
+ /* run deflate() on buffer until everything has been compressed */
+ do {
+ DBGPRINTF("in deflate() loop, avail_in %d, total_in %ld\n", pThis->zstrm.avail_in, pThis->zstrm.total_in);
+ pThis->zstrm.avail_out = pThis->sIOBufSize;
+ pThis->zstrm.next_out = pThis->pZipBuf;
+ zRet = zlibw.Deflate(&pThis->zstrm, bFlush ? Z_SYNC_FLUSH : Z_NO_FLUSH); /* no bad return value */
+ DBGPRINTF("after deflate, ret %d, avail_out %d\n", zRet, pThis->zstrm.avail_out);
+ outavail =pThis->sIOBufSize - pThis->zstrm.avail_out;
+ if(outavail != 0) {
+ CHKiRet(strmPhysWrite(pThis, (uchar*)pThis->pZipBuf, outavail));
+ }
+ } while (pThis->zstrm.avail_out == 0);
+
+finalize_it:
+ if(pThis->bzInitDone && pThis->bVeryReliableZip) {
+ doZipFinish(pThis);
+ }
+ RETiRet;
+}
+
+
+
+/* finish zlib buffer, to be called before closing the ZIP file (if
+ * running in stream mode).
+ */
+static rsRetVal
+doZipFinish(strm_t *pThis)
+{
+ int zRet; /* zlib return state */
+ DEFiRet;
+ unsigned outavail;
+ assert(pThis != NULL);
+
+ if(!pThis->bzInitDone)
+ goto done;
+
+ pThis->zstrm.avail_in = 0;
+ /* run deflate() on buffer until everything has been compressed */
+ do {
+ DBGPRINTF("in deflate() loop, avail_in %d, total_in %ld\n", pThis->zstrm.avail_in, pThis->zstrm.total_in);
+ pThis->zstrm.avail_out = pThis->sIOBufSize;
+ pThis->zstrm.next_out = pThis->pZipBuf;
+ zRet = zlibw.Deflate(&pThis->zstrm, Z_FINISH); /* no bad return value */
+ DBGPRINTF("after deflate, ret %d, avail_out %d\n", zRet, pThis->zstrm.avail_out);
+ outavail = pThis->sIOBufSize - pThis->zstrm.avail_out;
+ if(outavail != 0) {
+ CHKiRet(strmPhysWrite(pThis, (uchar*)pThis->pZipBuf, outavail));
+ }
+ } while (pThis->zstrm.avail_out == 0);
+
+finalize_it:
+ zRet = zlibw.DeflateEnd(&pThis->zstrm);
+ if(zRet != Z_OK) {
+ DBGPRINTF("error %d returned from zlib/deflateEnd()\n", zRet);
+ }
+
+ pThis->bzInitDone = 0;
+done: RETiRet;
+}
+
+/* flush stream output buffer to persistent storage. This can be called at any time
+ * and is automatically called when the output buffer is full.
+ * rgerhards, 2008-01-10
+ */
+static rsRetVal
+strmFlushInternal(strm_t *pThis, int bFlushZip)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) flush, buflen %ld%s\n", pThis->fd,
+ (pThis->pszFName == NULL) ? "N/A" : (char*)pThis->pszFName,
+ (long) pThis->iBufPtr, (pThis->iBufPtr == 0) ? " (no need to flush)" : "");
+
+ if(pThis->tOperationsMode != STREAMMODE_READ && pThis->iBufPtr > 0) {
+ iRet = strmSchedWrite(pThis, pThis->pIOBuf, pThis->iBufPtr, bFlushZip);
+ }
+
+ RETiRet;
+}
+
+
+/* flush stream output buffer to persistent storage. This can be called at any time
+ * and is automatically called when the output buffer is full. This function is for
+ * use by EXTERNAL callers. Do NOT use it internally. It locks the async writer
+ * mutex if ther is need to do so.
+ * rgerhards, 2010-03-18
+ */
+static rsRetVal
+strmFlush(strm_t *pThis)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ if(pThis->bAsyncWrite)
+ d_pthread_mutex_lock(&pThis->mut);
+ CHKiRet(strmFlushInternal(pThis, 1));
+
+finalize_it:
+ if(pThis->bAsyncWrite)
+ d_pthread_mutex_unlock(&pThis->mut);
+
+ RETiRet;
+}
+
+
+/* seek a stream to a specific location. Pending writes are flushed, read data
+ * is invalidated.
+ * rgerhards, 2008-01-12
+ */
+static rsRetVal strmSeek(strm_t *pThis, off64_t offs)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ if(pThis->fd == -1) {
+ CHKiRet(strmOpenFile(pThis));
+ } else {
+ CHKiRet(strmFlushInternal(pThis, 0));
+ }
+ long long i;
+ DBGOPRINT((obj_t*) pThis, "file %d seek, pos %llu\n", pThis->fd, (long long unsigned) offs);
+ i = lseek64(pThis->fd, offs, SEEK_SET);
+ if(i != offs) {
+ DBGPRINTF("strmSeek: error %lld seeking to offset %lld\n", i, offs);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ pThis->iCurrOffs = offs; /* we are now at *this* offset */
+ pThis->iBufPtr = 0; /* buffer invalidated */
+
+finalize_it:
+ RETiRet;
+}
+
+/* multi-file seek, seeks to file number & offset within file. This
+ * is a support function for the queue, in circular mode. DO NOT USE
+ * IT FOR OTHER NEEDS - it may not work as expected. It will
+ * seek to the new position and delete interim files, as it skips them.
+ * Note: this code can be removed when the queue gets a new disk store
+ * handler (if and when it does ;)).
+ * The output parameter bytesDel receives the number of bytes that have
+ * been deleted (if a file is deleted) or 0 if nothing was deleted.
+ * rgerhards, 2012-11-07
+ */
+rsRetVal
+strmMultiFileSeek(strm_t *pThis, int FNum, off64_t offs, off64_t *bytesDel)
+{
+ struct stat statBuf;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ if(FNum == 0 && offs == 0) { /* happens during queue init */
+ *bytesDel = 0;
+ FINALIZE;
+ }
+
+ if(pThis->iCurrFNum != FNum) {
+ /* Note: we assume that no more than one file is skipped - an
+ * assumption that is being used also by the whole rest of the
+ * code and most notably the queue subsystem.
+ */
+ CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir,
+ pThis->pszFName, pThis->lenFName, pThis->iCurrFNum,
+ pThis->iFileNumDigits));
+ stat((char*)pThis->pszCurrFName, &statBuf);
+ *bytesDel = statBuf.st_size;
+ DBGPRINTF("strmMultiFileSeek: detected new filenum, was %d, new %d, "
+ "deleting '%s' (%lld bytes)\n", pThis->iCurrFNum, FNum,
+ pThis->pszCurrFName, (long long) *bytesDel);
+ unlink((char*)pThis->pszCurrFName);
+ if(pThis->cryprov != NULL)
+ pThis->cryprov->DeleteStateFiles(pThis->pszCurrFName);
+ free(pThis->pszCurrFName);
+ pThis->pszCurrFName = NULL;
+ pThis->iCurrFNum = FNum;
+ } else {
+ *bytesDel = 0;
+ }
+ pThis->iCurrOffs = offs;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* seek to current offset. This is primarily a helper to readjust the OS file
+ * pointer after a strm object has been deserialized.
+ */
+static rsRetVal strmSeekCurrOffs(strm_t *pThis)
+{
+ off64_t targetOffs;
+ uchar c;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ if(pThis->cryprov == NULL || pThis->tOperationsMode != STREAMMODE_READ) {
+ iRet = strmSeek(pThis, pThis->iCurrOffs);
+ FINALIZE;
+ }
+
+ /* As the cryprov may use CBC or similiar things, we need to read skip data */
+ targetOffs = pThis->iCurrOffs;
+ pThis->iCurrOffs = 0;
+ DBGOPRINT((obj_t*) pThis, "encrypted, doing skip read of %lld bytes\n",
+ (long long) targetOffs);
+ while(targetOffs != pThis->iCurrOffs) {
+ CHKiRet(strmReadChar(pThis, &c));
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* write a *single* character to a stream object -- rgerhards, 2008-01-10
+ */
+static rsRetVal strmWriteChar(strm_t *pThis, uchar c)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+
+ if(pThis->bAsyncWrite)
+ d_pthread_mutex_lock(&pThis->mut);
+
+ if(pThis->bDisabled)
+ ABORT_FINALIZE(RS_RET_STREAM_DISABLED);
+
+ /* if the buffer is full, we need to flush before we can write */
+ if(pThis->iBufPtr == pThis->sIOBufSize) {
+ CHKiRet(strmFlushInternal(pThis, 0));
+ }
+ /* we now always have space for one character, so we simply copy it */
+ *(pThis->pIOBuf + pThis->iBufPtr) = c;
+ pThis->iBufPtr++;
+
+finalize_it:
+ if(pThis->bAsyncWrite)
+ d_pthread_mutex_unlock(&pThis->mut);
+
+ RETiRet;
+}
+
+
+/* write an integer value (actually a long) to a stream object
+ * Note that we do not need to lock the mutex here, because we call
+ * strmWrite(), which does the lock (aka: we must not lock it, else we
+ * would run into a recursive lock, resulting in a deadlock!)
+ */
+static rsRetVal strmWriteLong(strm_t *pThis, long i)
+{
+ DEFiRet;
+ uchar szBuf[32];
+
+ ASSERT(pThis != NULL);
+
+ CHKiRet(srUtilItoA((char*)szBuf, sizeof(szBuf), i));
+ CHKiRet(strmWrite(pThis, szBuf, strlen((char*)szBuf)));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* write memory buffer to a stream object.
+ * process the data in chunks and copy it over to our buffer. The caller-provided data
+ * may theoritically be larger than our buffer. In that case, we do multiple copies. One
+ * may argue if it were more efficient to write out the caller-provided buffer in that case
+ * and earlier versions of rsyslog did this. However, this introduces a lot of complexity
+ * inside the buffered writer and potential performance bottlenecks when trying to solve
+ * it. Now keep in mind that we actually do (almost?) never have a case where the
+ * caller-provided buffer is larger than our one. So instead of optimizing a case
+ * which normally does not exist, we expect some degradation in its case but make us
+ * perform better in the regular cases. -- rgerhards, 2009-07-07
+ * Note: the pThis->iBufPtr == pThis->sIOBufSize logic below looks a bit like an
+ * on-off error. In fact, it is not, because iBufPtr always points to the next
+ * *free* byte in the buffer. So if it is sIOBufSize - 1, there actually is one
+ * free byte left. This came up during a code walkthrough and was considered
+ * worth nothing. -- rgerhards, 2010-03-10
+ */
+static rsRetVal
+strmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf)
+{
+ DEFiRet;
+ size_t iWrite;
+ size_t iOffset;
+
+ ASSERT(pThis != NULL);
+ ASSERT(pBuf != NULL);
+
+ /* DEV DEBUG ONLY DBGPRINTF("strmWrite(%p[%s], '%65.65s', %ld);, disabled %d, sizelim %ld, size %lld\n", pThis, pThis->pszCurrFName, pBuf,(long) lenBuf, pThis->bDisabled, (long) pThis->iSizeLimit, (long long) pThis->iCurrOffs); */
+ if(pThis->bDisabled)
+ ABORT_FINALIZE(RS_RET_STREAM_DISABLED);
+
+ if(pThis->bAsyncWrite)
+ d_pthread_mutex_lock(&pThis->mut);
+
+ iOffset = 0;
+ do {
+ if(pThis->iBufPtr == pThis->sIOBufSize) {
+ CHKiRet(strmFlushInternal(pThis, 0)); /* get a new buffer for rest of data */
+ }
+ iWrite = pThis->sIOBufSize - pThis->iBufPtr; /* this fits in current buf */
+ if(iWrite > lenBuf)
+ iWrite = lenBuf;
+ memcpy(pThis->pIOBuf + pThis->iBufPtr, pBuf + iOffset, iWrite);
+ pThis->iBufPtr += iWrite;
+ iOffset += iWrite;
+ lenBuf -= iWrite;
+ } while(lenBuf > 0);
+
+ /* now check if the buffer right at the end of the write is full and, if so,
+ * write it. This seems more natural than waiting (hours?) for the next message...
+ */
+ if(pThis->iBufPtr == pThis->sIOBufSize) {
+ CHKiRet(strmFlushInternal(pThis, 0)); /* get a new buffer for rest of data */
+ }
+
+finalize_it:
+ if(pThis->bAsyncWrite) {
+ if(pThis->bDoTimedWait == 0) {
+ /* we potentially have a partial buffer, so re-activate the
+ * writer thread that it can set and pick up timeouts.
+ */
+ pThis->bDoTimedWait = 1;
+ pthread_cond_signal(&pThis->notEmpty);
+ }
+ d_pthread_mutex_unlock(&pThis->mut);
+ }
+
+ RETiRet;
+}
+
+
+/* property set methods */
+/* simple ones first */
+DEFpropSetMeth(strm, iMaxFileSize, int)
+DEFpropSetMeth(strm, iFileNumDigits, int)
+DEFpropSetMeth(strm, tOperationsMode, int)
+DEFpropSetMeth(strm, tOpenMode, mode_t)
+DEFpropSetMeth(strm, sType, strmType_t)
+DEFpropSetMeth(strm, iZipLevel, int)
+DEFpropSetMeth(strm, bVeryReliableZip, int)
+DEFpropSetMeth(strm, bSync, int)
+DEFpropSetMeth(strm, sIOBufSize, size_t)
+DEFpropSetMeth(strm, iSizeLimit, off_t)
+DEFpropSetMeth(strm, iFlushInterval, int)
+DEFpropSetMeth(strm, pszSizeLimitCmd, uchar*)
+DEFpropSetMeth(strm, cryprov, cryprov_if_t*)
+DEFpropSetMeth(strm, cryprovData, void*)
+
+static rsRetVal strmSetbDeleteOnClose(strm_t *pThis, int val)
+{
+ pThis->bDeleteOnClose = val;
+ if(pThis->cryprov != NULL) {
+ pThis->cryprov->SetDeleteOnClose(pThis->cryprovFileData, pThis->bDeleteOnClose);
+ }
+ return RS_RET_OK;
+}
+
+static rsRetVal strmSetiMaxFiles(strm_t *pThis, int iNewVal)
+{
+ pThis->iMaxFiles = iNewVal;
+ pThis->iFileNumDigits = getNumberDigits(iNewVal);
+ return RS_RET_OK;
+}
+
+
+/* set the stream's file prefix
+ * The passed-in string is duplicated. So if the caller does not need
+ * it any longer, it must free it.
+ * rgerhards, 2008-01-09
+ */
+static rsRetVal
+strmSetFName(strm_t *pThis, uchar *pszName, size_t iLenName)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ ASSERT(pszName != NULL);
+
+ if(iLenName < 1)
+ ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING);
+
+ if(pThis->pszFName != NULL)
+ free(pThis->pszFName);
+
+ if((pThis->pszFName = MALLOC(sizeof(uchar) * (iLenName + 1))) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ memcpy(pThis->pszFName, pszName, iLenName + 1); /* always think about the \0! */
+ pThis->lenFName = iLenName;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* set the stream's directory
+ * The passed-in string is duplicated. So if the caller does not need
+ * it any longer, it must free it.
+ * rgerhards, 2008-01-09
+ */
+static rsRetVal
+strmSetDir(strm_t *pThis, uchar *pszDir, size_t iLenDir)
+{
+ DEFiRet;
+
+ ASSERT(pThis != NULL);
+ ASSERT(pszDir != NULL);
+
+ if(iLenDir < 1)
+ ABORT_FINALIZE(RS_RET_FILE_PREFIX_MISSING);
+
+ CHKmalloc(pThis->pszDir = MALLOC(sizeof(uchar) * (iLenDir + 1)));
+
+ memcpy(pThis->pszDir, pszDir, iLenDir + 1); /* always think about the \0! */
+ pThis->lenDir = iLenDir;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* support for data records
+ * The stream class is able to write to multiple files. However, there are
+ * situation (actually quite common), where a single data record should not
+ * be split across files. This may be problematic if multiple stream write
+ * calls are used to create the record. To support that, we provide the
+ * bInRecord status variable. If it is set, no file spliting occurs. Once
+ * it is set to 0, a check is done if a split is necessary and it then
+ * happens. For a record-oriented caller, the proper sequence is:
+ *
+ * strmRecordBegin()
+ * strmWrite...()
+ * strmRecordEnd()
+ *
+ * Please note that records do not affect the writing of output buffers. They
+ * are always written when full. The only thing affected is circular files
+ * creation. So it is safe to write large records.
+ *
+ * IMPORTANT: RecordBegin() can not be nested! It is a programming error
+ * if RecordBegin() is called while already in a record!
+ *
+ * rgerhards, 2008-01-10
+ */
+static rsRetVal strmRecordBegin(strm_t *pThis)
+{
+ ASSERT(pThis != NULL);
+ ASSERT(pThis->bInRecord == 0);
+ pThis->bInRecord = 1;
+ return RS_RET_OK;
+}
+
+static rsRetVal strmRecordEnd(strm_t *pThis)
+{
+ DEFiRet;
+ ASSERT(pThis != NULL);
+ ASSERT(pThis->bInRecord == 1);
+
+ pThis->bInRecord = 0;
+ iRet = strmCheckNextOutputFile(pThis); /* check if we need to switch files */
+
+ RETiRet;
+}
+/* end stream record support functions */
+
+
+/* This method serializes a stream object. That means the whole
+ * object is modified into text form. That text form is suitable for
+ * later reconstruction of the object.
+ * The most common use case for this method is the creation of an
+ * on-disk representation of the message object.
+ * We do not serialize the dynamic properties.
+ * rgerhards, 2008-01-10
+ */
+static rsRetVal strmSerialize(strm_t *pThis, strm_t *pStrm)
+{
+ DEFiRet;
+ int i;
+ int64 l;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ ISOBJ_TYPE_assert(pStrm, strm);
+
+ strmFlushInternal(pThis, 0);
+ CHKiRet(obj.BeginSerialize(pStrm, (obj_t*) pThis));
+
+ objSerializeSCALAR(pStrm, iCurrFNum, INT);
+ objSerializePTR(pStrm, pszFName, PSZ);
+ objSerializeSCALAR(pStrm, iMaxFiles, INT);
+ objSerializeSCALAR(pStrm, bDeleteOnClose, INT);
+
+ i = pThis->sType;
+ objSerializeSCALAR_VAR(pStrm, sType, INT, i);
+
+ i = pThis->tOperationsMode;
+ objSerializeSCALAR_VAR(pStrm, tOperationsMode, INT, i);
+
+ i = pThis->tOpenMode;
+ objSerializeSCALAR_VAR(pStrm, tOpenMode, INT, i);
+
+ l = pThis->iCurrOffs;
+ objSerializeSCALAR_VAR(pStrm, iCurrOffs, INT64, l);
+
+ l = pThis->inode;
+ objSerializeSCALAR_VAR(pStrm, inode, INT64, l);
+
+ objSerializePTR(pStrm, prevLineSegment, PSZ);
+
+ CHKiRet(obj.EndSerialize(pStrm));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* duplicate a stream object excluding dynamic properties. This function is
+ * primarily meant to provide a duplicate that later on can be used to access
+ * the data. This is needed, for example, for a restart of the disk queue.
+ * Note that ConstructFinalize() is NOT called. So our caller may change some
+ * properties before finalizing things.
+ * rgerhards, 2009-05-26
+ */
+rsRetVal
+strmDup(strm_t *pThis, strm_t **ppNew)
+{
+ strm_t *pNew = NULL;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ assert(ppNew != NULL);
+
+ CHKiRet(strmConstruct(&pNew));
+ pNew->sType = pThis->sType;
+ pNew->iCurrFNum = pThis->iCurrFNum;
+ CHKmalloc(pNew->pszFName = ustrdup(pThis->pszFName));
+ pNew->lenFName = pThis->lenFName;
+ CHKmalloc(pNew->pszDir = ustrdup(pThis->pszDir));
+ pNew->lenDir = pThis->lenDir;
+ pNew->tOperationsMode = pThis->tOperationsMode;
+ pNew->tOpenMode = pThis->tOpenMode;
+ pNew->iMaxFileSize = pThis->iMaxFileSize;
+ pNew->iMaxFiles = pThis->iMaxFiles;
+ pNew->iFileNumDigits = pThis->iFileNumDigits;
+ pNew->bDeleteOnClose = pThis->bDeleteOnClose;
+ pNew->iCurrOffs = pThis->iCurrOffs;
+
+ *ppNew = pNew;
+ pNew = NULL;
+
+finalize_it:
+ if(pNew != NULL)
+ strmDestruct(&pNew);
+
+ RETiRet;
+}
+
+/* set a user write-counter. This counter is initialized to zero and
+ * receives the number of bytes written. It is accurate only after a
+ * flush(). This hook is provided as a means to control disk size usage.
+ * The pointer must be valid at all times (so if it is on the stack, be sure
+ * to remove it when you exit the function). Pointers are removed by
+ * calling strmSetWCntr() with a NULL param. Only one pointer is settable,
+ * any new set overwrites the previous one.
+ * rgerhards, 2008-02-27
+ */
+static rsRetVal
+strmSetWCntr(strm_t *pThis, number_t *pWCnt)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ if(pWCnt != NULL)
+ *pWCnt = 0;
+ pThis->pUsrWCntr = pWCnt;
+
+ RETiRet;
+}
+
+
+#include "stringbuf.h"
+
+/* This function can be used as a generic way to set properties.
+ * rgerhards, 2008-01-11
+ */
+#define isProp(name) !rsCStrSzStrCmp(pProp->pcsName, UCHAR_CONSTANT(name), sizeof(name) - 1)
+static rsRetVal strmSetProperty(strm_t *pThis, var_t *pProp)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ ASSERT(pProp != NULL);
+
+ if(isProp("sType")) {
+ CHKiRet(strmSetsType(pThis, (strmType_t) pProp->val.num));
+ } else if(isProp("iCurrFNum")) {
+ pThis->iCurrFNum = pProp->val.num;
+ } else if(isProp("pszFName")) {
+ CHKiRet(strmSetFName(pThis, rsCStrGetSzStrNoNULL(pProp->val.pStr), rsCStrLen(pProp->val.pStr)));
+ } else if(isProp("tOperationsMode")) {
+ CHKiRet(strmSettOperationsMode(pThis, pProp->val.num));
+ } else if(isProp("tOpenMode")) {
+ CHKiRet(strmSettOpenMode(pThis, pProp->val.num));
+ } else if(isProp("iCurrOffs")) {
+ pThis->iCurrOffs = pProp->val.num;
+ } else if(isProp("inode")) {
+ pThis->inode = (ino_t) pProp->val.num;
+ } else if(isProp("iMaxFileSize")) {
+ CHKiRet(strmSetiMaxFileSize(pThis, pProp->val.num));
+ } else if(isProp("iMaxFiles")) {
+ CHKiRet(strmSetiMaxFiles(pThis, pProp->val.num));
+ } else if(isProp("iFileNumDigits")) {
+ CHKiRet(strmSetiFileNumDigits(pThis, pProp->val.num));
+ } else if(isProp("bDeleteOnClose")) {
+ CHKiRet(strmSetbDeleteOnClose(pThis, pProp->val.num));
+ } else if(isProp("prevLineSegment")) {
+ CHKiRet(rsCStrConstructFromCStr(&pThis->prevLineSegment, pProp->val.pStr));
+ }
+
+finalize_it:
+ RETiRet;
+}
+#undef isProp
+
+
+/* return the current offset inside the stream. Note that on two consequtive calls, the offset
+ * reported on the second call may actually be lower than on the first call. This is due to
+ * file circulation. A caller must deal with that. -- rgerhards, 2008-01-30
+ */
+static rsRetVal
+strmGetCurrOffset(strm_t *pThis, int64 *pOffs)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+ ASSERT(pOffs != NULL);
+
+ *pOffs = pThis->iCurrOffs;
+
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(strm)
+CODESTARTobjQueryInterface(strm)
+ if(pIf->ifVersion != strmCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = strmConstruct;
+ pIf->ConstructFinalize = strmConstructFinalize;
+ pIf->Destruct = strmDestruct;
+ pIf->ReadChar = strmReadChar;
+ pIf->UnreadChar = strmUnreadChar;
+ pIf->ReadLine = strmReadLine;
+ pIf->SeekCurrOffs = strmSeekCurrOffs;
+ pIf->Write = strmWrite;
+ pIf->WriteChar = strmWriteChar;
+ pIf->WriteLong = strmWriteLong;
+ pIf->SetFName = strmSetFName;
+ pIf->SetDir = strmSetDir;
+ pIf->Flush = strmFlush;
+ pIf->RecordBegin = strmRecordBegin;
+ pIf->RecordEnd = strmRecordEnd;
+ pIf->Serialize = strmSerialize;
+ pIf->GetCurrOffset = strmGetCurrOffset;
+ pIf->Dup = strmDup;
+ pIf->SetWCntr = strmSetWCntr;
+ pIf->CheckFileChange = CheckFileChange;
+ /* set methods */
+ pIf->SetbDeleteOnClose = strmSetbDeleteOnClose;
+ pIf->SetiMaxFileSize = strmSetiMaxFileSize;
+ pIf->SetiMaxFiles = strmSetiMaxFiles;
+ pIf->SetiFileNumDigits = strmSetiFileNumDigits;
+ pIf->SettOperationsMode = strmSettOperationsMode;
+ pIf->SettOpenMode = strmSettOpenMode;
+ pIf->SetsType = strmSetsType;
+ pIf->SetiZipLevel = strmSetiZipLevel;
+ pIf->SetbVeryReliableZip = strmSetbVeryReliableZip;
+ pIf->SetbSync = strmSetbSync;
+ pIf->SetsIOBufSize = strmSetsIOBufSize;
+ pIf->SetiSizeLimit = strmSetiSizeLimit;
+ pIf->SetiFlushInterval = strmSetiFlushInterval;
+ pIf->SetpszSizeLimitCmd = strmSetpszSizeLimitCmd;
+ pIf->Setcryprov = strmSetcryprov;
+ pIf->SetcryprovData = strmSetcryprovData;
+finalize_it:
+ENDobjQueryInterface(strm)
+
+
+/* Initialize the stream class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-01-09
+ */
+BEGINObjClassInit(strm, 1, OBJ_IS_CORE_MODULE)
+ /* request objects we use */
+
+ OBJSetMethodHandler(objMethod_SERIALIZE, strmSerialize);
+ OBJSetMethodHandler(objMethod_SETPROPERTY, strmSetProperty);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, strmConstructFinalize);
+ENDObjClassInit(strm)
+
+/* vi:set ai:
+ */
diff --git a/runtime/stream.h b/runtime/stream.h
new file mode 100644
index 00000000..61d5ede2
--- /dev/null
+++ b/runtime/stream.h
@@ -0,0 +1,212 @@
+/* Definition of serial stream class (strm).
+ *
+ * A serial stream provides serial data access. In theory, serial streams
+ * can be implemented via a number of methods (e.g. files or in-memory
+ * streams). In practice, there currently only exist the file type (aka
+ * "driver").
+ *
+ * In practice, many stream features are bound to files. I have not yet made
+ * any serious effort, except for the naming of this class, to try to make
+ * the interfaces very generic. However, I assume that we could work much
+ * like in the strm class, where some properties are simply ignored when
+ * the wrong strm mode is selected (which would translate here to the wrong
+ * stream mode).
+ *
+ * Most importantly, this class provides generic input and output functions
+ * which can directly be used to work with the strms and file output. It
+ * provides such useful things like a circular file buffer and, hopefully
+ * at a later stage, a lazy writer. The object is also seriazable and thus
+ * can easily be persistet. The bottom line is that it makes much sense to
+ * use this class whereever possible as its features may grow in the future.
+ *
+ * An important note on writing gzip format via zlib (kept anonymous
+ * by request):
+ *
+ * --------------------------------------------------------------------------
+ * We'd like to make sure the output file is in full gzip format
+ * (compatible with gzip -d/zcat etc). There is a flag in how the output
+ * is initialized within zlib to properly add the gzip wrappers to the
+ * output. (gzip is effectively a small metadata wrapper around raw
+ * zstream output.)
+ *
+ * I had written an old bit of code to do this - the documentation on
+ * deflatInit2() was pretty tricky to nail down on this specific feature:
+ *
+ * int deflateInit2 (z_streamp strm, int level, int method, int windowBits,
+ * int memLevel, int strategy);
+ *
+ * I believe "31" would be the value for the "windowBits" field that you'd
+ * want to try:
+ *
+ * deflateInit2(zstrmptr, 6, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY);
+ * --------------------------------------------------------------------------
+ *
+ * Copyright 2008-2013 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#ifndef STREAM_H_INCLUDED
+#define STREAM_H_INCLUDED
+
+#include <pthread.h>
+#include "obj-types.h"
+#include "glbl.h"
+#include "stream.h"
+#include "zlibw.h"
+#include "cryprov.h"
+
+/* stream types */
+typedef enum {
+ STREAMTYPE_FILE_SINGLE = 0, /**< read a single file */
+ STREAMTYPE_FILE_CIRCULAR = 1, /**< circular files */
+ STREAMTYPE_FILE_MONITOR = 2, /**< monitor a (third-party) file */
+ STREAMTYPE_NAMED_PIPE = 3 /**< file is a named pipe (so far, tested for output only) */
+} strmType_t;
+
+typedef enum { /* when extending, do NOT change existing modes! */
+ STREAMMMODE_INVALID = 0,
+ STREAMMODE_READ = 1,
+ STREAMMODE_WRITE = 2,
+ STREAMMODE_WRITE_TRUNC = 3,
+ STREAMMODE_WRITE_APPEND = 4
+} strmMode_t;
+
+#define STREAM_ASYNC_NUMBUFS 2 /* must be a power of 2 -- TODO: make configurable */
+/* The strm_t data structure */
+typedef struct strm_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ strmType_t sType;
+ /* descriptive properties */
+ int iCurrFNum;/* current file number (NOT descriptor, but the number in the file name!) */
+ uchar *pszFName; /* prefix for generated filenames */
+ int lenFName;
+ strmMode_t tOperationsMode;
+ mode_t tOpenMode;
+ int64 iMaxFileSize;/* maximum size a file may grow to */
+ int iMaxFiles; /* maximum number of files if a circular mode is in use */
+ int iFileNumDigits;/* min number of digits to use in file number (only in circular mode) */
+ sbool bDeleteOnClose; /* set to 1 to auto-delete on close -- be careful with that setting! */
+ int64 iCurrOffs;/* current offset */
+ int64 *pUsrWCntr; /* NULL or a user-provided counter that receives the nbr of bytes written since the last CntrSet() */
+ /* dynamic properties, valid only during file open, not to be persistet */
+ sbool bDisabled; /* should file no longer be written to? (currently set only if omfile file size limit fails) */
+ sbool bSync; /* sync this file after every write? */
+ size_t sIOBufSize;/* size of IO buffer */
+ uchar *pszDir; /* Directory */
+ int lenDir;
+ int fd; /* the file descriptor, -1 if closed */
+ int fdDir; /* the directory's descriptor, in case bSync is requested (-1 if closed) */
+ ino_t inode; /* current inode for files being monitored (undefined else) */
+ uchar *pszCurrFName; /* name of current file (if open) */
+ uchar *pIOBuf; /* the iobuffer currently in use to gather data */
+ size_t iBufPtrMax; /* current max Ptr in Buffer (if partial read!) */
+ size_t iBufPtr; /* pointer into current buffer */
+ int iUngetC; /* char set via UngetChar() call or -1 if none set */
+ sbool bInRecord; /* if 1, indicates that we are currently writing a not-yet complete record */
+ int iZipLevel; /* zip level (0..9). If 0, zip is completely disabled */
+ Bytef *pZipBuf;
+ /* support for async flush procesing */
+ sbool bAsyncWrite; /* do asynchronous writes (always if a flush interval is given) */
+ sbool bStopWriter; /* shall writer thread terminate? */
+ sbool bDoTimedWait; /* instruct writer thread to do a times wait to support flush timeouts */
+ sbool bzInitDone; /* did we do an init of zstrm already? */
+ sbool bVeryReliableZip; /* shall we write interim headers to create a very reliable ZIP file? */
+ int iFlushInterval; /* flush in which interval - 0, no flushing */
+ pthread_mutex_t mut;/* mutex for flush in async mode */
+ pthread_cond_t notFull;
+ pthread_cond_t notEmpty;
+ pthread_cond_t isEmpty;
+ unsigned short iEnq; /* this MUST be unsigned as we use module arithmetic (else invalid indexing happens!) */
+ unsigned short iDeq; /* this MUST be unsigned as we use module arithmetic (else invalid indexing happens!) */
+ cryprov_if_t *cryprov; /* ptr to crypto provider; NULL = do not encrypt */
+ void *cryprovData; /* opaque data ptr for provider use */
+ void *cryprovFileData;/* opaque data ptr for file instance */
+ short iCnt; /* current nbr of elements in buffer */
+ z_stream zstrm; /* zip stream to use */
+ struct {
+ uchar *pBuf;
+ size_t lenBuf;
+ } asyncBuf[STREAM_ASYNC_NUMBUFS];
+ pthread_t writerThreadID;
+ /* support for omfile size-limiting commands, special counters, NOT persisted! */
+ off_t iSizeLimit; /* file size limit, 0 = no limit */
+ uchar *pszSizeLimitCmd; /* command to carry out when size limit is reached */
+ sbool bIsTTY; /* is this a tty file? */
+ cstr_t *prevLineSegment; /* for ReadLine, previous, unwritten part of file */
+} strm_t;
+
+
+/* interfaces */
+BEGINinterface(strm) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(strm_t **ppThis);
+ rsRetVal (*ConstructFinalize)(strm_t *pThis);
+ rsRetVal (*Destruct)(strm_t **ppThis);
+ rsRetVal (*SetMaxFileSize)(strm_t *pThis, int64 iMaxFileSize);
+ rsRetVal (*SetFileName)(strm_t *pThis, uchar *pszName, size_t iLenName);
+ rsRetVal (*ReadChar)(strm_t *pThis, uchar *pC);
+ rsRetVal (*UnreadChar)(strm_t *pThis, uchar c);
+ rsRetVal (*SeekCurrOffs)(strm_t *pThis);
+ rsRetVal (*Write)(strm_t *pThis, uchar *pBuf, size_t lenBuf);
+ rsRetVal (*WriteChar)(strm_t *pThis, uchar c);
+ rsRetVal (*WriteLong)(strm_t *pThis, long i);
+ rsRetVal (*SetFName)(strm_t *pThis, uchar *pszPrefix, size_t iLenPrefix);
+ rsRetVal (*SetDir)(strm_t *pThis, uchar *pszDir, size_t iLenDir);
+ rsRetVal (*Flush)(strm_t *pThis);
+ rsRetVal (*RecordBegin)(strm_t *pThis);
+ rsRetVal (*RecordEnd)(strm_t *pThis);
+ rsRetVal (*Serialize)(strm_t *pThis, strm_t *pStrm);
+ rsRetVal (*GetCurrOffset)(strm_t *pThis, int64 *pOffs);
+ rsRetVal (*SetWCntr)(strm_t *pThis, number_t *pWCnt);
+ rsRetVal (*Dup)(strm_t *pThis, strm_t **ppNew);
+ INTERFACEpropSetMeth(strm, bDeleteOnClose, int);
+ INTERFACEpropSetMeth(strm, iMaxFileSize, int);
+ INTERFACEpropSetMeth(strm, iMaxFiles, int);
+ INTERFACEpropSetMeth(strm, iFileNumDigits, int);
+ INTERFACEpropSetMeth(strm, tOperationsMode, int);
+ INTERFACEpropSetMeth(strm, tOpenMode, mode_t);
+ INTERFACEpropSetMeth(strm, sType, strmType_t);
+ INTERFACEpropSetMeth(strm, iZipLevel, int);
+ INTERFACEpropSetMeth(strm, bSync, int);
+ INTERFACEpropSetMeth(strm, sIOBufSize, size_t);
+ INTERFACEpropSetMeth(strm, iSizeLimit, off_t);
+ INTERFACEpropSetMeth(strm, iFlushInterval, int);
+ INTERFACEpropSetMeth(strm, pszSizeLimitCmd, uchar*);
+ /* v6 added */
+ rsRetVal (*ReadLine)(strm_t *pThis, cstr_t **ppCStr, int mode);
+ /* v7 added 2012-09-14 */
+ INTERFACEpropSetMeth(strm, bVeryReliableZip, int);
+ /* v8 added 2013-03-21 */
+ rsRetVal (*CheckFileChange)(strm_t *pThis);
+ /* v9 added 2013-04-04 */
+ INTERFACEpropSetMeth(strm, cryprov, cryprov_if_t*);
+ INTERFACEpropSetMeth(strm, cryprovData, void*);
+ENDinterface(strm)
+#define strmCURR_IF_VERSION 9 /* increment whenever you change the interface structure! */
+
+static inline int
+strmGetCurrFileNum(strm_t *pStrm) {
+ return pStrm->iCurrFNum;
+}
+
+/* prototypes */
+PROTOTYPEObjClassInit(strm);
+rsRetVal strmMultiFileSeek(strm_t *pThis, int fileNum, off64_t offs, off64_t *bytesDel);
+
+#endif /* #ifndef STREAM_H_INCLUDED */
diff --git a/runtime/strgen.c b/runtime/strgen.c
new file mode 100644
index 00000000..46be1236
--- /dev/null
+++ b/runtime/strgen.c
@@ -0,0 +1,279 @@
+/* strgen.c
+ * Module to handle string generators. These are C modules that receive
+ * the message object and return a custom-built string. The primary purpose
+ * for their existance is performance -- they do the same as template strings, but
+ * potentially faster (if well implmented).
+ *
+ * Module begun 2010-06-01 by Rainer Gerhards
+ *
+ * Copyright 2010 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "msg.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "strgen.h"
+#include "ruleset.h"
+#include "unicode-helper.h"
+#include "cfsysline.h"
+
+/* definitions for objects we access */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(ruleset)
+
+/* static data */
+
+/* config data */
+
+/* This is the list of all strgens known to us.
+ * This is also used to unload all modules on shutdown.
+ */
+strgenList_t *pStrgenLstRoot = NULL;
+
+
+/* intialize (but NOT allocate) a strgen list. Primarily meant as a hook
+ * which can be used to extend the list in the future. So far, just sets
+ * it to NULL.
+ */
+static rsRetVal
+InitStrgenList(strgenList_t **pListRoot)
+{
+ *pListRoot = NULL;
+ return RS_RET_OK;
+}
+
+
+/* destruct a strgen list. The list elements are destroyed, but the strgen objects
+ * themselves are not modified. (That is done at a late stage during rsyslogd
+ * shutdown and need not be considered here.)
+ */
+static rsRetVal
+DestructStrgenList(strgenList_t **ppListRoot)
+{
+ strgenList_t *pStrgenLst;
+ strgenList_t *pStrgenLstDel;
+
+ pStrgenLst = *ppListRoot;
+ while(pStrgenLst != NULL) {
+ pStrgenLstDel = pStrgenLst;
+ pStrgenLst = pStrgenLst->pNext;
+ free(pStrgenLstDel);
+ }
+ *ppListRoot = NULL;
+ return RS_RET_OK;
+}
+
+
+/* Add a strgen to the list. We use a VERY simple and ineffcient algorithm,
+ * but it is employed only for a few milliseconds during config processing. So
+ * I prefer to keep it very simple and with simple data structures. Unfortunately,
+ * we need to preserve the order, but I don't like to add a tail pointer as that
+ * would require a container object. So I do the extra work to skip to the tail
+ * when adding elements...
+ */
+static rsRetVal
+AddStrgenToList(strgenList_t **ppListRoot, strgen_t *pStrgen)
+{
+ strgenList_t *pThis;
+ strgenList_t *pTail;
+ DEFiRet;
+
+ CHKmalloc(pThis = MALLOC(sizeof(strgenList_t)));
+ pThis->pStrgen = pStrgen;
+ pThis->pNext = NULL;
+
+ if(*ppListRoot == NULL) {
+ pThis->pNext = *ppListRoot;
+ *ppListRoot = pThis;
+ } else {
+ /* find tail first */
+ for(pTail = *ppListRoot ; pTail->pNext != NULL ; pTail = pTail->pNext)
+ /* just search, do nothing else */;
+ /* add at tail */
+ pTail->pNext = pThis;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* find a strgen based on the provided name */
+static rsRetVal
+FindStrgen(strgen_t **ppStrgen, uchar *pName)
+{
+ strgenList_t *pThis;
+ DEFiRet;
+
+ for(pThis = pStrgenLstRoot ; pThis != NULL ; pThis = pThis->pNext) {
+ if(ustrcmp(pThis->pStrgen->pName, pName) == 0) {
+ *ppStrgen = pThis->pStrgen;
+ FINALIZE; /* found it, iRet still eq. OK! */
+ }
+ }
+
+ iRet = RS_RET_PARSER_NOT_FOUND;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* --- END helper functions for strgen list handling --- */
+
+
+BEGINobjConstruct(strgen) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(strgen)
+
+/* ConstructionFinalizer. The most important chore is to add the strgen object
+ * to our global list of available strgens.
+ * rgerhards, 2009-11-03
+ */
+rsRetVal strgenConstructFinalize(strgen_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strgen);
+ CHKiRet(AddStrgenToList(&pStrgenLstRoot, pThis));
+ DBGPRINTF("Strgen '%s' added to list of available strgens.\n", pThis->pName);
+
+finalize_it:
+ RETiRet;
+}
+
+BEGINobjDestruct(strgen) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(strgen)
+ dbgprintf("destructing strgen '%s'\n", pThis->pName);
+ free(pThis->pName);
+ENDobjDestruct(strgen)
+
+/* set the strgen name - string is copied over, call can continue to use it,
+ * but must free it if desired.
+ */
+static rsRetVal
+SetName(strgen_t *pThis, uchar *name)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strgen);
+ assert(name != NULL);
+
+ if(pThis->pName != NULL) {
+ free(pThis->pName);
+ pThis->pName = NULL;
+ }
+
+ CHKmalloc(pThis->pName = ustrdup(name));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* set a pointer to "our" module. Note that no module
+ * pointer must already be set.
+ */
+static rsRetVal
+SetModPtr(strgen_t *pThis, modInfo_t *pMod)
+{
+ ISOBJ_TYPE_assert(pThis, strgen);
+ assert(pMod != NULL);
+ assert(pThis->pModule == NULL);
+ pThis->pModule = pMod;
+ return RS_RET_OK;
+}
+
+
+/* queryInterface function-- rgerhards, 2009-11-03
+ */
+BEGINobjQueryInterface(strgen)
+CODESTARTobjQueryInterface(strgen)
+ if(pIf->ifVersion != strgenCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = strgenConstruct;
+ pIf->ConstructFinalize = strgenConstructFinalize;
+ pIf->Destruct = strgenDestruct;
+ pIf->SetName = SetName;
+ pIf->SetModPtr = SetModPtr;
+ pIf->InitStrgenList = InitStrgenList;
+ pIf->DestructStrgenList = DestructStrgenList;
+ pIf->AddStrgenToList = AddStrgenToList;
+ pIf->FindStrgen = FindStrgen;
+finalize_it:
+ENDobjQueryInterface(strgen)
+
+
+/* This destroys the master strgenlist and all of its strgen entries. MUST only be
+ * done when the module is shut down. Strgen modules are NOT unloaded, rsyslog
+ * does that at a later stage for all dynamically loaded modules.
+ */
+static void
+destroyMasterStrgenList(void)
+{
+ strgenList_t *pStrgenLst;
+ strgenList_t *pStrgenLstDel;
+
+ pStrgenLst = pStrgenLstRoot;
+ while(pStrgenLst != NULL) {
+ strgenDestruct(&pStrgenLst->pStrgen);
+ pStrgenLstDel = pStrgenLst;
+ pStrgenLst = pStrgenLst->pNext;
+ free(pStrgenLstDel);
+ }
+}
+
+/* Exit our class.
+ * rgerhards, 2009-11-04
+ */
+BEGINObjClassExit(strgen, OBJ_IS_CORE_MODULE) /* class, version */
+ destroyMasterStrgenList();
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(ruleset, CORE_COMPONENT);
+ENDObjClassExit(strgen)
+
+
+/* Initialize the strgen class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2009-11-02
+ */
+BEGINObjClassInit(strgen, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+ InitStrgenList(&pStrgenLstRoot);
+ENDObjClassInit(strgen)
+
diff --git a/runtime/strgen.h b/runtime/strgen.h
new file mode 100644
index 00000000..3819dccd
--- /dev/null
+++ b/runtime/strgen.h
@@ -0,0 +1,60 @@
+/* header for strgen.c
+ *
+ * Copyright 2010 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#ifndef INCLUDED_STRGEN_H
+#define INCLUDED_STRGEN_H
+
+
+/* we create a small helper object, a list of strgens, that we can use to
+ * build a chain of them whereever this is needed.
+ */
+struct strgenList_s {
+ strgen_t *pStrgen;
+ strgenList_t *pNext;
+};
+
+
+/* the strgen object, a dummy because we have only static methods */
+struct strgen_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ uchar *pName; /* name of this strgen */
+ modInfo_t *pModule; /* pointer to strgen's module */
+};
+
+/* interfaces */
+BEGINinterface(strgen) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(strgen_t **ppThis);
+ rsRetVal (*ConstructFinalize)(strgen_t *pThis);
+ rsRetVal (*Destruct)(strgen_t **ppThis);
+ rsRetVal (*SetName)(strgen_t *pThis, uchar *name);
+ rsRetVal (*SetModPtr)(strgen_t *pThis, modInfo_t *pMod);
+ rsRetVal (*FindStrgen)(strgen_t **ppThis, uchar*name);
+ rsRetVal (*InitStrgenList)(strgenList_t **pListRoot);
+ rsRetVal (*DestructStrgenList)(strgenList_t **pListRoot);
+ rsRetVal (*AddStrgenToList)(strgenList_t **pListRoot, strgen_t *pStrgen);
+ENDinterface(strgen)
+#define strgenCURR_IF_VERSION 1 /* increment whenever you change the interface above! */
+
+
+/* prototypes */
+PROTOTYPEObj(strgen);
+
+#endif /* #ifndef INCLUDED_STRGEN_H */
diff --git a/runtime/stringbuf.c b/runtime/stringbuf.c
new file mode 100644
index 00000000..cb4f0457
--- /dev/null
+++ b/runtime/stringbuf.c
@@ -0,0 +1,1055 @@
+/* This is the byte-counted string class for rsyslog. It is a replacement
+ * for classical \0 terminated string functions. We introduce it in
+ * the hope it will make the program more secure, obtain some performance
+ * and, most importantly, lay they foundation for syslog-protocol, which
+ * requires strings to be able to handle embedded \0 characters.
+ * Please see syslogd.c for license information.
+ * All functions in this "class" start with rsCStr (rsyslog Counted String).
+ * begun 2005-09-07 rgerhards
+ * did some optimization (read: bugs!) rgerhards, 2009-06-16
+ *
+ * Copyright (C) 2007-2012 Adiscon GmbH
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <libestr.h>
+#include "rsyslog.h"
+#include "stringbuf.h"
+#include "srUtils.h"
+#include "regexp.h"
+#include "obj.h"
+
+uchar* rsCStrGetSzStr(cstr_t *pThis);
+
+/* ################################################################# *
+ * private members *
+ * ################################################################# */
+
+/* static data */
+DEFobjCurrIf(obj)
+DEFobjCurrIf(regexp)
+
+/* ################################################################# *
+ * public members *
+ * ################################################################# */
+
+
+rsRetVal cstrConstruct(cstr_t **ppThis)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ ASSERT(ppThis != NULL);
+
+ CHKmalloc(pThis = (cstr_t*) calloc(1, sizeof(cstr_t)));
+
+ rsSETOBJTYPE(pThis, OIDrsCStr);
+ pThis->pBuf = NULL;
+ pThis->pszBuf = NULL;
+ pThis->iBufSize = 0;
+ pThis->iStrLen = 0;
+ *ppThis = pThis;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* construct from sz string
+ * rgerhards 2005-09-15
+ */
+rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ assert(ppThis != NULL);
+
+ CHKiRet(rsCStrConstruct(&pThis));
+
+ pThis->iBufSize = pThis->iStrLen = strlen((char *) sz);
+ if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) {
+ RSFREEOBJ(pThis);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ /* we do NOT need to copy the \0! */
+ memcpy(pThis->pBuf, sz, pThis->iStrLen);
+
+ *ppThis = pThis;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* a helper function for rsCStr*Strf()
+ */
+static rsRetVal rsCStrConstructFromszStrv(cstr_t **ppThis, uchar *fmt, va_list ap)
+{
+ DEFiRet;
+ cstr_t *pThis;
+ va_list ap2;
+ int len;
+
+ assert(ppThis != NULL);
+
+ va_copy(ap2, ap);
+ len = vsnprintf(NULL, 0, (char*)fmt, ap2);
+ va_end(ap2);
+
+ if(len < 0)
+ ABORT_FINALIZE(RS_RET_ERR);
+
+ CHKiRet(rsCStrConstruct(&pThis));
+
+ pThis->iBufSize = pThis->iStrLen = len;
+ len++; /* account for the \0 written by vsnprintf */
+ if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * len)) == NULL) {
+ RSFREEOBJ(pThis);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ vsnprintf((char*)pThis->pBuf, len, (char*)fmt, ap);
+ *ppThis = pThis;
+finalize_it:
+ RETiRet;
+}
+
+
+/* construct from a printf-style formated string
+ */
+rsRetVal rsCStrConstructFromszStrf(cstr_t **ppThis, char *fmt, ...)
+{
+ DEFiRet;
+ va_list ap;
+
+ va_start(ap, fmt);
+ iRet = rsCStrConstructFromszStrv(ppThis, (uchar*)fmt, ap);
+ va_end(ap);
+
+ RETiRet;
+}
+
+
+/* construct from es_str_t string
+ * rgerhards 2010-12-03
+ */
+rsRetVal cstrConstructFromESStr(cstr_t **ppThis, es_str_t *str)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ assert(ppThis != NULL);
+
+ CHKiRet(rsCStrConstruct(&pThis));
+
+ pThis->iBufSize = pThis->iStrLen = es_strlen(str);
+ if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) {
+ RSFREEOBJ(pThis);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ /* we do NOT need to copy the \0! */
+ memcpy(pThis->pBuf, es_getBufAddr(str), pThis->iStrLen);
+
+ *ppThis = pThis;
+
+finalize_it:
+ RETiRet;
+}
+
+/* construct from CStr object. only the counted string is
+ * copied, not the szString.
+ * rgerhards 2005-10-18
+ */
+rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ assert(ppThis != NULL);
+ rsCHECKVALIDOBJECT(pFrom, OIDrsCStr);
+
+ CHKiRet(rsCStrConstruct(&pThis));
+
+ pThis->iBufSize = pThis->iStrLen = pFrom->iStrLen;
+ if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) {
+ RSFREEOBJ(pThis);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ /* copy properties */
+ memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen);
+
+ *ppThis = pThis;
+finalize_it:
+ RETiRet;
+}
+
+
+void rsCStrDestruct(cstr_t **ppThis)
+{
+ cstr_t *pThis = *ppThis;
+
+ free(pThis->pBuf);
+ free(pThis->pszBuf);
+ RSFREEOBJ(pThis);
+ *ppThis = NULL;
+}
+
+
+/* extend the string buffer if its size is insufficient.
+ * Param iMinNeeded is the minumum free space needed. If it is larger
+ * than the default alloc increment, space for at least this amount is
+ * allocated. In practice, a bit more is allocated because we envision that
+ * some more characters may be added after these.
+ * rgerhards, 2008-01-07
+ * changed to utilized realloc() -- rgerhards, 2009-06-16
+ */
+rsRetVal
+rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded)
+{
+ uchar *pNewBuf;
+ size_t iNewSize;
+ DEFiRet;
+
+ /* first compute the new size needed */
+ if(iMinNeeded > RS_STRINGBUF_ALLOC_INCREMENT) {
+ /* we allocate "n" ALLOC_INCREMENTs. Usually, that should
+ * leave some room after the absolutely needed one. It also
+ * reduces memory fragmentation. Note that all of this are
+ * integer operations (very important to understand what is
+ * going on)! Parenthesis are for better readibility.
+ */
+ iNewSize = (iMinNeeded / RS_STRINGBUF_ALLOC_INCREMENT + 1) * RS_STRINGBUF_ALLOC_INCREMENT;
+ } else {
+ iNewSize = pThis->iBufSize + RS_STRINGBUF_ALLOC_INCREMENT;
+ }
+ iNewSize += pThis->iBufSize; /* add current size */
+
+ /* DEV debugging only: dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize); */
+ CHKmalloc(pNewBuf = (uchar*) realloc(pThis->pBuf, iNewSize * sizeof(uchar)));
+ pThis->iBufSize = iNewSize;
+ pThis->pBuf = pNewBuf;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* append a string of known length. In this case, we make sure we do at most
+ * one additional memory allocation.
+ * I optimized this function to use memcpy(), among others. Consider it a
+ * rewrite (which may be good to know in case of bugs) -- rgerhards, 2008-01-07
+ */
+rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen)
+{
+ DEFiRet;
+
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ assert(psz != NULL);
+
+ /* does the string fit? */
+ if(pThis->iStrLen + iStrLen > pThis->iBufSize) {
+ CHKiRet(rsCStrExtendBuf(pThis, iStrLen)); /* need more memory! */
+ }
+
+ /* ok, now we always have sufficient continues memory to do a memcpy() */
+ memcpy(pThis->pBuf + pThis->iStrLen, psz, iStrLen);
+ pThis->iStrLen += iStrLen;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* changed to be a wrapper to rsCStrAppendStrWithLen() so that
+ * we can save some time when we have the length but do not
+ * need to change existing code.
+ * rgerhards, 2007-07-03
+ */
+rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz)
+{
+ return rsCStrAppendStrWithLen(pThis, psz, strlen((char*) psz));
+}
+
+
+/* append the contents of one cstr_t object to another
+ * rgerhards, 2008-02-25
+ */
+rsRetVal cstrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend)
+{
+ return rsCStrAppendStrWithLen(pThis, pstrAppend->pBuf, pstrAppend->iStrLen);
+}
+
+
+/* append a printf-style formated string
+ */
+rsRetVal rsCStrAppendStrf(cstr_t *pThis, uchar *fmt, ...)
+{
+ DEFiRet;
+ va_list ap;
+ cstr_t *pStr = NULL;
+
+ va_start(ap, fmt);
+ iRet = rsCStrConstructFromszStrv(&pStr, fmt, ap);
+ va_end(ap);
+
+ CHKiRet(iRet);
+
+ iRet = cstrAppendCStr(pThis, pStr);
+ rsCStrDestruct(&pStr);
+finalize_it:
+ RETiRet;
+}
+
+
+rsRetVal rsCStrAppendInt(cstr_t *pThis, long i)
+{
+ DEFiRet;
+ uchar szBuf[32];
+
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+
+ CHKiRet(srUtilItoA((char*) szBuf, sizeof(szBuf), i));
+
+ iRet = rsCStrAppendStr(pThis, szBuf);
+finalize_it:
+ RETiRet;
+}
+
+
+/* Sets the string object to the classigal sz-string provided.
+ * Any previously stored vlaue is discarded. If a NULL pointer
+ * the the new value (pszNew) is provided, an empty string is
+ * created (this is NOT an error!).
+ * rgerhards, 2005-10-18
+ */
+rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+
+ free(pThis->pBuf);
+ free(pThis->pszBuf);
+ if(pszNew == NULL) {
+ pThis->iStrLen = 0;
+ pThis->iBufSize = 0;
+ pThis->pBuf = NULL;
+ pThis->pszBuf = NULL;
+ } else {
+ pThis->iStrLen = strlen((char*)pszNew);
+ pThis->iBufSize = pThis->iStrLen;
+ pThis->pszBuf = NULL;
+
+ /* now save the new value */
+ if((pThis->pBuf = (uchar*) MALLOC(sizeof(uchar) * pThis->iStrLen)) == NULL) {
+ RSFREEOBJ(pThis);
+ return RS_RET_OUT_OF_MEMORY;
+ }
+
+ /* we do NOT need to copy the \0! */
+ memcpy(pThis->pBuf, pszNew, pThis->iStrLen);
+ }
+
+ return RS_RET_OK;
+}
+
+/* Converts the CStr object to a classical sz string and returns that.
+ * Same restrictions as in rsCStrGetSzStr() applies (see there!). This
+ * function here guarantees that a valid string is returned, even if
+ * the CStr object currently holds a NULL pointer string buffer. If so,
+ * "" is returned.
+ * rgerhards 2005-10-19
+ * WARNING: The returned pointer MUST NOT be freed, as it may be
+ * obtained from that constant memory pool (in case of NULL!)
+ */
+uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ if(pThis->pBuf == NULL)
+ return (uchar*) "";
+ else
+ return rsCStrGetSzStr(pThis);
+}
+
+
+/* Converts the CStr object to a classical zero-terminated C string
+ * and returns that string. The caller must not free it and must not
+ * destroy the CStr object as long as the ascii string is used.
+ * This function may return NULL, if the string is currently NULL. This
+ * is a feature, not a bug. If you need non-NULL in any case, use
+ * rsCStrGetSzStrNoNULL() instead.
+ * rgerhards, 2005-09-15
+ */
+uchar* rsCStrGetSzStr(cstr_t *pThis)
+{
+ size_t i;
+
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+
+ if(pThis->pBuf != NULL)
+ if(pThis->pszBuf == NULL) {
+ /* we do not yet have a usable sz version - so create it... */
+ if((pThis->pszBuf = MALLOC((pThis->iStrLen + 1) * sizeof(uchar))) == NULL) {
+ /* TODO: think about what to do - so far, I have no bright
+ * idea... rgerhards 2005-09-07
+ */
+ }
+ else { /* we can create the sz String */
+ /* now copy it while doing a sanity check. The string might contain a
+ * \0 byte. There is no way how a sz string can handle this. For
+ * the time being, we simply replace it with space - something that
+ * could definitely be improved (TODO).
+ * 2005-09-15 rgerhards
+ */
+ for(i = 0 ; i < pThis->iStrLen ; ++i) {
+ if(pThis->pBuf[i] == '\0')
+ pThis->pszBuf[i] = ' ';
+ else
+ pThis->pszBuf[i] = pThis->pBuf[i];
+ }
+ /* write terminator... */
+ pThis->pszBuf[i] = '\0';
+ }
+ }
+
+ return(pThis->pszBuf);
+}
+
+
+/* Converts the CStr object to a classical zero-terminated C string,
+ * returns that string and destroys the CStr object. The returned string
+ * MUST be freed by the caller. The function might return NULL if
+ * no memory can be allocated.
+ *
+ * This is the NEW replacement for rsCStrConvSzStrAndDestruct which does
+ * no longer utilize a special buffer but soley works on pBuf (and also
+ * assumes that cstrFinalize had been called).
+ *
+ * Parameters are as follows:
+ * pointer to the object, pointer to string-pointer to receive string and
+ * bRetNULL: 0 - must not return NULL on empty string, return "" in that
+ * case, 1 - return NULL instead of an empty string.
+ * PLEASE NOTE: the caller must free the memory returned in ppSz in any case
+ * (except, of course, if it is NULL).
+ */
+rsRetVal cstrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL)
+{
+ DEFiRet;
+ uchar* pRetBuf;
+
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ assert(ppSz != NULL);
+ assert(bRetNULL == 0 || bRetNULL == 1);
+
+ if(pThis->pBuf == NULL) {
+ if(bRetNULL == 0) {
+ CHKmalloc(pRetBuf = MALLOC(sizeof(uchar)));
+ *pRetBuf = '\0';
+ } else {
+ pRetBuf = NULL;
+ }
+ } else
+ pRetBuf = pThis->pBuf;
+
+ *ppSz = pRetBuf;
+
+finalize_it:
+ /* We got it, now free the object ourselfs. Please note
+ * that we can NOT use the rsCStrDestruct function as it would
+ * also free the sz String buffer, which we pass on to the user.
+ */
+ RSFREEOBJ(pThis);
+ RETiRet;
+}
+
+
+/* return the length of the current string
+ * 2005-09-09 rgerhards
+ * Please note: this is only a function in a debug build.
+ * For release builds, it is a macro defined in stringbuf.h.
+ * This is due to performance reasons.
+ */
+#ifndef NDEBUG
+int cstrLen(cstr_t *pThis)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ return(pThis->iStrLen);
+}
+#endif
+
+/* Truncate characters from the end of the string.
+ * rgerhards 2005-09-15
+ */
+rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+
+ if(pThis->iStrLen < nTrunc)
+ return RS_TRUNCAT_TOO_LARGE;
+
+ pThis->iStrLen -= nTrunc;
+
+ if(pThis->pszBuf != NULL) {
+ /* in this case, we adjust the psz representation
+ * by writing a new \0 terminator - this is by far
+ * the fastest way and outweights the additional memory
+ * required. 2005-9-19 rgerhards.
+ */
+ pThis->pszBuf[pThis->iStrLen] = '\0';
+ }
+
+ return RS_RET_OK;
+}
+
+/* Trim trailing whitespace from a given string
+ */
+rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis)
+{
+ register int i;
+ register uchar *pC;
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+
+ i = pThis->iStrLen;
+ pC = pThis->pBuf + i - 1;
+ while(i > 0 && isspace((int)*pC)) {
+ --pC;
+ --i;
+ }
+ /* i now is the new string length! */
+ pThis->iStrLen = i;
+
+ return RS_RET_OK;
+}
+
+/* Trim trailing whitespace from a given string
+ */
+rsRetVal cstrTrimTrailingWhiteSpace(cstr_t *pThis)
+{
+ register int i;
+ register uchar *pC;
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+
+ if(pThis->iStrLen == 0)
+ goto done; /* empty string -> nothing to trim ;) */
+ i = pThis->iStrLen;
+ pC = pThis->pBuf + i - 1;
+ while(i > 0 && isspace((int)*pC)) {
+ --pC;
+ --i;
+ }
+ /* i now is the new string length! */
+ pThis->iStrLen = i;
+ pThis->pBuf[pThis->iStrLen] = '0'; /* we always have this space */
+
+done: return RS_RET_OK;
+}
+
+/* compare two string objects - works like strcmp(), but operates
+ * on CStr objects. Please note that this version here is
+ * faster in the majority of cases, simply because it can
+ * rely on StrLen.
+ * rgerhards 2005-09-19
+ * fixed bug, in which only the last byte was actually compared
+ * in equal-size strings.
+ * rgerhards, 2005-09-26
+ */
+int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2)
+{
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ rsCHECKVALIDOBJECT(pCS2, OIDrsCStr);
+ if(pCS1->iStrLen == pCS2->iStrLen)
+ if(pCS1->iStrLen == 0)
+ return 0; /* zero-sized string are equal ;) */
+ else { /* we now have two non-empty strings of equal
+ * length, so we need to actually check if they
+ * are equal.
+ */
+ register size_t i;
+ for(i = 0 ; i < pCS1->iStrLen ; ++i) {
+ if(pCS1->pBuf[i] != pCS2->pBuf[i])
+ return pCS1->pBuf[i] - pCS2->pBuf[i];
+ }
+ /* if we arrive here, the strings are equal */
+ return 0;
+ }
+ else
+ return pCS1->iStrLen - pCS2->iStrLen;
+}
+
+
+/* check if a sz-type string starts with a CStr object. This function
+ * is initially written to support the "startswith" property-filter
+ * comparison operation. Maybe it also has other needs.
+ * This functions is modelled after the strcmp() series, thus a
+ * return value of 0 indicates that the string starts with the
+ * sequence while -1 indicates it does not!
+ * rgerhards 2005-10-19
+ */
+int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz)
+{
+ register int i;
+ int iMax;
+
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ assert(psz != NULL);
+ assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */
+ if(iLenSz >= pCS1->iStrLen) {
+ /* we need to checkusing pCS1->iStrLen charactes at maximum, thus
+ * we move it to iMax.
+ */
+ iMax = pCS1->iStrLen;
+ if(iMax == 0)
+ return 0; /* yes, it starts with a zero-sized string ;) */
+ else { /* we now have something to compare, so let's do it... */
+ for(i = 0 ; i < iMax ; ++i) {
+ if(psz[i] != pCS1->pBuf[i])
+ return psz[i] - pCS1->pBuf[i];
+ }
+ /* if we arrive here, the string actually starts with pCS1 */
+ return 0;
+ }
+ }
+ else
+ return -1; /* pCS1 is less then psz */
+}
+
+
+/* check if a CStr object starts with a sz-type string.
+ * This functions is modelled after the strcmp() series, thus a
+ * return value of 0 indicates that the string starts with the
+ * sequence while -1 indicates it does not!
+ * rgerhards 2005-09-26
+ */
+int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz)
+{
+ register size_t i;
+
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ assert(psz != NULL);
+ assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */
+ if(pCS1->iStrLen >= iLenSz) {
+ /* we are using iLenSz below, because we need to check
+ * iLenSz characters at maximum (start with!)
+ */
+ if(iLenSz == 0)
+ return 0; /* yes, it starts with a zero-sized string ;) */
+ else { /* we now have something to compare, so let's do it... */
+ for(i = 0 ; i < iLenSz ; ++i) {
+ if(pCS1->pBuf[i] != psz[i])
+ return pCS1->pBuf[i] - psz[i];
+ }
+ /* if we arrive here, the string actually starts with psz */
+ return 0;
+ }
+ }
+ else
+ return -1; /* pCS1 is less then psz */
+}
+
+
+/* The same as rsCStrStartsWithSzStr(), but does a case-insensitive
+ * comparison. TODO: consolidate the two.
+ * rgerhards 2008-02-28
+ */
+int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz)
+{
+ register size_t i;
+
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ assert(psz != NULL);
+ assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */
+ if(pCS1->iStrLen >= iLenSz) {
+ /* we are using iLenSz below, because we need to check
+ * iLenSz characters at maximum (start with!)
+ */
+ if(iLenSz == 0)
+ return 0; /* yes, it starts with a zero-sized string ;) */
+ else { /* we now have something to compare, so let's do it... */
+ for(i = 0 ; i < iLenSz ; ++i) {
+ if(tolower(pCS1->pBuf[i]) != tolower(psz[i]))
+ return tolower(pCS1->pBuf[i]) - tolower(psz[i]);
+ }
+ /* if we arrive here, the string actually starts with psz */
+ return 0;
+ }
+ }
+ else
+ return -1; /* pCS1 is less then psz */
+}
+
+
+/* check if a CStr object matches a regex.
+ * msamia@redhat.com 2007-07-12
+ * @return returns 0 if matched
+ * bug: doesn't work for CStr containing \0
+ * rgerhards, 2007-07-16: bug is no real bug, because rsyslogd ensures there
+ * never is a \0 *inside* a property string.
+ * Note that the function returns -1 if regexp functionality is not available.
+ * rgerhards: 2009-03-04: ERE support added, via parameter iType: 0 - BRE, 1 - ERE
+ * Arnaud Cornet/rgerhards: 2009-04-02: performance improvement by caching compiled regex
+ * If a caller does not need the cached version, it must still provide memory for it
+ * and must call rsCStrRegexDestruct() afterwards.
+ */
+rsRetVal rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz, int iType, void *rc)
+{
+ regex_t **cache = (regex_t**) rc;
+ int ret;
+ DEFiRet;
+
+ assert(pCS1 != NULL);
+ assert(psz != NULL);
+ assert(cache != NULL);
+
+ if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) {
+ if (*cache == NULL) {
+ *cache = calloc(sizeof(regex_t), 1);
+ regexp.regcomp(*cache, (char*) rsCStrGetSzStr(pCS1), (iType == 1 ? REG_EXTENDED : 0) | REG_NOSUB);
+ }
+ ret = regexp.regexec(*cache, (char*) psz, 0, NULL, 0);
+ if(ret != 0)
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ } else {
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* free a cached compiled regex
+ * Caller must provide a pointer to a buffer that was created by
+ * rsCStrSzStrMatchRegexCache()
+ */
+void rsCStrRegexDestruct(void *rc)
+{
+ regex_t **cache = rc;
+
+ assert(cache != NULL);
+ assert(*cache != NULL);
+
+ if(objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) {
+ regexp.regfree(*cache);
+ free(*cache);
+ *cache = NULL;
+ }
+}
+
+
+/* compare a rsCStr object with a classical sz string. This function
+ * is almost identical to rsCStrZsStrCmp(), but it also takes an offset
+ * to the CStr object from where the comparison is to start.
+ * I have thought quite a while if it really makes sense to more or
+ * less duplicate the code. After all, if you call it with an offset of
+ * zero, the functionality is exactly the same. So it looks natural to
+ * just have a single function. However, supporting the offset requires
+ * some (few) additional integer operations. While they are few, they
+ * happen at places in the code that is run very frequently. All in all,
+ * I have opted for performance and thus duplicated the code. I hope
+ * this is a good, or at least acceptable, compromise.
+ * rgerhards, 2005-09-26
+ * This function also has an offset-pointer which allows to
+ * specify *where* the compare operation should begin in
+ * the CStr. If everything is to be compared, it must be set
+ * to 0. If some leading bytes are to be skipped, it must be set
+ * to the first index that is to be compared. It must not be
+ * set higher than the string length (this is considered a
+ * program bug and will lead to unpredictable results and program aborts).
+ * rgerhards 2005-09-26
+ */
+int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz)
+{
+ BEGINfunc
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ assert(iOffset < pCS1->iStrLen);
+ assert(psz != NULL);
+ assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */
+ if((pCS1->iStrLen - iOffset) == iLenSz) {
+ /* we are using iLenSz below, because the lengths
+ * are equal and iLenSz is faster to access
+ */
+ if(iLenSz == 0) {
+ return 0; /* zero-sized strings are equal ;) */
+ ENDfunc
+ } else { /* we now have two non-empty strings of equal
+ * length, so we need to actually check if they
+ * are equal.
+ */
+ register size_t i;
+ for(i = 0 ; i < iLenSz ; ++i) {
+ if(pCS1->pBuf[i+iOffset] != psz[i])
+ return pCS1->pBuf[i+iOffset] - psz[i];
+ }
+ /* if we arrive here, the strings are equal */
+ return 0;
+ ENDfunc
+ }
+ }
+ else {
+ return pCS1->iStrLen - iOffset - iLenSz;
+ ENDfunc
+ }
+}
+
+
+/* Converts a string to a number. If the string dos not contain a number,
+ * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined.
+ * If all goes well, pNumber contains the number that the string was converted
+ * to.
+ */
+rsRetVal
+rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber)
+{
+ DEFiRet;
+ number_t n;
+ int bIsNegative;
+ size_t i;
+
+ ASSERT(pStr != NULL);
+ ASSERT(pNumber != NULL);
+
+ if(pStr->iStrLen == 0) {
+ /* can be converted to 0! (by convention) */
+ pNumber = 0;
+ FINALIZE;
+ }
+
+ /* first skip whitespace (if present) */
+ for(i = 0 ; i < pStr->iStrLen && isspace(pStr->pBuf[i]) ; ++i) {
+ /*DO NOTHING*/
+ }
+
+ /* we have a string, so let's check its syntax */
+ if(pStr->pBuf[i] == '+') {
+ ++i; /* skip that char */
+ bIsNegative = 0;
+ } else if(pStr->pBuf[0] == '-') {
+ ++i; /* skip that char */
+ bIsNegative = 1;
+ } else {
+ bIsNegative = 0;
+ }
+
+ /* TODO: octal? hex? */
+ n = 0;
+ while(i < pStr->iStrLen && isdigit(pStr->pBuf[i])) {
+ n = n * 10 + pStr->pBuf[i] - '0';
+ ++i;
+ }
+
+ if(i < pStr->iStrLen) /* non-digits before end of string? */
+ ABORT_FINALIZE(RS_RET_NOT_A_NUMBER);
+
+ if(bIsNegative)
+ n *= -1;
+
+ /* we got it, so return the number */
+ *pNumber = n;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Converts a string to a boolen. First tries to convert to a number. If
+ * that succeeds, we are done (number is then used as boolean value). If
+ * that fails, we look if the string is "yes" or "true". If so, a value
+ * of 1 is returned. In all other cases, a value of 0 is returned. Please
+ * note that we do not have a specific boolean type, so we return a number.
+ * so, these are
+ * RS_RET_NOT_A_NUMBER is returned and the contents of pNumber is undefined.
+ * If all goes well, pNumber contains the number that the string was converted
+ * to.
+ */
+rsRetVal
+rsCStrConvertToBool(cstr_t *pStr, number_t *pBool)
+{
+ DEFiRet;
+
+ ASSERT(pStr != NULL);
+ ASSERT(pBool != NULL);
+
+ iRet = rsCStrConvertToNumber(pStr, pBool);
+
+ if(iRet != RS_RET_NOT_A_NUMBER) {
+ FINALIZE; /* in any case, we have nothing left to do */
+ }
+
+ /* TODO: maybe we can do better than strcasecmp ;) -- overhead! */
+ if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "true")) {
+ *pBool = 1;
+ } else if(!strcasecmp((char*)rsCStrGetSzStr(pStr), "yes")) {
+ *pBool = 1;
+ } else {
+ *pBool = 0;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* compare a rsCStr object with a classical sz string.
+ * Just like rsCStrCStrCmp, just for a different data type.
+ * There must not only the sz string but also its length be
+ * provided. If the caller does not know the length he can
+ * call with
+ * rsCstrSzStrCmp(pCS, psz, strlen((char*)psz));
+ * we are not doing the strlen((char*)) ourselfs as the caller might
+ * already know the length and in such cases we can save the
+ * overhead of doing it one more time (strelen() is costly!).
+ * The bottom line is that the provided length MUST be correct!
+ * The to sz string pointer must not be NULL!
+ * rgerhards 2005-09-26
+ */
+int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz)
+{
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ assert(psz != NULL);
+ assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */
+ if(pCS1->iStrLen == iLenSz)
+ /* we are using iLenSz below, because the lengths
+ * are equal and iLenSz is faster to access
+ */
+ if(iLenSz == 0)
+ return 0; /* zero-sized strings are equal ;) */
+ else { /* we now have two non-empty strings of equal
+ * length, so we need to actually check if they
+ * are equal.
+ */
+ return strncmp((char*)pCS1->pBuf, (char*)psz, iLenSz);
+ }
+ else
+ return pCS1->iStrLen - iLenSz;
+}
+
+
+/* Locate the first occurence of this rsCStr object inside a standard sz string.
+ * Returns the offset (0-bound) of this first occurrence. If not found, -1 is
+ * returned. Both parameters MUST be given (NULL is not allowed).
+ * rgerhards 2005-09-19
+ */
+int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz)
+{
+ int i;
+ int iMax;
+ int bFound;
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ assert(sz != NULL);
+
+ if(pThis->iStrLen == 0)
+ return 0;
+
+ /* compute the largest index where a match could occur - after all,
+ * the to-be-located string must be able to be present in the
+ * searched string (it needs its size ;)).
+ */
+ iMax = strlen((char*)sz) - pThis->iStrLen;
+
+ bFound = 0;
+ i = 0;
+ while(i <= iMax && !bFound) {
+ size_t iCheck;
+ uchar *pComp = sz + i;
+ for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck)
+ if(*(pComp + iCheck) != *(pThis->pBuf + iCheck))
+ break;
+ if(iCheck == pThis->iStrLen)
+ bFound = 1; /* found! - else it wouldn't be equal */
+ else
+ ++i; /* on to the next try */
+ }
+
+ return(bFound ? i : -1);
+}
+
+
+/* This is the same as rsCStrLocateInSzStr(), but does a case-insensitve
+ * comparison.
+ * TODO: over time, consolidate the two.
+ * rgerhards, 2008-02-28
+ */
+int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz)
+{
+ int i;
+ int iMax;
+ int bFound;
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ assert(sz != NULL);
+
+ if(pThis->iStrLen == 0)
+ return 0;
+
+ /* compute the largest index where a match could occur - after all,
+ * the to-be-located string must be able to be present in the
+ * searched string (it needs its size ;)).
+ */
+ iMax = strlen((char*)sz) - pThis->iStrLen;
+
+ bFound = 0;
+ i = 0;
+ while(i <= iMax && !bFound) {
+ size_t iCheck;
+ uchar *pComp = sz + i;
+ for(iCheck = 0 ; iCheck < pThis->iStrLen ; ++iCheck)
+ if(tolower(*(pComp + iCheck)) != tolower(*(pThis->pBuf + iCheck)))
+ break;
+ if(iCheck == pThis->iStrLen)
+ bFound = 1; /* found! - else it wouldn't be equal */
+ else
+ ++i; /* on to the next try */
+ }
+
+ return(bFound ? i : -1);
+}
+
+
+/* our exit function. TODO: remove once converted to a class
+ * rgerhards, 2008-03-11
+ */
+rsRetVal strExit()
+{
+ DEFiRet;
+ objRelease(regexp, LM_REGEXP_FILENAME);
+ RETiRet;
+}
+
+
+/* our init function. TODO: remove once converted to a class
+ */
+rsRetVal strInit()
+{
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* vi:set ai:
+ */
diff --git a/runtime/stringbuf.h b/runtime/stringbuf.h
new file mode 100644
index 00000000..d0502a5b
--- /dev/null
+++ b/runtime/stringbuf.h
@@ -0,0 +1,230 @@
+/* stringbuf.h
+ * The counted string object
+ *
+ * This is the byte-counted string class for rsyslog. It is a replacement
+ * for classical \0 terminated string functions. We introduce it in
+ * the hope it will make the program more secure, obtain some performance
+ * and, most importantly, lay they foundation for syslog-protocol, which
+ * requires strings to be able to handle embedded \0 characters.
+ *
+ * \author Rainer Gerhards <rgerhards@adiscon.com>
+ * \date 2005-09-07
+ * Initial version begun.
+ *
+ * Copyright 2005-2012 Adiscon GmbH. All Rights Reserved.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef _STRINGBUF_H_INCLUDED__
+#define _STRINGBUF_H_INCLUDED__ 1
+
+#include <assert.h>
+#include <libestr.h>
+
+/**
+ * The dynamic string buffer object.
+ */
+typedef struct cstr_s
+{
+#ifndef NDEBUG
+ rsObjID OID; /**< object ID */
+#endif
+ uchar *pBuf; /**< pointer to the string buffer, may be NULL if string is empty */
+ uchar *pszBuf; /**< pointer to the sz version of the string (after it has been created )*/
+ size_t iBufSize; /**< current maximum size of the string buffer */
+ size_t iStrLen; /**< length of the string in characters. */
+} cstr_t;
+
+
+/**
+ * Construct a rsCStr object.
+ */
+rsRetVal cstrConstruct(cstr_t **ppThis);
+#define rsCStrConstruct(x) cstrConstruct((x))
+rsRetVal cstrConstructFromESStr(cstr_t **ppThis, es_str_t *str);
+rsRetVal rsCStrConstructFromszStr(cstr_t **ppThis, uchar *sz);
+rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, cstr_t *pFrom);
+rsRetVal rsCStrConstructFromszStrf(cstr_t **ppThis, char *fmt, ...) __attribute__((format(printf,2, 3)));
+
+/**
+ * Destruct the string buffer object.
+ */
+void rsCStrDestruct(cstr_t **ppThis);
+#define cstrDestruct(x) rsCStrDestruct((x))
+
+
+/* Append a character to the current string object. This may only be done until
+ * cstrFinalize() is called.
+ * rgerhards, 2009-06-16
+ */
+rsRetVal rsCStrExtendBuf(cstr_t *pThis, size_t iMinNeeded); /* our helper, NOT a public interface! */
+static inline rsRetVal cstrAppendChar(cstr_t *pThis, uchar c)
+{
+ rsRetVal iRet = RS_RET_OK;
+
+ if(pThis->iStrLen >= pThis->iBufSize) {
+ CHKiRet(rsCStrExtendBuf(pThis, 1)); /* need more memory! */
+ }
+
+ /* ok, when we reach this, we have sufficient memory */
+ *(pThis->pBuf + pThis->iStrLen++) = c;
+
+finalize_it:
+ return iRet;
+}
+
+
+/* some inline functions for things that are really frequently called... */
+
+/* Finalize the string object. This must be called after all data is added to it
+ * but before that data is used.
+ * rgerhards, 2009-06-16
+ */
+static inline rsRetVal
+cstrFinalize(cstr_t *pThis)
+{
+ rsRetVal iRet = RS_RET_OK;
+
+ if(pThis->iStrLen > 0) {
+ /* terminate string only if one exists */
+ CHKiRet(cstrAppendChar(pThis, '\0'));
+ --pThis->iStrLen; /* do NOT count the \0 byte */
+ }
+
+finalize_it:
+ return iRet;
+}
+
+
+/* Returns the cstr data as a classical C sz string. We use that the
+ * Finalizer did properly terminate our string (but we may stil be NULL).
+ * So it is vital that the finalizer is called BEFORe this function here!
+ * The caller must not free or otherwise manipulate the returned string and must not
+ * destroy the CStr object as long as the ascii string is used.
+ * This function may return NULL, if the string is currently NULL. This
+ * is a feature, not a bug. If you need non-NULL in any case, use
+ * cstrGetSzStrNoNULL() instead.
+ * Note that due to the new single-buffer interface this function almost does nothing!
+ * rgerhards, 2006-09-16
+ */
+static inline uchar* cstrGetSzStr(cstr_t *pThis)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ return(pThis->pBuf);
+}
+
+
+/* Converts the CStr object to a classical sz string and returns that.
+ * Same restrictions as in cstrGetSzStr() applies (see there!). This
+ * function here guarantees that a valid string is returned, even if
+ * the CStr object currently holds a NULL pointer string buffer. If so,
+ * "" is returned.
+ * rgerhards 2005-10-19
+ * WARNING: The returned pointer MUST NOT be freed, as it may be
+ * obtained from that constant memory pool (in case of NULL!)
+ */
+static inline uchar* cstrGetSzStrNoNULL(cstr_t *pThis)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ if(pThis->pBuf == NULL)
+ return (uchar*) "";
+ else
+ return cstrGetSzStr(pThis);
+}
+
+
+/**
+ * Truncate "n" number of characters from the end of the
+ * string. The buffer remains unchanged, just the
+ * string length is manipulated. This is for performance
+ * reasons.
+ */
+rsRetVal rsCStrTruncate(cstr_t *pThis, size_t nTrunc);
+
+rsRetVal rsCStrTrimTrailingWhiteSpace(cstr_t *pThis);
+rsRetVal cstrTrimTrailingWhiteSpace(cstr_t *pThis);
+
+/**
+ * Append a string to the buffer. For performance reasons,
+ * use rsCStrAppenStrWithLen() if you know the length.
+ *
+ * \param psz pointer to string to be appended. Must not be NULL.
+ */
+rsRetVal rsCStrAppendStr(cstr_t *pThis, uchar* psz);
+
+/**
+ * Append a string to the buffer.
+ *
+ * \param psz pointer to string to be appended. Must not be NULL.
+ * \param iStrLen the length of the string pointed to by psz
+ */
+rsRetVal rsCStrAppendStrWithLen(cstr_t *pThis, uchar* psz, size_t iStrLen);
+
+/**
+ * Append a printf-style formated string to the buffer.
+ *
+ * \param fmt pointer to the format string (see man 3 printf for details). Must not be NULL.
+ */
+rsRetVal rsCStrAppendStrf(cstr_t *pThis, uchar *fmt, ...);
+
+/**
+ * Append an integer to the string. No special formatting is
+ * done.
+ */
+rsRetVal rsCStrAppendInt(cstr_t *pThis, long i);
+
+
+rsRetVal strExit(void); /* TODO: remove once we have a real object interface! */
+uchar* __attribute__((deprecated)) rsCStrGetSzStr(cstr_t *pThis);
+uchar* rsCStrGetSzStrNoNULL(cstr_t *pThis);
+rsRetVal rsCStrSetSzStr(cstr_t *pThis, uchar *pszNew);
+int rsCStrCStrCmp(cstr_t *pCS1, cstr_t *pCS2);
+int rsCStrSzStrCmp(cstr_t *pCS1, uchar *psz, size_t iLenSz);
+int rsCStrOffsetSzStrCmp(cstr_t *pCS1, size_t iOffset, uchar *psz, size_t iLenSz);
+int rsCStrLocateSzStr(cstr_t *pCStr, uchar *sz);
+int rsCStrLocateInSzStr(cstr_t *pThis, uchar *sz);
+int rsCStrCaseInsensitiveLocateInSzStr(cstr_t *pThis, uchar *sz);
+int rsCStrStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz);
+int rsCStrCaseInsensitveStartsWithSzStr(cstr_t *pCS1, uchar *psz, size_t iLenSz);
+int rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz);
+rsRetVal rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz, int iType, void *cache);
+void rsCStrRegexDestruct(void *rc);
+rsRetVal rsCStrConvertToNumber(cstr_t *pStr, number_t *pNumber);
+rsRetVal rsCStrConvertToBool(cstr_t *pStr, number_t *pBool);
+
+/* in migration */
+#define rsCStrAppendCStr(pThis, pstrAppend) cstrAppendCStr(pThis, pstrAppend)
+
+/* new calling interface */
+rsRetVal cstrFinalize(cstr_t *pThis);
+rsRetVal cstrConvSzStrAndDestruct(cstr_t *pThis, uchar **ppSz, int bRetNULL);
+rsRetVal cstrAppendCStr(cstr_t *pThis, cstr_t *pstrAppend);
+
+/* now come inline-like functions */
+#ifdef NDEBUG
+# define cstrLen(x) ((int)((x)->iStrLen))
+#else
+ int cstrLen(cstr_t *pThis);
+#endif
+#define rsCStrLen(s) cstrLen((s))
+
+#define rsCStrGetBufBeg(x) ((x)->pBuf)
+
+rsRetVal strInit();
+rsRetVal strExit();
+
+#endif /* single include */
diff --git a/runtime/strms_sess.c b/runtime/strms_sess.c
new file mode 100644
index 00000000..2537e8d8
--- /dev/null
+++ b/runtime/strms_sess.c
@@ -0,0 +1,303 @@
+/* strms_sess.c
+ *
+ * This implements a session of the strmsrv object. For general
+ * comments, see header of strmsrv.c.
+ *
+ * Copyright 2007-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include "rsyslog.h"
+#include "dirty.h"
+#include "module-template.h"
+#include "net.h"
+#include "strmsrv.h"
+#include "strms_sess.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "netstrm.h"
+#include "msg.h"
+#include "prop.h"
+#include "datetime.h"
+
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(netstrm)
+DEFobjCurrIf(datetime)
+
+static int iMaxLine; /* maximum size of a single message */
+
+/* forward definitions */
+static rsRetVal Close(strms_sess_t *pThis);
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(strms_sess) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(strms_sess)
+
+
+/* ConstructionFinalizer
+ */
+static rsRetVal
+strms_sessConstructFinalize(strms_sess_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ if(pThis->pSrv->OnSessConstructFinalize != NULL) {
+ CHKiRet(pThis->pSrv->OnSessConstructFinalize(&pThis->pUsr));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* destructor for the strms_sess object */
+BEGINobjDestruct(strms_sess) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(strms_sess)
+ if(pThis->pStrm != NULL)
+ netstrm.Destruct(&pThis->pStrm);
+
+ if(pThis->pSrv->pOnSessDestruct != NULL) {
+ pThis->pSrv->pOnSessDestruct(&pThis->pUsr);
+ }
+ /* now destruct our own properties */
+ free(pThis->fromHost);
+ if(pThis->fromHostIP != NULL)
+ prop.Destruct(&pThis->fromHostIP);
+ENDobjDestruct(strms_sess)
+
+
+/* debugprint for the strms_sess object */
+BEGINobjDebugPrint(strms_sess) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(strms_sess)
+ENDobjDebugPrint(strms_sess)
+
+
+/* set property functions */
+/* set the hostname. Note that the caller *hands over* the string. That is,
+ * the caller no longer controls it once SetHost() has received it. Most importantly,
+ * the caller must not free it. -- rgerhards, 2008-04-24
+ */
+static rsRetVal
+SetHost(strms_sess_t *pThis, uchar *pszHost)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ free(pThis->fromHost);
+ pThis->fromHost = pszHost;
+ RETiRet;
+}
+
+/* set the remote host's IP. Note that the caller *hands over* the property. That is,
+ * the caller no longer controls it once SetHostIP() has received it. Most importantly,
+ * the caller must not destruct it. -- rgerhards, 2008-05-16
+ */
+static rsRetVal
+SetHostIP(strms_sess_t *pThis, prop_t *ip)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ if(pThis->fromHostIP != NULL)
+ prop.Destruct(&pThis->fromHostIP);
+ pThis->fromHostIP = ip;
+ RETiRet;
+}
+
+static rsRetVal
+SetStrm(strms_sess_t *pThis, netstrm_t *pStrm)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ pThis->pStrm = pStrm;
+ RETiRet;
+}
+
+
+/* set our parent, the strmsrv object */
+static rsRetVal
+SetStrmsrv(strms_sess_t *pThis, strmsrv_t *pSrv)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ ISOBJ_TYPE_assert(pSrv, strmsrv);
+ pThis->pSrv = pSrv;
+ RETiRet;
+}
+
+
+/* set our parent listener info*/
+static rsRetVal
+SetLstnInfo(strms_sess_t *pThis, strmLstnPortList_t *pLstnInfo)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ assert(pLstnInfo != NULL);
+ pThis->pLstnInfo = pLstnInfo;
+ RETiRet;
+}
+
+
+static rsRetVal
+SetUsrP(strms_sess_t *pThis, void *pUsr)
+{
+ DEFiRet;
+ pThis->pUsr = pUsr;
+ RETiRet;
+}
+
+
+static void *
+GetUsrP(strms_sess_t *pThis)
+{
+ return pThis->pUsr;
+}
+
+
+/* Closes a STRM session
+ * No attention is paid to the return code
+ * of close, so potential-double closes are not detected.
+ */
+static rsRetVal
+Close(strms_sess_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ netstrm.Destruct(&pThis->pStrm);
+ free(pThis->fromHost);
+ pThis->fromHost = NULL; /* not really needed, but... */
+ if(pThis->fromHostIP != NULL)
+ prop.Destruct(&pThis->fromHostIP);
+
+ RETiRet;
+}
+
+
+
+/* Processes the data received via a STRM session. If there
+ * is no other way to handle it, data is discarded.
+ * Input parameter data is the data received, iLen is its
+ * len as returned from recv(). iLen must be 1 or more (that
+ * is errors must be handled by caller!). iSTRMSess must be
+ * the index of the STRM session that received the data.
+ * rgerhards 2005-07-04
+ * And another change while generalizing. We now return either
+ * RS_RET_OK, which means the session should be kept open
+ * or anything else, which means it must be closed.
+ * rgerhards, 2008-03-01
+ */
+static rsRetVal
+DataRcvd(strms_sess_t *pThis, char *pData, size_t iLen)
+{
+ DEFiRet;
+ char *pEnd;
+
+ ISOBJ_TYPE_assert(pThis, strms_sess);
+ assert(pData != NULL);
+ assert(iLen > 0);
+
+ /* We now copy the message to the session buffer. */
+ pEnd = pData + iLen; /* this is one off, which is intensional */
+
+ while(pData < pEnd) {
+ CHKiRet(pThis->pSrv->OnCharRcvd(pThis, (uchar)*pData++));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(strms_sess)
+CODESTARTobjQueryInterface(strms_sess)
+ if(pIf->ifVersion != strms_sessCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->DebugPrint = strms_sessDebugPrint;
+ pIf->Construct = strms_sessConstruct;
+ pIf->ConstructFinalize = strms_sessConstructFinalize;
+ pIf->Destruct = strms_sessDestruct;
+
+ pIf->Close = Close;
+ pIf->DataRcvd = DataRcvd;
+
+ pIf->SetUsrP = SetUsrP;
+ pIf->GetUsrP = GetUsrP;
+ pIf->SetStrmsrv = SetStrmsrv;
+ pIf->SetLstnInfo = SetLstnInfo;
+ pIf->SetHost = SetHost;
+ pIf->SetHostIP = SetHostIP;
+ pIf->SetStrm = SetStrm;
+finalize_it:
+ENDobjQueryInterface(strms_sess)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(strms_sess, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(strms_sess)
+ /* release objects we no longer need */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(netstrm, LM_NETSTRMS_FILENAME);
+ objRelease(datetime, CORE_COMPONENT);
+ENDObjClassExit(strms_sess)
+
+
+/* Initialize our class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-29
+ */
+BEGINObjClassInit(strms_sess, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ iMaxLine = glbl.GetMaxLine(); /* get maximum size we currently support */
+ objRelease(glbl, CORE_COMPONENT);
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, strms_sessDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, strms_sessConstructFinalize);
+ENDObjClassInit(strms_sess)
+
+/* vim:set ai:
+ */
diff --git a/runtime/strms_sess.h b/runtime/strms_sess.h
new file mode 100644
index 00000000..86f692a8
--- /dev/null
+++ b/runtime/strms_sess.h
@@ -0,0 +1,74 @@
+/* Definitions for strms_sess class. This implements a session of the
+ * generic stream server.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_STRMS_SESS_H
+#define INCLUDED_STRMS_SESS_H
+
+#include "obj.h"
+
+/* a forward-definition, we are somewhat cyclic */
+struct strmsrv_s;
+
+/* the strms_sess object */
+struct strms_sess_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ strmsrv_t *pSrv; /* pointer back to my server (e.g. for callbacks) */
+ strmLstnPortList_t *pLstnInfo; /* pointer back to listener info */
+ netstrm_t *pStrm;
+ uchar *fromHost;
+ prop_t *fromHostIP;
+ void *pUsr; /* a user-pointer */
+};
+
+
+/* interfaces */
+BEGINinterface(strms_sess) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(strms_sess);
+ rsRetVal (*Construct)(strms_sess_t **ppThis);
+ rsRetVal (*ConstructFinalize)(strms_sess_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(strms_sess_t **ppThis);
+ rsRetVal (*Close)(strms_sess_t *pThis);
+ rsRetVal (*DataRcvd)(strms_sess_t *pThis, char *pData, size_t iLen);
+ /* set methods */
+ rsRetVal (*SetStrmsrv)(strms_sess_t *pThis, struct strmsrv_s *pSrv);
+ rsRetVal (*SetLstnInfo)(strms_sess_t *pThis, strmLstnPortList_t *pLstnInfo);
+ rsRetVal (*SetUsrP)(strms_sess_t*, void*);
+ void* (*GetUsrP)(strms_sess_t*);
+ rsRetVal (*SetHost)(strms_sess_t *pThis, uchar*);
+ rsRetVal (*SetHostIP)(strms_sess_t *pThis, prop_t*);
+ rsRetVal (*SetStrm)(strms_sess_t *pThis, netstrm_t*);
+ rsRetVal (*SetOnMsgReceive)(strms_sess_t *pThis, rsRetVal (*OnMsgReceive)(strms_sess_t*, uchar*, int));
+ENDinterface(strms_sess)
+#define strms_sessCURR_IF_VERSION 3 /* increment whenever you change the interface structure! */
+/* interface changes
+ * to version v2, rgerhards, 2009-05-22
+ * - Data structures changed
+ * - SetLstnInfo entry point added
+ * version 3, rgerhads, 2013-01-21:
+ * - signature of SetHostIP() changed
+ */
+
+
+/* prototypes */
+PROTOTYPEObj(strms_sess);
+
+
+#endif /* #ifndef INCLUDED_STRMS_SESS_H */
diff --git a/runtime/strmsrv.c b/runtime/strmsrv.c
new file mode 100644
index 00000000..e8b544b8
--- /dev/null
+++ b/runtime/strmsrv.c
@@ -0,0 +1,972 @@
+/* strmsrv.c
+ *
+ * This builds a basic stream server. It handles connection creation but
+ * not any protocol. Instead, it calls a "data received" entry point of the
+ * caller with any data received, in which case the caller must react accordingly.
+ * This module works together with the netstream drivers.
+ *
+ * There are actually two classes within the stream server code: one is
+ * the strmsrv itself, the other one is its sessions. This is a helper
+ * class to strmsrv.
+ *
+ * File begun on 2009-06-01 by RGerhards based on strmsrv.c. Note that strmsrv is
+ * placed under LGPL, which is possible because I carefully evaluated and
+ * eliminated all those parts of strmsrv which were not written by me.
+ *
+ * TODO: I would consider it useful to migrate tcpsrv.c/tcps_sess.c to this stream
+ * class here. The requires a little bit redesign, but should not be too hard. The
+ * core idea, already begun here, is that we still support lots of callbacks, but
+ * provide "canned" implementations for standard cases. That way, most upper-layer
+ * modules can be kept rather simple and without any extra overhead. Note that
+ * to support this, tcps_sess.c would need to extract the message reception state
+ * machine to a separate module which then is called via the DoCharRcvd() interface
+ * of this class here. -- rgerhards, 2009-06-01
+ *
+ * Copyright 2007-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include "rsyslog.h"
+#include "dirty.h"
+#include "cfsysline.h"
+#include "module-template.h"
+#include "net.h"
+#include "srUtils.h"
+#include "conf.h"
+#include "strmsrv.h"
+#include "obj.h"
+#include "glbl.h"
+#include "netstrms.h"
+#include "netstrm.h"
+#include "nssel.h"
+#include "errmsg.h"
+#include "prop.h"
+#include "unicode-helper.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* defines */
+#define STRMSESS_MAX_DEFAULT 200 /* default for nbr of strm sessions if no number is given */
+#define STRMLSTN_MAX_DEFAULT 20 /* default for nbr of listeners */
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(conf)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(strms_sess)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(net)
+DEFobjCurrIf(netstrms)
+DEFobjCurrIf(netstrm)
+DEFobjCurrIf(nssel)
+DEFobjCurrIf(prop)
+
+/* forward definitions */
+static rsRetVal create_strm_socket(strmsrv_t *pThis);
+
+/* standard callbacks, if the caller did not provide us with them (this helps keep us
+ * flexible while at the same time permits very simple upper-layer modules)
+ */
+/* this shall go into a specific ACL module! */
+static int
+isPermittedHost(struct sockaddr __attribute__((unused)) *addr, char __attribute__((unused)) *fromHostFQDN,
+ void __attribute__((unused)) *pUsrSrv, void __attribute__((unused)) *pUsrSess)
+{
+ return 1;
+}
+
+
+static rsRetVal
+doOpenLstnSocks(strmsrv_t *pSrv)
+{
+ ISOBJ_TYPE_assert(pSrv, strmsrv);
+ return create_strm_socket(pSrv);
+}
+
+
+static rsRetVal
+doRcvData(strms_sess_t *pSess, char *buf, size_t lenBuf, ssize_t *piLenRcvd)
+{
+ DEFiRet;
+ assert(pSess != NULL);
+ assert(piLenRcvd != NULL);
+
+ *piLenRcvd = lenBuf;
+ CHKiRet(netstrm.Rcv(pSess->pStrm, (uchar*) buf, piLenRcvd));
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+onRegularClose(strms_sess_t *pSess)
+{
+ DEFiRet;
+ assert(pSess != NULL);
+
+ /* process any incomplete frames left over */
+ //strms_sess.PrepareClose(pSess);
+ /* Session closed */
+ strms_sess.Close(pSess);
+ RETiRet;
+}
+
+
+static rsRetVal
+onErrClose(strms_sess_t *pSess)
+{
+ DEFiRet;
+ assert(pSess != NULL);
+
+ strms_sess.Close(pSess);
+ RETiRet;
+}
+
+/* ------------------------------ end callbacks ------------------------------ */
+
+/* add new listener port to listener port list
+ * rgerhards, 2009-05-21
+ */
+static inline rsRetVal
+addNewLstnPort(strmsrv_t *pThis, uchar *pszPort)
+{
+ strmLstnPortList_t *pEntry;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+
+ /* create entry */
+ CHKmalloc(pEntry = MALLOC(sizeof(strmLstnPortList_t)));
+ pEntry->pszPort = pszPort;
+ pEntry->pSrv = pThis;
+ CHKmalloc(pEntry->pszInputName = ustrdup(pThis->pszInputName));
+
+ /* and add to list */
+ pEntry->pNext = pThis->pLstnPorts;
+ pThis->pLstnPorts = pEntry;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* configure STRM listener settings.
+ * Note: pszPort is handed over to us - the caller MUST NOT free it!
+ * rgerhards, 2008-03-20
+ */
+static rsRetVal
+configureSTRMListen(strmsrv_t *pThis, uchar *pszPort)
+{
+ int i;
+ uchar *pPort = pszPort;
+ DEFiRet;
+
+ assert(pszPort != NULL);
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+
+ /* extract port */
+ i = 0;
+ while(isdigit((int) *pPort)) {
+ i = i * 10 + *pPort++ - '0';
+ }
+
+ if(i >= 0 && i <= 65535) {
+ CHKiRet(addNewLstnPort(pThis, pszPort));
+ } else {
+ errmsg.LogError(0, NO_ERRCODE, "Invalid STRM listen port %s - ignored.\n", pszPort);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Initialize the session table
+ * returns 0 if OK, somewhat else otherwise
+ */
+static rsRetVal
+STRMSessTblInit(strmsrv_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ assert(pThis->pSessions == NULL);
+
+ dbgprintf("Allocating buffer for %d STRM sessions.\n", pThis->iSessMax);
+ if((pThis->pSessions = (strms_sess_t **) calloc(pThis->iSessMax, sizeof(strms_sess_t *))) == NULL) {
+ dbgprintf("Error: STRMSessInit() could not alloc memory for STRM session table.\n");
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* find a free spot in the session table. If the table
+ * is full, -1 is returned, else the index of the free
+ * entry (0 or higher).
+ */
+static int
+STRMSessTblFindFreeSpot(strmsrv_t *pThis)
+{
+ register int i;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+
+ for(i = 0 ; i < pThis->iSessMax ; ++i) {
+ if(pThis->pSessions[i] == NULL)
+ break;
+ }
+
+ return((i < pThis->iSessMax) ? i : -1);
+}
+
+
+/* Get the next session index. Free session tables entries are
+ * skipped. This function is provided the index of the last
+ * session entry, or -1 if no previous entry was obtained. It
+ * returns the index of the next session or -1, if there is no
+ * further entry in the table. Please note that the initial call
+ * might as well return -1, if there is no session at all in the
+ * session table.
+ */
+static int
+STRMSessGetNxtSess(strmsrv_t *pThis, int iCurr)
+{
+ register int i;
+
+ BEGINfunc
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ assert(pThis->pSessions != NULL);
+ for(i = iCurr + 1 ; i < pThis->iSessMax ; ++i) {
+ if(pThis->pSessions[i] != NULL)
+ break;
+ }
+
+ ENDfunc
+ return((i < pThis->iSessMax) ? i : -1);
+}
+
+
+/* De-Initialize STRM listner sockets.
+ * This function deinitializes everything, including freeing the
+ * session table. No STRM listen receive operations are permitted
+ * unless the subsystem is reinitialized.
+ * rgerhards, 2007-06-21
+ */
+static void deinit_strm_listener(strmsrv_t *pThis)
+{
+ int i;
+ strmLstnPortList_t *pEntry;
+ strmLstnPortList_t *pDel;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+
+ if(pThis->pSessions != NULL) {
+ /* close all STRM connections! */
+ i = STRMSessGetNxtSess(pThis, -1);
+ while(i != -1) {
+ strms_sess.Destruct(&pThis->pSessions[i]);
+ /* now get next... */
+ i = STRMSessGetNxtSess(pThis, i);
+ }
+
+ /* we are done with the session table - so get rid of it... */
+ free(pThis->pSessions);
+ pThis->pSessions = NULL; /* just to make sure... */
+ }
+
+ /* free list of strm listen ports */
+ pEntry = pThis->pLstnPorts;
+ while(pEntry != NULL) {
+ free(pEntry->pszPort);
+ free(pEntry->pszInputName);
+ pDel = pEntry;
+ pEntry = pEntry->pNext;
+ free(pDel);
+ }
+
+ /* finally close our listen streams */
+ for(i = 0 ; i < pThis->iLstnMax ; ++i) {
+ netstrm.Destruct(pThis->ppLstn + i);
+ }
+}
+
+
+/* add a listen socket to our listen socket array. This is a callback
+ * invoked from the netstrm class. -- rgerhards, 2008-04-23
+ */
+static rsRetVal
+addStrmLstn(void *pUsr, netstrm_t *pLstn)
+{
+ strmLstnPortList_t *pPortList = (strmLstnPortList_t *) pUsr;
+ strmsrv_t *pThis = pPortList->pSrv;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ ISOBJ_TYPE_assert(pLstn, netstrm);
+
+ if(pThis->iLstnMax >= STRMLSTN_MAX_DEFAULT)
+ ABORT_FINALIZE(RS_RET_MAX_LSTN_REACHED);
+
+ pThis->ppLstn[pThis->iLstnMax] = pLstn;
+ pThis->ppLstnPort[pThis->iLstnMax] = pPortList;
+ ++pThis->iLstnMax;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Initialize STRM listener socket for a single port
+ * rgerhards, 2009-05-21
+ */
+static inline rsRetVal
+initSTRMListener(strmsrv_t *pThis, strmLstnPortList_t *pPortEntry)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ assert(pPortEntry != NULL);
+
+ /* TODO: add capability to specify local listen address! */
+ CHKiRet(netstrm.LstnInit(pThis->pNS, (void*)pPortEntry, addStrmLstn, pPortEntry->pszPort, NULL, pThis->iSessMax));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Initialize STRM sockets (for listener) and listens on them */
+static rsRetVal
+create_strm_socket(strmsrv_t *pThis)
+{
+ strmLstnPortList_t *pEntry;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+
+ /* init all configured ports */
+ pEntry = pThis->pLstnPorts;
+ while(pEntry != NULL) {
+ CHKiRet(initSTRMListener(pThis, pEntry));
+ pEntry = pEntry->pNext;
+ }
+
+ /* OK, we had success. Now it is also time to
+ * initialize our connections
+ */
+ if(STRMSessTblInit(pThis) != 0) {
+ /* OK, we are in some trouble - we could not initialize the
+ * session table, so we can not continue. We need to free all
+ * we have assigned so far, because we can not really use it...
+ */
+ errmsg.LogError(0, RS_RET_ERR, "Could not initialize STRM session table, suspending STRM message reception.");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Accept new STRM connection; make entry in session table. If there
+ * is no more space left in the connection table, the new STRM
+ * connection is immediately dropped.
+ * ppSess has a pointer to the newly created session, if it succeeds.
+ * If it does not succeed, no session is created and ppSess is
+ * undefined. If the user has provided an OnSessAccept Callback,
+ * this one is executed immediately after creation of the
+ * session object, so that it can do its own initialization.
+ * rgerhards, 2008-03-02
+ */
+static rsRetVal
+SessAccept(strmsrv_t *pThis, strmLstnPortList_t *pLstnInfo, strms_sess_t **ppSess, netstrm_t *pStrm)
+{
+ DEFiRet;
+ strms_sess_t *pSess = NULL;
+ netstrm_t *pNewStrm = NULL;
+ int iSess = -1;
+ struct sockaddr_storage *addr;
+ uchar *fromHostFQDN = NULL;
+ prop_t *ip = NULL;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ assert(pLstnInfo != NULL);
+
+ CHKiRet(netstrm.AcceptConnReq(pStrm, &pNewStrm));
+
+ /* Add to session list */
+ iSess = STRMSessTblFindFreeSpot(pThis);
+ if(iSess == -1) {
+ errno = 0;
+ errmsg.LogError(0, RS_RET_MAX_SESS_REACHED, "too many strm sessions - dropping incoming request");
+ ABORT_FINALIZE(RS_RET_MAX_SESS_REACHED);
+ }
+
+ if(pThis->bUseKeepAlive) {
+ CHKiRet(netstrm.EnableKeepAlive(pNewStrm));
+ }
+
+ /* we found a free spot and can construct our session object */
+ CHKiRet(strms_sess.Construct(&pSess));
+ CHKiRet(strms_sess.SetStrmsrv(pSess, pThis));
+ CHKiRet(strms_sess.SetLstnInfo(pSess, pLstnInfo));
+
+ /* get the host name */
+ CHKiRet(netstrm.GetRemoteHName(pNewStrm, &fromHostFQDN));
+ CHKiRet(netstrm.GetRemoteIP(pNewStrm, &ip));
+ CHKiRet(netstrm.GetRemAddr(pNewStrm, &addr));
+ /* TODO: check if we need to strip the domain name here -- rgerhards, 2008-04-24 */
+
+ /* Here we check if a host is permitted to send us messages. If it isn't, we do not further
+ * process the message but log a warning (if we are configured to do this).
+ * rgerhards, 2005-09-26
+ */
+ if(pThis->pIsPermittedHost != NULL
+ && !pThis->pIsPermittedHost((struct sockaddr*) addr, (char*) fromHostFQDN, pThis->pUsr, pSess->pUsr)) {
+ dbgprintf("%s is not an allowed sender\n", fromHostFQDN);
+ if(glbl.GetOption_DisallowWarning()) {
+ errno = 0;
+ errmsg.LogError(0, RS_RET_HOST_NOT_PERMITTED, "STRM message from disallowed sender %s discarded", fromHostFQDN);
+ }
+ ABORT_FINALIZE(RS_RET_HOST_NOT_PERMITTED);
+ }
+
+ /* OK, we have an allowed sender, so let's continue, what
+ * means we can finally fill in the session object.
+ */
+ CHKiRet(strms_sess.SetHost(pSess, fromHostFQDN));
+ fromHostFQDN = NULL; /* we handed this string over */
+ CHKiRet(strms_sess.SetHostIP(pSess, ip));
+ ip = NULL; /* we handed this string over */
+ CHKiRet(strms_sess.SetStrm(pSess, pNewStrm));
+ pNewStrm = NULL; /* prevent it from being freed in error handler, now done in strms_sess! */
+ CHKiRet(strms_sess.ConstructFinalize(pSess));
+
+ /* check if we need to call our callback */
+ if(pThis->pOnSessAccept != NULL) {
+ CHKiRet(pThis->pOnSessAccept(pThis, pSess));
+ }
+
+ *ppSess = pSess;
+ pThis->pSessions[iSess] = pSess;
+ pSess = NULL; /* this is now also handed over */
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pSess != NULL)
+ strms_sess.Destruct(&pSess);
+ if(pNewStrm != NULL)
+ netstrm.Destruct(&pNewStrm);
+ free(fromHostFQDN);
+ if(ip != NULL)
+ prop.Destruct(&ip);
+ }
+
+ RETiRet;
+}
+
+
+static void
+RunCancelCleanup(void *arg)
+{
+ nssel_t **ppSel = (nssel_t**) arg;
+
+ if(*ppSel != NULL)
+ nssel.Destruct(ppSel);
+}
+
+
+/* This function is called to gather input. */
+#pragma GCC diagnostic ignored "-Wempty-body"
+static rsRetVal
+Run(strmsrv_t *pThis)
+{
+ DEFiRet;
+ int nfds;
+ int i;
+ int iSTRMSess;
+ int bIsReady;
+ strms_sess_t *pNewSess;
+ nssel_t *pSel;
+ ssize_t iRcvd;
+ rsRetVal localRet;
+
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+
+ /* this is an endless loop - it is terminated by the framework canelling
+ * this thread. Thus, we also need to instantiate a cancel cleanup handler
+ * to prevent us from leaking anything. -- rgerharsd, 20080-04-24
+ */
+ pthread_cleanup_push(RunCancelCleanup, (void*) &pSel);
+ while(1) {
+ CHKiRet(nssel.Construct(&pSel));
+ // TODO: set driver
+ CHKiRet(nssel.ConstructFinalize(pSel));
+
+ /* Add the STRM listen sockets to the list of read descriptors. */
+ for(i = 0 ; i < pThis->iLstnMax ; ++i) {
+ CHKiRet(nssel.Add(pSel, pThis->ppLstn[i], NSDSEL_RD));
+ }
+
+ /* do the sessions */
+ iSTRMSess = STRMSessGetNxtSess(pThis, -1);
+ while(iSTRMSess != -1) {
+ /* TODO: access to pNsd is NOT really CLEAN, use method... */
+ CHKiRet(nssel.Add(pSel, pThis->pSessions[iSTRMSess]->pStrm, NSDSEL_RD));
+ /* now get next... */
+ iSTRMSess = STRMSessGetNxtSess(pThis, iSTRMSess);
+ }
+
+ /* wait for io to become ready */
+ CHKiRet(nssel.Wait(pSel, &nfds));
+
+ for(i = 0 ; i < pThis->iLstnMax ; ++i) {
+ CHKiRet(nssel.IsReady(pSel, pThis->ppLstn[i], NSDSEL_RD, &bIsReady, &nfds));
+ if(bIsReady) {
+ dbgprintf("New connect on NSD %p.\n", pThis->ppLstn[i]);
+ SessAccept(pThis, pThis->ppLstnPort[i], &pNewSess, pThis->ppLstn[i]);
+ --nfds; /* indicate we have processed one */
+ }
+ }
+
+ /* now check the sessions */
+ iSTRMSess = STRMSessGetNxtSess(pThis, -1);
+ while(nfds && iSTRMSess != -1) {
+ CHKiRet(nssel.IsReady(pSel, pThis->pSessions[iSTRMSess]->pStrm, NSDSEL_RD, &bIsReady, &nfds));
+ if(bIsReady) {
+ char buf[8*1024]; /* reception buffer - may hold a partial or multiple messages */
+ dbgprintf("netstream %p with new data\n", pThis->pSessions[iSTRMSess]->pStrm);
+
+ /* Receive message */
+ iRet = pThis->pRcvData(pThis->pSessions[iSTRMSess], buf, sizeof(buf), &iRcvd);
+ switch(iRet) {
+ case RS_RET_CLOSED:
+ pThis->pOnRegularClose(pThis->pSessions[iSTRMSess]);
+ strms_sess.Destruct(&pThis->pSessions[iSTRMSess]);
+ break;
+ case RS_RET_RETRY:
+ /* we simply ignore retry - this is not an error, but we also have not received anything */
+ break;
+ case RS_RET_OK:
+ /* valid data received, process it! */
+ localRet = strms_sess.DataRcvd(pThis->pSessions[iSTRMSess], buf, iRcvd);
+ if(localRet != RS_RET_OK) {
+ /* in this case, something went awfully wrong.
+ * We are instructed to terminate the session.
+ */
+ errmsg.LogError(0, localRet, "Tearing down STRM Session %d - see "
+ "previous messages for reason(s)\n", iSTRMSess);
+ pThis->pOnErrClose(pThis->pSessions[iSTRMSess]);
+ strms_sess.Destruct(&pThis->pSessions[iSTRMSess]);
+ }
+ break;
+ default:
+ errno = 0;
+ errmsg.LogError(0, iRet, "netstream session %p will be closed due to error\n",
+ pThis->pSessions[iSTRMSess]->pStrm);
+ pThis->pOnErrClose(pThis->pSessions[iSTRMSess]);
+ strms_sess.Destruct(&pThis->pSessions[iSTRMSess]);
+ break;
+ }
+ --nfds; /* indicate we have processed one */
+ }
+ iSTRMSess = STRMSessGetNxtSess(pThis, iSTRMSess);
+ }
+ CHKiRet(nssel.Destruct(&pSel));
+finalize_it: /* this is a very special case - this time only we do not exit the function,
+ * because that would not help us either. So we simply retry it. Let's see
+ * if that actually is a better idea. Exiting the loop wasn't we always
+ * crashed, which made sense (the rest of the engine was not prepared for
+ * that) -- rgerhards, 2008-05-19
+ */
+ /*EMPTY*/;
+ }
+
+ /* note that this point is usually not reached */
+ pthread_cleanup_pop(0); /* remove cleanup handler */
+
+ RETiRet;
+}
+#pragma GCC diagnostic warning "-Wempty-body"
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(strmsrv) /* be sure to specify the object type also in END macro! */
+ pThis->iSessMax = STRMSESS_MAX_DEFAULT; /* TODO: useful default ;) */
+ /* set default callbacks (used if caller does not overwrite them) */
+ pThis->pIsPermittedHost = isPermittedHost;
+ pThis->OpenLstnSocks = doOpenLstnSocks;
+ pThis->pRcvData = doRcvData;
+ pThis->pOnRegularClose = onRegularClose;
+ pThis->pOnErrClose = onErrClose;
+ /* session specific callbacks */
+ //pThis->OnSessConstructFinalize =
+ //pThis->pOnSessDestruct =
+ENDobjConstruct(strmsrv)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+strmsrvConstructFinalize(strmsrv_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+
+ /* prepare network stream subsystem */
+ CHKiRet(netstrms.Construct(&pThis->pNS));
+ CHKiRet(netstrms.SetDrvrMode(pThis->pNS, pThis->iDrvrMode));
+ if(pThis->pszDrvrAuthMode != NULL)
+ CHKiRet(netstrms.SetDrvrAuthMode(pThis->pNS, pThis->pszDrvrAuthMode));
+ if(pThis->pPermPeers != NULL)
+ CHKiRet(netstrms.SetDrvrPermPeers(pThis->pNS, pThis->pPermPeers));
+ // TODO: set driver!
+ CHKiRet(netstrms.ConstructFinalize(pThis->pNS));
+
+ /* set up listeners */
+ CHKmalloc(pThis->ppLstn = calloc(STRMLSTN_MAX_DEFAULT, sizeof(netstrm_t*)));
+ CHKmalloc(pThis->ppLstnPort = calloc(STRMLSTN_MAX_DEFAULT, sizeof(strmLstnPortList_t*)));
+ iRet = pThis->OpenLstnSocks(pThis);
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->pNS != NULL)
+ netstrms.Destruct(&pThis->pNS);
+ }
+ RETiRet;
+}
+
+
+/* destructor for the strmsrv object */
+BEGINobjDestruct(strmsrv) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(strmsrv)
+ if(pThis->OnDestruct != NULL)
+ pThis->OnDestruct(pThis->pUsr);
+
+ deinit_strm_listener(pThis);
+
+ if(pThis->pNS != NULL)
+ netstrms.Destruct(&pThis->pNS);
+ free(pThis->pszDrvrAuthMode);
+ free(pThis->ppLstn);
+ free(pThis->ppLstnPort);
+ free(pThis->pszInputName);
+ENDobjDestruct(strmsrv)
+
+
+/* debugprint for the strmsrv object */
+BEGINobjDebugPrint(strmsrv) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(strmsrv)
+ENDobjDebugPrint(strmsrv)
+
+/* set functions */
+static rsRetVal
+SetCBIsPermittedHost(strmsrv_t *pThis, int (*pCB)(struct sockaddr *addr, char *fromHostFQDN, void*, void*))
+{
+ DEFiRet;
+ pThis->pIsPermittedHost = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnSessAccept(strmsrv_t *pThis, rsRetVal (*pCB)(strmsrv_t*, strms_sess_t*))
+{
+ DEFiRet;
+ pThis->pOnSessAccept = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnDestruct(strmsrv_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->OnDestruct = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnSessConstructFinalize(strmsrv_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->OnSessConstructFinalize = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnSessDestruct(strmsrv_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->pOnSessDestruct = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnRegularClose(strmsrv_t *pThis, rsRetVal (*pCB)(strms_sess_t*))
+{
+ DEFiRet;
+ pThis->pOnRegularClose = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnErrClose(strmsrv_t *pThis, rsRetVal (*pCB)(strms_sess_t*))
+{
+ DEFiRet;
+ pThis->pOnErrClose = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOpenLstnSocks(strmsrv_t *pThis, rsRetVal (*pCB)(strmsrv_t*))
+{
+ DEFiRet;
+ pThis->OpenLstnSocks = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetUsrP(strmsrv_t *pThis, void *pUsr)
+{
+ DEFiRet;
+ pThis->pUsr = pUsr;
+ RETiRet;
+}
+
+static rsRetVal
+SetKeepAlive(strmsrv_t *pThis, int iVal)
+{
+ DEFiRet;
+ dbgprintf("strmsrv: keep-alive set to %d\n", iVal);
+ pThis->bUseKeepAlive = iVal;
+ RETiRet;
+}
+
+static rsRetVal
+SetOnCharRcvd(strmsrv_t *pThis, rsRetVal (*OnCharRcvd)(strms_sess_t*, uchar))
+{
+ DEFiRet;
+ assert(OnCharRcvd != NULL);
+ pThis->OnCharRcvd = OnCharRcvd;
+ RETiRet;
+}
+
+/* Set the input name to use -- rgerhards, 2008-12-10 */
+static rsRetVal
+SetInputName(strmsrv_t *pThis, uchar *name)
+{
+ uchar *pszName;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ if(name == NULL)
+ pszName = NULL;
+ else
+ CHKmalloc(pszName = ustrdup(name));
+ free(pThis->pszInputName);
+ pThis->pszInputName = pszName;
+finalize_it:
+ RETiRet;
+}
+
+
+/* here follows a number of methods that shuffle authentication settings down
+ * to the drivers. Drivers not supporting these settings may return an error
+ * state.
+ * -------------------------------------------------------------------------- */
+
+/* set the driver mode -- rgerhards, 2008-04-30 */
+static rsRetVal
+SetDrvrMode(strmsrv_t *pThis, int iMode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ pThis->iDrvrMode = iMode;
+ RETiRet;
+}
+
+
+/* set the driver authentication mode -- rgerhards, 2008-05-19 */
+static rsRetVal
+SetDrvrAuthMode(strmsrv_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ CHKmalloc(pThis->pszDrvrAuthMode = ustrdup(mode));
+finalize_it:
+ RETiRet;
+}
+
+
+/* set the driver's permitted peers -- rgerhards, 2008-05-19 */
+static rsRetVal
+SetDrvrPermPeers(strmsrv_t *pThis, permittedPeers_t *pPermPeers)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ pThis->pPermPeers = pPermPeers;
+ RETiRet;
+}
+
+
+/* End of methods to shuffle autentication settings to the driver.;
+
+ * -------------------------------------------------------------------------- */
+
+
+/* set max number of sessions
+ * this must be called before ConstructFinalize, or it will have no effect!
+ * rgerhards, 2009-04-09
+ */
+static rsRetVal
+SetSessMax(strmsrv_t *pThis, int iMax)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strmsrv);
+ pThis->iSessMax = iMax;
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(strmsrv)
+CODESTARTobjQueryInterface(strmsrv)
+ if(pIf->ifVersion != strmsrvCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->DebugPrint = strmsrvDebugPrint;
+ pIf->Construct = strmsrvConstruct;
+ pIf->ConstructFinalize = strmsrvConstructFinalize;
+ pIf->Destruct = strmsrvDestruct;
+
+ pIf->configureSTRMListen = configureSTRMListen;
+ pIf->create_strm_socket = create_strm_socket;
+ pIf->Run = Run;
+
+ pIf->SetKeepAlive = SetKeepAlive;
+ pIf->SetUsrP = SetUsrP;
+ pIf->SetInputName = SetInputName;
+ pIf->SetSessMax = SetSessMax;
+ pIf->SetDrvrMode = SetDrvrMode;
+ pIf->SetDrvrAuthMode = SetDrvrAuthMode;
+ pIf->SetDrvrPermPeers = SetDrvrPermPeers;
+ pIf->SetCBIsPermittedHost = SetCBIsPermittedHost;
+ pIf->SetCBOpenLstnSocks = SetCBOpenLstnSocks;
+ pIf->SetCBOnSessAccept = SetCBOnSessAccept;
+ pIf->SetCBOnSessConstructFinalize = SetCBOnSessConstructFinalize;
+ pIf->SetCBOnSessDestruct = SetCBOnSessDestruct;
+ pIf->SetCBOnDestruct = SetCBOnDestruct;
+ pIf->SetCBOnRegularClose = SetCBOnRegularClose;
+ pIf->SetCBOnErrClose = SetCBOnErrClose;
+ pIf->SetOnCharRcvd = SetOnCharRcvd;
+
+finalize_it:
+ENDobjQueryInterface(strmsrv)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(strmsrv, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(strmsrv)
+ /* release objects we no longer need */
+ objRelease(strms_sess, DONT_LOAD_LIB);
+ objRelease(conf, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(netstrms, DONT_LOAD_LIB);
+ objRelease(nssel, DONT_LOAD_LIB);
+ objRelease(netstrm, LM_NETSTRMS_FILENAME);
+ objRelease(net, LM_NET_FILENAME);
+ENDObjClassExit(strmsrv)
+
+
+/* Initialize our class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-29
+ */
+BEGINObjClassInit(strmsrv, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ /* request objects we use */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(net, LM_NET_FILENAME));
+ CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME));
+ CHKiRet(objUse(netstrm, DONT_LOAD_LIB));
+ CHKiRet(objUse(nssel, DONT_LOAD_LIB));
+ CHKiRet(objUse(strms_sess, DONT_LOAD_LIB));
+ CHKiRet(objUse(conf, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, strmsrvDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, strmsrvConstructFinalize);
+ENDObjClassInit(strmsrv)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ /* de-init in reverse order! */
+ strmsrvClassExit();
+ strms_sessClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(strms_sessClassInit(pModInfo));
+ CHKiRet(strmsrvClassInit(pModInfo)); /* must be done after strms_sess, as we use it */
+ENDmodInit
+
+/* vim:set ai:
+ */
diff --git a/runtime/strmsrv.h b/runtime/strmsrv.h
new file mode 100644
index 00000000..9ef28e47
--- /dev/null
+++ b/runtime/strmsrv.h
@@ -0,0 +1,110 @@
+/* Definitions for strmsrv class.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_STRMSRV_H
+#define INCLUDED_STRMSRV_H
+
+#include "obj.h"
+#include "strms_sess.h"
+
+/* list of strm listen ports */
+struct strmLstnPortList_s {
+ uchar *pszPort; /**< the ports the listener shall listen on */
+ uchar *pszInputName; /**< value to be used as input name */
+ strmsrv_t *pSrv; /**< pointer to higher-level server instance */
+ strmLstnPortList_t *pNext; /**< next port or NULL */
+};
+
+
+/* the strmsrv object */
+struct strmsrv_s {
+ BEGINobjInstance; /**< Data to implement generic object - MUST be the first data element! */
+ int bUseKeepAlive; /**< use socket layer KEEPALIVE handling? */
+ netstrms_t *pNS; /**< pointer to network stream subsystem */
+ int iDrvrMode; /**< mode of the stream driver to use */
+ uchar *pszDrvrAuthMode; /**< auth mode of the stream driver to use */
+ uchar *pszInputName; /**< value to be used as input name */
+ permittedPeers_t *pPermPeers;/**< driver's permitted peers */
+ int iLstnMax; /**< max nbr of listeners currently supported */
+ netstrm_t **ppLstn; /**< our netstream listners */
+ strmLstnPortList_t **ppLstnPort; /**< pointer to relevant listen port description */
+ int iSessMax; /**< max number of sessions supported */
+ strmLstnPortList_t *pLstnPorts; /**< head pointer for listen ports */
+ int addtlFrameDelim; /**< additional frame delimiter for plain STRM syslog framing (e.g. to handle NetScreen) */
+ strms_sess_t **pSessions;/**< array of all of our sessions */
+ void *pUsr; /**< a user-settable pointer (provides extensibility for "derived classes")*/
+ /* callbacks */
+ int (*pIsPermittedHost)(struct sockaddr *addr, char *fromHostFQDN, void*pUsrSrv, void*pUsrSess);
+ rsRetVal (*pRcvData)(strms_sess_t*, char*, size_t, ssize_t *);
+ rsRetVal (*OpenLstnSocks)(struct strmsrv_s*);
+ rsRetVal (*pOnListenDeinit)(void*);
+ rsRetVal (*OnDestruct)(void*);
+ rsRetVal (*pOnRegularClose)(strms_sess_t *pSess);
+ rsRetVal (*pOnErrClose)(strms_sess_t *pSess);
+ /* session specific callbacks */
+ rsRetVal (*pOnSessAccept)(strmsrv_t *, strms_sess_t*);
+ rsRetVal (*OnSessConstructFinalize)(void*);
+ rsRetVal (*pOnSessDestruct)(void*);
+ rsRetVal (*OnCharRcvd)(strms_sess_t*, uchar);
+};
+
+
+/* interfaces */
+BEGINinterface(strmsrv) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(strmsrv);
+ rsRetVal (*Construct)(strmsrv_t **ppThis);
+ rsRetVal (*ConstructFinalize)(strmsrv_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(strmsrv_t **ppThis);
+ rsRetVal (*configureSTRMListen)(strmsrv_t*, uchar *pszPort);
+ //rsRetVal (*SessAccept)(strmsrv_t *pThis, strmLstnPortList_t*, strms_sess_t **ppSess, netstrm_t *pStrm);
+ rsRetVal (*create_strm_socket)(strmsrv_t *pThis);
+ rsRetVal (*Run)(strmsrv_t *pThis);
+ /* set methods */
+ rsRetVal (*SetAddtlFrameDelim)(strmsrv_t*, int);
+ rsRetVal (*SetInputName)(strmsrv_t*, uchar*);
+ rsRetVal (*SetKeepAlive)(strmsrv_t*, int);
+ rsRetVal (*SetUsrP)(strmsrv_t*, void*);
+ rsRetVal (*SetCBIsPermittedHost)(strmsrv_t*, int (*) (struct sockaddr *addr, char*, void*, void*));
+ rsRetVal (*SetCBOpenLstnSocks)(strmsrv_t *, rsRetVal (*)(strmsrv_t*));
+ rsRetVal (*SetCBOnDestruct)(strmsrv_t*, rsRetVal (*) (void*));
+ rsRetVal (*SetCBOnRegularClose)(strmsrv_t*, rsRetVal (*) (strms_sess_t*));
+ rsRetVal (*SetCBOnErrClose)(strmsrv_t*, rsRetVal (*) (strms_sess_t*));
+ rsRetVal (*SetDrvrMode)(strmsrv_t *pThis, int iMode);
+ rsRetVal (*SetDrvrAuthMode)(strmsrv_t *pThis, uchar *pszMode);
+ rsRetVal (*SetDrvrPermPeers)(strmsrv_t *pThis, permittedPeers_t*);
+ /* session specifics */
+ rsRetVal (*SetCBOnSessAccept)(strmsrv_t*, rsRetVal (*) (strmsrv_t*, strms_sess_t*));
+ rsRetVal (*SetCBOnSessDestruct)(strmsrv_t*, rsRetVal (*) (void*));
+ rsRetVal (*SetCBOnSessConstructFinalize)(strmsrv_t*, rsRetVal (*) (void*));
+ rsRetVal (*SetSessMax)(strmsrv_t *pThis, int iMaxSess);
+ rsRetVal (*SetOnCharRcvd)(strmsrv_t *pThis, rsRetVal (*OnMsgCharRcvd)(strms_sess_t*, uchar));
+ENDinterface(strmsrv)
+#define strmsrvCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+/* change for v?:
+ */
+
+
+/* prototypes */
+PROTOTYPEObj(strmsrv);
+
+/* the name of our library binary */
+#define LM_STRMSRV_FILENAME "lmstrmsrv"
+
+#endif /* #ifndef INCLUDED_STRMSRV_H */
diff --git a/runtime/syslogd-types.h b/runtime/syslogd-types.h
new file mode 100644
index 00000000..6947a110
--- /dev/null
+++ b/runtime/syslogd-types.h
@@ -0,0 +1,111 @@
+/* syslogd-type.h
+ * This file contains type defintions used by syslogd and its modules.
+ * It is a required input for any module.
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef SYSLOGD_TYPES_INCLUDED
+#define SYSLOGD_TYPES_INCLUDED 1
+
+#include "stringbuf.h"
+#include <sys/param.h>
+#if HAVE_SYSLOG_H
+#include <syslog.h>
+#endif
+
+/* we use RSTRUE/FALSE to prevent name claches with other packages */
+#define RSFALSE 0
+#define RSTRUE 1
+
+#ifdef UT_NAMESIZE
+# define UNAMESZ UT_NAMESIZE /* length of a login name */
+#else
+# define UNAMESZ 8 /* length of a login name */
+#endif
+#define MAXUNAMES 20 /* maximum number of user names */
+#define MAXFNAME 200 /* max file pathname length */
+
+#define _DB_MAXDBLEN 128 /* maximum number of db */
+#define _DB_MAXUNAMELEN 128 /* maximum number of user name */
+#define _DB_MAXPWDLEN 128 /* maximum number of user's pass */
+#define _DB_DELAYTIMEONERROR 20 /* If an error occur we stop logging until
+ a delayed time is over */
+
+
+/* we define features of the syslog code. This features can be used
+ * to check if modules are compatible with them - and possible other
+ * applications I do not yet envision. -- rgerhards, 2007-07-24
+ */
+typedef enum _syslogFeature {
+ sFEATURERepeatedMsgReduction = 1, /* for output modules */
+ sFEATURENonCancelInputTermination = 2, /* for input modules */
+ sFEATUREAutomaticSanitazion = 3, /* for parser modules */
+ sFEATUREAutomaticPRIParsing = 4 /* for parser modules */
+} syslogFeature;
+
+/* we define our own facility and severities */
+/* facility and severity codes */
+typedef struct _syslogCode {
+ char *c_name;
+ int c_val;
+} syslogCODE;
+
+/* values for host comparisons specified with host selector blocks
+ * (+host, -host). rgerhards 2005-10-18.
+ */
+enum _EHostnameCmpMode {
+ HN_NO_COMP = 0, /* do not compare hostname */
+ HN_COMP_MATCH = 1, /* hostname must match */
+ HN_COMP_NOMATCH = 2 /* hostname must NOT match */
+};
+typedef enum _EHostnameCmpMode EHostnameCmpMode;
+
+/* time type numerical values for structure below */
+#define TIME_TYPE_UNINIT 0
+#define TIME_TYPE_RFC3164 1
+#define TIME_TYPE_RFC5424 2
+/* rgerhards 2004-11-11: the following structure represents
+ * a time as it is used in syslog.
+ * rgerhards, 2009-06-23: packed structure for better cache performance
+ * (but left ultimate decision about packing to compiler)
+ */
+struct syslogTime {
+ intTiny timeType; /* 0 - unitinialized , 1 - RFC 3164, 2 - syslog-protocol */
+ intTiny month;
+ intTiny day;
+ intTiny hour; /* 24 hour clock */
+ intTiny minute;
+ intTiny second;
+ intTiny secfracPrecision;
+ intTiny OffsetMinute; /* UTC offset in minutes */
+ intTiny OffsetHour; /* UTC offset in hours
+ * full UTC offset minutes = OffsetHours*60 + OffsetMinute. Then use
+ * OffsetMode to know the direction.
+ */
+ char OffsetMode; /* UTC offset + or - */
+ short year;
+ int secfrac; /* fractional seconds (must be 32 bit!) */
+};
+typedef struct syslogTime syslogTime_t;
+
+#endif /* #ifndef SYSLOGD_TYPES_INCLUDED */
+/* vi:set ai:
+ */
diff --git a/runtime/typedefs.h b/runtime/typedefs.h
new file mode 100644
index 00000000..5cc24e4a
--- /dev/null
+++ b/runtime/typedefs.h
@@ -0,0 +1,203 @@
+/* This defines some types commonly used. Do NOT include any other
+ * rsyslog runtime file.
+ *
+ * Begun 2010-11-25 RGerhards
+ *
+ * Copyright (C) 2005-2008 by Rainer Gerhards and Adiscon GmbH
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#ifndef INCLUDED_TYPEDEFS_H
+#define INCLUDED_TYPEDEFS_H
+
+/* some universal fixed size integer defines ... */
+typedef long long int64;
+typedef long long unsigned uint64;
+typedef int64 number_t; /* type to use for numbers - TODO: maybe an autoconf option? */
+typedef char intTiny; /* 0..127! */
+typedef unsigned char uintTiny; /* 0..255! */
+
+/* define some base data types */
+
+typedef unsigned char uchar;/* get rid of the unhandy "unsigned char" */
+typedef struct aUsrp_s aUsrp_t;
+typedef struct thrdInfo thrdInfo_t;
+typedef struct obj_s obj_t;
+typedef struct ruleset_s ruleset_t;
+typedef struct rule_s rule_t;
+//typedef struct filed selector_t;/* TODO: this so far resides in syslogd.c, think about modularization */
+typedef struct NetAddr netAddr_t;
+typedef struct netstrms_s netstrms_t;
+typedef struct netstrm_s netstrm_t;
+typedef struct nssel_s nssel_t;
+typedef struct nspoll_s nspoll_t;
+typedef enum nsdsel_waitOp_e nsdsel_waitOp_t;
+typedef struct nsd_ptcp_s nsd_ptcp_t;
+typedef struct nsd_gtls_s nsd_gtls_t;
+typedef struct nsd_gsspi_s nsd_gsspi_t;
+typedef struct nsd_nss_s nsd_nss_t;
+typedef struct nsdsel_ptcp_s nsdsel_ptcp_t;
+typedef struct nsdsel_gtls_s nsdsel_gtls_t;
+typedef struct nsdpoll_ptcp_s nsdpoll_ptcp_t;
+typedef struct wti_s wti_t;
+typedef struct msg msg_t;
+typedef struct queue_s qqueue_t;
+typedef struct prop_s prop_t;
+typedef struct interface_s interface_t;
+typedef struct objInfo_s objInfo_t;
+typedef enum rsRetVal_ rsRetVal; /**< friendly type for global return value */
+typedef rsRetVal (*errLogFunc_t)(uchar*); /* this is a trick to store a function ptr to a function returning a function ptr... */
+typedef struct permittedPeers_s permittedPeers_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */
+typedef struct permittedPeerWildcard_s permittedPeerWildcard_t; /* this should go away in the long term -- rgerhards, 2008-05-19 */
+typedef struct tcpsrv_s tcpsrv_t;
+typedef struct tcps_sess_s tcps_sess_t;
+typedef struct strmsrv_s strmsrv_t;
+typedef struct strms_sess_s strms_sess_t;
+typedef struct vmstk_s vmstk_t;
+typedef struct batch_obj_s batch_obj_t;
+typedef struct batch_s batch_t;
+typedef struct wtp_s wtp_t;
+typedef struct modInfo_s modInfo_t;
+typedef struct parser_s parser_t;
+typedef struct parserList_s parserList_t;
+typedef struct strgen_s strgen_t;
+typedef struct strgenList_s strgenList_t;
+typedef struct statsobj_s statsobj_t;
+typedef struct nsd_epworkset_s nsd_epworkset_t;
+typedef struct templates_s templates_t;
+typedef struct queuecnf_s queuecnf_t;
+typedef struct rulesets_s rulesets_t;
+typedef struct globals_s globals_t;
+typedef struct defaults_s defaults_t;
+typedef struct actions_s actions_t;
+typedef struct rsconf_s rsconf_t;
+typedef struct cfgmodules_s cfgmodules_t;
+typedef struct cfgmodules_etry_s cfgmodules_etry_t;
+typedef struct outchannels_s outchannels_t;
+typedef struct modConfData_s modConfData_t;
+typedef struct instanceConf_s instanceConf_t;
+typedef struct ratelimit_s ratelimit_t;
+typedef struct action_s action_t;
+typedef int rs_size_t; /* we do never need more than 2Gig strings, signed permits to
+ * use -1 as a special flag. */
+typedef rsRetVal (*prsf_t)(struct vmstk_s*, int); /* pointer to a RainerScript function */
+typedef uint64 qDeqID; /* queue Dequeue order ID. 32 bits is considered dangerously few */
+
+typedef struct tcpLstnPortList_s tcpLstnPortList_t; // TODO: rename?
+typedef struct strmLstnPortList_s strmLstnPortList_t; // TODO: rename?
+
+/* under Solaris (actually only SPARC), we need to redefine some types
+ * to be void, so that we get void* pointers. Otherwise, we will see
+ * alignment errors.
+ */
+#ifdef OS_SOLARIS
+ typedef void * obj_t_ptr;
+ typedef void nsd_t;
+ typedef void nsdsel_t;
+ typedef void nsdpoll_t;
+#else
+ typedef obj_t *obj_t_ptr;
+ typedef obj_t nsd_t;
+ typedef obj_t nsdsel_t;
+ typedef obj_t nsdpoll_t;
+#endif
+
+
+#ifdef __hpux
+typedef unsigned int u_int32_t; /* TODO: is this correct? */
+typedef int socklen_t;
+#endif
+
+typedef struct epoll_event epoll_event_t;
+
+typedef char sbool; /* (small bool) I intentionally use char, to keep it slim so that many fit into the CPU cache! */
+
+/* settings for flow control
+ * TODO: is there a better place for them? -- rgerhards, 2008-03-14
+ */
+typedef enum {
+ eFLOWCTL_NO_DELAY = 0, /**< UDP and other non-delayable sources */
+ eFLOWCTL_LIGHT_DELAY = 1, /**< some light delay possible, but no extended period of time */
+ eFLOWCTL_FULL_DELAY = 2 /**< delay possible for extended period of time */
+} flowControl_t;
+
+/* filter operations */
+typedef enum {
+ FIOP_NOP = 0, /* do not use - No Operation */
+ FIOP_CONTAINS = 1, /* contains string? */
+ FIOP_ISEQUAL = 2, /* is (exactly) equal? */
+ FIOP_STARTSWITH = 3, /* starts with a string? */
+ FIOP_REGEX = 4, /* matches a (BRE) regular expression? */
+ FIOP_EREREGEX = 5, /* matches a ERE regular expression? */
+ FIOP_ISEMPTY = 6 /* string empty <=> strlen(s) == 0 ?*/
+} fiop_t;
+
+/* types of configuration handlers
+ */
+typedef enum cslCmdHdlrType {
+ eCmdHdlrInvalid = 0, /* invalid handler type - indicates a coding error */
+ eCmdHdlrCustomHandler, /* custom handler, just call handler function */
+ eCmdHdlrUID,
+ eCmdHdlrGID,
+ eCmdHdlrBinary,
+ eCmdHdlrFileCreateMode,
+ eCmdHdlrInt,
+ eCmdHdlrNonNegInt,
+ eCmdHdlrPositiveInt,
+ eCmdHdlrSize,
+ eCmdHdlrGetChar,
+ eCmdHdlrFacility,
+ eCmdHdlrSeverity,
+ eCmdHdlrGetWord,
+ eCmdHdlrString,
+ eCmdHdlrArray,
+ eCmdHdlrQueueType,
+ eCmdHdlrGoneAway /* statment existed, but is no longer supported */
+} ecslCmdHdrlType;
+
+
+/* the next type describes $Begin .. $End block object types
+ */
+typedef enum cslConfObjType {
+ eConfObjGlobal = 0, /* global directives */
+ eConfObjAction, /* action-specific directives */
+ /* now come states that indicate that we wait for a block-end. These are
+ * states that permit us to do some safety checks and they hopefully ease
+ * migration to a "real" parser/grammar.
+ */
+ eConfObjActionWaitEnd,
+ eConfObjAlways /* always valid, very special case (guess $End only!) */
+} ecslConfObjType;
+
+
+/* multi-submit support.
+ * This is done via a simple data structure, which holds the number of elements
+ * as well as an array of to-be-submitted messages.
+ * rgerhards, 2009-06-16
+ */
+typedef struct multi_submit_s multi_submit_t;
+struct multi_submit_s {
+ short maxElem; /* maximum number of Elements */
+ short nElem; /* current number of Elements, points to the next one FREE */
+ msg_t **ppMsgs;
+};
+
+#endif /* multi-include protection */
+/* vim:set ai:
+ */
diff --git a/runtime/unicode-helper.h b/runtime/unicode-helper.h
new file mode 100644
index 00000000..b7db2769
--- /dev/null
+++ b/runtime/unicode-helper.h
@@ -0,0 +1,67 @@
+/* This is the header file for unicode support.
+ *
+ * Currently, this is a dummy module.
+ * The following functions are wrappers which hopefully enable us to move
+ * from 8-bit chars to unicode with relative ease when we finally attack this
+ *
+ * Note: while we prefer inline functions, this leads to invalid references in
+ * core dumps. So in a debug build, we use macros where appropriate...
+ *
+ * Begun 2009-05-21 RGerhards
+ *
+ * Copyright (C) 2009-2012 by Rainer Gerhards and Adiscon GmbH
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_UNICODE_HELPER_H
+#define INCLUDED_UNICODE_HELPER_H
+
+#include <string.h>
+
+#ifdef DEBUG
+# define ustrncpy(psz1, psz2, len) strncpy((char*)(psz1), (char*)(psz2), (len))
+# define ustrdup(psz) (uchar*)strdup((char*)(psz))
+#else
+ static inline uchar* ustrncpy(uchar *psz1, uchar *psz2, size_t len)
+ {
+ return (uchar*) strncpy((char*) psz1, (char*) psz2, len);
+ }
+
+ static inline uchar* ustrdup(uchar *psz)
+ {
+ return (uchar*) strdup((char*)psz);
+ }
+
+#endif /* #ifdef DEBUG */
+
+static inline int ustrcmp(uchar *psz1, uchar *psz2)
+{
+ return strcmp((char*) psz1, (char*) psz2);
+}
+
+static inline int ustrlen(uchar *psz)
+{
+ return strlen((char*) psz);
+}
+
+
+#define UCHAR_CONSTANT(x) ((uchar*) (x))
+#define CHAR_CONVERT(x) ((char*) (x))
+
+#endif /* multi-include protection */
+/* vim:set ai:
+ */
diff --git a/runtime/unlimited_select.h b/runtime/unlimited_select.h
new file mode 100644
index 00000000..ec1e4498
--- /dev/null
+++ b/runtime/unlimited_select.h
@@ -0,0 +1,45 @@
+/* unlimited_select.h
+ * Tweak the macros for accessing fd_set so that the select() syscall
+ * won't be limited to a particular number of file descriptors.
+ *
+ * Copyright 2009-2012 Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef UNLIMITED_SELECT_H_INCLUDED
+#define UNLIMITED_SELECT_H_INCLUDED
+
+#include <string.h>
+#include <stdlib.h>
+#include <sys/select.h>
+#include "glbl.h"
+
+#ifdef USE_UNLIMITED_SELECT
+# undef FD_ZERO
+# define FD_ZERO(set) memset((set), 0, glbl.GetFdSetSize());
+#endif
+
+#ifdef USE_UNLIMITED_SELECT
+void freeFdSet(fd_set *p) {
+ free(p);
+}
+#else
+# define freeFdSet(x)
+#endif
+
+#endif /* #ifndef UNLIMITED_SELECT_H_INCLUDED */
diff --git a/runtime/var.c b/runtime/var.c
new file mode 100644
index 00000000..eecc5d6a
--- /dev/null
+++ b/runtime/var.c
@@ -0,0 +1,128 @@
+/* var.c - a typeless variable class
+ *
+ * This class is used to represent variable values, which may have any type.
+ * Among others, it will be used inside rsyslog's expression system, but
+ * also internally at any place where a typeless variable is needed.
+ *
+ * Module begun 2008-02-20 by Rainer Gerhards, with some code taken
+ * from the obj.c/.h files.
+ *
+ * Copyright 2007, 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "srUtils.h"
+#include "var.h"
+
+/* static data */
+DEFobjStaticHelpers
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(var) /* be sure to specify the object type also in END macro! */
+ENDobjConstruct(var)
+
+
+/* ConstructionFinalizer
+ * rgerhards, 2008-01-09
+ */
+rsRetVal varConstructFinalize(var_t __attribute__((unused)) *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, var);
+
+ RETiRet;
+}
+
+
+/* destructor for the var object */
+BEGINobjDestruct(var) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(var)
+ if(pThis->pcsName != NULL)
+ rsCStrDestruct(&pThis->pcsName);
+ if(pThis->varType == VARTYPE_STR) {
+ if(pThis->val.pStr != NULL)
+ rsCStrDestruct(&pThis->val.pStr);
+ }
+ENDobjDestruct(var)
+
+
+/* DebugPrint support for the var object */
+BEGINobjDebugPrint(var) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(var)
+ switch(pThis->varType) {
+ case VARTYPE_STR:
+ dbgoprint((obj_t*) pThis, "type: cstr, val '%s'\n", rsCStrGetSzStr(pThis->val.pStr));
+ break;
+ case VARTYPE_NUMBER:
+ dbgoprint((obj_t*) pThis, "type: number, val %lld\n", pThis->val.num);
+ break;
+ default:
+ dbgoprint((obj_t*) pThis, "type %d currently not suppored in debug output\n", pThis->varType);
+ break;
+ }
+ENDobjDebugPrint(var)
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-21
+ */
+BEGINobjQueryInterface(var)
+CODESTARTobjQueryInterface(var)
+ if(pIf->ifVersion != varCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->Construct = varConstruct;
+ pIf->ConstructFinalize = varConstructFinalize;
+ pIf->Destruct = varDestruct;
+ pIf->DebugPrint = varDebugPrint;
+finalize_it:
+ENDobjQueryInterface(var)
+
+
+/* Initialize the var class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(var, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+
+ /* now set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, varDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, varConstructFinalize);
+ENDObjClassInit(var)
+
+/* vi:set ai:
+ */
diff --git a/runtime/var.h b/runtime/var.h
new file mode 100644
index 00000000..3d0847d9
--- /dev/null
+++ b/runtime/var.h
@@ -0,0 +1,63 @@
+/* The var object.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_VAR_H
+#define INCLUDED_VAR_H
+
+#include "stringbuf.h"
+
+/* data types */
+typedef enum {
+ VARTYPE_NONE = 0, /* currently no value set */
+ VARTYPE_STR = 1,
+ VARTYPE_NUMBER = 2,
+ VARTYPE_SYSLOGTIME = 3
+} varType_t;
+
+/* the var object */
+typedef struct var_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ cstr_t *pcsName;
+ varType_t varType;
+ union {
+ number_t num;
+ es_str_t *str;
+ cstr_t *pStr;
+ syslogTime_t vSyslogTime;
+
+ } val;
+} var_t;
+
+
+/* interfaces */
+BEGINinterface(var) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(var);
+ rsRetVal (*Construct)(var_t **ppThis);
+ rsRetVal (*ConstructFinalize)(var_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(var_t **ppThis);
+ENDinterface(var)
+#define varCURR_IF_VERSION 2 /* increment whenever you change the interface above! */
+/* v2 - 2011-07-15/rger: on the way to remove var */
+
+
+/* prototypes */
+PROTOTYPEObj(var);
+
+#endif /* #ifndef INCLUDED_VAR_H */
diff --git a/runtime/wti.c b/runtime/wti.c
new file mode 100644
index 00000000..f91fb5a9
--- /dev/null
+++ b/runtime/wti.c
@@ -0,0 +1,399 @@
+/* wti.c
+ *
+ * This file implements the worker thread instance (wti) class.
+ *
+ * File begun on 2008-01-20 by RGerhards based on functions from the
+ * previous queue object class (the wti functions have been extracted)
+ *
+ * There is some in-depth documentation available in doc/dev_queue.html
+ * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it
+ * if you are getting aquainted to the object.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+#include <pthread.h>
+#include <errno.h>
+
+#include "rsyslog.h"
+#include "stringbuf.h"
+#include "srUtils.h"
+#include "wtp.h"
+#include "wti.h"
+#include "obj.h"
+#include "glbl.h"
+#include "atomic.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+/* forward-definitions */
+
+/* methods */
+
+/* get the header for debug messages
+ * The caller must NOT free or otherwise modify the returned string!
+ */
+static inline uchar *
+wtiGetDbgHdr(wti_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, wti);
+
+ if(pThis->pszDbgHdr == NULL)
+ return (uchar*) "wti"; /* should not normally happen */
+ else
+ return pThis->pszDbgHdr;
+}
+
+
+/* return the current worker processing state. For the sake of
+ * simplicity, we do not use the iRet interface. -- rgerhards, 2009-07-17
+ */
+sbool
+wtiGetState(wti_t *pThis)
+{
+ return ATOMIC_FETCH_32BIT(&pThis->bIsRunning, &pThis->mutIsRunning);
+}
+
+
+/* Set this thread to "always running" state (can not be unset)
+ * rgerhards, 2009-07-20
+ */
+rsRetVal
+wtiSetAlwaysRunning(wti_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, wti);
+ pThis->bAlwaysRunning = RSTRUE;
+ return RS_RET_OK;
+}
+
+/* Set status (thread is running or not), actually an property of
+ * use for wtp, but we need to have it per thread instance (thus it
+ * is inside wti). -- rgerhards, 2009-07-17
+ */
+rsRetVal
+wtiSetState(wti_t *pThis, sbool bNewVal)
+{
+ ISOBJ_TYPE_assert(pThis, wti);
+ if(bNewVal) {
+ ATOMIC_STORE_1_TO_INT(&pThis->bIsRunning, &pThis->mutIsRunning);
+ } else {
+ ATOMIC_STORE_0_TO_INT(&pThis->bIsRunning, &pThis->mutIsRunning);
+ }
+ return RS_RET_OK;
+}
+
+
+/* advise all workers to start by interrupting them. That should unblock all srSleep()
+ * calls.
+ */
+rsRetVal
+wtiWakeupThrd(wti_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wti);
+
+
+ if(wtiGetState(pThis)) {
+ /* we first try the cooperative "cancel" interface */
+ pthread_kill(pThis->thrdID, SIGTTIN);
+ DBGPRINTF("sent SIGTTIN to worker thread 0x%x\n", (unsigned) pThis->thrdID);
+ }
+
+ RETiRet;
+}
+
+
+/* Cancel the thread. If the thread is not running. But it is save and legal to
+ * call wtiCancelThrd() in such situations. This function only returns when the
+ * thread has terminated. Else we may get race conditions all over the code...
+ * Note that when waiting for the thread to terminate, we do a busy wait, checking
+ * progress every 10ms. It is very unlikely that we will ever cancel a thread
+ * and, if so, it will only happen at the end of the rsyslog run. So doing this
+ * kind of non-optimal wait is considered preferable over using condition variables.
+ * rgerhards, 2008-02-26
+ */
+rsRetVal
+wtiCancelThrd(wti_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wti);
+
+
+ if(wtiGetState(pThis)) {
+ /* we first try the cooperative "cancel" interface */
+ pthread_kill(pThis->thrdID, SIGTTIN);
+ DBGPRINTF("sent SIGTTIN to worker thread 0x%x, giving it a chance to terminate\n", (unsigned) pThis->thrdID);
+ srSleep(0, 10000);
+ }
+
+ if(wtiGetState(pThis)) {
+ DBGPRINTF("cooperative worker termination failed, using cancellation...\n");
+ DBGOPRINT((obj_t*) pThis, "canceling worker thread\n");
+ pthread_cancel(pThis->thrdID);
+ /* now wait until the thread terminates... */
+ while(wtiGetState(pThis)) {
+ srSleep(0, 10000);
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* Destructor */
+BEGINobjDestruct(wti) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(wti)
+ /* actual destruction */
+ batchFree(&pThis->batch);
+ DESTROY_ATOMIC_HELPER_MUT(pThis->mutIsRunning);
+
+ free(pThis->pszDbgHdr);
+ENDobjDestruct(wti)
+
+
+/* Standard-Constructor for the wti object
+ */
+BEGINobjConstruct(wti) /* be sure to specify the object type also in END macro! */
+ INIT_ATOMIC_HELPER_MUT(pThis->mutIsRunning);
+ENDobjConstruct(wti)
+
+
+/* Construction finalizer
+ * rgerhards, 2008-01-17
+ */
+rsRetVal
+wtiConstructFinalize(wti_t *pThis)
+{
+ DEFiRet;
+ int iDeqBatchSize;
+
+ ISOBJ_TYPE_assert(pThis, wti);
+
+ DBGPRINTF("%s: finalizing construction of worker instance data\n", wtiGetDbgHdr(pThis));
+
+ /* initialize our thread instance descriptor (no concurrency here) */
+ pThis->bIsRunning = RSFALSE;
+
+ /* we now alloc the array for user pointers. We obtain the max from the queue itself. */
+ CHKiRet(pThis->pWtp->pfGetDeqBatchSize(pThis->pWtp->pUsr, &iDeqBatchSize));
+ CHKiRet(batchInit(&pThis->batch, iDeqBatchSize));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* cancellation cleanup handler for queueWorker ()
+ * Most importantly, it must bring back the batch into a consistent state.
+ * Keep in mind that cancellation is disabled if we run into
+ * the cancel cleanup handler (and have been cancelled).
+ * rgerhards, 2008-01-16
+ */
+static void
+wtiWorkerCancelCleanup(void *arg)
+{
+ wti_t *pThis = (wti_t*) arg;
+ wtp_t *pWtp;
+
+ BEGINfunc
+ ISOBJ_TYPE_assert(pThis, wti);
+ pWtp = pThis->pWtp;
+ ISOBJ_TYPE_assert(pWtp, wtp);
+
+ DBGPRINTF("%s: cancelation cleanup handler called.\n", wtiGetDbgHdr(pThis));
+ pWtp->pfObjProcessed(pWtp->pUsr, pThis);
+ DBGPRINTF("%s: done cancelation cleanup handler.\n", wtiGetDbgHdr(pThis));
+
+ ENDfunc
+}
+
+
+/* wait for queue to become non-empty or timeout
+ * helper to wtiWorker. Note the the predicate is
+ * re-tested by the caller, so it is OK to NOT do it here.
+ * rgerhards, 2009-05-20
+ */
+static inline void
+doIdleProcessing(wti_t *pThis, wtp_t *pWtp, int *pbInactivityTOOccured)
+{
+ struct timespec t;
+
+ BEGINfunc
+ DBGPRINTF("%s: worker IDLE, waiting for work.\n", wtiGetDbgHdr(pThis));
+
+ if(pThis->bAlwaysRunning) {
+ /* never shut down any started worker */
+ d_pthread_cond_wait(pWtp->pcondBusy, pWtp->pmutUsr);
+ } else {
+ timeoutComp(&t, pWtp->toWrkShutdown);/* get absolute timeout */
+ if(d_pthread_cond_timedwait(pWtp->pcondBusy, pWtp->pmutUsr, &t) != 0) {
+ DBGPRINTF("%s: inactivity timeout, worker terminating...\n", wtiGetDbgHdr(pThis));
+ *pbInactivityTOOccured = 1; /* indicate we had a timeout */
+ }
+ }
+ DBGOPRINT((obj_t*) pThis, "worker awoke from idle processing\n");
+ ENDfunc
+}
+
+
+/* generic worker thread framework. Note that we prohibit cancellation
+ * during almost all times, because it can have very undesired side effects.
+ * However, we may need to cancel a thread if the consumer blocks for too
+ * long (during shutdown). So what we do is block cancellation, and every
+ * consumer must enable it during the periods where it is safe.
+ */
+#pragma GCC diagnostic ignored "-Wempty-body"
+rsRetVal
+wtiWorker(wti_t *pThis)
+{
+ wtp_t *pWtp; /* our worker thread pool */
+ int bInactivityTOOccured = 0;
+ rsRetVal localRet;
+ rsRetVal terminateRet;
+ int iCancelStateSave;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wti);
+ pWtp = pThis->pWtp; /* shortcut */
+ ISOBJ_TYPE_assert(pWtp, wtp);
+
+ dbgSetThrdName(pThis->pszDbgHdr);
+ pthread_cleanup_push(wtiWorkerCancelCleanup, pThis);
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+
+ /* now we have our identity, on to real processing */
+ while(1) { /* loop will be broken below - need to do mutex locks */
+ if(pWtp->pfRateLimiter != NULL) { /* call rate-limiter, if defined */
+ pWtp->pfRateLimiter(pWtp->pUsr);
+ }
+
+ d_pthread_mutex_lock(pWtp->pmutUsr);
+
+ /* first check if we are in shutdown process (but evaluate a bit later) */
+ terminateRet = wtpChkStopWrkr(pWtp, MUTEX_ALREADY_LOCKED);
+ if(terminateRet == RS_RET_TERMINATE_NOW) {
+ /* we now need to free the old batch */
+ localRet = pWtp->pfObjProcessed(pWtp->pUsr, pThis);
+ DBGOPRINT((obj_t*) pThis, "terminating worker because of TERMINATE_NOW mode, del iRet %d\n",
+ localRet);
+ d_pthread_mutex_unlock(pWtp->pmutUsr);
+ break;
+ }
+
+ /* try to execute and process whatever we have */
+ /* Note that this function releases and re-aquires the mutex. The returned
+ * information on idle state must be processed before releasing the mutex again.
+ */
+ localRet = pWtp->pfDoWork(pWtp->pUsr, pThis);
+
+ if(localRet == RS_RET_ERR_QUEUE_EMERGENCY) {
+ d_pthread_mutex_unlock(pWtp->pmutUsr);
+ break; /* end of loop */
+ } else if(localRet == RS_RET_IDLE) {
+ if(terminateRet == RS_RET_TERMINATE_WHEN_IDLE || bInactivityTOOccured) {
+ d_pthread_mutex_unlock(pWtp->pmutUsr);
+ DBGOPRINT((obj_t*) pThis, "terminating worker terminateRet=%d, bInactivityTOOccured=%d\n",
+ terminateRet, bInactivityTOOccured);
+ break; /* end of loop */
+ }
+ doIdleProcessing(pThis, pWtp, &bInactivityTOOccured);
+ d_pthread_mutex_unlock(pWtp->pmutUsr);
+ continue; /* request next iteration */
+ }
+
+ d_pthread_mutex_unlock(pWtp->pmutUsr);
+
+ bInactivityTOOccured = 0; /* reset for next run */
+ }
+
+ /* indicate termination */
+ pthread_cleanup_pop(0); /* remove cleanup handler */
+ pthread_setcancelstate(iCancelStateSave, NULL);
+
+ RETiRet;
+}
+#pragma GCC diagnostic warning "-Wempty-body"
+
+
+/* some simple object access methods */
+DEFpropSetMeth(wti, pWtp, wtp_t*)
+
+/* set the debug header message
+ * The passed-in string is duplicated. So if the caller does not need
+ * it any longer, it must free it. Must be called only before object is finalized.
+ * rgerhards, 2008-01-09
+ */
+rsRetVal
+wtiSetDbgHdr(wti_t *pThis, uchar *pszMsg, size_t lenMsg)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wti);
+ assert(pszMsg != NULL);
+
+ if(lenMsg < 1)
+ ABORT_FINALIZE(RS_RET_PARAM_ERROR);
+
+ if(pThis->pszDbgHdr != NULL) {
+ free(pThis->pszDbgHdr);
+ }
+
+ if((pThis->pszDbgHdr = MALLOC(sizeof(uchar) * lenMsg + 1)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ memcpy(pThis->pszDbgHdr, pszMsg, lenMsg + 1); /* always think about the \0! */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* dummy */
+rsRetVal wtiQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; }
+
+/* exit our class
+ */
+BEGINObjClassExit(wti, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsdsel_gtls)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ENDObjClassExit(wti)
+
+
+/* Initialize the wti class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-01-09
+ */
+BEGINObjClassInit(wti, 1, OBJ_IS_CORE_MODULE) /* one is the object version (most important for persisting) */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ENDObjClassInit(wti)
+
+/* vi:set ai:
+ */
diff --git a/runtime/wti.h b/runtime/wti.h
new file mode 100644
index 00000000..014251f0
--- /dev/null
+++ b/runtime/wti.h
@@ -0,0 +1,59 @@
+/* Definition of the worker thread instance (wti) class.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WTI_H_INCLUDED
+#define WTI_H_INCLUDED
+
+#include <pthread.h>
+#include "wtp.h"
+#include "obj.h"
+#include "batch.h"
+
+
+/* the worker thread instance class */
+struct wti_s {
+ BEGINobjInstance;
+ pthread_t thrdID; /* thread ID */
+ int bIsRunning; /* is this thread currently running? (must be int for atomic op!) */
+ sbool bAlwaysRunning; /* should this thread always run? */
+ wtp_t *pWtp; /* my worker thread pool (important if only the work thread instance is passed! */
+ batch_t batch; /* pointer to an object array meaningful for current user pointer (e.g. queue pUsr data elemt) */
+ uchar *pszDbgHdr; /* header string for debug messages */
+ DEF_ATOMIC_HELPER_MUT(mutIsRunning);
+};
+
+
+/* prototypes */
+rsRetVal wtiConstruct(wti_t **ppThis);
+rsRetVal wtiConstructFinalize(wti_t *pThis);
+rsRetVal wtiDestruct(wti_t **ppThis);
+rsRetVal wtiWorker(wti_t *pThis);
+rsRetVal wtiSetDbgHdr(wti_t *pThis, uchar *pszMsg, size_t lenMsg);
+rsRetVal wtiCancelThrd(wti_t *pThis);
+rsRetVal wtiSetAlwaysRunning(wti_t *pThis);
+rsRetVal wtiSetState(wti_t *pThis, sbool bNew);
+rsRetVal wtiWakeupThrd(wti_t *pThis);
+sbool wtiGetState(wti_t *pThis);
+PROTOTYPEObjClassInit(wti);
+PROTOTYPEpropSetMeth(wti, pszDbgHdr, uchar*);
+PROTOTYPEpropSetMeth(wti, pWtp, wtp_t*);
+
+#endif /* #ifndef WTI_H_INCLUDED */
diff --git a/runtime/wtp.c b/runtime/wtp.c
new file mode 100644
index 00000000..19151e7c
--- /dev/null
+++ b/runtime/wtp.c
@@ -0,0 +1,553 @@
+/* wtp.c
+ *
+ * This file implements the worker thread pool (wtp) class.
+ *
+ * File begun on 2008-01-20 by RGerhards
+ *
+ * There is some in-depth documentation available in doc/dev_queue.html
+ * (and in the web doc set on http://www.rsyslog.com/doc). Be sure to read it
+ * if you are getting aquainted to the object.
+ *
+ * Copyright 2008,2009 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * The rsyslog runtime library is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * The rsyslog runtime library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with the rsyslog runtime library. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * A copy of the GPL can be found in the file "COPYING" in this distribution.
+ * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
+ */
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <signal.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <atomic.h>
+#if HAVE_SYS_PRCTL_H
+# include <sys/prctl.h>
+#endif
+
+/// TODO: check on solaris if this is any longer needed - I don't think so - rgerhards, 2009-09-20
+//#ifdef OS_SOLARIS
+//# include <sched.h>
+//#endif
+
+#include "rsyslog.h"
+#include "stringbuf.h"
+#include "srUtils.h"
+#include "wtp.h"
+#include "wti.h"
+#include "obj.h"
+#include "unicode-helper.h"
+#include "glbl.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+/* forward-definitions */
+
+/* methods */
+
+/* get the header for debug messages
+ * The caller must NOT free or otherwise modify the returned string!
+ */
+static inline uchar *
+wtpGetDbgHdr(wtp_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ if(pThis->pszDbgHdr == NULL)
+ return (uchar*) "wtp"; /* should not normally happen */
+ else
+ return pThis->pszDbgHdr;
+}
+
+
+
+/* Not implemented dummy function for constructor */
+static rsRetVal NotImplementedDummy() { return RS_RET_NOT_IMPLEMENTED; }
+/* Standard-Constructor for the wtp object
+ */
+BEGINobjConstruct(wtp) /* be sure to specify the object type also in END macro! */
+ pthread_mutex_init(&pThis->mutWtp, NULL);
+ pthread_cond_init(&pThis->condThrdTrm, NULL);
+ pthread_attr_init(&pThis->attrThrd);
+ /* Set thread scheduling policy to default */
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+ pthread_attr_setschedpolicy(&pThis->attrThrd, default_thr_sched_policy);
+ pthread_attr_setschedparam(&pThis->attrThrd, &default_sched_param);
+ pthread_attr_setinheritsched(&pThis->attrThrd, PTHREAD_EXPLICIT_SCHED);
+#endif
+ pthread_attr_setdetachstate(&pThis->attrThrd, PTHREAD_CREATE_DETACHED);
+ /* set all function pointers to "not implemented" dummy so that we can safely call them */
+ pThis->pfChkStopWrkr = NotImplementedDummy;
+ pThis->pfGetDeqBatchSize = NotImplementedDummy;
+ pThis->pfDoWork = NotImplementedDummy;
+ pThis->pfObjProcessed = NotImplementedDummy;
+ INIT_ATOMIC_HELPER_MUT(pThis->mutCurNumWrkThrd);
+ INIT_ATOMIC_HELPER_MUT(pThis->mutWtpState);
+ENDobjConstruct(wtp)
+
+
+/* Construction finalizer
+ * rgerhards, 2008-01-17
+ */
+rsRetVal
+wtpConstructFinalize(wtp_t *pThis)
+{
+ DEFiRet;
+ int i;
+ uchar pszBuf[64];
+ size_t lenBuf;
+ wti_t *pWti;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ DBGPRINTF("%s: finalizing construction of worker thread pool\n", wtpGetDbgHdr(pThis));
+ /* alloc and construct workers - this can only be done in finalizer as we previously do
+ * not know the max number of workers
+ */
+ CHKmalloc(pThis->pWrkr = MALLOC(sizeof(wti_t*) * pThis->iNumWorkerThreads));
+
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) {
+ CHKiRet(wtiConstruct(&pThis->pWrkr[i]));
+ pWti = pThis->pWrkr[i];
+ lenBuf = snprintf((char*)pszBuf, sizeof(pszBuf), "%s/w%d", wtpGetDbgHdr(pThis), i);
+ CHKiRet(wtiSetDbgHdr(pWti, pszBuf, lenBuf));
+ CHKiRet(wtiSetpWtp(pWti, pThis));
+ CHKiRet(wtiConstructFinalize(pWti));
+ }
+
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Destructor */
+BEGINobjDestruct(wtp) /* be sure to specify the object type also in END and CODESTART macros! */
+ int i;
+CODESTARTobjDestruct(wtp)
+ /* destruct workers */
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i)
+ wtiDestruct(&pThis->pWrkr[i]);
+
+ free(pThis->pWrkr);
+ pThis->pWrkr = NULL;
+
+ /* actual destruction */
+ pthread_cond_destroy(&pThis->condThrdTrm);
+ pthread_mutex_destroy(&pThis->mutWtp);
+ pthread_attr_destroy(&pThis->attrThrd);
+ DESTROY_ATOMIC_HELPER_MUT(pThis->mutCurNumWrkThrd);
+ DESTROY_ATOMIC_HELPER_MUT(pThis->mutWtpState);
+
+ free(pThis->pszDbgHdr);
+ENDobjDestruct(wtp)
+
+
+/* Sent a specific state for the worker thread pool. -- rgerhards, 2008-01-21
+ * We do not need to do atomic instructions as set operations are only
+ * called when terminating the pool, and then in strict sequence. So we
+ * can never overwrite each other. On the other hand, it also doesn't
+ * matter if the read operation obtains an older value, as we then simply
+ * do one more iteration, what is perfectly legal (during shutdown
+ * they are awoken in any case). -- rgerhards, 2009-07-20
+ */
+rsRetVal
+wtpSetState(wtp_t *pThis, wtpState_t iNewState)
+{
+ ISOBJ_TYPE_assert(pThis, wtp);
+ pThis->wtpState = iNewState; // TODO: do we need a mutex here? 2010-04-26
+ return RS_RET_OK;
+}
+
+
+/* check if the worker shall shutdown (1 = yes, 0 = no)
+ * Note: there may be two mutexes locked, the bLockUsrMutex is the one in our "user"
+ * (e.g. the queue clas)
+ * rgerhards, 2008-01-21
+ */
+rsRetVal
+wtpChkStopWrkr(wtp_t *pThis, int bLockUsrMutex)
+{
+ DEFiRet;
+ wtpState_t wtpState;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+ /* we need a consistent value, but it doesn't really matter if it is changed
+ * right after the fetch - then we simply do one more iteration in the worker
+ */
+ wtpState = (wtpState_t) ATOMIC_FETCH_32BIT((int*)&pThis->wtpState, &pThis->mutWtpState);
+
+ if(wtpState == wtpState_SHUTDOWN_IMMEDIATE) {
+ ABORT_FINALIZE(RS_RET_TERMINATE_NOW);
+ } else if(wtpState == wtpState_SHUTDOWN) {
+ ABORT_FINALIZE(RS_RET_TERMINATE_WHEN_IDLE);
+ }
+
+ /* try customer handler if one was set and we do not yet have a definite result */
+ if(pThis->pfChkStopWrkr != NULL) {
+ iRet = pThis->pfChkStopWrkr(pThis->pUsr, bLockUsrMutex);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+#pragma GCC diagnostic ignored "-Wempty-body"
+/* Send a shutdown command to all workers and see if they terminate.
+ * A timeout may be specified. This function may also be called with
+ * the current number of workers being 0, in which case it does not
+ * shut down any worker.
+ * rgerhards, 2008-01-14
+ */
+rsRetVal
+wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout)
+{
+ DEFiRet;
+ int bTimedOut;
+ int i;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ /* lock mutex to prevent races (may otherwise happen during idle processing and such...) */
+ d_pthread_mutex_lock(pThis->pmutUsr);
+ wtpSetState(pThis, tShutdownCmd);
+ pthread_cond_broadcast(pThis->pcondBusy); /* wake up all workers */
+ /* awake workers in retry loop */
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) {
+ wtiWakeupThrd(pThis->pWrkr[i]);
+ }
+ d_pthread_mutex_unlock(pThis->pmutUsr);
+
+ /* wait for worker thread termination */
+ d_pthread_mutex_lock(&pThis->mutWtp);
+ pthread_cleanup_push(mutexCancelCleanup, &pThis->mutWtp);
+ bTimedOut = 0;
+ while(pThis->iCurNumWrkThrd > 0 && !bTimedOut) {
+ DBGPRINTF("%s: waiting %ldms on worker thread termination, %d still running\n",
+ wtpGetDbgHdr(pThis), timeoutVal(ptTimeout),
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd));
+
+ if(d_pthread_cond_timedwait(&pThis->condThrdTrm, &pThis->mutWtp, ptTimeout) != 0) {
+ DBGPRINTF("%s: timeout waiting on worker thread termination\n", wtpGetDbgHdr(pThis));
+ bTimedOut = 1; /* we exit the loop on timeout */
+ }
+
+ /* awake workers in retry loop */
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) {
+ wtiWakeupThrd(pThis->pWrkr[i]);
+ }
+
+ }
+ pthread_cleanup_pop(1);
+
+ if(bTimedOut)
+ iRet = RS_RET_TIMED_OUT;
+
+ RETiRet;
+}
+#pragma GCC diagnostic warning "-Wempty-body"
+
+
+/* Unconditionally cancel all running worker threads.
+ * rgerhards, 2008-01-14
+ */
+rsRetVal
+wtpCancelAll(wtp_t *pThis)
+{
+ DEFiRet;
+ int i;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ /* go through all workers and cancel those that are active */
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) {
+ wtiCancelThrd(pThis->pWrkr[i]);
+ }
+
+ RETiRet;
+}
+
+
+/* this function contains shared code for both regular worker shutdown as
+ * well as shutdown via cancellation. We can not simply use pthread_cleanup_pop(1)
+ * as this introduces a race in the debug system (RETiRet system).
+ * rgerhards, 2009-10-26
+ */
+static inline void
+wtpWrkrExecCleanup(wti_t *pWti)
+{
+ wtp_t *pThis;
+
+ BEGINfunc
+ ISOBJ_TYPE_assert(pWti, wti);
+ pThis = pWti->pWtp;
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ /* the order of the next two statements is important! */
+ wtiSetState(pWti, WRKTHRD_STOPPED);
+ ATOMIC_DEC(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd);
+
+ DBGPRINTF("%s: Worker thread %lx, terminated, num workers now %d\n",
+ wtpGetDbgHdr(pThis), (unsigned long) pWti,
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd));
+
+ ENDfunc
+}
+
+
+/* cancellation cleanup handler for executing worker decrements the worker counter.
+ * rgerhards, 2009-07-20
+ */
+static void
+wtpWrkrExecCancelCleanup(void *arg)
+{
+ wti_t *pWti = (wti_t*) arg;
+ wtp_t *pThis;
+
+ BEGINfunc
+ ISOBJ_TYPE_assert(pWti, wti);
+ pThis = pWti->pWtp;
+ ISOBJ_TYPE_assert(pThis, wtp);
+ DBGPRINTF("%s: Worker thread %lx requested to be cancelled.\n",
+ wtpGetDbgHdr(pThis), (unsigned long) pWti);
+
+ wtpWrkrExecCleanup(pWti);
+
+ ENDfunc
+ /* NOTE: we must call ENDfunc FIRST, because otherwise the schedule may activate the main
+ * thread after the broadcast, which could destroy the debug class, resulting in a potential
+ * segfault. So we need to do the broadcast as actually the last action in our processing
+ */
+ pthread_cond_broadcast(&pThis->condThrdTrm); /* activate anyone waiting on thread shutdown */
+}
+
+
+/* wtp worker shell. This is started and calls into the actual
+ * wti worker.
+ * rgerhards, 2008-01-21
+ */
+#pragma GCC diagnostic ignored "-Wempty-body"
+static void *
+wtpWorker(void *arg) /* the arg is actually a wti object, even though we are in wtp! */
+{
+ wti_t *pWti = (wti_t*) arg;
+ wtp_t *pThis;
+ sigset_t sigSet;
+# if HAVE_PRCTL && defined PR_SET_NAME
+ uchar *pszDbgHdr;
+ uchar thrdName[32] = "rs:";
+# endif
+
+ BEGINfunc
+ ISOBJ_TYPE_assert(pWti, wti);
+ pThis = pWti->pWtp;
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ /* block all signals */
+ sigfillset(&sigSet);
+ pthread_sigmask(SIG_BLOCK, &sigSet, NULL);
+
+ /* but ignore SIGTTN, which we (ab)use to signal the thread to shutdown -- rgerhards, 2009-07-20 */
+ sigemptyset(&sigSet);
+ sigaddset(&sigSet, SIGTTIN);
+ pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL);
+
+# if HAVE_PRCTL && defined PR_SET_NAME
+ /* set thread name - we ignore if the call fails, has no harsh consequences... */
+ pszDbgHdr = wtpGetDbgHdr(pThis);
+ ustrncpy(thrdName+3, pszDbgHdr, 20);
+ if(prctl(PR_SET_NAME, thrdName, 0, 0, 0) != 0) {
+ DBGPRINTF("prctl failed, not setting thread name for '%s'\n", wtpGetDbgHdr(pThis));
+ }
+ dbgOutputTID((char*)thrdName);
+# endif
+
+ pthread_cleanup_push(wtpWrkrExecCancelCleanup, pWti);
+ wtiWorker(pWti);
+ pthread_cleanup_pop(0);
+ wtpWrkrExecCleanup(pWti);
+
+ ENDfunc
+ /* NOTE: we must call ENDfunc FIRST, because otherwise the schedule may activate the main
+ * thread after the broadcast, which could destroy the debug class, resulting in a potential
+ * segfault. So we need to do the broadcast as actually the last action in our processing
+ */
+ pthread_cond_broadcast(&pThis->condThrdTrm); /* activate anyone waiting on thread shutdown */
+ pthread_exit(0);
+}
+#pragma GCC diagnostic warning "-Wempty-body"
+
+
+/* start a new worker */
+static rsRetVal
+wtpStartWrkr(wtp_t *pThis)
+{
+ wti_t *pWti;
+ int i;
+ int iState;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ d_pthread_mutex_lock(&pThis->mutWtp);
+
+ /* find free spot in thread table. */
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) {
+ if(wtiGetState(pThis->pWrkr[i]) == WRKTHRD_STOPPED) {
+ break;
+ }
+ }
+
+ if(i == pThis->iNumWorkerThreads)
+ ABORT_FINALIZE(RS_RET_NO_MORE_THREADS);
+
+ if(i == 0 || pThis->toWrkShutdown == -1) {
+ wtiSetAlwaysRunning(pThis->pWrkr[i]);
+ }
+
+ pWti = pThis->pWrkr[i];
+ wtiSetState(pWti, WRKTHRD_RUNNING);
+ iState = pthread_create(&(pWti->thrdID), &pThis->attrThrd, wtpWorker, (void*) pWti);
+ ATOMIC_INC(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd); /* we got one more! */
+
+ DBGPRINTF("%s: started with state %d, num workers now %d\n",
+ wtpGetDbgHdr(pThis), iState,
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd));
+
+finalize_it:
+ d_pthread_mutex_unlock(&pThis->mutWtp);
+ RETiRet;
+}
+
+
+/* set the number of worker threads that should be running. If less than currently running,
+ * a new worker may be started. Please note that there is no guarantee the number of workers
+ * said will be running after we exit this function. It is just a hint. If the number is
+ * higher than one, and no worker is started, the "busy" condition is signaled to awake a worker.
+ * So the caller can assume that there is at least one worker re-checking if there is "work to do"
+ * after this function call.
+ * rgerhards, 2008-01-21
+ */
+rsRetVal
+wtpAdviseMaxWorkers(wtp_t *pThis, int nMaxWrkr)
+{
+ DEFiRet;
+ int nMissing; /* number workers missing to run */
+ int i;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ if(nMaxWrkr == 0)
+ FINALIZE;
+
+ if(nMaxWrkr > pThis->iNumWorkerThreads) /* limit to configured maximum */
+ nMaxWrkr = pThis->iNumWorkerThreads;
+
+ nMissing = nMaxWrkr - ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd);
+
+ if(nMissing > 0) {
+ DBGPRINTF("%s: high activity - starting %d additional worker thread(s).\n",
+ wtpGetDbgHdr(pThis), nMissing);
+ /* start the rqtd nbr of workers */
+ for(i = 0 ; i < nMissing ; ++i) {
+ CHKiRet(wtpStartWrkr(pThis));
+ }
+ } else {
+ pthread_cond_signal(pThis->pcondBusy);
+ }
+
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* some simple object access methods */
+DEFpropSetMeth(wtp, toWrkShutdown, long)
+DEFpropSetMeth(wtp, wtpState, wtpState_t)
+DEFpropSetMeth(wtp, iNumWorkerThreads, int)
+DEFpropSetMeth(wtp, pUsr, void*)
+DEFpropSetMethPTR(wtp, pmutUsr, pthread_mutex_t)
+DEFpropSetMethPTR(wtp, pcondBusy, pthread_cond_t)
+DEFpropSetMethFP(wtp, pfChkStopWrkr, rsRetVal(*pVal)(void*, int))
+DEFpropSetMethFP(wtp, pfRateLimiter, rsRetVal(*pVal)(void*))
+DEFpropSetMethFP(wtp, pfGetDeqBatchSize, rsRetVal(*pVal)(void*, int*))
+DEFpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*))
+DEFpropSetMethFP(wtp, pfObjProcessed, rsRetVal(*pVal)(void*, wti_t*))
+
+
+/* set the debug header message
+ * The passed-in string is duplicated. So if the caller does not need
+ * it any longer, it must free it. Must be called only before object is finalized.
+ * rgerhards, 2008-01-09
+ */
+rsRetVal
+wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+ assert(pszMsg != NULL);
+
+ if(lenMsg < 1)
+ ABORT_FINALIZE(RS_RET_PARAM_ERROR);
+
+ if(pThis->pszDbgHdr != NULL) {
+ free(pThis->pszDbgHdr);
+ pThis->pszDbgHdr = NULL;
+ }
+
+ if((pThis->pszDbgHdr = MALLOC(sizeof(uchar) * lenMsg + 1)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ memcpy(pThis->pszDbgHdr, pszMsg, lenMsg + 1); /* always think about the \0! */
+
+finalize_it:
+ RETiRet;
+}
+
+/* dummy */
+rsRetVal wtpQueryInterface(void) { return RS_RET_NOT_IMPLEMENTED; }
+
+/* exit our class
+ */
+BEGINObjClassExit(wtp, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsdsel_gtls)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ENDObjClassExit(wtp)
+
+
+/* Initialize the stream class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-01-09
+ */
+BEGINObjClassInit(wtp, 1, OBJ_IS_CORE_MODULE)
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ENDObjClassInit(wtp)
+
+/* vi:set ai:
+ */
diff --git a/runtime/wtp.h b/runtime/wtp.h
new file mode 100644
index 00000000..25992f7f
--- /dev/null
+++ b/runtime/wtp.h
@@ -0,0 +1,100 @@
+/* Definition of the worker thread pool (wtp) object.
+ *
+ * Copyright 2008-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef WTP_H_INCLUDED
+#define WTP_H_INCLUDED
+
+#include <pthread.h>
+#include "obj.h"
+#include "atomic.h"
+
+/* states for worker threads. */
+#define WRKTHRD_STOPPED RSFALSE
+#define WRKTHRD_RUNNING RSTRUE
+
+
+/* possible states of a worker thread pool */
+typedef enum {
+ wtpState_RUNNING = 0, /* runs in regular mode */
+ wtpState_SHUTDOWN = 1, /* worker threads shall shutdown when idle */
+ wtpState_SHUTDOWN_IMMEDIATE = 2 /* worker threads shall shutdown ASAP, even if not idle */
+} wtpState_t;
+
+
+/* the worker thread pool (wtp) object */
+struct wtp_s {
+ BEGINobjInstance;
+ wtpState_t wtpState;
+ int iNumWorkerThreads;/* number of worker threads to use */
+ int iCurNumWrkThrd;/* current number of active worker threads */
+ struct wti_s **pWrkr;/* array with control structure for the worker thread(s) associated with this wtp */
+ int toWrkShutdown; /* timeout for idle workers in ms, -1 means indefinite (0 is immediate) */
+ rsRetVal (*pConsumer)(void *); /* user-supplied consumer function for dewtpd messages */
+ /* synchronization variables */
+ pthread_mutex_t mutWtp; /* mutex for the wtp's thread management */
+ pthread_cond_t condThrdTrm;/* signalled when threads terminate */
+ /* end sync variables */
+ /* user objects */
+ void *pUsr; /* pointer to user object (in this case, the queue the wtp belongs to) */
+ pthread_attr_t attrThrd;/* attribute for new threads (created just once and cached here) */
+ pthread_mutex_t *pmutUsr;
+ pthread_cond_t *pcondBusy; /* condition the user will signal "busy again, keep runing" on (awakes worker) */
+ rsRetVal (*pfChkStopWrkr)(void *pUsr, int);
+ rsRetVal (*pfGetDeqBatchSize)(void *pUsr, int*); /* obtains max dequeue count from queue config */
+ rsRetVal (*pfObjProcessed)(void *pUsr, wti_t *pWti); /* indicate user object is processed */
+ rsRetVal (*pfRateLimiter)(void *pUsr);
+ rsRetVal (*pfDoWork)(void *pUsr, void *pWti);
+ /* end user objects */
+ uchar *pszDbgHdr; /* header string for debug messages */
+ DEF_ATOMIC_HELPER_MUT(mutCurNumWrkThrd);
+ DEF_ATOMIC_HELPER_MUT(mutWtpState);
+};
+
+/* some symbolic constants for easier reference */
+
+
+/* prototypes */
+rsRetVal wtpConstruct(wtp_t **ppThis);
+rsRetVal wtpConstructFinalize(wtp_t *pThis);
+rsRetVal wtpDestruct(wtp_t **ppThis);
+rsRetVal wtpAdviseMaxWorkers(wtp_t *pThis, int nMaxWrkr);
+rsRetVal wtpProcessThrdChanges(wtp_t *pThis);
+rsRetVal wtpChkStopWrkr(wtp_t *pThis, int bLockUsrMutex);
+rsRetVal wtpSetState(wtp_t *pThis, wtpState_t iNewState);
+rsRetVal wtpWakeupAllWrkr(wtp_t *pThis);
+rsRetVal wtpCancelAll(wtp_t *pThis);
+rsRetVal wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg);
+rsRetVal wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout);
+PROTOTYPEObjClassInit(wtp);
+PROTOTYPEpropSetMethFP(wtp, pfChkStopWrkr, rsRetVal(*pVal)(void*, int));
+PROTOTYPEpropSetMethFP(wtp, pfRateLimiter, rsRetVal(*pVal)(void*));
+PROTOTYPEpropSetMethFP(wtp, pfGetDeqBatchSize, rsRetVal(*pVal)(void*, int*));
+PROTOTYPEpropSetMethFP(wtp, pfDoWork, rsRetVal(*pVal)(void*, void*));
+PROTOTYPEpropSetMethFP(wtp, pfObjProcessed, rsRetVal(*pVal)(void*, wti_t*));
+PROTOTYPEpropSetMeth(wtp, toWrkShutdown, long);
+PROTOTYPEpropSetMeth(wtp, wtpState, wtpState_t);
+PROTOTYPEpropSetMeth(wtp, iMaxWorkerThreads, int);
+PROTOTYPEpropSetMeth(wtp, pUsr, void*);
+PROTOTYPEpropSetMeth(wtp, iNumWorkerThreads, int);
+PROTOTYPEpropSetMethPTR(wtp, pmutUsr, pthread_mutex_t);
+PROTOTYPEpropSetMethPTR(wtp, pcondBusy, pthread_cond_t);
+
+#endif /* #ifndef WTP_H_INCLUDED */
diff --git a/runtime/zlibw.c b/runtime/zlibw.c
new file mode 100644
index 00000000..31963cc1
--- /dev/null
+++ b/runtime/zlibw.c
@@ -0,0 +1,124 @@
+/* The zlibwrap object.
+ *
+ * This is an rsyslog object wrapper around zlib.
+ *
+ * Copyright 2009-2012 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.h"
+#include <string.h>
+#include <assert.h>
+#include <zlib.h>
+
+#include "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "zlibw.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+
+
+/* ------------------------------ methods ------------------------------ */
+
+/* zlib make strong use of macros for its interface functions, so we can not simply
+ * pass function pointers to them. Instead, we create very small wrappers which call
+ * the relevant entry points.
+ */
+
+static int myDeflateInit(z_streamp strm, int level)
+{
+ return deflateInit(strm, level);
+}
+
+static int myDeflateInit2(z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy)
+{
+ return deflateInit2(strm, level, method, windowBits, memLevel, strategy);
+}
+
+static int myDeflateEnd(z_streamp strm)
+{
+ return deflateEnd(strm);
+}
+
+static int myDeflate(z_streamp strm, int flush)
+{
+ return deflate(strm, flush);
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(zlibw)
+CODESTARTobjQueryInterface(zlibw)
+ if(pIf->ifVersion != zlibwCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+
+ /* ok, we have the right interface, so let's fill it
+ * Please note that we may also do some backwards-compatibility
+ * work here (if we can support an older interface version - that,
+ * of course, also affects the "if" above).
+ */
+ pIf->DeflateInit = myDeflateInit;
+ pIf->DeflateInit2 = myDeflateInit2;
+ pIf->Deflate = myDeflate;
+ pIf->DeflateEnd = myDeflateEnd;
+finalize_it:
+ENDobjQueryInterface(zlibw)
+
+
+/* Initialize the zlibw class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(zlibw, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+
+ /* set our own handlers */
+ENDObjClassInit(zlibw)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+
+ CHKiRet(zlibwClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ENDmodInit
+/* vi:set ai:
+ */
diff --git a/runtime/zlibw.h b/runtime/zlibw.h
new file mode 100644
index 00000000..2dee1b18
--- /dev/null
+++ b/runtime/zlibw.h
@@ -0,0 +1,44 @@
+/* The zlibw object. It encapsulates the zlib functionality. The primary
+ * purpose of this wrapper class is to enable rsyslogd core to be build without
+ * zlib libraries.
+ *
+ * Copyright 2009-2012 Adiscon GmbH.
+ *
+ * This file is part of the rsyslog runtime library.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef INCLUDED_ZLIBW_H
+#define INCLUDED_ZLIBW_H
+
+#include <zlib.h>
+
+/* interfaces */
+BEGINinterface(zlibw) /* name must also be changed in ENDinterface macro! */
+ int (*DeflateInit)(z_streamp strm, int);
+ int (*DeflateInit2)(z_streamp strm, int level, int method, int windowBits, int memLevel, int strategy);
+ int (*Deflate)(z_streamp strm, int);
+ int (*DeflateEnd)(z_streamp strm);
+ENDinterface(zlibw)
+#define zlibwCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+PROTOTYPEObj(zlibw);
+
+/* the name of our library binary */
+#define LM_ZLIBW_FILENAME "lmzlibw"
+
+#endif /* #ifndef INCLUDED_ZLIBW_H */