summaryrefslogtreecommitdiffstats
path: root/runtime
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:28:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 16:28:20 +0000
commitdcc721a95bef6f0d8e6d8775b8efe33e5aecd562 (patch)
tree66a2774cd0ee294d019efd71d2544c70f42b2842 /runtime
parentInitial commit. (diff)
downloadrsyslog-dcc721a95bef6f0d8e6d8775b8efe33e5aecd562.tar.xz
rsyslog-dcc721a95bef6f0d8e6d8775b8efe33e5aecd562.zip
Adding upstream version 8.2402.0.upstream/8.2402.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--runtime/Makefile.am318
-rw-r--r--runtime/Makefile.in2110
-rw-r--r--runtime/atomic.h265
-rw-r--r--runtime/batch.h129
-rw-r--r--runtime/cfsysline.c1081
-rw-r--r--runtime/cfsysline.h62
-rw-r--r--runtime/conf.c640
-rw-r--r--runtime/conf.h73
-rw-r--r--runtime/cryprov.h48
-rw-r--r--runtime/datetime.c1413
-rw-r--r--runtime/datetime.h100
-rw-r--r--runtime/debug.c478
-rw-r--r--runtime/debug.h78
-rw-r--r--runtime/dnscache.c472
-rw-r--r--runtime/dnscache.h31
-rw-r--r--runtime/dynstats.c622
-rw-r--r--runtime/dynstats.h83
-rw-r--r--runtime/errmsg.c288
-rw-r--r--runtime/errmsg.h38
-rw-r--r--runtime/glbl.c1523
-rw-r--r--runtime/glbl.h159
-rw-r--r--runtime/gss-misc.c315
-rw-r--r--runtime/gss-misc.h44
-rw-r--r--runtime/hashtable.c341
-rw-r--r--runtime/hashtable.h202
-rw-r--r--runtime/hashtable_itr.c189
-rw-r--r--runtime/hashtable_itr.h103
-rw-r--r--runtime/hashtable_private.h75
-rw-r--r--runtime/im-helper.h62
-rw-r--r--runtime/janitor.c103
-rw-r--r--runtime/janitor.h35
-rw-r--r--runtime/lib_ksi_queue.c220
-rw-r--r--runtime/lib_ksi_queue.h53
-rw-r--r--runtime/lib_ksils12.c2173
-rw-r--r--runtime/lib_ksils12.h250
-rw-r--r--runtime/libgcry.c765
-rw-r--r--runtime/libgcry.h87
-rw-r--r--runtime/libgcry_common.c200
-rw-r--r--runtime/linkedlist.c415
-rw-r--r--runtime/linkedlist.h71
-rw-r--r--runtime/lmcry_gcry.c339
-rw-r--r--runtime/lmcry_gcry.h39
-rw-r--r--runtime/lmsig_ksi-ls12.c346
-rw-r--r--runtime/lmsig_ksi-ls12.h40
-rw-r--r--runtime/lookup.c1093
-rw-r--r--runtime/lookup.h116
-rw-r--r--runtime/module-template.h1250
-rw-r--r--runtime/modules.c1488
-rw-r--r--runtime/modules.h213
-rw-r--r--runtime/msg.c5476
-rw-r--r--runtime/msg.h281
-rw-r--r--runtime/net.c1741
-rw-r--r--runtime/net.h180
-rw-r--r--runtime/net_ossl.c1217
-rw-r--r--runtime/net_ossl.h173
-rw-r--r--runtime/netstrm.c508
-rw-r--r--runtime/netstrm.h114
-rw-r--r--runtime/netstrms.c553
-rw-r--r--runtime/netstrms.h94
-rw-r--r--runtime/nsd.h134
-rw-r--r--runtime/nsd_gtls.c2451
-rw-r--r--runtime/nsd_gtls.h123
-rw-r--r--runtime/nsd_ossl.c1510
-rw-r--r--runtime/nsd_ossl.h85
-rw-r--r--runtime/nsd_ptcp.c1153
-rw-r--r--runtime/nsd_ptcp.h52
-rw-r--r--runtime/nsdpoll_ptcp.c320
-rw-r--r--runtime/nsdpoll_ptcp.h61
-rw-r--r--runtime/nsdsel_gtls.c317
-rw-r--r--runtime/nsdsel_gtls.h41
-rw-r--r--runtime/nsdsel_ossl.c290
-rw-r--r--runtime/nsdsel_ossl.h42
-rw-r--r--runtime/nsdsel_ptcp.c221
-rw-r--r--runtime/nsdsel_ptcp.h45
-rw-r--r--runtime/nspoll.c218
-rw-r--r--runtime/nspoll.h66
-rw-r--r--runtime/nssel.c254
-rw-r--r--runtime/nssel.h57
-rw-r--r--runtime/obj-types.h444
-rw-r--r--runtime/obj.c1409
-rw-r--r--runtime/obj.h132
-rw-r--r--runtime/objomsr.c158
-rw-r--r--runtime/objomsr.h51
-rw-r--r--runtime/operatingstate.c177
-rw-r--r--runtime/operatingstate.h32
-rw-r--r--runtime/parser.c742
-rw-r--r--runtime/parser.h77
-rw-r--r--runtime/perctile_ringbuf.c360
-rw-r--r--runtime/perctile_ringbuf.h34
-rw-r--r--runtime/perctile_stats.c695
-rw-r--r--runtime/perctile_stats.h89
-rw-r--r--runtime/prop.c262
-rw-r--r--runtime/prop.h73
-rw-r--r--runtime/queue.c3609
-rw-r--r--runtime/queue.h247
-rw-r--r--runtime/ratelimit.c436
-rw-r--r--runtime/ratelimit.h55
-rw-r--r--runtime/regexp.c384
-rw-r--r--runtime/regexp.h44
-rw-r--r--runtime/rsconf.c1601
-rw-r--r--runtime/rsconf.h299
-rw-r--r--runtime/rsyslog.c298
-rw-r--r--runtime/rsyslog.h814
-rw-r--r--runtime/ruleset.c1194
-rw-r--r--runtime/ruleset.h106
-rw-r--r--runtime/sigprov.h37
-rw-r--r--runtime/srUtils.h106
-rw-r--r--runtime/srutils.c868
-rw-r--r--runtime/statsobj.c761
-rw-r--r--runtime/statsobj.h203
-rw-r--r--runtime/stream.c2533
-rw-r--r--runtime/stream.h254
-rw-r--r--runtime/strgen.c278
-rw-r--r--runtime/strgen.h60
-rw-r--r--runtime/stringbuf.c777
-rw-r--r--runtime/stringbuf.h157
-rw-r--r--runtime/syslogd-types.h114
-rw-r--r--runtime/tcpclt.c525
-rw-r--r--runtime/tcpclt.h76
-rw-r--r--runtime/tcps_sess.c625
-rw-r--r--runtime/tcps_sess.h90
-rw-r--r--runtime/tcpsrv.c1799
-rw-r--r--runtime/tcpsrv.h233
-rw-r--r--runtime/timezones.c205
-rw-r--r--runtime/timezones.h38
-rw-r--r--runtime/typedefs.h314
-rw-r--r--runtime/unicode-helper.h44
-rw-r--r--runtime/unlimited_select.h45
-rw-r--r--runtime/var.c131
-rw-r--r--runtime/var.h62
-rw-r--r--runtime/wti.c573
-rw-r--r--runtime/wti.h144
-rw-r--r--runtime/wtp.c650
-rw-r--r--runtime/wtp.h108
-rw-r--r--runtime/zlibw.c239
-rw-r--r--runtime/zlibw.h51
-rw-r--r--runtime/zstdw.c191
-rw-r--r--runtime/zstdw.h43
138 files changed, 64571 insertions, 0 deletions
diff --git a/runtime/Makefile.am b/runtime/Makefile.am
new file mode 100644
index 0000000..f1f5c4c
--- /dev/null
+++ b/runtime/Makefile.am
@@ -0,0 +1,318 @@
+sbin_PROGRAMS =
+man_MANS =
+noinst_LIBRARIES =
+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 \
+ janitor.c \
+ janitor.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 \
+ operatingstate.c \
+ operatingstate.h \
+ debug.c \
+ debug.h \
+ obj.c \
+ obj.h \
+ modules.c \
+ modules.h \
+ statsobj.c \
+ statsobj.h \
+ dynstats.c \
+ dynstats.h \
+ perctile_ringbuf.c \
+ perctile_ringbuf.h \
+ perctile_stats.c \
+ perctile_stats.h \
+ 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 \
+ lookup.c \
+ lookup.h \
+ cfsysline.c \
+ cfsysline.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 \
+ timezones.c \
+ timezones.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 OS_LINUX
+#librsyslog_la_SOURCES += \
+#endif
+
+if WITH_MODDIRS
+librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -D_PATH_MODDIR=\"$(pkglibdir)/:$(moddirs)\"
+else
+librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS -D_PATH_MODDIR=\"$(pkglibdir)/\" -I\$(top_srcdir) -I\$(top_srcdir)/grammar
+endif
+#librsyslog_la_LDFLAGS = -module -avoid-version
+librsyslog_la_CPPFLAGS += $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(LIBUUID_CFLAGS) $(LIBFASTJSON_CFLAGS) ${LIBESTR_CFLAGS}
+librsyslog_la_LIBADD = $(DL_LIBS) $(RT_LIBS) $(LIBUUID_LIBS) $(LIBFASTJSON_LIBS) ${LIBESTR_LIBS} -lm
+
+if ENABLE_LIBLOGGING_STDLOG
+librsyslog_la_CPPFLAGS += ${LIBLOGGING_STDLOG_CFLAGS}
+librsyslog_la_LIBADD += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+librsyslog_la_CPPFLAGS += -I\$(top_srcdir)/tools
+
+#
+# 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 =
+
+if ENABLE_LIBLOGGING_STDLOG
+lmregexp_la_CPPFLAGS += $(LIBLOGGING_STDLOG_CFLAGS)
+lmregexp_la_LDFLAGS += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+endif
+
+#
+# zlib support
+#
+pkglib_LTLIBRARIES += lmzlibw.la
+lmzlibw_la_SOURCES = zlibw.c zlibw.h
+lmzlibw_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmzlibw_la_LDFLAGS = -module -avoid-version $(ZLIB_LIBS)
+lmzlibw_la_LIBADD =
+
+if ENABLE_LIBLOGGING_STDLOG
+lmzlibw_la_CPPFLAGS += $(LIBLOGGING_STDLOG_CFLAGS)
+lmzlibw_la_LDFLAGS += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+
+#
+# basic network support, needed for rsyslog startup (e.g. our own system name)
+#
+pkglib_LTLIBRARIES += lmnet.la
+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 =
+
+if ENABLE_INET
+pkglib_LTLIBRARIES += lmnetstrms.la
+if ENABLE_LIBLOGGING_STDLOG
+lmnet_la_CPPFLAGS += $(LIBLOGGING_STDLOG_CFLAGS)
+lmnet_la_LDFLAGS += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+# 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 =
+
+if ENABLE_LIBLOGGING_STDLOG
+lmnetstrms_la_CPPFLAGS += $(LIBLOGGING_STDLOG_CFLAGS)
+lmnetstrms_la_LDFLAGS += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+# 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 =
+
+if ENABLE_LIBLOGGING_STDLOG
+lmnsd_ptcp_la_CPPFLAGS += $(LIBLOGGING_STDLOG_CFLAGS)
+lmnsd_ptcp_la_LDFLAGS += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+endif # if ENABLE_INET
+
+#
+# openssl base and netstream driver
+#
+if ENABLE_OPENSSL
+# noinst_LTLIBRARIES += lmnsd_ossl.la
+pkglib_LTLIBRARIES += lmnsd_ossl.la
+lmnsd_ossl_la_SOURCES = net_ossl.c net_ossl.h nsd_ossl.c nsd_ossl.h nsdsel_ossl.c nsdsel_ossl.h
+lmnsd_ossl_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(OPENSSL_CFLAGS)
+lmnsd_ossl_la_LDFLAGS = -module -avoid-version
+lmnsd_ossl_la_LIBADD = $(OPENSSL_LIBS)
+
+endif
+
+#
+# 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_DEPENDENCIES = libgcry.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 zstd
+#
+if ENABLE_LIBZSTD
+pkglib_LTLIBRARIES += lmzstdw.la
+ lmzstdw_la_SOURCES = zstdw.c zstdw.h
+ lmzstdw_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+ lmzstdw_la_LDFLAGS = -module -avoid-version $(ZSTD_LIBS)
+ lmzstdw_la_LIBADD = -lzstd
+endif
+
+
+#
+# gssapi support
+#
+if ENABLE_GSSAPI
+pkglib_LTLIBRARIES += lmgssutil.la
+lmgssutil_la_SOURCES = gss-misc.c gss-misc.h
+lmgssutil_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmgssutil_la_LDFLAGS = -module -avoid-version
+lmgssutil_la_LIBADD = $(GSS_LIBS)
+endif
+
+pkglib_LTLIBRARIES += lmtcpsrv.la lmtcpclt.la
+#
+#
+# TCP (stream) server support
+#
+lmtcpsrv_la_SOURCES = \
+ tcps_sess.c \
+ tcps_sess.h \
+ tcpsrv.c \
+ tcpsrv.h
+lmtcpsrv_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmtcpsrv_la_LDFLAGS = -module -avoid-version
+lmtcpsrv_la_LIBADD =
+
+if ENABLE_LIBLOGGING_STDLOG
+lmtcpsrv_la_CPPFLAGS += $(LIBLOGGING_STDLOG_CFLAGS)
+lmtcpsrv_la_LDFLAGS += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+#
+# TCP (stream) client support
+#
+lmtcpclt_la_SOURCES = \
+ tcpclt.c \
+ tcpclt.h
+lmtcpclt_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+lmtcpclt_la_LDFLAGS = -module -avoid-version
+lmtcpclt_la_LIBADD =
+
+if ENABLE_LIBLOGGING_STDLOG
+lmtcpclt_la_CPPFLAGS += $(LIBLOGGING_STDLOG_CFLAGS)
+lmtcpclt_la_LDFLAGS += $(LIBLOGGING_STDLOG_LIBS)
+endif
+
+
+#
+# support library for Guardtime KSI-LS12
+#
+if ENABLE_KSI_LS12
+ pkglib_LTLIBRARIES += lmsig_ksi_ls12.la
+ lmsig_ksi_ls12_la_SOURCES = lmsig_ksi-ls12.c lmsig_ksi-ls12.h lib_ksils12.c \
+ lib_ksils12.h lib_ksi_queue.c lib_ksi_queue.h
+ lmsig_ksi_ls12_la_CPPFLAGS = $(RSRT_CFLAGS) $(GT_KSI_LS12_CFLAGS)
+ lmsig_ksi_ls12_la_LDFLAGS = -module -avoid-version $(GT_KSI_LS12_LIBS)
+endif
diff --git a/runtime/Makefile.in b/runtime/Makefile.in
new file mode 100644
index 0000000..60c71f5
--- /dev/null
+++ b/runtime/Makefile.in
@@ -0,0 +1,2110 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+ if test -z '$(MAKELEVEL)'; then \
+ false; \
+ elif test -n '$(MAKE_HOST)'; then \
+ true; \
+ elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+ true; \
+ else \
+ false; \
+ fi; \
+}
+am__make_running_with_option = \
+ case $${target_option-} in \
+ ?) ;; \
+ *) echo "am__make_running_with_option: internal error: invalid" \
+ "target option '$${target_option-}' specified" >&2; \
+ exit 1;; \
+ esac; \
+ has_opt=no; \
+ sane_makeflags=$$MAKEFLAGS; \
+ if $(am__is_gnu_make); then \
+ sane_makeflags=$$MFLAGS; \
+ else \
+ case $$MAKEFLAGS in \
+ *\\[\ \ ]*) \
+ bs=\\; \
+ sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+ | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \
+ esac; \
+ fi; \
+ skip_next=no; \
+ strip_trailopt () \
+ { \
+ flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+ }; \
+ for flg in $$sane_makeflags; do \
+ test $$skip_next = yes && { skip_next=no; continue; }; \
+ case $$flg in \
+ *=*|--*) continue;; \
+ -*I) strip_trailopt 'I'; skip_next=yes;; \
+ -*I?*) strip_trailopt 'I';; \
+ -*O) strip_trailopt 'O'; skip_next=yes;; \
+ -*O?*) strip_trailopt 'O';; \
+ -*l) strip_trailopt 'l'; skip_next=yes;; \
+ -*l?*) strip_trailopt 'l';; \
+ -[dEDm]) skip_next=yes;; \
+ -[JT]) skip_next=yes;; \
+ esac; \
+ case $$flg in \
+ *$$target_option*) has_opt=yes; break;; \
+ esac; \
+ done; \
+ test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+sbin_PROGRAMS =
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_1 = ${LIBLOGGING_STDLOG_CFLAGS}
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_2 = $(LIBLOGGING_STDLOG_LIBS)
+
+#
+# regular expression support
+#
+@ENABLE_REGEXP_TRUE@am__append_3 = lmregexp.la
+@ENABLE_LIBLOGGING_STDLOG_TRUE@@ENABLE_REGEXP_TRUE@am__append_4 = $(LIBLOGGING_STDLOG_CFLAGS)
+@ENABLE_LIBLOGGING_STDLOG_TRUE@@ENABLE_REGEXP_TRUE@am__append_5 = $(LIBLOGGING_STDLOG_LIBS)
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_6 = $(LIBLOGGING_STDLOG_CFLAGS)
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_7 = $(LIBLOGGING_STDLOG_LIBS)
+
+# netstream drivers
+
+# plain tcp driver - main driver
+@ENABLE_INET_TRUE@am__append_8 = lmnetstrms.la lmnsd_ptcp.la
+@ENABLE_INET_TRUE@@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_9 = $(LIBLOGGING_STDLOG_CFLAGS)
+@ENABLE_INET_TRUE@@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_10 = $(LIBLOGGING_STDLOG_LIBS)
+@ENABLE_INET_TRUE@@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_11 = $(LIBLOGGING_STDLOG_CFLAGS)
+@ENABLE_INET_TRUE@@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_12 = $(LIBLOGGING_STDLOG_LIBS)
+@ENABLE_INET_TRUE@@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_13 = $(LIBLOGGING_STDLOG_CFLAGS)
+@ENABLE_INET_TRUE@@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_14 = $(LIBLOGGING_STDLOG_LIBS)
+
+#
+# openssl base and netstream driver
+#
+# noinst_LTLIBRARIES += lmnsd_ossl.la
+@ENABLE_OPENSSL_TRUE@am__append_15 = lmnsd_ossl.la
+
+#
+# GnuTLS netstream driver
+#
+@ENABLE_GNUTLS_TRUE@am__append_16 = lmnsd_gtls.la
+
+#
+# support library for libgcrypt
+#
+@ENABLE_LIBGCRYPT_TRUE@am__append_17 = libgcry.la
+@ENABLE_LIBGCRYPT_TRUE@am__append_18 = lmcry_gcry.la
+
+#
+# support library for zstd
+#
+@ENABLE_LIBZSTD_TRUE@am__append_19 = lmzstdw.la
+
+#
+# gssapi support
+#
+@ENABLE_GSSAPI_TRUE@am__append_20 = lmgssutil.la
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_21 = $(LIBLOGGING_STDLOG_CFLAGS)
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_22 = $(LIBLOGGING_STDLOG_LIBS)
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_23 = $(LIBLOGGING_STDLOG_CFLAGS)
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__append_24 = $(LIBLOGGING_STDLOG_LIBS)
+
+#
+# support library for Guardtime KSI-LS12
+#
+@ENABLE_KSI_LS12_TRUE@am__append_25 = lmsig_ksi_ls12.la
+subdir = runtime
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ac_check_define.m4 \
+ $(top_srcdir)/m4/atomic_operations.m4 \
+ $(top_srcdir)/m4/atomic_operations_64bit.m4 \
+ $(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+ $(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+ $(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+ $(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(pkglibdir)"
+PROGRAMS = $(sbin_PROGRAMS)
+LIBRARIES = $(noinst_LIBRARIES)
+am__vpath_adj_setup = srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`;
+am__vpath_adj = case $$p in \
+ $(srcdir)/*) f=`echo "$$p" | sed "s|^$$srcdirstrip/||"`;; \
+ *) f=$$p;; \
+ esac;
+am__strip_dir = f=`echo $$p | sed -e 's|^.*/||'`;
+am__install_max = 40
+am__nobase_strip_setup = \
+ srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*|]/\\\\&/g'`
+am__nobase_strip = \
+ for p in $$list; do echo "$$p"; done | sed -e "s|$$srcdirstrip/||"
+am__nobase_list = $(am__nobase_strip_setup); \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed "s| $$srcdirstrip/| |;"' / .*\//!s/ .*/ ./; s,\( .*\)/[^/]*$$,\1,' | \
+ $(AWK) 'BEGIN { files["."] = "" } { files[$$2] = files[$$2] " " $$1; \
+ if (++n[$$2] == $(am__install_max)) \
+ { print $$2, files[$$2]; n[$$2] = 0; files[$$2] = "" } } \
+ END { for (dir in files) print dir, files[dir] }'
+am__base_list = \
+ sed '$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;$$!N;s/\n/ /g' | \
+ sed '$$!N;$$!N;$$!N;$$!N;s/\n/ /g'
+am__uninstall_files_from_dir = { \
+ test -z "$$files" \
+ || { test ! -d "$$dir" && test ! -f "$$dir" && test ! -r "$$dir"; } \
+ || { echo " ( cd '$$dir' && rm -f" $$files ")"; \
+ $(am__cd) "$$dir" && rm -f $$files; }; \
+ }
+LTLIBRARIES = $(noinst_LTLIBRARIES) $(pkglib_LTLIBRARIES)
+libgcry_la_LIBADD =
+am__libgcry_la_SOURCES_DIST = libgcry.c libgcry_common.c libgcry.h
+@ENABLE_LIBGCRYPT_TRUE@am_libgcry_la_OBJECTS = libgcry_la-libgcry.lo \
+@ENABLE_LIBGCRYPT_TRUE@ libgcry_la-libgcry_common.lo
+libgcry_la_OBJECTS = $(am_libgcry_la_OBJECTS)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 =
+@ENABLE_LIBGCRYPT_TRUE@am_libgcry_la_rpath =
+am__DEPENDENCIES_1 =
+@ENABLE_LIBLOGGING_STDLOG_TRUE@am__DEPENDENCIES_2 = \
+@ENABLE_LIBLOGGING_STDLOG_TRUE@ $(am__DEPENDENCIES_1)
+librsyslog_la_DEPENDENCIES = $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_1) $(am__DEPENDENCIES_1) \
+ $(am__DEPENDENCIES_2)
+am__dirstamp = $(am__leading_dot)dirstamp
+am_librsyslog_la_OBJECTS = librsyslog_la-rsyslog.lo \
+ librsyslog_la-dnscache.lo librsyslog_la-glbl.lo \
+ librsyslog_la-conf.lo librsyslog_la-janitor.lo \
+ librsyslog_la-rsconf.lo librsyslog_la-parser.lo \
+ librsyslog_la-strgen.lo librsyslog_la-msg.lo \
+ librsyslog_la-linkedlist.lo librsyslog_la-objomsr.lo \
+ librsyslog_la-stringbuf.lo librsyslog_la-datetime.lo \
+ librsyslog_la-srutils.lo librsyslog_la-errmsg.lo \
+ librsyslog_la-operatingstate.lo librsyslog_la-debug.lo \
+ librsyslog_la-obj.lo librsyslog_la-modules.lo \
+ librsyslog_la-statsobj.lo librsyslog_la-dynstats.lo \
+ librsyslog_la-perctile_ringbuf.lo \
+ librsyslog_la-perctile_stats.lo librsyslog_la-stream.lo \
+ librsyslog_la-var.lo librsyslog_la-wtp.lo librsyslog_la-wti.lo \
+ librsyslog_la-queue.lo librsyslog_la-ruleset.lo \
+ librsyslog_la-prop.lo librsyslog_la-ratelimit.lo \
+ librsyslog_la-lookup.lo librsyslog_la-cfsysline.lo \
+ ../librsyslog_la-action.lo ../librsyslog_la-threads.lo \
+ ../librsyslog_la-parse.lo librsyslog_la-hashtable.lo \
+ librsyslog_la-hashtable_itr.lo ../librsyslog_la-outchannel.lo \
+ ../librsyslog_la-template.lo librsyslog_la-timezones.lo
+librsyslog_la_OBJECTS = $(am_librsyslog_la_OBJECTS)
+am__lmcry_gcry_la_SOURCES_DIST = lmcry_gcry.c lmcry_gcry.h
+@ENABLE_LIBGCRYPT_TRUE@am_lmcry_gcry_la_OBJECTS = \
+@ENABLE_LIBGCRYPT_TRUE@ lmcry_gcry_la-lmcry_gcry.lo
+lmcry_gcry_la_OBJECTS = $(am_lmcry_gcry_la_OBJECTS)
+lmcry_gcry_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmcry_gcry_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_LIBGCRYPT_TRUE@am_lmcry_gcry_la_rpath = -rpath $(pkglibdir)
+@ENABLE_GSSAPI_TRUE@lmgssutil_la_DEPENDENCIES = $(am__DEPENDENCIES_1)
+am__lmgssutil_la_SOURCES_DIST = gss-misc.c gss-misc.h
+@ENABLE_GSSAPI_TRUE@am_lmgssutil_la_OBJECTS = \
+@ENABLE_GSSAPI_TRUE@ lmgssutil_la-gss-misc.lo
+lmgssutil_la_OBJECTS = $(am_lmgssutil_la_OBJECTS)
+lmgssutil_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmgssutil_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_GSSAPI_TRUE@am_lmgssutil_la_rpath = -rpath $(pkglibdir)
+lmnet_la_DEPENDENCIES =
+am_lmnet_la_OBJECTS = lmnet_la-net.lo
+lmnet_la_OBJECTS = $(am_lmnet_la_OBJECTS)
+lmnet_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmnet_la_LDFLAGS) $(LDFLAGS) -o $@
+lmnetstrms_la_DEPENDENCIES =
+am__lmnetstrms_la_SOURCES_DIST = netstrms.c netstrms.h netstrm.c \
+ netstrm.h nssel.c nssel.h nspoll.c nspoll.h
+@ENABLE_INET_TRUE@am_lmnetstrms_la_OBJECTS = \
+@ENABLE_INET_TRUE@ lmnetstrms_la-netstrms.lo \
+@ENABLE_INET_TRUE@ lmnetstrms_la-netstrm.lo \
+@ENABLE_INET_TRUE@ lmnetstrms_la-nssel.lo \
+@ENABLE_INET_TRUE@ lmnetstrms_la-nspoll.lo
+lmnetstrms_la_OBJECTS = $(am_lmnetstrms_la_OBJECTS)
+lmnetstrms_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmnetstrms_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_INET_TRUE@am_lmnetstrms_la_rpath = -rpath $(pkglibdir)
+@ENABLE_GNUTLS_TRUE@lmnsd_gtls_la_DEPENDENCIES = \
+@ENABLE_GNUTLS_TRUE@ $(am__DEPENDENCIES_1)
+am__lmnsd_gtls_la_SOURCES_DIST = nsd_gtls.c nsd_gtls.h nsdsel_gtls.c \
+ nsdsel_gtls.h
+@ENABLE_GNUTLS_TRUE@am_lmnsd_gtls_la_OBJECTS = \
+@ENABLE_GNUTLS_TRUE@ lmnsd_gtls_la-nsd_gtls.lo \
+@ENABLE_GNUTLS_TRUE@ lmnsd_gtls_la-nsdsel_gtls.lo
+lmnsd_gtls_la_OBJECTS = $(am_lmnsd_gtls_la_OBJECTS)
+lmnsd_gtls_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmnsd_gtls_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_GNUTLS_TRUE@am_lmnsd_gtls_la_rpath = -rpath $(pkglibdir)
+@ENABLE_OPENSSL_TRUE@lmnsd_ossl_la_DEPENDENCIES = \
+@ENABLE_OPENSSL_TRUE@ $(am__DEPENDENCIES_1)
+am__lmnsd_ossl_la_SOURCES_DIST = net_ossl.c net_ossl.h nsd_ossl.c \
+ nsd_ossl.h nsdsel_ossl.c nsdsel_ossl.h
+@ENABLE_OPENSSL_TRUE@am_lmnsd_ossl_la_OBJECTS = \
+@ENABLE_OPENSSL_TRUE@ lmnsd_ossl_la-net_ossl.lo \
+@ENABLE_OPENSSL_TRUE@ lmnsd_ossl_la-nsd_ossl.lo \
+@ENABLE_OPENSSL_TRUE@ lmnsd_ossl_la-nsdsel_ossl.lo
+lmnsd_ossl_la_OBJECTS = $(am_lmnsd_ossl_la_OBJECTS)
+lmnsd_ossl_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmnsd_ossl_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_OPENSSL_TRUE@am_lmnsd_ossl_la_rpath = -rpath $(pkglibdir)
+lmnsd_ptcp_la_DEPENDENCIES =
+am__lmnsd_ptcp_la_SOURCES_DIST = nsd_ptcp.c nsd_ptcp.h nsdsel_ptcp.c \
+ nsdsel_ptcp.h nsdpoll_ptcp.c nsdpoll_ptcp.h
+@ENABLE_INET_TRUE@am_lmnsd_ptcp_la_OBJECTS = \
+@ENABLE_INET_TRUE@ lmnsd_ptcp_la-nsd_ptcp.lo \
+@ENABLE_INET_TRUE@ lmnsd_ptcp_la-nsdsel_ptcp.lo \
+@ENABLE_INET_TRUE@ lmnsd_ptcp_la-nsdpoll_ptcp.lo
+lmnsd_ptcp_la_OBJECTS = $(am_lmnsd_ptcp_la_OBJECTS)
+lmnsd_ptcp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmnsd_ptcp_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_INET_TRUE@am_lmnsd_ptcp_la_rpath = -rpath $(pkglibdir)
+lmregexp_la_DEPENDENCIES =
+am__lmregexp_la_SOURCES_DIST = regexp.c regexp.h
+@ENABLE_REGEXP_TRUE@am_lmregexp_la_OBJECTS = lmregexp_la-regexp.lo
+lmregexp_la_OBJECTS = $(am_lmregexp_la_OBJECTS)
+lmregexp_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmregexp_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_REGEXP_TRUE@am_lmregexp_la_rpath = -rpath $(pkglibdir)
+lmsig_ksi_ls12_la_LIBADD =
+am__lmsig_ksi_ls12_la_SOURCES_DIST = lmsig_ksi-ls12.c lmsig_ksi-ls12.h \
+ lib_ksils12.c lib_ksils12.h lib_ksi_queue.c lib_ksi_queue.h
+@ENABLE_KSI_LS12_TRUE@am_lmsig_ksi_ls12_la_OBJECTS = \
+@ENABLE_KSI_LS12_TRUE@ lmsig_ksi_ls12_la-lmsig_ksi-ls12.lo \
+@ENABLE_KSI_LS12_TRUE@ lmsig_ksi_ls12_la-lib_ksils12.lo \
+@ENABLE_KSI_LS12_TRUE@ lmsig_ksi_ls12_la-lib_ksi_queue.lo
+lmsig_ksi_ls12_la_OBJECTS = $(am_lmsig_ksi_ls12_la_OBJECTS)
+lmsig_ksi_ls12_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC \
+ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \
+ $(AM_CFLAGS) $(CFLAGS) $(lmsig_ksi_ls12_la_LDFLAGS) $(LDFLAGS) \
+ -o $@
+@ENABLE_KSI_LS12_TRUE@am_lmsig_ksi_ls12_la_rpath = -rpath $(pkglibdir)
+lmtcpclt_la_DEPENDENCIES =
+am_lmtcpclt_la_OBJECTS = lmtcpclt_la-tcpclt.lo
+lmtcpclt_la_OBJECTS = $(am_lmtcpclt_la_OBJECTS)
+lmtcpclt_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmtcpclt_la_LDFLAGS) $(LDFLAGS) -o $@
+lmtcpsrv_la_DEPENDENCIES =
+am_lmtcpsrv_la_OBJECTS = lmtcpsrv_la-tcps_sess.lo \
+ lmtcpsrv_la-tcpsrv.lo
+lmtcpsrv_la_OBJECTS = $(am_lmtcpsrv_la_OBJECTS)
+lmtcpsrv_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmtcpsrv_la_LDFLAGS) $(LDFLAGS) -o $@
+lmzlibw_la_DEPENDENCIES =
+am_lmzlibw_la_OBJECTS = lmzlibw_la-zlibw.lo
+lmzlibw_la_OBJECTS = $(am_lmzlibw_la_OBJECTS)
+lmzlibw_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmzlibw_la_LDFLAGS) $(LDFLAGS) -o $@
+lmzstdw_la_DEPENDENCIES =
+am__lmzstdw_la_SOURCES_DIST = zstdw.c zstdw.h
+@ENABLE_LIBZSTD_TRUE@am_lmzstdw_la_OBJECTS = lmzstdw_la-zstdw.lo
+lmzstdw_la_OBJECTS = $(am_lmzstdw_la_OBJECTS)
+lmzstdw_la_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(lmzstdw_la_LDFLAGS) $(LDFLAGS) -o $@
+@ENABLE_LIBZSTD_TRUE@am_lmzstdw_la_rpath = -rpath $(pkglibdir)
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo " GEN " $@;
+am__v_GEN_1 =
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 =
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/depcomp
+am__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ../$(DEPDIR)/librsyslog_la-action.Plo \
+ ../$(DEPDIR)/librsyslog_la-outchannel.Plo \
+ ../$(DEPDIR)/librsyslog_la-parse.Plo \
+ ../$(DEPDIR)/librsyslog_la-template.Plo \
+ ../$(DEPDIR)/librsyslog_la-threads.Plo \
+ ./$(DEPDIR)/libgcry_la-libgcry.Plo \
+ ./$(DEPDIR)/libgcry_la-libgcry_common.Plo \
+ ./$(DEPDIR)/librsyslog_la-cfsysline.Plo \
+ ./$(DEPDIR)/librsyslog_la-conf.Plo \
+ ./$(DEPDIR)/librsyslog_la-datetime.Plo \
+ ./$(DEPDIR)/librsyslog_la-debug.Plo \
+ ./$(DEPDIR)/librsyslog_la-dnscache.Plo \
+ ./$(DEPDIR)/librsyslog_la-dynstats.Plo \
+ ./$(DEPDIR)/librsyslog_la-errmsg.Plo \
+ ./$(DEPDIR)/librsyslog_la-glbl.Plo \
+ ./$(DEPDIR)/librsyslog_la-hashtable.Plo \
+ ./$(DEPDIR)/librsyslog_la-hashtable_itr.Plo \
+ ./$(DEPDIR)/librsyslog_la-janitor.Plo \
+ ./$(DEPDIR)/librsyslog_la-linkedlist.Plo \
+ ./$(DEPDIR)/librsyslog_la-lookup.Plo \
+ ./$(DEPDIR)/librsyslog_la-modules.Plo \
+ ./$(DEPDIR)/librsyslog_la-msg.Plo \
+ ./$(DEPDIR)/librsyslog_la-obj.Plo \
+ ./$(DEPDIR)/librsyslog_la-objomsr.Plo \
+ ./$(DEPDIR)/librsyslog_la-operatingstate.Plo \
+ ./$(DEPDIR)/librsyslog_la-parser.Plo \
+ ./$(DEPDIR)/librsyslog_la-perctile_ringbuf.Plo \
+ ./$(DEPDIR)/librsyslog_la-perctile_stats.Plo \
+ ./$(DEPDIR)/librsyslog_la-prop.Plo \
+ ./$(DEPDIR)/librsyslog_la-queue.Plo \
+ ./$(DEPDIR)/librsyslog_la-ratelimit.Plo \
+ ./$(DEPDIR)/librsyslog_la-rsconf.Plo \
+ ./$(DEPDIR)/librsyslog_la-rsyslog.Plo \
+ ./$(DEPDIR)/librsyslog_la-ruleset.Plo \
+ ./$(DEPDIR)/librsyslog_la-srutils.Plo \
+ ./$(DEPDIR)/librsyslog_la-statsobj.Plo \
+ ./$(DEPDIR)/librsyslog_la-stream.Plo \
+ ./$(DEPDIR)/librsyslog_la-strgen.Plo \
+ ./$(DEPDIR)/librsyslog_la-stringbuf.Plo \
+ ./$(DEPDIR)/librsyslog_la-timezones.Plo \
+ ./$(DEPDIR)/librsyslog_la-var.Plo \
+ ./$(DEPDIR)/librsyslog_la-wti.Plo \
+ ./$(DEPDIR)/librsyslog_la-wtp.Plo \
+ ./$(DEPDIR)/lmcry_gcry_la-lmcry_gcry.Plo \
+ ./$(DEPDIR)/lmgssutil_la-gss-misc.Plo \
+ ./$(DEPDIR)/lmnet_la-net.Plo \
+ ./$(DEPDIR)/lmnetstrms_la-netstrm.Plo \
+ ./$(DEPDIR)/lmnetstrms_la-netstrms.Plo \
+ ./$(DEPDIR)/lmnetstrms_la-nspoll.Plo \
+ ./$(DEPDIR)/lmnetstrms_la-nssel.Plo \
+ ./$(DEPDIR)/lmnsd_gtls_la-nsd_gtls.Plo \
+ ./$(DEPDIR)/lmnsd_gtls_la-nsdsel_gtls.Plo \
+ ./$(DEPDIR)/lmnsd_ossl_la-net_ossl.Plo \
+ ./$(DEPDIR)/lmnsd_ossl_la-nsd_ossl.Plo \
+ ./$(DEPDIR)/lmnsd_ossl_la-nsdsel_ossl.Plo \
+ ./$(DEPDIR)/lmnsd_ptcp_la-nsd_ptcp.Plo \
+ ./$(DEPDIR)/lmnsd_ptcp_la-nsdpoll_ptcp.Plo \
+ ./$(DEPDIR)/lmnsd_ptcp_la-nsdsel_ptcp.Plo \
+ ./$(DEPDIR)/lmregexp_la-regexp.Plo \
+ ./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksi_queue.Plo \
+ ./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksils12.Plo \
+ ./$(DEPDIR)/lmsig_ksi_ls12_la-lmsig_ksi-ls12.Plo \
+ ./$(DEPDIR)/lmtcpclt_la-tcpclt.Plo \
+ ./$(DEPDIR)/lmtcpsrv_la-tcps_sess.Plo \
+ ./$(DEPDIR)/lmtcpsrv_la-tcpsrv.Plo \
+ ./$(DEPDIR)/lmzlibw_la-zlibw.Plo \
+ ./$(DEPDIR)/lmzstdw_la-zstdw.Plo
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+ $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+ $(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo " CC " $@;
+am__v_CC_1 =
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+ $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+ $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo " CCLD " $@;
+am__v_CCLD_1 =
+SOURCES = $(libgcry_la_SOURCES) $(librsyslog_la_SOURCES) \
+ $(lmcry_gcry_la_SOURCES) $(lmgssutil_la_SOURCES) \
+ $(lmnet_la_SOURCES) $(lmnetstrms_la_SOURCES) \
+ $(lmnsd_gtls_la_SOURCES) $(lmnsd_ossl_la_SOURCES) \
+ $(lmnsd_ptcp_la_SOURCES) $(lmregexp_la_SOURCES) \
+ $(lmsig_ksi_ls12_la_SOURCES) $(lmtcpclt_la_SOURCES) \
+ $(lmtcpsrv_la_SOURCES) $(lmzlibw_la_SOURCES) \
+ $(lmzstdw_la_SOURCES)
+DIST_SOURCES = $(am__libgcry_la_SOURCES_DIST) $(librsyslog_la_SOURCES) \
+ $(am__lmcry_gcry_la_SOURCES_DIST) \
+ $(am__lmgssutil_la_SOURCES_DIST) $(lmnet_la_SOURCES) \
+ $(am__lmnetstrms_la_SOURCES_DIST) \
+ $(am__lmnsd_gtls_la_SOURCES_DIST) \
+ $(am__lmnsd_ossl_la_SOURCES_DIST) \
+ $(am__lmnsd_ptcp_la_SOURCES_DIST) \
+ $(am__lmregexp_la_SOURCES_DIST) \
+ $(am__lmsig_ksi_ls12_la_SOURCES_DIST) $(lmtcpclt_la_SOURCES) \
+ $(lmtcpsrv_la_SOURCES) $(lmzlibw_la_SOURCES) \
+ $(am__lmzstdw_la_SOURCES_DIST)
+am__can_run_installinfo = \
+ case $$AM_UPDATE_INFO_DIR in \
+ n|no|NO) false;; \
+ *) (install-info --version) >/dev/null 2>&1;; \
+ esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates. Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+ BEGIN { nonempty = 0; } \
+ { items[$$0] = 1; nonempty = 1; } \
+ END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique. This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+ list='$(am__tagged_files)'; \
+ unique=`for i in $$list; do \
+ if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+ done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+APU_CFLAGS = @APU_CFLAGS@
+APU_LIBS = @APU_LIBS@
+AR = @AR@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CIVETWEB_LIBS = @CIVETWEB_LIBS@
+CONF_FILE_PATH = @CONF_FILE_PATH@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CURL_CFLAGS = @CURL_CFLAGS@
+CURL_LIBS = @CURL_LIBS@
+CYGPATH_W = @CYGPATH_W@
+CZMQ_CFLAGS = @CZMQ_CFLAGS@
+CZMQ_LIBS = @CZMQ_LIBS@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DL_LIBS = @DL_LIBS@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FAUP_LIBS = @FAUP_LIBS@
+FGREP = @FGREP@
+GLIB_CFLAGS = @GLIB_CFLAGS@
+GLIB_LIBS = @GLIB_LIBS@
+GNUTLS_CFLAGS = @GNUTLS_CFLAGS@
+GNUTLS_LIBS = @GNUTLS_LIBS@
+GREP = @GREP@
+GSS_LIBS = @GSS_LIBS@
+GT_KSI_LS12_CFLAGS = @GT_KSI_LS12_CFLAGS@
+GT_KSI_LS12_LIBS = @GT_KSI_LS12_LIBS@
+HASH_XXHASH_LIBS = @HASH_XXHASH_LIBS@
+HIREDIS_CFLAGS = @HIREDIS_CFLAGS@
+HIREDIS_LIBS = @HIREDIS_LIBS@
+IMUDP_LIBS = @IMUDP_LIBS@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+IP = @IP@
+JAVA = @JAVA@
+JAVAC = @JAVAC@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LEX = @LEX@
+LEXLIB = @LEXLIB@
+LEX_OUTPUT_ROOT = @LEX_OUTPUT_ROOT@
+LIBCAPNG_CFLAGS = @LIBCAPNG_CFLAGS@
+LIBCAPNG_LIBS = @LIBCAPNG_LIBS@
+LIBCAPNG_PRESENT_CFLAGS = @LIBCAPNG_PRESENT_CFLAGS@
+LIBCAPNG_PRESENT_LIBS = @LIBCAPNG_PRESENT_LIBS@
+LIBDBI_CFLAGS = @LIBDBI_CFLAGS@
+LIBDBI_LIBS = @LIBDBI_LIBS@
+LIBESTR_CFLAGS = @LIBESTR_CFLAGS@
+LIBESTR_LIBS = @LIBESTR_LIBS@
+LIBEVENT_CFLAGS = @LIBEVENT_CFLAGS@
+LIBEVENT_LIBS = @LIBEVENT_LIBS@
+LIBFASTJSON_CFLAGS = @LIBFASTJSON_CFLAGS@
+LIBFASTJSON_LIBS = @LIBFASTJSON_LIBS@
+LIBGCRYPT_CFLAGS = @LIBGCRYPT_CFLAGS@
+LIBGCRYPT_CONFIG = @LIBGCRYPT_CONFIG@
+LIBGCRYPT_LIBS = @LIBGCRYPT_LIBS@
+LIBLOGGING_CFLAGS = @LIBLOGGING_CFLAGS@
+LIBLOGGING_LIBS = @LIBLOGGING_LIBS@
+LIBLOGGING_STDLOG_CFLAGS = @LIBLOGGING_STDLOG_CFLAGS@
+LIBLOGGING_STDLOG_LIBS = @LIBLOGGING_STDLOG_LIBS@
+LIBLOGNORM_CFLAGS = @LIBLOGNORM_CFLAGS@
+LIBLOGNORM_LIBS = @LIBLOGNORM_LIBS@
+LIBLZ4_CFLAGS = @LIBLZ4_CFLAGS@
+LIBLZ4_LIBS = @LIBLZ4_LIBS@
+LIBM = @LIBM@
+LIBMONGOC_CFLAGS = @LIBMONGOC_CFLAGS@
+LIBMONGOC_LIBS = @LIBMONGOC_LIBS@
+LIBOBJS = @LIBOBJS@
+LIBRDKAFKA_CFLAGS = @LIBRDKAFKA_CFLAGS@
+LIBRDKAFKA_LIBS = @LIBRDKAFKA_LIBS@
+LIBS = @LIBS@
+LIBSYSTEMD_CFLAGS = @LIBSYSTEMD_CFLAGS@
+LIBSYSTEMD_JOURNAL_CFLAGS = @LIBSYSTEMD_JOURNAL_CFLAGS@
+LIBSYSTEMD_JOURNAL_LIBS = @LIBSYSTEMD_JOURNAL_LIBS@
+LIBSYSTEMD_LIBS = @LIBSYSTEMD_LIBS@
+LIBTOOL = @LIBTOOL@
+LIBUUID_CFLAGS = @LIBUUID_CFLAGS@
+LIBUUID_LIBS = @LIBUUID_LIBS@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MYSQL_CFLAGS = @MYSQL_CFLAGS@
+MYSQL_CONFIG = @MYSQL_CONFIG@
+MYSQL_LIBS = @MYSQL_LIBS@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OPENSSL_CFLAGS = @OPENSSL_CFLAGS@
+OPENSSL_LIBS = @OPENSSL_LIBS@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PGSQL_CFLAGS = @PGSQL_CFLAGS@
+PGSQL_LIBS = @PGSQL_LIBS@
+PG_CONFIG = @PG_CONFIG@
+PID_FILE_PATH = @PID_FILE_PATH@
+PKG_CONFIG = @PKG_CONFIG@
+PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@
+PKG_CONFIG_PATH = @PKG_CONFIG_PATH@
+PROTON_CFLAGS = @PROTON_CFLAGS@
+PROTON_LIBS = @PROTON_LIBS@
+PROTON_PROACTOR_CFLAGS = @PROTON_PROACTOR_CFLAGS@
+PROTON_PROACTOR_LIBS = @PROTON_PROACTOR_LIBS@
+PTHREADS_CFLAGS = @PTHREADS_CFLAGS@
+PTHREADS_LIBS = @PTHREADS_LIBS@
+PYTHON = @PYTHON@
+PYTHON_EXEC_PREFIX = @PYTHON_EXEC_PREFIX@
+PYTHON_PLATFORM = @PYTHON_PLATFORM@
+PYTHON_PREFIX = @PYTHON_PREFIX@
+PYTHON_VERSION = @PYTHON_VERSION@
+RABBITMQ_CFLAGS = @RABBITMQ_CFLAGS@
+RABBITMQ_LIBS = @RABBITMQ_LIBS@
+RANLIB = @RANLIB@
+READLINK = @READLINK@
+REDIS = @REDIS@
+RELP_CFLAGS = @RELP_CFLAGS@
+RELP_LIBS = @RELP_LIBS@
+RSRT_CFLAGS = @RSRT_CFLAGS@
+RSRT_CFLAGS1 = @RSRT_CFLAGS1@
+RSRT_LIBS = @RSRT_LIBS@
+RSRT_LIBS1 = @RSRT_LIBS1@
+RST2MAN = @RST2MAN@
+RT_LIBS = @RT_LIBS@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+SNMP_CFLAGS = @SNMP_CFLAGS@
+SNMP_LIBS = @SNMP_LIBS@
+SOL_LIBS = @SOL_LIBS@
+STRIP = @STRIP@
+TCL_BIN_DIR = @TCL_BIN_DIR@
+TCL_INCLUDE_SPEC = @TCL_INCLUDE_SPEC@
+TCL_LIB_FILE = @TCL_LIB_FILE@
+TCL_LIB_FLAG = @TCL_LIB_FLAG@
+TCL_LIB_SPEC = @TCL_LIB_SPEC@
+TCL_PATCH_LEVEL = @TCL_PATCH_LEVEL@
+TCL_SRC_DIR = @TCL_SRC_DIR@
+TCL_STUB_LIB_FILE = @TCL_STUB_LIB_FILE@
+TCL_STUB_LIB_FLAG = @TCL_STUB_LIB_FLAG@
+TCL_STUB_LIB_SPEC = @TCL_STUB_LIB_SPEC@
+TCL_VERSION = @TCL_VERSION@
+UDPSPOOF_CFLAGS = @UDPSPOOF_CFLAGS@
+UDPSPOOF_LIBS = @UDPSPOOF_LIBS@
+VALGRIND = @VALGRIND@
+VERSION = @VERSION@
+WARN_CFLAGS = @WARN_CFLAGS@
+WARN_LDFLAGS = @WARN_LDFLAGS@
+WARN_SCANNERFLAGS = @WARN_SCANNERFLAGS@
+WGET = @WGET@
+YACC = @YACC@
+YACC_FOUND = @YACC_FOUND@
+YFLAGS = @YFLAGS@
+ZLIB_CFLAGS = @ZLIB_CFLAGS@
+ZLIB_LIBS = @ZLIB_LIBS@
+ZSTD_CFLAGS = @ZSTD_CFLAGS@
+ZSTD_LIBS = @ZSTD_LIBS@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+moddirs = @moddirs@
+oldincludedir = @oldincludedir@
+pdfdir = @pdfdir@
+pkgpyexecdir = @pkgpyexecdir@
+pkgpythondir = @pkgpythondir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+pyexecdir = @pyexecdir@
+pythondir = @pythondir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+man_MANS =
+noinst_LIBRARIES =
+noinst_LTLIBRARIES = librsyslog.la $(am__append_17)
+
+#
+# zlib support
+#
+
+#
+# basic network support, needed for rsyslog startup (e.g. our own system name)
+#
+pkglib_LTLIBRARIES = $(am__append_3) lmzlibw.la lmnet.la \
+ $(am__append_8) $(am__append_15) $(am__append_16) \
+ $(am__append_18) $(am__append_19) $(am__append_20) lmtcpsrv.la \
+ lmtcpclt.la $(am__append_25)
+#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 \
+ janitor.c \
+ janitor.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 \
+ operatingstate.c \
+ operatingstate.h \
+ debug.c \
+ debug.h \
+ obj.c \
+ obj.h \
+ modules.c \
+ modules.h \
+ statsobj.c \
+ statsobj.h \
+ dynstats.c \
+ dynstats.h \
+ perctile_ringbuf.c \
+ perctile_ringbuf.h \
+ perctile_stats.c \
+ perctile_stats.h \
+ 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 \
+ lookup.c \
+ lookup.h \
+ cfsysline.c \
+ cfsysline.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 \
+ timezones.c \
+ timezones.h
+
+#librsyslog_la_LDFLAGS = -module -avoid-version
+@WITH_MODDIRS_FALSE@librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS \
+@WITH_MODDIRS_FALSE@ -D_PATH_MODDIR=\"$(pkglibdir)/\" \
+@WITH_MODDIRS_FALSE@ -I\$(top_srcdir) -I\$(top_srcdir)/grammar \
+@WITH_MODDIRS_FALSE@ $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) \
+@WITH_MODDIRS_FALSE@ $(LIBUUID_CFLAGS) $(LIBFASTJSON_CFLAGS) \
+@WITH_MODDIRS_FALSE@ ${LIBESTR_CFLAGS} $(am__append_1) \
+@WITH_MODDIRS_FALSE@ -I\$(top_srcdir)/tools
+# 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 OS_LINUX
+#librsyslog_la_SOURCES += \
+#endif
+#librsyslog_la_LDFLAGS = -module -avoid-version
+@WITH_MODDIRS_TRUE@librsyslog_la_CPPFLAGS = -DSD_EXPORT_SYMBOLS \
+@WITH_MODDIRS_TRUE@ -D_PATH_MODDIR=\"$(pkglibdir)/:$(moddirs)\" \
+@WITH_MODDIRS_TRUE@ $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) \
+@WITH_MODDIRS_TRUE@ $(LIBUUID_CFLAGS) $(LIBFASTJSON_CFLAGS) \
+@WITH_MODDIRS_TRUE@ ${LIBESTR_CFLAGS} $(am__append_1) \
+@WITH_MODDIRS_TRUE@ -I\$(top_srcdir)/tools
+librsyslog_la_LIBADD = $(DL_LIBS) $(RT_LIBS) $(LIBUUID_LIBS) \
+ $(LIBFASTJSON_LIBS) ${LIBESTR_LIBS} -lm $(am__append_2)
+@ENABLE_REGEXP_TRUE@lmregexp_la_SOURCES = regexp.c regexp.h
+@ENABLE_REGEXP_TRUE@lmregexp_la_CPPFLAGS = $(PTHREADS_CFLAGS) \
+@ENABLE_REGEXP_TRUE@ $(RSRT_CFLAGS) $(am__append_4)
+@ENABLE_REGEXP_TRUE@lmregexp_la_LDFLAGS = -module -avoid-version \
+@ENABLE_REGEXP_TRUE@ $(am__append_5)
+@ENABLE_REGEXP_TRUE@lmregexp_la_LIBADD =
+lmzlibw_la_SOURCES = zlibw.c zlibw.h
+lmzlibw_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) \
+ $(am__append_6)
+lmzlibw_la_LDFLAGS = -module -avoid-version $(ZLIB_LIBS) \
+ $(am__append_7)
+lmzlibw_la_LIBADD =
+lmnet_la_SOURCES = net.c net.h
+lmnet_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(am__append_9)
+lmnet_la_LDFLAGS = -module -avoid-version \
+ ../compat/compat_la-getifaddrs.lo $(am__append_10)
+lmnet_la_LIBADD =
+
+# network stream master class and stream factory
+@ENABLE_INET_TRUE@lmnetstrms_la_SOURCES = netstrms.c netstrms.h \
+@ENABLE_INET_TRUE@ netstrm.c netstrm.h \
+@ENABLE_INET_TRUE@ nssel.c nssel.h \
+@ENABLE_INET_TRUE@ nspoll.c nspoll.h
+
+@ENABLE_INET_TRUE@lmnetstrms_la_CPPFLAGS = $(PTHREADS_CFLAGS) \
+@ENABLE_INET_TRUE@ $(RSRT_CFLAGS) $(am__append_11)
+@ENABLE_INET_TRUE@lmnetstrms_la_LDFLAGS = -module -avoid-version \
+@ENABLE_INET_TRUE@ $(am__append_12)
+@ENABLE_INET_TRUE@lmnetstrms_la_LIBADD =
+@ENABLE_INET_TRUE@lmnsd_ptcp_la_SOURCES = nsd_ptcp.c nsd_ptcp.h \
+@ENABLE_INET_TRUE@ nsdsel_ptcp.c nsdsel_ptcp.h \
+@ENABLE_INET_TRUE@ nsdpoll_ptcp.c nsdpoll_ptcp.h
+
+@ENABLE_INET_TRUE@lmnsd_ptcp_la_CPPFLAGS = $(PTHREADS_CFLAGS) \
+@ENABLE_INET_TRUE@ $(RSRT_CFLAGS) $(am__append_13)
+@ENABLE_INET_TRUE@lmnsd_ptcp_la_LDFLAGS = -module -avoid-version \
+@ENABLE_INET_TRUE@ $(am__append_14)
+@ENABLE_INET_TRUE@lmnsd_ptcp_la_LIBADD =
+@ENABLE_OPENSSL_TRUE@lmnsd_ossl_la_SOURCES = net_ossl.c net_ossl.h nsd_ossl.c nsd_ossl.h nsdsel_ossl.c nsdsel_ossl.h
+@ENABLE_OPENSSL_TRUE@lmnsd_ossl_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(OPENSSL_CFLAGS)
+@ENABLE_OPENSSL_TRUE@lmnsd_ossl_la_LDFLAGS = -module -avoid-version
+@ENABLE_OPENSSL_TRUE@lmnsd_ossl_la_LIBADD = $(OPENSSL_LIBS)
+@ENABLE_GNUTLS_TRUE@lmnsd_gtls_la_SOURCES = nsd_gtls.c nsd_gtls.h nsdsel_gtls.c nsdsel_gtls.h
+@ENABLE_GNUTLS_TRUE@lmnsd_gtls_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) $(GNUTLS_CFLAGS)
+@ENABLE_GNUTLS_TRUE@lmnsd_gtls_la_LDFLAGS = -module -avoid-version
+@ENABLE_GNUTLS_TRUE@lmnsd_gtls_la_LIBADD = $(GNUTLS_LIBS)
+@ENABLE_LIBGCRYPT_TRUE@libgcry_la_SOURCES = libgcry.c libgcry_common.c libgcry.h
+@ENABLE_LIBGCRYPT_TRUE@libgcry_la_CPPFLAGS = $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS)
+@ENABLE_LIBGCRYPT_TRUE@lmcry_gcry_la_DEPENDENCIES = libgcry.la
+@ENABLE_LIBGCRYPT_TRUE@lmcry_gcry_la_SOURCES = lmcry_gcry.c lmcry_gcry.h
+@ENABLE_LIBGCRYPT_TRUE@lmcry_gcry_la_CPPFLAGS = $(RSRT_CFLAGS) $(LIBGCRYPT_CFLAGS)
+@ENABLE_LIBGCRYPT_TRUE@lmcry_gcry_la_LDFLAGS = -module -avoid-version
+@ENABLE_LIBGCRYPT_TRUE@lmcry_gcry_la_LIBADD = libgcry.la $(LIBGCRYPT_LIBS)
+@ENABLE_LIBZSTD_TRUE@lmzstdw_la_SOURCES = zstdw.c zstdw.h
+@ENABLE_LIBZSTD_TRUE@lmzstdw_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+@ENABLE_LIBZSTD_TRUE@lmzstdw_la_LDFLAGS = -module -avoid-version $(ZSTD_LIBS)
+@ENABLE_LIBZSTD_TRUE@lmzstdw_la_LIBADD = -lzstd
+@ENABLE_GSSAPI_TRUE@lmgssutil_la_SOURCES = gss-misc.c gss-misc.h
+@ENABLE_GSSAPI_TRUE@lmgssutil_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
+@ENABLE_GSSAPI_TRUE@lmgssutil_la_LDFLAGS = -module -avoid-version
+@ENABLE_GSSAPI_TRUE@lmgssutil_la_LIBADD = $(GSS_LIBS)
+#
+#
+# TCP (stream) server support
+#
+lmtcpsrv_la_SOURCES = \
+ tcps_sess.c \
+ tcps_sess.h \
+ tcpsrv.c \
+ tcpsrv.h
+
+lmtcpsrv_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) \
+ $(am__append_21)
+lmtcpsrv_la_LDFLAGS = -module -avoid-version $(am__append_22)
+lmtcpsrv_la_LIBADD =
+
+#
+# TCP (stream) client support
+#
+lmtcpclt_la_SOURCES = \
+ tcpclt.c \
+ tcpclt.h
+
+lmtcpclt_la_CPPFLAGS = $(PTHREADS_CFLAGS) $(RSRT_CFLAGS) \
+ $(am__append_23)
+lmtcpclt_la_LDFLAGS = -module -avoid-version $(am__append_24)
+lmtcpclt_la_LIBADD =
+@ENABLE_KSI_LS12_TRUE@lmsig_ksi_ls12_la_SOURCES = lmsig_ksi-ls12.c lmsig_ksi-ls12.h lib_ksils12.c \
+@ENABLE_KSI_LS12_TRUE@ lib_ksils12.h lib_ksi_queue.c lib_ksi_queue.h
+
+@ENABLE_KSI_LS12_TRUE@lmsig_ksi_ls12_la_CPPFLAGS = $(RSRT_CFLAGS) $(GT_KSI_LS12_CFLAGS)
+@ENABLE_KSI_LS12_TRUE@lmsig_ksi_ls12_la_LDFLAGS = -module -avoid-version $(GT_KSI_LS12_LIBS)
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps)
+ @for dep in $?; do \
+ case '$(am__configure_deps)' in \
+ *$$dep*) \
+ ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+ && { if test -f $@; then exit 0; else break; fi; }; \
+ exit 1;; \
+ esac; \
+ done; \
+ echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu runtime/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --gnu runtime/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+ @case '$?' in \
+ *config.status*) \
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+ *) \
+ echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: $(am__configure_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): $(am__aclocal_m4_deps)
+ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-sbinPROGRAMS: $(sbin_PROGRAMS)
+ @$(NORMAL_INSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ if test -n "$$list"; then \
+ echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \
+ fi; \
+ for p in $$list; do echo "$$p $$p"; done | \
+ sed 's/$(EXEEXT)$$//' | \
+ while read p p1; do if test -f $$p \
+ || test -f $$p1 \
+ ; then echo "$$p"; echo "$$p"; else :; fi; \
+ done | \
+ sed -e 'p;s,.*/,,;n;h' \
+ -e 's|.*|.|' \
+ -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+ sed 'N;N;N;s,\n, ,g' | \
+ $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+ { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+ if ($$2 == $$4) files[d] = files[d] " " $$1; \
+ else { print "f", $$3 "/" $$4, $$1; } } \
+ END { for (d in files) print "f", d, files[d] }' | \
+ while read type dir files; do \
+ if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+ test -z "$$files" || { \
+ echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \
+ $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \
+ } \
+ ; done
+
+uninstall-sbinPROGRAMS:
+ @$(NORMAL_UNINSTALL)
+ @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \
+ files=`for p in $$list; do echo "$$p"; done | \
+ sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+ -e 's/$$/$(EXEEXT)/' \
+ `; \
+ test -n "$$list" || exit 0; \
+ echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \
+ cd "$(DESTDIR)$(sbindir)" && rm -f $$files
+
+clean-sbinPROGRAMS:
+ @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \
+ echo " rm -f" $$list; \
+ rm -f $$list || exit $$?; \
+ test -n "$(EXEEXT)" || exit 0; \
+ list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+ echo " rm -f" $$list; \
+ rm -f $$list
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+clean-noinstLTLIBRARIES:
+ -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES)
+ @list='$(noinst_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+install-pkglibLTLIBRARIES: $(pkglib_LTLIBRARIES)
+ @$(NORMAL_INSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ list2=; for p in $$list; do \
+ if test -f $$p; then \
+ list2="$$list2 $$p"; \
+ else :; fi; \
+ done; \
+ test -z "$$list2" || { \
+ echo " $(MKDIR_P) '$(DESTDIR)$(pkglibdir)'"; \
+ $(MKDIR_P) "$(DESTDIR)$(pkglibdir)" || exit 1; \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 '$(DESTDIR)$(pkglibdir)'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL) $(INSTALL_STRIP_FLAG) $$list2 "$(DESTDIR)$(pkglibdir)"; \
+ }
+
+uninstall-pkglibLTLIBRARIES:
+ @$(NORMAL_UNINSTALL)
+ @list='$(pkglib_LTLIBRARIES)'; test -n "$(pkglibdir)" || list=; \
+ for p in $$list; do \
+ $(am__strip_dir) \
+ echo " $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f '$(DESTDIR)$(pkglibdir)/$$f'"; \
+ $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=uninstall rm -f "$(DESTDIR)$(pkglibdir)/$$f"; \
+ done
+
+clean-pkglibLTLIBRARIES:
+ -test -z "$(pkglib_LTLIBRARIES)" || rm -f $(pkglib_LTLIBRARIES)
+ @list='$(pkglib_LTLIBRARIES)'; \
+ locs=`for p in $$list; do echo $$p; done | \
+ sed 's|^[^/]*$$|.|; s|/[^/]*$$||; s|$$|/so_locations|' | \
+ sort -u`; \
+ test -z "$$locs" || { \
+ echo rm -f $${locs}; \
+ rm -f $${locs}; \
+ }
+
+libgcry.la: $(libgcry_la_OBJECTS) $(libgcry_la_DEPENDENCIES) $(EXTRA_libgcry_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(am_libgcry_la_rpath) $(libgcry_la_OBJECTS) $(libgcry_la_LIBADD) $(LIBS)
+../$(am__dirstamp):
+ @$(MKDIR_P) ..
+ @: > ../$(am__dirstamp)
+../$(DEPDIR)/$(am__dirstamp):
+ @$(MKDIR_P) ../$(DEPDIR)
+ @: > ../$(DEPDIR)/$(am__dirstamp)
+../librsyslog_la-action.lo: ../$(am__dirstamp) \
+ ../$(DEPDIR)/$(am__dirstamp)
+../librsyslog_la-threads.lo: ../$(am__dirstamp) \
+ ../$(DEPDIR)/$(am__dirstamp)
+../librsyslog_la-parse.lo: ../$(am__dirstamp) \
+ ../$(DEPDIR)/$(am__dirstamp)
+../librsyslog_la-outchannel.lo: ../$(am__dirstamp) \
+ ../$(DEPDIR)/$(am__dirstamp)
+../librsyslog_la-template.lo: ../$(am__dirstamp) \
+ ../$(DEPDIR)/$(am__dirstamp)
+
+librsyslog.la: $(librsyslog_la_OBJECTS) $(librsyslog_la_DEPENDENCIES) $(EXTRA_librsyslog_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(LINK) $(librsyslog_la_OBJECTS) $(librsyslog_la_LIBADD) $(LIBS)
+
+lmcry_gcry.la: $(lmcry_gcry_la_OBJECTS) $(lmcry_gcry_la_DEPENDENCIES) $(EXTRA_lmcry_gcry_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmcry_gcry_la_LINK) $(am_lmcry_gcry_la_rpath) $(lmcry_gcry_la_OBJECTS) $(lmcry_gcry_la_LIBADD) $(LIBS)
+
+lmgssutil.la: $(lmgssutil_la_OBJECTS) $(lmgssutil_la_DEPENDENCIES) $(EXTRA_lmgssutil_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmgssutil_la_LINK) $(am_lmgssutil_la_rpath) $(lmgssutil_la_OBJECTS) $(lmgssutil_la_LIBADD) $(LIBS)
+
+lmnet.la: $(lmnet_la_OBJECTS) $(lmnet_la_DEPENDENCIES) $(EXTRA_lmnet_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmnet_la_LINK) -rpath $(pkglibdir) $(lmnet_la_OBJECTS) $(lmnet_la_LIBADD) $(LIBS)
+
+lmnetstrms.la: $(lmnetstrms_la_OBJECTS) $(lmnetstrms_la_DEPENDENCIES) $(EXTRA_lmnetstrms_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmnetstrms_la_LINK) $(am_lmnetstrms_la_rpath) $(lmnetstrms_la_OBJECTS) $(lmnetstrms_la_LIBADD) $(LIBS)
+
+lmnsd_gtls.la: $(lmnsd_gtls_la_OBJECTS) $(lmnsd_gtls_la_DEPENDENCIES) $(EXTRA_lmnsd_gtls_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmnsd_gtls_la_LINK) $(am_lmnsd_gtls_la_rpath) $(lmnsd_gtls_la_OBJECTS) $(lmnsd_gtls_la_LIBADD) $(LIBS)
+
+lmnsd_ossl.la: $(lmnsd_ossl_la_OBJECTS) $(lmnsd_ossl_la_DEPENDENCIES) $(EXTRA_lmnsd_ossl_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmnsd_ossl_la_LINK) $(am_lmnsd_ossl_la_rpath) $(lmnsd_ossl_la_OBJECTS) $(lmnsd_ossl_la_LIBADD) $(LIBS)
+
+lmnsd_ptcp.la: $(lmnsd_ptcp_la_OBJECTS) $(lmnsd_ptcp_la_DEPENDENCIES) $(EXTRA_lmnsd_ptcp_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmnsd_ptcp_la_LINK) $(am_lmnsd_ptcp_la_rpath) $(lmnsd_ptcp_la_OBJECTS) $(lmnsd_ptcp_la_LIBADD) $(LIBS)
+
+lmregexp.la: $(lmregexp_la_OBJECTS) $(lmregexp_la_DEPENDENCIES) $(EXTRA_lmregexp_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmregexp_la_LINK) $(am_lmregexp_la_rpath) $(lmregexp_la_OBJECTS) $(lmregexp_la_LIBADD) $(LIBS)
+
+lmsig_ksi_ls12.la: $(lmsig_ksi_ls12_la_OBJECTS) $(lmsig_ksi_ls12_la_DEPENDENCIES) $(EXTRA_lmsig_ksi_ls12_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmsig_ksi_ls12_la_LINK) $(am_lmsig_ksi_ls12_la_rpath) $(lmsig_ksi_ls12_la_OBJECTS) $(lmsig_ksi_ls12_la_LIBADD) $(LIBS)
+
+lmtcpclt.la: $(lmtcpclt_la_OBJECTS) $(lmtcpclt_la_DEPENDENCIES) $(EXTRA_lmtcpclt_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmtcpclt_la_LINK) -rpath $(pkglibdir) $(lmtcpclt_la_OBJECTS) $(lmtcpclt_la_LIBADD) $(LIBS)
+
+lmtcpsrv.la: $(lmtcpsrv_la_OBJECTS) $(lmtcpsrv_la_DEPENDENCIES) $(EXTRA_lmtcpsrv_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmtcpsrv_la_LINK) -rpath $(pkglibdir) $(lmtcpsrv_la_OBJECTS) $(lmtcpsrv_la_LIBADD) $(LIBS)
+
+lmzlibw.la: $(lmzlibw_la_OBJECTS) $(lmzlibw_la_DEPENDENCIES) $(EXTRA_lmzlibw_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmzlibw_la_LINK) -rpath $(pkglibdir) $(lmzlibw_la_OBJECTS) $(lmzlibw_la_LIBADD) $(LIBS)
+
+lmzstdw.la: $(lmzstdw_la_OBJECTS) $(lmzstdw_la_DEPENDENCIES) $(EXTRA_lmzstdw_la_DEPENDENCIES)
+ $(AM_V_CCLD)$(lmzstdw_la_LINK) $(am_lmzstdw_la_rpath) $(lmzstdw_la_OBJECTS) $(lmzstdw_la_LIBADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+ -rm -f ../*.$(OBJEXT)
+ -rm -f ../*.lo
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@../$(DEPDIR)/librsyslog_la-action.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@../$(DEPDIR)/librsyslog_la-outchannel.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@../$(DEPDIR)/librsyslog_la-parse.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@../$(DEPDIR)/librsyslog_la-template.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@../$(DEPDIR)/librsyslog_la-threads.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgcry_la-libgcry.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libgcry_la-libgcry_common.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-cfsysline.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-conf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-datetime.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-debug.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-dnscache.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-dynstats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-errmsg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-glbl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-hashtable.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-hashtable_itr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-janitor.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-linkedlist.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-lookup.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-modules.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-msg.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-obj.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-objomsr.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-operatingstate.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-parser.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-perctile_ringbuf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-perctile_stats.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-prop.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-queue.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-ratelimit.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-rsconf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-rsyslog.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-ruleset.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-srutils.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-statsobj.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-stream.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-strgen.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-stringbuf.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-timezones.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-var.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-wti.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/librsyslog_la-wtp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmcry_gcry_la-lmcry_gcry.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmgssutil_la-gss-misc.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnet_la-net.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnetstrms_la-netstrm.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnetstrms_la-netstrms.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnetstrms_la-nspoll.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnetstrms_la-nssel.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_gtls_la-nsd_gtls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_gtls_la-nsdsel_gtls.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_ossl_la-net_ossl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_ossl_la-nsd_ossl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_ossl_la-nsdsel_ossl.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_ptcp_la-nsd_ptcp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_ptcp_la-nsdpoll_ptcp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmnsd_ptcp_la-nsdsel_ptcp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmregexp_la-regexp.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksi_queue.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksils12.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmsig_ksi_ls12_la-lmsig_ksi-ls12.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtcpclt_la-tcpclt.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtcpsrv_la-tcps_sess.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmtcpsrv_la-tcpsrv.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmzlibw_la-zlibw.Plo@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/lmzstdw_la-zstdw.Plo@am__quote@ # am--include-marker
+
+$(am__depfiles_remade):
+ @$(MKDIR_P) $(@D)
+ @echo '# dummy' >$@-t && $(am__mv) $@-t $@
+
+am--depfiles: $(am__depfiles_remade)
+
+.c.o:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+libgcry_la-libgcry.lo: libgcry.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgcry_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libgcry_la-libgcry.lo -MD -MP -MF $(DEPDIR)/libgcry_la-libgcry.Tpo -c -o libgcry_la-libgcry.lo `test -f 'libgcry.c' || echo '$(srcdir)/'`libgcry.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgcry_la-libgcry.Tpo $(DEPDIR)/libgcry_la-libgcry.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libgcry.c' object='libgcry_la-libgcry.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgcry_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgcry_la-libgcry.lo `test -f 'libgcry.c' || echo '$(srcdir)/'`libgcry.c
+
+libgcry_la-libgcry_common.lo: libgcry_common.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgcry_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT libgcry_la-libgcry_common.lo -MD -MP -MF $(DEPDIR)/libgcry_la-libgcry_common.Tpo -c -o libgcry_la-libgcry_common.lo `test -f 'libgcry_common.c' || echo '$(srcdir)/'`libgcry_common.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/libgcry_la-libgcry_common.Tpo $(DEPDIR)/libgcry_la-libgcry_common.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='libgcry_common.c' object='libgcry_la-libgcry_common.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(libgcry_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o libgcry_la-libgcry_common.lo `test -f 'libgcry_common.c' || echo '$(srcdir)/'`libgcry_common.c
+
+librsyslog_la-rsyslog.lo: rsyslog.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-rsyslog.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-rsyslog.Tpo -c -o librsyslog_la-rsyslog.lo `test -f 'rsyslog.c' || echo '$(srcdir)/'`rsyslog.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-rsyslog.Tpo $(DEPDIR)/librsyslog_la-rsyslog.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rsyslog.c' object='librsyslog_la-rsyslog.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-rsyslog.lo `test -f 'rsyslog.c' || echo '$(srcdir)/'`rsyslog.c
+
+librsyslog_la-dnscache.lo: dnscache.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-dnscache.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-dnscache.Tpo -c -o librsyslog_la-dnscache.lo `test -f 'dnscache.c' || echo '$(srcdir)/'`dnscache.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-dnscache.Tpo $(DEPDIR)/librsyslog_la-dnscache.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dnscache.c' object='librsyslog_la-dnscache.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-dnscache.lo `test -f 'dnscache.c' || echo '$(srcdir)/'`dnscache.c
+
+librsyslog_la-glbl.lo: glbl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-glbl.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-glbl.Tpo -c -o librsyslog_la-glbl.lo `test -f 'glbl.c' || echo '$(srcdir)/'`glbl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-glbl.Tpo $(DEPDIR)/librsyslog_la-glbl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='glbl.c' object='librsyslog_la-glbl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-glbl.lo `test -f 'glbl.c' || echo '$(srcdir)/'`glbl.c
+
+librsyslog_la-conf.lo: conf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-conf.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-conf.Tpo -c -o librsyslog_la-conf.lo `test -f 'conf.c' || echo '$(srcdir)/'`conf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-conf.Tpo $(DEPDIR)/librsyslog_la-conf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='conf.c' object='librsyslog_la-conf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-conf.lo `test -f 'conf.c' || echo '$(srcdir)/'`conf.c
+
+librsyslog_la-janitor.lo: janitor.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-janitor.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-janitor.Tpo -c -o librsyslog_la-janitor.lo `test -f 'janitor.c' || echo '$(srcdir)/'`janitor.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-janitor.Tpo $(DEPDIR)/librsyslog_la-janitor.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='janitor.c' object='librsyslog_la-janitor.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-janitor.lo `test -f 'janitor.c' || echo '$(srcdir)/'`janitor.c
+
+librsyslog_la-rsconf.lo: rsconf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-rsconf.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-rsconf.Tpo -c -o librsyslog_la-rsconf.lo `test -f 'rsconf.c' || echo '$(srcdir)/'`rsconf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-rsconf.Tpo $(DEPDIR)/librsyslog_la-rsconf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='rsconf.c' object='librsyslog_la-rsconf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-rsconf.lo `test -f 'rsconf.c' || echo '$(srcdir)/'`rsconf.c
+
+librsyslog_la-parser.lo: parser.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-parser.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-parser.Tpo -c -o librsyslog_la-parser.lo `test -f 'parser.c' || echo '$(srcdir)/'`parser.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-parser.Tpo $(DEPDIR)/librsyslog_la-parser.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='parser.c' object='librsyslog_la-parser.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-parser.lo `test -f 'parser.c' || echo '$(srcdir)/'`parser.c
+
+librsyslog_la-strgen.lo: strgen.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-strgen.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-strgen.Tpo -c -o librsyslog_la-strgen.lo `test -f 'strgen.c' || echo '$(srcdir)/'`strgen.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-strgen.Tpo $(DEPDIR)/librsyslog_la-strgen.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='strgen.c' object='librsyslog_la-strgen.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-strgen.lo `test -f 'strgen.c' || echo '$(srcdir)/'`strgen.c
+
+librsyslog_la-msg.lo: msg.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-msg.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-msg.Tpo -c -o librsyslog_la-msg.lo `test -f 'msg.c' || echo '$(srcdir)/'`msg.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-msg.Tpo $(DEPDIR)/librsyslog_la-msg.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='msg.c' object='librsyslog_la-msg.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-msg.lo `test -f 'msg.c' || echo '$(srcdir)/'`msg.c
+
+librsyslog_la-linkedlist.lo: linkedlist.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-linkedlist.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-linkedlist.Tpo -c -o librsyslog_la-linkedlist.lo `test -f 'linkedlist.c' || echo '$(srcdir)/'`linkedlist.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-linkedlist.Tpo $(DEPDIR)/librsyslog_la-linkedlist.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='linkedlist.c' object='librsyslog_la-linkedlist.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-linkedlist.lo `test -f 'linkedlist.c' || echo '$(srcdir)/'`linkedlist.c
+
+librsyslog_la-objomsr.lo: objomsr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-objomsr.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-objomsr.Tpo -c -o librsyslog_la-objomsr.lo `test -f 'objomsr.c' || echo '$(srcdir)/'`objomsr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-objomsr.Tpo $(DEPDIR)/librsyslog_la-objomsr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='objomsr.c' object='librsyslog_la-objomsr.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-objomsr.lo `test -f 'objomsr.c' || echo '$(srcdir)/'`objomsr.c
+
+librsyslog_la-stringbuf.lo: stringbuf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-stringbuf.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-stringbuf.Tpo -c -o librsyslog_la-stringbuf.lo `test -f 'stringbuf.c' || echo '$(srcdir)/'`stringbuf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-stringbuf.Tpo $(DEPDIR)/librsyslog_la-stringbuf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stringbuf.c' object='librsyslog_la-stringbuf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-stringbuf.lo `test -f 'stringbuf.c' || echo '$(srcdir)/'`stringbuf.c
+
+librsyslog_la-datetime.lo: datetime.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-datetime.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-datetime.Tpo -c -o librsyslog_la-datetime.lo `test -f 'datetime.c' || echo '$(srcdir)/'`datetime.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-datetime.Tpo $(DEPDIR)/librsyslog_la-datetime.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='datetime.c' object='librsyslog_la-datetime.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-datetime.lo `test -f 'datetime.c' || echo '$(srcdir)/'`datetime.c
+
+librsyslog_la-srutils.lo: srutils.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-srutils.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-srutils.Tpo -c -o librsyslog_la-srutils.lo `test -f 'srutils.c' || echo '$(srcdir)/'`srutils.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-srutils.Tpo $(DEPDIR)/librsyslog_la-srutils.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='srutils.c' object='librsyslog_la-srutils.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-srutils.lo `test -f 'srutils.c' || echo '$(srcdir)/'`srutils.c
+
+librsyslog_la-errmsg.lo: errmsg.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-errmsg.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-errmsg.Tpo -c -o librsyslog_la-errmsg.lo `test -f 'errmsg.c' || echo '$(srcdir)/'`errmsg.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-errmsg.Tpo $(DEPDIR)/librsyslog_la-errmsg.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='errmsg.c' object='librsyslog_la-errmsg.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-errmsg.lo `test -f 'errmsg.c' || echo '$(srcdir)/'`errmsg.c
+
+librsyslog_la-operatingstate.lo: operatingstate.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-operatingstate.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-operatingstate.Tpo -c -o librsyslog_la-operatingstate.lo `test -f 'operatingstate.c' || echo '$(srcdir)/'`operatingstate.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-operatingstate.Tpo $(DEPDIR)/librsyslog_la-operatingstate.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='operatingstate.c' object='librsyslog_la-operatingstate.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-operatingstate.lo `test -f 'operatingstate.c' || echo '$(srcdir)/'`operatingstate.c
+
+librsyslog_la-debug.lo: debug.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-debug.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-debug.Tpo -c -o librsyslog_la-debug.lo `test -f 'debug.c' || echo '$(srcdir)/'`debug.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-debug.Tpo $(DEPDIR)/librsyslog_la-debug.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='debug.c' object='librsyslog_la-debug.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-debug.lo `test -f 'debug.c' || echo '$(srcdir)/'`debug.c
+
+librsyslog_la-obj.lo: obj.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-obj.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-obj.Tpo -c -o librsyslog_la-obj.lo `test -f 'obj.c' || echo '$(srcdir)/'`obj.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-obj.Tpo $(DEPDIR)/librsyslog_la-obj.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='obj.c' object='librsyslog_la-obj.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-obj.lo `test -f 'obj.c' || echo '$(srcdir)/'`obj.c
+
+librsyslog_la-modules.lo: modules.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-modules.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-modules.Tpo -c -o librsyslog_la-modules.lo `test -f 'modules.c' || echo '$(srcdir)/'`modules.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-modules.Tpo $(DEPDIR)/librsyslog_la-modules.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='modules.c' object='librsyslog_la-modules.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-modules.lo `test -f 'modules.c' || echo '$(srcdir)/'`modules.c
+
+librsyslog_la-statsobj.lo: statsobj.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-statsobj.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-statsobj.Tpo -c -o librsyslog_la-statsobj.lo `test -f 'statsobj.c' || echo '$(srcdir)/'`statsobj.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-statsobj.Tpo $(DEPDIR)/librsyslog_la-statsobj.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='statsobj.c' object='librsyslog_la-statsobj.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-statsobj.lo `test -f 'statsobj.c' || echo '$(srcdir)/'`statsobj.c
+
+librsyslog_la-dynstats.lo: dynstats.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-dynstats.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-dynstats.Tpo -c -o librsyslog_la-dynstats.lo `test -f 'dynstats.c' || echo '$(srcdir)/'`dynstats.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-dynstats.Tpo $(DEPDIR)/librsyslog_la-dynstats.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='dynstats.c' object='librsyslog_la-dynstats.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-dynstats.lo `test -f 'dynstats.c' || echo '$(srcdir)/'`dynstats.c
+
+librsyslog_la-perctile_ringbuf.lo: perctile_ringbuf.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-perctile_ringbuf.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-perctile_ringbuf.Tpo -c -o librsyslog_la-perctile_ringbuf.lo `test -f 'perctile_ringbuf.c' || echo '$(srcdir)/'`perctile_ringbuf.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-perctile_ringbuf.Tpo $(DEPDIR)/librsyslog_la-perctile_ringbuf.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='perctile_ringbuf.c' object='librsyslog_la-perctile_ringbuf.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-perctile_ringbuf.lo `test -f 'perctile_ringbuf.c' || echo '$(srcdir)/'`perctile_ringbuf.c
+
+librsyslog_la-perctile_stats.lo: perctile_stats.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-perctile_stats.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-perctile_stats.Tpo -c -o librsyslog_la-perctile_stats.lo `test -f 'perctile_stats.c' || echo '$(srcdir)/'`perctile_stats.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-perctile_stats.Tpo $(DEPDIR)/librsyslog_la-perctile_stats.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='perctile_stats.c' object='librsyslog_la-perctile_stats.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-perctile_stats.lo `test -f 'perctile_stats.c' || echo '$(srcdir)/'`perctile_stats.c
+
+librsyslog_la-stream.lo: stream.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-stream.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-stream.Tpo -c -o librsyslog_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-stream.Tpo $(DEPDIR)/librsyslog_la-stream.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='stream.c' object='librsyslog_la-stream.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-stream.lo `test -f 'stream.c' || echo '$(srcdir)/'`stream.c
+
+librsyslog_la-var.lo: var.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-var.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-var.Tpo -c -o librsyslog_la-var.lo `test -f 'var.c' || echo '$(srcdir)/'`var.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-var.Tpo $(DEPDIR)/librsyslog_la-var.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='var.c' object='librsyslog_la-var.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-var.lo `test -f 'var.c' || echo '$(srcdir)/'`var.c
+
+librsyslog_la-wtp.lo: wtp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-wtp.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-wtp.Tpo -c -o librsyslog_la-wtp.lo `test -f 'wtp.c' || echo '$(srcdir)/'`wtp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-wtp.Tpo $(DEPDIR)/librsyslog_la-wtp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wtp.c' object='librsyslog_la-wtp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-wtp.lo `test -f 'wtp.c' || echo '$(srcdir)/'`wtp.c
+
+librsyslog_la-wti.lo: wti.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-wti.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-wti.Tpo -c -o librsyslog_la-wti.lo `test -f 'wti.c' || echo '$(srcdir)/'`wti.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-wti.Tpo $(DEPDIR)/librsyslog_la-wti.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='wti.c' object='librsyslog_la-wti.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-wti.lo `test -f 'wti.c' || echo '$(srcdir)/'`wti.c
+
+librsyslog_la-queue.lo: queue.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-queue.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-queue.Tpo -c -o librsyslog_la-queue.lo `test -f 'queue.c' || echo '$(srcdir)/'`queue.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-queue.Tpo $(DEPDIR)/librsyslog_la-queue.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='queue.c' object='librsyslog_la-queue.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-queue.lo `test -f 'queue.c' || echo '$(srcdir)/'`queue.c
+
+librsyslog_la-ruleset.lo: ruleset.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-ruleset.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-ruleset.Tpo -c -o librsyslog_la-ruleset.lo `test -f 'ruleset.c' || echo '$(srcdir)/'`ruleset.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-ruleset.Tpo $(DEPDIR)/librsyslog_la-ruleset.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ruleset.c' object='librsyslog_la-ruleset.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-ruleset.lo `test -f 'ruleset.c' || echo '$(srcdir)/'`ruleset.c
+
+librsyslog_la-prop.lo: prop.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-prop.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-prop.Tpo -c -o librsyslog_la-prop.lo `test -f 'prop.c' || echo '$(srcdir)/'`prop.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-prop.Tpo $(DEPDIR)/librsyslog_la-prop.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='prop.c' object='librsyslog_la-prop.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-prop.lo `test -f 'prop.c' || echo '$(srcdir)/'`prop.c
+
+librsyslog_la-ratelimit.lo: ratelimit.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-ratelimit.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-ratelimit.Tpo -c -o librsyslog_la-ratelimit.lo `test -f 'ratelimit.c' || echo '$(srcdir)/'`ratelimit.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-ratelimit.Tpo $(DEPDIR)/librsyslog_la-ratelimit.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='ratelimit.c' object='librsyslog_la-ratelimit.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-ratelimit.lo `test -f 'ratelimit.c' || echo '$(srcdir)/'`ratelimit.c
+
+librsyslog_la-lookup.lo: lookup.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-lookup.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-lookup.Tpo -c -o librsyslog_la-lookup.lo `test -f 'lookup.c' || echo '$(srcdir)/'`lookup.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-lookup.Tpo $(DEPDIR)/librsyslog_la-lookup.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lookup.c' object='librsyslog_la-lookup.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-lookup.lo `test -f 'lookup.c' || echo '$(srcdir)/'`lookup.c
+
+librsyslog_la-cfsysline.lo: cfsysline.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-cfsysline.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-cfsysline.Tpo -c -o librsyslog_la-cfsysline.lo `test -f 'cfsysline.c' || echo '$(srcdir)/'`cfsysline.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-cfsysline.Tpo $(DEPDIR)/librsyslog_la-cfsysline.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='cfsysline.c' object='librsyslog_la-cfsysline.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-cfsysline.lo `test -f 'cfsysline.c' || echo '$(srcdir)/'`cfsysline.c
+
+../librsyslog_la-action.lo: ../action.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ../librsyslog_la-action.lo -MD -MP -MF ../$(DEPDIR)/librsyslog_la-action.Tpo -c -o ../librsyslog_la-action.lo `test -f '../action.c' || echo '$(srcdir)/'`../action.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ../$(DEPDIR)/librsyslog_la-action.Tpo ../$(DEPDIR)/librsyslog_la-action.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../action.c' object='../librsyslog_la-action.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../librsyslog_la-action.lo `test -f '../action.c' || echo '$(srcdir)/'`../action.c
+
+../librsyslog_la-threads.lo: ../threads.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ../librsyslog_la-threads.lo -MD -MP -MF ../$(DEPDIR)/librsyslog_la-threads.Tpo -c -o ../librsyslog_la-threads.lo `test -f '../threads.c' || echo '$(srcdir)/'`../threads.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ../$(DEPDIR)/librsyslog_la-threads.Tpo ../$(DEPDIR)/librsyslog_la-threads.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../threads.c' object='../librsyslog_la-threads.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../librsyslog_la-threads.lo `test -f '../threads.c' || echo '$(srcdir)/'`../threads.c
+
+../librsyslog_la-parse.lo: ../parse.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ../librsyslog_la-parse.lo -MD -MP -MF ../$(DEPDIR)/librsyslog_la-parse.Tpo -c -o ../librsyslog_la-parse.lo `test -f '../parse.c' || echo '$(srcdir)/'`../parse.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ../$(DEPDIR)/librsyslog_la-parse.Tpo ../$(DEPDIR)/librsyslog_la-parse.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../parse.c' object='../librsyslog_la-parse.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../librsyslog_la-parse.lo `test -f '../parse.c' || echo '$(srcdir)/'`../parse.c
+
+librsyslog_la-hashtable.lo: hashtable.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-hashtable.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-hashtable.Tpo -c -o librsyslog_la-hashtable.lo `test -f 'hashtable.c' || echo '$(srcdir)/'`hashtable.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-hashtable.Tpo $(DEPDIR)/librsyslog_la-hashtable.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hashtable.c' object='librsyslog_la-hashtable.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-hashtable.lo `test -f 'hashtable.c' || echo '$(srcdir)/'`hashtable.c
+
+librsyslog_la-hashtable_itr.lo: hashtable_itr.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-hashtable_itr.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-hashtable_itr.Tpo -c -o librsyslog_la-hashtable_itr.lo `test -f 'hashtable_itr.c' || echo '$(srcdir)/'`hashtable_itr.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-hashtable_itr.Tpo $(DEPDIR)/librsyslog_la-hashtable_itr.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='hashtable_itr.c' object='librsyslog_la-hashtable_itr.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-hashtable_itr.lo `test -f 'hashtable_itr.c' || echo '$(srcdir)/'`hashtable_itr.c
+
+../librsyslog_la-outchannel.lo: ../outchannel.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ../librsyslog_la-outchannel.lo -MD -MP -MF ../$(DEPDIR)/librsyslog_la-outchannel.Tpo -c -o ../librsyslog_la-outchannel.lo `test -f '../outchannel.c' || echo '$(srcdir)/'`../outchannel.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ../$(DEPDIR)/librsyslog_la-outchannel.Tpo ../$(DEPDIR)/librsyslog_la-outchannel.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../outchannel.c' object='../librsyslog_la-outchannel.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../librsyslog_la-outchannel.lo `test -f '../outchannel.c' || echo '$(srcdir)/'`../outchannel.c
+
+../librsyslog_la-template.lo: ../template.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT ../librsyslog_la-template.lo -MD -MP -MF ../$(DEPDIR)/librsyslog_la-template.Tpo -c -o ../librsyslog_la-template.lo `test -f '../template.c' || echo '$(srcdir)/'`../template.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) ../$(DEPDIR)/librsyslog_la-template.Tpo ../$(DEPDIR)/librsyslog_la-template.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='../template.c' object='../librsyslog_la-template.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o ../librsyslog_la-template.lo `test -f '../template.c' || echo '$(srcdir)/'`../template.c
+
+librsyslog_la-timezones.lo: timezones.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT librsyslog_la-timezones.lo -MD -MP -MF $(DEPDIR)/librsyslog_la-timezones.Tpo -c -o librsyslog_la-timezones.lo `test -f 'timezones.c' || echo '$(srcdir)/'`timezones.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/librsyslog_la-timezones.Tpo $(DEPDIR)/librsyslog_la-timezones.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='timezones.c' object='librsyslog_la-timezones.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(librsyslog_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o librsyslog_la-timezones.lo `test -f 'timezones.c' || echo '$(srcdir)/'`timezones.c
+
+lmcry_gcry_la-lmcry_gcry.lo: lmcry_gcry.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmcry_gcry_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmcry_gcry_la-lmcry_gcry.lo -MD -MP -MF $(DEPDIR)/lmcry_gcry_la-lmcry_gcry.Tpo -c -o lmcry_gcry_la-lmcry_gcry.lo `test -f 'lmcry_gcry.c' || echo '$(srcdir)/'`lmcry_gcry.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmcry_gcry_la-lmcry_gcry.Tpo $(DEPDIR)/lmcry_gcry_la-lmcry_gcry.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lmcry_gcry.c' object='lmcry_gcry_la-lmcry_gcry.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmcry_gcry_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmcry_gcry_la-lmcry_gcry.lo `test -f 'lmcry_gcry.c' || echo '$(srcdir)/'`lmcry_gcry.c
+
+lmgssutil_la-gss-misc.lo: gss-misc.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmgssutil_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmgssutil_la-gss-misc.lo -MD -MP -MF $(DEPDIR)/lmgssutil_la-gss-misc.Tpo -c -o lmgssutil_la-gss-misc.lo `test -f 'gss-misc.c' || echo '$(srcdir)/'`gss-misc.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmgssutil_la-gss-misc.Tpo $(DEPDIR)/lmgssutil_la-gss-misc.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='gss-misc.c' object='lmgssutil_la-gss-misc.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmgssutil_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmgssutil_la-gss-misc.lo `test -f 'gss-misc.c' || echo '$(srcdir)/'`gss-misc.c
+
+lmnet_la-net.lo: net.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnet_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnet_la-net.lo -MD -MP -MF $(DEPDIR)/lmnet_la-net.Tpo -c -o lmnet_la-net.lo `test -f 'net.c' || echo '$(srcdir)/'`net.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnet_la-net.Tpo $(DEPDIR)/lmnet_la-net.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='net.c' object='lmnet_la-net.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnet_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnet_la-net.lo `test -f 'net.c' || echo '$(srcdir)/'`net.c
+
+lmnetstrms_la-netstrms.lo: netstrms.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnetstrms_la-netstrms.lo -MD -MP -MF $(DEPDIR)/lmnetstrms_la-netstrms.Tpo -c -o lmnetstrms_la-netstrms.lo `test -f 'netstrms.c' || echo '$(srcdir)/'`netstrms.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnetstrms_la-netstrms.Tpo $(DEPDIR)/lmnetstrms_la-netstrms.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netstrms.c' object='lmnetstrms_la-netstrms.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnetstrms_la-netstrms.lo `test -f 'netstrms.c' || echo '$(srcdir)/'`netstrms.c
+
+lmnetstrms_la-netstrm.lo: netstrm.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnetstrms_la-netstrm.lo -MD -MP -MF $(DEPDIR)/lmnetstrms_la-netstrm.Tpo -c -o lmnetstrms_la-netstrm.lo `test -f 'netstrm.c' || echo '$(srcdir)/'`netstrm.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnetstrms_la-netstrm.Tpo $(DEPDIR)/lmnetstrms_la-netstrm.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='netstrm.c' object='lmnetstrms_la-netstrm.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnetstrms_la-netstrm.lo `test -f 'netstrm.c' || echo '$(srcdir)/'`netstrm.c
+
+lmnetstrms_la-nssel.lo: nssel.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnetstrms_la-nssel.lo -MD -MP -MF $(DEPDIR)/lmnetstrms_la-nssel.Tpo -c -o lmnetstrms_la-nssel.lo `test -f 'nssel.c' || echo '$(srcdir)/'`nssel.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnetstrms_la-nssel.Tpo $(DEPDIR)/lmnetstrms_la-nssel.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nssel.c' object='lmnetstrms_la-nssel.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnetstrms_la-nssel.lo `test -f 'nssel.c' || echo '$(srcdir)/'`nssel.c
+
+lmnetstrms_la-nspoll.lo: nspoll.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnetstrms_la-nspoll.lo -MD -MP -MF $(DEPDIR)/lmnetstrms_la-nspoll.Tpo -c -o lmnetstrms_la-nspoll.lo `test -f 'nspoll.c' || echo '$(srcdir)/'`nspoll.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnetstrms_la-nspoll.Tpo $(DEPDIR)/lmnetstrms_la-nspoll.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nspoll.c' object='lmnetstrms_la-nspoll.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnetstrms_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnetstrms_la-nspoll.lo `test -f 'nspoll.c' || echo '$(srcdir)/'`nspoll.c
+
+lmnsd_gtls_la-nsd_gtls.lo: nsd_gtls.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_gtls_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_gtls_la-nsd_gtls.lo -MD -MP -MF $(DEPDIR)/lmnsd_gtls_la-nsd_gtls.Tpo -c -o lmnsd_gtls_la-nsd_gtls.lo `test -f 'nsd_gtls.c' || echo '$(srcdir)/'`nsd_gtls.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_gtls_la-nsd_gtls.Tpo $(DEPDIR)/lmnsd_gtls_la-nsd_gtls.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsd_gtls.c' object='lmnsd_gtls_la-nsd_gtls.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_gtls_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_gtls_la-nsd_gtls.lo `test -f 'nsd_gtls.c' || echo '$(srcdir)/'`nsd_gtls.c
+
+lmnsd_gtls_la-nsdsel_gtls.lo: nsdsel_gtls.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_gtls_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_gtls_la-nsdsel_gtls.lo -MD -MP -MF $(DEPDIR)/lmnsd_gtls_la-nsdsel_gtls.Tpo -c -o lmnsd_gtls_la-nsdsel_gtls.lo `test -f 'nsdsel_gtls.c' || echo '$(srcdir)/'`nsdsel_gtls.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_gtls_la-nsdsel_gtls.Tpo $(DEPDIR)/lmnsd_gtls_la-nsdsel_gtls.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsdsel_gtls.c' object='lmnsd_gtls_la-nsdsel_gtls.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_gtls_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_gtls_la-nsdsel_gtls.lo `test -f 'nsdsel_gtls.c' || echo '$(srcdir)/'`nsdsel_gtls.c
+
+lmnsd_ossl_la-net_ossl.lo: net_ossl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ossl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_ossl_la-net_ossl.lo -MD -MP -MF $(DEPDIR)/lmnsd_ossl_la-net_ossl.Tpo -c -o lmnsd_ossl_la-net_ossl.lo `test -f 'net_ossl.c' || echo '$(srcdir)/'`net_ossl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_ossl_la-net_ossl.Tpo $(DEPDIR)/lmnsd_ossl_la-net_ossl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='net_ossl.c' object='lmnsd_ossl_la-net_ossl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ossl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_ossl_la-net_ossl.lo `test -f 'net_ossl.c' || echo '$(srcdir)/'`net_ossl.c
+
+lmnsd_ossl_la-nsd_ossl.lo: nsd_ossl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ossl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_ossl_la-nsd_ossl.lo -MD -MP -MF $(DEPDIR)/lmnsd_ossl_la-nsd_ossl.Tpo -c -o lmnsd_ossl_la-nsd_ossl.lo `test -f 'nsd_ossl.c' || echo '$(srcdir)/'`nsd_ossl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_ossl_la-nsd_ossl.Tpo $(DEPDIR)/lmnsd_ossl_la-nsd_ossl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsd_ossl.c' object='lmnsd_ossl_la-nsd_ossl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ossl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_ossl_la-nsd_ossl.lo `test -f 'nsd_ossl.c' || echo '$(srcdir)/'`nsd_ossl.c
+
+lmnsd_ossl_la-nsdsel_ossl.lo: nsdsel_ossl.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ossl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_ossl_la-nsdsel_ossl.lo -MD -MP -MF $(DEPDIR)/lmnsd_ossl_la-nsdsel_ossl.Tpo -c -o lmnsd_ossl_la-nsdsel_ossl.lo `test -f 'nsdsel_ossl.c' || echo '$(srcdir)/'`nsdsel_ossl.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_ossl_la-nsdsel_ossl.Tpo $(DEPDIR)/lmnsd_ossl_la-nsdsel_ossl.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsdsel_ossl.c' object='lmnsd_ossl_la-nsdsel_ossl.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ossl_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_ossl_la-nsdsel_ossl.lo `test -f 'nsdsel_ossl.c' || echo '$(srcdir)/'`nsdsel_ossl.c
+
+lmnsd_ptcp_la-nsd_ptcp.lo: nsd_ptcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ptcp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_ptcp_la-nsd_ptcp.lo -MD -MP -MF $(DEPDIR)/lmnsd_ptcp_la-nsd_ptcp.Tpo -c -o lmnsd_ptcp_la-nsd_ptcp.lo `test -f 'nsd_ptcp.c' || echo '$(srcdir)/'`nsd_ptcp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_ptcp_la-nsd_ptcp.Tpo $(DEPDIR)/lmnsd_ptcp_la-nsd_ptcp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsd_ptcp.c' object='lmnsd_ptcp_la-nsd_ptcp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ptcp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_ptcp_la-nsd_ptcp.lo `test -f 'nsd_ptcp.c' || echo '$(srcdir)/'`nsd_ptcp.c
+
+lmnsd_ptcp_la-nsdsel_ptcp.lo: nsdsel_ptcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ptcp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_ptcp_la-nsdsel_ptcp.lo -MD -MP -MF $(DEPDIR)/lmnsd_ptcp_la-nsdsel_ptcp.Tpo -c -o lmnsd_ptcp_la-nsdsel_ptcp.lo `test -f 'nsdsel_ptcp.c' || echo '$(srcdir)/'`nsdsel_ptcp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_ptcp_la-nsdsel_ptcp.Tpo $(DEPDIR)/lmnsd_ptcp_la-nsdsel_ptcp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsdsel_ptcp.c' object='lmnsd_ptcp_la-nsdsel_ptcp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ptcp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_ptcp_la-nsdsel_ptcp.lo `test -f 'nsdsel_ptcp.c' || echo '$(srcdir)/'`nsdsel_ptcp.c
+
+lmnsd_ptcp_la-nsdpoll_ptcp.lo: nsdpoll_ptcp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ptcp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmnsd_ptcp_la-nsdpoll_ptcp.lo -MD -MP -MF $(DEPDIR)/lmnsd_ptcp_la-nsdpoll_ptcp.Tpo -c -o lmnsd_ptcp_la-nsdpoll_ptcp.lo `test -f 'nsdpoll_ptcp.c' || echo '$(srcdir)/'`nsdpoll_ptcp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmnsd_ptcp_la-nsdpoll_ptcp.Tpo $(DEPDIR)/lmnsd_ptcp_la-nsdpoll_ptcp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='nsdpoll_ptcp.c' object='lmnsd_ptcp_la-nsdpoll_ptcp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmnsd_ptcp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmnsd_ptcp_la-nsdpoll_ptcp.lo `test -f 'nsdpoll_ptcp.c' || echo '$(srcdir)/'`nsdpoll_ptcp.c
+
+lmregexp_la-regexp.lo: regexp.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmregexp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmregexp_la-regexp.lo -MD -MP -MF $(DEPDIR)/lmregexp_la-regexp.Tpo -c -o lmregexp_la-regexp.lo `test -f 'regexp.c' || echo '$(srcdir)/'`regexp.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmregexp_la-regexp.Tpo $(DEPDIR)/lmregexp_la-regexp.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='regexp.c' object='lmregexp_la-regexp.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmregexp_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmregexp_la-regexp.lo `test -f 'regexp.c' || echo '$(srcdir)/'`regexp.c
+
+lmsig_ksi_ls12_la-lmsig_ksi-ls12.lo: lmsig_ksi-ls12.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmsig_ksi_ls12_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmsig_ksi_ls12_la-lmsig_ksi-ls12.lo -MD -MP -MF $(DEPDIR)/lmsig_ksi_ls12_la-lmsig_ksi-ls12.Tpo -c -o lmsig_ksi_ls12_la-lmsig_ksi-ls12.lo `test -f 'lmsig_ksi-ls12.c' || echo '$(srcdir)/'`lmsig_ksi-ls12.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmsig_ksi_ls12_la-lmsig_ksi-ls12.Tpo $(DEPDIR)/lmsig_ksi_ls12_la-lmsig_ksi-ls12.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lmsig_ksi-ls12.c' object='lmsig_ksi_ls12_la-lmsig_ksi-ls12.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmsig_ksi_ls12_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmsig_ksi_ls12_la-lmsig_ksi-ls12.lo `test -f 'lmsig_ksi-ls12.c' || echo '$(srcdir)/'`lmsig_ksi-ls12.c
+
+lmsig_ksi_ls12_la-lib_ksils12.lo: lib_ksils12.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmsig_ksi_ls12_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmsig_ksi_ls12_la-lib_ksils12.lo -MD -MP -MF $(DEPDIR)/lmsig_ksi_ls12_la-lib_ksils12.Tpo -c -o lmsig_ksi_ls12_la-lib_ksils12.lo `test -f 'lib_ksils12.c' || echo '$(srcdir)/'`lib_ksils12.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmsig_ksi_ls12_la-lib_ksils12.Tpo $(DEPDIR)/lmsig_ksi_ls12_la-lib_ksils12.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib_ksils12.c' object='lmsig_ksi_ls12_la-lib_ksils12.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmsig_ksi_ls12_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmsig_ksi_ls12_la-lib_ksils12.lo `test -f 'lib_ksils12.c' || echo '$(srcdir)/'`lib_ksils12.c
+
+lmsig_ksi_ls12_la-lib_ksi_queue.lo: lib_ksi_queue.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmsig_ksi_ls12_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmsig_ksi_ls12_la-lib_ksi_queue.lo -MD -MP -MF $(DEPDIR)/lmsig_ksi_ls12_la-lib_ksi_queue.Tpo -c -o lmsig_ksi_ls12_la-lib_ksi_queue.lo `test -f 'lib_ksi_queue.c' || echo '$(srcdir)/'`lib_ksi_queue.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmsig_ksi_ls12_la-lib_ksi_queue.Tpo $(DEPDIR)/lmsig_ksi_ls12_la-lib_ksi_queue.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='lib_ksi_queue.c' object='lmsig_ksi_ls12_la-lib_ksi_queue.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmsig_ksi_ls12_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmsig_ksi_ls12_la-lib_ksi_queue.lo `test -f 'lib_ksi_queue.c' || echo '$(srcdir)/'`lib_ksi_queue.c
+
+lmtcpclt_la-tcpclt.lo: tcpclt.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmtcpclt_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmtcpclt_la-tcpclt.lo -MD -MP -MF $(DEPDIR)/lmtcpclt_la-tcpclt.Tpo -c -o lmtcpclt_la-tcpclt.lo `test -f 'tcpclt.c' || echo '$(srcdir)/'`tcpclt.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmtcpclt_la-tcpclt.Tpo $(DEPDIR)/lmtcpclt_la-tcpclt.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tcpclt.c' object='lmtcpclt_la-tcpclt.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmtcpclt_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmtcpclt_la-tcpclt.lo `test -f 'tcpclt.c' || echo '$(srcdir)/'`tcpclt.c
+
+lmtcpsrv_la-tcps_sess.lo: tcps_sess.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmtcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmtcpsrv_la-tcps_sess.lo -MD -MP -MF $(DEPDIR)/lmtcpsrv_la-tcps_sess.Tpo -c -o lmtcpsrv_la-tcps_sess.lo `test -f 'tcps_sess.c' || echo '$(srcdir)/'`tcps_sess.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmtcpsrv_la-tcps_sess.Tpo $(DEPDIR)/lmtcpsrv_la-tcps_sess.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tcps_sess.c' object='lmtcpsrv_la-tcps_sess.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmtcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmtcpsrv_la-tcps_sess.lo `test -f 'tcps_sess.c' || echo '$(srcdir)/'`tcps_sess.c
+
+lmtcpsrv_la-tcpsrv.lo: tcpsrv.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmtcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmtcpsrv_la-tcpsrv.lo -MD -MP -MF $(DEPDIR)/lmtcpsrv_la-tcpsrv.Tpo -c -o lmtcpsrv_la-tcpsrv.lo `test -f 'tcpsrv.c' || echo '$(srcdir)/'`tcpsrv.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmtcpsrv_la-tcpsrv.Tpo $(DEPDIR)/lmtcpsrv_la-tcpsrv.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='tcpsrv.c' object='lmtcpsrv_la-tcpsrv.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmtcpsrv_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmtcpsrv_la-tcpsrv.lo `test -f 'tcpsrv.c' || echo '$(srcdir)/'`tcpsrv.c
+
+lmzlibw_la-zlibw.lo: zlibw.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmzlibw_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmzlibw_la-zlibw.lo -MD -MP -MF $(DEPDIR)/lmzlibw_la-zlibw.Tpo -c -o lmzlibw_la-zlibw.lo `test -f 'zlibw.c' || echo '$(srcdir)/'`zlibw.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmzlibw_la-zlibw.Tpo $(DEPDIR)/lmzlibw_la-zlibw.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zlibw.c' object='lmzlibw_la-zlibw.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmzlibw_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmzlibw_la-zlibw.lo `test -f 'zlibw.c' || echo '$(srcdir)/'`zlibw.c
+
+lmzstdw_la-zstdw.lo: zstdw.c
+@am__fastdepCC_TRUE@ $(AM_V_CC)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmzstdw_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -MT lmzstdw_la-zstdw.lo -MD -MP -MF $(DEPDIR)/lmzstdw_la-zstdw.Tpo -c -o lmzstdw_la-zstdw.lo `test -f 'zstdw.c' || echo '$(srcdir)/'`zstdw.c
+@am__fastdepCC_TRUE@ $(AM_V_at)$(am__mv) $(DEPDIR)/lmzstdw_la-zstdw.Tpo $(DEPDIR)/lmzstdw_la-zstdw.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='zstdw.c' object='lmzstdw_la-zstdw.lo' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(lmzstdw_la_CPPFLAGS) $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) -c -o lmzstdw_la-zstdw.lo `test -f 'zstdw.c' || echo '$(srcdir)/'`zstdw.c
+
+mostlyclean-libtool:
+ -rm -f *.lo
+
+clean-libtool:
+ -rm -rf .libs _libs
+ -rm -rf ../.libs ../_libs
+
+ID: $(am__tagged_files)
+ $(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ set x; \
+ here=`pwd`; \
+ $(am__define_uniq_tagged_files); \
+ shift; \
+ if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+ test -n "$$unique" || unique=$$empty_fix; \
+ if test $$# -gt 0; then \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ "$$@" $$unique; \
+ else \
+ $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+ $$unique; \
+ fi; \
+ fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+ $(am__define_uniq_tagged_files); \
+ test -z "$(CTAGS_ARGS)$$unique" \
+ || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+ $$unique
+
+GTAGS:
+ here=`$(am__cd) $(top_builddir) && pwd` \
+ && $(am__cd) $(top_srcdir) \
+ && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+ list='$(am__tagged_files)'; \
+ case "$(srcdir)" in \
+ [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+ *) sdir=$(subdir)/$(srcdir) ;; \
+ esac; \
+ for i in $$list; do \
+ if test -f "$$i"; then \
+ echo "$(subdir)/$$i"; \
+ else \
+ echo "$$sdir/$$i"; \
+ fi; \
+ done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+ -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(DISTFILES)
+ @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+ list='$(DISTFILES)'; \
+ dist_files=`for file in $$list; do echo $$file; done | \
+ sed -e "s|^$$srcdirstrip/||;t" \
+ -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+ case $$dist_files in \
+ */*) $(MKDIR_P) `echo "$$dist_files" | \
+ sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+ sort -u` ;; \
+ esac; \
+ for file in $$dist_files; do \
+ if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+ if test -d $$d/$$file; then \
+ dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+ if test -d "$(distdir)/$$file"; then \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+ cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+ find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+ fi; \
+ cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+ else \
+ test -f "$(distdir)/$$file" \
+ || cp -p $$d/$$file "$(distdir)/$$file" \
+ || exit 1; \
+ fi; \
+ done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS) $(LIBRARIES) $(LTLIBRARIES)
+installdirs:
+ for dir in "$(DESTDIR)$(sbindir)" "$(DESTDIR)$(pkglibdir)"; do \
+ test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+ done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+ @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+ if test -z '$(STRIP)'; then \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ install; \
+ else \
+ $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+ install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+ "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+ fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+ -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+ -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+ -rm -f ../$(DEPDIR)/$(am__dirstamp)
+ -rm -f ../$(am__dirstamp)
+
+maintainer-clean-generic:
+ @echo "This command is intended for maintainers to use"
+ @echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \
+ clean-noinstLTLIBRARIES clean-pkglibLTLIBRARIES \
+ clean-sbinPROGRAMS mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ../$(DEPDIR)/librsyslog_la-action.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-outchannel.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-parse.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-template.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-threads.Plo
+ -rm -f ./$(DEPDIR)/libgcry_la-libgcry.Plo
+ -rm -f ./$(DEPDIR)/libgcry_la-libgcry_common.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-cfsysline.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-conf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-datetime.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-debug.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-dnscache.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-dynstats.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-errmsg.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-glbl.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-hashtable.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-hashtable_itr.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-janitor.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-linkedlist.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-lookup.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-modules.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-msg.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-obj.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-objomsr.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-operatingstate.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-parser.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-perctile_ringbuf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-perctile_stats.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-prop.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-queue.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-ratelimit.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-rsconf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-rsyslog.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-ruleset.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-srutils.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-statsobj.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-stream.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-strgen.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-stringbuf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-timezones.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-var.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-wti.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-wtp.Plo
+ -rm -f ./$(DEPDIR)/lmcry_gcry_la-lmcry_gcry.Plo
+ -rm -f ./$(DEPDIR)/lmgssutil_la-gss-misc.Plo
+ -rm -f ./$(DEPDIR)/lmnet_la-net.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-netstrm.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-netstrms.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-nspoll.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-nssel.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_gtls_la-nsd_gtls.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_gtls_la-nsdsel_gtls.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ossl_la-net_ossl.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ossl_la-nsd_ossl.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ossl_la-nsdsel_ossl.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ptcp_la-nsd_ptcp.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ptcp_la-nsdpoll_ptcp.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ptcp_la-nsdsel_ptcp.Plo
+ -rm -f ./$(DEPDIR)/lmregexp_la-regexp.Plo
+ -rm -f ./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksi_queue.Plo
+ -rm -f ./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksils12.Plo
+ -rm -f ./$(DEPDIR)/lmsig_ksi_ls12_la-lmsig_ksi-ls12.Plo
+ -rm -f ./$(DEPDIR)/lmtcpclt_la-tcpclt.Plo
+ -rm -f ./$(DEPDIR)/lmtcpsrv_la-tcps_sess.Plo
+ -rm -f ./$(DEPDIR)/lmtcpsrv_la-tcpsrv.Plo
+ -rm -f ./$(DEPDIR)/lmzlibw_la-zlibw.Plo
+ -rm -f ./$(DEPDIR)/lmzstdw_la-zstdw.Plo
+ -rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+ distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-pkglibLTLIBRARIES install-sbinPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+ -rm -f ../$(DEPDIR)/librsyslog_la-action.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-outchannel.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-parse.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-template.Plo
+ -rm -f ../$(DEPDIR)/librsyslog_la-threads.Plo
+ -rm -f ./$(DEPDIR)/libgcry_la-libgcry.Plo
+ -rm -f ./$(DEPDIR)/libgcry_la-libgcry_common.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-cfsysline.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-conf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-datetime.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-debug.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-dnscache.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-dynstats.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-errmsg.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-glbl.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-hashtable.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-hashtable_itr.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-janitor.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-linkedlist.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-lookup.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-modules.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-msg.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-obj.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-objomsr.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-operatingstate.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-parser.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-perctile_ringbuf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-perctile_stats.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-prop.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-queue.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-ratelimit.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-rsconf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-rsyslog.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-ruleset.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-srutils.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-statsobj.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-stream.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-strgen.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-stringbuf.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-timezones.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-var.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-wti.Plo
+ -rm -f ./$(DEPDIR)/librsyslog_la-wtp.Plo
+ -rm -f ./$(DEPDIR)/lmcry_gcry_la-lmcry_gcry.Plo
+ -rm -f ./$(DEPDIR)/lmgssutil_la-gss-misc.Plo
+ -rm -f ./$(DEPDIR)/lmnet_la-net.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-netstrm.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-netstrms.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-nspoll.Plo
+ -rm -f ./$(DEPDIR)/lmnetstrms_la-nssel.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_gtls_la-nsd_gtls.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_gtls_la-nsdsel_gtls.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ossl_la-net_ossl.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ossl_la-nsd_ossl.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ossl_la-nsdsel_ossl.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ptcp_la-nsd_ptcp.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ptcp_la-nsdpoll_ptcp.Plo
+ -rm -f ./$(DEPDIR)/lmnsd_ptcp_la-nsdsel_ptcp.Plo
+ -rm -f ./$(DEPDIR)/lmregexp_la-regexp.Plo
+ -rm -f ./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksi_queue.Plo
+ -rm -f ./$(DEPDIR)/lmsig_ksi_ls12_la-lib_ksils12.Plo
+ -rm -f ./$(DEPDIR)/lmsig_ksi_ls12_la-lmsig_ksi-ls12.Plo
+ -rm -f ./$(DEPDIR)/lmtcpclt_la-tcpclt.Plo
+ -rm -f ./$(DEPDIR)/lmtcpsrv_la-tcps_sess.Plo
+ -rm -f ./$(DEPDIR)/lmtcpsrv_la-tcpsrv.Plo
+ -rm -f ./$(DEPDIR)/lmzlibw_la-zlibw.Plo
+ -rm -f ./$(DEPDIR)/lmzstdw_la-zstdw.Plo
+ -rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+ mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-pkglibLTLIBRARIES uninstall-sbinPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \
+ clean-generic clean-libtool clean-noinstLIBRARIES \
+ clean-noinstLTLIBRARIES clean-pkglibLTLIBRARIES \
+ clean-sbinPROGRAMS cscopelist-am ctags ctags-am distclean \
+ distclean-compile distclean-generic distclean-libtool \
+ distclean-tags distdir dvi dvi-am html html-am info info-am \
+ install install-am install-data install-data-am install-dvi \
+ install-dvi-am install-exec install-exec-am install-html \
+ install-html-am install-info install-info-am install-man \
+ install-pdf install-pdf-am install-pkglibLTLIBRARIES \
+ install-ps install-ps-am install-sbinPROGRAMS install-strip \
+ installcheck installcheck-am installdirs maintainer-clean \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am \
+ uninstall-pkglibLTLIBRARIES uninstall-sbinPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/runtime/atomic.h b/runtime/atomic.h
new file mode 100644
index 0000000..06af1dc
--- /dev/null
+++ b/runtime/atomic.h
@@ -0,0 +1,265 @@
+/* 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_SUB_unsigned(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) ((int) __sync_fetch_and_and(data, 0xffffffff))
+# define ATOMIC_FETCH_32BIT_unsigned(data, phlpmut) ((int) __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_OR_INT_TO_INT(data, phlpmut, 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))
+# define PREFER_FETCH_32BIT(data) ((unsigned) __sync_fetch_and_and(&(data), 0xffffffff))
+# define PREFER_STORE_0_TO_INT(data) __sync_fetch_and_and(data, 0)
+# define PREFER_STORE_1_TO_INT(data) __sync_fetch_and_or(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); \
+ }
+
+# define ATOMIC_OR_INT_TO_INT(data, hlpmut, val) { \
+ pthread_mutex_lock(hlpmut); \
+ *(data) = val; \
+ 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 int
+ ATOMIC_FETCH_32BIT_unsigned(unsigned *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);
+ }
+
+ static inline void
+ ATOMIC_SUB_unsigned(unsigned *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)
+# define PREFER_FETCH_32BIT(data) ((unsigned) (data))
+# define PREFER_STORE_0_TO_INT(data) (*(data) = 0)
+# define PREFER_STORE_1_TO_INT(data) (*(data) = 1)
+
+#endif
+
+/* we need to handle 64bit atomics seperately as some platforms have
+ * 32 bit atomics, but not 64 bit ones... -- rgerhards, 2010-12-01
+ */
+#ifdef HAVE_ATOMIC_BUILTINS64
+# define ATOMIC_INC_uint64(data, phlpmut) ((void) __sync_fetch_and_add(data, 1))
+# define ATOMIC_ADD_uint64(data, phlpmut, value) ((void) __sync_fetch_and_add(data, value))
+# define ATOMIC_DEC_uint64(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_ADD_uint64(data, phlpmut, value) { \
+ pthread_mutex_lock(phlpmut); \
+ *data += value; \
+ 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_BUILTINS64 */
+
+#endif /* #ifndef INCLUDED_ATOMIC_H */
diff --git a/runtime/batch.h b/runtime/batch.h
new file mode 100644
index 0000000..c1bef80
--- /dev/null
+++ b/runtime/batch.h
@@ -0,0 +1,129 @@
+/* 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-2013 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 <stdlib.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 {
+ smsg_t *pMsg;
+};
+
+/* 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! */
+ qDeqID deqID; /* ID of dequeue operation that generated this batch */
+ 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!).
+ */
+};
+
+
+/* get number of msgs for this batch */
+#define batchNumMsgs(pBatch) ((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 __attribute__((unused))
+batchSetElemState(batch_t * const pBatch, const int i, const 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
+ */
+#define batchIsValidElem(pBatch, i) ((pBatch)->eltState[(i)] != BATCH_STATE_DISC)
+
+
+/* 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 __attribute__((unused))
+batchFree(batch_t * const pBatch) {
+ 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 __attribute__((unused))
+batchInit(batch_t *const pBatch, const int maxElem)
+{
+ DEFiRet;
+ 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)));
+finalize_it:
+ RETiRet;
+}
+
+#endif /* #ifndef BATCH_H_INCLUDED */
diff --git a/runtime/cfsysline.c b/runtime/cfsysline.c
new file mode 100644
index 0000000..34b1cd7
--- /dev/null
+++ b/runtime/cfsysline.c
@@ -0,0 +1,1081 @@
+/* cfsysline.c
+ * Implementation of the configuration system line object.
+ *
+ * File begun on 2007-07-30 by RGerhards
+ *
+ * Copyright (C) 2007-2020 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 <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <pwd.h>
+#include <grp.h>
+
+#include "rsyslog.h"
+#include "cfsysline.h"
+#include "obj.h"
+#include "conf.h"
+#include "errmsg.h"
+#include "srUtils.h"
+#include "unicode-helper.h"
+#include "rsconf.h"
+#include "parserif.h"
+
+
+/* static data */
+DEFobjCurrIf(obj)
+
+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') {
+ 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;
+ 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*, int64), 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;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ CHKiRet(doGetSize(pp, NULL,&i));
+ p = *pp;
+ if(i > 2147483648ll) { /*2^31*/
+ LogError(0, RS_RET_INVALID_VALUE,
+ "value %lld too large for integer argument.", i);
+ 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;
+ 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') ) ) {
+ LogError(0, RS_RET_INVALID_VALUE, "value must be octal (e.g 0644).");
+ 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), ' ') != 0) {
+ 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 {
+ 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), ' ') != 0) {
+ if(loadConf->globals.abortOnIDResolutionFail) {
+ fprintf(stderr, "could not extract group name: %s\n", (char*)szName);
+ exit(1); /* good exit */
+ } else {
+ LogError(0, RS_RET_NOT_FOUND, "could not extract group name");
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+ }
+
+ do {
+ char *p;
+
+ /* Increase bufsize and try again.*/
+ bufSize *= 2;
+ CHKmalloc(p = realloc(stringBuf, bufSize));
+ stringBuf = p;
+ err = getgrnam_r((char*)szName, &gBuf, stringBuf, bufSize, &pgBuf);
+ } while((pgBuf == NULL) && (err == ERANGE));
+
+ if(pgBuf == NULL) {
+ if (err != 0) {
+ LogError(err, RS_RET_NOT_FOUND, "Query for group '%s' resulted in an error",
+ szName);
+ } else {
+ LogError(0, RS_RET_NOT_FOUND, "ID for group '%s' could not be found", szName);
+ }
+ iRet = RS_RET_NOT_FOUND;
+ if(loadConf->globals.abortOnIDResolutionFail) {
+ fprintf(stderr, "ID for group '%s' could not be found or error\n", szName);
+ exit(1); /* good exit */
+ }
+ } 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), ' ') != 0) {
+ if(loadConf->globals.abortOnIDResolutionFail) {
+ fprintf(stderr, "could not extract user name: %s\n", (char*)szName);
+ exit(1); /* good exit */
+ } else {
+ 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) {
+ if(loadConf->globals.abortOnIDResolutionFail) {
+ fprintf(stderr, "ID for user '%s' could not be found or error\n", (char*)szName);
+ exit(1); /* good exit */
+ } else {
+ 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++));
+ }
+ 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 = NULL;
+ uchar *pNewVal;
+
+ assert(pp != NULL);
+ assert(*pp != NULL);
+
+ CHKiRet(getWord(pp, &pStrB));
+ CHKiRet(cstrConvSzStrAndDestruct(&pStrB, &pNewVal, 0));
+
+ 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(cstrGetSzStrNoNULL(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)
+{
+ parser_warnmsg("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
+ */
+static 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;
+ /* some non-legacy handler (used in v6+ solely) */
+ case eCmdHdlrInvalid:
+ case eCmdHdlrNonNegInt:
+ case eCmdHdlrPositiveInt:
+ case eCmdHdlrString:
+ case eCmdHdlrArray:
+ case eCmdHdlrQueueType:
+ default:
+ dbgprintf("error: command handler type %d not implemented in legacy system\n", pThis->eType);
+ 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
+ * parameters 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(const 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(const 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 */
+ CHKiRet(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);
+ if(iRet == RS_RET_NOT_FOUND) {
+ /* It is not considered an error if a module had no
+ hanlers registered. */
+ iRet = RS_RET_OK;
+ }
+
+ 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 */
+
+ iRet = llFind(&llCmdList, (void *) pCmdName, (void*) &pCmd);
+
+ if(iRet == RS_RET_NOT_FOUND) {
+ 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)) {
+ 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;
+
+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("System 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(void)
+{
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+
+ CHKiRet(llInit(&llCmdList, cslcDestruct, cslcKeyDestruct, strcasecmp));
+
+finalize_it:
+ RETiRet;
+}
diff --git a/runtime/cfsysline.h b/runtime/cfsysline.h
new file mode 100644
index 0000000..44e85ca
--- /dev/null
+++ b/runtime/cfsysline.h
@@ -0,0 +1,62 @@
+/* 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(const uchar *pCmdName, int bChainingPermitted, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(),
+ void *pData, void *pOwnerCookie);
+rsRetVal regCfSysLineHdlr2(const 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 0000000..5eee37e
--- /dev/null
+++ b/runtime/conf.c
@@ -0,0 +1,640 @@
+/* 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-2016 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 configuration 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(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. */
+static 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), ' ') != 0) {
+ 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 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.
+ */
+static rsRetVal
+doNameLine(uchar **pp, void* pVal)
+{
+ DEFiRet;
+ uchar *p;
+ enum eDirective eDir;
+ char szName[128];
+
+ assert(pp != NULL);
+ p = *pp;
+ assert(p != NULL);
+
+ PRAGMA_DIAGNOSTIC_PUSH
+ PRAGMA_IGNORE_Wvoid_pointer_to_enum_cast;
+ /* this time, pVal actually is NOT a pointer! It is save to case, as
+ * the enum was written to it, so there can be no loss of bits (ptr is larger).
+ */
+ eDir = (enum eDirective) pVal;
+ PRAGMA_DIAGNOSTIC_POP
+
+ if(getSubString(&p, szName, sizeof(szName), ',') != 0) {
+ 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
+ */
+static rsRetVal
+cfsysline(uchar *p)
+{
+ DEFiRet;
+ uchar szCmd[64];
+
+ assert(p != NULL);
+ errno = 0;
+ if(getSubString(&p, (char*) szCmd, sizeof(szCmd), ' ') != 0) {
+ 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 */
+ 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 = NULL;
+ 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 != '#') {
+ 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;
+ }
+ cstrFinalize(pStrB);
+ CHKiRet(cstrConvSzStrAndDestruct(&pStrB, &tplName, 0));
+ }
+
+ CHKiRet(OMSRsetEntry(pOMSR, iEntry, tplName, iTplOpts));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ free(tplName);
+ if(pStrB != NULL)
+ cstrDestruct(&pStrB);
+ }
+
+ *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; /* this MUST be int, as -1 is used to convey an error state */
+ 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);
+ 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);
+ 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;
+}
+
+
+/* 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) {
+ if((iRet = addAction(&pAction, pMod, pModData, pOMSR, NULL, NULL)) == RS_RET_OK) {
+ /* here check if the module is compatible with select features
+ * (currently, we have no such features!) */
+ 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 occurred 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(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(net, LM_NET_FILENAME));
+ 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)
diff --git a/runtime/conf.h b/runtime/conf.h
new file mode 100644
index 0000000..96c5711
--- /dev/null
+++ b/runtime/conf.h
@@ -0,0 +1,73 @@
+/* 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 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 0000000..9829b1b
--- /dev/null
+++ b/runtime/cryprov.h
@@ -0,0 +1,48 @@
+/* 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
+
+/* 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 0000000..22dc7d0
--- /dev/null
+++ b/runtime/datetime.c
@@ -0,0 +1,1413 @@
+/* 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-2016 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>
+#include <string.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"
+#include "rsconf.h"
+#include "timezones.h"
+
+/* static data */
+DEFobjStaticHelpers
+
+/* the following table of ten powers saves us some computation */
+static const int tenPowers[6] = { 1, 10, 100, 1000, 10000, 100000 };
+
+/* the following table saves us from computing an additional date to get
+ * the ordinal day of the year - at least from 1967-2099
+ * Note: non-2038+ compliant systems (Solaris) will generate compiler
+ * warnings on the post 2038-rollover years.
+ */
+static const int yearInSec_startYear = 1967;
+/* for x in $(seq 1967 2099) ; do
+ * printf %s', ' $(date --date="Dec 31 ${x} UTC 23:59:59" +%s)
+ * done |fold -w 70 -s */
+static const long long yearInSecs[] = {
+ -63158401, -31536001, -1, 31535999, 63071999, 94694399, 126230399,
+ 157766399, 189302399, 220924799, 252460799, 283996799, 315532799,
+ 347155199, 378691199, 410227199, 441763199, 473385599, 504921599,
+ 536457599, 567993599, 599615999, 631151999, 662687999, 694223999,
+ 725846399, 757382399, 788918399, 820454399, 852076799, 883612799,
+ 915148799, 946684799, 978307199, 1009843199, 1041379199, 1072915199,
+ 1104537599, 1136073599, 1167609599, 1199145599, 1230767999,
+ 1262303999, 1293839999, 1325375999, 1356998399, 1388534399,
+ 1420070399, 1451606399, 1483228799, 1514764799, 1546300799,
+ 1577836799, 1609459199, 1640995199, 1672531199, 1704067199,
+ 1735689599, 1767225599, 1798761599, 1830297599, 1861919999,
+ 1893455999, 1924991999, 1956527999, 1988150399, 2019686399,
+ 2051222399, 2082758399, 2114380799, 2145916799, 2177452799,
+ 2208988799, 2240611199, 2272147199, 2303683199, 2335219199,
+ 2366841599, 2398377599, 2429913599, 2461449599, 2493071999,
+ 2524607999, 2556143999, 2587679999, 2619302399, 2650838399,
+ 2682374399, 2713910399, 2745532799, 2777068799, 2808604799,
+ 2840140799, 2871763199, 2903299199, 2934835199, 2966371199,
+ 2997993599, 3029529599, 3061065599, 3092601599, 3124223999,
+ 3155759999, 3187295999, 3218831999, 3250454399, 3281990399,
+ 3313526399, 3345062399, 3376684799, 3408220799, 3439756799,
+ 3471292799, 3502915199, 3534451199, 3565987199, 3597523199,
+ 3629145599, 3660681599, 3692217599, 3723753599, 3755375999,
+ 3786911999, 3818447999, 3849983999, 3881606399, 3913142399,
+ 3944678399, 3976214399, 4007836799, 4039372799, 4070908799,
+ 4102444799};
+
+/* note ramge is 1969 -> 2100 because it needs to access previous/next year */
+/* for x in $(seq 1969 2100) ; do
+ * printf %s', ' $(date --date="Dec 28 ${x} UTC 12:00:00" +%V)
+ * done | fold -w 70 -s */
+static const int weeksInYear[] = {
+ 52, 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52, 53, 52, 52, 52, 52,
+ 52, 53, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52,
+ 52, 53, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52,
+ 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52,
+ 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52,
+ 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52,
+ 53, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52,
+ 53, 52, 52, 52, 52, 53, 52, 52, 52, 52, 52, 53, 52,
+};
+
+static const char* monthNames[12] = {
+ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
+};
+
+/* ------------------------------ methods ------------------------------ */
+
+
+/**
+ * Convert struct timeval to syslog_time
+ */
+static void
+timeval2syslogTime(struct timeval *tp, struct syslogTime *t, const int inUTC)
+{
+ struct tm *tm;
+ struct tm tmBuf;
+ long lBias;
+ time_t secs;
+
+#if defined(__hpux)
+ struct timezone tz;
+# endif
+ secs = tp->tv_sec;
+ if(inUTC)
+ tm = gmtime_r(&secs, &tmBuf);
+ else
+ tm = localtime_r(&secs, &tmBuf);
+
+ t->year = tm->tm_year + 1900;
+ t->month = tm->tm_mon + 1;
+ t->day = tm->tm_mday;
+ t->wday = tm->tm_wday;
+ t->hour = tm->tm_hour;
+ t->minute = tm->tm_min;
+ t->second = tm->tm_sec;
+ t->secfrac = tp->tv_usec;
+ t->secfracPrecision = 6;
+
+ if(inUTC) {
+ t->OffsetMode = '+';
+ lBias = 0;
+ } else {
+# if defined(__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;
+# elif defined(_AIX)
+ /* AIXPORT : IBM documentation notice that 'extern long timezone'
+ * is setted after calling tzset.
+ * Recent version of AIX, localtime_r call inside tzset.
+ */
+ if (tm->tm_isdst) tzset();
+ lBias = - timezone;
+# 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 */
+ t->inUTC = inUTC;
+}
+
+/**
+ * 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, const int inUTC)
+{
+ struct timeval tp;
+/* AIXPORT : fix build error : "tm_gmtoff" is not a member of "struct tm"
+ * Choose the HPUX code path, only for this function.
+ * This is achieved by adding a check to _AIX wherever _hpux is checked
+ */
+
+
+#if defined(__hpux) || defined(_AIX)
+ struct timezone tz;
+# endif
+
+ assert(t != NULL);
+#if defined(__hpux) || defined(_AIX)
+ /* 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, inUTC);
+}
+
+
+/* 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
+ */
+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;
+}
+
+dateTimeFormat_t getDateTimeFormatFromStr(const char * const __restrict__ s) {
+ assert(s != NULL);
+
+ if (strcmp(s, "date-rfc3164") == 0)
+ return DATE_RFC3164;
+ if (strcmp(s, "date-rfc3339") == 0)
+ return DATE_RFC3339;
+ if (strcmp(s, "date-unix") == 0)
+ return DATE_UNIX;
+
+ return DATE_INVALID;
+}
+
+/*******************************************************************
+ * 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. We do not permit
+ * integer overruns, this the guard against INT_MAX.
+ *
+ * \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 int
+srSLMGParseInt32(uchar** ppsz, int *pLenStr)
+{
+ register int i;
+
+ i = 0;
+ while(*pLenStr > 0 && **ppsz >= '0' && **ppsz <= '9' && i < INT_MAX/10-1) {
+ 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 - */
+ int 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++ != '-' || year < 0 || year >= 2100) {
+ DBGPRINTF("ParseTIMESTAMP3339: invalid year: %d, pszTS: '%c'\n", year, *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-acquired 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
+ *
+ * We support this format:
+ * [yyyy] Mon mm [yyyy] hh:mm:ss[.subsec][ [yyyy ]/[TZSTRING:]]
+ * Note that [yyyy] and [.subsec] are non-standard but frequently occur.
+ * Also [yyyy] can only occur once -- if it occurs twice, we flag the
+ * timestamp as invalid. if bParseTZ is true, we try to obtain a
+ * TZSTRING. Note that in this case it MUST be terminated by a colon
+ * (Cisco format). This option is a bit dangerous, as it could already
+ * by the tag. So it MUST only be enabled in specialised parsers.
+ * subsec, [yyyy] in front, TZSTRING was added in 2014-07-08 rgerhards
+ * Similarly, we try to detect a year after the timestamp if
+ * bDetectYearAfterTime is set. This is mutally exclusive with bParseTZ.
+ * Note: bDetectYearAfterTime will misdetect hostnames in the range
+ * 2000..2100 as years, so this option should explicitly be turned on
+ * and is not meant for general consumption.
+ */
+static rsRetVal
+ParseTIMESTAMP3164(struct syslogTime *pTime, uchar** ppszTS, int *pLenStr,
+ const int bParseTZ,
+ const int bDetectYearAfterTime)
+{
+ /* 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;
+ int secfrac; /* fractional seconds (must be 32 bit!) */
+ int secfracPrecision;
+ char tzstring[16];
+ char OffsetMode = '\0'; /* UTC offset: \0 -> indicate no update */
+ char OffsetHour = '\0'; /* UTC offset in hours */
+ int OffsetMinute = 0; /* UTC offset in minutes */
+ /* 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(lenStr < 3)
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+
+ /* first check if we have a year in front of the timestamp. some devices (e.g. Brocade)
+ * do this. As it is pretty straightforward to detect and chance of misinterpretation
+ * is low, we try to parse it.
+ */
+ if(*pszTS >= '0' && *pszTS <= '9') {
+ /* OK, either we have a prepended year or an invalid format! */
+ year = srSLMGParseInt32(&pszTS, &lenStr);
+ if(year < 1970 || year > 2100 || *pszTS != ' ')
+ ABORT_FINALIZE(RS_RET_INVLD_TIME);
+ ++pszTS; /* skip SP */
+ }
+
+ /* 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
+ */
+ 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(year == 0 && 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);
+
+ /* as an extension e.g. found in CISCO IOS, we support sub-second resultion.
+ * It's presence is indicated by a dot immediately following the second.
+ */
+ if(lenStr > 0 && *pszTS == '.') {
+ --lenStr;
+ uchar *pszStart = ++pszTS;
+ secfrac = srSLMGParseInt32(&pszTS, &lenStr);
+ secfracPrecision = (int) (pszTS - pszStart);
+ } else {
+ secfracPrecision = 0;
+ secfrac = 0;
+ }
+
+ /* try to parse the TZSTRING if we are instructed to do so */
+ if(bParseTZ && lenStr > 2 && *pszTS == ' ') {
+ int i;
+ for( ++pszTS, --lenStr, i = 0
+ ; lenStr > 0 && i < (int) sizeof(tzstring) - 1 && *pszTS != ':' && *pszTS != ' '
+ ; --lenStr)
+ tzstring[i++] = *pszTS++;
+ if(i > 0) {
+ /* found TZ, apply it */
+ tzinfo_t* tzinfo;
+ tzstring[i] = '\0';
+ if((tzinfo = glblFindTimezone(runConf, (char*) tzstring)) == NULL) {
+ DBGPRINTF("ParseTIMESTAMP3164: invalid TZ string '%s' -- ignored\n",
+ tzstring);
+ } else {
+ OffsetMode = tzinfo->offsMode;
+ OffsetHour = tzinfo->offsHour;
+ OffsetMinute = tzinfo->offsMin;
+ }
+ }
+ }
+ if(bDetectYearAfterTime && year == 0 && lenStr > 5 && *pszTS == ' ') {
+ int j;
+ int y = 0;
+ for(j = 1 ; j < 5 ; ++j) {
+ if(pszTS[j] < '0' || pszTS[j] > '9')
+ break;
+ y = 10 * y + pszTS[j] - '0';
+ }
+ if(lenStr > 6 && pszTS[5] != ' ')
+ y = 0; /* no year! */
+ if(2000 <= y && y < 2100) {
+ year = y;
+ pszTS += 5; /* we need to preserve the SP, checked below */
+ lenStr -= 5;
+ }
+ }
+
+ /* 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->secfrac = secfrac;
+ pTime->secfracPrecision = secfracPrecision;
+ if(OffsetMode != '\0') { /* need to update TZ info? */
+ pTime->OffsetMode = OffsetMode;
+ pTime->OffsetHour = OffsetHour;
+ pTime->OffsetMinute = OffsetMinute;
+ }
+ *pLenStr = lenStr;
+
+finalize_it:
+ RETiRet;
+}
+
+void
+applyDfltTZ(struct syslogTime *pTime, char *tz)
+{
+ pTime->OffsetMode = tz[0];
+ pTime->OffsetHour = (tz[1] - '0') * 10 + (tz[2] - '0');
+ pTime->OffsetMinute = (tz[4] - '0') * 10 + (tz[5] - '0');
+
+}
+
+/*******************************************************************
+ * 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 occurred.
+ */
+static 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;
+
+}
+
+static 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 occurred.
+ * The buffer must be at least 7 bytes large.
+ * rgerhards, 2008-06-06
+ */
+static 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 occurred.
+ */
+static int
+formatTimestamp3339(struct syslogTime *ts, char* pBuf)
+{
+ int iBuf;
+ int power;
+ int secfrac;
+ short digit;
+
+ 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';
+
+ 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 occurred.
+ * 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.
+ */
+static int
+formatTimestamp3164(struct syslogTime *ts, char* pBuf, int bBuggyDay)
+{
+ 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
+ * Note: it would be better to use something similar to mktime() here.
+ * Unfortunately, mktime() semantics are problematic: first of all, it
+ * works on local time, on the machine's time zone. In syslog, we have
+ * to deal with multiple time zones at once, so we cannot plainly rely
+ * on the local zone, and so we cannot rely on mktime(). One solution would
+ * be to refactor all time-related functions so that they are all guarded
+ * by a mutex to ensure TZ consistency (which would also enable us to
+ * change the TZ at will for specific function calls). But that would
+ * potentially mean a lot of overhead.
+ * Also, mktime() has some side effects, at least setting of tzname. With
+ * a refactoring as described above that should probably not be a problem,
+ * but would also need more work. For some more thoughts on this topic,
+ * have a look here:
+ * http://stackoverflow.com/questions/18355101/is-standard-c-mktime-thread-safe-on-linux
+ * In conclusion, we keep our own code for generating the unix timestamp.
+ * rgerhards, 2016-03-02
+ */
+static time_t
+syslogTime2time_t(const struct syslogTime *ts)
+{
+ long MonthInDays, NumberOfYears, NumberOfDays;
+ int utcOffset;
+ time_t TimeInUnixFormat;
+
+ if(ts->year < 1970 || ts->year > 2100) {
+ TimeInUnixFormat = 0;
+ LogError(0, RS_RET_ERR, "syslogTime2time_t: invalid year %d "
+ "in timestamp - returning 1970-01-01 instead", ts->year);
+ goto done;
+ }
+
+ /* 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;
+ }
+ /* adjust for leap years */
+ if((ts->year % 100 != 0 && ts->year % 4 == 0) || (ts->year == 2000)) {
+ if(ts->month > 2)
+ MonthInDays++;
+ }
+
+
+ /* 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 - yearInSec_startYear - 1;
+ NumberOfDays = MonthInDays + ts->day - 1;
+ TimeInUnixFormat = (time_t) (yearInSecs[NumberOfYears] + 1) + NumberOfDays * 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;
+done:
+ 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
+ */
+static int
+formatTimestampUnix(struct syslogTime *ts, char *pBuf)
+{
+ snprintf(pBuf, 11, "%u", (unsigned) syslogTime2time_t(ts));
+ return 11;
+}
+
+/* 0 - Sunday, 1, Monday, ...
+ * Note that we cannot use strftime() and helpers as they rely on the TZ
+ * variable (again, arghhhh). So we need to do it ourselves...
+ * Note: in the year 2100, this algorithm does not work correctly (due to
+ * leap time rules. To fix it then (*IF* this code really still exists then),
+ * just use 2100 as new anchor year and adapt the initial day number.
+ */
+int getWeekdayNbr(struct syslogTime *ts)
+{
+ int wday;
+ int g, f;
+
+ g = ts->year;
+ if(ts->month < 3) {
+ g--;
+ f = ts->month + 13;
+ } else {
+ f = ts->month + 1;
+ }
+ wday = ((36525*g)/100) + ((306*f)/10) + ts->day - 621049;
+ wday %= 7;
+ return wday;
+}
+
+/* getOrdinal - 1-366 day of the year
+ * I've given little thought to leap seconds here.
+ */
+int getOrdinal(struct syslogTime *ts)
+{
+ int yday;
+ time_t thistime;
+ time_t previousyears;
+ int utcOffset;
+ time_t seconds_into_year;
+
+ if(ts->year < 1970 || ts->year > 2100) {
+ yday = 0;
+ LogError(0, RS_RET_ERR, "getOrdinal: invalid year %d "
+ "in timestamp - returning 1970-01-01 instead", ts->year);
+ goto done;
+ }
+
+ thistime = syslogTime2time_t(ts);
+
+ previousyears = (time_t) yearInSecs[ts->year - yearInSec_startYear - 1];
+
+ /* adjust previous years to match 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 */
+ previousyears += utcOffset;
+
+ /* subtract seconds from previous years */
+ seconds_into_year = thistime - previousyears;
+
+ /* divide by seconds in a day and truncate to int */
+ yday = seconds_into_year / 86400;
+done:
+ return yday;
+}
+
+/* getWeek - 1-52 week of the year */
+int getWeek(struct syslogTime *ts)
+{
+ int weekNum;
+ struct syslogTime yt;
+ int curDow;
+ int jan1Dow;
+ int curYearDay;
+
+ /* initialize a timestamp for january 1st of the current year */
+ yt.year = ts->year;
+ yt.month = 1;
+ yt.day = 1;
+ yt.hour = 0;
+ yt.minute = 0;
+ yt.second = 0;
+ yt.secfracPrecision = 0;
+ yt.secfrac = 0;
+ yt.OffsetMinute = ts->OffsetMinute;
+ yt.OffsetHour = ts->OffsetHour;
+ yt.OffsetMode = ts->OffsetMode;
+ yt.timeType = TIME_TYPE_RFC3164; /* low-res time */
+
+ /* get current day in year, current day of week
+ * and the day of week of 1/1 */
+ curYearDay = getOrdinal(ts);
+ curDow = getWeekdayNbr(ts);
+ jan1Dow = getWeekdayNbr(&yt);
+
+ /* calculate week of year for given date by pinning 1/1 as start
+ * of year, then going back and adjusting for the actual week day. */
+ weekNum = ((curYearDay + 6) / 7);
+ if (curDow < jan1Dow) {
+ ++weekNum;
+ }
+ return weekNum;
+}
+
+/* getISOWeek - 1-53 week of the year */
+int getISOWeek(struct syslogTime *ts, int *year)
+{
+ int weekNum;
+ int curDow;
+ int curYearDay;
+
+ /* get current day in year, current day of week
+ * and the day of week of 1/1 */
+ curYearDay = getOrdinal(ts);
+ curDow = getWeekdayNbr(ts);
+
+ /* map from 0 - Sunday, 1, Monday to 1, Monday, 7 - Sunday */
+ if (curDow == 0) {
+ curDow = 7;
+ }
+ /* make ordinal in range 1-366 */
+ curYearDay++;
+
+ weekNum = (10 + curYearDay - curDow) / 7;
+ *year = ts->year;
+ if (weekNum == 0) {
+ /* this is actually W52 or W53 of previous year */
+ weekNum = weeksInYear[ts->year - 1 - 1969];
+ *year = ts->year - 1;
+ } else if (weekNum > weeksInYear[ts->year - 1969]) {
+ /* this is actually W01 of next year */
+ weekNum = 1;
+ *year = ts->year + 1;
+ }
+
+ return weekNum;
+}
+
+void
+timeConvertToUTC(const struct syslogTime *const __restrict__ local,
+ struct syslogTime *const __restrict__ utc)
+{
+ struct timeval tp;
+ tp.tv_sec = syslogTime2time_t(local);
+ tp.tv_usec = local->secfrac;
+ timeval2syslogTime(&tp, utc, 1);
+}
+
+/**
+ * Format a UNIX timestamp.
+ */
+static int
+formatUnixTimeFromTime_t(time_t unixtime, const char *format, char *pBuf,
+ __attribute__((unused)) uint pBufMax) {
+
+ struct tm lt;
+
+ assert(format != NULL);
+ assert(pBuf != NULL);
+
+ // Convert to struct tm
+ if (gmtime_r(&unixtime, &lt) == NULL) {
+ DBGPRINTF("Unexpected error calling gmtime_r().\n");
+ return -1;
+ }
+
+ // Do our conversions
+ if (strcmp(format, "date-rfc3164") == 0) {
+ assert(pBufMax >= 16);
+
+ // Unlikely to run into this situation, but you never know...
+ if (lt.tm_mon < 0 || lt.tm_mon > 11) {
+ DBGPRINTF("lt.tm_mon is out of range. Value: %d\n", lt.tm_mon);
+ return -1;
+ }
+
+ // MMM dd HH:mm:ss
+ sprintf(pBuf, "%s %2d %.2d:%.2d:%.2d",
+ monthNames[lt.tm_mon], lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec
+ );
+ } else if (strcmp(format, "date-rfc3339") == 0) {
+ assert(pBufMax >= 26);
+
+ // YYYY-MM-DDTHH:mm:ss+00:00
+ sprintf(pBuf, "%d-%.2d-%.2dT%.2d:%.2d:%.2dZ",
+ lt.tm_year + 1900, lt.tm_mon + 1, lt.tm_mday, lt.tm_hour, lt.tm_min, lt.tm_sec
+ );
+ }
+
+ return strlen(pBuf);
+}
+
+/* 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;
+ pIf->formatUnixTimeFromTime_t = formatUnixTimeFromTime_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 */
+ENDObjClassInit(datetime)
+
+/* vi:set ai:
+ */
diff --git a/runtime/datetime.h b/runtime/datetime.h
new file mode 100644
index 0000000..ee13e2b
--- /dev/null
+++ b/runtime/datetime.h
@@ -0,0 +1,100 @@
+/* The datetime object. Contains time-related functions.
+ *
+ * Copyright 2008-2015 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;
+
+typedef enum {
+ DATE_INVALID = -1,
+ DATE_RFC3164 = 0,
+ DATE_RFC3339 = 1,
+ DATE_UNIX = 2,
+} dateTimeFormat_t;
+
+/* interfaces */
+BEGINinterface(datetime) /* name must also be changed in ENDinterface macro! */
+ void (*getCurrTime)(struct syslogTime *t, time_t *ttSeconds, const int inUTC);
+ rsRetVal (*ParseTIMESTAMP3339)(struct syslogTime *pTime, uchar** ppszTS, int*);
+ rsRetVal (*ParseTIMESTAMP3164)(struct syslogTime *pTime, uchar** pszTS, int*, const int bParseTZ,
+ const int bDetectYearAfterTime);
+ 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 , v10, 2016-01-12*/
+ void (*timeval2syslogTime)(struct timeval *tp, struct syslogTime *t, const int inUTC);
+ /* v7, 2012-03-29 */
+ int (*formatTimestampUnix)(struct syslogTime *ts, char*pBuf);
+ time_t (*syslogTime2time_t)(const struct syslogTime *ts);
+ /* v11, 2017-10-05 */
+ int (*formatUnixTimeFromTime_t)(time_t time, const char *format, char *pBuf, uint pBufMax);
+ENDinterface(datetime)
+#define datetimeCURR_IF_VERSION 11 /* 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
+ * 8 - ParseTIMESTAMP3164 has addtl parameter to permit TZ string parsing
+ * 9 - ParseTIMESTAMP3164 has addtl parameter to permit year parsing
+ * 10 - functions having addtl paramater inUTC to emit time in UTC:
+ * timeval2syslogTime, getCurrtime
+ * 11 - Add formatUnixTimeFromTime_t
+ */
+
+#define PARSE3164_TZSTRING 1
+#define NO_PARSE3164_TZSTRING 0
+
+#define PERMIT_YEAR_AFTER_TIME 1
+#define NO_PERMIT_YEAR_AFTER_TIME 0
+
+/* two defines for functions that create timestamps either in local
+ * time or UTC.
+ */
+#define TIME_IN_UTC 1
+#define TIME_IN_LOCALTIME 0
+
+/* prototypes */
+PROTOTYPEObj(datetime);
+void applyDfltTZ(struct syslogTime *pTime, char *tz);
+int getWeekdayNbr(struct syslogTime *ts);
+int getOrdinal(struct syslogTime *ts);
+int getWeek(struct syslogTime *ts);
+int getISOWeek(struct syslogTime *ts, int *year);
+void timeConvertToUTC(const struct syslogTime *const __restrict__ local, struct syslogTime *const __restrict__ utc);
+time_t getTime(time_t *ttSeconds);
+dateTimeFormat_t getDateTimeFormatFromStr(const char * const __restrict__ s);
+
+#endif /* #ifndef INCLUDED_DATETIME_H */
diff --git a/runtime/debug.c b/runtime/debug.c
new file mode 100644
index 0000000..6e6c9fd
--- /dev/null
+++ b/runtime/debug.c
@@ -0,0 +1,478 @@
+/* debug.c
+ *
+ * This file proides debug support.
+ *
+ * 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-2018 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 "cfsysline.h"
+#include "obj.h"
+
+
+/* static data (some time to be replaced) */
+DEFobjCurrIf(obj)
+int Debug = DEBUG_OFF; /* debug flag - read-only after startup */
+int debugging_on = 0; /* read-only, except on sig USR1 */
+int dbgTimeoutToStderr = 0;
+static int bPrintTime = 1; /* print a timestamp together with debug message */
+static int bOutputTidToStderr = 0;/* output TID to stderr on thread creation */
+char *pszAltDbgFileName = NULL; /* if set, debug output is *also* sent to here */
+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 */
+static uint64_t dummy_errcount = 0; /* just to avoid some static analyzer complaints */
+static pthread_key_t keyThrdName;
+
+
+/* output the current thread ID to "relevant" places
+ * (what "relevant" means is determinded by various ways)
+ */
+void
+dbgOutputTID(char* name __attribute__((unused)))
+{
+# 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
+}
+
+
+/* build a string with the thread name. If none is set, the thread ID is
+ * used instead. Caller must provide buffer space.
+ */
+static void ATTR_NONNULL()
+dbgGetThrdName(char *const pszBuf, const size_t lenBuf, const pthread_t thrdID)
+{
+ assert(pszBuf != NULL);
+
+ const char *const thrdName = pthread_getspecific(keyThrdName);
+ if(thrdName == NULL) {
+ snprintf(pszBuf, lenBuf, "%lx", (long) thrdID);
+ } else {
+ snprintf(pszBuf, lenBuf, "%-15s", thrdName);
+ }
+}
+
+
+/* set a name for the current thread */
+void ATTR_NONNULL()
+dbgSetThrdName(const uchar *const pszName)
+{
+ (void) pthread_setspecific(keyThrdName, strdup((char*) pszName));
+}
+
+
+/* destructor for thread name (called by pthreads!) */
+static void dbgThrdNameDestruct(void *arg)
+{
+ free(arg);
+}
+
+
+/* write the debug message. This is a helper to dbgprintf and dbgoprint which
+ * contains common code. added 2008-09-26 rgerhards
+ * Note: We need to split the function due to the bad nature of POSIX
+ * cancel cleanup handlers.
+ */
+static void DBGL_UNUSED
+dbgprint(obj_t *pObj, char *pszMsg, const char *pszFileName, const size_t lenMsg)
+{
+ uchar *pszObjName = NULL;
+ 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(pObj != NULL) {
+ pszObjName = obj.GetName(pObj);
+ }
+
+ /* 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();
+ }
+
+ dbgGetThrdName(pszThrdName, sizeof(pszThrdName), ptLastThrdID);
+
+ 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;
+ }
+ lenWriteBuf = snprintf(pszWriteBuf + offsWriteBuf, sizeof(pszWriteBuf) - offsWriteBuf,
+ "%s: ", pszFileName);
+ offsWriteBuf += lenWriteBuf;
+ }
+ 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)) {
+ ++dummy_errcount;
+ }
+ }
+ if(altdbg != -1) {
+ if(write(altdbg, pszWriteBuf, offsWriteBuf)) {
+ ++dummy_errcount;
+ }
+ }
+
+ bWasNL = (pszMsg[lenMsg - 1] == '\n') ? 1 : 0;
+}
+
+
+static int DBGL_UNUSED
+checkDbgFile(const char *srcname)
+{
+ if(glblDbgFilesNum == 0) {
+ return 1;
+ }
+ if(glblDbgWhitelist) {
+ if(bsearch(srcname, glblDbgFiles, glblDbgFilesNum, sizeof(char*), bs_arrcmp_glblDbgFiles) == NULL) {
+ return 0;
+ } else {
+ return 1;
+ }
+ } else {
+ if(bsearch(srcname, glblDbgFiles, glblDbgFilesNum, sizeof(char*), bs_arrcmp_glblDbgFiles) != NULL) {
+ return 0;
+ } else {
+ return 1;
+ }
+ }
+}
+/* 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
+ */
+#ifndef DEBUGLESS
+void
+r_dbgoprint( const char *srcname, obj_t *pObj, const char *fmt, ...)
+{
+ va_list ap;
+ char pszWriteBuf[32*1024];
+ size_t lenWriteBuf;
+
+ if(!(Debug && debugging_on))
+ return;
+
+ if(!checkDbgFile(srcname)) {
+ 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(pObj, pszWriteBuf, srcname, lenWriteBuf);
+}
+#endif
+
+/* print some debug output when no object is given
+ * WARNING: duplicate code, see dbgoprint above!
+ */
+#ifndef DEBUGLESS
+void
+r_dbgprintf(const char *srcname, const char *fmt, ...)
+{
+ va_list ap;
+ char pszWriteBuf[32*1024];
+ size_t lenWriteBuf;
+
+ if(!(Debug && debugging_on)) {
+ return;
+ }
+
+ if(!checkDbgFile(srcname)) {
+ 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, srcname, lenWriteBuf);
+}
+#endif
+
+/* 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 ATTR_NONNULL()
+dbgGetRTOptNamVal(const uchar **const ppszOpt, uchar **const ppOptName)
+{
+ assert(ppszOpt != NULL);
+ assert(*ppszOpt != NULL);
+ int bRet = 0;
+ const uchar *p = *ppszOpt;
+ size_t i;
+ static uchar optname[128] = "";
+
+ /* skip whitespace */
+ while(*p && isspace(*p))
+ ++p;
+
+ /* name: up until whitespace */
+ i = 0;
+ while(i < (sizeof(optname) - 1) && *p && !isspace(*p)) {
+ optname[i++] = *p++;
+ }
+
+ if(i > 0) {
+ bRet = 1;
+ optname[i] = '\0';
+ }
+
+ /* done */
+ *ppszOpt = p;
+ *ppOptName = optname;
+ return bRet;
+}
+
+
+/* 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)
+{
+ const uchar *pszOpts;
+ 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)) {
+ if(!strcasecmp((char*)optname, "help")) {
+ fprintf(stderr,
+ "rsyslogd " VERSION " runtime debug support - help requested, "
+ "rsyslog terminates\n\nenvironment variables:\n"
+ "additional 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"
+ "NoLogTimestamp\n"
+ "Nostdout\n"
+ "OutputTidToStderr\n"
+ "DebugOnDemand - enables debugging on USR1, but does not turn on output\n"
+ "\nSee debug.html in your doc set or https://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 configuration file, "
+ "use USR1 signal to activate.\n");
+ debugging_on = 0;
+ } else if(!strcasecmp((char*)optname, "nologtimestamp")) {
+ bPrintTime = 0;
+ } else if(!strcasecmp((char*)optname, "nostdout")) {
+ stddbg = -1;
+ } else if(!strcasecmp((char*)optname, "outputtidtostderr")) {
+ bOutputTidToStderr = 1;
+ } else {
+ fprintf(stderr, "rsyslogd " VERSION " error: invalid debug option '%s' "
+ "- ignored\n", 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)
+{
+ rsRetVal iRet; /* do not use DEFiRet, as this makes calls into the debug system! */
+
+
+ (void) pthread_key_create(&keyThrdName, dbgThrdNameDestruct);
+
+ /* 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 */
+
+ const char *dbgto2stderr;
+ dbgto2stderr = getenv("RSYSLOG_DEBUG_TIMEOUTS_TO_STDERR");
+ dbgTimeoutToStderr = (dbgto2stderr != NULL && !strcmp(dbgto2stderr, "on")) ? 1 : 0;
+ if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslogd: NOTE: RSYSLOG_DEBUG_TIMEOUTS_TO_STDERR activated\n");
+ }
+ 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)
+{
+ pthread_key_delete(keyThrdName);
+ if(altdbg != -1)
+ close(altdbg);
+ return RS_RET_OK;
+}
diff --git a/runtime/debug.h b/runtime/debug.h
new file mode 100644
index 0000000..13c93cb
--- /dev/null
+++ b/runtime/debug.h
@@ -0,0 +1,78 @@
+/* debug.h
+ *
+ * Definitions for the debug module.
+ *
+ * Copyright 2008-2018 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 */
+extern int dbgTimeoutToStderr;
+
+
+/* prototypes */
+rsRetVal dbgClassInit(void);
+rsRetVal dbgClassExit(void);
+void dbgSetDebugFile(uchar *fn);
+void dbgSetDebugLevel(int level);
+void dbgSetThrdName(const uchar *pszName);
+void dbgOutputTID(char* name);
+int dbgGetDbglogFd(void);
+
+/* external data */
+extern char *pszAltDbgFileName; /* if set, debug output is *also* sent to here */
+extern int altdbg; /* and the handle for alternate debug output */
+
+/* macros */
+#ifdef DEBUGLESS
+# define DBGL_UNUSED __attribute__((__unused__))
+ static inline void r_dbgoprint(const char DBGL_UNUSED *srcname, obj_t DBGL_UNUSED *pObj,
+ const char DBGL_UNUSED *fmt, ...) {}
+ static inline void r_dbgprintf(const char DBGL_UNUSED *srcname, const char DBGL_UNUSED *fmt, ...) {}
+#else
+# define DBGL_UNUSED
+ void r_dbgoprint(const char *srcname, obj_t *pObj, const char *fmt, ...) __attribute__((format(printf, 3, 4)));
+ void r_dbgprintf(const char *srcname, const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+#endif
+
+#define DBGPRINTF(...) if(Debug) { r_dbgprintf(__FILE__, __VA_ARGS__); }
+#define DBGOPRINT(...) if(Debug) { r_dbgoprint(__FILE__, __VA_ARGS__); }
+#define dbgprintf(...) r_dbgprintf(__FILE__, __VA_ARGS__)
+#define dbgoprint(...) r_dbgoprint(__FILE__, __VA_ARGS__)
+
+/* things originally introduced for now removed rtinst */
+#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)
+
+#endif /* #ifndef DEBUG_H_INCLUDED */
diff --git a/runtime/dnscache.c b/runtime/dnscache.c
new file mode 100644
index 0000000..7dd0aab
--- /dev/null
+++ b/runtime/dnscache.c
@@ -0,0 +1,472 @@
+/* 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-2019 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"
+#include "rsconf.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;
+ time_t validUntil;
+ 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(prop)
+static dnscache_t dnsCache;
+static prop_t *staticErrValue;
+
+
+/* Our hash function.
+ */
+static unsigned int
+hash_from_key_fn(void *k)
+{
+ int len = 0;
+ uchar *rkey; /* we treat this as opaque bytes */
+ unsigned hashval = 1;
+
+ switch (((struct sockaddr *)k)->sa_family) {
+ case AF_INET:
+ len = sizeof (struct in_addr);
+ rkey = (uchar*) &(((struct sockaddr_in *)k)->sin_addr);
+ break;
+ case AF_INET6:
+ len = sizeof (struct in6_addr);
+ rkey = (uchar*) &(((struct sockaddr_in6 *)k)->sin6_addr);
+ break;
+ default:
+ dbgprintf("hash_from_key_fn: unknown address family!\n");
+ len = 0;
+ rkey = NULL;
+ }
+ while(len--)
+ hashval = hashval * 33 + *rkey++;
+
+ return hashval;
+}
+
+
+static int
+key_equals_fn(void *key1, void *key2)
+{
+ int RetVal = 0;
+
+ if(((struct sockaddr *)key1)->sa_family != ((struct sockaddr *)key2)->sa_family) {
+ return 0;
+ }
+ switch (((struct sockaddr *)key1)->sa_family) {
+ case AF_INET:
+ RetVal = !memcmp(&((struct sockaddr_in *)key1)->sin_addr,
+ &((struct sockaddr_in *)key2)->sin_addr, sizeof (struct in_addr));
+ break;
+ case AF_INET6:
+ RetVal = !memcmp(&((struct sockaddr_in6 *)key1)->sin6_addr,
+ &((struct sockaddr_in6 *)key2)->sin6_addr, sizeof (struct in6_addr));
+ break;
+ }
+
+ return RetVal;
+}
+
+/* destruct a cache entry.
+ * Precondition: entry must already be unlinked from list
+ */
+static void ATTR_NONNULL()
+entryDestruct(dnscache_entry_t *const 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(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(prop, CORE_COMPONENT);
+ RETiRet;
+}
+
+
+/* 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 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 void
+setLocalHostName(dnscache_entry_t *etry)
+{
+ uchar *fqdnLower;
+ uchar *p;
+ 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);
+ hostbuf[i] = '\0';
+
+ /* 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 ATTR_NONNULL()
+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(runConf)) {
+ 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) {
+ 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(runConf) == 1) {
+ LogError(0, RS_RET_MALICIOUS_ENTITY,
+ "Malicious PTR record, message dropped "
+ "IP = \"%s\" HOST = \"%s\"",
+ szIP, fqdnBuf);
+ 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
+ */
+ LogError(0, NO_ERRCODE,
+ "Malicious PTR record (message accepted, but used IP "
+ "instead of PTR name: IP = \"%s\" HOST = \"%s\"",
+ szIP, fqdnBuf);
+
+ 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! */
+ }
+
+ prop.CreateStringProp(&etry->ip, (uchar*)szIP, strlen(szIP));
+
+ if(error || glbl.GetDisableDNS(runConf)) {
+ 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 rsRetVal ATTR_NONNULL()
+addEntry(struct sockaddr_storage *const addr, dnscache_entry_t **const pEtry)
+{
+ int r;
+ dnscache_entry_t *etry = NULL;
+ DEFiRet;
+
+ /* entry still does not exist, so add it */
+ struct sockaddr_storage *const keybuf = malloc(sizeof(struct sockaddr_storage));
+ CHKmalloc(keybuf);
+ CHKmalloc(etry = malloc(sizeof(dnscache_entry_t)));
+ resolveAddr(addr, etry);
+ assert(etry != NULL);
+ memcpy(&etry->addr, addr, SALEN((struct sockaddr*) addr));
+ etry->nUsed = 0;
+ if(runConf->globals.dnscacheEnableTTL) {
+ etry->validUntil = time(NULL) + runConf->globals.dnscacheDefaultTTL;
+ }
+
+ memcpy(keybuf, addr, sizeof(struct sockaddr_storage));
+
+ r = hashtable_insert(dnsCache.ht, keybuf, etry);
+ if(r == 0) {
+ DBGPRINTF("dnscache: inserting element failed\n");
+ }
+ *pEtry = etry;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ free(keybuf);
+ }
+ RETiRet;
+}
+
+
+static rsRetVal ATTR_NONNULL(1, 5)
+findEntry(struct sockaddr_storage *const addr,
+ prop_t **const fqdn, prop_t **const fqdnLowerCase,
+ prop_t **const localName, prop_t **const ip)
+{
+ DEFiRet;
+
+ pthread_rwlock_rdlock(&dnsCache.rwlock);
+ dnscache_entry_t * etry = hashtable_search(dnsCache.ht, addr);
+ DBGPRINTF("findEntry: 1st lookup found %p\n", etry);
+
+ if(etry == NULL || (runConf->globals.dnscacheEnableTTL && (etry->validUntil <= time(NULL)))) {
+ pthread_rwlock_unlock(&dnsCache.rwlock);
+ pthread_rwlock_wrlock(&dnsCache.rwlock);
+ etry = hashtable_search(dnsCache.ht, addr); /* re-query, might have changed */
+ DBGPRINTF("findEntry: 2nd lookup found %p\n", etry);
+ if(etry == NULL || (runConf->globals.dnscacheEnableTTL && (etry->validUntil <= time(NULL)))) {
+ if(etry != NULL) {
+ DBGPRINTF("hashtable: entry timed out, discarding it; "
+ "valid until %lld, now %lld\n",
+ (long long) etry->validUntil, (long long) time(NULL));
+ dnscache_entry_t *const deleted = hashtable_remove(dnsCache.ht, addr);
+ if(deleted != etry) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "dnscache %d: removed different "
+ "hashtable entry than expected - please report issue; "
+ "rsyslog version is %s", __LINE__, VERSION);
+ }
+ entryDestruct(etry);
+ }
+ /* now entry doesn't exist in any case, so let's (re)create it */
+ CHKiRet(addEntry(addr, &etry));
+ }
+ }
+
+ 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);
+ RETiRet;
+}
+
+
+/* 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 ATTR_NONNULL(1, 5)
+dnscacheLookup(struct sockaddr_storage *const addr,
+ prop_t **const fqdn, prop_t **const fqdnLowerCase,
+ prop_t **const localName, prop_t **const ip)
+{
+ DEFiRet;
+
+ iRet = findEntry(addr, fqdn, fqdnLowerCase, localName, ip);
+
+ if(iRet != RS_RET_OK) {
+ 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 0000000..0ec1dbc
--- /dev/null
+++ b/runtime/dnscache.h
@@ -0,0 +1,31 @@
+/* Definitions for dnscache module.
+ *
+ * Copyright 2011-2019 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 ATTR_NONNULL(1, 5) dnscacheLookup(struct sockaddr_storage *const addr,
+ prop_t **const fqdn, prop_t **const fqdnLowerCase,
+ prop_t **const localName, prop_t **const ip);
+
+#endif /* #ifndef INCLUDED_DNSCACHE_H */
diff --git a/runtime/dynstats.c b/runtime/dynstats.c
new file mode 100644
index 0000000..1bdba57
--- /dev/null
+++ b/runtime/dynstats.c
@@ -0,0 +1,622 @@
+/*
+ * 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 <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "srUtils.h"
+#include "errmsg.h"
+#include "rsconf.h"
+#include "unicode-helper.h"
+
+/* definitions for objects we access */
+DEFobjStaticHelpers
+DEFobjCurrIf(statsobj)
+
+#define DYNSTATS_PARAM_NAME "name"
+#define DYNSTATS_PARAM_RESETTABLE "resettable"
+#define DYNSTATS_PARAM_MAX_CARDINALITY "maxCardinality"
+#define DYNSTATS_PARAM_UNUSED_METRIC_LIFE "unusedMetricLife" /* in seconds */
+
+#define DYNSTATS_DEFAULT_RESETTABILITY 1
+#define DYNSTATS_DEFAULT_MAX_CARDINALITY 2000
+#define DYNSTATS_DEFAULT_UNUSED_METRIC_LIFE 3600 /* seconds */
+
+#define DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH 100
+#define DYNSTATS_METRIC_NAME_SEPARATOR '.'
+#define DYNSTATS_HASHTABLE_SIZE_OVERPROVISIONING 1.25
+
+static struct cnfparamdescr modpdescr[] = {
+ { DYNSTATS_PARAM_NAME, eCmdHdlrString, CNFPARAM_REQUIRED },
+ { DYNSTATS_PARAM_RESETTABLE, eCmdHdlrBinary, 0 },
+ { DYNSTATS_PARAM_MAX_CARDINALITY, eCmdHdlrPositiveInt, 0},
+ { DYNSTATS_PARAM_UNUSED_METRIC_LIFE, eCmdHdlrPositiveInt, 0} /* in minutes */
+};
+
+static struct cnfparamblk modpblk =
+{
+ CNFPARAMBLK_VERSION,
+ sizeof(modpdescr)/sizeof(struct cnfparamdescr),
+ modpdescr
+};
+
+rsRetVal
+dynstatsClassInit(void) {
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+ CHKiRet(objUse(statsobj, CORE_COMPONENT));
+finalize_it:
+ RETiRet;
+}
+
+static void
+dynstats_destroyCtr(dynstats_ctr_t *ctr) {
+ statsobj.DestructUnlinkedCounter(ctr->pCtr);
+ free(ctr->metric);
+ free(ctr);
+}
+
+static void /* assumes exclusive access to bucket */
+dynstats_destroyCountersIn(dynstats_bucket_t *b, htable *table, dynstats_ctr_t *ctrs) {
+ dynstats_ctr_t *ctr;
+ int ctrs_purged = 0;
+ hashtable_destroy(table, 0);
+ while (ctrs != NULL) {
+ ctr = ctrs;
+ ctrs = ctrs->next;
+ dynstats_destroyCtr(ctr);
+ ctrs_purged++;
+ }
+ STATSCOUNTER_ADD(b->ctrMetricsPurged, b->mutCtrMetricsPurged, ctrs_purged);
+ ATOMIC_SUB_unsigned(&b->metricCount, ctrs_purged, &b->mutMetricCount);
+}
+
+static void /* assumes exclusive access to bucket */
+dynstats_destroyCounters(dynstats_bucket_t *b) {
+ statsobj.UnlinkAllCounters(b->stats);
+ dynstats_destroyCountersIn(b, b->table, b->ctrs);
+}
+
+static void
+dynstats_destroyBucket(dynstats_buckets_t *bkts, dynstats_bucket_t* b) {
+ pthread_rwlock_wrlock(&b->lock);
+ dynstats_destroyCounters(b);
+ dynstats_destroyCountersIn(b, b->survivor_table, b->survivor_ctrs);
+ statsobj.Destruct(&b->stats);
+ free(b->name);
+ pthread_rwlock_unlock(&b->lock);
+ pthread_rwlock_destroy(&b->lock);
+ pthread_mutex_destroy(&b->mutMetricCount);
+ statsobj.DestructCounter(bkts->global_stats, b->pOpsOverflowCtr);
+ statsobj.DestructCounter(bkts->global_stats, b->pNewMetricAddCtr);
+ statsobj.DestructCounter(bkts->global_stats, b->pNoMetricCtr);
+ statsobj.DestructCounter(bkts->global_stats, b->pMetricsPurgedCtr);
+ statsobj.DestructCounter(bkts->global_stats, b->pOpsIgnoredCtr);
+ statsobj.DestructCounter(bkts->global_stats, b->pPurgeTriggeredCtr);
+ free(b);
+}
+
+static rsRetVal
+dynstats_addBucketMetrics(dynstats_buckets_t *bkts, dynstats_bucket_t *b, const uchar* name) {
+ uchar *metric_name_buff, *metric_suffix;
+ const uchar *suffix_litteral;
+ int name_len;
+ DEFiRet;
+
+ name_len = ustrlen(name);
+ CHKmalloc(metric_name_buff = malloc((name_len + DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH + 1) * sizeof(uchar)));
+
+ strcpy((char*)metric_name_buff, (char*)name);
+ metric_suffix = metric_name_buff + name_len;
+ *metric_suffix = DYNSTATS_METRIC_NAME_SEPARATOR;
+ metric_suffix++;
+
+ suffix_litteral = UCHAR_CONSTANT("ops_overflow");
+ ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrOpsOverflow, b->mutCtrOpsOverflow);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrOpsOverflow),
+ &b->pOpsOverflowCtr, 1));
+
+ suffix_litteral = UCHAR_CONSTANT("new_metric_add");
+ ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrNewMetricAdd, b->mutCtrNewMetricAdd);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrNewMetricAdd),
+ &b->pNewMetricAddCtr, 1));
+
+ suffix_litteral = UCHAR_CONSTANT("no_metric");
+ ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrNoMetric, b->mutCtrNoMetric);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrNoMetric),
+ &b->pNoMetricCtr, 1));
+
+ suffix_litteral = UCHAR_CONSTANT("metrics_purged");
+ ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrMetricsPurged, b->mutCtrMetricsPurged);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrMetricsPurged),
+ &b->pMetricsPurgedCtr, 1));
+
+ suffix_litteral = UCHAR_CONSTANT("ops_ignored");
+ ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrOpsIgnored, b->mutCtrOpsIgnored);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrOpsIgnored),
+ &b->pOpsIgnoredCtr, 1));
+
+ suffix_litteral = UCHAR_CONSTANT("purge_triggered");
+ ustrncpy(metric_suffix, suffix_litteral, DYNSTATS_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrPurgeTriggered, b->mutCtrPurgeTriggered);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrPurgeTriggered),
+ &b->pPurgeTriggeredCtr, 1));
+
+finalize_it:
+ free(metric_name_buff);
+ if (iRet != RS_RET_OK) {
+ if (b->pOpsOverflowCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pOpsOverflowCtr);
+ }
+ if (b->pNewMetricAddCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pNewMetricAddCtr);
+ }
+ if (b->pNoMetricCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pNoMetricCtr);
+ }
+ if (b->pMetricsPurgedCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pMetricsPurgedCtr);
+ }
+ if (b->pOpsIgnoredCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pOpsIgnoredCtr);
+ }
+ if (b->pPurgeTriggeredCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pPurgeTriggeredCtr);
+ }
+ }
+ RETiRet;
+}
+
+static void
+no_op_free(void __attribute__((unused)) *ignore) {}
+
+static rsRetVal /* assumes exclusive access to bucket */
+dynstats_rebuildSurvivorTable(dynstats_bucket_t *b) {
+ htable *survivor_table = NULL;
+ htable *new_table = NULL;
+ size_t htab_sz;
+ DEFiRet;
+
+ htab_sz = (size_t) (DYNSTATS_HASHTABLE_SIZE_OVERPROVISIONING * b->maxCardinality + 1);
+ if (b->table == NULL) {
+ CHKmalloc(survivor_table = create_hashtable(htab_sz, hash_from_string, key_equals_string,
+ no_op_free));
+ }
+ CHKmalloc(new_table = create_hashtable(htab_sz, hash_from_string, key_equals_string, no_op_free));
+ statsobj.UnlinkAllCounters(b->stats);
+ if (b->survivor_table != NULL) {
+ dynstats_destroyCountersIn(b, b->survivor_table, b->survivor_ctrs);
+ }
+ b->survivor_table = (b->table == NULL) ? survivor_table : b->table;
+ b->survivor_ctrs = b->ctrs;
+ b->table = new_table;
+ b->ctrs = NULL;
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ LogError(errno, RS_RET_INTERNAL_ERROR, "error trying to evict "
+ "TTL-expired metrics of dyn-stats bucket named: %s", b->name);
+ if (new_table == NULL) {
+ LogError(errno, RS_RET_INTERNAL_ERROR, "error trying to "
+ "initialize hash-table for dyn-stats bucket named: %s", b->name);
+ } else {
+ assert(0); /* "can" not happen -- triggers Coverity CID 184307:
+ hashtable_destroy(new_table, 0);
+ We keep this as guard should code above change in the future */
+ }
+ if (b->table == NULL) {
+ if (survivor_table == NULL) {
+ LogError(errno, RS_RET_INTERNAL_ERROR, "error trying to initialize "
+ "ttl-survivor hash-table for dyn-stats bucket named: %s", b->name);
+ } else {
+ hashtable_destroy(survivor_table, 0);
+ }
+ }
+ }
+ RETiRet;
+}
+
+static rsRetVal
+dynstats_resetBucket(dynstats_bucket_t *b) {
+ DEFiRet;
+ pthread_rwlock_wrlock(&b->lock);
+ CHKiRet(dynstats_rebuildSurvivorTable(b));
+ STATSCOUNTER_INC(b->ctrPurgeTriggered, b->mutCtrPurgeTriggered);
+ timeoutComp(&b->metricCleanupTimeout, b->unusedMetricLife);
+finalize_it:
+ pthread_rwlock_unlock(&b->lock);
+ RETiRet;
+}
+
+static void
+dynstats_resetIfExpired(dynstats_bucket_t *b) {
+ long timeout;
+ pthread_rwlock_rdlock(&b->lock);
+ timeout = timeoutVal(&b->metricCleanupTimeout);
+ pthread_rwlock_unlock(&b->lock);
+ if (timeout == 0) {
+ LogMsg(0, RS_RET_TIMED_OUT, LOG_INFO, "dynstats: bucket '%s' is being reset", b->name);
+ dynstats_resetBucket(b);
+ }
+}
+
+static void
+dynstats_readCallback(statsobj_t __attribute__((unused)) *ignore, void *b) {
+ dynstats_buckets_t *bkts;
+ bkts = &runConf->dynstats_buckets;
+
+ pthread_rwlock_rdlock(&bkts->lock);
+ dynstats_resetIfExpired((dynstats_bucket_t *) b);
+ pthread_rwlock_unlock(&bkts->lock);
+}
+
+static rsRetVal
+dynstats_initNewBucketStats(dynstats_bucket_t *b) {
+ DEFiRet;
+
+ CHKiRet(statsobj.Construct(&b->stats));
+ CHKiRet(statsobj.SetOrigin(b->stats, UCHAR_CONSTANT("dynstats.bucket")));
+ CHKiRet(statsobj.SetName(b->stats, b->name));
+ CHKiRet(statsobj.SetReportingNamespace(b->stats, UCHAR_CONSTANT("values")));
+ statsobj.SetReadNotifier(b->stats, dynstats_readCallback, b);
+ CHKiRet(statsobj.ConstructFinalize(b->stats));
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+dynstats_newBucket(const uchar* name, uint8_t resettable, uint32_t maxCardinality, uint32_t unusedMetricLife) {
+ dynstats_bucket_t *b;
+ dynstats_buckets_t *bkts;
+ uint8_t lock_initialized, metric_count_mutex_initialized;
+ pthread_rwlockattr_t bucket_lock_attr;
+ DEFiRet;
+
+ lock_initialized = metric_count_mutex_initialized = 0;
+ b = NULL;
+
+ bkts = &loadConf->dynstats_buckets;
+
+ if (bkts->initialized) {
+ CHKmalloc(b = calloc(1, sizeof(dynstats_bucket_t)));
+ b->resettable = resettable;
+ b->maxCardinality = maxCardinality;
+ b->unusedMetricLife = 1000 * unusedMetricLife;
+ CHKmalloc(b->name = ustrdup(name));
+
+ pthread_rwlockattr_init(&bucket_lock_attr);
+#ifdef HAVE_PTHREAD_RWLOCKATTR_SETKIND_NP
+ pthread_rwlockattr_setkind_np(&bucket_lock_attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
+#endif
+
+ pthread_rwlock_init(&b->lock, &bucket_lock_attr);
+ lock_initialized = 1;
+ pthread_mutex_init(&b->mutMetricCount, NULL);
+ metric_count_mutex_initialized = 1;
+
+ CHKiRet(dynstats_initNewBucketStats(b));
+
+ CHKiRet(dynstats_resetBucket(b));
+
+ CHKiRet(dynstats_addBucketMetrics(bkts, b, name));
+
+ pthread_rwlock_wrlock(&bkts->lock);
+ if (bkts->list == NULL) {
+ bkts->list = b;
+ } else {
+ b->next = bkts->list;
+ bkts->list = b;
+ }
+ pthread_rwlock_unlock(&bkts->lock);
+ } else {
+ LogError(0, RS_RET_INTERNAL_ERROR, "dynstats: bucket creation failed, as "
+ "global-initialization of buckets was unsuccessful");
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ if (metric_count_mutex_initialized) {
+ pthread_mutex_destroy(&b->mutMetricCount);
+ }
+ if (lock_initialized) {
+ pthread_rwlock_destroy(&b->lock);
+ }
+ if (b != NULL) {
+ dynstats_destroyBucket(bkts, b);
+ }
+ }
+ RETiRet;
+}
+
+rsRetVal
+dynstats_processCnf(struct cnfobj *o) {
+ struct cnfparamvals *pvals;
+ short i;
+ uchar *name = NULL;
+ uint8_t resettable = DYNSTATS_DEFAULT_RESETTABILITY;
+ uint32_t maxCardinality = DYNSTATS_DEFAULT_MAX_CARDINALITY;
+ uint32_t unusedMetricLife = DYNSTATS_DEFAULT_UNUSED_METRIC_LIFE;
+ DEFiRet;
+
+ pvals = nvlstGetParams(o->nvlst, &modpblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+
+ for(i = 0 ; i < modpblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_NAME)) {
+ CHKmalloc(name = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
+ } else if (!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_RESETTABLE)) {
+ resettable = (pvals[i].val.d.n != 0);
+ } else if (!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_MAX_CARDINALITY)) {
+ maxCardinality = (uint32_t) pvals[i].val.d.n;
+ } else if (!strcmp(modpblk.descr[i].name, DYNSTATS_PARAM_UNUSED_METRIC_LIFE)) {
+ unusedMetricLife = (uint32_t) pvals[i].val.d.n;
+ } else {
+ dbgprintf("dyn_stats: program error, non-handled "
+ "param '%s'\n", modpblk.descr[i].name);
+ }
+ }
+ if (name != NULL) {
+ CHKiRet(dynstats_newBucket(name, resettable, maxCardinality, unusedMetricLife));
+ }
+
+finalize_it:
+ free(name);
+ cnfparamvalsDestruct(pvals, &modpblk);
+ RETiRet;
+}
+
+rsRetVal
+dynstats_initCnf(dynstats_buckets_t *bkts) {
+ DEFiRet;
+
+ bkts->initialized = 0;
+
+ bkts->list = NULL;
+ CHKiRet(statsobj.Construct(&bkts->global_stats));
+ CHKiRet(statsobj.SetOrigin(bkts->global_stats, UCHAR_CONSTANT("dynstats")));
+ CHKiRet(statsobj.SetName(bkts->global_stats, UCHAR_CONSTANT("global")));
+ CHKiRet(statsobj.SetReportingNamespace(bkts->global_stats, UCHAR_CONSTANT("values")));
+ CHKiRet(statsobj.ConstructFinalize(bkts->global_stats));
+ pthread_rwlock_init(&bkts->lock, NULL);
+
+ bkts->initialized = 1;
+
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ statsobj.Destruct(&bkts->global_stats);
+ }
+ RETiRet;
+}
+
+void
+dynstats_destroyAllBuckets(void) {
+ dynstats_buckets_t *bkts;
+ dynstats_bucket_t *b;
+ bkts = &runConf->dynstats_buckets;
+ if (bkts->initialized) {
+ pthread_rwlock_wrlock(&bkts->lock);
+ while(1) {
+ b = bkts->list;
+ if (b == NULL) {
+ break;
+ } else {
+ bkts->list = b->next;
+ dynstats_destroyBucket(bkts, b);
+ }
+ }
+ statsobj.Destruct(&bkts->global_stats);
+ pthread_rwlock_unlock(&bkts->lock);
+ pthread_rwlock_destroy(&bkts->lock);
+ }
+}
+
+dynstats_bucket_t *
+dynstats_findBucket(const uchar* name) {
+ dynstats_buckets_t *bkts;
+ dynstats_bucket_t *b;
+ bkts = &loadConf->dynstats_buckets;
+ if (bkts->initialized) {
+ pthread_rwlock_rdlock(&bkts->lock);
+ b = bkts->list;
+ while(b != NULL) {
+ if (! ustrcmp(name, b->name)) {
+ break;
+ }
+ b = b->next;
+ }
+ pthread_rwlock_unlock(&bkts->lock);
+ } else {
+ b = NULL;
+ LogError(0, RS_RET_INTERNAL_ERROR, "dynstats: bucket lookup failed, as global-initialization "
+ "of buckets was unsuccessful");
+ }
+
+ return b;
+}
+
+static rsRetVal
+dynstats_createCtr(dynstats_bucket_t *b, const uchar* metric, dynstats_ctr_t **ctr) {
+ DEFiRet;
+
+ CHKmalloc(*ctr = calloc(1, sizeof(dynstats_ctr_t)));
+ CHKmalloc((*ctr)->metric = ustrdup(metric));
+ STATSCOUNTER_INIT((*ctr)->ctr, (*ctr)->mutCtr);
+ CHKiRet(statsobj.AddManagedCounter(b->stats, metric, ctrType_IntCtr,
+ b->resettable ? CTR_FLAG_MUST_RESET : CTR_FLAG_NONE,
+ &(*ctr)->ctr, &(*ctr)->pCtr, 0));
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ if ((*ctr) != NULL) {
+ free((*ctr)->metric);
+ free(*ctr);
+ *ctr = NULL;
+ }
+ }
+ RETiRet;
+}
+
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" /* TODO: how can we fix these warnings? */
+#endif
+static rsRetVal
+dynstats_addNewCtr(dynstats_bucket_t *b, const uchar* metric, uint8_t doInitialIncrement) {
+ dynstats_ctr_t *ctr;
+ dynstats_ctr_t *found_ctr, *survivor_ctr, *effective_ctr;
+ int created;
+ uchar *copy_of_key = NULL;
+ DEFiRet;
+
+ created = 0;
+ ctr = NULL;
+
+ if ((unsigned) ATOMIC_FETCH_32BIT_unsigned(&b->metricCount, &b->mutMetricCount) >= b->maxCardinality) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ CHKiRet(dynstats_createCtr(b, metric, &ctr));
+
+ pthread_rwlock_wrlock(&b->lock);
+ found_ctr = (dynstats_ctr_t*) hashtable_search(b->table, ctr->metric);
+ if (found_ctr != NULL) {
+ if (doInitialIncrement) {
+ STATSCOUNTER_INC(found_ctr->ctr, found_ctr->mutCtr);
+ }
+ } else {
+ copy_of_key = ustrdup(ctr->metric);
+ if (copy_of_key != NULL) {
+ survivor_ctr = (dynstats_ctr_t*) hashtable_search(b->survivor_table, ctr->metric);
+ if (survivor_ctr == NULL) {
+ effective_ctr = ctr;
+ } else {
+ effective_ctr = survivor_ctr;
+ if (survivor_ctr->prev != NULL) {
+ survivor_ctr->prev->next = survivor_ctr->next;
+ }
+ if (survivor_ctr->next != NULL) {
+ survivor_ctr->next->prev = survivor_ctr->prev;
+ }
+ if (survivor_ctr == b->survivor_ctrs) {
+ b->survivor_ctrs = survivor_ctr->next;
+ }
+ }
+ if ((created = hashtable_insert(b->table, copy_of_key, effective_ctr))) {
+ statsobj.AddPreCreatedCtr(b->stats, effective_ctr->pCtr);
+ }
+ }
+ if (created) {
+ if (b->ctrs != NULL) {
+ b->ctrs->prev = effective_ctr;
+ }
+ effective_ctr->prev = NULL;
+ effective_ctr->next = b->ctrs;
+ b->ctrs = effective_ctr;
+ if (doInitialIncrement) {
+ STATSCOUNTER_INC(effective_ctr->ctr, effective_ctr->mutCtr);
+ }
+ }
+ }
+ pthread_rwlock_unlock(&b->lock);
+
+ if (found_ctr != NULL) {
+ //ignore
+ } else if (created && (effective_ctr != survivor_ctr)) {
+ ATOMIC_INC(&b->metricCount, &b->mutMetricCount);
+ STATSCOUNTER_INC(b->ctrNewMetricAdd, b->mutCtrNewMetricAdd);
+ } else if (! created) {
+ if (copy_of_key != NULL) {
+ free(copy_of_key);
+ }
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+finalize_it:
+ if (((! created) || (effective_ctr != ctr)) && (ctr != NULL)) {
+ dynstats_destroyCtr(ctr);
+ }
+ RETiRet;
+}
+#if defined(__GNUC__) && !defined(__clang__)
+#pragma GCC diagnostic pop
+#endif
+
+rsRetVal
+dynstats_inc(dynstats_bucket_t *b, uchar* metric) {
+ dynstats_ctr_t *ctr;
+ DEFiRet;
+
+ if (! GatherStats) {
+ FINALIZE;
+ }
+
+ if (ustrlen(metric) == 0) {
+ STATSCOUNTER_INC(b->ctrNoMetric, b->mutCtrNoMetric);
+ FINALIZE;
+ }
+
+ if (pthread_rwlock_tryrdlock(&b->lock) == 0) {
+ ctr = (dynstats_ctr_t *) hashtable_search(b->table, metric);
+ if (ctr != NULL) {
+ STATSCOUNTER_INC(ctr->ctr, ctr->mutCtr);
+ }
+ pthread_rwlock_unlock(&b->lock);
+ } else {
+ ABORT_FINALIZE(RS_RET_NOENTRY);
+ }
+
+ if (ctr == NULL) {
+ CHKiRet(dynstats_addNewCtr(b, metric, 1));
+ }
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ if (iRet == RS_RET_NOENTRY) {
+ /* NOTE: this is not tested (because it requires very strong orchestration to
+ guarantee contended lock for testing) */
+ STATSCOUNTER_INC(b->ctrOpsIgnored, b->mutCtrOpsIgnored);
+ } else {
+ STATSCOUNTER_INC(b->ctrOpsOverflow, b->mutCtrOpsOverflow);
+ }
+ }
+ RETiRet;
+}
+
diff --git a/runtime/dynstats.h b/runtime/dynstats.h
new file mode 100644
index 0000000..a8986f4
--- /dev/null
+++ b/runtime/dynstats.h
@@ -0,0 +1,83 @@
+/*
+ * 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_DYNSTATS_H
+#define INCLUDED_DYNSTATS_H
+
+#include "hashtable.h"
+
+typedef struct hashtable htable;
+
+struct dynstats_ctr_s {
+ STATSCOUNTER_DEF(ctr, mutCtr);
+ ctr_t *pCtr;
+ uchar *metric;
+ /* linked list ptr */
+ struct dynstats_ctr_s *next;
+ struct dynstats_ctr_s *prev;
+};
+
+struct dynstats_bucket_s {
+ htable *table;
+ uchar *name;
+ pthread_rwlock_t lock;
+ statsobj_t *stats;
+ STATSCOUNTER_DEF(ctrOpsOverflow, mutCtrOpsOverflow);
+ ctr_t *pOpsOverflowCtr;
+ STATSCOUNTER_DEF(ctrNewMetricAdd, mutCtrNewMetricAdd);
+ ctr_t *pNewMetricAddCtr;
+ STATSCOUNTER_DEF(ctrNoMetric, mutCtrNoMetric);
+ ctr_t *pNoMetricCtr;
+ STATSCOUNTER_DEF(ctrMetricsPurged, mutCtrMetricsPurged);
+ ctr_t *pMetricsPurgedCtr;
+ STATSCOUNTER_DEF(ctrOpsIgnored, mutCtrOpsIgnored);
+ ctr_t *pOpsIgnoredCtr;
+ STATSCOUNTER_DEF(ctrPurgeTriggered, mutCtrPurgeTriggered);
+ ctr_t *pPurgeTriggeredCtr;
+ struct dynstats_bucket_s *next; /* linked list ptr */
+ struct dynstats_ctr_s *ctrs;
+ /*survivor objects are used to keep counter values around for upto unused-ttl duration,
+ so in case it is accessed within (ttl - 2 * ttl) time-period we can re-store the
+ accumulator value from this */
+ struct dynstats_ctr_s *survivor_ctrs;
+ htable *survivor_table;
+
+ uint32_t maxCardinality;
+ uint32_t metricCount;
+ pthread_mutex_t mutMetricCount;
+ uint32_t unusedMetricLife;
+ uint32_t lastResetTs;
+ struct timespec metricCleanupTimeout;
+ uint8_t resettable;
+};
+
+struct dynstats_buckets_s {
+ struct dynstats_bucket_s *list;
+ statsobj_t *global_stats;
+ pthread_rwlock_t lock;
+ uint8_t initialized;
+};
+
+rsRetVal dynstats_initCnf(dynstats_buckets_t *b);
+rsRetVal dynstats_processCnf(struct cnfobj *o);
+dynstats_bucket_t * dynstats_findBucket(const uchar* name);
+rsRetVal dynstats_inc(dynstats_bucket_t *bucket, uchar* metric);
+void dynstats_destroyAllBuckets(void);
+void dynstats_resetExpired(void);
+rsRetVal dynstatsClassInit(void);
+
+#endif /* #ifndef INCLUDED_DYNSTATS_H */
diff --git a/runtime/errmsg.c b/runtime/errmsg.c
new file mode 100644
index 0000000..ecc0452
--- /dev/null
+++ b/runtime/errmsg.c
@@ -0,0 +1,288 @@
+/* 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-2018 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "rsyslog.h"
+#include "obj.h"
+#include "msg.h"
+#include "errmsg.h"
+#include "operatingstate.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+#include "rsconf.h"
+
+/* static data */
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
+
+static int bHadErrMsgs; /* indicates if we had error messages since reset of this flag
+ * This is used to abort a run if the config is unclean.
+ */
+
+static int fdOversizeMsgLog = -1;
+static pthread_mutex_t oversizeMsgLogMut = PTHREAD_MUTEX_INITIALIZER;
+
+/* ------------------------------ methods ------------------------------ */
+
+/* Resets the error message flag. Must be done before processing config
+ * files.
+ */
+void
+resetErrMsgsFlag(void)
+{
+ bHadErrMsgs = 0;
+}
+
+int
+hadErrMsgs(void)
+{
+ return bHadErrMsgs;
+}
+
+/* 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
+doLogMsg(const int iErrno, const int iErrCode, const int severity, const char *msg)
+{
+ char buf[2048];
+ char errStr[1024];
+
+ dbgprintf("Called LogMsg, msg: %s\n", msg);
+ osf_write(OSF_TAG_MSG, msg);
+
+ if(iErrno != 0) {
+ rs_strerror_r(iErrno, errStr, sizeof(errStr));
+ if(iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) {
+ snprintf(buf, sizeof(buf), "%s: %s [v%s]", msg, errStr, VERSION);
+ } else {
+ snprintf(buf, sizeof(buf), "%s: %s [v%s try https://www.rsyslog.com/e/%d ]",
+ msg, errStr, VERSION, iErrCode * -1);
+ }
+ } else {
+ if(iErrCode == NO_ERRCODE || iErrCode == RS_RET_ERR) {
+ snprintf(buf, sizeof(buf), "%s [v%s]", msg, VERSION);
+ } else {
+ snprintf(buf, sizeof(buf), "%s [v%s try https://www.rsyslog.com/e/%d ]", msg,
+ VERSION, iErrCode * -1);
+ }
+ }
+ buf[sizeof(buf) - 1] = '\0'; /* just to be on the safe side... */
+ errno = 0;
+
+ const int msglen = (int) strlen(buf);
+ if(msglen > glblGetMaxLine(ourConf)) {
+ /* in extreme cases, our error messages may be longer than the configured
+ * max message size. If so, we just truncate without further indication, as
+ * anything else would probably lead to a death loop on error messages.
+ * Note that we do not split, as we really do not anticipate there is
+ * much value in supporting extremely short max message sizes - we assume
+ * it's just a testbench thing. -- rgerhards, 2018-05-11
+ */
+ buf[glblGetMaxLine(ourConf)] = '\0'; /* space must be available! */
+ }
+
+ glblErrLogger(severity, iErrCode, (uchar*)buf);
+
+ if(severity == LOG_ERR)
+ bHadErrMsgs = 1;
+}
+
+/* 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
+ */
+void __attribute__((format(printf, 3, 4)))
+LogError(const int iErrno, const int iErrCode, const char *fmt, ... )
+{
+ va_list ap;
+ char buf[2048];
+ int lenBuf;
+
+ va_start(ap, fmt);
+ lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap);
+ if(lenBuf < 0) {
+ strncpy(buf, "error message lost due to problem with vsnprintf", sizeof(buf));
+ }
+ va_end(ap);
+ buf[sizeof(buf) - 1] = '\0'; /* just to be on the safe side... */
+
+ doLogMsg(iErrno, iErrCode, LOG_ERR, buf);
+}
+
+/* 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
+ */
+void __attribute__((format(printf, 4, 5)))
+LogMsg(const int iErrno, const int iErrCode, const int severity, const char *fmt, ... )
+{
+ va_list ap;
+ char buf[2048];
+ int lenBuf;
+
+ va_start(ap, fmt);
+ lenBuf = vsnprintf(buf, sizeof(buf), fmt, ap);
+ if(lenBuf < 0) {
+ strncpy(buf, "error message lost due to problem with vsnprintf", sizeof(buf));
+ }
+ va_end(ap);
+ buf[sizeof(buf) - 1] = '\0'; /* just to be on the safe side... */
+
+ doLogMsg(iErrno, iErrCode, severity, buf);
+}
+
+
+/* Write an oversize message to the oversize message error log.
+ * We do NOT handle errors during writing that log other than emitting
+ * yet another error message. The reason is that there really is nothing
+ * else that we could do in that case.
+ * rgerhards, 2018-05-03
+ */
+rsRetVal ATTR_NONNULL()
+writeOversizeMessageLog(const smsg_t *const pMsg)
+{
+ struct json_object *json = NULL;
+ char *rendered = NULL;
+ struct json_object *jval;
+ uchar *buf;
+ size_t toWrite;
+ ssize_t wrRet;
+ int dummy;
+ int mutexLocked = 0;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pMsg, msg);
+
+ if(glblGetOversizeMsgErrorFile(runConf) == NULL) {
+ FINALIZE;
+ }
+
+ pthread_mutex_lock(&oversizeMsgLogMut);
+ mutexLocked = 1;
+
+ if(fdOversizeMsgLog == -1) {
+ fdOversizeMsgLog = open((char*)glblGetOversizeMsgErrorFile(runConf),
+ O_WRONLY|O_CREAT|O_APPEND|O_LARGEFILE|O_CLOEXEC,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
+ if(fdOversizeMsgLog == -1) {
+ LogError(errno, RS_RET_ERR, "error opening oversize message log file %s",
+ glblGetOversizeMsgErrorFile(runConf));
+ FINALIZE;
+ }
+ }
+
+ assert(fdOversizeMsgLog != -1);
+ json = json_object_new_object();
+ if(json == NULL) {
+ FINALIZE;
+ }
+
+ getRawMsg(pMsg, &buf, &dummy);
+ jval = json_object_new_string((char*)buf);
+ json_object_object_add(json, "rawmsg", jval);
+
+ getInputName(pMsg, &buf, &dummy);
+ jval = json_object_new_string((char*)buf);
+ json_object_object_add(json, "input", jval);
+
+ CHKmalloc(rendered = strdup((char*)fjson_object_to_json_string(json)));
+
+ toWrite = strlen(rendered) + 1;
+ /* Note: we overwrite the '\0' terminator with '\n' -- so we avoid
+ * calling malloc() -- write() does NOT need '\0'!
+ */
+ rendered[toWrite-1] = '\n'; /* NO LONGER A STRING! */
+ wrRet = write(fdOversizeMsgLog, rendered, toWrite);
+ if(wrRet != (ssize_t) toWrite) {
+ LogError(errno, RS_RET_IO_ERROR,
+ "error writing oversize message log file %s, write returned %lld",
+ glblGetOversizeMsgErrorFile(runConf), (long long) wrRet);
+ }
+
+finalize_it:
+ free(rendered);
+ if(mutexLocked) {
+ pthread_mutex_unlock(&oversizeMsgLogMut);
+ }
+ if(json != NULL) {
+ fjson_object_put(json);
+ }
+ RETiRet;
+}
+
+
+void
+errmsgDoHUP(void)
+{
+ pthread_mutex_lock(&oversizeMsgLogMut);
+ if(fdOversizeMsgLog != -1) {
+ close(fdOversizeMsgLog);
+ fdOversizeMsgLog = -1;
+ }
+ pthread_mutex_unlock(&oversizeMsgLogMut);
+}
+
+
+void
+errmsgExit(void)
+{
+ if(fdOversizeMsgLog != -1) {
+ close(fdOversizeMsgLog);
+ }
+}
diff --git a/runtime/errmsg.h b/runtime/errmsg.h
new file mode 100644
index 0000000..aad002b
--- /dev/null
+++ b/runtime/errmsg.h
@@ -0,0 +1,38 @@
+/* The errmsg object. It is used to emit error message inside rsyslog.
+ *
+ * Copyright 2008-2018 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 "obj-types.h"
+
+#define NO_ERRCODE -1
+
+/* prototypes */
+void errmsgExit(void);
+void errmsgDoHUP(void);
+void resetErrMsgsFlag(void);
+int hadErrMsgs(void);
+void __attribute__((format(printf, 3, 4))) LogError(const int iErrno, const int iErrCode, const char *fmt, ... );
+void __attribute__((format(printf, 4, 5)))
+ LogMsg(const int iErrno, const int iErrCode, const int severity, const char *fmt, ... );
+rsRetVal ATTR_NONNULL() writeOversizeMessageLog(const smsg_t *const pMsg);
+
+#endif /* #ifndef INCLUDED_ERRMSG_H */
diff --git a/runtime/glbl.c b/runtime/glbl.c
new file mode 100644
index 0000000..6b4cb29
--- /dev/null
+++ b/runtime/glbl.c
@@ -0,0 +1,1523 @@
+/* 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-2023 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 <sys/wait.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <ctype.h>
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.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 "action.h"
+#include "parserif.h"
+#include "rainerscript.h"
+#include "srUtils.h"
+#include "operatingstate.h"
+#include "net.h"
+#include "rsconf.h"
+#include "queue.h"
+#include "dnscache.h"
+#include "parser.h"
+#include "timezones.h"
+
+/* some defaults */
+#ifndef DFLT_NETSTRM_DRVR
+# define DFLT_NETSTRM_DRVR ((uchar*)"ptcp")
+#endif
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(prop)
+DEFobjCurrIf(net)
+
+/* static data
+ * For this object, these variables are obviously what makes the "meat" of the
+ * class...
+ */
+
+static struct cnfobj *mainqCnfObj = NULL;/* main queue object, to be used later in startup sequence */
+static int bPreserveFQDN = 0; /* should FQDNs always be preserved? */
+static prop_t *propLocalIPIF = NULL;/* IP address to report for the local host (default is 127.0.0.1) */
+static int propLocalIPIF_set = 0; /* is propLocalIPIF already set? */
+static prop_t *propLocalHostName = NULL;/* our hostname as FQDN - read-only after startup */
+static prop_t *propLocalHostNameToDelete = NULL;/* see GenerateLocalHostName function hdr comment! */
+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 int iMaxLine = 8096;
+int bTerminateInputs = 0; /* global switch that inputs shall terminate ASAP (1=> terminate) */
+int glblUnloadModules = 1;
+char** glblDbgFiles = NULL;
+size_t glblDbgFilesNum = 0;
+int glblDbgWhitelist = 1;
+int glblPermitCtlC = 0;
+
+pid_t glbl_ourpid;
+#ifndef HAVE_ATOMIC_BUILTINS
+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 },
+ { "operatingstatefile", eCmdHdlrString, 0 },
+ { "dropmsgswithmaliciousdnsptrrecords", eCmdHdlrBinary, 0 },
+ { "localhostname", eCmdHdlrGetWord, 0 },
+ { "preservefqdn", eCmdHdlrBinary, 0 },
+ { "debug.onshutdown", eCmdHdlrBinary, 0 },
+ { "debug.logfile", eCmdHdlrString, 0 },
+ { "debug.gnutls", eCmdHdlrNonNegInt, 0 },
+ { "debug.unloadmodules", eCmdHdlrBinary, 0 },
+ { "defaultnetstreamdrivercafile", eCmdHdlrString, 0 },
+ { "defaultnetstreamdrivercrlfile", eCmdHdlrString, 0 },
+ { "defaultnetstreamdriverkeyfile", eCmdHdlrString, 0 },
+ { "defaultnetstreamdrivercertfile", eCmdHdlrString, 0 },
+ { "defaultnetstreamdriver", eCmdHdlrString, 0 },
+ { "netstreamdrivercaextrafiles", eCmdHdlrString, 0 },
+ { "maxmessagesize", eCmdHdlrSize, 0 },
+ { "oversizemsg.errorfile", eCmdHdlrGetWord, 0 },
+ { "oversizemsg.report", eCmdHdlrBinary, 0 },
+ { "oversizemsg.input.mode", eCmdHdlrGetWord, 0 },
+ { "reportchildprocessexits", eCmdHdlrGetWord, 0 },
+ { "action.reportsuspension", eCmdHdlrBinary, 0 },
+ { "action.reportsuspensioncontinuation", eCmdHdlrBinary, 0 },
+ { "parser.controlcharacterescapeprefix", eCmdHdlrGetChar, 0 },
+ { "parser.droptrailinglfonreception", eCmdHdlrBinary, 0 },
+ { "parser.escapecontrolcharactersonreceive", eCmdHdlrBinary, 0 },
+ { "parser.spacelfonreceive", eCmdHdlrBinary, 0 },
+ { "parser.escape8bitcharactersonreceive", eCmdHdlrBinary, 0},
+ { "parser.escapecontrolcharactertab", eCmdHdlrBinary, 0},
+ { "parser.escapecontrolcharacterscstyle", eCmdHdlrBinary, 0 },
+ { "parser.parsehostnameandtag", eCmdHdlrBinary, 0 },
+ { "parser.permitslashinprogramname", eCmdHdlrBinary, 0 },
+ { "stdlog.channelspec", eCmdHdlrString, 0 },
+ { "janitor.interval", eCmdHdlrPositiveInt, 0 },
+ { "senders.reportnew", eCmdHdlrBinary, 0 },
+ { "senders.reportgoneaway", eCmdHdlrBinary, 0 },
+ { "senders.timeoutafter", eCmdHdlrPositiveInt, 0 },
+ { "senders.keeptrack", eCmdHdlrBinary, 0 },
+ { "inputs.timeout.shutdown", eCmdHdlrPositiveInt, 0 },
+ { "privdrop.group.keepsupplemental", eCmdHdlrBinary, 0 },
+ { "privdrop.group.id", eCmdHdlrPositiveInt, 0 },
+ { "privdrop.group.name", eCmdHdlrGID, 0 },
+ { "privdrop.user.id", eCmdHdlrPositiveInt, 0 },
+ { "privdrop.user.name", eCmdHdlrUID, 0 },
+ { "net.ipprotocol", eCmdHdlrGetWord, 0 },
+ { "net.acladdhostnameonfail", eCmdHdlrBinary, 0 },
+ { "net.aclresolvehostname", eCmdHdlrBinary, 0 },
+ { "net.enabledns", eCmdHdlrBinary, 0 },
+ { "net.permitACLwarning", eCmdHdlrBinary, 0 },
+ { "abortonuncleanconfig", eCmdHdlrBinary, 0 },
+ { "abortonfailedqueuestartup", eCmdHdlrBinary, 0 },
+ { "variables.casesensitive", eCmdHdlrBinary, 0 },
+ { "environment", eCmdHdlrArray, 0 },
+ { "processinternalmessages", eCmdHdlrBinary, 0 },
+ { "umask", eCmdHdlrFileCreateMode, 0 },
+ { "security.abortonidresolutionfail", eCmdHdlrBinary, 0 },
+ { "internal.developeronly.options", eCmdHdlrInt, 0 },
+ { "internalmsg.ratelimit.interval", eCmdHdlrPositiveInt, 0 },
+ { "internalmsg.ratelimit.burst", eCmdHdlrPositiveInt, 0 },
+ { "internalmsg.severity", eCmdHdlrSeverity, 0 },
+ { "errormessagestostderr.maxnumber", eCmdHdlrPositiveInt, 0 },
+ { "shutdown.enable.ctlc", eCmdHdlrBinary, 0 },
+ { "default.action.queue.timeoutshutdown", eCmdHdlrInt, 0 },
+ { "default.action.queue.timeoutactioncompletion", eCmdHdlrInt, 0 },
+ { "default.action.queue.timeoutenqueue", eCmdHdlrInt, 0 },
+ { "default.action.queue.timeoutworkerthreadshutdown", eCmdHdlrInt, 0 },
+ { "default.ruleset.queue.timeoutshutdown", eCmdHdlrInt, 0 },
+ { "default.ruleset.queue.timeoutactioncompletion", eCmdHdlrInt, 0 },
+ { "default.ruleset.queue.timeoutenqueue", eCmdHdlrInt, 0 },
+ { "default.ruleset.queue.timeoutworkerthreadshutdown", eCmdHdlrInt, 0 },
+ { "reverselookup.cache.ttl.default", eCmdHdlrNonNegInt, 0 },
+ { "reverselookup.cache.ttl.enable", eCmdHdlrBinary, 0 },
+ { "parser.supportcompressionextension", eCmdHdlrBinary, 0 },
+ { "shutdown.queue.doublesize", eCmdHdlrBinary, 0 },
+ { "debug.files", eCmdHdlrArray, 0 },
+ { "debug.whitelist", eCmdHdlrBinary, 0 },
+ { "libcapng.default", eCmdHdlrBinary, 0 },
+ { "libcapng.enable", eCmdHdlrBinary, 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?)
+ */
+
+int
+glblGetMaxLine(rsconf_t *cnf)
+{
+ /* glblGetMaxLine might be invoked before our configuration exists */
+ return((cnf != NULL) ? cnf->globals.iMaxLine : iMaxLine);
+}
+
+
+int
+GetGnuTLSLoglevel(rsconf_t *cnf)
+{
+ return(cnf->globals.iGnuTLSLoglevel);
+}
+
+/* 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(PreserveFQDN, bPreserveFQDN, int)
+SIMP_PROP(mainqCnfObj, mainqCnfObj, struct cnfobj *)
+#ifdef USE_UNLIMITED_SELECT
+SIMP_PROP(FdSetSize, iFdSetSize, int)
+#endif
+
+#undef SIMP_PROP
+#undef SIMP_PROP_SET
+#undef SIMP_PROP_GET
+
+/* This is based on the previous SIMP_PROP but as a getter it uses
+ * additional parameter specifying the configuration it belongs to.
+ * The setter uses loadConf
+ */
+#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) \
+{ \
+ loadConf->globals.nameVar = newVal; \
+ return RS_RET_OK; \
+}
+#define SIMP_PROP_GET(nameFunc, nameVar, dataType) \
+static dataType Get##nameFunc(rsconf_t *cnf) \
+{ \
+ return(cnf->globals.nameVar); \
+}
+
+SIMP_PROP(DropMalPTRMsgs, bDropMalPTRMsgs, int)
+SIMP_PROP(DisableDNS, bDisableDNS, int)
+SIMP_PROP(ParserEscapeControlCharactersCStyle, parser.bParserEscapeCCCStyle, int)
+SIMP_PROP(ParseHOSTNAMEandTAG, parser.bParseHOSTNAMEandTAG, int)
+SIMP_PROP(OptionDisallowWarning, optionDisallowWarning, int)
+/* We omit setter on purpose, because we want to customize it */
+SIMP_PROP_GET(DfltNetstrmDrvrCAF, pszDfltNetstrmDrvrCAF, uchar*)
+SIMP_PROP_GET(DfltNetstrmDrvrCRLF, pszDfltNetstrmDrvrCRLF, uchar*)
+SIMP_PROP_GET(DfltNetstrmDrvrCertFile, pszDfltNetstrmDrvrCertFile, uchar*)
+SIMP_PROP_GET(DfltNetstrmDrvrKeyFile, pszDfltNetstrmDrvrKeyFile, uchar*)
+SIMP_PROP_GET(NetstrmDrvrCAExtraFiles, pszNetstrmDrvrCAExtraFiles, uchar*)
+SIMP_PROP_GET(ParserControlCharacterEscapePrefix, parser.cCCEscapeChar, uchar)
+SIMP_PROP_GET(ParserDropTrailingLFOnReception, parser.bDropTrailingLF, int)
+SIMP_PROP_GET(ParserEscapeControlCharactersOnReceive, parser.bEscapeCCOnRcv, int)
+SIMP_PROP_GET(ParserSpaceLFOnReceive, parser.bSpaceLFOnRcv, int)
+SIMP_PROP_GET(ParserEscape8BitCharactersOnReceive, parser.bEscape8BitChars, int)
+SIMP_PROP_GET(ParserEscapeControlCharacterTab, parser.bEscapeTab, int)
+
+#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 rsRetVal
+storeLocalHostIPIF(uchar *myIP)
+{
+ DEFiRet;
+ if(propLocalIPIF != NULL) {
+ CHKiRet(prop.Destruct(&propLocalIPIF));
+ }
+ 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 this to be set 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_set) {
+ 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) {
+ 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) {
+ 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';
+ LogError(0, RS_RET_WRN_WRKDIR, "$WorkDirectory: trailing slashes "
+ "removed, new value is '%s'", pNewVal);
+ }
+
+ if(stat((char*) pNewVal, &sb) != 0) {
+ 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)) {
+ LogError(0, RS_RET_ERR_WRKDIR, "$WorkDirectory: %s not a directory - directive ignored",
+ pNewVal);
+ ABORT_FINALIZE(RS_RET_ERR_WRKDIR);
+ }
+
+ free(loadConf->globals.pszWorkDir);
+ loadConf->globals.pszWorkDir = pNewVal;
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal
+setDfltNetstrmDrvrCAF(void __attribute__((unused)) *pVal, uchar *pNewVal) {
+ DEFiRet;
+ FILE *fp;
+ free(loadConf->globals.pszDfltNetstrmDrvrCAF);
+ loadConf->globals.pszDfltNetstrmDrvrCAF = pNewVal;
+ fp = fopen((const char*)pNewVal, "r");
+ if(fp == NULL) {
+ LogError(errno, RS_RET_NO_FILE_ACCESS,
+ "error: defaultnetstreamdrivercafile file '%s' "
+ "could not be accessed", pNewVal);
+ } else {
+ fclose(fp);
+ }
+
+ RETiRet;
+}
+
+static rsRetVal
+setNetstrmDrvrCAExtraFiles(void __attribute__((unused)) *pVal, uchar *pNewVal) {
+ DEFiRet;
+ FILE *fp;
+ char* token;
+ int error = 0;
+ free(loadConf->globals.pszNetstrmDrvrCAExtraFiles);
+
+ token = strtok((char*)pNewVal, ",");
+ // Here, fopen per strtok ...
+ while(token != NULL) {
+ fp = fopen((const char*)token, "r");
+ if(fp == NULL) {
+ LogError(errno, RS_RET_NO_FILE_ACCESS,
+ "error: netstreamdrivercaextrafiles file '%s' "
+ "could not be accessed", token);
+ error = 1;
+ } else {
+ fclose(fp);
+ }
+ token = strtok(NULL, ",");
+ }
+ if(!error) {
+ loadConf->globals.pszNetstrmDrvrCAExtraFiles = pNewVal;
+ } else {
+ loadConf->globals.pszNetstrmDrvrCAExtraFiles = NULL;
+ }
+ RETiRet;
+}
+
+static rsRetVal
+setDfltNetstrmDrvrCRLF(void __attribute__((unused)) *pVal, uchar *pNewVal) {
+ DEFiRet;
+ FILE *fp;
+ free(loadConf->globals.pszDfltNetstrmDrvrCRLF);
+ loadConf->globals.pszDfltNetstrmDrvrCRLF = pNewVal;
+ fp = fopen((const char*)pNewVal, "r");
+ if(fp == NULL) {
+ LogError(errno, RS_RET_NO_FILE_ACCESS,
+ "error: defaultnetstreamdrivercrlfile file '%s' "
+ "could not be accessed", pNewVal);
+ } else {
+ fclose(fp);
+ }
+
+ RETiRet;
+}
+
+
+static rsRetVal
+setDfltNetstrmDrvrCertFile(void __attribute__((unused)) *pVal, uchar *pNewVal) {
+ DEFiRet;
+ FILE *fp;
+
+ free(loadConf->globals.pszDfltNetstrmDrvrCertFile);
+ loadConf->globals.pszDfltNetstrmDrvrCertFile = pNewVal;
+ fp = fopen((const char*)pNewVal, "r");
+ if(fp == NULL) {
+ LogError(errno, RS_RET_NO_FILE_ACCESS,
+ "error: defaultnetstreamdrivercertfile '%s' "
+ "could not be accessed", pNewVal);
+ } else {
+ fclose(fp);
+ }
+
+ RETiRet;
+}
+
+static rsRetVal
+setDfltNetstrmDrvrKeyFile(void __attribute__((unused)) *pVal, uchar *pNewVal) {
+ DEFiRet;
+ FILE *fp;
+
+ free(loadConf->globals.pszDfltNetstrmDrvrKeyFile);
+ loadConf->globals.pszDfltNetstrmDrvrKeyFile = pNewVal;
+ fp = fopen((const char*)pNewVal, "r");
+ if(fp == NULL) {
+ LogError(errno, RS_RET_NO_FILE_ACCESS,
+ "error: defaultnetstreamdriverkeyfile '%s' "
+ "could not be accessed", pNewVal);
+ } else {
+ fclose(fp);
+ }
+
+ RETiRet;
+}
+
+static rsRetVal
+setDfltNetstrmDrvr(void __attribute__((unused)) *pVal, uchar *pNewVal) {
+ DEFiRet;
+ free(loadConf->globals.pszDfltNetstrmDrvr);
+ loadConf->globals.pszDfltNetstrmDrvr = pNewVal;
+ RETiRet;
+}
+
+static rsRetVal
+setParserControlCharacterEscapePrefix(void __attribute__((unused)) *pVal, uchar *pNewVal) {
+ DEFiRet;
+ loadConf->globals.parser.cCCEscapeChar = *pNewVal;
+ RETiRet;
+}
+
+static rsRetVal
+setParserDropTrailingLFOnReception(void __attribute__((unused)) *pVal, int pNewVal) {
+ DEFiRet;
+ loadConf->globals.parser.bDropTrailingLF = pNewVal;
+ RETiRet;
+}
+
+static rsRetVal
+setParserEscapeControlCharactersOnReceive(void __attribute__((unused)) *pVal, int pNewVal) {
+ DEFiRet;
+ loadConf->globals.parser.bEscapeCCOnRcv = pNewVal;
+ RETiRet;
+}
+
+static rsRetVal
+setParserSpaceLFOnReceive(void __attribute__((unused)) *pVal, int pNewVal) {
+ DEFiRet;
+ loadConf->globals.parser.bSpaceLFOnRcv = pNewVal;
+ RETiRet;
+}
+
+static rsRetVal
+setParserEscape8BitCharactersOnReceive(void __attribute__((unused)) *pVal, int pNewVal) {
+ DEFiRet;
+ loadConf->globals.parser.bEscape8BitChars = pNewVal;
+ RETiRet;
+}
+
+static rsRetVal
+setParserEscapeControlCharacterTab(void __attribute__((unused)) *pVal, int pNewVal) {
+ DEFiRet;
+ loadConf->globals.parser.bEscapeTab = pNewVal;
+ RETiRet;
+}
+
+/* This function is used both by legacy and RainerScript conf. It is a real setter. */
+static void
+setMaxLine(const int64_t iNew)
+{
+ if(iNew < 128) {
+ LogError(0, RS_RET_INVALID_VALUE, "maxMessageSize tried to set "
+ "to %lld, but cannot be less than 128 - set to 128 "
+ "instead", (long long) iNew);
+ loadConf->globals.iMaxLine = 128;
+ } else if(iNew > (int64_t) INT_MAX) {
+ LogError(0, RS_RET_INVALID_VALUE, "maxMessageSize larger than "
+ "INT_MAX (%d) - reduced to INT_MAX", INT_MAX);
+ loadConf->globals.iMaxLine = INT_MAX;
+ } else {
+ loadConf->globals.iMaxLine = (int) iNew;
+ }
+}
+
+
+
+static rsRetVal
+legacySetMaxMessageSize(void __attribute__((unused)) *pVal, int64_t iNew)
+{
+ setMaxLine(iNew);
+ return RS_RET_OK;
+}
+
+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;
+}
+
+static rsRetVal ATTR_NONNULL()
+setOversizeMsgInputMode(const uchar *const mode)
+{
+ DEFiRet;
+ if(!strcmp((char*)mode, "truncate")) {
+ loadConf->globals.oversizeMsgInputMode = glblOversizeMsgInputMode_Truncate;
+ } else if(!strcmp((char*)mode, "split")) {
+ loadConf->globals.oversizeMsgInputMode = glblOversizeMsgInputMode_Split;
+ } else if(!strcmp((char*)mode, "accept")) {
+ loadConf->globals.oversizeMsgInputMode = glblOversizeMsgInputMode_Accept;
+ } else {
+ loadConf->globals.oversizeMsgInputMode = glblOversizeMsgInputMode_Truncate;
+ }
+ RETiRet;
+}
+
+static rsRetVal ATTR_NONNULL()
+setReportChildProcessExits(const uchar *const mode)
+{
+ DEFiRet;
+ if(!strcmp((char*)mode, "none")) {
+ loadConf->globals.reportChildProcessExits = REPORT_CHILD_PROCESS_EXITS_NONE;
+ } else if(!strcmp((char*)mode, "errors")) {
+ loadConf->globals.reportChildProcessExits = REPORT_CHILD_PROCESS_EXITS_ERRORS;
+ } else if(!strcmp((char*)mode, "all")) {
+ loadConf->globals.reportChildProcessExits = REPORT_CHILD_PROCESS_EXITS_ALL;
+ } else {
+ LogError(0, RS_RET_CONF_PARAM_INVLD,
+ "invalid value '%s' for global parameter reportChildProcessExits -- ignored",
+ mode);
+ iRet = RS_RET_CONF_PARAM_INVLD;
+ }
+ RETiRet;
+}
+
+static int
+getDefPFFamily(rsconf_t *cnf)
+{
+ return cnf->globals.iDefPFFamily;
+}
+
+/* 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)
+{
+ assert(propLocalIPIF != NULL);
+ return(propLocalIPIF);
+}
+
+
+/* set our local hostname. Free previous hostname, if it was already set.
+ * Note that we do now do this in a thread. As such, the method here
+ * is NOT 100% clean. If we run into issues, we need to think about
+ * refactoring the whole way we handle the local host name processing.
+ * Possibly using a PROP might be the right solution then.
+ */
+static rsRetVal
+SetLocalHostName(uchar *const newname)
+{
+ uchar *toFree;
+ if(LocalHostName == NULL || strcmp((const char*)LocalHostName, (const char*) newname)) {
+ toFree = LocalHostName;
+ LocalHostName = newname;
+ } else {
+ toFree = newname;
+ }
+ free(toFree);
+ return RS_RET_OK;
+}
+
+
+/* return our local hostname. if it is not set, "[localhost]" is returned
+ */
+uchar*
+glblGetLocalHostName(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);
+}
+
+
+/* return the name of the file where oversize messages are written to
+ */
+uchar*
+glblGetOversizeMsgErrorFile(rsconf_t *cnf)
+{
+ return cnf->globals.oversizeMsgErrorFile;
+}
+
+const uchar*
+glblGetOperatingStateFile(rsconf_t *cnf)
+{
+ return cnf->globals.operatingStateFile;
+}
+
+/* return the mode with which oversize messages will be put forward
+ */
+int
+glblGetOversizeMsgInputMode(rsconf_t *cnf)
+{
+ return cnf->globals.oversizeMsgInputMode;
+}
+
+int
+glblReportOversizeMessage(rsconf_t *cnf)
+{
+ return cnf->globals.reportOversizeMsg;
+}
+
+
+/* logs a message indicating that a child process has terminated.
+ * If name != NULL, prints it as the program name.
+ */
+void
+glblReportChildProcessExit(rsconf_t *cnf, const uchar *name, pid_t pid, int status)
+{
+ DBGPRINTF("waitpid for child %ld returned status: %2.2x\n", (long) pid, status);
+
+ if(cnf->globals.reportChildProcessExits == REPORT_CHILD_PROCESS_EXITS_NONE
+ || (cnf->globals.reportChildProcessExits == REPORT_CHILD_PROCESS_EXITS_ERRORS
+ && WIFEXITED(status) && WEXITSTATUS(status) == 0)) {
+ return;
+ }
+
+ if(WIFEXITED(status)) {
+ int severity = WEXITSTATUS(status) == 0 ? LOG_INFO : LOG_WARNING;
+ if(name != NULL) {
+ LogMsg(0, NO_ERRCODE, severity, "program '%s' (pid %ld) exited with status %d",
+ name, (long) pid, WEXITSTATUS(status));
+ } else {
+ LogMsg(0, NO_ERRCODE, severity, "child process (pid %ld) exited with status %d",
+ (long) pid, WEXITSTATUS(status));
+ }
+ } else if(WIFSIGNALED(status)) {
+ if(name != NULL) {
+ LogMsg(0, NO_ERRCODE, LOG_WARNING, "program '%s' (pid %ld) terminated by signal %d",
+ name, (long) pid, WTERMSIG(status));
+ } else {
+ LogMsg(0, NO_ERRCODE, LOG_WARNING, "child process (pid %ld) terminated by signal %d",
+ (long) pid, WTERMSIG(status));
+ }
+ }
+}
+
+
+/* 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
+ * NOTE: This function tries to avoid locking by not destructing the previous value
+ * immediately. This is so that current readers can continue to use the previous name.
+ * Otherwise, we would need to use read/write locks to protect the update process.
+ * In order to do so, we save the previous value and delete it when we are called again
+ * the next time. Note that this in theory is racy and can lead to a double-free.
+ * In practice, however, the window of exposure to trigger this is extremely short
+ * and as this functions is very infrequently being called (on HUP), the trigger
+ * condition for this bug is so highly unlikely that it never occurs in practice.
+ * Probably if you HUP rsyslog every few milliseconds, but who does that...
+ * To further reduce risk potential, we do only update the property when there
+ * actually is a hostname change, which makes it even less likely.
+ * rgerhards, 2013-10-28
+ */
+static rsRetVal
+GenerateLocalHostNameProperty(void)
+{
+ uchar *pszPrev;
+ int lenPrev;
+ prop_t *hostnameNew;
+ uchar *pszName;
+ DEFiRet;
+
+ if(propLocalHostNameToDelete != NULL)
+ prop.Destruct(&propLocalHostNameToDelete);
+
+ 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);
+
+ if(propLocalHostName == NULL)
+ pszPrev = (uchar*)""; /* make sure strcmp() below does not match */
+ else
+ prop.GetString(propLocalHostName, &pszPrev, &lenPrev);
+
+ if(ustrcmp(pszPrev, pszName)) {
+ /* we need to update */
+ CHKiRet(prop.Construct(&hostnameNew));
+ CHKiRet(prop.SetString(hostnameNew, pszName, ustrlen(pszName)));
+ CHKiRet(prop.ConstructFinalize(hostnameNew));
+ propLocalHostNameToDelete = propLocalHostName;
+ propLocalHostName = hostnameNew;
+ }
+
+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(rsconf_t *cnf)
+{
+ return(cnf->globals.pszWorkDir == NULL ? (uchar*) "" : cnf->globals.pszWorkDir);
+}
+
+/* return the "raw" working directory, which means
+ * NULL if unset.
+ */
+const uchar *
+glblGetWorkDirRaw(rsconf_t *cnf)
+{
+ return cnf->globals.pszWorkDir;
+}
+
+/* return the current default netstream driver */
+static uchar*
+GetDfltNetstrmDrvr(rsconf_t *cnf)
+{
+ return(cnf->globals.pszDfltNetstrmDrvr == NULL ? DFLT_NETSTRM_DRVR : cnf->globals.pszDfltNetstrmDrvr);
+}
+
+/* [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] */
+ pIf->GetDefPFFamily = getDefPFFamily;
+ pIf->GetDisableDNS = GetDisableDNS;
+ pIf->GetMaxLine = glblGetMaxLine;
+ pIf->GetOptionDisallowWarning = GetOptionDisallowWarning;
+ pIf->GetDfltNetstrmDrvrCAF = GetDfltNetstrmDrvrCAF;
+ pIf->GetDfltNetstrmDrvrCRLF = GetDfltNetstrmDrvrCRLF;
+ pIf->GetDfltNetstrmDrvrCertFile = GetDfltNetstrmDrvrCertFile;
+ pIf->GetDfltNetstrmDrvrKeyFile = GetDfltNetstrmDrvrKeyFile;
+ pIf->GetDfltNetstrmDrvr = GetDfltNetstrmDrvr;
+ pIf->GetNetstrmDrvrCAExtraFiles = GetNetstrmDrvrCAExtraFiles;
+ pIf->GetParserControlCharacterEscapePrefix = GetParserControlCharacterEscapePrefix;
+ pIf->GetParserDropTrailingLFOnReception = GetParserDropTrailingLFOnReception;
+ pIf->GetParserEscapeControlCharactersOnReceive = GetParserEscapeControlCharactersOnReceive;
+ pIf->GetParserSpaceLFOnReceive = GetParserSpaceLFOnReceive;
+ pIf->GetParserEscape8BitCharactersOnReceive = GetParserEscape8BitCharactersOnReceive;
+ pIf->GetParserEscapeControlCharacterTab = GetParserEscapeControlCharacterTab;
+ pIf->GetLocalHostName = glblGetLocalHostName;
+ pIf->SetLocalHostName = SetLocalHostName;
+#define SIMP_PROP(name) \
+ pIf->Get##name = Get##name; \
+ pIf->Set##name = Set##name;
+ SIMP_PROP(PreserveFQDN);
+ SIMP_PROP(DropMalPTRMsgs);
+ SIMP_PROP(mainqCnfObj);
+ SIMP_PROP(LocalFQDNName)
+ SIMP_PROP(LocalDomain)
+ SIMP_PROP(ParserEscapeControlCharactersCStyle)
+ SIMP_PROP(ParseHOSTNAMEandTAG)
+#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(loadConf->globals.pszDfltNetstrmDrvr);
+ loadConf->globals.pszDfltNetstrmDrvr = NULL;
+ free(loadConf->globals.pszDfltNetstrmDrvrCAF);
+ loadConf->globals.pszDfltNetstrmDrvrCAF = NULL;
+ free(loadConf->globals.pszDfltNetstrmDrvrCRLF);
+ loadConf->globals.pszDfltNetstrmDrvrCRLF = NULL;
+ free(loadConf->globals.pszDfltNetstrmDrvrKeyFile);
+ loadConf->globals.pszDfltNetstrmDrvrKeyFile = NULL;
+ free(loadConf->globals.pszDfltNetstrmDrvrCertFile);
+ loadConf->globals.pszDfltNetstrmDrvrCertFile = NULL;
+ free(LocalHostNameOverride);
+ LocalHostNameOverride = NULL;
+ free(loadConf->globals.oversizeMsgErrorFile);
+ loadConf->globals.oversizeMsgErrorFile = NULL;
+ loadConf->globals.oversizeMsgInputMode = glblOversizeMsgInputMode_Accept;
+ loadConf->globals.reportChildProcessExits = REPORT_CHILD_PROCESS_EXITS_ERRORS;
+ free(loadConf->globals.pszWorkDir);
+ loadConf->globals.pszWorkDir = NULL;
+ free((void*)loadConf->globals.operatingStateFile);
+ loadConf->globals.operatingStateFile = NULL;
+ loadConf->globals.bDropMalPTRMsgs = 0;
+ bPreserveFQDN = 0;
+ loadConf->globals.iMaxLine = 8192;
+ loadConf->globals.reportOversizeMsg = 1;
+ loadConf->globals.parser.cCCEscapeChar = '#';
+ loadConf->globals.parser.bDropTrailingLF = 1;
+ loadConf->globals.parser.bEscapeCCOnRcv = 1; /* default is to escape control characters */
+ loadConf->globals.parser.bSpaceLFOnRcv = 0;
+ loadConf->globals.parser.bEscape8BitChars = 0; /* default is not to escape control characters */
+ loadConf->globals.parser.bEscapeTab = 1; /* default is to escape tab characters */
+ loadConf->globals.parser.bParserEscapeCCCStyle = 0;
+#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(mainqCnfObj);
+ mainqCnfObj = NULL;
+ free(cnfparamvals);
+ cnfparamvals = NULL;
+}
+
+/* handle the timezone() object. Each incarnation adds one additional
+ * zone info to the global table of time zones.
+ */
+
+int
+bs_arrcmp_glblDbgFiles(const void *s1, const void *s2)
+{
+ return strcmp((char*)s1, *(char**)s2);
+}
+
+
+
+/* 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)
+{
+ int i;
+
+ cnfparamvals = nvlstGetParams(o->nvlst, &paramblk, cnfparamvals);
+ if(cnfparamvals == NULL) {
+ LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing global "
+ "config parameters [global(...)]");
+ goto done;
+ }
+ if(Debug) {
+ dbgprintf("glbl param blk after glblProcessCnf:\n");
+ cnfparamsPrint(&paramblk, cnfparamvals);
+ }
+
+ /* The next thing is a bit hackish and should be changed in higher
+ * versions. There are a select few parameters which we need to
+ * act on immediately. These are processed here.
+ */
+ for(i = 0 ; i < paramblk.nParams ; ++i) {
+ if(!cnfparamvals[i].bUsed)
+ continue;
+ if(!strcmp(paramblk.descr[i].name, "processinternalmessages")) {
+ loadConf->globals.bProcessInternalMessages = (int) cnfparamvals[i].val.d.n;
+ cnfparamvals[i].bUsed = TRUE;
+ } else if(!strcmp(paramblk.descr[i].name, "internal.developeronly.options")) {
+ loadConf->globals.glblDevOptions = (uint64_t) cnfparamvals[i].val.d.n;
+ cnfparamvals[i].bUsed = TRUE;
+ } else if(!strcmp(paramblk.descr[i].name, "stdlog.channelspec")) {
+#ifndef ENABLE_LIBLOGGING_STDLOG
+ LogError(0, RS_RET_ERR, "rsyslog wasn't "
+ "compiled with liblogging-stdlog support. "
+ "The 'stdlog.channelspec' parameter "
+ "is ignored. Note: the syslog API is used instead.\n");
+#else
+ loadConf->globals.stdlog_chanspec = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ /* we need to re-open with the new channel */
+ stdlog_close(loadConf->globals.stdlog_hdl);
+ loadConf->globals.stdlog_hdl = stdlog_open("rsyslogd", 0, STDLOG_SYSLOG,
+ (char*) loadConf->globals.stdlog_chanspec);
+ cnfparamvals[i].bUsed = TRUE;
+#endif
+ } else if(!strcmp(paramblk.descr[i].name, "operatingstatefile")) {
+ if(loadConf->globals.operatingStateFile != NULL) {
+ LogError(errno, RS_RET_PARAM_ERROR,
+ "error: operatingStateFile already set to '%s' - "
+ "new value ignored", loadConf->globals.operatingStateFile);
+ } else {
+ loadConf->globals.operatingStateFile =
+ (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ osf_open();
+ }
+ } else if(!strcmp(paramblk.descr[i].name, "security.abortonidresolutionfail")) {
+ loadConf->globals.abortOnIDResolutionFail = (int) cnfparamvals[i].val.d.n;
+ cnfparamvals[i].bUsed = TRUE;
+ }
+ }
+done: return;
+}
+
+/* Set mainq parameters. Note that when this is not called, we'll use the
+ * legacy parameter config. mainq parameters can only be set once.
+ */
+void
+glblProcessMainQCnf(struct cnfobj *o)
+{
+ if(mainqCnfObj == NULL) {
+ mainqCnfObj = o;
+ } else {
+ LogError(0, RS_RET_ERR, "main_queue() object can only be specified "
+ "once - all but first ignored\n");
+ }
+}
+
+/* destruct the main q cnf object after it is no longer needed. This is
+ * also used to do some final checks.
+ */
+void
+glblDestructMainqCnfObj(void)
+{
+ /* Only destruct if not NULL! */
+ if (mainqCnfObj != NULL) {
+ nvlstChkUnused(mainqCnfObj->nvlst);
+ cnfobjDestruct(mainqCnfObj);
+ mainqCnfObj = NULL;
+ }
+}
+
+static int
+qs_arrcmp_glblDbgFiles(const void *s1, const void *s2)
+{
+ return strcmp(*((char**)s1), *((char**)s2));
+}
+
+/* set an environment variable */
+static rsRetVal
+do_setenv(const char *const var)
+{
+ char varname[128];
+ const char *val = var;
+ size_t i;
+ DEFiRet;
+
+ for(i = 0 ; *val != '=' ; ++i, ++val) {
+ if(i == sizeof(varname)-i) {
+ parser_errmsg("environment variable name too long "
+ "[max %zu chars] or malformed entry: '%s'",
+ sizeof(varname)-1, var);
+ ABORT_FINALIZE(RS_RET_ERR_SETENV);
+ }
+ if(*val == '\0') {
+ parser_errmsg("environment variable entry is missing "
+ "equal sign (for value): '%s'", var);
+ ABORT_FINALIZE(RS_RET_ERR_SETENV);
+ }
+ varname[i] = *val;
+ }
+ varname[i] = '\0';
+ ++val;
+ DBGPRINTF("do_setenv, var '%s', val '%s'\n", varname, val);
+
+ if(setenv(varname, val, 1) != 0) {
+ char errStr[1024];
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ parser_errmsg("error setting environment variable "
+ "'%s' to '%s': %s", varname, val, errStr);
+ ABORT_FINALIZE(RS_RET_ERR_SETENV);
+ }
+
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This processes the "regular" parameters which are to be set after the
+ * config has been fully loaded.
+ */
+rsRetVal
+glblDoneLoadCnf(void)
+{
+ int i;
+ unsigned char *cstr;
+ DEFiRet;
+ CHKiRet(objUse(net, CORE_COMPONENT));
+
+ sortTimezones(loadConf);
+ DBGPRINTF("Timezone information table (%d entries):\n", loadConf->timezones.ntzinfos);
+ displayTimezones(loadConf);
+
+ 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, "libcapng.default")) {
+#ifdef ENABLE_LIBCAPNG
+ loadConf->globals.bAbortOnFailedLibcapngSetup = (int) cnfparamvals[i].val.d.n;
+#else
+ LogError(0, RS_RET_ERR, "rsyslog wasn't "
+ "compiled with libcap-ng support.");
+#endif
+ } else if(!strcmp(paramblk.descr[i].name, "libcapng.enable")) {
+#ifdef ENABLE_LIBCAPNG
+ loadConf->globals.bCapabilityDropEnabled = (int) cnfparamvals[i].val.d.n;
+#else
+ LogError(0, RS_RET_ERR, "rsyslog wasn't "
+ "compiled with libcap-ng support.");
+#endif
+ } else if(!strcmp(paramblk.descr[i].name, "variables.casesensitive")) {
+ const int val = (int) cnfparamvals[i].val.d.n;
+ fjson_global_do_case_sensitive_comparison(val);
+ DBGPRINTF("global/config: set case sensitive variables to %d\n",
+ val);
+ } 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")) {
+ cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setDfltNetstrmDrvrKeyFile(NULL, cstr);
+ } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdrivercertfile")) {
+ cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setDfltNetstrmDrvrCertFile(NULL, cstr);
+ } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdrivercafile")) {
+ cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setDfltNetstrmDrvrCAF(NULL, cstr);
+ } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdrivercrlfile")) {
+ cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setDfltNetstrmDrvrCRLF(NULL, cstr);
+ } else if(!strcmp(paramblk.descr[i].name, "defaultnetstreamdriver")) {
+ cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setDfltNetstrmDrvr(NULL, cstr);
+ } else if(!strcmp(paramblk.descr[i].name, "netstreamdrivercaextrafiles")) {
+ cstr = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setNetstrmDrvrCAExtraFiles(NULL, cstr);
+ } else if(!strcmp(paramblk.descr[i].name, "preservefqdn")) {
+ bPreserveFQDN = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name,
+ "dropmsgswithmaliciousdnsptrrecords")) {
+ loadConf->globals.bDropMalPTRMsgs = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "action.reportsuspension")) {
+ loadConf->globals.bActionReportSuspension = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "action.reportsuspensioncontinuation")) {
+ loadConf->globals.bActionReportSuspensionCont = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "maxmessagesize")) {
+ setMaxLine(cnfparamvals[i].val.d.n);
+ } else if(!strcmp(paramblk.descr[i].name, "oversizemsg.errorfile")) {
+ free(loadConf->globals.oversizeMsgErrorFile);
+ loadConf->globals.oversizeMsgErrorFile = (uchar*)es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ } else if(!strcmp(paramblk.descr[i].name, "oversizemsg.report")) {
+ loadConf->globals.reportOversizeMsg = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "oversizemsg.input.mode")) {
+ const char *const tmp = es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setOversizeMsgInputMode((uchar*) tmp);
+ free((void*)tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "reportchildprocessexits")) {
+ const char *const tmp = es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setReportChildProcessExits((uchar*) tmp);
+ free((void*)tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "debug.onshutdown")) {
+ loadConf->globals.debugOnShutdown = (int) cnfparamvals[i].val.d.n;
+ LogError(0, RS_RET_OK, "debug: onShutdown set to %d", loadConf->globals.debugOnShutdown);
+ } else if(!strcmp(paramblk.descr[i].name, "debug.gnutls")) {
+ loadConf->globals.iGnuTLSLoglevel = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "debug.unloadmodules")) {
+ glblUnloadModules = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "parser.controlcharacterescapeprefix")) {
+ uchar* tmp = (uchar*) es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ setParserControlCharacterEscapePrefix(NULL, tmp);
+ free(tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.droptrailinglfonreception")) {
+ const int tmp = (int) cnfparamvals[i].val.d.n;
+ setParserDropTrailingLFOnReception(NULL, tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.escapecontrolcharactersonreceive")) {
+ const int tmp = (int) cnfparamvals[i].val.d.n;
+ setParserEscapeControlCharactersOnReceive(NULL, tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.spacelfonreceive")) {
+ const int tmp = (int) cnfparamvals[i].val.d.n;
+ setParserSpaceLFOnReceive(NULL, tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.escape8bitcharactersonreceive")) {
+ const int tmp = (int) cnfparamvals[i].val.d.n;
+ setParserEscape8BitCharactersOnReceive(NULL, tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.escapecontrolcharactertab")) {
+ const int tmp = (int) cnfparamvals[i].val.d.n;
+ setParserEscapeControlCharacterTab(NULL, tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.escapecontrolcharacterscstyle")) {
+ const int tmp = (int) cnfparamvals[i].val.d.n;
+ SetParserEscapeControlCharactersCStyle(tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.parsehostnameandtag")) {
+ const int tmp = (int) cnfparamvals[i].val.d.n;
+ SetParseHOSTNAMEandTAG(tmp);
+ } else if(!strcmp(paramblk.descr[i].name, "parser.permitslashinprogramname")) {
+ loadConf->globals.parser.bPermitSlashInProgramname = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "debug.logfile")) {
+ if(pszAltDbgFileName == NULL) {
+ pszAltDbgFileName = es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ /* can actually happen if debug system also opened altdbg */
+ if(altdbg != -1) {
+ close(altdbg);
+ }
+ if((altdbg = open(pszAltDbgFileName, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY
+ |O_CLOEXEC, S_IRUSR|S_IWUSR)) == -1) {
+ LogError(0, RS_RET_ERR, "debug log file '%s' could not be opened",
+ pszAltDbgFileName);
+ }
+ }
+ LogError(0, RS_RET_OK, "debug log file is '%s', fd %d", pszAltDbgFileName, altdbg);
+ } else if(!strcmp(paramblk.descr[i].name, "janitor.interval")) {
+ loadConf->globals.janitorInterval = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "net.ipprotocol")) {
+ char *proto = es_str2cstr(cnfparamvals[i].val.d.estr, NULL);
+ if(!strcmp(proto, "unspecified")) {
+ loadConf->globals.iDefPFFamily = PF_UNSPEC;
+ } else if(!strcmp(proto, "ipv4-only")) {
+ loadConf->globals.iDefPFFamily = PF_INET;
+ } else if(!strcmp(proto, "ipv6-only")) {
+ loadConf->globals.iDefPFFamily = PF_INET6;
+ } else{
+ LogError(0, RS_RET_ERR, "invalid net.ipprotocol "
+ "parameter '%s' -- ignored", proto);
+ }
+ free(proto);
+ } else if(!strcmp(paramblk.descr[i].name, "senders.reportnew")) {
+ loadConf->globals.reportNewSenders = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "senders.reportgoneaway")) {
+ loadConf->globals.reportGoneAwaySenders = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "senders.timeoutafter")) {
+ loadConf->globals.senderStatsTimeout = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "senders.keeptrack")) {
+ loadConf->globals.senderKeepTrack = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "inputs.timeout.shutdown")) {
+ loadConf->globals.inputTimeoutShutdown = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "privdrop.group.keepsupplemental")) {
+ loadConf->globals.gidDropPrivKeepSupplemental = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "privdrop.group.id")) {
+ loadConf->globals.gidDropPriv = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "privdrop.group.name")) {
+ loadConf->globals.gidDropPriv = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "privdrop.user.id")) {
+ loadConf->globals.uidDropPriv = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "privdrop.user.name")) {
+ loadConf->globals.uidDropPriv = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "security.abortonidresolutionfail")) {
+ loadConf->globals.abortOnIDResolutionFail = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "net.acladdhostnameonfail")) {
+ loadConf->globals.ACLAddHostnameOnFail = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "net.aclresolvehostname")) {
+ loadConf->globals.ACLDontResolve = !((int) cnfparamvals[i].val.d.n);
+ } else if(!strcmp(paramblk.descr[i].name, "net.enabledns")) {
+ SetDisableDNS(!((int) cnfparamvals[i].val.d.n));
+ } else if(!strcmp(paramblk.descr[i].name, "net.permitwarning")) {
+ SetOptionDisallowWarning(!((int) cnfparamvals[i].val.d.n));
+ } else if(!strcmp(paramblk.descr[i].name, "abortonuncleanconfig")) {
+ loadConf->globals.bAbortOnUncleanConfig = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "abortonfailedqueuestartup")) {
+ loadConf->globals.bAbortOnFailedQueueStartup = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "internalmsg.ratelimit.burst")) {
+ loadConf->globals.intMsgRateLimitBurst = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "internalmsg.ratelimit.interval")) {
+ loadConf->globals.intMsgRateLimitItv = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "internalmsg.severity")) {
+ loadConf->globals.intMsgsSeverityFilter = (int) cnfparamvals[i].val.d.n;
+ if((loadConf->globals.intMsgsSeverityFilter < 0) ||
+ (loadConf->globals.intMsgsSeverityFilter > 7)) {
+ parser_errmsg("invalid internalmsg.severity value");
+ loadConf->globals.intMsgsSeverityFilter = DFLT_INT_MSGS_SEV_FILTER;
+ }
+ } else if(!strcmp(paramblk.descr[i].name, "environment")) {
+ for(int j = 0 ; j < cnfparamvals[i].val.d.ar->nmemb ; ++j) {
+ char *const var = es_str2cstr(cnfparamvals[i].val.d.ar->arr[j], NULL);
+ do_setenv(var);
+ free(var);
+ }
+ } else if(!strcmp(paramblk.descr[i].name, "errormessagestostderr.maxnumber")) {
+ loadConf->globals.maxErrMsgToStderr = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "debug.files")) {
+ free(glblDbgFiles); /* "fix" Coverity false positive */
+ glblDbgFilesNum = cnfparamvals[i].val.d.ar->nmemb;
+ glblDbgFiles = (char**) malloc(cnfparamvals[i].val.d.ar->nmemb * sizeof(char*));
+ for(int j = 0 ; j < cnfparamvals[i].val.d.ar->nmemb ; ++j) {
+ glblDbgFiles[j] = es_str2cstr(cnfparamvals[i].val.d.ar->arr[j], NULL);
+ }
+ qsort(glblDbgFiles, glblDbgFilesNum, sizeof(char*), qs_arrcmp_glblDbgFiles);
+ } else if(!strcmp(paramblk.descr[i].name, "debug.whitelist")) {
+ glblDbgWhitelist = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "shutdown.queue.doublesize")) {
+ loadConf->globals.shutdownQueueDoubleSize = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "umask")) {
+ loadConf->globals.umask = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "shutdown.enable.ctlc")) {
+ loadConf->globals.permitCtlC = (int) cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.action.queue.timeoutshutdown")) {
+ loadConf->globals.actq_dflt_toQShutdown = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.action.queue.timeoutactioncompletion")) {
+ loadConf->globals.actq_dflt_toActShutdown = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.action.queue.timeoutenqueue")) {
+ loadConf->globals.actq_dflt_toEnq = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.action.queue.timeoutworkerthreadshutdown")) {
+ loadConf->globals.actq_dflt_toWrkShutdown = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.ruleset.queue.timeoutshutdown")) {
+ loadConf->globals.ruleset_dflt_toQShutdown = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.ruleset.queue.timeoutactioncompletion")) {
+ loadConf->globals.ruleset_dflt_toActShutdown = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.ruleset.queue.timeoutenqueue")) {
+ loadConf->globals.ruleset_dflt_toEnq = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "default.ruleset.queue.timeoutworkerthreadshutdown")) {
+ loadConf->globals.ruleset_dflt_toWrkShutdown = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "reverselookup.cache.ttl.default")) {
+ loadConf->globals.dnscacheDefaultTTL = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "reverselookup.cache.ttl.enable")) {
+ loadConf->globals.dnscacheEnableTTL = cnfparamvals[i].val.d.n;
+ } else if(!strcmp(paramblk.descr[i].name, "parser.supportcompressionextension")) {
+ loadConf->globals.bSupportCompressionExtension = cnfparamvals[i].val.d.n;
+ } else {
+ dbgprintf("glblDoneLoadCnf: program error, non-handled "
+ "param '%s'\n", paramblk.descr[i].name);
+ }
+ }
+
+ if(loadConf->globals.debugOnShutdown && Debug != DEBUG_FULL) {
+ Debug = DEBUG_ONDEMAND;
+ stddbg = -1;
+ }
+
+finalize_it:
+ /* we have now read the config. We need to query the local host name now
+ * as it was set by the config.
+ *
+ * Note: early messages are already emited, and have "[localhost]" as
+ * hostname. These messages are currently in iminternal queue. Once they
+ * are taken from that queue, the hostname will be adapted.
+ */
+ queryLocalHostname(loadConf);
+ RETiRet;
+}
+
+
+/* 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));
+
+ /* intialize properties */
+ storeLocalHostIPIF((uchar*)"127.0.0.1");
+
+ /* 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, SetDropMalPTRMsgs,
+ NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriver", 0, eCmdHdlrGetWord, setDfltNetstrmDrvr, NULL,
+ NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercafile", 0, eCmdHdlrGetWord,
+ setDfltNetstrmDrvrCAF, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercrlfile", 0, eCmdHdlrGetWord,
+ setDfltNetstrmDrvrCRLF, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdriverkeyfile", 0, eCmdHdlrGetWord,
+ setDfltNetstrmDrvrKeyFile, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"defaultnetstreamdrivercertfile", 0, eCmdHdlrGetWord,
+ setDfltNetstrmDrvrCertFile, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"localhostname", 0, eCmdHdlrGetWord, NULL, &LocalHostNameOverride, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"localhostipif", 0, eCmdHdlrGetWord, setLocalHostIPIF, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"netstreamdrivercaextrafiles", 0, eCmdHdlrGetWord, setNetstrmDrvrCAExtraFiles,
+ NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"optimizeforuniprocessor", 0, eCmdHdlrGoneAway, NULL, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"preservefqdn", 0, eCmdHdlrBinary, NULL, &bPreserveFQDN, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"maxmessagesize", 0, eCmdHdlrSize, legacySetMaxMessageSize, NULL, NULL));
+
+ /* Deprecated parser config options */
+ CHKiRet(regCfSysLineHdlr((uchar *)"controlcharacterescapeprefix", 0, eCmdHdlrGetChar,
+ setParserControlCharacterEscapePrefix, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary,
+ setParserDropTrailingLFOnReception, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactersonreceive", 0, eCmdHdlrBinary,
+ setParserEscapeControlCharactersOnReceive, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"spacelfonreceive", 0, eCmdHdlrBinary,
+ setParserSpaceLFOnReceive, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"escape8bitcharactersonreceive", 0, eCmdHdlrBinary,
+ setParserEscape8BitCharactersOnReceive, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactertab", 0, eCmdHdlrBinary,
+ setParserEscapeControlCharacterTab, NULL, 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(LocalDomain);
+ free(LocalHostName);
+ free(LocalHostNameOverride);
+ free(LocalFQDNName);
+ objRelease(prop, CORE_COMPONENT);
+ if(propLocalHostNameToDelete != NULL)
+ prop.Destruct(&propLocalHostNameToDelete);
+ DESTROY_ATOMIC_HELPER_MUT(mutTerminateInputs);
+ENDObjClassExit(glbl)
diff --git a/runtime/glbl.h b/runtime/glbl.h
new file mode 100644
index 0000000..d75d867
--- /dev/null
+++ b/runtime/glbl.h
@@ -0,0 +1,159 @@
+/* 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-2022 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>
+#ifdef ENABLE_LIBLOGGING_STDLOG
+#include <liblogging/stdlog.h>
+#endif
+#include "rainerscript.h"
+#include "prop.h"
+
+#define glblGetIOBufSize() 4096 /* size of the IO buffer, e.g. for strm class */
+#define glblOversizeMsgInputMode_Truncate 0
+#define glblOversizeMsgInputMode_Split 1
+#define glblOversizeMsgInputMode_Accept 2
+
+extern pid_t glbl_ourpid;
+
+/* interfaces */
+BEGINinterface(glbl) /* name must also be changed in ENDinterface macro! */
+ uchar* (*GetWorkDir)(rsconf_t *cnf);
+ int (*GetMaxLine)(rsconf_t *cnf);
+#define SIMP_PROP(name, dataType) \
+ dataType (*Get##name)(void); \
+ rsRetVal (*Set##name)(dataType);
+ SIMP_PROP(OptimizeUniProc, int)
+ SIMP_PROP(PreserveFQDN, int)
+ SIMP_PROP(LocalFQDNName, uchar*)
+ SIMP_PROP(mainqCnfObj, struct cnfobj*)
+ SIMP_PROP(LocalHostName, uchar*)
+ SIMP_PROP(LocalDomain, 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 */
+ /* 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] */
+ /* v9 - 2015-01-12 SetMaxLine method removed */
+ /* v10 - global variables should be moved to the rsconf_t data structure, so
+ * dynamic configuration reload can be introduced. This is why each getter needs additional
+ * parameter specifying a configuration it belongs to(either loadConf or runConf)
+ */
+#undef SIMP_PROP
+#define SIMP_PROP(name, dataType) \
+ dataType (*Get##name)(rsconf_t *cnf); \
+ rsRetVal (*Set##name)(dataType);
+
+ SIMP_PROP(DropMalPTRMsgs, int)
+ SIMP_PROP(DfltNetstrmDrvrCAF, uchar*)
+ SIMP_PROP(DfltNetstrmDrvrCRLF, uchar*)
+ SIMP_PROP(DfltNetstrmDrvrCertFile, uchar*)
+ SIMP_PROP(DfltNetstrmDrvrKeyFile, uchar*)
+ SIMP_PROP(DfltNetstrmDrvr, uchar*)
+ SIMP_PROP(DefPFFamily, int)
+ SIMP_PROP(DisableDNS, int)
+ SIMP_PROP(NetstrmDrvrCAExtraFiles, uchar*)
+ SIMP_PROP(ParserControlCharacterEscapePrefix, uchar)
+ SIMP_PROP(ParserDropTrailingLFOnReception, int)
+ SIMP_PROP(ParserEscapeControlCharactersOnReceive, int)
+ SIMP_PROP(ParserSpaceLFOnReceive, int)
+ SIMP_PROP(ParserEscape8BitCharactersOnReceive, int)
+ SIMP_PROP(ParserEscapeControlCharacterTab, int)
+ SIMP_PROP(ParserEscapeControlCharactersCStyle, int)
+ SIMP_PROP(ParseHOSTNAMEandTAG, int)
+ SIMP_PROP(OptionDisallowWarning, int)
+
+#undef SIMP_PROP
+ENDinterface(glbl)
+#define glblCURR_IF_VERSION 10 /* increment whenever you change the interface structure! */
+/* version 2 had PreserveFQDN added - rgerhards, 2008-12-08 */
+
+/* the remaining prototypes */
+PROTOTYPEObj(glbl);
+
+extern int glblUnloadModules;
+extern short janitorInterval;
+extern char** glblDbgFiles;
+extern size_t glblDbgFilesNum;
+extern int glblDbgWhitelist;
+extern int glblPermitCtlC;
+extern int bTerminateInputs;
+#ifndef HAVE_ATOMIC_BUILTINS
+extern DEF_ATOMIC_HELPER_MUT(mutTerminateInputs);
+#endif
+
+/* Developer options enable some strange things for developer-only testing.
+ * These should never be enabled in a user build, except if explicitly told
+ * by a developer. The options are acutally flags, so they should be powers
+ * of two. Flag assignment may change between versions, **backward
+ * compatibility is NOT necessary**.
+ * rgerhards, 2018-04-28
+ */
+#define DEV_OPTION_KEEP_RUNNING_ON_HARD_CONF_ERROR 1
+#define DEV_OPTION_8_1905_HANG_TEST 2 // TODO: remove - temporary for bughunt
+
+#define glblGetOurPid() glbl_ourpid
+#define glblSetOurPid(pid) { glbl_ourpid = (pid); }
+
+void glblPrepCnf(void);
+void glblProcessCnf(struct cnfobj *o);
+void glblProcessMainQCnf(struct cnfobj *o);
+void glblDestructMainqCnfObj(void);
+rsRetVal glblDoneLoadCnf(void);
+const uchar * glblGetWorkDirRaw(rsconf_t *cnf);
+int GetGnuTLSLoglevel(rsconf_t *cnf);
+int glblGetMaxLine(rsconf_t *cnf);
+int bs_arrcmp_glblDbgFiles(const void *s1, const void *s2);
+uchar* glblGetOversizeMsgErrorFile(rsconf_t *cnf);
+const uchar* glblGetOperatingStateFile(rsconf_t *cnf);
+int glblGetOversizeMsgInputMode(rsconf_t *cnf);
+int glblReportOversizeMessage(rsconf_t *cnf);
+void glblReportChildProcessExit(rsconf_t *cnf, const uchar *name, pid_t pid, int status);
+uchar *glblGetLocalHostName(void);
+
+#endif /* #ifndef GLBL_H_INCLUDED */
diff --git a/runtime/gss-misc.c b/runtime/gss-misc.c
new file mode 100644
index 0000000..cb7ca9b
--- /dev/null
+++ b/runtime/gss-misc.c
@@ -0,0 +1,315 @@
+/* gss-misc.c
+ * This is a miscellaneous helper class for gss-api features.
+ *
+ * Copyright 2007-2017 Rainer Gerhards and Adiscon GmbH.
+ *
+ * This file is part of rsyslog.
+ *
+ * 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.
+ */
+#include "config.h"
+#include "rsyslog.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <netdb.h>
+#include <arpa/inet.h>
+#include <fnmatch.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <gssapi/gssapi.h>
+#include "dirty.h"
+#include "syslogd-types.h"
+#include "srUtils.h"
+#include "net.h"
+#include "template.h"
+#include "msg.h"
+#include "module-template.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "gss-misc.h"
+#include "debug.h"
+#include "glbl.h"
+#include "unlimited_select.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+static void display_status_(char *m, OM_uint32 code, int type)
+{
+ OM_uint32 maj_stat, min_stat, msg_ctx = 0;
+ gss_buffer_desc msg;
+
+ do {
+ maj_stat = gss_display_status(&min_stat, code, type, GSS_C_NO_OID, &msg_ctx, &msg);
+ if (maj_stat != GSS_S_COMPLETE) {
+ LogError(0, NO_ERRCODE, "GSS-API error in gss_display_status called from <%s>\n", m);
+ break;
+ } else {
+ char buf[1024];
+ snprintf(buf, sizeof(buf), "GSS-API error %s: %s\n", m, (char *) msg.value);
+ buf[sizeof(buf) - 1] = '\0';
+ LogError(0, NO_ERRCODE, "%s", buf);
+ }
+ if (msg.length != 0)
+ gss_release_buffer(&min_stat, &msg);
+ } while (msg_ctx);
+}
+
+
+static void display_status(char *m, OM_uint32 maj_stat, OM_uint32 min_stat)
+{
+ display_status_(m, maj_stat, GSS_C_GSS_CODE);
+ display_status_(m, min_stat, GSS_C_MECH_CODE);
+}
+
+
+static void display_ctx_flags(OM_uint32 flags)
+{
+ if (flags & GSS_C_DELEG_FLAG)
+ dbgprintf("GSS_C_DELEG_FLAG\n");
+ if (flags & GSS_C_MUTUAL_FLAG)
+ dbgprintf("GSS_C_MUTUAL_FLAG\n");
+ if (flags & GSS_C_REPLAY_FLAG)
+ dbgprintf("GSS_C_REPLAY_FLAG\n");
+ if (flags & GSS_C_SEQUENCE_FLAG)
+ dbgprintf("GSS_C_SEQUENCE_FLAG\n");
+ if (flags & GSS_C_CONF_FLAG)
+ dbgprintf("GSS_C_CONF_FLAG\n");
+ if (flags & GSS_C_INTEG_FLAG)
+ dbgprintf("GSS_C_INTEG_FLAG\n");
+}
+
+
+static int read_all(int fd, char *buf, unsigned int nbyte)
+{
+ int ret;
+ char *ptr;
+ struct timeval tv;
+#ifdef USE_UNLIMITED_SELECT
+ fd_set *pRfds = malloc(glbl.GetFdSetSize());
+
+ if (pRfds == NULL)
+ return -1;
+#else
+ fd_set rfds;
+ fd_set *pRfds = &rfds;
+#endif
+
+ for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) {
+ FD_ZERO(pRfds);
+ FD_SET(fd, pRfds);
+ tv.tv_sec = 1;
+ tv.tv_usec = 0;
+
+ if ((ret = select(FD_SETSIZE, pRfds, NULL, NULL, &tv)) <= 0
+ || !FD_ISSET(fd, pRfds)) {
+ freeFdSet(pRfds);
+ return ret;
+ }
+ ret = recv(fd, ptr, nbyte, 0);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ freeFdSet(pRfds);
+ return (ret);
+ } else if (ret == 0) {
+ freeFdSet(pRfds);
+ return (ptr - buf);
+ }
+ }
+
+ freeFdSet(pRfds);
+ return (ptr - buf);
+}
+
+
+static int write_all(int fd, char *buf, unsigned int nbyte)
+{
+ int ret;
+ char *ptr;
+
+ for (ptr = buf; nbyte; ptr += ret, nbyte -= ret) {
+ ret = send(fd, ptr, nbyte, 0);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ return (ret);
+ } else if (ret == 0) {
+ return (ptr - buf);
+ }
+ }
+
+ return (ptr - buf);
+}
+
+
+static int recv_token(int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4] = "xxx"; // initialized to make clang static analyzer happy
+ unsigned int len;
+
+ ret = read_all(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ LogError(0, NO_ERRCODE, "GSS-API error reading token length");
+ return -1;
+ } else if (!ret) {
+ return 0;
+ } else if (ret != 4) {
+ LogError(0, NO_ERRCODE, "GSS-API error reading token length");
+ return -1;
+ }
+
+ len = ((lenbuf[0] << 24)
+ | (lenbuf[1] << 16)
+ | (lenbuf[2] << 8)
+ | lenbuf[3]);
+ tok->length = ntohl(len);
+
+ tok->value = (char *) malloc(tok->length ? tok->length : 1);
+ if (tok->length && tok->value == NULL) {
+ LogError(0, NO_ERRCODE, "Out of memory allocating token data\n");
+ return -1;
+ }
+
+ ret = read_all(s, (char *) tok->value, tok->length);
+ if (ret < 0) {
+ LogError(0, NO_ERRCODE, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ } else if (ret != (int) tok->length) {
+ LogError(0, NO_ERRCODE, "GSS-API error reading token data");
+ free(tok->value);
+ return -1;
+ }
+
+ return 1;
+}
+
+
+static int send_token(int s, gss_buffer_t tok)
+{
+ int ret;
+ unsigned char lenbuf[4];
+ unsigned int len;
+
+ if (tok->length > 0xffffffffUL)
+ abort(); /* TODO: we need to reconsider this, abort() is not really
+ a solution - degrade, but keep running */
+ len = htonl(tok->length);
+ lenbuf[0] = (len >> 24) & 0xff;
+ lenbuf[1] = (len >> 16) & 0xff;
+ lenbuf[2] = (len >> 8) & 0xff;
+ lenbuf[3] = len & 0xff;
+
+ ret = write_all(s, (char *) lenbuf, 4);
+ if (ret < 0) {
+ LogError(0, NO_ERRCODE, "GSS-API error sending token length");
+ return -1;
+ } else if (ret != 4) {
+ LogError(0, NO_ERRCODE, "GSS-API error sending token length");
+ return -1;
+ }
+
+ ret = write_all(s, tok->value, tok->length);
+ if (ret < 0) {
+ LogError(0, NO_ERRCODE, "GSS-API error sending token data");
+ return -1;
+ } else if (ret != (int) tok->length) {
+ LogError(0, NO_ERRCODE, "GSS-API error sending token data");
+ return -1;
+ }
+
+ return 0;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(gssutil)
+CODESTARTobjQueryInterface(gssutil)
+ if(pIf->ifVersion != gssutilCURR_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->recv_token = recv_token;
+ pIf->send_token = send_token;
+ pIf->display_status = display_status;
+ pIf->display_ctx_flags = display_ctx_flags;
+
+finalize_it:
+ENDobjQueryInterface(gssutil)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(gssutil, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(gssutil)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ENDObjClassExit(gssutil)
+
+
+/* Initialize our class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-29
+ */
+BEGINAbstractObjClassInit(gssutil, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ENDObjClassInit(gssutil)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ gssutilClassExit();
+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(gssutilClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
diff --git a/runtime/gss-misc.h b/runtime/gss-misc.h
new file mode 100644
index 0000000..4c8a5ec
--- /dev/null
+++ b/runtime/gss-misc.h
@@ -0,0 +1,44 @@
+/* Definitions for gssutil class. This implements a session of the
+ * plain TCP server.
+ *
+ * Copyright 2008 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.
+ */
+#ifndef GSS_MISC_H_INCLUDED
+#define GSS_MISC_H_INCLUDED 1
+
+#include <gssapi/gssapi.h>
+#include "obj.h"
+
+/* interfaces */
+BEGINinterface(gssutil) /* name must also be changed in ENDinterface macro! */
+ int (*recv_token)(int s, gss_buffer_t tok);
+ int (*send_token)(int s, gss_buffer_t tok);
+ void (*display_status)(char *m, OM_uint32 maj_stat, OM_uint32 min_stat);
+ void (*display_ctx_flags)(OM_uint32 flags);
+ENDinterface(gssutil)
+#define gssutilCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+PROTOTYPEObj(gssutil);
+
+/* the name of our library binary */
+#define LM_GSSUTIL_FILENAME "lmgssutil"
+
+#endif /* #ifndef GSS_MISC_H_INCLUDED */
diff --git a/runtime/hashtable.c b/runtime/hashtable.c
new file mode 100644
index 0000000..4673a50
--- /dev/null
+++ b/runtime/hashtable.c
@@ -0,0 +1,341 @@
+/* Copyright (C) 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+/* taken from http://www.cl.cam.ac.uk/~cwc22/hashtable/ */
+
+#include "hashtable_private.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+
+/* this code has several warnings, but we ignore them because
+ * this seems to work and we do not want to engage in that code body. If
+ * we really run into troubles, it is better to change to libfastjson, which
+ * we should do in the medium to long term anyhow...
+ */
+#ifdef __GNUC__
+#pragma GCC diagnostic ignored "-Wredundant-decls"
+#endif
+
+/*
+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;
+}
+
+/*****************************************************************************/
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunknown-attributes"
+#endif
+unsigned int
+#if defined(__clang__)
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+#endif
+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!
+ */
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunknown-attributes"
+#endif
+unsigned __attribute__((nonnull(1))) int
+#if defined(__clang__)
+__attribute__((no_sanitize("unsigned-integer-overflow")))
+#endif
+hash_from_string(void *k)
+{
+ char *rkey = (char*) k;
+ unsigned hashval = 1;
+
+ while (*rkey)
+ 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 0000000..4a55d67
--- /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);
+
+/*
+ * 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 __attribute__((nonnull(1))) int hash_from_string(void *k) ;
+int key_equals_string(void *key1, void *key2);
+
+#endif /* __HASHTABLE_CWC22_H__ */
diff --git a/runtime/hashtable_itr.c b/runtime/hashtable_itr.c
new file mode 100644
index 0000000..a065f12
--- /dev/null
+++ b/runtime/hashtable_itr.c
@@ -0,0 +1,189 @@
+/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#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 0000000..91dee6e
--- /dev/null
+++ b/runtime/hashtable_itr.h
@@ -0,0 +1,103 @@
+/* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> */
+
+#ifndef __HASHTABLE_ITR_CWC22__
+#define __HASHTABLE_ITR_CWC22__
+#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 */
+
+#define hashtable_iterator_key(i) ((i)->e->k)
+
+/*****************************************************************************/
+/* value - return the value of the (key,value) pair at the current position */
+
+#define hashtable_iterator_value(i) ((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 0000000..4a2d978
--- /dev/null
+++ b/runtime/hashtable_private.h
@@ -0,0 +1,75 @@
+/* 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 */
+#define indexFor(tablelength, hashvalue) ((hashvalue) % (tablelength))
+
+
+/*****************************************************************************/
+#define freekey(X) free(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 0000000..7ba1d25
--- /dev/null
+++ b/runtime/im-helper.h
@@ -0,0 +1,62 @@
+/* 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-2016 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 */
diff --git a/runtime/janitor.c b/runtime/janitor.c
new file mode 100644
index 0000000..e7a6bd3
--- /dev/null
+++ b/runtime/janitor.c
@@ -0,0 +1,103 @@
+/* janitor.c - rsyslog's janitor
+ *
+ * The rsyslog janitor can be used to periodically clean out
+ * resources. It was initially developed to close files that
+ * were not written to for some time (omfile plugin), but has
+ * a generic interface that can be used for all similar tasks.
+ *
+ * Module begun 2014-05-15 by Rainer Gerhards
+ *
+ * Copyright (C) 2014-2015 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 <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <pthread.h>
+
+#include "rsyslog.h"
+#include "janitor.h"
+
+static struct janitorEtry *janitorRoot = NULL; /* TODO: move to runConf? */
+static pthread_mutex_t janitorMut = PTHREAD_MUTEX_INITIALIZER;
+
+rsRetVal
+janitorAddEtry(void (*cb)(void*), const char *id, void *pUsr)
+{
+ struct janitorEtry *etry = NULL;
+ DEFiRet;
+ CHKmalloc(etry = malloc(sizeof(struct janitorEtry)));
+ CHKmalloc(etry->id = strdup(id));
+ etry->pUsr = pUsr;
+ etry->cb = cb;
+ etry->next = janitorRoot;
+ pthread_mutex_lock(&janitorMut);
+ janitorRoot = etry;
+ pthread_mutex_unlock(&janitorMut);
+ DBGPRINTF("janitor: entry %p, id '%s' added\n", etry, id);
+finalize_it:
+ if(iRet != RS_RET_OK && etry != NULL)
+ free(etry);
+ RETiRet;
+}
+
+rsRetVal
+janitorDelEtry(const char *__restrict__ const id)
+{
+ struct janitorEtry *curr, *prev = NULL;
+ DEFiRet;
+
+ pthread_mutex_lock(&janitorMut);
+ for(curr = janitorRoot ; curr != NULL ; curr = curr->next) {
+ if(!strcmp(curr->id, id)) {
+ if(prev == NULL) {
+ janitorRoot = curr->next;
+ } else {
+ prev->next = curr->next;
+ }
+ free(curr->id);
+ free(curr);
+ DBGPRINTF("janitor: deleted entry '%s'\n", id);
+ ABORT_FINALIZE(RS_RET_OK);
+ }
+ prev = curr;
+ }
+ DBGPRINTF("janitor: to be deleted entry '%s' not found\n", id);
+ iRet = RS_RET_NOT_FOUND;
+finalize_it:
+ pthread_mutex_unlock(&janitorMut);
+ RETiRet;
+}
+
+/* run the janitor; all entries are processed */
+void
+janitorRun(void)
+{
+ struct janitorEtry *curr;
+
+ dbgprintf("janitorRun() called\n");
+ pthread_mutex_lock(&janitorMut);
+ for(curr = janitorRoot ; curr != NULL ; curr = curr->next) {
+ DBGPRINTF("janitor: processing entry %p, id '%s'\n",
+ curr, curr->id);
+ curr->cb(curr->pUsr);
+ }
+ pthread_mutex_unlock(&janitorMut);
+}
diff --git a/runtime/janitor.h b/runtime/janitor.h
new file mode 100644
index 0000000..69a2bd5
--- /dev/null
+++ b/runtime/janitor.h
@@ -0,0 +1,35 @@
+/* rsyslog's janitor
+ *
+ * Copyright (C) 2014 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_JANITOR_H
+#define INCLUDED_JANITOR_H
+
+struct janitorEtry {
+ struct janitorEtry *next;
+ char *id; /* ID used to remove entry */
+ void (*cb)(void *pUsr);
+ void *pUsr; /* user-settable pointer (passed to callback) */
+};
+
+rsRetVal janitorAddEtry(void (*cb)(void*), const char *id, void *pUsr);
+rsRetVal janitorDelEtry(const char *__restrict__ const id);
+void janitorRun(void);
+
+#endif /* #ifndef INCLUDED_JANITOR_H */
diff --git a/runtime/lib_ksi_queue.c b/runtime/lib_ksi_queue.c
new file mode 100644
index 0000000..f407156
--- /dev/null
+++ b/runtime/lib_ksi_queue.c
@@ -0,0 +1,220 @@
+#include <malloc.h>
+#include <time.h>
+#include <errno.h>
+#include "lib_ksi_queue.h"
+
+RingBuffer* RingBuffer_new(size_t size) {
+ RingBuffer *p = calloc(1, sizeof (RingBuffer));
+ if (!p)
+ return NULL;
+
+ p->buffer = calloc(size, sizeof (void*));
+ p->size = size;
+ return p;
+}
+
+void RingBuffer_free(RingBuffer* this) {
+ if (this->buffer != NULL)
+ free(this->buffer);
+ free(this);
+}
+
+static bool RingBuffer_grow(RingBuffer* this) {
+ void **pTmp = calloc(this->size * RB_GROW_FACTOR, sizeof (void*));
+ void *pTmpItem = NULL;
+ if (!pTmp)
+ return false;
+
+ for (size_t i = 0; i < this->size; ++i) {
+ RingBuffer_popFront(this, &pTmpItem);
+ pTmp[i] = pTmpItem;
+ }
+
+ free(this->buffer);
+ this->buffer = pTmp;
+ this->head = 0;
+ this->tail = this->size;
+ this->count = this->size;
+ this->size = this->size * RB_GROW_FACTOR;
+ return true;
+}
+
+bool RingBuffer_pushBack(RingBuffer* this, void* item) {
+
+ if (this->size == this->count && !RingBuffer_grow(this))
+ return false;
+
+ if(this->size == 0)
+ return false;
+
+ this->buffer[this->tail] = item;
+ this->tail = (this->tail + 1) % this->size;
+ this->count += 1;
+ return true;
+}
+
+bool RingBuffer_popFront(RingBuffer* this, void** item) {
+ if (this->count == 0)
+ return false;
+
+ *item = this->buffer[this->head];
+ this->buffer[this->head] = NULL;
+ this->count -= 1;
+ this->head = (this->head + 1) % this->size;
+ return true;
+}
+
+bool RingBuffer_peekFront(RingBuffer* this, void** item) {
+ if (this->count == 0)
+ return false;
+
+ *item = this->buffer[this->head];
+ return true;
+}
+
+size_t RingBuffer_count(RingBuffer* this) {
+ return this->count;
+}
+
+bool RingBuffer_getItem(RingBuffer* this, size_t index, void** item) {
+ if (this->count == 0 || index >= this->count)
+ return false;
+
+ *item = this->buffer[(this->head + index) % this->size];
+ return true;
+}
+
+
+ProtectedQueue* ProtectedQueue_new(size_t queueSize) {
+ ProtectedQueue *p = calloc(1, sizeof (ProtectedQueue));
+ if (!p)
+ return NULL;
+
+ pthread_mutex_init(&p->mutex, 0);
+ p->bStop = false;
+ p->workItems = RingBuffer_new(queueSize);
+ return p;
+}
+
+void ProtectedQueue_free(ProtectedQueue* this) {
+ pthread_mutex_destroy(&this->mutex);
+ pthread_cond_destroy(&this->condition);
+ this->bStop = true;
+ RingBuffer_free(this->workItems);
+ free(this);
+}
+
+/// Signal stop. All threads waiting in FetchItme will be returned false from FetchItem
+
+void ProtectedQueue_stop(ProtectedQueue* this) {
+ this->bStop = true;
+ pthread_cond_broadcast(&this->condition);
+}
+
+/// Atomically adds an item into work item queue and releases a thread waiting
+/// in FetchItem
+
+bool ProtectedQueue_addItem(ProtectedQueue* this, void* item) {
+ bool ret = false;
+
+ if (this->bStop)
+ return false;
+
+ pthread_mutex_lock(&this->mutex);
+ if ((ret = RingBuffer_pushBack(this->workItems, item)) == true)
+ pthread_cond_signal(&this->condition);
+ pthread_mutex_unlock(&this->mutex);
+ return ret;
+}
+
+bool ProtectedQueue_peekFront(ProtectedQueue* this, void** item) {
+ bool ret;
+ pthread_mutex_lock(&this->mutex);
+ ret = RingBuffer_peekFront(this->workItems, item);
+ pthread_mutex_unlock(&this->mutex);
+ return ret;
+}
+
+bool ProtectedQueue_popFront(ProtectedQueue* this, void** item) {
+ bool ret;
+ pthread_mutex_lock(&this->mutex);
+ ret = RingBuffer_popFront(this->workItems, item);
+ pthread_mutex_unlock(&this->mutex);
+ return ret;
+}
+
+size_t ProtectedQueue_popFrontBatch(ProtectedQueue* this, void** items, size_t bufSize) {
+ size_t i;
+ pthread_mutex_lock(&this->mutex);
+ for (i = 0; RingBuffer_count(this->workItems) > 0 && i < bufSize; ++i)
+ RingBuffer_popFront(this->workItems, items[i]);
+ pthread_mutex_unlock(&this->mutex);
+ return i;
+}
+
+bool ProtectedQueue_getItem(ProtectedQueue* this, size_t index, void** item) {
+ bool ret=false;
+ pthread_mutex_lock(&this->mutex);
+ ret=RingBuffer_getItem(this->workItems, index, item);
+ pthread_mutex_unlock(&this->mutex);
+ return ret;
+}
+
+/* Waits for a new work item or timeout (if specified). Returns 0 in case of exit
+ * condition, 1 if item became available and ETIMEDOUT in case of timeout. */
+int ProtectedQueue_waitForItem(ProtectedQueue* this, void** item, uint64_t timeout) {
+ struct timespec ts;
+ pthread_mutex_lock(&this->mutex);
+
+ if (timeout > 0) {
+ clock_gettime(CLOCK_REALTIME, &ts);
+ ts.tv_sec += timeout / 1000LL;
+ ts.tv_nsec += (timeout % 1000LL)*1000LL;
+ }
+
+ if (timeout) {
+ if (pthread_cond_timedwait(&this->condition, &this->mutex, &ts) == ETIMEDOUT) {
+ pthread_mutex_unlock(&this->mutex);
+ return ETIMEDOUT;
+ }
+ } else
+ pthread_cond_wait(&this->condition, &this->mutex);
+ if (this->bStop) {
+ pthread_mutex_unlock(&this->mutex);
+ return 0;
+ }
+
+ if (RingBuffer_count(this->workItems) != 0 && item != NULL)
+ RingBuffer_popFront(this->workItems, item);
+
+ pthread_mutex_unlock(&this->mutex);
+
+ return 1;
+}
+
+size_t ProtectedQueue_count(ProtectedQueue* this) {
+ size_t nCount;
+ pthread_mutex_lock(&this->mutex);
+ nCount = RingBuffer_count(this->workItems);
+ pthread_mutex_unlock(&this->mutex);
+ return nCount;
+}
+
+void *worker_thread_main(void *arg) {
+ int res;
+ void* item;
+ WorkerThreadContext* tc = (WorkerThreadContext*) arg;
+
+ while (1) {
+ item = NULL;
+ res = ProtectedQueue_waitForItem(tc->queue, &item, tc->timeout);
+ if (tc->queue->bStop)
+ return NULL;
+
+ if (res == ETIMEDOUT) {
+ if (!tc->timeoutFunc())
+ return NULL;
+ } else if (item != NULL && !tc->workerFunc(item))
+ return NULL;
+ }
+}
diff --git a/runtime/lib_ksi_queue.h b/runtime/lib_ksi_queue.h
new file mode 100644
index 0000000..20ad680
--- /dev/null
+++ b/runtime/lib_ksi_queue.h
@@ -0,0 +1,53 @@
+#ifndef INCLUDED_LIBRSKSI_QUEUE_H
+#define INCLUDED_LIBRSKSI_QUEUE_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <pthread.h>
+
+#define RB_GROW_FACTOR 2
+
+typedef struct RingBuffer_st {
+ void **buffer;
+ size_t size;
+ size_t count;
+ size_t head;
+ size_t tail;
+} RingBuffer;
+
+RingBuffer* RingBuffer_new(size_t size);
+void RingBuffer_free(RingBuffer* this);
+bool RingBuffer_pushBack(RingBuffer* this, void* item);
+bool RingBuffer_popFront(RingBuffer* this, void** item);
+bool RingBuffer_peekFront(RingBuffer* this, void** item);
+bool RingBuffer_getItem(RingBuffer* this, size_t index, void** item);
+size_t RingBuffer_count(RingBuffer* this);
+
+typedef struct ProtectedQueue_st {
+ bool bStop;
+ RingBuffer *workItems;
+ pthread_mutex_t mutex;
+ pthread_cond_t condition;
+} ProtectedQueue;
+
+ProtectedQueue* ProtectedQueue_new(size_t queueSize);
+void ProtectedQueue_free(ProtectedQueue* this);
+void ProtectedQueue_stop(ProtectedQueue* this);
+bool ProtectedQueue_addItem(ProtectedQueue* this, void* item);
+bool ProtectedQueue_peekFront(ProtectedQueue* this, void** item);
+bool ProtectedQueue_popFront(ProtectedQueue* this, void** item);
+size_t ProtectedQueue_popFrontBatch(ProtectedQueue* this, void** items, size_t bufSize);
+int ProtectedQueue_waitForItem(ProtectedQueue* this, void** item, uint64_t timeout);
+size_t ProtectedQueue_count(ProtectedQueue* this);
+bool ProtectedQueue_getItem(ProtectedQueue* this, size_t index, void** item);
+
+typedef struct WorkerThreadContext_st {
+ bool (*workerFunc)(void*);
+ bool (*timeoutFunc)(void);
+ ProtectedQueue* queue;
+ unsigned timeout;
+} WorkerThreadContext;
+
+void *worker_thread_main(void *arg);
+
+#endif //INCLUDED_LIBRSKSI_QUEUE_H
diff --git a/runtime/lib_ksils12.c b/runtime/lib_ksils12.c
new file mode 100644
index 0000000..489f7fd
--- /dev/null
+++ b/runtime/lib_ksils12.c
@@ -0,0 +1,2173 @@
+/* lib_ksils12.c - rsyslog's KSI-LS12 support library
+ *
+ * Regarding the online algorithm for Merkle tree signing. Expected
+ * calling sequence is:
+ *
+ * sigblkConstruct
+ * for each signature block:
+ * sigblkInitKSI
+ * for each record:
+ * sigblkAddRecordKSI
+ * sigblkFinishKSI
+ * sigblkDestruct
+ *
+ * Obviously, the next call after sigblkFinsh must either be to
+ * sigblkInitKSI 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-2018 Adiscon GmbH and Guardtime, Inc.
+ *
+ * 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>
+#include <stdarg.h>
+#include <limits.h>
+
+#include <ksi/ksi.h>
+#include <ksi/tlv_element.h>
+#include <ksi/hash.h>
+#include <ksi/net_async.h>
+#include <ksi/net_ha.h>
+#include <ksi/net_uri.h>
+#include <ksi/signature_builder.h>
+#include "rsyslog.h"
+#include "errmsg.h"
+#include "lib_ksils12.h"
+#include "lib_ksi_queue.h"
+
+#ifndef VERSION
+#define VERSION "no-version"
+#endif
+
+#define KSI_BUF_SIZE 4096
+
+static const char *blockFileSuffix = ".logsig.parts/blocks.dat";
+static const char *sigFileSuffix = ".logsig.parts/block-signatures.dat";
+static const char *ls12FileSuffix = ".logsig";
+static const char *blockCloseReason = "com.guardtime.blockCloseReason";
+
+
+#define LS12_FILE_HEADER "LOGSIG12"
+#define LS12_BLOCKFILE_HEADER "LOG12BLK"
+#define LS12_SIGFILE_HEADER "LOG12SIG"
+#define LS12_SIGNATURE_TIMEOUT 60
+
+/* Worker queue item type identifier */
+typedef enum QITEM_type_en {
+ QITEM_SIGNATURE_REQUEST = 0x00,
+ QITEM_CLOSE_FILE,
+ QITEM_NEW_FILE,
+ QITEM_QUIT
+} QITEM_type;
+
+/* Worker queue item status identifier */
+typedef enum QITEM_status_en {
+ /* State assigned to any item added to queue (initial state). */
+ QITEM_WAITING = 0x00,
+
+ /* State assigned to #QITEM_SIGNATURE_REQUEST item when it is sent out. */
+ QITEM_SENT,
+
+ /* State assigned to #QITEM_SIGNATURE_REQUEST item when request failed or succeeded. */
+ QITEM_DONE
+} QITEM_status;
+
+
+/* Worker queue job item */
+typedef struct QueueItem_st {
+ QITEM_type type;
+ QITEM_status status;
+ KSI_DataHash *root;
+ FILE *file; /* To keep track of the target signature file. */
+ uint64_t intarg1; /* Block time limit or record count or not used. */
+ uint64_t intarg2; /* Level of the sign request or not used. */
+ KSI_AsyncHandle *respHandle;
+ int ksi_status;
+ time_t request_time;
+} QueueItem;
+
+static bool queueAddCloseFile(rsksictx ctx, ksifile kf);
+static bool queueAddNewFile(rsksictx ctx, ksifile kf);
+static bool queueAddQuit(rsksictx ctx);
+static bool queueAddSignRequest(rsksictx ctx, ksifile kf, KSI_DataHash *root, unsigned level);
+static int sigblkFinishKSINoSignature(ksifile ksi, const char *reason);
+
+void *signer_thread(void *arg);
+
+static void __attribute__((format(printf, 2, 3)))
+report(rsksictx ctx, const char *errmsg, ...) {
+ char buf[1024];
+ int r;
+ va_list args;
+ va_start(args, errmsg);
+
+ r = vsnprintf(buf, sizeof (buf), errmsg, args);
+ buf[sizeof(buf)-1] = '\0';
+ va_end(args);
+
+ if(ctx->logFunc == NULL)
+ return;
+
+ if(r>0 && r<(int)sizeof(buf))
+ ctx->logFunc(ctx->usrptr, (uchar*)buf);
+ else
+ ctx->logFunc(ctx->usrptr, (uchar*)errmsg);
+}
+
+static void
+reportErr(rsksictx ctx, const char *const errmsg)
+{
+ if(ctx->errFunc == NULL)
+ goto done;
+ ctx->errFunc(ctx->usrptr, (uchar*)errmsg);
+done: return;
+}
+
+static const char *
+level2str(int level) {
+ switch (level) {
+ case KSI_LOG_DEBUG: return "DEBUG";
+ case KSI_LOG_INFO: return "INFO";
+ case KSI_LOG_NOTICE: return "NOTICE";
+ case KSI_LOG_WARN: return "WARN";
+ case KSI_LOG_ERROR: return "ERROR";
+ default: return "UNKNOWN LOG LEVEL";
+ }
+}
+
+void
+reportKSIAPIErr(rsksictx ctx, ksifile ksi, const char *apiname, int ecode)
+{
+ char errbuf[4096];
+ char ksi_errbuf[4096];
+ KSI_ERR_getBaseErrorMessage(ctx->ksi_ctx, ksi_errbuf, sizeof(ksi_errbuf), NULL, NULL);
+ snprintf(errbuf, sizeof(errbuf), "%s[%s:%d]: %s (%s)",
+ (ksi == NULL) ? (uchar*) "" : ksi->blockfilename,
+ apiname, ecode, KSI_getErrorString(ecode), ksi_errbuf);
+
+ errbuf[sizeof(errbuf)-1] = '\0';
+ reportErr(ctx, errbuf);
+}
+
+void
+rsksisetErrFunc(rsksictx ctx, void (*func)(void*, uchar *), void *usrptr)
+{
+ ctx->usrptr = usrptr;
+ ctx->errFunc = func;
+}
+
+void
+rsksisetLogFunc(rsksictx ctx, void (*func)(void*, uchar *), void *usrptr)
+{
+ ctx->usrptr = usrptr;
+ ctx->logFunc = func;
+}
+
+static ksifile
+rsksifileConstruct(rsksictx ctx) {
+ ksifile ksi = NULL;
+ if ((ksi = calloc(1, sizeof (struct ksifile_s))) == NULL)
+ goto done;
+ ksi->ctx = ctx;
+ ksi->hashAlg = ctx->hashAlg;
+ ksi->blockTimeLimit = ctx->blockTimeLimit;
+ ksi->blockSizeLimit = 1 << (ctx->effectiveBlockLevelLimit - 1);
+ ksi->bKeepRecordHashes = ctx->bKeepRecordHashes;
+ ksi->bKeepTreeHashes = ctx->bKeepTreeHashes;
+ ksi->lastLeaf[0] = ctx->hashAlg;
+
+done:
+ return ksi;
+}
+
+/* return the actual length in to-be-written octets of an integer */
+static uint8_t
+tlvGetIntSize(uint64_t val) {
+ uint8_t n = 0;
+ while (val != 0) {
+ val >>= 8;
+ n++;
+ }
+ return n;
+}
+
+static int
+tlvWriteOctetString(FILE *f, const uint8_t *data, uint16_t len) {
+ if (fwrite(data, len, 1, f) != 1)
+ return RSGTE_IO;
+ return 0;
+}
+
+static int
+tlvWriteHeader8(FILE *f, int flags, uint8_t tlvtype, int len) {
+ unsigned char buf[2];
+ assert((flags & RSGT_TYPE_MASK) == 0);
+ assert((tlvtype & RSGT_TYPE_MASK) == tlvtype);
+ buf[0] = (flags & ~RSGT_FLAG_TLV16) | tlvtype;
+ buf[1] = len & 0xff;
+
+ return tlvWriteOctetString(f, buf, 2);
+}
+
+static int
+tlvWriteHeader16(FILE *f, int flags, uint16_t tlvtype, uint16_t len)
+{
+ uint16_t typ;
+ unsigned char buf[4];
+ assert((flags & RSGT_TYPE_MASK) == 0);
+ assert((tlvtype >> 8 & RSGT_TYPE_MASK) == (tlvtype >> 8));
+ typ = ((flags | RSGT_FLAG_TLV16) << 8) | tlvtype;
+
+ buf[0] = typ >> 8;
+ buf[1] = typ & 0xff;
+ buf[2] = (len >> 8) & 0xff;
+ buf[3] = len & 0xff;
+
+ return tlvWriteOctetString(f, buf, 4);
+}
+
+static int
+tlvGetHeaderSize(uint16_t tag, size_t size) {
+ if (tag <= RSGT_TYPE_MASK && size <= 0xff)
+ return 2;
+ if ((tag >> 8) <= RSGT_TYPE_MASK && size <= 0xffff)
+ return 4;
+ return 0;
+}
+
+static int
+tlvWriteHeader(FILE *f, int flags, uint16_t tlvtype, uint16_t len) {
+ int headersize = tlvGetHeaderSize(tlvtype, flags);
+ if (headersize == 2)
+ return tlvWriteHeader8(f, flags, tlvtype, len);
+ else if (headersize == 4)
+ return tlvWriteHeader16(f, flags, tlvtype, len);
+ else
+ return 0;
+}
+
+static int
+tlvWriteOctetStringTLV(FILE *f, int flags, uint16_t tlvtype, const uint8_t *data, uint16_t len) {
+ if (tlvWriteHeader(f, flags, tlvtype, len) != 0)
+ return RSGTE_IO;
+
+ if (fwrite(data, len, 1, f) != 1)
+ return RSGTE_IO;
+
+ return 0;
+}
+
+static int
+tlvWriteInt64TLV(FILE *f, int flags, uint16_t tlvtype, uint64_t val) {
+ unsigned char buf[8];
+ uint8_t count = tlvGetIntSize(val);
+ uint64_t nTmp;
+
+ if (tlvWriteHeader(f, flags, tlvtype, count) != 0)
+ return RSGTE_IO;
+
+ nTmp = val;
+ for (int i = count - 1; i >= 0; i--) {
+ buf[i] = 0xFF & nTmp;
+ nTmp = nTmp >> 8;
+ }
+
+ if (fwrite(buf, count, 1, f) != 1)
+ return RSGTE_IO;
+
+ return 0;
+}
+
+static int
+tlvWriteHashKSI(ksifile ksi, uint16_t tlvtype, KSI_DataHash *rec) {
+ int r;
+ const unsigned char *imprint;
+ size_t imprint_len;
+ r = KSI_DataHash_getImprint(rec, &imprint, &imprint_len);
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHash_getImprint", r);
+ return r;
+ }
+
+ return tlvWriteOctetStringTLV(ksi->blockFile, 0, tlvtype, imprint, imprint_len);
+}
+
+static int
+tlvWriteBlockHdrKSI(ksifile ksi) {
+ unsigned tlvlen;
+ uint8_t hash_algo = ksi->hashAlg;
+ int r;
+
+ tlvlen = 2 + 1 /* hash algo TLV */ +
+ 2 + KSI_getHashLength(ksi->hashAlg) /* iv */ +
+ 2 + KSI_getHashLength(ksi->lastLeaf[0]) + 1;
+ /* last hash */;
+
+ /* write top-level TLV object block-hdr */
+ CHKr(tlvWriteHeader(ksi->blockFile, 0x00, 0x0901, tlvlen));
+
+ /* hash-algo */
+ CHKr(tlvWriteOctetStringTLV(ksi->blockFile, 0x00, 0x01, &hash_algo, 1));
+
+ /* block-iv */
+ CHKr(tlvWriteOctetStringTLV(ksi->blockFile, 0x00, 0x02,
+ ksi->IV, KSI_getHashLength(ksi->hashAlg)));
+
+ /* last-hash */
+ CHKr(tlvWriteOctetStringTLV(ksi->blockFile, 0x00, 0x03,
+ ksi->lastLeaf, KSI_getHashLength(ksi->lastLeaf[0]) + 1));
+done:
+ return r;
+}
+
+static int
+tlvWriteKSISigLS12(FILE *outfile, size_t record_count, uchar *der, uint16_t lenDer) {
+ int r = 0;
+ int totalSize = 2 + tlvGetIntSize(record_count) + 4 + lenDer;
+
+ CHKr(tlvWriteHeader(outfile, 0x00, 0x0904, totalSize));
+ CHKr(tlvWriteInt64TLV(outfile, 0x00, 0x01, record_count));
+ CHKr(tlvWriteOctetStringTLV(outfile, 0x00, 0x0905, der, lenDer));
+done:
+ return r;
+}
+
+static int
+tlvWriteNoSigLS12(FILE *outfile, size_t record_count, const KSI_DataHash *hash, const char *errorText) {
+ int r = 0;
+ int totalSize = 0;
+ int noSigSize = 0;
+ const unsigned char *imprint = NULL;
+ size_t imprintLen = 0;
+
+ KSI_DataHash_getImprint(hash, &imprint, &imprintLen);
+
+ noSigSize = 2 + imprintLen + (errorText ? (2 + strlen(errorText) + 1) : 0);
+ totalSize = 2 + tlvGetIntSize(record_count) + 2 + noSigSize;
+
+ CHKr(tlvWriteHeader(outfile, 0x00, 0x0904, totalSize));
+ CHKr(tlvWriteInt64TLV(outfile, 0x00, 0x01, record_count));
+ CHKr(tlvWriteHeader(outfile, 0x00, 0x02, noSigSize));
+ CHKr(tlvWriteOctetStringTLV(outfile, 0x00, 0x01, imprint, imprintLen));
+ if (errorText)
+ CHKr(tlvWriteOctetStringTLV(outfile, 0x00, 0x02, (uint8_t*) errorText, strlen(errorText) + 1));
+done:
+ return r;
+}
+
+static int
+tlvCreateMetadata(ksifile ksi, uint64_t record_index, const char *key,
+ const char *value, unsigned char *buffer, size_t *len) {
+ int r = 0;
+ KSI_TlvElement *metadata = NULL, *attrib_tlv = NULL;
+ KSI_Utf8String *key_tlv = NULL, *value_tlv = NULL;
+ KSI_Integer *index_tlv = NULL;
+
+ CHKr(KSI_TlvElement_new(&metadata));
+ metadata->ftlv.tag = 0x0911;
+
+ CHKr(KSI_Integer_new(ksi->ctx->ksi_ctx, record_index, &index_tlv));
+ CHKr(KSI_TlvElement_setInteger(metadata, 0x01, index_tlv));
+
+ CHKr(KSI_TlvElement_new(&attrib_tlv));
+ attrib_tlv->ftlv.tag = 0x02;
+
+ CHKr(KSI_Utf8String_new(ksi->ctx->ksi_ctx, key, strlen(key) + 1, &key_tlv));
+ CHKr(KSI_TlvElement_setUtf8String(attrib_tlv, 0x01, key_tlv));
+
+ CHKr(KSI_Utf8String_new(ksi->ctx->ksi_ctx, value, strlen(value) + 1, &value_tlv));
+ CHKr(KSI_TlvElement_setUtf8String(attrib_tlv, 0x02, value_tlv));
+
+ CHKr(KSI_TlvElement_setElement(metadata, attrib_tlv));
+
+ CHKr(KSI_TlvElement_serialize(metadata, buffer, 0xFFFF, len, 0));
+
+done:
+ if (metadata) KSI_TlvElement_free(metadata);
+ if (attrib_tlv) KSI_TlvElement_free(attrib_tlv);
+ if (key_tlv) KSI_Utf8String_free(key_tlv);
+ if (value_tlv) KSI_Utf8String_free(value_tlv);
+ if (index_tlv) KSI_Integer_free(index_tlv);
+
+ return r;
+}
+
+#define KSI_FILE_AMOUNT_INC 32
+
+static int
+rsksiExpandRegisterIfNeeded(rsksictx ctx, size_t inc) {
+ int ret = RSGTE_INTERNAL;
+ ksifile *tmp = NULL;
+
+ if (ctx == NULL || inc == 0) {
+ return RSGTE_INTERNAL;
+ }
+
+ if (ctx->ksiCount < ctx->ksiCapacity) {
+ return RSGTE_SUCCESS;
+ }
+
+ /* If needed allocate memory for the buffer. */
+ tmp = (ksifile*)realloc(ctx->ksi, sizeof(ksifile) * (ctx->ksiCapacity + inc));
+ if (tmp == NULL) {
+ ret = RSGTE_OOM;
+ goto done;
+ }
+
+ /* Make sure that allocated pointers are all set to NULL. */
+ memset(tmp + ctx->ksiCapacity, 0, sizeof(ksifile) * inc);
+
+ /* Update buffer capacity. */
+ ctx->ksiCapacity += inc;
+ ctx->ksi = tmp;
+ tmp = NULL;
+ ret = RSGTE_SUCCESS;
+
+done:
+ free(tmp);
+ return ret;
+}
+
+static int
+rsksiRegisterKsiFile(rsksictx ctx, ksifile ksi) {
+ int ret = RSGTE_INTERNAL;
+
+ if (ctx == NULL || ksi == NULL) {
+ return RSGTE_INTERNAL;
+ }
+
+ /* To be extra sure that ksifile buffer is initialized correctly, clear variables. */
+ if (ctx->ksi == NULL) {
+ ctx->ksiCount = 0;
+ ctx->ksiCapacity = 0;
+ }
+
+ ret = rsksiExpandRegisterIfNeeded(ctx, KSI_FILE_AMOUNT_INC);
+ if (ret != RSGTE_SUCCESS) goto done;
+
+ ctx->ksi[ctx->ksiCount] = ksi;
+ ctx->ksiCount++;
+ ret = RSGTE_SUCCESS;
+
+done:
+ return ret;
+}
+
+static int
+rsksiDeregisterKsiFile(rsksictx ctx, ksifile ksi) {
+ int ret = RSGTE_INTERNAL;
+ size_t i = 0;
+
+ if (ctx == NULL || ksi == NULL) {
+ return RSGTE_INTERNAL;
+ }
+
+
+ for (i = 0; i < ctx->ksiCount; i++) {
+ if (ctx->ksi[i] != NULL && ctx->ksi[i] == ksi) {
+ size_t lastElement = ctx->ksiCount - 1;
+
+ if (i != lastElement) {
+ ctx->ksi[i] = ctx->ksi[lastElement];
+ }
+
+ ctx->ksi[lastElement] = NULL;
+
+ ctx->ksiCount--;
+ ret = RSGTE_SUCCESS;
+ goto done;
+ }
+ }
+
+done:
+ return ret;
+}
+
+/* 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 bool
+ksiReadStateFile(ksifile ksi) {
+ int fd = -1;
+ struct rsksistatefile sf;
+ bool ret = false;
+
+ fd = open((char*)ksi->statefilename, O_RDONLY|O_NOCTTY|O_CLOEXEC, 0600);
+ if (fd == -1)
+ goto done;
+
+ if (read(fd, &sf, sizeof (sf)) != sizeof (sf)) goto done;
+ if (strncmp(sf.hdr, "KSISTAT10", 9)) goto done;
+
+ if (KSI_getHashLength(sf.hashID) != sf.lenHash ||
+ KSI_getHashLength(sf.hashID) > KSI_MAX_IMPRINT_LEN - 1)
+ goto done;
+
+ if (read(fd, ksi->lastLeaf + 1, sf.lenHash) != sf.lenHash)
+ goto done;
+
+ ksi->lastLeaf[0] = sf.hashID;
+ ret = true;
+
+done:
+ if (!ret) {
+ memset(ksi->lastLeaf, 0, sizeof (ksi->lastLeaf));
+ ksi->lastLeaf[0] = ksi->hashAlg;
+ }
+
+ if (fd != -1)
+ close(fd);
+ return ret;
+}
+
+/* persist all information that we need to re-open and append
+ * to a log signature file.
+ */
+static void
+ksiWwriteStateFile(ksifile ksi)
+{
+ int fd;
+ struct rsksistatefile sf;
+
+ fd = open((char*)ksi->statefilename,
+ O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, ksi->ctx->fCreateMode);
+ if(fd == -1)
+ goto done;
+ if (ksi->ctx->fileUID != (uid_t) - 1 || ksi->ctx->fileGID != (gid_t) - 1) {
+ /* we need to set owner/group */
+ if (fchown(fd, ksi->ctx->fileUID, ksi->ctx->fileGID) != 0) {
+ report(ksi->ctx, "lmsig_ksi: chown for file '%s' failed: %s",
+ ksi->statefilename, strerror(errno));
+ }
+ }
+
+ memcpy(sf.hdr, "KSISTAT10", 9);
+ sf.hashID = ksi->hashAlg;
+ sf.lenHash = KSI_getHashLength(ksi->lastLeaf[0]);
+ /* 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, ksi->lastLeaf + 1, sf.lenHash)) {
+ };
+ close(fd);
+done:
+ return;
+}
+
+
+static int
+ksiCloseSigFile(ksifile ksi) {
+ fclose(ksi->blockFile);
+ ksi->blockFile = NULL;
+ if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS)
+ queueAddCloseFile(ksi->ctx, ksi);
+
+ ksiWwriteStateFile(ksi);
+ return 0;
+}
+
+static int mkpath(char* path, mode_t mode, uid_t uid, gid_t gid) {
+
+ if(path == NULL)
+ return 1;
+
+ for (char *p = strchr(path + 1, '/'); p; p = strchr(p + 1, '/')) {
+ *p = '\0';
+ if (mkdir(path, mode) == 0) {
+ if (uid != (uid_t) -1 || gid != (uid_t) -1) {
+ if (chown(path, uid, gid)) {
+ LogError(errno, RS_RET_IO_ERROR,
+ "ksils12 signatures: could not change to "
+ "configured owner - files may be unaccessible");
+ }
+ }
+ }
+ else if (errno != EEXIST) {
+ *p = '/';
+ return -1;
+ }
+
+ *p = '/';
+ }
+ return 0;
+}
+
+static FILE*
+ksiCreateFile(rsksictx ctx, const char *path, uid_t uid, gid_t gid, int mode, bool lockit, const char* header) {
+ int fd = -1;
+ struct stat stat_st;
+ FILE *f = NULL;
+ struct flock lock = {F_WRLCK, SEEK_SET, 0, 0, 0};
+
+ if(path ==NULL)
+ return NULL;
+
+ if (mkpath((char*) path, ctx->fDirCreateMode, ctx->dirUID, ctx->dirGID) != 0) {
+ report(ctx, "ksiCreateFile: mkpath failed for %s", path);
+ goto done;
+ }
+
+ fd = open(path, O_RDWR | O_APPEND | O_NOCTTY | O_CLOEXEC, 0600);
+ if (fd == -1) {
+ fd = open(path, O_RDWR | O_CREAT | O_NOCTTY | O_CLOEXEC, mode);
+ if (fd == -1) {
+ report(ctx, "creating file '%s' failed: %s", path, strerror(errno));
+ goto done;
+ }
+
+ if (uid != (uid_t) - 1 || gid != (gid_t) - 1) {
+ if (fchown(fd, uid, gid) != 0) {
+ report(ctx, "lmsig_ksi: chown for file '%s' failed: %s",
+ path, strerror(errno));
+ }
+ }
+ }
+
+ if (lockit && fcntl(fd, F_SETLK, &lock) != 0)
+ report(ctx, "fcntl error: %s", strerror(errno));
+
+ f = fdopen(fd, "a");
+ if (f == NULL) {
+ report(ctx, "fdopen for '%s' failed: %s", path, strerror(errno));
+ goto done;
+ }
+
+ setvbuf(f, NULL, _IOFBF, KSI_BUF_SIZE);
+
+ if (fstat(fd, &stat_st) == -1) {
+ reportErr(ctx, "ksiOpenSigFile: can not stat file");
+ goto done;
+ }
+
+ if (stat_st.st_size == 0 && header != NULL) {
+ if(fwrite(header, strlen(header), 1, f) != 1) {
+ report(ctx, "ksiOpenSigFile: fwrite for file %s failed: %s",
+ path, strerror(errno));
+ goto done;
+ }
+ }
+ /* Write header immediately as when using dynafile it is possible that the same
+ * file is opened 2x in sequence (caused by small dynafile cache where files are
+ * frequently closed and reopened). If the header already exists double header is
+ * not written. The content of the file is ordered by signer thread.
+ */
+ fflush(f);
+done:
+ return f;
+}
+
+static void handle_ksi_config(rsksictx ctx, KSI_AsyncService *as, KSI_Config *config) {
+ int res = KSI_UNKNOWN_ERROR;
+ KSI_Integer *intValue = NULL;
+
+ if (KSI_Config_getMaxRequests(config, &intValue) == KSI_OK && intValue != NULL) {
+ ctx->max_requests = KSI_Integer_getUInt64(intValue);
+ report(ctx, "KSI gateway has reported a max requests value of %llu",
+ (long long unsigned) ctx->max_requests);
+
+ if(as) {
+ /* libksi expects size_t. */
+ size_t optValue = 0;
+
+ optValue = ctx->max_requests;
+ res = KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_MAX_REQUEST_COUNT,
+ (void*)optValue);
+ if(res != KSI_OK)
+ reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_setOption(max_request)", res);
+
+ optValue = 3 * ctx->max_requests * ctx->blockSigTimeout;
+ KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_REQUEST_CACHE_SIZE,
+ (void*)optValue);
+ }
+ }
+
+ intValue = NULL;
+ if(KSI_Config_getMaxLevel(config, &intValue) == KSI_OK && intValue != NULL) {
+ uint64_t newLevel = 0;
+ newLevel = KSI_Integer_getUInt64(intValue);
+ report(ctx, "KSI gateway has reported a max level value of %llu",
+ (long long unsigned) newLevel);
+ newLevel=MIN(newLevel, ctx->blockLevelLimit);
+ if(ctx->effectiveBlockLevelLimit != newLevel) {
+ report(ctx, "Changing the configured block level limit from %llu to %llu",
+ (long long unsigned) ctx->effectiveBlockLevelLimit,
+ (long long unsigned) newLevel);
+ ctx->effectiveBlockLevelLimit = newLevel;
+ }
+ else if(newLevel < 2) {
+ report(ctx, "KSI gateway has reported an invalid level limit value (%llu), "
+ "plugin disabled", (long long unsigned) newLevel);
+ ctx->disabled = true;
+ }
+ }
+
+ intValue = NULL;
+ if (KSI_Config_getAggrPeriod(config, &intValue) == KSI_OK && intValue != NULL) {
+ uint64_t newThreadSleep = 0;
+ newThreadSleep = KSI_Integer_getUInt64(intValue);
+ report(ctx, "KSI gateway has reported an aggregation period value of %llu",
+ (long long unsigned) newThreadSleep);
+
+ newThreadSleep = MIN(newThreadSleep, ctx->threadSleepms);
+ if(ctx->threadSleepms != newThreadSleep) {
+ report(ctx, "Changing async signer thread sleep from %llu to %llu",
+ (long long unsigned) ctx->threadSleepms,
+ (long long unsigned) newThreadSleep);
+ ctx->threadSleepms = newThreadSleep;
+ }
+ }
+}
+
+static int
+isAggrConfNeeded(rsksictx ctx) {
+ time_t now = 0;
+
+ now = time(NULL);
+
+ if ((uint64_t)ctx->tConfRequested + ctx->confInterval <= (uint64_t)now || ctx->tConfRequested == 0) {
+ ctx->tConfRequested = now;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* note: if file exists, the last hash for chaining must
+ * be read from file.
+ */
+static int
+ksiOpenSigFile(ksifile ksi) {
+ int r = 0, tmpRes = 0;
+ const char *header;
+ FILE* signatureFile = NULL;
+
+ if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS)
+ header = LS12_BLOCKFILE_HEADER;
+ else
+ header = LS12_FILE_HEADER;
+
+ ksi->blockFile = ksiCreateFile(ksi->ctx, (char*) ksi->blockfilename, ksi->ctx->fileUID,
+ ksi->ctx->fileGID, ksi->ctx->fCreateMode, true, header);
+
+ if (ksi->blockFile == NULL) {
+ r = RSGTE_IO;
+ goto done;
+ }
+
+ /* create the file for ksi signatures if needed */
+ if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) {
+ signatureFile = ksiCreateFile(ksi->ctx, (char*) ksi->ksifilename, ksi->ctx->fileUID,
+ ksi->ctx->fileGID, ksi->ctx->fCreateMode, true, LS12_SIGFILE_HEADER);
+
+ if (signatureFile == NULL) {
+ r = RSGTE_IO;
+ goto done;
+ }
+
+ ksi->sigFile = signatureFile;
+ queueAddNewFile(ksi->ctx, ksi);
+ }
+
+ /* 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.
+ */
+ ksiReadStateFile(ksi);
+
+ if (ksi->ctx->syncMode == LOGSIG_SYNCHRONOUS) {
+ if (isAggrConfNeeded(ksi->ctx)) {
+ KSI_Config *config = NULL;
+
+ tmpRes = KSI_receiveAggregatorConfig(ksi->ctx->ksi_ctx, &config);
+ if (tmpRes == KSI_OK) {
+ handle_ksi_config(ksi->ctx, NULL, config);
+ } else {
+ reportKSIAPIErr(ksi->ctx, NULL, "KSI_receiveAggregatorConfig", tmpRes);
+ }
+ KSI_Config_free(config);
+ }
+ }
+
+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
+ */
+static void
+seedIVKSI(ksifile ksi)
+{
+ int hashlen;
+ int fd;
+ const char *rnd_device = ksi->ctx->random_source ? ksi->ctx->random_source : "/dev/urandom";
+
+ hashlen = KSI_getHashLength(ksi->hashAlg);
+ ksi->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(rnd_device, O_RDONLY)) >= 0) {
+ if(read(fd, ksi->IV, hashlen) == hashlen) {}; /* keep compiler happy */
+ close(fd);
+ }
+}
+
+static int
+create_signer_thread(rsksictx ctx) {
+ int r;
+ if (ctx->signer_state != SIGNER_STARTED) {
+ if ((r = pthread_mutex_init(&ctx->module_lock, 0)))
+ report(ctx, "pthread_mutex_init: %s", strerror(r));
+ ctx->signer_queue = ProtectedQueue_new(10);
+
+ ctx->signer_state = SIGNER_INIT;
+ if ((r = pthread_create(&ctx->signer_thread, NULL, signer_thread, ctx))) {
+ report(ctx, "pthread_create: %s", strerror(r));
+ ctx->signer_state = SIGNER_IDLE;
+ return RSGTE_INTERNAL;
+ }
+
+ /* Lock until init. */
+ while(*((volatile int*)&ctx->signer_state) & SIGNER_INIT);
+
+ if (ctx->signer_state != SIGNER_STARTED) {
+ return RSGTE_INTERNAL;
+ }
+ }
+
+ return RSGTE_SUCCESS;
+}
+
+rsksictx
+rsksiCtxNew(void) {
+ rsksictx ctx;
+ ctx = calloc(1, sizeof (struct rsksictx_s));
+ KSI_CTX_new(&ctx->ksi_ctx); // TODO: error check (probably via a generic macro?)
+ ctx->hasher = NULL;
+ ctx->hashAlg = KSI_getHashAlgorithmByName("default");
+ ctx->blockTimeLimit = 0;
+ ctx->bKeepTreeHashes = false;
+ ctx->bKeepRecordHashes = true;
+ ctx->max_requests = (1 << 8);
+ ctx->blockSigTimeout = 10;
+ ctx->confInterval = 3600;
+ ctx->tConfRequested = 0;
+ ctx->threadSleepms = 1000;
+ ctx->errFunc = NULL;
+ ctx->usrptr = NULL;
+ ctx->fileUID = -1;
+ ctx->fileGID = -1;
+ ctx->dirUID = -1;
+ ctx->dirGID = -1;
+ ctx->fCreateMode = 0644;
+ ctx->fDirCreateMode = 0700;
+#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22
+ ctx->roundCount = 0;
+ ctx->bRoundLock = 0;
+#endif
+ ctx->syncMode = LOGSIG_SYNCHRONOUS;
+ ctx->signer_state = SIGNER_IDLE;
+ ctx->disabled = false;
+ ctx->ksi = NULL;
+
+ /*if (pthread_mutex_init(&ctx->module_lock, 0))
+ report(ctx, "pthread_mutex_init: %s", strerror(errno));
+ ctx->signer_queue = ProtectedQueue_new(10);*/
+
+ /* Creating a thread this way works only in daemon mode but not when being run
+ interactively when not forked */
+ /*ret = pthread_atfork(NULL, NULL, create_signer_thread);
+ if (ret != 0)
+ report(ctx, "pthread_atfork error: %s", strerror(ret));*/
+
+ return ctx;
+}
+
+static
+int rsksiStreamLogger(void *logCtx, int logLevel, const char *message)
+{
+ char time_buf[32];
+ struct tm *tm_info;
+ time_t timer;
+ FILE *f = (FILE *)logCtx;
+
+ timer = time(NULL);
+
+ tm_info = localtime(&timer);
+ if (tm_info == NULL) {
+ return KSI_UNKNOWN_ERROR;
+ }
+
+ if (f != NULL) {
+ flockfile(f); /* for thread safety */
+ if (strftime(time_buf, sizeof(time_buf), "%d.%m.%Y %H:%M:%S", tm_info)) {
+ if (fprintf(f, "%s [%s] %lu - %s\n", level2str(logLevel),
+ time_buf, pthread_self(), message) > 0) { }
+ }
+ funlockfile(f);
+ }
+
+ return KSI_OK;
+}
+
+int
+rsksiInitModule(rsksictx ctx) {
+ int res = 0;
+
+ if(ctx->debugFileName != NULL) {
+ ctx->debugFile = fopen(ctx->debugFileName, "w");
+ if(ctx->debugFile) {
+ res = KSI_CTX_setLoggerCallback(ctx->ksi_ctx, rsksiStreamLogger, ctx->debugFile);
+ if (res != KSI_OK)
+ reportKSIAPIErr(ctx, NULL, "Unable to set logger callback", res);
+ res = KSI_CTX_setLogLevel(ctx->ksi_ctx, ctx->debugLevel);
+ if (res != KSI_OK)
+ reportKSIAPIErr(ctx, NULL, "Unable to set log level", res);
+ }
+ else {
+ report(ctx, "Could not open logfile %s: %s", ctx->debugFileName, strerror(errno));
+ }
+ }
+
+ KSI_CTX_setOption(ctx->ksi_ctx, KSI_OPT_AGGR_HMAC_ALGORITHM, (void*)((size_t)ctx->hmacAlg));
+
+ return create_signer_thread(ctx);
+}
+
+/* either returns ksifile object or NULL if something went wrong */
+ksifile
+rsksiCtxOpenFile(rsksictx ctx, unsigned char *logfn)
+{
+ int ret = RSGTE_INTERNAL;
+ ksifile ksi;
+ char fn[MAXFNAME+1];
+
+ if (ctx->disabled)
+ return NULL;
+
+ pthread_mutex_lock(&ctx->module_lock);
+
+ /* The thread cannot be be created in rsksiCtxNew because in daemon mode the
+ process forks after rsksiCtxNew and the thread disappears */
+ if (ctx->signer_state != SIGNER_STARTED) {
+ ret = rsksiInitModule(ctx);
+ if (ret != RSGTE_SUCCESS) {
+ report(ctx, "Unable to init. KSI module, signing service disabled");
+ ctx->disabled = true;
+ pthread_mutex_unlock(&ctx->module_lock);
+ return NULL;
+ }
+ }
+
+ if ((ksi = rsksifileConstruct(ctx)) == NULL)
+ goto done;
+
+ snprintf(fn, sizeof (fn), "%s.ksistate", logfn);
+ fn[MAXFNAME] = '\0'; /* be on safe side */
+ ksi->statefilename = (uchar*) strdup(fn);
+
+ if (ctx->syncMode == LOGSIG_ASYNCHRONOUS) {
+ /* filename for blocks of hashes*/
+ snprintf(fn, sizeof (fn), "%s%s", logfn, blockFileSuffix);
+ fn[MAXFNAME] = '\0'; /* be on safe side */
+ ksi->blockfilename = (uchar*) strdup(fn);
+
+ /* filename for KSI signatures*/
+ snprintf(fn, sizeof (fn), "%s%s", logfn, sigFileSuffix);
+ fn[MAXFNAME] = '\0'; /* be on safe side */
+ ksi->ksifilename = (uchar*) strdup(fn);
+ } else if (ctx->syncMode == LOGSIG_SYNCHRONOUS) {
+ snprintf(fn, sizeof (fn), "%s%s", logfn, ls12FileSuffix);
+ fn[MAXFNAME] = '\0'; /* be on safe side */
+ ksi->blockfilename = (uchar*) strdup(fn);
+ }
+
+ if (ksiOpenSigFile(ksi) != 0) {
+ reportErr(ctx, "signature file open failed");
+ /* Free memory */
+ free(ksi);
+ ksi = NULL;
+ }
+
+done:
+ /* Register ksi file in rsksictx for keeping track of block timeouts. */
+ rsksiRegisterKsiFile(ctx, ksi);
+ pthread_mutex_unlock(&ctx->module_lock);
+ return ksi;
+}
+
+
+/* Returns RSGTE_SUCCESS on success, error code otherwise. If algo is unknown or
+ * is not trusted, default hash function is used.
+ */
+int
+rsksiSetHashFunction(rsksictx ctx, char *algName) {
+ if (ctx == NULL || algName == NULL) {
+ return RSGTE_INTERNAL;
+ }
+
+ int r, id = KSI_getHashAlgorithmByName(algName);
+ if (!KSI_isHashAlgorithmSupported(id)) {
+ report(ctx, "Hash function '%s' is not supported - using default", algName);
+ ctx->hashAlg = KSI_getHashAlgorithmByName("default");
+ } else {
+ if(!KSI_isHashAlgorithmTrusted(id)) {
+ report(ctx, "Hash function '%s' is not trusted - using default", algName);
+ ctx->hashAlg = KSI_getHashAlgorithmByName("default");
+ }
+ else
+ ctx->hashAlg = id;
+ }
+
+ if ((r = KSI_DataHasher_open(ctx->ksi_ctx, ctx->hashAlg, &ctx->hasher)) != KSI_OK) {
+ reportKSIAPIErr(ctx, NULL, "KSI_DataHasher_open", r);
+ ctx->disabled = true;
+ return r;
+ }
+
+ return RSGTE_SUCCESS;
+}
+
+int
+rsksiSetHmacFunction(rsksictx ctx, char *algName) {
+ int id = KSI_getHashAlgorithmByName(algName);
+ if (!KSI_isHashAlgorithmSupported(id)) {
+ report(ctx, "HMAC function '%s' is not supported - using default", algName);
+ ctx->hmacAlg = KSI_getHashAlgorithmByName("default");
+ } else {
+ if(!KSI_isHashAlgorithmTrusted(id)) {
+ report(ctx, "HMAC function '%s' is not trusted - using default", algName);
+ ctx->hmacAlg = KSI_getHashAlgorithmByName("default");
+ }
+ else
+ ctx->hmacAlg = id;
+ }
+ return 0;
+}
+
+int
+rsksifileDestruct(ksifile ksi) {
+ int r = 0;
+ rsksictx ctx = NULL;
+ if (ksi == NULL)
+ return RSGTE_INTERNAL;
+
+ pthread_mutex_lock(&ksi->ctx->module_lock);
+
+ ctx = ksi->ctx;
+
+ /* Deregister ksifile so it is not used by signer thread anymore. Note that files are not closed yet! */
+ rsksiDeregisterKsiFile(ctx, ksi);
+
+ if (!ksi->disabled && ksi->bInBlk) {
+ sigblkAddMetadata(ksi, blockCloseReason, "Block closed due to file closure.");
+ r = sigblkFinishKSI(ksi);
+ }
+ /* Note that block file is closed immediately but signature file will be closed
+ * by the signer thread scheduled by signer thread work queue.
+ */
+ if(!ksi->disabled)
+ r = ksiCloseSigFile(ksi);
+ free(ksi->blockfilename);
+ free(ksi->statefilename);
+ free(ksi->ksifilename);
+
+ free(ksi);
+
+ pthread_mutex_unlock(&ctx->module_lock);
+ return r;
+}
+
+/* This can only be used when signer thread has terminated or within the thread. */
+static void
+rsksifileForceFree(ksifile ksi) {
+ if (ksi == NULL) return;
+
+ if (ksi->sigFile != NULL) fclose(ksi->sigFile);
+ if (ksi->blockFile != NULL) fclose(ksi->blockFile);
+ free(ksi->blockfilename);
+ free(ksi->statefilename);
+ free(ksi->ksifilename);
+ free(ksi);
+ return;
+}
+
+/* This can only be used when signer thread has terminated or within the thread. */
+static void
+rsksictxForceFreeSignatures(rsksictx ctx) {
+ size_t i = 0;
+
+ if (ctx == NULL || ctx->ksi == NULL) return;
+
+ for (i = 0; i < ctx->ksiCount; i++) {
+ if (ctx->ksi[i] != NULL) {
+ rsksifileForceFree(ctx->ksi[i]);
+ ctx->ksi[i] = NULL;
+ }
+ }
+
+ ctx->ksiCount = 0;
+ return;
+}
+
+/* This can only be used when signer thread has terminated or within the thread. */
+static int
+rsksictxForceCloseWithoutSig(rsksictx ctx, const char *reason) {
+ size_t i = 0;
+ if (ctx == NULL || ctx->ksi == NULL) return RSGTE_INTERNAL;
+ for (i = 0; i < ctx->ksiCount; i++) {
+ if (ctx->ksi[i] != NULL) {
+ int ret = RSGTE_INTERNAL;
+
+ /* Only if block contains records, create metadata, close the block and add
+ * no signature marker. Closing block without record will produce redundant
+ * blocks that needs to be signed afterward.
+ */
+ if (ctx->ksi[i]->nRecords > 0) {
+ ret = sigblkFinishKSINoSignature(ctx->ksi[i], reason);
+ if (ret != RSGTE_SUCCESS) return ret;
+ }
+
+ /* Free files and remove object from the list. */
+ rsksifileForceFree(ctx->ksi[i]);
+ ctx->ksi[i] = NULL;
+ }
+ }
+
+ ctx->ksiCount = 0;
+ return RSGTE_SUCCESS;
+}
+
+void
+rsksiCtxDel(rsksictx ctx) {
+ if (ctx == NULL)
+ return;
+
+ /* Note that even in sync. mode signer thread is created and needs to be closed
+ * correctly.
+ */
+ if (ctx->signer_state == SIGNER_STARTED) {
+ queueAddQuit(ctx);
+ /* Wait until thread closes to be able to safely free the resources. */
+ pthread_join(ctx->signer_thread, NULL);
+ ProtectedQueue_free(ctx->signer_queue);
+ pthread_mutex_destroy(&ctx->module_lock);
+ }
+
+ free(ctx->aggregatorUri);
+ free(ctx->aggregatorId);
+ free(ctx->aggregatorKey);
+ free(ctx->debugFileName);
+
+ if (ctx->random_source)
+ free(ctx->random_source);
+
+ KSI_DataHasher_free(ctx->hasher);
+ KSI_CTX_free(ctx->ksi_ctx);
+
+ if(ctx->debugFile!=NULL)
+ fclose(ctx->debugFile);
+
+ /* After signer thread is terminated there should be no open signature files,
+ * but to be extra sure that all files are closed, recheck the list of opened
+ * signature files.
+ */
+ rsksictxForceFreeSignatures(ctx);
+ free(ctx->ksi);
+
+ free(ctx);
+}
+
+/* new sigblk is initialized, but maybe in existing ctx */
+void
+sigblkInitKSI(ksifile ksi)
+{
+ if(ksi == NULL) goto done;
+ seedIVKSI(ksi);
+ memset(ksi->roots, 0, sizeof (ksi->roots));
+ ksi->nRoots = 0;
+ ksi->nRecords = 0;
+ ksi->bInBlk = 1;
+ ksi->blockStarted = time(NULL); //TODO: maybe milli/nanoseconds should be used
+ ksi->blockSizeLimit = 1 << (ksi->ctx->effectiveBlockLevelLimit - 1);
+
+ /* flush the optional debug file when starting a new block */
+ if(ksi->ctx->debugFile != NULL)
+ fflush(ksi->ctx->debugFile);
+
+done:
+ return;
+}
+
+int
+sigblkCreateMask(ksifile ksi, KSI_DataHash **m) {
+ int r = 0;
+
+ CHKr(KSI_DataHasher_reset(ksi->ctx->hasher));
+ CHKr(KSI_DataHasher_add(ksi->ctx->hasher, ksi->lastLeaf, KSI_getHashLength(ksi->lastLeaf[0]) + 1));
+ CHKr(KSI_DataHasher_add(ksi->ctx->hasher, ksi->IV, KSI_getHashLength(ksi->hashAlg)));
+ CHKr(KSI_DataHasher_close(ksi->ctx->hasher, m));
+
+done:
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHasher", r);
+ r = RSGTE_HASH_CREATE;
+ }
+ return r;
+}
+int
+sigblkCreateHash(ksifile ksi, KSI_DataHash **out, const uchar *rec, const size_t len) {
+ int r = 0;
+
+ CHKr(KSI_DataHasher_reset(ksi->ctx->hasher));
+ CHKr(KSI_DataHasher_add(ksi->ctx->hasher, rec, len));
+ CHKr(KSI_DataHasher_close(ksi->ctx->hasher, out));
+
+done:
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHasher", r);
+ r = RSGTE_HASH_CREATE;
+ }
+
+ return r;
+}
+
+
+int
+sigblkHashTwoNodes(ksifile ksi, KSI_DataHash **out, KSI_DataHash *left, KSI_DataHash *right,
+ uint8_t level) {
+ int r = 0;
+
+ CHKr(KSI_DataHasher_reset(ksi->ctx->hasher));
+ CHKr(KSI_DataHasher_addImprint(ksi->ctx->hasher, left));
+ CHKr(KSI_DataHasher_addImprint(ksi->ctx->hasher, right));
+ CHKr(KSI_DataHasher_add(ksi->ctx->hasher, &level, 1));
+ CHKr(KSI_DataHasher_close(ksi->ctx->hasher, out));
+
+done:
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "KSI_DataHash_create", r);
+ r = RSGTE_HASH_CREATE;
+ }
+
+ return r;
+}
+int
+sigblkAddMetadata(ksifile ksi, const char *key, const char *value) {
+ unsigned char buffer[0xFFFF];
+ size_t encoded_size = 0;
+ int ret = 0;
+
+ tlvCreateMetadata(ksi, ksi->nRecords, key, value, buffer, &encoded_size);
+ sigblkAddLeaf(ksi, buffer, encoded_size, true);
+
+ return ret;
+}
+
+int
+sigblkAddRecordKSI(ksifile ksi, const uchar *rec, const size_t len) {
+ int ret = 0;
+ if (ksi == NULL || ksi->disabled)
+ return 0;
+
+ pthread_mutex_lock(&ksi->ctx->module_lock);
+
+ if ((ret = sigblkAddLeaf(ksi, rec, len, false)) != 0)
+ goto done;
+
+ if (ksi->nRecords == ksi->blockSizeLimit) {
+ sigblkFinishKSI(ksi);
+ sigblkInitKSI(ksi);
+ }
+
+done:
+ pthread_mutex_unlock(&ksi->ctx->module_lock);
+ return ret;
+}
+
+
+int
+sigblkAddLeaf(ksifile ksi, const uchar *leafData, const size_t leafLength, bool metadata) {
+
+ KSI_DataHash *mask, *leafHash, *treeNode, *tmpTreeNode;
+ uint8_t j;
+ const unsigned char *pTmp;
+ size_t len;
+
+ int r = 0;
+
+ if (ksi == NULL || ksi->disabled) goto done;
+ CHKr(sigblkCreateMask(ksi, &mask));
+ CHKr(sigblkCreateHash(ksi, &leafHash, leafData, leafLength));
+
+ if(ksi->nRecords == 0)
+ tlvWriteBlockHdrKSI(ksi);
+
+ /* metadata record has to be written into the block file too*/
+ if (metadata)
+ tlvWriteOctetString(ksi->blockFile, leafData, leafLength);
+
+ if (ksi->bKeepRecordHashes)
+ tlvWriteHashKSI(ksi, 0x0902, leafHash);
+
+ /* normal leaf and metadata record are hashed in different order */
+ if (!metadata) { /* hash leaf */
+ if ((r = sigblkHashTwoNodes(ksi, &treeNode, mask, leafHash, 1)) != 0) goto done;
+ } else {
+ if ((r = sigblkHashTwoNodes(ksi, &treeNode, leafHash, mask, 1)) != 0) goto done;
+ }
+
+ /* persists x here if Merkle tree needs to be persisted! */
+ if(ksi->bKeepTreeHashes)
+ tlvWriteHashKSI(ksi, 0x0903, treeNode);
+
+ KSI_DataHash_getImprint(treeNode, &pTmp, &len);
+ memcpy(ksi->lastLeaf, pTmp, len);
+
+ for(j = 0 ; j < ksi->nRoots ; ++j) {
+ if (ksi->roots[j] == NULL) {
+ ksi->roots[j] = treeNode;
+ treeNode = NULL;
+ break;
+ } else if (treeNode != NULL) {
+ /* hash interim node */
+ tmpTreeNode = treeNode;
+ r = sigblkHashTwoNodes(ksi, &treeNode, ksi->roots[j], tmpTreeNode, j + 2);
+ KSI_DataHash_free(ksi->roots[j]);
+ ksi->roots[j] = NULL;
+ KSI_DataHash_free(tmpTreeNode);
+ if (r != 0) goto done;
+ if(ksi->bKeepTreeHashes)
+ tlvWriteHashKSI(ksi, 0x0903, treeNode);
+ }
+ }
+ if (treeNode != NULL) {
+ /* new level, append "at the top" */
+ ksi->roots[ksi->nRoots] = treeNode;
+ ++ksi->nRoots;
+ assert(ksi->nRoots < MAX_ROOTS);
+ treeNode = NULL;
+ }
+ ++ksi->nRecords;
+
+ /* cleanup (x is cleared as part of the roots array) */
+ KSI_DataHash_free(mask);
+ KSI_DataHash_free(leafHash);
+
+done:
+ return r;
+}
+
+static int
+sigblkCheckTimeOut(rsksictx ctx) {
+ int ret = RSGTE_INTERNAL;
+ time_t now;
+ char buf[KSI_BUF_SIZE];
+ size_t i = 0;
+
+ if (ctx == NULL) {
+ return RSGTE_INTERNAL;
+ }
+
+ pthread_mutex_lock(&ctx->module_lock);
+
+ if (ctx->ksi == NULL || ctx->disabled || !ctx->blockTimeLimit) {
+ ret = RSGTE_SUCCESS;
+ goto done;
+ }
+
+ now = time(NULL);
+
+ for (i = 0; i < ctx->ksiCount; i++) {
+ ksifile ksi = ctx->ksi[i];
+
+ if (ksi == NULL) continue; /* To avoide unexpected crash. */
+ if (!ksi->bInBlk) continue; /* Not inside a block, nothing to close nor sign. */
+ if ((time_t) (ksi->blockStarted + ctx->blockTimeLimit) > now) continue;
+
+ snprintf(buf, KSI_BUF_SIZE, "Block closed due to reaching time limit %d", ctx->blockTimeLimit);
+ sigblkAddMetadata(ksi, blockCloseReason, buf);
+ sigblkFinishKSI(ksi);
+ sigblkInitKSI(ksi);
+ }
+
+done:
+ pthread_mutex_unlock(&ctx->module_lock);
+ return ret;
+}
+
+
+static int
+sigblkSign(ksifile ksi, KSI_DataHash *hash, int level)
+{
+ unsigned char *der = NULL;
+ size_t lenDer = 0;
+ int r = KSI_OK;
+ int ret = 0;
+ KSI_Signature *sig = NULL;
+
+ /* Sign the root hash. */
+ r = KSI_Signature_signAggregated(ksi->ctx->ksi_ctx, hash, level, &sig);
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "KSI_Signature_createAggregated", r);
+ ret = 1;
+ goto signing_done;
+ }
+
+ /* Serialize Signature. */
+ r = KSI_Signature_serialize(sig, &der, &lenDer);
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "KSI_Signature_serialize", r);
+ ret = 1;
+ lenDer = 0;
+ goto signing_done;
+ }
+
+signing_done:
+ /* if signing failed the signature will be written as zero size */
+ if (r == KSI_OK) {
+ r = tlvWriteKSISigLS12(ksi->blockFile, ksi->nRecords, der, lenDer);
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteKSISigLS12", r);
+ ret = 1;
+ }
+ } else
+ r = tlvWriteNoSigLS12(ksi->blockFile, ksi->nRecords, hash, KSI_getErrorString(r));
+
+ if (r != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteBlockSigKSI", r);
+ ret = 1;
+ }
+
+ if (sig != NULL)
+ KSI_Signature_free(sig);
+ if (der != NULL)
+ KSI_free(der);
+ return ret;
+}
+
+unsigned
+sigblkCalcLevel(unsigned leaves) {
+ unsigned level = 0;
+ unsigned c = leaves;
+ while (c > 1) {
+ level++;
+ c >>= 1;
+ }
+
+ if (1 << level < (int)leaves)
+ level++;
+
+ return level;
+}
+
+static int
+sigblkFinishTree(ksifile ksi, KSI_DataHash **hsh) {
+ int ret = RSGTE_INTERNAL;
+ KSI_DataHash *root = NULL;
+ KSI_DataHash *rootDel = NULL;
+ int8_t j = 0;
+
+ if (ksi == NULL || hsh == NULL) {
+ goto done;
+ }
+
+ if (ksi->nRecords == 0) {
+ ret = RSGTE_SUCCESS;
+ goto done;
+ }
+
+ root = NULL;
+ for(j = 0 ; j < ksi->nRoots ; ++j) {
+ if(root == NULL) {
+ root = ksi->roots[j];
+ ksi->roots[j] = NULL;
+ } else if (ksi->roots[j] != NULL) {
+ rootDel = root;
+ root = NULL;
+ ret = sigblkHashTwoNodes(ksi, &root, ksi->roots[j], rootDel, j + 2);
+ KSI_DataHash_free(ksi->roots[j]);
+ ksi->roots[j] = NULL;
+ KSI_DataHash_free(rootDel);
+ rootDel = NULL;
+ if(ksi->bKeepTreeHashes) {
+ tlvWriteHashKSI(ksi, 0x0903, root);
+ }
+ if(ret != KSI_OK) goto done; /* checks sigblkHashTwoNodes() result! */
+ }
+ }
+
+ *hsh = root;
+ root = NULL;
+ ret = RSGTE_SUCCESS;
+
+done:
+ KSI_DataHash_free(root);
+ KSI_DataHash_free(rootDel);
+ return ret;
+}
+
+
+int
+sigblkFinishKSI(ksifile ksi)
+{
+ KSI_DataHash *root = NULL;
+ int ret = RSGTE_INTERNAL;
+ unsigned level = 0;
+
+ if (ksi == NULL) {
+ goto done;
+ }
+
+ if (ksi->nRecords == 0) {
+ ret = RSGTE_SUCCESS;
+ goto done;
+ }
+
+ ret = sigblkFinishTree(ksi, &root);
+ if (ret != RSGTE_SUCCESS) goto done;
+
+ //Multiplying leaves count by 2 to account for blinding masks
+ level=sigblkCalcLevel(2 * ksi->nRecords);
+
+ //in case of async mode we append the root hash to signer queue
+ if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) {
+ ret = tlvWriteNoSigLS12(ksi->blockFile, ksi->nRecords, root, NULL);
+ if (ret != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteNoSigLS12", ret);
+ goto done;
+ }
+
+ queueAddSignRequest(ksi->ctx, ksi, root, level);
+ root = NULL;
+ } else {
+ sigblkSign(ksi, root, level);
+ }
+
+ ret = RSGTE_SUCCESS;
+
+done:
+ KSI_DataHash_free(root);
+ free(ksi->IV);
+ ksi->IV = NULL;
+ ksi->bInBlk = 0;
+ return ret;
+}
+
+static int
+sigblkFinishKSINoSignature(ksifile ksi, const char *reason)
+{
+ KSI_DataHash *root = NULL;
+ int ret = RSGTE_INTERNAL;
+
+ if (ksi == NULL || ksi->ctx == NULL ||
+ (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS && ksi->sigFile == NULL) ||
+ ksi->blockFile == NULL || reason == NULL) {
+ goto done;
+ }
+
+ ret = sigblkAddMetadata(ksi, blockCloseReason, reason);
+ if (ret != RSGTE_SUCCESS) goto done;
+
+ ret = sigblkFinishTree(ksi, &root);
+ if (ret != RSGTE_SUCCESS) goto done;
+
+ ret = tlvWriteNoSigLS12(ksi->blockFile, ksi->nRecords, root, reason);
+ if (ret != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteNoSigLS12", ret);
+ goto done;
+ }
+
+ if (ksi->ctx->syncMode == LOGSIG_ASYNCHRONOUS) {
+ ret = tlvWriteNoSigLS12(ksi->sigFile, ksi->nRecords, root, reason);
+ if (ret != KSI_OK) {
+ reportKSIAPIErr(ksi->ctx, ksi, "tlvWriteNoSigLS12", ret);
+ goto done;
+ }
+ }
+
+ ret = RSGTE_SUCCESS;
+
+done:
+ KSI_DataHash_free(root);
+ free(ksi->IV);
+ ksi->IV=NULL;
+ ksi->bInBlk = 0;
+ return ret;
+}
+
+int
+rsksiSetAggregator(rsksictx ctx, char *uri, char *loginid, char *key) {
+ int r;
+ char *strTmp, *strTmpUri;
+
+ /* only use the strings if they are not empty */
+ ctx->aggregatorUri = (uri != NULL && strlen(uri) != 0) ? strdup(uri) : NULL;
+ ctx->aggregatorId = (loginid != NULL && strlen(loginid) != 0) ? strdup(loginid) : NULL;
+ ctx->aggregatorKey = (key != NULL && strlen(key) != 0) ? strdup(key) : NULL;
+
+ /* split the URI string up for possible HA endpoints */
+ strTmp = ctx->aggregatorUri;
+ while((strTmpUri = strsep(&strTmp, "|") ) != NULL) {
+ if(ctx->aggregatorEndpointCount >= KSI_CTX_HA_MAX_SUBSERVICES) {
+ report(ctx, "Maximum number (%d) of service endoints reached, ignoring endpoint: %s",
+ KSI_CTX_HA_MAX_SUBSERVICES, strTmpUri);
+ }
+ else {
+ ctx->aggregatorEndpoints[ctx->aggregatorEndpointCount] = strTmpUri;
+ ctx->aggregatorEndpointCount++;
+ }
+ }
+
+ r = KSI_CTX_setAggregator(ctx->ksi_ctx, ctx->aggregatorUri, ctx->aggregatorId, ctx->aggregatorKey);
+ if(r != KSI_OK) {
+ ctx->disabled = true;
+ reportKSIAPIErr(ctx, NULL, "KSI_CTX_setAggregator", r);
+ return KSI_INVALID_ARGUMENT;
+ }
+
+ return r;
+}
+
+
+int
+rsksiSetDebugFile(rsksictx ctx, char *val) {
+ if(!val)
+ return KSI_INVALID_ARGUMENT;
+
+ ctx->debugFileName=strdup(val);
+ return KSI_OK;
+}
+
+static bool
+add_queue_item(rsksictx ctx, QITEM_type type, KSI_DataHash *root, FILE *sigFile, uint64_t intarg1, uint64_t intarg2) {
+ QueueItem *qi = (QueueItem*) malloc(sizeof (QueueItem));
+ if (!qi) {
+ ctx->disabled = true;
+ return false;
+ }
+
+ qi->root = root;
+ qi->file = sigFile;
+
+ qi->type = type;
+ qi->status = QITEM_WAITING;
+ qi->intarg1 = intarg1;
+ qi->intarg2 = intarg2;
+ qi->respHandle = NULL;
+ qi->ksi_status = KSI_UNKNOWN_ERROR;
+ qi->request_time = time(NULL);
+ if (ProtectedQueue_addItem(ctx->signer_queue, qi) == false) {
+ ctx->disabled = true;
+ free(qi);
+ return false;
+ }
+ return true;
+}
+
+static bool
+queueAddCloseFile(rsksictx ctx, ksifile ksi) {
+ return add_queue_item(ctx, QITEM_CLOSE_FILE, NULL, ksi->sigFile, 0, 0);
+}
+
+static bool
+queueAddNewFile(rsksictx ctx, ksifile ksi) {
+ return add_queue_item(ctx, QITEM_NEW_FILE, NULL, ksi->sigFile, time(NULL) + ctx->blockTimeLimit, 0);
+}
+
+static bool
+queueAddQuit(rsksictx ctx) {
+ return add_queue_item(ctx, QITEM_QUIT, NULL, NULL, 0, 0);
+}
+
+static bool
+queueAddSignRequest(rsksictx ctx, ksifile ksi, KSI_DataHash *root, unsigned level) {
+ return add_queue_item(ctx, QITEM_SIGNATURE_REQUEST, root, ksi->sigFile, ksi->nRecords, level);
+}
+
+static bool
+save_response(rsksictx ctx, FILE* outfile, QueueItem *item) {
+ bool ret = false;
+ KSI_Signature *sig = NULL;
+ unsigned char *raw = NULL;
+ size_t raw_len;
+ int res = KSI_OK;
+
+ if(item->respHandle != NULL && item->ksi_status == KSI_OK) {
+ CHECK_KSI_API(KSI_AsyncHandle_getSignature(item->respHandle, &sig), ctx,
+ "KSI_AsyncHandle_getSignature");
+ CHECK_KSI_API(KSI_Signature_serialize(sig, &raw, &raw_len), ctx,
+ "KSI_Signature_serialize");
+ tlvWriteKSISigLS12(outfile, item->intarg1, raw, raw_len);
+ KSI_free(raw);
+ }
+ else {
+ tlvWriteNoSigLS12(outfile, item->intarg1, item->root, KSI_getErrorString(item->ksi_status));
+ }
+ ret = true;
+
+cleanup:
+ if(res != KSI_OK)
+ tlvWriteNoSigLS12(outfile, item->intarg1, item->root, KSI_getErrorString(res));
+
+ KSI_Signature_free(sig);
+
+ return ret;
+}
+
+static KSI_DataHash* clone_hash(KSI_CTX *ksi_ctx, const KSI_DataHash* hash) {
+ int res = KSI_UNKNOWN_ERROR;
+ const unsigned char *imprint = NULL;
+ size_t imprint_len = 0;
+ KSI_DataHash* tmp = NULL;
+
+ if (hash == NULL) return NULL;
+ res = KSI_DataHash_getImprint(hash, &imprint, &imprint_len);
+ if (res != KSI_OK) return NULL;
+ res = KSI_DataHash_fromImprint(ksi_ctx, imprint, imprint_len, &tmp);
+ if (res != KSI_OK) return NULL;
+
+ return tmp;
+}
+
+static bool
+process_requests_async(rsksictx ctx, KSI_CTX *ksi_ctx, KSI_AsyncService *as) {
+ bool ret = false;
+ QueueItem *item = NULL;
+ int res = KSI_OK, tmpRes;
+ KSI_AsyncHandle *reqHandle = NULL;
+ KSI_AsyncHandle *respHandle = NULL;
+ KSI_DataHash *clonedHash = NULL;
+ KSI_AggregationReq *req = NULL;
+ KSI_Config *config = NULL;
+ KSI_Integer *level;
+ long extError;
+ KSI_Utf8String *errorMsg;
+ int state, ksi_status;
+ unsigned i;
+ size_t p;
+
+ KSI_AsyncService_getPendingCount(as, &p);
+
+ /* Check if there are pending/available responses and associate them with the request items */
+ while(true) {
+ respHandle = NULL;
+ item = NULL;
+ tmpRes=KSI_AsyncService_run(as, &respHandle, &p);
+ if(tmpRes!=KSI_OK)
+ reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_run", tmpRes);
+
+ if (respHandle == NULL) { /* nothing received */
+ break;
+ }
+
+#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22
+ if (p != 0 && ctx->roundCount > 0) {
+ ctx->roundCount--;
+ } else {
+ ctx->bRoundLock = 0;
+ ctx->roundCount = 0;
+ }
+#endif
+ state = KSI_ASYNC_STATE_UNDEFINED;
+
+ CHECK_KSI_API(KSI_AsyncHandle_getState(respHandle, &state), ctx, "KSI_AsyncHandle_getState");
+
+ if(state == KSI_ASYNC_STATE_PUSH_CONFIG_RECEIVED) {
+ res = KSI_AsyncHandle_getConfig(respHandle, &config);
+ if(res == KSI_OK) {
+ handle_ksi_config(ctx, as, config);
+ KSI_AsyncHandle_free(respHandle);
+ } else
+ reportKSIAPIErr(ctx, NULL, "KSI_AsyncHandle_getConfig", res);
+ }
+ else if(state == KSI_ASYNC_STATE_RESPONSE_RECEIVED) {
+ CHECK_KSI_API(KSI_AsyncHandle_getRequestCtx(respHandle, (const void**)&item), ctx,
+ "KSI_AsyncHandle_getRequestCtx");
+ item->respHandle = respHandle;
+ item->ksi_status = KSI_OK;
+ }
+ else if(state == KSI_ASYNC_STATE_ERROR) {
+ CHECK_KSI_API(KSI_AsyncHandle_getRequestCtx(respHandle, (const void**)&item), ctx,
+ "KSI_AsyncHandle_getRequestCtx");
+ errorMsg = NULL;
+ KSI_AsyncHandle_getError(respHandle, &ksi_status);
+ KSI_AsyncHandle_getExtError(respHandle, &extError);
+ KSI_AsyncHandle_getErrorMessage(respHandle, &errorMsg);
+ report(ctx, "Asynchronous request returned error %s (%d), %lu %s",
+ KSI_getErrorString(ksi_status), ksi_status, extError,
+ errorMsg ? KSI_Utf8String_cstr(errorMsg) : "");
+ KSI_AsyncHandle_free(respHandle);
+
+ if(item)
+ item->ksi_status = ksi_status;
+ }
+
+ if(item)
+ item->status = QITEM_DONE;
+ }
+
+ KSI_AsyncService_getPendingCount(as, &p);
+
+ /* Send all the new requests in the back of the queue to the server */
+ for(i = 0; i < ProtectedQueue_count(ctx->signer_queue); i++) {
+ item = NULL;
+ if(!ProtectedQueue_getItem(ctx->signer_queue, i, (void**)&item) || !item)
+ continue;
+ /* ingore non request queue items */
+ if(item->type != QITEM_SIGNATURE_REQUEST)
+ continue;
+
+ /* stop at first processed item */
+ if(item->status != QITEM_WAITING)
+ continue;
+
+ /* Due to a bug in libksi it is possible that async signer may send out
+ * more signing requests than permitted by the gateway. Workaround is to
+ * keep track of signing requests here.
+ */
+#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22
+ if (ctx->roundCount >= ctx->max_requests) ctx->bRoundLock = 1;
+ if (ctx->bRoundLock) break;
+#endif
+
+
+ /* The data hash is produced in another thread by another KSI_CTX and
+ * libksi internal uses KSI_DataHash cache to reduce the amount of
+ * memory allocations by recycling old objects. Lets clone the hash
+ * value with current KSI_CTX as we can not be sure that this thread is
+ * not affecting the data hash cache operated by another thread.
+ */
+ clonedHash = clone_hash(ksi_ctx, item->root);
+ CHECK_KSI_API(KSI_AggregationReq_new(ksi_ctx, &req), ctx, "KSI_AggregationReq_new");
+ CHECK_KSI_API(KSI_AggregationReq_setRequestHash((KSI_AggregationReq*)req,
+ clonedHash), ctx,
+ "KSI_AggregationReq_setRequestHash");
+ clonedHash = NULL;
+ CHECK_KSI_API(KSI_Integer_new(ksi_ctx, item->intarg2, &level), ctx,
+ "KSI_Integer_new");
+ CHECK_KSI_API(KSI_AggregationReq_setRequestLevel(req, level), ctx,
+ "KSI_AggregationReq_setRequestLevel");
+ CHECK_KSI_API(KSI_AsyncAggregationHandle_new(ksi_ctx, req, &reqHandle), ctx,
+ "KSI_AsyncAggregationHandle_new");
+ CHECK_KSI_API(KSI_AsyncHandle_setRequestCtx(reqHandle, (void*)item, NULL), ctx,
+ "KSI_AsyncRequest_setRequestContext");
+ res = KSI_AsyncService_addRequest(as, reqHandle); /* this can fail because of throttling */
+
+ if (res == KSI_OK) {
+ item->status = QITEM_SENT;
+#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22
+ ctx->roundCount++;
+#endif
+ } else {
+ reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_addRequest", res);
+ KSI_AsyncHandle_free(reqHandle);
+ item->status = QITEM_DONE;
+ item->ksi_status = res;
+ break;
+ }
+
+ if (i != 0 && i % ctx->max_requests == 0) {
+ CHECK_KSI_API(KSI_AsyncService_run(as, NULL, NULL), ctx,
+ "KSI_AsyncService_run");
+ }
+ }
+ CHECK_KSI_API(KSI_AsyncService_run(as, NULL, NULL), ctx,
+ "KSI_AsyncService_run");
+
+ /* Save all consequent fulfilled responses in the front of the queue to the signature file */
+ while(ProtectedQueue_count(ctx->signer_queue)) {
+ item = NULL;
+ if(!ProtectedQueue_getItem(ctx->signer_queue, 0, (void**)&item))
+ break;
+
+ if(!item) {
+ ProtectedQueue_popFront(ctx->signer_queue, (void**) &item);
+ continue;
+ }
+
+ /* stop at first non request queue item (maybe file close/open, quit) */
+ if(item->type!=QITEM_SIGNATURE_REQUEST)
+ break;
+
+ /* stop at first unfinished queue item because the signatures need to be ordered */
+ if(item->status != QITEM_DONE)
+ break;
+
+ ProtectedQueue_popFront(ctx->signer_queue, (void**) &item);
+ save_response(ctx, item->file, item);
+ fflush(item->file);
+ /* the main thread has to be locked when the hash is freed to avoid a race condition */
+ /* TODO: this need more elegant solution, hash should be detached from creation context*/
+ pthread_mutex_lock(&ctx->module_lock);
+ KSI_DataHash_free(item->root);
+ KSI_AsyncHandle_free(item->respHandle);
+ free(item);
+ pthread_mutex_unlock(&ctx->module_lock);
+ }
+
+ ret = true;
+
+cleanup:
+ KSI_DataHash_free(clonedHash);
+ KSI_AsyncService_getPendingCount(as, &p);
+ return ret;
+}
+
+/* This can only be used when signer thread has terminated or within the thread. */
+static bool
+rsksictxCloseAllPendingBlocksWithoutSignature(rsksictx ctx, const char *reason) {
+ bool ret = false;
+ QueueItem *item = NULL;
+ int res = KSI_OK;
+
+ /* Save all consequent fulfilled responses in the front of the queue to the signature file */
+ while(ProtectedQueue_count(ctx->signer_queue)) {
+ item = NULL;
+ ProtectedQueue_popFront(ctx->signer_queue, (void**) &item);
+
+ if(item == NULL) {
+ continue;
+ }
+
+ /* Skip non request queue item. */
+ if(item->type == QITEM_SIGNATURE_REQUEST) {
+ res = tlvWriteNoSigLS12(item->file, item->intarg1, item->root, reason);
+ if (res != KSI_OK) {
+ reportKSIAPIErr(ctx, NULL, "tlvWriteNoSigLS12", res);
+ ret = false;
+ goto cleanup;
+ }
+ fflush(item->file);
+ }
+
+ KSI_DataHash_free(item->root);
+ KSI_AsyncHandle_free(item->respHandle);
+ free(item);
+ }
+
+ ret = true;
+
+cleanup:
+ return ret;
+}
+
+
+static void
+request_async_config(rsksictx ctx, KSI_CTX *ksi_ctx, KSI_AsyncService *as) {
+ KSI_Config *cfg = NULL;
+ KSI_AsyncHandle *cfgHandle = NULL;
+ KSI_AggregationReq *cfgReq = NULL;
+ int res;
+ bool bSuccess = false;
+
+ CHECK_KSI_API(KSI_AggregationReq_new(ksi_ctx, &cfgReq), ctx, "KSI_AggregationReq_new");
+ CHECK_KSI_API(KSI_Config_new(ksi_ctx, &cfg), ctx, "KSI_Config_new");
+ CHECK_KSI_API(KSI_AggregationReq_setConfig(cfgReq, cfg), ctx, "KSI_AggregationReq_setConfig");
+ CHECK_KSI_API(KSI_AsyncAggregationHandle_new(ksi_ctx, cfgReq, &cfgHandle), ctx,
+ "KSI_AsyncAggregationHandle_new");
+ CHECK_KSI_API(KSI_AsyncService_addRequest(as, cfgHandle), ctx, "KSI_AsyncService_addRequest");
+
+ bSuccess = true;
+
+cleanup:
+ if(!bSuccess) {
+ if(cfgHandle)
+ KSI_AsyncHandle_free(cfgHandle);
+ else if(cfgReq)
+ KSI_AggregationReq_free(cfgReq);
+ else if(cfg)
+ KSI_Config_free(cfg);
+ }
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
+void *signer_thread(void *arg) {
+ int res = KSI_UNKNOWN_ERROR;
+ rsksictx ctx = (rsksictx) arg;
+ KSI_CTX *ksi_ctx = NULL;
+ KSI_AsyncService *as = NULL;
+ size_t size_t_value = 0;
+ size_t ksiFileCount = 0;
+ int endpoints = 0;
+ bool bSleep = true;
+
+
+ CHECK_KSI_API(KSI_CTX_new(&ksi_ctx), ctx, "KSI_CTX_new");
+ CHECK_KSI_API(KSI_CTX_setAggregator(ksi_ctx,
+ ctx->aggregatorUri, ctx->aggregatorId, ctx->aggregatorKey),
+ ctx, "KSI_CTX_setAggregator");
+
+ if(ctx->debugFile) {
+ res = KSI_CTX_setLoggerCallback(ksi_ctx, rsksiStreamLogger, ctx->debugFile);
+ if (res != KSI_OK)
+ reportKSIAPIErr(ctx, NULL, "Unable to set logger callback", res);
+ res = KSI_CTX_setLogLevel(ksi_ctx, ctx->debugLevel);
+ if (res != KSI_OK)
+ reportKSIAPIErr(ctx, NULL, "Unable to set log level", res);
+ }
+
+ CHECK_KSI_API(KSI_CTX_setOption(ksi_ctx, KSI_OPT_AGGR_HMAC_ALGORITHM, (void*)((size_t)ctx->hmacAlg)),
+ ctx, "KSI_CTX_setOption");
+
+ res = KSI_SigningHighAvailabilityService_new(ksi_ctx, &as);
+ if (res != KSI_OK) {
+ reportKSIAPIErr(ctx, NULL, "KSI_SigningAsyncService_new", res);
+ }
+ else {
+ int i = 0;
+ for (i = 0; i < ctx->aggregatorEndpointCount; i++) {
+ res = KSI_AsyncService_addEndpoint(as,
+ ctx->aggregatorEndpoints[i], ctx->aggregatorId, ctx->aggregatorKey);
+ if (res != KSI_OK) {
+ //This can fail if the protocol is not supported by async api.
+ reportKSIAPIErr(ctx, NULL, "KSI_AsyncService_addEndpoint", res);
+ continue;
+ }
+
+ endpoints++;
+ }
+ }
+
+ if(endpoints == 0) { /* no endpoint accepted, deleting the service */
+ report(ctx, "No endpoints added, signing service disabled");
+ ctx->disabled = true;
+ KSI_AsyncService_free(as);
+ as=NULL;
+ goto cleanup;
+ }
+
+ /* Lets use buffer value, as libksi requires size_t. */
+ size_t_value = ctx->max_requests;
+ KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_REQUEST_CACHE_SIZE,
+ (void*)size_t_value);
+ size_t_value = ctx->blockSigTimeout;
+ KSI_AsyncService_setOption(as, KSI_ASYNC_OPT_SND_TIMEOUT,
+ (void*)size_t_value);
+
+
+ ctx->signer_state = SIGNER_STARTED;
+ while (true) {
+ QueueItem *item = NULL;
+
+ if (isAggrConfNeeded(ctx)) {
+ request_async_config(ctx, ksi_ctx, as);
+ }
+
+ /* Wait for a work item or timeout*/
+ if (bSleep) {
+ ProtectedQueue_waitForItem(ctx->signer_queue, NULL, ctx->threadSleepms);
+ }
+ bSleep = true;
+
+ /* Check for block time limit. */
+ sigblkCheckTimeOut(ctx);
+
+ /* in case there are no items go around*/
+ if (ProtectedQueue_count(ctx->signer_queue) == 0) {
+ process_requests_async(ctx, ksi_ctx, as);
+ continue;
+ }
+
+ /* process signing requests only if there is an open signature file */
+ if(ksiFileCount > 0) {
+ /* check for pending/unsent requests in asynchronous service */
+ if(!process_requests_async(ctx, ksi_ctx, as)) {
+ // probably fatal error, disable signing, error should be already reported
+ ctx->disabled = true;
+ goto cleanup;
+ }
+ }
+ /* if there are sig. requests still in the front, then we have to start over*/
+ if (ProtectedQueue_peekFront(ctx->signer_queue, (void**) &item)
+ && item->type == QITEM_SIGNATURE_REQUEST)
+ continue;
+
+ /* Handle other types of work items */
+ if (ProtectedQueue_popFront(ctx->signer_queue, (void**) &item) != 0) {
+ /* There is no point to sleep after processing non request type item
+ * as there is great possibility that next item can already be
+ * processed. */
+ bSleep = false;
+
+ if (item->type == QITEM_CLOSE_FILE) {
+ if (item->file) {
+ fclose(item->file);
+ item->file = NULL;
+ }
+
+ if (ksiFileCount > 0) ksiFileCount--;
+ } else if (item->type == QITEM_NEW_FILE) {
+ ksiFileCount++;
+ } else if (item->type == QITEM_QUIT) {
+ free(item);
+
+ /* Will look into work queue for pending KSI signatures and will output
+ * unsigned block marker instead of actual KSI signature to finalize this
+ * thread quickly.
+ */
+ rsksictxCloseAllPendingBlocksWithoutSignature(ctx,
+ "Signing not finished due to sudden closure of lmsig_ksi-ls12 module.");
+ rsksictxForceCloseWithoutSig(ctx,
+ "Block closed due to sudden closure of lmsig_ksi-ls12 module.");
+
+ goto cleanup;
+ }
+
+ free(item);
+ }
+ }
+
+cleanup:
+
+ KSI_AsyncService_free(as);
+ KSI_CTX_free(ksi_ctx);
+ ctx->signer_state = SIGNER_STOPPED;
+
+ return NULL;
+}
+#pragma GCC diagnostic push
diff --git a/runtime/lib_ksils12.h b/runtime/lib_ksils12.h
new file mode 100644
index 0000000..c85ff85
--- /dev/null
+++ b/runtime/lib_ksils12.h
@@ -0,0 +1,250 @@
+/* lib_ksils12.h - rsyslog's KSI-LS12 support library
+ *
+ * Copyright 2013-2017 Adiscon GmbH and Guardtime, Inc.
+ *
+ * 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_KSILS12_H
+#define INCLUDED_KSILS12_H
+#include <ksi/ksi.h>
+
+#include "lib_ksi_queue.h"
+
+#define MAX_ROOTS 64
+
+/* Flags and record types for TLV handling */
+#define RSGT_FLAG_NONCRIT 0x20
+#define RSGT_FLAG_FORWARD 0x40
+#define RSGT_TYPE_MASK 0x1f
+#define RSGT_FLAG_TLV16 0x80
+
+/* check return state of operation and abort, if non-OK */
+#define CHKr(code) if((r = code) != 0) goto done
+
+/* check the return value of a ksi api call and log a message in case of error */
+#define CHECK_KSI_API(code, context, msg) if((res = code) != 0) do { \
+ reportKSIAPIErr(context, NULL, msg, res); \
+ goto cleanup; \
+ } while (0)
+
+
+typedef enum LOGSIG_SyncMode_en {
+ /** The block hashes and ksi signatures in one file */
+ LOGSIG_ASYNCHRONOUS = 0x00,
+ /** The block hashes and ksi signatures split into separate files */
+ LOGSIG_SYNCHRONOUS = 0x01
+} LOGSIG_SyncMode;
+
+enum {
+ /* Signer state assigned before the signer thread is initialized. State remains
+ * until thread initialization begins. In case of system failure to create new
+ * thread state remains the same.
+ */
+ SIGNER_IDLE = 0x01,
+
+ /* Signer state assigned while signer thread initialization is in progress.
+ */
+ SIGNER_INIT = 0x02,
+
+ /* Signer state assigned when signer thread is initialized and ready to work.
+ */
+ SIGNER_STARTED = 0x04,
+
+ /* Thread state assigned when signer thread is being closed (signer thread returns).
+ */
+ SIGNER_STOPPED = 0x08
+};
+
+/* 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...]
+ */
+
+typedef struct rsksictx_s *rsksictx;
+typedef struct ksifile_s *ksifile;
+typedef struct ksierrctx_s ksierrctx_t;
+
+
+/* context for gt calls. This primarily serves as a container for the
+ * config settings. The actual file-specific data is kept in ksifile.
+ */
+struct rsksictx_s {
+ KSI_CTX *ksi_ctx; /* libksi's context object */
+ KSI_DataHasher *hasher;
+ KSI_HashAlgorithm hashAlg;
+ KSI_HashAlgorithm hmacAlg;
+ uint8_t bKeepRecordHashes;
+ uint8_t bKeepTreeHashes;
+ uint64_t confInterval;
+ time_t tConfRequested;
+ uint64_t blockLevelLimit;
+ uint32_t blockTimeLimit;
+ uint32_t blockSigTimeout;
+ uint32_t effectiveBlockLevelLimit; /* level limit adjusted by gateway settings */
+ uint32_t threadSleepms;
+ uint8_t syncMode;
+ uid_t fileUID; /* IDs for creation */
+ uid_t dirUID;
+ gid_t fileGID;
+ gid_t dirGID;
+ int fCreateMode; /* mode to use when creating files */
+ int fDirCreateMode; /* mode to use when creating files */
+ char* aggregatorUri;
+ char* aggregatorId;
+ char* aggregatorKey;
+ char* aggregatorEndpoints[KSI_CTX_HA_MAX_SUBSERVICES];
+ int aggregatorEndpointCount;
+ char* random_source;
+ pthread_mutex_t module_lock;
+ pthread_t signer_thread;
+ ProtectedQueue *signer_queue;
+#if KSI_SDK_VER_MAJOR == 3 && KSI_SDK_VER_MINOR < 22
+ size_t roundCount; /* Count of signing requests in round. */
+ uint8_t bRoundLock; /* A lock for async. signer. */
+#endif
+ int signer_state;
+ uint8_t disabled; /* permits to disable the plugin --> set to 1 */
+
+ ksifile *ksi; /* List of signature files for keeping track of block timeouts. */
+ size_t ksiCapacity;
+ size_t ksiCount;
+
+ char *debugFileName;
+ int debugLevel;
+ FILE *debugFile;
+ uint64_t max_requests;
+ void (*errFunc)(void *, unsigned char*);
+ void (*logFunc)(void *, unsigned char*);
+ void *usrptr; /* for error function */
+};
+
+/* this describes a file, as far as librsksi is concerned */
+struct ksifile_s {
+ /* the following data items are mirrored from rsksictx to
+ * increase cache hit ratio (they are frequently accesed).
+ */
+ KSI_HashAlgorithm hashAlg;
+ uint8_t bKeepRecordHashes;
+ uint8_t bKeepTreeHashes;
+ uint64_t blockSizeLimit;
+ uint32_t blockTimeLimit;
+ /* end mirrored properties */
+ uint8_t disabled; /* permits to disable this file --> set to 1 */
+ uint8_t *IV; /* initial value for blinding masks */
+ unsigned char lastLeaf[KSI_MAX_IMPRINT_LEN]; /* last leaf hash (maybe of previous block)
+ --> preserve on term */
+ unsigned char *blockfilename;
+ unsigned char *ksifilename;
+ unsigned char *statefilename;
+ uint64_t nRecords; /* current number of records in current block */
+ uint64_t bInBlk; /* are we currently inside a blk --> need to finish on close */
+ time_t blockStarted;
+ int8_t nRoots;
+ /* algo engineering: roots structure is split into two arrays
+ * in order to improve cache hits.
+ */
+ KSI_DataHash *roots[MAX_ROOTS];
+ /* data members for the associated TLV file */
+ FILE *blockFile;
+ FILE *sigFile; /* Note that this may only be closed by signer thread or when signer thread has terminated. */
+ rsksictx ctx;
+};
+
+/* the following defines the ksistate file record. Currently, this record
+ * is fixed, we may change that over time.
+ */
+struct rsksistatefile {
+ char hdr[9]; /* must be "KSISTAT10" */
+ uint8_t hashID;
+ uint8_t lenHash;
+ /* after that, the hash value is contained within the file */
+};
+
+/* error states */
+#define RSGTE_SUCCESS 0 /* Success state */
+#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_SIG_EXTEND 6/* error extending signature */
+#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_SIGNATURE 17 /* Signature is invalid (KSI_Signature_verifyDataHash)*/
+#define RSGTE_TS_CREATEHASH 18 /* error creating HASH (KSI_DataHash_create) */
+#define RSGTE_TS_DERENCODE 19 /* error DER-Encoding a timestamp */
+#define RSGTE_HASH_CREATE 20 /* error creating a hash */
+#define RSGTE_END_OF_SIG 21 /* unexpected end of signature - more log line exist */
+#define RSGTE_END_OF_LOG 22 /* unexpected end of log file - more signatures exist */
+#define RSGTE_EXTRACT_HASH 23 /* error extracting hashes for record */
+#define RSGTE_CONFIG_ERROR 24 /* Configuration error */
+#define RSGTE_NETWORK_ERROR 25 /* Network error */
+#define RSGTE_MISS_KSISIG 26 /* KSI signature missing */
+#define RSGTE_INTERNAL 27 /* Internal error */
+
+#define getIVLenKSI(bh) (hashOutputLengthOctetsKSI((bh)->hashID))
+#define rsksiSetBlockLevelLimit(ctx, limit) ((ctx)->blockLevelLimit = (ctx)->effectiveBlockLevelLimit = limit)
+#define rsksiSetBlockTimeLimit(ctx, limit) ((ctx)->blockTimeLimit = limit)
+#define rsksiSetBlockSigTimeout(ctx, val) ((ctx)->blockSigTimeout = val)
+#define rsksiSetConfInterval(ctx, val) ((ctx)->confInterval = val)
+#define rsksiSetKeepRecordHashes(ctx, val) ((ctx)->bKeepRecordHashes = val)
+#define rsksiSetKeepTreeHashes(ctx, val) ((ctx)->bKeepTreeHashes = val)
+#define rsksiSetFileFormat(ctx, val) ((ctx)->fileFormat = val)
+#define rsksiSetSyncMode(ctx, val) ((ctx)->syncMode = val)
+#define rsksiSetRandomSource(ctx, val) ((ctx)->random_source = strdup(val))
+#define rsksiSetFileUID(ctx, val) ((ctx)->fileUID = val) /* IDs for creation */
+#define rsksiSetDirUID(ctx, val) ((ctx)->dirUID = val)
+#define rsksiSetFileGID(ctx, val) ((ctx)->fileGID= val)
+#define rsksiSetDirGID(ctx, val) ((ctx)->dirGID = val)
+#define rsksiSetCreateMode(ctx, val) ((ctx)->fCreateMode= val)
+#define rsksiSetDirCreateMode(ctx, val) ((ctx)->fDirCreateMode = val)
+#define rsksiSetDebugLevel(ctx, val) ((ctx)->debugLevel = val)
+
+
+int rsksiSetDebugFile(rsksictx ctx, char *val);
+int rsksiSetAggregator(rsksictx ctx, char *uri, char *loginid, char *key);
+int rsksiSetHashFunction(rsksictx ctx, char *algName);
+int rsksiSetHmacFunction(rsksictx ctx, char *algName);
+int rsksiInitModule(rsksictx ctx);
+rsksictx rsksiCtxNew(void);
+void rsksisetErrFunc(rsksictx ctx, void (*func)(void*, unsigned char *), void *usrptr);
+void rsksisetLogFunc(rsksictx ctx, void (*func)(void*, unsigned char *), void *usrptr);
+void reportKSIAPIErr(rsksictx ctx, ksifile ksi, const char *apiname, int ecode);
+ksifile rsksiCtxOpenFile(rsksictx ctx, unsigned char *logfn);
+int rsksifileDestruct(ksifile ksi);
+void rsksiCtxDel(rsksictx ctx);
+void sigblkInitKSI(ksifile ksi);
+int sigblkAddRecordKSI(ksifile ksi, const unsigned char *rec, const size_t len);
+int sigblkAddLeaf(ksifile ksi, const unsigned char *rec, const size_t len, bool metadata);
+unsigned sigblkCalcLevel(unsigned leaves);
+int sigblkFinishKSI(ksifile ksi);
+int sigblkAddMetadata(ksifile ksi, const char *key, const char *value);
+int sigblkCreateMask(ksifile ksi, KSI_DataHash **m);
+int sigblkCreateHash(ksifile ksi, KSI_DataHash **r, const unsigned char *rec, const size_t len);
+int sigblkHashTwoNodes(ksifile ksi, KSI_DataHash **node, KSI_DataHash *m, KSI_DataHash *r, uint8_t level);
+
+#endif /* #ifndef INCLUDED_KSILS12_H */
diff --git a/runtime/libgcry.c b/runtime/libgcry.c
new file mode 100644
index 0000000..c5a8171
--- /dev/null
+++ b/runtime/libgcry.c
@@ -0,0 +1,765 @@
+/* gcry.c - rsyslog's libgcrypt based crypto provider
+ *
+ * Copyright 2013-2018 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 "debug.h"
+#include "libgcry.h"
+
+#define READBUF_SIZE 4096 /* size of the read buffer */
+
+static rsRetVal rsgcryBlkBegin(gcryfile gf);
+
+int
+rsgcryAlgoname2Algo(char *const __restrict__ 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;
+}
+
+int
+rsgcryModename2Mode(char *const __restrict__ 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;
+}
+static rsRetVal
+eiWriteRec(gcryfile gf, const char *recHdr, size_t lenRecHdr, const char *buf, size_t lenBuf)
+{
+ struct iovec iov[3];
+ ssize_t nwritten, towrite;
+ DEFiRet;
+
+ iov[0].iov_base = (void*)recHdr;
+ iov[0].iov_len = lenRecHdr;
+ iov[1].iov_base = (void*)buf;
+ iov[1].iov_len = lenBuf;
+ iov[2].iov_base = (void*)"\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) {
+ CHKiRet(eiOpenRead(gf));
+ assert(gf->fd != -1); /* cannot happen after successful return */
+ bNeedClose = 1;
+ }
+
+ 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 %zd 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));
+ const char *const cmp_IV = "IV"; // work-around for static analyzer
+ if(strcmp(rectype, cmp_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 %zd, expected %zd\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));
+ const char *const const_END = "END"; // clang static analyzer work-around
+ if(strcmp(rectype, const_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 __attribute__((nonnull(2)))
+eiWriteIV(gcryfile gf, const uchar *const 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", (long long) 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));
+ if(ctx != NULL) {
+ 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->key);
+ free(ctx);
+ }
+}
+
+static 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 %zd chars, blkLength %zd, mod %zd, pad %zd\n",
+ *plen, pF->blkLength, *plen % pF->blkLength, nPad);
+ for(i = 0 ; i < nPad ; ++i)
+ buf[(*plen)+i] = 0x00;
+ (*plen)+= nPad;
+}
+
+static void ATTR_NONNULL()
+removePadding(uchar *const buf, size_t *const plen)
+{
+ const size_t len = *plen;
+ size_t iSrc, iDst;
+
+ iSrc = 0;
+ /* skip to first NUL */
+ while(iSrc < len && buf[iSrc] == '\0') {
+ ++iSrc;
+ }
+ iDst = iSrc;
+
+ while(iSrc < len) {
+ if(buf[iSrc] != 0x00)
+ buf[iDst++] = buf[iSrc];
+ ++iSrc;
+ }
+
+ *plen = iDst;
+}
+
+/* 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;
+}
+
+/* We use random numbers to initiate the IV. Rsyslog runtime will ensure
+ * we get a sufficiently large number.
+ */
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wunknown-attributes"
+#endif
+static rsRetVal
+#if defined(__clang__)
+__attribute__((no_sanitize("shift"))) /* IV shift causes known overflow */
+#endif
+seedIV(gcryfile gf, uchar **iv)
+{
+ long rndnum = 0; /* keep compiler happy -- this value is always overriden */
+ DEFiRet;
+
+ CHKmalloc(*iv = calloc(1, gf->blkLength));
+ for(size_t i = 0 ; i < gf->blkLength; ++i) {
+ const int shift = (i % 4) * 8;
+ if(shift == 0) { /* need new random data? */
+ rndnum = randomNumber();
+ }
+ (*iv)[i] = 0xff & ((rndnum & (255u << shift)) >> shift);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static 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! */
+ iRet = 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 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;
+ const char openMode = gf->openMode;
+
+ 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(openMode == 'r') {
+ readIV(gf, &iv);
+ readBlkEnd(gf);
+ } else {
+ CHKiRet(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(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 0000000..15fce71
--- /dev/null
+++ b/runtime/libgcry.h
@@ -0,0 +1,87 @@
+/* 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>
+#include <gcrypt.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(const 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);
+int rsgcryModename2Mode(char *const __restrict__ modename);
+int rsgcryAlgoname2Algo(char *const __restrict__ algoname);
+
+/* 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 __attribute__((unused))
+gcryfileSetDeleteOnClose(gcryfile gf, const int val)
+{
+ if(gf != NULL)
+ gf->bDeleteOnClose = val;
+}
+
+#endif /* #ifndef INCLUDED_LIBGCRY_H */
diff --git a/runtime/libgcry_common.c b/runtime/libgcry_common.c
new file mode 100644
index 0000000..1cc56fe
--- /dev/null
+++ b/runtime/libgcry_common.c
@@ -0,0 +1,200 @@
+/* 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)
+ * on error, errno is set and can be queried
+ * 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(const char *const fn, char **const key, unsigned *const keylen)
+{
+ struct stat sb;
+ int r = -1;
+
+ const int fd = open(fn, O_RDONLY);
+ if(fd < 0) goto done;
+ if(fstat(fd, &sb) == -1) goto done;
+ if(sb.st_size > 64*1024) {
+ errno = EMSGSIZE;
+ goto done;
+ }
+ if((*key = malloc(sb.st_size)) == NULL) goto done;
+ if(read(fd, *key, sb.st_size) != sb.st_size) goto done;
+ *keylen = sb.st_size;
+ r = 0;
+done:
+ if(fd >= 0) {
+ close(fd);
+ }
+ 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/linkedlist.c b/runtime/linkedlist.c
new file mode 100644
index 0000000..7088737
--- /dev/null
+++ b/runtime/linkedlist.c
@@ -0,0 +1,415 @@
+/* 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)(void*), rsRetVal (*pKeyDestructor)(void*),
+int (*pCmpOp)(void*,void*))
+{
+ 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;
+
+ assert(pThis != NULL);
+
+ pElt = pThis->pRoot;
+ while(pElt != NULL) {
+ /* keep the list structure in a consistent state as
+ * the destructor bellow may reference it again
+ */
+ pThis->pRoot = pElt->pNext;
+ if(pElt->pNext == NULL)
+ pThis->pLast = NULL;
+
+ /* we ignore errors during destruction, as we need to try
+ * finish the linked list in any case.
+ */
+ llDestroyElt(pThis, pElt);
+ pElt = pThis->pRoot;
+ }
+
+ 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 0000000..1cd3a14
--- /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)(), 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 0000000..21cfaff
--- /dev/null
+++ b/runtime/lmcry_gcry.c
@@ -0,0 +1,339 @@
+/* lmcry_gcry.c
+ *
+ * An implementation of the cryprov interface for libgcrypt.
+ *
+ * Copyright 2013-2017 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 <errno.h>
+
+#include "module-template.h"
+#include "glbl.h"
+#include "errmsg.h"
+#include "cryprov.h"
+#include "parserif.h"
+#include "libgcry.h"
+#include "lmcry_gcry.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+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)
+{
+ LogError(0, RS_RET_CRYPROV_ERR, "Crypto Provider"
+ "Error: %s - disabling encryption", emsg);
+}
+#endif
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(lmcry_gcry)
+ CHKmalloc(pThis->ctx = gcryCtxNew());
+finalize_it:
+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 = 0;
+ 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(pvals == NULL) {
+ parser_errmsg("error crypto provider gcryconfig parameters]");
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ 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) {
+ LogError(errno, RS_RET_ERR, "error reading keyfile %s",
+ keyfile);
+ ABORT_FINALIZE(RS_RET_INVALID_PARAMS);
+ }
+ }
+ if(keyprogram != NULL) {
+ r = gcryGetKeyFromProg((char*)keyprogram, (char**)&key, &keylen);
+ if(r != 0) {
+ 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) {
+ LogError(0, RS_RET_INVALID_PARAMS, "Key length %d expected, but "
+ "key of length %d given", r, keylen);
+ ABORT_FINALIZE(RS_RET_INVALID_PARAMS);
+ }
+
+finalize_it:
+ free(key);
+ free(keyfile);
+ free(algo);
+ free(keyprogram);
+ free(mode);
+ if(pvals != NULL)
+ cnfparamvalsDestruct(pvals, pblk);
+ 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);
+
+ iRet = rsgcryInitCrypt(pThis->ctx, pgf, fn, openMode);
+ if(iRet != RS_RET_OK) {
+ 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(glbl, CORE_COMPONENT);
+
+ rsgcryExit();
+ENDObjClassExit(lmcry_gcry)
+
+
+BEGINObjClassInit(lmcry_gcry, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+
+ if(rsgcryInit() != 0) {
+ 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 0000000..f9c854b
--- /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_ksi-ls12.c b/runtime/lmsig_ksi-ls12.c
new file mode 100644
index 0000000..0336bc2
--- /dev/null
+++ b/runtime/lmsig_ksi-ls12.c
@@ -0,0 +1,346 @@
+/* lmsig_ksi-ls12.c
+ *
+ * An implementation of the sigprov interface for KSI-LS12.
+ *
+ * Copyright 2013-2017 Adiscon GmbH and Guardtime, Inc.
+ *
+ * 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_ksi-ls12.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+/* tables for interfacing with the v6 config system */
+static struct cnfparamdescr cnfpdescr[] = {
+ { "sig.hashfunction", eCmdHdlrGetWord, 0 },
+ { "sig.aggregator.url", eCmdHdlrGetWord, CNFPARAM_REQUIRED},
+ { "sig.aggregator.user", eCmdHdlrGetWord, 0},
+ { "sig.aggregator.key", eCmdHdlrGetWord, 0},
+ { "sig.aggregator.hmacAlg", eCmdHdlrGetWord, 0 },
+ { "sig.block.levelLimit", eCmdHdlrSize, CNFPARAM_REQUIRED},
+ { "sig.block.timeLimit", eCmdHdlrInt, 0},
+ { "sig.block.signtimeout", eCmdHdlrInt, 0},
+ { "sig.confinterval", eCmdHdlrInt, 0},
+ { "sig.keeprecordhashes", eCmdHdlrBinary, 0 },
+ { "sig.keeptreehashes", eCmdHdlrBinary, 0},
+ { "sig.fileformat", eCmdHdlrString, 0},
+ { "sig.syncmode", eCmdHdlrString, 0},
+ { "sig.randomsource", eCmdHdlrString, 0},
+ { "sig.debugfile", eCmdHdlrString, 0},
+ { "sig.debuglevel", eCmdHdlrInt, 0},
+ { "dirowner", eCmdHdlrUID, 0}, /* legacy: dirowner */
+ { "dirownernum", eCmdHdlrInt, 0 }, /* legacy: dirownernum */
+ { "dirgroup", eCmdHdlrGID, 0 }, /* legacy: dirgroup */
+ { "dirgroupnum", eCmdHdlrInt, 0 }, /* legacy: dirgroupnum */
+ { "fileowner", eCmdHdlrUID, 0 }, /* legacy: fileowner */
+ { "fileownernum", eCmdHdlrInt, 0 }, /* legacy: fileownernum */
+ { "filegroup", eCmdHdlrGID, 0 }, /* legacy: filegroup */
+ { "filegroupnum", eCmdHdlrInt, 0 }, /* legacy: filegroupnum */
+ { "dircreatemode", eCmdHdlrFileCreateMode, 0 }, /* legacy: dircreatemode */
+ { "filecreatemode", eCmdHdlrFileCreateMode, 0 } /* legacy: filecreatemode */
+};
+static struct cnfparamblk pblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfpdescr)/sizeof(struct cnfparamdescr),
+ cnfpdescr
+ };
+
+
+static void
+errfunc(__attribute__((unused)) void *usrptr, uchar *emsg)
+{
+ LogError(0, RS_RET_SIGPROV_ERR, "KSI Signature Provider"
+ "Error: %s", emsg);
+}
+
+static void
+logfunc(__attribute__((unused)) void *usrptr, uchar *emsg)
+{
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO,
+ "KSI/LS12 Signature Provider: %s", emsg);
+}
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(lmsig_ksi_ls12)
+ pThis->ctx = rsksiCtxNew();
+ rsksisetErrFunc(pThis->ctx, errfunc, NULL);
+ rsksisetLogFunc(pThis->ctx, logfunc, NULL);
+ENDobjConstruct(lmsig_ksi_ls12)
+
+
+/* destructor for the lmsig_ksi object */
+BEGINobjDestruct(lmsig_ksi_ls12) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(lmsig_ksi_ls12)
+ rsksiCtxDel(pThis->ctx);
+ENDobjDestruct(lmsig_ksi_ls12)
+
+#define REPORT_PARAM_MISSING(param) \
+ do { \
+ pThis->ctx->disabled = true; \
+ LogError(0, RS_RET_ERR, "%s missing - signing disabled", param); \
+ /* TODO: ABORT_FINALIZE actually is useless because the return value is not checked by the caller*/ \
+ ABORT_FINALIZE(RS_RET_KSI_ERR); \
+ } while(0)
+
+
+
+/* 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)
+{
+ char *ag_uri = NULL, *ag_loginid = NULL, *ag_key = NULL;
+ char *hash=NULL, *hmac = NULL;
+ lmsig_ksi_ls12_t *pThis = (lmsig_ksi_ls12_t*) pT;
+ int i;
+ uchar *cstr;
+ struct cnfparamvals *pvals;
+ DEFiRet;
+ pvals = nvlstGetParams(lst, &pblk, NULL);
+ if(pvals == NULL) {
+ LogError(0, RS_RET_ERR, "Failed to load configuration - signing disabled");
+ pThis->ctx->disabled=true;
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+ if(Debug) {
+ dbgprintf("sig param blk in lmsig_ksi:\n");
+ cnfparamsPrint(&pblk, pvals);
+ }
+
+ for(i = 0 ; i < pblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(pblk.descr[i].name, "sig.hashfunction")) {
+ hash = (char*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if (!strcmp(pblk.descr[i].name, "sig.aggregator.url")) {
+ ag_uri = es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(pblk.descr[i].name, "sig.aggregator.user")) {
+ ag_loginid = es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if (!strcmp(pblk.descr[i].name, "sig.aggregator.key")) {
+ ag_key = es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(pblk.descr[i].name, "sig.aggregator.hmacAlg")) {
+ hmac = (char*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if (!strcmp(pblk.descr[i].name, "sig.block.levelLimit")) {
+ if (pvals[i].val.d.n < 2) {
+ LogError(0, RS_RET_ERR, "sig.block.levelLimit "
+ "%llu invalid - signing disabled", pvals[i].val.d.n);
+ pThis->ctx->disabled = true;
+ } else {
+ rsksiSetBlockLevelLimit(pThis->ctx, pvals[i].val.d.n);
+ }
+ } else if (!strcmp(pblk.descr[i].name, "sig.block.timeLimit")) {
+ if (pvals[i].val.d.n < 0) {
+ LogError(0, RS_RET_ERR, "sig.block.timeLimit "
+ "%llu invalid - signing disabled", pvals[i].val.d.n);
+ pThis->ctx->disabled = true;
+ } else {
+ rsksiSetBlockTimeLimit(pThis->ctx, pvals[i].val.d.n);
+ }
+ } else if (!strcmp(pblk.descr[i].name, "sig.confinterval")) {
+ if (pvals[i].val.d.n < 0) {
+ LogError(0, RS_RET_ERR, "sig.confinterval "
+ "%llu invalid - signing disabled", pvals[i].val.d.n);
+ pThis->ctx->disabled = true;
+ } else {
+ rsksiSetConfInterval(pThis->ctx, pvals[i].val.d.n);
+ }
+ } else if (!strcmp(pblk.descr[i].name, "sig.keeprecordhashes")) {
+ rsksiSetKeepRecordHashes(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "sig.block.signtimeout")) {
+ if (pvals[i].val.d.n < 0) {
+ LogError(0, RS_RET_ERR, "sig.block.signtimeout "
+ "%llu invalid - signing disabled", pvals[i].val.d.n);
+ pThis->ctx->disabled = true;
+ } else {
+ rsksiSetBlockSigTimeout(pThis->ctx, pvals[i].val.d.n);
+ }
+ } else if(!strcmp(pblk.descr[i].name, "sig.keeptreehashes")) {
+ rsksiSetKeepTreeHashes(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "sig.syncmode")) {
+ cstr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ if (!strcasecmp((char*) cstr, "sync")) rsksiSetSyncMode(pThis->ctx, LOGSIG_SYNCHRONOUS);
+ else if (!strcasecmp((char*) cstr, "async")) rsksiSetSyncMode(pThis->ctx, LOGSIG_ASYNCHRONOUS);
+ else LogError(0, RS_RET_ERR, "sig.syncmode '%s' unknown - using default", cstr);
+ free(cstr);
+ } else if (!strcmp(pblk.descr[i].name, "sig.randomsource")) {
+ cstr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ rsksiSetRandomSource(pThis->ctx, (char*) cstr);
+ free(cstr);
+ } else if (!strcmp(pblk.descr[i].name, "sig.debugfile")) {
+ cstr = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ rsksiSetDebugFile(pThis->ctx, (char*) cstr);
+ free(cstr);
+ } else if (!strcmp(pblk.descr[i].name, "sig.debuglevel")) {
+ rsksiSetDebugLevel(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "dirowner")) {
+ rsksiSetDirUID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "dirownernum")) {
+ rsksiSetDirUID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "dirgroup")) {
+ rsksiSetDirGID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "dirgroupnum")) {
+ rsksiSetDirGID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "fileowner")) {
+ rsksiSetFileUID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "fileownernum")) {
+ rsksiSetFileUID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "filegroup")) {
+ rsksiSetFileGID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "filegroupnum")) {
+ rsksiSetFileGID(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "dircreatemode")) {
+ rsksiSetDirCreateMode(pThis->ctx, pvals[i].val.d.n);
+ } else if (!strcmp(pblk.descr[i].name, "filecreatemode")) {
+ rsksiSetCreateMode(pThis->ctx, pvals[i].val.d.n);
+ } else {
+ DBGPRINTF("lmsig_ksi: program error, non-handled "
+ "param '%s'\n", pblk.descr[i].name);
+ }
+ }
+
+ if(rsksiSetHashFunction(pThis->ctx, hash ? hash : (char*) "default") != KSI_OK) {
+ ABORT_FINALIZE(RS_RET_KSI_ERR);
+ }
+
+ if(rsksiSetHmacFunction(pThis->ctx, hmac ? hmac : (char*) "default") != KSI_OK) {
+ ABORT_FINALIZE(RS_RET_KSI_ERR);
+ }
+
+ if(rsksiSetAggregator(pThis->ctx, ag_uri, ag_loginid, ag_key) != KSI_OK) {
+ ABORT_FINALIZE(RS_RET_KSI_ERR);
+ }
+
+finalize_it:
+ free(ag_uri);
+ free(ag_loginid);
+ free(ag_key);
+ free(hash);
+ free(hmac);
+
+ if(pvals != NULL)
+ cnfparamvalsDestruct(pvals, &pblk);
+ RETiRet;
+}
+
+
+static rsRetVal
+OnFileOpen(void *pT, uchar *fn, void *pGF) {
+ lmsig_ksi_ls12_t *pThis = (lmsig_ksi_ls12_t*) pT;
+ ksifile *pgf = (ksifile*) pGF;
+ DEFiRet;
+ /* note: if *pgf is set to NULL, this auto-disables GT functions */
+ *pgf = rsksiCtxOpenFile(pThis->ctx, fn);
+ sigblkInitKSI(*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_ksi-ls12: onRecordWrite (%d): %s\n", lenRec - 1, rec);
+ sigblkAddRecordKSI(pF, rec, lenRec - 1);
+
+ RETiRet;
+}
+
+static rsRetVal
+OnFileClose(void *pF)
+{
+ DEFiRet;
+ DBGPRINTF("lmsig_ksi_ls12: onFileClose\n");
+ rsksifileDestruct(pF);
+
+ RETiRet;
+}
+
+BEGINobjQueryInterface(lmsig_ksi_ls12)
+CODESTARTobjQueryInterface(lmsig_ksi_ls12)
+ 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_ksi_ls12Construct;
+ pIf->SetCnfParam = SetCnfParam;
+ pIf->Destruct = (rsRetVal(*)(void*)) lmsig_ksi_ls12Destruct;
+ pIf->OnFileOpen = OnFileOpen;
+ pIf->OnRecordWrite = OnRecordWrite;
+ pIf->OnFileClose = OnFileClose;
+finalize_it:
+ENDobjQueryInterface(lmsig_ksi_ls12)
+
+
+BEGINObjClassExit(lmsig_ksi_ls12, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(lmsig_ksi_ls12)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ENDObjClassExit(lmsig_ksi_ls12)
+
+
+BEGINObjClassInit(lmsig_ksi_ls12, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ENDObjClassInit(lmsig_ksi_ls12)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+lmsig_ksi_ls12ClassExit();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION;
+CHKiRet(lmsig_ksi_ls12ClassInit(pModInfo));
+ENDmodInit
diff --git a/runtime/lmsig_ksi-ls12.h b/runtime/lmsig_ksi-ls12.h
new file mode 100644
index 0000000..3ac5cce
--- /dev/null
+++ b/runtime/lmsig_ksi-ls12.h
@@ -0,0 +1,40 @@
+/* An implementation of the sigprov interface for KSI-LS12.
+ *
+ * Copyright 2013-2017 Adiscon GmbH and Guardtime, Inc.
+ *
+ * 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_LS12_KSI_H
+#define INCLUDED_LMSIG_LS12_KSI_H
+#include "sigprov.h"
+#include "lib_ksils12.h"
+
+/* interface is defined in sigprov.h, we just implement it! */
+#define lmsig_ksi_ls12CURR_IF_VERSION sigprovCURR_IF_VERSION
+typedef sigprov_if_t lmsig_ksi_ls12_if_t;
+
+/* the lmsig_ksi object */
+struct lmsig_ksi_ls12_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ rsksictx ctx; /* librsksi context - contains all we need */
+};
+typedef struct lmsig_ksi_ls12_s lmsig_ksi_ls12_t;
+
+/* prototypes */
+PROTOTYPEObj(lmsig_ksi_ls12);
+
+#endif /* #ifndef INCLUDED_LMSIG_LS12_KSI_H */
diff --git a/runtime/lookup.c b/runtime/lookup.c
new file mode 100644
index 0000000..4408581
--- /dev/null
+++ b/runtime/lookup.c
@@ -0,0 +1,1093 @@
+/* lookup.c
+ * Support for lookup tables in RainerScript.
+ *
+ * Copyright 2013-2023 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 <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <json.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "srUtils.h"
+#include "errmsg.h"
+#include "lookup.h"
+#include "msg.h"
+#include "rsconf.h"
+#include "dirty.h"
+#include "unicode-helper.h"
+
+PRAGMA_IGNORE_Wdeprecated_declarations
+/* definitions for objects we access */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+/* forward definitions */
+static rsRetVal lookupReadFile(lookup_t *pThis, const uchar* name, const uchar* filename);
+static void lookupDestruct(lookup_t *pThis);
+
+/* static data */
+/* tables for interfacing with the v6 config system (as far as we need to) */
+static struct cnfparamdescr modpdescr[] = {
+ { "name", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "file", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "reloadOnHUP", eCmdHdlrBinary, 0 }
+};
+static struct cnfparamblk modpblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(modpdescr)/sizeof(struct cnfparamdescr),
+ modpdescr
+ };
+
+/* internal data-types */
+typedef struct uint32_index_val_s {
+ uint32_t index;
+ uchar *val;
+} uint32_index_val_t;
+
+const char * reloader_prefix = "lkp_tbl_reloader:";
+
+static void *
+lookupTableReloader(void *self);
+
+static void
+lookupStopReloader(lookup_ref_t *pThis);
+
+
+/* create a new lookup table object AND include it in our list of
+ * lookup tables.
+ */
+static rsRetVal
+lookupNew(lookup_ref_t **ppThis)
+{
+ lookup_ref_t *pThis = NULL;
+ lookup_t *t = NULL;
+ DEFiRet;
+
+ CHKmalloc(pThis = calloc(1, sizeof(lookup_ref_t)));
+ CHKmalloc(t = calloc(1, sizeof(lookup_t)));
+ pThis->do_reload = pThis->do_stop = 0;
+ pThis->reload_on_hup = 1; /*DO reload on HUP (default)*/
+
+ pThis->next = NULL;
+ if(loadConf->lu_tabs.root == NULL) {
+ loadConf->lu_tabs.root = pThis;
+ } else {
+ loadConf->lu_tabs.last->next = pThis;
+ }
+ loadConf->lu_tabs.last = pThis;
+
+ pThis->self = t;
+
+ *ppThis = pThis;
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ LogError(errno, iRet, "a lookup table could not be initialized");
+ free(t);
+ free(pThis);
+ }
+ RETiRet;
+}
+
+/* activate a lookup table entry once rsyslog is ready to do so */
+static rsRetVal
+lookupActivateTable(lookup_ref_t *pThis)
+{
+ DEFiRet;
+ int initialized = 0;
+
+ DBGPRINTF("lookupActivateTable called\n");
+ CHKiConcCtrl(pthread_rwlock_init(&pThis->rwlock, NULL));
+ initialized++; /*1*/
+ CHKiConcCtrl(pthread_mutex_init(&pThis->reloader_mut, NULL));
+ initialized++; /*2*/
+ CHKiConcCtrl(pthread_cond_init(&pThis->run_reloader, NULL));
+ initialized++; /*3*/
+ CHKiConcCtrl(pthread_attr_init(&pThis->reloader_thd_attr));
+ initialized++; /*4*/
+ pThis->do_reload = pThis->do_stop = 0;
+ CHKiConcCtrl(pthread_create(&pThis->reloader, &pThis->reloader_thd_attr,
+ lookupTableReloader, pThis));
+ initialized++; /*5*/
+
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ LogError(errno, iRet, "a lookup table could not be activated: "
+ "failed at init-step %d (please enable debug logs for details)",
+ initialized);
+ /* Can not happen with current code, but might occur in the future when
+ * an error-condition as added after step 5. If we leave it in, Coverity
+ * scan complains. So we comment it out but do not remove the code.
+ * Triggered by CID 185426
+ if (initialized > 4) lookupStopReloader(pThis);
+ */
+ if (initialized > 3) pthread_attr_destroy(&pThis->reloader_thd_attr);
+ if (initialized > 2) pthread_cond_destroy(&pThis->run_reloader);
+ if (initialized > 1) pthread_mutex_destroy(&pThis->reloader_mut);
+ if (initialized > 0) pthread_rwlock_destroy(&pThis->rwlock);
+ }
+ RETiRet;
+}
+
+/*must be called with reloader_mut acquired*/
+static void ATTR_NONNULL()
+freeStubValueForReloadFailure(lookup_ref_t *const pThis)
+{
+ if (pThis->stub_value_for_reload_failure != NULL) {
+ free(pThis->stub_value_for_reload_failure);
+ pThis->stub_value_for_reload_failure = NULL;
+ }
+}
+
+static void
+lookupStopReloader(lookup_ref_t *pThis) {
+ pthread_mutex_lock(&pThis->reloader_mut);
+ freeStubValueForReloadFailure(pThis);
+ pThis->do_reload = 0;
+ pThis->do_stop = 1;
+ pthread_cond_signal(&pThis->run_reloader);
+ pthread_mutex_unlock(&pThis->reloader_mut);
+ pthread_join(pThis->reloader, NULL);
+}
+
+static void
+lookupRefDestruct(lookup_ref_t *pThis)
+{
+ lookupStopReloader(pThis);
+ pthread_mutex_destroy(&pThis->reloader_mut);
+ pthread_cond_destroy(&pThis->run_reloader);
+ pthread_attr_destroy(&pThis->reloader_thd_attr);
+
+ pthread_rwlock_destroy(&pThis->rwlock);
+ lookupDestruct(pThis->self);
+ free(pThis->name);
+ free(pThis->filename);
+ free(pThis);
+}
+
+static void
+destructTable_str(lookup_t *pThis) {
+ uint32_t i = 0;
+ lookup_string_tab_entry_t *entries = pThis->table.str->entries;
+ for (i = 0; i < pThis->nmemb; i++) {
+ free(entries[i].key);
+ }
+ free(entries);
+ free(pThis->table.str);
+}
+
+
+static void
+destructTable_arr(lookup_t *pThis) {
+ free(pThis->table.arr->interned_val_refs);
+ free(pThis->table.arr);
+}
+
+static void
+destructTable_sparseArr(lookup_t *pThis) {
+ free(pThis->table.sprsArr->entries);
+ free(pThis->table.sprsArr);
+}
+
+static void
+lookupDestruct(lookup_t *pThis) {
+ uint32_t i;
+
+ if (pThis == NULL) return;
+
+ if (pThis->type == STRING_LOOKUP_TABLE) {
+ destructTable_str(pThis);
+ } else if (pThis->type == ARRAY_LOOKUP_TABLE) {
+ destructTable_arr(pThis);
+ } else if (pThis->type == SPARSE_ARRAY_LOOKUP_TABLE) {
+ destructTable_sparseArr(pThis);
+ } else if (pThis->type == STUBBED_LOOKUP_TABLE) {
+ /*nothing to be done*/
+ }
+
+ for (i = 0; i < pThis->interned_val_count; i++) {
+ free(pThis->interned_vals[i]);
+ }
+ free(pThis->interned_vals);
+ free(pThis->nomatch);
+ free(pThis);
+}
+
+void
+lookupInitCnf(lookup_tables_t *lu_tabs)
+{
+ lu_tabs->root = NULL;
+ lu_tabs->last = NULL;
+}
+
+void
+lookupDestroyCnf(void)
+{
+ lookup_ref_t *luref, *luref_next;
+ for(luref = runConf->lu_tabs.root ; luref != NULL ; ) {
+ luref_next = luref->next;
+ lookupRefDestruct(luref);
+ luref = luref_next;
+ }
+}
+
+/* comparison function for qsort() */
+static int
+qs_arrcmp_strtab(const void *s1, const void *s2)
+{
+ return ustrcmp(((lookup_string_tab_entry_t*)s1)->key, ((lookup_string_tab_entry_t*)s2)->key);
+}
+
+static int
+qs_arrcmp_ustrs(const void *s1, const void *s2)
+{
+ return ustrcmp(*(uchar**)s1, *(uchar**)s2);
+}
+
+static int
+qs_arrcmp_uint32_index_val(const void *s1, const void *s2)
+{
+ uint32_t first_value = ((uint32_index_val_t*)s1)->index;
+ uint32_t second_value = ((uint32_index_val_t*)s2)->index;
+ if (first_value < second_value) {
+ return -1;
+ }
+ return first_value - second_value;
+}
+
+static int
+qs_arrcmp_sprsArrtab(const void *s1, const void *s2)
+{
+ uint32_t first_value = ((lookup_sparseArray_tab_entry_t*)s1)->key;
+ uint32_t second_value = ((lookup_sparseArray_tab_entry_t*)s2)->key;
+ if (first_value < second_value) {
+ return -1;
+ }
+ return first_value - second_value;
+}
+
+/* comparison function for bsearch() and string array compare
+ * this is for the string lookup table type
+ */
+static int
+bs_arrcmp_strtab(const void *s1, const void *s2)
+{
+ return strcmp((char*)s1, (char*)((lookup_string_tab_entry_t*)s2)->key);
+}
+
+static int
+bs_arrcmp_str(const void *s1, const void *s2)
+{
+ return ustrcmp((uchar*)s1, *(uchar**)s2);
+}
+
+static int
+bs_arrcmp_sprsArrtab(const void *s1, const void *s2)
+{
+ uint32_t key = *(uint32_t*)s1;
+ uint32_t array_member_value = ((lookup_sparseArray_tab_entry_t*)s2)->key;
+ if (key < array_member_value) {
+ return -1;
+ }
+ return key - array_member_value;
+}
+
+static inline const char*
+defaultVal(lookup_t *pThis) {
+ return (pThis->nomatch == NULL) ? "" : (const char*) pThis->nomatch;
+}
+
+/* lookup_fn for different types of tables */
+static es_str_t*
+lookupKey_stub(lookup_t *pThis, lookup_key_t __attribute__((unused)) key) {
+ return es_newStrFromCStr((char*) pThis->nomatch, ustrlen(pThis->nomatch));
+}
+
+static es_str_t*
+lookupKey_str(lookup_t *pThis, lookup_key_t key) {
+ lookup_string_tab_entry_t *entry;
+ const char *r;
+ if(pThis->nmemb == 0) {
+ entry = NULL;
+ } else {
+ assert(pThis->table.str->entries);
+ entry = bsearch(key.k_str, pThis->table.str->entries, pThis->nmemb,
+ sizeof(lookup_string_tab_entry_t), bs_arrcmp_strtab);
+ }
+ if(entry == NULL) {
+ r = defaultVal(pThis);
+ } else {
+ r = (const char*)entry->interned_val_ref;
+ }
+ return es_newStrFromCStr(r, strlen(r));
+}
+
+static es_str_t*
+lookupKey_arr(lookup_t *pThis, lookup_key_t key) {
+ const char *r;
+ uint32_t uint_key = key.k_uint;
+ if ((pThis->nmemb == 0) || (uint_key < pThis->table.arr->first_key)) {
+ r = defaultVal(pThis);
+ } else {
+ uint32_t idx = uint_key - pThis->table.arr->first_key;
+ if (idx >= pThis->nmemb) {
+ r = defaultVal(pThis);
+ } else {
+ r = (char*) pThis->table.arr->interned_val_refs[idx];
+ }
+ }
+
+ return es_newStrFromCStr(r, strlen(r));
+}
+
+typedef int (comp_fn_t)(const void *s1, const void *s2);
+
+static void *
+bsearch_lte(const void *key, const void *base, size_t nmemb, size_t size, comp_fn_t *comp_fn)
+{
+ size_t l, u, idx;
+ const void *p;
+ int comparison;
+
+ l = 0;
+ u = nmemb;
+ if (l == u) {
+ return NULL;
+ }
+ while (l < u) {
+ idx = (l + u) / 2;
+ p = (void *) (((const char *) base) + (idx * size));
+ comparison = (*comp_fn)(key, p);
+ if (comparison < 0)
+ u = idx;
+ else if (comparison > 0)
+ l = idx + 1;
+ else
+ return (void *) p;
+ }
+ if (comparison < 0) {
+ if (idx == 0) {
+ return NULL;
+ }
+ idx--;
+ }
+ return (void *) (((const char *) base) + ( idx * size));
+}
+
+static es_str_t*
+lookupKey_sprsArr(lookup_t *pThis, lookup_key_t key) {
+ lookup_sparseArray_tab_entry_t *entry;
+ const char *r;
+ if (pThis->nmemb == 0) {
+ entry = NULL;
+ } else {
+ entry = bsearch_lte(&key.k_uint, pThis->table.sprsArr->entries, pThis->nmemb,
+ sizeof(lookup_sparseArray_tab_entry_t), bs_arrcmp_sprsArrtab);
+ }
+
+ if(entry == NULL) {
+ r = defaultVal(pThis);
+ } else {
+ r = (const char*)entry->interned_val_ref;
+ }
+ return es_newStrFromCStr(r, strlen(r));
+}
+
+/* builders for different table-types */
+
+#define NO_INDEX_ERROR(type, name) \
+ LogError(0, RS_RET_INVALID_VALUE, "'%s' lookup table named: '%s' has record(s) without 'index' "\
+"field", type, name); \
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+
+static rsRetVal
+build_StringTable(lookup_t *pThis, struct json_object *jtab, const uchar* name) {
+ uint32_t i;
+ struct json_object *jrow, *jindex, *jvalue;
+ uchar *value, *canonicalValueRef;
+ DEFiRet;
+
+ pThis->table.str = NULL;
+ CHKmalloc(pThis->table.str = calloc(1, sizeof(lookup_string_tab_t)));
+ if (pThis->nmemb > 0) {
+ CHKmalloc(pThis->table.str->entries = calloc(pThis->nmemb, sizeof(lookup_string_tab_entry_t)));
+
+ for(i = 0; i < pThis->nmemb; i++) {
+ jrow = json_object_array_get_idx(jtab, i);
+ jindex = json_object_object_get(jrow, "index");
+ jvalue = json_object_object_get(jrow, "value");
+ if (jindex == NULL || json_object_is_type(jindex, json_type_null)) {
+ NO_INDEX_ERROR("string", name);
+ }
+ CHKmalloc(pThis->table.str->entries[i].key = ustrdup((uchar*) json_object_get_string(jindex)));
+ value = (uchar*) json_object_get_string(jvalue);
+ uchar **found = (uchar**) bsearch(value, pThis->interned_vals,
+ pThis->interned_val_count, sizeof(uchar*), bs_arrcmp_str);
+ if(found == NULL) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "lookup.c:build_StringTable(): "
+ "internal error, bsearch returned NULL for '%s'", value);
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+ // I give up, I see no way to remove false positive -- rgerhards, 2017-10-24
+ #ifndef __clang_analyzer__
+ canonicalValueRef = *found;
+ if(canonicalValueRef == NULL) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "lookup.c:build_StringTable(): "
+ "internal error, canonicalValueRef returned from bsearch "
+ "is NULL for '%s'", value);
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+ pThis->table.str->entries[i].interned_val_ref = canonicalValueRef;
+ #endif
+ }
+ qsort(pThis->table.str->entries, pThis->nmemb, sizeof(lookup_string_tab_entry_t), qs_arrcmp_strtab);
+ }
+
+ pThis->lookup = lookupKey_str;
+ pThis->key_type = LOOKUP_KEY_TYPE_STRING;
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+build_ArrayTable(lookup_t *pThis, struct json_object *jtab, const uchar *name) {
+ uint32_t i;
+ struct json_object *jrow, *jindex, *jvalue;
+ uchar *canonicalValueRef;
+ uint32_t prev_index, _index;
+ uint8_t prev_index_set;
+ uint32_index_val_t *indexes = NULL;
+ DEFiRet;
+
+ prev_index_set = 0;
+
+ pThis->table.arr = NULL;
+ CHKmalloc(pThis->table.arr = calloc(1, sizeof(lookup_array_tab_t)));
+ if (pThis->nmemb > 0) {
+ CHKmalloc(indexes = calloc(pThis->nmemb, sizeof(uint32_index_val_t)));
+ CHKmalloc(pThis->table.arr->interned_val_refs = calloc(pThis->nmemb, sizeof(uchar*)));
+
+ for(i = 0; i < pThis->nmemb; i++) {
+ jrow = json_object_array_get_idx(jtab, i);
+ jindex = json_object_object_get(jrow, "index");
+ jvalue = json_object_object_get(jrow, "value");
+ if (jindex == NULL || json_object_is_type(jindex, json_type_null)) {
+ NO_INDEX_ERROR("array", name);
+ }
+ indexes[i].index = (uint32_t) json_object_get_int(jindex);
+ indexes[i].val = (uchar*) json_object_get_string(jvalue);
+ }
+ qsort(indexes, pThis->nmemb, sizeof(uint32_index_val_t), qs_arrcmp_uint32_index_val);
+ for(i = 0; i < pThis->nmemb; i++) {
+ _index = indexes[i].index;
+ if (prev_index_set == 0) {
+ prev_index = _index;
+ prev_index_set = 1;
+ pThis->table.arr->first_key = _index;
+ } else {
+ if (_index != ++prev_index) {
+ LogError(0, RS_RET_INVALID_VALUE, "'array' lookup table name: '%s' "
+ "has non-contiguous members between index '%d' and '%d'",
+ name, prev_index, _index);
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+ }
+ }
+ uchar *const *const canonicalValueRef_ptr = bsearch(indexes[i].val, pThis->interned_vals,
+ pThis->interned_val_count, sizeof(uchar*), bs_arrcmp_str);
+ if(canonicalValueRef_ptr == NULL) {
+ LogError(0, RS_RET_ERR, "BUG: canonicalValueRef not found in "
+ "build_ArrayTable(), %s:%d", __FILE__, __LINE__);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ canonicalValueRef = *canonicalValueRef_ptr;
+ assert(canonicalValueRef != NULL);
+ pThis->table.arr->interned_val_refs[i] = canonicalValueRef;
+ }
+ }
+
+ pThis->lookup = lookupKey_arr;
+ pThis->key_type = LOOKUP_KEY_TYPE_UINT;
+
+finalize_it:
+ free(indexes);
+ RETiRet;
+}
+
+static rsRetVal
+build_SparseArrayTable(lookup_t *pThis, struct json_object *jtab, const uchar* name) {
+ uint32_t i;
+ struct json_object *jrow, *jindex, *jvalue;
+ uchar *value, *canonicalValueRef;
+ DEFiRet;
+
+ pThis->table.str = NULL;
+ CHKmalloc(pThis->table.sprsArr = calloc(1, sizeof(lookup_sparseArray_tab_t)));
+ if (pThis->nmemb > 0) {
+ CHKmalloc(pThis->table.sprsArr->entries = calloc(pThis->nmemb, sizeof(lookup_sparseArray_tab_entry_t)));
+
+ for(i = 0; i < pThis->nmemb; i++) {
+ jrow = json_object_array_get_idx(jtab, i);
+ jindex = json_object_object_get(jrow, "index");
+ jvalue = json_object_object_get(jrow, "value");
+ if (jindex == NULL || json_object_is_type(jindex, json_type_null)) {
+ NO_INDEX_ERROR("sparseArray", name);
+ }
+ pThis->table.sprsArr->entries[i].key = (uint32_t) json_object_get_int(jindex);
+ value = (uchar*) json_object_get_string(jvalue);
+ uchar *const *const canonicalValueRef_ptr = bsearch(value, pThis->interned_vals,
+ pThis->interned_val_count, sizeof(uchar*), bs_arrcmp_str);
+ if(canonicalValueRef_ptr == NULL) {
+ LogError(0, RS_RET_ERR, "BUG: canonicalValueRef not found in "
+ "build_SparseArrayTable(), %s:%d", __FILE__, __LINE__);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ canonicalValueRef = *canonicalValueRef_ptr;
+ assert(canonicalValueRef != NULL);
+ pThis->table.sprsArr->entries[i].interned_val_ref = canonicalValueRef;
+ }
+ qsort(pThis->table.sprsArr->entries, pThis->nmemb, sizeof(lookup_sparseArray_tab_entry_t),
+ qs_arrcmp_sprsArrtab);
+ }
+
+ pThis->lookup = lookupKey_sprsArr;
+ pThis->key_type = LOOKUP_KEY_TYPE_UINT;
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+lookupBuildStubbedTable(lookup_t *pThis, const uchar* stub_val) {
+ DEFiRet;
+
+ CHKmalloc(pThis->nomatch = ustrdup(stub_val));
+ pThis->lookup = lookupKey_stub;
+ pThis->type = STUBBED_LOOKUP_TABLE;
+ pThis->key_type = LOOKUP_KEY_TYPE_NONE;
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+lookupBuildTable_v1(lookup_t *pThis, struct json_object *jroot, const uchar* name) {
+ struct json_object *jnomatch, *jtype, *jtab;
+ struct json_object *jrow, *jvalue;
+ const char *table_type, *nomatch_value;
+ const uchar **all_values;
+ const uchar *curr, *prev;
+ uint32_t i, j;
+ uint32_t uniq_values;
+
+ DEFiRet;
+ all_values = NULL;
+
+ jnomatch = json_object_object_get(jroot, "nomatch");
+ jtype = json_object_object_get(jroot, "type");
+ jtab = json_object_object_get(jroot, "table");
+ if (jtab == NULL || !json_object_is_type(jtab, json_type_array)) {
+ LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' has invalid table definition", name);
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+ }
+ pThis->nmemb = json_object_array_length(jtab);
+ table_type = json_object_get_string(jtype);
+ if (table_type == NULL) {
+ table_type = "string";
+ }
+
+ CHKmalloc(all_values = malloc(pThis->nmemb * sizeof(uchar*)));
+
+ /* before actual table can be loaded, prepare all-value list and remove duplicates*/
+ for(i = 0; i < pThis->nmemb; i++) {
+ jrow = json_object_array_get_idx(jtab, i);
+ jvalue = json_object_object_get(jrow, "value");
+ if (jvalue == NULL || json_object_is_type(jvalue, json_type_null)) {
+ LogError(0, RS_RET_INVALID_VALUE, "'%s' lookup table named: '%s' has record(s) "
+ "without 'value' field", table_type, name);
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+ }
+ all_values[i] = (const uchar*) json_object_get_string(jvalue);
+ }
+ qsort(all_values, pThis->nmemb, sizeof(uchar*), qs_arrcmp_ustrs);
+ uniq_values = 1;
+ for(i = 1; i < pThis->nmemb; i++) {
+ curr = all_values[i];
+ prev = all_values[i - 1];
+ if (ustrcmp(prev, curr) != 0) {
+ uniq_values++;
+ }
+ }
+
+ if (pThis->nmemb > 0) {
+ CHKmalloc(pThis->interned_vals = malloc(uniq_values * sizeof(uchar*)));
+ j = 0;
+ CHKmalloc(pThis->interned_vals[j++] = ustrdup(all_values[0]));
+ for(i = 1; i < pThis->nmemb ; ++i) {
+ curr = all_values[i];
+ prev = all_values[i - 1];
+ if (ustrcmp(prev, curr) != 0) {
+ CHKmalloc(pThis->interned_vals[j++] = ustrdup(all_values[i]));
+ }
+ }
+ pThis->interned_val_count = uniq_values;
+ }
+ /* uniq values captured (sorted) */
+
+ nomatch_value = json_object_get_string(jnomatch);
+ if (nomatch_value != NULL) {
+ CHKmalloc(pThis->nomatch = (uchar*) strdup(nomatch_value));
+ }
+
+ if (strcmp(table_type, "array") == 0) {
+ pThis->type = ARRAY_LOOKUP_TABLE;
+ CHKiRet(build_ArrayTable(pThis, jtab, name));
+ } else if (strcmp(table_type, "sparseArray") == 0) {
+ pThis->type = SPARSE_ARRAY_LOOKUP_TABLE;
+ CHKiRet(build_SparseArrayTable(pThis, jtab, name));
+ } else if (strcmp(table_type, "string") == 0) {
+ pThis->type = STRING_LOOKUP_TABLE;
+ CHKiRet(build_StringTable(pThis, jtab, name));
+ } else {
+ LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' uses unupported "
+ "type: '%s'", name, table_type);
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+ }
+finalize_it:
+ if (all_values != NULL) free(all_values);
+ RETiRet;
+}
+
+static rsRetVal
+lookupBuildTable(lookup_t *pThis, struct json_object *jroot, const uchar* name)
+{
+ struct json_object *jversion;
+ int version = 1;
+
+ DEFiRet;
+
+ jversion = json_object_object_get(jroot, "version");
+ if (jversion != NULL && !json_object_is_type(jversion, json_type_null)) {
+ version = json_object_get_int(jversion);
+ } else {
+ LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' doesn't specify version "
+ "(will use default value: %d)", name, version);
+ }
+ if (version == 1) {
+ CHKiRet(lookupBuildTable_v1(pThis, jroot, name));
+ } else {
+ LogError(0, RS_RET_INVALID_VALUE, "lookup table named: '%s' uses unsupported "
+ "version: %d", name, version);
+ ABORT_FINALIZE(RS_RET_INVALID_VALUE);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* find a lookup table. This is a naive O(n) algo, but this really
+ * doesn't matter as it is called only a few times during config
+ * load. The function returns either a pointer to the requested
+ * table or NULL, if not found.
+ */
+lookup_ref_t * ATTR_NONNULL()
+lookupFindTable(uchar *name)
+{
+ lookup_ref_t *curr;
+
+ for(curr = loadConf->lu_tabs.root ; curr != NULL ; curr = curr->next) {
+ if(!ustrcmp(curr->name, name))
+ break;
+ }
+ return curr;
+}
+
+
+/* this reloads a lookup table. This is done while the engine is running,
+ * as such the function must ensure proper locking and proper order of
+ * operations (so that nothing can interfere). If the table cannot be loaded,
+ * the old table is continued to be used.
+ */
+static rsRetVal
+lookupReloadOrStub(lookup_ref_t *pThis, const uchar* stub_val) {
+ lookup_t *newlu, *oldlu; /* dummy to be able to use support functions without
+ affecting current settings. */
+ DEFiRet;
+
+ oldlu = pThis->self;
+ newlu = NULL;
+
+ DBGPRINTF("reload requested for lookup table '%s'\n", pThis->name);
+ CHKmalloc(newlu = calloc(1, sizeof(lookup_t)));
+ if (stub_val == NULL) {
+ CHKiRet(lookupReadFile(newlu, pThis->name, pThis->filename));
+ } else {
+ CHKiRet(lookupBuildStubbedTable(newlu, stub_val));
+ }
+ /* all went well, copy over data members */
+ pthread_rwlock_wrlock(&pThis->rwlock);
+ pThis->self = newlu;
+ pthread_rwlock_unlock(&pThis->rwlock);
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ if (stub_val == NULL) {
+ LogError(0, RS_RET_INTERNAL_ERROR,
+ "lookup table '%s' could not be reloaded from file '%s'",
+ pThis->name, pThis->filename);
+ } else {
+ LogError(0, RS_RET_INTERNAL_ERROR,
+ "lookup table '%s' could not be stubbed with value '%s'",
+ pThis->name, stub_val);
+ }
+ lookupDestruct(newlu);
+ } else {
+ if (stub_val == NULL) {
+ LogMsg(0, RS_RET_OK, LOG_INFO, "lookup table '%s' reloaded from file '%s'",
+ pThis->name, pThis->filename);
+ } else {
+ LogError(0, RS_RET_OK, "lookup table '%s' stubbed with value '%s'",
+ pThis->name, stub_val);
+ }
+ lookupDestruct(oldlu);
+ }
+ RETiRet;
+}
+
+static rsRetVal
+lookupDoStub(lookup_ref_t *pThis, const uchar* stub_val)
+{
+ int already_stubbed = 0;
+ DEFiRet;
+ pthread_rwlock_rdlock(&pThis->rwlock);
+ if (pThis->self->type == STUBBED_LOOKUP_TABLE &&
+ ustrcmp(pThis->self->nomatch, stub_val) == 0)
+ already_stubbed = 1;
+ pthread_rwlock_unlock(&pThis->rwlock);
+ if (! already_stubbed) {
+ LogError(0, RS_RET_OK, "stubbing lookup table '%s' with value '%s'",
+ pThis->name, stub_val);
+ CHKiRet(lookupReloadOrStub(pThis, stub_val));
+ } else {
+ LogError(0, RS_RET_OK, "lookup table '%s' is already stubbed with value '%s'",
+ pThis->name, stub_val);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static uint8_t
+lookupIsReloadPending(lookup_ref_t *pThis) {
+ uint8_t reload_pending;
+ pthread_mutex_lock(&pThis->reloader_mut);
+ reload_pending = pThis->do_reload;
+ pthread_mutex_unlock(&pThis->reloader_mut);
+ return reload_pending;
+}
+
+/* note: stub_val_if_reload_fails may or may not be NULL */
+rsRetVal ATTR_NONNULL(1)
+lookupReload(lookup_ref_t *const pThis, const uchar *const stub_val_if_reload_fails)
+{
+ uint8_t locked = 0;
+ int lock_errno = 0;
+ DEFiRet;
+ assert(pThis != NULL);
+ if ((lock_errno = pthread_mutex_trylock(&pThis->reloader_mut)) == 0) {
+ locked = 1;
+ /*so it doesn't leak memory in situation where 2 reload requests are issued back to back*/
+ freeStubValueForReloadFailure(pThis);
+ if (stub_val_if_reload_fails != NULL) {
+ CHKmalloc(pThis->stub_value_for_reload_failure = ustrdup(stub_val_if_reload_fails));
+ }
+ pThis->do_reload = 1;
+ pthread_cond_signal(&pThis->run_reloader);
+ } else {
+ LogError(lock_errno, RS_RET_INTERNAL_ERROR, "attempt to trigger "
+ "reload of lookup table '%s' failed (not stubbing)", pThis->name);
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ /* we can choose to stub the table here, but it'll hurt because
+ the table reloader may take time to complete the reload
+ and stubbing because of a concurrent reload message may
+ not be desirable (except in very tightly controled environments
+ where reload-triggering messages pushed are timed accurately
+ and an idempotency-filter is used to reject re-deliveries) */
+ }
+finalize_it:
+ if (locked) {
+ pthread_mutex_unlock(&pThis->reloader_mut);
+ }
+ RETiRet;
+}
+
+static rsRetVal ATTR_NONNULL()
+lookupDoReload(lookup_ref_t *pThis)
+{
+ DEFiRet;
+ iRet = lookupReloadOrStub(pThis, NULL);
+ if ((iRet != RS_RET_OK) &&
+ (pThis->stub_value_for_reload_failure != NULL)) {
+ iRet = lookupDoStub(pThis, pThis->stub_value_for_reload_failure);
+ }
+ freeStubValueForReloadFailure(pThis);
+ RETiRet;
+}
+
+void *
+lookupTableReloader(void *self)
+{
+ lookup_ref_t *pThis = (lookup_ref_t*) self;
+ pthread_mutex_lock(&pThis->reloader_mut);
+ while(1) {
+ if (pThis->do_stop) {
+ break;
+ } else if (pThis->do_reload) {
+ lookupDoReload(pThis);
+ pThis->do_reload = 0;
+ } else {
+ pthread_cond_wait(&pThis->run_reloader, &pThis->reloader_mut);
+ }
+ }
+ pthread_mutex_unlock(&pThis->reloader_mut);
+ return NULL;
+}
+
+/* reload all lookup tables on HUP */
+void
+lookupDoHUP(void)
+{
+ lookup_ref_t *luref;
+ for(luref = runConf->lu_tabs.root ; luref != NULL ; luref = luref->next) {
+ if (luref->reload_on_hup) {
+ lookupReload(luref, NULL);
+ }
+ }
+}
+
+/* activate lookup table system config
+ * most importantly, this means tarting the lookup table reloader thread in the
+ * right process space - it is a difference if we fork or not!
+ */
+void
+lookupActivateConf(void)
+{
+ DBGPRINTF("lookup tables: activate config \n");
+ lookup_ref_t *luref;
+ for(luref = runConf->lu_tabs.root ; luref != NULL ; luref = luref->next) {
+ DBGPRINTF("lookup actiate: processing %p\n", luref);
+ lookupActivateTable(luref);
+ }
+ DBGPRINTF("lookup tables: activate done\n");
+}
+
+uint
+lookupPendingReloadCount(void)
+{
+ uint pending_reload_count = 0;
+ lookup_ref_t *luref;
+ for(luref = runConf->lu_tabs.root ; luref != NULL ; luref = luref->next) {
+ if (lookupIsReloadPending(luref)) {
+ pending_reload_count++;
+ }
+ }
+ return pending_reload_count;
+}
+
+
+/* returns either a pointer to the value (read only!) or NULL
+ * if either the key could not be found or an error occurred.
+ * Note that an estr_t object is returned. The caller is
+ * responsible for freeing it.
+ */
+es_str_t *
+lookupKey(lookup_ref_t *pThis, lookup_key_t key)
+{
+ es_str_t *estr;
+ lookup_t *t;
+ pthread_rwlock_rdlock(&pThis->rwlock);
+ t = pThis->self;
+ estr = t->lookup(t, key);
+ pthread_rwlock_unlock(&pThis->rwlock);
+ return estr;
+}
+
+
+/* note: widely-deployed json_c 0.9 does NOT support incremental
+ * parsing. In order to keep compatible with e.g. Ubuntu 12.04LTS,
+ * we read the file into one big memory buffer and parse it at once.
+ * While this is not very elegant, it will not pose any real issue
+ * for "reasonable" lookup tables (and "unreasonably" large ones
+ * will probably have other issues as well...).
+ */
+static rsRetVal ATTR_NONNULL()
+lookupReadFile(lookup_t *const pThis, const uchar *const name, const uchar *const filename)
+{
+ struct json_tokener *tokener = NULL;
+ struct json_object *json = NULL;
+ char *iobuf = NULL;
+ int fd = -1;
+ ssize_t nread;
+ struct stat sb;
+ DEFiRet;
+
+
+ if((fd = open((const char*) filename, O_RDONLY)) == -1) {
+ LogError(errno, RS_RET_FILE_NOT_FOUND,
+ "lookup table file '%s' could not be opened", filename);
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ }
+
+ if(fstat(fd, &sb) == -1) {
+ LogError(errno, RS_RET_FILE_NOT_FOUND,
+ "lookup table file '%s' stat failed", filename);
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ }
+
+ CHKmalloc(iobuf = malloc(sb.st_size));
+
+ tokener = json_tokener_new();
+ nread = read(fd, iobuf, sb.st_size);
+ if(nread != (ssize_t) sb.st_size) {
+ LogError(errno, RS_RET_READ_ERR,
+ "lookup table file '%s' read error", filename);
+ ABORT_FINALIZE(RS_RET_READ_ERR);
+ }
+
+ json = json_tokener_parse_ex(tokener, iobuf, sb.st_size);
+ if(json == NULL) {
+ LogError(0, RS_RET_JSON_PARSE_ERR,
+ "lookup table file '%s' json parsing error",
+ filename);
+ ABORT_FINALIZE(RS_RET_JSON_PARSE_ERR);
+ }
+ free(iobuf); /* early free to sever resources*/
+ iobuf = NULL; /* make sure no double-free */
+
+ /* got json object, now populate our own in-memory structure */
+ CHKiRet(lookupBuildTable(pThis, json, name));
+
+finalize_it:
+ if (fd != -1) {
+ close(fd);
+ }
+ free(iobuf);
+ if(tokener != NULL)
+ json_tokener_free(tokener);
+ if(json != NULL)
+ json_object_put(json);
+ RETiRet;
+}
+
+
+rsRetVal
+lookupTableDefProcessCnf(struct cnfobj *o)
+{
+ struct cnfparamvals *pvals;
+ lookup_ref_t *lu;
+ short i;
+#ifdef HAVE_PTHREAD_SETNAME_NP
+ char *reloader_thd_name = NULL;
+ int thd_name_len = 0;
+#endif
+ DEFiRet;
+ lu = NULL;
+
+ pvals = nvlstGetParams(o->nvlst, &modpblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+ DBGPRINTF("lookupTableDefProcessCnf params:\n");
+ cnfparamsPrint(&modpblk, pvals);
+
+ CHKiRet(lookupNew(&lu));
+
+ for(i = 0 ; i < modpblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(modpblk.descr[i].name, "file")) {
+ CHKmalloc(lu->filename = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
+ } else if(!strcmp(modpblk.descr[i].name, "name")) {
+ CHKmalloc(lu->name = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
+ } else if(!strcmp(modpblk.descr[i].name, "reloadOnHUP")) {
+ lu->reload_on_hup = (pvals[i].val.d.n != 0);
+ } else {
+ dbgprintf("lookup_table: program error, non-handled "
+ "param '%s'\n", modpblk.descr[i].name);
+ }
+ }
+ const uchar *const lu_name = lu->name; /* we need a const to keep TSAN happy :-( */
+ const uchar *const lu_filename = lu->filename; /* we need a const to keep TSAN happy :-( */
+ if(lu_name == NULL || lu_filename == NULL) {
+ iRet = RS_RET_INTERNAL_ERROR;
+ LogError(0, iRet, "internal error: lookup table name not set albeit being mandatory");
+ ABORT_FINALIZE(iRet);
+ }
+#ifdef HAVE_PTHREAD_SETNAME_NP
+ thd_name_len = ustrlen(lu_name) + strlen(reloader_prefix) + 1;
+ CHKmalloc(reloader_thd_name = malloc(thd_name_len));
+ strcpy(reloader_thd_name, reloader_prefix);
+ strcpy(reloader_thd_name + strlen(reloader_prefix), (char*) lu_name);
+ reloader_thd_name[thd_name_len - 1] = '\0';
+#if defined(__NetBSD__)
+ pthread_setname_np(lu->reloader, "%s", reloader_thd_name);
+#elif defined(__APPLE__)
+ pthread_setname_np(reloader_thd_name); // must check
+#else
+ pthread_setname_np(lu->reloader, reloader_thd_name);
+#endif
+#endif
+ CHKiRet(lookupReadFile(lu->self, lu_name, lu_filename));
+ LogMsg(0, RS_RET_OK, LOG_INFO, "lookup table '%s' loaded from file '%s'",
+ lu_name, lu->filename);
+
+finalize_it:
+#ifdef HAVE_PTHREAD_SETNAME_NP
+ free(reloader_thd_name);
+#endif
+ cnfparamvalsDestruct(pvals, &modpblk);
+ if (iRet != RS_RET_OK) {
+ if (lu != NULL) {
+ lookupDestruct(lu->self);
+ lu->self = NULL;
+ }
+ }
+ RETiRet;
+}
+
+void
+lookupClassExit(void)
+{
+ objRelease(glbl, CORE_COMPONENT);
+}
+
+rsRetVal
+lookupClassInit(void)
+{
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+finalize_it:
+ RETiRet;
+}
diff --git a/runtime/lookup.h b/runtime/lookup.h
new file mode 100644
index 0000000..7c0aa28
--- /dev/null
+++ b/runtime/lookup.h
@@ -0,0 +1,116 @@
+/* header for lookup.c
+ *
+ * Copyright 2013-2023 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_LOOKUP_H
+#define INCLUDED_LOOKUP_H
+#include <libestr.h>
+
+#define STRING_LOOKUP_TABLE 1
+#define ARRAY_LOOKUP_TABLE 2
+#define SPARSE_ARRAY_LOOKUP_TABLE 3
+#define STUBBED_LOOKUP_TABLE 4
+
+#define LOOKUP_KEY_TYPE_STRING 1
+#define LOOKUP_KEY_TYPE_UINT 2
+#define LOOKUP_KEY_TYPE_NONE 3
+
+struct lookup_tables_s {
+ lookup_ref_t *root; /* the root of the template list */
+ lookup_ref_t *last; /* points to the last element of the template list */
+};
+
+struct lookup_array_tab_s {
+ uint32_t first_key;
+ uchar **interned_val_refs;
+};
+
+struct lookup_sparseArray_tab_entry_s {
+ uint32_t key;
+ uchar *interned_val_ref;
+};
+
+struct lookup_sparseArray_tab_s {
+ lookup_sparseArray_tab_entry_t *entries;
+};
+
+struct lookup_string_tab_entry_s {
+ uchar *key;
+ uchar *interned_val_ref;
+};
+
+struct lookup_string_tab_s {
+ lookup_string_tab_entry_t *entries;
+};
+
+struct lookup_ref_s {
+ pthread_rwlock_t rwlock; /* protect us in case of dynamic reloads */
+ uchar *name;
+ uchar *filename;
+ lookup_t *self;
+ lookup_ref_t *next;
+ /* reload specific attributes */
+ pthread_mutex_t reloader_mut; /* signaling + access to reload-flow variables*/
+ /* rwlock(above) may be acquired inside critical-section reloader_mut guards */
+ pthread_cond_t run_reloader;
+ pthread_t reloader;
+ pthread_attr_t reloader_thd_attr;
+ uchar *stub_value_for_reload_failure;
+ uint8_t do_reload;
+ uint8_t do_stop;
+ uint8_t reload_on_hup;
+};
+
+typedef es_str_t* (lookup_fn_t)(lookup_t*, lookup_key_t);
+
+/* a single lookup table */
+struct lookup_s {
+ uint32_t nmemb;
+ uint8_t type;
+ uint8_t key_type;
+ union {
+ lookup_string_tab_t *str;
+ lookup_array_tab_t *arr;
+ lookup_sparseArray_tab_t *sprsArr;
+ } table;
+ uint32_t interned_val_count;
+ uchar **interned_vals;
+ uchar *nomatch;
+ lookup_fn_t *lookup;
+};
+
+union lookup_key_u {
+ uchar* k_str;
+ uint32_t k_uint;
+};
+
+/* prototypes */
+void lookupInitCnf(lookup_tables_t *lu_tabs);
+rsRetVal lookupTableDefProcessCnf(struct cnfobj *o);
+lookup_ref_t *lookupFindTable(uchar *name);
+es_str_t * lookupKey(lookup_ref_t *pThis, lookup_key_t key);
+void lookupDestroyCnf(void);
+void lookupClassExit(void);
+void lookupDoHUP(void);
+rsRetVal lookupReload(lookup_ref_t *pThis, const uchar *stub_value_if_reload_fails);
+uint lookupPendingReloadCount(void);
+rsRetVal lookupClassInit(void);
+void lookupActivateConf(void);
+
+#endif /* #ifndef INCLUDED_LOOKUP_H */
diff --git a/runtime/module-template.h b/runtime/module-template.h
new file mode 100644
index 0000000..9c9cbd1
--- /dev/null
+++ b/runtime/module-template.h
@@ -0,0 +1,1250 @@
+/* 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-2015 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 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
+#define DEF_FMOD_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_FUNCTION MODULE_TYPE(eMOD_FUNCTION)
+#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;\
+ 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;\
+}
+
+/* createWrkrInstance()
+ */
+#define BEGINcreateWrkrInstance \
+static rsRetVal createWrkrInstance(wrkrInstanceData_t **ppWrkrData, instanceData *pData)\
+ {\
+ DEFiRet; /* store error code here */\
+ wrkrInstanceData_t *pWrkrData; /* use this to point to data elements */
+
+#define CODESTARTcreateWrkrInstance \
+ if((pWrkrData = calloc(1, sizeof(wrkrInstanceData_t))) == NULL) {\
+ *ppWrkrData = NULL;\
+ return RS_RET_OUT_OF_MEMORY;\
+ } \
+ pWrkrData->pData = pData;
+
+#define ENDcreateWrkrInstance \
+ *ppWrkrData = pWrkrData;\
+ RETiRet;\
+}
+
+/* freeWrkrInstance */
+#define BEGINfreeWrkrInstance \
+static rsRetVal freeWrkrInstance(void* pd)\
+{\
+ DEFiRet;\
+ wrkrInstanceData_t *pWrkrData;
+
+#define CODESTARTfreeWrkrInstance \
+ pWrkrData = (wrkrInstanceData_t*) pd;
+
+#define ENDfreeWrkrInstance \
+ if(pWrkrData != NULL)\
+ free(pWrkrData); /* we need to free this in any case */\
+ RETiRet;\
+}
+
+
+/* isCompatibleWithFeature()
+ */
+#define BEGINisCompatibleWithFeature \
+static rsRetVal isCompatibleWithFeature(syslogFeature __attribute__((unused)) eFeat)\
+{\
+ rsRetVal iRet = RS_RET_INCOMPATIBLE; \
+
+#define CODESTARTisCompatibleWithFeature
+
+#define ENDisCompatibleWithFeature \
+ RETiRet;\
+}
+
+
+/* beginTransaction()
+ * introduced in v4.3.3 -- rgerhards, 2009-04-27
+ */
+#define BEGINbeginTransaction \
+static rsRetVal beginTransaction(wrkrInstanceData_t __attribute__((unused)) *pWrkrData)\
+{\
+ DEFiRet;
+
+#define CODESTARTbeginTransaction /* currently empty, but may be extended */
+
+#define ENDbeginTransaction \
+ RETiRet;\
+}
+
+
+/* commitTransaction()
+ * Commits a transaction. Note that beginTransaction() must have been
+ * called before this entry point. It receives the full batch of messages
+ * to be processed in pParam parameter.
+ * introduced in v8.1.3 -- rgerhards, 2013-12-04
+ */
+#define BEGINcommitTransaction \
+static rsRetVal commitTransaction(wrkrInstanceData_t __attribute__((unused)) *const pWrkrData, \
+ actWrkrIParams_t *const pParams, const unsigned nParams)\
+{\
+ DEFiRet;
+
+#define CODESTARTcommitTransaction /* currently empty, but may be extended */
+
+#define ENDcommitTransaction \
+ RETiRet;\
+}
+
+/* endTransaction()
+ * introduced in v4.3.3 -- rgerhards, 2009-04-27
+ */
+#define BEGINendTransaction \
+static rsRetVal endTransaction(wrkrInstanceData_t __attribute__((unused)) *pWrkrData)\
+{\
+ DEFiRet;
+
+#define CODESTARTendTransaction /* currently empty, but may be extended */
+
+#define ENDendTransaction \
+ RETiRet;\
+}
+
+
+/* doAction()
+ */
+#define BEGINdoAction \
+static rsRetVal doAction(void * pMsgData, wrkrInstanceData_t __attribute__((unused)) *pWrkrData)\
+{\
+ uchar **ppString = (uchar **) pMsgData; \
+ DEFiRet;
+
+#define CODESTARTdoAction \
+ /* ppString may be NULL if the output module requested no strings */
+
+#define ENDdoAction \
+ RETiRet;\
+}
+
+/* below is a variant of doAction where the passed-in data is not the common
+ * case of string.
+ */
+#define BEGINdoAction_NoStrings \
+static rsRetVal doAction(void * pMsgData, wrkrInstanceData_t __attribute__((unused)) *pWrkrData)\
+{\
+ DEFiRet;
+
+
+/* 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: ATTR_UNUSED; /* semi-colon needed according to gcc doc! */\
+ 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;\
+}
+
+/* a special replacement macro for modules that do not support legacy config at all */
+#define NO_LEGACY_CONF_parseSelectorAct \
+static rsRetVal parseSelectorAct(uchar **pp ATTR_UNUSED, void **ppModData ATTR_UNUSED, \
+ omodStringRequest_t **ppOMSR ATTR_UNUSED)\
+{\
+ return RS_RET_LEGA_ACT_NOT_SUPPORTED;\
+}
+
+/* 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 __attribute__((unused)) *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;\
+}
+
+
+
+/* newParserInst()
+ * This is basically the equivalent to newActInst() for creating parser
+ * module (listener) instances.
+ */
+#define BEGINnewParserInst \
+static rsRetVal newParserInst(struct nvlst *lst, void *pinst)\
+{\
+ instanceConf_t *inst; \
+ DEFiRet;
+
+#define CODESTARTnewParserInst \
+
+#define CODE_STD_FINALIZERnewParserInst
+
+#define ENDnewParserInst \
+ if(iRet == RS_RET_OK) \
+ *((instanceConf_t**)pinst) = inst; \
+ RETiRet;\
+}
+
+
+/* freeParserInst */
+#define BEGINfreeParserInst \
+static rsRetVal freeParserInst(void* pi)\
+{\
+ DEFiRet;\
+ instanceConf_t *pInst;
+
+#define CODESTARTfreeParserInst\
+ pInst = (instanceConf_t*) pi;
+
+#define ENDfreeParserInst\
+ if(pInst != NULL)\
+ free(pInst);\
+ 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(wrkrInstanceData_t __attribute__((unused)) *pWrkrData)\
+{\
+ DEFiRet;
+
+#define CODESTARTtryResume \
+ assert(pWrkrData != 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)) {\
+ 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 WHICH DO NOT SUPPORT TRANSACTIONS.
+ */
+#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 the standard block for queryEtryPt for output
+ * modules using the transaction interface.
+ */
+#define CODEqueryEtryPt_STD_OMODTX_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES \
+ else if(!strcmp((char*) name, "beginTransaction")) {\
+ *pEtryPoint = beginTransaction;\
+ } else if(!strcmp((char*) name, "commitTransaction")) {\
+ *pEtryPoint = commitTransaction;\
+ } 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;\
+ }
+
+/* standard queries for output module interface in rsyslog v8+ */
+#define CODEqueryEtryPt_STD_OMOD8_QUERIES \
+ else if(!strcmp((char*) name, "createWrkrInstance")) {\
+ *pEtryPoint = createWrkrInstance;\
+ } else if(!strcmp((char*) name, "freeWrkrInstance")) {\
+ *pEtryPoint = freeWrkrInstance;\
+ }
+
+/* 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 PARSER
+ * modules obeying the v2+ config interface.
+ */
+#define CODEqueryEtryPt_STD_PMOD2_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES \
+ else if(!strcmp((char*) name, "parse2")) {\
+ *pEtryPoint = parse2;\
+ } else if(!strcmp((char*) name, "GetParserName")) {\
+ *pEtryPoint = GetParserName;\
+ } else if(!strcmp((char*) name, "newParserInst")) {\
+ *pEtryPoint = newParserInst;\
+ } else if(!strcmp((char*) name, "freeParserInst")) {\
+ *pEtryPoint = freeParserInst;\
+ } \
+ CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES
+
+
+
+/* the following definition is the standard block for queryEtryPt for rscript function
+ * modules. This can be used if no specific handling (e.g. to cover version
+ * differences) is needed.
+ */
+#define CODEqueryEtryPt_STD_FMOD_QUERIES \
+ CODEqueryEtryPt_STD_MOD_QUERIES \
+ else if(!strcmp((char*) name, "getFunctArray")) {\
+ *pEtryPoint = getFunctArray;\
+ }
+
+/* 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 __attribute__((unused)) modInit##uniqName(int iIFVersRequested __attribute__((unused)), \
+int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), \
+modInfo_t __attribute__((unused)) *pModInfo);\
+rsRetVal __attribute__((unused)) 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)) { \
+ 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;\
+ 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 HUP at the action level is to be 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;\
+}
+
+
+/* doHUPWrkr()
+ * This is like doHUP(), but on an action worker level.
+ * rgerhards, 2015-03-25
+ */
+#define CODEqueryEtryPt_doHUPWrkr \
+ else if(!strcmp((char*) name, "doHUPWrkr")) {\
+ *pEtryPoint = doHUPWrkr;\
+ }
+#define BEGINdoHUPWrkr \
+static rsRetVal doHUPWrkr(wrkrInstanceData_t __attribute__((unused)) *pWrkrData)\
+{\
+ DEFiRet;
+
+#define CODESTARTdoHUPWrkr
+
+#define ENDdoHUPWrkr \
+ 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 (v1 config interface)
+ */
+#define BEGINparse \
+static rsRetVal parse(smsg_t *pMsg)\
+{\
+ DEFiRet;
+
+#define CODESTARTparse \
+ assert(pMsg != NULL);
+
+#define ENDparse \
+ RETiRet;\
+}
+
+
+/* parse2() - main entry point of parser modules (v2+ config interface)
+ */
+#define BEGINparse2 \
+static rsRetVal parse2(instanceConf_t *const pInst, smsg_t *pMsg)\
+{\
+ DEFiRet;
+
+#define CODESTARTparse2 \
+ assert(pInst != NULL);\
+ assert(pMsg != NULL);
+
+#define ENDparse2 \
+ RETiRet;\
+}
+
+
+/* strgen() - main entry point of parser modules
+ * Note that we do NOT use size_t as this permits us to store the
+ * values directly into optimized heap structures.
+ * ppBuf is the buffer pointer
+ * pLenBuf is the current max size of this buffer
+ * pStrLen is an output parameter that MUST hold the length
+ * of the generated string on exit (this is cached)
+ */
+#define BEGINstrgen \
+static rsRetVal strgen(smsg_t *const pMsg, actWrkrIParams_t *const iparam) \
+{\
+ DEFiRet;
+
+#define CODESTARTstrgen \
+ assert(pMsg != NULL);
+
+#define ENDstrgen \
+ RETiRet;\
+}
+
+
+
+/* getFunctArray() - main entry point of parser modules
+ * Note that we do NOT use size_t as this permits us to store the
+ * values directly into optimized heap structures.
+ * ppBuf is the buffer pointer
+ * pLenBuf is the current max size of this buffer
+ * pStrLen is an output parameter that MUST hold the length
+ * of the generated string on exit (this is cached)
+ */
+#define BEGINgetFunctArray \
+static rsRetVal getFunctArray(int *const version, const struct scriptFunct**const functArray) \
+{\
+ DEFiRet;
+
+#define CODESTARTgetFunctArray
+
+#define ENDgetFunctArray \
+ 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 0000000..b39bd9f
--- /dev/null
+++ b/runtime/modules.c
@@ -0,0 +1,1488 @@
+/* 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-2018 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>
+#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>
+
+#include "rsyslog.h"
+#include "rainerscript.h"
+#include "cfsysline.h"
+#include "rsconf.h"
+#include "modules.h"
+#include "errmsg.h"
+#include "parser.h"
+#include "strgen.h"
+
+/* static data */
+DEFobjStaticHelpers
+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
+ };
+
+
+typedef rsRetVal (*pModInit_t)(int,int*, rsRetVal(**)(), rsRetVal(*)(), modInfo_t*);
+
+/* 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(__attribute__((unused)) void * dummy)
+{
+ return RS_RET_OK;
+}
+static rsRetVal
+dummyEndTransaction(__attribute__((unused)) void * dummy)
+{
+ return RS_RET_OK;
+}
+static rsRetVal
+dummyIsCompatibleWithFeature(__attribute__((unused)) syslogFeature eFeat)
+{
+ return RS_RET_INCOMPATIBLE;
+}
+static rsRetVal
+dummynewActInst(uchar *modName, struct nvlst __attribute__((unused)) *dummy1,
+ void __attribute__((unused)) **dummy2, omodStringRequest_t __attribute__((unused)) **dummy3)
+{
+ 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, const char *pszUsr)
+{
+ modUsr_t *pUsr;
+
+ 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:
+ return;
+}
+
+
+/* remove a user from the current user list
+ * rgerhards, 2008-03-11
+ */
+static void
+modUsrDel(modInfo_t *pThis, const 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
+modUsrPrintAll(void)
+{
+ modInfo_t *pMod;
+
+ 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);
+ }
+}
+
+#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
+ DBGPRINTF("moduleDestruct: compiled with valgrind, do "
+ "not unload module\n");
+# else
+ if(glblUnloadModules) {
+ if(pThis->eKeepType == eMOD_NOKEEP) {
+ dlclose(pThis->pModHdlr);
+ }
+ } else {
+ DBGPRINTF("moduleDestruct: not unloading module "
+ "due to user configuration\n");
+ }
+# 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 one 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 void ATTR_NONNULL()
+addModToGlblList(modInfo_t *const 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 = NULL;
+ 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)) {
+ 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:
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL)
+ free(pNew);
+ }
+ 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)
+{
+ if(pNew != NULL) {
+ free(*pNew);
+ *pNew = NULL;
+ }
+}
+
+
+/* Add a module to the config module list for current loadConf.
+ * Requires last pointer obtained by readyModForCnf().
+ * The module pointer is handed over to this function. It is no
+ * longer available to caller one we are called.
+ */
+rsRetVal ATTR_NONNULL(1)
+addModToCnfList(cfgmodules_etry_t **const pNew, cfgmodules_etry_t *const pLast)
+{
+ DEFiRet;
+ assert(*pNew != NULL);
+
+ if(loadConf == NULL) {
+ abortCnfUse(pNew);
+ 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:
+ *pNew = NULL;
+ 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(pModInit_t modInit, uchar *name, void *pModHdlr, modInfo_t **pNewModule)
+{
+ rsRetVal localRet;
+ modInfo_t *pNew = NULL;
+ uchar *pName;
+ 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;
+ FINALIZE;
+ }
+
+ 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.im.newInpInst = NULL;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"doHUP", &pNew->doHUP);
+ if(localRet != RS_RET_OK && localRet != RS_RET_MODULE_ENTRY_POINT_NOT_FOUND)
+ 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*)"parseSelectorAct", &pNew->mod.om.parseSelectorAct));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"tryResume", &pNew->tryResume));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"createWrkrInstance",
+ &pNew->mod.om.createWrkrInstance));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeWrkrInstance",
+ &pNew->mod.om.freeWrkrInstance));
+
+ /* 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*)"doHUPWrkr", &pNew->doHUPWrkr);
+ 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);
+
+ pNew->mod.om.supportsTX = 1;
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"beginTransaction", &pNew->mod.om.beginTransaction);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->mod.om.beginTransaction = dummyBeginTransaction;
+ pNew->mod.om.supportsTX = 0;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"doAction",
+ &pNew->mod.om.doAction);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->mod.om.doAction = NULL;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"commitTransaction",
+ &pNew->mod.om.commitTransaction);
+ if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->mod.om.commitTransaction = NULL;
+ } else if(localRet != RS_RET_OK) {
+ ABORT_FINALIZE(localRet);
+ }
+
+ if(pNew->mod.om.doAction == NULL && pNew->mod.om.commitTransaction == NULL) {
+ LogError(0, RS_RET_INVLD_OMOD,
+ "module %s does neither provide doAction() "
+ "nor commitTransaction() interface - cannot "
+ "load", name);
+ ABORT_FINALIZE(RS_RET_INVLD_OMOD);
+ }
+
+ if(pNew->mod.om.commitTransaction != NULL) {
+ if(pNew->mod.om.doAction != NULL){
+ LogError(0, RS_RET_INVLD_OMOD,
+ "module %s provides both doAction() "
+ "and commitTransaction() interface, using "
+ "commitTransaction()", name);
+ pNew->mod.om.doAction = NULL;
+ }
+ if(pNew->mod.om.beginTransaction == NULL){
+ LogError(0, RS_RET_INVLD_OMOD,
+ "module %s provides both commitTransaction() "
+ "but does not provide beginTransaction() - "
+ "cannot load", name);
+ ABORT_FINALIZE(RS_RET_INVLD_OMOD);
+ }
+ }
+
+
+ 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:
+ localRet = (*pNew->modQueryEtryPt)((uchar*)"parse2",
+ &pNew->mod.pm.parse2);
+ if(localRet == RS_RET_OK) {
+ pNew->mod.pm.parse = NULL;
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"newParserInst",
+ &pNew->mod.pm.newParserInst));
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"freeParserInst",
+ &pNew->mod.pm.freeParserInst));
+ } else if(localRet == RS_RET_MODULE_ENTRY_POINT_NOT_FOUND) {
+ pNew->mod.pm.parse2 = NULL;
+ pNew->mod.pm.newParserInst = NULL;
+ pNew->mod.pm.freeParserInst = NULL;
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"parse", &pNew->mod.pm.parse));
+ } else {
+ ABORT_FINALIZE(localRet);
+ }
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"GetParserName", &GetName));
+ CHKiRet(GetName(&pName));
+ CHKiRet(parserConstructViaModAndName(pNew, pName, NULL));
+ 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));
+ 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_FUNCTION:
+ CHKiRet((*pNew->modQueryEtryPt)((uchar*)"getFunctArray", &pNew->mod.fm.getFunctArray));
+ int version;
+ struct scriptFunct *functArray;
+ pNew->mod.fm.getFunctArray(&version, &functArray);
+ dbgprintf("LLL: %s\n", functArray[0].fname);
+ addMod2List(version, functArray);
+ 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_FUNCTION:
+ dbgprintf("function");
+ 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_FUNCTION:
+ dbgprintf("Function Module Entry Points\n");
+ dbgprintf("\tgetFunctArray: 0x%lx\n", (unsigned long) pMod->mod.fm.getFunctArray);
+ break;
+ case eMOD_ANY: /* this is mostly to keep the compiler happy! */
+ break;
+ }
+ dbgprintf("\n");
+ pMod = GetNxt(pMod); /* done, go next */
+ }
+}
+
+
+/* HUP all modules that support it - except for actions, which
+ * need (and have) specialised HUP handling.
+ */
+void
+modDoHUP(void)
+{
+ modInfo_t *pMod;
+
+ pthread_mutex_lock(&mutObjGlobalOp);
+ pMod = GetNxt(NULL);
+ while(pMod != NULL) {
+ if(pMod->eType != eMOD_OUT && pMod->doHUP != NULL) {
+ DBGPRINTF("HUPing module %s\n", (char*) modGetName(pMod));
+ pMod->doHUP(NULL);
+ }
+ pMod = GetNxt(pMod); /* done, go next */
+ }
+ pthread_mutex_unlock(&mutObjGlobalOp);
+}
+
+
+/* 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);
+ 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);
+ }
+ }
+
+ RETiRet;
+}
+
+/* find module with given name in global list */
+static 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 ATTR_NONNULL(1)
+Load(uchar *const pModName, const sbool bConfLoad, struct nvlst *const lst)
+{
+ size_t iPathLen, iModNameLen;
+ int bHasExtension;
+ void *pModHdlr;
+ pModInit_t pModInit;
+ modInfo_t *pModInfo;
+ cfgmodules_etry_t *pNew = NULL;
+ cfgmodules_etry_t *pLast = NULL;
+ uchar *pModDirCurr, *pModDirNext;
+ int iLoadCnt;
+ struct dlhandle_s *pHandle = NULL;
+ uchar pathBuf[PATH_MAX+1];
+ uchar *pPathBuf = pathBuf;
+ size_t lenPathBuf = sizeof(pathBuf);
+ rsRetVal localRet;
+ cstr_t *load_err_msg = NULL;
+ 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) {
+ 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(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(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);
+ }
+
+ /* 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);
+ }
+
+ if(pModHdlr == NULL) {
+ char errmsg[4096];
+ snprintf(errmsg, sizeof(errmsg), "%strying to load module %s: %s",
+ (load_err_msg == NULL) ? "" : " //////// ",
+ pPathBuf, dlerror());
+ if(load_err_msg == NULL) {
+ rsCStrConstructFromszStr(&load_err_msg, (uchar*)errmsg);
+ } else {
+ rsCStrAppendStr(load_err_msg, (uchar*)errmsg);
+ }
+ }
+
+ iLoadCnt++;
+
+ } while(pModHdlr == NULL && *pModName != '/' && pModDirNext);
+
+ if(load_err_msg != NULL) {
+ cstrFinalize(load_err_msg);
+ }
+
+ if(!pModHdlr) {
+ LogError(0, RS_RET_MODULE_LOAD_ERR_DLOPEN, "could not load module '%s', errors: %s", pModName,
+ (load_err_msg == NULL) ? "NONE SEEN???" : (const char*) cstrGetSzStrNoNULL(load_err_msg));
+ ABORT_FINALIZE(RS_RET_MODULE_LOAD_ERR_DLOPEN);
+ }
+ if(!(pModInit = (pModInit_t)dlsym(pModHdlr, "modInit"))) {
+ 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) {
+ 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) {
+ LogError(0, localRet,
+ "module '%s', failed processing config parameters",
+ pPathBuf);
+ ABORT_FINALIZE(localRet);
+ }
+ }
+ pModInfo->bSetModCnfCalled = 1;
+ }
+ addModToCnfList(&pNew, pLast);
+ }
+
+finalize_it:
+ if(load_err_msg != NULL) {
+ cstrDestruct(&load_err_msg);
+ }
+ if(pPathBuf != pathBuf) /* used malloc()ed memory? */
+ free(pPathBuf);
+ if(iRet != RS_RET_OK)
+ abortCnfUse(&pNew);
+ free(pNew); /* is NULL again if properly consumed, else clean up */
+ 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) {
+ 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(const 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(const 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 */
+ 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 */
+ENDObjClassInit(module)
+
+/* vi:set ai:
+ */
diff --git a/runtime/modules.h b/runtime/modules.h
new file mode 100644
index 0000000..f1c940e
--- /dev/null
+++ b/runtime/modules.h
@@ -0,0 +1,213 @@
+/* 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-2018 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_FUNCTION = 5, /*rscript function module*/
+ eMOD_ANY = 6 /* 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(**)(void*)); /* 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 *); /* HUP handler, action level */
+ rsRetVal (*doHUPWrkr)(void *); /* HUP handler, wrkr instance level */
+ /* 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 */
+ 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 (*commitTransaction)(void *const, actWrkrIParams_t *const, const unsigned);
+ rsRetVal (*doAction)(void** params, void*pWrkrData);
+ 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);
+ rsRetVal (*createWrkrInstance)(void*ppWrkrData, void*pData);
+ rsRetVal (*freeWrkrInstance)(void*pWrkrData);
+ sbool supportsTX; /* set if the module supports transactions */
+ } om;
+ struct { /* data for library modules */
+ char dummy;
+ } lm;
+ struct { /* data for parser modules */
+ rsRetVal (*newParserInst)(struct nvlst *lst, void *pinst);
+ rsRetVal (*freeParserInst)(void *pinst);
+ rsRetVal (*parse2)(instanceConf_t *const, smsg_t*);
+ rsRetVal (*parse)(smsg_t*);
+ } pm;
+ struct { /* data for strgen modules */
+ rsRetVal (*strgen)(const smsg_t*const, actWrkrIParams_t *const iparam);
+ } sm;
+ struct { /* data for rscript modules */
+ rsRetVal (*getFunctArray)(int *const, struct scriptFunct**);
+ } fm;
+ } 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)(const char *srcFile, modInfo_t *pThis);
+ /**< must be called before a module is used (ref counting) */
+ rsRetVal (*Release)(const 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 5 /* 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 ATTR_NONNULL(1) addModToCnfList(cfgmodules_etry_t **pNew, cfgmodules_etry_t *pLast);
+rsRetVal readyModForCnf(modInfo_t *pThis, cfgmodules_etry_t **ppNew, cfgmodules_etry_t **ppLast);
+void modDoHUP(void);
+
+#endif /* #ifndef MODULES_H_INCLUDED */
diff --git a/runtime/msg.c b/runtime/msg.c
new file mode 100644
index 0000000..b35bc1d
--- /dev/null
+++ b/runtime/msg.c
@@ -0,0 +1,5476 @@
+/* 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-2023 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>
+#ifdef HAVE_SYSINFO_UPTIME
+#include <sys/sysinfo.h>
+#endif
+#include <netdb.h>
+#include <libestr.h>
+#include <json.h>
+#ifdef HAVE_MALLOC_H
+# include <malloc.h>
+#endif
+#ifdef USE_LIBUUID
+# include <uuid/uuid.h>
+#endif
+#include <errno.h>
+#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"
+#include "parserif.h"
+#include "errmsg.h"
+
+#define DEV_DEBUG 0 /* set to 1 to enable very verbose developer debugging messages */
+
+/* inlines */
+extern void msgSetPRI(smsg_t *const __restrict__ pMsg, syslog_pri_t pri);
+
+/* TODO: move the global variable root to the config object - had no time to to it
+ * right now before vacation -- rgerhards, 2013-07-22
+ */
+static pthread_mutex_t glblVars_lock;
+struct json_object *global_var_root = NULL;
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(regexp)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(net)
+DEFobjCurrIf(var)
+
+static const char *one_digit[10] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9" };
+
+static const 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 const char *wdayNames[7] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+
+/* Table of days in a year, needed for getYearDay */
+static const char *daysInYear[366] = {
+ "001", "002", "003", "004", "005", "006", "007", "008", "009",
+ "010", "011", "012", "013", "014", "015", "016", "017", "018", "019",
+ "020", "021", "022", "023", "024", "025", "026", "027", "028", "029",
+ "030", "031", "032", "033", "034", "035", "036", "037", "038", "039",
+ "040", "041", "042", "043", "044", "045", "046", "047", "048", "049",
+ "050", "051", "052", "053", "054", "055", "056", "057", "058", "059",
+ "060", "061", "062", "063", "064", "065", "066", "067", "068", "069",
+ "070", "071", "072", "073", "074", "075", "076", "077", "078", "079",
+ "080", "081", "082", "083", "084", "085", "086", "087", "088", "089",
+ "090", "091", "092", "093", "094", "095", "096", "097", "098", "099",
+ "100", "101", "102", "103", "104", "105", "106", "107", "108", "109",
+ "110", "111", "112", "113", "114", "115", "116", "117", "118", "119",
+ "120", "121", "122", "123", "124", "125", "126", "127", "128", "129",
+ "130", "131", "132", "133", "134", "135", "136", "137", "138", "139",
+ "140", "141", "142", "143", "144", "145", "146", "147", "148", "149",
+ "150", "151", "152", "153", "154", "155", "156", "157", "158", "159",
+ "160", "161", "162", "163", "164", "165", "166", "167", "168", "169",
+ "170", "171", "172", "173", "174", "175", "176", "177", "178", "179",
+ "180", "181", "182", "183", "184", "185", "186", "187", "188", "189",
+ "190", "191", "192", "193", "194", "195", "196", "197", "198", "199",
+ "200", "201", "202", "203", "204", "205", "206", "207", "208", "209",
+ "210", "211", "212", "213", "214", "215", "216", "217", "218", "219",
+ "220", "221", "222", "223", "224", "225", "226", "227", "228", "229",
+ "230", "231", "232", "233", "234", "235", "236", "237", "238", "239",
+ "240", "241", "242", "243", "244", "245", "246", "247", "248", "249",
+ "250", "251", "252", "253", "254", "255", "256", "257", "258", "259",
+ "260", "261", "262", "263", "264", "265", "266", "267", "268", "269",
+ "270", "271", "272", "273", "274", "275", "276", "277", "278", "279",
+ "280", "281", "282", "283", "284", "285", "286", "287", "288", "289",
+ "290", "291", "292", "293", "294", "295", "296", "297", "298", "299",
+ "300", "301", "302", "303", "304", "305", "306", "307", "308", "309",
+ "310", "311", "312", "313", "314", "315", "316", "317", "318", "319",
+ "320", "321", "322", "323", "324", "325", "326", "327", "328", "329",
+ "330", "331", "332", "333", "334", "335", "336", "337", "338", "339",
+ "340", "341", "342", "343", "344", "345", "346", "347", "348", "349",
+ "350", "351", "352", "353", "354", "355", "356", "357", "358", "359",
+ "360", "361", "362", "363", "364", "365", "366"};
+
+/* The following is a table of supported years. This permits us
+ * to avoid dynamic memory allocation. Note that the time-based
+ * algos need to be upgraded after the year 2099 in any case.
+ * Quite honestly, I don't expect that this is a real problem ;)
+ */
+static const char *years[] = {
+ "1967", "1968", "1969", "1970", "1971", "1972", "1973", "1974",
+ "1975", "1976", "1977", "1978", "1979", "1980", "1981", "1982",
+ "1983", "1984", "1985", "1986", "1987", "1988", "1989", "1990",
+ "1991", "1992", "1993", "1994", "1995", "1996", "1997", "1998",
+ "1999", "2000", "2001", "2002", "2003", "2004", "2005", "2006",
+ "2007", "2008", "2009", "2010", "2011", "2012", "2013", "2014",
+ "2015", "2016", "2017", "2018", "2019", "2020", "2021", "2022",
+ "2023", "2024", "2025", "2026", "2027", "2028", "2029", "2030",
+ "2031", "2032", "2033", "2034", "2035", "2036", "2037", "2038",
+ "2039", "2040", "2041", "2042", "2043", "2044", "2045", "2046",
+ "2047", "2048", "2049", "2050", "2051", "2052", "2053", "2054",
+ "2055", "2056", "2057", "2058", "2059", "2060", "2061", "2062",
+ "2063", "2064", "2065", "2066", "2067", "2068", "2069", "2070",
+ "2071", "2072", "2073", "2074", "2075", "2076", "2077", "2078",
+ "2079", "2080", "2081", "2082", "2083", "2084", "2085", "2086",
+ "2087", "2088", "2089", "2090", "2091", "2092", "2093", "2094",
+ "2095", "2096", "2097", "2098", "2099" };
+
+static struct {
+ uchar *pszName;
+} syslog_pri_names[200] = {
+ { UCHAR_CONSTANT("0") },
+ { UCHAR_CONSTANT("1") },
+ { UCHAR_CONSTANT("2") },
+ { UCHAR_CONSTANT("3") },
+ { UCHAR_CONSTANT("4") },
+ { UCHAR_CONSTANT("5") },
+ { UCHAR_CONSTANT("6") },
+ { UCHAR_CONSTANT("7") },
+ { UCHAR_CONSTANT("8") },
+ { UCHAR_CONSTANT("9") },
+ { UCHAR_CONSTANT("10") },
+ { UCHAR_CONSTANT("11") },
+ { UCHAR_CONSTANT("12") },
+ { UCHAR_CONSTANT("13") },
+ { UCHAR_CONSTANT("14") },
+ { UCHAR_CONSTANT("15") },
+ { UCHAR_CONSTANT("16") },
+ { UCHAR_CONSTANT("17") },
+ { UCHAR_CONSTANT("18") },
+ { UCHAR_CONSTANT("19") },
+ { UCHAR_CONSTANT("20") },
+ { UCHAR_CONSTANT("21") },
+ { UCHAR_CONSTANT("22") },
+ { UCHAR_CONSTANT("23") },
+ { UCHAR_CONSTANT("24") },
+ { UCHAR_CONSTANT("25") },
+ { UCHAR_CONSTANT("26") },
+ { UCHAR_CONSTANT("27") },
+ { UCHAR_CONSTANT("28") },
+ { UCHAR_CONSTANT("29") },
+ { UCHAR_CONSTANT("30") },
+ { UCHAR_CONSTANT("31") },
+ { UCHAR_CONSTANT("32") },
+ { UCHAR_CONSTANT("33") },
+ { UCHAR_CONSTANT("34") },
+ { UCHAR_CONSTANT("35") },
+ { UCHAR_CONSTANT("36") },
+ { UCHAR_CONSTANT("37") },
+ { UCHAR_CONSTANT("38") },
+ { UCHAR_CONSTANT("39") },
+ { UCHAR_CONSTANT("40") },
+ { UCHAR_CONSTANT("41") },
+ { UCHAR_CONSTANT("42") },
+ { UCHAR_CONSTANT("43") },
+ { UCHAR_CONSTANT("44") },
+ { UCHAR_CONSTANT("45") },
+ { UCHAR_CONSTANT("46") },
+ { UCHAR_CONSTANT("47") },
+ { UCHAR_CONSTANT("48") },
+ { UCHAR_CONSTANT("49") },
+ { UCHAR_CONSTANT("50") },
+ { UCHAR_CONSTANT("51") },
+ { UCHAR_CONSTANT("52") },
+ { UCHAR_CONSTANT("53") },
+ { UCHAR_CONSTANT("54") },
+ { UCHAR_CONSTANT("55") },
+ { UCHAR_CONSTANT("56") },
+ { UCHAR_CONSTANT("57") },
+ { UCHAR_CONSTANT("58") },
+ { UCHAR_CONSTANT("59") },
+ { UCHAR_CONSTANT("60") },
+ { UCHAR_CONSTANT("61") },
+ { UCHAR_CONSTANT("62") },
+ { UCHAR_CONSTANT("63") },
+ { UCHAR_CONSTANT("64") },
+ { UCHAR_CONSTANT("65") },
+ { UCHAR_CONSTANT("66") },
+ { UCHAR_CONSTANT("67") },
+ { UCHAR_CONSTANT("68") },
+ { UCHAR_CONSTANT("69") },
+ { UCHAR_CONSTANT("70") },
+ { UCHAR_CONSTANT("71") },
+ { UCHAR_CONSTANT("72") },
+ { UCHAR_CONSTANT("73") },
+ { UCHAR_CONSTANT("74") },
+ { UCHAR_CONSTANT("75") },
+ { UCHAR_CONSTANT("76") },
+ { UCHAR_CONSTANT("77") },
+ { UCHAR_CONSTANT("78") },
+ { UCHAR_CONSTANT("79") },
+ { UCHAR_CONSTANT("80") },
+ { UCHAR_CONSTANT("81") },
+ { UCHAR_CONSTANT("82") },
+ { UCHAR_CONSTANT("83") },
+ { UCHAR_CONSTANT("84") },
+ { UCHAR_CONSTANT("85") },
+ { UCHAR_CONSTANT("86") },
+ { UCHAR_CONSTANT("87") },
+ { UCHAR_CONSTANT("88") },
+ { UCHAR_CONSTANT("89") },
+ { UCHAR_CONSTANT("90") },
+ { UCHAR_CONSTANT("91") },
+ { UCHAR_CONSTANT("92") },
+ { UCHAR_CONSTANT("93") },
+ { UCHAR_CONSTANT("94") },
+ { UCHAR_CONSTANT("95") },
+ { UCHAR_CONSTANT("96") },
+ { UCHAR_CONSTANT("97") },
+ { UCHAR_CONSTANT("98") },
+ { UCHAR_CONSTANT("99") },
+ { UCHAR_CONSTANT("100") },
+ { UCHAR_CONSTANT("101") },
+ { UCHAR_CONSTANT("102") },
+ { UCHAR_CONSTANT("103") },
+ { UCHAR_CONSTANT("104") },
+ { UCHAR_CONSTANT("105") },
+ { UCHAR_CONSTANT("106") },
+ { UCHAR_CONSTANT("107") },
+ { UCHAR_CONSTANT("108") },
+ { UCHAR_CONSTANT("109") },
+ { UCHAR_CONSTANT("110") },
+ { UCHAR_CONSTANT("111") },
+ { UCHAR_CONSTANT("112") },
+ { UCHAR_CONSTANT("113") },
+ { UCHAR_CONSTANT("114") },
+ { UCHAR_CONSTANT("115") },
+ { UCHAR_CONSTANT("116") },
+ { UCHAR_CONSTANT("117") },
+ { UCHAR_CONSTANT("118") },
+ { UCHAR_CONSTANT("119") },
+ { UCHAR_CONSTANT("120") },
+ { UCHAR_CONSTANT("121") },
+ { UCHAR_CONSTANT("122") },
+ { UCHAR_CONSTANT("123") },
+ { UCHAR_CONSTANT("124") },
+ { UCHAR_CONSTANT("125") },
+ { UCHAR_CONSTANT("126") },
+ { UCHAR_CONSTANT("127") },
+ { UCHAR_CONSTANT("128") },
+ { UCHAR_CONSTANT("129") },
+ { UCHAR_CONSTANT("130") },
+ { UCHAR_CONSTANT("131") },
+ { UCHAR_CONSTANT("132") },
+ { UCHAR_CONSTANT("133") },
+ { UCHAR_CONSTANT("134") },
+ { UCHAR_CONSTANT("135") },
+ { UCHAR_CONSTANT("136") },
+ { UCHAR_CONSTANT("137") },
+ { UCHAR_CONSTANT("138") },
+ { UCHAR_CONSTANT("139") },
+ { UCHAR_CONSTANT("140") },
+ { UCHAR_CONSTANT("141") },
+ { UCHAR_CONSTANT("142") },
+ { UCHAR_CONSTANT("143") },
+ { UCHAR_CONSTANT("144") },
+ { UCHAR_CONSTANT("145") },
+ { UCHAR_CONSTANT("146") },
+ { UCHAR_CONSTANT("147") },
+ { UCHAR_CONSTANT("148") },
+ { UCHAR_CONSTANT("149") },
+ { UCHAR_CONSTANT("150") },
+ { UCHAR_CONSTANT("151") },
+ { UCHAR_CONSTANT("152") },
+ { UCHAR_CONSTANT("153") },
+ { UCHAR_CONSTANT("154") },
+ { UCHAR_CONSTANT("155") },
+ { UCHAR_CONSTANT("156") },
+ { UCHAR_CONSTANT("157") },
+ { UCHAR_CONSTANT("158") },
+ { UCHAR_CONSTANT("159") },
+ { UCHAR_CONSTANT("160") },
+ { UCHAR_CONSTANT("161") },
+ { UCHAR_CONSTANT("162") },
+ { UCHAR_CONSTANT("163") },
+ { UCHAR_CONSTANT("164") },
+ { UCHAR_CONSTANT("165") },
+ { UCHAR_CONSTANT("166") },
+ { UCHAR_CONSTANT("167") },
+ { UCHAR_CONSTANT("168") },
+ { UCHAR_CONSTANT("169") },
+ { UCHAR_CONSTANT("170") },
+ { UCHAR_CONSTANT("171") },
+ { UCHAR_CONSTANT("172") },
+ { UCHAR_CONSTANT("173") },
+ { UCHAR_CONSTANT("174") },
+ { UCHAR_CONSTANT("175") },
+ { UCHAR_CONSTANT("176") },
+ { UCHAR_CONSTANT("177") },
+ { UCHAR_CONSTANT("178") },
+ { UCHAR_CONSTANT("179") },
+ { UCHAR_CONSTANT("180") },
+ { UCHAR_CONSTANT("181") },
+ { UCHAR_CONSTANT("182") },
+ { UCHAR_CONSTANT("183") },
+ { UCHAR_CONSTANT("184") },
+ { UCHAR_CONSTANT("185") },
+ { UCHAR_CONSTANT("186") },
+ { UCHAR_CONSTANT("187") },
+ { UCHAR_CONSTANT("188") },
+ { UCHAR_CONSTANT("189") },
+ { UCHAR_CONSTANT("190") },
+ { UCHAR_CONSTANT("191") },
+ { UCHAR_CONSTANT("192") },
+ { UCHAR_CONSTANT("193") },
+ { UCHAR_CONSTANT("194") },
+ { UCHAR_CONSTANT("195") },
+ { UCHAR_CONSTANT("196") },
+ { UCHAR_CONSTANT("197") },
+ { UCHAR_CONSTANT("198") },
+ { UCHAR_CONSTANT("199") }
+ };
+static char hexdigit[16] =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8',
+ '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+#if defined(_AIX)
+/* AIXPORT : replace facility names with aso and caa only for AIX */
+static const char *syslog_fac_names[LOG_NFACILITIES] = { "kern", "user", "mail", "daemon", "auth", "syslog", "lpr",
+ "news", "uucp", "cron", "authpriv", "ftp", "aso", "audit",
+ "alert", "caa", "local0", "local1", "local2", "local3",
+ "local4", "local5", "local6", "local7", "invld" };
+/* length of the facility names string (for optimizatiions) */
+static short len_syslog_fac_names[LOG_NFACILITIES] = { 4, 4, 4, 6, 4, 6, 3,
+ 4, 4, 4, 8, 3, 3, 5,
+ 5, 3, 6, 6, 6, 6,
+ 6, 6, 6, 6, 5 };
+
+#else
+/*syslog facility names (as of RFC5424) */
+static const char *syslog_fac_names[LOG_NFACILITIES] = { "kern", "user", "mail", "daemon", "auth", "syslog", "lpr",
+ "news", "uucp", "cron", "authpriv", "ftp", "ntp", "audit",
+ "alert", "clock", "local0", "local1", "local2", "local3",
+ "local4", "local5", "local6", "local7", "invld" };
+/* length of the facility names string (for optimizatiions) */
+static short len_syslog_fac_names[LOG_NFACILITIES] = { 4, 4, 4, 6, 4, 6, 3,
+ 4, 4, 4, 8, 3, 3, 5,
+ 5, 5, 6, 6, 6, 6,
+ 6, 6, 6, 6, 5 };
+#endif
+
+/* table of severity names (in numerical order)*/
+static const 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 const char *syslog_number_names[LOG_NFACILITIES] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+ "10", "11", "12", "13", "14",
+ "15", "16", "17", "18", "19", "20", "21", "22", "23", "24" };
+
+/* 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(smsg_t * const pM, sbool bLockMutex);
+static rsRetVal jsonPathFindParent(struct json_object *jroot, uchar *name, uchar *leaf,
+ struct json_object **parent, int bCreate);
+static uchar * jsonPathGetLeaf(uchar *name, int lenName);
+struct json_object *jsonDeepCopy(struct json_object *src);
+static json_bool jsonVarExtract(struct json_object* root, const char *key, struct json_object **value);
+void getRawMsgAfterPRI(smsg_t * const pM, uchar **pBuf, int *piLen);
+
+
+/* the locking and unlocking implementations: */
+static inline void
+MsgLock(smsg_t *pThis)
+{
+ #if DEV_DEBUG == 1
+ dbgprintf("MsgLock(0x%lx)\n", (unsigned long) pThis);
+ #endif
+ pthread_mutex_lock(&pThis->mut);
+}
+static inline void
+MsgUnlock(smsg_t *pThis)
+{
+ #if DEV_DEBUG == 1
+ dbgprintf("MsgUnlock(0x%lx)\n", (unsigned long) pThis);
+ #endif
+ pthread_mutex_unlock(&pThis->mut);
+}
+
+
+/* set RcvFromIP name in msg object WITHOUT calling AddRef.
+ * rgerhards, 2013-01-22
+ */
+static inline void
+MsgSetRcvFromIPWithoutAddRef(smsg_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
+ */
+static void MsgSetRcvFromWithoutAddRef(smsg_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)
+ * pRuleset pointer inside msg is updated. If ruleset cannot be found,
+ * no update is done and an error message emitted.
+ */
+static void ATTR_NONNULL()
+MsgSetRulesetByName(smsg_t * const pMsg, cstr_t *const rulesetName)
+{
+ uchar *const rs_name = rsCStrGetSzStrNoNULL(rulesetName);
+ const rsRetVal localRet =
+ rulesetGetRuleset(runConf, &(pMsg->pRuleset), rs_name);
+
+ if(localRet != RS_RET_OK) {
+ LogError(0, localRet, "msg: ruleset '%s' could not be found and could not "
+ "be assigned to message object. This possibly leads to the message "
+ "being processed incorrectly. We cannot do anything against this, but "
+ "wanted to let you know.", rs_name);
+ }
+}
+
+/* do a DNS reverse resolution, if not already done, reflect status
+ * rgerhards, 2009-11-16
+ */
+static rsRetVal
+resolveDNS(smsg_t * const 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) {
+ if (pMsg->msgFlags & PRESERVE_CASE) {
+ localRet = net.cvthname(pMsg->rcvFrom.pfrominet, NULL, &localName, &ip);
+ } else {
+ 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;
+}
+
+
+void
+getInputName(const smsg_t * const pM, uchar **ppsz, int *const plen)
+{
+ if(pM == NULL || pM->pInputName == NULL) {
+ *ppsz = UCHAR_CONSTANT("");
+ *plen = 0;
+ } else {
+ prop.GetString(pM->pInputName, ppsz, plen);
+ }
+}
+
+
+static uchar*
+getRcvFromIP(smsg_t * const pM)
+{
+ uchar *psz;
+ int len;
+ 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);
+ }
+ return psz;
+}
+
+
+/* map a property name (string) to a property ID */
+rsRetVal
+propNameToID(const uchar *const pName, propid_t *const pPropID)
+{
+ DEFiRet;
+
+ /* sometimes there are aliases to the original MonitoWare
+ * property names. These come after || in the ifs below. */
+ if(!strcasecmp((char*) pName, "msg")) {
+ *pPropID = PROP_MSG;
+ } else if(!strcasecmp((char*) pName, "timestamp")
+ || !strcasecmp((char*) pName, "timereported")) {
+ *pPropID = PROP_TIMESTAMP;
+ } else if(!strcasecmp((char*) pName, "hostname") || !strcasecmp((char*) pName, "source")) {
+ *pPropID = PROP_HOSTNAME;
+ } else if(!strcasecmp((char*) pName, "syslogtag")) {
+ *pPropID = PROP_SYSLOGTAG;
+ } else if(!strcasecmp((char*) pName, "rawmsg")) {
+ *pPropID = PROP_RAWMSG;
+ } else if(!strcasecmp((char*) pName, "rawmsg-after-pri")) {
+ *pPropID = PROP_RAWMSG_AFTER_PRI;
+ } else if(!strcasecmp((char*) pName, "inputname")) {
+ *pPropID = PROP_INPUTNAME;
+ } else if(!strcasecmp((char*) pName, "fromhost")) {
+ *pPropID = PROP_FROMHOST;
+ } else if(!strcasecmp((char*) pName, "fromhost-ip")) {
+ *pPropID = PROP_FROMHOST_IP;
+ } else if(!strcasecmp((char*) pName, "pri")) {
+ *pPropID = PROP_PRI;
+ } else if(!strcasecmp((char*) pName, "pri-text")) {
+ *pPropID = PROP_PRI_TEXT;
+ } else if(!strcasecmp((char*) pName, "iut")) {
+ *pPropID = PROP_IUT;
+ } else if(!strcasecmp((char*) pName, "syslogfacility")) {
+ *pPropID = PROP_SYSLOGFACILITY;
+ } else if(!strcasecmp((char*) pName, "syslogfacility-text")) {
+ *pPropID = PROP_SYSLOGFACILITY_TEXT;
+ } else if(!strcasecmp((char*) pName, "syslogseverity") || !strcasecmp((char*) pName, "syslogpriority")) {
+ *pPropID = PROP_SYSLOGSEVERITY;
+ } else if(!strcasecmp((char*) pName, "syslogseverity-text") ||
+ !strcasecmp((char*) pName, "syslogpriority-text")) {
+ *pPropID = PROP_SYSLOGSEVERITY_TEXT;
+ } else if(!strcasecmp((char*) pName, "timegenerated")) {
+ *pPropID = PROP_TIMEGENERATED;
+ } else if(!strcasecmp((char*) pName, "programname")) {
+ *pPropID = PROP_PROGRAMNAME;
+ } else if(!strcasecmp((char*) pName, "protocol-version")) {
+ *pPropID = PROP_PROTOCOL_VERSION;
+ } else if(!strcasecmp((char*) pName, "structured-data")) {
+ *pPropID = PROP_STRUCTURED_DATA;
+ } else if(!strcasecmp((char*) pName, "app-name")) {
+ *pPropID = PROP_APP_NAME;
+ } else if(!strcasecmp((char*) pName, "procid")) {
+ *pPropID = PROP_PROCID;
+ } else if(!strcasecmp((char*) pName, "msgid")) {
+ *pPropID = PROP_MSGID;
+ } else if(!strcasecmp((char*) pName, "jsonmesg")) {
+ *pPropID = PROP_JSONMESG;
+ } else if(!strcasecmp((char*) pName, "parsesuccess")) {
+ *pPropID = PROP_PARSESUCCESS;
+#ifdef USE_LIBUUID
+ } else if(!strcasecmp((char*) pName, "uuid")) {
+ *pPropID = PROP_UUID;
+#endif
+ /* here start system properties (those, that do not relate to the message itself */
+ } else if(!strcasecmp((char*) pName, "$NOW")) {
+ *pPropID = PROP_SYS_NOW;
+ } else if(!strcasecmp((char*) pName, "$YEAR")) {
+ *pPropID = PROP_SYS_YEAR;
+ } else if(!strcasecmp((char*) pName, "$MONTH")) {
+ *pPropID = PROP_SYS_MONTH;
+ } else if(!strcasecmp((char*) pName, "$DAY")) {
+ *pPropID = PROP_SYS_DAY;
+ } else if(!strcasecmp((char*) pName, "$HOUR")) {
+ *pPropID = PROP_SYS_HOUR;
+ } else if(!strcasecmp((char*) pName, "$HHOUR")) {
+ *pPropID = PROP_SYS_HHOUR;
+ } else if(!strcasecmp((char*) pName, "$QHOUR")) {
+ *pPropID = PROP_SYS_QHOUR;
+ } else if(!strcasecmp((char*) pName, "$MINUTE")) {
+ *pPropID = PROP_SYS_MINUTE;
+ } else if(!strcasecmp((char*) pName, "$WDAY")) {
+ *pPropID = PROP_SYS_WDAY;
+ } else if(!strcasecmp((char*) pName, "$now-utc")) {
+ *pPropID = PROP_SYS_NOW_UTC;
+ } else if(!strcasecmp((char*) pName, "$year-utc")) {
+ *pPropID = PROP_SYS_YEAR_UTC;
+ } else if(!strcasecmp((char*) pName, "$month-utc")) {
+ *pPropID = PROP_SYS_MONTH_UTC;
+ } else if(!strcasecmp((char*) pName, "$day-utc")) {
+ *pPropID = PROP_SYS_DAY_UTC;
+ } else if(!strcasecmp((char*) pName, "$hour-utc")) {
+ *pPropID = PROP_SYS_HOUR_UTC;
+ } else if(!strcasecmp((char*) pName, "$hhour-utc")) {
+ *pPropID = PROP_SYS_HHOUR_UTC;
+ } else if(!strcasecmp((char*) pName, "$qhour-utc")) {
+ *pPropID = PROP_SYS_QHOUR_UTC;
+ } else if(!strcasecmp((char*) pName, "$minute-utc")) {
+ *pPropID = PROP_SYS_MINUTE_UTC;
+ } else if(!strcasecmp((char*) pName, "$wday-utc")) {
+ *pPropID = PROP_SYS_WDAY_UTC;
+ } else if(!strcasecmp((char*) pName, "$now-unixtimestamp")) {
+ *pPropID = PROP_SYS_NOW_UXTIMESTAMP;
+ } else if(!strcasecmp((char*) pName, "$MYHOSTNAME")) {
+ *pPropID = PROP_SYS_MYHOSTNAME;
+ } else if(!strcasecmp((char*) pName, "$!all-json")) {
+ *pPropID = PROP_CEE_ALL_JSON;
+ } else if(!strcasecmp((char*) pName, "$!all-json-plain")) {
+ *pPropID = PROP_CEE_ALL_JSON_PLAIN;
+ } else if(!strcasecmp((char*) pName, "$BOM")) {
+ *pPropID = PROP_SYS_BOM;
+ } else if(!strcasecmp((char*) pName, "$UPTIME")) {
+ *pPropID = PROP_SYS_UPTIME;
+ } else if(!strncmp((char*) pName, "$!", 2) || pName[0] == '!') {
+ *pPropID = PROP_CEE;
+ } else if(!strncmp((char*) pName, "$.", 2) || pName[0] == '.') {
+ *pPropID = PROP_LOCAL_VAR;
+ } else if(!strncmp((char*) pName, "$/", 2) || pName[0] == '/') {
+ *pPropID = PROP_GLOBAL_VAR;
+ } else {
+ DBGPRINTF("PROP_INVALID for name '%s'\n", pName);
+ *pPropID = PROP_INVALID;
+ iRet = RS_RET_VAR_NOT_FOUND;
+ }
+
+ 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_RAWMSG_AFTER_PRI:
+ return UCHAR_CONSTANT("rawmsg-after-pri");
+ 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_JSONMESG:
+ return UCHAR_CONSTANT("jsonmesg");
+ case PROP_PARSESUCCESS:
+ return UCHAR_CONSTANT("parsesuccess");
+#ifdef USE_LIBUUID
+ case PROP_UUID:
+ return UCHAR_CONSTANT("uuid");
+#endif
+ 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_NOW_UTC:
+ return UCHAR_CONSTANT("$NOW-UTC");
+ case PROP_SYS_YEAR_UTC:
+ return UCHAR_CONSTANT("$YEAR-UTC");
+ case PROP_SYS_MONTH_UTC:
+ return UCHAR_CONSTANT("$MONTH-UTC");
+ case PROP_SYS_DAY_UTC:
+ return UCHAR_CONSTANT("$DAY-UTC");
+ case PROP_SYS_HOUR_UTC:
+ return UCHAR_CONSTANT("$HOUR-UTC");
+ case PROP_SYS_HHOUR_UTC:
+ return UCHAR_CONSTANT("$HHOUR-UTC");
+ case PROP_SYS_QHOUR_UTC:
+ return UCHAR_CONSTANT("$QHOUR-UTC");
+ case PROP_SYS_MINUTE_UTC:
+ return UCHAR_CONSTANT("$MINUTE-UTC");
+ case PROP_SYS_WDAY:
+ return UCHAR_CONSTANT("$WDAY");
+ case PROP_SYS_WDAY_UTC:
+ return UCHAR_CONSTANT("$WDAY-UTC");
+ case PROP_SYS_NOW_UXTIMESTAMP:
+ return UCHAR_CONSTANT("$NOW-UNIXTIMESTAMP");
+ case PROP_SYS_MYHOSTNAME:
+ return UCHAR_CONSTANT("$MYHOSTNAME");
+ case PROP_CEE_ALL_JSON:
+ return UCHAR_CONSTANT("$!all-json");
+ case PROP_CEE_ALL_JSON_PLAIN:
+ return UCHAR_CONSTANT("$!all-json-plain");
+ case PROP_SYS_BOM:
+ return UCHAR_CONSTANT("$BOM");
+ case PROP_SYS_UPTIME:
+ return UCHAR_CONSTANT("$UPTIME");
+ case PROP_CEE:
+ return UCHAR_CONSTANT("*CEE-based property*");
+ case PROP_LOCAL_VAR:
+ return UCHAR_CONSTANT("*LOCAL_VARIABLE*");
+ case PROP_GLOBAL_VAR:
+ return UCHAR_CONSTANT("*GLOBAL_VARIABLE*");
+ 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 rsRetVal
+msgBaseConstruct(smsg_t **ppThis)
+{
+ DEFiRet;
+ smsg_t *pM;
+
+ assert(ppThis != NULL);
+ CHKmalloc(pM = malloc(sizeof(smsg_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 = LOG_DEBUG;
+ pM->iFacility = LOG_INVLD;
+ 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->pszStrucData = NULL;
+ pM->lenStrucData = 0;
+ 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;
+ pM->localvars = NULL;
+ pM->dfltTZ[0] = '\0';
+ 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);
+
+ #if DEV_DEBUG == 1
+ dbgprintf("msgConstruct\t0x%x, ref 1\n", (int)pM);
+ #endif
+
+ *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(smsg_t **ppThis, const struct syslogTime *stTime, const 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(smsg_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), TIME_IN_LOCALTIME);
+ 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(smsg_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(smsg_t *pThis)
+{
+ if(pThis->iLenTAG >= CONF_TAG_BUFSIZE)
+ free(pThis->TAG.pszTAG);
+}
+static inline void freeHOSTNAME(smsg_t *pThis)
+{
+ if(pThis->iLenHOSTNAME >= CONF_HOSTNAME_BUFSIZE)
+ free(pThis->pszHOSTNAME);
+}
+
+
+rsRetVal msgDestruct(smsg_t **ppThis)
+{
+ DEFiRet;
+ smsg_t *pThis;
+ int currRefCount;
+# ifdef HAVE_MALLOC_TRIM
+ int currCnt;
+# endif
+CODESTARTobjDestruct(msg)
+ #if DEV_DEBUG == 1
+ dbgprintf("msgDestruct\t0x%lx, "
+ "Ref now: %d\n", (unsigned long)pThis, pThis->iRefCount - 1);
+ #endif
+# ifdef HAVE_ATOMIC_BUILTINS
+ currRefCount = ATOMIC_DEC_AND_FETCH(&pThis->iRefCount, NULL);
+# else
+ MsgLock(pThis);
+ currRefCount = --pThis->iRefCount;
+# endif
+ if(currRefCount == 0)
+ {
+ #if DEV_DEBUG == 1
+ dbgprintf("msgDestruct\t0x%lx, RefCount now 0, doing DESTROY\n",
+ (unsigned long)pThis);
+ #endif
+ 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);
+ free(pThis->pszStrucData);
+ if(pThis->iLenPROGNAME >= CONF_PROGNAME_BUFSIZE)
+ free(pThis->PROGNAME.ptr);
+ 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->localvars != NULL)
+ json_object_put(pThis->localvars);
+ 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.
+ */
+# ifdef 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;\
+ }\
+ cstrFinalize(pNew->pCS##name); \
+ }
+/* 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
+ */
+smsg_t* MsgDup(smsg_t* pOld)
+{
+ smsg_t* pNew;
+ rsRetVal localRet;
+
+ assert(pOld != NULL);
+
+ 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->tRcvdAt = pOld->tRcvdAt;
+ 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->pszRawMsg == pOld->szRawMsg) {
+ 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);
+ }
+ }
+ if(pOld->pszStrucData == NULL) {
+ pNew->pszStrucData = NULL;
+ } else {
+ pNew->pszStrucData = (uchar*)strdup((char*)pOld->pszStrucData);
+ pNew->lenStrucData = pOld->lenStrucData;
+ }
+
+ tmpCOPYCSTR(APPNAME);
+ tmpCOPYCSTR(PROCID);
+ tmpCOPYCSTR(MSGID);
+
+ if(pOld->json != NULL)
+ pNew->json = jsonDeepCopy(pOld->json);
+ if(pOld->localvars != NULL)
+ pNew->localvars = jsonDeepCopy(pOld->localvars);
+
+ /* 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.
+ */
+
+ 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(smsg_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));
+ psz = pThis->pszStrucData;
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("pszStrucData"), PROPTYPE_PSZ, (void*) psz));
+ if(pThis->json != NULL) {
+ MsgLock(pThis);
+ psz = (uchar*) json_object_get_string(pThis->json);
+ MsgUnlock(pThis);
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("json"), PROPTYPE_PSZ, (void*) psz));
+ }
+ if(pThis->localvars != NULL) {
+ MsgLock(pThis);
+ psz = (uchar*) json_object_get_string(pThis->localvars);
+ MsgUnlock(pThis);
+ CHKiRet(obj.SerializeProp(pStrm, UCHAR_CONSTANT("localvars"), PROPTYPE_PSZ, (void*) psz));
+ }
+
+ objSerializePTR(pStrm, pCSAPPNAME, CSTR);
+ objSerializePTR(pStrm, pCSPROCID, CSTR);
+ objSerializePTR(pStrm, pCSMSGID, CSTR);
+
+ objSerializePTR(pStrm, pszUUID, PSZ);
+
+ if(pThis->pRuleset != NULL) {
+ 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, INT);
+
+ 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 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(smsg_t * const pMsg, strm_t *pStrm)
+{
+ prop_t *myProp;
+ prop_t *propRcvFrom = NULL;
+ prop_t *propRcvFromIP = NULL;
+ struct json_tokener *tokener;
+ 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("pszStrucData")) {
+ MsgSetStructuredData(pMsg, (char*) rsCStrGetSzStrNoNULL(pVar->val.pStr));
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("json")) {
+ tokener = json_tokener_new();
+ pMsg->json = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pVar->val.pStr),
+ cstrLen(pVar->val.pStr));
+ json_tokener_free(tokener);
+ reinitVar(pVar);
+ CHKiRet(objDeserializeProperty(pVar, pStrm));
+ }
+ if(isProp("localvars")) {
+ tokener = json_tokener_new();
+ pMsg->localvars = json_tokener_parse_ex(tokener, (char*)rsCStrGetSzStrNoNULL(pVar->val.pStr),
+ cstrLen(pVar->val.pStr));
+ json_tokener_free(tokener);
+ 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")) {
+ DBGPRINTF("error property: %s\n", rsCStrGetSzStrNoNULL(pVar->pcsName));
+ ABORT_FINALIZE(RS_RET_DS_PROP_SEQ_ERR);
+ }
+ MsgSetMSGoffs(pMsg, pVar->val.num);
+finalize_it:
+ if(pVar != NULL)
+ var.Destruct(&pVar);
+ if(Debug && iRet != RS_RET_OK) {
+ dbgprintf("MsgDeserialize error %d\n", iRet);
+ }
+ 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);
+ */
+smsg_t *MsgAddRef(smsg_t * const pM)
+{
+ assert(pM != NULL);
+# ifdef HAVE_ATOMIC_BUILTINS
+ ATOMIC_INC(&pM->iRefCount, NULL);
+# else
+ MsgLock(pM);
+ pM->iRefCount++;
+ MsgUnlock(pM);
+# endif
+ #if DEV_DEBUG == 1
+ dbgprintf("MsgAddRef\t0x%x done, Ref now: %d\n", (int)pM, pM->iRefCount);
+ #endif
+ return(pM);
+}
+
+
+/* This functions tries to acquire 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 acquirePROCIDFromTAG(smsg_t * const pM)
+{
+ register int i;
+ uchar *pszTag;
+ DEFiRet;
+
+ assert(pM != NULL);
+
+ if(pM->pCSPROCID != NULL)
+ return RS_RET_OK; /* we are already done ;) */
+
+ if(msgGetProtocolVersion(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, finally we could obtain a PROCID. So let's use it ;) */
+ 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 rsRetVal
+acquireProgramName(smsg_t * const 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] != '[')
+ && (runConf->globals.parser.bPermitSlashInProgramname || (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(smsg_t * const 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 */
+static const char *getProtocolVersionString(smsg_t * const pM)
+{
+ assert(pM != NULL);
+ return(pM->iProtocolVersion ? "1" : "0");
+}
+
+void
+msgSetPRI(smsg_t *const __restrict__ pMsg, syslog_pri_t pri)
+{
+ if(pri > LOG_MAXPRI)
+ pri = LOG_PRI_INVLD;
+ pMsg->iFacility = pri2fac(pri),
+ pMsg->iSeverity = pri2sev(pri);
+}
+
+#ifdef USE_LIBUUID
+/* note: libuuid seems not to be thread-safe, so we need
+ * to get some safeguards in place.
+ */
+static pthread_mutex_t mutUUID = PTHREAD_MUTEX_INITIALIZER;
+
+static void call_uuid_generate(uuid_t uuid)
+{
+ pthread_mutex_lock(&mutUUID);
+ pthread_cleanup_push(mutexCancelCleanup, &mutUUID);
+ uuid_generate(uuid);
+ pthread_cleanup_pop(1);
+}
+
+static void msgSetUUID(smsg_t * const pM)
+{
+ size_t lenRes = sizeof(uuid_t) * 2 + 1;
+ char hex_char [] = "0123456789ABCDEF";
+ unsigned int byte_nbr;
+ uuid_t uuid;
+
+ dbgprintf("[MsgSetUUID] START, lenRes %llu\n", (long long unsigned) lenRes);
+ assert(pM != NULL);
+
+ if((pM->pszUUID = (uchar*) malloc(lenRes)) == NULL) {
+ pM->pszUUID = (uchar *)"";
+ } else {
+ call_uuid_generate(uuid);
+ 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];
+ }
+
+ pM->pszUUID[lenRes-1] = '\0';
+ dbgprintf("[MsgSetUUID] UUID : %s LEN: %d \n", pM->pszUUID, (int)lenRes);
+ }
+ dbgprintf("[MsgSetUUID] END\n");
+}
+
+static void getUUID(smsg_t * const 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
+
+int ATTR_NONNULL()
+getRawMsgLen(const smsg_t *const pMsg)
+{
+ return (pMsg->pszRawMsg == NULL) ? 0 : pMsg->iLenRawMsg;
+}
+
+void
+getRawMsg(const smsg_t * const 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;
+ }
+ }
+}
+
+void
+getRawMsgAfterPRI(smsg_t * const pM, uchar **pBuf, int *piLen)
+{
+ if(pM == NULL) {
+ *pBuf= UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ if(pM->pszRawMsg == NULL) {
+ *pBuf= UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ /* unfortunately, pM->offAfterPRI seems NOT to be
+ * correct/consistent in all cases. imuxsock and imudp
+ * seem to have other values than imptcp. Testbench
+ * covers some of that. As a work-around, we caluculate
+ * the value ourselfes here. -- rgerhards, 2015-10-09
+ */
+ size_t offAfterPRI = 0;
+ if(pM->pszRawMsg[0] == '<') { /* do we have a PRI? */
+ if(pM->pszRawMsg[2] == '>')
+ offAfterPRI = 3;
+ else if(pM->pszRawMsg[3] == '>')
+ offAfterPRI = 4;
+ else if(pM->pszRawMsg[4] == '>')
+ offAfterPRI = 5;
+ }
+ *pBuf = pM->pszRawMsg + offAfterPRI;
+ *piLen = pM->iLenRawMsg - offAfterPRI;
+ }
+ }
+}
+
+
+/* note: setMSGLen() is only for friends who really know what they
+ * do. Setting an invalid length can be desasterous!
+ */
+void setMSGLen(smsg_t * const pM, int lenMsg)
+{
+ pM->iLenMSG = lenMsg;
+}
+
+int getMSGLen(smsg_t * const pM)
+{
+ return((pM == NULL) ? 0 : pM->iLenMSG);
+}
+
+uchar *getMSG(smsg_t * const 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 */
+int
+getPRIi(const smsg_t * const pM)
+{
+ syslog_pri_t pri = (pM->iFacility << 3) + (pM->iSeverity);
+ if(pri > 191)
+ pri = LOG_PRI_INVLD;
+ return pri;
+}
+
+
+/* Get PRI value in text form
+ */
+const char *
+getPRI(smsg_t * const 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;
+}
+
+
+static const char *
+formatISOWeekOrYear(enum tplFormatTypes eFmt, struct syslogTime *pTm)
+{
+ if(pTm->year >= 1970 && pTm->year <= 2099) {
+ int isoWeekYear;
+ int isoWeek;
+
+ isoWeek = getISOWeek(pTm, &isoWeekYear);
+
+ if (eFmt == tplFmtISOWeek) {
+ return two_digits[isoWeek];
+ } else {
+ return years[isoWeekYear - 1967];
+ }
+ } else {
+ return "YEAR OUT OF RANGE(1970-2099)";
+ }
+}
+
+const char *
+getTimeReported(smsg_t * const pM, enum tplFormatTypes eFmt)
+{
+ 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);
+ case tplFmtWDayName:
+ return wdayNames[getWeekdayNbr(&pM->tTIMESTAMP)];
+ case tplFmtWDay:
+ return one_digit[getWeekdayNbr(&pM->tTIMESTAMP)];
+ case tplFmtMonth:
+ return two_digits[(int)pM->tTIMESTAMP.month];
+ case tplFmtYear:
+ if(pM->tTIMESTAMP.year >= 1967 && pM->tTIMESTAMP.year <= 2099)
+ return years[pM->tTIMESTAMP.year - 1967];
+ else
+ return "YEAR OUT OF RANGE(1967-2099)";
+ case tplFmtDay:
+ return two_digits[(int)pM->tTIMESTAMP.day];
+ case tplFmtHour:
+ return two_digits[(int)pM->tTIMESTAMP.hour];
+ case tplFmtMinute:
+ return two_digits[(int)pM->tTIMESTAMP.minute];
+ case tplFmtSecond:
+ return two_digits[(int)pM->tTIMESTAMP.second];
+ case tplFmtTZOffsHour:
+ return two_digits[(int)pM->tTIMESTAMP.OffsetHour];
+ case tplFmtTZOffsMin:
+ return two_digits[(int)pM->tTIMESTAMP.OffsetMinute];
+ case tplFmtTZOffsDirection:
+ return (pM->tTIMESTAMP.OffsetMode == '+')? "+" : "-";
+ case tplFmtOrdinal:
+ return daysInYear[getOrdinal(&pM->tTIMESTAMP)];
+ case tplFmtWeek:
+ return two_digits[getWeek(&pM->tTIMESTAMP)];
+ case tplFmtISOWeek:
+ case tplFmtISOWeekYear:
+ return formatISOWeekOrYear(eFmt, &pM->tTIMESTAMP);
+ }
+ return "INVALID eFmt OPTION!";
+}
+
+
+
+static const char *getTimeUTC(struct syslogTime *const __restrict__ pTmIn,
+ const enum tplFormatTypes eFmt,
+ unsigned short *const __restrict__ pbMustBeFreed)
+{
+ struct syslogTime tUTC;
+ char *retbuf = NULL;
+
+ timeConvertToUTC(pTmIn, &tUTC);
+ struct syslogTime *const pTm = &tUTC;
+
+ switch(eFmt) {
+ case tplFmtDefault:
+ if((retbuf = malloc(16)) != NULL) {
+ datetime.formatTimestamp3164(pTm, retbuf, 0);
+ }
+ break;
+ case tplFmtMySQLDate:
+ if((retbuf = malloc(15)) != NULL) {
+ datetime.formatTimestampToMySQL(pTm, retbuf);
+ }
+ break;
+ case tplFmtPgSQLDate:
+ if((retbuf = malloc(21)) != NULL) {
+ datetime.formatTimestampToPgSQL(pTm, retbuf);
+ }
+ break;
+ case tplFmtRFC3164Date:
+ case tplFmtRFC3164BuggyDate:
+ if((retbuf = malloc(16)) != NULL) {
+ datetime.formatTimestamp3164(pTm, retbuf, (eFmt == tplFmtRFC3164BuggyDate));
+ }
+ break;
+ case tplFmtRFC3339Date:
+ if((retbuf = malloc(33)) != NULL) {
+ datetime.formatTimestamp3339(pTm, retbuf);
+ }
+ break;
+ case tplFmtUnixDate:
+ if((retbuf = malloc(12)) != NULL) {
+ datetime.formatTimestampUnix(pTm, retbuf);
+ }
+ break;
+ case tplFmtSecFrac:
+ if((retbuf = malloc(7)) != NULL) {
+ datetime.formatTimestampSecFrac(pTm, retbuf);
+ }
+ break;
+ case tplFmtWDayName:
+ retbuf = strdup(wdayNames[getWeekdayNbr(pTm)]);
+ break;
+ case tplFmtWDay:
+ retbuf = strdup(one_digit[getWeekdayNbr(pTm)]);
+ break;
+ case tplFmtMonth:
+ retbuf = strdup(two_digits[(int)pTm->month]);
+ break;
+ case tplFmtYear:
+ if(pTm->year >= 1967 && pTm->year <= 2099)
+ retbuf = strdup(years[pTm->year - 1967]);
+ else
+ retbuf = strdup("YEAR OUT OF RANGE(1967-2099)");
+ break;
+ case tplFmtDay:
+ retbuf = strdup(two_digits[(int)pTm->day]);
+ break;
+ case tplFmtHour:
+ retbuf = strdup(two_digits[(int)pTm->hour]);
+ break;
+ case tplFmtMinute:
+ retbuf = strdup(two_digits[(int)pTm->minute]);
+ break;
+ case tplFmtSecond:
+ retbuf = strdup(two_digits[(int)pTm->second]);
+ break;
+ case tplFmtTZOffsHour:
+ retbuf = strdup(two_digits[(int)pTm->OffsetHour]);
+ break;
+ case tplFmtTZOffsMin:
+ retbuf = strdup(two_digits[(int)pTm->OffsetMinute]);
+ break;
+ case tplFmtTZOffsDirection:
+ retbuf = strdup((pTm->OffsetMode == '+')? "+" : "-");
+ break;
+ case tplFmtOrdinal:
+ retbuf = strdup(daysInYear[getOrdinal(pTm)]);
+ break;
+ case tplFmtWeek:
+ retbuf = strdup(two_digits[getWeek(pTm)]);
+ break;
+ case tplFmtISOWeek:
+ case tplFmtISOWeekYear:
+ retbuf = strdup(formatISOWeekOrYear(eFmt, pTm));
+ break;
+ }
+
+ if(retbuf == NULL) {
+ retbuf = (char*)"internal error: invalid eFmt option or malloc problem";
+ *pbMustBeFreed = 0;
+ } else {
+ *pbMustBeFreed = 1;
+ }
+ return retbuf;
+}
+
+static const char *
+getTimeGenerated(smsg_t *const __restrict__ pM,
+ const enum tplFormatTypes eFmt)
+{
+ struct syslogTime *const pTm = &pM->tRcvdAt;
+ 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(pTm, 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(pTm, 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(pTm, 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(pTm, 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(pTm, pM->pszRcvdAt3339);
+ }
+ MsgUnlock(pM);
+ return(pM->pszRcvdAt3339);
+ case tplFmtUnixDate:
+ MsgLock(pM);
+ if(pM->pszRcvdAt_Unix[0] == '\0') {
+ datetime.formatTimestampUnix(pTm, 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(pTm, pM->pszRcvdAt_SecFrac);
+ }
+ MsgUnlock(pM);
+ }
+ return(pM->pszRcvdAt_SecFrac);
+ case tplFmtWDayName:
+ return wdayNames[getWeekdayNbr(pTm)];
+ case tplFmtWDay:
+ return one_digit[getWeekdayNbr(pTm)];
+ case tplFmtMonth:
+ return two_digits[(int)pTm->month];
+ case tplFmtYear:
+ if(pTm->year >= 1967 && pTm->year <= 2099)
+ return years[pTm->year - 1967];
+ else
+ return "YEAR OUT OF RANGE(1967-2099)";
+ case tplFmtDay:
+ return two_digits[(int)pTm->day];
+ case tplFmtHour:
+ return two_digits[(int)pTm->hour];
+ case tplFmtMinute:
+ return two_digits[(int)pTm->minute];
+ case tplFmtSecond:
+ return two_digits[(int)pTm->second];
+ case tplFmtTZOffsHour:
+ return two_digits[(int)pTm->OffsetHour];
+ case tplFmtTZOffsMin:
+ return two_digits[(int)pTm->OffsetMinute];
+ case tplFmtTZOffsDirection:
+ return (pTm->OffsetMode == '+')? "+" : "-";
+ case tplFmtOrdinal:
+ return daysInYear[getOrdinal(pTm)];
+ case tplFmtWeek:
+ return two_digits[getWeek(pTm)];
+ case tplFmtISOWeek:
+ case tplFmtISOWeekYear:
+ return formatISOWeekOrYear(eFmt, pTm);
+ }
+ return "INVALID eFmt OPTION!";
+}
+
+
+static const char *getSeverity(smsg_t * const pM)
+{
+ const char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(pM->iSeverity > 7) {
+ name = "invld";
+ } else {
+ name = syslog_number_names[pM->iSeverity];
+ }
+
+ return name;
+}
+
+
+static const char *getSeverityStr(smsg_t * const pM)
+{
+ const char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(pM->iSeverity > 7) {
+ name = "invld";
+ } else {
+ name = syslog_severity_names[pM->iSeverity];
+ }
+
+ return name;
+}
+
+static const char *getFacility(smsg_t * const pM)
+{
+ const char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(pM->iFacility > 23) {
+ name = "invld";
+ } else {
+ name = syslog_number_names[pM->iFacility];
+ }
+
+ return name;
+}
+
+static const char *getFacilityStr(smsg_t * const pM)
+{
+ const char *name = NULL;
+
+ if(pM == NULL)
+ return "";
+
+ if(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(smsg_t * const 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(smsg_t * const pMsg, int 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 ATTR_NONNULL(1,2)
+MsgSetAPPNAME(smsg_t *__restrict__ const pMsg, const char *pszAPPNAME)
+{
+ DEFiRet;
+ assert(pMsg != NULL);
+ if(pszAPPNAME[0] == '\0') {
+ pszAPPNAME = "-"; /* RFC5424 NIL value */
+ }
+ if(pMsg->pCSAPPNAME == NULL) {
+ /* we need to obtain the object first */
+ CHKiRet(rsCStrConstruct(&pMsg->pCSAPPNAME));
+ }
+ /* if we reach this point, we have the object */
+ CHKiRet(rsCStrSetSzStr(pMsg->pCSAPPNAME, (uchar*) pszAPPNAME));
+ cstrFinalize(pMsg->pCSAPPNAME);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* rgerhards 2004-11-24: set PROCID in msg object
+ */
+rsRetVal MsgSetPROCID(smsg_t *__restrict__ const pMsg, const 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));
+ cstrFinalize(pMsg->pCSPROCID);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* check if we have a procid, and, if not, try to acquire/emulate it.
+ * This must be called WITHOUT the message lock being held.
+ * rgerhards, 2009-06-26
+ */
+static void preparePROCID(smsg_t * const 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)
+ acquirePROCIDFromTAG(pM);
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+ }
+}
+
+
+#if 0
+/* rgerhards, 2005-11-24
+ */
+static int getPROCIDLen(smsg_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(smsg_t * const 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(smsg_t * const pMsg, const 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 */
+ CHKiRet(rsCStrSetSzStr(pMsg->pCSMSGID, (uchar*) pszMSGID));
+ cstrFinalize(pMsg->pCSMSGID);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Return state of last parser. If it had success, "OK" is returned, else
+ * "FAIL". All from the constant pool.
+ */
+static const char *getParseSuccess(smsg_t * const pM)
+{
+ return (pM->bParseSuccess) ? "OK" : "FAIL";
+}
+
+
+/* al, 2011-07-26: LockMsg to avoid race conditions
+ */
+static const char *getMSGID(smsg_t * const 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(smsg_t * const pMsg, int bSuccess)
+{
+ assert(pMsg != NULL);
+ pMsg->bParseSuccess = bSuccess;
+}
+
+
+/* return full message as a json string */
+const uchar*
+msgGetJSONMESG(smsg_t *__restrict__ const pMsg)
+{
+ struct json_object *json;
+ struct json_object *jval;
+ uchar *pRes; /* result pointer */
+ rs_size_t bufLen = -1; /* length of string or -1, if not known */
+
+ json = json_object_new_object();
+
+ jval = json_object_new_string((char*)getMSG(pMsg));
+ json_object_object_add(json, "msg", jval);
+
+ getRawMsg(pMsg, &pRes, &bufLen);
+ jval = json_object_new_string((char*)pRes);
+ json_object_object_add(json, "rawmsg", jval);
+
+ pRes = (uchar*)getTimeReported(pMsg, tplFmtRFC3339Date);
+ jval = json_object_new_string((char*)pRes);
+ json_object_object_add(json, "timereported", jval);
+
+ jval = json_object_new_string(getHOSTNAME(pMsg));
+ json_object_object_add(json, "hostname", jval);
+
+ getTAG(pMsg, &pRes, &bufLen, LOCK_MUTEX);
+ jval = json_object_new_string((char*)pRes);
+ json_object_object_add(json, "syslogtag", jval);
+
+ getInputName(pMsg, &pRes, &bufLen);
+ jval = json_object_new_string((char*)pRes);
+ json_object_object_add(json, "inputname", jval);
+
+ jval = json_object_new_string((char*)getRcvFrom(pMsg));
+ json_object_object_add(json, "fromhost", jval);
+
+ jval = json_object_new_string((char*)getRcvFromIP(pMsg));
+ json_object_object_add(json, "fromhost-ip", jval);
+
+ jval = json_object_new_string(getPRI(pMsg));
+ json_object_object_add(json, "pri", jval);
+
+ jval = json_object_new_string(getFacility(pMsg));
+ json_object_object_add(json, "syslogfacility", jval);
+
+ jval = json_object_new_string(getSeverity(pMsg));
+ json_object_object_add(json, "syslogseverity", jval);
+
+ pRes = (uchar*)getTimeGenerated(pMsg, tplFmtRFC3339Date);
+ jval = json_object_new_string((char*)pRes);
+ json_object_object_add(json, "timegenerated", jval);
+
+ jval = json_object_new_string((char*)getProgramName(pMsg, LOCK_MUTEX));
+ json_object_object_add(json, "programname", jval);
+
+ jval = json_object_new_string(getProtocolVersionString(pMsg));
+ json_object_object_add(json, "protocol-version", jval);
+
+ MsgGetStructuredData(pMsg, &pRes, &bufLen);
+ jval = json_object_new_string((char*)pRes);
+ json_object_object_add(json, "structured-data", jval);
+
+ jval = json_object_new_string(getAPPNAME(pMsg, LOCK_MUTEX));
+ json_object_object_add(json, "app-name", jval);
+
+ jval = json_object_new_string(getPROCID(pMsg, LOCK_MUTEX));
+ json_object_object_add(json, "procid", jval);
+
+ jval = json_object_new_string(getMSGID(pMsg));
+ json_object_object_add(json, "msgid", jval);
+
+#ifdef USE_LIBUUID
+ if(pMsg->pszUUID == NULL) {
+ jval = NULL;
+ } else {
+ getUUID(pMsg, &pRes, &bufLen);
+ jval = json_object_new_string((char*)pRes);
+ }
+ json_object_object_add(json, "uuid", jval);
+#endif
+
+ json_object_object_add(json, "$!", json_object_get(pMsg->json));
+
+ pRes = (uchar*) strdup(json_object_get_string(json));
+ json_object_put(json);
+ return pRes;
+}
+
+/* rgerhards 2009-06-12: set associated ruleset
+ */
+void MsgSetRuleset(smsg_t * const pMsg, ruleset_t *pRuleset)
+{
+ assert(pMsg != NULL);
+ pMsg->pRuleset = pRuleset;
+}
+
+
+/* set TAG in msg object
+ * (rewritten 2009-06-18 rgerhards)
+ */
+void MsgSetTAG(smsg_t *__restrict__ const pMsg, const uchar* pszBuf, const 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 void ATTR_NONNULL(1)
+tryEmulateTAG(smsg_t *const pM, const 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(msgGetProtocolVersion(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);
+ }
+ /* Signal change in TAG for acquireProgramName */
+ pM->iLenPROGNAME = -1;
+ }
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+}
+
+
+void ATTR_NONNULL(2,3)
+getTAG(smsg_t * const pM, uchar **const ppBuf, int *const piLen, const sbool bLockMutex)
+{
+ if(bLockMutex == LOCK_MUTEX)
+ MsgLock(pM);
+
+ if(pM == NULL) {
+ *ppBuf = UCHAR_CONSTANT("");
+ *piLen = 0;
+ } else {
+ if(pM->iLenTAG == 0)
+ tryEmulateTAG(pM, MUTEX_ALREADY_LOCKED);
+ 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;
+ }
+ }
+
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+}
+
+
+int getHOSTNAMELen(smsg_t * const 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;
+}
+
+
+const char *getHOSTNAME(smsg_t * const 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(smsg_t * const pM)
+{
+ uchar *psz;
+ int len;
+
+ 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);
+ }
+ return psz;
+}
+
+
+/* rgerhards 2004-11-24: set STRUCTURED DATA in msg object
+ */
+rsRetVal MsgSetStructuredData(smsg_t * const pMsg, const char* pszStrucData)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pMsg, msg);
+ free(pMsg->pszStrucData);
+ CHKmalloc(pMsg->pszStrucData = (uchar*)strdup(pszStrucData));
+ pMsg->lenStrucData = strlen(pszStrucData);
+finalize_it:
+ RETiRet;
+}
+
+
+/* get the "STRUCTURED-DATA" as sz string, including length */
+void
+MsgGetStructuredData(smsg_t * const pM, uchar **pBuf, rs_size_t *len)
+{
+ MsgLock(pM);
+ if(pM->pszStrucData == NULL) {
+ *pBuf = UCHAR_CONSTANT("-"),
+ *len = 1;
+ } else {
+ *pBuf = pM->pszStrucData,
+ *len = pM->lenStrucData;
+ }
+ MsgUnlock(pM);
+}
+
+/* get the "programname" as sz string
+ * rgerhards, 2005-10-19
+ */
+uchar * ATTR_NONNULL(1)
+getProgramName(smsg_t *const pM, const sbool bLockMutex)
+{
+ if(bLockMutex == LOCK_MUTEX) {
+ MsgLock(pM);
+ }
+
+ if(pM->iLenPROGNAME == -1) {
+ if(pM->iLenTAG == 0) {
+ uchar *pRes;
+ rs_size_t bufLen = -1;
+ getTAG(pM, &pRes, &bufLen, MUTEX_ALREADY_LOCKED);
+ }
+ acquireProgramName(pM);
+ }
+
+ if(bLockMutex == LOCK_MUTEX) {
+ MsgUnlock(pM);
+ }
+ return (pM->iLenPROGNAME < CONF_PROGNAME_BUFSIZE) ? pM->PROGNAME.szBuf
+ : pM->PROGNAME.ptr;
+}
+
+
+
+/* check if we have a APPNAME, and, if not, try to acquire/emulate it.
+ * rgerhards, 2009-06-26
+ */
+static void ATTR_NONNULL(1)
+prepareAPPNAME(smsg_t *const pM, const 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) {
+ if(msgGetProtocolVersion(pM) == 0) {
+ /* only then it makes sense to emulate */
+ MsgSetAPPNAME(pM, (char*)getProgramName(pM, MUTEX_ALREADY_LOCKED));
+ }
+ }
+
+ if(bLockMutex == LOCK_MUTEX)
+ MsgUnlock(pM);
+ }
+}
+
+/* rgerhards, 2005-11-24
+ */
+char *getAPPNAME(smsg_t * const pM, const 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(smsg_t * const pM, const 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(smsg_t *pThis, prop_t *inputName)
+{
+ assert(pThis != NULL);
+
+ prop.AddRef(inputName);
+ if(pThis->pInputName != NULL)
+ prop.Destruct(&pThis->pInputName);
+ pThis->pInputName = inputName;
+}
+
+/* Set default TZ. Note that at most 7 chars are set, as we would
+ * otherwise overrun our buffer!
+ */
+void MsgSetDfltTZ(smsg_t *pThis, char *tz)
+{
+ strncpy(pThis->dfltTZ, tz, 7);
+ pThis->dfltTZ[7] = '\0'; /* ensure 0-Term in case of overflow! */
+}
+
+
+/* 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(smsg_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(smsg_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(smsg_t * const pThis, const uchar *psz, const 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(smsg_t *pThis, prop_t *new)
+{
+ assert(pThis != NULL);
+
+ prop.AddRef(new);
+ MsgSetRcvFromIPWithoutAddRef(pThis, new);
+ 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(smsg_t *const pThis, const uchar *psz, const 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(smsg_t *pThis, const uchar* pszHOSTNAME, const 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(smsg_t * const pMsg, int offs)
+{
+ ISOBJ_TYPE_assert(pMsg, msg);
+ pMsg->offMSG = offs;
+ if(offs > pMsg->iLenRawMsg) {
+ assert((int)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 smsg_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(smsg_t *pThis, const 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;
+}
+
+/* truncate the (raw) message to configured max size.
+ * The function makes sure that the stored rawmsg remains
+ * properly terminated by '\0'.
+ */
+void ATTR_NONNULL()
+MsgTruncateToMaxSize(smsg_t *const pThis)
+{
+ ISOBJ_TYPE_assert(pThis, msg);
+ const int maxMsgSize = glblGetMaxLine(runConf);
+ assert(pThis->iLenRawMsg > maxMsgSize);
+
+ const int deltaSize = pThis->iLenRawMsg - maxMsgSize;
+ pThis->pszRawMsg[maxMsgSize] = '\0';
+ pThis->iLenRawMsg = maxMsgSize;
+ if(pThis->iLenMSG < deltaSize) {
+ pThis->iLenMSG = 0;
+ } else {
+ pThis->iLenMSG -= deltaSize;
+ }
+}
+
+/* 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 ATTR_NONNULL()
+MsgSetRawMsg(smsg_t *const pThis, const char*const pszRawMsg, const size_t lenMsg)
+{
+ ISOBJ_TYPE_assert(pThis, msg);
+ int deltaSize;
+ if(pThis->pszRawMsg != pThis->szRawMsg)
+ free(pThis->pszRawMsg);
+
+ deltaSize = (int) lenMsg - pThis->iLenRawMsg; /* value < 0 in truncation case! */
+ 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! */
+ /* correct other information */
+ if(pThis->iLenRawMsg > pThis->offMSG)
+ pThis->iLenMSG += deltaSize;
+ else
+ pThis->iLenMSG = 0;
+}
+
+
+/* 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(smsg_t * const pMsg, char* pszRawMsg)
+{
+ MsgSetRawMsg(pMsg, pszRawMsg, strlen(pszRawMsg));
+}
+
+
+/* create textual representation of facility and severity.
+ * The variable pRes must point to a user-supplied buffer of
+ * at least 20 characters.
+ */
+static uchar *
+textpri(const smsg_t *const __restrict__ pMsg)
+{
+ int lenfac = len_syslog_fac_names[pMsg->iFacility];
+ int lensev = len_syslog_severity_names[pMsg->iSeverity];
+ int totlen = lenfac + 1 + lensev + 1;
+ char *pRes = malloc(totlen);
+ if(pRes != NULL) {
+ memcpy(pRes, syslog_fac_names[pMsg->iFacility], lenfac);
+ pRes[lenfac] = '.';
+ memcpy(pRes+lenfac+1, syslog_severity_names[pMsg->iSeverity], lensev+1 /* for \0! */);
+ }
+ return (uchar*)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, NOW_WDAY } eNOWType;
+#define tmpBUFSIZE 16 /* size of formatting buffer */
+static uchar *getNOW(eNOWType eNow, struct syslogTime *t, const int inUTC)
+{
+ uchar *pBuf;
+ struct syslogTime tt;
+
+ if((pBuf = (uchar*) malloc(tmpBUFSIZE)) == NULL) {
+ return NULL;
+ }
+
+ if(t == NULL) { /* can happen if called via script engine */
+ datetime.getCurrTime(&tt, NULL, inUTC);
+ t = &tt;
+ }
+
+ if(t->year == 0 || t->inUTC != inUTC) { /* not yet set! */
+ datetime.getCurrTime(t, NULL, inUTC);
+ }
+
+ 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->minute/30], 3);
+ break;
+ case NOW_QHOUR:
+ memcpy(pBuf, two_digits[t->minute/15], 3);
+ break;
+ case NOW_MINUTE:
+ memcpy(pBuf, two_digits[(int)t->minute], 3);
+ break;
+ case NOW_WDAY:
+ memcpy(pBuf, one_digit[(int)t->wday], 2);
+ break;
+ }
+
+ return(pBuf);
+}
+#undef tmpBUFSIZE /* clean up */
+
+
+/* helper function to obtain correct JSON root and mutex depending on
+ * property type (essentially based on the property id. If a non-json
+ * property id is given the function errors out.
+ * Note well: jroot points to a pointer to a (ptr to a) json object.
+ * This is necessary because the caller needs a pointer to where the
+ * json object pointer is stored, that in turn is necessary because
+ * while the address of the actual pointer stays stable, the actual
+ * content is volatile until the caller has locked the variable tree,
+ * which we DO NOT do to keep calling semantics simple.
+ */
+static rsRetVal ATTR_NONNULL()
+getJSONRootAndMutex(smsg_t *const pMsg, const propid_t id,
+ struct json_object ***const jroot, pthread_mutex_t **const mut)
+{
+ DEFiRet;
+ assert(jroot != NULL); /* asserts also help static analyzer! */
+ assert(mut != NULL);
+ assert(*mut == NULL); /* caller shall have initialized this one! */
+ assert(id == PROP_CEE || id == PROP_LOCAL_VAR || id == PROP_GLOBAL_VAR);
+
+ if(id == PROP_CEE) {
+ *mut = &pMsg->mut;
+ *jroot = &pMsg->json;
+ } else if(id == PROP_LOCAL_VAR) {
+ *mut = &pMsg->mut;
+ *jroot = &pMsg->localvars;
+ } else if(id == PROP_GLOBAL_VAR) {
+ *mut = &glblVars_lock;
+ *jroot = &global_var_root;
+ } else {
+ LogError(0, RS_RET_NON_JSON_PROP, "internal error: "
+ "getJSONRootAndMutex; invalid property id %d", id);
+ iRet = RS_RET_NON_JSON_PROP;
+ }
+
+ RETiRet;
+}
+
+/* basically same function, but does not use property id, but the the
+ * variable name type indicator (char after starting $, e.g. $!myvar --> CEE)
+ */
+static rsRetVal ATTR_NONNULL()
+getJSONRootAndMutexByVarChar(smsg_t *const pMsg, const char c,
+ struct json_object ***const jroot, pthread_mutex_t **const mut)
+{
+ DEFiRet;
+ propid_t id;
+ assert(c == '!' || c == '.' || c == '/');
+
+ switch(c) {
+ case '!':
+ id = PROP_CEE;
+ break;
+ case '.':
+ id = PROP_LOCAL_VAR;
+ break;
+ case '/':
+ id = PROP_GLOBAL_VAR;
+ break;
+ default:
+ LogError(0, RS_RET_NON_JSON_PROP, "internal error: "
+ "getJSONRootAndMutex; invalid indicator char %c(%2.2x)", c, c);
+ ABORT_FINALIZE(RS_RET_NON_JSON_PROP);
+ break;
+ }
+ iRet = getJSONRootAndMutex(pMsg, id, jroot, mut);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Get a JSON-Property as string value (used for various types of JSON-based vars) */
+rsRetVal
+getJSONPropVal(smsg_t * const pMsg, msgPropDescr_t *pProp, uchar **pRes, rs_size_t *buflen,
+ unsigned short *pbMustBeFreed)
+{
+ uchar *leaf;
+ struct json_object **jroot;
+ struct json_object *parent;
+ struct json_object *field;
+ pthread_mutex_t *mut = NULL;
+ DEFiRet;
+
+ *pRes = NULL;
+ CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut));
+ pthread_mutex_lock(mut);
+
+ if(*jroot == NULL) FINALIZE;
+
+ if(!strcmp((char*)pProp->name, "!")) {
+ field = *jroot;
+ } else {
+ leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen);
+ CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0));
+ if(jsonVarExtract(parent, (char*)leaf, &field) == FALSE)
+ field = NULL;
+ }
+ if(field != NULL) {
+ *pRes = (uchar*) strdup(json_object_get_string(field));
+ *buflen = (int) ustrlen(*pRes);
+ *pbMustBeFreed = 1;
+ }
+
+finalize_it:
+ if(mut != NULL)
+ pthread_mutex_unlock(mut);
+ if(*pRes == NULL) {
+ /* could not find any value, so set it to empty */
+ *pRes = (unsigned char*)"";
+ *pbMustBeFreed = 0;
+ }
+ RETiRet;
+}
+
+
+/* Get a JSON-based-variable as native json object, except
+ * when it is string type, in which case a string is returned.
+ * This is an optimization to not use JSON when not strictly
+ * necessary. This in turn is helpful, as calling json-c is
+ * *very* expensive due to our need for locking and deep
+ * copies.
+ * The caller needs to check pjson and pcstr: one of them
+ * is non-NULL and contains the return value. Note that
+ * the caller is responsible for freeing the string pointer
+ * it if is being returned.
+ */
+rsRetVal
+msgGetJSONPropJSONorString(smsg_t * const pMsg, msgPropDescr_t *pProp, struct json_object **pjson,
+ uchar **pcstr)
+{
+ struct json_object **jroot;
+ uchar *leaf;
+ struct json_object *parent;
+ pthread_mutex_t *mut = NULL;
+ DEFiRet;
+
+ *pjson = NULL, *pcstr = NULL;
+
+ CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut));
+ pthread_mutex_lock(mut);
+ if(!strcmp((char*)pProp->name, "!")) {
+ *pjson = *jroot;
+ FINALIZE;
+ }
+ if(*jroot == NULL) {
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+ leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen);
+ CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0));
+ if(jsonVarExtract(parent, (char*)leaf, pjson) == FALSE) {
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+ if(*pjson == NULL) {
+ /* we had a NULL json object and represent this as empty string */
+ *pcstr = (uchar*) strdup("");
+ } else {
+ if(json_object_get_type(*pjson) == json_type_string) {
+ *pcstr = (uchar*) strdup(json_object_get_string(*pjson));
+ *pjson = NULL;
+ }
+ }
+
+finalize_it:
+ /* we need a deep copy, as another thread may modify the object */
+ if(*pjson != NULL)
+ *pjson = jsonDeepCopy(*pjson);
+ if(mut != NULL)
+ pthread_mutex_unlock(mut);
+ RETiRet;
+}
+
+
+
+/* Get a JSON-based-variable as native json object */
+rsRetVal
+msgGetJSONPropJSON(smsg_t * const pMsg, msgPropDescr_t *pProp, struct json_object **pjson)
+{
+ struct json_object **jroot;
+ uchar *leaf;
+ struct json_object *parent;
+ pthread_mutex_t *mut = NULL;
+ DEFiRet;
+
+ *pjson = NULL;
+
+ CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut));
+ pthread_mutex_lock(mut);
+
+ if(!strcmp((char*)pProp->name, "!")) {
+ *pjson = *jroot;
+ FINALIZE;
+ }
+ leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen);
+ CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0));
+ if(jsonVarExtract(parent, (char*)leaf, pjson) == FALSE) {
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+finalize_it:
+ /* we need a deep copy, as another thread may modify the object */
+ if(*pjson != NULL)
+ *pjson = jsonDeepCopy(*pjson);
+ if(mut != NULL)
+ pthread_mutex_unlock(mut);
+ RETiRet;
+}
+
+
+/* Helper for jsonAddVal(), to be called onces we know there are actually
+ * json escapes inside the string. If so, this function takes over.
+ * Splitting the functions permits us to make some performance optimizations.
+ * For further details, see jsonAddVal().
+ */
+static rsRetVal ATTR_NONNULL(1, 4)
+jsonAddVal_escaped(uchar *const pSrc,
+ const unsigned buflen,
+ const unsigned len_none_escaped_head,
+ es_str_t **dst,
+ const int escapeAll)
+{
+ unsigned char c;
+ es_size_t i;
+ char numbuf[4];
+ unsigned ni;
+ unsigned char nc;
+ int j;
+ uchar wrkbuf[100000];
+ size_t dst_realloc_size;
+ size_t dst_size;
+ uchar *dst_base;
+ uchar *dst_w;
+ uchar *newbuf;
+ DEFiRet;
+
+ assert(len_none_escaped_head <= buflen);
+ /* first copy over unescaped head string */
+ if(len_none_escaped_head+10 > sizeof(wrkbuf)) {
+ dst_size = 2 * len_none_escaped_head;
+ CHKmalloc(dst_base = malloc(dst_size));
+ } else {
+ dst_size = sizeof(wrkbuf);
+ dst_base = wrkbuf;
+ }
+ dst_realloc_size = dst_size - 10; /* some buffer for escaping */
+ dst_w = dst_base;
+ memcpy(dst_w, pSrc, len_none_escaped_head);
+ dst_w += len_none_escaped_head;
+
+ /* now do the escaping */
+ for(i = len_none_escaped_head ; i < buflen ; ++i) {
+ const size_t dst_offset = dst_w - dst_base;
+ if(dst_offset >= dst_realloc_size) {
+ const size_t new_size = 2 * dst_size;
+ if(dst_base == wrkbuf) {
+ CHKmalloc(newbuf = malloc(new_size));
+ memcpy(newbuf, dst_base, dst_offset);
+ } else {
+ CHKmalloc(newbuf = realloc(dst_base, new_size));
+ }
+ dst_size = new_size;
+ dst_realloc_size = new_size - 10; /* some buffer for escaping */
+ dst_base = newbuf;
+ dst_w = dst_base + dst_offset;
+ }
+ c = pSrc[i];
+ if( (c >= 0x30 && c <= 0x5b)
+ || (c >= 0x23 && c <= 0x2e)
+ || (c >= 0x5d /* && c <= 0x10FFFF*/)
+ || c == 0x20 || c == 0x21) {
+ /* no need to escape */
+ *dst_w++ = c;
+ } else {
+ /* we must escape, try RFC4627-defined special sequences first */
+ switch(c) {
+ case '\0':
+ *dst_w++ = '\\';
+ *dst_w++ = 'u';
+ *dst_w++ = '0';
+ *dst_w++ = '0';
+ *dst_w++ = '0';
+ *dst_w++ = '0';
+ break;
+ case '\"':
+ *dst_w++ = '\\';
+ *dst_w++ = '"';
+ break;
+ case '/':
+ *dst_w++ = '\\';
+ *dst_w++ = '/';
+ break;
+ case '\\':
+ if (escapeAll == RSFALSE) {
+ ni = i + 1;
+ if (ni <= buflen) {
+ nc = pSrc[ni];
+
+ /* Attempt to not double encode */
+ if ( nc == '"' || nc == '/' || nc == '\\' || nc == 'b' || nc == 'f'
+ || nc == 'n' || nc == 'r' || nc == 't' || nc == 'u') {
+ *dst_w++ = c;
+ *dst_w++ = nc;
+ i = ni;
+ break;
+ }
+ }
+ }
+ *dst_w++ = '\\';
+ *dst_w++ = '\\';
+ break;
+ case '\010':
+ *dst_w++ = '\\';
+ *dst_w++ = 'b';
+ break;
+ case '\014':
+ *dst_w++ = '\\';
+ *dst_w++ = 'f';
+ break;
+ case '\n':
+ *dst_w++ = '\\';
+ *dst_w++ = 'n';
+ break;
+ case '\r':
+ *dst_w++ = '\\';
+ *dst_w++ = 'r';
+ break;
+ case '\t':
+ *dst_w++ = '\\';
+ *dst_w++ = 't';
+ break;
+ default:
+ /* TODO : proper Unicode encoding (see header comment) */
+ for(j = 0 ; j < 4 ; ++j) {
+ numbuf[3-j] = hexdigit[c % 16];
+ c = c / 16;
+ }
+ *dst_w++ = '\\';
+ *dst_w++ = 'u';
+ *dst_w++ = numbuf[0];
+ *dst_w++ = numbuf[1];
+ *dst_w++ = numbuf[2];
+ *dst_w++ = numbuf[3];
+ break;
+ }
+ }
+ }
+ if(*dst == NULL) {
+ *dst = es_newStrFromBuf((char *) dst_base, dst_w - dst_base);
+ } else {
+ es_addBuf(dst, (const char *) dst_base, dst_w - dst_base);
+ }
+finalize_it:
+ if(dst_base != wrkbuf) {
+ free(dst_base);
+ }
+ 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. if escapeAll is false, previously
+ * escaped strings are left as is
+ */
+static rsRetVal ATTR_NONNULL(1, 3)
+jsonAddVal(uchar *const pSrc, const unsigned buflen, es_str_t **dst, const int escapeAll)
+{
+ es_size_t i;
+ DEFiRet;
+
+ for(i = 0 ; i < buflen ; ++i) {
+ const uchar c = pSrc[i];
+ if(! ( (c >= 0x30 && c <= 0x5b)
+ || (c >= 0x23 && c <= 0x2e)
+ || (c >= 0x5d /* && c <= 0x10FFFF*/)
+ || c == 0x20 || c == 0x21)
+ ) {
+ iRet = jsonAddVal_escaped(pSrc, buflen, i, dst, escapeAll);
+ FINALIZE;
+ }
+ }
+ if(*dst != NULL) {
+ es_addBuf(dst, (const char *) pSrc, buflen);
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* encode a property in JSON escaped format. This is a helper
+ * to MsgGetProp. It needs to update all provided parameters.
+ * 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, int escapeAll)
+{
+ unsigned buflen;
+ uchar *pSrc;
+ es_str_t *dst = NULL;
+ DEFiRet;
+
+ pSrc = *ppRes;
+ buflen = (*pBufLen == -1) ? (int) ustrlen(pSrc) : *pBufLen;
+ CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll));
+
+ 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 ATTR_NONNULL()
+jsonField(const struct templateEntry *const pTpe,
+ uchar **const ppRes,
+ unsigned short *const pbMustBeFreed,
+ int *const pBufLen,
+ int escapeAll)
+{
+ unsigned buflen;
+ uchar *pSrc;
+ es_str_t *dst = NULL;
+ int is_numeric = 1;
+ DEFiRet;
+
+ pSrc = *ppRes;
+ buflen = (*pBufLen == -1) ? (int) ustrlen(pSrc) : *pBufLen;
+dbgprintf("jsonEncode: datatype: %u, onEmpty: %u val %*s\n", (unsigned) pTpe->data.field.options.dataType,
+(unsigned) pTpe->data.field.options.onEmpty, buflen, pSrc);
+ if(buflen == 0) {
+ if(pTpe->data.field.options.onEmpty == TPE_DATAEMPTY_SKIP) {
+ FINALIZE;
+ }
+ is_numeric = 0;
+ }
+ /* 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, "\":");
+ if(buflen == 0 && pTpe->data.field.options.onEmpty == TPE_DATAEMPTY_NULL) {
+ es_addBufConstcstr(&dst, "null");
+ } else {
+ if(pTpe->data.field.options.dataType == TPE_DATATYPE_AUTO) {
+ for(unsigned i = 0 ; i < buflen ; ++i) {
+ if(pSrc[i] < '0' || pSrc[i] > '9') {
+ is_numeric = 0;
+ break;
+ }
+ }
+ if(!is_numeric) {
+ es_addChar(&dst, '"');
+ }
+ CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll));
+ if(!is_numeric) {
+ es_addChar(&dst, '"');
+ }
+ } else if(pTpe->data.field.options.dataType == TPE_DATATYPE_STRING) {
+ es_addChar(&dst, '"');
+ CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll));
+ es_addChar(&dst, '"');
+ } else if(pTpe->data.field.options.dataType == TPE_DATATYPE_NUMBER) {
+ if(buflen == 0) {
+ es_addChar(&dst, '0');
+ } else {
+ CHKiRet(jsonAddVal(pSrc, buflen, &dst, escapeAll));
+ }
+ } else if(pTpe->data.field.options.dataType == TPE_DATATYPE_BOOL) {
+ if(buflen == 1 && *pSrc == '0') {
+ es_addBufConstcstr(&dst, "false");
+ } else {
+ es_addBufConstcstr(&dst, "true");
+ }
+ }
+ }
+
+ 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(smsg_t *__restrict__ const pMsg, struct templateEntry *__restrict__ const pTpe,
+ msgPropDescr_t *pProp, rs_size_t *__restrict__ const pPropLen,
+ unsigned short *__restrict__ const pbMustBeFreed, struct syslogTime * const 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;
+ int bDateInUTC;
+
+ 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(pProp->id) {
+ case PROP_MSG:
+ pRes = getMSG(pMsg);
+ bufLen = getMSGLen(pMsg);
+ break;
+ case PROP_TIMESTAMP:
+ if(pTpe != NULL) {
+ datefmt = pTpe->data.field.eDateFormat;
+ bDateInUTC = pTpe->data.field.options.bDateInUTC;
+ } else {
+ datefmt = tplFmtDefault;
+ bDateInUTC = 0;
+ }
+ if(bDateInUTC) {
+ pRes = (uchar*)getTimeUTC(&pMsg->tTIMESTAMP, datefmt, pbMustBeFreed);
+ } else {
+ pRes = (uchar*)getTimeReported(pMsg, datefmt);
+ }
+ break;
+ case PROP_HOSTNAME:
+ pRes = (uchar*)getHOSTNAME(pMsg);
+ bufLen = getHOSTNAMELen(pMsg);
+ break;
+ case PROP_SYSLOGTAG:
+ getTAG(pMsg, &pRes, &bufLen, LOCK_MUTEX);
+ break;
+ case PROP_RAWMSG:
+ getRawMsg(pMsg, &pRes, &bufLen);
+ break;
+ case PROP_RAWMSG_AFTER_PRI:
+ getRawMsgAfterPRI(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:
+ pRes = textpri(pMsg);
+ if(pRes == NULL)
+ RET_OUT_OF_MEMORY;
+ *pbMustBeFreed = 1;
+ 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;
+ bDateInUTC = pTpe->data.field.options.bDateInUTC;
+ } else {
+ datefmt = tplFmtDefault;
+ bDateInUTC = 0;
+ }
+ if(bDateInUTC) {
+ pRes = (uchar*)getTimeUTC(&pMsg->tRcvdAt, datefmt, pbMustBeFreed);
+ } else {
+ 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:
+ MsgGetStructuredData(pMsg, &pRes, &bufLen);
+ 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;
+ case PROP_JSONMESG:
+ pRes = (uchar*)msgGetJSONMESG(pMsg);
+ *pbMustBeFreed = 1;
+ 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, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 10;
+ }
+ break;
+ case PROP_SYS_YEAR:
+ if((pRes = getNOW(NOW_YEAR, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 4;
+ }
+ break;
+ case PROP_SYS_MONTH:
+ if((pRes = getNOW(NOW_MONTH, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_DAY:
+ if((pRes = getNOW(NOW_DAY, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_HOUR:
+ if((pRes = getNOW(NOW_HOUR, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_HHOUR:
+ if((pRes = getNOW(NOW_HHOUR, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_QHOUR:
+ if((pRes = getNOW(NOW_QHOUR, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_MINUTE:
+ if((pRes = getNOW(NOW_MINUTE, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_NOW_UTC:
+ if((pRes = getNOW(NOW_NOW, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 10;
+ }
+ break;
+ case PROP_SYS_YEAR_UTC:
+ if((pRes = getNOW(NOW_YEAR, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 4;
+ }
+ break;
+ case PROP_SYS_MONTH_UTC:
+ if((pRes = getNOW(NOW_MONTH, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_DAY_UTC:
+ if((pRes = getNOW(NOW_DAY, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_HOUR_UTC:
+ if((pRes = getNOW(NOW_HOUR, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_HHOUR_UTC:
+ if((pRes = getNOW(NOW_HHOUR, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_QHOUR_UTC:
+ if((pRes = getNOW(NOW_QHOUR, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_MINUTE_UTC:
+ if((pRes = getNOW(NOW_MINUTE, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 2;
+ }
+ break;
+ case PROP_SYS_WDAY:
+ if((pRes = getNOW(NOW_WDAY, ttNow, TIME_IN_LOCALTIME)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 1;
+ }
+ break;
+ case PROP_SYS_WDAY_UTC:
+ if((pRes = getNOW(NOW_WDAY, ttNow, TIME_IN_UTC)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ *pbMustBeFreed = 1;
+ bufLen = 1;
+ }
+ break;
+ case PROP_SYS_NOW_UXTIMESTAMP:
+ if((pRes = malloc(16)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ } else {
+ snprintf((char*) pRes, 16-1, "%lld", (long long) getTime(NULL));
+ pRes[16-1] = '\0';
+ *pbMustBeFreed = 1;
+ bufLen = -1;
+ }
+ break;
+ case PROP_SYS_MYHOSTNAME:
+ pRes = glbl.GetLocalHostName();
+ break;
+ case PROP_CEE_ALL_JSON:
+ case PROP_CEE_ALL_JSON_PLAIN:
+ if(pMsg->json == NULL) {
+ pRes = (uchar*) "{}";
+ bufLen = 2;
+ *pbMustBeFreed = 0;
+ } else {
+ const char *jstr;
+ MsgLock(pMsg);
+ int jflag = 0;
+ if(pProp->id == PROP_CEE_ALL_JSON) {
+ jflag = JSON_C_TO_STRING_SPACED;
+ } else if(pProp->id == PROP_CEE_ALL_JSON_PLAIN) {
+ jflag = JSON_C_TO_STRING_PLAIN;
+ }
+ jstr = json_object_to_json_string_ext(pMsg->json, jflag);
+ MsgUnlock(pMsg);
+ if(jstr == NULL) {
+ RET_OUT_OF_MEMORY;
+ }
+ pRes = (uchar*)strdup(jstr);
+ if(pRes == NULL) {
+ RET_OUT_OF_MEMORY;
+ }
+ *pbMustBeFreed = 1;
+ }
+ break;
+ case PROP_CEE:
+ case PROP_LOCAL_VAR:
+ case PROP_GLOBAL_VAR:
+ getJSONPropVal(pMsg, pProp, &pRes, &bufLen, pbMustBeFreed);
+ break;
+ case PROP_SYS_BOM:
+ 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;
+
+# elif defined(__FreeBSD__)
+
+ {
+ struct timespec tp;
+
+ if((pRes = (uchar*) malloc(32)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ }
+
+ if(clock_gettime(CLOCK_UPTIME, &tp) == -1) {
+ free(pRes);
+ *pPropLen = sizeof("**SYSCALL FAILED**") - 1;
+ return(UCHAR_CONSTANT("**SYSCALL FAILED**"));
+ }
+
+ *pbMustBeFreed = 1;
+
+ snprintf((char*) pRes, 32, "%ld", tp.tv_sec);
+ }
+
+# else
+
+ {
+ struct sysinfo s_info;
+
+ if((pRes = (uchar*) malloc(32)) == NULL) {
+ RET_OUT_OF_MEMORY;
+ }
+
+ if(sysinfo(&s_info) < 0) {
+ free(pRes);
+ *pPropLen = sizeof("**SYSCALL FAILED**") - 1;
+ return(UCHAR_CONSTANT("**SYSCALL FAILED**"));
+ }
+
+ *pbMustBeFreed = 1;
+
+ snprintf((char*) pRes, 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", pProp->id);
+ *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) ? (int) 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);
+ 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;
+ } 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);
+
+ 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;
+ } else if(iTo < 0) {
+ /* note: we ADD negative value, 0-based (-1)! */
+ iTo = bufLen - 1 + iTo;
+ if(iTo < 0) {
+ iTo = 0;
+ }
+ }
+ }
+ if(iFrom >= bufLen) {
+ DBGPRINTF("msgGetProp: iFrom %d >= buflen %d, returning empty string\n",
+ iFrom, bufLen);
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ pRes = (uchar*) "";
+ *pbMustBeFreed = 0;
+ bufLen = 0;
+ } else if(iFrom == 0 && iTo >= bufLen && pTpe->data.field.options.bFixedWidth == 0) {
+ /* 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! */
+ if (pTpe->data.field.options.bFixedWidth == 0)
+ iTo = bufLen - 1;
+
+ iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */
+ pBufStart = pBuf = malloc(iLen + 1);
+ 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(iLen) {
+ if (*pSb) {
+ *pBuf++ = *pSb;
+ ++pSb;
+ } else {
+ *pBuf++ = ' ';
+ }
+ --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);
+ 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 *pSrc;
+ 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);
+ if(pB == NULL) {
+ if(*pbMustBeFreed == 1)
+ free(pRes);
+ RET_OUT_OF_MEMORY;
+ }
+ for(pSrc = pRes; *pSrc; pSrc++) {
+ if(iscntrl((int) *pSrc)) {
+ snprintf((char*)szCCEsc, sizeof(szCCEsc), "#%3.3d", *pSrc);
+ for(i = 0 ; i < 4 ; ++i)
+ *pB++ = szCCEsc[i];
+ } else {
+ *pB++ = *pSrc;
+ }
+ }
+ *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);
+ if(pB == NULL) {
+ RET_OUT_OF_MEMORY;
+ }
+ memcpy(pB, pRes, iLn - 1);
+ pRes = pB;
+ *pbMustBeFreed = 1;
+ }
+ *(pRes + iLn - 1) = '\0'; /* drop LF ;) */
+ --bufLen;
+ }
+ }
+
+ /* Now everything is squased as much as possible and more or less ready to
+ * go. This is the perfect place to compress any remaining spaces, if so
+ * instructed by the user/config.
+ */
+ if(pTpe->data.field.options.bCompressSP) {
+ int needCompress = 0;
+ int hadSP = 0;
+ uchar *pB;
+ if(*pbMustBeFreed == 0) {
+ for(pB = pRes ; *pB && needCompress == 0 ; ++pB) {
+ if(*pB == ' ') {
+ if(hadSP) {
+ uchar *const tmp = ustrdup(pRes);
+ if(tmp == NULL)
+ /* better not compress than
+ * loose message. */
+ break;
+ *pbMustBeFreed = 1;
+ pRes = tmp;
+ needCompress = 1;
+ } else {
+ hadSP = 1;
+ }
+ }
+ }
+ } else {
+ /* If we can modify the buffer in any case, we
+ * do NOT check if we actually need to compress,
+ * but "just do it" - that's the quickest way
+ * to get it done.
+ */
+ needCompress = 1;
+ }
+ if(needCompress) {
+ hadSP = 0;
+ uchar *pDst = pRes;
+ int needCopy = 0;
+ for(pB = pRes ; *pB ; ++pB) {
+ if(*pB == ' ') {
+ if(hadSP) {
+ needCopy = 1;
+ } else {
+ hadSP = 1;
+ if(needCopy)
+ *pDst = *pB;
+ ++pDst;
+ }
+ } else {
+ hadSP = 0;
+ if(needCopy)
+ *pDst = *pB;
+ ++pDst;
+ }
+ }
+ *pDst = '\0';
+ bufLen = pDst - pRes;
+ }
+ }
+
+ /* 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);
+ 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, RSTRUE);
+ } else if(pTpe->data.field.options.bJSONf) {
+ jsonField(pTpe, &pRes, pbMustBeFreed, &bufLen, RSTRUE);
+ } else if(pTpe->data.field.options.bJSONr) {
+ jsonEncode(&pRes, pbMustBeFreed, &bufLen, RSFALSE);
+ } else if(pTpe->data.field.options.bJSONfr) {
+ jsonField(pTpe, &pRes, pbMustBeFreed, &bufLen, RSFALSE);
+ }
+
+ *pPropLen = (bufLen == -1) ? (int) ustrlen(pRes) : bufLen;
+
+ return(pRes);
+}
+
+/* Set a single property based on the JSON object provided. The
+ * property name is extracted from the JSON object.
+ */
+static rsRetVal
+msgSetPropViaJSON(smsg_t *__restrict__ const pMsg, const char *name, struct json_object *json, int sharedReference)
+{
+ const char *psz;
+ int val;
+ prop_t *propFromHost = NULL;
+ prop_t *propRcvFromIP = NULL;
+ int bNeedFree = 1;
+ DEFiRet;
+
+ /* note: json_object_get_string() manages the memory of the returned
+ * string. So we MUST NOT free it!
+ */
+ dbgprintf("DDDD: msgSetPropViaJSON key: '%s'\n", name);
+ if(!strcmp(name, "rawmsg")) {
+ psz = json_object_get_string(json);
+ MsgSetRawMsg(pMsg, psz, strlen(psz));
+ } else if(!strcmp(name, "msg")) {
+ psz = json_object_get_string(json);
+ MsgReplaceMSG(pMsg, (const uchar*)psz, strlen(psz));
+ } else if(!strcmp(name, "syslogtag")) {
+ psz = json_object_get_string(json);
+ MsgSetTAG(pMsg, (const uchar*)psz, strlen(psz));
+ } else if(!strcmp(name, "pri")) {
+ val = json_object_get_int(json);
+ msgSetPRI(pMsg, val);
+ } else if(!strcmp(name, "syslogfacility")) {
+ val = json_object_get_int(json);
+ if(val >= 0 && val <= 24)
+ pMsg->iFacility = val;
+ else
+ DBGPRINTF("mmexternal: invalid fac %d requested -- ignored\n", val);
+ } else if(!strcmp(name, "syslogseverity")) {
+ val = json_object_get_int(json);
+ if(val >= 0 && val <= 7)
+ pMsg->iSeverity = val;
+ else
+ DBGPRINTF("mmexternal: invalid fac %d requested -- ignored\n", val);
+ } else if(!strcmp(name, "procid")) {
+ psz = json_object_get_string(json);
+ MsgSetPROCID(pMsg, psz);
+ } else if(!strcmp(name, "msgid")) {
+ psz = json_object_get_string(json);
+ MsgSetMSGID(pMsg, psz);
+ } else if(!strcmp(name, "structured-data")) {
+ psz = json_object_get_string(json);
+ MsgSetStructuredData(pMsg, psz);
+ } else if(!strcmp(name, "hostname") || !strcmp(name, "source")) {
+ psz = json_object_get_string(json);
+ MsgSetHOSTNAME(pMsg, (const uchar*)psz, strlen(psz));
+ } else if(!strcmp(name, "fromhost")) {
+ psz = json_object_get_string(json);
+ MsgSetRcvFromStr(pMsg, (const uchar*) psz, strlen(psz), &propFromHost);
+ prop.Destruct(&propFromHost);
+ } else if(!strcmp(name, "fromhost-ip")) {
+ psz = json_object_get_string(json);
+ MsgSetRcvFromIPStr(pMsg, (const uchar*)psz, strlen(psz), &propRcvFromIP);
+ prop.Destruct(&propRcvFromIP);
+ } else if(!strcmp(name, "$!")) {
+ /* msgAddJSON expects that it can keep the object without incremeting
+ * the json reference count. So we MUST NOT free (_put) the object in
+ * this case. -- rgerhards, 2018-09-14
+ */
+ bNeedFree = 0;
+ msgAddJSON(pMsg, (uchar*)"!", json, 0, sharedReference);
+ } else {
+ /* we ignore unknown properties */
+ DBGPRINTF("msgSetPropViaJSON: unkonwn property ignored: %s\n",
+ name);
+ }
+
+ if(bNeedFree) {
+ json_object_put(json);
+ }
+
+ RETiRet;
+}
+
+
+/* set message properties based on JSON string. This function does it all,
+ * including parsing the JSON string. If an error is detected, the operation
+ * is aborted at the time of error. Any modifications made before the
+ * error ocurs are still PERSISTED.
+ * This function is meant to support the external message modifiction module
+ * interface. As such, replacing properties is expressively permited. Note that
+ * properties which were derived from the message during parsing are NOT
+ * updated if the underlying (raw)msg property is changed.
+ */
+rsRetVal
+MsgSetPropsViaJSON(smsg_t *__restrict__ const pMsg, const uchar *__restrict__ const jsonstr)
+{
+ struct json_tokener *tokener = NULL;
+ struct json_object *json;
+ const char *errMsg;
+ DEFiRet;
+
+ DBGPRINTF("DDDDDD: JSON string for message mod: '%s'\n", jsonstr);
+ if(!strcmp((char*)jsonstr, "{}")) /* shortcut for a common case */
+ FINALIZE;
+
+ tokener = json_tokener_new();
+
+ json = json_tokener_parse_ex(tokener, (char*)jsonstr, ustrlen(jsonstr));
+ if(Debug) {
+ errMsg = NULL;
+ if(json == NULL) {
+ enum json_tokener_error err;
+
+ err = tokener->err;
+ if(err != json_tokener_continue)
+ errMsg = json_tokener_error_desc(err);
+ else
+ errMsg = "Unterminated input";
+ } else if(!json_object_is_type(json, json_type_object))
+ errMsg = "JSON value is not an object";
+ if(errMsg != NULL) {
+ DBGPRINTF("MsgSetPropsViaJSON: Error parsing JSON '%s': %s\n",
+ jsonstr, errMsg);
+ }
+ }
+ if(json == NULL || !json_object_is_type(json, json_type_object)) {
+ ABORT_FINALIZE(RS_RET_JSON_UNUSABLE);
+ }
+ MsgSetPropsViaJSON_Object(pMsg, json);
+
+finalize_it:
+ if(tokener != NULL)
+ json_tokener_free(tokener);
+ RETiRet;
+}
+
+
+/* Used by MsgSetPropsViaJSON to set properties.
+ * The same as MsgSetPropsViaJSON only that a json object is given and not a string
+ */
+rsRetVal
+MsgSetPropsViaJSON_Object(smsg_t *__restrict__ const pMsg, struct json_object *json)
+{
+ DEFiRet;
+ if(json == NULL || !json_object_is_type(json, json_type_object)) {
+ DBGPRINTF("MsgSetPropsViaJSON_Object: json NULL or not object type\n");
+ ABORT_FINALIZE(RS_RET_JSON_UNUSABLE);
+ }
+ struct json_object_iterator it = json_object_iter_begin(json);
+ struct json_object_iterator itEnd = json_object_iter_end(json);
+ while (!json_object_iter_equal(&it, &itEnd)) {
+ struct json_object *child = json_object_iter_peek_value(&it);
+ json_object_get(child);
+ msgSetPropViaJSON(pMsg, json_object_iter_peek_name(&it),
+ child, 0);
+ json_object_iter_next(&it);
+ }
+ json_object_put(json);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* get the severity - this is an entry point that
+ * satisfies the base object class getSeverity semantics.
+ * rgerhards, 2008-01-14
+ */
+rsRetVal
+MsgGetSeverity(smsg_t * const pMsg, int *piSeverity)
+{
+ *piSeverity = pMsg->iSeverity;
+ return RS_RET_OK;
+}
+
+
+static uchar *
+jsonPathGetLeaf(uchar *name, int lenName)
+{
+ int i;
+ for(i = lenName ; i >= 0 ; --i)
+ if(i == 0) {
+ if(name[0] == '!' || name[0] == '.' || name[0] == '/')
+ break;
+ } else {
+ if(name[i] == '!')
+ break;
+ }
+ if(name[i] == '!' || name[i] == '.' || name[i] == '/')
+ ++i;
+ return name + i;
+}
+
+static json_bool jsonVarExtract(struct json_object* root, const char *key, struct json_object **value) {
+ char namebuf[MAX_VARIABLE_NAME_LEN];
+ int key_len = strlen(key);
+ char *array_idx_start = strstr(key, "[");
+ char *array_idx_end = NULL;
+ char *array_idx_num_end_discovered = NULL;
+ struct json_object *arr = NULL;
+ if (array_idx_start != NULL) {
+ array_idx_end = strstr(array_idx_start, "]");
+ }
+ if (array_idx_end != NULL && (array_idx_end - key + 1) == key_len) {
+ errno = 0;
+ int idx = (int) strtol(array_idx_start + 1, &array_idx_num_end_discovered, 10);
+ if (errno == 0 && array_idx_num_end_discovered == array_idx_end) {
+ memcpy(namebuf, key, array_idx_start - key);
+ namebuf[array_idx_start - key] = '\0';
+ json_bool found_obj = json_object_object_get_ex(root, namebuf, &arr);
+ if (found_obj && json_object_is_type(arr, json_type_array)) {
+ int len = json_object_array_length(arr);
+ if (len > idx) {
+ *value = json_object_array_get_idx(arr, idx);
+ if (*value != NULL) return TRUE;
+ }
+ return FALSE;
+ }
+ }
+ }
+ return json_object_object_get_ex(root, key, value);
+}
+
+
+static rsRetVal
+jsonPathFindNext(struct json_object *root, uchar *namestart, uchar **name, uchar *leaf,
+ struct json_object **found, int bCreate)
+{
+ uchar namebuf[MAX_VARIABLE_NAME_LEN];
+ struct json_object *json;
+ size_t i;
+ uchar *p = *name;
+ DEFiRet;
+
+ if(*p == '!' || (*name == namestart && (*p == '.' || *p == '/')))
+ ++p;
+ for(i = 0 ; *p && !(p == namestart && (*p == '.' || *p == '/')) && *p != '!'
+ && p != leaf && i < sizeof(namebuf)-1 ; ++i, ++p)
+ namebuf[i] = *p;
+ if(i > 0) {
+ namebuf[i] = '\0';
+ if(jsonVarExtract(root, (char*)namebuf, &json) == FALSE) {
+ json = NULL;
+ }
+ } else
+ json = root;
+ if(json == NULL) {
+ if(!bCreate) {
+ ABORT_FINALIZE(RS_RET_JNAME_INVALID);
+ } else {
+ if (json_object_get_type(root) != json_type_object) {
+ DBGPRINTF("jsonPathFindNext with bCreate: not a container in json path, "
+ "name is '%s'\n", namestart);
+ ABORT_FINALIZE(RS_RET_INVLD_SETOP);
+ }
+ json = json_object_new_object();
+ json_object_object_add(root, (char*)namebuf, json);
+ }
+ }
+
+ *name = p;
+ *found = json;
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+jsonPathFindParent(struct json_object *jroot, uchar *name, uchar *leaf, struct json_object **parent,
+ const int bCreate)
+{
+ uchar *namestart;
+ DEFiRet;
+ namestart = name;
+ *parent = jroot;
+ while(name < leaf-1) {
+ CHKiRet(jsonPathFindNext(*parent, namestart, &name, leaf, parent, bCreate));
+ }
+ if(*parent == NULL)
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+jsonMerge(struct json_object *existing, struct json_object *json)
+{
+ /* TODO: check & handle duplicate names */
+ DEFiRet;
+
+ struct json_object_iterator it = json_object_iter_begin(json);
+ struct json_object_iterator itEnd = json_object_iter_end(json);
+ while (!json_object_iter_equal(&it, &itEnd)) {
+ json_object_object_add(existing, json_object_iter_peek_name(&it),
+ json_object_get(json_object_iter_peek_value(&it)));
+ json_object_iter_next(&it);
+ }
+ /* 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(smsg_t *const pMsg, msgPropDescr_t *pProp, struct json_object **jsonres)
+{
+ uchar *leaf;
+ struct json_object *parent;
+ struct json_object *field;
+ struct json_object **jroot = NULL;
+ pthread_mutex_t *mut = NULL;
+ DEFiRet;
+
+ CHKiRet(getJSONRootAndMutex(pMsg, pProp->id, &jroot, &mut));
+ pthread_mutex_lock(mut);
+
+ if(*jroot == NULL) {
+ field = NULL;
+ goto finalize_it;
+ }
+
+ if(!strcmp((char*)pProp->name, "!")) {
+ field = *jroot;
+ } else if(!strcmp((char*)pProp->name, ".")) {
+ field = *jroot;
+ } else {
+ leaf = jsonPathGetLeaf(pProp->name, pProp->nameLen);
+ CHKiRet(jsonPathFindParent(*jroot, pProp->name, leaf, &parent, 0));
+ if(jsonVarExtract(parent, (char*)leaf, &field) == FALSE)
+ field = NULL;
+ }
+ *jsonres = field;
+
+finalize_it:
+ if(mut != NULL)
+ pthread_mutex_unlock(mut);
+ RETiRet;
+}
+
+/* check if JSON variable exists (works on terminal var and container) */
+rsRetVal ATTR_NONNULL()
+msgCheckVarExists(smsg_t *const pMsg, msgPropDescr_t *pProp)
+{
+ struct json_object *jsonres = NULL;
+ DEFiRet;
+
+ CHKiRet(jsonFind(pMsg, pProp, &jsonres));
+ if(jsonres == NULL) {
+ iRet = RS_RET_NOT_FOUND;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+rsRetVal
+msgAddJSON(smsg_t * const pM, uchar *name, struct json_object *json, int force_reset, int sharedReference)
+{
+ /* TODO: error checks! This is a quick&dirty PoC! */
+ struct json_object **jroot;
+ struct json_object *parent, *leafnode;
+ struct json_object *given = NULL;
+ uchar *leaf;
+ pthread_mutex_t *mut = NULL;
+ DEFiRet;
+
+ CHKiRet(getJSONRootAndMutexByVarChar(pM, name[0], &jroot, &mut));
+ pthread_mutex_lock(mut);
+
+ if(name[0] == '/') { /* globl var special handling */
+ if (sharedReference) {
+ given = json;
+ json = jsonDeepCopy(json);
+ json_object_put(given);
+ }
+ }
+
+ if(name[1] == '\0') { /* full tree? */
+ if(*jroot == NULL)
+ *jroot = json;
+ else
+ CHKiRet(jsonMerge(*jroot, json));
+ } else {
+ if(*jroot == NULL) {
+ /* now we need a root obj */
+ *jroot = json_object_new_object();
+ }
+ leaf = jsonPathGetLeaf(name, ustrlen(name));
+ iRet = jsonPathFindParent(*jroot, name, leaf, &parent, 1);
+ if (unlikely(iRet != RS_RET_OK)) {
+ json_object_put(json);
+ FINALIZE;
+ }
+ if (json_object_get_type(parent) != json_type_object) {
+ DBGPRINTF("msgAddJSON: not a container in json path,"
+ "name is '%s'\n", name);
+ json_object_put(json);
+ ABORT_FINALIZE(RS_RET_INVLD_SETOP);
+ }
+ if(jsonVarExtract(parent, (char*)leaf, &leafnode) == FALSE)
+ leafnode = NULL;
+ /* 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
+ */
+ if (force_reset || (leafnode == NULL)) {
+ json_object_object_add(parent, (char*)leaf, json);
+ } else {
+ if(json_object_get_type(json) == json_type_object) {
+ CHKiRet(jsonMerge(*jroot, json));
+ } else {
+ /* 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", name);
+ json_object_put(json);
+ ABORT_FINALIZE(RS_RET_INVLD_SETOP);
+ }
+ json_object_object_add(parent, (char*)leaf, json);
+ }
+ }
+ }
+
+finalize_it:
+ if(mut != NULL)
+ pthread_mutex_unlock(mut);
+ RETiRet;
+}
+
+
+rsRetVal
+msgDelJSON(smsg_t * const pM, uchar *name)
+{
+ struct json_object **jroot;
+ struct json_object *parent, *leafnode;
+ uchar *leaf;
+ pthread_mutex_t *mut = NULL;
+ DEFiRet;
+
+ CHKiRet(getJSONRootAndMutexByVarChar(pM, name[0], &jroot, &mut));
+ pthread_mutex_lock(mut);
+
+ if(*jroot == NULL) {
+ DBGPRINTF("msgDelJSONVar; jroot empty in unset for property %s\n",
+ name);
+ FINALIZE;
+ }
+
+ if(name[1] == '\0') {
+ /* full tree! 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(*jroot);
+ *jroot = NULL;
+ } else {
+ leaf = jsonPathGetLeaf(name, ustrlen(name));
+ CHKiRet(jsonPathFindParent(*jroot, name, leaf, &parent, 0));
+ if(jsonVarExtract(parent, (char*)leaf, &leafnode) == FALSE)
+ leafnode = NULL;
+ 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:
+ if(mut != NULL)
+ pthread_mutex_unlock(mut);
+ RETiRet;
+}
+
+/* add Metadata to the message. This is stored in a special JSON
+ * container. Note that only string types are currently supported,
+ * what should pose absolutely no problem with the string-ish nature
+ * of rsyslog metadata.
+ * added 2015-01-09 rgerhards
+ */
+rsRetVal
+msgAddMetadata(smsg_t *const __restrict__ pMsg,
+ uchar *const __restrict__ metaname,
+ uchar *const __restrict__ metaval)
+{
+ DEFiRet;
+ struct json_object *const json = json_object_new_object();
+ CHKmalloc(json);
+ struct json_object *const jval = json_object_new_string((char*)metaval);
+ if(jval == NULL) {
+ json_object_put(json);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ json_object_object_add(json, (const char *const)metaname, jval);
+ iRet = msgAddJSON(pMsg, (uchar*)"!metadata", json, 0, 0);
+finalize_it:
+ RETiRet;
+}
+
+rsRetVal
+msgAddMultiMetadata(smsg_t *const __restrict__ pMsg,
+ const uchar ** __restrict__ metaname,
+ const uchar ** __restrict__ metaval,
+ const int count)
+{
+ DEFiRet;
+ int i = 0 ;
+ struct json_object *const json = json_object_new_object();
+ CHKmalloc(json);
+ for ( i = 0 ; i < count ; i++ ) {
+ struct json_object *const jval = json_object_new_string((char*)metaval[i]);
+ if(jval == NULL) {
+ json_object_put(json);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ json_object_object_add(json, (const char *const)metaname[i], jval);
+ }
+ iRet = msgAddJSON(pMsg, (uchar*)"!metadata", json, 0, 0);
+finalize_it:
+ RETiRet;
+}
+
+struct json_object *
+jsonDeepCopy(struct json_object *src)
+{
+ struct json_object *dst = NULL, *json;
+ 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_int64(json_object_get_int64(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();
+ struct json_object_iterator it = json_object_iter_begin(src);
+ struct json_object_iterator itEnd = json_object_iter_end(src);
+ while (!json_object_iter_equal(&it, &itEnd)) {
+ json = jsonDeepCopy(json_object_iter_peek_value(&it));
+ json_object_object_add(dst, json_object_iter_peek_name(&it), json);
+ json_object_iter_next(&it);
+ }
+ 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;
+ case json_type_null:
+ default:DBGPRINTF("jsonDeepCopy(): error unknown type %d\n",
+ json_object_get_type(src));
+ dst = NULL;
+ break;
+ }
+done: return dst;
+}
+
+
+rsRetVal
+msgSetJSONFromVar(smsg_t * const pMsg, uchar *varname, struct svar *v, int force_reset)
+{
+ 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_int64(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, json, force_reset, 0);
+finalize_it:
+ RETiRet;
+}
+
+rsRetVal
+MsgAddToStructuredData(smsg_t * const pMsg, uchar *toadd, rs_size_t len)
+{
+ uchar *newptr;
+ rs_size_t newlen;
+ int empty;
+ DEFiRet;
+ empty = pMsg->pszStrucData == NULL || pMsg->pszStrucData[0] == '-';
+ newlen = (empty) ? len : pMsg->lenStrucData + len;
+ CHKmalloc(newptr = (uchar*) realloc(pMsg->pszStrucData, newlen+1));
+ if(empty) {
+ memcpy(newptr, toadd, len);
+ } else {
+ memcpy(newptr+pMsg->lenStrucData, toadd, len);
+ }
+ pMsg->pszStrucData = newptr;
+ pMsg->pszStrucData[newlen] = '\0';
+ pMsg->lenStrucData = newlen;
+finalize_it:
+ RETiRet;
+}
+
+
+/* Fill a message propert description. Space must already be alloced
+ * by the caller. This is for efficiency, as we expect this to happen
+ * as part of a larger structure alloc.
+ * Note that CEE/LOCAL_VAR properties can come in either as
+ * "$!xx"/"$.xx" or "!xx"/".xx" - we will unify them here.
+ */
+rsRetVal
+msgPropDescrFill(msgPropDescr_t *pProp, uchar *name, int nameLen)
+{
+ propid_t id;
+ int offs;
+ DEFiRet;
+ if(propNameToID(name, &id) != RS_RET_OK) {
+ parser_errmsg("invalid property '%s'", name);
+ /* now try to find some common error causes */
+ if(!strcasecmp((char*)name, "myhostname"))
+ parser_errmsg("did you mean '$myhostname' instead of '%s'? "
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "bom"))
+ parser_errmsg("did you mean '$bom' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "now"))
+ parser_errmsg("did you mean '$now' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "year"))
+ parser_errmsg("did you mean '$year' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "month"))
+ parser_errmsg("did you mean '$month' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "day"))
+ parser_errmsg("did you mean '$day' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "hour"))
+ parser_errmsg("did you mean '$hour' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "hhour"))
+ parser_errmsg("did you mean '$hhour' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "qhour"))
+ parser_errmsg("did you mean '$qhour' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "minute"))
+ parser_errmsg("did you mean '$minute' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "now-utc"))
+ parser_errmsg("did you mean '$now-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "year-utc"))
+ parser_errmsg("did you mean '$year-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "month-utc"))
+ parser_errmsg("did you mean '$month-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "day-utc"))
+ parser_errmsg("did you mean '$day-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "hour-utc"))
+ parser_errmsg("did you mean '$hour-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "hhour-utc"))
+ parser_errmsg("did you mean '$hhour-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "qhour-utc"))
+ parser_errmsg("did you mean '$qhour-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ else if(!strcasecmp((char*)name, "minute-utc"))
+ parser_errmsg("did you mean '$minute-utc' instead of '%s'?"
+ "See also: https://www.rsyslog.com/rsyslog-info-1/", name);
+ ABORT_FINALIZE(RS_RET_INVLD_PROP);
+ }
+ if(id == PROP_CEE || id == PROP_LOCAL_VAR || id == PROP_GLOBAL_VAR) {
+ /* in these cases, we need the field name for later processing */
+ /* normalize name: remove $ if present */
+ offs = (name[0] == '$') ? 1 : 0;
+ pProp->name = ustrdup(name + offs);
+ pProp->nameLen = nameLen - offs;
+ /* we patch the root name, so that support functions do not need to
+ * check for different root chars. */
+ pProp->name[0] = '!';
+ }
+ pProp->id = id;
+finalize_it:
+ RETiRet;
+}
+
+void
+msgPropDescrDestruct(msgPropDescr_t *pProp)
+{
+ if(pProp != NULL) {
+ if(pProp->id == PROP_CEE ||
+ pProp->id == PROP_LOCAL_VAR ||
+ pProp->id == PROP_GLOBAL_VAR)
+ free(pProp->name);
+ }
+}
+
+
+/* dummy */
+static rsRetVal msgQueryInterface(interface_t __attribute__((unused)) *i) { 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)
+ pthread_mutex_init(&glblVars_lock, NULL);
+
+ /* 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 */
+# ifdef HAVE_MALLOC_TRIM
+ INIT_ATOMIC_HELPER_MUT(mutTrimCtr);
+# endif
+ENDObjClassInit(msg)
diff --git a/runtime/msg.h b/runtime/msg.h
new file mode 100644
index 0000000..f665144
--- /dev/null
+++ b/runtime/msg.h
@@ -0,0 +1,281 @@
+/* msg.h
+ * Header file for all msg-related functions.
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007-2018 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 <stdint.h>
+#include <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 bParseSuccess; /* set to reflect state of last executed higher level parser */
+ unsigned short iSeverity;/* the severity */
+ unsigned short iFacility;/* Facility code */
+ int offAfterPRI; /* offset, at which raw message WITHOUT PRI part starts in pszRawMsg */
+ int 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 characters) */
+ char *pszRcvdAt3339; /* time as RFC3164 formatted string (32 characters at most) */
+ char *pszRcvdAt_MySQL; /* rcvdAt as MySQL formatted string (always 14 characters) */
+ char *pszRcvdAt_PgSQL; /* rcvdAt as PgSQL formatted string (always 21 characters) */
+ char *pszTIMESTAMP3164; /* TIMESTAMP as RFC3164 formatted string (always 15 characters) */
+ char *pszTIMESTAMP3339; /* TIMESTAMP as RFC3339 formatted string (32 characters at most) */
+ char *pszTIMESTAMP_MySQL;/* TIMESTAMP as MySQL formatted string (always 14 characters) */
+ char *pszTIMESTAMP_PgSQL;/* TIMESTAMP as PgSQL formatted string (always 21 characters) */
+ uchar *pszStrucData; /* STRUCTURED-DATA */
+ uint16_t lenStrucData; /* (cached) length of 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;
+ struct json_object *localvars;
+ /* 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];
+ char dfltTZ[8]; /* 7 chars max, less overhead than ptr! */
+ 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 */
+#define PRESERVE_CASE 0x200
+/* preserve case in fromhost */
+
+/* (syslog) protocol types */
+#define MSG_LEGACY_PROTOCOL 0
+#define MSG_RFC5424_PROTOCOL 1
+
+#define MAX_VARIABLE_NAME_LEN 1024
+
+/* function prototypes
+ */
+PROTOTYPEObjClassInit(msg);
+rsRetVal msgConstruct(smsg_t **ppThis);
+rsRetVal msgConstructWithTime(smsg_t **ppThis, const struct syslogTime *stTime, const time_t ttGenTime);
+rsRetVal msgConstructForDeserializer(smsg_t **ppThis);
+rsRetVal msgConstructFinalizer(smsg_t *pThis);
+rsRetVal msgDestruct(smsg_t **ppM);
+smsg_t * MsgDup(smsg_t * pOld);
+smsg_t *MsgAddRef(smsg_t *pM);
+void setProtocolVersion(smsg_t *pM, int iNewVersion);
+void MsgSetInputName(smsg_t *pMsg, prop_t*);
+void MsgSetDfltTZ(smsg_t *pThis, char *tz);
+rsRetVal MsgSetAPPNAME(smsg_t *pMsg, const char* pszAPPNAME);
+rsRetVal MsgSetPROCID(smsg_t *pMsg, const char* pszPROCID);
+rsRetVal MsgSetMSGID(smsg_t *pMsg, const char* pszMSGID);
+void MsgSetParseSuccess(smsg_t *pMsg, int bSuccess);
+void MsgSetTAG(smsg_t *pMsg, const uchar* pszBuf, const size_t lenBuf);
+void MsgSetRuleset(smsg_t *pMsg, ruleset_t*);
+rsRetVal MsgSetFlowControlType(smsg_t *pMsg, flowControl_t eFlowCtl);
+rsRetVal MsgSetStructuredData(smsg_t *const pMsg, const char* pszStrucData);
+rsRetVal MsgAddToStructuredData(smsg_t *pMsg, uchar *toadd, rs_size_t len);
+void MsgGetStructuredData(smsg_t *pM, uchar **pBuf, rs_size_t *len);
+rsRetVal msgSetFromSockinfo(smsg_t *pThis, struct sockaddr_storage *sa);
+void MsgSetRcvFrom(smsg_t *pMsg, prop_t*);
+void MsgSetRcvFromStr(smsg_t *const pMsg, const uchar* pszRcvFrom, const int, prop_t **);
+rsRetVal MsgSetRcvFromIP(smsg_t *pMsg, prop_t*);
+rsRetVal MsgSetRcvFromIPStr(smsg_t *const pThis, const uchar *psz, const int len, prop_t **ppProp);
+void MsgSetHOSTNAME(smsg_t *pMsg, const uchar* pszHOSTNAME, const int lenHOSTNAME);
+rsRetVal MsgSetAfterPRIOffs(smsg_t *pMsg, int offs);
+void MsgSetMSGoffs(smsg_t *pMsg, int offs);
+void MsgSetRawMsgWOSize(smsg_t *pMsg, char* pszRawMsg);
+void ATTR_NONNULL() MsgSetRawMsg(smsg_t *const pThis, const char*const pszRawMsg, const size_t lenMsg);
+rsRetVal MsgReplaceMSG(smsg_t *pThis, const uchar* pszMSG, int lenMSG);
+uchar *MsgGetProp(smsg_t *pMsg, struct templateEntry *pTpe, msgPropDescr_t *pProp,
+ rs_size_t *pPropLen, unsigned short *pbMustBeFreed, struct syslogTime *ttNow);
+uchar *getRcvFrom(smsg_t *pM);
+void getTAG(smsg_t *pM, uchar **ppBuf, int *piLen, sbool);
+const char *getTimeReported(smsg_t *pM, enum tplFormatTypes eFmt);
+const char *getPRI(smsg_t *pMsg);
+int getPRIi(const smsg_t * const pM);
+int ATTR_NONNULL() getRawMsgLen(const smsg_t *const pMsg);
+void getRawMsg(const smsg_t *pM, uchar **pBuf, int *piLen);
+void ATTR_NONNULL() MsgTruncateToMaxSize(smsg_t *const pThis);
+rsRetVal msgAddJSON(smsg_t *pM, uchar *name, struct json_object *json, int force_reset, int sharedReference);
+rsRetVal msgAddMetadata(smsg_t *msg, uchar *metaname, uchar *metaval);
+rsRetVal msgAddMultiMetadata(smsg_t *msg, const uchar **metaname, const uchar **metaval, const int count);
+rsRetVal MsgGetSeverity(smsg_t *pThis, int *piSeverity);
+rsRetVal MsgDeserialize(smsg_t *pMsg, strm_t *pStrm);
+rsRetVal MsgSetPropsViaJSON(smsg_t *__restrict__ const pMsg, const uchar *__restrict__ const json);
+rsRetVal MsgSetPropsViaJSON_Object(smsg_t *__restrict__ const pMsg, struct json_object *json);
+const uchar* msgGetJSONMESG(smsg_t *__restrict__ const pMsg);
+
+uchar *getMSG(smsg_t *pM);
+const char *getHOSTNAME(smsg_t *pM);
+char *getPROCID(smsg_t *pM, sbool bLockMutex);
+char *getAPPNAME(smsg_t *pM, sbool bLockMutex);
+void setMSGLen(smsg_t *pM, int lenMsg);
+int getMSGLen(smsg_t *pM);
+void getInputName(const smsg_t * const pM, uchar **ppsz, int *const plen);
+
+int getHOSTNAMELen(smsg_t *pM);
+uchar *getProgramName(smsg_t *pM, sbool bLockMutex);
+uchar *getRcvFrom(smsg_t *pM);
+rsRetVal propNameToID(const uchar *pName, propid_t *pPropID);
+uchar *propIDToName(propid_t propID);
+rsRetVal ATTR_NONNULL() msgCheckVarExists(smsg_t *const pMsg, msgPropDescr_t *pProp);
+rsRetVal msgGetJSONPropJSON(smsg_t *pMsg, msgPropDescr_t *pProp, struct json_object **pjson);
+rsRetVal msgGetJSONPropJSONorString(smsg_t * const pMsg, msgPropDescr_t *pProp, struct json_object **pjson,
+uchar **pcstr);
+rsRetVal getJSONPropVal(smsg_t *pMsg, msgPropDescr_t *pProp, uchar **pRes, rs_size_t *buflen,
+unsigned short *pbMustBeFreed);
+rsRetVal msgSetJSONFromVar(smsg_t *pMsg, uchar *varname, struct svar *var, int force_reset);
+rsRetVal msgDelJSON(smsg_t *pMsg, uchar *varname);
+rsRetVal jsonFind(smsg_t *const pMsg, msgPropDescr_t *pProp, struct json_object **jsonres);
+struct json_object *jsonDeepCopy(struct json_object *src);
+
+rsRetVal msgPropDescrFill(msgPropDescr_t *pProp, uchar *name, int nameLen);
+void msgPropDescrDestruct(msgPropDescr_t *pProp);
+void msgSetPRI(smsg_t *const __restrict__ pMsg, syslog_pri_t pri);
+
+#define msgGetProtocolVersion(pM) ((pM)->iProtocolVersion)
+
+/* returns non-zero if the message has structured data, 0 otherwise */
+#define MsgHasStructuredData(pM) (((pM)->pszStrucData == NULL) ? 0 : 1)
+
+/* ------------------------------ some inline functions ------------------------------ */
+
+/* add Metadata to the message. This is stored in a special JSON
+ * container. Note that only string types are currently supported,
+ * what should pose absolutely no problem with the string-ish nature
+ * of rsyslog metadata.
+ * added 2015-01-09 rgerhards
+ */
+/* 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 __attribute__((unused))
+MsgSetRawMsgSize(smsg_t *const __restrict__ pMsg, const size_t newLen)
+{
+ assert(newLen <= (size_t) pMsg->iLenRawMsg);
+ pMsg->iLenRawMsg = newLen;
+ pMsg->pszRawMsg[newLen] = '\0';
+}
+
+/* get the ruleset that is associated with the ruleset.
+ * May be NULL. -- rgerhards, 2009-10-27
+ */
+#define MsgGetRuleset(pMsg) ((pMsg)->pRuleset)
+
+#endif /* #ifndef MSG_H_INCLUDED */
diff --git a/runtime/net.c b/runtime/net.c
new file mode 100644
index 0000000..ff46cbc
--- /dev/null
+++ b/runtime/net.c
@@ -0,0 +1,1741 @@
+/* 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-2018 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.
+ */
+#include "config.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>
+#ifdef HAVE_GETIFADDRS
+#include <ifaddrs.h>
+#else
+#include "compat/ifaddrs.h"
+#endif /* HAVE_GETIFADDRS */
+#include <sys/types.h>
+#include <arpa/inet.h>
+
+#include "rsyslog.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"
+#include "rsconf.h"
+
+#ifdef OS_SOLARIS
+#include <arpa/nameser_compat.h>
+# define s6_addr32 _S6_un._S6_u32
+ typedef unsigned int u_int32_t;
+#endif
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(prop)
+
+#ifndef HAVE_STRUCT_SOCKADDR_SA_LEN
+extern size_t SALEN(struct sockaddr *sa);
+#endif
+/* 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
+
+/* ------------------------------ begin permitted peers code ------------------------------ */
+
+
+/* sets the correct allow root pointer based on provided type
+ * rgerhards, 2008-12-01
+ */
+static 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 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(*pNew)));
+
+ 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;
+ pPeer->pWildcardLast = 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) {
+ 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 *const pPeer,
+ const uchar *pszNameToMatch,
+ int *const pbIsMatching)
+{
+ const permittedPeerWildcard_t *pWildcard;
+ const uchar *pC;
+ 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;
+ }
+ const uchar *const pStart = pC; /* start of current domain component */
+ 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) {
+ // I give up, I see now way to remove false positive -- rgerhards, 2017-10-23
+ #ifndef __clang_analyzer__
+ if(pWildcard->pszDomainPart[iWildcard] != pStart[iName]) {
+ *pbIsMatching = 0;
+ FINALIZE;
+ }
+ #endif
+ ++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 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 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 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)
+{
+ struct addrinfo *restmp = NULL;
+ 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.
+ */
+ 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)) {
+ 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)) {
+ 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.
+ */
+ 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(loadConf)) {
+ 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, '?') &&
+ loadConf->globals.ACLDontResolve == 0) {
+ /* single host - in this case, we pull its IP addresses from DNS
+ * and add IP-based ACLs.
+ */
+ struct addrinfo hints, *res;
+ 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) {
+ LogError(0, NO_ERRCODE, "DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard);
+
+ if (loadConf->globals.ACLAddHostnameOnFail) {
+ LogError(0, NO_ERRCODE, "Adding hostname \"%s\" to ACL as a wildcard "
+ "entry.", iAllow->addr.HostWildcard);
+ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits);
+ FINALIZE;
+ } else {
+ LogError(0, NO_ERRCODE, "Hostname \"%s\" WON\'T be added to ACL.",
+ iAllow->addr.HostWildcard);
+ ABORT_FINALIZE(RS_RET_NOENTRY);
+ }
+ }
+
+ restmp = res;
+ for ( ; 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) {
+ free(allowIP.addr.NetAddr);
+ 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 = (struct sockaddr *)
+ malloc(sizeof(struct sockaddr))) == 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) {
+ free(allowIP.addr.NetAddr);
+ 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) {
+ free(allowIP.addr.NetAddr);
+ FINALIZE;
+ }
+ }
+ break;
+ }
+ }
+ } 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:
+ if(restmp != NULL) {
+ freeaddrinfo (restmp);
+ }
+ RETiRet;
+}
+
+
+static const char *SENDER_TEXT[4] = { "", "UDP", "TCP", "GSS" };
+/* Print an allowed sender list. The caller must tell us which one.
+ * iListToPrint = 1 means UDP, 2 means TCP
+ * rgerhards, 2005-09-27
+ */
+static void
+PrintAllowedSenders(int iListToPrint)
+{
+ struct AllowedSenders *pSender;
+ uchar szIP[64];
+#ifdef USE_GSSAPI
+#define iListToPrint_MAX 3
+#else
+#define iListToPrint_MAX 2
+#endif
+ assert((iListToPrint > 0) && (iListToPrint <= iListToPrint_MAX));
+
+ dbgprintf("Allowed %s Senders:\n", SENDER_TEXT[iListToPrint]);
+
+ 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
+ */
+static 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 {
+ 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)) {
+ 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) {
+ 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) {
+ LogError(0, iRet, "Error %d adding allowed sender entry "
+ "- ignoring.", iRet);
+ } else {
+ LogError(0, iRet, "Error %d adding allowed sender entry "
+ "- terminating, nothing more will be added.", iRet);
+ rsParsDestruct(pPars);
+ free(uIP);
+ 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.
+ * 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 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;
+ }
+ /* fallthrough */
+ 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 use_so_bsdcompat = -1;
+
+ /* we guard the init code just by an atomic fetch to local variable. This still
+ * is racy, but the worst that can happen is that we do the very same init twice.
+ * This does hurt less than locking a mutex.
+ */
+ const int local_use_so_bsdcompat = PREFER_FETCH_32BIT(use_so_bsdcompat);
+ if(local_use_so_bsdcompat == -1) {
+ struct utsname myutsname;
+ unsigned int version, patchlevel;
+
+ if (uname(&myutsname) < 0) {
+ char errStr[1024];
+ dbgprintf("uname: %s\r\n", rs_strerror_r(errno, errStr, sizeof(errStr)));
+ PREFER_STORE_1_TO_INT(&use_so_bsdcompat);
+ FINALIZE;
+ }
+ /* 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);
+ PREFER_STORE_1_TO_INT(&use_so_bsdcompat);
+ FINALIZE;
+ }
+ /* 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)) {
+ PREFER_STORE_0_TO_INT(&use_so_bsdcompat);
+ FINALIZE;
+ }
+ }
+finalize_it:
+ return PREFER_FETCH_32BIT(use_so_bsdcompat);
+#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
+ */
+static void
+debugListenInfo(int fd, char *type)
+{
+ const char *szFamily;
+ int port;
+ union {
+ struct sockaddr_storage sa;
+ struct sockaddr_in sa4;
+ struct sockaddr_in6 sa6;
+ } sockaddr;
+ socklen_t saLen = sizeof(sockaddr.sa);
+
+ if(getsockname(fd, (struct sockaddr *) &sockaddr.sa, &saLen) == 0) {
+ switch(sockaddr.sa.ss_family) {
+ case PF_INET:
+ szFamily = "IPv4";
+ port = ntohs(sockaddr.sa4.sin_port);
+ break;
+ case PF_INET6:
+ szFamily = "IPv6";
+ port = ntohs(sockaddr.sa6.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
+ */
+static rsRetVal
+cvthname(struct sockaddr_storage *f, prop_t **localName, prop_t **fqdn, prop_t **ip)
+{
+ DEFiRet;
+ assert(f != NULL);
+ iRet = dnscacheLookup(f, fqdn, NULL, 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 always tries to return a FQDN, even so be quering DNS. So it
+ * is safe to assume for the caller that when the function does not return
+ * a FQDN, it simply is not available. The domain part of that string is
+ * normalized to lower case. The hostname is kept in mixed case for historic
+ * reasons.
+ */
+#define EMPTY_HOSTNAME_REPLACEMENT "localhost-empty-hostname"
+static rsRetVal
+getLocalHostname(rsconf_t *const pConf, uchar **ppName)
+{
+ DEFiRet;
+ char hnbuf[8192];
+ uchar *fqdn = NULL;
+ int empty_hostname = 1;
+
+ if(gethostname(hnbuf, sizeof(hnbuf)) != 0) {
+ strcpy(hnbuf, EMPTY_HOSTNAME_REPLACEMENT);
+ } else {
+ /* now guard against empty hostname
+ * see https://github.com/rsyslog/rsyslog/issues/1040
+ */
+ if(hnbuf[0] == '\0') {
+ strcpy(hnbuf, EMPTY_HOSTNAME_REPLACEMENT);
+ } else {
+ empty_hostname = 0;
+ hnbuf[sizeof(hnbuf)-1] = '\0'; /* be on the safe side... */
+ }
+ }
+
+ char *dot = strstr(hnbuf, ".");
+ struct addrinfo *res = NULL;
+ if(!empty_hostname && dot == NULL && pConf != NULL && !glbl.GetDisableDNS(pConf)) {
+ /* we need to (try) to find the real name via resolver */
+ struct addrinfo flags;
+ memset(&flags, 0, sizeof(flags));
+ flags.ai_flags = AI_CANONNAME;
+ int error = getaddrinfo((char*)hnbuf, NULL, &flags, &res);
+ if (error != 0 &&
+ error != EAI_NONAME && error != EAI_AGAIN && error != EAI_FAIL) {
+ /* If we get one of errors above, network is probably
+ * not working yet, so we fall back to local hostname below
+ */
+ LogError(0, RS_RET_ERR, "getaddrinfo failed obtaining local "
+ "hostname - using '%s' instead; error: %s",
+ hnbuf, gai_strerror(error));
+ }
+ if (res != NULL) {
+ /* When AI_CANONNAME is set first member of res linked-list */
+ /* should contain what we need */
+ if (res->ai_canonname != NULL && res->ai_canonname[0] != '\0') {
+ CHKmalloc(fqdn = (uchar*)strdup(res->ai_canonname));
+ dot = strstr((char*)fqdn, ".");
+ }
+ }
+ }
+
+ if(fqdn == NULL) {
+ /* already was FQDN or we could not obtain a better one */
+ CHKmalloc(fqdn = (uchar*) strdup(hnbuf));
+ }
+
+ if(dot != NULL)
+ for(char *p = dot+1 ; *p ; ++p)
+ *p = tolower(*p);
+
+ *ppName = fqdn;
+finalize_it:
+ if (res != NULL) {
+ freeaddrinfo(res);
+ }
+ RETiRet;
+}
+
+
+/* closes the UDP listen sockets (if they exist) and frees
+ * all dynamically assigned memory.
+ */
+static 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);
+ }
+}
+
+
+/* create a single UDP socket and bail out if an error occurs.
+ * This is called from a loop inside create_udp_socket which
+ * iterates through potentially multiple sockets. NOT to be
+ * used elsewhere.
+ */
+static rsRetVal ATTR_NONNULL(1, 2)
+create_single_udp_socket(int *const s, /* socket */
+ struct addrinfo *const r,
+ const uchar *const hostname,
+ const int bIsServer,
+ const int rcvbuf,
+ const int sndbuf,
+ const int ipfreebind,
+ const char *const device
+ )
+{
+ const int on = 1;
+ int sockflags;
+ int actrcvbuf;
+ int actsndbuf;
+ socklen_t optlen;
+ char errStr[1024];
+ DEFiRet;
+
+ assert(r != NULL); // does NOT work with -O2 or higher due to ATTR_NONNULL!
+ assert(s != NULL);
+
+# if defined (_AIX)
+ /* AIXPORT : socktype will be SOCK_DGRAM, as set in hints before */
+ *s = socket(r->ai_family, SOCK_DGRAM, r->ai_protocol);
+# else
+ *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+# endif
+ if (*s < 0) {
+ if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) {
+ LogError(errno, NO_ERRCODE, "create_udp_socket(), socket");
+ /* it is debateble if PF_INET with EAFNOSUPPORT should
+ * also be ignored...
+ */
+ }
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+# ifdef IPV6_V6ONLY
+ if (r->ai_family == AF_INET6) {
+ int ion = 1;
+ if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY,
+ (char *)&ion, sizeof (ion)) < 0) {
+ LogError(errno, RS_RET_ERR, "error creating UDP socket - setsockopt");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+# endif
+
+ if(device) {
+# if defined(SO_BINDTODEVICE)
+ if(setsockopt(*s, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) < 0)
+# endif
+ {
+ LogError(errno, RS_RET_ERR, "create UDP socket bound to device failed");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+
+ if(setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) {
+ LogError(errno, RS_RET_ERR, "create UDP socket failed to set REUSEADDR");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ /* We need to enable BSD compatibility. Otherwise an attacker
+ * could flood our log files by sending us tons of ICMP errors.
+ */
+ /* AIXPORT : SO_BSDCOMPAT socket option is deprecated, and its usage
+ * has been discontinued on most unixes, AIX does not support this option,
+ * hence avoid the call.
+ */
+# if !defined(OS_BSD) && !defined(__hpux) && !defined(_AIX)
+ if (should_use_so_bsdcompat()) {
+ if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, (char *) &on, sizeof(on)) < 0) {
+ LogError(errno, RS_RET_ERR, "create UDP socket failed to set BSDCOMPAT");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+# endif
+ if(bIsServer) {
+ DBGPRINTF("net.c: trying to set server socket %d to non-blocking mode\n", *s);
+ 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) {
+ LogError(errno, RS_RET_ERR, "net.c: socket %d fcntl(O_NONBLOCK)", *s);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+
+ if(sndbuf != 0) {
+# if defined(SO_SNDBUFFORCE)
+ if(setsockopt(*s, SOL_SOCKET, SO_SNDBUFFORCE, &sndbuf, sizeof(sndbuf)) < 0)
+# endif
+ {
+ /* if we fail, try to do it the regular way. Experiments show that at
+ * least some platforms do not return an error here, but silently set
+ * it to the max permitted value. So we do our error check a bit
+ * differently by querying the size below.
+ */
+ if(setsockopt(*s, SOL_SOCKET, SO_SNDBUF, &sndbuf, sizeof(sndbuf)) != 0) {
+ /* keep Coverity happy */
+ DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
+ "handled at later stages\n", __FILE__, __LINE__);
+ }
+ }
+ /* report socket buffer sizes */
+ optlen = sizeof(actsndbuf);
+ if(getsockopt(*s, SOL_SOCKET, SO_SNDBUF, &actsndbuf, &optlen) == 0) {
+ LogMsg(0, NO_ERRCODE, LOG_INFO,
+ "socket %d, actual os socket sndbuf size is %d", *s, actsndbuf);
+ if(sndbuf != 0 && actsndbuf/2 != sndbuf) {
+ LogError(errno, NO_ERRCODE,
+ "could not set os socket sndbuf size %d for socket %d, "
+ "value now is %d", sndbuf, *s, actsndbuf/2);
+ }
+ } else {
+ DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
+ *s, rs_strerror_r(errno, errStr, sizeof(errStr)));
+ }
+ }
+
+ if(rcvbuf != 0) {
+# if defined(SO_RCVBUFFORCE)
+ if(setsockopt(*s, SOL_SOCKET, SO_RCVBUFFORCE, &rcvbuf, sizeof(rcvbuf)) < 0)
+# endif
+ {
+ /* if we fail, try to do it the regular way. Experiments show that at
+ * least some platforms do not return an error here, but silently set
+ * it to the max permitted value. So we do our error check a bit
+ * differently by querying the size below.
+ */
+ if(setsockopt(*s, SOL_SOCKET, SO_RCVBUF, &rcvbuf, sizeof(rcvbuf)) != 0) {
+ /* keep Coverity happy */
+ DBGPRINTF("setsockopt in %s:%d failed - this is expected and "
+ "handled at later stages\n", __FILE__, __LINE__);
+ }
+ }
+ optlen = sizeof(actrcvbuf);
+ if(getsockopt(*s, SOL_SOCKET, SO_RCVBUF, &actrcvbuf, &optlen) == 0) {
+ LogMsg(0, NO_ERRCODE, LOG_INFO,
+ "socket %d, actual os socket rcvbuf size %d\n", *s, actrcvbuf);
+ if(rcvbuf != 0 && actrcvbuf/2 != rcvbuf) {
+ LogError(errno, NO_ERRCODE,
+ "cannot set os socket rcvbuf size %d for socket %d, value now is %d",
+ rcvbuf, *s, actrcvbuf/2);
+ }
+ } else {
+ DBGPRINTF("could not obtain os socket rcvbuf size for socket %d: %s\n",
+ *s, rs_strerror_r(errno, errStr, sizeof(errStr)));
+ }
+ }
+
+ 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
+ ) {
+ if (errno == EADDRNOTAVAIL && ipfreebind != IPFREEBIND_DISABLED) {
+ if (setsockopt(*s, IPPROTO_IP, IP_FREEBIND, &on, sizeof(on)) < 0) {
+ LogError(errno, RS_RET_ERR, "setsockopt(IP_FREEBIND)");
+ } else if (bind(*s, r->ai_addr, r->ai_addrlen) < 0) {
+ LogError(errno, RS_RET_ERR, "bind with IP_FREEBIND");
+ } else {
+ if (ipfreebind >= IPFREEBIND_ENABLED_WITH_LOG)
+ LogMsg(0, RS_RET_OK_WARN, LOG_WARNING,
+ "bound address %s IP free", hostname);
+ FINALIZE;
+ }
+ }
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(*s != -1) {
+ close(*s);
+ *s = -1;
+ }
+ }
+ RETiRet;
+}
+
+/* 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
+ * Note: server sockets are created in non-blocking mode, client ones
+ * are blocking.
+ * param rcvbuf indicates desired rcvbuf size; 0 means OS default,
+ * similar for sndbuf.
+ */
+static int *
+create_udp_socket(uchar *hostname,
+ uchar *pszPort,
+ const int bIsServer,
+ const int rcvbuf,
+ const int sndbuf,
+ const int ipfreebind,
+ char *device)
+{
+ struct addrinfo hints, *res, *r;
+ int error, maxs, *s, *socks;
+ rsRetVal localRet;
+
+ assert(!((pszPort == NULL) && (hostname == NULL))); /* one of them must be non-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(runConf);
+ hints.ai_socktype = SOCK_DGRAM;
+# if defined (_AIX)
+ /* AIXPORT : SOCK_DGRAM has the protocol IPPROTO_UDP
+ * getaddrinfo needs this hint on AIX
+ */
+ hints.ai_protocol = IPPROTO_UDP;
+# endif
+ error = getaddrinfo((char*) hostname, (char*) pszPort, &hints, &res);
+ if(error) {
+ LogError(0, NO_ERRCODE, "%s", gai_strerror(error));
+ 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) {
+ LogError(0, RS_RET_OUT_OF_MEMORY, "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) {
+ localRet = create_single_udp_socket(s, r, hostname, bIsServer, rcvbuf,
+ sndbuf, ipfreebind, device);
+ if(localRet == RS_RET_OK) {
+ (*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) {
+ LogError(0, NO_ERRCODE, "No UDP socket could successfully be initialized, "
+ "some functionality may be disabled.\n");
+ /* we do NOT need to close 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)
+{
+#ifdef _AIX
+ struct ifaddrs_rsys * ifaddrs = NULL;
+ struct ifaddrs_rsys * ifa;
+#else
+ struct ifaddrs * ifaddrs = NULL;
+ struct ifaddrs * ifa;
+#endif
+ union {
+ struct sockaddr *sa;
+ struct sockaddr_in *ipv4;
+ struct sockaddr_in6 *ipv6;
+ } savecast;
+ 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;
+ savecast.sa = ifa->ifa_addr;
+ if( (family == AF_INET6 || family == AF_UNSPEC)
+ && ifa->ifa_addr->sa_family == AF_INET6) {
+ pAddr = &(savecast.ipv6->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 = &(savecast.ipv4->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;
+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);
+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(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 0000000..88d2df5
--- /dev/null
+++ b/runtime/net.h
@@ -0,0 +1,180 @@
+/* Definitions for network-related stuff.
+ *
+ * Copyright 2007-2016 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 <arpa/inet.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 */
+
+/* portability: incase IP_FREEBIND is not defined */
+#ifndef IP_FREEBIND
+#define IP_FREEBIND 0
+#endif
+/* defines for IP_FREEBIND, currently being used in imudp */
+#define IPFREEBIND_DISABLED 0x00 /* don't enable IP_FREEBIND in sock option */
+#define IPFREEBIND_ENABLED_NO_LOG 0x01 /* enable IP_FREEBIND but no warn on success */
+#define IPFREEBIND_ENABLED_WITH_LOG 0x02 /* enable IP_FREEBIND and warn on success */
+
+#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 __attribute__((unused))
+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, int rcvbuf, int sndbuf,
+ int ipfreebind, char *device);
+ void (*closeUDPListenSockets)(int *finet);
+ int (*isAllowedSender)(uchar *pszType, struct sockaddr *pFrom, const char *pszFromHost); /* deprecated! */
+ rsRetVal (*getLocalHostname)(rsconf_t *const, 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, const 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);
+ /* v8 cvthname() signature change -- rgerhards, 2013-01-18 */
+ /* v9 create_udp_socket() signature change -- dsahern, 2016-11-11 */
+ /* v10 moved data members to rsconf_t -- alakatos, 2021-12-29 */
+ENDinterface(net)
+#define netCURR_IF_VERSION 10 /* 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/net_ossl.c b/runtime/net_ossl.c
new file mode 100644
index 0000000..60e3fa2
--- /dev/null
+++ b/runtime/net_ossl.c
@@ -0,0 +1,1217 @@
+/* net.c
+ * Implementation of network-related stuff.
+ *
+ * File begun on 2023-08-29 by Alorbach (extracted from net.c)
+ *
+ * Copyright 2023 Andre Lorbach 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.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <strings.h>
+
+#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 "parse.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "net.h"
+#include "net_ossl.h"
+#include "nsd_ptcp.h"
+#include "rsconf.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(net)
+DEFobjCurrIf(nsd_ptcp)
+
+/*--------------------------------------MT OpenSSL helpers ------------------------------------------*/
+static MUTEX_TYPE *mutex_buf = NULL;
+static sbool openssl_initialized = 0; // Avoid multiple initialization / deinitialization
+
+void locking_function(int mode, int n,
+ __attribute__((unused)) const char * file, __attribute__((unused)) int line)
+{
+ if (mode & CRYPTO_LOCK)
+ MUTEX_LOCK(mutex_buf[n]);
+ else
+ MUTEX_UNLOCK(mutex_buf[n]);
+}
+
+unsigned long id_function(void)
+{
+ return ((unsigned long)THREAD_ID);
+}
+
+
+struct CRYPTO_dynlock_value * dyn_create_function(
+ __attribute__((unused)) const char *file, __attribute__((unused)) int line)
+{
+ struct CRYPTO_dynlock_value *value;
+ value = (struct CRYPTO_dynlock_value *)malloc(sizeof(struct CRYPTO_dynlock_value));
+ if (!value)
+ return NULL;
+
+ MUTEX_SETUP(value->mutex);
+ return value;
+}
+
+void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l,
+ __attribute__((unused)) const char *file, __attribute__((unused)) int line)
+{
+ if (mode & CRYPTO_LOCK)
+ MUTEX_LOCK(l->mutex);
+ else
+ MUTEX_UNLOCK(l->mutex);
+}
+
+void dyn_destroy_function(struct CRYPTO_dynlock_value *l,
+ __attribute__((unused)) const char *file, __attribute__((unused)) int line)
+{
+ MUTEX_CLEANUP(l->mutex);
+ free(l);
+}
+
+/* set up support functions for openssl multi-threading. This must
+ * be done at library initialisation. If the function fails,
+ * processing can not continue normally. On failure, 0 is
+ * returned, on success 1.
+ */
+int opensslh_THREAD_setup(void)
+{
+ int i;
+ if (openssl_initialized == 1) {
+ DBGPRINTF("openssl: multithread setup already initialized\n");
+ return 1;
+ }
+
+ mutex_buf = (MUTEX_TYPE *)malloc(CRYPTO_num_locks( ) * sizeof(MUTEX_TYPE));
+ if (mutex_buf == NULL)
+ return 0;
+ for (i = 0; i < CRYPTO_num_locks( ); i++)
+ MUTEX_SETUP(mutex_buf[i]);
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ CRYPTO_set_id_callback(id_function);
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+ CRYPTO_set_locking_callback(locking_function);
+ /* The following three CRYPTO_... functions are the OpenSSL functions
+ for registering the callbacks we implemented above */
+ CRYPTO_set_dynlock_create_callback(dyn_create_function);
+ CRYPTO_set_dynlock_lock_callback(dyn_lock_function);
+ CRYPTO_set_dynlock_destroy_callback(dyn_destroy_function);
+
+ DBGPRINTF("openssl: multithread setup finished\n");
+ openssl_initialized = 1;
+ return 1;
+}
+
+/* shut down openssl - do this only when you are totally done
+ * with openssl.
+ */
+int opensslh_THREAD_cleanup(void)
+{
+ int i;
+ if (openssl_initialized == 0) {
+ DBGPRINTF("openssl: multithread cleanup already done\n");
+ return 1;
+ }
+ if (!mutex_buf)
+ return 0;
+
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ CRYPTO_set_id_callback(NULL);
+#endif /* OPENSSL_VERSION_NUMBER < 0x10100000L */
+ CRYPTO_set_locking_callback(NULL);
+ CRYPTO_set_dynlock_create_callback(NULL);
+ CRYPTO_set_dynlock_lock_callback(NULL);
+ CRYPTO_set_dynlock_destroy_callback(NULL);
+
+ for (i = 0; i < CRYPTO_num_locks( ); i++)
+ MUTEX_CLEANUP(mutex_buf[i]);
+
+ free(mutex_buf);
+ mutex_buf = NULL;
+
+ DBGPRINTF("openssl: multithread cleanup finished\n");
+ openssl_initialized = 0;
+ return 1;
+}
+/*-------------------------------------- MT OpenSSL helpers -----------------------------------------*/
+
+
+/*--------------------------------------OpenSSL helpers ------------------------------------------*/
+
+/* globally initialize OpenSSL
+ */
+void
+osslGlblInit(void)
+{
+ DBGPRINTF("openssl: entering osslGlblInit\n");
+
+ if((opensslh_THREAD_setup() == 0) ||
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ /* Setup OpenSSL library < 1.1.0 */
+ !SSL_library_init()
+#else
+ /* Setup OpenSSL library >= 1.1.0 with system default settings */
+ OPENSSL_init_ssl(0, NULL) == 0
+#endif
+ ) {
+ LogError(0, RS_RET_NO_ERRCODE, "Error: OpenSSL initialization failed!");
+ }
+
+ /* Load readable error strings */
+ SSL_load_error_strings();
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ /*
+ * ERR_load_*(), ERR_func_error_string(), ERR_get_error_line(), ERR_get_error_line_data(), ERR_get_state()
+ * OpenSSL now loads error strings automatically so these functions are not needed.
+ * SEE FOR MORE:
+ * https://www.openssl.org/docs/manmaster/man7/migration_guide.html
+ *
+ */
+#else
+ /* Load error strings into mem*/
+ ERR_load_BIO_strings();
+ ERR_load_crypto_strings();
+#endif
+}
+
+/* globally de-initialize OpenSSL */
+void
+osslGlblExit(void)
+{
+ DBGPRINTF("openssl: entering osslGlblExit\n");
+ ENGINE_cleanup();
+ ERR_free_strings();
+ EVP_cleanup();
+ CRYPTO_cleanup_all_ex_data();
+}
+
+
+/* initialize openssl context; called on
+ * - listener creation
+ * - outbound connection creation
+ * Once created, the ctx object is used by-subobjects (accepted inbound connections)
+ */
+static rsRetVal
+net_ossl_osslCtxInit(net_ossl_t *pThis, const SSL_METHOD *method)
+{
+ DEFiRet;
+ int bHaveCA;
+ int bHaveCRL;
+ int bHaveCert;
+ int bHaveKey;
+ int bHaveExtraCAFiles;
+ const char *caFile, *crlFile, *certFile, *keyFile;
+ char *extraCaFiles, *extraCaFile;
+ /* Setup certificates */
+ caFile = (char*) ((pThis->pszCAFile == NULL) ? glbl.GetDfltNetstrmDrvrCAF(runConf) : pThis->pszCAFile);
+ if(caFile == NULL) {
+ LogMsg(0, RS_RET_CA_CERT_MISSING, LOG_WARNING,
+ "Warning: CA certificate is not set");
+ bHaveCA = 0;
+ } else {
+ dbgprintf("osslCtxInit: OSSL CA file: '%s'\n", caFile);
+ bHaveCA = 1;
+ }
+ crlFile = (char*) ((pThis->pszCRLFile == NULL) ? glbl.GetDfltNetstrmDrvrCRLF(runConf) : pThis->pszCRLFile);
+ if(crlFile == NULL) {
+ bHaveCRL = 0;
+ } else {
+ dbgprintf("osslCtxInit: OSSL CRL file: '%s'\n", crlFile);
+ bHaveCRL = 1;
+ }
+ certFile = (char*) ((pThis->pszCertFile == NULL) ?
+ glbl.GetDfltNetstrmDrvrCertFile(runConf) : pThis->pszCertFile);
+ if(certFile == NULL) {
+ LogMsg(0, RS_RET_CERT_MISSING, LOG_WARNING,
+ "Warning: Certificate file is not set");
+ bHaveCert = 0;
+ } else {
+ dbgprintf("osslCtxInit: OSSL CERT file: '%s'\n", certFile);
+ bHaveCert = 1;
+ }
+ keyFile = (char*) ((pThis->pszKeyFile == NULL) ? glbl.GetDfltNetstrmDrvrKeyFile(runConf) : pThis->pszKeyFile);
+ if(keyFile == NULL) {
+ LogMsg(0, RS_RET_CERTKEY_MISSING, LOG_WARNING,
+ "Warning: Key file is not set");
+ bHaveKey = 0;
+ } else {
+ dbgprintf("osslCtxInit: OSSL KEY file: '%s'\n", keyFile);
+ bHaveKey = 1;
+ }
+ extraCaFiles = (char*) ((pThis->pszExtraCAFiles == NULL) ? glbl.GetNetstrmDrvrCAExtraFiles(runConf) :
+ pThis->pszExtraCAFiles);
+ if(extraCaFiles == NULL) {
+ bHaveExtraCAFiles = 0;
+ } else {
+ dbgprintf("osslCtxInit: OSSL EXTRA CA files: '%s'\n", extraCaFiles);
+ bHaveExtraCAFiles = 1;
+ }
+
+ /* Create main CTX Object based on method parameter */
+ pThis->ctx = SSL_CTX_new(method);
+
+ if(bHaveExtraCAFiles == 1) {
+ while((extraCaFile = strsep(&extraCaFiles, ","))) {
+ if(SSL_CTX_load_verify_locations(pThis->ctx, extraCaFile, NULL) != 1) {
+ LogError(0, RS_RET_TLS_CERT_ERR, "Error: Extra Certificate file could not be accessed. "
+ "Check at least: 1) file path is correct, 2) file exist, "
+ "3) permissions are correct, 4) file content is correct. "
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit",
+ "SSL_CTX_load_verify_locations");
+ ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
+ }
+ }
+ }
+ if(bHaveCA == 1 && SSL_CTX_load_verify_locations(pThis->ctx, caFile, NULL) != 1) {
+ LogError(0, RS_RET_TLS_CERT_ERR, "Error: CA certificate could not be accessed. "
+ "Check at least: 1) file path is correct, 2) file exist, "
+ "3) permissions are correct, 4) file content is correct. "
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "SSL_CTX_load_verify_locations");
+ ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
+ }
+ if(bHaveCRL == 1) {
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ // Get X509_STORE reference
+ X509_STORE *store = SSL_CTX_get_cert_store(pThis->ctx);
+ if (!X509_STORE_load_file(store, crlFile)) {
+ LogError(0, RS_RET_CRL_INVALID, "Error: CRL could not be accessed. "
+ "Check at least: 1) file path is correct, 2) file exist, "
+ "3) permissions are correct, 4) file content is correct. "
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "X509_STORE_load_file");
+ ABORT_FINALIZE(RS_RET_CRL_INVALID);
+ }
+ X509_STORE_set_flags(store, X509_V_FLAG_CRL_CHECK);
+#else
+# if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ // Get X509_STORE reference
+ X509_STORE *store = SSL_CTX_get_cert_store(pThis->ctx);
+ // Load the CRL PEM file
+ FILE *fp = fopen(crlFile, "r");
+ if(fp == NULL) {
+ LogError(0, RS_RET_CRL_MISSING, "Error: CRL could not be accessed. "
+ "Check at least: 1) file path is correct, 2) file exist, "
+ "3) permissions are correct, 4) file content is correct. "
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "fopen");
+ ABORT_FINALIZE(RS_RET_CRL_MISSING);
+ }
+ X509_CRL *crl = PEM_read_X509_CRL(fp, NULL, NULL, NULL);
+ fclose(fp);
+ if(crl == NULL) {
+ LogError(0, RS_RET_CRL_INVALID, "Error: Unable to read CRL."
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "PEM_read_X509_CRL");
+ ABORT_FINALIZE(RS_RET_CRL_INVALID);
+ }
+ // Add the CRL to the X509_STORE
+ if(!X509_STORE_add_crl(store, crl)) {
+ LogError(0, RS_RET_CRL_INVALID, "Error: Unable to add CRL to store."
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "X509_STORE_add_crl");
+ X509_CRL_free(crl);
+ ABORT_FINALIZE(RS_RET_CRL_INVALID);
+ }
+ // Set the X509_STORE to the SSL_CTX
+ // SSL_CTX_set_cert_store(pThis->ctx, store);
+ // Enable CRL checking
+ X509_VERIFY_PARAM *param = X509_VERIFY_PARAM_new();
+ X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_CRL_CHECK);
+ SSL_CTX_set1_param(pThis->ctx, param);
+ X509_VERIFY_PARAM_free(param);
+# else
+ LogError(0, RS_RET_SYS_ERR, "Warning: TLS library does not support X509_STORE_load_file"
+ "(requires OpenSSL 3.x or higher). Cannot use Certificate revocation list (CRL) '%s'.",
+ crlFile);
+# endif
+#endif
+ }
+ if(bHaveCert == 1 && SSL_CTX_use_certificate_chain_file(pThis->ctx, certFile) != 1) {
+ LogError(0, RS_RET_TLS_CERT_ERR, "Error: Certificate file could not be accessed. "
+ "Check at least: 1) file path is correct, 2) file exist, "
+ "3) permissions are correct, 4) file content is correct. "
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit",
+ "SSL_CTX_use_certificate_chain_file");
+ ABORT_FINALIZE(RS_RET_TLS_CERT_ERR);
+ }
+ if(bHaveKey == 1 && SSL_CTX_use_PrivateKey_file(pThis->ctx, keyFile, SSL_FILETYPE_PEM) != 1) {
+ LogError(0, RS_RET_TLS_KEY_ERR , "Error: Key could not be accessed. "
+ "Check at least: 1) file path is correct, 2) file exist, "
+ "3) permissions are correct, 4) file content is correct. "
+ "OpenSSL error info may follow in next messages");
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR, "osslCtxInit", "SSL_CTX_use_PrivateKey_file");
+ ABORT_FINALIZE(RS_RET_TLS_KEY_ERR);
+ }
+
+ /* Set CTX Options */
+ SSL_CTX_set_options(pThis->ctx, SSL_OP_NO_SSLv2); /* Disable insecure SSLv2 Protocol */
+ SSL_CTX_set_options(pThis->ctx, SSL_OP_NO_SSLv3); /* Disable insecure SSLv3 Protocol */
+ SSL_CTX_sess_set_cache_size(pThis->ctx,1024); /* TODO: make configurable? */
+
+ /* Set default VERIFY Options for OpenSSL CTX - and CALLBACK */
+ if (pThis->authMode == OSSL_AUTH_CERTANON) {
+ dbgprintf("osslCtxInit: SSL_VERIFY_NONE\n");
+ net_ossl_set_ctx_verify_callback(pThis->ctx, SSL_VERIFY_NONE);
+ } else {
+ dbgprintf("osslCtxInit: SSL_VERIFY_PEER\n");
+ net_ossl_set_ctx_verify_callback(pThis->ctx, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+ }
+
+ SSL_CTX_set_timeout(pThis->ctx, 30); /* Default Session Timeout, TODO: Make configureable */
+ SSL_CTX_set_mode(pThis->ctx, SSL_MODE_AUTO_RETRY);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+# if OPENSSL_VERSION_NUMBER <= 0x101010FFL
+ /* Enable Support for automatic EC temporary key parameter selection. */
+ SSL_CTX_set_ecdh_auto(pThis->ctx, 1);
+# else
+ /*
+ * SSL_CTX_set_ecdh_auto and SSL_CTX_set_tmp_ecdh are depreceated in higher
+ * OpenSSL Versions, so we no more need them - see for more:
+ * https://www.openssl.org/docs/manmaster/man3/SSL_CTX_set_ecdh_auto.html
+ */
+# endif
+#else
+ dbgprintf("osslCtxInit: openssl to old, cannot use SSL_CTX_set_ecdh_auto."
+ "Using SSL_CTX_set_tmp_ecdh with NID_X9_62_prime256v1/() instead.\n");
+ SSL_CTX_set_tmp_ecdh(pThis->ctx, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+#endif
+finalize_it:
+ RETiRet;
+}
+
+/* Helper function to print usefull OpenSSL errors
+ */
+void net_ossl_lastOpenSSLErrorMsg
+ (uchar *fromHost, int ret, SSL *ssl, int severity, const char* pszCallSource, const char* pszOsslApi)
+{
+ unsigned long un_error = 0;
+ int iSSLErr = 0;
+ if (ssl == NULL) {
+ /* Output Error Info*/
+ DBGPRINTF("lastOpenSSLErrorMsg: Error in '%s' with ret=%d\n", pszCallSource, ret);
+ } else {
+ /* if object is set, get error code */
+ iSSLErr = SSL_get_error(ssl, ret);
+ /* Output Debug as well */
+ DBGPRINTF("lastOpenSSLErrorMsg: %s Error in '%s': '%s(%d)' with ret=%d, errno=%d(%s), sslapi='%s'\n",
+ (iSSLErr == SSL_ERROR_SSL ? "SSL_ERROR_SSL" :
+ (iSSLErr == SSL_ERROR_SYSCALL ? "SSL_ERROR_SYSCALL" : "SSL_ERROR_UNKNOWN")),
+ pszCallSource, ERR_error_string(iSSLErr, NULL),
+ iSSLErr,
+ ret,
+ errno,
+ strerror(errno),
+ pszOsslApi);
+
+ /* Output error message */
+ LogMsg(0, RS_RET_NO_ERRCODE, severity,
+ "%s Error in '%s': '%s(%d)' with ret=%d, errno=%d(%s), sslapi='%s'\n",
+ (iSSLErr == SSL_ERROR_SSL ? "SSL_ERROR_SSL" :
+ (iSSLErr == SSL_ERROR_SYSCALL ? "SSL_ERROR_SYSCALL" : "SSL_ERROR_UNKNOWN")),
+ pszCallSource, ERR_error_string(iSSLErr, NULL),
+ iSSLErr,
+ ret,
+ errno,
+ strerror(errno),
+ pszOsslApi);
+ }
+
+ /* Loop through ERR_get_error */
+ while ((un_error = ERR_get_error()) > 0){
+ LogMsg(0, RS_RET_NO_ERRCODE, severity,
+ "net_ossl:remote '%s' OpenSSL Error Stack: %s", fromHost, ERR_error_string(un_error, NULL) );
+ }
+}
+
+/* initialize tls config commands in openssl context
+ */
+rsRetVal net_ossl_apply_tlscgfcmd(net_ossl_t *pThis, uchar *tlscfgcmd)
+{
+ DEFiRet;
+ char *pCurrentPos;
+ char *pNextPos;
+ char *pszCmd;
+ char *pszValue;
+ int iConfErr;
+
+ if (tlscfgcmd == NULL) {
+ FINALIZE;
+ }
+
+ dbgprintf("net_ossl_apply_tlscgfcmd: Apply tlscfgcmd: '%s'\n", tlscfgcmd);
+
+ /* Set working pointer */
+ pCurrentPos = (char*) tlscfgcmd;
+ if (pCurrentPos != NULL && strlen(pCurrentPos) > 0) {
+ // Create CTX Config Helper
+ SSL_CONF_CTX *cctx;
+ cctx = SSL_CONF_CTX_new();
+ if (pThis->sslState == osslServer) {
+ SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SERVER);
+ } else {
+ SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_CLIENT);
+ }
+ SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE);
+ SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_SHOW_ERRORS);
+ SSL_CONF_CTX_set_ssl_ctx(cctx, pThis->ctx);
+
+ do
+ {
+ pNextPos = index(pCurrentPos, '=');
+ if (pNextPos != NULL) {
+ while ( *pCurrentPos != '\0' &&
+ (*pCurrentPos == ' ' || *pCurrentPos == '\t') )
+ pCurrentPos++;
+ pszCmd = strndup(pCurrentPos, pNextPos-pCurrentPos);
+ pCurrentPos = pNextPos+1;
+ pNextPos = index(pCurrentPos, '\n');
+ pNextPos = (pNextPos == NULL ? index(pCurrentPos, ';') : pNextPos);
+ pszValue = (pNextPos == NULL ?
+ strdup(pCurrentPos) :
+ strndup(pCurrentPos, pNextPos - pCurrentPos));
+ pCurrentPos = (pNextPos == NULL ? NULL : pNextPos+1);
+
+ /* Add SSL Conf Command */
+ iConfErr = SSL_CONF_cmd(cctx, pszCmd, pszValue);
+ if (iConfErr > 0) {
+ dbgprintf("net_ossl_apply_tlscgfcmd: Successfully added Command "
+ "'%s':'%s'\n",
+ pszCmd, pszValue);
+ }
+ else {
+ LogError(0, RS_RET_SYS_ERR, "Failed to added Command: %s:'%s' "
+ "in net_ossl_apply_tlscgfcmd with error '%d'",
+ pszCmd, pszValue, iConfErr);
+ }
+
+ free(pszCmd);
+ free(pszValue);
+ } else {
+ /* Abort further parsing */
+ pCurrentPos = NULL;
+ }
+ }
+ while (pCurrentPos != NULL);
+
+ /* Finalize SSL Conf */
+ iConfErr = SSL_CONF_CTX_finish(cctx);
+ if (!iConfErr) {
+ LogError(0, RS_RET_SYS_ERR, "Error: setting openssl command parameters: %s"
+ "OpenSSL error info may follow in next messages",
+ tlscfgcmd);
+ net_ossl_lastOpenSSLErrorMsg(NULL, 0, NULL, LOG_ERR,
+ "net_ossl_apply_tlscgfcmd", "SSL_CONF_CTX_finish");
+ }
+ SSL_CONF_CTX_free(cctx);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* 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
+net_ossl_genfingerprintstr(uchar *pFingerprint, size_t sizeFingerprint, cstr_t **ppStr, const char* prefix)
+{
+ cstr_t *pStr = NULL;
+ uchar buf[4];
+ size_t i;
+ DEFiRet;
+
+ CHKiRet(rsCStrConstruct(&pStr));
+ CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*) prefix, strlen(prefix)));
+ for(i = 0 ; i < sizeFingerprint ; ++i) {
+ snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]);
+ CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3));
+ }
+ cstrFinalize(pStr);
+
+ *ppStr = pStr;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pStr != NULL)
+ rsCStrDestruct(&pStr);
+ }
+ 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
+net_ossl_chkonepeername(net_ossl_t *pThis, X509 *certpeer, uchar *pszPeerID, int *pbFoundPositiveMatch)
+{
+ permittedPeers_t *pPeer;
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ int osslRet;
+#endif
+ char *x509name = NULL;
+ DEFiRet;
+
+ if (certpeer == NULL) {
+ ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
+ }
+
+ ISOBJ_TYPE_assert(pThis, net_ossl);
+ assert(pszPeerID != NULL);
+ assert(pbFoundPositiveMatch != NULL);
+
+ /* Obtain Namex509 name from subject */
+ x509name = X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(certpeer), NULL, 0);
+
+ if(pThis->pPermPeers) { /* do we have configured peer IDs? */
+ pPeer = pThis->pPermPeers;
+ while(pPeer != NULL) {
+ CHKiRet(net.PermittedPeerWildcardMatch(pPeer, pszPeerID, pbFoundPositiveMatch));
+ if(*pbFoundPositiveMatch)
+ break;
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ /* if we did not succeed so far, try ossl X509_check_host
+ * ( Includes check against SubjectAlternativeName )
+ */
+ osslRet = X509_check_host( certpeer, (const char*)pPeer->pszID,
+ strlen((const char*)pPeer->pszID), 0, NULL);
+ if ( osslRet == 1 ) {
+ /* Found Peer cert in allowed Peerslist */
+ dbgprintf("net_ossl_chkonepeername: Client ('%s') is allowed (X509_check_host)\n",
+ x509name);
+ *pbFoundPositiveMatch = 1;
+ break;
+ } else if ( osslRet < 0 ) {
+ net_ossl_lastOpenSSLErrorMsg(NULL, osslRet, NULL, LOG_ERR,
+ "net_ossl_chkonepeername", "X509_check_host");
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ }
+#endif
+ /* Check next peer */
+ pPeer = pPeer->pNext;
+ }
+ } else {
+ LogMsg(0, RS_RET_TLS_NO_CERT, LOG_WARNING,
+ "net_ossl_chkonepeername: Peername check could not be done: "
+ "no peernames configured.");
+ }
+finalize_it:
+ if (x509name != NULL){
+ OPENSSL_free(x509name);
+ }
+
+ RETiRet;
+}
+
+
+/* Check the peer's ID in fingerprint auth mode.
+ * rgerhards, 2008-05-22
+ */
+rsRetVal
+net_ossl_peerfingerprint(net_ossl_t *pThis, X509* certpeer, uchar *fromHostIP)
+{
+ DEFiRet;
+ unsigned int n;
+ uchar fingerprint[20 /*EVP_MAX_MD_SIZE**/];
+ uchar fingerprintSha256[32 /*EVP_MAX_MD_SIZE**/];
+ size_t size;
+ size_t sizeSha256;
+ cstr_t *pstrFingerprint = NULL;
+ cstr_t *pstrFingerprintSha256 = NULL;
+ int bFoundPositiveMatch;
+ permittedPeers_t *pPeer;
+ const EVP_MD *fdig = EVP_sha1();
+ const EVP_MD *fdigSha256 = EVP_sha256();
+
+ ISOBJ_TYPE_assert(pThis, net_ossl);
+
+ if (certpeer == NULL) {
+ ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
+ }
+
+ /* obtain the SHA1 fingerprint */
+ size = sizeof(fingerprint);
+ if (!X509_digest(certpeer,fdig,fingerprint,&n)) {
+ dbgprintf("net_ossl_peerfingerprint: error X509cert is not valid!\n");
+ ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
+ }
+ sizeSha256 = sizeof(fingerprintSha256);
+ if (!X509_digest(certpeer,fdigSha256,fingerprintSha256,&n)) {
+ dbgprintf("net_ossl_peerfingerprint: error X509cert is not valid!\n");
+ ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
+ }
+ CHKiRet(net_ossl_genfingerprintstr(fingerprint, size, &pstrFingerprint, "SHA1"));
+ dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA1 fingerprint: %s\n",
+ cstrGetSzStrNoNULL(pstrFingerprint));
+ CHKiRet(net_ossl_genfingerprintstr(fingerprintSha256, sizeSha256, &pstrFingerprintSha256, "SHA256"));
+ dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA256 fingerprint: %s\n",
+ cstrGetSzStrNoNULL(pstrFingerprintSha256));
+
+ /* 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))) {
+ dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA1 MATCH found: %s\n",
+ pPeer->pszID);
+ bFoundPositiveMatch = 1;
+ } else if(!rsCStrSzStrCmp(pstrFingerprintSha256, pPeer->pszID, strlen((char*) pPeer->pszID))) {
+ dbgprintf("net_ossl_peerfingerprint: peer's certificate SHA256 MATCH found: %s\n",
+ pPeer->pszID);
+ bFoundPositiveMatch = 1;
+ } else {
+ dbgprintf("net_ossl_peerfingerprint: NOMATCH peer certificate: %s\n", pPeer->pszID);
+ pPeer = pPeer->pNext;
+ }
+ }
+
+ if(!bFoundPositiveMatch) {
+ dbgprintf("net_ossl_peerfingerprint: invalid peer fingerprint, not permitted to talk to it\n");
+ if(pThis->bReportAuthErr == 1) {
+ errno = 0;
+ LogMsg(0, RS_RET_INVALID_FINGERPRINT, LOG_WARNING,
+ "nsd_ossl:TLS session terminated with remote syslog server '%s': "
+ "Fingerprint check failed, not permitted to talk to %s",
+ fromHostIP, cstrGetSzStrNoNULL(pstrFingerprint));
+ pThis->bReportAuthErr = 0;
+ }
+ ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
+ }
+
+finalize_it:
+ if(pstrFingerprint != NULL)
+ cstrDestruct(&pstrFingerprint);
+ RETiRet;
+}
+
+
+/* Check the peer's ID in name auth mode.
+ */
+rsRetVal
+net_ossl_chkpeername(net_ossl_t *pThis, X509* certpeer, uchar *fromHostIP)
+{
+ DEFiRet;
+ uchar lnBuf[256];
+ int bFoundPositiveMatch;
+ cstr_t *pStr = NULL;
+ char *x509name = NULL;
+
+ ISOBJ_TYPE_assert(pThis, net_ossl);
+
+ bFoundPositiveMatch = 0;
+ CHKiRet(rsCStrConstruct(&pStr));
+
+ /* Obtain Namex509 name from subject */
+ x509name = X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(certpeer), NULL, 0);
+
+ dbgprintf("net_ossl_chkpeername: checking - peername '%s' on server '%s'\n", x509name, fromHostIP);
+ snprintf((char*)lnBuf, sizeof(lnBuf), "name: %s; ", x509name);
+ CHKiRet(rsCStrAppendStr(pStr, lnBuf));
+ CHKiRet(net_ossl_chkonepeername(pThis, certpeer, (uchar*)x509name, &bFoundPositiveMatch));
+
+ if(!bFoundPositiveMatch) {
+ dbgprintf("net_ossl_chkpeername: invalid peername, not permitted to talk to it\n");
+ if(pThis->bReportAuthErr == 1) {
+ cstrFinalize(pStr);
+ errno = 0;
+ LogMsg(0, RS_RET_INVALID_FINGERPRINT, LOG_WARNING,
+ "nsd_ossl:TLS session terminated with remote syslog server: "
+ "peer name not authorized, not permitted to talk to %s",
+ cstrGetSzStrNoNULL(pStr));
+ pThis->bReportAuthErr = 0;
+ }
+ ABORT_FINALIZE(RS_RET_INVALID_FINGERPRINT);
+ } else {
+ dbgprintf("net_ossl_chkpeername: permitted to talk!\n");
+ }
+
+finalize_it:
+ if (x509name != NULL){
+ OPENSSL_free(x509name);
+ }
+
+ if(pStr != NULL)
+ rsCStrDestruct(&pStr);
+ RETiRet;
+}
+
+
+/* check the ID of the remote peer - used for both fingerprint and
+ * name authentication.
+ */
+X509*
+net_ossl_getpeercert(net_ossl_t *pThis, SSL *ssl, uchar *fromHostIP)
+{
+ X509* certpeer;
+
+ ISOBJ_TYPE_assert(pThis, net_ossl);
+
+ /* Get peer certificate from SSL */
+ certpeer = SSL_get_peer_certificate(ssl);
+ if ( certpeer == NULL ) {
+ if(pThis->bReportAuthErr == 1 && 1) {
+ errno = 0;
+ pThis->bReportAuthErr = 0;
+ LogMsg(0, RS_RET_TLS_NO_CERT, LOG_WARNING,
+ "nsd_ossl:TLS session terminated with remote syslog server '%s': "
+ "Peer check failed, peer did not provide a certificate.", fromHostIP);
+ }
+ }
+ return certpeer;
+}
+
+
+/* Verify the validity of the remote peer's certificate.
+ */
+rsRetVal
+net_ossl_chkpeercertvalidity(net_ossl_t __attribute__((unused)) *pThis, SSL *ssl, uchar *fromHostIP)
+{
+ DEFiRet;
+ int iVerErr = X509_V_OK;
+
+ ISOBJ_TYPE_assert(pThis, net_ossl);
+ PermitExpiredCerts* pPermitExpiredCerts = (PermitExpiredCerts*) SSL_get_ex_data(ssl, 1);
+
+ iVerErr = SSL_get_verify_result(ssl);
+ if (iVerErr != X509_V_OK) {
+ if (iVerErr == X509_V_ERR_CERT_HAS_EXPIRED) {
+ if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_DENY) {
+ LogMsg(0, RS_RET_CERT_EXPIRED, LOG_INFO,
+ "net_ossl:TLS session terminated with remote syslog server '%s': "
+ "not permitted to talk to peer, certificate invalid: "
+ "Certificate expired: %s",
+ fromHostIP, X509_verify_cert_error_string(iVerErr));
+ ABORT_FINALIZE(RS_RET_CERT_EXPIRED);
+ } else if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_WARN) {
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING,
+ "net_ossl:CertValidity check - warning talking to peer '%s': "
+ "certificate expired: %s",
+ fromHostIP, X509_verify_cert_error_string(iVerErr));
+ } else {
+ dbgprintf("net_ossl_chkpeercertvalidity: talking to peer '%s': "
+ "certificate expired: %s\n",
+ fromHostIP, X509_verify_cert_error_string(iVerErr));
+ }/* Else do nothing */
+ } else if (iVerErr == X509_V_ERR_CERT_REVOKED) {
+ LogMsg(0, RS_RET_CERT_REVOKED, LOG_INFO,
+ "net_ossl:TLS session terminated with remote syslog server '%s': "
+ "not permitted to talk to peer, certificate invalid: "
+ "certificate revoked '%s'",
+ fromHostIP, X509_verify_cert_error_string(iVerErr));
+ ABORT_FINALIZE(RS_RET_CERT_EXPIRED);
+ } else {
+ LogMsg(0, RS_RET_CERT_INVALID, LOG_INFO,
+ "net_ossl:TLS session terminated with remote syslog server '%s': "
+ "not permitted to talk to peer, certificate validation failed: %s",
+ fromHostIP, X509_verify_cert_error_string(iVerErr));
+ ABORT_FINALIZE(RS_RET_CERT_INVALID);
+ }
+ } else {
+ dbgprintf("net_ossl_chkpeercertvalidity: client certificate validation success: %s\n",
+ X509_verify_cert_error_string(iVerErr));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+/* Verify Callback for X509 Certificate validation. Force visibility as this function is not called anywhere but
+* only used as callback!
+*/
+int
+net_ossl_verify_callback(int status, X509_STORE_CTX *store)
+{
+ char szdbgdata1[256];
+ char szdbgdata2[256];
+
+ dbgprintf("verify_callback: status %d\n", status);
+
+ if(status == 0) {
+ /* Retrieve all needed pointers */
+ X509 *cert = X509_STORE_CTX_get_current_cert(store);
+ int depth = X509_STORE_CTX_get_error_depth(store);
+ int err = X509_STORE_CTX_get_error(store);
+ SSL* ssl = X509_STORE_CTX_get_ex_data(store, SSL_get_ex_data_X509_STORE_CTX_idx());
+ int iVerifyMode = SSL_get_verify_mode(ssl);
+ nsd_t *pNsdTcp = (nsd_t*) SSL_get_ex_data(ssl, 0);
+ PermitExpiredCerts* pPermitExpiredCerts = (PermitExpiredCerts*) SSL_get_ex_data(ssl, 1);
+
+ dbgprintf("verify_callback: Certificate validation failed, Mode (%d)!\n", iVerifyMode);
+
+ X509_NAME_oneline(X509_get_issuer_name(cert), szdbgdata1, sizeof(szdbgdata1));
+ X509_NAME_oneline(RSYSLOG_X509_NAME_oneline(cert), szdbgdata2, sizeof(szdbgdata2));
+
+ uchar *fromHost = NULL;
+ if (pNsdTcp != NULL) {
+ nsd_ptcp.GetRemoteHName(pNsdTcp, &fromHost);
+ }
+
+ if (iVerifyMode != SSL_VERIFY_NONE) {
+ /* Handle expired Certificates **/
+ if (err == X509_V_OK || err == X509_V_ERR_CERT_HAS_EXPIRED) {
+ if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_PERMIT) {
+ dbgprintf("verify_callback: EXPIRED cert but PERMITTED at depth: %d \n\t"
+ "issuer = %s\n\t"
+ "subject = %s\n\t"
+ "err %d:%s\n", depth, szdbgdata1, szdbgdata2,
+ err, X509_verify_cert_error_string(err));
+
+ /* Set Status to OK*/
+ status = 1;
+ }
+ else if (pPermitExpiredCerts != NULL && *pPermitExpiredCerts == OSSL_EXPIRED_WARN) {
+ LogMsg(0, RS_RET_CERT_EXPIRED, LOG_WARNING,
+ "Certificate EXPIRED warning at depth: %d \n\t"
+ "issuer = %s\n\t"
+ "subject = %s\n\t"
+ "err %d:%s peer '%s'",
+ depth, szdbgdata1, szdbgdata2,
+ err, X509_verify_cert_error_string(err), fromHost);
+
+ /* Set Status to OK*/
+ status = 1;
+ }
+ else /* also default - if (pPermitExpiredCerts == OSSL_EXPIRED_DENY)*/ {
+ LogMsg(0, RS_RET_CERT_EXPIRED, LOG_ERR,
+ "Certificate EXPIRED at depth: %d \n\t"
+ "issuer = %s\n\t"
+ "subject = %s\n\t"
+ "err %d:%s\n\t"
+ "not permitted to talk to peer '%s', certificate invalid: "
+ "certificate expired",
+ depth, szdbgdata1, szdbgdata2,
+ err, X509_verify_cert_error_string(err), fromHost);
+ }
+ } else if (err == X509_V_ERR_CERT_REVOKED) {
+ LogMsg(0, RS_RET_CERT_REVOKED, LOG_ERR,
+ "Certificate REVOKED at depth: %d \n\t"
+ "issuer = %s\n\t"
+ "subject = %s\n\t"
+ "err %d:%s\n\t"
+ "not permitted to talk to peer '%s', certificate invalid: "
+ "certificate revoked",
+ depth, szdbgdata1, szdbgdata2,
+ err, X509_verify_cert_error_string(err), fromHost);
+ } else {
+ /* all other error codes cause failure */
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_ERR,
+ "Certificate error at depth: %d \n\t"
+ "issuer = %s\n\t"
+ "subject = %s\n\t"
+ "err %d:%s - peer '%s'",
+ depth, szdbgdata1, szdbgdata2,
+ err, X509_verify_cert_error_string(err), fromHost);
+ }
+ } else {
+ /* do not verify certs in ANON mode, just log into debug */
+ dbgprintf("verify_callback: Certificate validation DISABLED but Error at depth: %d \n\t"
+ "issuer = %s\n\t"
+ "subject = %s\n\t"
+ "err %d:%s\n", depth, szdbgdata1, szdbgdata2,
+ err, X509_verify_cert_error_string(err));
+
+ /* Set Status to OK*/
+ status = 1;
+ }
+ free(fromHost);
+ }
+
+ return status;
+}
+
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+static long
+RSYSLOG_BIO_debug_callback_ex(BIO *bio, int cmd, const char __attribute__((unused)) *argp,
+ size_t __attribute__((unused)) len, int argi, long __attribute__((unused)) argl,
+ int ret, size_t __attribute__((unused)) *processed)
+#else
+static long
+RSYSLOG_BIO_debug_callback(BIO *bio, int cmd, const char __attribute__((unused)) *argp,
+ int argi, long __attribute__((unused)) argl, long ret)
+#endif
+{
+ long ret2 = ret;
+ long r = 1;
+ if (BIO_CB_RETURN & cmd)
+ r = ret;
+ dbgprintf("openssl debugmsg: BIO[%p]: ", (void *)bio);
+ switch (cmd) {
+ case BIO_CB_FREE:
+ dbgprintf("Free - %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+/* Disabled due API changes for OpenSSL 1.1.0+ */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ case BIO_CB_READ:
+ if (bio->method->type & BIO_TYPE_DESCRIPTOR)
+ dbgprintf("read(%d,%lu) - %s fd=%d\n",
+ RSYSLOG_BIO_number_read(bio), (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_read(bio));
+ else
+ dbgprintf("read(%d,%lu) - %s\n", RSYSLOG_BIO_number_read(bio),
+ (unsigned long)argi, RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_WRITE:
+ if (bio->method->type & BIO_TYPE_DESCRIPTOR)
+ dbgprintf("write(%d,%lu) - %s fd=%d\n",
+ RSYSLOG_BIO_number_written(bio), (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_written(bio));
+ else
+ dbgprintf("write(%d,%lu) - %s\n",
+ RSYSLOG_BIO_number_written(bio),
+ (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio));
+ break;
+#else
+ case BIO_CB_READ:
+ dbgprintf("read %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_WRITE:
+ dbgprintf("write %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+#endif
+ case BIO_CB_PUTS:
+ dbgprintf("puts() - %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_GETS:
+ dbgprintf("gets(%lu) - %s\n", (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_CTRL:
+ dbgprintf("ctrl(%lu) - %s\n", (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_RETURN | BIO_CB_READ:
+ dbgprintf("read return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_WRITE:
+ dbgprintf("write return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_GETS:
+ dbgprintf("gets return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_PUTS:
+ dbgprintf("puts return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_CTRL:
+ dbgprintf("ctrl return %ld\n", ret2);
+ break;
+ default:
+ dbgprintf("bio callback - unknown type (%d)\n", cmd);
+ break;
+ }
+
+ return (r);
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+// Requires at least OpenSSL v1.1.1
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-parameter"
+static int
+net_ossl_generate_cookie(SSL *ssl, unsigned char *cookie, unsigned int *cookie_len)
+{
+ unsigned char result[EVP_MAX_MD_SIZE];
+ unsigned int resultlength;
+ unsigned char *sslHello;
+ unsigned int length;
+
+ sslHello = (unsigned char *) "rsyslog";
+ length = strlen((char *)sslHello);
+
+ // Generate the cookie using SHA256 hash
+ if (!EVP_Digest(sslHello, length, result, &resultlength, EVP_sha256(), NULL)) {
+ return 0;
+ }
+
+ memcpy(cookie, result, resultlength);
+ *cookie_len = resultlength;
+ dbgprintf("net_ossl_generate_cookie: generate cookie SUCCESS\n");
+
+ return 1;
+}
+#pragma GCC diagnostic pop
+
+static int
+net_ossl_verify_cookie(SSL *ssl, const unsigned char *cookie, unsigned int cookie_len)
+{
+ unsigned char cookie_gen[EVP_MAX_MD_SIZE];
+ unsigned int cookie_gen_len;
+
+ // Generate a cookie using the same method as in net_ossl_generate_cookie
+ if (!net_ossl_generate_cookie(ssl, cookie_gen, &cookie_gen_len)) {
+ dbgprintf("net_ossl_verify_cookie: generate cookie FAILED\n");
+ return 0;
+ }
+
+ // Check if the generated cookie matches the cookie received
+ if (cookie_len == cookie_gen_len && memcmp(cookie, cookie_gen, cookie_len) == 0) {
+ dbgprintf("net_ossl_verify_cookie: compare cookie SUCCESS\n");
+ return 1;
+ }
+
+ dbgprintf("net_ossl_verify_cookie: compare cookie FAILED\n");
+ return 0;
+}
+
+static rsRetVal
+net_ossl_ctx_init_cookie(net_ossl_t *pThis)
+{
+ DEFiRet;
+ // Set our cookie generation and verification callbacks
+ SSL_CTX_set_options(pThis->ctx, SSL_OP_COOKIE_EXCHANGE);
+ SSL_CTX_set_cookie_generate_cb(pThis->ctx, net_ossl_generate_cookie);
+ SSL_CTX_set_cookie_verify_cb(pThis->ctx, net_ossl_verify_cookie);
+ RETiRet;
+}
+#endif // OPENSSL_VERSION_NUMBER >= 0x10100000L
+
+/* ------------------------------ end OpenSSL helpers ------------------------------------------*/
+
+/* ------------------------------ OpenSSL Callback set helpers ---------------------------------*/
+void
+net_ossl_set_ssl_verify_callback(SSL *pSsl, int flags)
+{
+ /* Enable certificate valid checking */
+ SSL_set_verify(pSsl, flags, net_ossl_verify_callback);
+}
+
+void
+net_ossl_set_ctx_verify_callback(SSL_CTX *pCtx, int flags)
+{
+ /* Enable certificate valid checking */
+ SSL_CTX_set_verify(pCtx, flags, net_ossl_verify_callback);
+}
+
+void
+net_ossl_set_bio_callback(BIO *conn)
+{
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+ BIO_set_callback_ex(conn, RSYSLOG_BIO_debug_callback_ex);
+#else
+ BIO_set_callback(conn, RSYSLOG_BIO_debug_callback);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10100000L
+}
+/* ------------------------------ End OpenSSL Callback set helpers -----------------------------*/
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(net_ossl) /* be sure to specify the object type also in END macro! */
+ DBGPRINTF("net_ossl_construct: [%p]\n", pThis);
+ pThis->bReportAuthErr = 1;
+ENDobjConstruct(net_ossl)
+
+/* destructor for the net_ossl object */
+BEGINobjDestruct(net_ossl) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(net_ossl)
+ DBGPRINTF("net_ossl_destruct: [%p]\n", pThis);
+ /* Free SSL obj also if we do not have a session - or are NOT in TLS mode! */
+ if (pThis->ssl != NULL) {
+ DBGPRINTF("net_ossl_destruct: [%p] FREE pThis->ssl \n", pThis);
+ SSL_free(pThis->ssl);
+ pThis->ssl = NULL;
+ }
+ if(pThis->ctx != NULL && !pThis->ctx_is_copy) {
+ SSL_CTX_free(pThis->ctx);
+ }
+ free((void*) pThis->pszCAFile);
+ free((void*) pThis->pszCRLFile);
+ free((void*) pThis->pszKeyFile);
+ free((void*) pThis->pszCertFile);
+ free((void*) pThis->pszExtraCAFiles);
+ENDobjDestruct(net_ossl)
+
+/* queryInterface function */
+BEGINobjQueryInterface(net_ossl)
+CODESTARTobjQueryInterface(net_ossl)
+ DBGPRINTF("netosslQueryInterface\n");
+ if(pIf->ifVersion != net_osslCURR_IF_VERSION) {/* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+ pIf->Construct = (rsRetVal(*)(net_ossl_t**)) net_osslConstruct;
+ pIf->Destruct = (rsRetVal(*)(net_ossl_t**)) net_osslDestruct;
+ pIf->osslCtxInit = net_ossl_osslCtxInit;
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ pIf->osslCtxInitCookie = net_ossl_ctx_init_cookie;
+#endif
+finalize_it:
+ENDobjQueryInterface(net_ossl)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(net_ossl, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(net_ossl)
+ DBGPRINTF("netosslClassExit\n");
+ /* release objects we no longer need */
+ objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME);
+ objRelease(net, LM_NET_FILENAME);
+ objRelease(glbl, CORE_COMPONENT);
+ /* shut down OpenSSL */
+ osslGlblExit();
+ENDObjClassExit(net_ossl)
+
+
+/* Initialize the net_ossl class. Must be called as the very first method
+ * before anything else is called inside this class.
+ */
+BEGINObjClassInit(net_ossl, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ DBGPRINTF("net_osslClassInit\n");
+ // request objects we use
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(net, LM_NET_FILENAME));
+ CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME));
+ // Do global TLS init stuff
+ osslGlblInit();
+ENDObjClassInit(net_ossl)
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+/* vi:set ai:
+ */
diff --git a/runtime/net_ossl.h b/runtime/net_ossl.h
new file mode 100644
index 0000000..6e8a61f
--- /dev/null
+++ b/runtime/net_ossl.h
@@ -0,0 +1,173 @@
+/* Definitions for generic OpenSSL include stuff.
+ *
+ * Copyright 2023 Andre Lorbach 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_OSSL_H
+#define INCLUDED_NET_OSSL_H
+
+/* Needed OpenSSL Includes */
+#include <openssl/ssl.h>
+#include <openssl/x509v3.h>
+#include <openssl/err.h>
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+# include <openssl/bioerr.h>
+#endif
+#include <openssl/engine.h>
+#include <openssl/rand.h>
+
+/* Internal OpenSSL defined ENUMS */
+typedef enum {
+ OSSL_AUTH_CERTNAME = 0,
+ OSSL_AUTH_CERTFINGERPRINT = 1,
+ OSSL_AUTH_CERTVALID = 2,
+ OSSL_AUTH_CERTANON = 3
+} AuthMode;
+
+typedef enum {
+ OSSL_EXPIRED_PERMIT = 0,
+ OSSL_EXPIRED_DENY = 1,
+ OSSL_EXPIRED_WARN = 2
+} PermitExpiredCerts;
+
+typedef enum {
+ osslServer = 0, /**< Server SSL Object */
+ osslClient = 1 /**< Client SSL Object */
+} osslSslState_t;
+
+/* the net_ossl object */
+struct net_ossl_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ /* Config Cert vars */
+ const uchar *pszCAFile;
+ const uchar *pszCRLFile;
+ const uchar *pszExtraCAFiles;
+ const uchar *pszKeyFile;
+ const uchar *pszCertFile;
+ AuthMode authMode;
+ permittedPeers_t *pPermPeers; /* permitted peers */
+ 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. */
+ /* Open SSL objects */
+ BIO *bio; /* OpenSSL main BIO obj */
+ int ctx_is_copy;
+ SSL_CTX *ctx; /* credentials, ciphers, ... */
+ SSL *ssl; /* OpenSSL main SSL obj */
+ osslSslState_t sslState;/**< what must we retry? */
+};
+
+/* interface */
+BEGINinterface(net_ossl) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(net_ossl_t **ppThis);
+ rsRetVal (*Destruct)(net_ossl_t **ppThis);
+ rsRetVal (*osslCtxInit)(net_ossl_t *pThis, const SSL_METHOD *method);
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ rsRetVal (*osslCtxInitCookie)(net_ossl_t *pThis);
+#endif // OPENSSL_VERSION_NUMBER >= 0x10100000L
+ENDinterface(net_ossl)
+
+#define net_osslCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+// ------------------------------------------------------
+
+/* OpenSSL API differences */
+#if OPENSSL_VERSION_NUMBER >= 0x10100000L
+ #define RSYSLOG_X509_NAME_oneline(X509CERT) X509_get_subject_name(X509CERT)
+ #define RSYSLOG_BIO_method_name(SSLBIO) BIO_method_name(SSLBIO)
+ #define RSYSLOG_BIO_number_read(SSLBIO) BIO_number_read(SSLBIO)
+ #define RSYSLOG_BIO_number_written(SSLBIO) BIO_number_written(SSLBIO)
+#else
+ #define RSYSLOG_X509_NAME_oneline(X509CERT) (X509CERT != NULL ? X509CERT->cert_info->subject : NULL)
+ #define RSYSLOG_BIO_method_name(SSLBIO) SSLBIO->method->name
+ #define RSYSLOG_BIO_number_read(SSLBIO) SSLBIO->num
+ #define RSYSLOG_BIO_number_written(SSLBIO) SSLBIO->num
+#endif
+
+/*-----------------------------------------------------------------------------*/
+/* OpenSSL Global Helper functions prototypes */
+#define MUTEX_TYPE pthread_mutex_t
+#define MUTEX_SETUP(x) pthread_mutex_init(&(x), NULL)
+#define MUTEX_CLEANUP(x) pthread_mutex_destroy(&(x))
+#define MUTEX_LOCK(x) pthread_mutex_lock(&(x))
+#define MUTEX_UNLOCK(x) pthread_mutex_unlock(&(x))
+#define THREAD_ID pthread_self()
+
+/* This array will store all of the mutexes available to OpenSSL. */
+struct CRYPTO_dynlock_value
+{
+ MUTEX_TYPE mutex;
+};
+
+void dyn_destroy_function(struct CRYPTO_dynlock_value *l,
+ __attribute__((unused)) const char *file, __attribute__((unused)) int line);
+void dyn_lock_function(int mode, struct CRYPTO_dynlock_value *l,
+ __attribute__((unused)) const char *file, __attribute__((unused)) int line);
+struct CRYPTO_dynlock_value * dyn_create_function(
+ __attribute__((unused)) const char *file, __attribute__((unused)) int line);
+unsigned long id_function(void);
+void locking_function(int mode, int n,
+ __attribute__((unused)) const char * file, __attribute__((unused)) int line);
+
+int opensslh_THREAD_setup(void);
+int opensslh_THREAD_cleanup(void);
+
+void osslGlblInit(void);
+void osslGlblExit(void);
+
+/*-----------------------------------------------------------------------------*/
+
+/* Prototypes for openssl helper functions */
+__attribute__((visibility("default"))) void net_ossl_lastOpenSSLErrorMsg
+ (uchar *fromHost, const int ret, SSL *ssl, int severity, const char* pszCallSource, const char* pszOsslApi);
+__attribute__((visibility("default"))) void net_ossl_set_ssl_verify_callback(SSL *pSsl, int flags);
+__attribute__((visibility("default"))) void net_ossl_set_ctx_verify_callback(SSL_CTX *pCtx, int flags);
+__attribute__((visibility("default"))) void net_ossl_set_bio_callback(BIO *conn);
+__attribute__((visibility("default"))) int net_ossl_verify_callback(int status, X509_STORE_CTX *store);
+__attribute__((visibility("default"))) rsRetVal net_ossl_apply_tlscgfcmd(net_ossl_t *pThis, uchar *tlscfgcmd);
+__attribute__((visibility("default"))) rsRetVal
+ net_ossl_chkpeercertvalidity(net_ossl_t *pThis, SSL *ssl, uchar *fromHostIP);
+__attribute__((visibility("default"))) X509*
+ net_ossl_getpeercert(net_ossl_t *pThis, SSL *ssl, uchar *fromHostIP);
+__attribute__((visibility("default"))) rsRetVal
+ net_ossl_peerfingerprint(net_ossl_t *pThis, X509* certpeer, uchar *fromHostIP);
+__attribute__((visibility("default"))) rsRetVal
+ net_ossl_chkpeername(net_ossl_t *pThis, X509* certpeer, uchar *fromHostIP);
+
+/*
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+long RSYSLOG_BIO_debug_callback_ex(BIO *bio, int cmd, const char __attribute__((unused)) *argp,
+ size_t __attribute__((unused)) len, int argi, long __attribute__((unused)) argl,
+ int ret, size_t __attribute__((unused)) *processed);
+#else
+long RSYSLOG_BIO_debug_callback(BIO *bio, int cmd, const char __attribute__((unused)) *argp,
+ int argi, long __attribute__((unused)) argl, long ret);
+#endif
+*/
+
+/* prototypes */
+PROTOTYPEObj(net_ossl);
+
+/* the name of our library binary */
+// #define LM_NET_OSSL_FILENAME "lmnet_ossl"
+#define LM_NET_OSSL_FILENAME "lmnsd_ossl"
+
+
+#endif /* #ifndef INCLUDED_NET_OSSL_H */
diff --git a/runtime/netstrm.c b/runtime/netstrm.c
new file mode 100644
index 0000000..d20957e
--- /dev/null
+++ b/runtime/netstrm.c
@@ -0,0 +1,508 @@
+/* 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).
+ *
+ * Note on processing:
+ * - Initiating a listener may be driver-specific, but in regard to TLS/non-TLS
+ * it actually is not. This is because TLS is negotiated after a connection
+ * has been established. So it is the "acceptConnReq" driver entry where TLS
+ * params need to be applied.
+ *
+ * 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-2020 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(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)
+ if(pThis->pDrvrData != NULL)
+ iRet = pThis->Drvr.Destruct(&pThis->pDrvrData);
+ENDobjDestruct(netstrm)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+netstrmConstructFinalize(netstrm_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ return pThis->Drvr.Construct(&pThis->pDrvrData);
+}
+
+/* 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 ATTR_NONNULL(1,3,5)
+LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ const int iSessMax, const tcpLstnParams_t *const cnf_params)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pNS, netstrms);
+ assert(fAddLstn != NULL);
+ assert(cnf_params->pszPort != NULL);
+
+ CHKiRet(pNS->Drvr.LstnInit(pNS, pUsr, fAddLstn, iSessMax, cnf_params));
+
+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 occurred and
+ * oserr holds the exact error cause.
+ * rgerhards, 2008-03-17
+ */
+static rsRetVal
+Rcv(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf, int *const oserr)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.Rcv(pThis->pDrvrData, pBuf, pLenBuf, oserr);
+ 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 permitexpiredcerts mode -- alorbach, 2018-12-20
+ */
+static rsRetVal
+SetDrvrPermitExpiredCerts(netstrm_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetPermitExpiredCerts(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;
+}
+
+/* Mandate also verification of Extended key usage / purpose field */
+static rsRetVal
+SetDrvrCheckExtendedKeyUsage(netstrm_t *pThis, int ChkExtendedKeyUsage)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetCheckExtendedKeyUsage(pThis->pDrvrData, ChkExtendedKeyUsage);
+ RETiRet;
+}
+
+/* Mandate stricter name checking per RFC 6125 - ignoce CN if any SAN present */
+static rsRetVal
+SetDrvrPrioritizeSAN(netstrm_t *pThis, int prioritizeSan)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetPrioritizeSAN(pThis->pDrvrData, prioritizeSan);
+ RETiRet;
+}
+
+/* tls verify depth */
+static rsRetVal
+SetDrvrTlsVerifyDepth(netstrm_t *pThis, int verifyDepth)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetTlsVerifyDepth(pThis->pDrvrData, verifyDepth);
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsCAFile(netstrm_t *const pThis, const uchar *const file)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetTlsCAFile(pThis->pDrvrData, file);
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsCRLFile(netstrm_t *const pThis, const uchar *const file)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetTlsCRLFile(pThis->pDrvrData, file);
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsKeyFile(netstrm_t *const pThis, const uchar *const file)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetTlsKeyFile(pThis->pDrvrData, file);
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsCertFile(netstrm_t *const pThis, const uchar *const file)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetTlsCertFile(pThis->pDrvrData, file);
+ 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;
+}
+
+/* Keep-Alive options
+ */
+static rsRetVal
+SetKeepAliveProbes(netstrm_t *pThis, int keepAliveProbes)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetKeepAliveProbes(pThis->pDrvrData, keepAliveProbes);
+ RETiRet;
+}
+
+/* Keep-Alive options
+ */
+static rsRetVal
+SetKeepAliveTime(netstrm_t *pThis, int keepAliveTime)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetKeepAliveTime(pThis->pDrvrData, keepAliveTime);
+ RETiRet;
+}
+
+/* Keep-Alive options
+ */
+static rsRetVal
+SetKeepAliveIntvl(netstrm_t *pThis, int keepAliveIntvl)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetKeepAliveIntvl(pThis->pDrvrData, keepAliveIntvl);
+ RETiRet;
+}
+
+/* gnutls priority string */
+static rsRetVal
+SetGnutlsPriorityString(netstrm_t *pThis, uchar *gnutlsPriorityString)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ iRet = pThis->Drvr.SetGnutlsPriorityString(pThis->pDrvrData, gnutlsPriorityString);
+ 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, char *device)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrm);
+ assert(port != NULL);
+ assert(host != NULL);
+ iRet = pThis->Drvr.Connect(pThis->pDrvrData, family, port, host, device);
+ 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->SetDrvrPermitExpiredCerts = SetDrvrPermitExpiredCerts;
+ pIf->SetDrvrPermPeers = SetDrvrPermPeers;
+ pIf->CheckConnection = CheckConnection;
+ pIf->GetSock = GetSock;
+ pIf->EnableKeepAlive = EnableKeepAlive;
+ pIf->SetKeepAliveProbes = SetKeepAliveProbes;
+ pIf->SetKeepAliveTime = SetKeepAliveTime;
+ pIf->SetKeepAliveIntvl = SetKeepAliveIntvl;
+ pIf->SetGnutlsPriorityString = SetGnutlsPriorityString;
+ pIf->SetDrvrCheckExtendedKeyUsage = SetDrvrCheckExtendedKeyUsage;
+ pIf->SetDrvrPrioritizeSAN = SetDrvrPrioritizeSAN;
+ pIf->SetDrvrTlsVerifyDepth = SetDrvrTlsVerifyDepth;
+ pIf->SetDrvrTlsCAFile = SetDrvrTlsCAFile;
+ pIf->SetDrvrTlsCRLFile = SetDrvrTlsCRLFile;
+ pIf->SetDrvrTlsKeyFile = SetDrvrTlsKeyFile;
+ pIf->SetDrvrTlsCertFile = SetDrvrTlsCertFile;
+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(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 */
+
+ /* set our own handlers */
+ENDObjClassInit(netstrm)
+/* vi:set ai:
+ */
diff --git a/runtime/netstrm.h b/runtime/netstrm.h
new file mode 100644
index 0000000..4b1d62c
--- /dev/null
+++ b/runtime/netstrm.h
@@ -0,0 +1,114 @@
+/* Definitions for the stream-based netstrmworking class.
+ *
+ * Copyright 2007-2020 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 "tcpsrv.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 */
+ uchar *pszDrvrAuthMode; /**< auth mode of the stream driver to use */
+ 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 (*AcceptConnReq)(netstrm_t *pThis, netstrm_t **ppNew);
+ rsRetVal (*Rcv)(netstrm_t *pThis, uchar *pRcvBuf, ssize_t *pLenBuf, int *oserr);
+ rsRetVal (*Send)(netstrm_t *pThis, uchar *pBuf, ssize_t *pLenBuf);
+ rsRetVal (*Connect)(netstrm_t *pThis, int family, unsigned char *port, unsigned char *host, char *device);
+ 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 (*SetDrvrPermitExpiredCerts)(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);
+ /* v7 */
+ rsRetVal (*SetKeepAliveProbes)(netstrm_t *pThis, int keepAliveProbes);
+ rsRetVal (*SetKeepAliveTime)(netstrm_t *pThis, int keepAliveTime);
+ rsRetVal (*SetKeepAliveIntvl)(netstrm_t *pThis, int keepAliveIntvl);
+ rsRetVal (*SetGnutlsPriorityString)(netstrm_t *pThis, uchar *priorityString);
+ /* v11 -- Parameter pszLstnFileName added to LstnInit*/
+ rsRetVal (ATTR_NONNULL(1,3,5) *LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*)(void*,netstrm_t*),
+ const int iSessMax, const tcpLstnParams_t *const cnf_params);
+ /* v12 -- two new binary flags added to gtls driver enabling stricter operation */
+ rsRetVal (*SetDrvrCheckExtendedKeyUsage)(netstrm_t *pThis, int ChkExtendedKeyUsage);
+ rsRetVal (*SetDrvrPrioritizeSAN)(netstrm_t *pThis, int prioritizeSan);
+
+ /* v14 -- Tls functions */
+ rsRetVal (*SetDrvrTlsVerifyDepth)(netstrm_t *pThis, int verifyDepth);
+
+ /* v15 -- Tls cert functions */
+ rsRetVal (*SetDrvrTlsCAFile)(netstrm_t *pThis, const uchar* file);
+ rsRetVal (*SetDrvrTlsCRLFile)(netstrm_t *pThis, const uchar* file);
+ rsRetVal (*SetDrvrTlsKeyFile)(netstrm_t *pThis, const uchar* file);
+ rsRetVal (*SetDrvrTlsCertFile)(netstrm_t *pThis, const uchar* file);
+ENDinterface(netstrm)
+#define netstrmCURR_IF_VERSION 16 /* 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
+ * interface version 7 added KeepAlive parameter set functions
+ * interface version 8 changed signature of Connect() -- dsa, 2016-11-14
+ * interface version 9 added SetGnutlsPriorityString -- PascalWithopf, 2017-08-08
+ * interface version 10 added oserr parameter to Rcv() -- rgerhards, 2017-09-04
+ * interface version 16 CRL file -- Oracle, 2022-01-16
+ * */
+
+/* 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 0000000..74795ff
--- /dev/null
+++ b/runtime/netstrms.c
@@ -0,0 +1,553 @@
+/* netstrms.c
+ *
+ * Work on this module begung 2008-04-23 by Rainer Gerhards.
+ *
+ * Copyright 2008-2021 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"
+#include "rsconf.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+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(runConf);
+ 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->pszDrvrPermitExpiredCerts != NULL) {
+ free(pThis->pszDrvrPermitExpiredCerts);
+ pThis->pszDrvrPermitExpiredCerts = NULL;
+ }
+ free((void*)pThis->pszDrvrCAFile);
+ pThis->pszDrvrCAFile = NULL;
+ free((void*)pThis->pszDrvrCRLFile);
+ pThis->pszDrvrCRLFile = NULL;
+ free((void*)pThis->pszDrvrKeyFile);
+ pThis->pszDrvrKeyFile = NULL;
+ free((void*)pThis->pszDrvrCertFile);
+ pThis->pszDrvrCertFile = NULL;
+ if(pThis->pBaseDrvrName != NULL) {
+ free(pThis->pBaseDrvrName);
+ pThis->pBaseDrvrName = NULL;
+ }
+ if(pThis->gnutlsPriorityString != NULL) {
+ free(pThis->gnutlsPriorityString);
+ pThis->gnutlsPriorityString = NULL;
+ }
+ENDobjDestruct(netstrms)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+netstrmsConstructFinalize(netstrms_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ iRet = loadDrvr(pThis);
+ 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;
+}
+
+
+/* return the driver permitexpiredcerts mode
+ * We use non-standard calling conventions because it makes an awful lot
+ * of sense here.
+ * alorbach, 2018-12-21
+ */
+static uchar*
+GetDrvrPermitExpiredCerts(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->pszDrvrPermitExpiredCerts;
+}
+
+/* set the driver permitexpiredcerts mode -- alorbach, 2018-12-20
+ */
+static rsRetVal
+SetDrvrPermitExpiredCerts(netstrms_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrPermitExpiredCerts = (uchar*) strdup((char*)mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsCAFile(netstrms_t *pThis, const uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrCAFile = (uchar*) strdup((char*)mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsCRLFile(netstrms_t *pThis, const uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrCRLFile = (uchar*) strdup((char*)mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsKeyFile(netstrms_t *pThis, const uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrKeyFile = (uchar*) strdup((char*)mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrTlsCertFile(netstrms_t *pThis, const uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrCertFile = (uchar*) strdup((char*)mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set the priorityString for GnuTLS
+ * PascalWithopf 2017-08-16
+ */
+static rsRetVal
+SetDrvrGnutlsPriorityString(netstrms_t *pThis, uchar *iVal)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ CHKmalloc(pThis->gnutlsPriorityString = (uchar*)strdup((char*)iVal));
+finalize_it:
+ RETiRet;
+}
+
+
+/* return the priorityString for GnuTLS
+ * PascalWithopf, 2017-08-16
+ */
+static uchar*
+GetDrvrGnutlsPriorityString(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->gnutlsPriorityString;
+}
+
+
+/* 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;
+}
+
+
+/* set the driver cert extended key usage check setting -- jvymazal, 2019-08-16 */
+static rsRetVal
+SetDrvrCheckExtendedKeyUsage(netstrms_t *pThis, int ChkExtendedKeyUsage)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ pThis->DrvrChkExtendedKeyUsage = ChkExtendedKeyUsage;
+ RETiRet;
+}
+
+
+/* return the driver cert extended key usage check setting
+ * jvymazal, 2019-08-16
+ */
+static int
+GetDrvrCheckExtendedKeyUsage(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->DrvrChkExtendedKeyUsage;
+}
+
+
+/* set the driver name checking policy -- jvymazal, 2019-08-16 */
+static rsRetVal
+SetDrvrPrioritizeSAN(netstrms_t *pThis, int prioritizeSan)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ pThis->DrvrPrioritizeSan = prioritizeSan;
+ RETiRet;
+}
+
+
+/* return the driver name checking policy
+ * jvymazal, 2019-08-16
+ */
+static int
+GetDrvrPrioritizeSAN(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->DrvrPrioritizeSan;
+}
+
+/* set the driver TlsVerifyDepth -- alorbach, 2019-12-20 */
+static rsRetVal
+SetDrvrTlsVerifyDepth(netstrms_t *pThis, int verifyDepth)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ pThis->DrvrVerifyDepth = verifyDepth;
+ RETiRet;
+}
+
+/* return the driver TlsVerifyDepth
+ * alorbach, 2019-12-20
+ */
+static int
+GetDrvrTlsVerifyDepth(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->DrvrVerifyDepth;
+}
+
+static const uchar *
+GetDrvrTlsCAFile(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->pszDrvrCAFile;
+}
+static const uchar *
+GetDrvrTlsCRLFile(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->pszDrvrCRLFile;
+}
+static const uchar *
+GetDrvrTlsKeyFile(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->pszDrvrKeyFile;
+}
+static const uchar *
+GetDrvrTlsCertFile(netstrms_t *pThis)
+{
+ ISOBJ_TYPE_assert(pThis, netstrms);
+ return pThis->pszDrvrCertFile;
+}
+
+/* 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->SetDrvrPermitExpiredCerts = SetDrvrPermitExpiredCerts;
+ pIf->GetDrvrPermitExpiredCerts = GetDrvrPermitExpiredCerts;
+ pIf->SetDrvrGnutlsPriorityString = SetDrvrGnutlsPriorityString;
+ pIf->GetDrvrGnutlsPriorityString = GetDrvrGnutlsPriorityString;
+ pIf->SetDrvrPermPeers = SetDrvrPermPeers;
+ pIf->GetDrvrPermPeers = GetDrvrPermPeers;
+ pIf->SetDrvrCheckExtendedKeyUsage = SetDrvrCheckExtendedKeyUsage;
+ pIf->GetDrvrCheckExtendedKeyUsage = GetDrvrCheckExtendedKeyUsage;
+ pIf->SetDrvrPrioritizeSAN = SetDrvrPrioritizeSAN;
+ pIf->GetDrvrPrioritizeSAN = GetDrvrPrioritizeSAN;
+ pIf->SetDrvrTlsVerifyDepth = SetDrvrTlsVerifyDepth;
+ pIf->GetDrvrTlsVerifyDepth = GetDrvrTlsVerifyDepth;
+ pIf->GetDrvrTlsCAFile = GetDrvrTlsCAFile;
+ pIf->GetDrvrTlsCRLFile = GetDrvrTlsCRLFile;
+ pIf->GetDrvrTlsKeyFile = GetDrvrTlsKeyFile;
+ pIf->GetDrvrTlsCertFile = GetDrvrTlsCertFile;
+ pIf->SetDrvrTlsCAFile = SetDrvrTlsCAFile;
+ pIf->SetDrvrTlsCRLFile = SetDrvrTlsCRLFile;
+ pIf->SetDrvrTlsKeyFile = SetDrvrTlsKeyFile;
+ pIf->SetDrvrTlsCertFile = SetDrvrTlsCertFile;
+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
diff --git a/runtime/netstrms.h b/runtime/netstrms.h
new file mode 100644
index 0000000..203ad22
--- /dev/null
+++ b/runtime/netstrms.h
@@ -0,0 +1,94 @@
+/* Definitions for the stream-based netstrmsworking class.
+ *
+ * Copyright 2007-2021 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 */
+ int DrvrChkExtendedKeyUsage; /**< if true, verify extended key usage in certs */
+ int DrvrPrioritizeSan; /**< if true, perform stricter checking of names in certs */
+ int DrvrVerifyDepth; /**< Verify Depth for certificate chains */
+ uchar *pszDrvrPermitExpiredCerts;
+ const uchar *pszDrvrCAFile;
+ const uchar *pszDrvrCRLFile;
+ const uchar *pszDrvrKeyFile;
+ const uchar *pszDrvrCertFile;
+ uchar *gnutlsPriorityString; /**< priorityString for connection */
+ permittedPeers_t *pPermPeers;/**< current driver's permitted peers */
+ rsRetVal(*fLstnInitDrvr)(netstrm_t*); /**< "late" driver-specific lstn init function NULL if none */
+
+ 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 (*SetDrvrPermitExpiredCerts)(netstrms_t *pThis, uchar*);
+ rsRetVal (*SetDrvrPermPeers)(netstrms_t *pThis, permittedPeers_t*);
+ int (*GetDrvrMode)(netstrms_t *pThis);
+ uchar* (*GetDrvrAuthMode)(netstrms_t *pThis);
+ uchar* (*GetDrvrPermitExpiredCerts)(netstrms_t *pThis);
+ permittedPeers_t* (*GetDrvrPermPeers)(netstrms_t *pThis);
+ rsRetVal (*SetDrvrGnutlsPriorityString)(netstrms_t *pThis, uchar*);
+ uchar* (*GetDrvrGnutlsPriorityString)(netstrms_t *pThis);
+ rsRetVal (*SetDrvrCheckExtendedKeyUsage)(netstrms_t *pThis, int ChkExtendedKeyUsage);
+ int (*GetDrvrCheckExtendedKeyUsage)(netstrms_t *pThis);
+ rsRetVal (*SetDrvrPrioritizeSAN)(netstrms_t *pThis, int prioritizeSan);
+ int (*GetDrvrPrioritizeSAN)(netstrms_t *pThis);
+ rsRetVal (*SetDrvrTlsVerifyDepth)(netstrms_t *pThis, int verifyDepth);
+ int (*GetDrvrTlsVerifyDepth)(netstrms_t *pThis);
+ /* v2 */
+ rsRetVal (*SetDrvrTlsCAFile)(netstrms_t *pThis, const uchar *);
+ const uchar* (*GetDrvrTlsCAFile)(netstrms_t *pThis);
+ rsRetVal (*SetDrvrTlsKeyFile)(netstrms_t *pThis, const uchar *);
+ const uchar* (*GetDrvrTlsKeyFile)(netstrms_t *pThis);
+ rsRetVal (*SetDrvrTlsCertFile)(netstrms_t *pThis, const uchar *);
+ const uchar* (*GetDrvrTlsCertFile)(netstrms_t *pThis);
+ /* v3 */
+ rsRetVal (*SetDrvrTlsCRLFile)(netstrms_t *pThis, const uchar *);
+ const uchar* (*GetDrvrTlsCRLFile)(netstrms_t *pThis);
+ENDinterface(netstrms)
+#define netstrmsCURR_IF_VERSION 3 /* 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 0000000..03df7d2
--- /dev/null
+++ b/runtime/nsd.h
@@ -0,0 +1,134 @@
+/* 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, int *oserr);
+ rsRetVal (*Send)(nsd_t *pThis, uchar *pBuf, ssize_t *pLenBuf);
+ rsRetVal (*Connect)(nsd_t *pThis, int family, unsigned char *port, unsigned char *host, char *device);
+ 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 (*SetPermitExpiredCerts)(nsd_t *pThis, uchar*); /* sets a driver specific permitexpiredcerts mode */
+ 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);
+ /* v8 */
+ rsRetVal (*SetKeepAliveIntvl)(nsd_t *pThis, int keepAliveIntvl);
+ rsRetVal (*SetKeepAliveProbes)(nsd_t *pThis, int keepAliveProbes);
+ rsRetVal (*SetKeepAliveTime)(nsd_t *pThis, int keepAliveTime);
+ rsRetVal (*SetGnutlsPriorityString)(nsd_t *pThis, uchar *gnutlsPriorityString);
+ /* v12 -- parameter pszLstnPortFileName added to LstnInit()*/
+ rsRetVal (ATTR_NONNULL(1,3,5) *LstnInit)(netstrms_t *pNS, void *pUsr, rsRetVal(*)(void*,netstrm_t*),
+ const int iSessMax, const tcpLstnParams_t *const cnf_params);
+ /* v13 -- two new binary flags added to gtls driver enabling stricter operation */
+ rsRetVal (*SetCheckExtendedKeyUsage)(nsd_t *pThis, int ChkExtendedKeyUsage);
+ rsRetVal (*SetPrioritizeSAN)(nsd_t *pThis, int prioritizeSan);
+
+ /* v14 -- Tls functions */
+ rsRetVal (*SetTlsVerifyDepth)(nsd_t *pThis, int verifyDepth);
+
+ /* v15 -- Tls functions */
+ rsRetVal (*SetTlsCAFile)(nsd_t *pThis, const uchar *);
+ rsRetVal (*SetTlsKeyFile)(nsd_t *pThis, const uchar *);
+ rsRetVal (*SetTlsCertFile)(nsd_t *pThis, const uchar *);
+
+ /* v16 - Tls CRL */
+ rsRetVal (*SetTlsCRLFile)(nsd_t *pThis, const uchar *);
+ENDinterface(nsd)
+#define nsdCURR_IF_VERSION 16 /* 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 version 8 added keep alive parameter set functions
+ * interface version 9 changed signature of Connect() -- dsa, 2016-11-14
+ * interface version 10 added SetGnutlsPriorityString() -- PascalWithopf, 2017-08-08
+ * interface version 11 added oserr to Rcv() signature -- rgerhards, 2017-09-04
+ */
+
+/* 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 0000000..b9c0f8a
--- /dev/null
+++ b/runtime/nsd_gtls.c
@@ -0,0 +1,2451 @@
+/* nsd_gtls.c
+ *
+ * An implementation of the nsd interface for GnuTLS.
+ *
+ * Copyright (C) 2007-2021 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 "netstrm.h"
+#include "netstrms.h"
+#include "nsd_ptcp.h"
+#include "nsdsel_gtls.h"
+#include "nsd_gtls.h"
+#include "unicode-helper.h"
+#include "rsconf.h"
+
+#if GNUTLS_VERSION_NUMBER <= 0x020b00
+GCRY_THREAD_OPTION_PTHREAD_IMPL;
+#endif
+MODULE_TYPE_LIB
+MODULE_TYPE_KEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(net)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(nsd_ptcp)
+
+/* Static Helper variables for certless communication */
+static gnutls_anon_client_credentials_t anoncred; /**< client anon credentials */
+static gnutls_anon_server_credentials_t anoncredSrv; /**< server anon credentials */
+static int dhBits = 2048; /**< number of bits for Diffie-Hellman key */
+static int dhMinBits = 512; /**< minimum number of bits for Diffie-Hellman key */
+
+static pthread_mutex_t mutGtlsStrerror;
+/*< a mutex protecting the potentially non-reentrant gtlStrerror() function */
+
+static gnutls_dh_params_t dh_params; /**< server DH parameters for anon mode */
+
+/* a macro to abort if GnuTLS error is not acceptable. We split this off from
+ * CHKgnutls() to avoid some Coverity report in cases where we know GnuTLS
+ * failed. Note: gnuRet must already be set accordingly!
+ */
+#define ABORTgnutls { \
+ uchar *pErr = gtlsStrerror(gnuRet); \
+ LogError(0, RS_RET_GNUTLS_ERR, "unexpected GnuTLS error %d in %s:%d: %s\n", \
+ gnuRet, __FILE__, __LINE__, pErr); \
+ free(pErr); \
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \
+}
+/* a macro to check GnuTLS calls against unexpected errors */
+#define CHKgnutls(x) { \
+ gnuRet = (x); \
+ if(gnuRet == GNUTLS_E_FILE_ERROR) { \
+ LogError(0, RS_RET_GNUTLS_ERR, "error reading file - a common cause is that the " \
+ "file does not exist"); \
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR); \
+ } else if(gnuRet != 0) { \
+ ABORTgnutls; \
+ } \
+}
+
+
+/* ------------------------------ GnuTLS specifics ------------------------------ */
+
+/* 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);
+}
+
+/* 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(const uchar *const pszFile, gnutls_datum_t *const 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) {
+ LogError(errno, RS_RET_FILE_NOT_FOUND, "can not read file '%s'", pszFile);
+ ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
+ }
+
+ if(fstat(fd, &stat_st) == -1) {
+ LogError(errno, 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) {
+ 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) {
+ LogError(0, RS_RET_IO_ERROR, "error or incomplete read of file '%s'", pszFile);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+finalize_it:
+ if(fd != -1)
+ close(fd);
+ 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 };
+ const uchar *keyFile;
+ const uchar *certFile;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ certFile = (pThis->pszCertFile == NULL) ? glbl.GetDfltNetstrmDrvrCertFile(runConf) : pThis->pszCertFile;
+ keyFile = (pThis->pszKeyFile == NULL) ? glbl.GetDfltNetstrmDrvrKeyFile(runConf) : pThis->pszKeyFile;
+
+ 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("gtlsLoadOurCertKey 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));
+ pThis->nOurCerts = sizeof(pThis->pOurCerts) / sizeof(gnutls_x509_crt_t);
+ gnuRet = gnutls_x509_crt_list_import(pThis->pOurCerts, &pThis->nOurCerts,
+ &data, GNUTLS_X509_FMT_PEM, GNUTLS_X509_CRT_LIST_IMPORT_FAIL_IF_EXCEED);
+ if(gnuRet < 0) {
+ ABORTgnutls;
+ }
+ pThis->bOurCertIsInit = 1;
+ 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_CERTLESS) {
+ dbgprintf("gtlsLoadOurCertKey certless exit\n");
+ pThis->bOurCertIsInit = 0;
+ pThis->bOurKeyIsInit = 0;
+ } else if(iRet != RS_RET_OK) {
+ dbgprintf("gtlsLoadOurCertKey error exit\n");
+ if(data.data != NULL)
+ free(data.data);
+ if(pThis->bOurCertIsInit) {
+ for(unsigned i=0; i<pThis->nOurCerts; ++i) {
+ gnutls_x509_crt_deinit(pThis->pOurCerts[i]);
+ }
+ pThis->bOurCertIsInit = 0;
+ }
+ if(pThis->bOurKeyIsInit) {
+ gnutls_x509_privkey_deinit(pThis->ourKey);
+ pThis->bOurKeyIsInit = 0;
+ }
+ } else {
+ dbgprintf("gtlsLoadOurCertKey Successfully Loaded cert '%s' and key: '%s'\n", certFile, keyFile);
+ }
+ 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_t session,
+ __attribute__((unused)) const gnutls_datum_t* req_ca_rdn,
+ int __attribute__((unused)) nreqs,
+ __attribute__((unused)) const gnutls_pk_algorithm_t* sign_algos,
+ int __attribute__((unused)) sign_algos_length,
+#if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION
+ gnutls_retr2_st* st
+#else
+ gnutls_retr_st *st
+#endif
+ )
+{
+ nsd_gtls_t *pThis;
+
+ pThis = (nsd_gtls_t*) gnutls_session_get_ptr(session);
+
+#if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION
+ st->cert_type = GNUTLS_CRT_X509;
+#else
+ st->type = GNUTLS_CRT_X509;
+#endif
+ st->ncerts = pThis->nOurCerts;
+ st->cert.x509 = pThis->pOurCerts;
+ 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 *const 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_t *cert_list;
+ unsigned cert_list_size = 0;
+ gnutls_x509_crt_t 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, (char*)szBuf);
+ szBuf[ustrlen(szBuf) - 1] = '\0'; /* strip linefeed */
+ CHKiRet(rsCStrAppendStrf(pStr, "Certificate 1 info: "
+ "certificate valid from %s ", szBuf));
+ ctime_r(&expiration_time, (char*)szBuf);
+ szBuf[ustrlen(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, (char*)szBuf, &tmp)
+ == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ szBufLen = tmp;
+ szBuf = malloc(tmp);
+ gnutls_x509_crt_get_dn(cert, (char*)szBuf, &tmp);
+ }
+ CHKiRet(rsCStrAppendStrf(pStr, "DN: %s; ", szBuf));
+
+ tmp = szBufLen;
+ if(gnutls_x509_crt_get_issuer_dn(cert, (char*)szBuf, &tmp)
+ == GNUTLS_E_SHORT_MEMORY_BUFFER) {
+ szBufLen = tmp;
+ szBuf = realloc((szBuf == szBufA) ? NULL : szBuf, tmp);
+ gnutls_x509_crt_get_issuer_dn(cert, (char*)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);
+ }
+
+ 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, const char* prefix)
+{
+ cstr_t *pStr = NULL;
+ uchar buf[4];
+ size_t i;
+ DEFiRet;
+
+ CHKiRet(rsCStrConstruct(&pStr));
+ CHKiRet(rsCStrAppendStrWithLen(pStr, (uchar*) prefix, strlen(prefix)));
+ for(i = 0 ; i < sizeFingerprint ; ++i) {
+ snprintf((char*)buf, sizeof(buf), ":%2.2X", pFingerprint[i]);
+ CHKiRet(rsCStrAppendStrWithLen(pStr, buf, 3));
+ }
+ 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);
+ DBGPRINTF("gtlsRecordRecv: start (Pending Data: %zd | Wanted Direction: %s)\n",
+ gnutls_record_check_pending(pThis->sess),
+ (gnutls_record_get_direction(pThis->sess) == gtlsDir_READ ? "READ" : "WRITE") );
+ lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF);
+ if(lenRcvd >= 0) {
+ DBGPRINTF("gtlsRecordRecv: gnutls_record_recv received %zd bytes\n", lenRcvd);
+ pThis->lenRcvBuf = lenRcvd;
+ pThis->ptrRcvBuf = 0;
+
+ /* Check for additional data in SSL buffer */
+ size_t stBytesLeft = gnutls_record_check_pending(pThis->sess);
+ if (stBytesLeft > 0 ){
+ DBGPRINTF("gtlsRecordRecv: %zd Bytes pending after gnutls_record_recv, expand buffer.\n",
+ stBytesLeft);
+ /* realloc buffer size and preserve char content */
+ char *const newbuf = realloc(pThis->pszRcvBuf, NSD_GTLS_MAX_RCVBUF+stBytesLeft);
+ CHKmalloc(newbuf);
+ pThis->pszRcvBuf = newbuf;
+
+ /* 2nd read will read missing bytes from the current SSL Packet */
+ lenRcvd = gnutls_record_recv(pThis->sess, pThis->pszRcvBuf+NSD_GTLS_MAX_RCVBUF, stBytesLeft);
+ if(lenRcvd > 0) {
+ DBGPRINTF("gtlsRecordRecv: 2nd SSL_read received %zd bytes\n",
+ (NSD_GTLS_MAX_RCVBUF+lenRcvd));
+ pThis->lenRcvBuf = NSD_GTLS_MAX_RCVBUF+lenRcvd;
+ } else {
+ if (lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) {
+ goto sslerragain; /* Go to ERR AGAIN handling */
+ } else {
+ /* Do all other error handling */
+ int gnuRet = lenRcvd;
+ ABORTgnutls;
+ }
+ }
+ }
+ } else if(lenRcvd == GNUTLS_E_AGAIN || lenRcvd == GNUTLS_E_INTERRUPTED) {
+sslerragain:
+ /* Check if the underlaying file descriptor needs to read or write data!*/
+ if (gnutls_record_get_direction(pThis->sess) == gtlsDir_READ) {
+ 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 {
+ uchar *pErr = gtlsStrerror(lenRcvd);
+ LogError(0, RS_RET_GNUTLS_ERR, "GnuTLS receive error %zd has wrong read direction(wants write) "
+ "- this could be caused by a broken connection. GnuTLS reports: %s\n",
+ lenRcvd, pErr);
+ free(pErr);
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ }
+ } else {
+ int gnuRet = lenRcvd;
+ ABORTgnutls;
+ }
+
+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(nsd_gtls_t *const pThis)
+{
+ int gnuRet = 0;
+ const uchar *keyFile;
+ const uchar *certFile;
+ uchar *pGnuErr; /* for GnuTLS error reporting */
+ DEFiRet;
+
+ certFile = (pThis->pszCertFile == NULL) ? glbl.GetDfltNetstrmDrvrCertFile(runConf) : pThis->pszCertFile;
+ keyFile = (pThis->pszKeyFile == NULL) ? glbl.GetDfltNetstrmDrvrKeyFile(runConf) : pThis->pszKeyFile;
+ dbgprintf("GTLS certificate file: '%s'\n", certFile);
+ dbgprintf("GTLS key file: '%s'\n", keyFile);
+ if(certFile == NULL) {
+ LogMsg(0, RS_RET_CERT_MISSING, LOG_WARNING, "warning: certificate file is not set");
+ }
+ if(keyFile == NULL) {
+ LogMsg(0, RS_RET_CERTKEY_MISSING, LOG_WARNING, "warning: key file is not set");
+ }
+
+ /* set certificate in gnutls */
+ if(certFile != NULL && keyFile != NULL) {
+ CHKgnutls(gnutls_certificate_set_x509_key_file(pThis->xcred, (char*)certFile, (char*)keyFile,
+ GNUTLS_X509_FMT_PEM));
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK && iRet != RS_RET_CERT_MISSING && iRet != RS_RET_CERTKEY_MISSING) {
+ pGnuErr = gtlsStrerror(gnuRet);
+ errno = 0;
+ LogError(0, iRet, "error adding our certificate. GnuTLS error %d, message: '%s', "
+ "key: '%s', cert: '%s'", gnuRet, pGnuErr, keyFile, certFile);
+ free(pGnuErr);
+ }
+ RETiRet;
+}
+
+/*
+* removecomment ifdef out if needed
+*/
+#ifdef false
+
+static void print_cipher_suite_list(const char *priorities)
+{
+ size_t i;
+ int ret;
+ unsigned int idx;
+ const char *name;
+ const char *err;
+ unsigned char id[2];
+ gnutls_protocol_t version;
+ gnutls_priority_t pcache;
+
+ if (priorities != NULL) {
+ printf("print_cipher_suite_list: Cipher suites for %s\n", priorities);
+
+ ret = gnutls_priority_init(&pcache, priorities, &err);
+ if (ret < 0) {
+ fprintf(stderr, "print_cipher_suite_list: Syntax error at: %s\n", err);
+ exit(1);
+ }
+
+ for (i = 0;; i++) {
+ ret = gnutls_priority_get_cipher_suite_index(pcache, i, &idx);
+ if (ret == GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE)
+ break;
+ if (ret == GNUTLS_E_UNKNOWN_CIPHER_SUITE)
+ continue;
+
+ name = gnutls_cipher_suite_info(idx, id, NULL, NULL, NULL, &version);
+
+ if (name != NULL)
+ dbgprintf("print_cipher_suite_list: %-50s\t0x%02x, 0x%02x\t%s\n",
+ name, (unsigned char) id[0],
+ (unsigned char) id[1],
+ gnutls_protocol_get_name(version));
+ }
+
+ return;
+ }
+}
+#endif
+
+/* initialize GnuTLS credential structure (certs etc) */
+static rsRetVal
+gtlsInitCred(nsd_gtls_t *const pThis )
+{
+ int gnuRet;
+ const uchar *cafile, *crlfile;
+ DEFiRet;
+
+ /* X509 stuff */
+ if (pThis->xcred == NULL) {
+ /* Allocate only ONCE */
+ CHKgnutls(gnutls_certificate_allocate_credentials(&pThis->xcred));
+ }
+
+ /* sets the trusted cas file */
+ cafile = (pThis->pszCAFile == NULL) ? glbl.GetDfltNetstrmDrvrCAF(runConf) : pThis->pszCAFile;
+ if(cafile == NULL) {
+ LogMsg(0, RS_RET_CA_CERT_MISSING, LOG_WARNING,
+ "Warning: CA certificate is not set");
+ } else {
+ dbgprintf("GTLS CA file: '%s'\n", cafile);
+ gnuRet = gnutls_certificate_set_x509_trust_file(pThis->xcred, (char*)cafile, GNUTLS_X509_FMT_PEM);
+ if(gnuRet == GNUTLS_E_FILE_ERROR) {
+ LogError(0, RS_RET_GNUTLS_ERR,
+ "error reading certificate file '%s' - a common cause is that the "
+ "file does not exist", cafile);
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ } else if(gnuRet < 0) {
+ /* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */
+ uchar *pErr = gtlsStrerror(gnuRet);
+ LogError(0, RS_RET_GNUTLS_ERR,
+ "unexpected GnuTLS error reading CA certificate file %d in %s:%d: %s\n",
+ gnuRet, __FILE__, __LINE__, pErr);
+ free(pErr);
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ }
+ }
+
+ crlfile = (pThis->pszCRLFile == NULL) ? glbl.GetDfltNetstrmDrvrCRLF(runConf) : pThis->pszCRLFile;
+ if(crlfile == NULL) {
+ dbgprintf("Certificate revocation list (CRL) file not set.");
+ } else {
+ dbgprintf("GTLS CRL file: '%s'\n", crlfile);
+ gnuRet = gnutls_certificate_set_x509_crl_file(pThis->xcred, (char*)crlfile, GNUTLS_X509_FMT_PEM);
+ if(gnuRet == GNUTLS_E_FILE_ERROR) {
+ LogError(0, RS_RET_GNUTLS_ERR,
+ "error reading Certificate revocation list (CRL) '%s' - a common cause is that the "
+ "file does not exist", crlfile);
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ } else if(gnuRet < 0) {
+ /* TODO; a more generic error-tracking function (this one based on CHKgnutls()) */
+ uchar *pErr = gtlsStrerror(gnuRet);
+ LogError(0, RS_RET_GNUTLS_ERR,
+ "unexpected GnuTLS error reading Certificate revocation list (CRL) %d in %s:%d: %s\n",
+ gnuRet, __FILE__, __LINE__, pErr);
+ free(pErr);
+ ABORT_FINALIZE(RS_RET_GNUTLS_ERR);
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* globally initialize GnuTLS */
+static rsRetVal
+gtlsGlblInit(void)
+{
+ int gnuRet;
+ DEFiRet;
+
+ dbgprintf("gtlsGlblInit: Running Version: '%#010x'\n", GNUTLS_VERSION_NUMBER);
+
+ /* 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());
+
+ if(GetGnuTLSLoglevel(runConf) > 0){
+ gnutls_global_set_log_function(logFunction);
+ gnutls_global_set_log_level(GetGnuTLSLoglevel(runConf));
+ /* 0 (no) to 9 (most), 10 everything */
+ }
+
+ /* Init Anon cipher helpers */
+ CHKgnutls(gnutls_dh_params_init(&dh_params));
+ CHKgnutls(gnutls_dh_params_generate2(dh_params, dhBits));
+
+ /* Allocate ANON Client Cred */
+ CHKgnutls(gnutls_anon_allocate_client_credentials(&anoncred));
+
+ /* Allocate ANON Server Cred */
+ CHKgnutls(gnutls_anon_allocate_server_credentials(&anoncredSrv));
+ gnutls_anon_set_server_dh_params(anoncredSrv, dh_params);
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+gtlsInitSession(nsd_gtls_t *pThis)
+{
+ DEFiRet;
+ int gnuRet = 0;
+ gnutls_session_t session;
+
+ gnutls_init(&session, GNUTLS_SERVER);
+ pThis->bHaveSess = 1;
+ pThis->bIsInitiator = 0;
+ pThis->sess = session;
+
+ /* Moved CertKey Loading to top */
+# if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION
+ /* 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) {
+ dbgprintf("gtlsInitSession: enable certificate checking (VerifyDepth=%d)\n", pThis->DrvrVerifyDepth);
+ gnutls_certificate_set_retrieve_function(pThis->xcred, gtlsClientCertCallback);
+ if (pThis->DrvrVerifyDepth != 0){
+ gnutls_certificate_set_verify_limits(pThis->xcred, 8200, pThis->DrvrVerifyDepth);
+ }
+ } else if(iRet == RS_RET_CERTLESS) {
+ dbgprintf("gtlsInitSession: certificates not configured, not loaded.\n");
+ } else {
+ ABORT_FINALIZE(iRet); /* we have an error case! */
+ }
+# endif
+
+ /* avoid calling all the priority functions, since the defaults are adequate. */
+ CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, pThis->xcred));
+
+ /* check for anon authmode */
+ if (pThis->authMode == GTLS_AUTH_CERTANON) {
+ dbgprintf("gtlsInitSession: anon authmode, gnutls_credentials_set GNUTLS_CRD_ANON\n");
+ CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_ANON, anoncredSrv));
+ gnutls_dh_set_prime_bits(pThis->sess, dhMinBits);
+ }
+
+ /* request client certificate if any. */
+ gnutls_certificate_server_set_request( pThis->sess, GNUTLS_CERT_REQUEST);
+
+
+finalize_it:
+ if(iRet != RS_RET_OK && iRet != RS_RET_CERTLESS) {
+ LogError(0, iRet, "gtlsInitSession failed to INIT Session %d", gnuRet);
+ }
+
+ 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(gnutls_x509_crt_t *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... */
+
+ 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 */
+ }
+ 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_t *pCert)
+{
+ uchar fingerprint[20];
+ uchar fingerprintSha256[32];
+ size_t size;
+ size_t sizeSha256;
+ cstr_t *pstrFingerprint = NULL;
+ cstr_t *pstrFingerprintSha256 = NULL;
+ int bFoundPositiveMatch;
+ permittedPeers_t *pPeer;
+ int gnuRet;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ /* obtain the SHA1 fingerprint */
+ size = sizeof(fingerprint);
+ sizeSha256 = sizeof(fingerprintSha256);
+ CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA1, fingerprint, &size));
+ CHKgnutls(gnutls_x509_crt_get_fingerprint(*pCert, GNUTLS_DIG_SHA256, fingerprintSha256, &sizeSha256));
+ CHKiRet(GenFingerprintStr(fingerprint, size, &pstrFingerprint, "SHA1"));
+ CHKiRet(GenFingerprintStr(fingerprintSha256, sizeSha256, &pstrFingerprintSha256, "SHA256"));
+ dbgprintf("peer's certificate SHA1 fingerprint: %s\n", cstrGetSzStrNoNULL(pstrFingerprint));
+ dbgprintf("peer's certificate SHA256 fingerprint: %s\n", cstrGetSzStrNoNULL(pstrFingerprintSha256));
+
+
+ /* 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))) {
+ dbgprintf("gtlsChkPeerFingerprint: peer's certificate SHA1 MATCH found: %s\n", pPeer->pszID);
+ bFoundPositiveMatch = 1;
+ } else if(!rsCStrSzStrCmp(pstrFingerprintSha256 , pPeer->pszID, strlen((char*) pPeer->pszID))) {
+ dbgprintf("gtlsChkPeerFingerprint: peer's certificate SHA256 MATCH found: %s\n", 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;
+ LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer fingerprint '%s' unknown - we are "
+ "not permitted to talk to it", cstrGetSzStrNoNULL(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_t *pCert)
+{
+ uchar lnBuf[256];
+ char szAltName[1024]; /* this is sufficient for the DNSNAME... */
+ int iAltName;
+ size_t szAltNameLen;
+ int bFoundPositiveMatch;
+ int bHaveSAN = 0;
+ 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) {
+ bHaveSAN = 1;
+ 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;
+ }
+
+ /* Check also CN only if not configured per stricter RFC 6125 or no SAN present*/
+ if(!bFoundPositiveMatch && (!pThis->bSANpriority || !bHaveSAN)) {
+ CHKiRet(gtlsGetCN(pCert, &pstrCN));
+ if(pstrCN != NULL) { /* NULL if there was no CN present */
+ dbgprintf("gtls now checking auth for CN '%s'\n", cstrGetSzStrNoNULL(pstrCN));
+ snprintf((char*)lnBuf, sizeof(lnBuf), "CN: %s; ", cstrGetSzStrNoNULL(pstrCN));
+ CHKiRet(rsCStrAppendStr(pStr, lnBuf));
+ CHKiRet(gtlsChkOnePeerName(pThis, cstrGetSzStrNoNULL(pstrCN), &bFoundPositiveMatch));
+ }
+ }
+
+ if(!bFoundPositiveMatch) {
+ dbgprintf("invalid peer name, not permitted to talk to it\n");
+ if(pThis->bReportAuthErr == 1) {
+ cstrFinalize(pStr);
+ errno = 0;
+ LogError(0, RS_RET_INVALID_FINGERPRINT, "error: peer name not authorized - "
+ "not permitted to talk to it. Names: %s",
+ cstrGetSzStrNoNULL(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_t *cert_list;
+ unsigned int list_size = 0;
+ gnutls_x509_crt_t 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) {
+ uchar *fromHost = NULL;
+ errno = 0;
+ pThis->bReportAuthErr = 0;
+ nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost);
+ LogError(0, RS_RET_TLS_NO_CERT, "error: peer %s did not provide a certificate, "
+ "not permitted to talk to it", fromHost);
+ free(fromHost);
+ }
+ 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;
+ const char *pszErrCause;
+ int gnuRet;
+ cstr_t *pStr = NULL;
+ unsigned stateCert;
+ const gnutls_datum_t *cert_list;
+ unsigned cert_list_size = 0;
+ gnutls_x509_crt_t cert;
+ unsigned i;
+ time_t ttCert;
+ time_t ttNow;
+ sbool bAbort = RSFALSE;
+ int iAbortCode = RS_RET_OK;
+
+ 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;
+ uchar *fromHost = NULL;
+ nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost);
+ LogError(0, RS_RET_TLS_NO_CERT,
+ "peer %s did not provide a certificate, not permitted to talk to it",
+ fromHost);
+ free(fromHost);
+ ABORT_FINALIZE(RS_RET_TLS_NO_CERT);
+ }
+#ifdef EXTENDED_CERT_CHECK_AVAILABLE
+ if (pThis->dataTypeCheck == GTLS_NONE) {
+#endif
+ CHKgnutls(gnutls_certificate_verify_peers2(pThis->sess, &stateCert));
+#ifdef EXTENDED_CERT_CHECK_AVAILABLE
+ } else { /* we have configured data to check in addition to cert */
+ gnutls_typed_vdata_st data;
+ data.type = GNUTLS_DT_KEY_PURPOSE_OID;
+ if (pThis->bIsInitiator) { /* client mode */
+ data.data = (uchar *)GNUTLS_KP_TLS_WWW_SERVER;
+ } else { /* server mode */
+ data.data = (uchar *)GNUTLS_KP_TLS_WWW_CLIENT;
+ }
+ data.size = ustrlen(data.data);
+ CHKgnutls(gnutls_certificate_verify_peers(pThis->sess, &data, 1, &stateCert));
+ }
+#endif
+
+ if(stateCert & GNUTLS_CERT_INVALID) {
+ /* Default abort code */
+ iAbortCode = RS_RET_CERT_INVALID;
+
+ /* provide error details if we have them */
+ if (stateCert & GNUTLS_CERT_EXPIRED ) {
+ dbgprintf("GnuTLS returned GNUTLS_CERT_EXPIRED, handling mode %d ...\n",
+ pThis->permitExpiredCerts);
+ /* Handle expired certs */
+ if (pThis->permitExpiredCerts == GTLS_EXPIRED_DENY) {
+ bAbort = RSTRUE;
+ iAbortCode = RS_RET_CERT_EXPIRED;
+ } else if (pThis->permitExpiredCerts == GTLS_EXPIRED_WARN) {
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING,
+ "Warning, certificate expired but expired certs are permitted");
+ } else {
+ dbgprintf("GnuTLS returned GNUTLS_CERT_EXPIRED, but expired certs are permitted.\n");
+ }
+ pszErrCause = "certificate expired";
+ } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_FOUND) {
+ pszErrCause = "signer not found";
+ bAbort = RSTRUE;
+ } else if(stateCert & GNUTLS_CERT_SIGNER_NOT_CA) {
+ pszErrCause = "signer is not a CA";
+ bAbort = RSTRUE;
+ } else if(stateCert & GNUTLS_CERT_INSECURE_ALGORITHM) {
+ pszErrCause = "insecure algorithm";
+ bAbort = RSTRUE;
+ } else if(stateCert & GNUTLS_CERT_REVOKED) {
+ pszErrCause = "certificate revoked";
+ bAbort = RSTRUE;
+ iAbortCode = RS_RET_CERT_REVOKED;
+#ifdef EXTENDED_CERT_CHECK_AVAILABLE
+ } else if(stateCert & GNUTLS_CERT_PURPOSE_MISMATCH) {
+ pszErrCause = "key purpose OID does not match";
+ bAbort = RSTRUE;
+#endif
+ } else {
+ pszErrCause = "GnuTLS returned no specific reason";
+ dbgprintf("GnuTLS returned no specific reason for GNUTLS_CERT_INVALID, certificate "
+ "status is %d\n", stateCert);
+ bAbort = RSTRUE;
+ }
+ }
+
+ if (bAbort == RSTRUE) {
+ uchar *fromHost = NULL;
+ nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost);
+ LogError(0, NO_ERRCODE, "not permitted to talk to peer '%s', certificate invalid: %s",
+ fromHost, pszErrCause);
+ free(fromHost);
+ gtlsGetCertInfo(pThis, &pStr);
+ LogError(0, NO_ERRCODE, "invalid cert info: %s", cstrGetSzStrNoNULL(pStr));
+ cstrDestruct(&pStr);
+ ABORT_FINALIZE(iAbortCode);
+ }
+
+ /* 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) {
+ uchar *fromHost = NULL;
+ nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost);
+ LogError(0, RS_RET_CERT_NOT_YET_ACTIVE, "not permitted to talk to peer '%s': "
+ "certificate %d not yet active", fromHost, i);
+ free(fromHost);
+ gtlsGetCertInfo(pThis, &pStr);
+ LogError(0, RS_RET_CERT_NOT_YET_ACTIVE,
+ "invalid cert info: %s", cstrGetSzStrNoNULL(pStr));
+ cstrDestruct(&pStr);
+ ABORT_FINALIZE(RS_RET_CERT_NOT_YET_ACTIVE);
+ }
+
+ 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;
+ gnutls_anon_free_server_credentials(anoncredSrv);
+ gnutls_dh_params_deinit(dh_params);
+ gnutls_global_deinit();
+ 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_WR);
+ while(gnuRet == GNUTLS_E_INTERRUPTED || gnuRet == GNUTLS_E_AGAIN) {
+ gnuRet = gnutls_bye(pThis->sess, GNUTLS_SHUT_WR);
+ }
+ }
+ 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 */
+PROTOTYPEobjDestruct(nsd_gtls);
+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);
+ }
+
+ free(pThis->pszConnectHost);
+ free(pThis->pszRcvBuf);
+ free((void*) pThis->pszCAFile);
+ free((void*) pThis->pszCRLFile);
+
+ if(pThis->bOurCertIsInit)
+ for(unsigned i=0; i<pThis->nOurCerts; ++i) {
+ gnutls_x509_crt_deinit(pThis->pOurCerts[i]);
+ }
+ if(pThis->bOurKeyIsInit)
+ gnutls_x509_privkey_deinit(pThis->ourKey);
+ if(pThis->bHaveSess)
+ gnutls_deinit(pThis->sess);
+ if(pThis->xcred != NULL
+ && (pThis->bIsInitiator || (!pThis->xcred_is_copy && (!pThis->bIsInitiator || pThis->bHaveSess)))
+ ) {
+ gnutls_certificate_free_credentials(pThis->xcred);
+ free((void*) pThis->pszKeyFile);
+ free((void*) pThis->pszCertFile);
+ }
+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 *const pNsd, const int mode)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ dbgprintf("(tls) mode: %d\n", mode);
+ if(mode != 0 && mode != 1) {
+ 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 {
+ 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);
+ }
+
+ dbgprintf("SetAuthMode to %s\n", (mode != NULL ? (char*)mode : "NULL"));
+/* TODO: clear stored IDs! */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set the PermitExpiredCerts mode. For us, the following is supported:
+ * on - fail if certificate is expired
+ * off - ignore expired certificates
+ * warn - warn if certificate is expired
+ * alorbach, 2018-12-20
+ */
+static rsRetVal
+SetPermitExpiredCerts(nsd_t *pNsd, uchar *mode)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ /* default is set to off! */
+ if(mode == NULL || !strcasecmp((char*)mode, "off")) {
+ pThis->permitExpiredCerts = GTLS_EXPIRED_DENY;
+ } else if(!strcasecmp((char*) mode, "warn")) {
+ pThis->permitExpiredCerts = GTLS_EXPIRED_WARN;
+ } else if(!strcasecmp((char*) mode, "on")) {
+ pThis->permitExpiredCerts = GTLS_EXPIRED_PERMIT;
+ } else {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: permitexpiredcerts mode '%s' not supported by "
+ "gtls netstream driver", mode);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+ dbgprintf("SetPermitExpiredCerts: Set Mode %s/%d\n",
+ (mode != NULL ? (char*)mode : "NULL"), pThis->permitExpiredCerts);
+
+/* 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) {
+ 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;
+}
+
+/* gnutls priority string
+ * PascalWithopf 2017-08-16
+ */
+static rsRetVal
+SetGnutlsPriorityString(nsd_t *pNsd, uchar *gnutlsPriorityString)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ pThis->gnutlsPriorityString = gnutlsPriorityString;
+ dbgprintf("gnutlsPriorityString: set to '%s'\n",
+ (gnutlsPriorityString != NULL ? (char*)gnutlsPriorityString : "NULL"));
+ RETiRet;
+}
+
+/* Set the driver cert extended key usage check setting
+ * 0 - ignore contents of extended key usage
+ * 1 - verify that cert contents is compatible with appropriate OID
+ * jvymazal, 2019-08-16
+ */
+static rsRetVal
+SetCheckExtendedKeyUsage(nsd_t *pNsd, int ChkExtendedKeyUsage)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(ChkExtendedKeyUsage != 0 && ChkExtendedKeyUsage != 1) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver ChkExtendedKeyUsage %d "
+ "not supported by gtls netstream driver", ChkExtendedKeyUsage);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+ pThis->dataTypeCheck = ChkExtendedKeyUsage;
+
+finalize_it:
+ RETiRet;
+}
+
+/* Set the driver name checking strictness
+ * 0 - less strict per RFC 5280, section 4.1.2.6 - either SAN or CN match is good
+ * 1 - more strict per RFC 6125 - if any SAN present it must match (CN is ignored)
+ * jvymazal, 2019-08-16
+ */
+static rsRetVal
+SetPrioritizeSAN(nsd_t *pNsd, int prioritizeSan)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(prioritizeSan != 0 && prioritizeSan != 1) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver prioritizeSan %d "
+ "not supported by gtls netstream driver", prioritizeSan);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+ pThis->bSANpriority = prioritizeSan;
+
+finalize_it:
+ RETiRet;
+}
+
+/* Set the driver tls verifyDepth
+ * alorbach, 2019-12-20
+ */
+static rsRetVal
+SetTlsVerifyDepth(nsd_t *pNsd, int verifyDepth)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if (verifyDepth == 0) {
+ FINALIZE;
+ }
+ assert(verifyDepth >= 2);
+ pThis->DrvrVerifyDepth = verifyDepth;
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCAFile(nsd_t *pNsd, const uchar *const caFile)
+{
+ DEFiRet;
+ nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(caFile == NULL) {
+ pThis->pszCAFile = NULL;
+ } else {
+ CHKmalloc(pThis->pszCAFile = (const uchar*) strdup((const char*) caFile));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCRLFile(nsd_t *pNsd, const uchar *const crlFile)
+{
+ DEFiRet;
+ nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(crlFile == NULL) {
+ pThis->pszCRLFile = NULL;
+ } else {
+ CHKmalloc(pThis->pszCRLFile = (const uchar*) strdup((const char*) crlFile));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsKeyFile(nsd_t *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(pszFile == NULL) {
+ pThis->pszKeyFile = NULL;
+ } else {
+ CHKmalloc(pThis->pszKeyFile = (const uchar*) strdup((const char*) pszFile));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCertFile(nsd_t *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ nsd_gtls_t *const pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ if(pszFile == NULL) {
+ pThis->pszCertFile = NULL;
+ } else {
+ CHKmalloc(pThis->pszCertFile = (const uchar*) strdup((const char*) pszFile));
+ }
+
+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;
+}
+
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveIntvl(nsd_t *pNsd, int keepAliveIntvl)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ assert(keepAliveIntvl >= 0);
+
+ nsd_ptcp.SetKeepAliveIntvl(pThis->pTcp, keepAliveIntvl);
+
+ RETiRet;
+}
+
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveProbes(nsd_t *pNsd, int keepAliveProbes)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ assert(keepAliveProbes >= 0);
+
+ nsd_ptcp.SetKeepAliveProbes(pThis->pTcp, keepAliveProbes);
+
+ RETiRet;
+}
+
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveTime(nsd_t *pNsd, int keepAliveTime)
+{
+ DEFiRet;
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_gtls);
+ assert(keepAliveTime >= 0);
+
+ nsd_ptcp.SetKeepAliveTime(pThis->pTcp, keepAliveTime);
+
+ 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;
+}
+
+
+/* Callback after netstrm obj init in nsd_ptcp - permits us to add some data */
+static rsRetVal
+LstnInitDrvr(netstrm_t *const pThis)
+{
+ DEFiRet;
+ CHKiRet(gtlsInitCred((nsd_gtls_t*) pThis->pDrvrData));
+ CHKiRet(gtlsAddOurCert((nsd_gtls_t*) pThis->pDrvrData));
+finalize_it:
+ 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 ATTR_NONNULL(1,3,5)
+LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ const int iSessMax, const tcpLstnParams_t *const cnf_params)
+{
+ DEFiRet;
+ pNS->fLstnInitDrvr = LstnInitDrvr;
+ iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, iSessMax, cnf_params);
+//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)
+{
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+
+ dbgprintf("CheckConnection for %p\n", pNsd);
+ return nsd_ptcp.CheckConnection(pThis->pTcp);
+}
+
+
+/* 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;
+ const char *error_position = NULL;
+
+ 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;
+ }
+ /* copy Properties to pnew first */
+ pNew->authMode = pThis->authMode;
+ pNew->permitExpiredCerts = pThis->permitExpiredCerts;
+ pNew->pPermPeers = pThis->pPermPeers;
+ pNew->gnutlsPriorityString = pThis->gnutlsPriorityString;
+ pNew->DrvrVerifyDepth = pThis->DrvrVerifyDepth;
+ pNew->dataTypeCheck = pThis->dataTypeCheck;
+ pNew->bSANpriority = pThis->bSANpriority;
+ pNew->pszCertFile = pThis->pszCertFile;
+ pNew->pszKeyFile = pThis->pszKeyFile;
+ pNew->xcred = pThis->xcred; // TODO: verify once again; xcred is read only at this stage
+ pNew->xcred_is_copy = 1; // do not free on pNew Destruction
+
+ /* if we reach this point, we are in TLS mode */
+ iRet = gtlsInitSession(pNew);
+ if (iRet != RS_RET_OK) {
+ if (iRet == RS_RET_CERTLESS) {
+ dbgprintf("AcceptConnReq certless mode\n");
+ /* Set status to OK */
+ iRet = RS_RET_OK;
+ } else {
+ goto finalize_it;
+ }
+ }
+ gtlsSetTransportPtr(pNew, ((nsd_ptcp_t*) (pNew->pTcp))->sock);
+
+ dbgprintf("AcceptConnReq bOurCertIsInit=%hu bOurKeyIsInit=%hu \n",
+ pNew->bOurCertIsInit, pNew->bOurKeyIsInit);
+
+ /* here is the priorityString set */
+ if(pNew->gnutlsPriorityString != NULL) {
+ dbgprintf("AcceptConnReq setting configured priority string (ciphers)\n");
+ if(gnutls_priority_set_direct(pNew->sess,
+ (const char*) pNew->gnutlsPriorityString,
+ &error_position)==GNUTLS_E_INVALID_REQUEST) {
+ LogError(0, RS_RET_GNUTLS_ERR, "Syntax Error in"
+ " Priority String: \"%s\"\n", error_position);
+ }
+ } else {
+ if(pThis->authMode == GTLS_AUTH_CERTANON) {
+ /* Allow ANON Ciphers */
+ dbgprintf("AcceptConnReq setting anon ciphers Try1: %s\n", GTLS_ANON_PRIO_NOTLSV13);
+ if(gnutls_priority_set_direct(pNew->sess,(const char*) GTLS_ANON_PRIO_NOTLSV13,
+ &error_position)==GNUTLS_E_INVALID_REQUEST) {
+ dbgprintf("AcceptConnReq setting anon ciphers Try2 (TLS1.3 unknown): %s\n",
+ GTLS_ANON_PRIO);
+ CHKgnutls(gnutls_priority_set_direct(pNew->sess, GTLS_ANON_PRIO, &error_position));
+ }
+ /* Uncomment for DEBUG
+ print_cipher_suite_list("NORMAL:+ANON-DH:+ANON-ECDH:+COMP-ALL"); */
+ } else {
+ /* Use default priorities */
+ dbgprintf("AcceptConnReq setting default ciphers\n");
+ CHKgnutls(gnutls_set_default_priority(pNew->sess));
+ }
+ }
+
+ /* 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);
+ 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 (error_position != NULL) {
+ dbgprintf("AcceptConnReq error_position=%s\n", error_position);
+}
+
+ 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, int *const oserr)
+{
+ 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, oserr));
+ 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 */
+ *oserr = errno;
+ 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:
+ if (iRet != RS_RET_OK &&
+ iRet != RS_RET_RETRY) {
+ /* We need to free the receive buffer in error error case unless a retry is wanted. , if we
+ * allocated one. -- rgerhards, 2008-12-03 -- moved here by alorbach, 2015-12-01
+ */
+ *pLenBuf = 0;
+ free(pThis->pszRcvBuf);
+ pThis->pszRcvBuf = NULL;
+ }
+ 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;
+ int wantsWriteData = 0;
+ 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) {
+ /* Check if the underlaying file descriptor needs to read or write data!*/
+ wantsWriteData = gnutls_record_get_direction(pThis->sess);
+ uchar *pErr = gtlsStrerror(iSent);
+ LogError(0, RS_RET_GNUTLS_ERR, "unexpected GnuTLS error %d, wantsWriteData=%d - this "
+ "could be caused by a broken connection. GnuTLS reports: %s\n",
+ iSent, wantsWriteData, pErr);
+ free(pErr);
+ gnutls_perror(iSent);
+ 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)
+{
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ return nsd_ptcp.EnableKeepAlive(pThis->pTcp);
+}
+
+
+/*
+ * SNI should not be used if the hostname is a bare IP address
+ */
+static int
+SetServerNameIfPresent(nsd_gtls_t *pThis, uchar *host) {
+ struct sockaddr_in sa;
+ struct sockaddr_in6 sa6;
+
+ int inet_pton_ret = inet_pton(AF_INET, CHAR_CONVERT(host), &(sa.sin_addr));
+
+ if (inet_pton_ret == 0) { // host wasn't a bare IPv4 address: try IPv6
+ inet_pton_ret = inet_pton(AF_INET6, CHAR_CONVERT(host), &(sa6.sin6_addr));
+ }
+
+ switch(inet_pton_ret) {
+ case 1: // host is a valid IP address: don't use SNI
+ return 0;
+ case 0: // host isn't a valid IP address: assume it's a domain name, use SNI
+ return gnutls_server_name_set(pThis->sess, GNUTLS_NAME_DNS, host, ustrlen(host));
+ default: // unexpected error
+ return -1;
+ }
+
+}
+
+/* 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
+ */
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations" /* TODO: FIX Warnings! */
+static rsRetVal
+Connect(nsd_t *pNsd, int family, uchar *port, uchar *host, char *device)
+{
+ nsd_gtls_t *pThis = (nsd_gtls_t*) pNsd;
+ int sock;
+ int gnuRet;
+ const char *error_position;
+# ifdef HAVE_GNUTLS_CERTIFICATE_TYPE_SET_PRIORITY
+ static const int cert_type_priority[2] = { GNUTLS_CRT_X509, 0 };
+# endif
+ DEFiRet;
+ dbgprintf("Connect to %s:%s\n", host, port);
+
+ ISOBJ_TYPE_assert(pThis, nsd_gtls);
+ assert(port != NULL);
+ assert(host != NULL);
+
+ CHKiRet(gtlsInitCred(pThis));
+ CHKiRet(gtlsAddOurCert(pThis));
+ CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host, device));
+
+ 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;
+
+ CHKgnutls(SetServerNameIfPresent(pThis, host));
+
+ /* 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) {
+# if HAVE_GNUTLS_CERTIFICATE_SET_RETRIEVE_FUNCTION
+ gnutls_certificate_set_retrieve_function(pThis->xcred, gtlsClientCertCallback);
+# else
+ gnutls_certificate_client_set_retrieve_function(pThis->xcred, gtlsClientCertCallback);
+# endif
+ dbgprintf("Connect: enable certificate checking (VerifyDepth=%d)\n", pThis->DrvrVerifyDepth);
+ if (pThis->DrvrVerifyDepth != 0) {
+ gnutls_certificate_set_verify_limits(pThis->xcred, 8200, pThis->DrvrVerifyDepth);
+ }
+ } else if(iRet == RS_RET_CERTLESS) {
+ dbgprintf("Connect: certificates not configured, not loaded.\n");
+ } else {
+ LogError(0, iRet, "Connect failed to INIT Session %d", gnuRet);
+ ABORT_FINALIZE(iRet);; /* we have an error case! */
+ }
+
+ /*priority string setzen*/
+ if(pThis->gnutlsPriorityString != NULL) {
+ dbgprintf("Connect: setting configured priority string (ciphers)\n");
+ if(gnutls_priority_set_direct(pThis->sess,
+ (const char*) pThis->gnutlsPriorityString,
+ &error_position)==GNUTLS_E_INVALID_REQUEST) {
+ LogError(0, RS_RET_GNUTLS_ERR, "Syntax Error in"
+ " Priority String: \"%s\"\n", error_position);
+ }
+ } else {
+ if(pThis->authMode == GTLS_AUTH_CERTANON || pThis->bOurCertIsInit == 0) {
+ /* Allow ANON Ciphers */
+ dbgprintf("Connect: setting anon ciphers Try1: %s\n", GTLS_ANON_PRIO_NOTLSV13);
+ if(gnutls_priority_set_direct(pThis->sess,(const char*) GTLS_ANON_PRIO_NOTLSV13,
+ &error_position)==GNUTLS_E_INVALID_REQUEST) {
+ dbgprintf("Connect: setting anon ciphers Try2 (TLS1.3 unknown): %s\n", GTLS_ANON_PRIO);
+ CHKgnutls(gnutls_priority_set_direct(pThis->sess, GTLS_ANON_PRIO, &error_position));
+ }
+ /* Uncomment for DEBUG
+ print_cipher_suite_list("NORMAL:+ANON-DH:+ANON-ECDH:+COMP-ALL"); */
+ } else {
+ /* Use default priorities */
+ dbgprintf("Connect: setting default ciphers\n");
+ CHKgnutls(gnutls_set_default_priority(pThis->sess));
+ }
+ }
+
+# ifdef HAVE_GNUTLS_CERTIFICATE_TYPE_SET_PRIORITY
+ /* The gnutls_certificate_type_set_priority function is deprecated
+ * and not available in recent GnuTLS versions. However, there is no
+ * doc how to properly replace it with gnutls_priority_set_direct.
+ * A lot of folks have simply removed it, when they also called
+ * gnutls_set_default_priority. This is what we now also do. If
+ * this causes problems or someone has an idea of how to replace
+ * the deprecated function in a better way, please let us know!
+ * In any case, we use it as long as it is available and let
+ * not insult us by the deprecation warnings.
+ * 2015-05-18 rgerhards
+ */
+ CHKgnutls(gnutls_certificate_type_set_priority(pThis->sess, cert_type_priority));
+# endif
+
+ /* put the x509 credentials to the current session */
+ CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_CERTIFICATE, pThis->xcred));
+
+ /* check for anon authmode */
+ if (pThis->authMode == GTLS_AUTH_CERTANON) {
+ dbgprintf("Connect: anon authmode, gnutls_credentials_set GNUTLS_CRD_ANON\n");
+ CHKgnutls(gnutls_credentials_set(pThis->sess, GNUTLS_CRD_ANON, anoncred));
+ gnutls_dh_set_prime_bits(pThis->sess, dhMinBits);
+ }
+
+ /* 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;
+ /* Free memory using gnutls api first*/
+ gnutls_certificate_free_credentials(pThis->xcred);
+ pThis->xcred = NULL;
+ /* Free other memory */
+ free(pThis->pszConnectHost);
+ pThis->pszConnectHost = NULL;
+ }
+ }
+
+ RETiRet;
+}
+#pragma GCC diagnostic pop
+
+
+/* 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->SetPermitExpiredCerts = SetPermitExpiredCerts;
+ pIf->SetPermPeers =SetPermPeers;
+ pIf->CheckConnection = CheckConnection;
+ pIf->GetRemoteHName = GetRemoteHName;
+ pIf->GetRemoteIP = GetRemoteIP;
+ pIf->GetRemAddr = GetRemAddr;
+ pIf->EnableKeepAlive = EnableKeepAlive;
+ pIf->SetKeepAliveIntvl = SetKeepAliveIntvl;
+ pIf->SetKeepAliveProbes = SetKeepAliveProbes;
+ pIf->SetKeepAliveTime = SetKeepAliveTime;
+ pIf->SetGnutlsPriorityString = SetGnutlsPriorityString;
+ pIf->SetCheckExtendedKeyUsage = SetCheckExtendedKeyUsage;
+ pIf->SetPrioritizeSAN = SetPrioritizeSAN;
+ pIf->SetTlsVerifyDepth = SetTlsVerifyDepth;
+ pIf->SetTlsCAFile = SetTlsCAFile;
+ pIf->SetTlsCRLFile = SetTlsCRLFile;
+ pIf->SetTlsKeyFile = SetTlsKeyFile;
+ pIf->SetTlsCertFile = SetTlsCertFile;
+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);
+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(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 0000000..ee0b7f9
--- /dev/null
+++ b/runtime/nsd_gtls.h
@@ -0,0 +1,123 @@
+/* An implementation of the nsd interface for GnuTLS.
+ *
+ * Copyright 2008-2021 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 16 * 1024 + 1/* TLS RFC 8449: max size of buffer for message reception */
+#define NSD_GTLS_MAX_CERT 10 /* max number of certs in our chain */
+
+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 enum {
+ gtlsDir_READ = 0, /**< GNUTLS wants READ */
+ gtlsDir_WRITE = 1 /**< GNUTLS wants WRITE */
+} gtlsDirection_t;
+
+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 */
+ const uchar *pszCAFile;
+ const uchar *pszCRLFile;
+ const uchar *pszKeyFile;
+ const uchar *pszCertFile;
+ gnutls_certificate_credentials_t xcred;
+ int xcred_is_copy;
+ 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;
+ enum {
+ GTLS_EXPIRED_PERMIT = 0,
+ GTLS_EXPIRED_DENY = 1,
+ GTLS_EXPIRED_WARN = 2
+ } permitExpiredCerts;
+ enum {
+ GTLS_NONE = 0,
+ GTLS_PURPOSE = 1
+ } dataTypeCheck;
+ int bSANpriority; /* if true, we do stricter checking (if any SAN present we do not cehck CN) */
+ 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_t 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 */
+ uchar *gnutlsPriorityString; /* gnutls priority string */
+ int DrvrVerifyDepth; /* Verify Depth for certificate chains */
+ gnutls_x509_crt_t pOurCerts[NSD_GTLS_MAX_CERT]; /**< our certificate, if in client mode
+ (unused in server mode) */
+ unsigned int nOurCerts; /* number of certificates in our chain */
+ gnutls_x509_privkey_t 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);
+
+/* the name of our library binary */
+#define LM_NSD_GTLS_FILENAME "lmnsd_gtls"
+
+#if GNUTLS_VERSION_NUMBER <= 0x00030000
+#define GTLS_ANON_PRIO_NOTLSV13 "NORMAL:-VERS-TLS1.3:+ANON-DH:+COMP-ALL"
+#define GTLS_ANON_PRIO "NORMAL:+ANON-DH:+COMP-ALL"
+#else
+#define GTLS_ANON_PRIO_NOTLSV13 "NORMAL:-VERS-TLS1.3:+ANON-DH:+ANON-ECDH:+COMP-ALL"
+#define GTLS_ANON_PRIO "NORMAL:+ANON-DH:+ANON-ECDH:+COMP-ALL"
+#endif
+
+#if GNUTLS_VERSION_MAJOR > 3 || (GNUTLS_VERSION_MAJOR == 3 && GNUTLS_VERSION_MINOR >=4)
+#define EXTENDED_CERT_CHECK_AVAILABLE
+#endif
+
+#endif /* #ifndef INCLUDED_NSD_GTLS_H */
diff --git a/runtime/nsd_ossl.c b/runtime/nsd_ossl.c
new file mode 100644
index 0000000..2d70fb6
--- /dev/null
+++ b/runtime/nsd_ossl.c
@@ -0,0 +1,1510 @@
+/* nsd_ossl.c
+ *
+ * An implementation of the nsd interface for OpenSSL.
+ *
+ * Copyright 2018-2023 Adiscon GmbH.
+ * Author: Andre Lorbach
+ *
+ * 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 <strings.h>
+
+#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 "netstrm.h"
+#include "netstrms.h"
+#include "datetime.h"
+#include "net_ossl.h" // Include OpenSSL Helpers
+#include "nsd_ptcp.h"
+#include "nsdsel_ossl.h"
+#include "nsd_ossl.h"
+#include "unicode-helper.h"
+#include "rsconf.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_KEEP
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(net)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(nsd_ptcp)
+DEFobjCurrIf(net_ossl)
+
+/* Some prototypes for helper functions used inside openssl driver */
+static rsRetVal applyGnutlsPriorityString(nsd_ossl_t *const pNsd);
+
+/*--------------------------------------OpenSSL helpers ------------------------------------------*/
+void nsd_ossl_lastOpenSSLErrorMsg(nsd_ossl_t const *pThis, const int ret, SSL *ssl, int severity,
+ const char* pszCallSource, const char* pszOsslApi)
+{
+ uchar *fromHost = NULL;
+ int errno_store = errno;
+
+ if(pThis != NULL) {
+ nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHost);
+ }
+
+ // Call helper in net_ossl
+ net_ossl_lastOpenSSLErrorMsg(fromHost, ret, ssl, severity, pszCallSource, pszOsslApi);
+
+ free(fromHost);
+ errno = errno_store;
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x30000000L && !defined(LIBRESSL_VERSION_NUMBER)
+long BIO_debug_callback_ex(BIO *bio, int cmd, const char __attribute__((unused)) *argp,
+ size_t __attribute__((unused)) len, int argi, long __attribute__((unused)) argl,
+ int ret, size_t __attribute__((unused)) *processed)
+#else
+long BIO_debug_callback(BIO *bio, int cmd, const char __attribute__((unused)) *argp,
+ int argi, long __attribute__((unused)) argl, long ret)
+#endif
+{
+ long ret2 = ret; // Helper value to avoid printf compile errors long<>int
+ long r = 1;
+ if (BIO_CB_RETURN & cmd)
+ r = ret;
+ dbgprintf("openssl debugmsg: BIO[%p]: ", (void *)bio);
+ switch (cmd) {
+ case BIO_CB_FREE:
+ dbgprintf("Free - %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+/* Disabled due API changes for OpenSSL 1.1.0+ */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ case BIO_CB_READ:
+ if (bio->method->type & BIO_TYPE_DESCRIPTOR)
+ dbgprintf("read(%d,%lu) - %s fd=%d\n",
+ RSYSLOG_BIO_number_read(bio), (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_read(bio));
+ else
+ dbgprintf("read(%d,%lu) - %s\n", RSYSLOG_BIO_number_read(bio),
+ (unsigned long)argi, RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_WRITE:
+ if (bio->method->type & BIO_TYPE_DESCRIPTOR)
+ dbgprintf("write(%d,%lu) - %s fd=%d\n",
+ RSYSLOG_BIO_number_written(bio), (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio), RSYSLOG_BIO_number_written(bio));
+ else
+ dbgprintf("write(%d,%lu) - %s\n",
+ RSYSLOG_BIO_number_written(bio),
+ (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio));
+ break;
+#else
+ case BIO_CB_READ:
+ dbgprintf("read %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_WRITE:
+ dbgprintf("write %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+#endif
+ case BIO_CB_PUTS:
+ dbgprintf("puts() - %s\n", RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_GETS:
+ dbgprintf("gets(%lu) - %s\n", (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_CTRL:
+ dbgprintf("ctrl(%lu) - %s\n", (unsigned long)argi,
+ RSYSLOG_BIO_method_name(bio));
+ break;
+ case BIO_CB_RETURN | BIO_CB_READ:
+ dbgprintf("read return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_WRITE:
+ dbgprintf("write return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_GETS:
+ dbgprintf("gets return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_PUTS:
+ dbgprintf("puts return %ld\n", ret2);
+ break;
+ case BIO_CB_RETURN | BIO_CB_CTRL:
+ dbgprintf("ctrl return %ld\n", ret2);
+ break;
+ default:
+ dbgprintf("bio callback - unknown type (%d)\n", cmd);
+ break;
+ }
+
+ return (r);
+}
+
+/* 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
+osslRecordRecv(nsd_ossl_t *pThis)
+{
+ ssize_t lenRcvd;
+ DEFiRet;
+ int err;
+
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ DBGPRINTF("osslRecordRecv: start\n");
+
+ lenRcvd = SSL_read(pThis->pNetOssl->ssl, pThis->pszRcvBuf, NSD_OSSL_MAX_RCVBUF);
+ if(lenRcvd > 0) {
+ DBGPRINTF("osslRecordRecv: SSL_read received %zd bytes\n", lenRcvd);
+ pThis->lenRcvBuf = lenRcvd;
+ pThis->ptrRcvBuf = 0;
+
+ /* Check for additional data in SSL buffer */
+ int iBytesLeft = SSL_pending(pThis->pNetOssl->ssl);
+ if (iBytesLeft > 0 ){
+ DBGPRINTF("osslRecordRecv: %d Bytes pending after SSL_Read, expand buffer.\n", iBytesLeft);
+ /* realloc buffer size and preserve char content */
+ char *const newbuf = realloc(pThis->pszRcvBuf, NSD_OSSL_MAX_RCVBUF+iBytesLeft);
+ CHKmalloc(newbuf);
+ pThis->pszRcvBuf = newbuf;
+
+ /* 2nd read will read missing bytes from the current SSL Packet */
+ lenRcvd = SSL_read(pThis->pNetOssl->ssl, pThis->pszRcvBuf+NSD_OSSL_MAX_RCVBUF, iBytesLeft);
+ if(lenRcvd > 0) {
+ DBGPRINTF("osslRecordRecv: 2nd SSL_read received %zd bytes\n",
+ (NSD_OSSL_MAX_RCVBUF+lenRcvd));
+ pThis->lenRcvBuf = NSD_OSSL_MAX_RCVBUF+lenRcvd;
+ } else {
+ goto sslerr;
+ }
+ }
+ } else {
+sslerr:
+ err = SSL_get_error(pThis->pNetOssl->ssl, lenRcvd);
+ if( err == SSL_ERROR_ZERO_RETURN ) {
+ DBGPRINTF("osslRecordRecv: SSL_ERROR_ZERO_RETURN received, connection may closed already\n");
+ ABORT_FINALIZE(RS_RET_RETRY);
+ }
+ else if(err == SSL_ERROR_SYSCALL) {
+ /* Output error and abort */
+ nsd_ossl_lastOpenSSLErrorMsg(pThis, lenRcvd, pThis->pNetOssl->ssl, LOG_INFO,
+ "osslRecordRecv", "SSL_read 1");
+ iRet = RS_RET_NO_ERRCODE;
+ /* Check for underlaying socket errors **/
+ if ( errno == ECONNRESET) {
+ DBGPRINTF("osslRecordRecv: SSL_ERROR_SYSCALL Errno %d, connection reset by peer\n",
+ errno);
+ /* Connection was dropped from remote site */
+ iRet = RS_RET_CLOSED;
+ } else {
+ DBGPRINTF("osslRecordRecv: SSL_ERROR_SYSCALL Errno %d\n", errno);
+ }
+ ABORT_FINALIZE(iRet);
+ }
+ else if(err != SSL_ERROR_WANT_READ &&
+ err != SSL_ERROR_WANT_WRITE) {
+ DBGPRINTF("osslRecordRecv: SSL_get_error #1 = %d, lenRcvd=%zd\n", err, lenRcvd);
+ /* Output OpenSSL error*/
+ nsd_ossl_lastOpenSSLErrorMsg(pThis, lenRcvd, pThis->pNetOssl->ssl, LOG_ERR,
+ "osslRecordRecv", "SSL_read 2");
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ } else {
+ DBGPRINTF("osslRecordRecv: SSL_get_error #2 = %d, lenRcvd=%zd\n", err, lenRcvd);
+ pThis->rtryCall = osslRtry_recv;
+ pThis->rtryOsslErr = err; /* Store SSL ErrorCode into*/
+ ABORT_FINALIZE(RS_RET_RETRY);
+ }
+ }
+
+// TODO: Check if MORE retry logic needed?
+
+finalize_it:
+ dbgprintf("osslRecordRecv return. nsd %p, iRet %d, lenRcvd %zd, lenRcvBuf %d, ptrRcvBuf %d\n",
+ pThis, iRet, lenRcvd, pThis->lenRcvBuf, pThis->ptrRcvBuf);
+ RETiRet;
+}
+
+static rsRetVal
+osslInitSession(nsd_ossl_t *pThis, osslSslState_t osslType) /* , nsd_ossl_t *pServer) */
+{
+ DEFiRet;
+ BIO *conn;
+ char pristringBuf[4096];
+ nsd_ptcp_t *pPtcp = (nsd_ptcp_t*) pThis->pTcp;
+
+ if(!(pThis->pNetOssl->ssl = SSL_new(pThis->pNetOssl->ctx))) {
+ pThis->pNetOssl->ssl = NULL;
+ nsd_ossl_lastOpenSSLErrorMsg(pThis, 0, pThis->pNetOssl->ssl, LOG_ERR, "osslInitSession", "SSL_new");
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ }
+
+ // Set SSL_MODE_AUTO_RETRY to SSL obj
+ SSL_set_mode(pThis->pNetOssl->ssl, SSL_MODE_AUTO_RETRY);
+
+ if (pThis->pNetOssl->authMode != OSSL_AUTH_CERTANON) {
+ dbgprintf("osslInitSession: enable certificate checking (Mode=%d, VerifyDepth=%d)\n",
+ pThis->pNetOssl->authMode, pThis->DrvrVerifyDepth);
+ /* Enable certificate valid checking */
+ net_ossl_set_ssl_verify_callback(pThis->pNetOssl->ssl, SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT);
+ if (pThis->DrvrVerifyDepth != 0) {
+ SSL_set_verify_depth(pThis->pNetOssl->ssl, pThis->DrvrVerifyDepth);
+ }
+ } else if (pThis->gnutlsPriorityString == NULL) {
+ /* Allow ANON Ciphers only in ANON Mode and if no custom priority string is defined */
+ #if OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined(LIBRESSL_VERSION_NUMBER)
+ /* NOTE: do never use: +eNULL, it DISABLES encryption! */
+ strncpy(pristringBuf, "ALL:+COMPLEMENTOFDEFAULT:+ADH:+ECDH:+aNULL@SECLEVEL=0",
+ sizeof(pristringBuf));
+ #else
+ strncpy(pristringBuf, "ALL:+COMPLEMENTOFDEFAULT:+ADH:+ECDH:+aNULL",
+ sizeof(pristringBuf));
+ #endif
+
+ dbgprintf("osslInitSession: setting anon ciphers: %s\n", pristringBuf);
+ if ( SSL_set_cipher_list(pThis->pNetOssl->ssl, pristringBuf) == 0 ){
+ dbgprintf("osslInitSession: Error setting ciphers '%s'\n", pristringBuf);
+ ABORT_FINALIZE(RS_RET_SYS_ERR);
+ }
+ }
+
+ /* Create BIO from ptcp socket! */
+ conn = BIO_new_socket(pPtcp->sock, BIO_CLOSE /*BIO_NOCLOSE*/);
+ dbgprintf("osslInitSession: Init conn BIO[%p] done\n", (void *)conn);
+
+ /* Set debug Callback for conn BIO as well! */
+ net_ossl_set_bio_callback(conn);
+
+ /* TODO: still needed? Set to NON blocking ! */
+ BIO_set_nbio( conn, 1 );
+ SSL_set_bio(pThis->pNetOssl->ssl, conn, conn);
+
+ if (osslType == osslServer) {
+ /* Server Socket */
+ SSL_set_accept_state(pThis->pNetOssl->ssl); /* sets ssl to work in server mode. */
+ pThis->pNetOssl->sslState = osslServer; /*set Server state */
+ } else {
+ /* Client Socket */
+ SSL_set_connect_state(pThis->pNetOssl->ssl); /*sets ssl to work in client mode.*/
+ pThis->pNetOssl->sslState = osslClient; /*set Client state */
+ }
+ pThis->bHaveSess = 1;
+
+ /* we are done */
+ FINALIZE;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* check if it is OK to talk to the remote peer
+ * rgerhards, 2008-05-21
+ */
+rsRetVal
+osslChkPeerAuth(nsd_ossl_t *pThis)
+{
+ DEFiRet;
+ X509 *certpeer;
+
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ uchar *fromHostIP = NULL;
+ nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHostIP);
+
+ /* call the actual function based on current auth mode */
+ switch(pThis->pNetOssl->authMode) {
+ case OSSL_AUTH_CERTNAME:
+ /* if we check the name, we must ensure the cert is valid */
+ certpeer = net_ossl_getpeercert(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP);
+ dbgprintf("osslChkPeerAuth: Check peer certname[%p]=%s\n",
+ (void *)pThis->pNetOssl->ssl, (certpeer != NULL ? "VALID" : "NULL"));
+ CHKiRet(net_ossl_chkpeercertvalidity(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP));
+ CHKiRet(net_ossl_chkpeername(pThis->pNetOssl, certpeer, fromHostIP));
+ break;
+ case OSSL_AUTH_CERTFINGERPRINT:
+ certpeer = net_ossl_getpeercert(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP);
+ dbgprintf("osslChkPeerAuth: Check peer fingerprint[%p]=%s\n",
+ (void *)pThis->pNetOssl->ssl, (certpeer != NULL ? "VALID" : "NULL"));
+ CHKiRet(net_ossl_chkpeercertvalidity(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP));
+ CHKiRet(net_ossl_peerfingerprint(pThis->pNetOssl, certpeer, fromHostIP));
+
+ break;
+ case OSSL_AUTH_CERTVALID:
+ certpeer = net_ossl_getpeercert(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP);
+ dbgprintf("osslChkPeerAuth: Check peer valid[%p]=%s\n",
+ (void *)pThis->pNetOssl->ssl, (certpeer != NULL ? "VALID" : "NULL"));
+ CHKiRet(net_ossl_chkpeercertvalidity(pThis->pNetOssl, pThis->pNetOssl->ssl, fromHostIP));
+ break;
+ case OSSL_AUTH_CERTANON:
+ FINALIZE;
+ break;
+ }
+finalize_it:
+ if (fromHostIP != NULL) {
+ free(fromHostIP);
+ }
+ RETiRet;
+}
+
+/* end a OpenSSL 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
+osslEndSess(nsd_ossl_t *pThis)
+{
+ DEFiRet;
+ uchar *fromHostIP = NULL;
+ int ret;
+ int err;
+
+ /* try closing SSL Connection */
+ if(pThis->bHaveSess) {
+ DBGPRINTF("osslEndSess: closing SSL Session ...\n");
+ ret = SSL_shutdown(pThis->pNetOssl->ssl);
+ nsd_ptcp.GetRemoteHName((nsd_t*)pThis->pTcp, &fromHostIP);
+ if (ret <= 0) {
+ err = SSL_get_error(pThis->pNetOssl->ssl, ret);
+ DBGPRINTF("osslEndSess: shutdown failed with err = %d\n", err);
+
+ /* ignore those SSL Errors on shutdown */
+ if( err != SSL_ERROR_SYSCALL &&
+ err != SSL_ERROR_ZERO_RETURN &&
+ err != SSL_ERROR_WANT_READ &&
+ err != SSL_ERROR_WANT_WRITE) {
+ /* Output Warning only */
+ nsd_ossl_lastOpenSSLErrorMsg(pThis, ret, pThis->pNetOssl->ssl, LOG_WARNING,
+ "osslEndSess", "SSL_shutdown");
+ }
+ /* Shutdown not finished, call SSL_read to do a bidirectional shutdown, see doc for more:
+ * https://www.openssl.org/docs/man1.1.1/man3/SSL_shutdown.html
+ */
+ char rcvBuf[NSD_OSSL_MAX_RCVBUF];
+ int iBytesRet = SSL_read(pThis->pNetOssl->ssl, rcvBuf, NSD_OSSL_MAX_RCVBUF);
+ DBGPRINTF("osslEndSess: Forcing ssl shutdown SSL_read (%d) to do a bidirectional shutdown\n",
+ iBytesRet);
+ if (ret < 0) {
+ /* Unsuccessful shutdown, log as INFO */
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO, "nsd_ossl: "
+ "TLS session terminated successfully to remote syslog server '%s' with SSL Error '%d':"
+ " End Session", fromHostIP, ret);
+ }
+ dbgprintf( "osslEndSess: TLS session terminated successfully to remote syslog server '%s'"
+ " End Session", fromHostIP);
+ } else {
+ dbgprintf("osslEndSess: TLS session terminated successfully with remote syslog server '%s':"
+ " End Session", fromHostIP);
+ }
+
+ /* Session closed */
+ pThis->bHaveSess = 0;
+ }
+
+ if (fromHostIP != NULL) {
+ free(fromHostIP);
+ }
+ RETiRet;
+}
+/* ---------------------------- end OpenSSL specifics ---------------------------- */
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(nsd_ossl) /* be sure to specify the object type also in END macro! */
+ /* construct nsd_ptcp helper */
+ CHKiRet(nsd_ptcp.Construct(&pThis->pTcp));
+ /* construct net_ossl helper */
+ CHKiRet(net_ossl.Construct(&pThis->pNetOssl));
+finalize_it:
+ENDobjConstruct(nsd_ossl)
+
+
+/* destructor for the nsd_ossl object */
+PROTOTYPEobjDestruct(nsd_ossl);
+BEGINobjDestruct(nsd_ossl) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nsd_ossl)
+ DBGPRINTF("nsd_ossl_destruct: [%p] Mode %d\n", pThis, pThis->iMode);
+ if(pThis->iMode == 1) {
+ osslEndSess(pThis);
+ }
+ /* TODO MOVE Free SSL obj also if we do not have a session - or are NOT in TLS mode! */
+ if (pThis->pNetOssl->ssl != NULL) {
+ DBGPRINTF("nsd_ossl_destruct: [%p] FREE pThis->pNetOssl->ssl \n", pThis);
+ SSL_free(pThis->pNetOssl->ssl);
+ pThis->pNetOssl->ssl = NULL;
+ }
+ /**/
+ if(pThis->pTcp != NULL) {
+ nsd_ptcp.Destruct(&pThis->pTcp);
+ }
+ if(pThis->pNetOssl != NULL) {
+ net_ossl.Destruct(&pThis->pNetOssl);
+ }
+
+ free(pThis->pszConnectHost);
+ free(pThis->pszRcvBuf);
+ENDobjDestruct(nsd_ossl)
+
+
+/* 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, const int mode)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if(mode != 0 && mode != 1) {
+ LogError(0, RS_RET_INVALID_DRVR_MODE, "error: driver mode %d not supported by"
+ " ossl netstream driver", mode);
+ }
+ pThis->iMode = mode;
+
+ 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 *const pNsd, uchar *const mode)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if(mode == NULL || !strcasecmp((char*)mode, "x509/name")) {
+ pThis->pNetOssl->authMode = OSSL_AUTH_CERTNAME;
+ } else if(!strcasecmp((char*) mode, "x509/fingerprint")) {
+ pThis->pNetOssl->authMode = OSSL_AUTH_CERTFINGERPRINT;
+ } else if(!strcasecmp((char*) mode, "x509/certvalid")) {
+ pThis->pNetOssl->authMode = OSSL_AUTH_CERTVALID;
+ } else if(!strcasecmp((char*) mode, "anon")) {
+ pThis->pNetOssl->authMode = OSSL_AUTH_CERTANON;
+
+ } else {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: authentication mode '%s' not supported by "
+ "ossl netstream driver", mode);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+ dbgprintf("SetAuthMode: Set Mode %s/%d\n", mode, pThis->pNetOssl->authMode);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set the PermitExpiredCerts mode. For us, the following is supported:
+ * on - fail if certificate is expired
+ * off - ignore expired certificates
+ * warn - warn if certificate is expired
+ * alorbach, 2018-12-20
+ */
+static rsRetVal
+SetPermitExpiredCerts(nsd_t *pNsd, uchar *mode)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ /* default is set to off! */
+ if(mode == NULL || !strcasecmp((char*)mode, "off")) {
+ pThis->permitExpiredCerts = OSSL_EXPIRED_DENY;
+ } else if(!strcasecmp((char*) mode, "warn")) {
+ pThis->permitExpiredCerts = OSSL_EXPIRED_WARN;
+ } else if(!strcasecmp((char*) mode, "on")) {
+ pThis->permitExpiredCerts = OSSL_EXPIRED_PERMIT;
+ } else {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: permitexpiredcerts mode '%s' not supported by "
+ "ossl netstream driver", mode);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+ dbgprintf("SetPermitExpiredCerts: Set Mode %s/%d\n", mode, pThis->permitExpiredCerts);
+
+/* 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_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if(pPermPeers == NULL)
+ FINALIZE;
+
+ if(pThis->pNetOssl->authMode != OSSL_AUTH_CERTFINGERPRINT && pThis->pNetOssl->authMode != OSSL_AUTH_CERTNAME) {
+ LogError(0, RS_RET_VALUE_NOT_IN_THIS_MODE, "authentication not supported by "
+ "ossl netstream driver in the configured authentication mode - ignored");
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_IN_THIS_MODE);
+ }
+ pThis->pNetOssl->pPermPeers = pPermPeers;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Provide access to the underlying OS socket. This is primarily
+ * useful for other drivers (like nsd_ossl) who utilize ourselfs
+ * for some of their functionality. -- rgerhards, 2008-04-18
+ */
+static rsRetVal
+SetSock(nsd_t *pNsd, int sock)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ assert(sock >= 0);
+
+ DBGPRINTF("SetSock for [%p]: Setting sock %d\n", pNsd, sock);
+ nsd_ptcp.SetSock(pThis->pTcp, sock);
+
+ RETiRet;
+}
+
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveIntvl(nsd_t *pNsd, int keepAliveIntvl)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ assert(keepAliveIntvl >= 0);
+
+ dbgprintf("SetKeepAliveIntvl: keepAliveIntvl=%d\n", keepAliveIntvl);
+ nsd_ptcp.SetKeepAliveIntvl(pThis->pTcp, keepAliveIntvl);
+
+ RETiRet;
+}
+
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveProbes(nsd_t *pNsd, int keepAliveProbes)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ assert(keepAliveProbes >= 0);
+
+ dbgprintf("SetKeepAliveProbes: keepAliveProbes=%d\n", keepAliveProbes);
+ nsd_ptcp.SetKeepAliveProbes(pThis->pTcp, keepAliveProbes);
+
+ RETiRet;
+}
+
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveTime(nsd_t *pNsd, int keepAliveTime)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ assert(keepAliveTime >= 0);
+
+ dbgprintf("SetKeepAliveTime: keepAliveTime=%d\n", keepAliveTime);
+ nsd_ptcp.SetKeepAliveTime(pThis->pTcp, keepAliveTime);
+
+ 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_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+
+ if(pThis->iMode == 0) {
+ nsd_ptcp.Abort(pThis->pTcp);
+ }
+
+ RETiRet;
+}
+
+
+/* Callback after netstrm obj init in nsd_ptcp - permits us to add some data */
+static rsRetVal
+LstnInitDrvr(netstrm_t *const pThis)
+{
+ DEFiRet;
+ nsd_ossl_t* pNsdOssl = (nsd_ossl_t*) pThis->pDrvrData;
+ /* Create main CTX Object. Use SSLv23_method for < Openssl 1.1.0 and TLS_method for all newer versions! */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ CHKiRet(net_ossl.osslCtxInit(pNsdOssl->pNetOssl, SSLv23_method()));
+#else
+ CHKiRet(net_ossl.osslCtxInit(pNsdOssl->pNetOssl, TLS_method()));
+#endif
+ // Apply PriorityString after Ctx Creation
+ applyGnutlsPriorityString(pNsdOssl);
+finalize_it:
+ RETiRet;
+}
+
+
+/* initialize the tcp socket for a listner
+ * Here, we use the ptcp driver - because there is nothing special
+ * at this point with OpenSSL. Things become special once we accept
+ * a session, but not during listener setup.
+ */
+static rsRetVal
+LstnInit(netstrms_t *pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ const int iSessMax, const tcpLstnParams_t *const cnf_params)
+{
+ DEFiRet;
+
+ dbgprintf("LstnInit for openssl: entering LstnInit (%p) for %s:%s SessMax=%d\n",
+ fAddLstn, cnf_params->pszAddr, cnf_params->pszPort, iSessMax);
+
+ pNS->fLstnInitDrvr = LstnInitDrvr;
+ /* Init TCP Listener using base ptcp class */
+ iRet = nsd_ptcp.LstnInit(pNS, pUsr, fAddLstn, iSessMax, cnf_params);
+ 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)
+{
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+
+ dbgprintf("CheckConnection for %p\n", pNsd);
+ return nsd_ptcp.CheckConnection(pThis->pTcp);
+}
+
+
+/* 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_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ iRet = nsd_ptcp.GetRemoteHName(pThis->pTcp, ppszHName);
+ dbgprintf("GetRemoteHName for %p = %s\n", pNsd, *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_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ iRet = nsd_ptcp.GetRemAddr(pThis->pTcp, ppAddr);
+ dbgprintf("GetRemAddr for %p\n", pNsd);
+ RETiRet;
+}
+
+
+/* get the remote host's IP address. Caller must Destruct the object. */
+static rsRetVal
+GetRemoteIP(nsd_t *pNsd, prop_t **ip)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ iRet = nsd_ptcp.GetRemoteIP(pThis->pTcp, ip);
+ dbgprintf("GetRemoteIP for %p\n", pNsd);
+ RETiRet;
+}
+
+
+/* Perform all necessary checks after Handshake
+ */
+rsRetVal
+osslPostHandshakeCheck(nsd_ossl_t *pNsd)
+{
+ DEFiRet;
+ char szDbg[255];
+ const SSL_CIPHER* sslCipher;
+
+ /* Some extra output for debugging openssl */
+ if (SSL_get_shared_ciphers(pNsd->pNetOssl->ssl,szDbg, sizeof szDbg) != NULL)
+ dbgprintf("osslPostHandshakeCheck: Debug Shared ciphers = %s\n", szDbg);
+
+ #if OPENSSL_VERSION_NUMBER >= 0x10002000L
+ if(SSL_get_shared_curve(pNsd->pNetOssl->ssl, -1) == 0) {
+ // This is not a failure
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO, "nsd_ossl: "
+ "Information, no shared curve between syslog client and server");
+ }
+ #endif
+ dbgprintf("osslPostHandshakeCheck: Debug Protocol Version: %s\n",
+ SSL_get_version(pNsd->pNetOssl->ssl));
+
+ sslCipher = (const SSL_CIPHER*) SSL_get_current_cipher(pNsd->pNetOssl->ssl);
+ if (sslCipher != NULL){
+ if(SSL_CIPHER_get_version(sslCipher) == NULL) {
+ LogError(0, RS_RET_NO_ERRCODE, "nsd_ossl:"
+ "TLS version mismatch between syslog client and server.");
+ }
+ dbgprintf("osslPostHandshakeCheck: Debug Cipher Version: %s Name: %s\n",
+ SSL_CIPHER_get_version(sslCipher), SSL_CIPHER_get_name(sslCipher));
+ }else {
+ LogError(0, RS_RET_NO_ERRCODE, "nsd_ossl:No shared ciphers between syslog client and server.");
+ }
+
+ FINALIZE;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Perform all necessary actions for Handshake
+ */
+rsRetVal
+osslHandshakeCheck(nsd_ossl_t *pNsd)
+{
+ DEFiRet;
+ uchar *fromHostIP = NULL;
+ int res, resErr;
+ dbgprintf("osslHandshakeCheck: Starting TLS Handshake for ssl[%p]\n", (void *)pNsd->pNetOssl->ssl);
+
+ if (pNsd->pNetOssl->sslState == osslServer) {
+ /* Handle Server SSL Object */
+ if((res = SSL_accept(pNsd->pNetOssl->ssl)) <= 0) {
+ /* Obtain SSL Error code */
+ nsd_ptcp.GetRemoteHName((nsd_t*)pNsd->pTcp, &fromHostIP);
+ resErr = SSL_get_error(pNsd->pNetOssl->ssl, res);
+ if( resErr == SSL_ERROR_WANT_READ ||
+ resErr == SSL_ERROR_WANT_WRITE) {
+ pNsd->rtryCall = osslRtry_handshake;
+ pNsd->rtryOsslErr = resErr; /* Store SSL ErrorCode into*/
+ dbgprintf("osslHandshakeCheck: OpenSSL Server handshake does not complete "
+ "immediately - setting to retry (this is OK and normal)\n");
+ FINALIZE;
+ } else if(resErr == SSL_ERROR_SYSCALL) {
+ dbgprintf("osslHandshakeCheck: OpenSSL Server handshake failed with SSL_ERROR_SYSCALL "
+ "- Aborting handshake.\n");
+ nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_WARNING,
+ "osslHandshakeCheck Server", "SSL_accept");
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING,
+ "nsd_ossl:TLS session terminated with remote client '%s': "
+ "Handshake failed with SSL_ERROR_SYSCALL", fromHostIP);
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ } else {
+ nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_ERR,
+ "osslHandshakeCheck Server", "SSL_accept");
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING,
+ "nsd_ossl:TLS session terminated with remote client '%s': "
+ "Handshake failed with error code: %d", fromHostIP, resErr);
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ }
+ }
+ } else {
+ /* Handle Client SSL Object */
+ if((res = SSL_do_handshake(pNsd->pNetOssl->ssl)) <= 0) {
+ /* Obtain SSL Error code */
+ nsd_ptcp.GetRemoteHName((nsd_t*)pNsd->pTcp, &fromHostIP);
+ resErr = SSL_get_error(pNsd->pNetOssl->ssl, res);
+ if( resErr == SSL_ERROR_WANT_READ ||
+ resErr == SSL_ERROR_WANT_WRITE) {
+ pNsd->rtryCall = osslRtry_handshake;
+ pNsd->rtryOsslErr = resErr; /* Store SSL ErrorCode into*/
+ dbgprintf("osslHandshakeCheck: OpenSSL Client handshake does not complete "
+ "immediately - setting to retry (this is OK and normal)\n");
+ FINALIZE;
+ } else if(resErr == SSL_ERROR_SYSCALL) {
+ dbgprintf("osslHandshakeCheck: OpenSSL Client handshake failed with SSL_ERROR_SYSCALL "
+ "- Aborting handshake.\n");
+ nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_WARNING,
+ "osslHandshakeCheck Client", "SSL_do_handshake");
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE /*RS_RET_RETRY*/);
+ } else {
+ dbgprintf("osslHandshakeCheck: OpenSSL Client handshake failed with %d "
+ "- Aborting handshake.\n", resErr);
+ nsd_ossl_lastOpenSSLErrorMsg(pNsd, res, pNsd->pNetOssl->ssl, LOG_ERR,
+ "osslHandshakeCheck Client", "SSL_do_handshake");
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_WARNING,
+ "nsd_ossl:TLS session terminated with remote syslog server '%s':"
+ "Handshake failed with error code: %d", fromHostIP, resErr);
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ }
+ }
+ }
+
+ /* Do post handshake stuff */
+ CHKiRet(osslPostHandshakeCheck(pNsd));
+
+ /* Now check authorization */
+ CHKiRet(osslChkPeerAuth(pNsd));
+finalize_it:
+ if (fromHostIP != NULL) {
+ free(fromHostIP);
+ }
+ if(iRet == RS_RET_OK) {
+ /* If no error occurred, set socket to SSL mode */
+ pNsd->iMode = 1;
+ }
+
+ 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;
+ nsd_ossl_t *pNew = NULL;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ CHKiRet(nsd_osslConstruct(&pNew));
+ CHKiRet(nsd_ptcp.Destruct(&pNew->pTcp));
+ dbgprintf("AcceptConnReq for [%p]: Accepting connection ... \n", (void *)pThis);
+ CHKiRet(nsd_ptcp.AcceptConnReq(pThis->pTcp, &pNew->pTcp));
+
+ if(pThis->iMode == 0) {
+ /*we are in non-TLS mode, so we are done */
+ DBGPRINTF("AcceptConnReq: NOT in TLS mode!\n");
+ *ppNew = (nsd_t*) pNew;
+ FINALIZE;
+ }
+
+ /* If we reach this point, we are in TLS mode */
+ pNew->pNetOssl->authMode = pThis->pNetOssl->authMode;
+ pNew->permitExpiredCerts = pThis->permitExpiredCerts;
+ pNew->pNetOssl->pPermPeers = pThis->pNetOssl->pPermPeers;
+ pNew->DrvrVerifyDepth = pThis->DrvrVerifyDepth;
+ pNew->gnutlsPriorityString = pThis->gnutlsPriorityString;
+ pNew->pNetOssl->ctx = pThis->pNetOssl->ctx;
+ pNew->pNetOssl->ctx_is_copy = 1; // do not free on pNew Destruction
+ CHKiRet(osslInitSession(pNew, osslServer));
+
+ /* Store nsd_ossl_t* reference in SSL obj */
+ SSL_set_ex_data(pNew->pNetOssl->ssl, 0, pThis->pTcp);
+ SSL_set_ex_data(pNew->pNetOssl->ssl, 1, &pThis->permitExpiredCerts);
+
+ /* We now do the handshake */
+ CHKiRet(osslHandshakeCheck(pNew));
+
+ *ppNew = (nsd_t*) pNew;
+finalize_it:
+ /* Accept appears to be done here */
+ if(pNew != NULL) {
+ DBGPRINTF("AcceptConnReq: END iRet = %d, pNew=[%p], pNsd->rtryCall=%d\n",
+ iRet, pNew, pNew->rtryCall);
+ }
+ if(iRet != RS_RET_OK) {
+ if(pNew != NULL) {
+ nsd_osslDestruct(&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, int *const oserr)
+{
+ DEFiRet;
+ ssize_t iBytesCopy; /* how many bytes are to be copied to the client buffer? */
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ DBGPRINTF("Rcv for %p\n", pNsd);
+
+ if(pThis->bAbortConn)
+ ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ);
+
+ if(pThis->iMode == 0) {
+ CHKiRet(nsd_ptcp.Rcv(pThis->pTcp, pBuf, pLenBuf, oserr));
+ 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_OSSL_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(osslRecordRecv(pThis));
+ }
+
+ if(pThis->lenRcvBuf == 0) { /* EOS */
+ *oserr = errno;
+ 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:
+ if (iRet != RS_RET_OK) {
+ if (iRet == RS_RET_CLOSED) {
+ if (pThis->pNetOssl->ssl != NULL) {
+ /* Set SSL Shutdown */
+ SSL_shutdown(pThis->pNetOssl->ssl);
+ dbgprintf("osslRcv SSL_shutdown done\n");
+ }
+ } else if (iRet != RS_RET_RETRY) {
+ /* We need to free the receive buffer in error error case unless a retry is wanted. , if we
+ * allocated one. -- rgerhards, 2008-12-03 -- moved here by alorbach, 2015-12-01
+ */
+ *pLenBuf = 0;
+ free(pThis->pszRcvBuf);
+ pThis->pszRcvBuf = NULL;
+ } else {
+ /* RS_RET_RETRY | Check for SSL Shutdown */
+ if (SSL_get_shutdown(pThis->pNetOssl->ssl) == SSL_RECEIVED_SHUTDOWN) {
+ dbgprintf("osslRcv received SSL_RECEIVED_SHUTDOWN!\n");
+ iRet = RS_RET_CLOSED;
+
+ /* Send Shutdown message back */
+ SSL_shutdown(pThis->pNetOssl->ssl);
+ }
+ }
+ }
+ dbgprintf("osslRcv 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)
+{
+ DEFiRet;
+ int iSent;
+ int err;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ DBGPRINTF("Send for %p\n", pNsd);
+
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+
+ if(pThis->bAbortConn)
+ ABORT_FINALIZE(RS_RET_CONNECTION_ABORTREQ);
+
+ if(pThis->iMode == 0) {
+ CHKiRet(nsd_ptcp.Send(pThis->pTcp, pBuf, pLenBuf));
+ FINALIZE;
+ }
+
+ while(1) {
+ iSent = SSL_write(pThis->pNetOssl->ssl, pBuf, *pLenBuf);
+ if(iSent > 0) {
+ *pLenBuf = iSent;
+ break;
+ } else {
+ err = SSL_get_error(pThis->pNetOssl->ssl, iSent);
+ if( err == SSL_ERROR_ZERO_RETURN ) {
+ DBGPRINTF("Send: SSL_ERROR_ZERO_RETURN received, retry next time\n");
+ ABORT_FINALIZE(RS_RET_RETRY);
+ }
+ else if(err == SSL_ERROR_SYSCALL) {
+ /* Output error and abort */
+ nsd_ossl_lastOpenSSLErrorMsg(pThis, iSent, pThis->pNetOssl->ssl, LOG_INFO,
+ "Send", "SSL_write");
+ iRet = RS_RET_NO_ERRCODE;
+ /* Check for underlaying socket errors **/
+ if ( errno == ECONNRESET) {
+ dbgprintf("Send: SSL_ERROR_SYSCALL Connection was reset by remote\n");
+ /* Connection was dropped from remote site */
+ iRet = RS_RET_CLOSED;
+ } else {
+ DBGPRINTF("Send: SSL_ERROR_SYSCALL Errno %d\n", errno);
+ }
+ ABORT_FINALIZE(iRet);
+ }
+ else if(err != SSL_ERROR_WANT_READ &&
+ err != SSL_ERROR_WANT_WRITE) {
+ /* Output error and abort */
+ nsd_ossl_lastOpenSSLErrorMsg(pThis, iSent, pThis->pNetOssl->ssl, LOG_ERR,
+ "Send", "SSL_write");
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ } else {
+ /* Check for SSL Shutdown */
+ if (SSL_get_shutdown(pThis->pNetOssl->ssl) == SSL_RECEIVED_SHUTDOWN) {
+ dbgprintf("osslRcv received SSL_RECEIVED_SHUTDOWN!\n");
+ ABORT_FINALIZE(RS_RET_CLOSED);
+ }
+ }
+ }
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* Enable KEEPALIVE handling on the socket.
+ * rgerhards, 2009-06-02
+ */
+static rsRetVal
+EnableKeepAlive(nsd_t *pNsd)
+{
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ return nsd_ptcp.EnableKeepAlive(pThis->pTcp);
+}
+
+
+/* open a connection to a remote host (server). With OpenSSL, we always
+ * open a plain tcp socket and then, if in TLS mode, do a handshake on it.
+ */
+static rsRetVal
+Connect(nsd_t *pNsd, int family, uchar *port, uchar *host, char *device)
+{
+ DEFiRet;
+ DBGPRINTF("openssl: entering Connect family=%d, device=%s\n", family, device);
+ nsd_ossl_t* pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+ assert(port != NULL);
+ assert(host != NULL);
+
+ /* Create main CTX Object. Use SSLv23_method for < Openssl 1.1.0 and TLS_method for all newer versions! */
+#if OPENSSL_VERSION_NUMBER < 0x10100000L
+ CHKiRet(net_ossl.osslCtxInit(pThis->pNetOssl, SSLv23_method()));
+#else
+ CHKiRet(net_ossl.osslCtxInit(pThis->pNetOssl, TLS_method()));
+#endif
+ // Apply PriorityString after Ctx Creation
+ applyGnutlsPriorityString(pThis);
+
+ // Perform TCP Connect
+ CHKiRet(nsd_ptcp.Connect(pThis->pTcp, family, port, host, device));
+
+ if(pThis->iMode == 0) {
+ /*we are in non-TLS mode, so we are done */
+ DBGPRINTF("Connect: NOT in TLS mode!\n");
+ FINALIZE;
+ }
+
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO, "nsd_ossl: "
+ "TLS Connection initiated with remote syslog server.");
+ /*if we reach this point we are in tls mode */
+ DBGPRINTF("Connect: TLS Mode\n");
+
+ /* Do SSL Session init */
+ CHKiRet(osslInitSession(pThis, osslClient));
+
+ /* Store nsd_ossl_t* reference in SSL obj */
+ SSL_set_ex_data(pThis->pNetOssl->ssl, 0, pThis->pTcp);
+ SSL_set_ex_data(pThis->pNetOssl->ssl, 1, &pThis->permitExpiredCerts);
+
+ /* We now do the handshake */
+ iRet = osslHandshakeCheck(pThis);
+finalize_it:
+ /* Connect appears to be done here */
+ dbgprintf("Connect: END iRet = %d, pThis=[%p], pNsd->rtryCall=%d\n",
+ iRet, pThis, pThis->rtryCall);
+ if(iRet != RS_RET_OK) {
+ if(pThis->bHaveSess) {
+ pThis->bHaveSess = 0;
+ SSL_free(pThis->pNetOssl->ssl);
+ pThis->pNetOssl->ssl = NULL;
+ }
+ }
+ RETiRet;
+}
+
+static rsRetVal
+SetGnutlsPriorityString(nsd_t *const pNsd, uchar *const gnutlsPriorityString)
+{
+ DEFiRet;
+ nsd_ossl_t* pThis = (nsd_ossl_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
+ sbool ApplySettings = 0;
+ if ( (gnutlsPriorityString != NULL && pThis->gnutlsPriorityString == NULL) ||
+ (gnutlsPriorityString != NULL &&
+ strcmp( (const char*)pThis->gnutlsPriorityString, (const char*)gnutlsPriorityString) != 0)
+ ) {
+ ApplySettings = 1;
+ }
+
+ pThis->gnutlsPriorityString = gnutlsPriorityString;
+ dbgprintf("gnutlsPriorityString: set to '%s' Apply %s\n",
+ (gnutlsPriorityString != NULL ? (char*)gnutlsPriorityString : "NULL"),
+ (ApplySettings == 1? "TRUE" : "FALSE"));
+ if (ApplySettings == 1) {
+ /* Apply Settings if necessary */
+ applyGnutlsPriorityString(pThis);
+ }
+
+#else
+ LogError(0, RS_RET_SYS_ERR, "Warning: TLS library does not support SSL_CONF_cmd API"
+ "(maybe it is too old?). Cannot use gnutlsPriorityString ('%s'). For more see: "
+ "https://www.rsyslog.com/doc/master/configuration/modules/imtcp.html#gnutlsprioritystring",
+ gnutlsPriorityString);
+#endif
+ RETiRet;
+}
+
+
+static rsRetVal
+applyGnutlsPriorityString(nsd_ossl_t *const pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nsd_ossl);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L && !defined(LIBRESSL_VERSION_NUMBER)
+ /* Note: we disable unkonwn functions. The corresponding error message is
+ * generated during SetGntuTLSPriorityString().
+ */
+ if(pThis->gnutlsPriorityString == NULL || pThis->pNetOssl->ctx == NULL) {
+ FINALIZE;
+ } else {
+ CHKiRet(net_ossl_apply_tlscgfcmd(pThis->pNetOssl, pThis->gnutlsPriorityString));
+ }
+#endif
+
+finalize_it:
+ RETiRet;
+}
+
+/* Set the driver cert extended key usage check setting, for now it is empty wrapper.
+ * TODO: implement openSSL version
+ * jvymazal, 2019-08-16
+ */
+static rsRetVal
+SetCheckExtendedKeyUsage(nsd_t __attribute__((unused)) *pNsd, int ChkExtendedKeyUsage)
+{
+ DEFiRet;
+ if(ChkExtendedKeyUsage != 0) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver ChkExtendedKeyUsage %d "
+ "not supported by ossl netstream driver", ChkExtendedKeyUsage);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* Set the driver name checking strictness, for now it is empty wrapper.
+ * TODO: implement openSSL version
+ * jvymazal, 2019-08-16
+ */
+static rsRetVal
+SetPrioritizeSAN(nsd_t __attribute__((unused)) *pNsd, int prioritizeSan)
+{
+ DEFiRet;
+ if(prioritizeSan != 0) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver prioritizeSan %d "
+ "not supported by ossl netstream driver", prioritizeSan);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* Set the driver tls verifyDepth
+ * alorbach, 2019-12-20
+ */
+static rsRetVal
+SetTlsVerifyDepth(nsd_t *pNsd, int verifyDepth)
+{
+ DEFiRet;
+ nsd_ossl_t *pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if (verifyDepth == 0) {
+ FINALIZE;
+ }
+ assert(verifyDepth >= 2);
+ pThis->DrvrVerifyDepth = verifyDepth;
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal
+SetTlsCAFile(nsd_t *pNsd, const uchar *const caFile)
+{
+ DEFiRet;
+ nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if(caFile == NULL) {
+ pThis->pNetOssl->pszCAFile = NULL;
+ } else {
+ CHKmalloc(pThis->pNetOssl->pszCAFile = (const uchar*) strdup((const char*) caFile));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCRLFile(nsd_t *pNsd, const uchar *const crlFile)
+{
+ DEFiRet;
+ nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if(crlFile == NULL) {
+ pThis->pNetOssl->pszCRLFile = NULL;
+ } else {
+ CHKmalloc(pThis->pNetOssl->pszCRLFile = (const uchar*) strdup((const char*) crlFile));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal
+SetTlsKeyFile(nsd_t *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if(pszFile == NULL) {
+ pThis->pNetOssl->pszKeyFile = NULL;
+ } else {
+ CHKmalloc(pThis->pNetOssl->pszKeyFile = (const uchar*) strdup((const char*) pszFile));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCertFile(nsd_t *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ nsd_ossl_t *const pThis = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ossl);
+ if(pszFile == NULL) {
+ pThis->pNetOssl->pszCertFile = NULL;
+ } else {
+ CHKmalloc(pThis->pNetOssl->pszCertFile = (const uchar*) strdup((const char*) pszFile));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nsd_ossl)
+CODESTARTobjQueryInterface(nsd_ossl)
+ 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_osslConstruct;
+ pIf->Destruct = (rsRetVal(*)(nsd_t**)) nsd_osslDestruct;
+ 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->SetPermitExpiredCerts = SetPermitExpiredCerts;
+ pIf->SetPermPeers =SetPermPeers;
+ pIf->CheckConnection = CheckConnection;
+ pIf->GetRemoteHName = GetRemoteHName;
+ pIf->GetRemoteIP = GetRemoteIP;
+ pIf->GetRemAddr = GetRemAddr;
+ pIf->EnableKeepAlive = EnableKeepAlive;
+ pIf->SetKeepAliveIntvl = SetKeepAliveIntvl;
+ pIf->SetKeepAliveProbes = SetKeepAliveProbes;
+ pIf->SetKeepAliveTime = SetKeepAliveTime;
+ pIf->SetGnutlsPriorityString = SetGnutlsPriorityString; /* we don't NEED this interface! */
+ pIf->SetCheckExtendedKeyUsage = SetCheckExtendedKeyUsage; /* we don't NEED this interface! */
+ pIf->SetPrioritizeSAN = SetPrioritizeSAN; /* we don't NEED this interface! */
+ pIf->SetTlsVerifyDepth = SetTlsVerifyDepth;
+ pIf->SetTlsCAFile = SetTlsCAFile;
+ pIf->SetTlsCRLFile = SetTlsCRLFile;
+ pIf->SetTlsKeyFile = SetTlsKeyFile;
+ pIf->SetTlsCertFile = SetTlsCertFile;
+
+finalize_it:
+ENDobjQueryInterface(nsd_ossl)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nsd_ossl, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsd_ossl)
+ /* release objects we no longer need */
+ objRelease(net_ossl, CORE_COMPONENT);
+ objRelease(nsd_ptcp, LM_NSD_PTCP_FILENAME);
+ objRelease(net, LM_NET_FILENAME);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+ENDObjClassExit(nsd_ossl)
+
+
+/* Initialize the nsd_ossl class. Must be called as the very first method
+ * before anything else is called inside this class.
+ */
+BEGINObjClassInit(nsd_ossl, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(net, LM_NET_FILENAME));
+ CHKiRet(objUse(nsd_ptcp, LM_NSD_PTCP_FILENAME));
+ CHKiRet(objUse(net_ossl, CORE_COMPONENT));
+ENDObjClassInit(nsd_ossl)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ nsdsel_osslClassExit();
+ nsd_osslClassExit();
+ net_osslClassExit();
+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(net_osslClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ CHKiRet(nsd_osslClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ CHKiRet(nsdsel_osslClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
diff --git a/runtime/nsd_ossl.h b/runtime/nsd_ossl.h
new file mode 100644
index 0000000..000b277
--- /dev/null
+++ b/runtime/nsd_ossl.h
@@ -0,0 +1,85 @@
+/* An implementation of the nsd interface for OpenSSL.
+ *
+ * Copyright 2018-2023 Adiscon GmbH.
+ * Author: Andre Lorbach
+ *
+ * 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_OSSL_H
+#define INCLUDED_NSD_OSSL_H
+
+#include "nsd.h"
+
+#define NSD_OSSL_MAX_RCVBUF 16 * 1024 + 1/* TLS RFC 8449: max size of buffer for message reception */
+
+typedef enum {
+ osslRtry_None = 0, /**< no call needs to be retried */
+ osslRtry_handshake = 1,
+ osslRtry_recv = 2
+} osslRtryCall_t; /**< IDs of calls that needs to be retried */
+
+typedef nsd_if_t nsd_ossl_if_t; /* we just *implement* this interface */
+
+/* the nsd_ossl object */
+struct nsd_ossl_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) */
+ PermitExpiredCerts permitExpiredCerts;
+ osslRtryCall_t rtryCall;/**< what must we retry? */
+ int rtryOsslErr; /**< store ssl error code into like SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE */
+ int bIsInitiator; /**< 0 if socket is the server end (listener), 1 if it is the initiator */
+ 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) */
+ uchar *gnutlsPriorityString; /* gnutls priority string */
+ int DrvrVerifyDepth; /* Verify Depth for certificate chains */
+ 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_OSSL_MAX_RCVBUF-1: data of that size present */
+ int ptrRcvBuf; /**< offset for next recv operation if 0 < lenRcvBuf < NSD_OSSL_MAX_RCVBUF */
+
+ /* OpenSSL and Config Cert vars inside net_ossl_t now */
+ net_ossl_t *pNetOssl; /* OSSL shared Config and object vars are here */
+};
+
+/* interface is defined in nsd.h, we just implement it! */
+#define nsd_osslCURR_IF_VERSION nsdCURR_IF_VERSION
+
+/* prototypes */
+PROTOTYPEObj(nsd_ossl);
+
+/* some prototypes for things used by our nsdsel_ossl helper class */
+uchar *osslStrerror(int error);
+rsRetVal osslChkPeerAuth(nsd_ossl_t *pThis);
+rsRetVal osslRecordRecv(nsd_ossl_t *pThis);
+rsRetVal osslHandshakeCheck(nsd_ossl_t *pNsd);
+void nsd_ossl_lastOpenSSLErrorMsg(nsd_ossl_t const *pThis, const int ret, SSL *ssl, int severity,
+ const char* pszCallSource, const char* pszOsslApi);
+rsRetVal osslPostHandshakeCheck(nsd_ossl_t *pNsd);
+
+/* the name of our library binary */
+#define LM_NSD_OSSL_FILENAME "lmnsd_ossl"
+
+#endif /* #ifndef INCLUDED_NSD_OSSL_H */
diff --git a/runtime/nsd_ptcp.c b/runtime/nsd_ptcp.c
new file mode 100644
index 0000000..6e2fd67
--- /dev/null
+++ b/runtime/nsd_ptcp.c
@@ -0,0 +1,1153 @@
+/* nsd_ptcp.c
+ *
+ * An implementation of the nsd interface for plain tcp sockets.
+ *
+ * Copyright 2007-2019 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>
+#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 <netinet/tcp.h>
+
+#include "rsyslog.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"
+#include "rsconf.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+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) {
+ 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 driver cert extended key usage check setting, not supported in ptcp.
+ * jvymazal, 2019-08-16
+ */
+static rsRetVal
+SetCheckExtendedKeyUsage(nsd_t __attribute__((unused)) *pNsd, int ChkExtendedKeyUsage)
+{
+ DEFiRet;
+ if(ChkExtendedKeyUsage != 0) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver ChkExtendedKeyUsage %d "
+ "not supported by ptcp netstream driver", ChkExtendedKeyUsage);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* Set the driver name checking strictness, not supported in ptcp.
+ * jvymazal, 2019-08-16
+ */
+static rsRetVal
+SetPrioritizeSAN(nsd_t __attribute__((unused)) *pNsd, int prioritizeSan)
+{
+ DEFiRet;
+ if(prioritizeSan != 0) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: driver prioritizeSan %d "
+ "not supported by ptcp netstream driver", prioritizeSan);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* Set the tls verify depth, not supported in ptcp.
+ * alorbach, 2019-12-20
+ */
+static rsRetVal
+SetTlsVerifyDepth(nsd_t __attribute__((unused)) *pNsd, int verifyDepth)
+{
+ nsd_ptcp_t __attribute__((unused)) *pThis = (nsd_ptcp_t*) pNsd;
+ DEFiRet;
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+ if (verifyDepth == 0) {
+ FINALIZE;
+ }
+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")) {
+ 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 PermitExpiredCerts mode. not supported in ptcp
+ * alorbach, 2018-12-20
+ */
+static rsRetVal
+SetPermitExpiredCerts(nsd_t __attribute__((unused)) *pNsd, uchar *mode)
+{
+ DEFiRet;
+ if(mode != NULL) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: permitexpiredcerts settingnot supported by "
+ "ptcp netstream driver");
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCAFile(nsd_t __attribute__((unused)) *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ if(pszFile != NULL) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: CA File setting not supported by "
+ "ptcp netstream driver - value %s", pszFile);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCRLFile(nsd_t __attribute__((unused)) *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ if(pszFile != NULL) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: CRL File setting not supported by "
+ "ptcp netstream driver - value %s", pszFile);
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsKeyFile(nsd_t __attribute__((unused)) *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ if(pszFile != NULL) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: TLS Key File setting not supported by "
+ "ptcp netstream driver");
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetTlsCertFile(nsd_t __attribute__((unused)) *pNsd, const uchar *const pszFile)
+{
+ DEFiRet;
+ if(pszFile != NULL) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: TLS Cert File setting not supported by "
+ "ptcp netstream driver");
+ ABORT_FINALIZE(RS_RET_VALUE_NOT_SUPPORTED);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+/* Set priorityString
+ * PascalWithopf 2017-08-18 */
+static rsRetVal
+SetGnutlsPriorityString(nsd_t __attribute__((unused)) *pNsd, uchar *iVal)
+{
+ DEFiRet;
+ if(iVal != NULL) {
+ LogError(0, RS_RET_VALUE_NOT_SUPPORTED, "error: "
+ "gnutlsPriorityString '%s' not supported by ptcp netstream "
+ "driver", iVal);
+ 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) {
+ 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;
+}
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveIntvl(nsd_t *pNsd, int keepAliveIntvl)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+
+ pThis->iKeepAliveIntvl = keepAliveIntvl;
+
+ RETiRet;
+}
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveProbes(nsd_t *pNsd, int keepAliveProbes)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+
+ pThis->iKeepAliveProbes = keepAliveProbes;
+
+ RETiRet;
+}
+
+/* Keep Alive Options
+ */
+static rsRetVal
+SetKeepAliveTime(nsd_t *pNsd, int keepAliveTime)
+{
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert((pThis), nsd_ptcp);
+
+ pThis->iKeepAliveTime = keepAliveTime;
+
+ 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 ATTR_NONNULL(1,3,5)
+LstnInit(netstrms_t *const pNS, void *pUsr, rsRetVal(*fAddLstn)(void*,netstrm_t*),
+ const int iSessMax, const tcpLstnParams_t *const cnf_params)
+{
+ DEFiRet;
+ netstrm_t *pNewStrm = NULL;
+ nsd_t *pNewNsd = NULL;
+ int error, maxs, on = 1;
+ int isIPv6 = 0;
+ int sock = -1;
+ int numSocks;
+ int sockflags;
+ int port_override = 0; /* if dyn port (0): use this for actually bound port */
+ struct addrinfo hints, *res = NULL, *r;
+ union {
+ struct sockaddr *sa;
+ struct sockaddr_in *ipv4;
+ struct sockaddr_in6 *ipv6;
+ } savecast;
+
+ ISOBJ_TYPE_assert(pNS, netstrms);
+ assert(fAddLstn != NULL);
+ assert(cnf_params->pszPort != NULL);
+ assert(iSessMax >= 0);
+
+ dbgprintf("creating tcp listen socket on port %s\n", cnf_params->pszPort);
+
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_flags = AI_PASSIVE;
+ hints.ai_family = glbl.GetDefPFFamily(runConf);
+ hints.ai_socktype = SOCK_STREAM;
+
+ error = getaddrinfo((const char*)cnf_params->pszAddr, (const char*) cnf_params->pszPort, &hints, &res);
+ if(error) {
+ LogError(0, RS_RET_INVALID_PORT, "error querying port '%s': %s",
+ (cnf_params->pszAddr == NULL) ? "**UNSPECIFIED**" : (const char*) cnf_params->pszAddr,
+ gai_strerror(error));
+ 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) {
+ if(port_override != 0) {
+ savecast.sa = (struct sockaddr*)r->ai_addr;
+ if(r->ai_family == AF_INET6) {
+ savecast.ipv6->sin6_port = port_override;
+ } else {
+ savecast.ipv4->sin_port = port_override;
+ }
+ }
+ 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) {
+ isIPv6 = 1;
+ 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.
+ */
+ #if !defined(_AIX) && !defined(BSD)
+ if(net.should_use_so_bsdcompat()) {
+ if (setsockopt(sock, SOL_SOCKET, SO_BSDCOMPAT,
+ (char *) &on, sizeof(on)) < 0) {
+ 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));
+ LogError(errno, NO_ERRCODE, "Error while binding tcp socket");
+ dbgprintf("error %d while binding tcp socket: %s\n", errno, errStr);
+ close(sock);
+ sock = -1;
+ continue;
+ }
+
+ /* if we bind to dynamic port (port 0 given), we will do so consistently. Thus
+ * once we got a dynamic port, we will keep it and use it for other protocols
+ * as well. As of my understanding, this should always work as the OS does not
+ * pick a port that is used by some protocol (well, at least this looks very
+ * unlikely...). If our asusmption is wrong, we should iterate until we find a
+ * combination that works - it is very unusual to have the same service listen
+ * on differnt ports on IPv4 and IPv6.
+ */
+ savecast.sa = (struct sockaddr*)r->ai_addr;
+ const int currport = (isIPv6) ? savecast.ipv6->sin6_port : savecast.ipv4->sin_port;
+ if(currport == 0) {
+ socklen_t socklen_r = r->ai_addrlen;
+ if(getsockname(sock, r->ai_addr, &socklen_r) == -1) {
+ LogError(errno, NO_ERRCODE, "nsd_ptcp: ListenPortFileName: getsockname:"
+ "error while trying to get socket");
+ }
+ r->ai_addrlen = socklen_r;
+ savecast.sa = (struct sockaddr*)r->ai_addr;
+ port_override = (isIPv6) ? savecast.ipv6->sin6_port : savecast.ipv4->sin_port;
+ if(cnf_params->pszLstnPortFileName != NULL) {
+ FILE *fp;
+ if((fp = fopen((const char*)cnf_params->pszLstnPortFileName, "w+")) == NULL) {
+ LogError(errno, RS_RET_IO_ERROR, "nsd_ptcp: ListenPortFileName: "
+ "error while trying to open file");
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ if(isIPv6) {
+ fprintf(fp, "%d", ntohs(savecast.ipv6->sin6_port));
+ } else {
+ fprintf(fp, "%d", ntohs(savecast.ipv4->sin_port));
+ }
+ fclose(fp);
+ }
+ }
+
+ 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));
+ CHKiRet(pNS->Drvr.SetMode(pNewNsd, netstrms.GetDrvrMode(pNS)));
+ CHKiRet(pNS->Drvr.SetCheckExtendedKeyUsage(pNewNsd, netstrms.GetDrvrCheckExtendedKeyUsage(pNS)));
+ CHKiRet(pNS->Drvr.SetPrioritizeSAN(pNewNsd, netstrms.GetDrvrPrioritizeSAN(pNS)));
+ CHKiRet(pNS->Drvr.SetTlsCAFile(pNewNsd, netstrms.GetDrvrTlsCAFile(pNS)));
+ CHKiRet(pNS->Drvr.SetTlsCRLFile(pNewNsd, netstrms.GetDrvrTlsCRLFile(pNS)));
+ CHKiRet(pNS->Drvr.SetTlsKeyFile(pNewNsd, netstrms.GetDrvrTlsKeyFile(pNS)));
+ CHKiRet(pNS->Drvr.SetTlsCertFile(pNewNsd, netstrms.GetDrvrTlsCertFile(pNS)));
+ CHKiRet(pNS->Drvr.SetTlsVerifyDepth(pNewNsd, netstrms.GetDrvrTlsVerifyDepth(pNS)));
+ CHKiRet(pNS->Drvr.SetAuthMode(pNewNsd, netstrms.GetDrvrAuthMode(pNS)));
+ CHKiRet(pNS->Drvr.SetPermitExpiredCerts(pNewNsd, netstrms.GetDrvrPermitExpiredCerts(pNS)));
+ CHKiRet(pNS->Drvr.SetPermPeers(pNewNsd, netstrms.GetDrvrPermPeers(pNS)));
+ CHKiRet(pNS->Drvr.SetGnutlsPriorityString(pNewNsd, netstrms.GetDrvrGnutlsPriorityString(pNS)));
+
+ CHKiRet(netstrms.CreateStrm(pNS, &pNewStrm));
+ pNewStrm->pDrvrData = (nsd_t*) pNewNsd;
+ if(pNS->fLstnInitDrvr != NULL) {
+ CHKiRet(pNS->fLstnInitDrvr(pNewStrm));
+ }
+ CHKiRet(fAddLstn(pUsr, pNewStrm));
+ pNewStrm = NULL;
+ /* sock has been handed over by SetSock() above, so invalidate it here
+ * coverity scan falsely identifies this as ressource leak
+ */
+ sock = -1;
+ ++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(sock != -1) {
+ close(sock);
+ }
+ if(res != NULL)
+ freeaddrinfo(res);
+
+ if(iRet != RS_RET_OK) {
+ if(pNewStrm != NULL)
+ netstrm.Destruct(&pNewStrm);
+ else 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 occurred and
+ * oserr holds the exact error cause.
+ * rgerhards, 2008-03-17
+ */
+static rsRetVal
+Rcv(nsd_t *pNsd, uchar *pRcvBuf, ssize_t *pLenBuf, int *const oserr)
+{
+ char errStr[1024];
+ DEFiRet;
+ nsd_ptcp_t *pThis = (nsd_ptcp_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsd_ptcp);
+ assert(oserr != NULL);
+/* AIXPORT : MSG_DONTWAIT not supported */
+#if defined (_AIX)
+#define MSG_DONTWAIT MSG_NONBLOCK
+#endif
+
+ *pLenBuf = recv(pThis->sock, pRcvBuf, *pLenBuf, MSG_DONTWAIT);
+ *oserr = errno;
+
+ 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);
+ }
+
+# if defined(IPPROTO_TCP) && defined(TCP_KEEPCNT)
+ if(pThis->iKeepAliveProbes > 0) {
+ optval = pThis->iKeepAliveProbes;
+ optlen = sizeof(optval);
+ ret = setsockopt(pThis->sock, IPPROTO_TCP, TCP_KEEPCNT, &optval, optlen);
+ } else {
+ ret = 0;
+ }
+# else
+ ret = -1;
+# endif
+ if(ret < 0) {
+ LogError(ret, NO_ERRCODE, "imptcp cannot set keepalive probes - ignored");
+ }
+
+# if defined(IPPROTO_TCP) && defined(TCP_KEEPIDLE)
+ if(pThis->iKeepAliveTime > 0) {
+ optval = pThis->iKeepAliveTime;
+ optlen = sizeof(optval);
+ ret = setsockopt(pThis->sock, IPPROTO_TCP, TCP_KEEPIDLE, &optval, optlen);
+ } else {
+ ret = 0;
+ }
+# else
+ ret = -1;
+# endif
+ if(ret < 0) {
+ LogError(ret, NO_ERRCODE, "imptcp cannot set keepalive time - ignored");
+ }
+
+# if defined(IPPROTO_TCP) && defined(TCP_KEEPCNT)
+ if(pThis->iKeepAliveIntvl > 0) {
+ optval = pThis->iKeepAliveIntvl;
+ optlen = sizeof(optval);
+ ret = setsockopt(pThis->sock, IPPROTO_TCP, TCP_KEEPINTVL, &optval, optlen);
+ } else {
+ ret = 0;
+ }
+# else
+ ret = -1;
+# endif
+ if(ret < 0) {
+ LogError(errno, NO_ERRCODE, "imptcp cannot set keepalive intvl - ignored");
+ }
+
+ dbgprintf("KEEPALIVE enabled for socket %d\n", pThis->sock);
+
+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, char *device)
+{
+ 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) {
+ LogError(errno, RS_RET_IO_ERROR, "cannot resolve hostname '%s'",
+ host);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ /* We need to copy Remote Hostname here for error logging purposes */
+ if((pThis->pRemHostName = malloc(strlen((char*)host)+1)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ memcpy(pThis->pRemHostName, host, strlen((char*)host)+1);
+
+ if((pThis->sock = socket(res->ai_family, res->ai_socktype, res->ai_protocol)) == -1) {
+ LogError(errno, RS_RET_IO_ERROR, "cannot bind socket for %s:%s",
+ host, port);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+
+ if(device) {
+# if defined(SO_BINDTODEVICE)
+ if(setsockopt(pThis->sock, SOL_SOCKET, SO_BINDTODEVICE, device, strlen(device) + 1) < 0)
+# endif
+ {
+ dbgprintf("setsockopt(SO_BINDTODEVICE) failed\n");
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ }
+
+ if(connect(pThis->sock, res->ai_addr, res->ai_addrlen) != 0) {
+ LogError(errno, RS_RET_IO_ERROR, "cannot connect to %s:%s",
+ host, port);
+ 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 (rc %d, errno %d)\n", rc, errno);
+ /* 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->SetPermitExpiredCerts = SetPermitExpiredCerts;
+ pIf->SetGnutlsPriorityString = SetGnutlsPriorityString;
+ 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;
+ pIf->SetKeepAliveIntvl = SetKeepAliveIntvl;
+ pIf->SetKeepAliveProbes = SetKeepAliveProbes;
+ pIf->SetKeepAliveTime = SetKeepAliveTime;
+ pIf->SetCheckExtendedKeyUsage = SetCheckExtendedKeyUsage;
+ pIf->SetPrioritizeSAN = SetPrioritizeSAN;
+ pIf->SetTlsVerifyDepth = SetTlsVerifyDepth;
+ pIf->SetTlsCAFile = SetTlsCAFile;
+ pIf->SetTlsCRLFile = SetTlsCRLFile;
+ pIf->SetTlsKeyFile = SetTlsKeyFile;
+ pIf->SetTlsCertFile = SetTlsCertFile;
+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(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(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 0000000..1c91718
--- /dev/null
+++ b/runtime/nsd_ptcp.h
@@ -0,0 +1,52 @@
+/* An implementation of the nsd interface for plain tcp sockets.
+ *
+ * Copyright 2007-2020 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 "tcpsrv.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 */
+ int iKeepAliveIntvl; /**< socket layer KEEPALIVE interval */
+ int iKeepAliveProbes; /**< socket layer KEEPALIVE probes */
+ int iKeepAliveTime; /**< socket layer KEEPALIVE timeout */
+};
+
+/* 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 0000000..641ae10
--- /dev/null
+++ b/runtime/nsdpoll_ptcp.c
@@ -0,0 +1,320 @@
+/* nsdpoll_ptcp.c
+ *
+ * An implementation of the nsd epoll() interface for plain tcp sockets.
+ *
+ * Copyright 2009-2016 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>
+#ifdef 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(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 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 */
+ 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 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 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));
+ 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));
+ 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;
+ }
+ *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);
+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(glbl, CORE_COMPONENT));
+
+ /* set our own handlers */
+ENDObjClassInit(nsdpoll_ptcp)
+#else
+
+#ifdef __xlc__ /* Xlc require some code, even unused, in source file*/
+static void dummy(void) {}
+#endif
+
+#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 0000000..0c0109b
--- /dev/null
+++ b/runtime/nsdpoll_ptcp.h
@@ -0,0 +1,61 @@
+/* An implementation of the nsd poll interface for plain tcp sockets.
+ *
+ * Copyright 2009-2016 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"
+#ifdef 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 {
+#ifdef 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 0000000..01cfb05
--- /dev/null
+++ b/runtime/nsdsel_gtls.c
@@ -0,0 +1,317 @@
+/* nsdsel_gtls.c
+ *
+ * An implementation of the nsd select() interface for GnuTLS.
+ *
+ * Copyright (C) 2008-2016 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(glbl)
+DEFobjCurrIf(nsdsel_ptcp)
+
+static 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);
+ }
+
+
+/* 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);
+ DBGPRINTF("Add on nsd %p:\n", pNsdGTLS);
+ 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;
+ }
+ }
+
+ dbgprintf("nsdsel_gtls: reached end on nsd %p, calling nsdsel_ptcp.Add with waitOp %d... \n", pNsdGTLS, waitOp);
+ /* 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 for %p->iBufferRcvReady=%d, data present\n",
+ pThis, pThis->iBufferRcvReady);
+ } 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("doRetry: 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 == GNUTLS_E_AGAIN || gnuRet == GNUTLS_E_INTERRUPTED) {
+ dbgprintf("doRetry: GnuTLS handshake retry did not finish - "
+ "setting to retry (this is OK and can happen)\n");
+ FINALIZE;
+ } else if(gnuRet == 0) {
+ pNsd->rtryCall = gtlsRtry_None; /* we are done */
+ /* we got a handshake, now check authorization */
+ CHKiRet(gtlsChkPeerAuth(pNsd));
+ } else {
+ uchar *pGnuErr = gtlsStrerror(gnuRet);
+ LogError(0, RS_RET_TLS_HANDSHAKE_ERR,
+ "GnuTLS handshake retry returned error: %s\n", pGnuErr);
+ free(pGnuErr);
+ ABORT_FINALIZE(RS_RET_TLS_HANDSHAKE_ERR);
+ }
+ break;
+ case gtlsRtry_recv:
+ dbgprintf("doRetry: retrying gtls recv, nsd: %p\n", pNsd);
+ iRet = gtlsRecordRecv(pNsd);
+ if (iRet == RS_RET_RETRY) {
+ // Check if there is pending data
+ size_t stBytesLeft = gnutls_record_check_pending(pNsd->sess);
+ if (stBytesLeft > 0) {
+ // We are in retry and more data waiting, finalize it
+ goto finalize_it;
+ } else {
+ dbgprintf("doRetry: gtlsRecordRecv returned RETRY, but there is no pending"
+ "data on nsd: %p\n", pNsd);
+ }
+ }
+ pNsd->rtryCall = gtlsRtry_None; /* no more data, we are done */
+ gnuRet = 0;
+ break;
+ case gtlsRtry_None:
+ 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);
+ LogError(0, RS_RET_GNUTLS_ERR, "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 */
+ 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_handshake) {
+ 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;
+ }
+ else if(pNsdGTLS->rtryCall == gtlsRtry_recv) {
+ iRet = doRetry(pNsdGTLS);
+ if(iRet == RS_RET_OK) {
+ *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, %p->buffer not available for this FD\n", pThis);
+ *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(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(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 0000000..1288097
--- /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_ossl.c b/runtime/nsdsel_ossl.c
new file mode 100644
index 0000000..cc649cf
--- /dev/null
+++ b/runtime/nsdsel_ossl.c
@@ -0,0 +1,290 @@
+/* nsdsel_ossl.c
+ *
+ * An implementation of the nsd select() interface for OpenSSL.
+ *
+ * Copyright (C) 2018-2018 Adiscon GmbH.
+ * Author: Andre Lorbach
+ *
+ * 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 "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "net_ossl.h" // Include OpenSSL Helpers
+#include "nsd.h"
+#include "nsd_ossl.h"
+#include "nsd_ptcp.h"
+#include "nsdsel_ptcp.h"
+#include "nsdsel_ossl.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(nsdsel_ptcp)
+
+static rsRetVal
+osslHasRcvInBuffer(nsd_ossl_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);
+}
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(nsdsel_ossl) /* be sure to specify the object type also in END macro! */
+ iRet = nsdsel_ptcp.Construct(&pThis->pTcp);
+ENDobjConstruct(nsdsel_ossl)
+
+
+/* destructor for the nsdsel_ossl object */
+BEGINobjDestruct(nsdsel_ossl) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(nsdsel_ossl)
+ if(pThis->pTcp != NULL)
+ nsdsel_ptcp.Destruct(&pThis->pTcp);
+ENDobjDestruct(nsdsel_ossl)
+
+
+/* Add a socket to the select set */
+static rsRetVal
+Add(nsdsel_t *pNsdsel, nsd_t *pNsd, nsdsel_waitOp_t waitOp)
+{
+ DEFiRet;
+ nsdsel_ossl_t *pThis = (nsdsel_ossl_t*) pNsdsel;
+ nsd_ossl_t *pNsdOSSL = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_ossl);
+ ISOBJ_TYPE_assert(pNsdOSSL, nsd_ossl);
+
+DBGPRINTF("Add on nsd %p:\n", pNsdOSSL);
+
+ if(pNsdOSSL->iMode == 1) {
+ if(waitOp == NSDSEL_RD && osslHasRcvInBuffer(pNsdOSSL)) {
+ ++pThis->iBufferRcvReady;
+ dbgprintf("nsdsel_ossl: data already present in buffer, initiating "
+ "dummy select %p->iBufferRcvReady=%d\n",
+ pThis, pThis->iBufferRcvReady);
+ FINALIZE;
+ }
+ if(pNsdOSSL->rtryCall != osslRtry_None) {
+/* // VERBOSE
+dbgprintf("nsdsel_ossl: rtryOsslErr=%d ... \n", pNsdOSSL->rtryOsslErr);
+*/
+ if (pNsdOSSL->rtryOsslErr == SSL_ERROR_WANT_READ) {
+ CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdOSSL->pTcp, NSDSEL_RD));
+ FINALIZE;
+ } else if (pNsdOSSL->rtryOsslErr == SSL_ERROR_WANT_WRITE) {
+ CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdOSSL->pTcp, NSDSEL_WR));
+ FINALIZE;
+ } else {
+ dbgprintf("nsdsel_ossl: rtryCall=%d, rtryOsslErr=%d, unexpected ... help?! ... \n",
+ pNsdOSSL->rtryCall, pNsdOSSL->rtryOsslErr);
+ ABORT_FINALIZE(RS_RET_NO_ERRCODE);
+ }
+
+ } else {
+ dbgprintf("nsdsel_ossl: rtryCall=%d, nothing to do ... \n",
+ pNsdOSSL->rtryCall);
+ }
+ }
+
+ dbgprintf("nsdsel_ossl: reached end, calling nsdsel_ptcp.Add with waitOp %d... \n", waitOp);
+ /* if we reach this point, we need no special handling */
+ CHKiRet(nsdsel_ptcp.Add(pThis->pTcp, pNsdOSSL->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_ossl_t *pThis = (nsdsel_ossl_t*) pNsdsel;
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_ossl);
+ if(pThis->iBufferRcvReady > 0) {
+ /* we still have data ready! */
+ *piNumReady = pThis->iBufferRcvReady;
+ dbgprintf("nsdsel_ossl: doing dummy select, data present\n");
+ } else {
+ iRet = nsdsel_ptcp.Select(pThis->pTcp, piNumReady);
+ }
+
+ RETiRet;
+}
+
+
+/* retry an interrupted OSSL operation
+ * rgerhards, 2008-04-30
+ */
+static rsRetVal
+doRetry(nsd_ossl_t *pNsd)
+{
+ DEFiRet;
+ nsd_ossl_t *pNsdOSSL = (nsd_ossl_t*) pNsd;
+
+ dbgprintf("doRetry: 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 osslRtry_handshake:
+ dbgprintf("doRetry: start osslHandshakeCheck, nsd: %p\n", pNsd);
+ /* Do the handshake again*/
+ CHKiRet(osslHandshakeCheck(pNsdOSSL));
+ pNsd->rtryCall = osslRtry_None; /* we are done */
+ break;
+ case osslRtry_recv:
+ dbgprintf("doRetry: retrying ossl recv, nsd: %p\n", pNsd);
+ CHKiRet(osslRecordRecv(pNsd));
+ pNsd->rtryCall = osslRtry_None; /* we are done */
+ break;
+ case osslRtry_None:
+ default:
+ assert(0); /* this shall not happen! */
+ dbgprintf("doRetry: ERROR, pNsd->rtryCall invalid in nsdsel_ossl.c:%d\n", __LINE__);
+ break;
+ }
+finalize_it:
+ if(iRet != RS_RET_OK && iRet != RS_RET_CLOSED && iRet != RS_RET_RETRY)
+ pNsd->bAbortConn = 1; /* request abort */
+ 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_ossl_t *pThis = (nsdsel_ossl_t*) pNsdsel;
+ nsd_ossl_t *pNsdOSSL = (nsd_ossl_t*) pNsd;
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_ossl);
+ ISOBJ_TYPE_assert(pNsdOSSL, nsd_ossl);
+
+DBGPRINTF("nsdsel_ossl IsReady EINTR\n");
+ if(pNsdOSSL->iMode == 1) {
+ if(waitOp == NSDSEL_RD && osslHasRcvInBuffer(pNsdOSSL)) {
+ *pbIsReady = 1;
+ --pThis->iBufferRcvReady; /* one "pseudo-read" less */
+ FINALIZE;
+ }
+ if(pNsdOSSL->rtryCall == osslRtry_handshake) {
+ CHKiRet(doRetry(pNsdOSSL));
+ /* 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;
+ }
+ else if(pNsdOSSL->rtryCall == osslRtry_recv) {
+ iRet = doRetry(pNsdOSSL);
+ if(iRet == RS_RET_OK) {
+ *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) {
+ *pbIsReady = 0;
+ FINALIZE;
+ }
+ }
+/* // VERBOSE
+dbgprintf("nsdl_ossl: IsReady before nsdsel_ptcp.IsReady for %p\n", pThis);
+*/
+ CHKiRet(nsdsel_ptcp.IsReady(pThis->pTcp, pNsdOSSL->pTcp, waitOp, pbIsReady));
+
+finalize_it:
+ RETiRet;
+}
+/* ------------------------------ end support for the select() interface ------------------------------ */
+
+
+/* queryInterface function */
+BEGINobjQueryInterface(nsdsel_ossl)
+CODESTARTobjQueryInterface(nsdsel_ossl)
+ 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_osslConstruct;
+ pIf->Destruct = (rsRetVal(*)(nsdsel_t**)) nsdsel_osslDestruct;
+ pIf->Add = Add;
+ pIf->Select = Select;
+ pIf->IsReady = IsReady;
+finalize_it:
+ENDobjQueryInterface(nsdsel_ossl)
+
+
+/* exit our class
+ */
+BEGINObjClassExit(nsdsel_ossl, OBJ_IS_CORE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(nsdsel_ossl)
+ /* release objects we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(nsdsel_ptcp, LM_NSD_PTCP_FILENAME);
+ENDObjClassExit(nsdsel_ossl)
+
+
+/* Initialize the nsdsel_ossl class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINObjClassInit(nsdsel_ossl, 1, OBJ_IS_CORE_MODULE) /* class, version */
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(nsdsel_ptcp, LM_NSD_PTCP_FILENAME));
+
+ /* set our own handlers */
+ENDObjClassInit(nsdsel_ossl)
+/* vi:set ai:
+ */
diff --git a/runtime/nsdsel_ossl.h b/runtime/nsdsel_ossl.h
new file mode 100644
index 0000000..bf40c28
--- /dev/null
+++ b/runtime/nsdsel_ossl.h
@@ -0,0 +1,42 @@
+/* An implementation of the nsd select interface for OpenSSL.
+ *
+ * Copyright (C) 2018-2018 Adiscon GmbH.
+ * Author: Andre Lorbach
+*
+ * 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_OSSL_H
+#define INCLUDED_NSDSEL_OSSL_H
+
+#include "nsd.h"
+typedef nsdsel_if_t nsdsel_ossl_if_t; /* we just *implement* this interface */
+
+/* the nsdsel_ossl object */
+struct nsdsel_ossl_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_osslCURR_IF_VERSION nsdCURR_IF_VERSION
+
+/* prototypes */
+PROTOTYPEObj(nsdsel_ossl);
+
+#endif /* #ifndef INCLUDED_NSDSEL_OSSL_H */
diff --git a/runtime/nsdsel_ptcp.c b/runtime/nsdsel_ptcp.c
new file mode 100644
index 0000000..d77c729
--- /dev/null
+++ b/runtime/nsdsel_ptcp.c
@@ -0,0 +1,221 @@
+/* nsdsel_ptcp.c
+ *
+ * An implementation of the nsd select() interface for plain tcp sockets.
+ *
+ * Copyright 2008-2018 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"
+
+#define FDSET_INCREMENT 1024 /* increment for struct pollfds array allocation */
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(nsdsel_ptcp) /* be sure to specify the object type also in END macro! */
+ pThis->currfds = 0;
+ pThis->maxfds = FDSET_INCREMENT;
+ CHKmalloc(pThis->fds = calloc(FDSET_INCREMENT, sizeof(struct pollfd)));
+finalize_it:
+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)
+ free(pThis->fds);
+ENDobjDestruct(nsdsel_ptcp)
+
+
+/* Add a socket to the select set */
+static rsRetVal ATTR_NONNULL()
+Add(nsdsel_t *const pNsdsel, nsd_t *const pNsd, const nsdsel_waitOp_t waitOp)
+{
+ DEFiRet;
+ nsdsel_ptcp_t *const pThis = (nsdsel_ptcp_t*) pNsdsel;
+ const nsd_ptcp_t *const pSock = (nsd_ptcp_t*) pNsd;
+ ISOBJ_TYPE_assert(pSock, nsd_ptcp);
+ ISOBJ_TYPE_assert(pThis, nsdsel_ptcp);
+
+ if(pThis->currfds == pThis->maxfds) {
+ struct pollfd *newfds;
+ CHKmalloc(newfds = realloc(pThis->fds,
+ sizeof(struct pollfd) * (pThis->maxfds + FDSET_INCREMENT)));
+ pThis->maxfds += FDSET_INCREMENT;
+ pThis->fds = newfds;
+ }
+
+ switch(waitOp) {
+ case NSDSEL_RD:
+ pThis->fds[pThis->currfds].events = POLLIN;
+ break;
+ case NSDSEL_WR:
+ pThis->fds[pThis->currfds].events = POLLOUT;
+ break;
+ case NSDSEL_RDWR:
+ pThis->fds[pThis->currfds].events = POLLIN | POLLOUT;
+ break;
+ }
+ pThis->fds[pThis->currfds].fd = pSock->sock;
+ ++pThis->currfds;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* perform the select() piNumReady returns how many descriptors are ready for IO
+ * TODO: add timeout!
+ */
+static rsRetVal ATTR_NONNULL()
+Select(nsdsel_t *const pNsdsel, int *const piNumReady)
+{
+ DEFiRet;
+ nsdsel_ptcp_t *pThis = (nsdsel_ptcp_t*) pNsdsel;
+
+ ISOBJ_TYPE_assert(pThis, nsdsel_ptcp);
+ assert(piNumReady != NULL);
+
+ /* Output debug first*/
+ if(Debug) {
+ dbgprintf("--------<NSDSEL_PTCP> calling poll, active fds (%d): ", pThis->currfds);
+ for(uint32_t i = 0; i <= pThis->currfds; ++i)
+ dbgprintf("%d ", pThis->fds[i].fd);
+ dbgprintf("\n");
+ }
+ assert(pThis->currfds >= 1);
+
+ /* now do the select */
+ *piNumReady = poll(pThis->fds, pThis->currfds, -1);
+ if(*piNumReady < 0) {
+ if(errno == EINTR) {
+ DBGPRINTF("nsdsel_ptcp received EINTR\n");
+ } else {
+ LogMsg(errno, RS_RET_POLL_ERR, LOG_WARNING,
+ "ndssel_ptcp: poll system call failed, may cause further troubles");
+ }
+ *piNumReady = 0;
+ }
+
+ RETiRet;
+}
+
+
+/* check if a socket is ready for IO */
+static rsRetVal ATTR_NONNULL()
+IsReady(nsdsel_t *const pNsdsel, nsd_t *const pNsd, const nsdsel_waitOp_t waitOp, int *const pbIsReady)
+{
+ DEFiRet;
+ const nsdsel_ptcp_t *const pThis = (nsdsel_ptcp_t*) pNsdsel;
+ const nsd_ptcp_t *const pSock = (nsd_ptcp_t*) pNsd;
+ ISOBJ_TYPE_assert(pThis, nsdsel_ptcp);
+ ISOBJ_TYPE_assert(pSock, nsd_ptcp);
+ const int sock = pSock->sock;
+ // TODO: consider doing a binary search
+
+ uint32_t idx;
+ for(idx = 0 ; idx < pThis->currfds ; ++idx) {
+ if(pThis->fds[idx].fd == sock)
+ break;
+ }
+ if(idx >= pThis->currfds) {
+ LogMsg(0, RS_RET_INTERNAL_ERROR, LOG_ERR,
+ "ndssel_ptcp: could not find socket %d which should be present", sock);
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+
+ const short revent = pThis->fds[idx].revents;
+ if (revent & POLLNVAL) {
+ DBGPRINTF("ndssel_ptcp: revent & POLLNVAL is TRUE, we had a race, ignoring, revent = %d", revent);
+ *pbIsReady = 0;
+ }
+ switch(waitOp) {
+ case NSDSEL_RD:
+ *pbIsReady = revent & POLLIN;
+ break;
+ case NSDSEL_WR:
+ *pbIsReady = revent & POLLOUT;
+ break;
+ case NSDSEL_RDWR:
+ *pbIsReady = revent & (POLLIN | POLLOUT);
+ break;
+ }
+
+finalize_it:
+ 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);
+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 */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ENDObjClassInit(nsdsel_ptcp)
diff --git a/runtime/nsdsel_ptcp.h b/runtime/nsdsel_ptcp.h
new file mode 100644
index 0000000..87270c9
--- /dev/null
+++ b/runtime/nsdsel_ptcp.h
@@ -0,0 +1,45 @@
+/* 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 <poll.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;
+ uint32_t maxfds;
+ uint32_t currfds;
+ struct pollfd *fds;
+};
+
+/* 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 0000000..5e17bb1
--- /dev/null
+++ b/runtime/nspoll.c
@@ -0,0 +1,218 @@
+/* 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-2014 Rainer Gerhards and Adiscon GmbH.
+ *
+ * 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 "nspoll.h"
+#include "rsconf.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(runConf);
+ 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
+ */
+ free(pThis->pBaseDrvrName);
+ 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;
+}
+
+
+/* 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(nspoll_t *pThis, uchar *pszName)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nspoll);
+ if(pThis->pBaseDrvrName != NULL) {
+ free(pThis->pBaseDrvrName);
+ pThis->pBaseDrvrName = NULL;
+ }
+
+ if(pszName != NULL) {
+ CHKmalloc(pThis->pBaseDrvrName = (uchar*) strdup((char*) pszName));
+ }
+finalize_it:
+ 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->SetDrvrName = SetDrvrName;
+ 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 0000000..08d7da8
--- /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.
+ *
+ * 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_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 */
+ /* v3 - 2013-09-17 by rgerhards */
+ rsRetVal (*SetDrvrName)(nspoll_t *pThis, uchar *name);
+ENDinterface(nspoll)
+#define nspollCURR_IF_VERSION 3 /* 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 0000000..bcfbfee
--- /dev/null
+++ b/runtime/nssel.c
@@ -0,0 +1,254 @@
+/* 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-2014 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"
+#include "rsconf.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(runConf);
+ 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
+ */
+ free(pThis->pBaseDrvrName);
+ 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;
+}
+
+
+/* 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(nssel_t *pThis, uchar *pszName)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, nssel);
+ if(pThis->pBaseDrvrName != NULL) {
+ free(pThis->pBaseDrvrName);
+ pThis->pBaseDrvrName = NULL;
+ }
+
+ if(pszName != NULL) {
+ CHKmalloc(pThis->pBaseDrvrName = (uchar*) strdup((char*) pszName));
+ }
+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 __attribute__((unused)) *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->SetDrvrName = SetDrvrName;
+ 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 0000000..f8a67c6
--- /dev/null
+++ b/runtime/nssel.h
@@ -0,0 +1,57 @@
+/* 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);
+ /* v2 - 2013-09-17 by rgerhards */
+ rsRetVal (*SetDrvrName)(nssel_t *pThis, uchar *name);
+ENDinterface(nssel)
+#define nsselCURR_IF_VERSION 2 /* 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 0000000..21fb89b
--- /dev/null
+++ b/runtime/obj-types.h
@@ -0,0 +1,444 @@
+/* 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-2019 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); \
+ fprintf(stderr, "%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); \
+ fflush(stderr); \
+ assert(!strcmp((char*)(((obj_t*)pObj)->pObjInfo->pszID), #objType)); \
+ } \
+ assert((unsigned) ((obj_t*)(pObj))->iObjCooCKiE == (unsigned) 0xBADEFEE); \
+ } while(0)
+ /* now the same for pointers to "regular" objects (like wrkrInstanceData) */
+# define PTR_ASSERT_DEF unsigned int _Assert_type;
+# define PTR_ASSERT_SET_TYPE(_ptr, _type) _ptr->_Assert_type = _type
+# define PTR_ASSERT_CHK(_ptr, _type) do { \
+ assert(_ptr != NULL); \
+ if(_ptr->_Assert_type != _type) {\
+ dbgprintf("%s:%d PTR_ASSERT_CHECK failure: invalid pointer type %x, " \
+ "expected %x\n", __FILE__, __LINE__, _ptr->_Assert_type, _type); \
+ fprintf(stderr, "%s:%d PTR_ASSERT_CHECK failure: invalid pointer type %x, " \
+ "expected %x\n", __FILE__, __LINE__, _ptr->_Assert_type, _type); \
+ assert(_ptr->_Assert_type == _type); \
+ } \
+ } 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)
+
+# define PTR_ASSERT_DEF
+# define PTR_ASSERT_SET_TYPE(_ptr, _type)
+# define PTR_ASSERT_CHK(_ptr, _type)
+#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)\
+ { \
+ pThis->prop = pVal; \
+ return RS_RET_OK; \
+ }
+#define PROTOTYPEpropSetMethPTR(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType*)
+#define DEFpropSetMethFP(obj, prop, dataType)\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType)\
+ { \
+ 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);\
+ rsRetVal obj##Set##prop(obj##_t *pThis, dataType pVal)\
+ { \
+ 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) \
+ static 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); \
+ 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 lengthy 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 PROTOTYPEobjDestruct(OBJ) \
+ rsRetVal OBJ##Destruct(OBJ##_t __attribute__((unused)) **ppThis)
+/* note: we generate a prototype in any case, as this does not hurt but
+ * many modules otherwise seem to miss one, which generates compiler
+ * warnings.
+ */
+#define BEGINobjDestruct(OBJ) \
+ rsRetVal OBJ##Destruct(OBJ##_t __attribute__((unused)) **ppThis);\
+ 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 smsg_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);\
+ 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);\
+ rsRetVal obj##QueryInterface(obj##_if_t *pIf) \
+ { \
+ DEFiRet; \
+
+#define CODESTARTobjQueryInterface(obj) \
+ assert(pIf != NULL);
+
+#define ENDobjQueryInterface(obj) \
+ RETiRet; \
+ }
+
+#define PROTOTYPEObjQueryInterface(obj) rsRetVal obj##QueryInterface(obj##_if_t *pIf)
+
+
+/* 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); \
+ PROTOTYPEObjQueryInterface(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 0000000..c78b1d2
--- /dev/null
+++ b/runtime/obj.c
@@ -0,0 +1,1409 @@
+/* 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 acquired 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-2019 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>
+
+#define DEV_DEBUG 0 /* set to 1 to enable very verbose developer debugging messages */
+
+/* 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(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(const char *szObjName, 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*))
+{
+ 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;
+ case PROPTYPE_NONE:
+ 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 occurrence 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;
+ }
+ 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;
+ }
+ 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;
+ }
+ 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;
+ case VARTYPE_NONE:
+ 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:
+ /* ensure the type of var is reset back to VARTYPE_NONE since
+ * the deconstruct method of var might free unallocated memory
+ */
+ if(iRet != RS_RET_OK && iRet != RS_RET_NO_PROPLINE) {
+ if(step <= 2) {
+ pProp->varType = VARTYPE_NONE;
+ }
+ }
+ 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);
+ strmDebugOutBuf(pStrm);
+ 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;
+ case VARTYPE_NONE:
+ 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:
+ if(Debug && iRet != RS_RET_OK) {
+ dbgprintf("objDeserializeTrailer fails with %d\n", iRet);
+ }
+
+ 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((char*)cstrGetSzStrNoNULL(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().
+ * Note: this is for the queue subsystem, and optimized for its use.
+ * 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);
+
+ if(Debug && iRet != RS_RET_OK) {
+ dbgprintf("objDeserializeWithMethods fails with %d, stream state:\n", iRet);
+ strmDebugOutBuf(pStrm);
+ }
+
+
+ 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((char*)cstrGetSzStrNoNULL(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
+ */
+uchar * ATTR_NONNULL()
+objGetName(obj_t *const pThis)
+{
+ uchar *ret;
+ uchar szName[128];
+
+ ISOBJ_assert(pThis);
+
+ if(pThis->pszName == NULL) {
+ snprintf((char*)szName, sizeof(szName), "%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;
+ }
+
+ return ret;
+}
+
+
+/* Find the objInfo object for the current object
+ * rgerhards, 2008-02-29
+ */
+static rsRetVal
+FindObjInfo(const char *const __restrict__ strOID, objInfo_t **ppInfo)
+{
+ DEFiRet;
+ int bFound;
+ int i;
+
+ bFound = 0;
+ i = 0;
+ while(!bFound && i < OBJ_NUM_IDS) {
+ if(arrObjInfo[i] != NULL && !strcmp(strOID, (const char*)arrObjInfo[i]->pszID)) {
+ bFound = 1;
+ break;
+ }
+ ++i;
+ }
+
+ if(!bFound)
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+
+ *ppInfo = arrObjInfo[i];
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ dbgprintf("caller requested object '%s', not found (iRet %d)\n", strOID, 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;
+ #if DEV_DEBUG == 1
+ dbgprintf("object '%s' successfully registered with "
+ "index %d, qIF %p\n", pszObjName, i, pInfo->QueryIF);
+ #endif
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ 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]);
+ #if DEV_DEBUG == 1
+ dbgprintf("object '%s' successfully unregistered with index %d\n", pszObjName, i);
+ #endif
+
+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(const char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf)
+{
+ DEFiRet;
+ objInfo_t *pObjInfo;
+
+
+ #if DEV_DEBUG == 1
+ dbgprintf("source file %s requests object '%s', "
+ " ifIsLoaded %d\n", srcFile, pObjName, pIf->ifIsLoaded);
+ #endif
+ 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;
+
+ iRet = FindObjInfo((const char*)pObjName, &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((const char*)pObjName, &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);
+ 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(const char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf)
+{
+ DEFiRet;
+ 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(FindObjInfo((const char*)pObjName, &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);
+
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+PROTOTYPEObjQueryInterface(obj);
+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 = objGetName;
+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);
+
+ /* TODO: implement the class exits! */
+#if 0
+ cfsyslineExit(pModInfo);
+ varClassExit(pModInfo);
+#endif
+ 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(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(strm, CORE_COMPONENT));
+
+finalize_it:
+ RETiRet;
+}
diff --git a/runtime/obj.h b/runtime/obj.h
new file mode 100644
index 0000000..0efe6e7
--- /dev/null
+++ b/runtime/obj.h
@@ -0,0 +1,132 @@
+/* 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-2018 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 (*)()) pHdlr))
+
+/* interfaces */
+BEGINinterface(obj) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*UseObj)(const char *srcFile, uchar *pObjName, uchar *pObjFile, interface_t *pIf);
+ rsRetVal (*ReleaseObj)(const 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);
+uchar *objGetName(obj_t *pThis);
+
+
+/* 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 0000000..0cf98d0
--- /dev/null
+++ b/runtime/objomsr.c
@@ -0,0 +1,158 @@
+/* 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 */
+ if(iNumEntries > 0) {
+ 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 0000000..d06db4a
--- /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/operatingstate.c b/runtime/operatingstate.c
new file mode 100644
index 0000000..04b4cbd
--- /dev/null
+++ b/runtime/operatingstate.c
@@ -0,0 +1,177 @@
+/* OperatingStateFile Handler.
+ *
+ * Copyright 2018 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 <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "rsyslog.h"
+#include "errmsg.h"
+#include "operatingstate.h"
+#include "rsconf.h"
+
+#ifndef O_LARGEFILE
+#define O_LARGEFILE 0
+#endif
+#ifndef HAVE_LSEEK64
+# define lseek64(fd, offset, whence) lseek(fd, offset, whence)
+#endif
+
+/* some important standard states */
+#define STATE_INITIALIZING "INITIALIZING"
+#define STATE_CLEAN_CLOSE "CLEAN CLOSE"
+
+static int fd_osf = -1;
+
+/* check if old osf points to a problem and, if so, report */
+static void
+osf_checkOnStartup(void)
+{
+ int do_rename = 1;
+ const char *fn_osf = (const char*) glblGetOperatingStateFile(loadConf);
+ char iobuf[sizeof(STATE_CLEAN_CLOSE)];
+ const int len_clean_close = sizeof(STATE_CLEAN_CLOSE) - 1;
+ assert(fn_osf != NULL);
+
+ const int fd = open(fn_osf, O_RDONLY|O_LARGEFILE|O_CLOEXEC, 0);
+ if(fd == -1) {
+ if(errno != ENOENT) {
+ LogError(errno, RS_RET_ERR, "error opening existing operatingStateFile '%s' - "
+ "this may be an indication of a problem; ignoring", fn_osf);
+ }
+ do_rename = 0;
+ goto done;
+ }
+ assert(fd != -1);
+ int offs = lseek64(fd, -(len_clean_close + 1), SEEK_END);
+ if(offs == -1){
+ LogError(errno, RS_RET_IO_ERROR, "error seeking to end of existing operatingStateFile "
+ "'%s' - this may be an indication of a problem, e.g. empty file", fn_osf);
+ goto done;
+ }
+ int rd = read(fd, iobuf, len_clean_close);
+ if(rd == -1) {
+ LogError(errno, RS_RET_IO_ERROR, "error reading existing operatingStateFile "
+ "'%s' - this probably indicates an improper shutdown", fn_osf);
+ goto done;
+ } else {
+ assert(rd <= len_clean_close);
+ iobuf[rd] = '\0';
+ if(rd != len_clean_close || strcmp(iobuf, STATE_CLEAN_CLOSE) != 0) {
+ LogError(errno, RS_RET_IO_ERROR, "existing operatingStateFile '%s' does not end "
+ "with '%s, instead it has '%s' - this probably indicates an "
+ "improper shutdown", fn_osf, STATE_CLEAN_CLOSE, iobuf);
+ goto done;
+ }
+ }
+
+ /* all ok! */
+ do_rename = 0;
+done:
+ if(fd != -1) {
+ close(fd);
+ }
+ if(do_rename) {
+ char newname[MAXFNAME];
+ snprintf(newname, sizeof(newname)-1, "%s.previous", fn_osf);
+ newname[MAXFNAME-1] = '\0';
+ if(rename(fn_osf, newname) != 0) {
+ LogError(errno, RS_RET_IO_ERROR, "could not rename existing operatingStateFile "
+ "'%s' to '%s' - ignored, but will probably be overwritten now",
+ fn_osf, newname);
+ } else {
+ LogMsg(errno, RS_RET_OK, LOG_INFO, "existing state file '%s' renamed "
+ "to '%s' - you may want to review it", fn_osf, newname);
+ }
+ }
+ return;
+}
+
+void
+osf_open(void)
+{
+ assert(fd_osf == -1);
+ const char *fn_osf = (const char*) glblGetOperatingStateFile(loadConf);
+ assert(fn_osf != NULL);
+
+ osf_checkOnStartup();
+
+ fd_osf = open(fn_osf, O_WRONLY|O_CREAT|O_APPEND|O_LARGEFILE|O_CLOEXEC,
+ S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP);
+ if(fd_osf == -1) {
+ LogError(errno, RS_RET_ERR, "error opening operatingStateFile '%s' for write - "
+ "ignoring it", fn_osf);
+ goto done;
+ }
+ assert(fd_osf != -1);
+
+ osf_write(OSF_TAG_STATE, STATE_INITIALIZING " " VERSION);
+done:
+ return;
+}
+
+
+void ATTR_NONNULL()
+osf_write(const char *const tag, const char *const line)
+{
+ char buf[1024]; /* intentionally small */
+ time_t tt;
+ ssize_t wr;
+ size_t len;
+ struct tm tm;
+
+ DBGPRINTF("osf: %s %s: ", tag, line); /* ensure everything is inside the debug log */
+
+ if(fd_osf == -1)
+ return;
+
+ time(&tt);
+ localtime_r(&tt, &tm);
+ len = snprintf(buf, sizeof(buf)-1, "%d%2.2d%2.2d-%2.2d%2.2d%2.2d: %-5.5s %s\n",
+ tm.tm_year+1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+ tag, line);
+ if(len > sizeof(buf)-1) {
+ len = sizeof(buf)-1; /* overflow, truncate */
+ }
+ wr = write(fd_osf, buf, len);
+ // TODO: handle EINTR
+ if(wr != (ssize_t) len) {
+ LogError(errno, RS_RET_IO_ERROR, "error writing operating state file - line lost");
+ }
+}
+
+
+void
+osf_close(void)
+{
+ if(fd_osf == -1)
+ return;
+ osf_write(OSF_TAG_STATE, STATE_CLEAN_CLOSE);
+ close(fd_osf);
+}
diff --git a/runtime/operatingstate.h b/runtime/operatingstate.h
new file mode 100644
index 0000000..a832a74
--- /dev/null
+++ b/runtime/operatingstate.h
@@ -0,0 +1,32 @@
+/* OperatingStateFile interface.
+ *
+ * Copyright 2018 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_OPERATINGSTATEFILE_H
+#define INCLUDED_OPERATINGSTATEFILE_H
+
+/* supported TAGS */
+#define OSF_TAG_STATE "STATE"
+#define OSF_TAG_MSG "MSG"
+
+void osf_open(void);
+void ATTR_NONNULL() osf_write(const char *const tag, const char *const line);
+void osf_close(void);
+
+#endif /* #ifndef INCLUDED_OPERATINGSTATEFILE_H */
diff --git a/runtime/parser.c b/runtime/parser.c
new file mode 100644
index 0000000..a22ad72
--- /dev/null
+++ b/runtime/parser.c
@@ -0,0 +1,742 @@
+/* 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-2021 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>
+#include <zlib.h>
+
+#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(datetime)
+DEFobjCurrIf(ruleset)
+
+/* static data */
+
+static char hexdigit[16] =
+ {'0', '1', '2', '3', '4', '5', '6', '7', '8',
+ '9', 'A', 'B', 'C', 'D', 'E', 'F' };
+
+BEGINobjDestruct(parser) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(parser)
+ DBGPRINTF("destructing parser '%s'\n", pThis->pName);
+ if(pThis->pInst != NULL) {
+ pThis->pModule->mod.pm.freeParserInst(pThis->pInst);
+ }
+ free(pThis->pName);
+ENDobjDestruct(parser)
+
+/* 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;
+ }
+DBGPRINTF("DDDDD: added parser '%s' to list %p\n", pParser->pName, ppListRoot);
+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(parserList_t *pParserListRoot, parser_t **ppParser, uchar *pName)
+{
+ parserList_t *pThis;
+ DEFiRet;
+
+ for(pThis = pParserListRoot ; 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(loadConf->parsers.pParsLstRoot, &pParser, pName));
+ CHKiRet(AddParserToList(&loadConf->parsers.pDfltParsLst, pParser));
+ DBGPRINTF("Parser '%s' added to default parser set.\n", pName);
+
+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 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;
+}
+
+
+
+
+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
+ */
+static rsRetVal parserConstructFinalize(parser_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, parser);
+ CHKiRet(AddParserToList(&loadConf->parsers.pParsLstRoot, pThis));
+ DBGPRINTF("Parser '%s' added to list of available parsers.\n", pThis->pName);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* construct a parser object via a pointer to the parser module
+ * and the name. This is a separate function because we need it
+ * in multiple spots inside the code.
+ */
+rsRetVal
+parserConstructViaModAndName(modInfo_t *__restrict__ pMod, uchar *const __restrict__ pName, void *pInst)
+{
+ rsRetVal localRet;
+ parser_t *pParser = NULL;
+ DEFiRet;
+
+ if(pInst == NULL && pMod->mod.pm.newParserInst != NULL) {
+ /* this happens for the default instance on ModLoad time */
+ CHKiRet(pMod->mod.pm.newParserInst(NULL, &pInst));
+ }
+ CHKiRet(parserConstruct(&pParser));
+ /* check some features */
+ localRet = pMod->isCompatibleWithFeature(sFEATUREAutomaticSanitazion);
+ if(localRet == RS_RET_OK){
+ pParser->bDoSanitazion = RSTRUE;
+ }
+ localRet = pMod->isCompatibleWithFeature(sFEATUREAutomaticPRIParsing);
+ if(localRet == RS_RET_OK){
+ CHKiRet(SetDoPRIParsing(pParser, RSTRUE));
+ }
+
+ CHKiRet(SetName(pParser, pName));
+ CHKiRet(SetModPtr(pParser, pMod));
+ pParser->pInst = pInst;
+ CHKiRet(parserConstructFinalize(pParser));
+finalize_it:
+ if(iRet != RS_RET_OK)
+ free(pParser);
+ RETiRet;
+}
+
+/* uncompress a received message if it is compressed.
+ * pMsg->pszRawMsg buffer is updated.
+ * rgerhards, 2008-10-09
+ */
+static rsRetVal uncompressMessage(smsg_t *pMsg)
+{
+ DEFiRet;
+ 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' &&
+ runConf->globals.bSupportCompressionExtension) { /* compressed data present? */
+ /* 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(runConf);
+ CHKmalloc(deflateBuf = malloc(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) {
+ 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);
+
+ 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 rsRetVal
+SanitizeMsg(smsg_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;
+ uchar pc;
+ 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(glbl.GetParserDropTrailingLFOnReception(runConf)
+ && lenMsg > 0 && 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(glbl.GetParserSpaceLFOnReceive(runConf) && pszMsg[iSrc] == '\n') {
+ pszMsg[iSrc] = ' ';
+ } else if(pszMsg[iSrc] == '\0' || glbl.GetParserEscapeControlCharactersOnReceive(runConf)) {
+ bNeedSanitize = 1;
+ if (!glbl.GetParserSpaceLFOnReceive(runConf)) {
+ break;
+ }
+ }
+ } else if(pszMsg[iSrc] > 127 && glbl.GetParserEscape8BitCharactersOnReceive(runConf)) {
+ 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(runConf);
+ 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(maxDest + 1));
+ if(iSrc > 0) {
+ iSrc--; /* go back to where everything is OK */
+ if(iSrc > maxDest) {
+ DBGPRINTF("parser.Sanitize: have oversize index %zd, "
+ "max %zd - corrected, but should not happen\n",
+ iSrc, maxDest);
+ iSrc = maxDest;
+ }
+ 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' || glbl.GetParserEscapeControlCharacterTab(runConf))) {
+ /* note: \0 must always be escaped, the rest of the code currently
+ * can not handle it! -- rgerhards, 2009-08-26
+ */
+ if(pszMsg[iSrc] == '\0' || glbl.GetParserEscapeControlCharactersOnReceive(runConf)) {
+ /* 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
+ */
+ if (glbl.GetParserEscapeControlCharactersCStyle(runConf)) {
+ pDst[iDst++] = '\\';
+
+ switch (pszMsg[iSrc]) {
+ case '\0':
+ pDst[iDst++] = '0';
+ break;
+ case '\a':
+ pDst[iDst++] = 'a';
+ break;
+ case '\b':
+ pDst[iDst++] = 'b';
+ break;
+ case '\x1b': /* equivalent to '\e' which is not accepted by XLC */
+ pDst[iDst++] = 'e';
+ break;
+ case '\f':
+ pDst[iDst++] = 'f';
+ break;
+ case '\n':
+ pDst[iDst++] = 'n';
+ break;
+ case '\r':
+ pDst[iDst++] = 'r';
+ break;
+ case '\t':
+ pDst[iDst++] = 't';
+ break;
+ case '\v':
+ pDst[iDst++] = 'v';
+ break;
+ default:
+ pDst[iDst++] = 'x';
+
+ pc = pszMsg[iSrc];
+ pDst[iDst++] = hexdigit[(pc & 0xF0) >> 4];
+ pDst[iDst++] = hexdigit[pc & 0xF];
+
+ break;
+ }
+
+ } else {
+ pDst[iDst++] = glbl.GetParserControlCharacterEscapePrefix(runConf);
+ 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 && glbl.GetParserEscape8BitCharactersOnReceive(runConf)) {
+ if (glbl.GetParserEscapeControlCharactersCStyle(runConf)) {
+ pDst[iDst++] = '\\';
+ pDst[iDst++] = 'x';
+
+ pc = pszMsg[iSrc];
+ pDst[iDst++] = hexdigit[(pc & 0xF0) >> 4];
+ pDst[iDst++] = hexdigit[pc & 0xF];
+
+ } else {
+ /* In this case, we also do the conversion. Note that this most
+ * probably breaks European languages. -- rgerhards, 2010-01-27
+ */
+ pDst[iDst++] = glbl.GetParserControlCharacterEscapePrefix(runConf);
+ 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 rsRetVal
+ParsePRI(smsg_t *pMsg)
+{
+ syslog_pri_t 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 == '<') {
+ pri = 0;
+ while(--lenMsg > 0 && isdigit((int) *++msg) && pri <= LOG_MAXPRI) {
+ pri = 10 * pri + (*msg - '0');
+ }
+ if(*msg == '>') {
+ ++msg;
+ } else {
+ pri = LOG_PRI_INVLD;
+ }
+ if(pri > LOG_MAXPRI)
+ pri = LOG_PRI_INVLD;
+ }
+ msgSetPRI(pMsg, pri);
+ MsgSetAfterPRIOffs(pMsg, (pri == LOG_PRI_INVLD) ? 0 : 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(smsg_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);
+
+ CHKiRet(uncompressMessage(pMsg));
+
+ /* 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(runConf, pMsg);
+ if(pParserList == NULL) {
+ pParserList = runConf->parsers.pDfltParsLst;
+ }
+ DBGPRINTF("parse using parser list %p%s.\n", pParserList,
+ (pParserList == runConf->parsers.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;
+ }
+ if(pParser->pModule->mod.pm.parse2 == NULL)
+ localRet = pParser->pModule->mod.pm.parse(pMsg);
+ else
+ localRet = pParser->pModule->mod.pm.parse2(pParser->pInst, 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) {
+ LogError(0, localRet, "Error: one message could not be processed by "
+ "any parser, message is being discarded (start of raw msg: '%.60s')",
+ 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;
+}
+
+/* This destroys the master parserlist and all of its parser entries.
+ * Parser modules are NOT unloaded, rsyslog does that at a later stage
+ * for all dynamically loaded modules.
+ */
+static rsRetVal
+destroyMasterParserList(parserList_t *pParserListRoot)
+{
+ DEFiRet;
+ parserList_t *pParsLst;
+ parserList_t *pParsLstDel;
+
+ pParsLst = pParserListRoot;
+ while(pParsLst != NULL) {
+ parserDestruct(&pParsLst->pParser);
+ pParsLstDel = pParsLst;
+ pParsLst = pParsLst->pNext;
+ free(pParsLstDel);
+ }
+ RETiRet;
+}
+
+/* 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->SetDoPRIParsing = SetDoPRIParsing;
+ pIf->ParseMsg = ParseMsg;
+ pIf->SanitizeMsg = SanitizeMsg;
+ pIf->DestructParserList = DestructParserList;
+ pIf->AddParserToList = AddParserToList;
+ pIf->AddDfltParser = AddDfltParser;
+ pIf->FindParser = FindParser;
+ pIf->destroyMasterParserList = destroyMasterParserList;
+finalize_it:
+ENDobjQueryInterface(parser)
+
+/* Exit our class.
+ * rgerhards, 2009-11-04
+ */
+BEGINObjClassExit(parser, OBJ_IS_CORE_MODULE) /* class, version */
+ objRelease(glbl, 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(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+ENDObjClassInit(parser)
diff --git a/runtime/parser.h b/runtime/parser.h
new file mode 100644
index 0000000..d028b1a
--- /dev/null
+++ b/runtime/parser.h
@@ -0,0 +1,77 @@
+/* header for parser.c
+ *
+ * Copyright 2008-2021 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 */
+ void *pInst; /* instance data for the parser (v2+ module interface) */
+ 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 (*SetDoPRIParsing)(parser_t *pThis, int);
+ rsRetVal (*FindParser)(parserList_t *pParserListRoot, parser_t **ppThis, uchar*name);
+ rsRetVal (*DestructParserList)(parserList_t **pListRoot);
+ rsRetVal (*AddParserToList)(parserList_t **pListRoot, parser_t *pParser);
+ rsRetVal (*destroyMasterParserList)(parserList_t *pParserListRoot);
+ /* static functions */
+ rsRetVal (*ParseMsg)(smsg_t *pMsg);
+ rsRetVal (*SanitizeMsg)(smsg_t *pMsg);
+ rsRetVal (*AddDfltParser)(uchar *);
+ENDinterface(parser)
+#define parserCURR_IF_VERSION 3 /* increment whenever you change the interface above! */
+/* version changes
+ 2 SetDoSanitization removed, no longer needed
+ 3 InitParserList removed, no longer needed
+ destroyMasterParserList added
+ findParser extended with new parameter specifying the parser list
+*/
+
+void printParserList(parserList_t *pList);
+
+/* prototypes */
+PROTOTYPEObj(parser);
+rsRetVal parserConstructViaModAndName(modInfo_t *pMod, uchar *const pName, void *parserInst);
+
+
+#endif /* #ifndef INCLUDED_PARSER_H */
diff --git a/runtime/perctile_ringbuf.c b/runtime/perctile_ringbuf.c
new file mode 100644
index 0000000..6eb3b66
--- /dev/null
+++ b/runtime/perctile_ringbuf.c
@@ -0,0 +1,360 @@
+/*
+ * 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 <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <math.h>
+#include <assert.h>
+#include "perctile_ringbuf.h"
+
+static uint64_t min(uint64_t a, uint64_t b) {
+ return a < b ? a : b;
+}
+
+/*
+ * circ buf macros derived from linux/circ_buf.h
+ */
+
+/* Return count in buffer. */
+#define CIRC_CNT(head,tail,size) (((head) - (tail)) & ((size)-1))
+
+/* Return space available, 0..size-1. We always leave one free char
+ as a completely full buffer has head == tail, which is the same as
+ empty. */
+#define CIRC_SPACE(head,tail,size) CIRC_CNT((tail),((head)+1),(size))
+
+/* Return count up to the end of the buffer. Carefully avoid
+ accessing head and tail more than once, so they can change
+ underneath us without returning inconsistent results. */
+#define CIRC_CNT_TO_END(head,tail,size) \
+ ({int end = (size) - (tail); \
+ int n = ((head) + end) & ((size)-1); \
+ n < end ? n : end;})
+
+/* Return space available up to the end of the buffer. */
+#define CIRC_SPACE_TO_END(head,tail,size) \
+ ({int end = (size) - 1 - (head); \
+ int n = (end + (tail)) & ((size)-1); \
+ n <= end ? n : end+1;})
+
+/* Move head by size. */
+#define CIRC_ADD(idx, size, offset) (((idx) + (offset)) & ((size) - 1))
+
+// simple use of the linux defined circular buffer.
+
+ringbuf_t* ringbuf_new(size_t count) {
+ // use nearest power of 2
+ double x = ceil(log2(count));
+ size_t bufsize = pow(2, x);
+
+ ringbuf_t *rb = calloc(1, sizeof(ringbuf_t));
+ // Note: count needs to be a power of 2, otherwise our macros won't work.
+ ITEM *pbuf = calloc(bufsize, sizeof(ITEM));
+ rb->cb.buf = pbuf;
+ rb->cb.head = rb->cb.tail = 0;
+ rb->size = bufsize;
+
+ return rb;
+}
+
+void ringbuf_del(ringbuf_t *rb) {
+ if (rb) {
+ if (rb->cb.buf) {
+ free(rb->cb.buf);
+ }
+ free(rb);
+ }
+}
+
+int ringbuf_append(ringbuf_t *rb, ITEM item) {
+ // lock it and add
+ int head = rb->cb.head,
+ tail = rb->cb.tail;
+
+ if (!CIRC_SPACE(head, tail, rb->size)) {
+ return -1;
+ } else {
+ /* insert item into buffer */
+ rb->cb.buf[head] = item;
+ // move head
+ rb->cb.head = CIRC_ADD(head, rb->size, 1);
+ }
+ return 0;
+}
+
+int ringbuf_append_with_overwrite(ringbuf_t *rb, ITEM item) {
+ int head = rb->cb.head,
+ tail = rb->cb.tail;
+ int ret = 0;
+
+ if (!CIRC_SPACE(head, tail, rb->size)) {
+ rb->cb.tail = CIRC_ADD(tail, rb->size, 1);
+ }
+ ret = ringbuf_append(rb, item);
+ assert(ret == 0); // we shouldn't fail due to no space.
+ return ret;
+}
+
+int ringbuf_read(ringbuf_t *rb, ITEM *buf, size_t count) {
+ int head = rb->cb.head,
+ tail = rb->cb.tail;
+ size_t copy_size = 0;
+
+ if (!CIRC_CNT(head, tail, rb->size)) {
+ return 0;
+ }
+
+ // copy to end of buffer
+ copy_size = min((size_t)CIRC_CNT_TO_END(head, tail, rb->size), count);
+ memcpy(buf, rb->cb.buf+tail, copy_size*sizeof(ITEM));
+
+ rb->cb.tail = CIRC_ADD(rb->cb.tail, rb->size, copy_size);
+ return copy_size;
+}
+
+size_t ringbuf_read_to_end(ringbuf_t *rb, ITEM *buf, size_t count) {
+ size_t nread = 0;
+ nread += ringbuf_read(rb, buf, count);
+ if (nread == 0) {
+ return nread;
+ }
+ // read the rest if buf circled around
+ nread += ringbuf_read(rb, buf + nread, count - nread);
+ assert(nread <= count);
+ return nread;
+}
+
+bool ringbuf_peek(ringbuf_t *rb, ITEM *item) {
+ if (CIRC_CNT(rb->cb.head, rb->cb.tail, rb->size) == 0) {
+ return false;
+ }
+
+ *item = rb->cb.buf[rb->cb.tail];
+ return true;
+}
+
+size_t ringbuf_capacity(ringbuf_t *rb) {
+ return rb->size;
+}
+
+/* ---- RINGBUFFER TESTS ---- */
+void ringbuf_init_test(void) {
+ size_t count = 4;
+ ringbuf_t *rb = ringbuf_new(count);
+ // verify ringbuf empty state
+ assert(rb->cb.head == 0);
+ assert(rb->cb.tail == 0);
+ assert(rb->size == 4);
+ ringbuf_del(rb);
+}
+
+void ringbuf_simple_test(void) {
+ size_t count = 3;
+ ITEM item = 0;
+ ringbuf_t *rb = ringbuf_new(count);
+ assert(ringbuf_append(rb, 1) == 0);
+ assert(ringbuf_append(rb, 2) == 0);
+ assert(ringbuf_append(rb, 3) == 0);
+
+ item = 0;
+ assert(ringbuf_peek(rb, &item) == 0);
+ assert(item == 1);
+}
+
+void ringbuf_append_test(void) {
+ size_t count = 4;
+ ringbuf_t *rb = ringbuf_new(count);
+
+ assert(ringbuf_append(rb, 1) == 0);
+ assert(ringbuf_append(rb, 2) == 0);
+ assert(ringbuf_append(rb, 3) == 0);
+
+ // check state of ringbuffer:
+ // {1, 2, 3, X}
+ // T H
+ assert(rb->cb.buf[0] == 1);
+ assert(rb->cb.buf[1] == 2);
+ assert(rb->cb.buf[2] == 3);
+ assert(rb->cb.buf[3] == 0);
+ assert(rb->cb.head == 3);
+ assert(rb->cb.tail == 0);
+
+ ringbuf_del(rb);
+}
+
+void ringbuf_append_wrap_test(void) {
+ size_t count = 4;
+ ITEM item = 0;
+
+ ringbuf_t *rb = ringbuf_new(count);
+ assert(ringbuf_append(rb, 1) == 0);
+ assert(ringbuf_append(rb, 2) == 0);
+ assert(ringbuf_append(rb, 3) == 0);
+ assert(ringbuf_append(rb, 4) != 0);
+
+ // check state of ringbuffer:
+ // {1, 2, 3, X}
+ // T H
+ assert(rb->cb.buf[0] == 1);
+ assert(rb->cb.buf[1] == 2);
+ assert(rb->cb.buf[2] == 3);
+ assert(rb->cb.head == 3);
+ assert(rb->cb.tail == 0);
+
+ item = 0;
+ assert(ringbuf_read(rb, &item, 1) == 1);
+ assert(item == 1);
+
+ assert(ringbuf_append(rb, 4) == 0);
+
+ // state of ringbuffer:
+ // {1, 2, 3, 4}
+ // H T
+ assert(rb->cb.buf[0] == 1);
+ assert(rb->cb.buf[1] == 2);
+ assert(rb->cb.buf[2] == 3);
+ assert(rb->cb.buf[3] == 4);
+ assert(rb->cb.head == 0);
+ assert(rb->cb.tail == 1);
+
+ item = 0;
+ assert(ringbuf_read(rb, &item, 1) == 1);
+ assert(item == 2);
+
+ // test wraparound
+ assert(ringbuf_append(rb, 5) == 0);
+
+ // state of ringbuffer:
+ // {1, 2, 3, 4}
+ // H T
+ assert(rb->cb.buf[0] == 5);
+ assert(rb->cb.buf[1] == 2);
+ assert(rb->cb.buf[2] == 3);
+ assert(rb->cb.buf[3] == 4);
+ assert(rb->cb.head == 1);
+ assert(rb->cb.tail == 2);
+
+ ringbuf_del(rb);
+}
+
+void ringbuf_append_overwrite_test(void) {
+ size_t count = 4;
+ ringbuf_t *rb = ringbuf_new(count);
+ assert(ringbuf_append_with_overwrite(rb, 1) == 0);
+ assert(ringbuf_append_with_overwrite(rb, 2) == 0);
+ assert(ringbuf_append_with_overwrite(rb, 3) == 0);
+ assert(ringbuf_append_with_overwrite(rb, 4) == 0);
+
+ // check state of ringbuffer:
+ // {1, 2, 3, 4}
+ // H T
+ assert(rb->cb.buf[0] == 1);
+ assert(rb->cb.buf[1] == 2);
+ assert(rb->cb.buf[2] == 3);
+ assert(rb->cb.buf[3] == 4);
+ assert(rb->cb.head == 0);
+ assert(rb->cb.tail == 1);
+
+ assert(ringbuf_append_with_overwrite(rb, 5) == 0);
+ // check state of ringbuffer:
+ // {5, 2, 3, 4}
+ // H T
+ assert(rb->cb.buf[0] == 5);
+ assert(rb->cb.buf[1] == 2);
+ assert(rb->cb.buf[2] == 3);
+ assert(rb->cb.buf[3] == 4);
+ assert(rb->cb.head == 1);
+ assert(rb->cb.tail == 2);
+
+ ringbuf_del(rb);
+}
+
+void ringbuf_read_test(void) {
+ size_t count = 4;
+ ringbuf_t *rb = ringbuf_new(count);
+ ITEM item_array[3];
+ ITEM expects[] = {1, 2, 3};
+ ITEM expects2[] = {4};
+
+ assert(ringbuf_append_with_overwrite(rb, 1) == 0);
+ assert(ringbuf_append_with_overwrite(rb, 2) == 0);
+ assert(ringbuf_append_with_overwrite(rb, 3) == 0);
+
+ assert(ringbuf_read(rb, item_array, count) == 3);
+ assert(memcmp(item_array, expects, 3) == 0);
+
+ // check state of ringbuffer:
+ // {X, X, X, X}
+ // H
+ // T
+ assert(rb->cb.head == 3);
+ assert(rb->cb.tail == 3);
+
+ assert(ringbuf_append_with_overwrite(rb, 4) == 0);
+ assert(ringbuf_read(rb, item_array, count) == 1);
+ assert(memcmp(item_array, expects2, 1) == 0);
+ assert(rb->cb.head == 0);
+ assert(rb->cb.tail == 0);
+
+ ringbuf_del(rb);
+}
+
+void ringbuf_read_to_end_test(void) {
+ size_t count = 4;
+ ringbuf_t *rb = ringbuf_new(count);
+ ITEM item_array[3];
+ ITEM expects[] = {1, 2, 3};
+ ITEM expects2[] = {4};
+
+ assert(ringbuf_append_with_overwrite(rb, 1) == 0);
+ assert(ringbuf_append_with_overwrite(rb, 2) == 0);
+ assert(ringbuf_append_with_overwrite(rb, 3) == 0);
+
+ // state of ringbuffer:
+ // {1, 2, 3, X}
+ // T H
+ assert(ringbuf_read_to_end(rb, item_array, 3) == 3);
+ assert(memcmp(item_array, expects, 3) == 0);
+
+ // check state of ringbuffer:
+ // {1, 2, 3, X}
+ // H
+ // T
+ assert(rb->cb.head == 3);
+ assert(rb->cb.tail == 3);
+
+ assert(ringbuf_append_with_overwrite(rb, 4) == 0);
+ // state of ringbuffer:
+ // {1, 2, 3, 4}
+ // H T
+ assert(ringbuf_read_to_end(rb, item_array, count) == 1);
+
+ // check state of ringbuffer (empty):
+ // {1, 2, 3, 4}
+ // H
+ // T
+ assert(memcmp(item_array, expects2, 1) == 0);
+ assert(rb->cb.head == 0);
+ assert(rb->cb.tail == 0);
+
+ ringbuf_del(rb);
+}
+/* ---- END RINGBUFFER TESTS ---- */
+
diff --git a/runtime/perctile_ringbuf.h b/runtime/perctile_ringbuf.h
new file mode 100644
index 0000000..27bc4a2
--- /dev/null
+++ b/runtime/perctile_ringbuf.h
@@ -0,0 +1,34 @@
+#include <stdlib.h>
+#include <stdbool.h>
+
+typedef int64_t ITEM;
+
+struct circ_buf {
+ ITEM *buf;
+ int head;
+ int tail;
+};
+
+struct ringbuf_s {
+ struct circ_buf cb;
+ size_t size;
+};
+typedef struct ringbuf_s ringbuf_t;
+
+ringbuf_t* ringbuf_new(size_t count);
+void ringbuf_del(ringbuf_t *rb);
+int ringbuf_append(ringbuf_t *rb, ITEM item);
+int ringbuf_append_with_overwrite(ringbuf_t *rb, ITEM item);
+int ringbuf_read(ringbuf_t *rb, ITEM *buf, size_t count);
+size_t ringbuf_read_to_end(ringbuf_t *rb, ITEM *buf, size_t count);
+bool ringbuf_peek(ringbuf_t *rb, ITEM *item);
+size_t ringbuf_capacity(ringbuf_t *rb);
+
+/* ringbuffer tests */
+void ringbuf_init_test(void);
+void ringbuf_simple_test(void);
+void ringbuf_append_test(void);
+void ringbuf_append_wrap_test(void);
+void ringbuf_append_overwrite_test(void);
+void ringbuf_read_test(void);
+void ringbuf_read_to_end_test(void);
diff --git a/runtime/perctile_stats.c b/runtime/perctile_stats.c
new file mode 100644
index 0000000..55a62aa
--- /dev/null
+++ b/runtime/perctile_stats.c
@@ -0,0 +1,695 @@
+/*
+ * 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 <errno.h>
+#include <sys/types.h>
+#include <assert.h>
+
+#include "unicode-helper.h"
+#include "rsyslog.h"
+#include "rsconf.h"
+#include "errmsg.h"
+#include "perctile_stats.h"
+#include "hashtable_itr.h"
+#include "perctile_ringbuf.h"
+#include "datetime.h"
+
+#include <stdio.h>
+#include <pthread.h>
+#include <math.h>
+
+/* Use this macro to enable debug for this module */
+#ifdef PERCTILE_STATS_DEBUG
+#define _DEBUG 1
+#else
+#define _DEBUG 0
+#endif
+#define PERCTILE_STATS_LOG(...) do { if(_DEBUG) fprintf(stderr, __VA_ARGS__); } while(0)
+
+/* definitions for objects we access */
+DEFobjStaticHelpers
+DEFobjCurrIf(statsobj)
+DEFobjCurrIf(datetime)
+
+#define PERCTILE_CONF_PARAM_NAME "name"
+#define PERCTILE_CONF_PARAM_PERCENTILES "percentiles"
+#define PERCTILE_CONF_PARAM_WINDOW_SIZE "windowsize"
+#define PERCTILE_CONF_PARAM_DELIM "delimiter"
+
+#define PERCTILE_MAX_BUCKET_NS_METRIC_LENGTH 128
+#define PERCTILE_METRIC_NAME_SEPARATOR '.'
+
+static struct cnfparamdescr modpdescr[] = {
+ { PERCTILE_CONF_PARAM_NAME, eCmdHdlrString, CNFPARAM_REQUIRED },
+ { PERCTILE_CONF_PARAM_DELIM, eCmdHdlrString, 0},
+ { PERCTILE_CONF_PARAM_PERCENTILES, eCmdHdlrArray, 0},
+ { PERCTILE_CONF_PARAM_WINDOW_SIZE, eCmdHdlrPositiveInt, 0},
+};
+
+static struct cnfparamblk modpblk = {
+ CNFPARAMBLK_VERSION,
+ sizeof(modpdescr)/sizeof(struct cnfparamdescr),
+ modpdescr
+};
+
+rsRetVal
+perctileClassInit(void) {
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+ CHKiRet(objUse(statsobj, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+finalize_it:
+ RETiRet;
+}
+
+static uint64_t min(uint64_t a, uint64_t b) {
+ return a < b ? a : b;
+}
+
+static uint64_t max(uint64_t a, uint64_t b) {
+ return a > b ? a : b;
+}
+
+static void perctileStatDestruct(perctile_bucket_t *b, perctile_stat_t *pstat) {
+ if (pstat) {
+ if (pstat->rb_observed_stats) {
+ ringbuf_del(pstat->rb_observed_stats);
+ }
+
+ if (pstat->ctrs) {
+ for (size_t i = 0; i < pstat->perctile_ctrs_count; ++i) {
+ perctile_ctr_t *ctr = &pstat->ctrs[i];
+ if (ctr->ref_ctr_percentile_stat) {
+ statsobj.DestructCounter(b->statsobj, ctr->ref_ctr_percentile_stat);
+ }
+ }
+ free(pstat->ctrs);
+ }
+
+ if (pstat->refCtrWindowCount) {
+ statsobj.DestructCounter(b->statsobj, pstat->refCtrWindowCount);
+ }
+ if (pstat->refCtrWindowMin) {
+ statsobj.DestructCounter(b->statsobj, pstat->refCtrWindowMin);
+ }
+ if (pstat->refCtrWindowMax) {
+ statsobj.DestructCounter(b->statsobj, pstat->refCtrWindowMax);
+ }
+ if (pstat->refCtrWindowSum) {
+ statsobj.DestructCounter(b->statsobj, pstat->refCtrWindowSum);
+ }
+ pthread_rwlock_destroy(&pstat->stats_lock);
+ free(pstat);
+ }
+}
+
+static void perctileBucketDestruct(perctile_bucket_t *bkt) {
+ PERCTILE_STATS_LOG("destructing perctile bucket\n");
+ if (bkt) {
+ pthread_rwlock_wrlock(&bkt->lock);
+ // Delete all items in hashtable
+ size_t count = hashtable_count(bkt->htable);
+ if (count) {
+ int ret = 0;
+ struct hashtable_itr *itr = hashtable_iterator(bkt->htable);
+ dbgprintf("%s() - All container instances, count=%zu...\n", __FUNCTION__, count);
+ do {
+ perctile_stat_t *pstat = hashtable_iterator_value(itr);
+ perctileStatDestruct(bkt, pstat);
+ ret = hashtable_iterator_advance(itr);
+ } while (ret);
+ free (itr);
+ dbgprintf("End of container instances.\n");
+ }
+ hashtable_destroy(bkt->htable, 0);
+ statsobj.Destruct(&bkt->statsobj);
+ pthread_rwlock_unlock(&bkt->lock);
+ pthread_rwlock_destroy(&bkt->lock);
+ free(bkt->perctile_values);
+ free(bkt->delim);
+ free(bkt->name);
+ free(bkt);
+ }
+}
+
+void perctileBucketsDestruct(void) {
+ perctile_buckets_t *bkts = &runConf->perctile_buckets;
+
+ if (bkts->initialized) {
+ perctile_bucket_t *head = bkts->listBuckets;
+ if (head) {
+ pthread_rwlock_wrlock(&bkts->lock);
+ perctile_bucket_t *pnode = head, *pnext = NULL;
+ while (pnode) {
+ pnext = pnode->next;
+ perctileBucketDestruct(pnode);
+ pnode = pnext;
+ }
+ pthread_rwlock_unlock(&bkts->lock);
+ }
+ statsobj.Destruct(&bkts->global_stats);
+ // destroy any global stats we keep specifically for this.
+ pthread_rwlock_destroy(&bkts->lock);
+ }
+}
+
+static perctile_bucket_t*
+findBucket(perctile_bucket_t *head, const uchar *name) {
+ perctile_bucket_t *pbkt_found = NULL;
+ // walk the linked list until the name is found
+ pthread_rwlock_rdlock(&head->lock);
+ for (perctile_bucket_t *pnode = head; pnode != NULL; pnode = pnode->next) {
+ if (ustrcmp(name, pnode->name) == 0) {
+ // found.
+ pbkt_found = pnode;
+ }
+ }
+ pthread_rwlock_unlock(&head->lock);
+ return pbkt_found;
+}
+
+#ifdef PERCTILE_STATS_DEBUG
+static void
+print_perctiles(perctile_bucket_t *bkt) {
+ if (hashtable_count(bkt->htable)) {
+ struct hashtable_itr *itr = hashtable_iterator(bkt->htable);
+ do {
+ uchar* key = hashtable_iterator_key(itr);
+ perctile_stat_t *perc_stat = hashtable_iterator_value(itr);
+ PERCTILE_STATS_LOG("print_perctile() - key: %s, perctile stat name: %s ", key, perc_stat->name);
+ } while (hashtable_iterator_advance(itr));
+ PERCTILE_STATS_LOG("\n");
+ }
+}
+#endif
+
+// Assumes a fully created pstat and bkt, also initiliazes some values in pstat.
+static rsRetVal
+initAndAddPerctileMetrics(perctile_stat_t *pstat, perctile_bucket_t *bkt, uchar* key) {
+ char stat_name[128];
+ int bytes = 0;
+ int stat_name_len = sizeof(stat_name);
+ DEFiRet;
+
+ bytes = snprintf((char*)pstat->name, sizeof(pstat->name), "%s", key);
+ if (bytes < 0 || bytes >= (int) sizeof(pstat->name)) {
+ LogError(0, iRet, "statname '%s' truncated - too long for buffer size: %d\n", stat_name, stat_name_len);
+ ABORT_FINALIZE(RS_RET_CONF_PARAM_INVLD);
+ }
+
+ int offset = snprintf(stat_name, stat_name_len, "%s%s", (char*)pstat->name, (char*)bkt->delim);
+ if (offset < 0 || offset >= stat_name_len) {
+ LogError(0, iRet, "statname '%s' truncated - too long for buffer size: %d\n", stat_name, stat_name_len);
+ ABORT_FINALIZE(RS_RET_CONF_PARAM_INVLD);
+ }
+
+ int remaining_size = stat_name_len - offset - 1;
+ // initialize the counters array
+ for (size_t i = 0; i < pstat->perctile_ctrs_count; ++i) {
+ perctile_ctr_t *ctr = &pstat->ctrs[i];
+
+ // bucket contains the supported percentile values.
+ ctr->percentile = bkt->perctile_values[i];
+ bytes = snprintf(stat_name+offset, remaining_size, "p%d", bkt->perctile_values[i]);
+ if (bytes < 0 || bytes >= remaining_size) {
+ LogError(0, iRet, "statname '%s' truncated - too long for buffer size: %d\n",
+ stat_name, stat_name_len);
+ ABORT_FINALIZE(RS_RET_CONF_PARAM_INVLD);
+ }
+ CHKiRet(statsobj.AddManagedCounter(bkt->statsobj, (uchar *)stat_name, ctrType_IntCtr,
+ CTR_FLAG_NONE, &ctr->ctr_perctile_stat, &ctr->ref_ctr_percentile_stat, 1));
+ }
+
+ bytes = snprintf(stat_name+offset, remaining_size, "window_min");
+ if (bytes < 0 || bytes >= remaining_size) {
+ LogError(0, iRet, "statname '%s' truncated - too long for buffer size: %d\n", stat_name, stat_name_len);
+ ABORT_FINALIZE(RS_RET_CONF_PARAM_INVLD);
+ }
+ CHKiRet(statsobj.AddManagedCounter(bkt->statsobj, (uchar *)stat_name, ctrType_IntCtr,
+ CTR_FLAG_NONE, &pstat->ctrWindowMin, &pstat->refCtrWindowMin, 1));
+
+ bytes = snprintf(stat_name+offset, remaining_size, "window_max");
+ if (bytes < 0 || bytes >= remaining_size) {
+ LogError(0, iRet, "statname '%s' truncated - too long for buffer size: %d\n", stat_name, stat_name_len);
+ ABORT_FINALIZE(RS_RET_CONF_PARAM_INVLD);
+ }
+ CHKiRet(statsobj.AddManagedCounter(bkt->statsobj, (uchar *)stat_name, ctrType_IntCtr,
+ CTR_FLAG_NONE, &pstat->ctrWindowMax, &pstat->refCtrWindowMax, 1));
+
+ bytes = snprintf(stat_name+offset, remaining_size, "window_sum");
+ if (bytes < 0 || bytes >= remaining_size) {
+ LogError(0, iRet, "statname '%s' truncated - too long for buffer size: %d\n", stat_name, stat_name_len);
+ ABORT_FINALIZE(RS_RET_CONF_PARAM_INVLD);
+ }
+ CHKiRet(statsobj.AddManagedCounter(bkt->statsobj, (uchar *)stat_name, ctrType_IntCtr,
+ CTR_FLAG_NONE, &pstat->ctrWindowSum, &pstat->refCtrWindowSum, 1));
+
+ bytes = snprintf(stat_name+offset, remaining_size, "window_count");
+ if (bytes < 0 || bytes >= remaining_size) {
+ LogError(0, iRet, "statname '%s' truncated - too long for buffer size: %d\n", stat_name, stat_name_len);
+ ABORT_FINALIZE(RS_RET_CONF_PARAM_INVLD);
+ }
+ CHKiRet(statsobj.AddManagedCounter(bkt->statsobj, (uchar *)stat_name, ctrType_IntCtr,
+ CTR_FLAG_NONE, &pstat->ctrWindowCount, &pstat->refCtrWindowCount, 1));
+
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ LogError(0, iRet, "Could not initialize percentile stats.");
+ }
+ RETiRet;
+}
+
+static rsRetVal
+perctile_observe(perctile_bucket_t *bkt, uchar* key, int64_t value) {
+ uint8_t lock_initialized = 0;
+ uchar* hash_key = NULL;
+ DEFiRet;
+ time_t now;
+ datetime.GetTime(&now);
+
+ pthread_rwlock_wrlock(&bkt->lock);
+ lock_initialized = 1;
+ perctile_stat_t *pstat = (perctile_stat_t*) hashtable_search(bkt->htable, key);
+ if (!pstat) {
+ PERCTILE_STATS_LOG("perctile_observe(): key '%s' not found - creating new pstat", key);
+ // create the pstat if not found
+ CHKmalloc(pstat = calloc(1, sizeof(perctile_stat_t)));
+ pstat->ctrs = calloc(bkt->perctile_values_count, sizeof(perctile_ctr_t));
+ if (!pstat->ctrs) {
+ free(pstat);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ pstat->perctile_ctrs_count = bkt->perctile_values_count;
+ pstat->rb_observed_stats = ringbuf_new(bkt->window_size);
+ if (!pstat->rb_observed_stats) {
+ free(pstat->ctrs);
+ free(pstat);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ pstat->bReported = 0;
+ pthread_rwlock_init(&pstat->stats_lock, NULL);
+
+ // init all stat counters here
+ pthread_rwlock_wrlock(&pstat->stats_lock);
+ pstat->ctrWindowCount = pstat->ctrWindowMax = pstat->ctrWindowSum = 0;
+ pstat->ctrWindowMin = value;
+ pthread_rwlock_unlock(&pstat->stats_lock);
+
+ iRet = initAndAddPerctileMetrics(pstat, bkt, key);
+ if (iRet != RS_RET_OK) {
+ perctileStatDestruct(bkt, pstat);
+ ABORT_FINALIZE(iRet);
+ }
+
+ CHKmalloc(hash_key = ustrdup(key));
+ if (!hashtable_insert(bkt->htable, hash_key, pstat)) {
+ perctileStatDestruct(bkt, pstat);
+ free(hash_key);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ PERCTILE_STATS_LOG("perctile_observe - new pstat created - name: %s\n", pstat->name);
+ STATSCOUNTER_INC(bkt->ctrNewKeyAdd, bkt->mutCtrNewKeyAdd);
+ }
+
+ // add this value into the ringbuffer
+ assert(pstat->rb_observed_stats);
+ if (ringbuf_append_with_overwrite(pstat->rb_observed_stats, value) != 0) {
+ // ringbuffer is operating in overwrite mode, so should never see this.
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ // update perctile specific stats
+ pthread_rwlock_wrlock(&pstat->stats_lock);
+ {
+ if (pstat->bReported) {
+ // reset window values
+ pstat->ctrWindowCount = pstat->ctrWindowSum = 0;
+ pstat->ctrWindowMin = pstat->ctrWindowMax = value;
+ pstat->bReported = 0;
+ }
+ ++(pstat->ctrWindowCount);
+ pstat->ctrWindowSum += value;
+ pstat->ctrWindowMin = min(pstat->ctrWindowMin, value);
+ pstat->ctrWindowMax = max(pstat->ctrWindowMax, value);
+ }
+ pthread_rwlock_unlock(&pstat->stats_lock);
+
+#ifdef PERCTILE_STATS_DEBUG
+ PERCTILE_STATS_LOG("perctile_observe - appended value: %lld to ringbuffer\n", value);
+ PERCTILE_STATS_LOG("ringbuffer contents... \n");
+ for (size_t i = 0; i < pstat->rb_observed_stats->size; ++i) {
+ PERCTILE_STATS_LOG("%lld ", pstat->rb_observed_stats->cb.buf[i]);
+ }
+ PERCTILE_STATS_LOG("\n");
+ print_perctiles(bkt);
+#endif
+finalize_it:
+ if (lock_initialized) {
+ pthread_rwlock_unlock(&bkt->lock);
+ }
+ if (iRet != RS_RET_OK) {
+ // clean up if there was an error
+ if (iRet == RS_RET_OUT_OF_MEMORY) {
+ STATSCOUNTER_INC(bkt->ctrOpsOverflow, bkt->mutCtrOpsOverflow);
+ }
+ }
+ RETiRet;
+}
+
+static int cmp(const void* p1, const void* p2) {
+ return (*(ITEM*)p1) - (*(ITEM*)p2);
+}
+
+static rsRetVal report_perctile_stats(perctile_bucket_t* pbkt) {
+ ITEM *buf = NULL;
+ struct hashtable_itr *itr = NULL;
+ DEFiRet;
+
+ pthread_rwlock_rdlock(&pbkt->lock);
+ if (hashtable_count(pbkt->htable)) {
+ itr = hashtable_iterator(pbkt->htable);
+ CHKmalloc(buf = malloc(pbkt->window_size*sizeof(ITEM)));
+ do {
+ memset(buf, 0, pbkt->window_size*sizeof(ITEM));
+ perctile_stat_t *perc_stat = hashtable_iterator_value(itr);
+ // ringbuffer read
+ size_t count = ringbuf_read_to_end(perc_stat->rb_observed_stats, buf, pbkt->window_size);
+ if (!count) {
+ continue;
+ }
+ PERCTILE_STATS_LOG("read %zu values\n", count);
+ // calculate the p95 based on the
+#ifdef PERCTILE_STATS_DEBUG
+ PERCTILE_STATS_LOG("ringbuffer contents... \n");
+ for (size_t i = 0; i < perc_stat->rb_observed_stats->size; ++i) {
+ PERCTILE_STATS_LOG("%lld ", perc_stat->rb_observed_stats->cb.buf[i]);
+ }
+ PERCTILE_STATS_LOG("\n");
+
+ PERCTILE_STATS_LOG("buffer contents... \n");
+ for (size_t i = 0; i < perc_stat->rb_observed_stats->size; ++i) {
+ PERCTILE_STATS_LOG("%lld ", buf[i]);
+ }
+ PERCTILE_STATS_LOG("\n");
+#endif
+ qsort(buf, count, sizeof(ITEM), cmp);
+#ifdef PERCTILE_STATS_DEBUG
+ PERCTILE_STATS_LOG("buffer contents after sort... \n");
+ for (size_t i = 0; i < perc_stat->rb_observed_stats->size; ++i) {
+ PERCTILE_STATS_LOG("%lld ", buf[i]);
+ }
+ PERCTILE_STATS_LOG("\n");
+#endif
+ PERCTILE_STATS_LOG("report_perctile_stats() - perctile stat has %zu counters.",
+ perc_stat->perctile_ctrs_count);
+ for (size_t i = 0; i < perc_stat->perctile_ctrs_count; ++i) {
+ perctile_ctr_t *pctr = &perc_stat->ctrs[i];
+ // get percentile - this can be cached.
+ int index = max(0, ((pctr->percentile/100.0) * count)-1);
+ // look into if we need to lock this.
+ pctr->ctr_perctile_stat = buf[index];
+ PERCTILE_STATS_LOG("report_perctile_stats() - index: %d, perctile stat [%s, %d, %llu]",
+ index, perc_stat->name, pctr->percentile, pctr->ctr_perctile_stat);
+ }
+ perc_stat->bReported = 1;
+ } while (hashtable_iterator_advance(itr));
+ }
+
+finalize_it:
+ pthread_rwlock_unlock(&pbkt->lock);
+ free(itr);
+ free(buf);
+ RETiRet;
+}
+
+static void
+perctile_readCallback(statsobj_t __attribute__((unused)) *ignore, void __attribute__((unused)) *b) {
+ perctile_buckets_t *bkts = &runConf->perctile_buckets;
+
+ pthread_rwlock_rdlock(&bkts->lock);
+ for (perctile_bucket_t *pbkt = bkts->listBuckets; pbkt != NULL; pbkt = pbkt->next) {
+ report_perctile_stats(pbkt);
+ }
+ pthread_rwlock_unlock(&bkts->lock);
+}
+
+static rsRetVal
+perctileInitNewBucketStats(perctile_bucket_t *b) {
+ DEFiRet;
+
+ CHKiRet(statsobj.Construct(&b->statsobj));
+ CHKiRet(statsobj.SetOrigin(b->statsobj, UCHAR_CONSTANT("percentile.bucket")));
+ CHKiRet(statsobj.SetName(b->statsobj, b->name));
+ CHKiRet(statsobj.SetReportingNamespace(b->statsobj, UCHAR_CONSTANT("values")));
+ statsobj.SetReadNotifier(b->statsobj, perctile_readCallback, b);
+ CHKiRet(statsobj.ConstructFinalize(b->statsobj));
+
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+perctileAddBucketMetrics(perctile_buckets_t *bkts, perctile_bucket_t *b, const uchar* name) {
+ uchar *metric_name_buff, *metric_suffix;
+ const uchar *suffix_litteral;
+ int name_len;
+ DEFiRet;
+
+ name_len = ustrlen(name);
+ CHKmalloc(metric_name_buff = malloc((name_len + PERCTILE_MAX_BUCKET_NS_METRIC_LENGTH + 1) * sizeof(uchar)));
+
+ strcpy((char*)metric_name_buff, (char*)name);
+ metric_suffix = metric_name_buff + name_len;
+ *metric_suffix = PERCTILE_METRIC_NAME_SEPARATOR;
+ metric_suffix++;
+
+ suffix_litteral = UCHAR_CONSTANT("new_metric_add");
+ ustrncpy(metric_suffix, suffix_litteral, PERCTILE_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrNewKeyAdd, b->mutCtrNewKeyAdd);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrNewKeyAdd),
+ &b->pNewKeyAddCtr, 1));
+
+ suffix_litteral = UCHAR_CONSTANT("ops_overflow");
+ ustrncpy(metric_suffix, suffix_litteral, PERCTILE_MAX_BUCKET_NS_METRIC_LENGTH);
+ STATSCOUNTER_INIT(b->ctrOpsOverflow, b->mutCtrOpsOverflow);
+ CHKiRet(statsobj.AddManagedCounter(bkts->global_stats, metric_name_buff, ctrType_IntCtr,
+ CTR_FLAG_RESETTABLE,
+ &(b->ctrOpsOverflow),
+ &b->pOpsOverflowCtr, 1));
+
+finalize_it:
+ free(metric_name_buff);
+ if (iRet != RS_RET_OK) {
+ if (b->pOpsOverflowCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pOpsOverflowCtr);
+ }
+ if (b->pNewKeyAddCtr != NULL) {
+ statsobj.DestructCounter(bkts->global_stats, b->pNewKeyAddCtr);
+ }
+ }
+ RETiRet;
+}
+
+/* Create new perctile bucket, and add it to our list of perctile buckets.
+*/
+static rsRetVal
+perctile_newBucket(const uchar *name, const uchar *delim,
+ uint8_t *perctiles, uint32_t perctilesCount, uint32_t windowSize) {
+ perctile_buckets_t *bkts;
+ perctile_bucket_t* b = NULL;
+ pthread_rwlockattr_t bucket_lock_attr;
+ DEFiRet;
+
+ bkts = &loadConf->perctile_buckets;
+
+ if (bkts->initialized)
+ {
+ CHKmalloc(b = calloc(1, sizeof(perctile_bucket_t)));
+
+ // initialize
+ pthread_rwlockattr_init(&bucket_lock_attr);
+ pthread_rwlock_init(&b->lock, &bucket_lock_attr);
+ CHKmalloc(b->htable = create_hashtable(7, hash_from_string, key_equals_string, NULL));
+ CHKmalloc(b->name = ustrdup(name));
+ if (delim) {
+ CHKmalloc(b->delim = ustrdup(delim));
+ } else {
+ CHKmalloc(b->delim = ustrdup("."));
+ }
+
+ CHKmalloc(b->perctile_values = calloc(perctilesCount, sizeof(uint8_t)));
+ b->perctile_values_count = perctilesCount;
+ memcpy(b->perctile_values, perctiles, perctilesCount * sizeof(uint8_t));
+ b->window_size = windowSize;
+ b->next = NULL;
+ PERCTILE_STATS_LOG("perctile_newBucket: create new bucket for %s,"
+ "with windowsize: %d, values_count: %zu\n",
+ b->name, b->window_size, b->perctile_values_count);
+
+ // add bucket to list of buckets
+ if (!bkts->listBuckets)
+ {
+ // none yet
+ bkts->listBuckets = b;
+ PERCTILE_STATS_LOG("perctile_newBucket: Adding new bucket to empty list \n");
+ }
+ else
+ {
+ b->next = bkts->listBuckets;
+ bkts->listBuckets = b;
+ PERCTILE_STATS_LOG("perctile_newBucket: prepended new bucket list \n");
+ }
+
+ // create the statsobj for this bucket
+ CHKiRet(perctileInitNewBucketStats(b));
+ CHKiRet(perctileAddBucketMetrics(bkts, b, name));
+ }
+ else
+ {
+ LogError(0, RS_RET_INTERNAL_ERROR, "perctile: bucket creation failed, as "
+ "global-initialization of buckets was unsuccessful");
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+finalize_it:
+ if (iRet != RS_RET_OK)
+ {
+ if (b != NULL) {
+ perctileBucketDestruct(b);
+ }
+ }
+ RETiRet;
+}
+
+// Public functions
+rsRetVal
+perctile_processCnf(struct cnfobj *o) {
+ struct cnfparamvals *pvals;
+ uchar *name = NULL;
+ uchar *delim = NULL;
+ uint8_t *perctiles = NULL;
+ uint32_t perctilesCount = 0;
+ uint64_t windowSize = 0;
+ DEFiRet;
+
+ pvals = nvlstGetParams(o->nvlst, &modpblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+
+ for(short i = 0 ; i < modpblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(modpblk.descr[i].name, PERCTILE_CONF_PARAM_NAME)) {
+ CHKmalloc(name = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
+ } else if(!strcmp(modpblk.descr[i].name, PERCTILE_CONF_PARAM_DELIM)) {
+ CHKmalloc(delim = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL));
+ } else if (!strcmp(modpblk.descr[i].name, PERCTILE_CONF_PARAM_PERCENTILES)) {
+ /* Only the first instance of this parameter will be accepted */
+ if (!perctiles) {
+ perctilesCount = pvals[i].val.d.ar->nmemb;
+ if (perctilesCount) {
+ CHKmalloc(perctiles = calloc(perctilesCount, sizeof(uint8_t)));
+ for (int j = 0; j < pvals[i].val.d.ar->nmemb; ++j) {
+ char *cstr = es_str2cstr(pvals[i].val.d.ar->arr[j], NULL);
+ perctiles[j] = atoi(cstr);
+ free(cstr);
+ }
+ }
+ }
+ } else if (!strcmp(modpblk.descr[i].name, PERCTILE_CONF_PARAM_WINDOW_SIZE)) {
+ windowSize = pvals[i].val.d.n;
+ } else {
+ dbgprintf("perctile: program error, non-handled "
+ "param '%s'\n", modpblk.descr[i].name);
+ }
+ }
+
+ if (name != NULL && perctiles != NULL) {
+ CHKiRet(perctile_newBucket(name, delim, perctiles, perctilesCount, windowSize));
+ }
+
+finalize_it:
+ free(name);
+ free(delim);
+ free(perctiles);
+ cnfparamvalsDestruct(pvals, &modpblk);
+ RETiRet;
+}
+
+rsRetVal
+perctile_initCnf(perctile_buckets_t *bkts) {
+ DEFiRet;
+
+ bkts->initialized = 0;
+ bkts->listBuckets = NULL;
+ CHKiRet(statsobj.Construct(&bkts->global_stats));
+ CHKiRet(statsobj.SetOrigin(bkts->global_stats, UCHAR_CONSTANT("percentile")));
+ CHKiRet(statsobj.SetName(bkts->global_stats, UCHAR_CONSTANT("global")));
+ CHKiRet(statsobj.SetReportingNamespace(bkts->global_stats, UCHAR_CONSTANT("values")));
+ CHKiRet(statsobj.ConstructFinalize(bkts->global_stats));
+ pthread_rwlock_init(&bkts->lock, NULL);
+ bkts->initialized = 1;
+
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ statsobj.Destruct(&bkts->global_stats);
+ }
+ RETiRet;
+}
+
+perctile_bucket_t*
+perctile_findBucket(const uchar* name) {
+ perctile_bucket_t *b = NULL;
+
+ perctile_buckets_t *bkts = &loadConf->perctile_buckets;
+ if (bkts->initialized) {
+ pthread_rwlock_rdlock(&bkts->lock);
+ if (bkts->listBuckets) {
+ b = findBucket(bkts->listBuckets, name);
+ }
+ pthread_rwlock_unlock(&bkts->lock);
+ } else {
+ LogError(0, RS_RET_INTERNAL_ERROR, "perctile: bucket lookup failed, as global-initialization "
+ "of buckets was unsuccessful");
+ }
+ return b;
+}
+
+rsRetVal
+perctile_obs(perctile_bucket_t *perctile_bkt, uchar* key, int64_t value) {
+ DEFiRet;
+ if (!perctile_bkt) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "perctile() - perctile bkt not available");
+ FINALIZE;
+ }
+ PERCTILE_STATS_LOG("perctile_obs() - bucket name: %s, key: %s, val: %" PRId64 "\n",
+ perctile_bkt->name, key, value);
+
+ CHKiRet(perctile_observe(perctile_bkt, key, value));
+
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "perctile_obs(): name: %s, key: %s, val: %" PRId64 "\n",
+ perctile_bkt->name, key, value);
+ }
+ RETiRet;
+}
diff --git a/runtime/perctile_stats.h b/runtime/perctile_stats.h
new file mode 100644
index 0000000..6a7ed0d
--- /dev/null
+++ b/runtime/perctile_stats.h
@@ -0,0 +1,89 @@
+/*
+ * 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_PERCTILE_STATS_H
+#define INCLUDED_PERCTILE_STATS_H
+
+#include "hashtable.h"
+#include "statsobj.h"
+
+struct perctile_ctr_s {
+ // percentile [0,100]
+ uint8_t percentile;
+ size_t index;
+ intctr_t ctr_perctile_stat;
+ ctr_t *ref_ctr_percentile_stat;
+};
+
+struct perctile_stat_s {
+ uchar name[128];
+ sbool bReported;
+ struct ringbuf_s *rb_observed_stats;
+ // array of requested perctile to track
+ struct perctile_ctr_s *ctrs;
+ size_t perctile_ctrs_count;
+
+ pthread_rwlock_t stats_lock;
+ intctr_t ctrWindowCount;
+ ctr_t *refCtrWindowCount;
+ intctr_t ctrWindowMin;
+ ctr_t *refCtrWindowMin;
+ intctr_t ctrWindowMax;
+ ctr_t *refCtrWindowMax;
+ intctr_t ctrWindowSum;
+ ctr_t *refCtrWindowSum;
+};
+
+struct perctile_bucket_s {
+ uchar *name;
+ uchar *delim;
+ // lock for entire bucket
+ pthread_rwlock_t lock;
+ struct hashtable *htable;
+ struct perctile_bucket_s *next;
+ statsobj_t *statsobj;
+ STATSCOUNTER_DEF(ctrNewKeyAdd, mutCtrNewKeyAdd);
+ ctr_t *pNewKeyAddCtr;
+ STATSCOUNTER_DEF(ctrOpsOverflow, mutCtrOpsOverflow);
+ ctr_t *pOpsOverflowCtr;
+ uint32_t window_size;
+ // These percentile values apply to all perctile stats in this bucket.
+ uint8_t *perctile_values;
+ size_t perctile_values_count;
+};
+
+struct perctile_buckets_s {
+ uint8_t initialized;
+ statsobj_t *global_stats;
+ pthread_rwlock_t lock;
+ struct perctile_bucket_s *listBuckets;
+};
+
+// keep these local for now.
+typedef struct perctile_ctr_s perctile_ctr_t;
+typedef struct perctile_stat_s perctile_stat_t;
+typedef struct perctile_bucket_s perctile_bucket_t;
+
+rsRetVal perctileClassInit(void);
+void perctileBucketsDestruct(void);
+rsRetVal perctile_initCnf(perctile_buckets_t *b);
+perctile_bucket_t* perctile_findBucket(const uchar* name);
+rsRetVal perctile_processCnf(struct cnfobj *cnf);
+rsRetVal perctile_obs(perctile_bucket_t *perctile_bkt, uchar* key, int64_t value);
+
+#endif /* #ifndef INCLUDED_PERCTILE_STATS_H */
diff --git a/runtime/prop.c b/runtime/prop.c
new file mode 100644
index 0000000..c4de5d7
--- /dev/null
+++ b/runtime/prop.c
@@ -0,0 +1,262 @@
+/* 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-2016 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
+
+//extern uchar *propGetSzStr(prop_t *pThis); /* expand inline function here */
+
+/* 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, const uchar *psz, const 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 {
+ if(pThis->szVal.psz != NULL) {
+ free(pThis->szVal.psz);
+ }
+ 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 */
+static rsRetVal GetString(prop_t *pThis, uchar **ppsz, int *plen)
+{
+ ISOBJ_TYPE_assert(pThis, prop);
+ if(pThis->len < CONF_PROP_BUFSIZE) {
+ *ppsz = pThis->szVal.sz;
+ } else {
+ *ppsz = pThis->szVal.psz;
+ }
+ *plen = pThis->len;
+ 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)
+{
+ if(pThis == NULL) {
+ DBGPRINTF("prop/AddRef is passed a NULL ptr - ignoring it "
+ "- further problems may occur\n");
+ FINALIZE;
+ }
+ ATOMIC_INC(&pThis->iRefCount, &pThis->mutRefCount);
+finalize_it:
+ 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 also (very, very) slightly faster.
+ * rgerhards, 2009-07-01
+ */
+static rsRetVal CreateStringProp(prop_t **ppThis, const uchar* psz, const int len)
+{
+ prop_t *pThis = NULL;
+ DEFiRet;
+
+ CHKiRet(propConstruct(&pThis));
+ CHKiRet(SetString(pThis, psz, len));
+ CHKiRet(propConstructFinalize(pThis));
+ *ppThis = pThis;
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis != NULL)
+ propDestruct(&pThis);
+ }
+
+ 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
+ */
+static rsRetVal CreateOrReuseStringProp(prop_t **ppThis, const uchar *psz, const 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 */
+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 */
+
+ /* 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 0000000..eed0f56
--- /dev/null
+++ b/runtime/prop.h
@@ -0,0 +1,73 @@
+/* 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, const uchar* psz, const 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, const uchar* psz, const int len);
+ rsRetVal (*CreateOrReuseStringProp)(prop_t **ppThis, const uchar *psz, const int len);
+ENDinterface(prop)
+#define propCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+
+/* get classic c-style string */
+/* Note: I know that "static inline" is not the right thing from a C99
+ * PoV, but some environments treat, even in C99 mode, compile
+ * non-static inline into the source even if not defined as "extern". This
+ * obviously results in linker errors. Using "static inline" as below together
+ * with "__attribute__((unused))" works in all cases. Note also that we
+ * cannot work around this as we would otherwise need to evaluate
+ * pThis more than once.
+ */
+static inline uchar * __attribute__((unused)) ATTR_NONNULL(1)
+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 0000000..edc9d35
--- /dev/null
+++ b/runtime/queue.c
@@ -0,0 +1,3609 @@
+/* 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 https://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-2019 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 <inttypes.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 "obj.h"
+#include "atomic.h"
+#include "errmsg.h"
+#include "datetime.h"
+#include "unicode-helper.h"
+#include "statsobj.h"
+#include "parserif.h"
+#include "rsconf.h"
+
+#ifdef OS_SOLARIS
+# include <sched.h>
+#endif
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(strm)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(statsobj)
+
+#if __GNUC__ >= 8
+#pragma GCC diagnostic ignored "-Wcast-function-type" // TODO: investigate further!
+#endif /* if __GNUC__ >= 8 */
+
+#ifdef ENABLE_IMDIAG
+unsigned int iOverallQueueSize = 0;
+#endif
+
+#define OVERSIZE_QUEUE_WATERMARK 500000 /* when is a queue considered to be "overly large"? */
+
+
+/* forward-definitions */
+static rsRetVal doEnqSingleObj(qqueue_t *pThis, flowControl_t flowCtlType, smsg_t *pMsg);
+static rsRetVal qqueueChkPersist(qqueue_t *pThis, int nUpdates);
+static rsRetVal RateLimiter(qqueue_t *pThis);
+static rsRetVal 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, smsg_t *pMsg);
+static rsRetVal qDestructDirect(qqueue_t __attribute__((unused)) *pThis);
+static rsRetVal qConstructDirect(qqueue_t __attribute__((unused)) *pThis);
+static rsRetVal qDestructDisk(qqueue_t *pThis);
+rsRetVal qqueueSetSpoolDir(qqueue_t *pThis, uchar *pszSpoolDir, int lenSpoolDir);
+
+/* 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.spooldirectory", eCmdHdlrGetWord, 0 },
+ { "queue.size", eCmdHdlrSize, 0 },
+ { "queue.dequeuebatchsize", eCmdHdlrInt, 0 },
+ { "queue.mindequeuebatchsize", eCmdHdlrInt, 0 },
+ { "queue.mindequeuebatchsize.timeout", 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 },
+ { "queue.samplinginterval", eCmdHdlrInt, 0 },
+ { "queue.takeflowctlfrommsg", eCmdHdlrBinary, 0 }
+};
+static struct cnfparamblk pblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(cnfpdescr)/sizeof(struct cnfparamdescr),
+ cnfpdescr
+ };
+
+/* support to detect duplicate queue file names */
+struct queue_filename {
+ struct queue_filename *next;
+ const char *dirname;
+ const char *filename;
+};
+struct queue_filename *queue_filename_root = NULL;
+
+/* debug aid */
+#if 0
+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]);
+ }
+}
+#endif
+static rsRetVal qqueuePersist(qqueue_t *pThis, int bIsCheckpoint);
+
+/* do cleanup when config is loaded */
+void qqueueDoneLoadCnf(void)
+{
+ struct queue_filename *next, *del;
+ next = queue_filename_root;
+ while(next != NULL) {
+ del = next;
+ next = next->next;
+ free((void*) del->filename);
+ free((void*) del->dirname);
+ free((void*) del);
+ }
+}
+
+
+/***********************************************************************
+ * 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 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 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 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 const char *
+getQueueTypeName(queueType_t t)
+{
+ const 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.mindequeuebatchsize: %d\n", pThis->iMinDeqBatchSize);
+ dbgoprint((obj_t*) pThis, "queue.mindequeuebatchsize.timeout: %d\n", pThis->toMinDeqBatchSize);
+ dbgoprint((obj_t*) pThis, "queue.maxdiskspace: %lld\n", pThis->sizeOnDiskMax);
+ 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.takeflowctlfrommsg: %d\n", pThis->takeFlowCtlFromMsg);
+ 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, "queue.dequeuetimeend: %d\n", pThis->iDeqtWinToHr);
+}
+
+
+/* get the physical queue size. Must only be called
+ * while mutex is locked!
+ * rgerhards, 2008-01-29
+ */
+static int
+getPhysicalQueueSize(qqueue_t *pThis)
+{
+ return (int) PREFER_FETCH_32BIT(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 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 void queueDrain(qqueue_t *pThis)
+{
+ smsg_t *pMsg;
+ assert(pThis != NULL);
+
+ 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);
+ }
+}
+
+
+/* --------------- 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 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, DENY_WORKER_START_DURING_SHUTDOWN);
+ /* disk queues have always one worker */
+ }
+ if(getLogicalQueueSize(pThis) == 0) {
+ iMaxWorkers = 0;
+ } else if(pThis->iMinMsgsPerWrkr == 0) {
+ iMaxWorkers = 1;
+ } else {
+ iMaxWorkers = getLogicalQueueSize(pThis) / pThis->iMinMsgsPerWrkr + 1;
+ }
+ wtpAdviseMaxWorkers(pThis->pWtpReg, iMaxWorkers, DENY_WORKER_START_DURING_SHUTDOWN);
+ }
+
+ 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, pThis->iNumWorkerThreads, 0, pThis->pConsumer));
+
+ /* give it a name */
+ snprintf((char*) pszDAQName, sizeof(pszDAQName), "%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(qqueueSetSpoolDir(pThis->pqDA, pThis->pszSpoolDir, pThis->lenSpoolDir));
+ 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));
+ pThis->pqDA->iDeqBatchSize = pThis->iDeqBatchSize;
+ pThis->pqDA->iMinDeqBatchSize = pThis->iMinDeqBatchSize;
+ pThis->pqDA->iMinMsgsPerWrkr = pThis->iMinMsgsPerWrkr;
+ pThis->pqDA->iLowWtrMrk = pThis->iLowWtrMrk;
+ if(pThis->useCryprov) {
+ /* hand over cryprov to DA queue - in-mem queue does no longer need it
+ * and DA queue will be kept active from now on until termination.
+ */
+ pThis->pqDA->useCryprov = pThis->useCryprov;
+ pThis->pqDA->cryprov = pThis->cryprov;
+ pThis->pqDA->cryprovData = pThis->cryprovData;
+ pThis->pqDA->cryprovName = pThis->cryprovName;
+ pThis->pqDA->cryprovNameFull = pThis->cryprovNameFull;
+ /* reset memory queue parameters */
+ pThis->useCryprov = 0;
+ /* pThis->cryprov cannot and need not be reset, is structure */
+ pThis->cryprovData = NULL;
+ pThis->cryprovName = NULL;
+ pThis->cryprovNameFull = NULL;
+ }
+
+ iRet = qqueueStart(runConf, 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! */
+ 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);
+ }
+ LogError(0, iRet, "%s: error creating disk queue - giving up.",
+ obj.GetName((obj_t*)pThis));
+ 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 ATTR_NONNULL()
+InitDA(qqueue_t *const pThis, const int bLockMutex)
+{
+ DEFiRet;
+ uchar pszBuf[64];
+ size_t lenBuf;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ if(bLockMutex == LOCK_MUTEX) {
+ d_pthread_mutex_lock(pThis->mut);
+ }
+
+ /* 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(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:
+ if(bLockMutex == LOCK_MUTEX) {
+ d_pthread_mutex_unlock(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, smsg_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, smsg_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, smsg_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, smsg_t **ppMsg)
+{
+ qLinkedList_t *pEntry;
+ DEFiRet;
+
+ pEntry = pThis->tVars.linklist.pDeqRoot;
+ if (pEntry != NULL) {
+ *ppMsg = pEntry->pMsg;
+ pThis->tVars.linklist.pDeqRoot = pEntry->pNext;
+ } else {
+ /* Check and return NULL for linklist.pDeqRoot */
+ dbgprintf("qDeqLinkedList: pDeqRoot is NULL!\n");
+ *ppMsg = NULL;
+ pThis->tVars.linklist.pDeqRoot = NULL;
+ }
+
+ 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. However,
+ * we do not really stop or destruct the in-memory disk queue object.
+ * Practice has shown that this may cause races during destruction which
+ * themselfs can lead to segfault. So we prefer to was some ressources by
+ * keeping the queue active.
+ * 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 rsRetVal
+queueSwitchToEmergencyMode(qqueue_t *pThis, rsRetVal initiatingError)
+{
+ pThis->iQueueSize = 0;
+ pThis->nLogDeq = 0;
+
+ pThis->qType = QUEUETYPE_DIRECT;
+ pThis->qConstruct = qConstructDirect;
+ pThis->qDestruct = qDestructDirect;
+ /* these entry points shall not be used in direct mode
+ * To catch program errors, make us abort if that happens!
+ * rgerhards, 2013-11-05
+ */
+ pThis->qAdd = qAddDirect;
+ pThis->MultiEnq = qqueueMultiEnqObjDirect;
+ pThis->qDel = NULL;
+ 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
+ */
+ }
+
+ 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, pThis->pszSpoolDir, pThis->lenSpoolDir));
+ CHKiRet(strm.SetbSync(pStrm, pThis->bSyncQueueFiles));
+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 {
+ LogError(errno, RS_RET_IO_ERROR, "queue: %s: error %d could not access .qi file",
+ obj.GetName((obj_t*) pThis), 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;
+ LogMsg(0, RS_RET_OK, LOG_INFO, "%s: queue files exist on disk, re-starting with "
+ "%d messages. This will keep the disk queue file open, details: "
+ "https://rainer.gerhards.net/2013/07/rsyslog-why-disk-assisted-queues-keep-a-file-open.html",
+ objGetName((obj_t*) pThis), getLogicalQueueSize(pThis));
+
+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, pThis->pszSpoolDir, pThis->lenSpoolDir));
+ 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, pThis->pszSpoolDir, pThis->lenSpoolDir));
+ 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, pThis->pszSpoolDir, pThis->lenSpoolDir));
+ 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);
+
+ free(pThis->pszQIFNam);
+ if(pThis->tVars.disk.pWrite != NULL) {
+ int64 currOffs;
+ strm.GetCurrOffset(pThis->tVars.disk.pWrite, &currOffs);
+ if(currOffs == 0) {
+ /* if no data is present, we can (and must!) delete this
+ * file. Else we can leave garbagge after termination.
+ */
+ strm.SetbDeleteOnClose(pThis->tVars.disk.pWrite, 1);
+ }
+ 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 ATTR_NONNULL(1,2)
+qAddDisk(qqueue_t *const pThis, smsg_t* pMsg)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pMsg, msg);
+ number_t nWriteCount;
+ const int oldfile = strmGetCurrFileNum(pThis->tVars.disk.pWrite);
+
+ 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);
+
+ /* Did we have a change in the on-disk file? If so, we
+ * should do a "robustness sync" of the .qi file to guard
+ * against the most harsh consequences of kill -9 and power off.
+ */
+ int newfile;
+ newfile = strmGetCurrFileNum(pThis->tVars.disk.pWrite);
+ if(newfile != oldfile) {
+ DBGOPRINT((obj_t*) pThis, "current to-be-written-to file has changed from "
+ "number %d to number %d - requiring a .qi write for robustness\n",
+ oldfile, newfile);
+ pThis->tVars.disk.nForcePersist = 2;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal
+qDeqDisk(qqueue_t *pThis, smsg_t **ppMsg)
+{
+ DEFiRet;
+ iRet = objDeserializeWithMethods(ppMsg, (uchar*) "msg", 3,
+ pThis->tVars.disk.pReadDeq, NULL,
+ NULL, msgConstructForDeserializer, NULL, MsgDeserialize);
+ if(iRet != RS_RET_OK) {
+ LogError(0, iRet, "%s: qDeqDisk error happened at around offset %lld",
+ obj.GetName((obj_t*)pThis),
+ (long long) pThis->tVars.disk.pReadDeq->iCurrOffs);
+ }
+ 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 qAddDirectWithWti(qqueue_t *pThis, smsg_t* pMsg, wti_t *pWti)
+{
+ batch_t singleBatch;
+ batch_obj_t batchObj;
+ batch_state_t batchState = BATCH_STATE_RDY;
+ 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;
+ iRet = pThis->pConsumer(pThis->pAction, &singleBatch, pWti);
+ msgDestruct(&pMsg);
+
+ RETiRet;
+}
+
+/* this is called if we do not have a pWti. This currently only happens
+ * when we are called from a main queue in direct mode. If so, we need
+ * to obtain a dummy pWti.
+ */
+static rsRetVal
+qAddDirect(qqueue_t *pThis, smsg_t* pMsg)
+{
+ wti_t *pWti;
+ DEFiRet;
+
+ pWti = wtiGetDummy();
+ pWti->pbShutdownImmediate = &pThis->bShutdownImmediate;
+ iRet = qAddDirectWithWti(pThis, pMsg, pWti);
+ RETiRet;
+}
+
+
+/* --------------- 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, smsg_t *pMsg)
+{
+ DEFiRet;
+
+ assert(pThis != NULL);
+
+ static int msgCnt = 0;
+
+ if(pThis->iSmpInterval > 0)
+ {
+ msgCnt = (msgCnt + 1) % (pThis->iSmpInterval);
+ if(msgCnt != 0)
+ {
+ msgDestruct(&pMsg);
+ goto finalize_it;
+ }
+ }
+
+ CHKiRet(pThis->qAdd(pThis, pMsg));
+
+ if(pThis->qType != QUEUETYPE_DIRECT) {
+ ATOMIC_INC(&pThis->iQueueSize, &pThis->mutQueueSize);
+# ifdef ENABLE_IMDIAG
+# ifdef HAVE_ATOMIC_BUILTINS
+ /* mutex is never used due to conditional compilation */
+ ATOMIC_INC(&iOverallQueueSize, &NULL);
+# else
+ ++iOverallQueueSize; /* racy, but we can't wait for a mutex! */
+# endif
+# endif
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* generic code to dequeue a queue entry
+ */
+static rsRetVal
+qqueueDeq(qqueue_t *pThis, smsg_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 rsRetVal ATTR_NONNULL(1)
+tryShutdownWorkersWithinQueueTimeout(qqueue_t *const 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, DENY_WORKER_START_DURING_SHUTDOWN);
+ 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, DENY_WORKER_START_DURING_SHUTDOWN);
+ /* 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) {
+ LogMsg(0, RS_RET_TIMED_OUT, LOG_INFO,
+ "%s: regular queue shutdown timed out on primary queue "
+ "(this is OK, timeout was %d)",
+ objGetName((obj_t*) pThis), pThis->toQShutdown);
+ } 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) {
+ LogMsg(0, RS_RET_TIMED_OUT, LOG_INFO,
+ "%s: regular queue shutdown timed out on DA queue (this is OK, "
+ "timeout was %d)", objGetName((obj_t*) pThis), pThis->toQShutdown);
+ } 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;
+ }
+
+ /* 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) {
+ LogMsg(0, RS_RET_TIMED_OUT, LOG_INFO,
+ "%s: immediate shutdown timed out on primary queue (this is acceptable and "
+ "triggers cancellation)", objGetName((obj_t*) pThis));
+ } else if(iRetLocal != RS_RET_OK) {
+ LogMsg(0, iRetLocal, LOG_WARNING,
+ "%s: potential internal error: unexpected return state after trying "
+ "immediate shutdown of the primary queue in disk save mode. "
+ "Continuing, but results are unpredictable", objGetName((obj_t*) pThis));
+ }
+
+ 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) {
+ LogMsg(0, RS_RET_TIMED_OUT, LOG_INFO,
+ "%s: immediate shutdown timed out on DA queue (this is acceptable and "
+ "triggers cancellation)", objGetName((obj_t*) pThis));
+ } else if(iRetLocal != RS_RET_OK) {
+ LogMsg(0, iRetLocal, LOG_WARNING,
+ "%s: potential internal error: unexpected return state after trying "
+ "immediate shutdown of the DA queue in disk save mode. "
+ "Continuing, but results are unpredictable", objGetName((obj_t*) pThis));
+ }
+
+ /* 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) {
+ LogMsg(0, iRetLocal, LOG_WARNING,
+ "%s: shutdown timed out on main queue DA worker pool "
+ "(this is not good, but possibly OK)",
+ objGetName((obj_t*) pThis));
+ } 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;
+
+ assert(pThis->qType != QUEUETYPE_DIRECT);
+
+ /* 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, objGetName((obj_t*) pThis));
+ /* ^-- 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, objGetName((obj_t*) pThis));
+ /* 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, objGetName((obj_t*) pThis));
+ /* 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.
+ */
+rsRetVal ATTR_NONNULL(1)
+qqueueShutdownWorkers(qqueue_t *const pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ if(pThis->qType == QUEUETYPE_DIRECT) {
+ FINALIZE;
+ }
+
+ assert(pThis->pqParent == NULL); /* detect invalid calling sequence */
+
+ DBGOPRINT((obj_t*) pThis, "initiating worker thread shutdown sequence %p\n", pThis);
+
+ CHKiRet(tryShutdownWorkersWithinQueueTimeout(pThis));
+
+ pthread_mutex_lock(pThis->mut);
+ int physQueueSize;
+ physQueueSize = getPhysicalQueueSize(pThis);
+ pthread_mutex_unlock(pThis->mut);
+ if(physQueueSize > 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*, wti_t*))
+{
+ DEFiRet;
+ qqueue_t *pThis;
+ const uchar *const workDir = glblGetWorkDirRaw(ourConf);
+
+ 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(workDir != NULL) {
+ if((pThis->pszSpoolDir = ustrdup(workDir)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ pThis->lenSpoolDir = ustrlen(pThis->pszSpoolDir);
+ }
+ /* set some water marks so that we have useful defaults if none are set specifically */
+ pThis->iFullDlyMrk = -1;
+ pThis->iLightDlyMrk = -1;
+ pThis->iMaxFileSize = 1024 * 1024; /* default is 1 MiB */
+ pThis->iQueueSize = 0;
+ pThis->nLogDeq = 0;
+ pThis->useCryprov = 0;
+ pThis->takeFlowCtlFromMsg = 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->iMinDeqBatchSize = 0; /* conservative default, should still provide good performance */
+ pThis->isRunning = 0;
+
+ 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->iMinDeqBatchSize = 0;
+ pThis->toMinDeqBatchSize = 1000;
+ pThis->iHighWtrMrk = -1; /* high water mark for disk-assisted queues */
+ pThis->iLowWtrMrk = -1; /* low water mark for disk-assisted queues */
+ pThis->iDiscardMrk = -1; /* 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 = loadConf->globals.actq_dflt_toQShutdown; /* queue shutdown */
+ pThis->toActShutdown = loadConf->globals.actq_dflt_toActShutdown; /* action shutdown (in phase 2) */
+ pThis->toEnq = loadConf->globals.actq_dflt_toEnq; /* timeout for queue enque */
+ pThis->toWrkShutdown = loadConf->globals.actq_dflt_toWrkShutdown; /* timeout for worker thread shutdown */
+ pThis->iMinMsgsPerWrkr = -1; /* 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 */
+ pThis->iSmpInterval = 0; /* disable sampling */
+}
+
+
+/* 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->iMinDeqBatchSize = 0;
+ pThis->toMinDeqBatchSize = 1000;
+ pThis->iHighWtrMrk = -1; /* high water mark for disk-assisted queues */
+ pThis->iLowWtrMrk = -1; /* low water mark for disk-assisted queues */
+ pThis->iDiscardMrk = -1; /* 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 = ourConf->globals.ruleset_dflt_toQShutdown;
+ pThis->toActShutdown = ourConf->globals.ruleset_dflt_toActShutdown;
+ pThis->toEnq = ourConf->globals.ruleset_dflt_toEnq;
+ pThis->toWrkShutdown = ourConf->globals.ruleset_dflt_toWrkShutdown;
+ pThis->iMinMsgsPerWrkr = -1; /* 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 */
+ pThis->iSmpInterval = 0; /* disable sampling */
+}
+
+
+/* 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, smsg_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 rsRetVal ATTR_NONNULL(1)
+DoDeleteBatchFromQStore(qqueue_t *const pThis, const int nElem)
+{
+ int i;
+ off64_t bytesDel = 0; /* keep CLANG static anaylzer happy */
+ 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, "doDeleteBatch: a %lld octet file has been deleted, now %lld "
+ "octets disk space used\n", (long long) 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);
+# ifdef ENABLE_IMDIAG
+# ifdef HAVE_ATOMIC_BUILTINS
+ /* mutex is never used due to conditional compilation */
+ ATOMIC_SUB(&iOverallQueueSize, nElem, &NULL);
+# else
+ iOverallQueueSize -= nElem; /* racy, but we can't wait for a mutex! */
+# endif
+# endif
+ ATOMIC_SUB(&pThis->nLogDeq, nElem, &pThis->mutLogDeq);
+ DBGPRINTF("doDeleteBatch: delete batch from store, new sizes: log %d, phys %d\n",
+ getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+ ++pThis->deqIDDel; /* one more batch dequeued */
+
+ if((pThis->qType == QUEUETYPE_DISK) && (bytesDel != 0)) {
+ qqueuePersist(pThis, QUEUE_CHECKPOINT); /* robustness persist .qi file */
+ }
+
+ RETiRet;
+}
+
+
+/* remove messages from the physical queue store that are fully processed. This is
+ * controlled via the to-delete list.
+ */
+static rsRetVal
+DeleteBatchFromQStore(qqueue_t *pThis, batch_t *pBatch)
+{
+ toDeleteLst_t *pTdl;
+ qDeqID deqIDDel;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ assert(pBatch != NULL);
+
+dbgprintf("rger: deleteBatchFromQStore, nElem %d\n", (int) pBatch->nElem);
+ 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 rsRetVal
+DeleteProcessedBatch(qqueue_t *pThis, batch_t *pBatch)
+{
+ int i;
+ smsg_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;
+ DBGPRINTF("DeleteProcessedBatch: etry %d state %d\n", i, pBatch->eltState[i]);
+ 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("DeleteProcessedBatch: error %d re-enqueuing unprocessed "
+ "data element - discarded\n", localRet);
+ }
+ }
+ msgDestruct(&pMsg);
+ }
+
+ DBGPRINTF("DeleteProcessedBatch: 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 rsRetVal ATTR_NONNULL()
+DequeueConsumableElements(qqueue_t *const pThis, wti_t *const pWti,
+ int *const piRemainingQueueSize, int *const pSkippedMsgs)
+{
+ int nDequeued;
+ int nDiscarded;
+ int nDeleted;
+ int iQueueSize;
+ int keep_running = 1;
+ struct timespec timeout;
+ smsg_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);
+ }
+
+ /* work-around clang static analyzer false positive, we need a const value */
+ const int iMinDeqBatchSize = pThis->iMinDeqBatchSize;
+ if(iMinDeqBatchSize > 0) {
+ timeoutComp(&timeout, pThis->toMinDeqBatchSize);/* get absolute timeout */
+ }
+
+ while((iQueueSize = getLogicalQueueSize(pThis)) > 0 && nDequeued < pThis->iDeqBatchSize) {
+ int rd_fd = -1;
+ int64_t rd_offs = 0;
+ int wr_fd = -1;
+ int64_t wr_offs = 0;
+ if(pThis->tVars.disk.pReadDeq != NULL) {
+ rd_fd = strmGetCurrFileNum(pThis->tVars.disk.pReadDeq);
+ rd_offs = pThis->tVars.disk.pReadDeq->iCurrOffs;
+ }
+ if(pThis->tVars.disk.pWrite != NULL) {
+ wr_fd = strmGetCurrFileNum(pThis->tVars.disk.pWrite);
+ wr_offs = pThis->tVars.disk.pWrite->iCurrOffs;
+ }
+ if(rd_fd != -1 && rd_fd == wr_fd && rd_offs == wr_offs) {
+ DBGPRINTF("problem on disk queue '%s': "
+ //"queue size log %d, phys %d, but rd_fd=wr_rd=%d and offs=%lld\n",
+ "queue size log %d, phys %d, but rd_fd=wr_rd=%d and offs=%" PRId64 "\n",
+ obj.GetName((obj_t*) pThis), iQueueSize, pThis->iQueueSize,
+ rd_fd, rd_offs);
+ *pSkippedMsgs = iQueueSize;
+# ifdef ENABLE_IMDIAG
+ iOverallQueueSize -= iQueueSize;
+# endif
+ pThis->iQueueSize -= iQueueSize;
+ iQueueSize = 0;
+ break;
+ }
+
+ localRet = qqueueDeq(pThis, &pMsg);
+ if(localRet == RS_RET_FILE_NOT_FOUND) {
+ DBGPRINTF("fatal error on disk queue '%s': file '%s' "
+ "not found, queue size said to be %d",
+ obj.GetName((obj_t*) pThis), "...", iQueueSize);
+ }
+ CHKiRet(localRet);
+
+ /* 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(nDequeued < iMinDeqBatchSize && getLogicalQueueSize(pThis) == 0) {
+ while(!pThis->bShutdownImmediate
+ && keep_running
+ && nDequeued < iMinDeqBatchSize
+ && getLogicalQueueSize(pThis) == 0) {
+ dbgprintf("%s minDeqBatchSize doing wait, batch is %d messages, "
+ "queue size %d\n", obj.GetName((obj_t*) pThis),
+ nDequeued, getLogicalQueueSize(pThis));
+ if(wtiWaitNonEmpty(pWti, timeout) == 0) { /* timeout? */
+ DBGPRINTF("%s minDeqBatchSize timeout, batch is %d messages\n",
+ obj.GetName((obj_t*) pThis), nDequeued);
+ keep_running = 0;
+ }
+ }
+ }
+ if(keep_running) {
+ keep_running = (getLogicalQueueSize(pThis) > 0)
+ && (nDequeued < pThis->iDeqBatchSize);
+ }
+ }
+
+ 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);
+
+ /* If messages where DISCARDED, we need to substract them from the OverallQueueSize */
+# ifdef ENABLE_IMDIAG
+# ifdef HAVE_ATOMIC_BUILTINS
+ ATOMIC_SUB(&iOverallQueueSize, nDiscarded, &NULL);
+# else
+ iOverallQueueSize -= nDiscarded; /* racy, but we can't wait for a mutex! */
+# endif
+ DBGOPRINT((obj_t*) pThis, "dequeued %d discarded %d QueueSize %d consumable elements, szlog %d sz phys %d\n",
+ nDequeued, nDiscarded, iOverallQueueSize, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+# else
+ DBGOPRINT((obj_t*) pThis, "dequeued %d discarded %d consumable elements, szlog %d sz phys %d\n",
+ nDequeued, nDiscarded, getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis));
+# endif
+
+ 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, int *const pSkippedMsgs)
+{
+ DEFiRet;
+ int iQueueSize = 0; /* keep the compiler happy... */
+
+ *pSkippedMsgs = 0;
+ /* dequeue element batch (still protected from mutex) */
+ iRet = DequeueConsumableElements(pThis, pWti, &iQueueSize, pSkippedMsgs);
+ if(*pSkippedMsgs > 0) {
+ LogError(0, RS_RET_ERR, "%s: lost %d messages from diskqueue (invalid .qi file)",
+ obj.GetName((obj_t*)pThis), *pSkippedMsgs);
+ }
+
+ /* 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) {
+ LogError(0, iRet, "%s: error dequeueing element - ignoring, "
+ "but strange things may happen", obj.GetName((obj_t*)pThis));
+ }
+
+ RETiRet;
+}
+
+
+/* The rate limiter
+ *
+ * IMPORTANT: the rate-limiter MUST unlock and re-lock the queue when
+ * it actually delays processing. Otherwise inputs are stalled.
+ *
+ * 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) {
+ pthread_mutex_unlock(pThis->mut);
+ DBGOPRINT((obj_t*) pThis, "outside dequeue time window, delaying %d seconds\n", iDelay);
+ srSleep(iDelay, 0);
+ pthread_mutex_lock(pThis->mut);
+ }
+
+ 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 rsRetVal
+DequeueForConsumer(qqueue_t *pThis, wti_t *pWti, int *const pSkippedMsgs)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pWti, wti);
+
+ CHKiRet(DequeueConsumable(pThis, pWti, pSkippedMsgs));
+
+ 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? */
+ int skippedMsgs = 0; /**< did the queue loose any messages (can happen with
+ ** disk queue if .qi file is corrupt */
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pWti, wti);
+
+ iRet = DequeueForConsumer(pThis, pWti, &skippedMsgs);
+ 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;
+
+ /* report errors, now that we are outside of queue lock */
+ if(skippedMsgs > 0) {
+ LogError(0, 0, "problem on disk queue '%s': "
+ "queue files contain %d messages fewer than specified "
+ "in .qi file -- we lost those messages. That's all we know.",
+ obj.GetName((obj_t*) pThis), skippedMsgs);
+ }
+
+ /* at this spot, we may be cancelled */
+ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &iCancelStateSave);
+
+
+ pWti->pbShutdownImmediate = &pThis->bShutdownImmediate;
+ CHKiRet(pThis->pConsumer(pThis->pAction, &pWti->batch, pWti));
+
+ /* we now need to check if we should deliberately delay processing a bit
+ * and, if so, do that. -- rgerhards, 2008-01-30
+ */
+ 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-acquire 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? */
+ int skippedMsgs = 0;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, qqueue);
+ ISOBJ_TYPE_assert(pWti, wti);
+
+ CHKiRet(DequeueForConsumer(pThis, pWti, &skippedMsgs));
+
+ /* 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++) {
+ iRet = qqueueEnqMsg(pThis->pqDA, eFLOWCTL_NO_DELAY, MsgAddRef(pWti->batch.pElem[i].pMsg));
+ if(iRet != RS_RET_OK) {
+ if(iRet == RS_RET_ERR_QUEUE_EMERGENCY) {
+ /* Queue emergency error occurred */
+ DBGOPRINT((obj_t*) pThis, "ConsumerDA:qqueueEnqMsg caught RS_RET_ERR_QUEUE_EMERGENCY,"
+ "aborting loop.\n");
+ FINALIZE;
+ } else {
+ DBGOPRINT((obj_t*) pThis, "ConsumerDA:qqueueEnqMsg item (%d) returned "
+ "with error state: '%d'\n", i, iRet);
+ }
+ }
+ pWti->batch.eltState[i] = BATCH_STATE_COMM; /* commited to other queue! */
+ }
+
+ /* but now cancellation is no longer permitted */
+ pthread_setcancelstate(iCancelStateSave, NULL);
+
+finalize_it:
+ /* Check the last return state of qqueueEnqMsg. If an error was returned, we acknowledge it only.
+ * Unless the error code is RS_RET_ERR_QUEUE_EMERGENCY, we reset the return state to RS_RET_OK.
+ * Otherwise the Caller functions would run into an infinite Loop trying to enqueue the
+ * same messages over and over again.
+ *
+ * However we do NOT overwrite positive return states like
+ * RS_RET_TERMINATE_NOW,
+ * RS_RET_NO_RUN,
+ * RS_RET_IDLE,
+ * RS_RET_TERMINATE_WHEN_IDLE
+ * These return states are important for Queue handling of the upper laying functions.
+ * RGer: Note that checking for iRet < 0 is a bit bold. In theory, positive iRet
+ * values are "OK" states, and things that the caller shall deal with. However,
+ * this has not been done so consistently. Andre convinced me that the current
+ * code is an elegant solution. However, if problems with queue workers and/or
+ * shutdown come up, this code here should be looked at suspiciously. In those
+ * cases it may work out to check all status codes explicitely, just to avoid
+ * a pitfall due to unexpected states being passed on to the caller.
+ */
+ if( iRet != RS_RET_OK &&
+ iRet != RS_RET_ERR_QUEUE_EMERGENCY &&
+ iRet < 0) {
+ DBGOPRINT((obj_t*) pThis, "ConsumerDA:qqueueEnqMsg Resetting iRet from %d back to RS_RET_OK\n", iRet);
+ iRet = RS_RET_OK;
+ } else {
+ DBGOPRINT((obj_t*) pThis, "ConsumerDA:qqueueEnqMsg returns with iRet %d\n", iRet);
+ }
+
+ /* now we are done, but potentially need to re-acquire the mutex */
+ if(bNeedReLock)
+ d_pthread_mutex_lock(pThis->mut);
+
+ RETiRet;
+}
+
+
+/* must only be called when the queue mutex is locked, else results
+ * are not stable!
+ */
+static rsRetVal
+qqueueChkStopWrkrDA(qqueue_t *pThis)
+{
+ DEFiRet;
+
+ DBGPRINTF("rger: 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;
+ RETiRet;
+}
+
+
+/* start up the queue - it must have been constructed and parameters defined
+ * before.
+ */
+rsRetVal
+qqueueStart(rsconf_t *cnf, qqueue_t *pThis) /* this is the ConstructionFinalizer */
+{
+ DEFiRet;
+ uchar pszBuf[64];
+ uchar pszQIFNam[MAXFNAME];
+ int wrk;
+ uchar *qName;
+ size_t lenBuf;
+
+ assert(pThis != NULL);
+
+ /* do not modify the queue if it's already running(happens when dynamic config reload is invoked
+ * and the queue is used in the new config as well)
+ */
+ if (pThis->isRunning)
+ FINALIZE;
+
+ dbgoprint((obj_t*) pThis, "starting queue\n");
+
+ if(pThis->pszSpoolDir == NULL) {
+ /* note: we need to pick the path so late as we do not have
+ * the workdir during early config load
+ */
+ if((pThis->pszSpoolDir = (uchar*) strdup((char*)glbl.GetWorkDir(cnf))) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ pThis->lenSpoolDir = ustrlen(pThis->pszSpoolDir);
+ }
+ /* 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;
+ /* pre-construct file name for .qi file */
+ pThis->lenQIFNam = snprintf((char*)pszQIFNam, sizeof(pszQIFNam),
+ "%s/%s.qi", (char*) pThis->pszSpoolDir, (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;
+ /* these entry points shall not be used in direct mode
+ * To catch program errors, make us abort if that happens!
+ * rgerhards, 2013-11-05
+ */
+ pThis->qAdd = qAddDirect;
+ pThis->MultiEnq = qqueueMultiEnqObjDirect;
+ pThis->qDel = NULL;
+ break;
+ }
+
+ /* 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) {
+ CHKmalloc(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->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, "params: type %d, enq-only %d, disk assisted %d, spoolDir '%s', maxFileSz %lld, "
+ "maxQSize %d, lqsize %d, pqsize %d, child %d, full delay %d, "
+ "light delay %d, deq batch size %d, min deq batch size %d, "
+ "high wtrmrk %d, low wtrmrk %d, "
+ "discardmrk %d, max wrkr %d, min msgs f. wrkr %d "
+ "takeFlowCtlFromMsg %d\n",
+ pThis->qType, pThis->bEnqOnly, pThis->bIsDA, pThis->pszSpoolDir,
+ pThis->iMaxFileSize, pThis->iMaxQueueSize,
+ getLogicalQueueSize(pThis), getPhysicalQueueSize(pThis),
+ pThis->pqParent == NULL ? 0 : 1, pThis->iFullDlyMrk, pThis->iLightDlyMrk,
+ pThis->iDeqBatchSize, pThis->iMinDeqBatchSize, pThis->iHighWtrMrk, pThis->iLowWtrMrk,
+ pThis->iDiscardMrk, (int) pThis->iNumWorkerThreads, (int) pThis->iMinMsgsPerWrkr,
+ pThis->takeFlowCtlFromMsg);
+
+ 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",
+ (int) (sizeof(pszBuf)-16),
+ obj.GetName((obj_t*) pThis)); /* leave some room inside the name for suffixes */
+ if(lenBuf >= sizeof(pszBuf)) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "%s:%d debug header too long: %zd - in "
+ "thory this cannot happen - truncating", __FILE__, __LINE__, lenBuf);
+ lenBuf = sizeof(pszBuf)-1;
+ pszBuf[lenBuf] = '\0';
+ }
+ 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(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));
+ CHKiRet(statsobj.SetOrigin(pThis->statsobj, (uchar*)"core.queue"));
+ /* 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, CTR_FLAG_NONE, &pThis->iQueueSize));
+
+ STATSCOUNTER_INIT(pThis->ctrEnqueued, pThis->mutCtrEnqueued);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("enqueued"),
+ ctrType_IntCtr, CTR_FLAG_RESETTABLE, &pThis->ctrEnqueued));
+
+ STATSCOUNTER_INIT(pThis->ctrFull, pThis->mutCtrFull);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("full"),
+ ctrType_IntCtr, CTR_FLAG_RESETTABLE, &pThis->ctrFull));
+
+ STATSCOUNTER_INIT(pThis->ctrFDscrd, pThis->mutCtrFDscrd);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("discarded.full"),
+ ctrType_IntCtr, CTR_FLAG_RESETTABLE, &pThis->ctrFDscrd));
+ STATSCOUNTER_INIT(pThis->ctrNFDscrd, pThis->mutCtrNFDscrd);
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("discarded.nf"),
+ ctrType_IntCtr, CTR_FLAG_RESETTABLE, &pThis->ctrNFDscrd));
+
+ pThis->ctrMaxqsize = 0; /* no mutex needed, thus no init call */
+ CHKiRet(statsobj.AddCounter(pThis->statsobj, UCHAR_CONSTANT("maxqsize"),
+ ctrType_Int, CTR_FLAG_NONE, &pThis->ctrMaxqsize));
+
+ CHKiRet(statsobj.ConstructFinalize(pThis->statsobj));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ /* note: a child uses it's parent mutex, so do not delete it! */
+ if(pThis->pqParent == NULL && pThis->mut != NULL)
+ free(pThis->mut);
+ } else {
+ pThis->isRunning = 1;
+ }
+ 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;
+ char *tmpQIFName = NULL;
+ strm_t *psQIF = NULL; /* Queue Info File */
+ char errStr[1024];
+
+ 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 */
+ }
+
+ int lentmpQIFName;
+#ifdef _AIX
+ lentmpQIFName = strlen( pThis->pszQIFNam) + strlen(".tmp") + 1;
+ tmpQIFName = malloc(sizeof(char)*lentmpQIFName);
+ if(tmpQIFName == NULL)
+ tmpQIFName = (char*)pThis->pszQIFNam;
+ snprintf(tmpQIFName, lentmpQIFName, "%s.tmp", pThis->pszQIFNam);
+#else
+ lentmpQIFName = asprintf((char **)&tmpQIFName, "%s.tmp", pThis->pszQIFNam);
+ if(tmpQIFName == NULL)
+ tmpQIFName = (char*)pThis->pszQIFNam;
+#endif
+
+ 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, (uchar*) tmpQIFName, lentmpQIFName));
+ 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));
+
+ strm.Destruct(&psQIF);
+ if(tmpQIFName != (char*)pThis->pszQIFNam) { /* pointer, not string comparison! */
+ if(rename(tmpQIFName, (char*)pThis->pszQIFNam) != 0) {
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ DBGOPRINT((obj_t*) pThis,
+ "FATAL error: renaming temporary .qi file failed: %s\n",
+ errStr);
+ ABORT_FINALIZE(RS_RET_RENAME_TMP_QI_ERROR);
+ }
+ }
+
+ /* 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(tmpQIFName != (char*)pThis->pszQIFNam) /* pointer, not string comparison! */
+ free(tmpQIFName);
+ 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 *const pThis, const 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 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, PERMIT_WORKER_START_DURING_SHUTDOWN); /* 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)
+ DBGOPRINT((obj_t*) pThis, "shutdown: begin to destruct queue\n");
+ if(ourConf->globals.shutdownQueueDoubleSize) {
+ pThis->iHighWtrMrk *= 2;
+ pThis->iMaxQueueSize *= 2;
+ }
+ 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)
+ qqueueShutdownWorkers(pThis);
+
+ if(pThis->bIsDA && getPhysicalQueueSize(pThis) > 0){
+ if(pThis->bSaveOnShutdown) {
+ LogMsg(0, RS_RET_TIMED_OUT, LOG_INFO,
+ "%s: queue holds %d messages after shutdown of workers. "
+ "queue.saveonshutdown is set, so data will now be spooled to disk",
+ objGetName((obj_t*) pThis), getPhysicalQueueSize(pThis));
+ CHKiRet(DoSaveOnShutdown(pThis));
+ } else {
+ LogMsg(0, RS_RET_TIMED_OUT, LOG_WARNING,
+ "%s: queue holds %d messages after shutdown of workers. "
+ "queue.saveonshutdown is NOT set, so data will be discarded.",
+ objGetName((obj_t*) pThis), getPhysicalQueueSize(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->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 spool directory. The directory MUST NOT be NULL.
+ * The passed-in string is duplicated. So if the caller does not need
+ * it any longer, it must free it.
+ */
+rsRetVal
+qqueueSetSpoolDir(qqueue_t *pThis, uchar *pszSpoolDir, int lenSpoolDir)
+{
+ DEFiRet;
+
+ free(pThis->pszSpoolDir);
+ CHKmalloc(pThis->pszSpoolDir = ustrdup(pszSpoolDir));
+ pThis->lenSpoolDir = lenSpoolDir;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* 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(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 rsRetVal
+doEnqSingleObj(qqueue_t *pThis, flowControl_t flowCtlType, smsg_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(unlikely(pThis->takeFlowCtlFromMsg)) { /* recommendation is NOT to use this option */
+ flowCtlType = pMsg->flowCtlType;
+ }
+ 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, "doEnqSingleObject: 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, "doEnqSingleObject: 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->bIsDA) && 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, "doEnqSingleObject: queue FULL - configured for immediate "
+ "discarding QueueSize=%d MaxQueueSize=%d sizeOnDisk=%lld "
+ "sizeOnDiskMax=%lld\n", pThis->iQueueSize, pThis->iMaxQueueSize,
+ pThis->tVars.disk.sizeOnDisk, pThis->sizeOnDiskMax);
+ STATSCOUNTER_INC(pThis->ctrFDscrd, pThis->mutCtrFDscrd);
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_QUEUE_FULL);
+ } else {
+ DBGOPRINT((obj_t*) pThis, "doEnqSingleObject: queue FULL - waiting %dms to drain.\n",
+ pThis->toEnq);
+ if(glbl.GetGlobalInputTermState()) {
+ DBGOPRINT((obj_t*) pThis, "doEnqSingleObject: queue FULL, discard due to "
+ "FORCE_TERM.\n");
+ ABORT_FINALIZE(RS_RET_FORCE_TERM);
+ }
+ timeoutComp(&t, pThis->toEnq);
+ const int r = pthread_cond_timedwait(&pThis->notFull, pThis->mut, &t);
+ if(dbgTimeoutToStderr && r != 0) {
+ fprintf(stderr, "%lld: queue timeout(%dms), error %d%s, "
+ "lost message %s\n", (long long) time(NULL), pThis->toEnq,
+ r, ( r == ETIMEDOUT) ? "[ETIMEDOUT]" : "", pMsg->pszRawMsg);
+ }
+ if(r == ETIMEDOUT) {
+ DBGOPRINT((obj_t*) pThis, "doEnqSingleObject: cond timeout, dropping message!\n");
+ STATSCOUNTER_INC(pThis->ctrFDscrd, pThis->mutCtrFDscrd);
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_QUEUE_FULL);
+ } else if(r != 0) {
+ DBGOPRINT((obj_t*) pThis, "doEnqSingleObject: cond error %d, dropping message!\n", r);
+ STATSCOUNTER_INC(pThis->ctrFDscrd, pThis->mutCtrFDscrd);
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_QUEUE_FULL);
+ }
+ dbgoprint((obj_t*) pThis, "doEnqSingleObject: wait solved queue full condition, enqueing\n");
+ }
+ }
+
+ /* and finally enqueue the message */
+ CHKiRet(qqueueAdd(pThis, pMsg));
+ STATSCOUNTER_SETMAX_NOMUT(pThis->ctrMaxqsize, pThis->iQueueSize);
+
+ /* check if we had a file rollover and need to persist
+ * the .qi file for robustness reasons.
+ * Note: the n=2 write is required for closing the old file and
+ * the n=1 write is required after opening and writing to the new
+ * file.
+ */
+ if(pThis->tVars.disk.nForcePersist > 0) {
+ DBGOPRINT((obj_t*) pThis, ".qi file write required for robustness reasons (n=%d)\n",
+ pThis->tVars.disk.nForcePersist);
+ pThis->tVars.disk.nForcePersist--;
+ qqueuePersist(pThis, QUEUE_CHECKPOINT);
+ }
+
+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;
+ wti_t *pWti;
+ DEFiRet;
+
+ pWti = wtiGetDummy();
+ pWti->pbShutdownImmediate = &pThis->bShutdownImmediate;
+
+ for(i = 0 ; i < pMultiSub->nElem ; ++i) {
+ CHKiRet(qAddDirectWithWti(pThis, (void*)pMultiSub->ppMsgs[i], pWti));
+ }
+
+finalize_it:
+ RETiRet;
+}
+/* ------------------------------ END multi-enqueue functions ------------------------------ */
+
+
+/* enqueue a new user data element
+ * Enqueues the new element and awakes worker thread.
+ */
+rsRetVal
+qqueueEnqMsg(qqueue_t *pThis, flowControl_t flowCtlType, smsg_t *pMsg)
+{
+ DEFiRet;
+ int iCancelStateSave;
+ ISOBJ_TYPE_assert(pThis, qqueue);
+
+ const int isNonDirectQ = pThis->qType != QUEUETYPE_DIRECT;
+
+ if(isNonDirectQ) {
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ d_pthread_mutex_lock(pThis->mut);
+ }
+
+ CHKiRet(doEnqSingleObj(pThis, flowCtlType, pMsg));
+
+ qqueueChkPersist(pThis, 1);
+
+finalize_it:
+ if(isNonDirectQ) {
+ /* 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 rsRetVal
+initCryprov(qqueue_t *pThis, struct nvlst *lst)
+{
+ uchar szDrvrName[1024];
+ DEFiRet;
+
+ if(snprintf((char*)szDrvrName, sizeof(szDrvrName), "lmcry_%s", pThis->cryprovName)
+ == sizeof(szDrvrName)) {
+ 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) {
+ 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) {
+ 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;
+}
+
+/* check the the queue file name is unique. */
+static rsRetVal ATTR_NONNULL()
+checkUniqueDiskFile(qqueue_t *const pThis)
+{
+ DEFiRet;
+ struct queue_filename *queue_fn_curr = queue_filename_root;
+ struct queue_filename *newetry = NULL;
+ const char *const curr_dirname = (pThis->pszSpoolDir == NULL) ? "" : (char*)pThis->pszSpoolDir;
+
+ if(pThis->pszFilePrefix == NULL) {
+ FINALIZE; /* no disk queue! */
+ }
+
+ while(queue_fn_curr != NULL) {
+ if(!strcmp((const char*) pThis->pszFilePrefix, queue_fn_curr->filename) &&
+ !strcmp(curr_dirname, queue_fn_curr->dirname)) {
+ parser_errmsg("queue directory '%s' and file name prefix '%s' already used. "
+ "This is not possible. Please make it unique.",
+ curr_dirname, pThis->pszFilePrefix);
+ ABORT_FINALIZE(RS_RET_ERR_QUEUE_FN_DUP);
+ }
+ queue_fn_curr = queue_fn_curr->next;
+ }
+
+ /* name ok, so let's add it to the list */
+ CHKmalloc(newetry = calloc(1, sizeof(struct queue_filename)));
+ CHKmalloc(newetry->filename = strdup((char*) pThis->pszFilePrefix));
+ CHKmalloc(newetry->dirname = strdup(curr_dirname));
+ newetry->next = queue_filename_root;
+ queue_filename_root = newetry;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(newetry != NULL) {
+ free((void*)newetry->filename);
+ free((void*)newetry);
+ }
+ }
+ RETiRet;
+}
+
+void
+qqueueCorrectParams(qqueue_t *pThis)
+{
+ int goodval; /* a "good value" to use for comparisons (different objects) */
+
+ if(pThis->iMaxQueueSize < 100
+ && (pThis->qType == QUEUETYPE_LINKEDLIST || pThis->qType == QUEUETYPE_FIXED_ARRAY)) {
+ LogMsg(0, RS_RET_OK_WARN, LOG_WARNING, "Note: queue.size=\"%d\" is very "
+ "low and can lead to unpredictable results. See also "
+ "https://www.rsyslog.com/lower-bound-for-queue-sizes/",
+ pThis->iMaxQueueSize);
+ }
+
+ /* we need to do a quick check if our water marks are set plausible. If not,
+ * we correct the most important shortcomings.
+ */
+ goodval = (pThis->iMaxQueueSize / 100) * 60;
+ if(pThis->iHighWtrMrk != -1 && pThis->iHighWtrMrk < goodval) {
+ LogMsg(0, RS_RET_CONF_PARSE_WARNING, LOG_WARNING, "queue \"%s\": high water mark "
+ "is set quite low at %d. You should only set it below "
+ "60%% (%d) if you have a good reason for this.",
+ obj.GetName((obj_t*) pThis), pThis->iHighWtrMrk, goodval);
+ }
+
+ if(pThis->iNumWorkerThreads > 1) {
+ goodval = (pThis->iMaxQueueSize / 100) * 10;
+ if(pThis->iMinMsgsPerWrkr != -1 && pThis->iMinMsgsPerWrkr < goodval) {
+ LogMsg(0, RS_RET_CONF_PARSE_WARNING, LOG_WARNING, "queue \"%s\": "
+ "queue.workerThreadMinimumMessage "
+ "is set quite low at %d. You should only set it below "
+ "10%% (%d) if you have a good reason for this.",
+ obj.GetName((obj_t*) pThis), pThis->iMinMsgsPerWrkr, goodval);
+ }
+ }
+
+ if(pThis->iDiscardMrk > pThis->iMaxQueueSize) {
+ LogError(0, RS_RET_PARAM_ERROR, "error: queue \"%s\": "
+ "queue.discardMark %d is set larger than queue.size",
+ obj.GetName((obj_t*) pThis), pThis->iDiscardMrk);
+ }
+
+ goodval = (pThis->iMaxQueueSize / 100) * 80;
+ if(pThis->iDiscardMrk != -1 && pThis->iDiscardMrk < goodval) {
+ LogMsg(0, RS_RET_CONF_PARSE_WARNING, LOG_WARNING,
+ "queue \"%s\": queue.discardMark "
+ "is set quite low at %d. You should only set it below "
+ "80%% (%d) if you have a good reason for this.",
+ obj.GetName((obj_t*) pThis), pThis->iDiscardMrk, goodval);
+ }
+
+ if(pThis->pszFilePrefix != NULL) { /* This means we have a potential DA queue */
+ if(pThis->iFullDlyMrk != -1 && pThis->iFullDlyMrk < pThis->iHighWtrMrk) {
+ LogMsg(0, RS_RET_CONF_WRN_FULLDLY_BELOW_HIGHWTR, LOG_WARNING,
+ "queue \"%s\": queue.fullDelayMark "
+ "is set below high water mark. This will result in DA mode "
+ " NOT being activated for full delayable messages: In many "
+ "cases this is a configuration error, please check if this "
+ "is really what you want",
+ obj.GetName((obj_t*) pThis));
+ }
+ }
+
+ /* now come parameter corrections and defaults */
+ if(pThis->iHighWtrMrk < 2 || pThis->iHighWtrMrk > pThis->iMaxQueueSize) {
+ pThis->iHighWtrMrk = (pThis->iMaxQueueSize / 100) * 90;
+ if(pThis->iHighWtrMrk == 0) { /* guard against very low max queue sizes! */
+ pThis->iHighWtrMrk = pThis->iMaxQueueSize;
+ }
+ }
+ if( pThis->iLowWtrMrk < 2
+ || pThis->iLowWtrMrk > pThis->iMaxQueueSize
+ || pThis->iLowWtrMrk > pThis->iHighWtrMrk ) {
+ pThis->iLowWtrMrk = (pThis->iMaxQueueSize / 100) * 70;
+ if(pThis->iLowWtrMrk == 0) {
+ pThis->iLowWtrMrk = 1;
+ }
+ }
+
+ if((pThis->iMinMsgsPerWrkr < 1
+ || pThis->iMinMsgsPerWrkr > pThis->iMaxQueueSize) ) {
+ pThis->iMinMsgsPerWrkr = pThis->iMaxQueueSize / pThis->iNumWorkerThreads;
+ }
+
+ if(pThis->iFullDlyMrk == -1 || pThis->iFullDlyMrk > pThis->iMaxQueueSize) {
+ pThis->iFullDlyMrk = (pThis->iMaxQueueSize / 100) * 97;
+ if(pThis->iFullDlyMrk == 0) {
+ pThis->iFullDlyMrk =
+ (pThis->iMaxQueueSize == 1) ? 1 : pThis->iMaxQueueSize - 1;
+ }
+ }
+
+ if(pThis->iLightDlyMrk == 0) {
+ pThis->iLightDlyMrk = pThis->iMaxQueueSize;
+ }
+
+ if(pThis->iLightDlyMrk == -1 || pThis->iLightDlyMrk > pThis->iMaxQueueSize) {
+ pThis->iLightDlyMrk = (pThis->iMaxQueueSize / 100) * 70;
+ if(pThis->iLightDlyMrk == 0) {
+ pThis->iLightDlyMrk =
+ (pThis->iMaxQueueSize == 1) ? 1 : pThis->iMaxQueueSize - 1;
+ }
+ }
+
+ if(pThis->iDiscardMrk < 1 || pThis->iDiscardMrk > pThis->iMaxQueueSize) {
+ pThis->iDiscardMrk = (pThis->iMaxQueueSize / 100) * 98;
+ if(pThis->iDiscardMrk == 0) {
+ /* for very small queues, we disable this by default */
+ pThis->iDiscardMrk = pThis->iMaxQueueSize;
+ }
+ }
+
+ if(pThis->iMaxQueueSize > 0 && pThis->iDeqBatchSize > pThis->iMaxQueueSize) {
+ pThis->iDeqBatchSize = pThis->iMaxQueueSize;
+ }
+}
+
+/* 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;
+ int n_params_set = 0;
+ DEFiRet;
+
+ pvals = nvlstGetParams(lst, &pblk, NULL);
+ if(pvals == NULL) {
+ parser_errmsg("error processing queue config parameters");
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+ if(Debug) {
+ dbgprintf("queue param blk:\n");
+ cnfparamsPrint(&pblk, pvals);
+ }
+ for(i = 0 ; i < pblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ n_params_set++;
+ 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.spooldirectory")) {
+ free(pThis->pszSpoolDir);
+ pThis->pszSpoolDir = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ pThis->lenSpoolDir = es_strlen(pvals[i].val.d.estr);
+ if(pThis->pszSpoolDir[pThis->lenSpoolDir-1] == '/') {
+ pThis->pszSpoolDir[pThis->lenSpoolDir-1] = '\0';
+ --pThis->lenSpoolDir;
+ parser_errmsg("queue.spooldirectory must not end with '/', "
+ "corrected to '%s'", pThis->pszSpoolDir);
+ }
+ } else if(!strcmp(pblk.descr[i].name, "queue.size")) {
+ if(pvals[i].val.d.n > 0x7fffffff) {
+ parser_warnmsg("queue.size higher than maximum (2147483647) - "
+ "corrected to maximum");
+ pvals[i].val.d.n = 0x7fffffff;
+ } else if(pvals[i].val.d.n > OVERSIZE_QUEUE_WATERMARK) {
+ parser_warnmsg("queue.size=%d is very large - is this "
+ "really intended? More info at "
+ "https://www.rsyslog.com/avoid-overly-large-in-memory-queues/",
+ (int) pvals[i].val.d.n);
+ }
+ 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.mindequeuebatchsize")) {
+ pThis->iMinDeqBatchSize = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.mindequeuebatchsize.timeout")) {
+ pThis->toMinDeqBatchSize = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.maxdiskspace")) {
+ pThis->sizeOnDiskMax = 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;
+ if(pThis->qType == QUEUETYPE_DIRECT) {
+ /* if we have a direct queue, we mimic this param was not set.
+ * Our prime intent is to make sure we detect when "real" params
+ * are set on a direct queue, and the type setting is obviously
+ * not relevant here.
+ */
+ n_params_set--;
+ }
+ } 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, "queue.dequeuetimeend")) {
+ pThis->iDeqtWinToHr = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.samplinginterval")) {
+ pThis->iSmpInterval = pvals[i].val.d.n;
+ } else if(!strcmp(pblk.descr[i].name, "queue.takeflowctlfrommsg")) {
+ pThis->takeFlowCtlFromMsg = pvals[i].val.d.n;
+ } else {
+ DBGPRINTF("queue: program error, non-handled "
+ "param '%s'\n", pblk.descr[i].name);
+ }
+ }
+
+ checkUniqueDiskFile(pThis);
+
+ if(pThis->qType == QUEUETYPE_DIRECT) {
+ if(n_params_set > 0) {
+ LogMsg(0, RS_RET_OK, LOG_WARNING, "warning on queue '%s': "
+ "queue is in direct mode, but parameters have been set. "
+ "These PARAMETERS cannot be applied and WILL BE IGNORED.",
+ obj.GetName((obj_t*) pThis));
+ }
+ } else if(pThis->qType == QUEUETYPE_DISK) {
+ if(pThis->pszFilePrefix == NULL) {
+ 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) {
+ 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);
+finalize_it:
+ RETiRet;
+}
+
+/* return 1 if the content of two qqueue_t structs equal */
+int
+queuesEqual(qqueue_t *pOld, qqueue_t *pNew)
+{
+ return (
+ NUM_EQUALS(qType) &&
+ NUM_EQUALS(iMaxQueueSize) &&
+ NUM_EQUALS(iDeqBatchSize) &&
+ NUM_EQUALS(iMinDeqBatchSize) &&
+ NUM_EQUALS(toMinDeqBatchSize) &&
+ NUM_EQUALS(sizeOnDiskMax) &&
+ NUM_EQUALS(iHighWtrMrk) &&
+ NUM_EQUALS(iLowWtrMrk) &&
+ NUM_EQUALS(iFullDlyMrk) &&
+ NUM_EQUALS(iLightDlyMrk) &&
+ NUM_EQUALS(iDiscardMrk) &&
+ NUM_EQUALS(iDiscardSeverity) &&
+ NUM_EQUALS(iPersistUpdCnt) &&
+ NUM_EQUALS(bSyncQueueFiles) &&
+ NUM_EQUALS(iNumWorkerThreads) &&
+ NUM_EQUALS(toQShutdown) &&
+ NUM_EQUALS(toActShutdown) &&
+ NUM_EQUALS(toEnq) &&
+ NUM_EQUALS(toWrkShutdown) &&
+ NUM_EQUALS(iMinMsgsPerWrkr) &&
+ NUM_EQUALS(iMaxFileSize) &&
+ NUM_EQUALS(bSaveOnShutdown) &&
+ NUM_EQUALS(iDeqSlowdown) &&
+ NUM_EQUALS(iDeqtWinFromHr) &&
+ NUM_EQUALS(iDeqtWinToHr) &&
+ NUM_EQUALS(iSmpInterval) &&
+ NUM_EQUALS(takeFlowCtlFromMsg) &&
+ USTR_EQUALS(pszFilePrefix) &&
+ USTR_EQUALS(cryprovName)
+ );
+}
+
+
+/* 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, iDiscardSeverity, int)
+DEFpropSetMeth(qqueue, iLightDlyMrk, int)
+DEFpropSetMeth(qqueue, iNumWorkerThreads, int)
+DEFpropSetMeth(qqueue, iMinMsgsPerWrkr, int)
+DEFpropSetMeth(qqueue, bSaveOnShutdown, int)
+DEFpropSetMeth(qqueue, pAction, action_t*)
+DEFpropSetMeth(qqueue, iDeqSlowdown, int)
+DEFpropSetMeth(qqueue, iDeqBatchSize, int)
+DEFpropSetMeth(qqueue, iMinDeqBatchSize, int)
+DEFpropSetMeth(qqueue, sizeOnDiskMax, int64)
+DEFpropSetMeth(qqueue, iSmpInterval, int)
+
+
+/* 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;
+# ifdef ENABLE_IMDIAG
+ iOverallQueueSize += pThis->iQueueSize;
+# endif
+ } 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 */
+static rsRetVal qqueueQueryInterface(interface_t __attribute__((unused)) *i) { 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(statsobj, CORE_COMPONENT));
+
+ /* now set our own handlers */
+ OBJSetMethodHandler(objMethod_SETPROPERTY, qqueueSetProperty);
+ENDObjClassInit(qqueue)
diff --git a/runtime/queue.h b/runtime/queue.h
new file mode 100644
index 0000000..dd989bd
--- /dev/null
+++ b/runtime/queue.h
@@ -0,0 +1,247 @@
+/* Definition of the queue support module.
+ *
+ * Copyright 2008-2019 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;
+ smsg_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 */
+ sbool takeFlowCtlFromMsg;/* override enq flow ctl by message property? */
+ 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 */
+ int iMinDeqBatchSize;/* min number of elements that shall be dequeued at once */
+ int toMinDeqBatchSize;/* timeout for MinDeqBatchSize, in ms */
+ /* 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*, wti_t*); /* 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)
+ */
+ /* type-specific handlers (set during construction) */
+ rsRetVal (*qConstruct)(struct queue_s *pThis);
+ rsRetVal (*qDestruct)(struct queue_s *pThis);
+ rsRetVal (*qAdd)(struct queue_s *pThis, smsg_t *pMsg);
+ rsRetVal (*qDeq)(struct queue_s *pThis, smsg_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;
+ 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 */
+ int nForcePersist;/* force persist of .qi file the next "n" times */
+ } 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 */
+ int iSmpInterval; /* line interval of sampling logs */
+ int isRunning;
+};
+
+
+/* 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 qqueueEnqMsg(qqueue_t *pThis, flowControl_t flwCtlType, smsg_t *pMsg);
+rsRetVal qqueueStart(rsconf_t *cnf, 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*, wti_t *));
+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);
+rsRetVal qqueueShutdownWorkers(qqueue_t *pThis);
+void qqueueDoneLoadCnf(void);
+int queuesEqual(qqueue_t *pOld, qqueue_t *pNew);
+void qqueueCorrectParams(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, iNumWorkerThreads, 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)
+
+#ifdef ENABLE_IMDIAG
+extern unsigned int iOverallQueueSize;
+#endif
+
+#endif /* #ifndef QUEUE_H_INCLUDED */
diff --git a/runtime/ratelimit.c b/runtime/ratelimit.c
new file mode 100644
index 0000000..1669962
--- /dev/null
+++ b/runtime/ratelimit.c
@@ -0,0 +1,436 @@
+/* ratelimit.c
+ * support for rate-limiting sources, including "last message
+ * repeated n times" processing.
+ *
+ * Copyright 2012-2020 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(glbl)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(parser)
+
+/* static data */
+
+/* generate a "repeated n times" message */
+static smsg_t *
+ratelimitGenRepMsg(ratelimit_t *ratelimit)
+{
+ smsg_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 rsRetVal
+doLastMessageRepeatedNTimes(ratelimit_t *ratelimit, smsg_t *pMsg, smsg_t **ppRepMsg)
+{
+ int bNeedUnlockMutex = 0;
+ DEFiRet;
+
+ 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 void
+tellLostCnt(ratelimit_t *ratelimit)
+{
+ uchar msgbuf[1024];
+ if(ratelimit->missed) {
+ snprintf((char*)msgbuf, sizeof(msgbuf),
+ "%s: %u messages lost due to rate-limiting (%u allowed within %u seconds)",
+ ratelimit->name, ratelimit->missed, ratelimit->burst, ratelimit->interval);
+ 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 int ATTR_NONNULL()
+withinRatelimit(ratelimit_t *__restrict__ const ratelimit,
+ time_t tt,
+ const char*const appname)
+{
+ int ret;
+ uchar msgbuf[1024];
+
+ if(ratelimit->bThreadSafe) {
+ pthread_mutex_lock(&ratelimit->mut);
+ }
+
+ 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 or if time has gone backwards */
+ if((tt > (time_t)(ratelimit->begin + ratelimit->interval)) || (tt < ratelimit->begin) ) {
+ 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 from <%s>: begin to drop messages due to rate-limiting",
+ ratelimit->name, appname);
+ logmsgInternal(RS_RET_RATE_LIMITED, LOG_SYSLOG|LOG_INFO, msgbuf, 0);
+ }
+ ret = 0;
+ }
+
+finalize_it:
+ if(ratelimit->bThreadSafe) {
+ pthread_mutex_unlock(&ratelimit->mut);
+ }
+ return ret;
+}
+
+/* ratelimit a message based on message count
+ * - handles only 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.
+ */
+rsRetVal
+ratelimitMsgCount(ratelimit_t *__restrict__ const ratelimit,
+ time_t tt,
+ const char* const appname)
+{
+ DEFiRet;
+ if(ratelimit->interval) {
+ if(withinRatelimit(ratelimit, tt, appname) == 0) {
+ ABORT_FINALIZE(RS_RET_DISCARDMSG);
+ }
+ }
+finalize_it:
+ if(Debug) {
+ if(iRet == RS_RET_DISCARDMSG)
+ DBGPRINTF("message discarded by ratelimiting\n");
+ }
+ RETiRet;
+}
+
+/* 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 *__restrict__ const ratelimit, smsg_t *pMsg, smsg_t **ppRepMsg)
+{
+ DEFiRet;
+ rsRetVal localRet;
+ int severity = 0;
+
+ *ppRepMsg = NULL;
+
+ if(runConf->globals.bReduceRepeatMsgs || ratelimit->severity > 0) {
+ /* consider early parsing only if really needed */
+ 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);
+ }
+ }
+ severity = pMsg->iSeverity;
+ }
+
+ /* Only the messages having severity level at or below the
+ * treshold (the value is >=) are subject to ratelimiting. */
+ if(ratelimit->interval && (severity >= ratelimit->severity)) {
+ char namebuf[512]; /* 256 for FGDN adn 256 for APPNAME should be enough */
+ snprintf(namebuf, sizeof namebuf, "%s:%s", getHOSTNAME(pMsg),
+ getAPPNAME(pMsg, 0));
+ if(withinRatelimit(ratelimit, pMsg->ttGenTime, namebuf) == 0) {
+ msgDestruct(&pMsg);
+ ABORT_FINALIZE(RS_RET_DISCARDMSG);
+ }
+ }
+ if(runConf->globals.bReduceRepeatMsgs) {
+ CHKiRet(doLastMessageRepeatedNTimes(ratelimit, pMsg, ppRepMsg));
+ }
+finalize_it:
+ if(Debug) {
+ if(iRet == RS_RET_DISCARDMSG)
+ DBGPRINTF("message discarded by ratelimiting\n");
+ }
+ RETiRet;
+}
+
+/* returns 1, if the ratelimiter performs any checks and 0 otherwise */
+int
+ratelimitChecked(ratelimit_t *ratelimit)
+{
+ return ratelimit->interval || runConf->globals.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, smsg_t *pMsg)
+{
+ rsRetVal localRet;
+ smsg_t *repMsg;
+ DEFiRet;
+
+ localRet = ratelimitMsg(ratelimit, pMsg, &repMsg);
+ if(pMultiSub == NULL) {
+ if(repMsg != NULL)
+ CHKiRet(submitMsg2(repMsg));
+ CHKiRet(localRet);
+ CHKiRet(submitMsg2(pMsg));
+ } else {
+ if(repMsg != NULL) {
+ pMultiSub->ppMsgs[pMultiSub->nElem++] = repMsg;
+ if(pMultiSub->nElem == pMultiSub->maxElem)
+ CHKiRet(multiSubmitMsg2(pMultiSub));
+ }
+ CHKiRet(localRet);
+ if(pMsg->iLenRawMsg > glblGetMaxLine(runConf)) {
+ /* oversize message needs special processing. We keep
+ * at least the previous batch as batch...
+ */
+ if(pMultiSub->nElem > 0) {
+ CHKiRet(multiSubmitMsg2(pMultiSub));
+ }
+ CHKiRet(submitMsg2(pMsg));
+ FINALIZE;
+ }
+ 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, const char *modname, const 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);
+ }
+ DBGPRINTF("ratelimit:%s:new ratelimiter\n", pThis->name);
+ *ppThis = pThis;
+finalize_it:
+ RETiRet;
+}
+
+
+/* enable linux-like ratelimiting */
+void
+ratelimitSetLinuxLike(ratelimit_t *ratelimit, unsigned int interval, unsigned int 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)
+{
+ smsg_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(parser, CORE_COMPONENT);
+}
+
+rsRetVal
+ratelimitModInit(void)
+{
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, 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 0000000..bb45e79
--- /dev/null
+++ b/runtime/ratelimit.h
@@ -0,0 +1,55 @@
+/* header for ratelimit.c
+ *
+ * Copyright 2012-2016 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 int interval;
+ unsigned int 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 */
+ unsigned nsupp; /**< nbr of msgs suppressed */
+ smsg_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, const char *modname, const char *dynname);
+void ratelimitSetThreadSafe(ratelimit_t *ratelimit);
+void ratelimitSetLinuxLike(ratelimit_t *ratelimit, unsigned int interval, unsigned int burst);
+void ratelimitSetNoTimeCache(ratelimit_t *ratelimit);
+void ratelimitSetSeverity(ratelimit_t *ratelimit, intTiny severity);
+rsRetVal ratelimitMsgCount(ratelimit_t *ratelimit, time_t tt, const char* const appname);
+rsRetVal ratelimitMsg(ratelimit_t *ratelimit, smsg_t *pMsg, smsg_t **ppRep);
+rsRetVal ratelimitAddMsg(ratelimit_t *ratelimit, multi_submit_t *pMultiSub, smsg_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 0000000..433c9c2
--- /dev/null
+++ b/runtime/regexp.c
@@ -0,0 +1,384 @@
+/* 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 <pthread.h>
+#include <regex.h>
+#include <string.h>
+#include <stdint.h>
+#include <assert.h>
+
+#include "rsyslog.h"
+#include "module-template.h"
+#include "obj.h"
+#include "regexp.h"
+#include "errmsg.h"
+#include "hashtable.h"
+#include "hashtable_itr.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+
+/* When using glibc, we enable per-thread regex to avoid lock contention.
+ * See:
+ * - https://github.com/rsyslog/rsyslog/issues/2759
+ * - https://github.com/rsyslog/rsyslog/pull/2786
+ * - https://sourceware.org/bugzilla/show_bug.cgi?id=11159
+ *
+ * This should not affect BSD as they don't seem to take a lock in regexec.
+ */
+#ifdef __GLIBC__
+#define USE_PERTHREAD_REGEX 1
+#else
+#define USE_PERTHREAD_REGEX 0
+#endif
+
+static pthread_mutex_t mut_regexp;
+
+// Map a regex_t to its associated uncompiled parameters.
+static struct hashtable *regex_to_uncomp = NULL;
+
+// Map a (regexp_t, pthead_t) to a perthread_regex.
+static struct hashtable *perthread_regexs = NULL;
+
+
+/*
+ * This stores un-compiled regex to allow further
+ * call to regexec to re-compile a new regex dedicated
+ * to the calling thread.
+ */
+typedef struct uncomp_regex {
+ char *regex;
+ int cflags;
+ regex_t *preg;
+} uncomp_regex_t;
+
+/*
+ * This stores a regex dedicated to a single thread.
+ */
+typedef struct perthread_regex {
+ const regex_t *original_preg;
+ regex_t preg;
+ int ret;
+ pthread_mutex_t lock;
+ pthread_t thread;
+} perthread_regex_t;
+
+
+static unsigned __attribute__((nonnull(1))) int hash_from_regex(void *k) {
+ return (uintptr_t)*(regex_t **)k;
+}
+
+static int key_equals_regex(void *key1, void *key2) {
+ return *(regex_t **)key1 == *(regex_t **)key2;
+}
+
+static unsigned __attribute__((nonnull(1))) int hash_from_tregex(void *k) {
+ perthread_regex_t *entry = k;
+ // Cast to (void*) is ok here because already used in other parts of the code.
+ uintptr_t thread_id = (uintptr_t)(void *)entry->thread;
+
+ return thread_id ^ (uintptr_t)entry->original_preg;
+}
+
+static int key_equals_tregex(void *key1, void *key2) {
+ perthread_regex_t *entry1 = key1;
+ perthread_regex_t *entry2 = key2;
+
+ return (pthread_equal(entry1->thread, entry2->thread) &&
+ entry1->original_preg == entry2->original_preg);
+}
+
+
+/* ------------------------------ methods ------------------------------ */
+
+
+// Create a copy of preg to be used by this thread only.
+static perthread_regex_t *create_perthread_regex(const regex_t *preg, uncomp_regex_t *uncomp) {
+ perthread_regex_t *entry = NULL;
+
+ if (Debug) {
+ DBGPRINTF("Creating new regex_t for thread %p original regexp_t %p (pattern: %s, cflags: %x)\n",
+ (void *)pthread_self(), preg,
+ uncomp->regex, uncomp->cflags);
+ }
+ entry = calloc(1, sizeof(*entry));
+ if (!entry)
+ return entry;
+ entry->original_preg = preg;
+ DBGPRINTF("regexp: regcomp %p %p\n", entry, &entry->preg);
+ entry->ret = regcomp(&entry->preg, uncomp->regex, uncomp->cflags);
+ pthread_mutex_init(&entry->lock, NULL);
+ entry->thread = pthread_self();
+ return entry;
+}
+
+// Get (or create) a regex_t to be used by the current thread.
+static perthread_regex_t *get_perthread_regex(const regex_t *preg) {
+ perthread_regex_t *entry = NULL;
+ perthread_regex_t key = { .original_preg = preg, .thread = pthread_self() };
+
+ pthread_mutex_lock(&mut_regexp);
+ entry = hashtable_search(perthread_regexs, (void *)&key);
+ if (!entry) {
+ uncomp_regex_t *uncomp = hashtable_search(regex_to_uncomp, (void *)&preg);
+
+ if (uncomp) {
+ entry = create_perthread_regex(preg, uncomp);
+ if(!hashtable_insert(perthread_regexs, (void *)entry, entry)) {
+ LogError(0, RS_RET_INTERNAL_ERROR,
+ "error trying to insert thread-regexp into hash-table - things "
+ "will not work 100%% correctly (mostly probably out of memory issue)");
+ }
+ }
+ }
+ if (entry) {
+ pthread_mutex_lock(&entry->lock);
+ }
+ pthread_mutex_unlock(&mut_regexp);
+ return entry;
+}
+
+static void remove_uncomp_regexp(regex_t *preg) {
+ uncomp_regex_t *uncomp = NULL;
+
+ pthread_mutex_lock(&mut_regexp);
+ uncomp = hashtable_remove(regex_to_uncomp, (void *)&preg);
+
+ if (uncomp) {
+ if (Debug) {
+ DBGPRINTF("Removing everything linked to regexp_t %p (pattern: %s, cflags: %x)\n",
+ preg, uncomp->regex, uncomp->cflags);
+ }
+ free(uncomp->regex);
+ free(uncomp);
+ }
+ pthread_mutex_unlock(&mut_regexp);
+}
+
+static void _regfree(regex_t *preg) {
+ int ret = 0;
+ struct hashtable_itr *itr = NULL;
+
+ if (!preg)
+ return;
+
+ regfree(preg);
+ remove_uncomp_regexp(preg);
+
+ pthread_mutex_lock(&mut_regexp);
+ if (!hashtable_count(perthread_regexs)) {
+ pthread_mutex_unlock(&mut_regexp);
+ return;
+ }
+
+ // This can be long to iterate other all regexps, but regfree doesn't get called
+ // a lot during processing.
+ itr = hashtable_iterator(perthread_regexs);
+ do {
+ perthread_regex_t *entry = (perthread_regex_t *)hashtable_iterator_value(itr);
+
+ // Do it before freeing the entry.
+ ret = hashtable_iterator_advance(itr);
+
+ if (entry->original_preg == preg) {
+ // This allows us to avoid freeing this while somebody is still using it.
+ pthread_mutex_lock(&entry->lock);
+ // We can unlock immediately after because mut_regexp is locked.
+ pthread_mutex_unlock(&entry->lock);
+ pthread_mutex_destroy(&entry->lock);
+ regfree(&entry->preg);
+
+ // Do it last because it will free entry.
+ hashtable_remove(perthread_regexs, (void *)entry);
+ }
+ } while (ret);
+ free(itr);
+
+ pthread_mutex_unlock(&mut_regexp);
+}
+
+static int _regcomp(regex_t *preg, const char *regex, int cflags) {
+ int ret = 0;
+ regex_t **ppreg = NULL;
+ uncomp_regex_t *uncomp;
+
+ // Remove previous data if caller forgot to call regfree().
+ remove_uncomp_regexp(preg);
+
+ // Make sure preg itself it correctly initalized.
+ ret = regcomp(preg, regex, cflags);
+ if (ret != 0)
+ return ret;
+
+ uncomp = calloc(1, sizeof(*uncomp));
+ if (!uncomp)
+ return REG_ESPACE;
+
+ uncomp->preg = preg;
+ uncomp->regex = strdup(regex);
+ uncomp->cflags = cflags;
+ pthread_mutex_lock(&mut_regexp);
+
+ // We need to allocate the key because hashtable will free it on remove.
+ ppreg = malloc(sizeof(regex_t *));
+ *ppreg = preg;
+ ret = hashtable_insert(regex_to_uncomp, (void *)ppreg, uncomp);
+ pthread_mutex_unlock(&mut_regexp);
+ if (ret == 0) {
+ free(uncomp->regex);
+ free(uncomp);
+ return REG_ESPACE;
+ }
+
+ perthread_regex_t *entry = get_perthread_regex(preg);
+ if (entry) {
+ ret = entry->ret;
+ pthread_mutex_unlock(&entry->lock);
+ } else {
+ ret = REG_ESPACE;
+ }
+ return ret;
+}
+
+static int _regexec(const regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags) {
+ perthread_regex_t *entry = get_perthread_regex(preg);
+ int ret = REG_NOMATCH;
+ if(entry != NULL) {
+ ret = regexec(&entry->preg, string, nmatch, pmatch, eflags);
+ pthread_mutex_unlock(&entry->lock);
+ }
+ return ret;
+}
+
+static size_t _regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size) {
+ perthread_regex_t *entry = get_perthread_regex(preg);
+
+ if (entry)
+ preg = &entry->preg;
+
+ size_t ret = regerror(errcode, preg, errbuf, errbuf_size);
+
+ if (entry)
+ pthread_mutex_unlock(&entry->lock);
+
+ return ret;
+}
+
+/* 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).
+ */
+ if (USE_PERTHREAD_REGEX) {
+ pIf->regcomp = _regcomp;
+ pIf->regexec = _regexec;
+ pIf->regerror = _regerror;
+ pIf->regfree = _regfree;
+ } else {
+ 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 */
+
+ if (USE_PERTHREAD_REGEX) {
+ pthread_mutex_init(&mut_regexp, NULL);
+
+ regex_to_uncomp = create_hashtable(100, hash_from_regex, key_equals_regex, NULL);
+ perthread_regexs = create_hashtable(100, hash_from_tregex, key_equals_tregex, NULL);
+ if(regex_to_uncomp == NULL || perthread_regexs == NULL) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "error trying to initialize hash-table "
+ "for regexp table. regexp will be disabled.");
+ if (regex_to_uncomp) hashtable_destroy(regex_to_uncomp, 1);
+ if (perthread_regexs) hashtable_destroy(perthread_regexs, 1);
+ regex_to_uncomp = NULL;
+ perthread_regexs = NULL;
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+ }
+
+ENDObjClassInit(regexp)
+
+
+/* Exit the class.
+ */
+BEGINObjClassExit(regexp, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ if (USE_PERTHREAD_REGEX) {
+ /* release objects we no longer need */
+ pthread_mutex_destroy(&mut_regexp);
+ if (regex_to_uncomp)
+ hashtable_destroy(regex_to_uncomp, 1);
+ if (perthread_regexs)
+ hashtable_destroy(perthread_regexs, 1);
+ }
+ENDObjClassExit(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 0000000..ec7c72c
--- /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 0000000..27047a5
--- /dev/null
+++ b/runtime/rsconf.c
@@ -0,0 +1,1601 @@
+/* rsconf.c - the rsyslog configuration system.
+ *
+ * Module begun 2011-04-19 by Rainer Gerhards
+ *
+ * Copyright 2011-2023 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 <pwd.h>
+#include <grp.h>
+#include <stdarg.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.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"
+#include "timezones.h"
+
+extern char* yytext;
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(ruleset)
+DEFobjCurrIf(module)
+DEFobjCurrIf(conf)
+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$.:%$.%\n$/:%$/%\n\n\"";
+static uchar template_SyslogProtocol23Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% "
+"%PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n\"";
+static uchar template_SyslogRFC5424Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% "
+"%PROCID% %MSGID% %STRUCTURED-DATA% %msg%\"";
+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%\\\"}\"";
+static uchar template_FullJSONFmt[] = "\"{\\\"message\\\":\\\"%msg:::json%\\\","
+"\\\"fromhost\\\":\\\"%HOSTNAME:::json%\\\","
+"\\\"programname\\\":\\\"%programname%\\\","
+"\\\"procid\\\":\\\"%PROCID%\\\","
+"\\\"msgid\\\":\\\"%MSGID%\\\","
+"\\\"facility\\\":\\\"%syslogfacility-text%\\\","
+"\\\"priority\\\":\\\"%syslogpriority-text%\\\","
+"\\\"timereported\\\":\\\"%timereported:::date-rfc3339%\\\","
+"\\\"timegenerated\\\":\\\"%timegenerated:::date-rfc3339%\\\"}\"";
+static uchar template_StdClickHouseFmt[] = "\"INSERT INTO rsyslog.SystemEvents (severity, facility, "
+"timestamp, hostname, tag, message) VALUES (%syslogseverity%, %syslogfacility%, "
+"'%timereported:::date-unixtimestamp%', '%hostname%', '%syslogtag%', '%msg%')\",STDSQL";
+/* 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
+ };
+
+static struct cnfparamdescr parserpdescr[] = {
+ { "type", eCmdHdlrString, CNFPARAM_REQUIRED },
+ { "name", eCmdHdlrString, CNFPARAM_REQUIRED }
+};
+static struct cnfparamblk parserpblk =
+ { CNFPARAMBLK_VERSION,
+ sizeof(parserpdescr)/sizeof(struct cnfparamdescr),
+ parserpdescr
+ };
+
+/* forward-definitions */
+void cnfDoCfsysline(char *ln);
+
+int rsconfNeedDropPriv(rsconf_t *const cnf)
+{
+ return ((cnf->globals.gidDropPriv != 0) || (cnf->globals.uidDropPriv != 0));
+}
+
+static void cnfSetDefaults(rsconf_t *pThis)
+{
+#ifdef ENABLE_LIBCAPNG
+ pThis->globals.bAbortOnFailedLibcapngSetup = 1;
+ pThis->globals.bCapabilityDropEnabled = 1;
+#endif
+ pThis->globals.bAbortOnUncleanConfig = 0;
+ pThis->globals.bAbortOnFailedQueueStartup = 0;
+ pThis->globals.bReduceRepeatMsgs = 0;
+ pThis->globals.bDebugPrintTemplateList = 1;
+ pThis->globals.bDebugPrintModuleList = 0;
+ pThis->globals.bDebugPrintCfSysLineHandlerList = 0;
+ pThis->globals.bLogStatusMsgs = DFLT_bLogStatusMsgs;
+ pThis->globals.bErrMsgToStderr = 1;
+ pThis->globals.maxErrMsgToStderr = -1;
+ pThis->globals.umask = -1;
+ pThis->globals.gidDropPrivKeepSupplemental = 0;
+ pThis->globals.abortOnIDResolutionFail = 1;
+ pThis->templates.root = NULL;
+ pThis->templates.last = NULL;
+ pThis->templates.lastStatic = NULL;
+ pThis->actions.nbrActions = 0;
+ pThis->actions.iActionNbr = 0;
+ pThis->globals.pszWorkDir = NULL;
+ pThis->globals.bDropMalPTRMsgs = 0;
+ pThis->globals.operatingStateFile = NULL;
+ pThis->globals.iGnuTLSLoglevel = 0;
+ pThis->globals.debugOnShutdown = 0;
+ pThis->globals.pszDfltNetstrmDrvrCAF = NULL;
+ pThis->globals.pszDfltNetstrmDrvrCRLF = NULL;
+ pThis->globals.pszDfltNetstrmDrvrCertFile = NULL;
+ pThis->globals.pszDfltNetstrmDrvrKeyFile = NULL;
+ pThis->globals.pszDfltNetstrmDrvr = NULL;
+ pThis->globals.oversizeMsgErrorFile = NULL;
+ pThis->globals.reportOversizeMsg = 1;
+ pThis->globals.oversizeMsgInputMode = 0;
+ pThis->globals.reportChildProcessExits = REPORT_CHILD_PROCESS_EXITS_ERRORS;
+ pThis->globals.bActionReportSuspension = 1;
+ pThis->globals.bActionReportSuspensionCont = 0;
+ pThis->globals.janitorInterval = 10;
+ pThis->globals.reportNewSenders = 0;
+ pThis->globals.reportGoneAwaySenders = 0;
+ pThis->globals.senderStatsTimeout = 12 * 60 * 60; /* 12 hr timeout for senders */
+ pThis->globals.senderKeepTrack = 0;
+ pThis->globals.inputTimeoutShutdown = 1000;
+ pThis->globals.iDefPFFamily = PF_UNSPEC;
+ pThis->globals.ACLAddHostnameOnFail = 0;
+ pThis->globals.ACLDontResolve = 0;
+ pThis->globals.bDisableDNS = 0;
+ pThis->globals.bProcessInternalMessages = 0;
+ const char *const log_dflt = getenv("RSYSLOG_DFLT_LOG_INTERNAL");
+ if(log_dflt != NULL && !strcmp(log_dflt, "1"))
+ pThis->globals.bProcessInternalMessages = 1;
+ pThis->globals.glblDevOptions = 0;
+ pThis->globals.intMsgRateLimitItv = 5;
+ pThis->globals.intMsgRateLimitBurst = 500;
+ pThis->globals.intMsgsSeverityFilter = DFLT_INT_MSGS_SEV_FILTER;
+ pThis->globals.permitCtlC = glblPermitCtlC;
+
+ pThis->globals.actq_dflt_toQShutdown = 10;
+ pThis->globals.actq_dflt_toActShutdown = 1000;
+ pThis->globals.actq_dflt_toEnq = 2000;
+ pThis->globals.actq_dflt_toWrkShutdown = 60000;
+
+ pThis->globals.ruleset_dflt_toQShutdown = 1500;
+ pThis->globals.ruleset_dflt_toActShutdown = 1000;
+ pThis->globals.ruleset_dflt_toEnq = 2000;
+ pThis->globals.ruleset_dflt_toWrkShutdown = 60000;
+
+ pThis->globals.dnscacheDefaultTTL = 24 * 60 * 60;
+ pThis->globals.dnscacheEnableTTL = 0;
+ pThis->globals.shutdownQueueDoubleSize = 0;
+ pThis->globals.optionDisallowWarning = 1;
+ pThis->globals.bSupportCompressionExtension = 1;
+ #ifdef ENABLE_LIBLOGGING_STDLOG
+ pThis->globals.stdlog_hdl = stdlog_open("rsyslogd", 0, STDLOG_SYSLOG, NULL);
+ pThis->globals.stdlog_chanspec = NULL;
+ #endif
+ pThis->globals.iMaxLine = 8096;
+
+ /* timezone specific*/
+ pThis->timezones.tzinfos = NULL;
+ pThis->timezones.ntzinfos = 0;
+
+ /* queue params */
+ pThis->globals.mainQ.iMainMsgQueueSize = 100000;
+ pThis->globals.mainQ.iMainMsgQHighWtrMark = 80000;
+ pThis->globals.mainQ.iMainMsgQLowWtrMark = 20000;
+ pThis->globals.mainQ.iMainMsgQDiscardMark = 98000;
+ pThis->globals.mainQ.iMainMsgQDiscardSeverity = 8;
+ pThis->globals.mainQ.iMainMsgQueueNumWorkers = 2;
+ 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 = 40000;
+ pThis->globals.mainQ.iMainMsgQDeqSlowdown = 0;
+ pThis->globals.mainQ.iMainMsgQueMaxDiskSpace = 0;
+ pThis->globals.mainQ.iMainMsgQueDeqBatchSize = 256;
+ pThis->globals.mainQ.bMainMsgQSaveOnShutdown = 1;
+ pThis->globals.mainQ.iMainMsgQueueDeqtWinFromHr = 0;
+ pThis->globals.mainQ.iMainMsgQueueDeqtWinToHr = 25;
+ pThis->pMsgQueue = NULL;
+
+ pThis->globals.parser.cCCEscapeChar = '#';
+ pThis->globals.parser.bDropTrailingLF = 1;
+ pThis->globals.parser.bEscapeCCOnRcv = 1;
+ pThis->globals.parser.bSpaceLFOnRcv = 0;
+ pThis->globals.parser.bEscape8BitChars = 0;
+ pThis->globals.parser.bEscapeTab = 1;
+ pThis->globals.parser.bParserEscapeCCCStyle = 0;
+ pThis->globals.parser.bPermitSlashInProgramname = 0;
+ pThis->globals.parser.bParseHOSTNAMEandTAG = 1;
+
+ pThis->parsers.pDfltParsLst = NULL;
+ pThis->parsers.pParsLstRoot = NULL;
+}
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(rsconf) /* be sure to specify the object type also in END macro! */
+ cnfSetDefaults(pThis);
+ lookupInitCnf(&pThis->lu_tabs);
+ CHKiRet(dynstats_initCnf(&pThis->dynstats_buckets));
+ CHKiRet(perctile_initCnf(&pThis->perctile_buckets));
+ CHKiRet(llInit(&pThis->rulesets.llRulesets, rulesetDestructForLinkedList,
+ rulesetKeyDestruct, strcasecmp));
+finalize_it:
+ENDobjConstruct(rsconf)
+
+
+/* ConstructionFinalizer
+ */
+static 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 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 */
+PROTOTYPEobjDestruct(rsconf);
+BEGINobjDestruct(rsconf) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(rsconf)
+ freeCnf(pThis);
+ tplDeleteAll(pThis);
+ dynstats_destroyAllBuckets();
+ perctileBucketsDestruct();
+ ochDeleteAll();
+ freeTimezones(pThis);
+ parser.DestructParserList(&pThis->parsers.pDfltParsLst);
+ parser.destroyMasterParserList(pThis->parsers.pParsLstRoot);
+ free(pThis->globals.mainQ.pszMainMsgQFName);
+ free(pThis->globals.pszConfDAGFile);
+ free(pThis->globals.pszWorkDir);
+ free(pThis->globals.operatingStateFile);
+ free(pThis->globals.pszDfltNetstrmDrvrCAF);
+ free(pThis->globals.pszDfltNetstrmDrvrCRLF);
+ free(pThis->globals.pszDfltNetstrmDrvrCertFile);
+ free(pThis->globals.pszDfltNetstrmDrvrKeyFile);
+ free(pThis->globals.pszDfltNetstrmDrvr);
+ free(pThis->globals.oversizeMsgErrorFile);
+ #ifdef ENABLE_LIBLOGGING_STDLOG
+ stdlog_close(pThis->globals.stdlog_hdl);
+ free(pThis->globals.stdlog_chanspec);
+ #endif
+ lookupDestroyCnf();
+ llDestroy(&(pThis->rulesets.llRulesets));
+ENDobjDestruct(rsconf)
+
+
+/* DebugPrint support for the rsconf object */
+PROTOTYPEObjDebugPrint(rsconf);
+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(pThis));
+ 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(pThis));
+ ochPrintList(pThis);
+ 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)
+
+
+static rsRetVal
+parserProcessCnf(struct cnfobj *o)
+{
+ struct cnfparamvals *pvals;
+ modInfo_t *pMod;
+ uchar *cnfModName = NULL;
+ uchar *parserName = NULL;
+ int paramIdx;
+ void *parserInst;
+ parser_t *myparser;
+ DEFiRet;
+
+ pvals = nvlstGetParams(o->nvlst, &parserpblk, NULL);
+ if(pvals == NULL) {
+ ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
+ }
+ DBGPRINTF("input param blk after parserProcessCnf:\n");
+ cnfparamsPrint(&parserpblk, pvals);
+ paramIdx = cnfparamGetIdx(&parserpblk, "name");
+ parserName = (uchar*)es_str2cstr(pvals[paramIdx].val.d.estr, NULL);
+ if(parser.FindParser(loadConf->parsers.pParsLstRoot, &myparser, parserName) != RS_RET_PARSER_NOT_FOUND) {
+ LogError(0, RS_RET_PARSER_NAME_EXISTS,
+ "parser module name '%s' already exists", parserName);
+ ABORT_FINALIZE(RS_RET_PARSER_NAME_EXISTS);
+ }
+
+ paramIdx = cnfparamGetIdx(&parserpblk, "type");
+ cnfModName = (uchar*)es_str2cstr(pvals[paramIdx].val.d.estr, NULL);
+ if((pMod = module.FindWithCnfName(loadConf, cnfModName, eMOD_PARSER)) == NULL) {
+ LogError(0, RS_RET_MOD_UNKNOWN, "parser module name '%s' is unknown", cnfModName);
+ ABORT_FINALIZE(RS_RET_MOD_UNKNOWN);
+ }
+ if(pMod->mod.pm.newParserInst == NULL) {
+ LogError(0, RS_RET_MOD_NO_PARSER_STMT,
+ "parser module '%s' does not support parser() statement", cnfModName);
+ ABORT_FINALIZE(RS_RET_MOD_NO_INPUT_STMT);
+ }
+ CHKiRet(pMod->mod.pm.newParserInst(o->nvlst, &parserInst));
+
+ /* all well, so let's (try) to add parser to config */
+ CHKiRet(parserConstructViaModAndName(pMod, parserName, parserInst));
+finalize_it:
+ free(cnfModName);
+ free(parserName);
+ cnfparamvalsDestruct(pvals, &parserpblk);
+ RETiRet;
+}
+
+
+/* Process input() objects */
+static 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) {
+ 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) {
+ 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);
+ }
+ iRet = pMod->mod.im.newInpInst(o->nvlst);
+finalize_it:
+ free(cnfModName);
+ cnfparamvalsDestruct(pvals, &inppblk);
+ RETiRet;
+}
+
+/*------------------------------ interface to flex/bison parser ------------------------------*/
+extern int yylineno;
+
+void
+parser_warnmsg(const 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';
+ LogMsg(0, RS_RET_CONF_PARSE_WARNING, LOG_WARNING,
+ "warning during parsing file %s, on or before line %d: %s",
+ cnfcurrfn, yylineno, errBuf);
+ va_end(ap);
+}
+
+void
+parser_errmsg(const 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';
+ if(cnfcurrfn == NULL) {
+ LogError(0, RS_RET_CONF_PARSE_ERROR,
+ "error during config processing: %s", errBuf);
+ } else {
+ 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(const char *s); /* we need this prototype to make compiler happy */
+int
+yyerror(const char *s)
+{
+ parser_errmsg("%s on token '%s'", s, yytext);
+ return 0;
+}
+void ATTR_NONNULL()
+cnfDoObj(struct cnfobj *const o)
+{
+ int bDestructObj = 1;
+ int bChkUnuse = 1;
+ assert(o != NULL);
+
+ dbgprintf("cnf:global:obj: ");
+ cnfobjPrint(o);
+
+ /* We need to check for object disabling as early as here to cover most
+ * of them at once and avoid needless initializations
+ * - jvymazal 2020-02-12
+ */
+ if (nvlstChkDisabled(o->nvlst)) {
+ dbgprintf("object disabled by configuration\n");
+ return;
+ }
+
+ switch(o->objType) {
+ case CNFOBJ_GLOBAL:
+ glblProcessCnf(o);
+ break;
+ case CNFOBJ_TIMEZONE:
+ glblProcessTimezone(o);
+ break;
+ case CNFOBJ_MAINQ:
+ glblProcessMainQCnf(o);
+ bDestructObj = 0;
+ break;
+ case CNFOBJ_MODULE:
+ modulesProcessCnf(o);
+ break;
+ case CNFOBJ_INPUT:
+ inputProcessCnf(o);
+ break;
+ case CNFOBJ_LOOKUP_TABLE:
+ lookupTableDefProcessCnf(o);
+ break;
+ case CNFOBJ_DYN_STATS:
+ dynstats_processCnf(o);
+ break;
+ case CNFOBJ_PERCTILE_STATS:
+ perctile_processCnf(o);
+ break;
+ case CNFOBJ_PARSER:
+ parserProcessCnf(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;
+ case CNFOBJ_ACTION:
+ default:
+ dbgprintf("cnfDoObj program error: unexpected object type %u\n",
+ o->objType);
+ break;
+ }
+ if(bDestructObj) {
+ 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);
+ LogError(0, RS_RET_BSD_BLOCKS_UNSUPPORTED,
+ "BSD-style blocks are no longer supported in rsyslog, "
+ "see https://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);
+ LogError(0, RS_RET_BSD_BLOCKS_UNSUPPORTED,
+ "BSD-style blocks are no longer supported in rsyslog, "
+ "see https://www.rsyslog.com/g/BSD for details and a "
+ "solution (Block '%s')", ln);
+ free(ln);
+}
+/*------------------------------ end interface to flex/bison parser ------------------------------*/
+
+
+
+/* drop to specified group
+ * if something goes wrong, the function never returns
+ */
+static
+rsRetVal doDropPrivGid(rsconf_t *cnf)
+{
+ int res;
+ uchar szBuf[1024];
+ DEFiRet;
+
+ if(!cnf->globals.gidDropPrivKeepSupplemental) {
+ res = setgroups(0, NULL); /* remove all supplemental group IDs */
+ if(res) {
+ LogError(errno, RS_RET_ERR_DROP_PRIV,
+ "could not remove supplemental group IDs");
+ ABORT_FINALIZE(RS_RET_ERR_DROP_PRIV);
+ }
+ DBGPRINTF("setgroups(0, NULL): %d\n", res);
+ }
+ res = setgid(cnf->globals.gidDropPriv);
+ if(res) {
+ LogError(errno, RS_RET_ERR_DROP_PRIV,
+ "could not set requested group id %d via setgid()", cnf->globals.gidDropPriv);
+ ABORT_FINALIZE(RS_RET_ERR_DROP_PRIV);
+ }
+
+ DBGPRINTF("setgid(%d): %d\n", cnf->globals.gidDropPriv, res);
+ snprintf((char*)szBuf, sizeof(szBuf), "rsyslogd's groupid changed to %d",
+ cnf->globals.gidDropPriv);
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, szBuf, 0);
+finalize_it:
+ RETiRet;
+}
+
+
+/* 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(rsconf_t *cnf)
+{
+ int res;
+ uchar szBuf[1024];
+ struct passwd *pw;
+ gid_t gid;
+
+ /* Try to set appropriate supplementary groups for this user.
+ * Failure is not fatal.
+ */
+ pw = getpwuid(cnf->globals.uidDropPriv);
+ if (pw) {
+ gid = getgid();
+ res = initgroups(pw->pw_name, gid);
+ DBGPRINTF("initgroups(%s, %ld): %d\n", pw->pw_name, (long) gid, res);
+ } else {
+ LogError(errno, NO_ERRCODE, "could not get username for userid '%d'",
+ cnf->globals.uidDropPriv);
+ }
+
+ res = setuid(cnf->globals.uidDropPriv);
+ 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", cnf->globals.uidDropPriv, res);
+ snprintf((char*)szBuf, sizeof(szBuf), "rsyslogd's userid changed to %d", cnf->globals.uidDropPriv);
+ 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 rsRetVal
+dropPrivileges(rsconf_t *cnf)
+{
+ DEFiRet;
+
+ if(cnf->globals.gidDropPriv != 0) {
+ CHKiRet(doDropPrivGid(cnf));
+ DBGPRINTF("group privileges have been dropped to gid %u\n", (unsigned)
+ cnf->globals.gidDropPriv);
+ }
+
+ if(cnf->globals.uidDropPriv != 0) {
+ doDropPrivUid(cnf);
+ DBGPRINTF("user privileges have been dropped to uid %u\n", (unsigned)
+ cnf->globals.uidDropPriv);
+ }
+
+finalize_it:
+ 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 rsRetVal
+tellCoreConfigLoadDone(void)
+{
+ DBGPRINTF("telling rsyslog core that config load for %p is done\n", loadConf);
+ return glblDoneLoadCnf();
+}
+
+
+/* Tell input modules that the config parsing stage is over. */
+static rsRetVal
+tellModulesConfigLoadDone(void)
+{
+ cfgmodules_etry_t *node;
+
+ DBGPRINTF("telling modules that config load for %p is done\n", loadConf);
+ node = module.GetNxtCnfType(loadConf, NULL, eMOD_ANY);
+ while(node != NULL) {
+ DBGPRINTF("beginCnfLoad(%p) for module '%s'\n", node->pMod->beginCnfLoad, node->pMod->pszName);
+ if(node->pMod->beginCnfLoad != NULL) {
+ DBGPRINTF("calling endCnfLoad() for module '%s'\n", node->pMod->pszName);
+ node->pMod->endCnfLoad(node->modCnf);
+ }
+ node = module.GetNxtCnfType(loadConf, node, eMOD_ANY); // loadConf -> runConf
+ }
+
+ 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;
+
+ 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(loadConf, node, eMOD_ANY); // runConf -> loadConf
+ }
+
+ 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;
+
+ 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) {
+ 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_ANY);
+ }
+
+ 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;
+
+ 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) {
+ 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_ANY);
+ }
+
+ 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;
+
+ 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);
+ }
+
+ 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);
+ }
+
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+/* load the main queue */
+static rsRetVal
+loadMainQueue(void)
+{
+ DEFiRet;
+ struct cnfobj *mainqCnfObj;
+
+ mainqCnfObj = glbl.GetmainqCnfObj();
+ DBGPRINTF("loadMainQueue: mainq cnf obj ptr is %p\n", mainqCnfObj);
+ /* create message queue */
+ iRet = createMainQueue(&loadConf->pMsgQueue, UCHAR_CONSTANT("main Q"),
+ (mainqCnfObj == NULL) ? NULL : mainqCnfObj->nvlst);
+ if (iRet == RS_RET_OK) {
+ if (runConf != NULL) { /* dynamic config reload */
+ int areEqual = queuesEqual(loadConf->pMsgQueue, runConf->pMsgQueue);
+ DBGPRINTF("Comparison of old and new main queues: %d\n", areEqual);
+ if (areEqual) { /* content of the new main queue is the same as it was in previous conf */
+ qqueueDestruct(&loadConf->pMsgQueue);
+ loadConf->pMsgQueue = runConf->pMsgQueue;
+ }
+ }
+ }
+
+ if(iRet != RS_RET_OK) {
+ /* 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;
+ }
+finalize_it:
+ glblDestructMainqCnfObj();
+ RETiRet;
+}
+
+/* activate the main queue */
+static rsRetVal
+activateMainQueue(void)
+{
+ DEFiRet;
+
+ DBGPRINTF("activateMainQueue: will try to activate main queue %p\n", runConf->pMsgQueue);
+
+ iRet = startMainQueue(runConf, runConf->pMsgQueue);
+ if(iRet != RS_RET_OK) {
+ /* 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;
+ }
+
+ if(runConf->globals.mainQ.MainMsgQueType == QUEUETYPE_DIRECT) {
+ PREFER_STORE_0_TO_INT(&bHaveMainQueue);
+ } else {
+ PREFER_STORE_1_TO_INT(&bHaveMainQueue);
+ }
+ 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;
+}
+
+/* Remove resources from previous config */
+static void
+cleanupOldCnf(rsconf_t *cnf)
+{
+ if (cnf == NULL)
+ FINALIZE;
+
+ if (runConf->pMsgQueue != cnf->pMsgQueue)
+ qqueueDestruct(&cnf->pMsgQueue);
+
+finalize_it:
+ return;
+}
+
+
+/* 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
+ */
+static rsRetVal
+activate(rsconf_t *cnf)
+{
+ DEFiRet;
+ rsconf_t *runCnfOld = runConf;
+
+ /* at this point, we "switch" over to the running conf */
+ runConf = cnf;
+ loadConf = NULL;
+# 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));
+
+ lookupActivateConf();
+ tellModulesActivateConfig();
+ startInputModules();
+ CHKiRet(activateActions());
+ CHKiRet(activateRulesetQueues());
+ CHKiRet(activateMainQueue());
+ /* finally let the inputs run... */
+ runInputModules();
+ qqueueDoneLoadCnf(); /* we no longer need config-load-only data structures */
+
+ dbgprintf("configuration %p activated\n", cnf);
+ cleanupOldCnf(runCnfOld);
+
+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 {
+ 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));
+ 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)
+{
+ free(loadConf->globals.mainQ.pszMainMsgQFName);
+
+ cnfSetDefaults(loadConf);
+
+ 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(void)
+{
+ 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 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, eCmdHdlrInt,
+ 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_SyslogRFC5424Format;
+ tplAddLine(ourConf, "RSYSLOG_SyslogRFC5424Format", &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_FullJSONFmt;
+ tplAddLine(ourConf, " FullJSONFmt", &pTmp);
+ pTmp = template_StdClickHouseFmt;
+ tplAddLine(ourConf, " StdClickHouseFmt", &pTmp);
+ pTmp = template_spoofadr;
+ tplLastStaticInit(ourConf, tplAddLine(ourConf, "RSYSLOG_omudpspoofDfltSourceTpl", &pTmp));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* validate the configuration pointed by conf, generate error messages, do
+ * optimizations, etc, etc,...
+ */
+static rsRetVal
+validateConf(rsconf_t *cnf)
+{
+ DEFiRet;
+
+ /* some checks */
+ if(cnf->globals.mainQ.iMainMsgQueueNumWorkers < 1) {
+ LogError(0, NO_ERRCODE, "$MainMsgQueueNumWorkers must be at least 1! Set to 1.\n");
+ cnf->globals.mainQ.iMainMsgQueueNumWorkers = 1;
+ }
+
+ if(cnf->globals.mainQ.MainMsgQueType == QUEUETYPE_DISK) {
+ errno = 0; /* for logerror! */
+ if(glbl.GetWorkDir(cnf) == NULL) {
+ LogError(0, NO_ERRCODE, "No $WorkDirectory specified - can not run main "
+ "message queue in 'disk' mode. Using 'FixedArray' instead.\n");
+ cnf->globals.mainQ.MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ }
+ if(cnf->globals.mainQ.pszMainMsgQFName == NULL) {
+ LogError(0, NO_ERRCODE, "No $MainMsgQueueFileName specified - can not run main "
+ "message queue in 'disk' mode. Using 'FixedArray' instead.\n");
+ cnf->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
+ */
+static rsRetVal
+load(rsconf_t **cnf, uchar *confFile)
+{
+ int iNbrActions = 0;
+ int r;
+ rsRetVal delayed_iRet = RS_RET_OK;
+ 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);
+ }
+
+ /* we run the optimizer even if we have an error, as it may spit out
+ * additional error messages and we want to see these even if we abort.
+ */
+ rulesetOptimizeAll(loadConf);
+
+ if(r == 1) {
+ LogError(0, RS_RET_CONF_PARSE_ERROR, "could not interpret master "
+ "config file '%s'.", confFile);
+ /* we usually keep running with the failure, so we need to continue for now */
+ delayed_iRet = RS_RET_CONF_PARSE_ERROR;
+ } else if(r == 2) { /* file not found? */
+ LogError(errno, RS_RET_CONF_FILE_NOT_FOUND, "could not open config file '%s'",
+ confFile);
+ ABORT_FINALIZE(RS_RET_CONF_FILE_NOT_FOUND);
+ } else if( (iNbrActions == 0)
+ && !(iConfigVerify & CONF_VERIFY_PARTIAL_CONF)) {
+ LogError(0, RS_RET_NO_ACTIONS, "there are no active actions configured. "
+ "Inputs would run, but no output whatsoever were created.");
+ ABORT_FINALIZE(RS_RET_NO_ACTIONS);
+ }
+ tellLexEndParsing();
+ DBGPRINTF("Number of actions in this configuration: %d\n", loadConf->actions.iActionNbr);
+
+ CHKiRet(tellCoreConfigLoadDone());
+ tellModulesConfigLoadDone();
+
+ tellModulesCheckConfig();
+ CHKiRet(validateConf(loadConf));
+ CHKiRet(loadMainQueue());
+
+ /* 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:
+ if(iRet == RS_RET_OK && delayed_iRet != RS_RET_OK) {
+ iRet = delayed_iRet;
+ }
+ 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->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(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(glbl, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+ objRelease(parser, CORE_COMPONENT);
+ENDObjClassExit(rsconf)
diff --git a/runtime/rsconf.h b/runtime/rsconf.h
new file mode 100644
index 0000000..453861e
--- /dev/null
+++ b/runtime/rsconf.h
@@ -0,0 +1,299 @@
+/* The rsconf object. It models a complete rsyslog configuration.
+ *
+ * Copyright 2011-2023 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"
+#include "lookup.h"
+#include "dynstats.h"
+#include "perctile_stats.h"
+#include "timezones.h"
+
+/* --- configuration objects (the plan is to have ALL upper layers in this file) --- */
+
+#define REPORT_CHILD_PROCESS_EXITS_NONE 0
+#define REPORT_CHILD_PROCESS_EXITS_ERRORS 1
+#define REPORT_CHILD_PROCESS_EXITS_ALL 2
+
+#ifndef DFLT_INT_MSGS_SEV_FILTER
+ #define DFLT_INT_MSGS_SEV_FILTER 6 /* Warning level and more important */
+#endif
+
+/* 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 */
+};
+
+/* parser config parameters */
+struct parsercnf_s {
+ uchar cCCEscapeChar; /* character to be used to start an escape sequence for control chars */
+ int bDropTrailingLF; /* drop trailing LF's on reception? */
+ int bEscapeCCOnRcv; /* escape control characters on reception: 0 - no, 1 - yes */
+ int bSpaceLFOnRcv; /* replace newlines with spaces on reception: 0 - no, 1 - yes */
+ int bEscape8BitChars; /* escape characters > 127 on reception: 0 - no, 1 - yes */
+ int bEscapeTab; /* escape tab control character when doing CC escapes: 0 - no, 1 - yes */
+ int bParserEscapeCCCStyle; /* escape control characters in c style: 0 - no, 1 - yes */
+ int bPermitSlashInProgramname;
+ int bParseHOSTNAMEandTAG; /* parser modification (based on startup params!) */
+};
+
+/* 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 {
+#ifdef ENABLE_LIBCAPNG
+ int bAbortOnFailedLibcapngSetup;
+ int bCapabilityDropEnabled;
+#endif
+ 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 maxErrMsgToStderr; /* how many messages to forward at most to stderr? */
+ int bAbortOnUncleanConfig; /* abort run (rather than starting with partial
+ config) if there was any issue in conf */
+ int bAbortOnFailedQueueStartup; /* similar to bAbortOnUncleanConfig, but abort if a queue
+ startup fails. This is not exactly an unclan config. */
+ int uidDropPriv; /* user-id to which priveleges should be dropped to */
+ int gidDropPriv; /* group-id to which priveleges should be dropped to */
+ int gidDropPrivKeepSupplemental; /* keep supplemental groups when dropping? */
+ int abortOnIDResolutionFail;
+ int umask; /* umask to use */
+ uchar *pszConfDAGFile; /* name of config DAG file, non-NULL means generate one */
+ uchar *pszWorkDir;
+ int bDropMalPTRMsgs;/* Drop messages which have malicious PTR records during DNS lookup */
+ uchar *operatingStateFile;
+ int debugOnShutdown; /* start debug log when we are shut down */
+ int iGnuTLSLoglevel;/* Sets GNUTLS Debug Level */
+ uchar *pszDfltNetstrmDrvrCAF; /* default CA file for the netstrm driver */
+ uchar *pszDfltNetstrmDrvrCRLF; /* default CRL file for the netstrm driver */
+ uchar *pszDfltNetstrmDrvrCertFile;/* default cert file for the netstrm driver (server) */
+ uchar *pszDfltNetstrmDrvrKeyFile; /* default key file for the netstrm driver (server) */
+ uchar *pszDfltNetstrmDrvr; /* module name of default netstream driver */
+ uchar *pszNetstrmDrvrCAExtraFiles; /* CA extra file for the netstrm driver */
+ uchar *oversizeMsgErrorFile; /* File where oversize messages are written to */
+ int reportOversizeMsg; /* shall error messages be generated for oversize messages? */
+ int oversizeMsgInputMode; /* Mode which oversize messages will be forwarded */
+ int reportChildProcessExits;
+ int bActionReportSuspension;
+ int bActionReportSuspensionCont;
+ short janitorInterval; /* interval (in minutes) at which the janitor runs */
+ int reportNewSenders;
+ int reportGoneAwaySenders;
+ int senderStatsTimeout;
+ int senderKeepTrack; /* keep track of known senders? */
+ int inputTimeoutShutdown; /* input shutdown timeout in ms */
+ int iDefPFFamily; /* protocol family (IPv4, IPv6 or both) */
+ int ACLAddHostnameOnFail; /* add hostname to acl when DNS resolving has failed */
+ int ACLDontResolve; /* add hostname to acl instead of resolving it to IP(s) */
+ int bDisableDNS; /* don't look up IP addresses of remote messages */
+ int bProcessInternalMessages; /* Should rsyslog itself process internal messages?
+ * 1 - yes
+ * 0 - send them to libstdlog (e.g. to push to journal) or syslog()
+ */
+ uint64_t glblDevOptions; /* to be used by developers only */
+ int intMsgRateLimitItv;
+ int intMsgRateLimitBurst;
+ int intMsgsSeverityFilter;/* filter for logging internal messages by syslog sev. */
+ int permitCtlC;
+
+ int actq_dflt_toQShutdown; /* queue shutdown */
+ int actq_dflt_toActShutdown; /* action shutdown (in phase 2) */
+ int actq_dflt_toEnq; /* timeout for queue enque */
+ int actq_dflt_toWrkShutdown; /* timeout for worker thread shutdown */
+
+ int ruleset_dflt_toQShutdown; /* queue shutdown */
+ int ruleset_dflt_toActShutdown; /* action shutdown (in phase 2) */
+ int ruleset_dflt_toEnq; /* timeout for queue enque */
+ int ruleset_dflt_toWrkShutdown; /* timeout for worker thread shutdown */
+
+ unsigned dnscacheDefaultTTL; /* 24 hrs default TTL */
+ int dnscacheEnableTTL; /* expire entries or not (0) ? */
+ int shutdownQueueDoubleSize;
+ int optionDisallowWarning; /* complain if message from disallowed sender is received */
+ int bSupportCompressionExtension;
+ #ifdef ENABLE_LIBLOGGING_STDLOG
+ stdlog_channel_t stdlog_hdl; /* handle to be used for stdlog */
+ uchar *stdlog_chanspec;
+ #endif
+ int iMaxLine; /* maximum length of a syslog message */
+
+ // 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 parameters */
+ parsercnf_t parser; /* parser parameters */
+};
+
+/* (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 {
+ int remove_me_when_first_real_member_is_added;
+};
+
+
+/* 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 parsers_s {
+ /* This is the list of all parsers known to us.
+ * This is also used to unload all modules on shutdown.
+ */
+ parserList_t *pParsLstRoot;
+
+ /* this is the list of the default parsers, to be used if no others
+ * are specified.
+ */
+ parserList_t *pDfltParsLst;
+};
+
+struct actions_s {
+ /* number of active actions */
+ unsigned nbrActions;
+ /* number of actions created. It is used to obtain unique IDs for the action. They
+ * should not be relied on for any long-term activity (e.g. disk queue names!), but they are nice
+ * to have during one instance of an rsyslogd run. For example, I use them to name actions when there
+ * is no better name available.
+ */
+ int iActionNbr;
+};
+
+
+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;
+ parsers_t parsers;
+ lookup_tables_t lu_tabs;
+ dynstats_buckets_t dynstats_buckets;
+ perctile_buckets_t perctile_buckets;
+ 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...
+ */
+ timezones_t timezones;
+ qqueue_t *pMsgQueue; /* the main message queue */
+};
+
+
+/* interfaces */
+BEGINinterface(rsconf) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(rsconf);
+ 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!) */
+
+
+int rsconfNeedDropPriv(rsconf_t *const cnf);
+
+/* 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 0000000..9b4c4c2
--- /dev/null
+++ b/runtime/rsyslog.c
@@ -0,0 +1,298 @@
+/* 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-2016 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>
+#ifdef ENABLE_LIBLOGGING_STDLOG
+#include <liblogging/stdlog.h>
+#endif
+
+#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 "lookup.h"
+#include "strgen.h"
+#include "statsobj.h"
+#include "atomic.h"
+#include "srUtils.h"
+
+pthread_attr_t default_thread_attr;
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+struct sched_param default_sched_param;
+int default_thr_sched_policy;
+#endif
+
+/* globally visible static data - see comment in rsyslog.h for details */
+uchar *glblModPath; /* module load path */
+void (*glblErrLogger)(const int, const int, const 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
+ */
+void
+dfltErrLogger(const int severity, const int iErr, const uchar *errMsg)
+{
+ fprintf(stderr, "rsyslog internal message (%d,%d): %s\n", severity, iErr, errMsg);
+}
+
+
+/* set the error log function
+ * rgerhards, 2008-04-18
+ */
+void
+rsrtSetErrLogger(void (*errLogger)(const int, const int, const uchar*))
+{
+ assert(errLogger != NULL);
+ glblErrLogger = errLogger;
+}
+
+
+/* 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 occurred). 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(const char **ppErrObj, obj_if_t *pObjIF)
+{
+ DEFiRet;
+ int ret;
+ char errstr[1024];
+
+ if(iRefCount == 0) {
+ seedRandomNumber();
+ /* init runtime only if not yet done */
+#ifdef ENABLE_LIBLOGGING_STDLOG
+ stdlog_init(0);
+#endif
+ ret = pthread_attr_init(&default_thread_attr);
+ if(ret != 0) {
+ rs_strerror_r(ret, errstr, sizeof(errstr));
+ fprintf(stderr, "rsyslogd: pthread_attr_init failed during "
+ "startup - can not continue. Error was %s\n", errstr);
+ exit(1);
+ }
+ pthread_attr_setstacksize(&default_thread_attr, 4096*1024);
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+ ret = pthread_getschedparam(pthread_self(), &default_thr_sched_policy,
+ &default_sched_param);
+ if(ret != 0) {
+ rs_strerror_r(ret, errstr, sizeof(errstr));
+ fprintf(stderr, "rsyslogd: pthread_getschedparam failed during "
+ "startup - ignoring. Error was %s\n", errstr);
+ default_thr_sched_policy = 0; /* should be default on all platforms */
+ }
+#if defined (_AIX)
+ pthread_attr_setstacksize(&default_thread_attr, 4096*512);
+#endif
+
+
+ ret = pthread_attr_setschedpolicy(&default_thread_attr, default_thr_sched_policy);
+ if(ret != 0) {
+ rs_strerror_r(ret, errstr, sizeof(errstr));
+ fprintf(stderr, "rsyslogd: pthread_attr_setschedpolicy failed during "
+ "startup - tried to set priority %d, now using default "
+ "priority instead. Error was: %s\n",
+ default_thr_sched_policy, errstr);
+ }
+ ret = pthread_attr_setschedparam(&default_thread_attr, &default_sched_param);
+ if(ret != 0) {
+ rs_strerror_r(ret, errstr, sizeof(errstr));
+ fprintf(stderr, "rsyslogd: pthread_attr_setschedparam failed during "
+ "startup - ignored Error was: %s\n", errstr);
+ }
+ ret = pthread_attr_setinheritsched(&default_thread_attr, PTHREAD_EXPLICIT_SCHED);
+ if(ret != 0) {
+ rs_strerror_r(ret, errstr, sizeof(errstr));
+ fprintf(stderr, "rsyslogd: pthread_attr_setinheritsched failed during "
+ "startup - ignoring. Error was: %s\n", errstr);
+ }
+#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));
+ if(ppErrObj != NULL) *ppErrObj = "lookup";
+ CHKiRet(lookupClassInit());
+ if(ppErrObj != NULL) *ppErrObj = "dynstats";
+ CHKiRet(dynstatsClassInit());
+ if(ppErrObj != NULL) *ppErrObj = "perctile_stats";
+ CHKiRet(perctileClassInit());
+
+ /* 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();
+ wtiClassExit();
+ wtpClassExit();
+ strgenClassExit();
+ propClassExit();
+ statsobjClassExit();
+
+ 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 0000000..6446b13
--- /dev/null
+++ b/runtime/rsyslog.h
@@ -0,0 +1,814 @@
+/* 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-2023 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
+#ifdef __GNUC__
+ #pragma GCC diagnostic ignored "-Wdeclaration-after-statement"
+ #pragma GCC diagnostic ignored "-Wredundant-decls" // TODO: remove!
+ #pragma GCC diagnostic ignored "-Wstrict-prototypes" // TODO: remove!
+ #pragma GCC diagnostic ignored "-Wswitch-default" // TODO: remove!
+ #if __GNUC__ >= 8
+ /* GCC, starting at least with version 8, is now really overdoing with it's
+ * warning messages. We turn those off that either cause an enormous amount
+ * of false positives or flag perfectly legal code as problematic.
+ */
+ /* That one causes warnings when we use variable buffers for error
+ * messages which may be truncated in the very unlikely case of all
+ * vars using max value. If going over the max size, the engine will
+ * most likely truncate due to max message size anyhow. Also, sizing
+ * the buffers for max-max message size is a wast of (stack) memory.
+ */
+ #pragma GCC diagnostic ignored "-Wformat-truncation"
+ /* The next one flags variable initializations within out exception handling
+ * (iRet system) as problematic, even though variables are not used in those
+ * cases. This would be a good diagnostic if gcc would actually check that
+ * a variable is used uninitialized. Unfortunately it does not do that. But
+ * the static analyzers we use as part of CI do, so we are covered in any
+ * case.
+ * Unfortunately ignoring this diagnostic leads to two more info lines
+ * being emitted where nobody knows what the mean and why they appear :-(
+ */
+ #pragma GCC diagnostic ignored "-Wjump-misses-init"
+ #endif /* if __GNUC__ >= 8 */
+
+ /* define a couple of attributes to improve cross-platform builds */
+ #if __GNUC__ > 6
+ #define CASE_FALLTHROUGH __attribute__((fallthrough));
+ #else
+ #define CASE_FALLTHROUGH
+ #endif
+
+ #define ATTR_NORETURN __attribute__ ((noreturn))
+ #define ATTR_UNUSED __attribute__((unused))
+ #define ATTR_NONNULL(...) __attribute__((nonnull(__VA_ARGS__)))
+
+#else /* ifdef __GNUC__ */
+
+ #define CASE_FALLTHROUGH
+ #define ATTR_NORETURN
+ #define ATTR_UNUSED
+ #define ATTR_NONNULL(...)
+
+#endif /* ifdef __GNUC__ */
+
+#if defined(_AIX)
+#include <sys/select.h>
+/* AIXPORT : start*/
+#define SRC_FD 13
+#define SRCMSG (sizeof(srcpacket))
+#endif
+/* src end */
+
+#include <pthread.h>
+#include "typedefs.h"
+
+#if defined(__GNUC__)
+ #define PRAGMA_INGORE_Wswitch_enum _Pragma("GCC diagnostic ignored \"-Wswitch-enum\"")
+ #define PRAGMA_IGNORE_Wempty_body _Pragma("GCC diagnostic ignored \"-Wempty-body\"")
+ #define PRAGMA_IGNORE_Wstrict_prototypes _Pragma("GCC diagnostic ignored \"-Wstrict-prototypes\"")
+ #define PRAGMA_IGNORE_Wold_style_definition \
+ _Pragma("GCC diagnostic ignored \"-Wold-style-definition\"")
+ #if defined(__clang_major__) && __clang_major__ >= 14
+ #define PRAGMA_IGNORE_Wvoid_pointer_to_enum_cast \
+ _Pragma("GCC diagnostic ignored \"-Wvoid-pointer-to-enum-cast\"")
+ #else
+ #define PRAGMA_IGNORE_Wvoid_pointer_to_enum_cast
+ #endif
+ #define PRAGMA_IGNORE_Wsign_compare _Pragma("GCC diagnostic ignored \"-Wsign-compare\"")
+ #define PRAGMA_IGNORE_Wpragmas _Pragma("GCC diagnostic ignored \"-Wpragmas\"")
+ #define PRAGMA_IGNORE_Wmissing_noreturn _Pragma("GCC diagnostic ignored \"-Wmissing-noreturn\"")
+ #define PRAGMA_IGNORE_Wexpansion_to_defined \
+ _Pragma("GCC diagnostic ignored \"-Wexpansion-to-defined\"")
+ #define PRAGMA_IGNORE_Wunknown_warning_option \
+ _Pragma("GCC diagnostic ignored \"-Wunknown-warning-option\"")
+ #define PRAGMA_IGNORE_Wunknown_attribute \
+ _Pragma("GCC diagnostic ignored \"-Wunknown-attribute\"")
+ #define PRAGMA_IGNORE_Wformat_nonliteral \
+ _Pragma("GCC diagnostic ignored \"-Wformat-nonliteral\"")
+ #define PRAGMA_IGNORE_Wdeprecated_declarations \
+ _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"")
+ #if __GNUC__ >= 5
+ #define PRAGMA_DIAGNOSTIC_PUSH \
+ _Pragma("GCC diagnostic push")
+ #define PRAGMA_DIAGNOSTIC_POP \
+ _Pragma("GCC diagnostic pop")
+ #else
+ #define PRAGMA_DIAGNOSTIC_PUSH
+ #define PRAGMA_DIAGNOSTIC_POP
+ #endif
+#else
+ #define PRAGMA_INGORE_Wswitch_enum
+ #define PRAGMA_IGNORE_Wsign_compare
+ #define PRAGMA_IGNORE_Wformat_nonliteral
+ #define PRAGMA_IGNORE_Wpragmas
+ #define PRAGMA_IGNORE_Wmissing_noreturn
+ #define PRAGMA_IGNORE_Wempty_body
+ #define PRAGMA_IGNORE_Wstrict_prototypes
+ #define PRAGMA_IGNORE_Wold_style_definition
+ #define PRAGMA_IGNORE_Wvoid_pointer_to_enum_cast
+ #define PRAGMA_IGNORE_Wdeprecated_declarations
+ #define PRAGMA_IGNORE_Wexpansion_to_defined
+ #define PRAGMA_IGNORE_Wunknown_attribute
+ #define PRAGMA_IGNORE_Wunknown_warning_option
+ #define PRAGMA_DIAGNOSTIC_PUSH
+ #define PRAGMA_DIAGNOSTIC_POP
+#endif
+
+/* ############################################################# *
+ * # 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 */
+
+#define CONST_LEN_CEE_COOKIE 5
+#define CONST_CEE_COOKIE "@cee:"
+
+/* ############################################################# *
+ * # 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_IPARAMS_BUFSIZE 16 /* initial size of iparams array in wti (is automatically extended) */
+#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 # *
+ * ############################################################# */
+
+/* make sure we uses consistent macros, no matter what the
+ * platform gives us.
+ */
+#undef LOG_NFACILITIES /* may be system defined, override */
+#define LOG_NFACILITIES 24+1 /* plus one for our special "invld" facility! */
+#define LOG_MAXPRI 191 /* highest supported valid PRI value --> RFC3164, RFC5424 */
+#undef LOG_MAKEPRI
+#define LOG_PRI_INVLD (LOG_INVLD|LOG_DEBUG)
+/* PRI is invalid --> special "invld.=debug" PRI code (rsyslog-specific) */
+
+#define LOG_EMERG 0 /* system is unusable */
+#define LOG_ALERT 1 /* action must be taken immediately */
+#define LOG_CRIT 2 /* critical conditions */
+#define LOG_ERR 3 /* error conditions */
+#define LOG_WARNING 4 /* warning conditions */
+#define LOG_NOTICE 5 /* normal but significant condition */
+#define LOG_INFO 6 /* informational */
+#define LOG_DEBUG 7 /* debug-level messages */
+
+#define LOG_KERN (0<<3) /* kernel messages */
+#define LOG_USER (1<<3) /* random user-level messages */
+#define LOG_MAIL (2<<3) /* mail system */
+#define LOG_DAEMON (3<<3) /* system daemons */
+#define LOG_AUTH (4<<3) /* security/authorization messages */
+#define LOG_SYSLOG (5<<3) /* messages generated internally by syslogd */
+#define LOG_LPR (6<<3) /* line printer subsystem */
+#define LOG_NEWS (7<<3) /* network news subsystem */
+#define LOG_UUCP (8<<3) /* UUCP subsystem */
+#if !defined(LOG_CRON)
+#define LOG_CRON (9<<3) /* clock daemon */
+#endif
+#define LOG_AUTHPRIV (10<<3) /* security/authorization messages (private) */
+#define LOG_FTP (11<<3) /* ftp daemon */
+#if defined(_AIX) /* AIXPORT : These are necessary for AIX */
+#define LOG_ASO (12<<3) /* Active System Optimizer. Reserved for internal use */
+#define LOG_CAA (15<<3) /* Cluster aware AIX subsystem */
+#endif
+#define LOG_LOCAL0 (16<<3) /* reserved for local use */
+#define LOG_LOCAL1 (17<<3) /* reserved for local use */
+#define LOG_LOCAL2 (18<<3) /* reserved for local use */
+#define LOG_LOCAL3 (19<<3) /* reserved for local use */
+#define LOG_LOCAL4 (20<<3) /* reserved for local use */
+#define LOG_LOCAL5 (21<<3) /* reserved for local use */
+#define LOG_LOCAL6 (22<<3) /* reserved for local use */
+#define LOG_LOCAL7 (23<<3) /* reserved for local use */
+#define LOG_FAC_INVLD 24
+#define LOG_INVLD (LOG_FAC_INVLD<<3) /* invalid facility/PRI code */
+
+/* we need to evaluate our argument only once, as otherwise we may
+ * have side-effects (this was seen in some version).
+ * Note: I know that "static inline" is not the right thing from a C99
+ * PoV, but some environments treat, even in C99 mode, compile
+ * non-static inline into the source even if not defined as "extern". This
+ * obviously results in linker errors. Using "static inline" as below together
+ * with "__attribute__((unused))" works in all cases. Note also that we
+ * cannot work around this in pri2fac, as we would otherwise need to evaluate
+ * pri more than once.
+ */
+static inline syslog_pri_t __attribute__((unused))
+pri2fac(const syslog_pri_t pri)
+{
+ unsigned fac = pri >> 3;
+ return (fac > 23) ? LOG_FAC_INVLD : fac;
+}
+#define pri2sev(pri) ((pri) & 0x07)
+
+/* 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
+
+
+/* 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_FILE_TRUNCATED = -51, /**< (input) file was truncated, not an error but a status */
+ 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_INOTIFY_INIT_FAILED = - 3009,
+ /**< the initialization of an inotify instance failed (no details available) */
+ RS_RET_FEN_INIT_FAILED = - 3010, /**< the initialization of a fen instance 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 occurred (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 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 occurred (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 occurred 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_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 or reading the kernel log socket */
+ 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 occurred 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 occurred during operation */
+ RS_RET_RCV_ERR = -2165, /**< error occurred 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) */
+ /* UNUSED, WAS; 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_INVLD_PROP = -2309,/**< property name error (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 */
+ RS_RET_CA_CERT_MISSING = -2329,/**< a CA cert is missing where one is required (e.g. TLS) */
+ RS_RET_CERT_MISSING = -2330,/**< a cert is missing where one is required (e.g. TLS) */
+ RS_RET_CERTKEY_MISSING = -2331,/**< a cert (private) key is missing where one is required (e.g. TLS) */
+ RS_RET_CRL_MISSING = -2332,/**< a CRL file is missing but not required (e.g. TLS) */
+ RS_RET_CRL_INVALID = -2333, /**< a CRL file PEM file failed validation */
+ RS_RET_CERT_REVOKED = -2334, /**< a certificate has been revoked */
+ RS_RET_STRUC_DATA_INVLD = -2349,/**< structured data is malformed */
+
+ /* 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 */
+ RS_RET_RELP_AUTH_FAIL = -2353,/**< RELP peer authentication failed */
+ RS_RET_ERR_UDPSEND = -2354,/**< sending msg via UDP failed */
+ RS_RET_LAST_ERRREPORT = -2355,/**< module does not emit more error messages as limit is reached */
+ RS_RET_READ_ERR = -2356,/**< read error occurred (file i/o) */
+ RS_RET_CONF_PARSE_WARNING = -2357,/**< warning parsing config file */
+ RS_RET_CONF_WRN_FULLDLY_BELOW_HIGHWTR = -2358,/**< warning queue full delay mark below high wtr mark */
+ RS_RET_RESUMED = -2359,/**< status: action was resumed (used for reporting) */
+ RS_RET_RELP_NO_TLS = -2360,/**< librel does not support TLS (but TLS requested) */
+ RS_RET_STATEFILE_WRONG_FNAME = -2361,/**< state file is for wrong file */
+ RS_RET_NAME_INVALID = -2362, /**< invalid name (in RainerScript) */
+
+ /* up to 2400 reserved for 7.5 & 7.6 */
+ RS_RET_INVLD_OMOD = -2400, /**< invalid output module, does not provide proper interfaces */
+ RS_RET_INVLD_INTERFACE_INPUT = -2401, /**< invalid value for "interface.input" parameter (ext progs) */
+ RS_RET_PARSER_NAME_EXISTS = -2402, /**< parser name already exists */
+ RS_RET_MOD_NO_PARSER_STMT = -2403, /**< (parser) module does not support parser() statement */
+
+ /* up to 2419 reserved for 8.4.x */
+ RS_RET_IMFILE_WILDCARD = -2420, /**< imfile file name contains wildcard, which may be problematic */
+ RS_RET_RELP_NO_TLS_AUTH = -2421,/**< librel does not support TLS authentication (but was requested) */
+ RS_RET_KAFKA_ERROR = -2422,/**< error reported by Apache Kafka subsystem. See message for details. */
+ RS_RET_KAFKA_NO_VALID_BROKERS = -2423,/**< no valid Kafka brokers configured/available */
+ RS_RET_KAFKA_PRODUCE_ERR = -2424,/**< error during Kafka produce function */
+ RS_RET_CONF_PARAM_INVLD = -2425,/**< config parameter is invalid */
+ RS_RET_KSI_ERR = -2426,/**< error in KSI subsystem */
+ RS_RET_ERR_LIBLOGNORM = -2427,/**< cannot obtain liblognorm ctx */
+ RS_RET_CONC_CTRL_ERR = -2428,/**< error in lock/unlock/condition/concurrent-modification operation */
+ RS_RET_SENDER_GONE_AWAY = -2429,/**< warning: sender not seen for configured amount of time */
+ RS_RET_SENDER_APPEARED = -2430,/**< info: new sender appeared */
+ RS_RET_FILE_ALREADY_IN_TABLE = -2431,/**< in imfile: table already contains to be added file */
+ RS_RET_ERR_DROP_PRIV = -2432,/**< error droping privileges */
+ RS_RET_FILE_OPEN_ERROR = -2433, /**< error other than "not found" occurred during open() */
+ RS_RET_RENAME_TMP_QI_ERROR = -2435, /**< renaming temporary .qi file failed */
+ RS_RET_ERR_SETENV = -2436, /**< error setting an environment variable */
+ RS_RET_DIR_CHOWN_ERROR = -2437, /**< error during chown() */
+ RS_RET_JSON_UNUSABLE = -2438, /**< JSON object is NULL or otherwise unusable */
+ RS_RET_OPERATION_STATUS = -2439, /**< operational status (info) message, no error */
+ RS_RET_UDP_MSGSIZE_TOO_LARGE = -2440, /**< a message is too large to be sent via UDP */
+ RS_RET_NON_JSON_PROP = -2441, /**< a non-json property id is provided where a json one is requried */
+ RS_RET_NO_TZ_SET = -2442, /**< system env var TZ is not set (status msg) */
+ RS_RET_FS_ERR = -2443, /**< file-system error */
+ RS_RET_POLL_ERR = -2444, /**< error in poll() system call */
+ RS_RET_OVERSIZE_MSG = -2445, /**< message is too long (above configured max) */
+ RS_RET_TLS_KEY_ERR = -2446, /**< TLS KEY has problems */
+ RS_RET_RABBITMQ_CONN_ERR = -2447, /**< RabbitMQ Connection error */
+ RS_RET_RABBITMQ_LOGIN_ERR = -2448, /**< RabbitMQ Login error */
+ RS_RET_RABBITMQ_CHANNEL_ERR = -2449, /**< RabbitMQ Connection error */
+ RS_RET_NO_WRKDIR_SET = -2450, /**< working directory not set, but desired by functionality */
+ RS_RET_ERR_QUEUE_FN_DUP = -2451, /**< duplicate queue file name */
+ RS_RET_REDIS_ERROR = -2452, /**< redis-specific error. See message foe details. */
+ RS_RET_REDIS_AUTH_FAILED = -2453, /**< redis authentication failure */
+ RS_RET_FAUP_INIT_OPTIONS_FAILED = -2454, /**< could not initialize faup options */
+ RS_RET_LIBCAPNG_ERR = -2455, /**< error during dropping the capabilities */
+
+ /* 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".
+ */
+#ifdef HAVE_BUILTIN_EXCEPT
+# define CHKiRet(code) if(__builtin_expect(((iRet = code) != RS_RET_OK), 0)) goto finalize_it
+# define likely(x) __builtin_expect(!!(x), 1)
+# define unlikely(x) __builtin_expect(!!(x), 0)
+#else
+# define CHKiRet(code) if((iRet = code) != RS_RET_OK) goto finalize_it
+# define likely(x) (x)
+# define unlikely(x) (x)
+#endif
+
+# define CHKiConcCtrl(code) { int tmp_CC; \
+ if ((tmp_CC = code) != 0) { \
+ iRet = RS_RET_CONC_CTRL_ERR; \
+ errno = tmp_CC; \
+ 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 rsRetVal iRet = RS_RET_OK
+#define RETiRet return iRet
+
+#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
+
+extern pthread_attr_t default_thread_attr;
+#ifdef HAVE_PTHREAD_SETSCHEDPARAM
+extern struct sched_param default_sched_param;
+extern int default_thr_sched_policy;
+#endif
+
+/* The following structure defines immutable parameters which need to
+ * be passed as action parameters.
+ *
+ * Note that output plugins may request multiple templates. Let's say
+ * an output requests n templates. Than the overall table must hold
+ * n*nbrMsgs records, and each messages begins on a n-boundary. There
+ * is a macro defined below to access the proper element.
+ *
+ * WARNING: THIS STRUCTURE IS PART OF THE ***OUTPUT MODULE INTERFACE***
+ * It is passed into the doCommit() function. Do NOT modify it until
+ * absolutely necessary - all output plugins need to be changed!
+ *
+ * If a change is "just" for internal working, consider adding a
+ * separate parameter outside of this structure. Of course, it is
+ * best to avoid this as well ;-)
+ * rgerhards, 2013-12-04
+ */
+struct actWrkrIParams {
+ uchar *param;
+ uint32_t lenBuf; /* length of string buffer (if string ptr) */
+ uint32_t lenStr; /* length of current string (if string ptr) */
+};
+
+/* macro to access actWrkrIParams base object:
+ * param is ptr to base address
+ * nActTpls is the number of templates the action has requested
+ * iMsg is the message index
+ * iTpl is the template index
+ * This macro can be used for read and write access.
+ */
+#define actParam(param, nActTpls, iMsg, iTpl) (param[(iMsg*nActTpls)+iTpl])
+
+/* 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
+
+#if !defined(O_CLOEXEC) && !defined(_AIX)
+/* of course, this limits the functionality... */
+# define O_CLOEXEC 0
+#endif
+
+/* some constants */
+#define MUTEX_ALREADY_LOCKED 0
+#define LOCK_MUTEX 1
+
+
+#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 void (*glblErrLogger)(const int, const int, const uchar*);
+
+/* some runtime prototypes */
+void processImInternal(void);
+rsRetVal rsrtInit(const char **ppErrObj, obj_if_t *pObjIF);
+rsRetVal rsrtExit(void);
+int rsrtIsInit(void);
+void rsrtSetErrLogger(void (*errLogger)(const int, const int, const uchar*));
+void dfltErrLogger(const int, const int, const uchar *errMsg);
+rsRetVal queryLocalHostname(rsconf_t *const);
+
+
+/* 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
+ */
+#ifdef OS_SOLARIS
+#define EMPTY_STRUCT int remove_me_when_first_real_member_is_added;
+#else
+#define EMPTY_STRUCT
+#endif
+
+/* 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 */
+
+
+/* here we add some stuff from the compatibility layer. A separate include
+ * would be cleaner, but would potentially require changes all over the
+ * place. So doing it here is better. The respective replacement
+ * functions should usually be found under ./compat -- rgerhards, 2015-05-20
+ */
+#ifndef HAVE_STRNDUP
+char * strndup(const char *s, size_t n);
+#endif
+
+#endif /* multi-include protection */
diff --git a/runtime/ruleset.c b/runtime/ruleset.c
new file mode 100644
index 0000000..c137ee3
--- /dev/null
+++ b/runtime/ruleset.c
@@ -0,0 +1,1194 @@
+/* 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-2021 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 "wti.h"
+#include "dirty.h" /* for main ruleset queue creation */
+
+
+/* static data */
+DEFobjStaticHelpers
+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, wti_t *pWti);
+static rsRetVal scriptExec(struct cnfstmt *root, smsg_t *pMsg, wti_t *pWti);
+
+
+/* ---------- 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_SET:
+ case S_UNSET:
+ case S_CALL_INDIRECT:
+ 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_FOREACH:
+ if(stmt->d.s_foreach.body != NULL)
+ scriptIterateAllActions(stmt->d.s_foreach.body,
+ 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;
+ case S_RELOAD_LOOKUP_TABLE: /* this is a NOP */
+ break;
+ default:
+ dbgprintf("error: unknown stmt type %u during iterateAll\n",
+ (unsigned) stmt->nodetype);
+ #ifndef NDEBUG
+ fprintf(stderr, "error: unknown stmt type %u during iterateAll\n",
+ (unsigned) stmt->nodetype);
+ #endif
+ assert(0); /* abort under debugging */
+ 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;
+}
+
+/* driver to iterate over all rulesets */
+DEFFUNC_llExecFunc(doActivateRulesetQueues)
+{
+ DEFiRet;
+ ruleset_t* pThis = (ruleset_t*) pData;
+ dbgprintf("Activating Ruleset Queue[%p] for Ruleset %s\n",
+ pThis->pQueue, pThis->pszName);
+ if(pThis->pQueue != NULL)
+ startMainQueue(runConf, pThis->pQueue);
+ RETiRet;
+}
+/* activate all ruleset queues */
+rsRetVal
+activateRulesetQueues(void)
+{
+ llExecFunc(&(runConf->rulesets.llRulesets), doActivateRulesetQueues, NULL);
+ return RS_RET_OK;
+}
+
+
+static rsRetVal
+execAct(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
+{
+ DEFiRet;
+ if(stmt->d.act->bDisabled) {
+ DBGPRINTF("action %d died, do NOT execute\n", stmt->d.act->iActionNbr);
+ FINALIZE;
+ }
+
+ DBGPRINTF("executing action %d\n", stmt->d.act->iActionNbr);
+ stmt->d.act->submitToActQ(stmt->d.act, pWti, pMsg);
+ if(iRet != RS_RET_DISCARDMSG) {
+ /* note: we ignore the error code here, as we do NEVER want to
+ * stop script execution due to action return code
+ */
+ iRet = RS_RET_OK;
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal ATTR_NONNULL()
+execSet(const struct cnfstmt *const stmt,
+ smsg_t *const pMsg,
+ wti_t *const __restrict__ pWti)
+{
+ struct svar result;
+ DEFiRet;
+ cnfexprEval(stmt->d.s_set.expr, &result, pMsg, pWti);
+ msgSetJSONFromVar(pMsg, stmt->d.s_set.varname, &result, stmt->d.s_set.force_reset);
+ varDelete(&result);
+ RETiRet;
+}
+
+static rsRetVal
+execUnset(struct cnfstmt *stmt, smsg_t *pMsg)
+{
+ DEFiRet;
+ msgDelJSON(pMsg, stmt->d.s_unset.varname);
+ RETiRet;
+}
+
+static rsRetVal
+execCallIndirect(struct cnfstmt *const __restrict__ stmt,
+ smsg_t *pMsg,
+ wti_t *const __restrict__ pWti)
+{
+ ruleset_t *pRuleset;
+ struct svar result;
+ int bMustFree; /* dummy parameter */
+ DEFiRet;
+
+ assert(stmt->d.s_call_ind.expr != NULL);
+
+ cnfexprEval(stmt->d.s_call_ind.expr, &result, pMsg, pWti);
+ uchar *const rsName = (uchar*) var2CString(&result, &bMustFree);
+ const rsRetVal localRet = rulesetGetRuleset(runConf, &pRuleset, rsName);
+ if(localRet != RS_RET_OK) {
+ /* in that case, we accept that a NOP will "survive" */
+ LogError(0, RS_RET_RULESET_NOT_FOUND, "error: CALL_INDIRECT: "
+ "ruleset '%s' cannot be found, treating as NOP\n", rsName);
+ FINALIZE;
+ }
+ DBGPRINTF("CALL_INDIRECT obtained ruleset ptr %p for ruleset '%s' [hasQueue:%d]\n",
+ pRuleset, rsName, rulesetHasQueue(pRuleset));
+ if(rulesetHasQueue(pRuleset)) {
+ CHKmalloc(pMsg = MsgDup((smsg_t*) pMsg));
+ DBGPRINTF("CALL_INDIRECT: forwarding message to async ruleset %p\n",
+ pRuleset->pQueue);
+ MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY);
+ MsgSetRuleset(pMsg, pRuleset);
+ /* Note: we intentionally use submitMsg2() here, as we process messages
+ * that were already run through the rate-limiter.
+ */
+ submitMsg2(pMsg);
+ } else {
+ CHKiRet(scriptExec(pRuleset->root, pMsg, pWti));
+ }
+finalize_it:
+ varDelete(&result);
+ free(rsName);
+ RETiRet;
+}
+
+static rsRetVal
+execCall(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
+{
+ DEFiRet;
+ if(stmt->d.s_call.ruleset == NULL) {
+ CHKiRet(scriptExec(stmt->d.s_call.stmt, pMsg, pWti));
+ } else {
+ CHKmalloc(pMsg = MsgDup((smsg_t*) pMsg));
+ DBGPRINTF("CALL: forwarding message to async ruleset %p\n",
+ stmt->d.s_call.ruleset->pQueue);
+ MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY);
+ MsgSetRuleset(pMsg, stmt->d.s_call.ruleset);
+ /* Note: we intentionally use submitMsg2() here, as we process messages
+ * that were already run through the rate-limiter.
+ */
+ submitMsg2(pMsg);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+execIf(struct cnfstmt *const stmt, smsg_t *const pMsg, wti_t *const pWti)
+{
+ sbool bRet;
+ DEFiRet;
+ bRet = cnfexprEvalBool(stmt->d.s_if.expr, pMsg, pWti);
+ DBGPRINTF("if condition result is %d\n", bRet);
+ if(bRet) {
+ if(stmt->d.s_if.t_then != NULL)
+ CHKiRet(scriptExec(stmt->d.s_if.t_then, pMsg, pWti));
+ } else {
+ if(stmt->d.s_if.t_else != NULL)
+ CHKiRet(scriptExec(stmt->d.s_if.t_else, pMsg, pWti));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+invokeForeachBodyWith(struct cnfstmt *stmt, json_object *o, smsg_t *pMsg, wti_t *pWti) {
+ struct svar v;
+ v.datatype = 'J';
+ v.d.json = o;
+ DEFiRet;
+ CHKiRet(msgSetJSONFromVar(pMsg, (uchar*)stmt->d.s_foreach.iter->var, &v, 1));
+ CHKiRet(scriptExec(stmt->d.s_foreach.body, pMsg, pWti));
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+callForeachArray(struct cnfstmt *stmt, json_object *arr, smsg_t *pMsg, wti_t *pWti) {
+ DEFiRet;
+ int len = json_object_array_length(arr);
+ json_object *curr;
+ for (int i = 0; i < len; i++) {
+ curr = json_object_array_get_idx(arr, i);
+ CHKiRet(invokeForeachBodyWith(stmt, curr, pMsg, pWti));
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+static rsRetVal
+callForeachObject(struct cnfstmt *stmt, json_object *arr, smsg_t *pMsg, wti_t *pWti) {
+ json_object *entry = NULL;
+ json_object *key = NULL;
+ const char **keys = NULL;
+ json_object *curr = NULL;
+ const char **curr_key;
+ struct json_object_iterator it;
+ struct json_object_iterator itEnd;
+ DEFiRet;
+
+ int len = json_object_object_length(arr);
+ CHKmalloc(keys = calloc(len, sizeof(char*)));
+ curr_key = keys;
+ it = json_object_iter_begin(arr);
+ itEnd = json_object_iter_end(arr);
+ while (!json_object_iter_equal(&it, &itEnd)) {
+ *curr_key = json_object_iter_peek_name(&it);
+ curr_key++;
+ json_object_iter_next(&it);
+ }
+ CHKmalloc(entry = json_object_new_object());
+ for (int i = 0; i < len; i++) {
+ if (json_object_object_get_ex(arr, keys[i], &curr)) {
+ CHKmalloc(key = json_object_new_string(keys[i]));
+ json_object_object_add(entry, "key", key);
+ key = NULL;
+ json_object_object_add(entry, "value", json_object_get(curr));
+ CHKiRet(invokeForeachBodyWith(stmt, entry, pMsg, pWti));
+ }
+ }
+finalize_it:
+ if (keys != NULL) free(keys);
+ if (entry != NULL) json_object_put(entry);
+ /* "fix" Coverity scan issue CID 185393: key currently can NOT be NULL
+ * However, instead of just removing the
+ * if (key != NULL) json_object_put(key);
+ * we put an assertion in its place.
+ */
+ assert(key == NULL);
+
+ RETiRet;
+}
+
+static rsRetVal ATTR_NONNULL()
+execForeach(struct cnfstmt *const stmt, smsg_t *const pMsg, wti_t *const pWti)
+{
+ json_object *arr = NULL;
+ DEFiRet;
+
+ /* arr can either be an array or an associative-array (obj) */
+ arr = cnfexprEvalCollection(stmt->d.s_foreach.iter->collection, pMsg, pWti);
+
+ if (arr == NULL) {
+ DBGPRINTF("foreach loop skipped, as object to iterate upon is empty\n");
+ FINALIZE;
+ } else if (json_object_is_type(arr, json_type_array) && json_object_array_length(arr) > 0) {
+ CHKiRet(callForeachArray(stmt, arr, pMsg, pWti));
+ } else if (json_object_is_type(arr, json_type_object) && json_object_object_length(arr) > 0) {
+ CHKiRet(callForeachObject(stmt, arr, pMsg, pWti));
+ } else {
+ DBGPRINTF("foreach loop skipped, as object to iterate upon is empty or is not an array\n");
+ FINALIZE;
+ }
+ CHKiRet(msgDelJSON(pMsg, (uchar*)stmt->d.s_foreach.iter->var));
+
+finalize_it:
+ if (arr != NULL) json_object_put(arr);
+
+ RETiRet;
+}
+
+static rsRetVal
+execPRIFILT(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
+{
+ int bRet;
+ DEFiRet;
+ 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;
+
+ DBGPRINTF("PRIFILT condition result is %d\n", bRet);
+ if(bRet) {
+ if(stmt->d.s_prifilt.t_then != NULL)
+ CHKiRet(scriptExec(stmt->d.s_prifilt.t_then, pMsg, pWti));
+ } else {
+ if(stmt->d.s_prifilt.t_else != NULL)
+ CHKiRet(scriptExec(stmt->d.s_prifilt.t_else, pMsg, pWti));
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* helper to execPROPFILT(), as the evaluation itself is quite lengthy */
+static int
+evalPROPFILT(struct cnfstmt *stmt, smsg_t *pMsg)
+{
+ unsigned short pbMustBeFreed;
+ uchar *pszPropVal;
+ int bRet = 0;
+ rs_size_t propLen;
+
+ if(stmt->d.s_propfilt.prop.id == PROP_INVALID)
+ goto done;
+
+ pszPropVal = MsgGetProp(pMsg, NULL, &stmt->d.s_propfilt.prop,
+ &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;
+ case FIOP_NOP:
+ 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) {
+ if(stmt->d.s_propfilt.prop.id == PROP_CEE) {
+ DBGPRINTF("Filter: check for CEE property '%s' (value '%s') ",
+ stmt->d.s_propfilt.prop.name, pszPropVal);
+ } else if(stmt->d.s_propfilt.prop.id == PROP_LOCAL_VAR) {
+ DBGPRINTF("Filter: check for local var '%s' (value '%s') ",
+ stmt->d.s_propfilt.prop.name, pszPropVal);
+ } else if(stmt->d.s_propfilt.prop.id == PROP_GLOBAL_VAR) {
+ DBGPRINTF("Filter: check for global var '%s' (value '%s') ",
+ stmt->d.s_propfilt.prop.name, pszPropVal);
+ } else {
+ DBGPRINTF("Filter: check for property '%s' (value '%s') ",
+ propIDToName(stmt->d.s_propfilt.prop.id), 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;
+}
+
+static rsRetVal
+execPROPFILT(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti)
+{
+ sbool bRet;
+ DEFiRet;
+
+ bRet = evalPROPFILT(stmt, pMsg);
+ DBGPRINTF("PROPFILT condition result is %d\n", bRet);
+ if(bRet)
+ CHKiRet(scriptExec(stmt->d.s_propfilt.t_then, pMsg, pWti));
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal ATTR_NONNULL()
+execReloadLookupTable(struct cnfstmt *stmt)
+{
+ assert(stmt != NULL);
+ lookup_ref_t *t;
+ DEFiRet;
+ t = stmt->d.s_reload_lookup_table.table;
+ if (t == NULL) {
+ ABORT_FINALIZE(RS_RET_NONE);
+ }
+
+ iRet = lookupReload(t, stmt->d.s_reload_lookup_table.stub_value);
+ /* Note that reload dispatched above is performed asynchronously,
+ on a different thread. So rsRetVal it returns means it was triggered
+ successfully, and not that it was reloaded successfully. */
+
+finalize_it:
+ RETiRet;
+}
+
+/* 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.
+ * rgerhards, 2012-09-04
+ */
+static rsRetVal ATTR_NONNULL(2, 3)
+scriptExec(struct cnfstmt *const root, smsg_t *const pMsg, wti_t *const pWti)
+{
+ struct cnfstmt *stmt;
+ DEFiRet;
+
+ for(stmt = root ; stmt != NULL ; stmt = stmt->next) {
+ if(*pWti->pbShutdownImmediate) {
+ DBGPRINTF("scriptExec: ShutdownImmediate set, "
+ "force terminating\n");
+ ABORT_FINALIZE(RS_RET_FORCE_TERM);
+ }
+ if(Debug) {
+ cnfstmtPrintOnly(stmt, 2, 0);
+ }
+ switch(stmt->nodetype) {
+ case S_NOP:
+ break;
+ case S_STOP:
+ ABORT_FINALIZE(RS_RET_DISCARDMSG);
+ break;
+ case S_ACT:
+ CHKiRet(execAct(stmt, pMsg, pWti));
+ break;
+ case S_SET:
+ CHKiRet(execSet(stmt, pMsg, pWti));
+ break;
+ case S_UNSET:
+ CHKiRet(execUnset(stmt, pMsg));
+ break;
+ case S_CALL:
+ CHKiRet(execCall(stmt, pMsg, pWti));
+ break;
+ case S_CALL_INDIRECT:
+ CHKiRet(execCallIndirect(stmt, pMsg, pWti));
+ break;
+ case S_IF:
+ CHKiRet(execIf(stmt, pMsg, pWti));
+ break;
+ case S_FOREACH:
+ CHKiRet(execForeach(stmt, pMsg, pWti));
+ break;
+ case S_PRIFILT:
+ CHKiRet(execPRIFILT(stmt, pMsg, pWti));
+ break;
+ case S_PROPFILT:
+ CHKiRet(execPROPFILT(stmt, pMsg, pWti));
+ break;
+ case S_RELOAD_LOOKUP_TABLE:
+ CHKiRet(execReloadLookupTable(stmt));
+ break;
+ default:
+ dbgprintf("error: unknown stmt type %u during exec\n",
+ (unsigned) stmt->nodetype);
+ break;
+ }
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* Process (consume) a batch of messages. Calls the actions configured.
+ * This is called by MAIN queues.
+ */
+static rsRetVal
+processBatch(batch_t *pBatch, wti_t *pWti)
+{
+ int i;
+ smsg_t *pMsg;
+ ruleset_t *pRuleset;
+ rsRetVal localRet;
+ DEFiRet;
+
+ DBGPRINTF("processBATCH: batch of %d elements must be processed\n", pBatch->nElem);
+
+ wtiResetExecState(pWti, pBatch);
+
+ /* execution phase */
+ for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pWti->pbShutdownImmediate) ; ++i) {
+ pMsg = pBatch->pElem[i].pMsg;
+ DBGPRINTF("processBATCH: next msg %d: %.128s\n", i, pMsg->pszRawMsg);
+ pRuleset = (pMsg->pRuleset == NULL) ? runConf->rulesets.pDflt : pMsg->pRuleset;
+ localRet = scriptExec(pRuleset->root, pMsg, pWti);
+ /* the most important case here is that processing may be aborted
+ * due to pbShutdownImmediate, in which case we MUST NOT flag this
+ * message as committed. If we would do so, the message would
+ * potentially be lost.
+ */
+ if(localRet == RS_RET_OK)
+ batchSetElemState(pBatch, i, BATCH_STATE_COMM);
+ else if(localRet == RS_RET_SUSPENDED)
+ --i;
+ }
+
+ /* commit phase */
+ DBGPRINTF("END batch execution phase, entering to commit phase "
+ "[processed %d of %d messages]\n", i, batchNumMsgs(pBatch));
+ actionCommitAllDirect(pWti);
+
+ DBGPRINTF("processBATCH: batch of %d elements has been processed\n", pBatch->nElem);
+ 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, smsg_t *pMsg)
+{
+ return (pMsg->pRuleset == NULL) ? conf->rulesets.pDflt->pParserLst : pMsg->pRuleset->pParserLst;
+}
+
+
+/* Add a script block to the current ruleset */
+static void ATTR_NONNULL(1)
+addScript(ruleset_t *const pThis, struct cnfstmt *const script)
+{
+ if(script == NULL) /* happens for include() */
+ return;
+ 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) ? runConf->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);
+ENDobjDestruct(ruleset)
+
+
+/* helper for Destructor, shut down queue workers */
+DEFFUNC_llExecFunc(doShutdownQueueWorkers)
+{
+ DEFiRet;
+ ruleset_t *const pThis = (ruleset_t*) pData;
+ DBGPRINTF("shutting down queue workers for ruleset %p, name %s, queue %p\n",
+ pThis, pThis->pszName, pThis->pQueue);
+ ISOBJ_TYPE_assert(pThis, ruleset);
+ if(pThis->pQueue != NULL) {
+ qqueueShutdownWorkers(pThis->pQueue);
+ }
+ RETiRet;
+}
+/* helper for Destructor, shut down actions (cnfstmt's in general) */
+DEFFUNC_llExecFunc(doDestructCnfStmt)
+{
+ DEFiRet;
+ ruleset_t *const pThis = (ruleset_t*) pData;
+ DBGPRINTF("shutting down actions and conf stmts for ruleset %p, name %s\n",
+ pThis, pThis->pszName);
+ ISOBJ_TYPE_assert(pThis, ruleset);
+ cnfstmtDestructLst(pThis->root);
+ RETiRet;
+}
+/* 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;
+
+ DBGPRINTF("rulesetDestructAllActions\n");
+ /* we first need to stop all queue workers, else we
+ * may run into trouble with "call" statements calling
+ * into then-destroyed rulesets.
+ * see: https://github.com/rsyslog/rsyslog/issues/1122
+ */
+ DBGPRINTF("destructAllActions: queue shutdown\n");
+ llExecFunc(&(conf->rulesets.llRulesets), doShutdownQueueWorkers, NULL);
+ DBGPRINTF("destructAllActions: action and conf stmt shutdown\n");
+ llExecFunc(&(conf->rulesets.llRulesets), doDestructCnfStmt, NULL);
+
+ 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;
+}
+
+struct cnfstmt * removeNOPs(struct cnfstmt *root);
+static void
+rulesetOptimize(ruleset_t *pRuleset)
+{
+ if(Debug) {
+ dbgprintf("ruleset '%s' before optimization:\n",
+ pRuleset->pszName);
+ rulesetDebugPrint((ruleset_t*) pRuleset);
+ }
+ pRuleset->root = 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 rsRetVal
+doRulesetCreateQueue(rsconf_t *conf, int *pNewVal)
+{
+ uchar *rsname;
+ DEFiRet;
+
+ if(conf->rulesets.pCurr == NULL) {
+ 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) {
+ 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(loadConf->parsers.pParsLstRoot, &pParser, pName);
+ if(iRet == RS_RET_PARSER_NOT_FOUND) {
+ 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) {
+ 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:
+ free(pName); /* no longer needed */
+
+ RETiRet;
+}
+
+static rsRetVal
+rulesetAddParser(void __attribute__((unused)) *pVal, uchar *pName)
+{
+ return doRulesetAddParser(loadConf->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;
+ int qtype;
+ 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) {
+ LogError(0, RS_RET_RULESET_EXISTS,
+ "error: ruleset '%s' specified more than once",
+ rsName);
+ cnfstmtDestructLst(o->script);
+ ABORT_FINALIZE(RS_RET_RULESET_EXISTS);
+ } else if(localRet != RS_RET_NOT_FOUND) {
+ ABORT_FINALIZE(localRet);
+ }
+
+ CHKiRet(rulesetConstruct(&pRuleset));
+ if((localRet = rulesetSetName(pRuleset, rsName)) != RS_RET_OK) {
+ rulesetDestruct(&pRuleset);
+ ABORT_FINALIZE(localRet);
+ }
+ if((localRet = rulesetConstructFinalize(loadConf, pRuleset)) != RS_RET_OK) {
+ rulesetDestruct(&pRuleset);
+ ABORT_FINALIZE(localRet);
+ }
+ 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);
+ /* note parserName is freed in doRulesetAddParser()! */
+ }
+ }
+
+ /* pick up ruleset queue parameters */
+ if(queueCnfParamsSet(o->nvlst)) {
+ if(pRuleset->pszName == NULL) {
+ rsname = (uchar*) "[ruleset]";
+ qtype = pRuleset->pQueue->qType;
+ } else {
+ rsname = pRuleset->pszName;
+ qtype = 3;
+ }
+ DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s', mode %d\n",
+ rsname, qtype);
+ 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(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 */
+
+ /* 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)
diff --git a/runtime/ruleset.h b/runtime/ruleset.h
new file mode 100644
index 0000000..2b8caca
--- /dev/null
+++ b/runtime/ruleset.h
@@ -0,0 +1,106 @@
+/* The ruleset object.
+ *
+ * This implements rulesets within rsyslog.
+ *
+ * Copyright 2009-2021 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*, wti_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, smsg_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);
+ /* v8: changed processBatch interface */
+ENDinterface(ruleset)
+#define rulesetCURR_IF_VERSION 8 /* 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
+ */
+#define rulesetGetName(pRuleset) ((pRuleset)->pszName)
+
+/* returns 1 if the ruleset has a queue associated, 0 if not */
+#define rulesetHasQueue(pRuleset) ( ((pRuleset)->pQueue != NULL) \
+ && ((pRuleset)->pQueue->qType != QUEUETYPE_DIRECT) ? 1 : 0 )
+
+
+/* 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);
+rsRetVal activateRulesetQueues(void);
+
+/* Set a current rule set to already-known pointer */
+#define rulesetSetCurrRulesetPtr(pRuleset) (loadConf->rulesets.pCurr = (pRuleset))
+
+#endif /* #ifndef INCLUDED_RULESET_H */
diff --git a/runtime/sigprov.h b/runtime/sigprov.h
new file mode 100644
index 0000000..a1ff006
--- /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 0000000..95fee37
--- /dev/null
+++ b/runtime/srUtils.h
@@ -0,0 +1,106 @@
+/*! \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-2018 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
+
+#include <stdlib.h>
+#include <stdint.h>
+
+/* 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 {
+ const 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(const uchar *const szFile, const size_t lenFile, const mode_t mode,
+ const uid_t uid, const gid_t gid, const 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, int64_t 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);
+void seedRandomNumber(void);
+void seedRandomNumberForChild(void);
+#define MAX_RANDOM_NUMBER RAND_MAX
+long int randomNumber(void);
+long long currentTimeMills(void);
+rsRetVal ATTR_NONNULL() split_binary_parameters(uchar **const szBinary,
+ char ***const aParams, int *const iParams, es_str_t *const param_binary);
+
+#endif /* #ifndef __SRUTILS_H_INCLUDED__ */
diff --git a/runtime/srutils.c b/runtime/srutils.c
new file mode 100644
index 0000000..3369975
--- /dev/null
+++ b/runtime/srutils.c
@@ -0,0 +1,868 @@
+/**\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-2018 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 <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 <inttypes.h>
+#include <fcntl.h>
+
+#include "rsyslog.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "glbl.h"
+#include "rsconf.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},
+ {"ntp", (12<<3) }, /* NTP, perhaps BSD-specific? */
+ {"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(_AIX) /* AIXPORT : These are necessary for AIX */
+ { "caa", LOG_CAA },
+ { "aso", LOG_ASO },
+#endif
+#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},
+ {"invld", LOG_INVLD},
+ {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
+ * The likeliest scenario for a prolonged contest of creating the parent directiories
+ * is within our process space. This can happen with a high probability when two
+ * threads, that want to start logging to files within same directory tree, are
+ * started close to each other. We should fix what we can. -- nipakoo, 2017-11-25
+ */
+static int real_makeFileParentDirs(const uchar *const szFile, const size_t lenFile, const mode_t mode,
+ const uid_t uid, const gid_t gid, const int bFailOnChownFail)
+{
+ uchar *p;
+ uchar *pszWork;
+ size_t len;
+
+ assert(szFile != NULL);
+ assert(lenFile > 0);
+
+ len = lenFile + 1; /* add one for '\0'-byte */
+ if((pszWork = malloc(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';
+ int bErr = 0;
+ if(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) {
+ LogError(errno, RS_RET_DIR_CHOWN_ERROR,
+ "chown for directory '%s' failed", pszWork);
+ if(bFailOnChownFail) {
+ /* ignore if configured to do so */
+ bErr = 1;
+ }
+ }
+ }
+ } else if(errno != EEXIST) {
+ /* EEXIST is ok, means this component exists */
+ bErr = 1;
+ }
+
+ if(bErr) {
+ int eSave = errno;
+ free(pszWork);
+ errno = eSave;
+ return -1;
+ }
+ *p = '/';
+ }
+ free(pszWork);
+ return 0;
+}
+/* note: this small function is the stub for the brain-dead POSIX cancel handling */
+int makeFileParentDirs(const uchar *const szFile, const size_t lenFile, const mode_t mode,
+ const uid_t uid, const gid_t gid, const int bFailOnChownFail)
+{
+ static pthread_mutex_t mutParentDir = PTHREAD_MUTEX_INITIALIZER;
+ int r; /* needs to be declared OUTSIDE of pthread_cleanup... macros! */
+ pthread_mutex_lock(&mutParentDir);
+ pthread_cleanup_push(mutexCancelCleanup, &mutParentDir);
+
+ r = real_makeFileParentDirs(szFile, lenFile, mode, uid, gid, bFailOnChownFail);
+
+ pthread_mutex_unlock(&mutParentDir);
+ pthread_cleanup_pop(0);
+ return r;
+}
+
+
+/* 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) {
+ /* waitpid will fail with errno == ECHILD if the child process has already
+ been reaped by the rsyslogd main loop (see rsyslogd.c) */
+ int status;
+ if(waitpid(pid, &status, 0) == pid) {
+ glblReportChildProcessExit(runConf, program, pid, status);
+ } else 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");
+ fprintf(stderr, "exec program was '%s' with param '%s'\n", program, arg);
+ 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
+ */
+PRAGMA_DIAGNOSTIC_PUSH
+PRAGMA_IGNORE_Wformat_nonliteral
+rsRetVal genFileName(uchar **ppName, uchar *pDirName, size_t lenDirName, uchar *pFName,
+ size_t lenFName, int64_t 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%d" PRId64, lNumDigits);
+ lenBuf = snprintf((char*)szBuf, sizeof(szBuf), szFmtBuf, lNum);
+ } else
+ lenBuf = snprintf((char*)szBuf, sizeof(szBuf), ".%" PRId64, lNum);
+ }
+
+ lenName = lenDirName + 1 + lenFName + lenBuf + 1; /* last +1 for \0 char! */
+ if((pName = malloc(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;
+}
+PRAGMA_DIAGNOSTIC_POP
+
+/* 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
+
+ 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;
+ }
+ return RS_RET_OK; /* so far, this is static... */
+}
+
+long long
+currentTimeMills(void)
+{
+ struct timespec tm;
+# if _POSIX_TIMERS <= 0
+ struct timeval tv;
+# endif
+
+# if _POSIX_TIMERS > 0
+ clock_gettime(CLOCK_REALTIME, &tm);
+# else
+ gettimeofday(&tv, NULL);
+ tm.tv_sec = tv.tv_sec;
+ tm.tv_nsec = tv.tv_usec * 1000;
+# endif
+
+ return ((long long) tm.tv_sec) * 1000 + (tm.tv_nsec / 1000000);
+}
+
+
+/* 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
+
+ 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;
+
+ return iTimeout;
+}
+
+
+/* cancellation cleanup handler - frees provided mutex
+ * rgerhards, 2008-01-14
+ */
+void
+mutexCancelCleanup(void *arg)
+{
+ assert(arg != NULL);
+ d_pthread_mutex_unlock((pthread_mutex_t*) arg);
+}
+
+
+/* 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;
+
+ tvSelectTimeout.tv_sec = iSeconds;
+ tvSelectTimeout.tv_usec = iuSeconds; /* micro seconds */
+ select(0, NULL, NULL, NULL, &tvSelectTimeout);
+}
+
+
+/* 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 occurred.
+ *
+ * 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 == '{') &&
+ (p == str || *(p-1) != '\\')) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static void seedRandomInsecureNumber(void)
+{
+ struct timespec t;
+ timeoutComp(&t, 0);
+ long long x = t.tv_sec * 3 + t.tv_nsec * 2;
+ srandom((unsigned int) x);
+}
+
+static long int randomInsecureNumber(void)
+{
+ return random();
+}
+
+#ifdef OS_LINUX
+static int fdURandom = -1;
+void seedRandomNumber(void)
+{
+ if(fdURandom >= 0) {
+ /* Already opened. */
+ return;
+ }
+ fdURandom = open("/dev/urandom", O_RDONLY);
+ if(fdURandom == -1) {
+ LogError(errno, RS_RET_IO_ERROR, "failed to seed random number generation,"
+ " will use fallback (open urandom failed)");
+ seedRandomInsecureNumber();
+ }
+}
+
+void seedRandomNumberForChild(void)
+{
+ /* The file descriptor inherited from our parent will have been closed after
+ * the fork. Discard this and call seedRandomNumber() to open /dev/urandom
+ * again.
+ */
+ fdURandom = -1;
+ seedRandomNumber();
+}
+
+long int randomNumber(void)
+{
+ long int ret;
+ if(fdURandom >= 0) {
+ if(read(fdURandom, &ret, sizeof(long int)) == -1) {
+ LogError(errno, RS_RET_IO_ERROR, "failed to generate random number, will"
+ " use fallback (read urandom failed)");
+ ret = randomInsecureNumber();
+ }
+ } else {
+ ret = randomInsecureNumber();
+ }
+ return ret;
+}
+#else
+void seedRandomNumber(void)
+{
+ seedRandomInsecureNumber();
+}
+
+void seedRandomNumberForChild(void)
+{
+ seedRandomNumber();
+}
+
+long int randomNumber(void)
+{
+ return randomInsecureNumber();
+}
+#endif
+
+
+/* process "binary" parameters where this is needed to execute
+ * programs (namely mmexternal and omprog).
+ * Most importantly, split them into argv[] and get the binary name
+ */
+rsRetVal ATTR_NONNULL()
+split_binary_parameters(uchar **const szBinary, char ***const __restrict__ aParams,
+ int *const iParams, es_str_t *const param_binary)
+{
+ es_size_t iCnt;
+ es_size_t iStr;
+ int iPrm;
+ es_str_t *estrParams = NULL;
+ es_str_t *estrBinary = param_binary;
+ es_str_t *estrTmp = NULL;
+ uchar *c;
+ int bInQuotes;
+ DEFiRet;
+ assert(iParams != NULL);
+ assert(param_binary != NULL);
+
+ /* Search for end of binary name */
+ c = es_getBufAddr(param_binary);
+ iCnt = 0;
+ while(iCnt < es_strlen(param_binary) ) {
+ if (c[iCnt] == ' ') {
+ /* Split binary name from parameters */
+ estrBinary = es_newStrFromSubStr( param_binary, 0, iCnt);
+ estrParams = es_newStrFromSubStr( param_binary, iCnt+1,
+ es_strlen(param_binary));
+ break;
+ }
+ iCnt++;
+ }
+ *szBinary = (uchar*)es_str2cstr(estrBinary, NULL);
+ DBGPRINTF("szBinary = '%s'\n", *szBinary);
+
+ *iParams = 1; /* we always have argv[0] */
+ /* count size of argv[] */
+ if (estrParams != NULL) {
+ (*iParams)++; /* last parameter is not counted in loop below! */
+ if(Debug) {
+ char *params = es_str2cstr(estrParams, NULL);
+ dbgprintf("szParams = '%s'\n", params);
+ free(params);
+ }
+ c = es_getBufAddr(estrParams);
+ for(iCnt = 0 ; iCnt < es_strlen(estrParams) ; ++iCnt) {
+ if (c[iCnt] == ' ' && c[iCnt-1] != '\\')
+ (*iParams)++;
+ }
+ }
+ DBGPRINTF("iParams %d (+1 for NULL terminator)\n", *iParams);
+
+ /* create argv[] */
+ CHKmalloc(*aParams = malloc((*iParams + 1) * sizeof(char*)));
+ iPrm = 0;
+ bInQuotes = FALSE;
+ /* Set first parameter to binary */
+ (*aParams)[iPrm] = strdup((char*)*szBinary);
+ iPrm++;
+ if (estrParams != NULL) {
+ iCnt = iStr = 0;
+ c = es_getBufAddr(estrParams); /* Reset to beginning */
+ while(iCnt < es_strlen(estrParams) ) {
+ if ( c[iCnt] == ' ' && !bInQuotes ) {
+ estrTmp = es_newStrFromSubStr( estrParams, iStr, iCnt-iStr);
+ } else if ( iCnt+1 >= es_strlen(estrParams) ) {
+ estrTmp = es_newStrFromSubStr( estrParams, iStr, iCnt-iStr+1);
+ } else if (c[iCnt] == '"') {
+ bInQuotes = !bInQuotes;
+ }
+
+ if ( estrTmp != NULL ) {
+ (*aParams)[iPrm] = es_str2cstr(estrTmp, NULL);
+ iStr = iCnt+1; /* Set new start */
+ DBGPRINTF("Param (%d): '%s'\n", iPrm, (*aParams)[iPrm]);
+ es_deleteStr( estrTmp );
+ estrTmp = NULL;
+ iPrm++;
+ }
+ iCnt++;
+ }
+ }
+ (*aParams)[iPrm] = NULL; /* NULL per argv[] convention */
+
+finalize_it:
+ if(estrBinary != param_binary) {
+ es_deleteStr(estrBinary);
+ }
+ if(estrParams != NULL) {
+ es_deleteStr(estrParams);
+ }
+ RETiRet;
+}
diff --git a/runtime/statsobj.c b/runtime/statsobj.c
new file mode 100644
index 0000000..ad959f5
--- /dev/null
+++ b/runtime/statsobj.c
@@ -0,0 +1,761 @@
+/* The statsobj object.
+ *
+ * This object provides a statistics-gathering facility inside rsyslog. This
+ * functionality will be pragmatically implemented and extended.
+ *
+ * Copyright 2010-2021 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 <inttypes.h>
+#include <pthread.h>
+#include <errno.h>
+#include <time.h>
+#include <assert.h>
+#include <json.h>
+
+#include "rsyslog.h"
+#include "unicode-helper.h"
+#include "obj.h"
+#include "statsobj.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+#include "errmsg.h"
+#include "hashtable.h"
+#include "hashtable_itr.h"
+#include "rsconf.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;
+static pthread_mutex_t mutSenders;
+
+static struct hashtable *stats_senders = NULL;
+
+/* ------------------------------ statsobj linked list maintenance ------------------------------ */
+
+static void
+addToObjList(statsobj_t *pThis)
+{
+ pthread_mutex_lock(&mutStats);
+ if (pThis->flags && STATSOBJ_FLAG_DO_PREPEND) {
+ pThis->next = objRoot;
+ if (objRoot != NULL) {
+ objRoot->prev = pThis;
+ }
+ objRoot = pThis;
+ if (objLast == NULL)
+ objLast = pThis;
+ } else {
+ pThis->prev = objLast;
+ if(objLast != NULL)
+ objLast->next = pThis;
+ objLast = pThis;
+ if(objRoot == NULL)
+ objRoot = pThis;
+ }
+ pthread_mutex_unlock(&mutStats);
+}
+
+
+static 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 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;
+ pThis->read_notifier = NULL;
+ pThis->flags = 0;
+ENDobjConstruct(statsobj)
+
+
+/* ConstructionFinalizer
+ */
+static rsRetVal
+statsobjConstructFinalize(statsobj_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, statsobj);
+ addToObjList(pThis);
+ RETiRet;
+}
+
+/* set read_notifier (a function which is invoked after stats are read).
+ */
+static rsRetVal
+setReadNotifier(statsobj_t *pThis, statsobj_read_notifier_t notifier, void* ctx)
+{
+ DEFiRet;
+ pThis->read_notifier = notifier;
+ pThis->read_notifier_ctx = ctx;
+ RETiRet;
+}
+
+
+/* set origin (module name, etc).
+ * Note that we make our own copy of the memory, caller is
+ * responsible to free up name it passes in (if required).
+ */
+static rsRetVal
+setOrigin(statsobj_t *pThis, uchar *origin)
+{
+ DEFiRet;
+ CHKmalloc(pThis->origin = ustrdup(origin));
+finalize_it:
+ 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;
+}
+
+static void
+setStatsObjFlags(statsobj_t *pThis, int flags) {
+ pThis->flags = flags;
+}
+
+static rsRetVal
+setReportingNamespace(statsobj_t *pThis, uchar *ns)
+{
+ DEFiRet;
+ CHKmalloc(pThis->reporting_ns = ustrdup(ns));
+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
+addManagedCounter(statsobj_t *pThis, const uchar *ctrName, statsCtrType_t ctrType, int8_t flags, void *pCtr,
+ctr_t **entryRef, int8_t linked)
+{
+ ctr_t *ctr;
+ DEFiRet;
+
+ *entryRef = NULL;
+
+ CHKmalloc(ctr = calloc(1, sizeof(ctr_t)));
+ ctr->next = NULL;
+ ctr->prev = NULL;
+ if((ctr->name = ustrdup(ctrName)) == NULL) {
+ DBGPRINTF("addCounter: OOM in strdup()\n");
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ ctr->flags = flags;
+ ctr->ctrType = ctrType;
+ switch(ctrType) {
+ case ctrType_IntCtr:
+ ctr->val.pIntCtr = (intctr_t*) pCtr;
+ break;
+ case ctrType_Int:
+ ctr->val.pInt = (int*) pCtr;
+ break;
+ }
+ if (linked) {
+ addCtrToList(pThis, ctr);
+ }
+ *entryRef = ctr;
+
+finalize_it:
+ if (iRet != RS_RET_OK) {
+ if (ctr != NULL) {
+ free(ctr->name);
+ free(ctr);
+ }
+ }
+ RETiRet;
+}
+
+static void
+addPreCreatedCounter(statsobj_t *pThis, ctr_t *pCtr)
+{
+ pCtr->next = NULL;
+ pCtr->prev = NULL;
+ addCtrToList(pThis, pCtr);
+}
+
+static rsRetVal
+addCounter(statsobj_t *pThis, const uchar *ctrName, statsCtrType_t ctrType, int8_t flags, void *pCtr)
+{
+ ctr_t *ctr;
+ DEFiRet;
+ iRet = addManagedCounter(pThis, ctrName, ctrType, flags, pCtr, &ctr, 1);
+ RETiRet;
+}
+
+static void
+destructUnlinkedCounter(ctr_t *ctr) {
+ free(ctr->name);
+ free(ctr);
+}
+
+static void
+destructCounter(statsobj_t *pThis, ctr_t *pCtr)
+{
+ pthread_mutex_lock(&pThis->mutCtr);
+ if (pCtr->prev != NULL) {
+ pCtr->prev->next = pCtr->next;
+ }
+ if (pCtr->next != NULL) {
+ pCtr->next->prev = pCtr->prev;
+ }
+ if (pThis->ctrLast == pCtr) {
+ pThis->ctrLast = pCtr->prev;
+ }
+ if (pThis->ctrRoot == pCtr) {
+ pThis->ctrRoot = pCtr->next;
+ }
+ pthread_mutex_unlock(&pThis->mutCtr);
+ destructUnlinkedCounter(pCtr);
+}
+
+static void
+resetResettableCtr(ctr_t *pCtr, int8_t bResetCtrs)
+{
+ if ((bResetCtrs && (pCtr->flags & CTR_FLAG_RESETTABLE)) ||
+ (pCtr->flags & CTR_FLAG_MUST_RESET)) {
+ switch(pCtr->ctrType) {
+ case ctrType_IntCtr:
+ *(pCtr->val.pIntCtr) = 0;
+ break;
+ case ctrType_Int:
+ *(pCtr->val.pInt) = 0;
+ break;
+ }
+ }
+}
+
+static rsRetVal
+addCtrForReporting(json_object *to, const uchar* field_name, intctr_t value) {
+ json_object *v;
+ DEFiRet;
+
+ /*We should migrate libfastjson to support uint64_t in addition to int64_t.
+ Although no counter is likely to grow to int64 max-value, this is theoritically
+ incorrect (as intctr_t is uint64)*/
+ CHKmalloc(v = json_object_new_int64((int64_t) value));
+
+ json_object_object_add(to, (const char*) field_name, v);
+finalize_it:
+ /* v cannot be NULL in error case, as this would only happen during malloc fail,
+ * which itself sets it to NULL -- so not doing cleanup here.
+ */
+ RETiRet;
+}
+
+static rsRetVal
+addContextForReporting(json_object *to, const uchar* field_name, const uchar* value) {
+ json_object *v;
+ DEFiRet;
+
+ CHKmalloc(v = json_object_new_string((const char*) value));
+
+ json_object_object_add(to, (const char*) field_name, v);
+finalize_it:
+ RETiRet;
+}
+
+static intctr_t
+accumulatedValue(ctr_t *pCtr) {
+ switch(pCtr->ctrType) {
+ case ctrType_IntCtr:
+ return *(pCtr->val.pIntCtr);
+ case ctrType_Int:
+ return *(pCtr->val.pInt);
+ }
+ return -1;
+}
+
+
+/* get all the object's countes together as CEE. */
+static rsRetVal
+getStatsLineCEE(statsobj_t *pThis, cstr_t **ppcstr, const statsFmtType_t fmt, const int8_t bResetCtrs)
+{
+ cstr_t *pcstr = NULL;
+ ctr_t *pCtr;
+ json_object *root, *values;
+ int locked = 0;
+ DEFiRet;
+
+ root = values = NULL;
+
+ CHKiRet(cstrConstruct(&pcstr));
+
+ if (fmt == statsFmt_CEE)
+ CHKiRet(rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(CONST_CEE_COOKIE" "), CONST_LEN_CEE_COOKIE + 1));
+
+ CHKmalloc(root = json_object_new_object());
+
+ CHKiRet(addContextForReporting(root, UCHAR_CONSTANT("name"), pThis->name));
+
+ if(pThis->origin != NULL) {
+ CHKiRet(addContextForReporting(root, UCHAR_CONSTANT("origin"), pThis->origin));
+ }
+
+ if (pThis->reporting_ns == NULL) {
+ values = json_object_get(root);
+ } else {
+ CHKmalloc(values = json_object_new_object());
+ json_object_object_add(root, (const char*) pThis->reporting_ns, json_object_get(values));
+ }
+
+ /* now add all counters to this line */
+ pthread_mutex_lock(&pThis->mutCtr);
+ locked = 1;
+ for(pCtr = pThis->ctrRoot ; pCtr != NULL ; pCtr = pCtr->next) {
+ if (fmt == statsFmt_JSON_ES) {
+ /* work-around for broken Elasticsearch JSON implementation:
+ * we need to replace dots by a different char, we use bang.
+ * Note: ES 2.0 does not longer accept dot in name
+ */
+ uchar esbuf[256];
+ strncpy((char*)esbuf, (char*)pCtr->name, sizeof(esbuf)-1);
+ esbuf[sizeof(esbuf)-1] = '\0';
+ for(uchar *c = esbuf ; *c ; ++c) {
+ if(*c == '.')
+ *c = '!';
+ }
+ CHKiRet(addCtrForReporting(values, esbuf, accumulatedValue(pCtr)));
+ } else {
+ CHKiRet(addCtrForReporting(values, pCtr->name, accumulatedValue(pCtr)));
+ }
+ resetResettableCtr(pCtr, bResetCtrs);
+ }
+ pthread_mutex_unlock(&pThis->mutCtr);
+ locked = 0;
+ CHKiRet(rsCStrAppendStr(pcstr, (const uchar*) json_object_to_json_string(root)));
+
+ cstrFinalize(pcstr);
+ *ppcstr = pcstr;
+ pcstr = NULL;
+
+finalize_it:
+ if(locked) {
+ pthread_mutex_unlock(&pThis->mutCtr);
+ }
+
+ if (pcstr != NULL) {
+ cstrDestruct(&pcstr);
+ }
+ if (root != NULL) {
+ json_object_put(root);
+ }
+ if (values != NULL) {
+ json_object_put(values);
+ }
+
+ RETiRet;
+}
+
+/* get all the object's countes together with object name as one line.
+ */
+static rsRetVal
+getStatsLine(statsobj_t *pThis, cstr_t **ppcstr, int8_t bResetCtrs)
+{
+ cstr_t *pcstr;
+ ctr_t *pCtr;
+ DEFiRet;
+
+ CHKiRet(cstrConstruct(&pcstr));
+ rsCStrAppendStr(pcstr, pThis->name);
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT(": "), 2);
+
+ if(pThis->origin != NULL) {
+ rsCStrAppendStrWithLen(pcstr, UCHAR_CONSTANT("origin="), 7);
+ rsCStrAppendStr(pcstr, pThis->origin);
+ cstrAppendChar(pcstr, ' ');
+ }
+
+ /* 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, ' ');
+ resetResettableCtr(pCtr, bResetCtrs);
+ }
+ pthread_mutex_unlock(&pThis->mutCtr);
+
+ cstrFinalize(pcstr);
+ *ppcstr = pcstr;
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* this function obtains all sender stats. hlper to getAllStatsLines()
+ * We need to keep this looked to avoid resizing of the hash table
+ * (what could otherwise cause a segfault).
+ */
+static void
+getSenderStats(rsRetVal(*cb)(void*, const char*),
+ void *usrptr,
+ statsFmtType_t fmt,
+ const int8_t bResetCtrs)
+{
+ struct hashtable_itr *itr = NULL;
+ struct sender_stats *stat;
+ char fmtbuf[2048];
+
+ pthread_mutex_lock(&mutSenders);
+
+ /* Iterator constructor only returns a valid iterator if
+ * the hashtable is not empty
+ */
+ if(hashtable_count(stats_senders) > 0) {
+ itr = hashtable_iterator(stats_senders);
+ do {
+ stat = (struct sender_stats*)hashtable_iterator_value(itr);
+ if(fmt == statsFmt_Legacy) {
+ snprintf(fmtbuf, sizeof(fmtbuf),
+ "_sender_stat: sender=%s messages=%"
+ PRIu64,
+ stat->sender, stat->nMsgs);
+ } else {
+ snprintf(fmtbuf, sizeof(fmtbuf),
+ "{ \"name\":\"_sender_stat\", "
+ "\"origin\":\"impstats\", "
+ "\"sender\":\"%s\", \"messages\":%"
+ PRIu64 "}",
+ stat->sender, stat->nMsgs);
+ }
+ fmtbuf[sizeof(fmtbuf)-1] = '\0';
+ cb(usrptr, fmtbuf);
+ if(bResetCtrs)
+ stat->nMsgs = 0;
+ } while (hashtable_iterator_advance(itr));
+ }
+
+ free(itr);
+ pthread_mutex_unlock(&mutSenders);
+}
+
+
+/* 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*, const char*), void *const usrptr, statsFmtType_t fmt, const int8_t bResetCtrs)
+{
+ statsobj_t *o;
+ cstr_t *cstr = NULL;
+ DEFiRet;
+
+ for(o = objRoot ; o != NULL ; o = o->next) {
+ switch(fmt) {
+ case statsFmt_Legacy:
+ CHKiRet(getStatsLine(o, &cstr, bResetCtrs));
+ break;
+ case statsFmt_CEE:
+ case statsFmt_JSON:
+ case statsFmt_JSON_ES:
+ CHKiRet(getStatsLineCEE(o, &cstr, fmt, bResetCtrs));
+ break;
+ }
+ CHKiRet(cb(usrptr, (const char*)cstrGetSzStrNoNULL(cstr)));
+ rsCStrDestruct(&cstr);
+ if (o->read_notifier != NULL) {
+ o->read_notifier(o, o->read_notifier_ctx);
+ }
+ }
+
+ getSenderStats(cb, usrptr, fmt, bResetCtrs);
+
+finalize_it:
+ if(cstr != NULL) {
+ rsCStrDestruct(&cstr);
+ }
+ RETiRet;
+}
+
+/* Enable statistics gathering. currently there is no function to disable it
+ * again, as this is right now not needed.
+ */
+static rsRetVal
+enableStats(void)
+{
+ GatherStats = 1;
+ return RS_RET_OK;
+}
+
+
+rsRetVal
+statsRecordSender(const uchar *sender, unsigned nMsgs, time_t lastSeen)
+{
+ struct sender_stats *stat;
+ int mustUnlock = 0;
+ DEFiRet;
+
+ if(stats_senders == NULL)
+ FINALIZE; /* unlikely: we could not init our hash table */
+
+ pthread_mutex_lock(&mutSenders);
+ mustUnlock = 1;
+ stat = hashtable_search(stats_senders, (void*)sender);
+ if(stat == NULL) {
+ DBGPRINTF("statsRecordSender: sender '%s' not found, adding\n",
+ sender);
+ CHKmalloc(stat = calloc(1, sizeof(struct sender_stats)));
+ stat->sender = (const uchar*)strdup((const char*)sender);
+ stat->nMsgs = 0;
+ if(runConf->globals.reportNewSenders) {
+ LogMsg(0, RS_RET_SENDER_APPEARED,
+ LOG_INFO, "new sender '%s'", stat->sender);
+ }
+ if(hashtable_insert(stats_senders, (void*)stat->sender,
+ (void*)stat) == 0) {
+ LogError(errno, RS_RET_INTERNAL_ERROR,
+ "error inserting sender '%s' into sender "
+ "hash table", sender);
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+ }
+
+ stat->nMsgs += nMsgs;
+ stat->lastSeen = lastSeen;
+ DBGPRINTF("DDDDD: statsRecordSender: '%s', nmsgs %u [%llu], lastSeen %llu\n", sender, nMsgs,
+ (long long unsigned) stat->nMsgs, (long long unsigned) lastSeen);
+
+finalize_it:
+ if(mustUnlock)
+ pthread_mutex_unlock(&mutSenders);
+ RETiRet;
+}
+
+static ctr_t*
+unlinkAllCounters(statsobj_t *pThis) {
+ ctr_t *ctr;
+ pthread_mutex_lock(&pThis->mutCtr);
+ ctr = pThis->ctrRoot;
+ pThis->ctrLast = NULL;
+ pThis->ctrRoot = NULL;
+ pthread_mutex_unlock(&pThis->mutCtr);
+ return ctr;
+}
+
+static void
+destructUnlinkedCounters(ctr_t *ctr) {
+ ctr_t *ctrToDel;
+
+ while(ctr != NULL) {
+ ctrToDel = ctr;
+ ctr = ctr->next;
+ destructUnlinkedCounter(ctrToDel);
+ }
+}
+
+/* check if a sender has not sent info to us for an extended period
+ * of time.
+ */
+void
+checkGoneAwaySenders(const time_t tCurr)
+{
+ struct hashtable_itr *itr = NULL;
+ struct sender_stats *stat;
+ const time_t rqdLast = tCurr - runConf->globals.senderStatsTimeout;
+ struct tm tm;
+
+ pthread_mutex_lock(&mutSenders);
+
+ /* Iterator constructor only returns a valid iterator if
+ * the hashtable is not empty
+ */
+ if(hashtable_count(stats_senders) > 0) {
+ itr = hashtable_iterator(stats_senders);
+ do {
+ stat = (struct sender_stats*)hashtable_iterator_value(itr);
+ if(stat->lastSeen < rqdLast) {
+ if(runConf->globals.reportGoneAwaySenders) {
+ localtime_r(&stat->lastSeen, &tm);
+ LogMsg(0, RS_RET_SENDER_GONE_AWAY,
+ LOG_WARNING,
+ "removing sender '%s' from connection "
+ "table, last seen at "
+ "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d",
+ stat->sender,
+ tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
+ tm.tm_hour, tm.tm_min, tm.tm_sec);
+ }
+ hashtable_remove(stats_senders, (void*)stat->sender);
+ }
+ } while (hashtable_iterator_advance(itr));
+ }
+
+ pthread_mutex_unlock(&mutSenders);
+ free(itr);
+}
+
+/* destructor for the statsobj object */
+BEGINobjDestruct(statsobj) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(statsobj)
+ removeFromObjList(pThis);
+
+ /* destruct counters */
+ destructUnlinkedCounters(unlinkAllCounters(pThis));
+
+ pthread_mutex_destroy(&pThis->mutCtr);
+ free(pThis->name);
+ free(pThis->origin);
+ free(pThis->reporting_ns);
+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->SetOrigin = setOrigin;
+ pIf->SetReadNotifier = setReadNotifier;
+ pIf->SetReportingNamespace = setReportingNamespace;
+ pIf->SetStatsObjFlags = setStatsObjFlags;
+ pIf->GetAllStatsLines = getAllStatsLines;
+ pIf->AddCounter = addCounter;
+ pIf->AddManagedCounter = addManagedCounter;
+ pIf->AddPreCreatedCtr = addPreCreatedCounter;
+ pIf->DestructCounter = destructCounter;
+ pIf->DestructUnlinkedCounter = destructUnlinkedCounter;
+ pIf->UnlinkAllCounters = unlinkAllCounters;
+ 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);
+ pthread_mutex_init(&mutSenders, NULL);
+
+ if((stats_senders = create_hashtable(100, hash_from_string, key_equals_string, NULL)) == NULL) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "error trying to initialize hash-table "
+ "for sender table. Sender statistics and warnings are disabled.");
+ ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
+ }
+ENDObjClassInit(statsobj)
+
+/* Exit the class.
+ */
+BEGINObjClassExit(statsobj, OBJ_IS_CORE_MODULE) /* class, version */
+ /* release objects we no longer need */
+ pthread_mutex_destroy(&mutStats);
+ pthread_mutex_destroy(&mutSenders);
+ hashtable_destroy(stats_senders, 1);
+ENDObjClassExit(statsobj)
diff --git a/runtime/statsobj.h b/runtime/statsobj.h
new file mode 100644
index 0000000..d57cd0e
--- /dev/null
+++ b/runtime/statsobj.h
@@ -0,0 +1,203 @@
+/* The statsobj object.
+ *
+ * Copyright 2010-2016 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_JSON_ES,
+ statsFmt_CEE
+} statsFmtType_t;
+
+/* counter flags */
+#define CTR_FLAG_NONE 0
+#define CTR_FLAG_RESETTABLE 1
+#define CTR_FLAG_MUST_RESET 2
+
+/* statsobj flags */
+#define STATSOBJ_FLAG_NONE 0
+#define STATSOBJ_FLAG_DO_PREPEND 1
+
+/* helper entity, the counter */
+typedef struct ctr_s {
+ uchar *name;
+ statsCtrType_t ctrType;
+ union {
+ intctr_t *pIntCtr;
+ int *pInt;
+ } val;
+ int8_t flags;
+ 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;
+ uchar *origin;
+ uchar *reporting_ns;
+ statsobj_read_notifier_t read_notifier;
+ void *read_notifier_ctx;
+ pthread_mutex_t mutCtr; /* to guard counter linked-list ops */
+ ctr_t *ctrRoot; /* doubly-linked list of statsobj counters */
+ ctr_t *ctrLast;
+ int flags;
+ /* used to link ourselves together */
+ statsobj_t *prev;
+ statsobj_t *next;
+};
+
+struct sender_stats {
+ const uchar *sender;
+ uint64_t nMsgs;
+ time_t lastSeen;
+};
+
+
+/* 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 (*SetOrigin)(statsobj_t *pThis, uchar *name); /* added v12, 2014-09-08 */
+ rsRetVal (*SetReadNotifier)(statsobj_t *pThis, statsobj_read_notifier_t notifier, void* ctx);
+ rsRetVal (*SetReportingNamespace)(statsobj_t *pThis, uchar *ns);
+ void (*SetStatsObjFlags)(statsobj_t *pThis, int flags);
+ rsRetVal (*GetAllStatsLines)(rsRetVal(*cb)(void*, const char*), void *usrptr, statsFmtType_t fmt,
+ int8_t bResetCtr);
+ rsRetVal (*AddCounter)(statsobj_t *pThis, const uchar *ctrName, statsCtrType_t ctrType, int8_t flags,
+ void *pCtr);
+ rsRetVal (*AddManagedCounter)(statsobj_t *pThis, const uchar *ctrName, statsCtrType_t ctrType, int8_t flags,
+ void *pCtr, ctr_t **ref, int8_t linked);
+ void (*AddPreCreatedCtr)(statsobj_t *pThis, ctr_t *ctr);
+ void (*DestructCounter)(statsobj_t *pThis, ctr_t *ref);
+ void (*DestructUnlinkedCounter)(ctr_t *ctr);
+ ctr_t* (*UnlinkAllCounters)(statsobj_t *pThis);
+ rsRetVal (*EnableStats)(void);
+ENDinterface(statsobj)
+#define statsobjCURR_IF_VERSION 13 /* 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
+ * v11, 2013-09-07: - add "flags" to AddCounter API
+ * - GetAllStatsLines got parameter telling if ctrs shall be reset
+ * v13, 2016-05-19: GetAllStatsLines cb data type changed (char* instead of cstr)
+ */
+
+
+/* prototypes */
+PROTOTYPEObj(statsobj);
+
+rsRetVal statsRecordSender(const uchar *sender, unsigned nMsgs, time_t lastSeen);
+/* checkGoneAwaySenders() is part of this module because all it needs is
+ * done by this module, so even though it's own processing is not directly
+ * related to stats, it makes sense to do it here... -- rgerhards, 2016-02-01
+ */
+void checkGoneAwaySenders(time_t);
+
+/* 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_ADD(ctr, mut, delta) \
+ if(GatherStats) \
+ ATOMIC_ADD_uint64(&ctr, &mut, delta);
+
+#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 0000000..73d63e4
--- /dev/null
+++ b/runtime/stream.c
@@ -0,0 +1,2533 @@
+/* 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-2022 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 <poll.h>
+#ifdef HAVE_SYS_PRCTL_H
+# include <sys/prctl.h>
+#endif
+
+#include "rsyslog.h"
+#include "stringbuf.h"
+#include "srUtils.h"
+#include "obj.h"
+#include "stream.h"
+#include "unicode-helper.h"
+#include "module-template.h"
+#include "errmsg.h"
+#include "zstdw.h"
+#include "cryprov.h"
+#include "datetime.h"
+#include "rsconf.h"
+
+/* some platforms do not have large file support :( */
+#ifndef O_LARGEFILE
+# define O_LARGEFILE 0
+#endif
+#ifndef HAVE_LSEEK64
+# define lseek64(fd, offset, whence) lseek(fd, offset, whence)
+#endif
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(zlibw)
+DEFobjCurrIf(zstdw)
+
+/* forward definitions */
+static rsRetVal strmFlushInternal(strm_t *pThis, int bFlushZip);
+static rsRetVal strmWrite(strm_t *__restrict__ const pThis, const uchar *__restrict__ const pBuf,
+ const size_t lenBuf);
+static rsRetVal strmOpenFile(strm_t *pThis);
+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 */
+
+
+/* note: this may return NULL if not line segment is currently set */
+// TODO: due to the cstrFinalize() this is not totally clean, albeit for our
+// current use case it does not hurt -- refactor! rgerhards, 2018-03-27
+const uchar * ATTR_NONNULL()
+strmGetPrevLineSegment(strm_t *const pThis)
+{
+ const uchar *ret = NULL;
+ if(pThis->prevLineSegment != NULL) {
+ cstrFinalize(pThis->prevLineSegment);
+ ret = rsCStrGetSzStrNoNULL(pThis->prevLineSegment);
+ }
+ return ret;
+}
+/* note: this may return NULL if not line segment is currently set */
+// TODO: due to the cstrFinalize() this is not totally clean, albeit for our
+// current use case it does not hurt -- refactor! rgerhards, 2018-03-27
+const uchar * ATTR_NONNULL()
+strmGetPrevMsgSegment(strm_t *const pThis)
+{
+ const uchar *ret = NULL;
+ if(pThis->prevMsgSegment != NULL) {
+ cstrFinalize(pThis->prevMsgSegment);
+ ret = rsCStrGetSzStrNoNULL(pThis->prevMsgSegment);
+ }
+ return ret;
+}
+
+
+int ATTR_NONNULL()
+strmGetPrevWasNL(const strm_t *const pThis)
+{
+ return pThis->bPrevWasNL;
+}
+
+
+/* output (current) file name for debug log purposes. Falls back to various
+ * levels of impreciseness if more precise name is not known.
+ */
+static const char *
+getFileDebugName(const strm_t *const pThis)
+{
+ return (pThis->pszCurrFName == NULL) ?
+ ((pThis->pszFName == NULL) ? "N/A" : (char*)pThis->pszFName)
+ : (const char*) pThis->pszCurrFName;
+}
+
+/* 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) {
+ LogError(0, RS_RET_ERR, "file size limit cmd for file '%s' "
+ "did no resolve situation\n", pszCurrFName);
+ } else {
+ LogError(0, RS_RET_ERR, "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;
+ case STREAMMMODE_INVALID:
+ 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;
+ }
+
+ if(pThis->bAsyncWrite)d_pthread_mutex_lock(&pThis->mut);
+ pThis->fd = open((char*)pThis->pszCurrFName, iFlags | O_LARGEFILE, pThis->tOpenMode);
+ if(pThis->bAsyncWrite) d_pthread_mutex_unlock(&pThis->mut);
+
+ const int errno_save = errno; /* dbgprintf can mangle it! */
+ DBGPRINTF("file '%s' opened as #%d with mode %d\n", pThis->pszCurrFName,
+ pThis->fd, (int) pThis->tOpenMode);
+ if(pThis->fd == -1) {
+ const rsRetVal errcode = (errno_save == ENOENT) ? RS_RET_FILE_NOT_FOUND
+ : RS_RET_FILE_OPEN_ERROR;
+ if(pThis->fileNotFoundError) {
+ if(pThis->noRepeatedErrorOutput == 0) {
+ LogError(errno_save, errcode, "file '%s': open error", pThis->pszCurrFName);
+ pThis->noRepeatedErrorOutput = 1;
+ }
+ } else {
+ DBGPRINTF("file '%s': open error", pThis->pszCurrFName);
+ }
+ ABORT_FINALIZE(errcode);
+ } else {
+ pThis->noRepeatedErrorOutput = 0;
+ }
+
+ 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("CheckFileChange: 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,
+ (long long unsigned) statName.st_size,
+ (long long unsigned) 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;
+ off_t offset;
+
+ assert(pThis != NULL);
+
+ if(pThis->fd != -1)
+ ABORT_FINALIZE(RS_RET_OK);
+
+ free(pThis->pszCurrFName);
+ 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;
+ pThis->iBufPtrMax = 0;
+ CHKiRet(getFileSize(pThis->pszCurrFName, &offset));
+ if(pThis->tOperationsMode == STREAMMODE_WRITE_APPEND) {
+ pThis->iCurrOffs = offset;
+ } else if(pThis->tOperationsMode == STREAMMODE_WRITE_TRUNC) {
+ if(offset != 0) {
+ LogError(0, 0, "file '%s' opened for truncate write, but "
+ "already contains %zd bytes\n",
+ pThis->pszCurrFName, (ssize_t) 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) {
+ assert(pThis->fd != -1);
+ } else {
+ 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 void
+strmWaitAsyncWriterDone(strm_t *pThis)
+{
+ 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);
+ }
+ }
+}
+
+/* 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 void
+stopWriter(strm_t *const pThis)
+{
+ pThis->bStopWriter = 1;
+ pthread_cond_signal(&pThis->notEmpty);
+ d_pthread_mutex_unlock(&pThis->mut);
+ pthread_join(pThis->writerThreadID, NULL);
+}
+
+
+
+/* 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, bDeleteOnClose %d\n", pThis->fd,
+ getFileDebugName(pThis), pThis->bDeleteOnClose);
+
+ if(pThis->tOperationsMode != STREAMMODE_READ) {
+ if(pThis->bAsyncWrite) {
+ strmWaitAsyncWriterDone(pThis);
+ }
+ strmFlushInternal(pThis, 0);
+ if(pThis->iZipLevel) {
+ doZipFinish(pThis);
+ }
+ if(pThis->bAsyncWrite) {
+ stopWriter(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) {
+ const rsRetVal localRet = strmOpenFile(pThis);
+ if(localRet != RS_RET_OK) {
+ LogError(0, localRet, "could not open file %s, this "
+ "may result in problems with encryption - "
+ "unfortunately, we cannot do anything against "
+ "this.", pThis->pszCurrFName);
+ }
+ }
+
+ /* the file may already be closed (or never have opened), so guard
+ * against this. -- rgerhards, 2010-03-19
+ */
+ if(pThis->fd != -1) {
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) closing\n",
+ pThis->fd, getFileDebugName(pThis));
+ 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);
+ }
+ }
+
+ pThis->iCurrOffs = 0; /* we are back at begin of file */
+
+finalize_it:
+ free(pThis->pszCurrFName);
+ pThis->pszCurrFName = NULL;
+ 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->sType == STREAMTYPE_FILE_CIRCULAR);
+ 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 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 ATTR_NONNULL()
+strmHandleEOF(strm_t *const 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:
+ DBGOPRINT((obj_t*) pThis, "file '%s' (%d) EOF, rotationCheck %d\n",
+ pThis->pszCurrFName, pThis->fd, pThis->rotationCheck);
+DBGPRINTF("RGER: EOF!\n");
+ ABORT_FINALIZE(RS_RET_EOF);
+ break;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* helper to checkTruncation */
+static rsRetVal ATTR_NONNULL()
+rereadTruncated(strm_t *const pThis, const int err_no, const char *const reason, const long long data)
+{
+ DEFiRet;
+
+ LogMsg(err_no, RS_RET_FILE_TRUNCATED, LOG_WARNING, "file '%s': truncation detected, "
+ "(%s) - re-start reading from beginning (data %lld)",
+ pThis->pszCurrFName, reason, data);
+ DBGPRINTF("checkTruncation, file %s last buffer CHANGED\n", pThis->pszCurrFName);
+ CHKiRet(strmCloseFile(pThis));
+ CHKiRet(strmOpenFile(pThis));
+ iRet = RS_RET_FILE_TRUNCATED;
+
+finalize_it:
+ RETiRet;
+}
+/* helper to read:
+ * Check if file has been truncated since last read and, if so, re-set reading
+ * to begin of file. To detect truncation, we try to re-read the last block.
+ * If that does not succeed or different data than from the original read is
+ * returned, truncation is assumed.
+ * NOTE: this function must be called only if truncation is enabled AND
+ * when the previous read buffer still is valid (aka "before the next read").
+ * It is ok to call with a 0-size buffer, which we than assume as begin of
+ * reading. In that case, no truncation will be detected.
+ * rgerhards, 2018-09-20
+ */
+static rsRetVal ATTR_NONNULL()
+checkTruncation(strm_t *const pThis)
+{
+ DEFiRet;
+ off64_t ret;
+ assert(pThis->bReopenOnTruncate);
+ assert(pThis->fd != -1);
+
+ DBGPRINTF("checkTruncation, file %s, iBufPtrMax %zd\n", pThis->pszCurrFName, pThis->iBufPtrMax);
+ if(pThis->iBufPtrMax == 0) {
+ FINALIZE;
+ }
+
+ const off64_t backseek = -1 * (off64_t) pThis->iBufPtrMax;
+ ret = lseek64(pThis->fd, backseek, SEEK_CUR);
+ if(ret < 0) {
+ iRet = rereadTruncated(pThis, errno,
+ "cannot seek backward to begin of last block", backseek);
+ FINALIZE;
+ }
+
+ const ssize_t lenRead = read(pThis->fd, pThis->pIOBuf_truncation, pThis->iBufPtrMax);
+ if(lenRead != (ssize_t) pThis->iBufPtrMax) {
+ iRet = rereadTruncated(pThis, errno,
+ "last block could not be re-read", lenRead);
+ FINALIZE;
+ }
+
+ if(!memcmp(pThis->pIOBuf_truncation, pThis->pIOBuf, pThis->iBufPtrMax)) {
+ DBGPRINTF("checkTruncation, file %s last buffer unchanged\n", pThis->pszCurrFName);
+ } else {
+ iRet = rereadTruncated(pThis, errno, "last block data different", 0);
+ }
+
+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;
+ }
+ }
+ if(pThis->bReopenOnTruncate) {
+ rsRetVal localRet = checkTruncation(pThis);
+ if(localRet == RS_RET_FILE_TRUNCATED) {
+ continue;
+ }
+ CHKiRet(localRet);
+ }
+ iLenRead = read(pThis->fd, pThis->pIOBuf, toRead);
+ DBGOPRINT((obj_t*) pThis, "file %d read %ld bytes\n", pThis->fd, iLenRead);
+ DBGOPRINT((obj_t*) pThis, "file %d read %*s\n", pThis->fd, (unsigned) iLenRead, (char*) pThis->pIOBuf);
+ /* 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;
+}
+
+
+/* debug output of current buffer */
+void
+strmDebugOutBuf(const strm_t *const pThis)
+{
+ int strtIdx = pThis->iBufPtr - 50;
+ if(strtIdx < 0)
+ strtIdx = 0;
+ DBGOPRINT((obj_t*) pThis, "strmRead ungetc %d, index %zd, max %zd, buf '%.*s', CURR: '%.*s'\n",
+ pThis->iUngetC, pThis->iBufPtr, pThis->iBufPtrMax, (int) pThis->iBufPtrMax - strtIdx,
+ pThis->pIOBuf+strtIdx, (int) (pThis->iBufPtrMax - pThis->iBufPtr), pThis->pIOBuf+pThis->iBufPtr);
+}
+
+/* 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 %zd, max %zd\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
+ *
+ * Parameter mode controls legacy multi-line processing:
+ * 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
+ */
+static rsRetVal ATTR_NONNULL(1, 2)
+strmReadLine(strm_t *const pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF,
+ const uchar *const escapeLFString, uint32_t trimLineOverBytes, int64 *const strtOffs)
+{
+ uchar c;
+ uchar finished;
+ const int escapeLFString_len = (escapeLFString == NULL) ? 4 : strlen((char*) escapeLFString);
+ DEFiRet;
+
+ assert(pThis != NULL);
+ assert(ppCStr != NULL);
+
+ CHKiRet(cstrConstruct(ppCStr));
+ CHKiRet(strmReadChar(pThis, &c));
+
+ /* append previous message to current message if necessary */
+ if(pThis->prevLineSegment != NULL) {
+ cstrFinalize(pThis->prevLineSegment);
+ dbgprintf("readLine: have previous line segment: '%s'\n",
+ rsCStrGetSzStrNoNULL(pThis->prevLineSegment));
+ CHKiRet(cstrAppendCStr(*ppCStr, pThis->prevLineSegment));
+ cstrDestruct(&pThis->prevLineSegment);
+ }
+ if(mode == 0) {
+ while(c != '\n') {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ }
+ if (trimLineOverBytes > 0 && (uint32_t) cstrLen(*ppCStr) > trimLineOverBytes) {
+ /* Truncate long line at trimLineOverBytes position */
+ dbgprintf("Truncate long line at %u, mode %d\n", trimLineOverBytes, mode);
+ rsCStrTruncate(*ppCStr, cstrLen(*ppCStr) - trimLineOverBytes);
+ cstrAppendChar(*ppCStr, '\n');
+ }
+ cstrFinalize(*ppCStr);
+ } else if(mode == 1) {
+ finished=0;
+ while(finished == 0){
+ if(c != '\n') {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ pThis->bPrevWasNL = 0;
+ } else {
+ if ((((*ppCStr)->iStrLen) > 0) ){
+ if(pThis->bPrevWasNL && escapeLFString_len > 0) {
+ rsCStrTruncate(*ppCStr, (bEscapeLF) ? escapeLFString_len : 1);
+ /* remove the prior newline */
+ finished=1;
+ } else {
+ if(bEscapeLF) {
+ if(escapeLFString == NULL) {
+ CHKiRet(rsCStrAppendStrWithLen(*ppCStr,
+ (uchar*)"#012", sizeof("#012")-1));
+ } else {
+ CHKiRet(rsCStrAppendStrWithLen(*ppCStr,
+ escapeLFString, escapeLFString_len));
+ }
+ } else {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ }
+ CHKiRet(strmReadChar(pThis, &c));
+ pThis->bPrevWasNL = 1;
+ }
+ } else {
+ finished=1; /* this is a blank line, a \n with nothing since
+ the last complete record */
+ }
+ }
+ }
+ cstrFinalize(*ppCStr);
+ pThis->bPrevWasNL = 0;
+ } 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(pThis->bPrevWasNL) {
+ if ((c == ' ') || (c == '\t')){
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ CHKiRet(strmReadChar(pThis, &c));
+ pThis->bPrevWasNL = 0;
+ } 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));
+ if(bEscapeLF && escapeLFString_len > 0) {
+ rsCStrTruncate(*ppCStr, (bEscapeLF) ? escapeLFString_len : 1);
+ }
+ finished=1;
+ }
+ } else { /* not the first character after a newline, add it to the buffer */
+ if(c == '\n') {
+ pThis->bPrevWasNL = 1;
+ if(bEscapeLF && escapeLFString_len > 0) {
+ if(escapeLFString == NULL) {
+ CHKiRet(rsCStrAppendStrWithLen(*ppCStr,
+ (uchar*)"#012", sizeof("#012")-1));
+ } else {
+ CHKiRet(rsCStrAppendStrWithLen(*ppCStr,
+ escapeLFString, escapeLFString_len));
+ }
+ } else {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ }
+ } else {
+ CHKiRet(cstrAppendChar(*ppCStr, c));
+ }
+ CHKiRet(strmReadChar(pThis, &c));
+ }
+ }
+ }
+ if (trimLineOverBytes > 0 && (uint32_t) cstrLen(*ppCStr) > trimLineOverBytes) {
+ /* Truncate long line at trimLineOverBytes position */
+ dbgprintf("Truncate long line at %u, mode %d\n", trimLineOverBytes, mode);
+ rsCStrTruncate(*ppCStr, cstrLen(*ppCStr) - trimLineOverBytes);
+ cstrAppendChar(*ppCStr, '\n');
+ }
+ cstrFinalize(*ppCStr);
+ pThis->bPrevWasNL = 0;
+ }
+
+finalize_it:
+ if(iRet == RS_RET_OK) {
+ if(strtOffs != NULL) {
+ *strtOffs = pThis->strtOffs;
+ }
+ pThis->strtOffs = pThis->iCurrOffs; /* we are at begin of next line */
+ } else {
+DBGPRINTF("RGER: strmReadLine iRet %d\n", iRet);
+ if(*ppCStr != NULL) {
+ if(cstrLen(*ppCStr) > 0) {
+ /* we may have an empty string in an unsuccesfull poll or after restart! */
+ if(rsCStrConstructFromCStr(&pThis->prevLineSegment, *ppCStr) != RS_RET_OK) {
+ /* we cannot do anything against this, but we can at least
+ * ensure we do not have any follow-on errors.
+ */
+ pThis->prevLineSegment = NULL;
+ }
+ }
+ cstrDestruct(ppCStr);
+ }
+ }
+
+ RETiRet;
+}
+
+/* check if the current multi line read is timed out
+ * @return 0 - no timeout, something else - timeout
+ */
+int
+strmReadMultiLine_isTimedOut(const strm_t *const __restrict__ pThis)
+{
+ /* note: order of evaluation is choosen so that the most inexpensive
+ * processing flow is used.
+ */
+ DBGPRINTF("strmReadMultiline_isTimedOut: prevMsgSeg %p, readTimeout %d, "
+ "lastRead %lld\n", pThis->prevMsgSegment, pThis->readTimeout,
+ (long long) pThis->lastRead);
+ return( (pThis->readTimeout)
+ && (pThis->prevMsgSegment != NULL)
+ && (getTime(NULL) > pThis->lastRead + pThis->readTimeout) );
+}
+
+/* read a multi-line message from a strm file.
+ * The multi-line message is terminated based on the user-provided
+ * startRegex or endRegex (Posix ERE). For performance reasons, the regex
+ * must already have been compiled by the user.
+ * added 2015-05-12 rgerhards
+ */
+rsRetVal ATTR_NONNULL(1,2)
+strmReadMultiLine(strm_t *pThis, cstr_t **ppCStr, regex_t *start_preg, regex_t *end_preg, const sbool bEscapeLF,
+ const uchar *const escapeLFString, const sbool discardTruncatedMsg, const sbool msgDiscardingError,
+ int64 *const strtOffs)
+{
+ uchar c;
+ uchar finished = 0;
+ cstr_t *thisLine = NULL;
+ rsRetVal readCharRet;
+ const time_t tCurr = pThis->readTimeout ? getTime(NULL) : 0;
+ size_t maxMsgSize = glblGetMaxLine(runConf);
+ DEFiRet;
+
+ do {
+ CHKiRet(strmReadChar(pThis, &c)); /* immediately exit on EOF */
+ pThis->lastRead = tCurr;
+ CHKiRet(cstrConstruct(&thisLine));
+ /* append previous message to current message if necessary */
+ if(pThis->prevLineSegment != NULL) {
+ CHKiRet(cstrAppendCStr(thisLine, pThis->prevLineSegment));
+ cstrDestruct(&pThis->prevLineSegment);
+ }
+
+ while(c != '\n') {
+ CHKiRet(cstrAppendChar(thisLine, c));
+ readCharRet = strmReadChar(pThis, &c);
+ if(readCharRet == RS_RET_EOF) {/* end of file reached without \n? */
+ CHKiRet(rsCStrConstructFromCStr(&pThis->prevLineSegment, thisLine));
+ }
+ CHKiRet(readCharRet);
+ }
+ cstrFinalize(thisLine);
+
+ /* we have a line, now let's assemble the message */
+ const int isStartMatch = start_preg ?
+ !regexec(start_preg, (char*)rsCStrGetSzStrNoNULL(thisLine), 0, NULL, 0) :
+ 0;
+ const int isEndMatch = end_preg ?
+ !regexec(end_preg, (char*)rsCStrGetSzStrNoNULL(thisLine), 0, NULL, 0) :
+ 0;
+
+ if(isStartMatch) {
+ /* in this case, the *previous* message is complete and we are
+ * at the start of a new one.
+ */
+ if(pThis->ignoringMsg == 0) {
+ if(pThis->prevMsgSegment != NULL) {
+ /* may be NULL in initial poll! */
+ finished = 1;
+ *ppCStr = pThis->prevMsgSegment;
+ }
+ }
+ CHKiRet(rsCStrConstructFromCStr(&pThis->prevMsgSegment, thisLine));
+ pThis->ignoringMsg = 0;
+ } else {
+ if(pThis->ignoringMsg == 0) {
+ if(pThis->prevMsgSegment == NULL) {
+ /* may be NULL in initial poll or after timeout! */
+ CHKiRet(rsCStrConstructFromCStr(&pThis->prevMsgSegment, thisLine));
+ } else {
+ if(bEscapeLF) {
+ if(escapeLFString == NULL) {
+ rsCStrAppendStrWithLen(pThis->prevMsgSegment, (uchar*)"\\n", 2);
+ } else {
+ rsCStrAppendStr(pThis->prevMsgSegment, escapeLFString);
+ }
+ } else {
+ cstrAppendChar(pThis->prevMsgSegment, '\n');
+ }
+
+
+ size_t currLineLen = cstrLen(thisLine);
+ if(currLineLen > 0) {
+ size_t len;
+ if((len = cstrLen(pThis->prevMsgSegment) + currLineLen) <
+ maxMsgSize) {
+ CHKiRet(cstrAppendCStr(pThis->prevMsgSegment, thisLine));
+ /* we could do this faster, but for now keep it simple */
+ } else {
+ if (cstrLen(pThis->prevMsgSegment) > maxMsgSize) {
+ len = 0;
+ } else {
+ len = currLineLen-(len-maxMsgSize);
+ for(size_t z=0; z<len; z++) {
+ cstrAppendChar(pThis->prevMsgSegment,
+ thisLine->pBuf[z]);
+ }
+ }
+ finished = 1;
+ *ppCStr = pThis->prevMsgSegment;
+ CHKiRet(rsCStrConstructFromszStr(&pThis->prevMsgSegment,
+ thisLine->pBuf+len));
+ if(discardTruncatedMsg == 1) {
+ pThis->ignoringMsg = 1;
+ }
+ if(msgDiscardingError == 1) {
+ if(discardTruncatedMsg == 1) {
+ LogError(0, RS_RET_ERR,
+ "imfile error: message received is "
+ "larger than max msg size; "
+ "rest of message will not be "
+ "processed");
+ } else {
+ LogError(0, RS_RET_ERR,
+ "imfile error: message received is "
+ "larger than max msg size; message "
+ "will be split and processed as "
+ "another message");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ if(isEndMatch) {
+ /* in this case, the *current* message is complete and we are
+ * at the end of it.
+ */
+ if(pThis->ignoringMsg == 0) {
+ if(pThis->prevMsgSegment != NULL) {
+ finished = 1;
+ *ppCStr = pThis->prevMsgSegment;
+ pThis->prevMsgSegment= NULL;
+ }
+ }
+ pThis->ignoringMsg = 0;
+ }
+ cstrDestruct(&thisLine);
+ } while(finished == 0);
+
+finalize_it:
+ *strtOffs = pThis->strtOffs;
+ if(thisLine != NULL) {
+ cstrDestruct(&thisLine);
+ }
+ if(iRet == RS_RET_OK) {
+ pThis->strtOffs = pThis->iCurrOffs; /* we are at begin of next line */
+ cstrFinalize(*ppCStr);
+ } else {
+ if( pThis->readTimeout
+ && (pThis->prevMsgSegment != NULL)
+ && (tCurr > pThis->lastRead + pThis->readTimeout)) {
+ if(rsCStrConstructFromCStr(ppCStr, pThis->prevMsgSegment) == RS_RET_OK) {
+ cstrFinalize(*ppCStr);
+ cstrDestruct(&pThis->prevMsgSegment);
+ pThis->lastRead = tCurr;
+ pThis->strtOffs = pThis->iCurrOffs; /* we are at begin of next line */
+ dbgprintf("stream: generated msg based on timeout: %s\n",
+ cstrGetSzStrNoNULL(*ppCStr));
+ iRet = RS_RET_OK;
+ }
+ }
+ }
+ 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->compressionDriver = STRM_COMPRESS_ZIP;
+ pThis->pszSizeLimitCmd = NULL;
+ pThis->prevLineSegment = NULL;
+ pThis->prevMsgSegment = NULL;
+ pThis->strtOffs = 0;
+ pThis->ignoringMsg = 0;
+ pThis->bPrevWasNL = 0;
+ pThis->fileNotFoundError = 1;
+ pThis->noRepeatedErrorOutput = 0;
+ pThis->lastRead = getTime(NULL);
+ENDobjConstruct(strm)
+
+
+/* ConstructionFinalizer
+ * rgerhards, 2008-01-09
+ */
+static rsRetVal strmConstructFinalize(strm_t *pThis)
+{
+ pthread_mutexattr_t mutAttr;
+ 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? */
+ if(pThis->compressionDriver == STRM_COMPRESS_ZSTD) {
+ localRet = objUse(zstdw, LM_ZSTDW_FILENAME);
+ if(localRet != RS_RET_OK) {
+ pThis->iZipLevel = 0;
+ LogError(0, localRet, "stream was requested with zstd compression mode, "
+ "but zstdw module unavailable - using without compression\n");
+ }
+ } else {
+ assert(pThis->compressionDriver == STRM_COMPRESS_ZIP);
+ localRet = objUse(zlibw, LM_ZLIBW_FILENAME);
+ if(localRet != RS_RET_OK) {
+ pThis->iZipLevel = 0;
+ LogError(0, localRet, "stream was requested with zip mode, but zlibw "
+ "module unavailable - using without zip\n");
+ }
+ }
+ /* 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(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->pszDir != NULL) {
+ 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);
+ }
+ }
+
+ /* if we have a flush interval, we need to do async writes in any case */
+ if(pThis->iFlushInterval != 0) {
+ pThis->bAsyncWrite = 1;
+ }
+
+ DBGPRINTF("file stream %s params: flush interval %d, async write %d\n",
+ getFileDebugName(pThis),
+ pThis->iFlushInterval, pThis->bAsyncWrite);
+
+ /* if we work asynchronously, we need a couple of synchronization objects */
+ if(pThis->bAsyncWrite) {
+ /* 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(&pThis->mut, &mutAttr);
+ 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(pThis->sIOBufSize));
+ }
+ pThis->pIOBuf = pThis->asyncBuf[0].pBuf;
+ pThis->bStopWriter = 0;
+ if(pthread_create(&pThis->writerThreadID,
+ &default_thread_attr,
+ 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(pThis->sIOBufSize));
+ CHKmalloc(pThis->pIOBuf_truncation = (char*) malloc(pThis->sIOBufSize));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* 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 strmCloseFile/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) {
+ 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);
+ free(pThis->pIOBuf_truncation);
+ }
+
+ /* Finally, we can free the resources.
+ * IMPORTANT: we MUST free this only AFTER the ansyncWriter has been stopped, else
+ * we get random errors...
+ */
+ if(pThis->compressionDriver == STRM_COMPRESS_ZSTD) {
+ zstdw.Destruct(pThis);
+ }
+ if(pThis->prevLineSegment)
+ cstrDestruct(&pThis->prevLineSegment);
+ if(pThis->prevMsgSegment)
+ cstrDestruct(&pThis->prevMsgSegment);
+ free(pThis->pszDir);
+ free(pThis->pZipBuf);
+ free(pThis->pszCurrFName);
+ free(pThis->pszFName);
+ free(pThis->pszSizeLimitCmd);
+ 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 || pThis->sType != STREAMTYPE_FILE_CIRCULAR)
+ 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);
+#ifndef __FreeBSD__
+ if(err == ERR_TTYHUP) {
+#else
+ /* Try to reopen our file descriptor even on errno 6, FreeBSD bug 200429
+ * Also try on errno 5, FreeBSD bug 211033
+ */
+ if(err == ERR_TTYHUP || err == ENXIO || err == EIO) {
+#endif /* __FreeBSD__ */
+ close(pThis->fd);
+ pThis->fd = -1;
+ CHKiRet(doPhysOpen(pThis));
+ }
+
+finalize_it:
+ RETiRet;
+}
+#undef ER_TTYHUP
+
+
+/* issue write() api calls until either the buffer is completely
+ * written or an error occurred (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 ATTR_NONNULL(1,2,3)
+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);
+#ifdef __FreeBSD__
+ sbool crnlNow = 0;
+#endif /* __FreeBSD__ */
+
+ lenBuf = *pLenBuf;
+ pWriteBuf = (char*) pBuf;
+ iTotalWritten = 0;
+ do {
+ #ifdef __FreeBSD__
+ if (pThis->bIsTTY && !pThis->iZipLevel && !pThis->cryprov) {
+ char *pNl = NULL;
+ if (crnlNow == 0) pNl = strchr(pWriteBuf, '\n');
+ else crnlNow = 0;
+ if (pNl == pWriteBuf) {
+ iWritten = write(pThis->fd, "\r", 1);
+ if (iWritten > 0) {
+ crnlNow = 1;
+ iWritten = 0;
+ }
+ } else iWritten = write(pThis->fd, pWriteBuf, pNl ? pNl - pWriteBuf : lenBuf);
+ } else
+ #endif /* __FreeBSD__ */
+ iWritten = write(pThis->fd, pWriteBuf, lenBuf);
+ if(iWritten < 0) {
+ const int err = errno;
+ iWritten = 0; /* we have written NO bytes! */
+ if(err == EBADF) {
+ DBGPRINTF("file %s: errno %d, fd %d no longer valid, recovery by "
+ "reopen; if you see this, consider reporting at "
+ "https://github.com/rsyslog/rsyslog/issues/3404 "
+ "so that we know when it happens. Include output of uname -a. "
+ "OS error reason", pThis->pszCurrFName, err, pThis->fd);
+ pThis->fd = -1;
+ CHKiRet(doPhysOpen(pThis));
+ } else {
+ if(err != EINTR) {
+ LogError(err, RS_RET_IO_ERROR, "file '%s'[%d] write error - see "
+ "https://www.rsyslog.com/solving-rsyslog-write-errors/ for help "
+ "OS error", pThis->pszCurrFName, pThis->fd);
+ }
+ if(err == EINTR) {
+ /*NO ERROR, just continue */;
+ } else if( !pThis->bIsTTY && ( err == ENOTCONN || err == EIO )) {
+ /* Failure for network file system, thus file needs to be closed
+ * and reopened. */
+ close(pThis->fd);
+ pThis->fd = -1;
+ CHKiRet(doPhysOpen(pThis));
+ } 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 rsRetVal
+doWriteInternal(strm_t *pThis, uchar *pBuf, const size_t lenBuf, const int bFlush)
+{
+ DEFiRet;
+
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) doWriteInternal: bFlush %d\n",
+ pThis->fd, getFileDebugName(pThis), bFlush);
+
+ 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 rsRetVal
+doAsyncWriteInternal(strm_t *pThis, size_t lenBuf, const int bFlushZip)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) doAsyncWriteInternal at begin: "
+ "iCnt %d, iEnq %d, bFlushZip %d\n",
+ pThis->fd, getFileDebugName(pThis),
+ pThis->iCnt, pThis->iEnq, bFlushZip);
+ /* 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;
+ if(!pThis->bFlushNow) /* if we already need to flush, do not overwrite */
+ pThis->bFlushNow = bFlushZip;
+
+ pThis->bDoTimedWait = 0; /* everything written, no need to timeout partial buffer writes */
+ if(++pThis->iCnt == 1) {
+ pthread_cond_signal(&pThis->notEmpty);
+ DBGOPRINT((obj_t*) pThis, "doAsyncWriteInternal signaled notEmpty\n");
+ }
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) doAsyncWriteInternal at exit: "
+ "iCnt %d, iEnq %d, bFlushZip %d\n",
+ pThis->fd, getFileDebugName(pThis),
+ pThis->iCnt, pThis->iEnq, bFlushZip);
+
+ 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, const 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, bFlushZip));
+ } 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);
+
+ ustrncpy(thrdName+3, pThis->pszFName, sizeof(thrdName)-4);
+ dbgOutputTID((char*)thrdName);
+# if defined(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) {
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) asyncWriterThread new iteration, "
+ "iCnt %d, bTimedOut %d, iFlushInterval %d\n", pThis->fd,
+ getFileDebugName(pThis),
+ pThis->iCnt, bTimedOut, pThis->iFlushInterval);
+ 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, 1);
+ bTimedOut = 0;
+ continue;
+ }
+ bTimedOut = 0;
+ if(pThis->bDoTimedWait) {
+ timeoutComp(&t, pThis->iFlushInterval * 1000); /* 1000 *millisconds* */
+ if((err = pthread_cond_timedwait(&pThis->notEmpty, &pThis->mut, &t)) != 0) {
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) asyncWriterThread timed out\n",
+ pThis->fd, getFileDebugName(pThis));
+ 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);
+ }
+ }
+
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) asyncWriterThread awoken, "
+ "iCnt %d, bTimedOut %d\n", pThis->fd, getFileDebugName(pThis),
+ pThis->iCnt, bTimedOut);
+ bTimedOut = 0; /* we may have timed out, but there *is* work to do... */
+
+ iDeq = pThis->iDeq++ % STREAM_ASYNC_NUMBUFS;
+ const int bFlush = (pThis->bFlushNow || bTimedOut) ? 1 : 0;
+ pThis->bFlushNow = 0;
+
+ /* 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, bFlush);
+ // 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);
+ }
+ }
+ /* Not reached */
+
+finalize_it:
+ DBGOPRINT((obj_t*) pThis, "file %d(%s) asyncWriterThread terminated\n",
+ pThis->fd, getFileDebugName(pThis));
+ 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 defined(HAVE_FDATASYNC) && !defined(__APPLE__)
+# 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) {
+ if(fsync(pThis->fdDir) != 0)
+ DBGPRINTF("stream/syncFile: fsync returned error, ignoring\n");
+ }
+
+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));
+ }
+
+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, const int bFlush)
+{
+ if(pThis->compressionDriver == STRM_COMPRESS_ZSTD) {
+ return zstdw.doStrmWrite(pThis, pBuf, lenBuf, bFlush, strmPhysWrite);
+ } else {
+ return zlibw.doStrmWrite(pThis, pBuf, lenBuf, bFlush, strmPhysWrite);
+ }
+}
+
+
+
+/* finish zlib buffer, to be called before closing the ZIP file (if
+ * running in stream mode).
+ */
+static rsRetVal
+doZipFinish(strm_t *pThis)
+{
+ if(pThis->compressionDriver == STRM_COMPRESS_ZSTD) {
+ return zstdw.doCompressFinish(pThis, strmPhysWrite);
+ } else {
+ return zlibw.doCompressFinish(pThis, strmPhysWrite);
+ }
+}
+
+/* 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, "strmFlushinternal: file %d(%s) flush, buflen %ld%s\n", pThis->fd,
+ getFileDebugName(pThis),
+ (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);
+
+ DBGOPRINT((obj_t*) pThis, "file %d strmFlush\n", pThis->fd);
+
+ 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 ATTR_NONNULL()
+strmSeek(strm_t *pThis, const off64_t offs)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ if(pThis->fd == -1) {
+ CHKiRet(strmOpenFile(pThis));
+ } else {
+ CHKiRet(strmFlushInternal(pThis, 0));
+ }
+ DBGOPRINT((obj_t*) pThis, "file %d seek, pos %llu\n", pThis->fd, (long long unsigned) offs);
+ const off64_t i = lseek64(pThis->fd, offs, SEEK_SET);
+ if(i != offs) {
+ LogError(errno, RS_RET_IO_ERROR, "file %s: unexpected error seeking to "
+ "offset %lld (ret %lld) - further malfunctions may happen",
+ pThis->pszCurrFName, (long long) i, (long long) offs);
+ ABORT_FINALIZE(RS_RET_IO_ERROR);
+ }
+ pThis->strtOffs = 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, unsigned int FNum, off64_t offs, off64_t *bytesDel)
+{
+ struct stat statBuf;
+ int skipped_files;
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, strm);
+
+ if(FNum == 0 && offs == 0) { /* happens during queue init */
+ *bytesDel = 0;
+ FINALIZE;
+ }
+
+ skipped_files = FNum - pThis->iCurrFNum;
+ *bytesDel = 0;
+
+ while(skipped_files > 0) {
+ CHKiRet(genFileName(&pThis->pszCurrFName, pThis->pszDir, pThis->lenDir,
+ pThis->pszFName, pThis->lenFName, pThis->iCurrFNum,
+ pThis->iFileNumDigits));
+ dbgprintf("rger: processing file %s\n", pThis->pszCurrFName);
+ if(stat((char*)pThis->pszCurrFName, &statBuf) != 0) {
+ LogError(errno, RS_RET_IO_ERROR, "unexpected error doing a stat() "
+ "on file %s - further malfunctions may happen",
+ pThis->pszCurrFName);
+ /* we do NOT error-terminate here as this could worsen the
+ * situation. As such, we just keep running and try to delete
+ * as many files as possible.
+ */
+ }
+ *bytesDel += statBuf.st_size;
+ DBGPRINTF("strmMultiFileSeek: detected new filenum, was %u, new %u, "
+ "deleting '%s' (%lld bytes)\n", pThis->iCurrFNum, FNum,
+ pThis->pszCurrFName, (long long) statBuf.st_size);
+ unlink((char*)pThis->pszCurrFName);
+ if(pThis->cryprov != NULL)
+ pThis->cryprov->DeleteStateFiles(pThis->pszCurrFName);
+ free(pThis->pszCurrFName);
+ pThis->pszCurrFName = NULL;
+ pThis->iCurrFNum++;
+ --skipped_files;
+ }
+ DBGOPRINT((obj_t*) pThis, "strmMultiFileSeek: deleted %lld bytes in this run\n",
+ (long long) *bytesDel);
+ pThis->strtOffs = 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->strtOffs = 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 *__restrict__ const pThis, const 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 *__restrict__ const pThis, const 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 ATTR_NONNULL(1,2)
+strmWrite(strm_t *__restrict__ const pThis, const uchar *__restrict__ const pBuf, size_t lenBuf)
+{
+ DEFiRet;
+ size_t iWrite;
+ size_t iOffset;
+
+ assert(pThis != NULL);
+ assert(pBuf != NULL);
+
+ 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 */
+ }
+ if(pThis->fd != -1 && pThis->iSizeLimit != 0) { /* Only check if fd already set */
+ CHKiRet(doSizeLimitProcessing(pThis));
+ }
+
+
+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, int64)
+DEFpropSetMeth(strm, iFileNumDigits, int)
+DEFpropSetMeth(strm, tOperationsMode, int)
+DEFpropSetMeth(strm, tOpenMode, mode_t)
+DEFpropSetMeth(strm, compressionDriver, strm_compressionDriver_t)
+DEFpropSetMeth(strm, sType, strmType_t)
+DEFpropSetMeth(strm, iZipLevel, int)
+DEFpropSetMeth(strm, bVeryReliableZip, int)
+DEFpropSetMeth(strm, bSync, int)
+DEFpropSetMeth(strm, bReopenOnTruncate, 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*)
+
+/* sets timeout in seconds */
+void ATTR_NONNULL()
+strmSetReadTimeout(strm_t *const __restrict__ pThis, const int val)
+{
+ ISOBJ_TYPE_assert(pThis, strm);
+ pThis->readTimeout = val;
+}
+
+static rsRetVal ATTR_NONNULL()
+strmSetbDeleteOnClose(strm_t *const pThis, const int val)
+{
+ ISOBJ_TYPE_assert(pThis, strm);
+ pThis->bDeleteOnClose = val;
+ if(pThis->cryprov != NULL) {
+ pThis->cryprov->SetDeleteOnClose(pThis->cryprovFileData, pThis->bDeleteOnClose);
+ }
+ return RS_RET_OK;
+}
+
+static rsRetVal ATTR_NONNULL()
+strmSetiMaxFiles(strm_t *const pThis, const int iNewVal)
+{
+ ISOBJ_TYPE_assert(pThis, strm);
+ pThis->iMaxFiles = iNewVal;
+ pThis->iFileNumDigits = getNumberDigits(iNewVal);
+ return RS_RET_OK;
+}
+
+static rsRetVal ATTR_NONNULL()
+strmSetFileNotFoundError(strm_t *const pThis, const int pFileNotFoundError)
+{
+ ISOBJ_TYPE_assert(pThis, strm);
+ pThis->fileNotFoundError = pFileNotFoundError;
+ 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(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(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); /* implicit cast is OK for persistance */
+ 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);
+
+ l = pThis->strtOffs;
+ objSerializeSCALAR_VAR(pStrm, strtOffs, INT64, l);
+
+ dbgprintf("strmSerialize: pThis->prevLineSegment %p\n", pThis->prevLineSegment);
+ if(pThis->prevLineSegment != NULL) {
+ cstrFinalize(pThis->prevLineSegment);
+ objSerializePTR(pStrm, prevLineSegment, CSTR);
+ }
+
+ if(pThis->prevMsgSegment != NULL) {
+ cstrFinalize(pThis->prevMsgSegment);
+ objSerializePTR(pStrm, prevMsgSegment, CSTR);
+ }
+
+ i = pThis->bPrevWasNL;
+ objSerializeSCALAR_VAR(pStrm, bPrevWasNL, INT, i);
+
+ 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
+ */
+static rsRetVal
+strmDup(strm_t *const 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->compressionDriver = pThis->compressionDriver;
+ 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;
+}
+
+
+static rsRetVal
+SetCompressionWorkers(strm_t *const pThis, int num_wrkrs)
+{
+ ISOBJ_TYPE_assert(pThis, strm);
+ if(num_wrkrs < 0)
+ num_wrkrs = 1;
+ pThis->zstd.num_wrkrs = num_wrkrs;
+ return RS_RET_OK;
+}
+
+
+/* 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 = (unsigned) 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("strtOffs")) {
+ pThis->strtOffs = pProp->val.num;
+ } else if(isProp("iMaxFileSize")) {
+ CHKiRet(strmSetiMaxFileSize(pThis, pProp->val.num));
+ } else if(isProp("fileNotFoundError")) {
+ CHKiRet(strmSetFileNotFoundError(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));
+ } else if(isProp("prevMsgSegment")) {
+ CHKiRet(rsCStrConstructFromCStr(&pThis->prevMsgSegment, pProp->val.pStr));
+ } else if(isProp("bPrevWasNL")) {
+ pThis->bPrevWasNL = (sbool) pProp->val.num;
+ }
+
+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->SetFileNotFoundError = strmSetFileNotFoundError;
+ pIf->SetDir = strmSetDir;
+ pIf->Flush = strmFlush;
+ pIf->RecordBegin = strmRecordBegin;
+ pIf->RecordEnd = strmRecordEnd;
+ pIf->Serialize = strmSerialize;
+ pIf->GetCurrOffset = strmGetCurrOffset;
+ pIf->Dup = strmDup;
+ pIf->SetCompressionWorkers = SetCompressionWorkers;
+ 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->SetcompressionDriver = strmSetcompressionDriver;
+ pIf->SetsType = strmSetsType;
+ pIf->SetiZipLevel = strmSetiZipLevel;
+ pIf->SetbVeryReliableZip = strmSetbVeryReliableZip;
+ pIf->SetbSync = strmSetbSync;
+ pIf->SetbReopenOnTruncate = strmSetbReopenOnTruncate;
+ 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)
diff --git a/runtime/stream.h b/runtime/stream.h
new file mode 100644
index 0000000..38db1ab
--- /dev/null
+++ b/runtime/stream.h
@@ -0,0 +1,254 @@
+/* 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-2018 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
+
+typedef struct strm_s strm_t; /* forward reference because of zlib... */
+
+#include <regex.h> // TODO: fix via own module
+#include <pthread.h>
+#include <stdint.h>
+#include <time.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;
+typedef enum {
+ STRM_COMPRESS_ZIP = 0,
+ STRM_COMPRESS_ZSTD = 1
+ } strm_compressionDriver_t;
+
+#define STREAM_ASYNC_NUMBUFS 2 /* must be a power of 2 -- TODO: make configurable */
+/* The strm_t data structure */
+struct strm_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ strmType_t sType;
+ /* descriptive properties */
+ unsigned 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 */
+ unsigned 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() */
+ sbool bPrevWasNL; /* used for readLine() when reading multi-line messages */
+ /* 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? */
+ sbool bReopenOnTruncate;
+ int rotationCheck; /* rotation check mode */
+ 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) */
+ int readTimeout;/* 0: do not timeout */
+ time_t lastRead;/* for timeout processing */
+ 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 */
+ char *pIOBuf_truncation; /* iobuffer used during trucation detection block re-reads */
+ 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 bFlushNow; /* shall we flush with the next async write? */
+ 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];
+ struct {
+ int num_wrkrs; /* nbr of worker threads */
+ void *cctx;
+ } zstd; /* supporting per-instance data if zstd is used */
+ 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, unprocessed part of file */
+ cstr_t *prevMsgSegment; /* for ReadMultiLine, previous, yet unprocessed part of msg */
+ int64 strtOffs; /* start offset in file for current line/msg */
+ int fileNotFoundError; /* boolean; if set, report file not found errors, else silently ignore */
+ int noRepeatedErrorOutput; /* if a file is missing the Error is only given once */
+ int ignoringMsg;
+ strm_compressionDriver_t compressionDriver;
+};
+
+
+/* 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 (*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 *const pThis, const uchar *const pBuf, size_t lenBuf);
+ rsRetVal (*WriteChar)(strm_t *pThis, uchar c);
+ rsRetVal (*WriteLong)(strm_t *pThis, long i);
+ rsRetVal (*SetFileNotFoundError)(strm_t *pThis, int pFileNotFoundError);
+ 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);
+ rsRetVal (*SetCompressionWorkers)(strm_t *const pThis, int num_wrkrs);
+ INTERFACEpropSetMeth(strm, bDeleteOnClose, int);
+ INTERFACEpropSetMeth(strm, iMaxFileSize, int64);
+ INTERFACEpropSetMeth(strm, iMaxFiles, int);
+ INTERFACEpropSetMeth(strm, iFileNumDigits, int);
+ INTERFACEpropSetMeth(strm, tOperationsMode, int);
+ INTERFACEpropSetMeth(strm, tOpenMode, mode_t);
+ INTERFACEpropSetMeth(strm, compressionDriver, strm_compressionDriver_t);
+ INTERFACEpropSetMeth(strm, sType, strmType_t);
+ INTERFACEpropSetMeth(strm, iZipLevel, int);
+ INTERFACEpropSetMeth(strm, bSync, int);
+ INTERFACEpropSetMeth(strm, bReopenOnTruncate, 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, uint8_t mode, sbool bEscapeLF, const uchar *,
+ uint32_t trimLineOverBytes, int64 *const strtOffs);
+ /* 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 14 /* increment whenever you change the interface structure! */
+/* V10, 2013-09-10: added new parameter bEscapeLF, changed mode to uint8_t (rgerhards) */
+/* V11, 2015-12-03: added new parameter bReopenOnTruncate */
+/* V12, 2015-12-11: added new parameter trimLineOverBytes, changed mode to uint32_t */
+/* V13, 2017-09-06: added new parameter strtoffs to ReadLine() */
+/* V14, 2019-11-13: added new parameter bEscapeLFString (rgerhards) */
+
+#define strmGetCurrFileNum(pStrm) ((pStrm)->iCurrFNum)
+
+/* prototypes */
+PROTOTYPEObjClassInit(strm);
+rsRetVal strmMultiFileSeek(strm_t *pThis, unsigned int fileNum, off64_t offs, off64_t *bytesDel);
+rsRetVal ATTR_NONNULL(1,2) strmReadMultiLine(strm_t *pThis, cstr_t **ppCStr, regex_t *start_preg,
+ regex_t *end_preg, const sbool bEscapeLF, const uchar *const escapeLFString,
+ const sbool discardTruncatedMsg, const sbool msgDiscardingError, int64 *const strtOffs);
+int strmReadMultiLine_isTimedOut(const strm_t *const __restrict__ pThis);
+void strmDebugOutBuf(const strm_t *const pThis);
+void strmSetReadTimeout(strm_t *const __restrict__ pThis, const int val);
+const uchar * ATTR_NONNULL() strmGetPrevLineSegment(strm_t *const pThis);
+const uchar * ATTR_NONNULL() strmGetPrevMsgSegment(strm_t *const pThis);
+int ATTR_NONNULL() strmGetPrevWasNL(const strm_t *const pThis);
+
+#endif /* #ifndef STREAM_H_INCLUDED */
diff --git a/runtime/strgen.c b/runtime/strgen.c
new file mode 100644
index 0000000..89381cb
--- /dev/null
+++ b/runtime/strgen.c
@@ -0,0 +1,278 @@
+/* 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 existence 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(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
+ */
+static 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;
+}
+
+PROTOTYPEobjDestruct(strgen);
+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(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(ruleset, CORE_COMPONENT));
+ InitStrgenList(&pStrgenLstRoot);
+ENDObjClassInit(strgen)
+
diff --git a/runtime/strgen.h b/runtime/strgen.h
new file mode 100644
index 0000000..3819dcc
--- /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 0000000..ea39b7c
--- /dev/null
+++ b/runtime/stringbuf.c
@@ -0,0 +1,777 @@
+/* This is the byte-counted string class for rsyslog.
+ * This object has a lot of legacy. Among others, it was started to
+ * support embedded \0 bytes, which looked like they were needed to
+ * be supported by RFC developments at that time. Later, this was
+ * no longer a requirement, and we refactored the class in 2016
+ * to some simpler internals which make use of the fact that no
+ * NUL can ever occur in rsyslog strings (they are escaped at the
+ * input side of rsyslog).
+ * It now serves primarily to a) dynamic string creation, b) keep
+ * old interfaces supported, and c) some special functionality,
+ * e.g. search. Further refactoring and simplificytin may make
+ * sense.
+ *
+ * Copyright (C) 2005-2019 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 "errmsg.h"
+#include "unicode-helper.h"
+
+#define DEV_DEBUG 0 /* set to 1 to enable very verbose developer debugging messages */
+
+
+/* ################################################################# *
+ * private members *
+ * ################################################################# */
+
+/* static data */
+DEFobjCurrIf(obj)
+DEFobjCurrIf(regexp)
+
+/* ################################################################# *
+ * public members *
+ * ################################################################# */
+
+
+rsRetVal
+cstrConstruct(cstr_t **const ppThis)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ CHKmalloc(pThis = (cstr_t*) malloc(sizeof(cstr_t)));
+ rsSETOBJTYPE(pThis, OIDrsCStr);
+ #ifndef NDEBUG
+ pThis->isFinalized = 0;
+ #endif
+ pThis->pBuf = NULL;
+ pThis->iBufSize = 0;
+ pThis->iStrLen = 0;
+ *ppThis = pThis;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* construct from sz string
+ * rgerhards 2005-09-15
+ */
+rsRetVal
+rsCStrConstructFromszStr(cstr_t **const ppThis, const uchar *const sz)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ CHKiRet(rsCStrConstruct(&pThis));
+
+ pThis->iStrLen = strlen((char *) sz);
+ pThis->iBufSize = strlen((char *) sz) + 1;
+ if((pThis->pBuf = (uchar*) malloc(pThis->iBufSize)) == 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, const char *fmt,
+va_list ap) __attribute__((format(printf,2, 0)));
+static rsRetVal
+rsCStrConstructFromszStrv(cstr_t **const ppThis, const char *const fmt, va_list ap)
+{
+ DEFiRet;
+ cstr_t *pThis;
+ va_list ap2;
+ int len;
+
+ 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->iStrLen = len;
+ pThis->iBufSize = len + 1;
+ len++; /* account for the \0 written by vsnprintf */
+ if((pThis->pBuf = (uchar*) malloc(pThis->iBufSize)) == 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, const char *fmt, ...)
+{
+ DEFiRet;
+ va_list ap;
+
+ va_start(ap, fmt);
+ iRet = rsCStrConstructFromszStrv(ppThis, fmt, ap);
+ va_end(ap);
+
+ RETiRet;
+}
+
+
+/* construct from es_str_t string
+ * rgerhards 2010-12-03
+ */
+rsRetVal
+cstrConstructFromESStr(cstr_t **const ppThis, es_str_t *const str)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ CHKiRet(rsCStrConstruct(&pThis));
+
+ pThis->iStrLen = es_strlen(str);
+ pThis->iBufSize = pThis->iStrLen + 1;
+ if((pThis->pBuf = (uchar*) malloc(pThis->iBufSize)) == 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.
+ * rgerhards 2005-10-18
+ */
+rsRetVal ATTR_NONNULL()
+rsCStrConstructFromCStr(cstr_t **const ppThis, const cstr_t *const pFrom)
+{
+ DEFiRet;
+ cstr_t *pThis;
+
+ rsCHECKVALIDOBJECT(pFrom, OIDrsCStr);
+
+ CHKiRet(rsCStrConstruct(&pThis));
+ if(pFrom->iStrLen > 0) {
+ pThis->iStrLen = pFrom->iStrLen;
+ pThis->iBufSize = pFrom->iStrLen + 1;
+ if((pThis->pBuf = (uchar*) malloc(pThis->iBufSize)) == NULL) {
+ RSFREEOBJ(pThis);
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ memcpy(pThis->pBuf, pFrom->pBuf, pThis->iStrLen);
+ }
+
+ *ppThis = pThis;
+finalize_it:
+ RETiRet;
+}
+
+
+void rsCStrDestruct(cstr_t **const ppThis)
+{
+ free((*ppThis)->pBuf);
+ RSFREEOBJ(*ppThis);
+ *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
+ */
+static rsRetVal
+rsCStrExtendBuf(cstr_t *const __restrict__ pThis, const 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 */
+
+ #if DEV_DEBUG == 1
+ dbgprintf("extending string buffer, old %d, new %d\n", pThis->iBufSize, iNewSize);
+ #endif
+ CHKmalloc(pNewBuf = (uchar*) realloc(pThis->pBuf, iNewSize));
+ pThis->iBufSize = iNewSize;
+ pThis->pBuf = pNewBuf;
+
+finalize_it:
+ RETiRet;
+}
+
+/* Append a character to the current string object. This may only be done until
+ * cstrFinalize() is called.
+ * rgerhards, 2009-06-16
+ */
+rsRetVal cstrAppendChar(cstr_t *const __restrict__ pThis, const uchar c)
+{
+ rsRetVal iRet = RS_RET_OK;
+
+ if(pThis->iStrLen+1 >= 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;
+}
+
+/* append a string of known length. In this case, we make sure we do at most
+ * one additional memory allocation.
+ */
+rsRetVal rsCStrAppendStrWithLen(cstr_t *const pThis, const uchar*const psz, const 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 *const pThis, const uchar*const 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, const char *fmt, ...)
+{
+ DEFiRet;
+ va_list ap;
+ cstr_t *pStr = NULL;
+
+ va_start(ap, fmt);
+ iRet = rsCStrConstructFromszStrv(&pStr, (char*)fmt, ap);
+ va_end(ap);
+
+ if(iRet != RS_RET_OK)
+ goto finalize_it;
+
+ 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 *const __restrict__ pThis,
+ uchar *const __restrict__ pszNew)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+
+ if(pszNew == NULL) {
+ free(pThis->pBuf);
+ pThis->pBuf = NULL;
+ pThis->iStrLen = 0;
+ pThis->iBufSize = 0;
+ } else {
+ const size_t newlen = strlen((char*)pszNew);
+ if(newlen > pThis->iBufSize) {
+ uchar *const newbuf = (uchar*) realloc(pThis->pBuf, newlen + 1);
+ if(newbuf == NULL) {
+ /* we keep the old value, best we can do */
+ return RS_RET_OUT_OF_MEMORY;
+ }
+ pThis->pBuf = newbuf;
+ pThis->iBufSize = newlen + 1;
+ }
+ pThis->iStrLen = newlen;
+ memcpy(pThis->pBuf, pszNew, pThis->iStrLen);
+ }
+
+ return RS_RET_OK;
+}
+
+/* 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.
+ */
+uchar*
+cstrGetSzStrNoNULL(cstr_t *const __restrict__ pThis)
+{
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ assert(pThis->isFinalized);
+ return (pThis->pBuf == NULL) ? (uchar*) "" : pThis->pBuf;
+}
+
+
+/* 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 **ppThis, uchar **ppSz, int bRetNULL)
+{
+ DEFiRet;
+ uchar* pRetBuf;
+ cstr_t *pThis;
+
+ assert(ppThis != NULL);
+ pThis = *ppThis;
+ assert(pThis->isFinalized);
+ rsCHECKVALIDOBJECT(pThis, OIDrsCStr);
+ assert(ppSz != NULL);
+ assert(bRetNULL == 0 || bRetNULL == 1);
+
+ if(pThis->pBuf == NULL) {
+ if(bRetNULL == 0) {
+ CHKmalloc(pRetBuf = malloc(1));
+ *pRetBuf = '\0';
+ } else {
+ pRetBuf = NULL;
+ }
+ } else {
+ pThis->pBuf[pThis->iStrLen] = '\0'; /* space for this is reserved */
+ 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);
+ *ppThis = NULL;
+
+ 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
+size_t 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;
+
+ return RS_RET_OK;
+}
+
+/* Trim trailing whitespace from a given string
+ */
+void
+cstrTrimTrailingWhiteSpace(cstr_t *const __restrict__ 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! */
+ if(i != (int) pThis->iStrLen) {
+ pThis->iStrLen = i;
+ pThis->pBuf[pThis->iStrLen] = '\0'; /* we always have this space */ //TODO: can we remove this?
+ }
+
+done: return;
+}
+
+/* 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 *const __restrict__ pCS1, cstr_t *const __restrict__ pCS2)
+{
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ rsCHECKVALIDOBJECT(pCS2, OIDrsCStr);
+ if(pCS1->iStrLen == pCS2->iStrLen)
+ if(pCS1->iStrLen == 0)
+ return 0; /* zero-sized string are equal ;) */
+ else
+ return memcmp(pCS1->pBuf, pCS2->pBuf, pCS1->iStrLen);
+ 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 *const __restrict__ pCS1,
+ uchar *const __restrict__ psz,
+ const size_t iLenSz)
+{
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ assert(psz != NULL);
+ assert(iLenSz == strlen((char*)psz)); /* just make sure during debugging! */
+ if(iLenSz >= pCS1->iStrLen) {
+ if(pCS1->iStrLen == 0)
+ return 0; /* yes, it starts with a zero-sized string ;) */
+ else
+ return memcmp(psz, pCS1->pBuf, pCS1->iStrLen);
+ } 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);
+ int errcode;
+ if((errcode = regexp.regcomp(*cache, (char*) rsCStrGetSzStrNoNULL(pCS1),
+ (iType == 1 ? REG_EXTENDED : 0) | REG_NOSUB))) {
+ char errbuff[512];
+ regexp.regerror(errcode, *cache, errbuff, sizeof(errbuff));
+ LogError(0, NO_ERRCODE, "Error in regular expression: %s\n", errbuff);
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+ }
+ 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 one 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)
+{
+ rsCHECKVALIDOBJECT(pCS1, OIDrsCStr);
+ assert(iOffset < pCS1->iStrLen);
+ 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 ;) */
+ } else { /* we now have two non-empty strings of equal
+ * length, so we need to actually check if they
+ * are equal.
+ */
+ return memcmp(pCS1->pBuf+iOffset, psz, iLenSz);
+ }
+ }
+ else {
+ return pCS1->iStrLen - iOffset - iLenSz;
+ }
+}
+
+
+/* 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)
+ if(iLenSz == 0)
+ return 0; /* zero-sized strings are equal ;) */
+ else
+ return strncmp((char*)pCS1->pBuf, (char*)psz, iLenSz);
+ else
+ return (ssize_t) pCS1->iStrLen - (ssize_t) iLenSz;
+}
+
+
+/* Locate the first occurrence 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 ATTR_NONNULL(1, 2)
+rsCStrLocateInSzStr(cstr_t *const pThis, uchar *const sz)
+{
+ size_t i;
+ size_t iMax;
+ size_t len_sz = ustrlen(sz);
+ 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 = (pThis->iStrLen >= len_sz) ? 0 : len_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 ? (int) i : -1);
+}
+
+
+/* our exit function. TODO: remove once converted to a class
+ * rgerhards, 2008-03-11
+ */
+rsRetVal strExit(void)
+{
+ DEFiRet;
+ objRelease(regexp, LM_REGEXP_FILENAME);
+ RETiRet;
+}
+
+
+/* our init function. TODO: remove once converted to a class
+ */
+rsRetVal
+strInit(void)
+{
+ DEFiRet;
+ CHKiRet(objGetObjInterface(&obj));
+
+finalize_it:
+ RETiRet;
+}
diff --git a/runtime/stringbuf.h b/runtime/stringbuf.h
new file mode 100644
index 0000000..8fa3bb5
--- /dev/null
+++ b/runtime/stringbuf.h
@@ -0,0 +1,157 @@
+/* stringbuf.h
+ * The counted string object
+ *
+ * \author Rainer Gerhards <rgerhards@adiscon.com>
+ * \date 2005-09-07
+ * Initial version begun.
+ *
+ * Copyright 2005-2016 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 */
+ sbool isFinalized;
+#endif
+ uchar *pBuf; /**< pointer to the string buffer, may be NULL if string is empty */
+ 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, const uchar *sz);
+rsRetVal rsCStrConstructFromCStr(cstr_t **ppThis, const cstr_t *pFrom);
+rsRetVal rsCStrConstructFromszStrf(cstr_t **ppThis, const 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 cstrAppendChar(cstr_t *pThis, const uchar c);
+
+/* 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
+ */
+#ifdef NDEBUG
+#define cstrFinalize(pThis) { \
+ if((pThis)->iStrLen > 0) \
+ (pThis)->pBuf[(pThis)->iStrLen] = '\0'; /* space is always reserved for this */ \
+}
+#else
+#define cstrFinalize(pThis) { \
+ if((pThis)->iStrLen > 0) \
+ (pThis)->pBuf[(pThis)->iStrLen] = '\0'; /* space is always reserved for this */ \
+ (pThis)->isFinalized = 1; \
+}
+#endif
+
+
+/**
+ * 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);
+
+void 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, const 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, const 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, const char *fmt, ...) __attribute__((format(printf,2, 3)));
+
+/**
+ * Append an integer to the string. No special formatting is
+ * done.
+ */
+rsRetVal rsCStrAppendInt(cstr_t *pThis, long i);
+
+
+rsRetVal strExit(void);
+uchar* cstrGetSzStrNoNULL(cstr_t *pThis);
+#define rsCStrGetSzStrNoNULL(x) cstrGetSzStrNoNULL(x)
+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 rsCStrSzStrStartsWithCStr(cstr_t *pCS1, uchar *psz, size_t iLenSz);
+rsRetVal rsCStrSzStrMatchRegex(cstr_t *pCS1, uchar *psz, int iType, void *cache);
+void rsCStrRegexDestruct(void *rc);
+
+/* new calling interface */
+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) ((size_t)((x)->iStrLen))
+#else
+ size_t cstrLen(cstr_t *pThis);
+#endif
+#define rsCStrLen(s) cstrLen((s))
+
+#define rsCStrGetBufBeg(x) ((x)->pBuf)
+
+rsRetVal strInit(void);
+
+#endif /* single include */
diff --git a/runtime/syslogd-types.h b/runtime/syslogd-types.h
new file mode 100644
index 0000000..0924a3c
--- /dev/null
+++ b/runtime/syslogd-types.h
@@ -0,0 +1,114 @@
+/* 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-2018 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>
+
+/* we use RSTRUE/FALSE to prevent name claches with other packages */
+#define RSFALSE 0
+#define RSTRUE 1
+
+#define MAXFNAME 4096 /* max file pathname length */
+
+#define _DB_MAXCONNINFOLEN 2048 /* maximum length connection string */
+#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 wday;
+ 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!) */
+ intTiny inUTC; /* forced UTC? */
+};
+typedef struct syslogTime syslogTime_t;
+
+struct tzinfo {
+ char *id;
+ char offsMode;
+ int8_t offsHour;
+ int8_t offsMin;
+};
+typedef struct tzinfo tzinfo_t;
+
+typedef enum { ACT_STRING_PASSING = 0, ACT_ARRAY_PASSING = 1, ACT_MSG_PASSING = 2,
+ ACT_JSON_PASSING = 3} paramPassing_t;
+
+#endif /* #ifndef SYSLOGD_TYPES_INCLUDED */
+/* vi:set ai:
+ */
diff --git a/runtime/tcpclt.c b/runtime/tcpclt.c
new file mode 100644
index 0000000..47064dd
--- /dev/null
+++ b/runtime/tcpclt.c
@@ -0,0 +1,525 @@
+/* tcpclt.c
+ *
+ * This is the implementation of TCP-based syslog clients (the counterpart
+ * of the tcpsrv class).
+ *
+ * Copyright 2007-2018 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.
+ */
+#include "config.h"
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.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 "syslogd-types.h"
+#include "net.h"
+#include "tcpclt.h"
+#include "module-template.h"
+#include "srUtils.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+
+/* Initialize TCP sockets (for sender)
+ */
+static int
+CreateSocket(struct addrinfo *addrDest)
+{
+ int fd;
+ struct addrinfo *r;
+
+ r = addrDest;
+
+ while(r != NULL) {
+ fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol);
+ if (fd != -1) {
+ /* We can not allow the TCP sender to block syslogd, at least
+ * not in a single-threaded design. That would cause rsyslogd to
+ * loose input messages - which obviously also would affect
+ * other selector lines, too. So we do set it to non-blocking and
+ * handle the situation ourselfs (by discarding messages). IF we run
+ * dual-threaded, however, the situation is different: in this case,
+ * the receivers and the selector line processing are only loosely
+ * coupled via a memory buffer. Now, I think, we can afford the extra
+ * wait time. Thus, we enable blocking mode for TCP if we compile with
+ * pthreads. -- rgerhards, 2005-10-25
+ * And now, we always run on multiple threads... -- rgerhards, 2007-12-20
+ */
+ if (connect (fd, r->ai_addr, r->ai_addrlen) != 0) {
+ if(errno == EINPROGRESS) {
+ /* this is normal - will complete later select */
+ return fd;
+ } else {
+ char errStr[1024];
+ dbgprintf("create tcp connection failed, reason %s",
+ rs_strerror_r(errno, errStr, sizeof(errStr)));
+ }
+
+ }
+ else {
+ return fd;
+ }
+ close(fd);
+ }
+ else {
+ char errStr[1024];
+ dbgprintf("couldn't create send socket, reason %s", rs_strerror_r(errno, errStr,
+ sizeof(errStr)));
+ }
+ r = r->ai_next;
+ }
+
+ dbgprintf("no working socket could be obtained");
+
+ return -1;
+}
+
+
+
+/* Build frame based on selected framing
+ * This function was created by pulling code from TCPSend()
+ * on 2007-12-27 by rgerhards. Older comments are still relevant.
+ *
+ * In order to support compressed messages via TCP, we must support an
+ * octet-counting based framing (LF may be part of the compressed message).
+ * We are now supporting the same mode that is available in IETF I-D
+ * syslog-transport-tls-05 (current at the time of this writing). This also
+ * eases things when we go ahead and implement that framing. I have now made
+ * available two cases where this framing is used: either by explitely
+ * specifying it in the config file or implicitely when sending a compressed
+ * message. In the later case, compressed and uncompressed messages within
+ * the same session have different framings. If it is explicitely set to
+ * octet-counting, only this framing mode is used within the session.
+ * rgerhards, 2006-12-07
+ */
+static rsRetVal
+TCPSendBldFrame(tcpclt_t *pThis, char **pmsg, size_t *plen, int *pbMustBeFreed)
+{
+ DEFiRet;
+ TCPFRAMINGMODE framingToUse;
+ int bIsCompressed;
+ size_t len;
+ char *msg;
+ char *buf = NULL; /* if this is non-NULL, it MUST be freed before return! */
+
+ assert(plen != NULL);
+ assert(pbMustBeFreed != NULL);
+ assert(pmsg != NULL);
+
+ msg = *pmsg;
+ len = *plen;
+ bIsCompressed = *msg == 'z'; /* cache this, so that we can modify the message buffer */
+ /* select framing for this record. If we have a compressed record, we always need to
+ * use octet counting because the data potentially contains all control characters
+ * including LF.
+ */
+ framingToUse = bIsCompressed ? TCP_FRAMING_OCTET_COUNTING : pThis->tcp_framing;
+
+ /* now check if we need to add a line terminator. We need to
+ * copy the string in memory in this case, this is probably
+ * quicker than using writev and definitely quicker than doing
+ * two socket calls.
+ * rgerhards 2005-07-22
+ *
+ * Some messages already contain a \n character at the end
+ * of the message. We append one only if we there is not
+ * already one. This seems the best fit, though this also
+ * means the message does not arrive unaltered at the final
+ * destination. But in the spirit of legacy syslog, this is
+ * probably the best to do...
+ * rgerhards 2005-07-20
+ */
+
+ /* Build frame based on selected framing */
+ if(framingToUse == TCP_FRAMING_OCTET_STUFFING) {
+ if((*(msg+len-1) != pThis->tcp_framingDelimiter)) {
+ /* in the malloc below, we need to add 2 to the length. The
+ * reason is that we a) add one character and b) len does
+ * not take care of the '\0' byte. Up until today, it was just
+ * +1 , which caused rsyslogd to sometimes dump core.
+ * I have added this comment so that the logic is not accidently
+ * changed again. rgerhards, 2005-10-25
+ */
+ if((buf = malloc(len + 2)) == NULL) {
+ /* extreme mem shortage, try to solve
+ * as good as we can. No point in calling
+ * any alarms, they might as well run out
+ * of memory (the risk is very high, so we
+ * do NOT risk that). If we have a message of
+ * more than 1 byte (what I guess), we simply
+ * overwrite the last character.
+ * rgerhards 2005-07-22
+ */
+ if(len > 1) {
+ *(msg+len-1) = pThis->tcp_framingDelimiter;
+ } else {
+ /* we simply can not do anything in
+ * this case (its an error anyhow...).
+ */
+ }
+ } else {
+ /* we got memory, so we can copy the message */
+ memcpy(buf, msg, len); /* do not copy '\0' */
+ *(buf+len) = pThis->tcp_framingDelimiter;
+ *(buf+len+1) = '\0';
+ msg = buf; /* use new one */
+ ++len; /* care for the \n */
+ }
+ }
+ } else {
+ /* Octect-Counting
+ * In this case, we need to always allocate a buffer. This is because
+ * we need to put a header in front of the message text
+ */
+ char szLenBuf[16];
+ int iLenBuf;
+
+ /* important: the printf-mask is "%d<sp>" because there must be a
+ * space after the len!
+ *//* The chairs of the IETF syslog-sec WG have announced that it is
+ * consensus to do the octet count on the SYSLOG-MSG part only. I am
+ * now changing the code to reflect this. Hopefully, it will not change
+ * once again (there can no compatibility layer programmed for this).
+ * To be on the save side, I just comment the code out. I mark these
+ * comments with "IETF20061218".
+ * rgerhards, 2006-12-19
+ */
+ iLenBuf = snprintf(szLenBuf, sizeof(szLenBuf), "%d ", (int) len);
+ /* IETF20061218 iLenBuf =
+ snprintf(szLenBuf, sizeof(szLenBuf), "%d ", len + iLenBuf);*/
+
+ if((buf = malloc(len + iLenBuf)) == NULL) {
+ /* we are out of memory. This is an extreme situation. We do not
+ * call any alarm handlers because they most likely run out of mem,
+ * too. We are brave enough to call debug output, though. Other than
+ * that, there is nothing left to do. We can not sent the message (as
+ * in case of the other framing, because the message is incomplete.
+ * We could, however, send two chunks (header and text separate), but
+ * that would cause a lot of complexity in the code. So we think it
+ * is appropriate enough to just make sure we do not crash in this
+ * very unlikely case. For this, it is justified just to loose
+ * the message. Rgerhards, 2006-12-07
+ */
+ dbgprintf("Error: out of memory when building TCP octet-counted "
+ "frame. Message is lost, trying to continue.\n");
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+ memcpy(buf, szLenBuf, iLenBuf); /* header */
+ memcpy(buf + iLenBuf, msg, len); /* message */
+ len += iLenBuf; /* new message size */
+ msg = buf; /* set message buffer */
+ }
+
+ /* frame building complete, on to actual sending */
+
+ *plen = len;
+ if(buf == NULL) {
+ /* msg not modified */
+ *pbMustBeFreed = 0;
+ } else {
+ *pmsg = msg;
+ *pbMustBeFreed = 1;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Sends a TCP message. It is first checked if the
+ * session is open and, if not, it is opened. Then the send
+ * is tried. If it fails, one silent re-try is made. If the send
+ * fails again, an error status (-1) is returned. If all goes well,
+ * 0 is returned. The TCP session is NOT torn down.
+ * For now, EAGAIN is ignored (causing message loss) - but it is
+ * hard to do something intelligent in this case. With this
+ * implementation here, we can not block and/or defer. Things are
+ * probably a bit better when we move to liblogging. The alternative
+ * would be to enhance the current select server with buffering and
+ * write descriptors. This seems not justified, given the expected
+ * short life span of this code (and the unlikeliness of this event).
+ * rgerhards 2005-07-06
+ * This function is now expected to stay. Libloging won't be used for
+ * that purpose. I have added the param "len", because it is known by the
+ * caller and so saves us some time. Also, it MUST be given because there
+ * may be NULs inside msg so that we can not rely on strlen(). Please note
+ * that the restrictions outlined above do not existin in multi-threaded
+ * mode, which we assume will now be most often used. So there is no
+ * real issue with the potential message loss in single-threaded builds.
+ * rgerhards, 2006-11-30
+ * I greatly restructured the function to be more generic and work
+ * with function pointers. So it now can be used with any type of transport,
+ * as long as it follows stream semantics. This was initially done to
+ * support plain TCP and GSS via common code.
+ */
+static int
+Send(tcpclt_t *pThis, void *pData, char *msg, size_t len)
+{
+ DEFiRet;
+ int bDone = 0;
+ int retry = 0;
+ int bMsgMustBeFreed = 0;/* must msg be freed at end of function? 0 - no, 1 - yes */
+
+ ISOBJ_TYPE_assert(pThis, tcpclt);
+ assert(pData != NULL);
+ assert(msg != NULL);
+ assert(len > 0);
+
+ CHKiRet(TCPSendBldFrame(pThis, &msg, &len, &bMsgMustBeFreed));
+
+ if(pThis->iRebindInterval > 0 && ++pThis->iNumMsgs == pThis->iRebindInterval) {
+ /* we need to rebind, and use the retry logic for this*/
+ CHKiRet(pThis->prepRetryFunc(pData)); /* try to recover */
+ pThis->iNumMsgs = 0;
+ }
+
+ while(!bDone) { /* loop is broken when send succeeds or error occurs */
+ CHKiRet(pThis->initFunc(pData));
+ iRet = pThis->sendFunc(pData, msg, len);
+
+ if(iRet == RS_RET_OK || iRet == RS_RET_DEFER_COMMIT || iRet == RS_RET_PREVIOUS_COMMITTED) {
+ /* we are done, we also use this as indication that the previous
+ * message was succesfully received (it's not always the case, but its at
+ * least our best shot at it -- rgerhards, 2008-03-12
+ * As of 2008-06-09, we have implemented an algorithm which detects connection
+ * loss quite good in some (common) scenarios. Thus, the probability of
+ * message duplication due to the code below has increased. We so now have
+ * a config setting, default off, that enables the user to request retransmits.
+ * However, if not requested, we do NOT need to do all the stuff needed for it.
+ */
+ if(pThis->bResendLastOnRecon == 1) {
+ if(pThis->prevMsg != NULL)
+ free(pThis->prevMsg);
+ /* if we can not alloc a new buffer, we silently ignore it. The worst that
+ * happens is that we lose our message recovery buffer - anything else would
+ * be worse, so don't try anything ;) -- rgerhards, 2008-03-12
+ */
+ if((pThis->prevMsg = malloc(len)) != NULL) {
+ memcpy(pThis->prevMsg, msg, len);
+ pThis->lenPrevMsg = len;
+ }
+ }
+
+ /* we are done with this record */
+ bDone = 1;
+ } else {
+ if(retry == 0) { /* OK, one retry */
+ ++retry;
+ CHKiRet(pThis->prepRetryFunc(pData)); /* try to recover */
+ /* now try to send our stored previous message (which most probably
+ * didn't make it. Note that if bResendLastOnRecon is 0, prevMsg will
+ * never become non-NULL, so the check below covers all cases.
+ */
+ if(pThis->prevMsg != NULL) {
+ CHKiRet(pThis->initFunc(pData));
+ CHKiRet(pThis->sendFunc(pData, pThis->prevMsg, pThis->lenPrevMsg));
+ }
+ } else {
+ /* OK, max number of retries reached, nothing we can do */
+ bDone = 1;
+ }
+ }
+ }
+
+finalize_it:
+ if(bMsgMustBeFreed)
+ free(msg);
+ RETiRet;
+}
+
+
+/* set functions */
+static rsRetVal
+SetResendLastOnRecon(tcpclt_t *pThis, int bResendLastOnRecon)
+{
+ DEFiRet;
+ pThis->bResendLastOnRecon = (short) bResendLastOnRecon;
+ RETiRet;
+}
+static rsRetVal
+SetSendInit(tcpclt_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->initFunc = pCB;
+ RETiRet;
+}
+static rsRetVal
+SetSendPrepRetry(tcpclt_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->prepRetryFunc = pCB;
+ RETiRet;
+}
+static rsRetVal
+SetSendFrame(tcpclt_t *pThis, rsRetVal (*pCB)(void*, char*, size_t))
+{
+ DEFiRet;
+ pThis->sendFunc = pCB;
+ RETiRet;
+}
+static rsRetVal
+SetFraming(tcpclt_t *pThis, TCPFRAMINGMODE framing)
+{
+ DEFiRet;
+ pThis->tcp_framing = framing;
+ RETiRet;
+}
+static rsRetVal
+SetFramingDelimiter(tcpclt_t *pThis, uchar tcp_framingDelimiter)
+{
+ DEFiRet;
+ pThis->tcp_framingDelimiter = tcp_framingDelimiter;
+ RETiRet;
+}
+static rsRetVal
+SetRebindInterval(tcpclt_t *pThis, int iRebindInterval)
+{
+ DEFiRet;
+ pThis->iRebindInterval = iRebindInterval;
+ RETiRet;
+}
+
+
+/* Standard-Constructor
+ */
+BEGINobjConstruct(tcpclt) /* be sure to specify the object type also in END macro! */
+ pThis->tcp_framingDelimiter = '\n';
+ENDobjConstruct(tcpclt)
+
+
+/* ConstructionFinalizer
+ */
+static rsRetVal
+tcpcltConstructFinalize(tcpclt_t __attribute__((unused)) *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpclt);
+
+ RETiRet;
+}
+
+
+/* destructor for the tcpclt object */
+BEGINobjDestruct(tcpclt) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(tcpclt)
+ if(pThis->prevMsg != NULL)
+ free(pThis->prevMsg);
+ENDobjDestruct(tcpclt)
+
+
+/* ------------------------------ handling the interface plumbing ------------------------------ */
+
+/* queryInterface function
+ * rgerhards, 2008-03-12
+ */
+BEGINobjQueryInterface(tcpclt)
+CODESTARTobjQueryInterface(tcpclt)
+ if(pIf->ifVersion != tcpcltCURR_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 = tcpcltConstruct;
+ pIf->ConstructFinalize = tcpcltConstructFinalize;
+ pIf->Destruct = tcpcltDestruct;
+
+ pIf->CreateSocket = CreateSocket;
+ pIf->Send = Send;
+
+ /* set functions */
+ pIf->SetResendLastOnRecon = SetResendLastOnRecon;
+ pIf->SetSendInit = SetSendInit;
+ pIf->SetSendFrame = SetSendFrame;
+ pIf->SetSendPrepRetry = SetSendPrepRetry;
+ pIf->SetFraming = SetFraming;
+ pIf->SetFramingDelimiter = SetFramingDelimiter;
+ pIf->SetRebindInterval = SetRebindInterval;
+
+finalize_it:
+ENDobjQueryInterface(tcpclt)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(tcpclt, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(tcpclt)
+ /* release objects we no longer need */
+ENDObjClassExit(tcpclt)
+
+
+/* Initialize our class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-29
+ */
+BEGINObjClassInit(tcpclt, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ /* request objects we use */
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcpcltConstructFinalize);
+ENDObjClassInit(tcpclt)
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+
+BEGINmodExit
+CODESTARTmodExit
+ /* de-init in reverse order! */
+ tcpcltClassExit();
+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(tcpcltClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
+
+
+/*
+ * vi:set ai:
+ */
diff --git a/runtime/tcpclt.h b/runtime/tcpclt.h
new file mode 100644
index 0000000..5e04b29
--- /dev/null
+++ b/runtime/tcpclt.h
@@ -0,0 +1,76 @@
+/* tcpclt.h
+ *
+ * This are the definitions for the TCP based clients class.
+ *
+ * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007-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 TCPCLT_H_INCLUDED
+#define TCPCLT_H_INCLUDED 1
+
+#include "obj.h"
+
+/* the tcpclt object */
+typedef struct tcpclt_s {
+ BEGINobjInstance; /**< Data to implement generic object - MUST be the first data element! */
+ TCPFRAMINGMODE tcp_framing;
+ uchar tcp_framingDelimiter;
+ char *prevMsg;
+ short bResendLastOnRecon; /* should the last message be resent on a successful reconnect? */
+ size_t lenPrevMsg;
+ /* session specific callbacks */
+ int iRebindInterval; /* how often should the send socket be rebound? */
+ int iNumMsgs; /* number of messages during current "rebind session" */
+ rsRetVal (*initFunc)(void*);
+ rsRetVal (*sendFunc)(void*, char*, size_t);
+ rsRetVal (*prepRetryFunc)(void*);
+} tcpclt_t;
+
+
+/* interfaces */
+BEGINinterface(tcpclt) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*Construct)(tcpclt_t **ppThis);
+ rsRetVal (*ConstructFinalize)(tcpclt_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(tcpclt_t **ppThis);
+ int (*Send)(tcpclt_t *pThis, void*pData, char*msg, size_t len);
+ int (*CreateSocket)(struct addrinfo *addrDest);
+ /* set methods */
+ rsRetVal (*SetResendLastOnRecon)(tcpclt_t*, int);
+ rsRetVal (*SetSendInit)(tcpclt_t*, rsRetVal (*)(void*));
+ rsRetVal (*SetSendFrame)(tcpclt_t*, rsRetVal (*)(void*, char*, size_t));
+ rsRetVal (*SetSendPrepRetry)(tcpclt_t*, rsRetVal (*)(void*));
+ rsRetVal (*SetFraming)(tcpclt_t*, TCPFRAMINGMODE framing);
+ /* v3, 2009-07-14*/
+ rsRetVal (*SetRebindInterval)(tcpclt_t*, int iRebindInterval);
+ /* v4, 2017-06-10*/
+ rsRetVal (*SetFramingDelimiter)(tcpclt_t*, uchar tcp_framingDelimiter);
+ENDinterface(tcpclt)
+#define tcpcltCURR_IF_VERSION 4 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+PROTOTYPEObj(tcpclt);
+
+/* the name of our library binary */
+#define LM_TCPCLT_FILENAME "lmtcpclt"
+
+#endif /* #ifndef TCPCLT_H_INCLUDED */
+/* vim:set ai:
+ */
diff --git a/runtime/tcps_sess.c b/runtime/tcps_sess.c
new file mode 100644
index 0000000..b0832b1
--- /dev/null
+++ b/runtime/tcps_sess.c
@@ -0,0 +1,625 @@
+/* tcps_sess.c
+ *
+ * This implements a session of the tcpsrv object. For general
+ * comments, see header of tcpsrv.c.
+ *
+ * NOTE: read comments in module-template.h to understand how this file
+ * works!
+ *
+ * File begun on 2008-03-01 by RGerhards (extracted from tcpsrv.c, which
+ * based on the BSD-licensed syslogd.c)
+ *
+ * Copyright 2007-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.
+ */
+#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 "unicode-helper.h"
+#include "module-template.h"
+#include "net.h"
+#include "tcpsrv.h"
+#include "tcps_sess.h"
+#include "obj.h"
+#include "errmsg.h"
+#include "netstrm.h"
+#include "msg.h"
+#include "datetime.h"
+#include "prop.h"
+#include "ratelimit.h"
+#include "debug.h"
+#include "rsconf.h"
+
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(netstrm)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(datetime)
+
+
+/* forward definitions */
+static rsRetVal Close(tcps_sess_t *pThis);
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(tcps_sess) /* be sure to specify the object type also in END macro! */
+ pThis->iMsg = 0; /* just make sure... */
+ pThis->iMaxLine = glbl.GetMaxLine(runConf);
+ pThis->inputState = eAtStrtFram; /* indicate frame header expected */
+ pThis->eFraming = TCP_FRAMING_OCTET_STUFFING; /* just make sure... */
+ /* now allocate the message reception buffer */
+ CHKmalloc(pThis->pMsg = (uchar*) malloc(pThis->iMaxLine + 1));
+finalize_it:
+ENDobjConstruct(tcps_sess)
+
+
+/* ConstructionFinalizer
+ */
+static rsRetVal
+tcps_sessConstructFinalize(tcps_sess_t __attribute__((unused)) *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ if(pThis->pSrv->OnSessConstructFinalize != NULL) {
+ CHKiRet(pThis->pSrv->OnSessConstructFinalize(&pThis->pUsr));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* destructor for the tcps_sess object */
+BEGINobjDestruct(tcps_sess) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(tcps_sess)
+ if(pThis->pStrm != NULL)
+ netstrm.Destruct(&pThis->pStrm);
+
+ if(pThis->pSrv->pOnSessDestruct != NULL) {
+ pThis->pSrv->pOnSessDestruct(&pThis->pUsr);
+ }
+ /* now destruct our own properties */
+ if(pThis->fromHost != NULL)
+ CHKiRet(prop.Destruct(&pThis->fromHost));
+ if(pThis->fromHostIP != NULL)
+ CHKiRet(prop.Destruct(&pThis->fromHostIP));
+ free(pThis->pMsg);
+ENDobjDestruct(tcps_sess)
+
+
+/* debugprint for the tcps_sess object */
+BEGINobjDebugPrint(tcps_sess) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(tcps_sess)
+ENDobjDebugPrint(tcps_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(tcps_sess_t *pThis, uchar *pszHost)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+
+ if(pThis->fromHost == NULL)
+ CHKiRet(prop.Construct(&pThis->fromHost));
+
+ CHKiRet(prop.SetString(pThis->fromHost, pszHost, ustrlen(pszHost)));
+
+finalize_it:
+ free(pszHost); /* we must free according to our (old) calling conventions */
+ 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(tcps_sess_t *pThis, prop_t *ip)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+
+ if(pThis->fromHostIP != NULL) {
+ prop.Destruct(&pThis->fromHostIP);
+ }
+ pThis->fromHostIP = ip;
+ RETiRet;
+}
+
+static rsRetVal
+SetStrm(tcps_sess_t *pThis, netstrm_t *pStrm)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ pThis->pStrm = pStrm;
+ RETiRet;
+}
+
+
+static rsRetVal
+SetMsgIdx(tcps_sess_t *pThis, int idx)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ pThis->iMsg = idx;
+ RETiRet;
+}
+
+
+/* set our parent, the tcpsrv object */
+static rsRetVal
+SetTcpsrv(tcps_sess_t *pThis, tcpsrv_t *pSrv)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ ISOBJ_TYPE_assert(pSrv, tcpsrv);
+ pThis->pSrv = pSrv;
+ RETiRet;
+}
+
+
+/* set our parent listener info*/
+static rsRetVal
+SetLstnInfo(tcps_sess_t *pThis, tcpLstnPortList_t *pLstnInfo)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ assert(pLstnInfo != NULL);
+ pThis->pLstnInfo = pLstnInfo;
+ /* set cached elements */
+ pThis->bSuppOctetFram = pLstnInfo->cnf_params->bSuppOctetFram;
+ pThis->bSPFramingFix = pLstnInfo->cnf_params->bSPFramingFix;
+ RETiRet;
+}
+
+
+static rsRetVal
+SetUsrP(tcps_sess_t *pThis, void *pUsr)
+{
+ DEFiRet;
+ pThis->pUsr = pUsr;
+ RETiRet;
+}
+
+
+static rsRetVal
+SetOnMsgReceive(tcps_sess_t *pThis, rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int))
+{
+ DEFiRet;
+ pThis->DoSubmitMessage = OnMsgReceive;
+ RETiRet;
+}
+
+
+/* This is a helper for submitting the message to the rsyslog core.
+ * It does some common processing, including resetting the various
+ * state variables to a "processed" state.
+ * Note that this function is also called if we had a buffer overflow
+ * due to a too-long message. So far, there is no indication this
+ * happened and it may be worth thinking about different handling
+ * of this case (what obviously would require a change to this
+ * function or some related code).
+ * rgerhards, 2009-04-23
+ */
+static rsRetVal
+defaultDoSubmitMessage(tcps_sess_t *pThis, struct syslogTime *stTime, time_t ttGenTime, multi_submit_t *pMultiSub)
+{
+ smsg_t *pMsg;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ const tcpLstnParams_t *const cnf_params = pThis->pLstnInfo->cnf_params;
+
+ if(pThis->iMsg == 0) {
+ DBGPRINTF("discarding zero-sized message\n");
+ FINALIZE;
+ }
+
+ if(pThis->DoSubmitMessage != NULL) {
+ pThis->DoSubmitMessage(pThis, pThis->pMsg, pThis->iMsg);
+ FINALIZE;
+ }
+
+ /* we now create our own message object and submit it to the queue */
+ CHKiRet(msgConstructWithTime(&pMsg, stTime, ttGenTime));
+ MsgSetRawMsg(pMsg, (char*)pThis->pMsg, pThis->iMsg);
+ MsgSetInputName(pMsg, cnf_params->pInputName);
+ if(cnf_params->dfltTZ[0] != '\0')
+ MsgSetDfltTZ(pMsg, (char*) cnf_params->dfltTZ);
+ MsgSetFlowControlType(pMsg, pThis->pSrv->bUseFlowControl
+ ? eFLOWCTL_LIGHT_DELAY : eFLOWCTL_NO_DELAY);
+ pMsg->msgFlags = NEEDS_PARSING | PARSE_HOSTNAME;
+ MsgSetRcvFrom(pMsg, pThis->fromHost);
+ CHKiRet(MsgSetRcvFromIP(pMsg, pThis->fromHostIP));
+ MsgSetRuleset(pMsg, cnf_params->pRuleset);
+
+ STATSCOUNTER_INC(pThis->pLstnInfo->ctrSubmit, pThis->pLstnInfo->mutCtrSubmit);
+ ratelimitAddMsg(pThis->pLstnInfo->ratelimiter, pMultiSub, pMsg);
+
+finalize_it:
+ /* reset status variables */
+ pThis->iMsg = 0;
+
+ RETiRet;
+}
+
+
+
+/* This should be called before a normal (non forced) close
+ * of a TCP session. This function checks if there is any unprocessed
+ * message left in the TCP stream. Such a message is probably a
+ * fragement. If evrything goes well, we must be right at the
+ * beginnig of a new frame without any data received from it. If
+ * not, there is some kind of a framing error. I think I remember that
+ * some legacy syslog/TCP implementations have non-LF terminated
+ * messages at the end of the stream. For now, we allow this behaviour.
+ * Later, it should probably become a configuration option.
+ * rgerhards, 2006-12-07
+ */
+static rsRetVal
+PrepareClose(tcps_sess_t *pThis)
+{
+ struct syslogTime stTime;
+ time_t ttGenTime;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+
+ if(pThis->inputState == eAtStrtFram) {
+ /* this is how it should be. There is no unprocessed
+ * data left and such we have nothing to do. For simplicity
+ * reasons, we immediately return in that case.
+ */
+ FINALIZE;
+ }
+
+ /* we have some data left! */
+ if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) {
+ /* In this case, we have an invalid frame count and thus
+ * generate an error message and discard the frame.
+ */
+ LogError(0, NO_ERRCODE, "Incomplete frame at end of stream in session %p - "
+ "ignoring extra data (a message may be lost).", pThis->pStrm);
+ /* nothing more to do */
+ } else { /* here, we have traditional framing. Missing LF at the end
+ * of message may occur. As such, we process the message in
+ * this case.
+ */
+ DBGPRINTF("Extra data at end of stream in legacy syslog/tcp message - processing\n");
+ datetime.getCurrTime(&stTime, &ttGenTime, TIME_IN_LOCALTIME);
+ defaultDoSubmitMessage(pThis, &stTime, ttGenTime, NULL);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Closes a TCP session
+ * No attention is paid to the return code
+ * of close, so potential-double closes are not detected.
+ */
+static rsRetVal
+Close(tcps_sess_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ netstrm.Destruct(&pThis->pStrm);
+ if(pThis->fromHost != NULL) {
+ prop.Destruct(&pThis->fromHost);
+ }
+ if(pThis->fromHostIP != NULL)
+ prop.Destruct(&pThis->fromHostIP);
+
+ RETiRet;
+}
+
+
+/* process the data received. As TCP is stream based, we need to process the
+ * data inside a state machine. The actual data received is passed in byte-by-byte
+ * from DataRcvd, and this function here compiles messages from them and submits
+ * the end result to the queue. Introducing this function fixes a long-term bug ;)
+ * rgerhards, 2008-03-14
+ */
+static rsRetVal ATTR_NONNULL(1)
+processDataRcvd(tcps_sess_t *pThis,
+ const char c,
+ struct syslogTime *stTime,
+ const time_t ttGenTime,
+ multi_submit_t *pMultiSub,
+ unsigned *const __restrict__ pnMsgs)
+{
+ DEFiRet;
+ const tcpLstnParams_t *const cnf_params = pThis->pLstnInfo->cnf_params;
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+
+ if(pThis->inputState == eAtStrtFram) {
+ if(c >= '0' && c <= '9' && pThis->bSuppOctetFram) {
+ pThis->inputState = eInOctetCnt;
+ pThis->iOctetsRemain = 0;
+ pThis->eFraming = TCP_FRAMING_OCTET_COUNTING;
+ } else if(c == ' ' && pThis->bSPFramingFix) {
+ /* Cisco ASA very occasionally sends a SP after a LF, which
+ * thrashes framing if not taken special care of. Here,
+ * we permit space *in front of the next frame* and
+ * ignore it.
+ */
+ FINALIZE;
+ } else {
+ pThis->inputState = eInMsg;
+ pThis->eFraming = TCP_FRAMING_OCTET_STUFFING;
+ }
+ }
+
+ if(pThis->inputState == eInMsg) {
+ #if 0 // set to 1 for ultra-verbose
+ DBGPRINTF("DEBUG: processDataRcvd c=%c remain=%d\n",
+ c, pThis->iOctetsRemain);
+ #endif
+
+ if(( ((c == '\n') && !pThis->pSrv->bDisableLFDelim)
+ || ((pThis->pSrv->addtlFrameDelim != TCPSRV_NO_ADDTL_DELIMITER)
+ && (c == pThis->pSrv->addtlFrameDelim))
+ ) && pThis->eFraming == TCP_FRAMING_OCTET_STUFFING) { /* record delimiter? */
+ defaultDoSubmitMessage(pThis, stTime, ttGenTime, pMultiSub);
+ ++(*pnMsgs);
+ pThis->inputState = eAtStrtFram;
+ } else {
+ /* IMPORTANT: here we copy the actual frame content to the message - for BOTH framing modes!
+ * If we have a message that is larger than the max msg size, we truncate it. This is the best
+ * we can do in light of what the engine supports. -- rgerhards, 2008-03-14
+ */
+ if(pThis->iMsg < pThis->iMaxLine) {
+ *(pThis->pMsg + pThis->iMsg++) = c;
+ } else {
+ /* emergency, we now need to flush, no matter if we are at end of message or not... */
+ DBGPRINTF("error: message received is larger than max msg size, we %s it - c=%x\n",
+ pThis->pSrv->discardTruncatedMsg == 1 ? "truncate" : "split", c);
+ defaultDoSubmitMessage(pThis, stTime, ttGenTime, pMultiSub);
+ ++(*pnMsgs);
+ if(pThis->pSrv->discardTruncatedMsg == 1) {
+ if (pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) {
+ pThis->iOctetsRemain--;
+ if (pThis->iOctetsRemain == 0) {
+ pThis->inputState = eAtStrtFram;
+ FINALIZE;
+ }
+ }
+ pThis->inputState = eInMsgTruncating;
+ FINALIZE;
+ }
+ }
+ }
+
+ if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) {
+ /* do we need to find end-of-frame via octet counting? */
+ pThis->iOctetsRemain--;
+ if(pThis->iOctetsRemain < 1) {
+ /* we have end of frame! */
+ defaultDoSubmitMessage(pThis, stTime, ttGenTime, pMultiSub);
+ ++(*pnMsgs);
+ pThis->inputState = eAtStrtFram;
+ }
+ }
+ } else if(pThis->inputState == eInMsgTruncating) {
+ if(pThis->eFraming == TCP_FRAMING_OCTET_COUNTING) {
+ DBGPRINTF("DEBUG: TCP_FRAMING_OCTET_COUNTING eInMsgTruncating c=%c remain=%d\n",
+ c, pThis->iOctetsRemain);
+
+ pThis->iOctetsRemain--;
+ if(pThis->iOctetsRemain < 1) {
+ pThis->inputState = eAtStrtFram;
+ }
+ } else {
+ if( ((c == '\n') && !pThis->pSrv->bDisableLFDelim)
+ || ((pThis->pSrv->addtlFrameDelim != TCPSRV_NO_ADDTL_DELIMITER)
+ && (c == pThis->pSrv->addtlFrameDelim))
+ ) {
+ pThis->inputState = eAtStrtFram;
+ }
+ }
+ } else {
+ assert(pThis->inputState == eInOctetCnt);
+ if(c >= '0' && c <= '9') { /* isdigit() the faster way */
+ if(pThis->iOctetsRemain <= 200000000) {
+ pThis->iOctetsRemain = pThis->iOctetsRemain * 10 + c - '0';
+ }
+ if(pThis->iMsg < pThis->iMaxLine) {
+ *(pThis->pMsg + pThis->iMsg++) = c;
+ }
+ } else { /* done with the octet count, so this must be the SP terminator */
+ uchar *propPeerName = NULL;
+ uchar *propPeerIP = NULL;
+ int lenPeerName = 0;
+ int lenPeerIP = 0;
+ DBGPRINTF("TCP Message with octet-counter, size %d.\n", pThis->iOctetsRemain);
+ prop.GetString(pThis->fromHost, &propPeerName, &lenPeerName);
+ prop.GetString(pThis->fromHost, &propPeerIP, &lenPeerIP);
+ if(c != ' ') {
+ LogError(0, NO_ERRCODE, "imtcp %s: Framing Error in received TCP message from "
+ "peer: (hostname) %s, (ip) %s: delimiter is not SP but has "
+ "ASCII value %d.", cnf_params->pszInputName, propPeerName, propPeerIP, c);
+ }
+ if(pThis->iOctetsRemain < 1) {
+ /* TODO: handle the case where the octet count is 0! */
+ LogError(0, NO_ERRCODE, "imtcp %s: Framing Error in received TCP message from "
+ "peer: (hostname) %s, (ip) %s: invalid octet count %d.",
+ cnf_params->pszInputName, propPeerName, propPeerIP, pThis->iOctetsRemain);
+ pThis->eFraming = TCP_FRAMING_OCTET_STUFFING;
+ } else if(pThis->iOctetsRemain > pThis->iMaxLine) {
+ /* while we can not do anything against it, we can at least log an indication
+ * that something went wrong) -- rgerhards, 2008-03-14
+ */
+ LogError(0, NO_ERRCODE, "imtcp %s: received oversize message from peer: "
+ "(hostname) %s, (ip) %s: size is %d bytes, max msg size "
+ "is %d, truncating...", cnf_params->pszInputName, propPeerName,
+ propPeerIP, pThis->iOctetsRemain, pThis->iMaxLine);
+ }
+ if(pThis->iOctetsRemain > pThis->pSrv->maxFrameSize) {
+ LogError(0, NO_ERRCODE, "imtcp %s: Framing Error in received TCP message from "
+ "peer: (hostname) %s, (ip) %s: frame too large: %d, change "
+ "to octet stuffing", cnf_params->pszInputName, propPeerName, propPeerIP,
+ pThis->iOctetsRemain);
+ pThis->eFraming = TCP_FRAMING_OCTET_STUFFING;
+ } else {
+ pThis->iMsg = 0;
+ }
+ pThis->inputState = eInMsg;
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Processes the data received via a TCP 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!). iTCPSess must be
+ * the index of the TCP 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
+ * As a performance optimization, we pick up the timestamp here. Acutally,
+ * this *is* the *correct* reception step for all the data we received, because
+ * we have just received a bunch of data! -- rgerhards, 2009-06-16
+ */
+#define NUM_MULTISUB 1024
+static rsRetVal
+DataRcvd(tcps_sess_t *pThis, char *pData, const size_t iLen)
+{
+ multi_submit_t multiSub;
+ smsg_t *pMsgs[NUM_MULTISUB];
+ struct syslogTime stTime;
+ time_t ttGenTime;
+ char *pEnd;
+ unsigned nMsgs = 0;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcps_sess);
+ assert(pData != NULL);
+ assert(iLen > 0);
+
+ datetime.getCurrTime(&stTime, &ttGenTime, TIME_IN_LOCALTIME);
+ multiSub.ppMsgs = pMsgs;
+ multiSub.maxElem = NUM_MULTISUB;
+ multiSub.nElem = 0;
+
+ /* We now copy the message to the session buffer. */
+ pEnd = pData + iLen; /* this is one off, which is intensional */
+
+ while(pData < pEnd) {
+ CHKiRet(processDataRcvd(pThis, *pData++, &stTime, ttGenTime, &multiSub, &nMsgs));
+ }
+ iRet = multiSubmitFlush(&multiSub);
+
+ if(runConf->globals.senderKeepTrack)
+ statsRecordSender(propGetSzStr(pThis->fromHost), nMsgs, ttGenTime);
+
+finalize_it:
+ RETiRet;
+}
+#undef NUM_MULTISUB
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(tcps_sess)
+CODESTARTobjQueryInterface(tcps_sess)
+ if(pIf->ifVersion != tcps_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 = tcps_sessDebugPrint;
+ pIf->Construct = tcps_sessConstruct;
+ pIf->ConstructFinalize = tcps_sessConstructFinalize;
+ pIf->Destruct = tcps_sessDestruct;
+
+ pIf->PrepareClose = PrepareClose;
+ pIf->Close = Close;
+ pIf->DataRcvd = DataRcvd;
+
+ pIf->SetUsrP = SetUsrP;
+ pIf->SetTcpsrv = SetTcpsrv;
+ pIf->SetLstnInfo = SetLstnInfo;
+ pIf->SetHost = SetHost;
+ pIf->SetHostIP = SetHostIP;
+ pIf->SetStrm = SetStrm;
+ pIf->SetMsgIdx = SetMsgIdx;
+ pIf->SetOnMsgReceive = SetOnMsgReceive;
+finalize_it:
+ENDobjQueryInterface(tcps_sess)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(tcps_sess, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(tcps_sess)
+ /* release objects we no longer need */
+ objRelease(netstrm, LM_NETSTRMS_FILENAME);
+ objRelease(datetime, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ENDObjClassExit(tcps_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(tcps_sess, 1, OBJ_IS_CORE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ /* request objects we use */
+ CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ objRelease(glbl, CORE_COMPONENT);
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, tcps_sessDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcps_sessConstructFinalize);
+ENDObjClassInit(tcps_sess)
+
+/* vim:set ai:
+ */
diff --git a/runtime/tcps_sess.h b/runtime/tcps_sess.h
new file mode 100644
index 0000000..578b559
--- /dev/null
+++ b/runtime/tcps_sess.h
@@ -0,0 +1,90 @@
+/* Definitions for tcps_sess class. This implements a session of the
+ * plain TCP server.
+ *
+ * Copyright 2008-2022 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_TCPS_SESS_H
+#define INCLUDED_TCPS_SESS_H
+
+#include "obj.h"
+#include "prop.h"
+
+/* a forward-definition, we are somewhat cyclic */
+struct tcpsrv_s;
+
+/* the tcps_sess object */
+struct tcps_sess_s {
+ BEGINobjInstance; /* Data to implement generic object - MUST be the first data element! */
+ tcpsrv_t *pSrv; /* pointer back to my server (e.g. for callbacks) */
+ tcpLstnPortList_t *pLstnInfo; /* pointer back to listener info */
+ netstrm_t *pStrm;
+ int iMsg; /* index of next char to store in msg */
+ sbool bSuppOctetFram; /**< copy from listener, to speed up access */
+ sbool bSPFramingFix;
+ enum {
+ eAtStrtFram,
+ eInOctetCnt,
+ eInMsg,
+ eInMsgTruncating
+ } inputState; /* our current state */
+ int iOctetsRemain; /* Number of Octets remaining in message */
+ TCPFRAMINGMODE eFraming;
+ uchar *pMsg; /* message (fragment) received */
+ prop_t *fromHost; /* host name we received messages from */
+ prop_t *fromHostIP;
+ void *pUsr; /* a user-pointer */
+ rsRetVal (*DoSubmitMessage)(tcps_sess_t*, uchar*, int); /* submit message callback */
+ int iMaxLine; /* fast lookup buffer for config property */
+};
+
+
+/* interfaces */
+BEGINinterface(tcps_sess) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(tcps_sess);
+ rsRetVal (*Construct)(tcps_sess_t **ppThis);
+ rsRetVal (*ConstructFinalize)(tcps_sess_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(tcps_sess_t **ppThis);
+ rsRetVal (*PrepareClose)(tcps_sess_t *pThis);
+ rsRetVal (*Close)(tcps_sess_t *pThis);
+ rsRetVal (*DataRcvd)(tcps_sess_t *pThis, char *pData, size_t iLen);
+ /* set methods */
+ rsRetVal (*SetTcpsrv)(tcps_sess_t *pThis, struct tcpsrv_s *pSrv);
+ rsRetVal (*SetLstnInfo)(tcps_sess_t *pThis, tcpLstnPortList_t *pLstnInfo);
+ rsRetVal (*SetUsrP)(tcps_sess_t*, void*);
+ rsRetVal (*SetHost)(tcps_sess_t *pThis, uchar*);
+ rsRetVal (*SetHostIP)(tcps_sess_t *pThis, prop_t*);
+ rsRetVal (*SetStrm)(tcps_sess_t *pThis, netstrm_t*);
+ rsRetVal (*SetMsgIdx)(tcps_sess_t *pThis, int);
+ rsRetVal (*SetOnMsgReceive)(tcps_sess_t *pThis, rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int));
+ENDinterface(tcps_sess)
+#define tcps_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, rgerhards, 2013-01-21:
+ * - signature of SetHostIP() changed
+ */
+
+
+/* prototypes */
+PROTOTYPEObj(tcps_sess);
+
+
+#endif /* #ifndef INCLUDED_TCPS_SESS_H */
diff --git a/runtime/tcpsrv.c b/runtime/tcpsrv.c
new file mode 100644
index 0000000..df9bcec
--- /dev/null
+++ b/runtime/tcpsrv.c
@@ -0,0 +1,1799 @@
+/* tcpsrv.c
+ *
+ * Common code for plain TCP syslog based servers. This is currently being
+ * utilized by imtcp and imgssapi.
+ *
+ * NOTE: this is *not* a generic TCP server, but one for syslog servers. For
+ * generic stream servers, please use ./runtime/strmsrv.c!
+ *
+ * There are actually two classes within the tcpserver code: one is
+ * the tcpsrv itself, the other one is its sessions. This is a helper
+ * class to tcpsrv.
+ *
+ * The common code here calls upon specific functionality by using
+ * callbacks. The specialised input modules need to set the proper
+ * callbacks before the code is run. The tcpsrv then calls back
+ * into the specific input modules at the appropriate time.
+ *
+ * NOTE: read comments in module-template.h to understand how this file
+ * works!
+ *
+ * File begun on 2007-12-21 by RGerhards (extracted from syslogd.c[which was
+ * licensed under BSD at the time of the rsyslog fork])
+ *
+ * Copyright 2007-2022 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.
+ */
+#include "config.h"
+#include <stdio.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 <pthread.h>
+#include <sys/types.h>
+#include <signal.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 "tcpsrv.h"
+#include "obj.h"
+#include "glbl.h"
+#include "netstrms.h"
+#include "netstrm.h"
+#include "nssel.h"
+#include "nspoll.h"
+#include "errmsg.h"
+#include "ruleset.h"
+#include "ratelimit.h"
+#include "unicode-helper.h"
+
+PRAGMA_INGORE_Wswitch_enum
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* defines */
+#define TCPSESS_MAX_DEFAULT 200 /* default for nbr of tcp sessions if no number is given */
+#define TCPLSTN_MAX_DEFAULT 20 /* default for nbr of listeners */
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(conf)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(ruleset)
+DEFobjCurrIf(tcps_sess)
+DEFobjCurrIf(net)
+DEFobjCurrIf(netstrms)
+DEFobjCurrIf(netstrm)
+DEFobjCurrIf(nssel)
+DEFobjCurrIf(nspoll)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(statsobj)
+
+static void startWorkerPool(void);
+
+/* The following structure controls the worker threads. Global data is
+ * needed for their access.
+ */
+static struct wrkrInfo_s {
+ pthread_t tid; /* the worker's thread ID */
+ pthread_cond_t run;
+ int idx;
+ tcpsrv_t *pSrv; /* pSrv == NULL -> idle */
+ nspoll_t *pPoll;
+ void *pUsr;
+ sbool enabled;
+ long long unsigned numCalled; /* how often was this called */
+} wrkrInfo[4];
+static sbool bWrkrRunning; /* are the worker threads running? */
+static pthread_mutex_t wrkrMut;
+static pthread_cond_t wrkrIdle;
+static int wrkrMax = 4;
+static int wrkrRunning;
+
+/* add new listener port to listener port list
+ * rgerhards, 2009-05-21
+ */
+static rsRetVal ATTR_NONNULL(1, 2)
+addNewLstnPort(tcpsrv_t *const pThis, tcpLstnParams_t *const cnf_params)
+{
+ tcpLstnPortList_t *pEntry;
+ uchar statname[64];
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ /* create entry */
+ CHKmalloc(pEntry = (tcpLstnPortList_t*)calloc(1, sizeof(tcpLstnPortList_t)));
+ pEntry->cnf_params = cnf_params;
+
+ strcpy((char*)pEntry->cnf_params->dfltTZ, (char*)pThis->dfltTZ);
+ pEntry->cnf_params->bSPFramingFix = pThis->bSPFramingFix;
+ pEntry->cnf_params->bPreserveCase = pThis->bPreserveCase;
+ pEntry->pSrv = pThis;
+
+
+ /* support statistics gathering */
+ CHKiRet(ratelimitNew(&pEntry->ratelimiter, "tcperver", NULL));
+ ratelimitSetLinuxLike(pEntry->ratelimiter, pThis->ratelimitInterval, pThis->ratelimitBurst);
+ ratelimitSetThreadSafe(pEntry->ratelimiter);
+
+ CHKiRet(statsobj.Construct(&(pEntry->stats)));
+ snprintf((char*)statname, sizeof(statname), "%s(%s)", cnf_params->pszInputName, cnf_params->pszPort);
+ statname[sizeof(statname)-1] = '\0'; /* just to be on the save side... */
+ CHKiRet(statsobj.SetName(pEntry->stats, statname));
+ CHKiRet(statsobj.SetOrigin(pEntry->stats, pThis->pszOrigin));
+ STATSCOUNTER_INIT(pEntry->ctrSubmit, pEntry->mutCtrSubmit);
+ CHKiRet(statsobj.AddCounter(pEntry->stats, UCHAR_CONSTANT("submitted"),
+ ctrType_IntCtr, CTR_FLAG_RESETTABLE, &(pEntry->ctrSubmit)));
+ CHKiRet(statsobj.ConstructFinalize(pEntry->stats));
+
+ /* all OK - add to list */
+ pEntry->pNext = pThis->pLstnPorts;
+ pThis->pLstnPorts = pEntry;
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pEntry != NULL) {
+ if(pEntry->cnf_params->pInputName != NULL) {
+ prop.Destruct(&pEntry->cnf_params->pInputName);
+ }
+ if(pEntry->ratelimiter != NULL) {
+ ratelimitDestruct(pEntry->ratelimiter);
+ }
+ if(pEntry->stats != NULL) {
+ statsobj.Destruct(&pEntry->stats);
+ }
+ free(pEntry);
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* configure TCP listener settings.
+ * Note: pszPort is handed over to us - the caller MUST NOT free it!
+ * rgerhards, 2008-03-20
+ */
+static rsRetVal ATTR_NONNULL(1,2)
+configureTCPListen(tcpsrv_t *const pThis, tcpLstnParams_t *const cnf_params)
+{
+ assert(cnf_params->pszPort != NULL);
+ int i;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ /* extract port */
+ const uchar *pPort = cnf_params->pszPort;
+ i = 0;
+ while(isdigit((int) *pPort)) {
+ i = i * 10 + *pPort++ - '0';
+ }
+
+ if(i >= 0 && i <= 65535) {
+ CHKiRet(addNewLstnPort(pThis, cnf_params));
+ } else {
+ LogError(0, NO_ERRCODE, "Invalid TCP listen port %s - ignored.\n", cnf_params->pszPort);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Initialize the session table
+ * returns 0 if OK, somewhat else otherwise
+ */
+static rsRetVal
+TCPSessTblInit(tcpsrv_t *pThis)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ assert(pThis->pSessions == NULL);
+
+ DBGPRINTF("Allocating buffer for %d TCP sessions.\n", pThis->iSessMax);
+ if((pThis->pSessions = (tcps_sess_t **) calloc(pThis->iSessMax, sizeof(tcps_sess_t *))) == NULL) {
+ DBGPRINTF("Error: TCPSessInit() could not alloc memory for TCP 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
+TCPSessTblFindFreeSpot(tcpsrv_t *pThis)
+{
+ register int i;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ 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
+TCPSessGetNxtSess(tcpsrv_t *pThis, int iCurr)
+{
+ register int i;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ assert(pThis->pSessions != NULL);
+ for(i = iCurr + 1 ; i < pThis->iSessMax ; ++i) {
+ if(pThis->pSessions[i] != NULL)
+ break;
+ }
+
+ return((i < pThis->iSessMax) ? i : -1);
+}
+
+
+/* De-Initialize TCP listner sockets.
+ * This function deinitializes everything, including freeing the
+ * session table. No TCP listen receive operations are permitted
+ * unless the subsystem is reinitialized.
+ * rgerhards, 2007-06-21
+ */
+static void ATTR_NONNULL()
+deinit_tcp_listener(tcpsrv_t *const pThis)
+{
+ int i;
+ tcpLstnPortList_t *pEntry;
+ tcpLstnPortList_t *pDel;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ if(pThis->pSessions != NULL) {
+ /* close all TCP connections! */
+ if(!pThis->bUsingEPoll) {
+ i = TCPSessGetNxtSess(pThis, -1);
+ while(i != -1) {
+ tcps_sess.Destruct(&pThis->pSessions[i]);
+ /* now get next... */
+ i = TCPSessGetNxtSess(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 tcp listen ports */
+ pEntry = pThis->pLstnPorts;
+ while(pEntry != NULL) {
+ prop.Destruct(&pEntry->cnf_params->pInputName);
+ free((void*)pEntry->cnf_params->pszInputName);
+ free((void*)pEntry->cnf_params->pszPort);
+ free((void*)pEntry->cnf_params->pszAddr);
+ free((void*)pEntry->cnf_params->pszLstnPortFileName);
+ free((void*)pEntry->cnf_params);
+ ratelimitDestruct(pEntry->ratelimiter);
+ statsobj.Destruct(&(pEntry->stats));
+ pDel = pEntry;
+ pEntry = pEntry->pNext;
+ free(pDel);
+ }
+
+ /* finally close our listen streams */
+ for(i = 0 ; i < pThis->iLstnCurr ; ++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
+addTcpLstn(void *pUsr, netstrm_t *pLstn)
+{
+ tcpLstnPortList_t *pPortList = (tcpLstnPortList_t *) pUsr;
+ tcpsrv_t *pThis = pPortList->pSrv;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ ISOBJ_TYPE_assert(pLstn, netstrm);
+
+ if(pThis->iLstnCurr >= pThis->iLstnMax)
+ ABORT_FINALIZE(RS_RET_MAX_LSTN_REACHED);
+
+ pThis->ppLstn[pThis->iLstnCurr] = pLstn;
+ pThis->ppLstnPort[pThis->iLstnCurr] = pPortList;
+ ++pThis->iLstnCurr;
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Initialize TCP listener socket for a single port
+ * Note: at this point, TLS vs. non-TLS does not matter; TLS params are
+ * set on connect!
+ * rgerhards, 2009-05-21
+ */
+static rsRetVal
+initTCPListener(tcpsrv_t *pThis, tcpLstnPortList_t *pPortEntry)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ assert(pPortEntry != NULL);
+
+ // pPortEntry->pszAddr = NULL ==> bind to all interfaces
+ CHKiRet(netstrm.LstnInit(pThis->pNS, (void*)pPortEntry, addTcpLstn,
+ pThis->iSessMax, pPortEntry->cnf_params));
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Initialize TCP sockets (for listener) and listens on them */
+static rsRetVal
+create_tcp_socket(tcpsrv_t *pThis)
+{
+ DEFiRet;
+ rsRetVal localRet;
+ tcpLstnPortList_t *pEntry;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ /* init all configured ports */
+ pEntry = pThis->pLstnPorts;
+ while(pEntry != NULL) {
+ localRet = initTCPListener(pThis, pEntry);
+ if(localRet != RS_RET_OK) {
+ LogError(0, localRet, "Could not create tcp listener, ignoring port "
+ "%s bind-address %s.",
+ (pEntry->cnf_params->pszPort == NULL) ? "**UNSPECIFIED**"
+ : (const char*) pEntry->cnf_params->pszPort,
+ (pEntry->cnf_params->pszAddr == NULL) ? "**UNSPECIFIED**"
+ : (const char*)pEntry->cnf_params->pszAddr);
+ }
+ pEntry = pEntry->pNext;
+ }
+
+ /* OK, we had success. Now it is also time to
+ * initialize our connections
+ */
+ if(TCPSessTblInit(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...
+ */
+ LogError(0, RS_RET_ERR, "Could not initialize TCP session table, suspending TCP "
+ "message reception.");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Accept new TCP connection; make entry in session table. If there
+ * is no more space left in the connection table, the new TCP
+ * 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(tcpsrv_t *pThis, tcpLstnPortList_t *pLstnInfo, tcps_sess_t **ppSess, netstrm_t *pStrm)
+{
+ DEFiRet;
+ tcps_sess_t *pSess = NULL;
+ netstrm_t *pNewStrm = NULL;
+ const tcpLstnParams_t *const cnf_params = pLstnInfo->cnf_params;
+ int iSess = -1;
+ struct sockaddr_storage *addr;
+ uchar *fromHostFQDN = NULL;
+ prop_t *fromHostIP = NULL;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ assert(pLstnInfo != NULL);
+
+ CHKiRet(netstrm.AcceptConnReq(pStrm, &pNewStrm));
+
+ /* Add to session list */
+ iSess = TCPSessTblFindFreeSpot(pThis);
+ if(iSess == -1) {
+ errno = 0;
+ LogError(0, RS_RET_MAX_SESS_REACHED, "too many tcp sessions - dropping incoming request");
+ ABORT_FINALIZE(RS_RET_MAX_SESS_REACHED);
+ }
+
+ if(pThis->bUseKeepAlive) {
+ CHKiRet(netstrm.SetKeepAliveProbes(pNewStrm, pThis->iKeepAliveProbes));
+ CHKiRet(netstrm.SetKeepAliveTime(pNewStrm, pThis->iKeepAliveTime));
+ CHKiRet(netstrm.SetKeepAliveIntvl(pNewStrm, pThis->iKeepAliveIntvl));
+ CHKiRet(netstrm.EnableKeepAlive(pNewStrm));
+ }
+
+ /* we found a free spot and can construct our session object */
+ if(pThis->gnutlsPriorityString != NULL) {
+ CHKiRet(netstrm.SetGnutlsPriorityString(pNewStrm, pThis->gnutlsPriorityString));
+ }
+ CHKiRet(tcps_sess.Construct(&pSess));
+ CHKiRet(tcps_sess.SetTcpsrv(pSess, pThis));
+ CHKiRet(tcps_sess.SetLstnInfo(pSess, pLstnInfo));
+ if(pThis->OnMsgReceive != NULL)
+ CHKiRet(tcps_sess.SetOnMsgReceive(pSess, pThis->OnMsgReceive));
+
+ /* get the host name */
+ CHKiRet(netstrm.GetRemoteHName(pNewStrm, &fromHostFQDN));
+ if (!cnf_params->bPreserveCase) {
+ /* preserve_case = off */
+ uchar *p;
+ for(p = fromHostFQDN; *p; p++) {
+ if (isupper((int) *p)) {
+ *p = tolower((int) *p);
+ }
+ }
+ }
+ CHKiRet(netstrm.GetRemoteIP(pNewStrm, &fromHostIP));
+ 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((struct sockaddr*) addr, (char*) fromHostFQDN, pThis->pUsr, pSess->pUsr)) {
+ DBGPRINTF("%s is not an allowed sender\n", fromHostFQDN);
+ if(glbl.GetOptionDisallowWarning(runConf)) {
+ errno = 0;
+ LogError(0, RS_RET_HOST_NOT_PERMITTED, "connection request 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(tcps_sess.SetHost(pSess, fromHostFQDN));
+ fromHostFQDN = NULL; /* we handed this string over */
+ CHKiRet(tcps_sess.SetHostIP(pSess, fromHostIP));
+ CHKiRet(tcps_sess.SetStrm(pSess, pNewStrm));
+ pNewStrm = NULL; /* prevent it from being freed in error handler, now done in tcps_sess! */
+ CHKiRet(tcps_sess.SetMsgIdx(pSess, 0));
+ CHKiRet(tcps_sess.ConstructFinalize(pSess));
+
+ /* check if we need to call our callback */
+ if(pThis->pOnSessAccept != NULL) {
+ CHKiRet(pThis->pOnSessAccept(pThis, pSess));
+ }
+
+ *ppSess = pSess;
+ if(!pThis->bUsingEPoll)
+ pThis->pSessions[iSess] = pSess;
+ pSess = NULL; /* this is now also handed over */
+
+ if(pThis->bEmitMsgOnOpen) {
+ LogMsg(0, RS_RET_NO_ERRCODE, LOG_INFO,
+ "imtcp: connection established with host: %s",
+ propGetSzStr(fromHostIP));
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(iRet != RS_RET_HOST_NOT_PERMITTED && pThis->bEmitMsgOnOpen) {
+ LogError(0, NO_ERRCODE, "imtcp: connection could not be "
+ "established with host: %s",
+ fromHostIP == NULL ? "(IP unknown)"
+ : (const char*)propGetSzStr(fromHostIP));
+ }
+ if(pSess != NULL)
+ tcps_sess.Destruct(&pSess);
+ if(pNewStrm != NULL)
+ netstrm.Destruct(&pNewStrm);
+ free(fromHostFQDN);
+ }
+
+ RETiRet;
+}
+
+
+static void
+RunCancelCleanup(void *arg)
+{
+ nspoll_t **ppPoll = (nspoll_t**) arg;
+
+ if (*ppPoll != NULL)
+ nspoll.Destruct(ppPoll);
+
+ /* Wait for any running workers to finish */
+ pthread_mutex_lock(&wrkrMut);
+ DBGPRINTF("tcpsrv terminating, waiting for %d workers\n", wrkrRunning);
+ while(wrkrRunning > 0) {
+ pthread_cond_wait(&wrkrIdle, &wrkrMut);
+ }
+ pthread_mutex_unlock(&wrkrMut);
+}
+
+static void
+RunSelectCancelCleanup(void *arg)
+{
+ nssel_t **ppSel = (nssel_t**) arg;
+
+ if(*ppSel != NULL)
+ nssel.Destruct(ppSel);
+}
+
+
+/* helper to close a session. Takes status of poll vs. select into consideration.
+ * rgerhards, 2009-11-25
+ */
+static rsRetVal
+closeSess(tcpsrv_t *pThis, tcps_sess_t **ppSess, nspoll_t *pPoll) {
+ DEFiRet;
+ if(pPoll != NULL) {
+ CHKiRet(nspoll.Ctl(pPoll, (*ppSess)->pStrm, 0, *ppSess, NSDPOLL_IN, NSDPOLL_DEL));
+ }
+ pThis->pOnRegularClose(*ppSess);
+ tcps_sess.Destruct(ppSess);
+finalize_it:
+ RETiRet;
+}
+
+
+/* process a receive request on one of the streams
+ * If pPoll is non-NULL, we have a netstream in epoll mode, which means we need
+ * to remove any descriptor we close from the epoll set.
+ * rgerhards, 2009-07-020
+ */
+static rsRetVal
+doReceive(tcpsrv_t *pThis, tcps_sess_t **ppSess, nspoll_t *pPoll)
+{
+ char buf[128*1024]; /* reception buffer - may hold a partial or multiple messages */
+ ssize_t iRcvd;
+ rsRetVal localRet;
+ DEFiRet;
+ uchar *pszPeer;
+ int lenPeer;
+ int oserr = 0;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ prop.GetString((*ppSess)->fromHostIP, &pszPeer, &lenPeer);
+ DBGPRINTF("netstream %p with new data from remote peer %s\n", (*ppSess)->pStrm, pszPeer);
+ /* Receive message */
+ iRet = pThis->pRcvData(*ppSess, buf, sizeof(buf), &iRcvd, &oserr);
+ switch(iRet) {
+ case RS_RET_CLOSED:
+ if(pThis->bEmitMsgOnClose) {
+ errno = 0;
+ LogError(0, RS_RET_PEER_CLOSED_CONN, "Netstream session %p closed by remote "
+ "peer %s.\n", (*ppSess)->pStrm, pszPeer);
+ }
+ CHKiRet(closeSess(pThis, ppSess, pPoll));
+ 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 = tcps_sess.DataRcvd(*ppSess, buf, iRcvd);
+ if(localRet != RS_RET_OK && localRet != RS_RET_QUEUE_FULL) {
+ /* in this case, something went awfully wrong.
+ * We are instructed to terminate the session.
+ */
+ LogError(oserr, localRet, "Tearing down TCP Session from %s", pszPeer);
+ CHKiRet(closeSess(pThis, ppSess, pPoll));
+ }
+ break;
+ default:
+ LogError(oserr, iRet, "netstream session %p from %s will be closed due to error",
+ (*ppSess)->pStrm, pszPeer);
+ CHKiRet(closeSess(pThis, ppSess, pPoll));
+ break;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+/* process a single workset item
+ */
+static rsRetVal ATTR_NONNULL(1)
+processWorksetItem(tcpsrv_t *const pThis, nspoll_t *pPoll, const int idx, void *pUsr)
+{
+ tcps_sess_t *pNewSess = NULL;
+ tcpLstnParams_t *cnf_params;
+
+ DEFiRet;
+
+ DBGPRINTF("tcpsrv: processing item %d, pUsr %p, bAbortConn\n", idx, pUsr);
+ if(pUsr == pThis->ppLstn) {
+ DBGPRINTF("New connect on NSD %p.\n", pThis->ppLstn[idx]);
+ iRet = SessAccept(pThis, pThis->ppLstnPort[idx], &pNewSess, pThis->ppLstn[idx]);
+ cnf_params = pThis->ppLstnPort[idx]->cnf_params;
+ if(iRet == RS_RET_OK) {
+ if(pPoll != NULL) {
+ CHKiRet(nspoll.Ctl(pPoll, pNewSess->pStrm, 0, pNewSess, NSDPOLL_IN, NSDPOLL_ADD));
+ }
+ DBGPRINTF("New session created with NSD %p.\n", pNewSess);
+ } else {
+ DBGPRINTF("tcpsrv: error %d during accept\n", iRet);
+ }
+ } else {
+ pNewSess = (tcps_sess_t*) pUsr;
+ cnf_params = pNewSess->pLstnInfo->cnf_params;
+ doReceive(pThis, &pNewSess, pPoll);
+ if(pPoll == NULL && pNewSess == NULL) {
+ pThis->pSessions[idx] = NULL;
+ }
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ LogError(0, iRet, "tcpsrv listener (inputname: '%s') failed "
+ "to process incoming connection with error %d",
+ (cnf_params->pszInputName == NULL) ? (uchar*)"*UNSET*" : cnf_params->pszInputName, iRet);
+ srSleep(0,20000); /* Sleep 20ms */
+ }
+ RETiRet;
+}
+
+
+/* worker to process incoming requests
+ */
+static void * ATTR_NONNULL(1)
+wrkr(void *const myself)
+{
+ struct wrkrInfo_s *const me = (struct wrkrInfo_s*) myself;
+
+
+ pthread_mutex_lock(&wrkrMut);
+ while(1) {
+ // wait for work, in which case pSrv will be populated
+ while(me->pSrv == NULL && glbl.GetGlobalInputTermState() == 0) {
+ pthread_cond_wait(&me->run, &wrkrMut);
+ }
+ if(me->pSrv == NULL) {
+ // only possible if glbl.GetGlobalInputTermState() == 1
+ // we need to query me->opSrv to avoid clang static
+ // analyzer false positive! -- rgerhards, 2017-10-23
+ assert(glbl.GetGlobalInputTermState() == 1);
+ break;
+ }
+ pthread_mutex_unlock(&wrkrMut);
+
+ ++me->numCalled;
+ processWorksetItem(me->pSrv, me->pPoll, me->idx, me->pUsr);
+
+ pthread_mutex_lock(&wrkrMut);
+ me->pSrv = NULL; /* indicate we are free again */
+ --wrkrRunning;
+ pthread_cond_broadcast(&wrkrIdle);
+ }
+ me->enabled = 0; /* indicate we are no longer available */
+ pthread_mutex_unlock(&wrkrMut);
+
+ return NULL;
+}
+
+/* This has been factored out from processWorkset() because
+ * pthread_cleanup_push() invokes setjmp() and this triggers the -Wclobbered
+ * warning for the iRet variable.
+ */
+static void
+waitForWorkers(void)
+{
+ pthread_mutex_lock(&wrkrMut);
+ pthread_cleanup_push(mutexCancelCleanup, &wrkrMut);
+ while(wrkrRunning > 0) {
+ pthread_cond_wait(&wrkrIdle, &wrkrMut);
+ }
+ pthread_cleanup_pop(1);
+}
+
+/* Process a workset, that is handle io. We become activated
+ * from either select or epoll handler. We split the workload
+ * out to a pool of threads, but try to avoid context switches
+ * as much as possible.
+ */
+static rsRetVal
+processWorkset(tcpsrv_t *pThis, nspoll_t *pPoll, int numEntries, nsd_epworkset_t workset[])
+{
+ int i;
+ int origEntries = numEntries;
+ DEFiRet;
+
+ DBGPRINTF("tcpsrv: ready to process %d event entries\n", numEntries);
+
+ while(numEntries > 0) {
+ if(glbl.GetGlobalInputTermState() == 1)
+ ABORT_FINALIZE(RS_RET_FORCE_TERM);
+ if(numEntries == 1) {
+ /* process self, save context switch */
+ iRet = processWorksetItem(pThis, pPoll, workset[numEntries-1].id, workset[numEntries-1].pUsr);
+ } else {
+ /* No cancel handler needed here, since no cancellation
+ * points are executed while wrkrMut is locked.
+ *
+ * Re-evaluate this if you add a DBGPRINTF or something!
+ */
+ pthread_mutex_lock(&wrkrMut);
+ /* check if there is a free worker */
+ for(i = 0 ; (i < wrkrMax) && ((wrkrInfo[i].pSrv != NULL) || (wrkrInfo[i].enabled == 0)) ; ++i)
+ /*do search*/;
+ if(i < wrkrMax) {
+ /* worker free -> use it! */
+ wrkrInfo[i].pSrv = pThis;
+ wrkrInfo[i].pPoll = pPoll;
+ wrkrInfo[i].idx = workset[numEntries -1].id;
+ wrkrInfo[i].pUsr = workset[numEntries -1].pUsr;
+ /* Note: we must increment wrkrRunning HERE and not inside the worker's
+ * code. This is because a worker may actually never start, and thus
+ * increment wrkrRunning, before we finish and check the running worker
+ * count. We can only avoid this by incrementing it here.
+ */
+ ++wrkrRunning;
+ pthread_cond_signal(&wrkrInfo[i].run);
+ pthread_mutex_unlock(&wrkrMut);
+ } else {
+ pthread_mutex_unlock(&wrkrMut);
+ /* no free worker, so we process this one ourselfs */
+ iRet = processWorksetItem(pThis, pPoll, workset[numEntries-1].id,
+ workset[numEntries-1].pUsr);
+ }
+ }
+ --numEntries;
+ }
+
+ if(origEntries > 1) {
+ /* we now need to wait until all workers finish. This is because the
+ * rest of this module can not handle the concurrency introduced
+ * by workers running during the epoll call.
+ */
+ waitForWorkers();
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function is called to gather input.
+ * This variant here is only used if we need to work with a netstream driver
+ * that does not support epoll().
+ */
+PRAGMA_DIAGNOSTIC_PUSH
+PRAGMA_IGNORE_Wempty_body
+static rsRetVal
+RunSelect(tcpsrv_t *pThis, nsd_epworkset_t workset[], size_t sizeWorkset)
+{
+ DEFiRet;
+ int nfds;
+ int i;
+ int iWorkset;
+ int iTCPSess;
+ int bIsReady;
+ nssel_t *pSel = NULL;
+ rsRetVal localRet;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ pthread_cleanup_push(RunSelectCancelCleanup, (void*) &pSel);
+ while(1) {
+ CHKiRet(nssel.Construct(&pSel));
+ if(pThis->pszDrvrName != NULL)
+ CHKiRet(nssel.SetDrvrName(pSel, pThis->pszDrvrName));
+ CHKiRet(nssel.ConstructFinalize(pSel));
+
+ /* Add the TCP listen sockets to the list of read descriptors. */
+ for(i = 0 ; i < pThis->iLstnCurr ; ++i) {
+ CHKiRet(nssel.Add(pSel, pThis->ppLstn[i], NSDSEL_RD));
+ }
+
+ /* do the sessions */
+ iTCPSess = TCPSessGetNxtSess(pThis, -1);
+ while(iTCPSess != -1) {
+ /* TODO: access to pNsd is NOT really CLEAN, use method... */
+ CHKiRet(nssel.Add(pSel, pThis->pSessions[iTCPSess]->pStrm, NSDSEL_RD));
+ DBGPRINTF("tcpsrv process session %d:\n", iTCPSess);
+
+ /* now get next... */
+ iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess);
+ }
+
+ /* wait for io to become ready */
+ CHKiRet(nssel.Wait(pSel, &nfds));
+ if(glbl.GetGlobalInputTermState() == 1)
+ break; /* terminate input! */
+
+ iWorkset = 0;
+ for(i = 0 ; i < pThis->iLstnCurr ; ++i) {
+ if(glbl.GetGlobalInputTermState() == 1)
+ ABORT_FINALIZE(RS_RET_FORCE_TERM);
+ CHKiRet(nssel.IsReady(pSel, pThis->ppLstn[i], NSDSEL_RD, &bIsReady, &nfds));
+ if(bIsReady) {
+ workset[iWorkset].id = i;
+ workset[iWorkset].pUsr = (void*) pThis->ppLstn;
+ /* this is a flag to indicate listen sock */
+ ++iWorkset;
+ if(iWorkset >= (int) sizeWorkset) {
+ processWorkset(pThis, NULL, iWorkset, workset);
+ iWorkset = 0;
+ }
+ --nfds; /* indicate we have processed one */
+ }
+ }
+
+ /* now check the sessions */
+ iTCPSess = TCPSessGetNxtSess(pThis, -1);
+ while(nfds && iTCPSess != -1) {
+ if(glbl.GetGlobalInputTermState() == 1)
+ ABORT_FINALIZE(RS_RET_FORCE_TERM);
+ localRet = nssel.IsReady(pSel, pThis->pSessions[iTCPSess]->pStrm, NSDSEL_RD,
+ &bIsReady, &nfds);
+ if(bIsReady || localRet != RS_RET_OK) {
+ workset[iWorkset].id = iTCPSess;
+ workset[iWorkset].pUsr = (void*) pThis->pSessions[iTCPSess];
+ ++iWorkset;
+ if(iWorkset >= (int) sizeWorkset) {
+ processWorkset(pThis, NULL, iWorkset, workset);
+ iWorkset = 0;
+ }
+ if(bIsReady)
+ --nfds; /* indicate we have processed one */
+ }
+ iTCPSess = TCPSessGetNxtSess(pThis, iTCPSess);
+ }
+
+ if(iWorkset > 0)
+ processWorkset(pThis, NULL, iWorkset, workset);
+
+ /* we need to copy back close descriptors */
+ nssel.Destruct(&pSel); /* no iRet check as it is overriden at start of loop! */
+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
+ */
+ if(pSel != NULL) { /* cleanup missing? happens during err exit! */
+ nssel.Destruct(&pSel);
+ }
+ }
+
+ pthread_cleanup_pop(1); /* execute and remove cleanup handler */
+
+ RETiRet;
+}
+PRAGMA_DIAGNOSTIC_POP
+
+static rsRetVal
+DoRun(tcpsrv_t *pThis, nspoll_t **ppPoll)
+{
+ DEFiRet;
+ int i;
+ nsd_epworkset_t workset[128]; /* 128 is currently fixed num of concurrent requests */
+ int numEntries;
+ nspoll_t *pPoll = NULL;
+ rsRetVal localRet;
+
+ if((localRet = nspoll.Construct(ppPoll)) == RS_RET_OK) {
+ pPoll = *ppPoll;
+ if(pThis->pszDrvrName != NULL)
+ CHKiRet(nspoll.SetDrvrName(pPoll, pThis->pszDrvrName));
+ localRet = nspoll.ConstructFinalize(pPoll);
+ }
+ if(localRet != RS_RET_OK) {
+ /* fall back to select */
+ DBGPRINTF("tcpsrv could not use epoll() interface, iRet=%d, using select()\n", localRet);
+ iRet = RunSelect(pThis, workset, sizeof(workset)/sizeof(nsd_epworkset_t));
+ FINALIZE;
+ }
+
+ DBGPRINTF("tcpsrv uses epoll() interface, nsdpoll driver found\n");
+
+ /* flag that we are in epoll mode */
+ pThis->bUsingEPoll = RSTRUE;
+
+ /* Add the TCP listen sockets to the list of sockets to monitor */
+ for(i = 0 ; i < pThis->iLstnCurr ; ++i) {
+ DBGPRINTF("Trying to add listener %d, pUsr=%p\n", i, pThis->ppLstn);
+ CHKiRet(nspoll.Ctl(pPoll, pThis->ppLstn[i], i, pThis->ppLstn, NSDPOLL_IN, NSDPOLL_ADD));
+ DBGPRINTF("Added listener %d\n", i);
+ }
+
+ while(glbl.GetGlobalInputTermState() == 0) {
+ numEntries = sizeof(workset)/sizeof(nsd_epworkset_t);
+ localRet = nspoll.Wait(pPoll, -1, &numEntries, workset);
+ if(glbl.GetGlobalInputTermState() == 1)
+ break; /* terminate input! */
+
+ /* check if we need to ignore the i/o ready state. We do this if we got an invalid
+ * return state. Validly, this can happen for RS_RET_EINTR, for other cases it may
+ * not be the right thing, but what is the right thing is really hard at this point...
+ */
+ if(localRet != RS_RET_OK)
+ continue;
+
+ processWorkset(pThis, pPoll, numEntries, workset);
+ }
+
+ /* remove the tcp listen sockets from the epoll set */
+ for(i = 0 ; i < pThis->iLstnCurr ; ++i) {
+ CHKiRet(nspoll.Ctl(pPoll, pThis->ppLstn[i], i, pThis->ppLstn, NSDPOLL_IN, NSDPOLL_DEL));
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function is called to gather input. It tries doing that via the epoll()
+ * interface. If the driver does not support that, it falls back to calling its
+ * select() equivalent.
+ * rgerhards, 2009-11-18
+ */
+static rsRetVal
+Run(tcpsrv_t *pThis)
+{
+ DEFiRet;
+ nspoll_t *pPoll = NULL;
+
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ if(pThis->iLstnCurr == 0) {
+ dbgprintf("tcpsrv: no listeneres at all (probably init error), terminating\n");
+ RETiRet; /* somewhat "dirty" exit to avoid issue with cancel handler */
+ }
+
+ /* check if we need to start the worker pool. Once it is running, all is
+ * well. Shutdown is done on modExit.
+ */
+ d_pthread_mutex_lock(&wrkrMut);
+ if(!bWrkrRunning) {
+ bWrkrRunning = 1;
+ startWorkerPool();
+ }
+ d_pthread_mutex_unlock(&wrkrMut);
+
+ /* We try to terminate cleanly, but install a cancellation clean-up
+ * handler in case we are cancelled.
+ */
+ pthread_cleanup_push(RunCancelCleanup, (void*) &pPoll);
+ iRet = DoRun(pThis, &pPoll);
+ pthread_cleanup_pop(1);
+
+ RETiRet;
+}
+
+
+/* Standard-Constructor */
+BEGINobjConstruct(tcpsrv) /* be sure to specify the object type also in END macro! */
+ pThis->iSessMax = TCPSESS_MAX_DEFAULT;
+ pThis->iLstnMax = TCPLSTN_MAX_DEFAULT;
+ pThis->addtlFrameDelim = TCPSRV_NO_ADDTL_DELIMITER;
+ pThis->maxFrameSize = 200000;
+ pThis->bDisableLFDelim = 0;
+ pThis->discardTruncatedMsg = 0;
+ pThis->OnMsgReceive = NULL;
+ pThis->dfltTZ[0] = '\0';
+ pThis->bSPFramingFix = 0;
+ pThis->ratelimitInterval = 0;
+ pThis->ratelimitBurst = 10000;
+ pThis->bUseFlowControl = 1;
+ pThis->pszDrvrName = NULL;
+ pThis->bPreserveCase = 1; /* preserve case in fromhost; default to true. */
+ pThis->DrvrTlsVerifyDepth = 0;
+ENDobjConstruct(tcpsrv)
+
+
+/* ConstructionFinalizer */
+static rsRetVal
+tcpsrvConstructFinalize(tcpsrv_t *pThis)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+
+ /* prepare network stream subsystem */
+ CHKiRet(netstrms.Construct(&pThis->pNS));
+ if(pThis->pszDrvrName != NULL)
+ CHKiRet(netstrms.SetDrvrName(pThis->pNS, pThis->pszDrvrName));
+ CHKiRet(netstrms.SetDrvrMode(pThis->pNS, pThis->iDrvrMode));
+ CHKiRet(netstrms.SetDrvrCheckExtendedKeyUsage(pThis->pNS, pThis->DrvrChkExtendedKeyUsage));
+ CHKiRet(netstrms.SetDrvrPrioritizeSAN(pThis->pNS, pThis->DrvrPrioritizeSan));
+ CHKiRet(netstrms.SetDrvrTlsVerifyDepth(pThis->pNS, pThis->DrvrTlsVerifyDepth));
+ if(pThis->pszDrvrAuthMode != NULL)
+ CHKiRet(netstrms.SetDrvrAuthMode(pThis->pNS, pThis->pszDrvrAuthMode));
+ /* Call SetDrvrPermitExpiredCerts required
+ * when param is NULL default handling for ExpiredCerts is set! */
+ CHKiRet(netstrms.SetDrvrPermitExpiredCerts(pThis->pNS, pThis->pszDrvrPermitExpiredCerts));
+ CHKiRet(netstrms.SetDrvrTlsCAFile(pThis->pNS, pThis->pszDrvrCAFile));
+ CHKiRet(netstrms.SetDrvrTlsCRLFile(pThis->pNS, pThis->pszDrvrCRLFile));
+ CHKiRet(netstrms.SetDrvrTlsKeyFile(pThis->pNS, pThis->pszDrvrKeyFile));
+ CHKiRet(netstrms.SetDrvrTlsCertFile(pThis->pNS, pThis->pszDrvrCertFile));
+ if(pThis->pPermPeers != NULL)
+ CHKiRet(netstrms.SetDrvrPermPeers(pThis->pNS, pThis->pPermPeers));
+ if(pThis->gnutlsPriorityString != NULL)
+ CHKiRet(netstrms.SetDrvrGnutlsPriorityString(pThis->pNS, pThis->gnutlsPriorityString));
+ CHKiRet(netstrms.ConstructFinalize(pThis->pNS));
+
+ /* set up listeners */
+ CHKmalloc(pThis->ppLstn = calloc(pThis->iLstnMax, sizeof(netstrm_t*)));
+ CHKmalloc(pThis->ppLstnPort = calloc(pThis->iLstnMax, sizeof(tcpLstnPortList_t*)));
+ iRet = pThis->OpenLstnSocks(pThis);
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis->pNS != NULL)
+ netstrms.Destruct(&pThis->pNS);
+ LogError(0, iRet, "tcpsrv could not create listener (inputname: '%s')",
+ (pThis->pszInputName == NULL) ? (uchar*)"*UNSET*" : pThis->pszInputName);
+ }
+ RETiRet;
+}
+
+
+/* destructor for the tcpsrv object */
+BEGINobjDestruct(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(tcpsrv)
+ if(pThis->OnDestruct != NULL)
+ pThis->OnDestruct(pThis->pUsr);
+
+ deinit_tcp_listener(pThis);
+
+ if(pThis->pNS != NULL)
+ netstrms.Destruct(&pThis->pNS);
+ free(pThis->pszDrvrName);
+ free(pThis->pszDrvrAuthMode);
+ free(pThis->pszDrvrPermitExpiredCerts);
+ free(pThis->pszDrvrCAFile);
+ free(pThis->pszDrvrCRLFile);
+ free(pThis->pszDrvrKeyFile);
+ free(pThis->pszDrvrCertFile);
+ free(pThis->ppLstn);
+ free(pThis->ppLstnPort);
+ free(pThis->pszInputName);
+ free(pThis->pszOrigin);
+ENDobjDestruct(tcpsrv)
+
+
+/* debugprint for the tcpsrv object */
+BEGINobjDebugPrint(tcpsrv) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDebugPrint(tcpsrv)
+ENDobjDebugPrint(tcpsrv)
+
+/* set functions */
+static rsRetVal
+SetCBIsPermittedHost(tcpsrv_t *pThis, int (*pCB)(struct sockaddr *addr, char *fromHostFQDN, void*, void*))
+{
+ DEFiRet;
+ pThis->pIsPermittedHost = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBRcvData(tcpsrv_t *pThis, rsRetVal (*pRcvData)(tcps_sess_t*, char*, size_t, ssize_t*, int*))
+{
+ DEFiRet;
+ pThis->pRcvData = pRcvData;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnListenDeinit(tcpsrv_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->pOnListenDeinit = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnSessAccept(tcpsrv_t *pThis, rsRetVal (*pCB)(tcpsrv_t*, tcps_sess_t*))
+{
+ DEFiRet;
+ pThis->pOnSessAccept = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnDestruct(tcpsrv_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->OnDestruct = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnSessConstructFinalize(tcpsrv_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->OnSessConstructFinalize = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnSessDestruct(tcpsrv_t *pThis, rsRetVal (*pCB)(void*))
+{
+ DEFiRet;
+ pThis->pOnSessDestruct = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnRegularClose(tcpsrv_t *pThis, rsRetVal (*pCB)(tcps_sess_t*))
+{
+ DEFiRet;
+ pThis->pOnRegularClose = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOnErrClose(tcpsrv_t *pThis, rsRetVal (*pCB)(tcps_sess_t*))
+{
+ DEFiRet;
+ pThis->pOnErrClose = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetCBOpenLstnSocks(tcpsrv_t *pThis, rsRetVal (*pCB)(tcpsrv_t*))
+{
+ DEFiRet;
+ pThis->OpenLstnSocks = pCB;
+ RETiRet;
+}
+
+static rsRetVal
+SetUsrP(tcpsrv_t *pThis, void *pUsr)
+{
+ DEFiRet;
+ pThis->pUsr = pUsr;
+ RETiRet;
+}
+
+static rsRetVal
+SetKeepAlive(tcpsrv_t *pThis, int iVal)
+{
+ DEFiRet;
+ DBGPRINTF("tcpsrv: keep-alive set to %d\n", iVal);
+ pThis->bUseKeepAlive = iVal;
+ RETiRet;
+}
+
+static rsRetVal
+SetKeepAliveIntvl(tcpsrv_t *pThis, int iVal)
+{
+ DEFiRet;
+ DBGPRINTF("tcpsrv: keep-alive interval set to %d\n", iVal);
+ pThis->iKeepAliveIntvl = iVal;
+ RETiRet;
+}
+
+static rsRetVal
+SetKeepAliveProbes(tcpsrv_t *pThis, int iVal)
+{
+ DEFiRet;
+ DBGPRINTF("tcpsrv: keep-alive probes set to %d\n", iVal);
+ pThis->iKeepAliveProbes = iVal;
+ RETiRet;
+}
+
+static rsRetVal
+SetKeepAliveTime(tcpsrv_t *pThis, int iVal)
+{
+ DEFiRet;
+ DBGPRINTF("tcpsrv: keep-alive timeout set to %d\n", iVal);
+ pThis->iKeepAliveTime = iVal;
+ RETiRet;
+}
+
+static rsRetVal
+SetGnutlsPriorityString(tcpsrv_t *pThis, uchar *iVal)
+{
+ DEFiRet;
+ DBGPRINTF("tcpsrv: gnutlsPriorityString set to %s\n",
+ (iVal == NULL) ? "(null)" : (const char*) iVal);
+ pThis->gnutlsPriorityString = iVal;
+ RETiRet;
+}
+
+
+static rsRetVal
+SetOnMsgReceive(tcpsrv_t *pThis, rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int))
+{
+ DEFiRet;
+ assert(OnMsgReceive != NULL);
+ pThis->OnMsgReceive = OnMsgReceive;
+ RETiRet;
+}
+
+
+/* set enable/disable standard LF frame delimiter (use with care!)
+ * -- rgerhards, 2010-01-03
+ */
+static rsRetVal
+SetbDisableLFDelim(tcpsrv_t *pThis, int bVal)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->bDisableLFDelim = bVal;
+ RETiRet;
+}
+
+
+/* discard the truncated msg part
+ * -- PascalWithopf, 2017-04-20
+ */
+static rsRetVal
+SetDiscardTruncatedMsg(tcpsrv_t *pThis, int discard)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->discardTruncatedMsg = discard;
+ RETiRet;
+}
+
+
+/* Set additional framing to use (if any) -- rgerhards, 2008-12-10 */
+static rsRetVal
+SetAddtlFrameDelim(tcpsrv_t *pThis, int iDelim)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->addtlFrameDelim = iDelim;
+ RETiRet;
+}
+
+
+/* Set max frame size for octet counted -- PascalWithopf, 2017-04-20*/
+static rsRetVal
+SetMaxFrameSize(tcpsrv_t *pThis, int maxFrameSize)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->maxFrameSize = maxFrameSize;
+ RETiRet;
+}
+
+
+static rsRetVal
+SetDfltTZ(tcpsrv_t *const pThis, uchar *const tz)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ strncpy((char*)pThis->dfltTZ, (char*)tz, sizeof(pThis->dfltTZ));
+ pThis->dfltTZ[sizeof(pThis->dfltTZ)-1] = '\0';
+ RETiRet;
+}
+
+
+static rsRetVal
+SetbSPFramingFix(tcpsrv_t *pThis, const sbool val)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->bSPFramingFix = val;
+ RETiRet;
+}
+
+static rsRetVal
+SetOrigin(tcpsrv_t *pThis, uchar *origin)
+{
+ DEFiRet;
+ free(pThis->pszOrigin);
+ pThis->pszOrigin = (origin == NULL) ? NULL : ustrdup(origin);
+ RETiRet;
+}
+
+/* Set the input name to use -- rgerhards, 2008-12-10 */
+static rsRetVal
+SetInputName(tcpsrv_t *const pThis,tcpLstnParams_t *const cnf_params, const uchar *const name)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ if(name == NULL)
+ cnf_params->pszInputName = NULL;
+ else
+ CHKmalloc(cnf_params->pszInputName = ustrdup(name));
+ free(pThis->pszInputName); // TODO: REMOVE ME
+ pThis->pszInputName = ustrdup("imtcp"); // TODO: REMOVE ME
+
+ /* we need to create a property */
+ CHKiRet(prop.Construct(&cnf_params->pInputName));
+ CHKiRet(prop.SetString(cnf_params->pInputName, cnf_params->pszInputName, ustrlen(cnf_params->pszInputName)));
+ CHKiRet(prop.ConstructFinalize(cnf_params->pInputName));
+finalize_it:
+ RETiRet;
+}
+
+
+/* Set the linux-like ratelimiter settings */
+static rsRetVal
+SetLinuxLikeRatelimiters(tcpsrv_t *pThis, unsigned int ratelimitInterval, unsigned int ratelimitBurst)
+{
+ DEFiRet;
+ pThis->ratelimitInterval = ratelimitInterval;
+ pThis->ratelimitBurst = ratelimitBurst;
+ RETiRet;
+}
+
+
+/* Set connection open notification */
+static rsRetVal
+SetNotificationOnRemoteOpen(tcpsrv_t *pThis, const int bNewVal)
+{
+ pThis->bEmitMsgOnOpen = bNewVal;
+ return RS_RET_OK;
+}
+/* Set connection close notification */
+static rsRetVal
+SetNotificationOnRemoteClose(tcpsrv_t *pThis, const int bNewVal)
+{
+ DEFiRet;
+ pThis->bEmitMsgOnClose = bNewVal;
+ 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(tcpsrv_t *pThis, const int iMode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->iDrvrMode = iMode;
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrName(tcpsrv_t *pThis, uchar *const name)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ free(pThis->pszDrvrName);
+ CHKmalloc(pThis->pszDrvrName = ustrdup(name));
+finalize_it:
+ RETiRet;
+}
+
+/* set the driver authentication mode -- rgerhards, 2008-05-19 */
+static rsRetVal
+SetDrvrAuthMode(tcpsrv_t *pThis, uchar *const mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ CHKmalloc(pThis->pszDrvrAuthMode = ustrdup(mode));
+finalize_it:
+ RETiRet;
+}
+
+/* set the driver permitexpiredcerts mode -- alorbach, 2018-12-20
+ */
+static rsRetVal
+SetDrvrPermitExpiredCerts(tcpsrv_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrPermitExpiredCerts = ustrdup(mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrCAFile(tcpsrv_t *const pThis, uchar *const mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrCAFile = ustrdup(mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrCRLFile(tcpsrv_t *const pThis, uchar *const mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrCRLFile = ustrdup(mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrKeyFile(tcpsrv_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrKeyFile = ustrdup(mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+static rsRetVal
+SetDrvrCertFile(tcpsrv_t *pThis, uchar *mode)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ if (mode != NULL) {
+ CHKmalloc(pThis->pszDrvrCertFile = ustrdup(mode));
+ }
+finalize_it:
+ RETiRet;
+}
+
+
+/* set the driver's permitted peers -- rgerhards, 2008-05-19 */
+static rsRetVal
+SetDrvrPermPeers(tcpsrv_t *pThis, permittedPeers_t *pPermPeers)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->pPermPeers = pPermPeers;
+ RETiRet;
+}
+
+/* set the driver cert extended key usage check setting -- jvymazal, 2019-08-16 */
+static rsRetVal
+SetDrvrCheckExtendedKeyUsage(tcpsrv_t *pThis, int ChkExtendedKeyUsage)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->DrvrChkExtendedKeyUsage = ChkExtendedKeyUsage;
+ RETiRet;
+}
+
+/* set the driver name checking policy -- jvymazal, 2019-08-16 */
+static rsRetVal
+SetDrvrPrioritizeSAN(tcpsrv_t *pThis, int prioritizeSan)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->DrvrPrioritizeSan = prioritizeSan;
+ RETiRet;
+}
+
+/* set the driver Set the driver tls verifyDepth -- alorbach, 2019-12-20 */
+static rsRetVal
+SetDrvrTlsVerifyDepth(tcpsrv_t *pThis, int verifyDepth)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->DrvrTlsVerifyDepth = verifyDepth;
+ RETiRet;
+}
+
+/* End of methods to shuffle autentication settings to the driver.;
+
+ * -------------------------------------------------------------------------- */
+
+
+/* set max number of listeners
+ * this must be called before ConstructFinalize, or it will have no effect!
+ * rgerhards, 2009-08-17
+ */
+static rsRetVal
+SetLstnMax(tcpsrv_t *pThis, int iMax)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->iLstnMax = iMax;
+ RETiRet;
+}
+
+
+/* set if flow control shall be supported
+ */
+static rsRetVal
+SetUseFlowControl(tcpsrv_t *pThis, int bUseFlowControl)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->bUseFlowControl = bUseFlowControl;
+ RETiRet;
+}
+
+
+/* set max number of sessions
+ * this must be called before ConstructFinalize, or it will have no effect!
+ * rgerhards, 2009-04-09
+ */
+static rsRetVal
+SetSessMax(tcpsrv_t *pThis, int iMax)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis->iSessMax = iMax;
+ RETiRet;
+}
+
+
+static rsRetVal
+SetPreserveCase(tcpsrv_t *pThis, int bPreserveCase)
+{
+ DEFiRet;
+ ISOBJ_TYPE_assert(pThis, tcpsrv);
+ pThis-> bPreserveCase = bPreserveCase;
+ RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-02-29
+ */
+BEGINobjQueryInterface(tcpsrv)
+CODESTARTobjQueryInterface(tcpsrv)
+ if(pIf->ifVersion != tcpsrvCURR_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 = tcpsrvDebugPrint;
+ pIf->Construct = tcpsrvConstruct;
+ pIf->ConstructFinalize = tcpsrvConstructFinalize;
+ pIf->Destruct = tcpsrvDestruct;
+
+ pIf->configureTCPListen = configureTCPListen;
+ pIf->create_tcp_socket = create_tcp_socket;
+ pIf->Run = Run;
+
+ pIf->SetKeepAlive = SetKeepAlive;
+ pIf->SetKeepAliveIntvl = SetKeepAliveIntvl;
+ pIf->SetKeepAliveProbes = SetKeepAliveProbes;
+ pIf->SetKeepAliveTime = SetKeepAliveTime;
+ pIf->SetGnutlsPriorityString = SetGnutlsPriorityString;
+ pIf->SetUsrP = SetUsrP;
+ pIf->SetInputName = SetInputName;
+ pIf->SetOrigin = SetOrigin;
+ pIf->SetDfltTZ = SetDfltTZ;
+ pIf->SetbSPFramingFix = SetbSPFramingFix;
+ pIf->SetAddtlFrameDelim = SetAddtlFrameDelim;
+ pIf->SetMaxFrameSize = SetMaxFrameSize;
+ pIf->SetbDisableLFDelim = SetbDisableLFDelim;
+ pIf->SetDiscardTruncatedMsg = SetDiscardTruncatedMsg;
+ pIf->SetSessMax = SetSessMax;
+ pIf->SetUseFlowControl = SetUseFlowControl;
+ pIf->SetLstnMax = SetLstnMax;
+ pIf->SetDrvrMode = SetDrvrMode;
+ pIf->SetDrvrAuthMode = SetDrvrAuthMode;
+ pIf->SetDrvrPermitExpiredCerts = SetDrvrPermitExpiredCerts;
+ pIf->SetDrvrCAFile = SetDrvrCAFile;
+ pIf->SetDrvrCRLFile = SetDrvrCRLFile;
+ pIf->SetDrvrKeyFile = SetDrvrKeyFile;
+ pIf->SetDrvrCertFile = SetDrvrCertFile;
+ pIf->SetDrvrName = SetDrvrName;
+ pIf->SetDrvrPermPeers = SetDrvrPermPeers;
+ pIf->SetCBIsPermittedHost = SetCBIsPermittedHost;
+ pIf->SetCBOpenLstnSocks = SetCBOpenLstnSocks;
+ pIf->SetCBRcvData = SetCBRcvData;
+ pIf->SetCBOnListenDeinit = SetCBOnListenDeinit;
+ pIf->SetCBOnSessAccept = SetCBOnSessAccept;
+ pIf->SetCBOnSessConstructFinalize = SetCBOnSessConstructFinalize;
+ pIf->SetCBOnSessDestruct = SetCBOnSessDestruct;
+ pIf->SetCBOnDestruct = SetCBOnDestruct;
+ pIf->SetCBOnRegularClose = SetCBOnRegularClose;
+ pIf->SetCBOnErrClose = SetCBOnErrClose;
+ pIf->SetOnMsgReceive = SetOnMsgReceive;
+ pIf->SetLinuxLikeRatelimiters = SetLinuxLikeRatelimiters;
+ pIf->SetNotificationOnRemoteClose = SetNotificationOnRemoteClose;
+ pIf->SetNotificationOnRemoteOpen = SetNotificationOnRemoteOpen;
+ pIf->SetPreserveCase = SetPreserveCase;
+ pIf->SetDrvrCheckExtendedKeyUsage = SetDrvrCheckExtendedKeyUsage;
+ pIf->SetDrvrPrioritizeSAN = SetDrvrPrioritizeSAN;
+ pIf->SetDrvrTlsVerifyDepth = SetDrvrTlsVerifyDepth;
+
+finalize_it:
+ENDobjQueryInterface(tcpsrv)
+
+
+/* exit our class
+ * rgerhards, 2008-03-10
+ */
+BEGINObjClassExit(tcpsrv, OBJ_IS_LOADABLE_MODULE) /* CHANGE class also in END MACRO! */
+CODESTARTObjClassExit(tcpsrv)
+ /* release objects we no longer need */
+ objRelease(tcps_sess, DONT_LOAD_LIB);
+ objRelease(conf, CORE_COMPONENT);
+ objRelease(prop, CORE_COMPONENT);
+ objRelease(statsobj, CORE_COMPONENT);
+ objRelease(ruleset, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(netstrms, DONT_LOAD_LIB);
+ objRelease(nssel, DONT_LOAD_LIB);
+ objRelease(netstrm, LM_NETSTRMS_FILENAME);
+ objRelease(net, LM_NET_FILENAME);
+ENDObjClassExit(tcpsrv)
+
+
+/* Initialize our class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-29
+ */
+BEGINObjClassInit(tcpsrv, 1, OBJ_IS_LOADABLE_MODULE) /* class, version - CHANGE class also in END MACRO! */
+ /* request objects we use */
+ 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(nspoll, DONT_LOAD_LIB));
+ CHKiRet(objUse(tcps_sess, DONT_LOAD_LIB));
+ CHKiRet(objUse(conf, CORE_COMPONENT));
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+ CHKiRet(objUse(statsobj, CORE_COMPONENT));
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+
+ /* set our own handlers */
+ OBJSetMethodHandler(objMethod_DEBUGPRINT, tcpsrvDebugPrint);
+ OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, tcpsrvConstructFinalize);
+ENDObjClassInit(tcpsrv)
+
+
+/* start worker threads
+ * Important: if we fork, this MUST be done AFTER forking
+ */
+static void
+startWorkerPool(void)
+{
+ int i;
+ int r;
+ pthread_attr_t sessThrdAttr;
+
+ /* We need to temporarily block all signals because the new thread
+ * inherits our signal mask. There is a race if we do not block them
+ * now, and we have seen in practice that this race causes grief.
+ * So we 1. save the current set, 2. block evertyhing, 3. start
+ * threads, and 4 reset the current set to saved state.
+ * rgerhards, 2019-08-16
+ */
+ sigset_t sigSet, sigSetSave;
+ sigfillset(&sigSet);
+ pthread_sigmask(SIG_SETMASK, &sigSet, &sigSetSave);
+
+ wrkrRunning = 0;
+ pthread_cond_init(&wrkrIdle, NULL);
+ pthread_attr_init(&sessThrdAttr);
+ pthread_attr_setstacksize(&sessThrdAttr, 4096*1024);
+ for(i = 0 ; i < wrkrMax ; ++i) {
+ /* init worker info structure! */
+ pthread_cond_init(&wrkrInfo[i].run, NULL);
+ wrkrInfo[i].pSrv = NULL;
+ wrkrInfo[i].numCalled = 0;
+ r = pthread_create(&wrkrInfo[i].tid, &sessThrdAttr, wrkr, &(wrkrInfo[i]));
+ if(r == 0) {
+ wrkrInfo[i].enabled = 1;
+ } else {
+ LogError(r, NO_ERRCODE, "tcpsrv error creating thread");
+ }
+ }
+ pthread_attr_destroy(&sessThrdAttr);
+ pthread_sigmask(SIG_SETMASK, &sigSetSave, NULL);
+}
+
+/* destroy worker pool structures and wait for workers to terminate
+ */
+static void
+stopWorkerPool(void)
+{
+ int i;
+ for(i = 0 ; i < wrkrMax ; ++i) {
+ pthread_mutex_lock(&wrkrMut);
+ pthread_cond_signal(&wrkrInfo[i].run); /* awake wrkr if not running */
+ pthread_mutex_unlock(&wrkrMut);
+ pthread_join(wrkrInfo[i].tid, NULL);
+ DBGPRINTF("tcpsrv: info: worker %d was called %llu times\n", i, wrkrInfo[i].numCalled);
+ pthread_cond_destroy(&wrkrInfo[i].run);
+ }
+ pthread_cond_destroy(&wrkrIdle);
+}
+
+
+/* --------------- here now comes the plumbing that makes as a library module --------------- */
+
+BEGINmodExit
+CODESTARTmodExit
+ if(bWrkrRunning) {
+ stopWorkerPool();
+ bWrkrRunning = 0;
+ }
+ /* de-init in reverse order! */
+ tcpsrvClassExit();
+ tcps_sessClassExit();
+ pthread_mutex_destroy(&wrkrMut);
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_LIB_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+ /* we just init the worker mutex, but do not start the workers themselves. This is deferred
+ * to the first call of Run(). Reasons for this:
+ * 1. depending on load order, tcpsrv gets loaded during rsyslog startup BEFORE
+ * it forks, in which case the workers would be running in the then-killed parent,
+ * leading to a defuncnt child (we actually had this bug).
+ * 2. depending on circumstances, Run() would possibly never be called, in which case
+ * the worker threads would be totally useless.
+ * Note that in order to guarantee a non-racy worker start, we need to guard the
+ * startup sequence by a mutex, which is why we init it here (no problem with fork()
+ * in this case as the mutex is a pure-memory structure).
+ * rgerhards, 2012-05-18
+ */
+ pthread_mutex_init(&wrkrMut, NULL);
+ bWrkrRunning = 0;
+
+ /* Initialize all classes that are in our module - this includes ourselfs */
+ CHKiRet(tcps_sessClassInit(pModInfo));
+ CHKiRet(tcpsrvClassInit(pModInfo)); /* must be done after tcps_sess, as we use it */
+ENDmodInit
diff --git a/runtime/tcpsrv.h b/runtime/tcpsrv.h
new file mode 100644
index 0000000..d1ffdc3
--- /dev/null
+++ b/runtime/tcpsrv.h
@@ -0,0 +1,233 @@
+/* Definitions for tcpsrv class.
+ *
+ * Copyright 2008-2022 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_TCPSRV_H
+#define INCLUDED_TCPSRV_H
+
+#include "obj.h"
+#include "prop.h"
+#include "net.h"
+#include "tcps_sess.h"
+#include "statsobj.h"
+
+/* support for framing anomalies */
+typedef enum ETCPsyslogFramingAnomaly {
+ frame_normal = 0,
+ frame_NetScreen = 1,
+ frame_CiscoIOS = 2
+} eTCPsyslogFramingAnomaly;
+
+
+/* config parameters for TCP listeners */
+struct tcpLstnParams_s {
+ const uchar *pszPort; /**< the ports the listener shall listen on */
+ const uchar *pszAddr; /**< the addrs the listener shall listen on */
+ sbool bSuppOctetFram; /**< do we support octect-counted framing? (if no->legay only!)*/
+ sbool bSPFramingFix; /**< support work-around for broken Cisco ASA framing? */
+ sbool bPreserveCase; /**< preserve case in fromhost */
+ const uchar *pszLstnPortFileName; /**< File in which the dynamic port is written */
+ uchar *pszStrmDrvrName; /**< stream driver to use */
+ uchar *pszInputName; /**< value to be used as input name */
+ prop_t *pInputName;
+ ruleset_t *pRuleset; /**< associated ruleset */
+ uchar dfltTZ[8]; /**< default TZ if none in timestamp; '\0' =No Default */
+};
+
+/* list of tcp listen ports */
+struct tcpLstnPortList_s {
+ tcpLstnParams_t *cnf_params; /**< listener config parameters */
+ tcpsrv_t *pSrv; /**< pointer to higher-level server instance */
+ statsobj_t *stats; /**< associated stats object */
+ ratelimit_t *ratelimiter;
+ STATSCOUNTER_DEF(ctrSubmit, mutCtrSubmit)
+ tcpLstnPortList_t *pNext; /**< next port or NULL */
+};
+
+#define TCPSRV_NO_ADDTL_DELIMITER -1 /* specifies that no additional delimiter is to be used in TCP framing */
+
+/* the tcpsrv object */
+struct tcpsrv_s {
+ BEGINobjInstance; /**< Data to implement generic object - MUST be the first data element! */
+ int bUseKeepAlive; /**< use socket layer KEEPALIVE handling? */
+ int iKeepAliveIntvl; /**< socket layer KEEPALIVE interval */
+ int iKeepAliveProbes; /**< socket layer KEEPALIVE probes */
+ int iKeepAliveTime; /**< socket layer KEEPALIVE timeout */
+ netstrms_t *pNS; /**< pointer to network stream subsystem */
+ int iDrvrMode; /**< mode of the stream driver to use */
+ int DrvrChkExtendedKeyUsage; /**< if true, verify extended key usage in certs */
+ int DrvrPrioritizeSan; /**< if true, perform stricter checking of names in certs */
+ int DrvrTlsVerifyDepth; /**< Verify Depth for certificate chains */
+ uchar *gnutlsPriorityString; /**< priority string for gnutls */
+ uchar *pszLstnPortFileName; /**< File in which the dynamic port is written */
+ uchar *pszDrvrAuthMode; /**< auth mode of the stream driver to use */
+ uchar *pszDrvrPermitExpiredCerts;/**< current driver setting for handlign expired certs */
+ uchar *pszDrvrCAFile;
+ uchar *pszDrvrCRLFile;
+ uchar *pszDrvrKeyFile;
+ uchar *pszDrvrCertFile;
+ uchar *pszDrvrName; /**< name of stream driver to use */
+ uchar *pszInputName; /**< value to be used as input name */ // TODO: REMOVE ME!!!!
+ uchar *pszOrigin; /**< module to be used as "origin" (e.g. for pstats) */
+ ruleset_t *pRuleset; /**< ruleset to bind to */
+ permittedPeers_t *pPermPeers;/**< driver's permitted peers */
+ sbool bEmitMsgOnClose; /**< emit an informational message when the remote peer closes connection */
+ sbool bEmitMsgOnOpen;
+ sbool bUsingEPoll; /**< are we in epoll mode (means we do not need to keep track of sessions!) */
+ sbool bUseFlowControl; /**< use flow control (make light delayable) */
+ sbool bSPFramingFix; /**< support work-around for broken Cisco ASA framing? */
+ int iLstnCurr; /**< max nbr of listeners currently supported */
+ netstrm_t **ppLstn; /**< our netstream listeners */
+ tcpLstnPortList_t **ppLstnPort; /**< pointer to relevant listen port description */
+ int iLstnMax; /**< max number of listeners supported */
+ int iSessMax; /**< max number of sessions supported */
+ uchar dfltTZ[8]; /**< default TZ if none in timestamp; '\0' =No Default */
+ tcpLstnPortList_t *pLstnPorts; /**< head pointer for listen ports */
+
+ int addtlFrameDelim; /**< additional frame delimiter for plain TCP syslog
+ framing (e.g. to handle NetScreen) */
+ int maxFrameSize; /**< max frame size for octet counted*/
+ int bDisableLFDelim; /**< if 1, standard LF frame delimiter is disabled (*very dangerous*) */
+ int discardTruncatedMsg;/**< discard msg part that has been truncated*/
+ sbool bPreserveCase; /**< preserve case in fromhost */
+ unsigned int ratelimitInterval;
+ unsigned int ratelimitBurst;
+ tcps_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)(tcps_sess_t*, char*, size_t, ssize_t *, int*);
+ rsRetVal (*OpenLstnSocks)(struct tcpsrv_s*);
+ rsRetVal (*pOnListenDeinit)(void*);
+ rsRetVal (*OnDestruct)(void*);
+ rsRetVal (*pOnRegularClose)(tcps_sess_t *pSess);
+ rsRetVal (*pOnErrClose)(tcps_sess_t *pSess);
+ /* session specific callbacks */
+ rsRetVal (*pOnSessAccept)(tcpsrv_t *, tcps_sess_t*);
+ rsRetVal (*OnSessConstructFinalize)(void*);
+ rsRetVal (*pOnSessDestruct)(void*);
+ rsRetVal (*OnMsgReceive)(tcps_sess_t *, uchar *pszMsg, int iLenMsg); /* submit message callback */
+};
+
+
+/**
+ * The following structure is a set of descriptors that need to be processed.
+ * This set will be the result of the epoll or select call and be used
+ * in the actual request processing stage. It serves as a basis
+ * to run multiple request by concurrent threads. -- rgerhards, 2011-01-24
+ */
+struct tcpsrv_workset_s {
+ int idx; /**< index into session table (or -1 if listener) */
+ void *pUsr;
+};
+
+
+/* interfaces */
+BEGINinterface(tcpsrv) /* name must also be changed in ENDinterface macro! */
+ INTERFACEObjDebugPrint(tcpsrv);
+ rsRetVal (*Construct)(tcpsrv_t **ppThis);
+ rsRetVal (*ConstructFinalize)(tcpsrv_t __attribute__((unused)) *pThis);
+ rsRetVal (*Destruct)(tcpsrv_t **ppThis);
+ rsRetVal (*ATTR_NONNULL(1,2) configureTCPListen)(tcpsrv_t*, tcpLstnParams_t *const cnf_params);
+ rsRetVal (*create_tcp_socket)(tcpsrv_t *pThis);
+ rsRetVal (*Run)(tcpsrv_t *pThis);
+ /* set methods */
+ rsRetVal (*SetAddtlFrameDelim)(tcpsrv_t*, int);
+ rsRetVal (*SetMaxFrameSize)(tcpsrv_t*, int);
+ rsRetVal (*SetInputName)(tcpsrv_t *const pThis,tcpLstnParams_t *const cnf_params, const uchar *const name);
+ rsRetVal (*SetUsrP)(tcpsrv_t*, void*);
+ rsRetVal (*SetCBIsPermittedHost)(tcpsrv_t*, int (*) (struct sockaddr *addr, char*, void*, void*));
+ rsRetVal (*SetCBOpenLstnSocks)(tcpsrv_t *, rsRetVal (*)(tcpsrv_t*));
+ rsRetVal (*SetCBRcvData)(tcpsrv_t *pThis, rsRetVal (*pRcvData)(tcps_sess_t*, char*, size_t, ssize_t*, int*));
+ rsRetVal (*SetCBOnListenDeinit)(tcpsrv_t*, rsRetVal (*)(void*));
+ rsRetVal (*SetCBOnDestruct)(tcpsrv_t*, rsRetVal (*) (void*));
+ rsRetVal (*SetCBOnRegularClose)(tcpsrv_t*, rsRetVal (*) (tcps_sess_t*));
+ rsRetVal (*SetCBOnErrClose)(tcpsrv_t*, rsRetVal (*) (tcps_sess_t*));
+ rsRetVal (*SetDrvrMode)(tcpsrv_t *pThis, int iMode);
+ rsRetVal (*SetDrvrAuthMode)(tcpsrv_t *pThis, uchar *pszMode);
+ rsRetVal (*SetDrvrPermitExpiredCerts)(tcpsrv_t *pThis, uchar *pszMode);
+ rsRetVal (*SetDrvrPermPeers)(tcpsrv_t *pThis, permittedPeers_t*);
+ /* session specifics */
+ rsRetVal (*SetCBOnSessAccept)(tcpsrv_t*, rsRetVal (*) (tcpsrv_t*, tcps_sess_t*));
+ rsRetVal (*SetCBOnSessDestruct)(tcpsrv_t*, rsRetVal (*) (void*));
+ rsRetVal (*SetCBOnSessConstructFinalize)(tcpsrv_t*, rsRetVal (*) (void*));
+ /* added v5 */
+ rsRetVal (*SetSessMax)(tcpsrv_t *pThis, int iMaxSess); /* 2009-04-09 */
+ /* added v6 */
+ rsRetVal (*SetOnMsgReceive)(tcpsrv_t *pThis,
+ rsRetVal (*OnMsgReceive)(tcps_sess_t*, uchar*, int)); /* 2009-05-24 */
+ rsRetVal (*SetRuleset)(tcpsrv_t *pThis, ruleset_t*); /* 2009-06-12 */
+ /* added v7 (accidently named v8!) */
+ rsRetVal (*SetLstnMax)(tcpsrv_t *pThis, int iMaxLstn); /* 2009-08-17 */
+ rsRetVal (*SetNotificationOnRemoteClose)(tcpsrv_t *pThis, int bNewVal); /* 2009-10-01 */
+ rsRetVal (*SetNotificationOnRemoteOpen)(tcpsrv_t *pThis, int bNewVal); /* 2022-08-23 */
+ /* added v9 -- rgerhards, 2010-03-01 */
+ rsRetVal (*SetbDisableLFDelim)(tcpsrv_t*, int);
+ /* added v10 -- rgerhards, 2011-04-01 */
+ rsRetVal (*SetDiscardTruncatedMsg)(tcpsrv_t*, int);
+ rsRetVal (*SetUseFlowControl)(tcpsrv_t*, int);
+ /* added v11 -- rgerhards, 2011-05-09 */
+ rsRetVal (*SetKeepAlive)(tcpsrv_t*, int);
+ /* added v13 -- rgerhards, 2012-10-15 */
+ rsRetVal (*SetLinuxLikeRatelimiters)(tcpsrv_t *pThis, unsigned int interval, unsigned int burst);
+ /* added v14 -- rgerhards, 2013-07-28 */
+ rsRetVal (*SetDfltTZ)(tcpsrv_t *pThis, uchar *dfltTZ);
+ /* added v15 -- rgerhards, 2013-09-17 */
+ rsRetVal (*SetDrvrName)(tcpsrv_t *pThis, uchar *pszName);
+ /* added v16 -- rgerhards, 2014-09-08 */
+ rsRetVal (*SetOrigin)(tcpsrv_t*, uchar*);
+ /* added v17 */
+ rsRetVal (*SetKeepAliveIntvl)(tcpsrv_t*, int);
+ rsRetVal (*SetKeepAliveProbes)(tcpsrv_t*, int);
+ rsRetVal (*SetKeepAliveTime)(tcpsrv_t*, int);
+ /* added v18 */
+ rsRetVal (*SetbSPFramingFix)(tcpsrv_t*, sbool);
+ /* added v19 -- PascalWithopf, 2017-08-08 */
+ rsRetVal (*SetGnutlsPriorityString)(tcpsrv_t*, uchar*);
+ /* added v21 -- Preserve case in fromhost, 2018-08-16 */
+ rsRetVal (*SetPreserveCase)(tcpsrv_t *pThis, int bPreserveCase);
+ /* added v23 -- Options for stricter driver behavior, 2019-08-16 */
+ rsRetVal (*SetDrvrCheckExtendedKeyUsage)(tcpsrv_t *pThis, int ChkExtendedKeyUsage);
+ rsRetVal (*SetDrvrPrioritizeSAN)(tcpsrv_t *pThis, int prioritizeSan);
+ /* added v24 -- Options for TLS verify depth driver behavior, 2019-12-20 */
+ rsRetVal (*SetDrvrTlsVerifyDepth)(tcpsrv_t *pThis, int verifyDepth);
+ /* added v25 -- Options for TLS certificates, 2021-07-19 */
+ rsRetVal (*SetDrvrCAFile)(tcpsrv_t *pThis, uchar *pszMode);
+ rsRetVal (*SetDrvrKeyFile)(tcpsrv_t *pThis, uchar *pszMode);
+ rsRetVal (*SetDrvrCertFile)(tcpsrv_t *pThis, uchar *pszMode);
+ /* added v26 -- Options for TLS CRL file */
+ rsRetVal (*SetDrvrCRLFile)(tcpsrv_t *pThis, uchar *pszMode);
+ENDinterface(tcpsrv)
+#define tcpsrvCURR_IF_VERSION 26 /* increment whenever you change the interface structure! */
+/* change for v4:
+ * - SetAddtlFrameDelim() added -- rgerhards, 2008-12-10
+ * - SetInputName() added -- rgerhards, 2008-12-10
+ * change for v5 and up: see above
+ * for v12: param bSuppOctetFram added to configureTCPListen
+ * for v20: add oserr to setCBRcvData signature -- rgerhards, 2017-09-04
+ */
+
+
+/* prototypes */
+PROTOTYPEObj(tcpsrv);
+
+/* the name of our library binary */
+#define LM_TCPSRV_FILENAME "lmtcpsrv"
+
+#endif /* #ifndef INCLUDED_TCPSRV_H */
diff --git a/runtime/timezones.c b/runtime/timezones.c
new file mode 100644
index 0000000..e1e0394
--- /dev/null
+++ b/runtime/timezones.c
@@ -0,0 +1,205 @@
+/* timezones.c
+ * Support for timezones in RainerScript.
+ *
+ * Copyright 2022 Attila Lakatos 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/types.h>
+#include <unistd.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include "rsyslog.h"
+#include "unicode-helper.h"
+#include "errmsg.h"
+#include "parserif.h"
+#include "rainerscript.h"
+#include "srUtils.h"
+#include "rsconf.h"
+
+
+static struct cnfparamdescr timezonecnfparamdescr[] = {
+ { "id", eCmdHdlrString, CNFPARAM_REQUIRED},
+ { "offset", eCmdHdlrGetWord, CNFPARAM_REQUIRED }
+};
+static struct cnfparamblk timezonepblk = {
+ CNFPARAMBLK_VERSION,
+ sizeof(timezonecnfparamdescr)/sizeof(struct cnfparamdescr),
+ timezonecnfparamdescr
+};
+
+/* Note: this function is NOT thread-safe!
+ * This is currently not needed as used only during
+ * initialization.
+ */
+static rsRetVal
+addTimezoneInfo(rsconf_t *cnf, uchar *tzid, char offsMode, int8_t offsHour, int8_t offsMin)
+{
+ DEFiRet;
+ tzinfo_t *newti;
+ CHKmalloc(newti = realloc(cnf->timezones.tzinfos, (cnf->timezones.ntzinfos+1)*sizeof(tzinfo_t)));
+ if((newti[cnf->timezones.ntzinfos].id = strdup((char*)tzid)) == NULL) {
+ free(newti);
+ DBGPRINTF("addTimezoneInfo: strdup failed with OOM\n");
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ newti[cnf->timezones.ntzinfos].offsMode = offsMode;
+ newti[cnf->timezones.ntzinfos].offsHour = offsHour;
+ newti[cnf->timezones.ntzinfos].offsMin = offsMin;
+ ++cnf->timezones.ntzinfos, cnf->timezones.tzinfos = newti;
+finalize_it:
+ RETiRet;
+}
+
+void
+glblProcessTimezone(struct cnfobj *o)
+{
+ struct cnfparamvals *pvals;
+ uchar *id = NULL;
+ uchar *offset = NULL;
+ char offsMode;
+ int8_t offsHour;
+ int8_t offsMin;
+ int i;
+
+ pvals = nvlstGetParams(o->nvlst, &timezonepblk, NULL);
+ if(pvals == NULL) {
+ LogError(0, RS_RET_MISSING_CNFPARAMS, "error processing timezone "
+ "config parameters");
+ goto done;
+ }
+ if(Debug) {
+ dbgprintf("timezone param blk after glblProcessTimezone:\n");
+ cnfparamsPrint(&timezonepblk, pvals);
+ }
+
+ for(i = 0 ; i < timezonepblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(timezonepblk.descr[i].name, "id")) {
+ id = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else if(!strcmp(timezonepblk.descr[i].name, "offset")) {
+ offset = (uchar*) es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else {
+ dbgprintf("glblProcessTimezone: program error, non-handled "
+ "param '%s'\n", timezonepblk.descr[i].name);
+ }
+ }
+
+ /* note: the following two checks for NULL are not strictly necessary
+ * as these are required parameters for the config block. But we keep
+ * them to make the clang static analyzer happy, which also helps
+ * guard against logic errors.
+ */
+ if(offset == NULL) {
+ parser_errmsg("offset parameter missing (logic error?), timezone config ignored");
+ goto done;
+ }
+ if(id == NULL) {
+ parser_errmsg("id parameter missing (logic error?), timezone config ignored");
+ goto done;
+ }
+
+ if( strlen((char*)offset) != 6
+ || !(offset[0] == '-' || offset[0] == '+')
+ || !(isdigit(offset[1]) && isdigit(offset[2]))
+ || offset[3] != ':'
+ || !(isdigit(offset[4]) && isdigit(offset[5]))
+ ) {
+ parser_errmsg("timezone offset has invalid format. Must be +/-hh:mm, e.g. \"-07:00\".");
+ goto done;
+ }
+
+ offsHour = (offset[1] - '0') * 10 + offset[2] - '0';
+ offsMin = (offset[4] - '0') * 10 + offset[5] - '0';
+ offsMode = offset[0];
+
+ if(offsHour > 12 || offsMin > 59) {
+ parser_errmsg("timezone offset outside of supported range (hours 0..12, minutes 0..59)");
+ goto done;
+ }
+
+ addTimezoneInfo(loadConf, id, offsMode, offsHour, offsMin);
+
+done:
+ cnfparamvalsDestruct(pvals, &timezonepblk);
+ free(id);
+ free(offset);
+}
+
+/* comparison function for qsort() and string array compare
+ * this is for the string lookup table type
+ */
+static int
+qs_arrcmp_tzinfo(const void *s1, const void *s2)
+{
+ return strcmp(((tzinfo_t*)s1)->id, ((tzinfo_t*)s2)->id);
+}
+
+void sortTimezones(rsconf_t *cnf)
+{
+ if (cnf->timezones.ntzinfos > 0) {
+ qsort(cnf->timezones.tzinfos, cnf->timezones.ntzinfos,
+ sizeof(tzinfo_t), qs_arrcmp_tzinfo);
+ }
+}
+
+void
+displayTimezones(rsconf_t *cnf)
+{
+ if(!Debug)
+ return;
+ for(int i = 0 ; i < cnf->timezones.ntzinfos ; ++i)
+ dbgprintf("tzinfo: '%s':%c%2.2d:%2.2d\n",
+ cnf->timezones.tzinfos[i].id, cnf->timezones.tzinfos[i].offsMode,
+ cnf->timezones.tzinfos[i].offsHour, cnf->timezones.tzinfos[i].offsMin);
+}
+
+static int
+bs_arrcmp_tzinfo(const void *s1, const void *s2)
+{
+ return strcmp((char*)s1, (char*)((tzinfo_t*)s2)->id);
+}
+
+/* returns matching timezone info or NULL if no entry exists */
+tzinfo_t*
+glblFindTimezone(rsconf_t *cnf, char *id)
+{
+ return (tzinfo_t*) bsearch(
+ id, cnf->timezones.tzinfos, cnf->timezones.ntzinfos, sizeof(tzinfo_t), bs_arrcmp_tzinfo);
+}
+
+static void
+freeTimezone(tzinfo_t *tzinfo)
+{
+ free(tzinfo->id);
+}
+
+void
+freeTimezones(rsconf_t *cnf)
+{
+ for(int i = 0; i < cnf->timezones.ntzinfos ; ++i)
+ freeTimezone(&cnf->timezones.tzinfos[i]);
+ if (cnf->timezones.ntzinfos > 0)
+ free(cnf->timezones.tzinfos);
+ cnf->timezones.tzinfos = NULL;
+}
diff --git a/runtime/timezones.h b/runtime/timezones.h
new file mode 100644
index 0000000..d2bcdb4
--- /dev/null
+++ b/runtime/timezones.h
@@ -0,0 +1,38 @@
+/* header for timezones.c
+ *
+ * Copyright 2022 Attila Lakatos 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_TIMEZONES_H
+#define INCLUDED_TIMEZONES_H
+
+#include "rsconf.h"
+
+/* timezone specific parameters*/
+struct timezones_s {
+ tzinfo_t *tzinfos;
+ int ntzinfos;
+};
+
+void displayTimezones(rsconf_t *cnf);
+void sortTimezones(rsconf_t *cnf);
+void glblProcessTimezone(struct cnfobj *o);
+tzinfo_t* glblFindTimezone(rsconf_t *cnf, char *id);
+void freeTimezones(rsconf_t *cnf);
+
+#endif
diff --git a/runtime/typedefs.h b/runtime/typedefs.h
new file mode 100644
index 0000000..0ce6da0
--- /dev/null
+++ b/runtime/typedefs.h
@@ -0,0 +1,314 @@
+/* This defines some types commonly used. Do NOT include any other
+ * rsyslog runtime file.
+ *
+ * Begun 2010-11-25 RGerhards
+ *
+ * Copyright (C) 2005-2020 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
+
+#include <stdint.h>
+#if defined(__FreeBSD__) || !defined(HAVE_LSEEK64)
+#include <sys/types.h>
+#endif
+
+/* some universal fixed size integer defines ... */
+#ifndef _AIX
+typedef long long int64;
+#endif
+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 uint16_t syslog_pri_t; /* to be used for syslog PRI values */
+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 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 net_ossl_s net_ossl_t;
+typedef struct nsd_ptcp_s nsd_ptcp_t;
+typedef struct nsd_gtls_s nsd_gtls_t;
+typedef struct nsd_ossl_s nsd_ossl_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 nsdsel_ossl_s nsdsel_ossl_t;
+typedef struct nsdpoll_ptcp_s nsdpoll_ptcp_t;
+typedef struct wti_s wti_t;
+typedef struct msgPropDescr_s msgPropDescr_t;
+typedef struct msg smsg_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 void (*statsobj_read_notifier_t)(statsobj_t *, void *);
+typedef struct nsd_epworkset_s nsd_epworkset_t;
+typedef struct templates_s templates_t;
+typedef struct parsers_s parsers_t;
+typedef struct queuecnf_s queuecnf_t;
+typedef struct parsercnf_s parsercnf_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 timezones_s timezones_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 lookup_string_tab_entry_s lookup_string_tab_entry_t;
+typedef struct lookup_string_tab_s lookup_string_tab_t;
+typedef struct lookup_array_tab_s lookup_array_tab_t;
+typedef struct lookup_sparseArray_tab_s lookup_sparseArray_tab_t;
+typedef struct lookup_sparseArray_tab_entry_s lookup_sparseArray_tab_entry_t;
+typedef struct lookup_tables_s lookup_tables_t;
+typedef union lookup_key_u lookup_key_t;
+
+typedef struct lookup_s lookup_t;
+typedef struct lookup_ref_s lookup_ref_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 tcpLstnParams_s tcpLstnParams_t;
+typedef struct tcpLstnPortList_s tcpLstnPortList_t; // TODO: rename?
+typedef struct strmLstnPortList_s strmLstnPortList_t; // TODO: rename?
+typedef struct actWrkrIParams actWrkrIParams_t;
+typedef struct dynstats_bucket_s dynstats_bucket_t;
+typedef struct dynstats_buckets_s dynstats_buckets_t;
+typedef struct perctile_buckets_s perctile_buckets_t;
+typedef struct dynstats_ctr_s dynstats_ctr_t;
+
+/* 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 signed 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;
+
+#ifndef HAVE_LSEEK64
+# ifndef HAVE_OFF64_T
+ typedef off_t off64_t;
+# endif
+#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_JSONMESG 24
+#define PROP_RAWMSG_AFTER_PRI 25
+#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_SYS_BOM 159
+#define PROP_SYS_UPTIME 160
+#define PROP_UUID 161
+#define PROP_SYS_NOW_UTC 162
+#define PROP_SYS_YEAR_UTC 163
+#define PROP_SYS_MONTH_UTC 164
+#define PROP_SYS_DAY_UTC 165
+#define PROP_SYS_HOUR_UTC 166
+#define PROP_SYS_HHOUR_UTC 167
+#define PROP_SYS_QHOUR_UTC 168
+#define PROP_SYS_MINUTE_UTC 169
+#define PROP_SYS_WDAY 170
+#define PROP_SYS_WDAY_UTC 171
+#define PROP_SYS_NOW_UXTIMESTAMP 173
+#define PROP_CEE 200
+#define PROP_CEE_ALL_JSON 201
+#define PROP_LOCAL_VAR 202
+#define PROP_GLOBAL_VAR 203
+#define PROP_CEE_ALL_JSON_PLAIN 204
+
+/* 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 */
+ smsg_t **ppMsgs;
+};
+
+/* the following structure is a helper to describe a message property */
+struct msgPropDescr_s {
+ propid_t id;
+ uchar *name; /* name and lenName are only set for dynamic */
+ int nameLen; /* properties (JSON) */
+};
+
+/* some forward-definitions from the grammar */
+struct nvlst;
+struct cnfobj;
+
+#endif /* multi-include protection */
diff --git a/runtime/unicode-helper.h b/runtime/unicode-helper.h
new file mode 100644
index 0000000..61dc2e0
--- /dev/null
+++ b/runtime/unicode-helper.h
@@ -0,0 +1,44 @@
+/* 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
+ *
+ * Begun 2009-05-21 RGerhards
+ *
+ * Copyright (C) 2009-2016 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>
+
+#define ustrncpy(psz1, psz2, len) strncpy((char*)(psz1), (char*)(psz2), (len))
+#define ustrdup(psz) (uchar*)strdup((char*)(psz))
+#define ustrcmp(psz1, psz2) (strcmp((const char*) (psz1), (const char*) (psz2)))
+#define ustrlen(psz) (strlen((const char*) (psz)))
+#define UCHAR_CONSTANT(x) ((uchar*) (x))
+#define CHAR_CONVERT(x) ((char*) (x))
+
+/* Compare values of two instances/configs/queues especially during dynamic config reload */
+#define USTR_EQUALS(var) \
+ ((pOld->var == NULL) ? (pNew->var == NULL) : (pNew->var != NULL && !ustrcmp(pOld->var, pNew->var)))
+#define NUM_EQUALS(var) (pOld->var == pNew->var)
+
+#endif /* multi-include protection */
diff --git a/runtime/unlimited_select.h b/runtime/unlimited_select.h
new file mode 100644
index 0000000..fcc0107
--- /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
+static inline 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 0000000..c636fbb
--- /dev/null
+++ b/runtime/var.c
@@ -0,0 +1,131 @@
+/* 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-2016 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
+ */
+static 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", rsCStrGetSzStrNoNULL(pThis->val.pStr));
+ break;
+ case VARTYPE_NUMBER:
+ dbgoprint((obj_t*) pThis, "type: number, val %lld\n", pThis->val.num);
+ break;
+ case VARTYPE_SYSLOGTIME:
+ case VARTYPE_NONE:
+ default:
+ dbgoprint((obj_t*) pThis, "type %d currently not supported 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 0000000..ab1c005
--- /dev/null
+++ b/runtime/var.h
@@ -0,0 +1,62 @@
+/* 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 0000000..96bae4d
--- /dev/null
+++ b/runtime/wti.c
@@ -0,0 +1,573 @@
+/* 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 https://www.rsyslog.com/doc/). Be sure to read it
+ * if you are getting aquainted to the object.
+ *
+ * Copyright 2008-2019 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 "errmsg.h"
+#include "wtp.h"
+#include "wti.h"
+#include "obj.h"
+#include "glbl.h"
+#include "action.h"
+#include "atomic.h"
+#include "rsconf.h"
+
+/* static data */
+DEFobjStaticHelpers
+DEFobjCurrIf(glbl)
+
+pthread_key_t thrd_wti_key;
+
+
+/* forward-definitions */
+
+/* methods */
+
+/* get the header for debug messages
+ * The caller must NOT free or otherwise modify the returned string!
+ */
+uchar * ATTR_NONNULL()
+wtiGetDbgHdr(const wti_t *const 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
+ */
+int ATTR_NONNULL()
+wtiGetState(wti_t *pThis)
+{
+ return ATOMIC_FETCH_32BIT(&pThis->bIsRunning, &pThis->mutIsRunning);
+}
+
+/* join terminated worker thread
+ * This may be called in any thread state, it will be a NOP if the
+ * thread is not to join.
+ */
+void ATTR_NONNULL()
+wtiJoinThrd(wti_t *const pThis)
+{
+ int r;
+ ISOBJ_TYPE_assert(pThis, wti);
+ if(wtiGetState(pThis) == WRKTHRD_WAIT_JOIN) {
+ DBGPRINTF("%s: joining terminated worker\n", wtiGetDbgHdr(pThis));
+ if((r = pthread_join(pThis->thrdID, NULL)) != 0) {
+ LogMsg(r, RS_RET_INTERNAL_ERROR, LOG_WARNING,
+ "rsyslog bug? wti cannot join terminated wrkr");
+ }
+ DBGPRINTF("%s: worker fully terminated\n", wtiGetDbgHdr(pThis));
+ wtiSetState(pThis, WRKTHRD_STOPPED);
+ if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslog debug: %s: thread joined\n",
+ wtiGetDbgHdr(pThis));
+ }
+ }
+}
+
+/* Set this thread to "always running" state (can not be unset)
+ * rgerhards, 2009-07-20
+ */
+rsRetVal ATTR_NONNULL()
+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 ATTR_NONNULL()
+wtiSetState(wti_t *pThis, const int newVal)
+{
+ ISOBJ_TYPE_assert(pThis, wti);
+ if(newVal == WRKTHRD_STOPPED) {
+ ATOMIC_STORE_0_TO_INT(&pThis->bIsRunning, &pThis->mutIsRunning);
+ } else {
+ ATOMIC_OR_INT_TO_INT(&pThis->bIsRunning, &pThis->mutIsRunning, newVal);
+ }
+ 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 %p\n", (void*) 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 ATTR_NONNULL()
+wtiCancelThrd(wti_t *pThis, const uchar *const cancelobj)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wti);
+
+ wtiJoinThrd(pThis);
+ if(wtiGetState(pThis) != WRKTHRD_STOPPED) {
+ LogMsg(0, RS_RET_ERR, LOG_WARNING, "%s: need to do cooperative cancellation "
+ "- some data may be lost, increase timeout?", cancelobj);
+ /* we first try the cooperative "cancel" interface */
+ pthread_kill(pThis->thrdID, SIGTTIN);
+ DBGPRINTF("sent SIGTTIN to worker thread %p, giving it a chance to terminate\n",
+ (void *) pThis->thrdID);
+ srSleep(0, 50000);
+ wtiJoinThrd(pThis);
+ }
+
+ if(wtiGetState(pThis) != WRKTHRD_STOPPED) {
+ LogMsg(0, RS_RET_ERR, LOG_WARNING, "%s: need to do hard cancellation", cancelobj);
+ if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslog debug: %s: need to do hard cancellation\n",
+ cancelobj);
+ }
+ pthread_cancel(pThis->thrdID);
+ pthread_kill(pThis->thrdID, SIGTTIN);
+ 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) != WRKTHRD_STOPPED && wtiGetState(pThis) != WRKTHRD_WAIT_JOIN) {
+ DBGOPRINT((obj_t*) pThis, "waiting on termination, state %d\n", wtiGetState(pThis));
+ srSleep(0, 10000);
+ }
+ }
+
+ wtiJoinThrd(pThis);
+ RETiRet;
+}
+
+/* note: this function is only called once in action.c */
+rsRetVal
+wtiNewIParam(wti_t *const pWti, action_t *const pAction, actWrkrIParams_t **piparams)
+{
+ actWrkrInfo_t *const wrkrInfo = &(pWti->actWrkrInfo[pAction->iActionNbr]);
+ actWrkrIParams_t *iparams;
+ int newMax;
+ DEFiRet;
+
+ if(wrkrInfo->p.tx.currIParam == wrkrInfo->p.tx.maxIParams) {
+ /* we need to extend */
+ newMax = (wrkrInfo->p.tx.maxIParams == 0) ? CONF_IPARAMS_BUFSIZE
+ : 2 * wrkrInfo->p.tx.maxIParams;
+ CHKmalloc(iparams = realloc(wrkrInfo->p.tx.iparams,
+ sizeof(actWrkrIParams_t) * pAction->iNumTpls * newMax));
+ memset(iparams + (wrkrInfo->p.tx.currIParam * pAction->iNumTpls), 0,
+ sizeof(actWrkrIParams_t) * pAction->iNumTpls * (newMax - wrkrInfo->p.tx.maxIParams));
+ wrkrInfo->p.tx.iparams = iparams;
+ wrkrInfo->p.tx.maxIParams = newMax;
+ }
+ *piparams = wrkrInfo->p.tx.iparams + wrkrInfo->p.tx.currIParam * pAction->iNumTpls;
+ ++wrkrInfo->p.tx.currIParam;
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* Destructor */
+BEGINobjDestruct(wti) /* be sure to specify the object type also in END and CODESTART macros! */
+CODESTARTobjDestruct(wti)
+ if(wtiGetState(pThis) != WRKTHRD_STOPPED) {
+ DBGPRINTF("%s: rsyslog bug: worker not stopped during shutdown\n",
+ wtiGetDbgHdr(pThis));
+ if(dbgTimeoutToStderr) {
+ fprintf(stderr, "RSYSLOG BUG: %s: worker not stopped during shutdown\n",
+ wtiGetDbgHdr(pThis));
+ } else {
+ assert(wtiGetState(pThis) == WRKTHRD_STOPPED);
+ }
+ }
+ /* actual destruction */
+ batchFree(&pThis->batch);
+ free(pThis->actWrkrInfo);
+ pthread_cond_destroy(&pThis->pcondBusy);
+ 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);
+ pthread_cond_init(&pThis->pcondBusy, NULL);
+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 (for %d actions)\n",
+ wtiGetDbgHdr(pThis), runConf->actions.iActionNbr);
+
+ /* initialize our thread instance descriptor (no concurrency here) */
+ pThis->bIsRunning = WRKTHRD_STOPPED;
+
+ /* must use calloc as we need zero-init */
+ CHKmalloc(pThis->actWrkrInfo = calloc(runConf->actions.iActionNbr, sizeof(actWrkrInfo_t)));
+
+ if(pThis->pWtp == NULL) {
+ dbgprintf("wtiConstructFinalize: pWtp not set, this may be intentional\n");
+ FINALIZE;
+ }
+
+ /* 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;
+
+ ISOBJ_TYPE_assert(pThis, wti);
+ pWtp = pThis->pWtp;
+ ISOBJ_TYPE_assert(pWtp, wtp);
+
+ DBGPRINTF("%s: cancellation cleanup handler called.\n", wtiGetDbgHdr(pThis));
+ pWtp->pfObjProcessed(pWtp->pUsr, pThis);
+ DBGPRINTF("%s: done cancellation cleanup handler.\n", wtiGetDbgHdr(pThis));
+
+}
+
+
+/* wait for queue to become non-empty or timeout
+ * this is introduced as helper to support queue minimum batch sizes, but may
+ * also be used for other cases. This function waits until the queue is non-empty
+ * or a timeout occurs. The timeout must be passed in as absolute value.
+ * @returns 0 if timeout occurs (queue still empty), something else otherwise
+ */
+int ATTR_NONNULL()
+wtiWaitNonEmpty(wti_t *const pThis, const struct timespec timeout)
+{
+ wtp_t *__restrict__ const pWtp = pThis->pWtp;
+ int r;
+
+ DBGOPRINT((obj_t*) pThis, "waiting on queue to become non-empty\n");
+ if(d_pthread_cond_timedwait(&pThis->pcondBusy, pWtp->pmutUsr, &timeout) != 0) {
+ r = 0;
+ } else {
+ r = 1;
+ }
+ DBGOPRINT((obj_t*) pThis, "waited on queue to become non-empty, result %d\n", r);
+ return r;
+}
+
+
+/* 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 void ATTR_NONNULL()
+doIdleProcessing(wti_t *const pThis, wtp_t *const pWtp, int *const pbInactivityTOOccurred)
+{
+ struct timespec t;
+
+ DBGPRINTF("%s: worker IDLE, waiting for work.\n", wtiGetDbgHdr(pThis));
+
+ if(pThis->bAlwaysRunning) {
+ /* never shut down any started worker */
+ d_pthread_cond_wait(&pThis->pcondBusy, pWtp->pmutUsr);
+ } else {
+ timeoutComp(&t, pWtp->toWrkShutdown);/* get absolute timeout */
+ if(d_pthread_cond_timedwait(&pThis->pcondBusy, pWtp->pmutUsr, &t) != 0) {
+ DBGPRINTF("%s: inactivity timeout, worker terminating...\n", wtiGetDbgHdr(pThis));
+ *pbInactivityTOOccurred = 1; /* indicate we had a timeout */
+ }
+ }
+ DBGOPRINT((obj_t*) pThis, "worker awoke from idle processing\n");
+}
+
+
+/* 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_DIAGNOSTIC_PUSH
+PRAGMA_IGNORE_Wempty_body
+rsRetVal
+wtiWorker(wti_t *__restrict__ const pThis)
+{
+ wtp_t *__restrict__ const pWtp = pThis->pWtp; /* our worker thread pool -- shortcut */
+ action_t *__restrict__ pAction;
+ rsRetVal localRet;
+ rsRetVal terminateRet;
+ actWrkrInfo_t *__restrict__ wrkrInfo;
+ int iCancelStateSave;
+ int i, j, k;
+ DEFiRet;
+
+ dbgSetThrdName(pThis->pszDbgHdr);
+ pthread_cleanup_push(wtiWorkerCancelCleanup, pThis);
+ int bInactivityTOOccurred = 0;
+ pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &iCancelStateSave);
+ DBGPRINTF("wti %p: worker starting\n", pThis);
+ /* now we have our identity, on to real processing */
+
+ /* note: in this loop, the mutex is "never" unlocked. Of course,
+ * this is not true: it actually is unlocked when the actual processing
+ * is done, as part of pWtp->pfDoWork() processing. Note that this
+ * function is required to re-lock it when done. We cannot do the
+ * lock/unlock here ourselfs, as pfDoWork() needs to access queue
+ * structures itself.
+ * The same goes for pfRateLimiter(). While we could unlock/lock when
+ * we call it, in practice the function is often called without any
+ * ratelimiting actually done. Only the rate limiter itself knows
+ * that. As such, it needs to bear the burden of doing the locking
+ * when required. -- rgerhards, 2013-11-20
+ */
+ d_pthread_mutex_lock(pWtp->pmutUsr);
+ while(1) { /* loop will be broken below */
+ if(pWtp->pfRateLimiter != NULL) { /* call rate-limiter, if defined */
+ pWtp->pfRateLimiter(pWtp->pUsr);
+ }
+
+ /* 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);
+ break;
+ }
+
+ /* try to execute and process whatever we have */
+ localRet = pWtp->pfDoWork(pWtp->pUsr, pThis);
+
+ if(localRet == RS_RET_ERR_QUEUE_EMERGENCY) {
+ break; /* end of loop */
+ } else if(localRet == RS_RET_IDLE) {
+ if(terminateRet == RS_RET_TERMINATE_WHEN_IDLE || bInactivityTOOccurred) {
+ DBGOPRINT((obj_t*) pThis, "terminating worker terminateRet=%d, "
+ "bInactivityTOOccurred=%d\n", terminateRet, bInactivityTOOccurred);
+ break; /* end of loop */
+ }
+ doIdleProcessing(pThis, pWtp, &bInactivityTOOccurred);
+ continue; /* request next iteration */
+ }
+
+ bInactivityTOOccurred = 0; /* reset for next run */
+ }
+
+ d_pthread_mutex_unlock(pWtp->pmutUsr);
+
+ DBGPRINTF("DDDD: wti %p: worker cleanup action instances\n", pThis);
+ for(i = 0 ; i < runConf->actions.iActionNbr ; ++i) {
+ wrkrInfo = &(pThis->actWrkrInfo[i]);
+ dbgprintf("wti %p, action %d, ptr %p\n", pThis, i, wrkrInfo->actWrkrData);
+ if(wrkrInfo->actWrkrData != NULL) {
+ pAction = wrkrInfo->pAction;
+ actionRemoveWorker(pAction, wrkrInfo->actWrkrData);
+ pAction->pMod->mod.om.freeWrkrInstance(wrkrInfo->actWrkrData);
+ if(pAction->isTransactional) {
+ /* free iparam "cache" - we need to go through to max! */
+ for(j = 0 ; j < wrkrInfo->p.tx.maxIParams ; ++j) {
+ for(k = 0 ; k < pAction->iNumTpls ; ++k) {
+ free(actParam(wrkrInfo->p.tx.iparams,
+ pAction->iNumTpls, j, k).param);
+ }
+ }
+ free(wrkrInfo->p.tx.iparams);
+ wrkrInfo->p.tx.iparams = NULL;
+ wrkrInfo->p.tx.currIParam = 0;
+ wrkrInfo->p.tx.maxIParams = 0;
+ } else {
+ releaseDoActionParams(pAction, pThis, 1);
+ }
+ wrkrInfo->actWrkrData = NULL; /* re-init for next activation */
+ }
+ }
+
+ /* indicate termination */
+ pthread_cleanup_pop(0); /* remove cleanup handler */
+ pthread_setcancelstate(iCancelStateSave, NULL);
+ dbgprintf("wti %p: exiting\n", pThis);
+
+ RETiRet;
+}
+PRAGMA_DIAGNOSTIC_POP
+
+
+/* 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, const 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(lenMsg + 1)) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ memcpy(pThis->pszDbgHdr, pszMsg, lenMsg + 1); /* always think about the \0! */
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function returns (and creates if necessary) a dummy wti suitable
+ * for use by the rule engine. It is intended to be used for direct-mode
+ * main queues (folks, don't do that!). Once created, data is stored in
+ * thread-specific storage.
+ * Note: we do NOT do error checking -- if this functions fails, all the
+ * rest will fail as well... (also, it will only fail under OOM, so...).
+ * Memleak: we leak pWti's when run in direct mode. However, this is only
+ * a cosmetic leak, as we need them until all inputs are terminated,
+ * what means essentially until rsyslog itself is terminated. So we
+ * don't care -- it's just not nice in valgrind, but that's it.
+ */
+wti_t *
+wtiGetDummy(void)
+{
+ wti_t *pWti;
+
+ pWti = (wti_t*) pthread_getspecific(thrd_wti_key);
+ if(pWti == NULL) {
+ wtiConstruct(&pWti);
+ if(pWti != NULL)
+ wtiConstructFinalize(pWti);
+ if(pthread_setspecific(thrd_wti_key, pWti) != 0) {
+ DBGPRINTF("wtiGetDummy: error setspecific thrd_wti_key\n");
+ }
+ }
+ return pWti;
+}
+
+/* dummy */
+static rsRetVal wtiQueryInterface(interface_t __attribute__((unused)) *i) { 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);
+ pthread_key_delete(thrd_wti_key);
+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) */
+ int r;
+ /* request objects we use */
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ r = pthread_key_create(&thrd_wti_key, NULL);
+ if(r != 0) {
+ dbgprintf("wti.c: pthread_key_create failed\n");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ENDObjClassInit(wti)
diff --git a/runtime/wti.h b/runtime/wti.h
new file mode 100644
index 0000000..71eed56
--- /dev/null
+++ b/runtime/wti.h
@@ -0,0 +1,144 @@
+/* Definition of the worker thread instance (wti) class.
+ *
+ * Copyright 2008-2017 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 <stdlib.h>
+#include "wtp.h"
+#include "obj.h"
+#include "batch.h"
+#include "action.h"
+
+
+#define ACT_STATE_RDY 0 /* action ready, waiting for new transaction */
+#define ACT_STATE_ITX 1 /* transaction active, waiting for new data or commit */
+/* 2 currently not being used */
+#define ACT_STATE_RTRY 3 /* failure occurred, trying to restablish ready state */
+#define ACT_STATE_SUSP 4 /* suspended due to failure (return fail until timeout expired) */
+#define ACT_STATE_DATAFAIL 5 /* suspended due to failure in data, which means the message in
+ questions needs to be dropped as it will always fail. The
+ action must still do a "normal" retry in order to bring
+ it back to regular state. */
+/* note: 3 bit bit field --> highest value is 7! */
+
+typedef struct actWrkrInfo {
+ action_t *pAction;
+ void *actWrkrData;
+ uint16_t uResumeOKinRow;/* number of times in a row that resume said OK with an
+ immediate failure following */
+ int iNbrResRtry; /* number of retries since last suspend */
+ sbool bHadAutoCommit; /* did an auto-commit happen during doAction()? */
+ struct {
+ unsigned actState : 3;
+ } flags;
+ union {
+ struct {
+ actWrkrIParams_t *iparams;/* dynamically sized array for transactional outputs */
+ int currIParam;
+ int maxIParams; /* current max */
+ } tx;
+ struct {
+ actWrkrIParams_t actParams[CONF_OMOD_NUMSTRINGS_MAXSIZE];
+ } nontx;
+ } p; /* short name for "parameters" */
+} actWrkrInfo_t;
+
+/* 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? */
+ int *pbShutdownImmediate;/* end processing of this batch immediately if set to 1 */
+ 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 */
+ actWrkrInfo_t *actWrkrInfo; /* *array* of action wrkr infos for all actions
+ (sized for max nbr of actions in config!) */
+ pthread_cond_t pcondBusy; /* condition to wake up the worker, protected by pmutUsr in wtp */
+ DEF_ATOMIC_HELPER_MUT(mutIsRunning)
+ struct {
+ uint8_t script_errno; /* errno-type interface for RainerScript functions */
+ uint8_t bPrevWasSuspended;
+ uint8_t bDoAutoCommit; /* do a commit after each message
+ * this is usually set for batches with 0 element, but may
+ * also be added as a user-selectable option (not implemented yet)
+ */
+ } execState; /* state for the execution engine */
+};
+
+
+/* prototypes */
+rsRetVal wtiConstruct(wti_t **ppThis);
+rsRetVal wtiConstructFinalize(wti_t * const pThis);
+rsRetVal wtiDestruct(wti_t **ppThis);
+rsRetVal wtiWorker(wti_t * const pThis);
+rsRetVal wtiSetDbgHdr(wti_t * const pThis, uchar *pszMsg, size_t lenMsg);
+uchar * ATTR_NONNULL() wtiGetDbgHdr(const wti_t *const pThis);
+rsRetVal wtiCancelThrd(wti_t * const pThis, const uchar *const cancelobj);
+void ATTR_NONNULL() wtiJoinThrd(wti_t *const pThis);
+rsRetVal wtiSetAlwaysRunning(wti_t * const pThis);
+rsRetVal wtiSetState(wti_t * const pThis, int bNew);
+rsRetVal wtiWakeupThrd(wti_t * const pThis);
+int wtiGetState(wti_t * const pThis);
+wti_t *wtiGetDummy(void);
+int ATTR_NONNULL() wtiWaitNonEmpty(wti_t *const pThis, const struct timespec timeout);
+PROTOTYPEObjClassInit(wti);
+PROTOTYPEObjClassExit(wti);
+PROTOTYPEpropSetMeth(wti, pszDbgHdr, uchar*);
+PROTOTYPEpropSetMeth(wti, pWtp, wtp_t*);
+
+#define getActionStateByNbr(pWti, iActNbr) ((uint8_t) ((pWti)->actWrkrInfo[(iActNbr)].flags.actState))
+#define getActionState(pWti, pAction) (((uint8_t) (pWti)->actWrkrInfo[(pAction)->iActionNbr].flags.actState))
+#define setActionState(pWti, pAction, newState) ((pWti)->actWrkrInfo[(pAction)->iActionNbr].flags.actState = \
+(newState))
+#define getActionResumeInRow(pWti, pAction) (((pWti)->actWrkrInfo[(pAction)->iActionNbr].uResumeOKinRow))
+#define setActionResumeInRow(pWti, pAction, val) ((pWti)->actWrkrInfo[(pAction)->iActionNbr].uResumeOKinRow = (val))
+#define incActionResumeInRow(pWti, pAction) ((pWti)->actWrkrInfo[(pAction)->iActionNbr].uResumeOKinRow++)
+#define getActionNbrResRtry(pWti, pAction) (((pWti)->actWrkrInfo[(pAction)->iActionNbr].iNbrResRtry))
+#define setActionNbrResRtry(pWti, pAction, val) ((pWti)->actWrkrInfo[(pAction)->iActionNbr].iNbrResRtry = (val))
+#define incActionNbrResRtry(pWti, pAction) ((pWti)->actWrkrInfo[(pAction)->iActionNbr].iNbrResRtry++)
+#define wtiInitIParam(piparams) (memset((piparams), 0, sizeof(actWrkrIParams_t)))
+
+#define wtiGetScriptErrno(pWti) ((pWti)->execState.script_errno)
+#define wtiSetScriptErrno(pWti, newval) (pWti)->execState.script_errno = (newval)
+
+static inline uint8_t ATTR_UNUSED ATTR_NONNULL(1)
+wtiGetPrevWasSuspended(const wti_t * const pWti)
+{
+ assert(pWti != NULL);
+ return pWti->execState.bPrevWasSuspended;
+}
+
+static inline void __attribute__((unused))
+wtiResetExecState(wti_t * const pWti, batch_t * const pBatch)
+{
+ wtiSetScriptErrno(pWti, 0);
+ pWti->execState.bPrevWasSuspended = 0;
+ pWti->execState.bDoAutoCommit = (batchNumMsgs(pBatch) == 1);
+}
+
+
+rsRetVal wtiNewIParam(wti_t *const pWti, action_t *const pAction, actWrkrIParams_t **piparams);
+#endif /* #ifndef WTI_H_INCLUDED */
diff --git a/runtime/wtp.c b/runtime/wtp.c
new file mode 100644
index 0000000..3bce02a
--- /dev/null
+++ b/runtime/wtp.c
@@ -0,0 +1,650 @@
+/* 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 https://www.rsyslog.com/doc/). Be sure to read it
+ * if you are getting aquainted to the object.
+ *
+ * Copyright 2008-2018 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>
+#ifdef 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"
+#include "errmsg.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 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_voidp_int(__attribute__((unused)) void* p1, __attribute__((unused)) int p2) {
+ return RS_RET_NOT_IMPLEMENTED; }
+static rsRetVal NotImplementedDummy_voidp_intp(__attribute__((unused)) void* p1, __attribute__((unused)) int* p2) {
+ return RS_RET_NOT_IMPLEMENTED; }
+static rsRetVal NotImplementedDummy_voidp_voidp(__attribute__((unused)) void* p1, __attribute__((unused)) void* p2) {
+ return RS_RET_NOT_IMPLEMENTED; }
+static rsRetVal NotImplementedDummy_voidp_wti_tp(__attribute__((unused)) void* p1, __attribute__((unused)) wti_t* p2) {
+ 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->condThrdInitDone, 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
+ /* set all function pointers to "not implemented" dummy so that we can safely call them */
+ pThis->pfChkStopWrkr = (rsRetVal (*)(void*,int))NotImplementedDummy_voidp_int;
+ pThis->pfGetDeqBatchSize = (rsRetVal (*)(void*,int*))NotImplementedDummy_voidp_intp;
+ pThis->pfDoWork = (rsRetVal (*)(void*,void*))NotImplementedDummy_voidp_voidp;
+ pThis->pfObjProcessed = (rsRetVal (*)(void*,wti_t*))NotImplementedDummy_voidp_wti_tp;
+ 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 (numworkerThreads %d)\n",
+ wtpGetDbgHdr(pThis), pThis->iNumWorkerThreads);
+ /* 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",
+ (int) (sizeof(pszBuf)-6), /* leave 6 chars for \0, "/w" and number: */
+ wtpGetDbgHdr(pThis), i);
+ if(lenBuf >= sizeof(pszBuf)) {
+ LogError(0, RS_RET_INTERNAL_ERROR, "%s:%d debug header too long: %zd - in "
+ "thory this cannot happen - truncating", __FILE__, __LINE__, lenBuf);
+ lenBuf = sizeof(pszBuf)-1;
+ pszBuf[lenBuf] = '\0';
+ }
+ 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)
+ d_pthread_mutex_lock(&pThis->mutWtp); /* make sure nobody is still using the mutex */
+ assert(pThis->iCurNumWrkThrd == 0);
+
+ /* destruct workers */
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i)
+ wtiDestruct(&pThis->pWrkr[i]);
+
+ free(pThis->pWrkr);
+ pThis->pWrkr = NULL;
+
+ /* actual destruction */
+ d_pthread_mutex_unlock(&pThis->mutWtp);
+ pthread_cond_destroy(&pThis->condThrdTrm);
+ pthread_cond_destroy(&pThis->condThrdInitDone);
+ 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;
+}
+
+/* join terminated worker threads */
+static void ATTR_NONNULL()
+wtpJoinTerminatedWrkr(wtp_t *const pThis)
+{
+ int i;
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) {
+ wtiJoinThrd(pThis->pWrkr[i]);
+ }
+}
+
+
+/* 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_DIAGNOSTIC_PUSH
+PRAGMA_IGNORE_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 ATTR_NONNULL()
+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);
+ /* awake workers in retry loop */
+ for(i = 0 ; i < pThis->iNumWorkerThreads ; ++i) {
+ wtpJoinTerminatedWrkr(pThis);
+ pthread_cond_signal(&pThis->pWrkr[i]->pcondBusy);
+ 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) {
+ wtpJoinTerminatedWrkr(pThis);
+ 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_DIAGNOSTIC_POP
+
+
+/* Unconditionally cancel all running worker threads.
+ * rgerhards, 2008-01-14
+ */
+rsRetVal ATTR_NONNULL()
+wtpCancelAll(wtp_t *pThis, const uchar *const cancelobj)
+{
+ 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], cancelobj);
+ }
+
+ 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 void
+wtpWrkrExecCleanup(wti_t *pWti)
+{
+ wtp_t *pThis;
+
+ ISOBJ_TYPE_assert(pWti, wti);
+ pThis = pWti->pWtp;
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+// TESTBENCH bughunt - remove when done! 2018-11-05 rgerhards
+if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslog debug: %s: enter WrkrExecCleanup\n", wtiGetDbgHdr(pWti));
+}
+ /* the order of the next two statements is important! */
+ wtiSetState(pWti, WRKTHRD_WAIT_JOIN);
+ ATOMIC_DEC(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd);
+
+ /* note: numWorkersNow is only for message generation, so we do not try
+ * hard to get it 100% accurate (as curently done, it is not).
+ */
+ const int numWorkersNow = ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd);
+ DBGPRINTF("%s: Worker thread %lx, terminated, num workers now %d\n",
+ wtpGetDbgHdr(pThis), (unsigned long) pWti, numWorkersNow);
+ if(numWorkersNow > 0) {
+ LogMsg(0, RS_RET_OPERATION_STATUS, LOG_INFO,
+ "%s: worker thread %lx terminated, now %d active worker threads",
+ wtpGetDbgHdr(pThis), (unsigned long) pWti, numWorkersNow);
+ }
+}
+
+
+/* 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;
+
+ 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);
+
+ 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_DIAGNOSTIC_PUSH
+PRAGMA_IGNORE_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 defined(HAVE_PRCTL) && defined(PR_SET_NAME)
+ uchar *pszDbgHdr;
+ uchar thrdName[32] = "rs:";
+# endif
+
+ ISOBJ_TYPE_assert(pWti, wti);
+ pThis = pWti->pWtp;
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+ /* block all signals except SIGTTIN and SIGSEGV */
+ sigfillset(&sigSet);
+ sigdelset(&sigSet, SIGTTIN);
+ sigdelset(&sigSet, SIGSEGV);
+ pthread_sigmask(SIG_BLOCK, &sigSet, NULL);
+
+# if defined(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
+
+// TESTBENCH bughunt - remove when done! 2018-11-05 rgerhards
+if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslog debug: %s: worker %p started\n", wtpGetDbgHdr(pThis), pThis);
+}
+ /* let the parent know we're done with initialization */
+ d_pthread_mutex_lock(&pThis->mutWtp);
+ wtiSetState(pWti, WRKTHRD_RUNNING);
+ pthread_cond_broadcast(&pThis->condThrdInitDone);
+ d_pthread_mutex_unlock(&pThis->mutWtp);
+
+ pthread_cleanup_push(wtpWrkrExecCancelCleanup, pWti);
+
+ wtiWorker(pWti);
+ pthread_cleanup_pop(0);
+ d_pthread_mutex_lock(&pThis->mutWtp);
+ pthread_cleanup_push(mutexCancelCleanup, &pThis->mutWtp);
+ wtpWrkrExecCleanup(pWti);
+
+ pthread_cond_broadcast(&pThis->condThrdTrm); /* activate anyone waiting on thread shutdown */
+ pthread_cleanup_pop(1); /* unlock mutex */
+ if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslog debug: %p: worker exiting\n", pWti);
+ }
+ pthread_exit(0);
+ return NULL; /* To suppress warning */
+}
+PRAGMA_DIAGNOSTIC_POP
+
+/* start a new worker */
+static rsRetVal ATTR_NONNULL()
+wtpStartWrkr(wtp_t *const pThis, const int permit_during_shutdown)
+{
+ wti_t *pWti;
+ int i;
+ int iState;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pThis, wtp);
+
+// TESTBENCH bughunt - remove when done! 2018-11-05 rgerhards
+if(dbgTimeoutToStderr) {
+ fprintf(stderr, "%s: worker start requested, num workers currently %d\n",
+ wtpGetDbgHdr(pThis),
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd));
+}
+ const wtpState_t wtpState = (wtpState_t) ATOMIC_FETCH_32BIT((int*)&pThis->wtpState, &pThis->mutWtpState);
+ if(wtpState != wtpState_RUNNING && !permit_during_shutdown) {
+ DBGPRINTF("%s: worker start requested during shutdown - ignored\n", wtpGetDbgHdr(pThis));
+ if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslog debug: %s: worker start requested during shutdown - ignored\n",
+ wtpGetDbgHdr(pThis));
+ }
+ return RS_RET_ERR; /* exceptional case, but really makes sense here! */
+ }
+
+ d_pthread_mutex_lock(&pThis->mutWtp);
+
+ wtpJoinTerminatedWrkr(pThis);
+ /* 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_INITIALIZING);
+ iState = pthread_create(&(pWti->thrdID), &pThis->attrThrd, wtpWorker, (void*) pWti);
+ ATOMIC_INC(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd); /* we got one more! */
+
+// TESTBENCH bughunt - remove when done! 2018-11-05 rgerhards
+if(dbgTimeoutToStderr) {
+ fprintf(stderr, "%s: wrkr start initiated with state %d, num workers now %d\n",
+ wtpGetDbgHdr(pThis), iState,
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd));
+}
+ DBGPRINTF("%s: started with state %d, num workers now %d\n",
+ wtpGetDbgHdr(pThis), iState,
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd));
+
+ /* wait for the new thread to initialize its signal mask and
+ * cancellation cleanup handler before proceeding
+ */
+ do {
+ d_pthread_cond_wait(&pThis->condThrdInitDone, &pThis->mutWtp);
+ } while((iState = wtiGetState(pWti)) == WRKTHRD_INITIALIZING);
+ DBGPRINTF("%s: new worker finished initialization with state %d, num workers now %d\n",
+ wtpGetDbgHdr(pThis), iState,
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd));
+// TESTBENCH bughunt - remove when done! 2018-11-05 rgerhards
+if(dbgTimeoutToStderr) {
+ fprintf(stderr, "rsyslog debug: %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.
+ * Parameter "permit_during_shutdown" if true, permits worker starts while the system is
+ * in shutdown state. The prime use case for this is persisting disk queues in enqueue only
+ * mode, which is activated during shutdown.
+ */
+rsRetVal ATTR_NONNULL()
+wtpAdviseMaxWorkers(wtp_t *const pThis, int nMaxWrkr, const int permit_during_shutdown)
+{
+ DEFiRet;
+ int nMissing; /* number workers missing to run */
+ int i, nRunning;
+
+ 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) {
+ if(ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd, &pThis->mutCurNumWrkThrd) > 0) {
+ LogMsg(0, RS_RET_OPERATION_STATUS, LOG_INFO,
+ "%s: high activity - starting %d additional worker thread(s), "
+ "currently %d active worker threads.",
+ wtpGetDbgHdr(pThis), nMissing,
+ ATOMIC_FETCH_32BIT(&pThis->iCurNumWrkThrd,
+ &pThis->mutCurNumWrkThrd) );
+ }
+ /* start the rqtd nbr of workers */
+ for(i = 0 ; i < nMissing ; ++i) {
+ CHKiRet(wtpStartWrkr(pThis, permit_during_shutdown));
+ }
+ } else {
+ /* we have needed number of workers, but they may be sleeping */
+ for(i = 0, nRunning = 0; i < pThis->iNumWorkerThreads && nRunning < nMaxWrkr; ++i) {
+ if (wtiGetState(pThis->pWrkr[i]) != WRKTHRD_STOPPED) {
+ pthread_cond_signal(&pThis->pWrkr[i]->pcondBusy);
+ nRunning++;
+ }
+ }
+ }
+
+
+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)
+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(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 */
+static rsRetVal wtpQueryInterface(interface_t __attribute__((unused)) *i) { 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)
diff --git a/runtime/wtp.h b/runtime/wtp.h
new file mode 100644
index 0000000..6fff374
--- /dev/null
+++ b/runtime/wtp.h
@@ -0,0 +1,108 @@
+/* Definition of the worker thread pool (wtp) object.
+ *
+ * Copyright 2008-2018 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.
+ * important: they need to be increasing with all previous state bits
+ * set. That is because we can only atomically or a value!
+ */
+#define WRKTHRD_STOPPED 0
+#define WRKTHRD_INITIALIZING 1
+#define WRKTHRD_RUNNING 3
+#define WRKTHRD_WAIT_JOIN 7
+
+
+/* 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 condThrdInitDone; /* signalled when a new thread is ready for work */
+ 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;
+ 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 */
+
+
+#define DENY_WORKER_START_DURING_SHUTDOWN 0
+#define PERMIT_WORKER_START_DURING_SHUTDOWN 1
+
+/* prototypes */
+rsRetVal wtpConstruct(wtp_t **ppThis);
+rsRetVal wtpConstructFinalize(wtp_t *pThis);
+rsRetVal wtpDestruct(wtp_t **ppThis);
+rsRetVal wtpAdviseMaxWorkers(wtp_t *pThis, int nMaxWrkr, const int permit_during_shutdown);
+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, const uchar *const cancelobj);
+rsRetVal wtpSetDbgHdr(wtp_t *pThis, uchar *pszMsg, size_t lenMsg);
+rsRetVal wtpShutdownAll(wtp_t *pThis, wtpState_t tShutdownCmd, struct timespec *ptTimeout);
+PROTOTYPEObjClassInit(wtp);
+PROTOTYPEObjClassExit(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);
+
+#endif /* #ifndef WTP_H_INCLUDED */
diff --git a/runtime/zlibw.c b/runtime/zlibw.c
new file mode 100644
index 0000000..77fd2e7
--- /dev/null
+++ b/runtime/zlibw.c
@@ -0,0 +1,239 @@
+/* The zlibwrap object.
+ *
+ * This is an rsyslog object wrapper around zlib.
+ *
+ * Copyright 2009-2022 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 "stream.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);
+}
+
+/* finish zlib buffer */
+static rsRetVal
+doCompressFinish(strm_t *pThis,
+ rsRetVal (*strmPhysWrite)(strm_t *pThis, uchar *pBuf, size_t lenBuf) )
+{
+ int zRet; /* zlib return state */
+ DEFiRet;
+ unsigned outavail;
+ assert(pThis != NULL);
+
+ assert(pThis->compressionDriver == STRM_COMPRESS_ZIP);
+
+ 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 = 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 = deflateEnd(&pThis->zstrm);
+ if(zRet != Z_OK) {
+ LogError(0, RS_RET_ZLIB_ERR, "error %d returned from zlib/deflateEnd()", zRet);
+ }
+
+ pThis->bzInitDone = 0;
+done: 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
+doStrmWrite(strm_t *pThis, uchar *pBuf, size_t lenBuf, const int bFlush,
+ rsRetVal (*strmPhysWrite)(strm_t *pThis, uchar *pBuf, size_t lenBuf) )
+{
+ int zRet; /* zlib return state */
+ DEFiRet;
+ unsigned outavail = 0;
+ assert(pThis != NULL);
+ assert(pBuf != NULL);
+
+ assert(pThis->compressionDriver == STRM_COMPRESS_ZIP);
+
+ 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 = deflateInit2(&pThis->zstrm, pThis->iZipLevel, Z_DEFLATED, 31, 9, Z_DEFAULT_STRATEGY);
+ if(zRet != Z_OK) {
+ LogError(0, RS_RET_ZLIB_ERR, "error %d returned from zlib/deflateInit2()", 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, bFlush %d\n",
+ pThis->zstrm.avail_in, pThis->zstrm.total_in, bFlush);
+ pThis->zstrm.avail_out = pThis->sIOBufSize;
+ pThis->zstrm.next_out = pThis->pZipBuf;
+ zRet = deflate(&pThis->zstrm, bFlush ? Z_SYNC_FLUSH : Z_NO_FLUSH); /* no bad return value */
+ DBGPRINTF("after deflate, ret %d, avail_out %d, to write %d\n",
+ zRet, pThis->zstrm.avail_out, outavail);
+ if(zRet != Z_OK) {
+ LogError(0, RS_RET_ZLIB_ERR, "error %d returned from zlib/Deflate()", zRet);
+ ABORT_FINALIZE(RS_RET_ZLIB_ERR);
+ }
+ 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) {
+ doCompressFinish(pThis, strmPhysWrite);
+ }
+ RETiRet;
+}
+/* destruction of caller's zlib ressources - a dummy for us */
+static rsRetVal
+zlib_Destruct(ATTR_UNUSED strm_t *pThis)
+{
+ return RS_RET_OK;
+}
+
+/* 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;
+ pIf->doStrmWrite = doStrmWrite;
+ pIf->doCompressFinish = doCompressFinish;
+ pIf->Destruct = zlib_Destruct;
+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 0000000..fdbd520
--- /dev/null
+++ b/runtime/zlibw.h
@@ -0,0 +1,51 @@
+/* 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-2022 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>
+
+#include "errmsg.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);
+ rsRetVal (*doStrmWrite)(strm_t *pThis, uchar *const pBuf, const size_t lenBuf, const int bFlush,
+ rsRetVal (*strmPhysWrite)(strm_t *pThis, uchar *pBuf, size_t lenBuf) );
+ rsRetVal (*doCompressFinish)(strm_t *pThis,
+ rsRetVal (*Destruct)(strm_t *pThis, uchar *pBuf, size_t lenBuf) );
+ rsRetVal (*Destruct)(strm_t *pThis);
+ENDinterface(zlibw)
+#define zlibwCURR_IF_VERSION 2 /* 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 */
diff --git a/runtime/zstdw.c b/runtime/zstdw.c
new file mode 100644
index 0000000..7f80dae
--- /dev/null
+++ b/runtime/zstdw.c
@@ -0,0 +1,191 @@
+/* The zstdw object.
+ *
+ * This is an rsyslog object wrapper around zstd.
+ *
+ * Copyright 2022 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 <zstd.h>
+
+#include "rsyslog.h"
+#include "errmsg.h"
+#include "stream.h"
+#include "module-template.h"
+#include "obj.h"
+#include "zstdw.h"
+
+MODULE_TYPE_LIB
+MODULE_TYPE_NOKEEP
+
+/* static data */
+DEFobjStaticHelpers
+
+
+/* finish buffer, to be called before closing the zstd file. */
+static rsRetVal
+zstd_doCompressFinish(strm_t *pThis,
+ rsRetVal (*strmPhysWrite)(strm_t *pThis, uchar *pBuf, size_t lenBuf) )
+{
+ size_t remaining = 0;
+ DEFiRet;
+ assert(pThis != NULL);
+
+ if(!pThis->bzInitDone)
+ goto done;
+
+ char dummybuf; /* not sure if we can pass in NULL as buffer address in this special case */
+ ZSTD_inBuffer input = { &dummybuf, 0, 0 };
+
+ do {
+ ZSTD_outBuffer output = { pThis->pZipBuf, pThis->sIOBufSize, 0 };
+ remaining = ZSTD_compressStream2(pThis->zstd.cctx, &output , &input, ZSTD_e_end);
+ if(ZSTD_isError(remaining)) {
+ LogError(0, RS_RET_ZLIB_ERR,
+ "error returned from ZSTD_compressStream2(): %s\n",
+ ZSTD_getErrorName(remaining));
+ ABORT_FINALIZE(RS_RET_ZLIB_ERR);
+ }
+
+ CHKiRet(strmPhysWrite(pThis, (uchar*)pThis->pZipBuf, output.pos));
+
+ } while (remaining != 0);
+
+finalize_it:
+done: RETiRet;
+}
+
+
+static rsRetVal
+zstd_doStrmWrite(strm_t *pThis, uchar *const pBuf, const size_t lenBuf, const int bFlush,
+ rsRetVal (*strmPhysWrite)(strm_t *pThis, uchar *pBuf, size_t lenBuf) )
+{
+ DEFiRet;
+ assert(pThis != NULL);
+ assert(pBuf != NULL);
+ if(!pThis->bzInitDone) {
+ pThis->zstd.cctx = (void*) ZSTD_createCCtx();
+ if(pThis->zstd.cctx == NULL) {
+ LogError(0, RS_RET_ZLIB_ERR, "error creating zstd context (ZSTD_createCCtx failed, "
+ "that's all we know");
+ ABORT_FINALIZE(RS_RET_ZLIB_ERR);
+ }
+
+ ZSTD_CCtx_setParameter(pThis->zstd.cctx, ZSTD_c_compressionLevel, pThis->iZipLevel);
+ ZSTD_CCtx_setParameter(pThis->zstd.cctx, ZSTD_c_checksumFlag, 1);
+ if(pThis->zstd.num_wrkrs > 0) {
+ ZSTD_CCtx_setParameter(pThis->zstd.cctx, ZSTD_c_nbWorkers,
+ pThis->zstd.num_wrkrs);
+ }
+ pThis->bzInitDone = RSTRUE;
+ }
+
+ /* now doing the compression */
+ ZSTD_inBuffer input = { pBuf, lenBuf, 0 };
+
+ // This following needs to be configurable? It's possibly sufficient to use e_flush
+ // only, as this can also be controlled by veryRobustZip. However, testbench will than
+ // not be able to check when all file lines are complete.
+ ZSTD_EndDirective const mode = bFlush ? ZSTD_e_flush : ZSTD_e_continue;
+ size_t remaining;
+ do {
+ ZSTD_outBuffer output = { pThis->pZipBuf, 128, 0 };
+ remaining = ZSTD_compressStream2(pThis->zstd.cctx, &output , &input, mode);
+ if(ZSTD_isError(remaining)) {
+ LogError(0, RS_RET_ZLIB_ERR, "error returned from ZSTD_compressStream2(): %s",
+ ZSTD_getErrorName(remaining));
+ ABORT_FINALIZE(RS_RET_ZLIB_ERR);
+ }
+
+ CHKiRet(strmPhysWrite(pThis, (uchar*)pThis->pZipBuf, output.pos));
+
+ } while ( mode == ZSTD_e_end ? (remaining != 0) : (input.pos != input.size));
+
+finalize_it:
+ if(pThis->bzInitDone && pThis->bVeryReliableZip) {
+ zstd_doCompressFinish(pThis, strmPhysWrite);
+ }
+ RETiRet;
+}
+
+/* destruction of caller's zstd ressources */
+static rsRetVal
+zstd_Destruct(strm_t *const pThis)
+{
+ DEFiRet;
+ assert(pThis != NULL);
+
+ if(!pThis->bzInitDone)
+ goto done;
+
+ const int result = ZSTD_freeCCtx(pThis->zstd.cctx);
+ if(ZSTD_isError(result)) {
+ LogError(0, RS_RET_ZLIB_ERR, "error from ZSTD_freeCCtx(): %s",
+ ZSTD_getErrorName(result));
+ }
+
+ pThis->bzInitDone = 0;
+done: RETiRet;
+}
+
+
+/* queryInterface function
+ * rgerhards, 2008-03-05
+ */
+BEGINobjQueryInterface(zstdw)
+CODESTARTobjQueryInterface(zstdw)
+ if(pIf->ifVersion != zstdwCURR_IF_VERSION) { /* check for current version, increment on each change */
+ ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED);
+ }
+ pIf->doStrmWrite = zstd_doStrmWrite;
+ pIf->doCompressFinish = zstd_doCompressFinish;
+ pIf->Destruct = zstd_Destruct;
+finalize_it:
+ENDobjQueryInterface(zstdw)
+
+
+/* Initialize the zstdw class. Must be called as the very first method
+ * before anything else is called inside this class.
+ * rgerhards, 2008-02-19
+ */
+BEGINAbstractObjClassInit(zstdw, 1, OBJ_IS_LOADABLE_MODULE) /* class, version */
+ENDObjClassInit(zstdw)
+
+
+/* --------------- 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(zstdwClassInit(pModInfo));
+ENDmodInit
diff --git a/runtime/zstdw.h b/runtime/zstdw.h
new file mode 100644
index 0000000..0dbc468
--- /dev/null
+++ b/runtime/zstdw.h
@@ -0,0 +1,43 @@
+/* The zstdw object. It encapsulates the zstd functionality. The primary
+ * purpose of this wrapper class is to enable rsyslogd core to be build without
+ * zstd libraries.
+ *
+ * Copyright 2022 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_ZSTDW_H
+#define INCLUDED_ZSTDW_H
+
+/* interfaces */
+BEGINinterface(zstdw) /* name must also be changed in ENDinterface macro! */
+ rsRetVal (*doStrmWrite)(strm_t *pThis, uchar *const pBuf, const size_t lenBuf, const int bFlush,
+ rsRetVal (*strmPhysWrite)(strm_t *pThis, uchar *pBuf, size_t lenBuf) );
+ rsRetVal (*doCompressFinish)(strm_t *pThis,
+ rsRetVal (*Destruct)(strm_t *pThis, uchar *pBuf, size_t lenBuf) );
+ rsRetVal (*Destruct)(strm_t *pThis);
+ENDinterface(zstdw)
+#define zstdwCURR_IF_VERSION 1 /* increment whenever you change the interface structure! */
+
+
+/* prototypes */
+PROTOTYPEObj(zstdw);
+
+/* the name of our library binary */
+#define LM_ZSTDW_FILENAME "lmzstdw"
+
+#endif /* #ifndef INCLUDED_ZSTDW_H */