diff options
Diffstat (limited to 'runtime')
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, <) == 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, ¶mblk, 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(¶mblk, 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, ¶ms)); + +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 */ |