summaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile.am29
-rwxr-xr-xtools/gnutls/cert-gen-selfsigned6
-rwxr-xr-xtools/gnutls/cert-show-fingerprint6
-rw-r--r--tools/iminternal.c190
-rw-r--r--tools/iminternal.h49
-rw-r--r--tools/omdiscard.c121
-rw-r--r--tools/omdiscard.h34
-rw-r--r--tools/omfile.c844
-rw-r--r--tools/omfile.h34
-rw-r--r--tools/omfwd.c704
-rw-r--r--tools/omfwd.h34
-rw-r--r--tools/omshell.c148
-rw-r--r--tools/omshell.h34
-rw-r--r--tools/omusrmsg.c326
-rw-r--r--tools/omusrmsg.h34
-rw-r--r--tools/pidfile.c156
-rw-r--r--tools/pidfile.h51
-rw-r--r--tools/rsyslog.conf.5728
-rw-r--r--tools/rsyslogd.8375
-rw-r--r--tools/syslogd.c3416
-rw-r--r--tools/syslogd.h99
21 files changed, 7418 insertions, 0 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
new file mode 100644
index 00000000..b2b7a8ca
--- /dev/null
+++ b/tools/Makefile.am
@@ -0,0 +1,29 @@
+sbin_PROGRAMS =
+man_MANS = rsyslogd.8 rsyslog.conf.5
+
+sbin_PROGRAMS += rsyslogd
+rsyslogd_SOURCES = \
+ syslogd.c \
+ syslogd.h \
+ omshell.c \
+ omshell.h \
+ omusrmsg.c \
+ omusrmsg.h \
+ omfwd.c \
+ omfwd.h \
+ omfile.c \
+ omfile.h \
+ omdiscard.c \
+ omdiscard.h \
+ iminternal.c \
+ iminternal.h \
+ pidfile.c \
+ pidfile.h \
+ \
+ ../dirty.h
+
+rsyslogd_CPPFLAGS = $(pthreads_cflags) $(rsrt_cflags)
+rsyslogd_LDADD = $(zlib_libs) $(pthreads_libs) $(rsrt_libs)
+rsyslogd_LDFLAGS = -export-dynamic
+
+EXTRA_DIST = $(man_MANS)
diff --git a/tools/gnutls/cert-gen-selfsigned b/tools/gnutls/cert-gen-selfsigned
new file mode 100755
index 00000000..e1c25386
--- /dev/null
+++ b/tools/gnutls/cert-gen-selfsigned
@@ -0,0 +1,6 @@
+#/bin/sh
+# generates a self-signed certificate and key suitable for use with rsyslog
+# 2008-05-08, rgerhards
+# TODO: make this a robust shell script
+certtool --generate-privkey --outfile $1-key.pem
+certtool --generate-self-signed --load-privkey $1-key.pem --outfile $1-cert.pem
diff --git a/tools/gnutls/cert-show-fingerprint b/tools/gnutls/cert-show-fingerprint
new file mode 100755
index 00000000..f61c6840
--- /dev/null
+++ b/tools/gnutls/cert-show-fingerprint
@@ -0,0 +1,6 @@
+#/bin/sh
+# must be called with the certificate file as first parameter. Displays all
+# fingerprints for the first certificate.
+# 2008-05-08, rgerhards
+# TODO: make this a robust shell script
+certtool -i < $1|grep Fingerprint
diff --git a/tools/iminternal.c b/tools/iminternal.c
new file mode 100644
index 00000000..60460a99
--- /dev/null
+++ b/tools/iminternal.c
@@ -0,0 +1,190 @@
+/* iminternal.c
+ * This file set implements the internal messages input module for rsyslog.
+ * Note: we currently do not have an input module spec, but
+ * we will have one in the future. This module needs then to be
+ * adapted.
+ *
+ * File begun on 2007-08-03 by RGerhards
+ *
+ * Copyright 2007 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 <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "syslogd.h"
+#include "linkedlist.h"
+#include "iminternal.h"
+
+static linkedList_t llMsgs;
+
+
+/* destructs an iminternal object
+ */
+static rsRetVal iminternalDestruct(iminternal_t *pThis)
+{
+ DEFiRet;
+
+ assert(pThis != NULL);
+
+ if(pThis->pMsg != NULL)
+ msgDestruct(&pThis->pMsg);
+
+ free(pThis);
+
+ RETiRet;
+}
+
+
+/* Construct an iminternal object
+ */
+static rsRetVal iminternalConstruct(iminternal_t **ppThis)
+{
+ DEFiRet;
+ iminternal_t *pThis;
+
+ assert(ppThis != NULL);
+
+ if((pThis = (iminternal_t*) calloc(1, sizeof(iminternal_t))) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis != NULL)
+ iminternalDestruct(pThis);
+ }
+
+ *ppThis = pThis;
+
+ RETiRet;
+}
+
+
+/* add a message to the linked list
+ * Note: the pMsg reference counter is not incremented. Consequently,
+ * the caller must NOT decrement it. The caller actually hands over
+ * full ownership of the pMsg object.
+ * The interface of this function is modelled after syslogd/logmsg(),
+ * for which it is an "replacement".
+ */
+rsRetVal iminternalAddMsg(int pri, msg_t *pMsg, int flags)
+{
+ DEFiRet;
+ iminternal_t *pThis;
+
+ assert(pMsg != NULL);
+
+ CHKiRet(iminternalConstruct(&pThis));
+
+ pThis->pri = pri;
+ pThis->pMsg = pMsg;
+ pThis->flags = flags;
+
+ CHKiRet(llAppend(&llMsgs, NULL, (void*) pThis));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ dbgprintf("iminternalAddMsg() error %d - can not otherwise report this error, message lost\n", iRet);
+ if(pThis != NULL)
+ iminternalDestruct(pThis);
+ }
+
+ RETiRet;
+}
+
+
+/* pull the first error message from the linked list, remove it
+ * from the list and return it to the caller. The caller is
+ * responsible for freeing the message!
+ */
+rsRetVal iminternalRemoveMsg(int *pPri, msg_t **ppMsg, int *pFlags)
+{
+ DEFiRet;
+ iminternal_t *pThis;
+ linkedListCookie_t llCookie = NULL;
+
+ assert(pPri != NULL);
+ assert(ppMsg != NULL);
+ assert(pFlags != NULL);
+
+ CHKiRet(llGetNextElt(&llMsgs, &llCookie, (void*)&pThis));
+ *pPri = pThis->pri;
+ *pFlags = pThis->flags;
+ *ppMsg = pThis->pMsg;
+ pThis->pMsg = NULL; /* we do no longer own it - important for destructor */
+
+ if(llDestroyRootElt(&llMsgs) != RS_RET_OK) {
+ dbgprintf("Root element of iminternal linked list could not be destroyed - there is "
+ "nothing we can do against it, we ignore it for now. Things may go wild "
+ "from here on. This is most probably a program logic error.\n");
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+/* tell the caller if we have any messages ready for processing.
+ * 0 means we have none, everything else means there is at least
+ * one message ready.
+ */
+rsRetVal iminternalHaveMsgReady(int* pbHaveOne)
+{
+ assert(pbHaveOne != NULL);
+
+ return llGetNumElts(&llMsgs, pbHaveOne);
+}
+
+
+/* initialize the iminternal subsystem
+ * must be called once at the start of the program
+ */
+rsRetVal modInitIminternal(void)
+{
+ DEFiRet;
+
+ iRet = llInit(&llMsgs, iminternalDestruct, NULL, NULL);
+
+ RETiRet;
+}
+
+
+/* de-initialize the iminternal subsystem
+ * must be called once at the end of the program
+ * Note: the error list must have been pulled first. We do
+ * NOT care if there are any errors left - we simply destroy
+ * them.
+ */
+rsRetVal modExitIminternal(void)
+{
+ DEFiRet;
+
+ iRet = llDestroy(&llMsgs);
+
+ RETiRet;
+}
+
+/*
+ * vi:set ai:
+ */
diff --git a/tools/iminternal.h b/tools/iminternal.h
new file mode 100644
index 00000000..8dc0f171
--- /dev/null
+++ b/tools/iminternal.h
@@ -0,0 +1,49 @@
+/* Definition of the internal messages input module.
+ *
+ * Note: we currently do not have an input module spec, but
+ * we will have one in the future. This module needs then to be
+ * adapted.
+ *
+ * Copyright 2007 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.
+ */
+
+#ifndef IMINTERNAL_H_INCLUDED
+#define IMINTERNAL_H_INCLUDED
+#include "template.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 iminternal_s { /* config file sysline parse entry */
+ int pri;
+ msg_t *pMsg; /* the message (in all its glory) */
+ int flags;
+};
+typedef struct iminternal_s iminternal_t;
+
+/* prototypes */
+rsRetVal modInitIminternal(void);
+rsRetVal modExitIminternal(void);
+rsRetVal iminternalAddMsg(int pri, msg_t *pMsg, int flags);
+rsRetVal iminternalHaveMsgReady(int* pbHaveOne);
+rsRetVal iminternalRemoveMsg(int *pPri, msg_t **ppMsg, int *pFlags);
+
+#endif /* #ifndef IMINTERNAL_H_INCLUDED */
diff --git a/tools/omdiscard.c b/tools/omdiscard.c
new file mode 100644
index 00000000..f13144e8
--- /dev/null
+++ b/tools/omdiscard.c
@@ -0,0 +1,121 @@
+/* omdiscard.c
+ * This is the implementation of the built-in discard output module.
+ *
+ * NOTE: read comments in module-template.h to understand how this file
+ * works!
+ *
+ * File begun on 2007-07-24 by RGerhards
+ *
+ * Copyright 2007 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 <assert.h>
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "omdiscard.h"
+#include "module-template.h"
+
+MODULE_TYPE_OUTPUT
+
+/* internal structures
+ */
+DEF_OMOD_STATIC_DATA
+
+typedef struct _instanceData {
+} instanceData;
+
+/* we do not need a createInstance()!
+BEGINcreateInstance
+CODESTARTcreateInstance
+ENDcreateInstance
+*/
+
+
+BEGINdbgPrintInstInfo
+CODESTARTdbgPrintInstInfo
+ /* do nothing */
+ENDdbgPrintInstInfo
+
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ /* we are not compatible with repeated msg reduction feature, so do not allow it */
+ENDisCompatibleWithFeature
+
+
+BEGINtryResume
+CODESTARTtryResume
+ENDtryResume
+
+BEGINdoAction
+CODESTARTdoAction
+ dbgprintf("\n");
+ iRet = RS_RET_DISCARDMSG;
+ENDdoAction
+
+
+BEGINfreeInstance
+CODESTARTfreeInstance
+ /* we do not have instance data, so we do not need to
+ * do anything here. -- rgerhards, 2007-07-25
+ */
+ENDfreeInstance
+
+
+BEGINparseSelectorAct
+CODESTARTparseSelectorAct
+CODE_STD_STRING_REQUESTparseSelectorAct(0)
+ pData = NULL; /* this action does not have any instance data */
+ p = *pp;
+
+ if(*p == '~') {
+ /* TODO: check the rest of the selector line - error reporting */
+ dbgprintf("discard\n");
+ } else {
+ iRet = RS_RET_CONFLINE_UNPROCESSED;
+ }
+CODE_STD_FINALIZERparseSelectorAct
+ENDparseSelectorAct
+
+
+BEGINmodExit
+CODESTARTmodExit
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_OMOD_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit(Discard)
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ENDmodInit
+/*
+ * vi:set ai:
+ */
diff --git a/tools/omdiscard.h b/tools/omdiscard.h
new file mode 100644
index 00000000..116308a4
--- /dev/null
+++ b/tools/omdiscard.h
@@ -0,0 +1,34 @@
+/* omdiscard.h
+ * These are the definitions for the built-in discard output module.
+ *
+ * File begun on 2007-07-24 by RGerhards
+ *
+ * Copyright 2007 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.
+ */
+#ifndef OMDISCARD_H_INCLUDED
+#define OMDISCARD_H_INCLUDED 1
+
+/* prototypes */
+rsRetVal modInitDiscard(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*);
+
+#endif /* #ifndef OMDISCARD_H_INCLUDED */
+/*
+ * vi:set ai:
+ */
diff --git a/tools/omfile.c b/tools/omfile.c
new file mode 100644
index 00000000..06875fe4
--- /dev/null
+++ b/tools/omfile.c
@@ -0,0 +1,844 @@
+/* omfile.c
+ * This is the implementation of the build-in file output module.
+ *
+ * Handles: eTypeCONSOLE, eTypeTTY, eTypeFILE, eTypePIPE
+ *
+ * NOTE: read comments in module-template.h to understand how this file
+ * works!
+ *
+ * File begun on 2007-07-21 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, 2008 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 <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#include <sys/file.h>
+
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "srUtils.h"
+#include "template.h"
+#include "outchannel.h"
+#include "omfile.h"
+#include "cfsysline.h"
+#include "module-template.h"
+#include "errmsg.h"
+
+MODULE_TYPE_OUTPUT
+
+/* internal structures
+ */
+DEF_OMOD_STATIC_DATA
+DEFobjCurrIf(errmsg)
+
+/* The following structure is a dynafile name cache entry.
+ */
+struct s_dynaFileCacheEntry {
+ uchar *pName; /* name currently open, if dynamic name */
+ short fd; /* name associated with file name in cache */
+ time_t lastUsed; /* for LRU - last access */
+};
+typedef struct s_dynaFileCacheEntry dynaFileCacheEntry;
+
+
+/* globals for default values */
+static int iDynaFileCacheSize = 10; /* max cache for dynamic files */
+static int fCreateMode = 0644; /* mode to use when creating files */
+static int fDirCreateMode = 0644; /* mode to use when creating files */
+static int bFailOnChown; /* fail if chown fails? */
+static uid_t fileUID; /* UID to be used for newly created files */
+static uid_t fileGID; /* GID to be used for newly created files */
+static uid_t dirUID; /* UID to be used for newly created directories */
+static uid_t dirGID; /* GID to be used for newly created directories */
+static int bCreateDirs; /* auto-create directories for dynaFiles: 0 - no, 1 - yes */
+static int bEnableSync = 0;/* enable syncing of files (no dash in front of pathname in conf): 0 - no, 1 - yes */
+static uchar *pszTplName = NULL; /* name of the default template to use */
+/* end globals for default values */
+
+
+typedef struct _instanceData {
+ uchar f_fname[MAXFNAME];/* file or template name (display only) */
+ short fd; /* file descriptor for (current) file */
+ enum {
+ eTypeFILE,
+ eTypeTTY,
+ eTypeCONSOLE,
+ eTypePIPE
+ } fileType;
+ char bDynamicName; /* 0 - static name, 1 - dynamic name (with properties) */
+ int fCreateMode; /* file creation mode for open() */
+ int fDirCreateMode; /* creation mode for mkdir() */
+ int bCreateDirs; /* auto-create directories? */
+ int bSyncFile; /* should the file by sync()'ed? 1- yes, 0- no */
+ uid_t fileUID; /* IDs for creation */
+ uid_t dirUID;
+ gid_t fileGID;
+ gid_t dirGID;
+ int bFailOnChown; /* fail creation if chown fails? */
+ int iCurrElt; /* currently active cache element (-1 = none) */
+ int iCurrCacheSize; /* currently cache size (1-based) */
+ int iDynaFileCacheSize; /* size of file handle cache */
+ /* The cache is implemented as an array. An empty element is indicated
+ * by a NULL pointer. Memory is allocated as needed. The following
+ * pointer points to the overall structure.
+ */
+ dynaFileCacheEntry **dynCache;
+ off_t f_sizeLimit; /* file size limit, 0 = no limit */
+ char *f_sizeLimitCmd; /* command to carry out when size limit is reached */
+} instanceData;
+
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ if(eFeat == sFEATURERepeatedMsgReduction)
+ iRet = RS_RET_OK;
+ENDisCompatibleWithFeature
+
+
+BEGINdbgPrintInstInfo
+CODESTARTdbgPrintInstInfo
+ if(pData->bDynamicName) {
+ printf("[dynamic]\n\ttemplate='%s'"
+ "\tfile cache size=%d\n"
+ "\tcreate directories: %s\n"
+ "\tfile owner %d, group %d\n"
+ "\tdirectory owner %d, group %d\n"
+ "\tfail if owner/group can not be set: %s\n",
+ pData->f_fname,
+ pData->iDynaFileCacheSize,
+ pData->bCreateDirs ? "yes" : "no",
+ pData->fileUID, pData->fileGID,
+ pData->dirUID, pData->dirGID,
+ pData->bFailOnChown ? "yes" : "no"
+ );
+ } else { /* regular file */
+ printf("%s", pData->f_fname);
+ if (pData->fd == -1)
+ printf(" (unused)");
+ }
+ENDdbgPrintInstInfo
+
+
+/* set the dynaFile cache size. Does some limit checking.
+ * rgerhards, 2007-07-31
+ */
+rsRetVal setDynaFileCacheSize(void __attribute__((unused)) *pVal, int iNewVal)
+{
+ DEFiRet;
+ uchar errMsg[128]; /* for dynamic error messages */
+
+ if(iNewVal < 1) {
+ snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar),
+ "DynaFileCacheSize must be greater 0 (%d given), changed to 1.", iNewVal);
+ errno = 0;
+ errmsg.LogError(0, RS_RET_VAL_OUT_OF_RANGE, "%s", errMsg);
+ iRet = RS_RET_VAL_OUT_OF_RANGE;
+ iNewVal = 1;
+ } else if(iNewVal > 10000) {
+ snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar),
+ "DynaFileCacheSize maximum is 10,000 (%d given), changed to 10,000.", iNewVal);
+ errno = 0;
+ errmsg.LogError(0, RS_RET_VAL_OUT_OF_RANGE, "%s", errMsg);
+ iRet = RS_RET_VAL_OUT_OF_RANGE;
+ iNewVal = 10000;
+ }
+
+ iDynaFileCacheSize = iNewVal;
+ dbgprintf("DynaFileCacheSize changed to %d.\n", iNewVal);
+
+ RETiRet;
+}
+
+
+/* Helper to cfline(). Parses a output channel name up until the first
+ * comma and then looks for the template specifier. Tries
+ * to find that template. Maps the output channel to the
+ * proper filed structure settings. Everything is stored in the
+ * filed struct. Over time, the dependency on filed might be
+ * removed.
+ * rgerhards 2005-06-21
+ */
+static rsRetVal cflineParseOutchannel(instanceData *pData, uchar* p, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts)
+{
+ DEFiRet;
+ size_t i;
+ struct outchannel *pOch;
+ char szBuf[128]; /* should be more than sufficient */
+
+ /* this must always be a file, because we can not set a size limit
+ * on a pipe...
+ * rgerhards 2005-06-21: later, this will be a separate type, but let's
+ * emulate things for the time being. When everything runs, we can
+ * extend it...
+ */
+ pData->fileType = eTypeFILE;
+
+ ++p; /* skip '$' */
+ i = 0;
+ /* get outchannel name */
+ while(*p && *p != ';' && *p != ' ' &&
+ i < sizeof(szBuf) / sizeof(char)) {
+ szBuf[i++] = *p++;
+ }
+ szBuf[i] = '\0';
+
+ /* got the name, now look up the channel... */
+ pOch = ochFind(szBuf, i);
+
+ if(pOch == NULL) {
+ char errMsg[128];
+ errno = 0;
+ snprintf(errMsg, sizeof(errMsg)/sizeof(char),
+ "outchannel '%s' not found - ignoring action line",
+ szBuf);
+ errmsg.LogError(0, RS_RET_NOT_FOUND, "%s", errMsg);
+ ABORT_FINALIZE(RS_RET_NOT_FOUND);
+ }
+
+ /* check if there is a file name in the outchannel... */
+ if(pOch->pszFileTemplate == NULL) {
+ char errMsg[128];
+ errno = 0;
+ snprintf(errMsg, sizeof(errMsg)/sizeof(char),
+ "outchannel '%s' has no file name template - ignoring action line",
+ szBuf);
+ errmsg.LogError(0, RS_RET_ERR, "%s", errMsg);
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ /* OK, we finally got a correct template. So let's use it... */
+ strncpy((char*) pData->f_fname, (char*) pOch->pszFileTemplate, MAXFNAME);
+ pData->f_sizeLimit = pOch->uSizeLimit;
+ /* WARNING: It is dangerous "just" to pass the pointer. As we
+ * never rebuild the output channel description, this is acceptable here.
+ */
+ pData->f_sizeLimitCmd = (char*) pOch->cmdOnSizeLimit;
+
+RUNLOG_VAR("%p", pszTplName);
+ iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts,
+ (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* rgerhards 2005-06-21: Try to resolve a size limit
+ * situation. This first runs the command, and then
+ * checks if we are still above the treshold.
+ * returns 0 if ok, 1 otherwise
+ * TODO: consider moving the initial check in here, too
+ */
+int resolveFileSizeLimit(instanceData *pData)
+{
+ uchar *pParams;
+ uchar *pCmd;
+ uchar *p;
+ off_t actualFileSize;
+ ASSERT(pData != NULL);
+
+ if(pData->f_sizeLimitCmd == NULL)
+ return 1; /* nothing we can do in this case... */
+
+ /* 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
+ */
+ /* 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.
+ */
+ if((pCmd = (uchar*)strdup((char*)pData->f_sizeLimitCmd)) == NULL) {
+ /* there is not much we can do - we make syslogd close the file in this case */
+ return 1;
+ }
+
+ for(p = pCmd ; *p && *p != ' ' ; ++p) {
+ /* JUST SKIP */
+ }
+
+ if(*p == ' ') {
+ *p = '\0'; /* pretend string-end */
+ pParams = p+1;
+ } else
+ pParams = NULL;
+
+ execProg(pCmd, 1, pParams);
+
+ pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
+ pData->fCreateMode);
+
+ actualFileSize = lseek(pData->fd, 0, SEEK_END);
+ if(actualFileSize >= pData->f_sizeLimit) {
+ /* OK, it didn't work out... */
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* This function deletes an entry from the dynamic file name
+ * cache. A pointer to the cache must be passed in as well
+ * as the index of the to-be-deleted entry. This index may
+ * point to an unallocated entry, in whcih case the
+ * function immediately returns. Parameter bFreeEntry is 1
+ * if the entry should be d_free()ed and 0 if not.
+ */
+static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry)
+{
+ ASSERT(pCache != NULL);
+
+ BEGINfunc;
+
+ if(pCache[iEntry] == NULL)
+ FINALIZE;
+
+ dbgprintf("Removed entry %d for file '%s' from dynaCache.\n", iEntry,
+ pCache[iEntry]->pName == NULL ? "[OPEN FAILED]" : (char*)pCache[iEntry]->pName);
+ /* if the name is NULL, this is an improperly initilized entry which
+ * needs to be discarded. In this case, neither the file is to be closed
+ * not the name to be freed.
+ */
+ if(pCache[iEntry]->pName != NULL) {
+ close(pCache[iEntry]->fd);
+ d_free(pCache[iEntry]->pName);
+ pCache[iEntry]->pName = NULL;
+ }
+
+ if(bFreeEntry) {
+ d_free(pCache[iEntry]);
+ pCache[iEntry] = NULL;
+ }
+
+finalize_it:
+ ENDfunc;
+}
+
+
+/* This function frees the dynamic file name cache.
+ */
+static void dynaFileFreeCache(instanceData *pData)
+{
+ register int i;
+ ASSERT(pData != NULL);
+
+ BEGINfunc;
+ for(i = 0 ; i < pData->iCurrCacheSize ; ++i) {
+ dynaFileDelCacheEntry(pData->dynCache, i, 1);
+ }
+
+ if(pData->dynCache != NULL)
+ d_free(pData->dynCache);
+ ENDfunc;
+}
+
+
+/* This is a shared code for both static and dynamic files.
+ */
+static void prepareFile(instanceData *pData, uchar *newFileName)
+{
+ if(access((char*)newFileName, F_OK) == 0) {
+ /* file already exists */
+ pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
+ pData->fCreateMode);
+ } else {
+ pData->fd = -1;
+ /* file does not exist, create it (and eventually parent directories */
+ if(pData->bCreateDirs) {
+ /* we fist need to create parent dirs if they are missing
+ * We do not report any errors here ourselfs but let the code
+ * fall through to error handler below.
+ */
+ if(makeFileParentDirs(newFileName, strlen((char*)newFileName),
+ pData->fDirCreateMode, pData->dirUID,
+ pData->dirGID, pData->bFailOnChown) == 0) {
+ pData->fd = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
+ pData->fCreateMode);
+ if(pData->fd != -1) {
+ /* check and set uid/gid */
+ if(pData->fileUID != (uid_t)-1 || pData->fileGID != (gid_t) -1) {
+ /* we need to set owner/group */
+ if(fchown(pData->fd, pData->fileUID,
+ pData->fileGID) != 0) {
+ if(pData->bFailOnChown) {
+ int eSave = errno;
+ close(pData->fd);
+ pData->fd = -1;
+ errno = eSave;
+ }
+ /* we will silently ignore the chown() failure
+ * if configured to do so.
+ */
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+/* This function handles dynamic file names. It checks if the
+ * requested file name is already open and, if not, does everything
+ * needed to switch to the it.
+ * Function returns 0 if all went well and non-zero otherwise.
+ * On successful return pData->fd must point to the correct file to
+ * be written.
+ * This is a helper to writeFile(). rgerhards, 2007-07-03
+ */
+static int prepareDynFile(instanceData *pData, uchar *newFileName, unsigned iMsgOpts)
+{
+ time_t ttOldest; /* timestamp of oldest element */
+ int iOldest;
+ int i;
+ int iFirstFree;
+ dynaFileCacheEntry **pCache;
+
+ ASSERT(pData != NULL);
+ ASSERT(newFileName != NULL);
+
+ pCache = pData->dynCache;
+
+ /* first check, if we still have the current file
+ * I *hope* this will be a performance enhancement.
+ */
+ if( (pData->iCurrElt != -1)
+ && !strcmp((char*) newFileName, (char*) pCache[pData->iCurrElt]->pName)) {
+ /* great, we are all set */
+ pCache[pData->iCurrElt]->lastUsed = time(NULL); /* update timestamp for LRU */
+ return 0;
+ }
+
+ /* ok, no luck. Now let's search the table if we find a matching spot.
+ * While doing so, we also prepare for creation of a new one.
+ */
+ iFirstFree = -1; /* not yet found */
+ iOldest = 0; /* we assume the first element to be the oldest - that will change as we loop */
+ ttOldest = time(NULL) + 1; /* there must always be an older one */
+ for(i = 0 ; i < pData->iCurrCacheSize ; ++i) {
+ if(pCache[i] == NULL) {
+ if(iFirstFree == -1)
+ iFirstFree = i;
+ } else { /* got an element, let's see if it matches */
+ if(!strcmp((char*) newFileName, (char*) pCache[i]->pName)) {
+ /* we found our element! */
+ pData->fd = pCache[i]->fd;
+ pData->iCurrElt = i;
+ pCache[i]->lastUsed = time(NULL); /* update timestamp for LRU */
+ return 0;
+ }
+ /* did not find it - so lets keep track of the counters for LRU */
+ if(pCache[i]->lastUsed < ttOldest) {
+ ttOldest = pCache[i]->lastUsed;
+ iOldest = i;
+ }
+ }
+ }
+
+ /* we have not found an entry */
+ if(iFirstFree == -1 && (pData->iCurrCacheSize < pData->iDynaFileCacheSize)) {
+ /* there is space left, so set it to that index */
+ iFirstFree = pData->iCurrCacheSize++;
+ }
+
+ if(iFirstFree == -1) {
+ dynaFileDelCacheEntry(pCache, iOldest, 0);
+ iFirstFree = iOldest; /* this one *is* now free ;) */
+ } else {
+ /* we need to allocate memory for the cache structure */
+ pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry));
+ if(pCache[iFirstFree] == NULL) {
+ dbgprintf("prepareDynfile(): could not alloc mem, discarding this request\n");
+ return -1;
+ }
+ }
+
+ /* Ok, we finally can open the file */
+ prepareFile(pData, newFileName);
+
+ /* file is either open now or an error state set */
+ if(pData->fd == -1) {
+ /* do not report anything if the message is an internally-generated
+ * message. Otherwise, we could run into a never-ending loop. The bad
+ * news is that we also lose errors on startup messages, but so it is.
+ */
+ if(iMsgOpts & INTERNAL_MSG)
+ dbgprintf("Could not open dynaFile, discarding message\n");
+ else
+ errmsg.LogError(0, NO_ERRCODE, "Could not open dynamic file '%s' - discarding message", (char*)newFileName);
+ dynaFileDelCacheEntry(pCache, iFirstFree, 1);
+ pData->iCurrElt = -1;
+ return -1;
+ }
+
+ pCache[iFirstFree]->fd = pData->fd;
+ pCache[iFirstFree]->pName = (uchar*)strdup((char*)newFileName); /* TODO: check for NULL (very unlikely) */
+ pCache[iFirstFree]->lastUsed = time(NULL);
+ pData->iCurrElt = iFirstFree;
+ dbgprintf("Added new entry %d for file cache, file '%s'.\n",
+ iFirstFree, newFileName);
+
+ return 0;
+}
+
+
+/* rgerhards 2004-11-11: write to a file output. This
+ * will be called for all outputs using file semantics,
+ * for example also for pipes.
+ */
+static rsRetVal writeFile(uchar **ppString, unsigned iMsgOpts, instanceData *pData)
+{
+ off_t actualFileSize;
+ DEFiRet;
+
+ ASSERT(pData != NULL);
+
+ /* first check if we have a dynamic file name and, if so,
+ * check if it still is ok or a new file needs to be created
+ */
+ if(pData->bDynamicName) {
+ if(prepareDynFile(pData, ppString[1], iMsgOpts) != 0)
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ /* create the message based on format specified */
+again:
+ /* check if we have a file size limit and, if so,
+ * obey to it.
+ */
+ if(pData->f_sizeLimit != 0) {
+ actualFileSize = lseek(pData->fd, 0, SEEK_END);
+ if(actualFileSize >= pData->f_sizeLimit) {
+ char errMsg[256];
+ /* for now, we simply disable a file once it is
+ * beyond the maximum size. This is better than having
+ * us aborted by the OS... rgerhards 2005-06-21
+ */
+ (void) close(pData->fd);
+ /* try to resolve the situation */
+ if(resolveFileSizeLimit(pData) != 0) {
+ /* didn't work out, so disable... */
+ snprintf(errMsg, sizeof(errMsg),
+ "no longer writing to file %s; grown beyond configured file size of %lld bytes, actual size %lld - configured command did not resolve situation",
+ pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize);
+ errno = 0;
+ errmsg.LogError(0, RS_RET_DISABLE_ACTION, "%s", errMsg);
+ ABORT_FINALIZE(RS_RET_DISABLE_ACTION);
+ } else {
+ snprintf(errMsg, sizeof(errMsg),
+ "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation",
+ pData->f_fname, (long long) pData->f_sizeLimit, (long long) actualFileSize);
+ errno = 0;
+ errmsg.LogError(0, NO_ERRCODE, "%s", errMsg);
+ }
+ }
+ }
+
+ if (write(pData->fd, ppString[0], strlen((char*)ppString[0])) < 0) {
+ int e = errno;
+
+ /* If a named pipe is full, just ignore it for now
+ - mrn 24 May 96 */
+ if (pData->fileType == eTypePIPE && e == EAGAIN)
+ ABORT_FINALIZE(RS_RET_OK);
+
+ /* If the filesystem is filled up, just ignore
+ * it for now and continue writing when possible
+ * based on patch for sysklogd by Martin Schulze on 2007-05-24
+ */
+ if (pData->fileType == eTypeFILE && e == ENOSPC)
+ ABORT_FINALIZE(RS_RET_OK);
+
+ (void) close(pData->fd);
+ /*
+ * Check for EBADF on TTY's due to vhangup()
+ * Linux uses EIO instead (mrn 12 May 96)
+ */
+ if ((pData->fileType == eTypeTTY || pData->fileType == eTypeCONSOLE)
+#ifdef linux
+ && e == EIO) {
+#else
+ && e == EBADF) {
+#endif
+ pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_NOCTTY);
+ if (pData->fd < 0) {
+ iRet = RS_RET_DISABLE_ACTION;
+ errmsg.LogError(0, NO_ERRCODE, "%s", pData->f_fname);
+ } else {
+ untty();
+ goto again;
+ }
+ } else {
+ iRet = RS_RET_DISABLE_ACTION;
+ errno = e;
+ errmsg.LogError(0, NO_ERRCODE, "%s", pData->f_fname);
+ }
+ } else if (pData->bSyncFile) {
+ fsync(pData->fd);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+BEGINcreateInstance
+CODESTARTcreateInstance
+ pData->fd = -1;
+ENDcreateInstance
+
+
+BEGINfreeInstance
+CODESTARTfreeInstance
+ if(pData->bDynamicName) {
+ dynaFileFreeCache(pData);
+ } else if(pData->fd != -1)
+ close(pData->fd);
+ENDfreeInstance
+
+
+BEGINtryResume
+CODESTARTtryResume
+ENDtryResume
+
+BEGINdoAction
+CODESTARTdoAction
+ dbgprintf(" (%s)\n", pData->f_fname);
+ /* pData->fd == -1 is an indicator that the we couldn't
+ * open the file at startup. For dynaFiles, this is ok,
+ * all others are doomed.
+ */
+ if(pData->bDynamicName || (pData->fd != -1))
+ iRet = writeFile(ppString, iMsgOpts, pData);
+ENDdoAction
+
+
+BEGINparseSelectorAct
+CODESTARTparseSelectorAct
+ /* yes, the if below is redundant, but I need it now. Will go away as
+ * the code further changes. -- rgerhards, 2007-07-25
+ */
+ if(*p == '$' || *p == '?' || *p == '|' || *p == '/' || *p == '-') {
+ if((iRet = createInstance(&pData)) != RS_RET_OK) {
+ ENDfunc
+ return iRet; /* this can not use RET_iRet! */
+ }
+ } else {
+ /* this is not clean, but we need it for the time being
+ * TODO: remove when cleaning up modularization
+ */
+ ENDfunc
+ return RS_RET_CONFLINE_UNPROCESSED;
+ }
+
+ if(*p == '-') {
+ pData->bSyncFile = 0;
+ p++;
+ } else {
+ pData->bSyncFile = bEnableSync ? 1 : 0;
+ }
+
+ pData->f_sizeLimit = 0; /* default value, use outchannels to configure! */
+
+ switch (*p)
+ {
+ case '$':
+ CODE_STD_STRING_REQUESTparseSelectorAct(1)
+ /* rgerhards 2005-06-21: this is a special setting for output-channel
+ * definitions. In the long term, this setting will probably replace
+ * anything else, but for the time being we must co-exist with the
+ * traditional mode lines.
+ * rgerhards, 2007-07-24: output-channels will go away. We keep them
+ * for compatibility reasons, but seems to have been a bad idea.
+ */
+ if((iRet = cflineParseOutchannel(pData, p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS)) == RS_RET_OK) {
+ pData->bDynamicName = 0;
+ pData->fCreateMode = fCreateMode; /* preserve current setting */
+ pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */
+ pData->fd = open((char*) pData->f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY,
+ pData->fCreateMode);
+ }
+ break;
+
+ case '?': /* This is much like a regular file handle, but we need to obtain
+ * a template name. rgerhards, 2007-07-03
+ */
+ CODE_STD_STRING_REQUESTparseSelectorAct(2)
+ ++p; /* eat '?' */
+ if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS,
+ (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName))
+ != RS_RET_OK)
+ break;
+ /* "filename" is actually a template name, we need this as string 1. So let's add it
+ * to the pOMSR. -- rgerhards, 2007-07-27
+ */
+ if((iRet = OMSRsetEntry(*ppOMSR, 1, (uchar*)strdup((char*) pData->f_fname), OMSR_NO_RQD_TPL_OPTS)) != RS_RET_OK)
+ break;
+
+ pData->bDynamicName = 1;
+ pData->iCurrElt = -1; /* no current element */
+ pData->fCreateMode = fCreateMode; /* freeze current setting */
+ pData->fDirCreateMode = fDirCreateMode; /* preserve current setting */
+ pData->bCreateDirs = bCreateDirs;
+ pData->bFailOnChown = bFailOnChown;
+ pData->fileUID = fileUID;
+ pData->fileGID = fileGID;
+ pData->dirUID = dirUID;
+ pData->dirGID = dirGID;
+ pData->iDynaFileCacheSize = iDynaFileCacheSize; /* freeze current setting */
+ /* we now allocate the cache table. We use calloc() intentionally, as we
+ * need all pointers to be initialized to NULL pointers.
+ */
+ if((pData->dynCache = (dynaFileCacheEntry**)
+ calloc(iDynaFileCacheSize, sizeof(dynaFileCacheEntry*))) == NULL) {
+ iRet = RS_RET_OUT_OF_MEMORY;
+ dbgprintf("Could not allocate memory for dynaFileCache - selector disabled.\n");
+ }
+ break;
+
+ case '|':
+ case '/':
+ CODE_STD_STRING_REQUESTparseSelectorAct(1)
+ /* rgerhards, 2007-0726: first check if file or pipe */
+ if(*p == '|') {
+ pData->fileType = eTypePIPE;
+ ++p;
+ } else {
+ pData->fileType = eTypeFILE;
+ }
+ /* rgerhards 2004-11-17: from now, we need to have different
+ * processing, because after the first comma, the template name
+ * to use is specified. So we need to scan for the first coma first
+ * and then look at the rest of the line.
+ */
+ if((iRet = cflineParseFileName(p, (uchar*) pData->f_fname, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS,
+ (pszTplName == NULL) ? (uchar*)"RSYSLOG_FileFormat" : pszTplName))
+ != RS_RET_OK)
+ break;
+
+ pData->bDynamicName = 0;
+ pData->fCreateMode = fCreateMode; /* preserve current setting */
+ pData->fDirCreateMode = fDirCreateMode;
+ pData->bCreateDirs = bCreateDirs;
+ pData->bFailOnChown = bFailOnChown;
+ pData->fileUID = fileUID;
+ pData->fileGID = fileGID;
+ pData->dirUID = dirUID;
+ pData->dirGID = dirGID;
+
+ if(pData->fileType == eTypePIPE) {
+ pData->fd = open((char*) pData->f_fname, O_RDWR|O_NONBLOCK);
+ } else {
+ prepareFile(pData, pData->f_fname);
+ }
+
+ if ( pData->fd < 0 ){
+ pData->fd = -1;
+ dbgprintf("Error opening log file: %s\n", pData->f_fname);
+ errmsg.LogError(0, NO_ERRCODE, "%s", pData->f_fname);
+ break;
+ }
+ if (isatty(pData->fd)) {
+ pData->fileType = eTypeTTY;
+ untty();
+ }
+ if (strcmp((char*) p, _PATH_CONSOLE) == 0)
+ pData->fileType = eTypeCONSOLE;
+ break;
+ default:
+ iRet = RS_RET_CONFLINE_UNPROCESSED;
+ break;
+ }
+CODE_STD_FINALIZERparseSelectorAct
+ENDparseSelectorAct
+
+
+/* Reset config variables for this module to default values.
+ * rgerhards, 2007-07-17
+ */
+static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ fileUID = -1;
+ fileGID = -1;
+ dirUID = -1;
+ dirGID = -1;
+ bFailOnChown = 1;
+ iDynaFileCacheSize = 10;
+ fCreateMode = 0644;
+ fDirCreateMode = 0644;
+ bCreateDirs = 1;
+ bEnableSync = 0;
+ if(pszTplName != NULL) {
+ free(pszTplName);
+ pszTplName = NULL;
+ }
+
+ return RS_RET_OK;
+}
+
+
+BEGINmodExit
+CODESTARTmodExit
+ if(pszTplName != NULL)
+ free(pszTplName);
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_OMOD_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit(File)
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"dynafilecachesize", 0, eCmdHdlrInt, (void*) setDynaFileCacheSize, NULL, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirowner", 0, eCmdHdlrUID, NULL, &dirUID, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"dirgroup", 0, eCmdHdlrGID, NULL, &dirGID, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"fileowner", 0, eCmdHdlrUID, NULL, &fileUID, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"filegroup", 0, eCmdHdlrGID, NULL, &fileGID, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"dircreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fDirCreateMode, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"filecreatemode", 0, eCmdHdlrFileCreateMode, NULL, &fCreateMode, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"createdirs", 0, eCmdHdlrBinary, NULL, &bCreateDirs, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"failonchownfailure", 0, eCmdHdlrBinary, NULL, &bFailOnChown, STD_LOADABLE_MODULE_ID));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"actionfileenablesync", 0, eCmdHdlrBinary, NULL, &bEnableSync, STD_LOADABLE_MODULE_ID));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionfiledefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
+ENDmodInit
+/* vi:set ai:
+ */
diff --git a/tools/omfile.h b/tools/omfile.h
new file mode 100644
index 00000000..03e081f3
--- /dev/null
+++ b/tools/omfile.h
@@ -0,0 +1,34 @@
+/* omfile.h
+ * These are the definitions for the build-in file output module.
+ *
+ * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007 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.
+ */
+#ifndef OMFILE_H_INCLUDED
+#define OMFILE_H_INCLUDED 1
+
+/* prototypes */
+rsRetVal modInitFile(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*);
+
+#endif /* #ifndef OMFILE_H_INCLUDED */
+/*
+ * vi:set ai:
+ */
diff --git a/tools/omfwd.c b/tools/omfwd.c
new file mode 100644
index 00000000..30761a87
--- /dev/null
+++ b/tools/omfwd.c
@@ -0,0 +1,704 @@
+/* omfwd.c
+ * This is the implementation of the build-in forwarding output module.
+ *
+ * NOTE: read comments in module-template.h to understand how this file
+ * works!
+ *
+ * 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" 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 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 <fnmatch.h>
+#include <assert.h>
+#include <errno.h>
+#include <ctype.h>
+#include <unistd.h>
+#ifdef USE_NETZIP
+#include <zlib.h>
+#endif
+#include <pthread.h>
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "srUtils.h"
+#include "net.h"
+#include "netstrms.h"
+#include "netstrm.h"
+#include "omfwd.h"
+#include "template.h"
+#include "msg.h"
+#include "tcpclt.h"
+#include "cfsysline.h"
+#include "module-template.h"
+#include "glbl.h"
+#include "errmsg.h"
+
+MODULE_TYPE_OUTPUT
+
+/* internal structures
+ */
+DEF_OMOD_STATIC_DATA
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(net)
+DEFobjCurrIf(netstrms)
+DEFobjCurrIf(netstrm)
+DEFobjCurrIf(tcpclt)
+
+typedef struct _instanceData {
+ netstrms_t *pNS; /* netstream subsystem */
+ netstrm_t *pNetstrm; /* our output netstream */
+ uchar *pszStrmDrvr;
+ uchar *pszStrmDrvrAuthMode;
+ permittedPeers_t *pPermPeers;
+ int iStrmDrvrMode;
+ char *f_hname;
+ int *pSockArray; /* sockets to use for UDP */
+ int bIsConnected; /* are we connected to remote host? 0 - no, 1 - yes, UDP means addr resolved */
+ struct addrinfo *f_addr;
+ int compressionLevel; /* 0 - no compression, else level for zlib */
+ char *port;
+ int protocol;
+# define FORW_UDP 0
+# define FORW_TCP 1
+ /* following fields for TCP-based delivery */
+ tcpclt_t *pTCPClt; /* our tcpclt object */
+} instanceData;
+
+/* config data */
+static uchar *pszTplName = NULL; /* name of the default template to use */
+static uchar *pszStrmDrvr = NULL; /* name of the stream driver to use */
+static short iStrmDrvrMode = 0; /* mode for stream driver, driver-dependent (0 mostly means plain tcp) */
+static short bResendLastOnRecon = 0; /* should the last message be re-sent on a successful reconnect? */
+static uchar *pszStrmDrvrAuthMode = NULL; /* authentication mode to use */
+
+static permittedPeers_t *pPermPeers = NULL;
+
+/* get the syslog forward port from selector_t. The passed in
+ * struct must be one that is setup for forwarding.
+ * rgerhards, 2007-06-28
+ * We may change the implementation to try to lookup the port
+ * if it is unspecified. So far, we use the IANA default auf 514.
+ */
+static char *getFwdPt(instanceData *pData)
+{
+ assert(pData != NULL);
+ if(pData->port == NULL)
+ return("514");
+ else
+ return(pData->port);
+}
+
+
+/* destruct the TCP helper objects
+ * This, for example, is needed after something went wrong.
+ * This function is void because it "can not" fail.
+ * rgerhards, 2008-06-04
+ */
+static inline void
+DestructTCPInstanceData(instanceData *pData)
+{
+ assert(pData != NULL);
+ if(pData->pNetstrm != NULL)
+ netstrm.Destruct(&pData->pNetstrm);
+ if(pData->pNS != NULL)
+ netstrms.Destruct(&pData->pNS);
+}
+
+BEGINcreateInstance
+CODESTARTcreateInstance
+ENDcreateInstance
+
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ if(eFeat == sFEATURERepeatedMsgReduction)
+ iRet = RS_RET_OK;
+ENDisCompatibleWithFeature
+
+
+BEGINfreeInstance
+CODESTARTfreeInstance
+ if(pData->f_addr != NULL) { /* TODO: is the check ok? */
+ freeaddrinfo(pData->f_addr);
+ pData->f_addr = NULL;
+ }
+ if(pData->port != NULL)
+ free(pData->port);
+
+ /* final cleanup */
+ DestructTCPInstanceData(pData);
+ if(pData->pSockArray != NULL)
+ net.closeUDPListenSockets(pData->pSockArray);
+
+ if(pData->protocol == FORW_TCP) {
+ tcpclt.Destruct(&pData->pTCPClt);
+ }
+
+ if(pData->f_hname != NULL)
+ free(pData->f_hname);
+ if(pData->pszStrmDrvr != NULL)
+ free(pData->pszStrmDrvr);
+ if(pData->pszStrmDrvrAuthMode != NULL)
+ free(pData->pszStrmDrvrAuthMode);
+ if(pData->pPermPeers != NULL)
+ net.DestructPermittedPeers(&pData->pPermPeers);
+ENDfreeInstance
+
+
+BEGINdbgPrintInstInfo
+CODESTARTdbgPrintInstInfo
+ printf("%s", pData->f_hname);
+ENDdbgPrintInstInfo
+
+
+/* Send a message via UDP
+ * rgehards, 2007-12-20
+ */
+static rsRetVal UDPSend(instanceData *pData, char *msg, size_t len)
+{
+ DEFiRet;
+ struct addrinfo *r;
+ int i;
+ unsigned lsent = 0;
+ int bSendSuccess;
+
+ if(pData->pSockArray != NULL) {
+ /* we need to track if we have success sending to the remote
+ * peer. Success is indicated by at least one sendto() call
+ * succeeding. We track this be bSendSuccess. We can not simply
+ * rely on lsent, as a call might initially work, but a later
+ * call fails. Then, lsent has the error status, even though
+ * the sendto() succeeded. -- rgerhards, 2007-06-22
+ */
+ bSendSuccess = FALSE;
+ for (r = pData->f_addr; r; r = r->ai_next) {
+ for (i = 0; i < *pData->pSockArray; i++) {
+ lsent = sendto(pData->pSockArray[i+1], msg, len, 0, r->ai_addr, r->ai_addrlen);
+ if (lsent == len) {
+ bSendSuccess = TRUE;
+ break;
+ } else {
+ int eno = errno;
+ char errStr[1024];
+ dbgprintf("sendto() error: %d = %s.\n",
+ eno, rs_strerror_r(eno, errStr, sizeof(errStr)));
+ }
+ }
+ if (lsent == len && !send_to_all)
+ break;
+ }
+ /* finished looping */
+ if (bSendSuccess == FALSE) {
+ dbgprintf("error forwarding via udp, suspending\n");
+ iRet = RS_RET_SUSPENDED;
+ }
+ }
+
+ RETiRet;
+}
+
+
+/* set the permitted peers -- rgerhards, 2008-05-19
+ */
+static rsRetVal
+setPermittedPeer(void __attribute__((unused)) *pVal, uchar *pszID)
+{
+ DEFiRet;
+ CHKiRet(net.AddPermittedPeer(&pPermPeers, pszID));
+ free(pszID); /* no longer needed, but we must free it as of interface def */
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* CODE FOR SENDING TCP MESSAGES */
+
+
+/* Send a frame via plain TCP protocol
+ * rgerhards, 2007-12-28
+ */
+static rsRetVal TCPSendFrame(void *pvData, char *msg, size_t len)
+{
+ DEFiRet;
+ ssize_t lenSend;
+ instanceData *pData = (instanceData *) pvData;
+
+ lenSend = len;
+ netstrm.CheckConnection(pData->pNetstrm); /* hack for plain tcp syslog - see ptcp driver for details */
+ CHKiRet(netstrm.Send(pData->pNetstrm, (uchar*)msg, &lenSend));
+ dbgprintf("TCP sent %ld bytes, requested %ld\n", (long) lenSend, (long) len);
+
+ if(lenSend != (ssize_t) len) {
+ /* no real error, could "just" not send everything...
+ * For the time being, we ignore this...
+ * rgerhards, 2005-10-25
+ */
+ dbgprintf("message not completely (tcp)send, ignoring %ld\n", (long) lenSend);
+ usleep(1000); /* experimental - might be benefitial in this situation */
+ /* TODO: we need to revisit this code -- rgerhards, 2007-12-28 */
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This function is called immediately before a send retry is attempted.
+ * It shall clean up whatever makes sense.
+ * rgerhards, 2007-12-28
+ */
+static rsRetVal TCPSendPrepRetry(void *pvData)
+{
+ DEFiRet;
+ instanceData *pData = (instanceData *) pvData;
+
+ assert(pData != NULL);
+ DestructTCPInstanceData(pData);
+ RETiRet;
+}
+
+
+/* initializes everything so that TCPSend can work.
+ * rgerhards, 2007-12-28
+ */
+static rsRetVal TCPSendInit(void *pvData)
+{
+ DEFiRet;
+ instanceData *pData = (instanceData *) pvData;
+
+ assert(pData != NULL);
+ if(pData->pNetstrm == NULL) {
+ CHKiRet(netstrms.Construct(&pData->pNS));
+ /* the stream driver must be set before the object is finalized! */
+ CHKiRet(netstrms.SetDrvrName(pData->pNS, pszStrmDrvr));
+ CHKiRet(netstrms.ConstructFinalize(pData->pNS));
+
+ /* now create the actual stream and connect to the server */
+ CHKiRet(netstrms.CreateStrm(pData->pNS, &pData->pNetstrm));
+ CHKiRet(netstrm.ConstructFinalize(pData->pNetstrm));
+ CHKiRet(netstrm.SetDrvrMode(pData->pNetstrm, pData->iStrmDrvrMode));
+ /* now set optional params, but only if they were actually configured */
+ if(pData->pszStrmDrvrAuthMode != NULL) {
+ CHKiRet(netstrm.SetDrvrAuthMode(pData->pNetstrm, pData->pszStrmDrvrAuthMode));
+ }
+ if(pData->pPermPeers != NULL) {
+ CHKiRet(netstrm.SetDrvrPermPeers(pData->pNetstrm, pData->pPermPeers));
+ }
+ /* params set, now connect */
+ CHKiRet(netstrm.Connect(pData->pNetstrm, glbl.GetDefPFFamily(),
+ (uchar*)getFwdPt(pData), (uchar*)pData->f_hname));
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ DestructTCPInstanceData(pData);
+ }
+
+ RETiRet;
+}
+
+
+/* try to resume connection if it is not ready
+ * rgerhards, 2007-08-02
+ */
+static rsRetVal doTryResume(instanceData *pData)
+{
+ int iErr;
+ struct addrinfo *res;
+ struct addrinfo hints;
+ DEFiRet;
+
+ if(pData->bIsConnected)
+ FINALIZE;
+
+ /* The remote address is not yet known and needs to be obtained */
+ dbgprintf(" %s\n", pData->f_hname);
+ if(pData->protocol == FORW_UDP) {
+ memset(&hints, 0, sizeof(hints));
+ /* port must be numeric, because config file syntax requires this */
+ hints.ai_flags = AI_NUMERICSERV;
+ hints.ai_family = glbl.GetDefPFFamily();
+ hints.ai_socktype = SOCK_DGRAM;
+ if((iErr = (getaddrinfo(pData->f_hname, getFwdPt(pData), &hints, &res))) != 0) {
+ dbgprintf("could not get addrinfo for hostname '%s':'%s': %d%s\n",
+ pData->f_hname, getFwdPt(pData), iErr, gai_strerror(iErr));
+ ABORT_FINALIZE(RS_RET_SUSPENDED);
+ }
+ dbgprintf("%s found, resuming.\n", pData->f_hname);
+ pData->f_addr = res;
+ pData->bIsConnected = 1;
+ if(pData->pSockArray == NULL) {
+ pData->pSockArray = net.create_udp_socket((uchar*)pData->f_hname, NULL, 0);
+ }
+ } else {
+ CHKiRet(TCPSendInit((void*)pData));
+ }
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pData->f_addr != NULL) {
+ freeaddrinfo(pData->f_addr);
+ pData->f_addr = NULL;
+ }
+ iRet = RS_RET_SUSPENDED;
+ }
+
+ RETiRet;
+}
+
+
+BEGINtryResume
+CODESTARTtryResume
+ iRet = doTryResume(pData);
+ENDtryResume
+
+BEGINdoAction
+ char *psz; /* temporary buffering */
+ register unsigned l;
+CODESTARTdoAction
+ CHKiRet(doTryResume(pData));
+
+ dbgprintf(" %s:%s/%s\n", pData->f_hname, getFwdPt(pData),
+ pData->protocol == FORW_UDP ? "udp" : "tcp");
+
+ psz = (char*) ppString[0];
+ l = strlen((char*) psz);
+ if (l > MAXLINE)
+ l = MAXLINE;
+
+# ifdef USE_NETZIP
+ /* Check if we should compress and, if so, do it. We also
+ * check if the message is large enough to justify compression.
+ * The smaller the message, the less likely is a gain in compression.
+ * To save CPU cycles, we do not try to compress very small messages.
+ * What "very small" means needs to be configured. Currently, it is
+ * hard-coded but this may be changed to a config parameter.
+ * rgerhards, 2006-11-30
+ */
+ if(pData->compressionLevel && (l > MIN_SIZE_FOR_COMPRESS)) {
+ Bytef out[MAXLINE+MAXLINE/100+12] = "z";
+ uLongf destLen = sizeof(out) / sizeof(Bytef);
+ uLong srcLen = l;
+ int ret;
+ ret = compress2((Bytef*) out+1, &destLen, (Bytef*) psz,
+ srcLen, pData->compressionLevel);
+ dbgprintf("Compressing message, length was %d now %d, return state %d.\n",
+ l, (int) destLen, ret);
+ if(ret != Z_OK) {
+ /* if we fail, we complain, but only in debug mode
+ * Otherwise, we are silent. In any case, we ignore the
+ * failed compression and just sent the uncompressed
+ * data, which is still valid. So this is probably the
+ * best course of action.
+ * rgerhards, 2006-11-30
+ */
+ dbgprintf("Compression failed, sending uncompressed message\n");
+ } else if(destLen+1 < l) {
+ /* only use compression if there is a gain in using it! */
+ dbgprintf("there is gain in compression, so we do it\n");
+ psz = (char*) out;
+ l = destLen + 1; /* take care for the "z" at message start! */
+ }
+ ++destLen;
+ }
+# endif
+
+ if(pData->protocol == FORW_UDP) {
+ /* forward via UDP */
+ CHKiRet(UDPSend(pData, psz, l));
+ } else {
+ /* forward via TCP */
+ rsRetVal ret;
+ ret = tcpclt.Send(pData->pTCPClt, pData, psz, l);
+ if(ret != RS_RET_OK) {
+ /* error! */
+ dbgprintf("error forwarding via tcp, suspending\n");
+ DestructTCPInstanceData(pData);
+ iRet = RS_RET_SUSPENDED;
+ }
+ }
+finalize_it:
+ENDdoAction
+
+
+/* This function loads TCP support, if not already loaded. It will be called
+ * during config processing. To server ressources, TCP support will only
+ * be loaded if it actually is used. -- rgerhard, 2008-04-17
+ */
+static rsRetVal
+loadTCPSupport(void)
+{
+ DEFiRet;
+ CHKiRet(objUse(netstrms, LM_NETSTRMS_FILENAME));
+ CHKiRet(objUse(netstrm, LM_NETSTRMS_FILENAME));
+ CHKiRet(objUse(tcpclt, LM_TCPCLT_FILENAME));
+
+finalize_it:
+ RETiRet;
+}
+
+
+BEGINparseSelectorAct
+ uchar *q;
+ int i;
+ int bErr;
+ rsRetVal localRet;
+ struct addrinfo;
+ TCPFRAMINGMODE tcp_framing = TCP_FRAMING_OCTET_STUFFING;
+CODESTARTparseSelectorAct
+CODE_STD_STRING_REQUESTparseSelectorAct(1)
+ if(*p != '@')
+ ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
+
+ CHKiRet(createInstance(&pData));
+
+ ++p; /* eat '@' */
+ if(*p == '@') { /* indicator for TCP! */
+ localRet = loadTCPSupport();
+ if(localRet != RS_RET_OK) {
+ errmsg.LogError(0, localRet, "could not activate network stream modules for TCP "
+ "(internal error %d) - are modules missing?", localRet);
+ ABORT_FINALIZE(localRet);
+ }
+ pData->protocol = FORW_TCP;
+ ++p; /* eat this '@', too */
+ } else {
+ pData->protocol = FORW_UDP;
+ }
+ /* we are now after the protocol indicator. Now check if we should
+ * use compression. We begin to use a new option format for this:
+ * @(option,option)host:port
+ * The first option defined is "z[0..9]" where the digit indicates
+ * the compression level. If it is not given, 9 (best compression) is
+ * assumed. An example action statement might be:
+ * @@(z5,o)127.0.0.1:1400
+ * Which means send via TCP with medium (5) compresion (z) to the local
+ * host on port 1400. The '0' option means that octet-couting (as in
+ * IETF I-D syslog-transport-tls) is to be used for framing (this option
+ * applies to TCP-based syslog only and is ignored when specified with UDP).
+ * That is not yet implemented.
+ * rgerhards, 2006-12-07
+ */
+ if(*p == '(') {
+ /* at this position, it *must* be an option indicator */
+ do {
+ ++p; /* eat '(' or ',' (depending on when called) */
+ /* check options */
+ if(*p == 'z') { /* compression */
+# ifdef USE_NETZIP
+ ++p; /* eat */
+ if(isdigit((int) *p)) {
+ int iLevel;
+ iLevel = *p - '0';
+ ++p; /* eat */
+ pData->compressionLevel = iLevel;
+ } else {
+ errmsg.LogError(0, NO_ERRCODE, "Invalid compression level '%c' specified in "
+ "forwardig action - NOT turning on compression.",
+ *p);
+ }
+# else
+ errmsg.LogError(0, NO_ERRCODE, "Compression requested, but rsyslogd is not compiled "
+ "with compression support - request ignored.");
+# endif /* #ifdef USE_NETZIP */
+ } else if(*p == 'o') { /* octet-couting based TCP framing? */
+ ++p; /* eat */
+ /* no further options settable */
+ tcp_framing = TCP_FRAMING_OCTET_COUNTING;
+ } else { /* invalid option! Just skip it... */
+ errmsg.LogError(0, NO_ERRCODE, "Invalid option %c in forwarding action - ignoring.", *p);
+ ++p; /* eat invalid option */
+ }
+ /* the option processing is done. We now do a generic skip
+ * to either the next option or the end of the option
+ * block.
+ */
+ while(*p && *p != ')' && *p != ',')
+ ++p; /* just skip it */
+ } while(*p && *p == ','); /* Attention: do.. while() */
+ if(*p == ')')
+ ++p; /* eat terminator, on to next */
+ else
+ /* we probably have end of string - leave it for the rest
+ * of the code to handle it (but warn the user)
+ */
+ errmsg.LogError(0, NO_ERRCODE, "Option block not terminated in forwarding action.");
+ }
+ /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0')
+ * now skip to port and then template name. rgerhards 2005-07-06
+ */
+ for(q = p ; *p && *p != ';' && *p != ':' && *p != '#' ; ++p)
+ /* JUST SKIP */;
+
+ pData->port = NULL;
+ if(*p == ':') { /* process port */
+ uchar * tmp;
+
+ *p = '\0'; /* trick to obtain hostname (later)! */
+ tmp = ++p;
+ for(i=0 ; *p && isdigit((int) *p) ; ++p, ++i)
+ /* SKIP AND COUNT */;
+ pData->port = malloc(i + 1);
+ if(pData->port == NULL) {
+ errmsg.LogError(0, NO_ERRCODE, "Could not get memory to store syslog forwarding port, "
+ "using default port, results may not be what you intend\n");
+ /* we leave f_forw.port set to NULL, this is then handled by getFwdPt(). */
+ } else {
+ memcpy(pData->port, tmp, i);
+ *(pData->port + i) = '\0';
+ }
+ }
+
+ /* now skip to template */
+ bErr = 0;
+ while(*p && *p != ';' && *p != '#' && !isspace((int) *p))
+ ++p; /*JUST SKIP*/
+
+ /* TODO: make this if go away! */
+ if(*p == ';' || *p == '#' || isspace(*p)) {
+ uchar cTmp = *p;
+ *p = '\0'; /* trick to obtain hostname (later)! */
+ CHKmalloc(pData->f_hname = strdup((char*) q));
+ *p = cTmp;
+ } else {
+ CHKmalloc(pData->f_hname = strdup((char*) q));
+ }
+
+ /* process template */
+ CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS,
+ (pszTplName == NULL) ? (uchar*)"RSYSLOG_TraditionalForwardFormat" : pszTplName));
+
+ if(pData->protocol == FORW_TCP) {
+ /* create our tcpclt */
+ CHKiRet(tcpclt.Construct(&pData->pTCPClt));
+ CHKiRet(tcpclt.SetResendLastOnRecon(pData->pTCPClt, bResendLastOnRecon));
+ /* and set callbacks */
+ CHKiRet(tcpclt.SetSendInit(pData->pTCPClt, TCPSendInit));
+ CHKiRet(tcpclt.SetSendFrame(pData->pTCPClt, TCPSendFrame));
+ CHKiRet(tcpclt.SetSendPrepRetry(pData->pTCPClt, TCPSendPrepRetry));
+ CHKiRet(tcpclt.SetFraming(pData->pTCPClt, tcp_framing));
+ pData->iStrmDrvrMode = iStrmDrvrMode;
+ if(pszStrmDrvr != NULL)
+ CHKmalloc(pData->pszStrmDrvr = (uchar*)strdup((char*)pszStrmDrvr));
+ if(pszStrmDrvrAuthMode != NULL)
+ CHKmalloc(pData->pszStrmDrvrAuthMode =
+ (uchar*)strdup((char*)pszStrmDrvrAuthMode));
+ if(pPermPeers != NULL) {
+ pData->pPermPeers = pPermPeers;
+ pPermPeers = NULL;
+ }
+ }
+
+CODE_STD_FINALIZERparseSelectorAct
+ENDparseSelectorAct
+
+
+/* a common function to free our configuration variables - used both on exit
+ * and on $ResetConfig processing. -- rgerhards, 2008-05-16
+ */
+static void
+freeConfigVars(void)
+{
+ if(pszTplName != NULL) {
+ free(pszTplName);
+ pszTplName = NULL;
+ }
+ if(pszStrmDrvr != NULL) {
+ free(pszStrmDrvr);
+ pszStrmDrvr = NULL;
+ }
+ if(pszStrmDrvrAuthMode != NULL) {
+ free(pszStrmDrvrAuthMode);
+ pszStrmDrvrAuthMode = NULL;
+ }
+ if(pPermPeers != NULL) {
+ free(pPermPeers);
+ }
+}
+
+
+BEGINmodExit
+CODESTARTmodExit
+ /* release what we no longer need */
+ objRelease(errmsg, CORE_COMPONENT);
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(net, LM_NET_FILENAME);
+ objRelease(netstrm, LM_NETSTRMS_FILENAME);
+ objRelease(netstrms, LM_NETSTRMS_FILENAME);
+ objRelease(tcpclt, LM_TCPCLT_FILENAME);
+
+ freeConfigVars();
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_OMOD_QUERIES
+ENDqueryEtryPt
+
+
+/* Reset config variables for this module to default values.
+ * rgerhards, 2008-03-28
+ */
+static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ freeConfigVars();
+
+ /* we now must reset all non-string values */
+ iStrmDrvrMode = 0;
+ bResendLastOnRecon = 0;
+
+ return RS_RET_OK;
+}
+
+
+BEGINmodInit(Fwd)
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(net,LM_NET_FILENAME));
+
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionforwarddefaulttemplate", 0, eCmdHdlrGetWord, NULL, &pszTplName, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriver", 0, eCmdHdlrGetWord, NULL, &pszStrmDrvr, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdrivermode", 0, eCmdHdlrInt, NULL, &iStrmDrvrMode, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriverauthmode", 0, eCmdHdlrGetWord, NULL, &pszStrmDrvrAuthMode, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionsendstreamdriverpermittedpeer", 0, eCmdHdlrGetWord, setPermittedPeer, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionsendresendlastmsgonreconnect", 0, eCmdHdlrBinary, NULL, &bResendLastOnRecon, NULL));
+ CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
+ENDmodInit
+
+/* vim:set ai:
+ */
diff --git a/tools/omfwd.h b/tools/omfwd.h
new file mode 100644
index 00000000..dea432e5
--- /dev/null
+++ b/tools/omfwd.h
@@ -0,0 +1,34 @@
+/* omfwd.h
+ * These are the definitions for the build-in forwarding output module.
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007 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.
+ */
+#ifndef OMFWD_H_INCLUDED
+#define OMFWD_H_INCLUDED 1
+
+/* prototypes */
+rsRetVal modInitFwd(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*);
+
+#endif /* #ifndef OMFWD_H_INCLUDED */
+/*
+ * vi:set ai:
+ */
diff --git a/tools/omshell.c b/tools/omshell.c
new file mode 100644
index 00000000..7b815869
--- /dev/null
+++ b/tools/omshell.c
@@ -0,0 +1,148 @@
+/* omshell.c
+ * This is the implementation of the build-in shell output module.
+ *
+ * NOTE: read comments in module-template.h to understand how this file
+ * works!
+ *
+ * shell support was initially written by bkalkbrenner 2005-09-20
+ *
+ * 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" 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 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 <assert.h>
+#include "syslogd.h"
+#include "syslogd-types.h"
+#include "srUtils.h"
+#include "omshell.h"
+#include "module-template.h"
+#include "errmsg.h"
+
+MODULE_TYPE_OUTPUT
+
+/* internal structures
+ */
+DEF_OMOD_STATIC_DATA
+DEFobjCurrIf(errmsg)
+
+typedef struct _instanceData {
+ uchar progName[MAXFNAME]; /* program to execute */
+} instanceData;
+
+
+BEGINcreateInstance
+CODESTARTcreateInstance
+ENDcreateInstance
+
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ if(eFeat == sFEATURERepeatedMsgReduction)
+ iRet = RS_RET_OK;
+ENDisCompatibleWithFeature
+
+
+BEGINfreeInstance
+CODESTARTfreeInstance
+ENDfreeInstance
+
+
+BEGINdbgPrintInstInfo
+CODESTARTdbgPrintInstInfo
+ printf("%s", pData->progName);
+ENDdbgPrintInstInfo
+
+
+BEGINtryResume
+CODESTARTtryResume
+ENDtryResume
+
+BEGINdoAction
+CODESTARTdoAction
+ /* TODO: using pData->progName is not clean from the point of
+ * modularization. We'll change that as we go ahead with modularization.
+ * rgerhards, 2007-07-20
+ */
+ dbgprintf("\n");
+ if(execProg((uchar*) pData->progName, 1, ppString[0]) == 0)
+ errmsg.LogError(0, NO_ERRCODE, "Executing program '%s' failed", (char*)pData->progName);
+ENDdoAction
+
+
+BEGINparseSelectorAct
+CODESTARTparseSelectorAct
+CODE_STD_STRING_REQUESTparseSelectorAct(1)
+ /* yes, the if below is redundant, but I need it now. Will go away as
+ * the code further changes. -- rgerhards, 2007-07-25
+ */
+ if(*p == '^') {
+ if((iRet = createInstance(&pData)) != RS_RET_OK)
+ goto finalize_it;
+ }
+
+
+ switch (*p)
+ {
+ case '^': /* bkalkbrenner 2005-09-20: execute shell command */
+ dbgprintf("exec\n");
+ ++p;
+ iRet = cflineParseFileName(p, (uchar*) pData->progName, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS,
+ (uchar*)"RSYSLOG_TraditionalFileFormat");
+ break;
+ default:
+ iRet = RS_RET_CONFLINE_UNPROCESSED;
+ break;
+ }
+
+CODE_STD_FINALIZERparseSelectorAct
+ENDparseSelectorAct
+
+
+BEGINmodExit
+CODESTARTmodExit
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_OMOD_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit(Shell)
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ENDmodInit
+
+/*
+ * vi:set ai:
+ */
diff --git a/tools/omshell.h b/tools/omshell.h
new file mode 100644
index 00000000..3061ad07
--- /dev/null
+++ b/tools/omshell.h
@@ -0,0 +1,34 @@
+/* omshell.c
+ * These are the definitions for the build-in shell output module.
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007 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.
+ */
+#ifndef ACTSHELL_H_INCLUDED
+#define ACTSHELL_H_INCLUDED 1
+
+/* prototypes */
+rsRetVal modInitShell(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*);
+
+#endif /* #ifndef ACTSHELL_H_INCLUDED */
+/*
+ * vi:set ai:
+ */
diff --git a/tools/omusrmsg.c b/tools/omusrmsg.c
new file mode 100644
index 00000000..830bbc87
--- /dev/null
+++ b/tools/omusrmsg.c
@@ -0,0 +1,326 @@
+/* omusrmsg.c
+ * This is the implementation of the build-in output module for sending
+ * user messages.
+ *
+ * NOTE: read comments in module-template.h to understand how this file
+ * works!
+ *
+ * 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" 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, 2008 Rainer Gerhards and Adiscon GmbH.
+ *
+ * rgerhards, 2008-07-04 (happy Independence Day!): rsyslog inherited the
+ * wall functionality from sysklogd. Sysklogd was single-threaded and could
+ * not afford to spent a lot of time inside a single action. Thus, it forked
+ * off a new process to do the wall. In rsyslog, however, this creates some
+ * grief with the threading model. Also, we do not really need to de-couple
+ * processing, because we have ample ways to do it in rsyslog. Plus, the
+ * default main message queue will care for a somewhat longer execution time.
+ * So in short, the real fix to the problem is an architecture change. From
+ * now on, we will not fork off a new process but rather do the notification
+ * within the current one. This also reduces system overhead.
+ *
+ * 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 <assert.h>
+#include <signal.h>
+#include <sys/param.h>
+#include <utmp.h>
+#include <unistd.h>
+#include <sys/uio.h>
+#include <sys/stat.h>
+#include <errno.h>
+#if HAVE_FCNTL_H
+#include <fcntl.h>
+#else
+#include <sys/msgbuf.h>
+#endif
+#if HAVE_PATHS_H
+#include <paths.h>
+#endif
+#include "srUtils.h"
+#include "stringbuf.h"
+#include "syslogd-types.h"
+#include "syslogd.h"
+#include "omusrmsg.h"
+#include "module-template.h"
+#include "errmsg.h"
+
+
+/* portability: */
+#ifndef _PATH_DEV
+# define _PATH_DEV "/dev/"
+#endif
+
+
+MODULE_TYPE_OUTPUT
+
+/* internal structures
+ */
+DEF_OMOD_STATIC_DATA
+DEFobjCurrIf(errmsg)
+
+typedef struct _instanceData {
+ int bIsWall; /* 1- is wall, 0 - individual users */
+ char uname[MAXUNAMES][UNAMESZ+1];
+} instanceData;
+
+
+BEGINcreateInstance
+CODESTARTcreateInstance
+ENDcreateInstance
+
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ if(eFeat == sFEATURERepeatedMsgReduction)
+ iRet = RS_RET_OK;
+ENDisCompatibleWithFeature
+
+
+BEGINfreeInstance
+CODESTARTfreeInstance
+ /* TODO: free the instance pointer (currently a leak, will go away) */
+ENDfreeInstance
+
+
+BEGINdbgPrintInstInfo
+ register int i;
+CODESTARTdbgPrintInstInfo
+ for (i = 0; i < MAXUNAMES && *pData->uname[i]; i++)
+ dbgprintf("%s, ", pData->uname[i]);
+ENDdbgPrintInstInfo
+
+
+/**
+ * BSD setutent/getutent() replacement routines
+ * The following routines emulate setutent() and getutent() under
+ * BSD because they are not available there. We only emulate what we actually
+ * need! rgerhards 2005-03-18
+ */
+#ifdef OS_BSD
+static FILE *BSD_uf = NULL;
+void setutent(void)
+{
+ assert(BSD_uf == NULL);
+ if ((BSD_uf = fopen(_PATH_UTMP, "r")) == NULL) {
+ errmsg.LogError(NO_ERRCODE, "%s", _PATH_UTMP);
+ return;
+ }
+}
+
+struct utmp* getutent(void)
+{
+ static struct utmp st_utmp;
+
+ if(fread((char *)&st_utmp, sizeof(st_utmp), 1, BSD_uf) != 1)
+ return NULL;
+
+ return(&st_utmp);
+}
+
+void endutent(void)
+{
+ fclose(BSD_uf);
+ BSD_uf = NULL;
+}
+#endif /* #ifdef OS_BSD */
+
+
+/* WALLMSG -- Write a message to the world at large
+ *
+ * Write the specified message to either the entire
+ * world, or a list of approved users.
+ *
+ * rgerhards, 2005-10-19: applying the following sysklogd patch:
+ * Tue May 4 16:52:01 CEST 2004: Solar Designer <solar@openwall.com>
+ * Adjust the size of a variable to prevent a buffer overflow
+ * should _PATH_DEV ever contain something different than "/dev/".
+ * rgerhards, 2008-07-04: changing the function to no longer use fork() but
+ * continue run on its thread instead.
+ */
+static rsRetVal wallmsg(uchar* pMsg, instanceData *pData)
+{
+
+ uchar szErr[512];
+ char p[sizeof(_PATH_DEV) + UNAMESZ];
+ register int i;
+ int errnoSave;
+ int ttyf;
+ int wrRet;
+ struct utmp ut;
+ struct utmp *uptr;
+ struct stat statb;
+ DEFiRet;
+
+ assert(pMsg != NULL);
+
+ /* open the user login file */
+ setutent();
+
+ /* scan the user login file */
+ while((uptr = getutent())) {
+ memcpy(&ut, uptr, sizeof(ut));
+ /* is this slot used? */
+ if(ut.ut_name[0] == '\0')
+ continue;
+#ifndef OS_BSD
+ if(ut.ut_type != USER_PROCESS)
+ continue;
+#endif
+ if(!(strncmp (ut.ut_name,"LOGIN", 6))) /* paranoia */
+ continue;
+
+ /* should we send the message to this user? */
+ if(pData->bIsWall == 0) {
+ for(i = 0; i < MAXUNAMES; i++) {
+ if(!pData->uname[i][0]) {
+ i = MAXUNAMES;
+ break;
+ }
+ if(strncmp(pData->uname[i], ut.ut_name, UNAMESZ) == 0)
+ break;
+ }
+ if(i == MAXUNAMES) /* user not found? */
+ continue; /* on to next user! */
+ }
+
+ /* compute the device name */
+ strcpy(p, _PATH_DEV);
+ strncat(p, ut.ut_line, UNAMESZ);
+
+ /* we must be careful when writing to the terminal. A terminal may block
+ * (for example, a user has pressed <ctl>-s). In that case, we can not
+ * wait indefinitely. So we need to use non-blocking I/O. In case we would
+ * block, we simply do not send the message, because that's the best we can
+ * do. -- rgerhards, 2008-07-04
+ */
+
+ /* open the terminal */
+ if((ttyf = open(p, O_WRONLY|O_NOCTTY|O_NONBLOCK)) >= 0) {
+ if(fstat(ttyf, &statb) == 0 && (statb.st_mode & S_IWRITE)) {
+ wrRet = write(ttyf, pMsg, strlen((char*)pMsg));
+ if(Debug && wrRet == -1) {
+ /* we record the state to the debug log */
+ errnoSave = errno;
+ rs_strerror_r(errno, (char*)szErr, sizeof(szErr));
+ dbgprintf("write to terminal '%s' failed with [%d]:%s\n",
+ p, errnoSave, szErr);
+ }
+ }
+ close(ttyf);
+ ttyf = -1;
+ }
+ }
+
+ /* close the user login file */
+ endutent();
+ RETiRet;
+}
+
+
+BEGINtryResume
+CODESTARTtryResume
+ENDtryResume
+
+BEGINdoAction
+CODESTARTdoAction
+ dbgprintf("\n");
+ iRet = wallmsg(ppString[0], pData);
+ENDdoAction
+
+
+BEGINparseSelectorAct
+ uchar *q;
+ int i;
+CODESTARTparseSelectorAct
+CODE_STD_STRING_REQUESTparseSelectorAct(1)
+ /* User names must begin with a gnu e-regex:
+ * [a-zA-Z0-9_.]
+ * plus '*' for wall
+ */
+ if(!*p || !((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
+ || (*p >= '0' && *p <= '9') || *p == '_' || *p == '.' || *p == '*'))
+ ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
+
+ CHKiRet(createInstance(&pData));
+
+ if(*p == '*') { /* wall */
+ dbgprintf("write-all");
+ ++p; /* eat '*' */
+ pData->bIsWall = 1; /* write to all users */
+ CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*) " WallFmt"));
+ } else {
+ /* everything else beginning with the regex above
+ * is currently treated as a user name -- TODO: is this portable?
+ */
+ dbgprintf("users: %s\n", p); /* ASP */
+ pData->bIsWall = 0; /* write to individual users */
+ for (i = 0; i < MAXUNAMES && *p && *p != ';'; i++) {
+ for (q = p; *q && *q != ',' && *q != ';'; )
+ q++;
+ (void) strncpy((char*) pData->uname[i], (char*) p, UNAMESZ);
+ if ((q - p) > UNAMESZ)
+ pData->uname[i][UNAMESZ] = '\0';
+ else
+ pData->uname[i][q - p] = '\0';
+ while (*q == ',' || *q == ' ')
+ q++;
+ p = q;
+ }
+ /* done, on to the template
+ * TODO: we need to handle the case where i >= MAXUNAME!
+ */
+ if((iRet = cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar*)" StdUsrMsgFmt"))
+ != RS_RET_OK)
+ goto finalize_it;
+ }
+CODE_STD_FINALIZERparseSelectorAct
+ENDparseSelectorAct
+
+
+BEGINmodExit
+CODESTARTmodExit
+ENDmodExit
+
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_OMOD_QUERIES
+ENDqueryEtryPt
+
+
+BEGINmodInit(UsrMsg)
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ENDmodInit
+
+/* vim:set ai:
+ */
diff --git a/tools/omusrmsg.h b/tools/omusrmsg.h
new file mode 100644
index 00000000..52e780f7
--- /dev/null
+++ b/tools/omusrmsg.h
@@ -0,0 +1,34 @@
+/* omusrmsg.c
+ * These are the definitions for the build-in user message output module.
+ *
+ * File begun on 2007-07-13 by RGerhards (extracted from syslogd.c)
+ *
+ * Copyright 2007 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.
+ */
+#ifndef OMUSRMSG_H_INCLUDED
+#define OMUSRMSG_H_INCLUDED 1
+
+/* prototypes */
+rsRetVal modInitUsrMsg(int iIFVersRequested __attribute__((unused)), int *ipIFVersProvided, rsRetVal (**pQueryEtryPt)(), rsRetVal (*pHostQueryEtryPt)(uchar*, rsRetVal (**)()), modInfo_t*);
+
+#endif /* #ifndef OMUSRMSG_H_INCLUDED */
+/*
+ * vi:set ai:
+ */
diff --git a/tools/pidfile.c b/tools/pidfile.c
new file mode 100644
index 00000000..2be13da6
--- /dev/null
+++ b/tools/pidfile.c
@@ -0,0 +1,156 @@
+/*
+ pidfile.c - interact with pidfiles
+ Copyright (c) 1995 Martin Schulze <Martin.Schulze@Linux.DE>
+
+ * 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"
+
+/*
+ * Sat Aug 19 13:24:33 MET DST 1995: Martin Schulze
+ * First version (v0.2) released
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <string.h>
+#include <errno.h>
+#include <signal.h>
+#ifdef __sun
+#include <fcntl.h>
+#endif
+
+#include "srUtils.h"
+
+/* read_pid
+ *
+ * Reads the specified pidfile and returns the read pid.
+ * 0 is returned if either there's no pidfile, it's empty
+ * or no pid can be read.
+ */
+int read_pid (char *pidfile)
+{
+ FILE *f;
+ int pid;
+
+ if (!(f=fopen(pidfile,"r")))
+ return 0;
+ fscanf(f,"%d", &pid);
+ fclose(f);
+ return pid;
+}
+
+/* check_pid
+ *
+ * Reads the pid using read_pid and looks up the pid in the process
+ * table (using /proc) to determine if the process already exists. If
+ * so 1 is returned, otherwise 0.
+ */
+int check_pid (char *pidfile)
+{
+ int pid = read_pid(pidfile);
+
+ /* Amazing ! _I_ am already holding the pid file... */
+ if ((!pid) || (pid == getpid ()))
+ return 0;
+
+ /*
+ * The 'standard' method of doing this is to try and do a 'fake' kill
+ * of the process. If an ESRCH error is returned the process cannot
+ * be found -- GW
+ */
+ /* But... errno is usually changed only on error.. */
+ if (kill(pid, 0) && errno == ESRCH)
+ return(0);
+
+ return pid;
+}
+
+/* write_pid
+ *
+ * Writes the pid to the specified file. If that fails 0 is
+ * returned, otherwise the pid.
+ */
+int write_pid (char *pidfile)
+{
+ FILE *f;
+ int fd;
+ int pid;
+
+ if ( ((fd = open(pidfile, O_RDWR|O_CREAT, 0644)) == -1)
+ || ((f = fdopen(fd, "r+")) == NULL) ) {
+ fprintf(stderr, "Can't open or create %s.\n", pidfile);
+ return 0;
+ }
+
+ /* It seems to be acceptable that we do not lock the pid file
+ * if we run under Solaris. In any case, it is highly unlikely
+ * that two instances try to access this file. And flock is really
+ * causing me grief on my initial steps on Solaris. Some time later,
+ * we might re-enable it (or use some alternate method).
+ * 2006-02-16 rgerhards
+ */
+
+#if HAVE_FLOCK
+ if (flock(fd, LOCK_EX|LOCK_NB) == -1) {
+ fscanf(f, "%d", &pid);
+ fclose(f);
+ printf("Can't lock, lock is held by pid %d.\n", pid);
+ return 0;
+ }
+#endif
+
+ pid = getpid();
+ if (!fprintf(f,"%d\n", pid)) {
+ char errStr[1024];
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ printf("Can't write pid , %s.\n", errStr);
+ close(fd);
+ return 0;
+ }
+ fflush(f);
+
+#if HAVE_FLOCK
+ if (flock(fd, LOCK_UN) == -1) {
+ char errStr[1024];
+ rs_strerror_r(errno, errStr, sizeof(errStr));
+ printf("Can't unlock pidfile %s, %s.\n", pidfile, errStr);
+ close(fd);
+ return 0;
+ }
+#endif
+ close(fd);
+
+ return pid;
+}
+
+/* remove_pid
+ *
+ * Remove the the specified file. The result from unlink(2)
+ * is returned
+ */
+int remove_pid (char *pidfile)
+{
+ return unlink (pidfile);
+}
+
diff --git a/tools/pidfile.h b/tools/pidfile.h
new file mode 100644
index 00000000..40be9069
--- /dev/null
+++ b/tools/pidfile.h
@@ -0,0 +1,51 @@
+/*
+ pidfile.h - interact with pidfiles
+ Copyright (c) 1995 Martin Schulze <Martin.Schulze@Linux.DE>
+
+ * 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.
+*/
+
+/* read_pid
+ *
+ * Reads the specified pidfile and returns the read pid.
+ * 0 is returned if either there's no pidfile, it's empty
+ * or no pid can be read.
+ */
+int read_pid (char *pidfile);
+
+/* check_pid
+ *
+ * Reads the pid using read_pid and looks up the pid in the process
+ * table (using /proc) to determine if the process already exists. If
+ * so 1 is returned, otherwise 0.
+ */
+int check_pid (char *pidfile);
+
+/* write_pid
+ *
+ * Writes the pid to the specified file. If that fails 0 is
+ * returned, otherwise the pid.
+ */
+int write_pid (char *pidfile);
+
+/* remove_pid
+ *
+ * Remove the the specified file. The result from unlink(2)
+ * is returned
+ */
+int remove_pid (char *pidfile);
diff --git a/tools/rsyslog.conf.5 b/tools/rsyslog.conf.5
new file mode 100644
index 00000000..eb498537
--- /dev/null
+++ b/tools/rsyslog.conf.5
@@ -0,0 +1,728 @@
+.\" rsyslog.conf - rsyslogd(8) configuration file
+.\" Copyright 2003-2008 Rainer Gerhards and Adiscon GmbH.
+.\"
+.\" This file is part of the rsyslog package, an enhanced system log daemon.
+.\"
+.\" This program 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 2 of the License, or
+.\" (at your option) any later version.
+.\"
+.\" This program 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 this program; if not, write to the Free Software
+.\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
+.\"
+.TH RSYSLOG.CONF 5 "11 July 2008" "Version 3.18.0" "Linux System Administration"
+.SH NAME
+rsyslog.conf \- rsyslogd(8) configuration file
+.SH DESCRIPTION
+The
+.I rsyslog.conf
+file is the main configuration file for the
+.BR rsyslogd (8)
+which logs system messages on *nix systems. This file specifies rules
+for logging. For special features see the
+.BR rsyslogd (8)
+manpage. Rsyslog.conf is backward-compatible with sysklogd's syslog.conf file. So if you migrate
+from sysklogd you can rename it and it should work.
+
+.B Note that this version of rsyslog ships with extensive documentation in html format.
+This is provided in the ./doc subdirectory and probably
+in a separate package if you installed rsyslog via a packaging system.
+To use rsyslog's advanced features, you
+.B need
+to look at the html documentation, because the man pages only cover
+basic aspects of operation.
+
+
+.SH MODULES
+
+Rsyslog has a modular design. Consequently, there is a growing number
+of modules. See the html documentation for their full description.
+
+.TP
+.I omsnmp
+SNMP trap output module
+.TP
+.I omgssapi
+Output module for GSS-enabled syslog
+.TP
+.I ommysql
+Output module for MySQL
+.TP
+.I omrelp
+Output module for the reliable RELP protocol (prevents message loss).
+For details, see below at imrelp and the html documentation.
+It can be used like this:
+.IP
+*.* :omrelp:server:port
+.IP
+*.* :omrelp:192.168.0.1:2514 # actual sample
+.TP
+.I ompgsql
+Output module for PostgreSQL
+.TP
+.I omlibdbi
+Generic database output module (Firebird/Interbase, MS SQL, Sybase,
+SQLite, Ingres, Oracle, mSQL)
+.TP
+.I imfile
+Input module for text files
+.TP
+.I imudp
+Input plugin for UDP syslog. Replaces the deprecated -r option. Can be
+used like this:
+.IP
+$ModLoad imudp
+.IP
+$InputUDPServerRun 514
+.TP
+.I imtcp
+Input plugin for plain TCP syslog. Replaces the deprecated -t
+option. Can be used like this:
+.IP
+$ModLoad imtcp
+.IP
+$InputTCPServerRun 514
+.TP
+.TP
+.I imtcp
+Input plugin for the RELP protocol. RELP can be used instead
+of UDP or plain TCP syslog to provide reliable delivery of
+syslog messages. Please note that plain TCP syslog does NOT
+provide truly reliable delivery, with it messages may be lost
+when there is a connection problem or the server shuts down.
+RELP prevents message loss in those cases.
+It can be used like this:
+.IP
+$ModLoad imrelp
+.IP
+$InputRELPServerRun 2514
+.TP
+.I imgssapi
+Input plugin for plain TCP and GSS-enable syslog
+.TP
+.I immark
+Support for mark messages
+.TP
+.I imklog
+Kernel logging. To include kernel log messages, you need to do
+.IP
+$ModLoad imklog
+
+Please note that the klogd daemon is no longer necessary and consequently
+no longer provided by the rsyslog package.
+.TP
+.I imuxsock
+Unix sockets, including the system log socket. You need to specify
+.IP
+$ModLoad imudp
+
+in order to receive log messages from local system processes. This
+config directive should only left out if you know exactly what you
+are doing.
+
+
+.SH BASIC STRUCTURE
+
+Lines starting with a hash mark ('#') and empty lines are ignored.
+Rsyslog.conf should contain following sections (sorted by recommended order in file):
+
+.TP
+Global directives
+Global directives set some global properties of whole rsyslog daemon, for example size of main
+message queue ($MainMessageQueueSize), loading external modules ($ModLoad) and so on.
+All global directives need to be specified on a line by their own and must start with
+a dollar-sign. The complete list of global directives can be found in html documentation in doc
+directory or online on web pages.
+
+.TP
+Templates
+Templates allow you to specify format of the logged message. They are also used for dynamic
+file name generation. They have to be defined before they are used in rules. For more info
+about templates see TEMPLATES section of this manpage.
+
+.TP
+Output channels
+Output channels provide an umbrella for any type of output that the user might want.
+They have to be defined before they are used in rules. For more info about output channels
+see OUTPUT CHANNELS section of this manpage.
+
+.TP
+Rules (selector + action)
+Every rule line consists of two fields, a selector field and an action field. These
+two fields are separated by one or more spaces or tabs. The selector field specifies
+a pattern of facilities and priorities belonging to the specified action.
+
+.SH ACTIONS
+The action field of a rule describes what to do with the message. In general, message content
+is written to a kind of "logfile". But also other actions might be done, like writing to a
+database table or forwarding to another host.
+
+.SS Regular file
+Typically messages are logged to real files. The file has to be specified with full pathname,
+beginning with a slash ('/').
+
+.B Example:
+.RS
+*.* /var/log/traditionalfile.log;RSYSLOG_TraditionalFormat # log to a file in the traditional format
+.RE
+
+Note: if you would like to use high-precision timestamps in your log files,
+just remove the ";RSYSLOG_TraditionalFormat". That will select the default
+template, which, if not changed, uses RFC 3339 timestamps.
+
+.B Example:
+.RS
+*.* /var/log/file.log # log to a file with RFC3339 timestamps
+.RE
+
+.SS Named pipes
+This version of rsyslogd(8) has support for logging output to named pipes (fifos). A fifo or
+named pipe can be used as a destination for log messages by prepending a pipe symbol ('|')
+to the name of the file. This is handy for debugging. Note that the fifo must be created with
+the mkfifo(1) command before rsyslogd(8) is started.
+
+.SS Terminal and console
+If the file you specified is a tty, special tty-handling is done, same with /dev/console.
+
+.SS Remote machine
+There are three ways to forward message: the traditional UDP transport, which is extremely
+lossy but standard, the plain TCP based transport which loses messages only during certain
+situations but is widely available and the RELP transport which does not lose messages
+but is currently available only as part of rsyslogd 3.15.0 and above.
+
+To forward messages to another host via UDP, prepend the hostname with the at sign ("@").
+To forward it via plain tcp, prepend two at signs ("@@"). To forward via RELP, prepend the
+string ":omrelp:" in front of the hostname.
+
+.B Example:
+.RS
+*.* @192.168.0.1
+.RE
+.sp
+In the example above, messages are forwarded via UDP to the machine 192.168.0.1, the destination
+port defaults to 514. Due to the nature of UDP, you will probably lose some messages in transit.
+If you expect high traffic volume, you can expect to lose a quite noticeable number of messages
+(the higher the traffic, the more likely and severe is message loss).
+
+.B If you would like to prevent message loss, use RELP:
+.RS
+*.* :omrelp:192.168.0.1:2514
+.RE
+.sp
+Note that a port number was given as there is no standard port for relp.
+
+Keep in mind that you need to load the correct input and output plugins (see "Modules" above).
+
+Please note that rsyslogd offers a variety of options in regarding to remote
+forwarding. For full details, please see the html documentation.
+
+.SS List of users
+Usually critical messages are also directed to ``root'' on that machine. You can specify a list
+of users that shall get the message by simply writing the login. You may specify more than one
+user by separating them with commas (','). If they're logged in they get the message. Don't
+think a mail would be sent, that might be too late.
+
+.SS Everyone logged on
+Emergency messages often go to all users currently online to notify them that something strange
+is happening with the system. To specify this wall(1)-feature use an asterisk ('*').
+
+.SS Database table
+This allows logging of the message to a database table.
+By default, a MonitorWare-compatible schema is required for this to work. You can
+create that schema with the createDB.SQL file that came with the rsyslog package. You can also
+use any other schema of your liking - you just need to define a proper template and assign this
+template to the action.
+
+See the html documentation for further details on database logging.
+
+.SS Discard
+If the discard action is carried out, the received message is immediately discarded. Discard
+can be highly effective if you want to filter out some annoying messages that otherwise would
+fill your log files. To do that, place the discard actions early in your log files.
+This often plays well with property-based filters, giving you great freedom in specifying
+what you do not want.
+
+Discard is just the single tilde character with no further parameters.
+.sp
+.B Example:
+.RS
+*.* ~ # discards everything.
+.RE
+
+
+.SS Output channel
+Binds an output channel definition (see there for details) to this action. Output channel actions
+must start with a $-sign, e.g. if you would like to bind your output channel definition "mychannel"
+to the action, use "$mychannel". Output channels support template definitions like all all other
+actions.
+
+.SS Shell execute
+This executes a program in a subshell. The program is passed the template-generated message as the
+only command line parameter. Rsyslog waits until the program terminates and only then continues to run.
+
+.B Example:
+.RS
+^program-to-execute;template
+.RE
+
+The program-to-execute can be any valid executable. It receives the template string as a single parameter
+(argv[1]).
+
+.SH FILTER CONDITIONS
+Rsyslog offers three different types "filter conditions":
+.sp 0
+ * "traditional" severity and facility based selectors
+.sp 0
+ * property-based filters
+.sp 0
+ * expression-based filters
+.RE
+
+.SS Blocks
+Rsyslogd supports BSD-style blocks inside rsyslog.conf. Each block of lines is separated from
+the previous block by a program or hostname specification. A block will only log messages
+corresponding to the most recent program and hostname specifications given. Thus, a block which
+selects "ppp" as the program, directly followed by a block that selects messages from the
+hostname "dialhost", then the second block will only log messages from the ppp program on dialhost.
+
+.SS Selectors
+.B Selectors are the traditional way of filtering syslog messages.
+They have been kept in rsyslog with their original syntax, because it is well-known, highly
+effective and also needed for compatibility with stock syslogd configuration files. If you just
+need to filter based on priority and facility, you should do this with selector lines. They are
+not second-class citizens in rsyslog and offer the best performance for this job.
+
+.SS Property-Based Filters
+Property-based filters are unique to rsyslogd. They allow to filter on any property, like HOSTNAME,
+syslogtag and msg.
+
+A property-based filter must start with a colon in column 0. This tells rsyslogd that it is the new
+filter type. The colon must be followed by the property name, a comma, the name of the compare
+operation to carry out, another comma and then the value to compare against. This value must be quoted.
+There can be spaces and tabs between the commas. Property names and compare operations are
+case-sensitive, so "msg" works, while "MSG" is an invalid property name. In brief, the syntax is as follows:
+.sp
+.RS
+:property, [!]compare-operation, "value"
+.RE
+
+The following compare-operations are currently supported:
+.sp
+.RS
+.B contains
+.RS
+Checks if the string provided in value is contained in the property
+.RE
+.sp
+.B isequal
+.RS
+Compares the "value" string provided and the property contents. These two values must be exactly equal to match.
+.RE
+.sp
+.B startswith
+.RS
+Checks if the value is found exactly at the beginning of the property value
+.RE
+.sp
+.B regex
+.RS
+Compares the property against the provided regular expression.
+.RE
+
+.SS Expression-Based Filters
+See the html documentation for this feature.
+
+
+.SH TEMPLATES
+
+Every output in rsyslog uses templates - this holds true for files, user
+messages and so on. Templates compatible with the stock syslogd
+formats are hardcoded into rsyslogd. If no template is specified, we use
+one of these hardcoded templates. Search for "template_" in syslogd.c and
+you will find the hardcoded ones.
+
+A template consists of a template directive, a name, the actual template text
+and optional options. A sample is:
+
+.RS
+.B $template MyTemplateName,"\\\\7Text %property% some more text\\\\n",<options>
+.RE
+
+The "$template" is the template directive. It tells rsyslog that this line
+contains a template. The backslash is an escape character. For example, \\7 rings the
+bell (this is an ASCII value), \\n is a new line. The set in rsyslog is a bit restricted
+currently.
+
+All text in the template is used literally, except for things within percent
+signs. These are properties and allow you access to the contents of the syslog
+message. Properties are accessed via the property replacer and it can for example
+pick a substring or do date-specific formatting. More on this is the PROPERTY REPLACER
+section of this manpage.
+
+To escape:
+.sp 0
+ % = \\%
+.sp 0
+ \\ = \\\\ --> '\\' is used to escape (as in C)
+.sp 0
+$template TraditionalFormat,"%timegenerated% %HOSTNAME% %syslogtag%%msg%\n"
+
+Properties can be accessed by the property replacer (see there for details).
+
+.B Please note that templates can also by used to generate selector lines with dynamic file names.
+For example, if you would like to split syslog messages from different hosts
+to different files (one per host), you can define the following template:
+
+.RS
+.B $template DynFile,"/var/log/system-%HOSTNAME%.log"
+.RE
+
+This template can then be used when defining an output selector line. It will
+result in something like "/var/log/system-localhost.log"
+
+.SS Template options
+The <options> part is optional. It carries options influencing the template as whole.
+See details below. Be sure NOT to mistake template options with property options - the
+later ones are processed by the property replacer and apply to a SINGLE property, only
+(and not the whole template).
+
+Template options are case-insensitive. Currently defined are:
+
+.RS
+.TP
+sql
+format the string suitable for a SQL statement in MySQL format. This will replace single
+quotes ("'") and the backslash character by their backslash-escaped counterpart
+("\'" and "\\") inside each field. Please note that in MySQL configuration, the NO_BACKSLASH_ESCAPES
+mode must be turned off for this format to work (this is the default).
+
+.TP
+stdsql
+format the string suitable for a SQL statement that is to be sent to a standards-compliant
+sql server. This will replace single quotes ("'") by two single quotes ("''") inside each field.
+You must use stdsql together with MySQL if in MySQL configuration the NO_BACKSLASH_ESCAPES
+is turned on.
+.RE
+
+Either the
+.B sql
+or
+.B stdsql
+option
+.B MUST
+be specified when a template is used for writing to a database,
+otherwise injection might occur. Please note that due to the unfortunate fact
+that several vendors have violated the sql standard and introduced their own
+escape methods, it is impossible to have a single option doing all the work.
+So you yourself must make sure you are using the right format.
+.B If you choose the wrong one, you are still vulnerable to sql injection.
+
+Please note that the database writer *checks* that the sql option is present
+in the template. If it is not present, the write database action is disabled.
+This is to guard you against accidental forgetting it and then becoming
+vulnerable to SQL injection. The sql option can also be useful with files -
+especially if you want to import them into a database on another machine for
+performance reasons. However, do NOT use it if you do not have a real need for
+it - among others, it takes some toll on the processing time. Not much, but on
+a really busy system you might notice it ;)
+
+The default template for the write to database action has the sql option set.
+
+.SS Template examples
+Please note that the samples are split across multiple lines. A template MUST
+NOT actually be split across multiple lines.
+
+A template that resembles traditional syslogd file output:
+.sp
+.RS
+$template TraditionalFormat,"%timegenerated% %HOSTNAME%
+.sp 0
+%syslogtag%%msg:::drop-last-lf%\n"
+.RE
+
+A template that tells you a little more about the message:
+.sp
+.RS
+$template precise,"%syslogpriority%,%syslogfacility%,%timegenerated%,%HOSTNAME%,
+.sp 0
+%syslogtag%,%msg%\n"
+.RE
+
+A template for RFC 3164 format:
+.sp
+.RS
+$template RFC3164fmt,"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%msg%"
+.RE
+
+A template for the format traditionally used for user messages:
+.sp
+.RS
+$template usermsg," XXXX%syslogtag%%msg%\n\r"
+.RE
+
+And a template with the traditional wall-message format:
+.sp
+.RS
+$template wallmsg,"\\r\\n\\7Message from syslogd@%HOSTNAME% at %timegenerated%"
+.RE
+
+.B A template that can be used for writing to a database (please note the SQL template option)
+.sp
+.RS
+.ad l
+$template MySQLInsert,"insert iut, message, receivedat values
+('%iut%', '%msg:::UPPERCASE%', '%timegenerated:::date-mysql%')
+into systemevents\\r\\n", SQL
+
+NOTE 1: This template is embedded into core application under name
+.B StdDBFmt
+, so you don't need to define it.
+.sp
+NOTE 2: You have to have MySQL module installed to use this template.
+.ad
+.RE
+
+.SH OUTPUT CHANNELS
+
+Output Channels are a new concept first introduced in rsyslog 0.9.0. As of this writing,
+it is most likely that they will be replaced by something different in the future.
+So if you use them, be prepared to change you configuration file syntax when you upgrade
+to a later release.
+
+Output channels are defined via an $outchannel directive. It's syntax is as follows:
+.sp
+.RS
+.B $outchannel name,file-name,max-size,action-on-max-size
+.RE
+
+name is the name of the output channel (not the file), file-name is the file name to be
+written to, max-size the maximum allowed size and action-on-max-size a command to be issued
+when the max size is reached. This command always has exactly one parameter. The binary is
+that part of action-on-max-size before the first space, its parameter is everything behind
+that space.
+
+Keep in mind that $outchannel just defines a channel with "name". It does not activate it.
+To do so, you must use a selector line (see below). That selector line includes the channel
+name plus an $ sign in front of it. A sample might be:
+.sp
+.RS
+*.* $mychannel
+.RE
+
+.SH PROPERTY REPLACER
+The property replacer is a core component in rsyslogd's output system. A syslog message has
+a number of well-defined properties (see below). Each of this properties can be accessed and
+manipulated by the property replacer. With it, it is easy to use only part of a property value
+or manipulate the value, e.g. by converting all characters to lower case.
+
+.SS Accessing Properties
+Syslog message properties are used inside templates. They are accessed by putting them between
+percent signs. Properties can be modified by the property replacer. The full syntax is as follows:
+.sp
+.RS
+.B %propname:fromChar:toChar:options%
+.RE
+
+propname is the name of the property to access.
+.B It is case-sensitive.
+
+.SS Available Properties
+.TP
+.B msg
+the MSG part of the message (aka "the message" ;))
+.TP
+.B rawmsg
+the message exactly as it was received from the socket. Should be useful for debugging.
+.TP
+.B HOSTNAME
+hostname from the message
+.TP
+.B FROMHOST
+hostname of the system the message was received from (in a relay chain, this is the system immediately
+in front of us and not necessarily the original sender)
+.TP
+.B syslogtag
+TAG from the message
+.TP
+.B programname
+the "static" part of the tag, as defined by BSD syslogd. For example, when TAG is "named[12345]",
+programname is "named".
+.TP
+.B PRI
+PRI part of the message - undecoded (single value)
+.TP
+.B PRI-text
+the PRI part of the message in a textual form (e.g. "syslog.info")
+.TP
+.B IUT
+the monitorware InfoUnitType - used when talking to a MonitorWare backend (also for phpLogCon)
+.TP
+.B syslogfacility
+the facility from the message - in numerical form
+.TP
+.B syslogfacility-text
+the facility from the message - in text form
+.TP
+.B syslogseverity
+severity from the message - in numerical form
+.TP
+.B syslogseverity-text
+severity from the message - in text form
+.TP
+.B timegenerated
+timestamp when the message was RECEIVED. Always in high resolution
+.TP
+.B timereported
+timestamp from the message. Resolution depends on what was provided in the message (in most cases, only seconds)
+.TP
+.B TIMESTAMP
+alias for timereported
+.TP
+.B PROTOCOL-VERSION
+The contents of the PROTOCOL-VERSION field from IETF draft draft-ietf-syslog-protocol
+.TP
+.B STRUCTURED-DATA
+The contents of the STRUCTURED-DATA field from IETF draft draft-ietf-syslog-protocol
+.TP
+.B APP-NAME
+The contents of the APP-NAME field from IETF draft draft-ietf-syslog-protocol
+.TP
+.B PROCID
+The contents of the PROCID field from IETF draft draft-ietf-syslog-protocol
+.TP
+.B MSGID
+The contents of the MSGID field from IETF draft draft-ietf-syslog-protocol
+.TP
+.B $NOW
+The current date stamp in the format YYYY-MM-DD
+.TP
+.B $YEAR
+The current year (4-digit)
+.TP
+.B $MONTH
+The current month (2-digit)
+.TP
+.B $DAY
+The current day of the month (2-digit)
+.TP
+.B $HOUR
+The current hour in military (24 hour) time (2-digit)
+.TP
+.B $MINUTE
+The current minute (2-digit)
+
+.P
+Properties starting with a $-sign are so-called system properties. These do NOT stem from the
+message but are rather internally-generated.
+
+.SS Character Positions
+FromChar and toChar are used to build substrings. They specify the offset within the string that
+should be copied. Offset counting starts at 1, so if you need to obtain the first 2 characters of
+the message text, you can use this syntax: "%msg:1:2%". If you do not wish to specify from and to,
+but you want to specify options, you still need to include the colons. For example, if you would
+like to convert the full message text to lower case, use "%msg:::lowercase%". If you would like to
+extract from a position until the end of the string, you can place a dollar-sign ("$") in toChar
+(e.g. %msg:10:$%, which will extract from position 10 to the end of the string).
+
+There is also support for
+.B regular expressions.
+To use them, you need to place a "R" into FromChar.
+This tells rsyslog that a regular expression instead of position-based extraction is desired. The
+actual regular expression
+.B must
+then be provided in toChar. The regular expression must be followed
+by the string "--end". It denotes the end of the regular expression and will not become part of it.
+If you are using regular expressions, the property replacer will return the part of the property text
+that matches the regular expression. An example for a property replacer sequence with a regular
+expression is: "%msg:R:.*Sev:. \\(.*\\) \\[.*--end%"
+
+Also, extraction can be done based on so-called "fields". To do so, place a "F" into FromChar. A field
+in its current definition is anything that is delimited by a delimiter character. The delimiter by
+default is TAB (US-ASCII value 9). However, if can be changed to any other US-ASCII character by
+specifying a comma and the decimal US-ASCII value of the delimiter immediately after the "F". For example,
+to use comma (",") as a delimiter, use this field specifier: "F,44". If your syslog data is delimited,
+this is a quicker way to extract than via regular expressions (actually, a *much* quicker way). Field
+counting starts at 1. Field zero is accepted, but will always lead to a "field not found" error. The same
+happens if a field number higher than the number of fields in the property is requested. The field number
+must be placed in the "ToChar" parameter. An example where the 3rd field (delimited by TAB) from the msg
+property is extracted is as follows: "%msg:F:3%". The same example with semicolon as delimiter is
+"%msg:F,59:3%".
+
+Please note that the special characters "F" and "R" are case-sensitive. Only upper case works, lower case
+will return an error. There are no white spaces permitted inside the sequence (that will lead to error
+messages and will NOT provide the intended result).
+
+.SS Property Options
+Property options are case-insensitive. Currently, the following options are defined:
+.TP
+uppercase
+convert property to lowercase only
+.TP
+lowercase
+convert property text to uppercase only
+.TP
+drop-last-lf
+The last LF in the message (if any), is dropped. Especially useful for PIX.
+.TP
+date-mysql
+format as mysql date
+.TP
+date-rfc3164
+format as RFC 3164 date
+.TP
+date-rfc3339
+format as RFC 3339 date
+.TP
+escape-cc
+replace control characters (ASCII value 127 and values less then 32) with an escape sequence. The sequence is "#<charval>" where charval is the 3-digit decimal value of the control character. For example, a tabulator would be replaced by "#009".
+.TP
+space-cc
+replace control characters by spaces
+.TP
+drop-cc
+drop control characters - the resulting string will neither contain control characters, escape sequences nor any other replacement character like space.
+
+.SH QUEUED OPERATIONS
+Rsyslogd supports queued operations to handle offline outputs
+(like remote syslogd's or database servers being down). When running in
+queued mode, rsyslogd buffers messages to memory and optionally to disk
+(on an as-needed basis). Queues survive rsyslogd restarts.
+
+It is highly suggested to use remote forwarding and database writing
+in queued mode, only.
+
+To learn more about queued operations, see the html documentation.
+
+.SH FILES
+.PD 0
+.TP
+.I /etc/rsyslog.conf
+Configuration file for
+.B rsyslogd
+
+.SH SEE ALSO
+.BR rsyslogd (8),
+.BR logger (1),
+.BR syslog (3)
+
+The complete documentation can be found in the doc folder of the rsyslog distribution or online at
+
+.RS
+.B http://www.rsyslog.com/doc
+
+.RE
+Please note that the man page reflects only a subset of the configuration options. Be sure to read
+the html documentation for all features and details. This is especially vital if you plan to set
+up a more-then-extremely-simple system.
+
+.SH AUTHORS
+.B rsyslogd
+is taken from sysklogd sources, which have been heavily modified
+by Rainer Gerhards (rgerhards@adiscon.com) and others.
diff --git a/tools/rsyslogd.8 b/tools/rsyslogd.8
new file mode 100644
index 00000000..91f2016e
--- /dev/null
+++ b/tools/rsyslogd.8
@@ -0,0 +1,375 @@
+.\" Copyright 2004-2008 Rainer Gerhards and Adiscon for the rsyslog modifications
+.\" May be distributed under the GNU General Public License
+.\"
+.TH RSYSLOGD 8 "11 July 2008" "Version 3.18.0" "Linux System Administration"
+.SH NAME
+rsyslogd \- reliable and extended syslogd
+.SH SYNOPSIS
+.B rsyslogd
+.RB [ " \-4 " ]
+.RB [ " \-6 " ]
+.RB [ " \-A " ]
+.RB [ " \-d " ]
+.RB [ " \-f "
+.I config file
+]
+.br
+.RB [ " \-i "
+.I pid file
+]
+.RB [ " \-l "
+.I hostlist
+]
+.RB [ " \-n " ]
+.br
+.RB [ " \-q " ]
+.RB [ " \-Q " ]
+.RB [ " \-s "
+.I domainlist
+]
+.RB [ " \-v " ]
+.RB [ " \-w " ]
+.RB [ " \-x " ]
+.LP
+.SH DESCRIPTION
+.B Rsyslogd
+is a system utility providing support for message logging.
+Support of both internet and
+unix domain sockets enables this utility to support both local
+and remote logging.
+
+.B Note that this version of rsyslog ships with extensive documentation in html format.
+This is provided in the ./doc subdirectory and probably
+in a separate package if you installed rsyslog via a packaging system.
+To use rsyslog's advanced features, you
+.B need
+to look at the html documentation, because the man pages only cover
+basic aspects of operation.
+.B For details and configuration examples, see the rsyslog.conf (5)
+.B man page and the online documentation at http://www.rsyslog.com/doc
+
+.BR Rsyslogd (8)
+is derived from the sysklogd package which in turn is derived from the
+stock BSD sources.
+
+.B Rsyslogd
+provides a kind of logging that many modern programs use. Every logged
+message contains at least a time and a hostname field, normally a
+program name field, too, but that depends on how trusty the logging
+program is. The rsyslog package supports free definition of output formats
+via templates. It also supports precise timestamps and writing directly
+to databases. If the database option is used, tools like phpLogCon can
+be used to view the log data.
+
+While the
+.B rsyslogd
+sources have been heavily modified a couple of notes
+are in order. First of all there has been a systematic attempt to
+ensure that rsyslogd follows its default, standard BSD behavior. Of course,
+some configuration file changes are necessary in order to support the
+template system. However, rsyslogd should be able to use a standard
+syslog.conf and act like the orginal syslogd. However, an original syslogd
+will not work correctly with a rsyslog-enhanced configuration file. At
+best, it will generate funny looking file names.
+The second important concept to note is that this version of rsyslogd
+interacts transparently with the version of syslog found in the
+standard libraries. If a binary linked to the standard shared
+libraries fails to function correctly we would like an example of the
+anomalous behavior.
+
+The main configuration file
+.I /etc/rsyslog.conf
+or an alternative file, given with the
+.B "\-f"
+option, is read at startup. Any lines that begin with the hash mark
+(``#'') and empty lines are ignored. If an error occurs during parsing
+the error element is ignored. It is tried to parse the rest of the line.
+
+.LP
+.SH OPTIONS
+.B Note that in version 3 of rsyslog a number of command line options
+.B have been deprecated and replaced with config file directives. The
+.B -c option controls the backward compatibility mode in use.
+.TP
+.BI "\-A"
+When sending UDP messages, there are potentially multiple pathes to
+the target destination. By default,
+.B rsyslogd
+only sends to the first target it can successfully send to. If -A
+is given, messages are sent to all targets. This may improve
+reliability, but may also cause message duplicaton. This option
+should enabled only if it is fully understood.
+.TP
+.BI "\-4"
+Causes
+.B rsyslogd
+to listen to IPv4 addresses only.
+If neither -4 nor -6 is given,
+.B rsyslogd
+listens to all configured addresses of the system.
+.TP
+.BI "\-6"
+Causes
+.B rsyslogd
+to listen to IPv6 addresses only.
+If neither -4 nor -6 is given,
+.B rsyslogd
+listens to all configured addresses of the system.
+.TP
+.BI "\-c " "version"
+Selects the desired backward compatibility mode. It must always be the
+first option on the command line, as it influences processing of the
+other options. To use the rsyslog v3 native interface, specify -c3. To
+use compatibility mode , either do not use -c at all or use
+-c<version> where
+.IR version
+is the rsyslog version that it shall be
+compatible with. Using -c0 tells rsyslog to be command-line compatible
+to sysklogd, which is the default if -c is not given.
+.B Please note that rsyslogd issues warning messages if the -c3
+.B command line option is not given.
+This is to alert you that your are running in compatibility
+mode. Compatibility mode interfers with you rsyslog.conf commands and
+may cause some undesired side-effects. It is meant to be used with a
+plain old rsyslog.conf - if you use new features, things become
+messy. So the best advice is to work through this document, convert
+your options and config file and then use rsyslog in native mode. In
+order to aid you in this process, rsyslog logs every
+compatibility-mode config file directive it has generated. So you can
+simply copy them from your logfile and paste them to the config.
+.TP
+.B "\-d"
+Turns on debug mode. Using this the daemon will not proceed a
+.BR fork (2)
+to set itself in the background, but opposite to that stay in the
+foreground and write much debug information on the current tty. See the
+DEBUGGING section for more information.
+.TP
+.BI "\-f " "config file"
+Specify an alternative configuration file instead of
+.IR /etc/rsyslog.conf ","
+which is the default.
+.TP
+.BI "\-i " "pid file"
+Specify an alternative pid file instead of the default one.
+This option must be used if multiple instances of rsyslogd should
+run on a single machine.
+.TP
+.BI "\-l " "hostlist"
+Specify a hostname that should be logged only with its simple hostname
+and not the fqdn. Multiple hosts may be specified using the colon
+(``:'') separator.
+.TP
+.B "\-n"
+Avoid auto-backgrounding. This is needed especially if the
+.B rsyslogd
+is started and controlled by
+.BR init (8).
+.TP
+.BI "\-q " "add hostname if DNS fails during ACL processing"
+During ACL processing, hostnames are resolved to IP addreses for
+performance reasons. If DNS fails during that process, the hostname
+is added as wildcard text, which results in proper, but somewhat
+slower operation once DNS is up again.
+.TP
+.BI "\-Q " "do not resolve hostnames during ACL processing"
+Do not resolve hostnames to IP addresses during ACL processing.
+.TP
+.BI "\-s " "domainlist"
+Specify a domainname that should be stripped off before
+logging. Multiple domains may be specified using the colon (``:'')
+separator.
+Please be advised that no sub-domains may be specified but only entire
+domains. For example if
+.B "\-s north.de"
+is specified and the host logging resolves to satu.infodrom.north.de
+no domain would be cut, you will have to specify two domains like:
+.BR "\-s north.de:infodrom.north.de" .
+.TP
+.B "\-v"
+Print version and exit.
+.TP
+.B "\-w"
+Supress warnings issued when messages are received from non-authorized
+machines (those, that are in no AllowedSender list).
+.TP
+.B "\-x"
+Disable DNS for remote messages.
+.LP
+.SH SIGNALS
+.B Rsyslogd
+reacts to a set of signals. You may easily send a signal to
+.B rsyslogd
+using the following:
+.IP
+.nf
+kill -SIGNAL $(cat /var/run/syslogd.pid)
+.fi
+.PP
+Note that -SIGNAL must be replaced with the actual signal
+you are trying to send, e.g. with HUP. So it then becomes:
+.IP
+.nf
+kill -HUP $(cat /var/run/syslogd.pid)
+.fi
+.PP
+.TP
+.B HUP
+This lets
+.B rsyslogd
+perform a re-initialization. All open files are closed, the
+configuration file (default is
+.IR /etc/rsyslog.conf ")"
+will be reread and the
+.BR rsyslog (3)
+facility is started again.
+.TP
+.B TERM ", " INT ", " QUIT
+.B Rsyslogd
+will die.
+.TP
+.B USR1
+Switch debugging on/off. This option can only be used if
+.B rsyslogd
+is started with the
+.B "\-d"
+debug option.
+.TP
+.B CHLD
+Wait for childs if some were born, because of wall'ing messages.
+.LP
+.SH SECURITY THREATS
+There is the potential for the rsyslogd daemon to be
+used as a conduit for a denial of service attack.
+A rogue program(mer) could very easily flood the rsyslogd daemon with
+syslog messages resulting in the log files consuming all the remaining
+space on the filesystem. Activating logging over the inet domain
+sockets will of course expose a system to risks outside of programs or
+individuals on the local machine.
+
+There are a number of methods of protecting a machine:
+.IP 1.
+Implement kernel firewalling to limit which hosts or networks have
+access to the 514/UDP socket.
+.IP 2.
+Logging can be directed to an isolated or non-root filesystem which,
+if filled, will not impair the machine.
+.IP 3.
+The ext2 filesystem can be used which can be configured to limit a
+certain percentage of a filesystem to usage by root only. \fBNOTE\fP
+that this will require rsyslogd to be run as a non-root process.
+\fBALSO NOTE\fP that this will prevent usage of remote logging on the default port since
+rsyslogd will be unable to bind to the 514/UDP socket.
+.IP 4.
+Disabling inet domain sockets will limit risk to the local machine.
+.SS Message replay and spoofing
+If remote logging is enabled, messages can easily be spoofed and replayed.
+As the messages are transmitted in clear-text, an attacker might use
+the information obtained from the packets for malicious things. Also, an
+attacker might replay recorded messages or spoof a sender's IP address,
+which could lead to a wrong perception of system activity. These can
+be prevented by using GSS-API authentication and encryption. Be sure
+to think about syslog network security before enabling it.
+.LP
+.SH DEBUGGING
+When debugging is turned on using
+.B "\-d"
+option then
+.B rsyslogd
+will be very verbose by writing much of what it does on stdout.
+.SH FILES
+.PD 0
+.TP
+.I /etc/rsyslog.conf
+Configuration file for
+.BR rsyslogd .
+See
+.BR rsyslog.conf (5)
+for exact information.
+.TP
+.I /dev/log
+The Unix domain socket to from where local syslog messages are read.
+.TP
+.I /var/run/rsyslogd.pid
+The file containing the process id of
+.BR rsyslogd .
+.TP
+.I prefix/lib/rsyslog
+Default directory for
+.B rsyslogd
+modules. The
+.I prefix
+is specified during compilation (e.g. /usr/local).
+.SH ENVIRONMENT
+.TP
+.B RSYSLOG_DEBUG
+Controls runtime debug support.It contains an option string with the
+following options possible (all are case insensitive):
+
+.RS
+.IP LogFuncFlow
+Print out the logical flow of functions (entering and exiting them)
+.IP FileTrace
+Specifies which files to trace LogFuncFlow. If not set (the
+default), a LogFuncFlow trace is provided for all files. Set to
+limit it to the files specified.FileTrace may be specified multiple
+times, one file each (e.g. export RSYSLOG_DEBUG="LogFuncFlow
+FileTrace=vm.c FileTrace=expr.c"
+.IP PrintFuncDB
+Print the content of the debug function database whenever debug
+information is printed (e.g. abort case)!
+.IP PrintAllDebugInfoOnExit
+Print all debug information immediately before rsyslogd exits
+(currently not implemented!)
+.IP PrintMutexAction
+Print mutex action as it happens. Useful for finding deadlocks and
+such.
+.IP NoLogTimeStamp
+Do not prefix log lines with a timestamp (default is to do that).
+.IP NoStdOut
+Do not emit debug messages to stdout. If RSYSLOG_DEBUGLOG is not
+set, this means no messages will be displayed at all.
+.IP Help
+Display a very short list of commands - hopefully a life saver if
+you can't access the documentation...
+.RE
+
+.TP
+.B RSYSLOG_DEBUGLOG
+If set, writes (allmost) all debug message to the specified log file
+in addition to stdout.
+.TP
+.B RSYSLOG_MODDIR
+Provides the default directory in which loadable modules reside.
+.PD
+.SH BUGS
+Please review the file BUGS for up-to-date information on known
+bugs and annouyances.
+.SH Further Information
+Please visit
+.BR http://www.rsyslog.com/doc
+for additional information, tutorials and a support forum.
+.SH SEE ALSO
+.BR rsyslog.conf (5),
+.BR logger (1),
+.BR syslog (2),
+.BR syslog (3),
+.BR services (5),
+.BR savelog (8)
+.LP
+.SH COLLABORATORS
+.B rsyslogd
+is derived from sysklogd sources, which in turn was taken from
+the BSD sources. Special thanks to Greg Wettstein (greg@wind.enjellic.com)
+and Martin Schulze (joey@linux.de) for the fine sysklogd package.
+
+.PD 0
+.TP
+Rainer Gerhards
+.TP
+Adiscon GmbH
+.TP
+Grossrinderfeld, Germany
+.TP
+rgerhards@adiscon.com
+.PD
diff --git a/tools/syslogd.c b/tools/syslogd.c
new file mode 100644
index 00000000..5edf92ce
--- /dev/null
+++ b/tools/syslogd.c
@@ -0,0 +1,3416 @@
+/**
+ * \brief This is the main file of the rsyslogd daemon.
+ *
+ * Please visit the rsyslog project at
+ *
+ * http://www.rsyslog.com
+ *
+ * to learn more about it and discuss any questions you may have.
+ *
+ * rsyslog had initially been forked from the sysklogd project.
+ * I would like to express my thanks to the developers of the sysklogd
+ * package - without it, I would have had a much harder start...
+ *
+ * Please note that while rsyslog started from the sysklogd code base,
+ * it nowadays has almost nothing left in common with it. Allmost all
+ * parts of the code have been rewritten.
+ *
+ * This Project was intiated and is maintained by
+ * Rainer Gerhards <rgerhards@hq.adiscon.com>. See
+ * AUTHORS to learn who helped make it become a reality.
+ *
+ * If you have questions about rsyslogd in general, please email
+ * info@adiscon.com. To learn more about rsyslogd, please visit
+ * http://www.rsyslog.com.
+ *
+ * \author Rainer Gerhards <rgerhards@adiscon.com>
+ * \date 2003-10-17
+ * Some initial modifications on the sysklogd package to support
+ * liblogging. These have actually not yet been merged to the
+ * source you see currently (but they hopefully will)
+ *
+ * \date 2004-10-28
+ * Restarted the modifications of sysklogd. This time, we
+ * focus on a simpler approach first. The initial goal is to
+ * provide MySQL database support (so that syslogd can log
+ * to the database).
+ *
+ * rsyslog - An Enhanced syslogd Replacement.
+ * Copyright 2003-2008 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"
+
+/* change the following setting to e.g. 32768 if you would like to
+ * support large message sizes for IHE (32k is the current maximum
+ * needed for IHE). I was initially tempted to increase it to 32k,
+ * but there is a large memory footprint with the current
+ * implementation in rsyslog. This will change as the processing
+ * changes, but I have re-set it to 1k, because the vast majority
+ * of messages is below that and the memory savings is huge, at
+ * least compared to the overall memory footprint.
+ *
+ * If you intend to receive Windows Event Log data (e.g. via
+ * EventReporter - www.eventreporter.com), you might want to
+ * increase this number to an even higher value, as event
+ * log messages can be very lengthy.
+ * rgerhards, 2005-07-05
+ *
+ * during my recent testing, it showed that 4k seems to be
+ * the typical maximum for UDP based syslog. This is a IP stack
+ * restriction. Not always ... but very often. If you go beyond
+ * that value, be sure to test that rsyslogd actually does what
+ * you think it should do ;) Also, it is a good idea to check the
+ * doc set for anything on IHE - it most probably has information on
+ * message sizes.
+ * rgerhards, 2005-08-05
+ *
+ * I have increased the default message size to 2048 to be in sync
+ * with recent IETF syslog standardization efforts.
+ * rgerhards, 2006-11-30
+ */
+#define DEFUPRI (LOG_USER|LOG_NOTICE)
+#define TIMERINTVL 30 /* interval for checking flush, mark */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stddef.h>
+#include <ctype.h>
+#include <limits.h>
+#define GNU_SOURCE
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+#include <assert.h>
+#include <libgen.h>
+
+#ifdef __sun
+# include <errno.h>
+#else
+# include <sys/errno.h>
+#endif
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sys/file.h>
+
+#if HAVE_SYS_TIMESPEC_H
+# include <sys/timespec.h>
+#endif
+
+#if HAVE_SYS_STAT_H
+# include <sys/stat.h>
+#endif
+
+#include <signal.h>
+
+#if HAVE_PATHS_H
+#include <paths.h>
+#endif
+
+#ifdef USE_NETZIP
+#include <zlib.h>
+#endif
+
+#include <netdb.h>
+
+#include "pidfile.h"
+#include "srUtils.h"
+#include "stringbuf.h"
+#include "syslogd-types.h"
+#include "template.h"
+#include "outchannel.h"
+#include "syslogd.h"
+
+#include "msg.h"
+#include "modules.h"
+#include "action.h"
+#include "iminternal.h"
+#include "cfsysline.h"
+#include "omshell.h"
+#include "omusrmsg.h"
+#include "omfwd.h"
+#include "omfile.h"
+#include "omdiscard.h"
+#include "threads.h"
+#include "queue.h"
+#include "stream.h"
+#include "conf.h"
+#include "vm.h"
+#include "errmsg.h"
+#include "datetime.h"
+#include "sysvar.h"
+
+/* definitions for objects we access */
+DEFobjCurrIf(obj)
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(conf)
+DEFobjCurrIf(expr)
+DEFobjCurrIf(vm)
+DEFobjCurrIf(var)
+DEFobjCurrIf(module)
+DEFobjCurrIf(errmsg)
+DEFobjCurrIf(net) /* TODO: make go away! */
+
+
+/* forward definitions */
+static rsRetVal GlobalClassExit(void);
+
+/* We define our own set of syslog defintions so that we
+ * do not need to rely on (possibly different) implementations.
+ * 2007-07-19 rgerhards
+ */
+/* missing definitions for solaris
+ * 2006-02-16 Rger
+ */
+#ifdef __sun
+# define LOG_AUTHPRIV LOG_AUTH
+#endif
+#define INTERNAL_NOPRI 0x10 /* the "no priority" priority */
+#define LOG_FTP (11<<3) /* ftp daemon */
+
+
+#ifndef UTMP_FILE
+#ifdef UTMP_FILENAME
+#define UTMP_FILE UTMP_FILENAME
+#else
+#ifdef _PATH_UTMP
+#define UTMP_FILE _PATH_UTMP
+#else
+#define UTMP_FILE "/etc/utmp"
+#endif
+#endif
+#endif
+
+#ifndef _PATH_LOGCONF
+#define _PATH_LOGCONF "/etc/rsyslog.conf"
+#endif
+
+#ifndef _PATH_MODDIR
+#define _PATH_MODDIR "/lib/rsyslog/"
+#endif
+
+#if defined(SYSLOGD_PIDNAME)
+# undef _PATH_LOGPID
+# if defined(FSSTND)
+# ifdef OS_BSD
+# define _PATH_VARRUN "/var/run/"
+# endif
+# if defined(__sun) || defined(__hpux)
+# define _PATH_VARRUN "/var/run/"
+# endif
+# define _PATH_LOGPID _PATH_VARRUN SYSLOGD_PIDNAME
+# else
+# define _PATH_LOGPID "/etc/" SYSLOGD_PIDNAME
+# endif
+#else
+# ifndef _PATH_LOGPID
+# if defined(__sun) || defined(__hpux)
+# define _PATH_VARRUN "/var/run/"
+# endif
+# if defined(FSSTND)
+# define _PATH_LOGPID _PATH_VARRUN "rsyslogd.pid"
+# else
+# define _PATH_LOGPID "/etc/rsyslogd.pid"
+# endif
+# endif
+#endif
+
+#ifndef _PATH_DEV
+# define _PATH_DEV "/dev/"
+#endif
+
+#ifndef _PATH_TTY
+#define _PATH_TTY "/dev/tty"
+#endif
+
+static uchar *ConfFile = (uchar*) _PATH_LOGCONF; /* read-only after startup */
+static char *PidFile = _PATH_LOGPID; /* read-only after startup */
+
+static pid_t myPid; /* our pid for use in self-generated messages, e.g. on startup */
+/* mypid is read-only after the initial fork() */
+static int restart = 0; /* do restart (config read) - multithread safe */
+
+static int bParseHOSTNAMEandTAG = 1; /* global config var: should the hostname and tag be
+ * parsed inside message - rgerhards, 2006-03-13 */
+static int bFinished = 0; /* used by termination signal handler, read-only except there
+ * is either 0 or the number of the signal that requested the
+ * termination.
+ */
+
+/* Intervals at which we flush out "message repeated" messages,
+ * in seconds after previous message is logged. After each flush,
+ * we move to the next interval until we reach the largest.
+ * TODO: this shall go into action object! -- rgerhards, 2008-01-29
+ */
+int repeatinterval[2] = { 30, 60 }; /* # of secs before flush */
+
+#define LIST_DELIMITER ':' /* delimiter between two hosts */
+
+struct filed *Files = NULL; /* read-only after init() (but beware of sigusr1!) */
+
+static pid_t ppid; /* This is a quick and dirty hack used for spliting main/startup thread */
+
+typedef struct legacyOptsLL_s {
+ uchar *line;
+ struct legacyOptsLL_s *next;
+} legacyOptsLL_t;
+legacyOptsLL_t *pLegacyOptsLL = NULL;
+
+/* global variables for config file state */
+static int bDropTrailingLF = 1; /* drop trailing LF's on reception? */
+int iCompatibilityMode = 0; /* version we should be compatible with; 0 means sysklogd. It is
+ the default, so if no -c<n> option is given, we make ourselvs
+ as compatible to sysklogd as possible. */
+static int bDebugPrintTemplateList = 1;/* output template list in debug mode? */
+static int bDebugPrintCfSysLineHandlerList = 1;/* output cfsyslinehandler list in debug mode? */
+static int bDebugPrintModuleList = 1;/* output module list in debug mode? */
+static uchar cCCEscapeChar = '\\';/* character to be used to start an escape sequence for control chars */
+static int bEscapeCCOnRcv = 1; /* escape control characters on reception: 0 - no, 1 - yes */
+int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */
+int bActExecWhenPrevSusp; /* execute action only when previous one was suspended? */
+int iActExecOnceInterval = 0; /* execute action once every nn seconds */
+/* end global config file state variables */
+
+int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */
+int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */
+static int NoFork = 0; /* don't fork - don't run in daemon mode - read-only after startup */
+static int bHaveMainQueue = 0;/* set to 1 if the main queue - in queueing mode - is available
+ * If the main queue is either not yet ready or not running in
+ * queueing mode (mode DIRECT!), then this is set to 0.
+ */
+
+extern int errno;
+
+/* main message queue and its configuration parameters */
+static queue_t *pMsgQueue = NULL; /* the main message queue */
+static int iMainMsgQueueSize = 10000; /* size of the main message queue above */
+static int iMainMsgQHighWtrMark = 8000; /* high water mark for disk-assisted queues */
+static int iMainMsgQLowWtrMark = 2000; /* low water mark for disk-assisted queues */
+static int iMainMsgQDiscardMark = 9800; /* begin to discard messages */
+static int iMainMsgQDiscardSeverity = 8; /* by default, discard nothing to prevent unintentional loss */
+static int iMainMsgQueueNumWorkers = 1; /* number of worker threads for the mm queue above */
+static queueType_t MainMsgQueType = QUEUETYPE_FIXED_ARRAY; /* type of the main message queue above */
+static uchar *pszMainMsgQFName = NULL; /* prefix for the main message queue file */
+static int64 iMainMsgQueMaxFileSize = 1024*1024;
+static int iMainMsgQPersistUpdCnt = 0; /* persist queue info every n updates */
+static int iMainMsgQtoQShutdown = 0; /* queue shutdown */
+static int iMainMsgQtoActShutdown = 1000; /* action shutdown (in phase 2) */
+static int iMainMsgQtoEnq = 2000; /* timeout for queue enque */
+static int iMainMsgQtoWrkShutdown = 60000; /* timeout for worker thread shutdown */
+static int iMainMsgQWrkMinMsgs = 100; /* minimum messages per worker needed to start a new one */
+static int iMainMsgQDeqSlowdown = 0; /* dequeue slowdown (simple rate limiting) */
+static int64 iMainMsgQueMaxDiskSpace = 0; /* max disk space allocated 0 ==> unlimited */
+static int bMainMsgQSaveOnShutdown = 1; /* save queue on shutdown (when DA enabled)? */
+static int iMainMsgQueueDeqtWinFromHr = 0; /* hour begin of time frame when queue is to be dequeued */
+static int iMainMsgQueueDeqtWinToHr = 25; /* hour begin of time frame when queue is to be dequeued */
+
+
+/* support for simple textual representation of FIOP names
+ * rgerhards, 2005-09-27
+ */
+static char* getFIOPName(unsigned iFIOP)
+{
+ char *pRet;
+ switch(iFIOP) {
+ case FIOP_CONTAINS:
+ pRet = "contains";
+ break;
+ case FIOP_ISEQUAL:
+ pRet = "isequal";
+ break;
+ case FIOP_STARTSWITH:
+ pRet = "startswith";
+ break;
+ case FIOP_REGEX:
+ pRet = "regex";
+ break;
+ default:
+ pRet = "NOP";
+ break;
+ }
+ return pRet;
+}
+
+
+/* Reset config variables to default values.
+ * rgerhards, 2007-07-17
+ */
+static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
+{
+ cCCEscapeChar = '#';
+ bActExecWhenPrevSusp = 0;
+ iActExecOnceInterval = 0;
+ bDebugPrintTemplateList = 1;
+ bDebugPrintCfSysLineHandlerList = 1;
+ bDebugPrintModuleList = 1;
+ bEscapeCCOnRcv = 1; /* default is to escape control characters */
+ bReduceRepeatMsgs = 0;
+ if(pszMainMsgQFName != NULL) {
+ free(pszMainMsgQFName);
+ pszMainMsgQFName = NULL;
+ }
+ iMainMsgQueueSize = 10000;
+ iMainMsgQHighWtrMark = 8000;
+ iMainMsgQLowWtrMark = 2000;
+ iMainMsgQDiscardMark = 9800;
+ iMainMsgQDiscardSeverity = 4;
+ iMainMsgQueMaxFileSize = 1024 * 1024;
+ iMainMsgQueueNumWorkers = 1;
+ iMainMsgQPersistUpdCnt = 0;
+ iMainMsgQtoQShutdown = 0;
+ iMainMsgQtoActShutdown = 1000;
+ iMainMsgQtoEnq = 2000;
+ iMainMsgQtoWrkShutdown = 60000;
+ iMainMsgQWrkMinMsgs = 100;
+ iMainMsgQDeqSlowdown = 0;
+ bMainMsgQSaveOnShutdown = 1;
+ MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ iMainMsgQueMaxDiskSpace = 0;
+ glbliActionResumeRetryCount = 0;
+
+ return RS_RET_OK;
+}
+
+
+/* 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%'\nrawmsg: '%rawmsg%'\n\n\"";
+static uchar template_SyslogProtocol23Format[] = "\"<%PRI%>1 %TIMESTAMP:::date-rfc3339% %HOSTNAME% %APP-NAME% %PROCID% %MSGID% %STRUCTURED-DATA% %msg%\n\"";
+static uchar template_TraditionalFileFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n\"";
+static uchar template_FileFormat[] = "\"%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag%%msg:::sp-if-no-1st-sp%%msg:::drop-last-lf%\n\"";
+static uchar template_WallFmt[] = "\"\r\n\7Message from syslogd@%HOSTNAME% at %timegenerated% ...\r\n %syslogtag%%msg%\n\r\"";
+static uchar template_ForwardFormat[] = "\"<%PRI%>%TIMESTAMP:::date-rfc3339% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%\"";
+static uchar template_TraditionalForwardFormat[] = "\"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag:1:32%%msg:::sp-if-no-1st-sp%%msg%\"";
+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";
+/* end template */
+
+
+/* up to the next comment, prototypes that should be removed by reordering */
+/* Function prototypes. */
+static char **crunch_list(char *list);
+static void reapchild();
+static void debug_switch();
+static void sighup_handler();
+static void freeSelectors(void);
+static void processImInternal(void);
+
+
+static int usage(void)
+{
+ fprintf(stderr, "usage: rsyslogd [-cversion] [-46AdnqQvwx] [-lhostlist] [-sdomainlist]\n"
+ " [-fconffile] [-ipidfile]\n"
+ "To run rsyslogd in native mode, use \"rsyslogd -c3 <other options>\"\n\n"
+ "For further information see http://www.rsyslog.com/doc\n");
+ exit(1); /* "good" exit - done to terminate usage() */
+}
+
+
+/* function to destruct a selector_t object
+ * rgerhards, 2007-08-01
+ */
+rsRetVal
+selectorDestruct(void *pVal)
+{
+ selector_t *pThis = (selector_t *) pVal;
+
+ assert(pThis != NULL);
+
+ if(pThis->pCSHostnameComp != NULL)
+ rsCStrDestruct(&pThis->pCSHostnameComp);
+ if(pThis->pCSProgNameComp != NULL)
+ rsCStrDestruct(&pThis->pCSProgNameComp);
+
+ if(pThis->f_filter_type == FILTER_PROP) {
+ if(pThis->f_filterData.prop.pCSPropName != NULL)
+ rsCStrDestruct(&pThis->f_filterData.prop.pCSPropName);
+ if(pThis->f_filterData.prop.pCSCompValue != NULL)
+ rsCStrDestruct(&pThis->f_filterData.prop.pCSCompValue);
+ } else if(pThis->f_filter_type == FILTER_EXPR) {
+ if(pThis->f_filterData.f_expr != NULL)
+ expr.Destruct(&pThis->f_filterData.f_expr);
+ }
+
+ llDestroy(&pThis->llActList);
+ free(pThis);
+
+ return RS_RET_OK;
+}
+
+
+/* function to construct a selector_t object
+ * rgerhards, 2007-08-01
+ */
+rsRetVal
+selectorConstruct(selector_t **ppThis)
+{
+ DEFiRet;
+ selector_t *pThis;
+
+ assert(ppThis != NULL);
+
+ if((pThis = (selector_t*) calloc(1, sizeof(selector_t))) == NULL) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ CHKiRet(llInit(&pThis->llActList, actionDestruct, NULL, NULL));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ if(pThis != NULL) {
+ selectorDestruct(pThis);
+ }
+ }
+ *ppThis = pThis;
+ RETiRet;
+}
+
+
+/* rgerhards, 2005-10-24: crunch_list is called only during option processing. So
+ * it is never called once rsyslogd is running (not even when HUPed). This code
+ * contains some exits, but they are considered safe because they only happen
+ * during startup. Anyhow, when we review the code here, we might want to
+ * reconsider the exit()s.
+ */
+static char **crunch_list(char *list)
+{
+ int count, i;
+ char *p, *q;
+ char **result = NULL;
+
+ p = list;
+
+ /* strip off trailing delimiters */
+ while (p[strlen(p)-1] == LIST_DELIMITER) {
+ count--;
+ p[strlen(p)-1] = '\0';
+ }
+ /* cut off leading delimiters */
+ while (p[0] == LIST_DELIMITER) {
+ count--;
+ p++;
+ }
+
+ /* count delimiters to calculate elements */
+ for (count=i=0; p[i]; i++)
+ if (p[i] == LIST_DELIMITER) count++;
+
+ if ((result = (char **)malloc(sizeof(char *) * (count+2))) == NULL) {
+ printf ("Sorry, can't get enough memory, exiting.\n");
+ exit(0); /* safe exit, because only called during startup */
+ }
+
+ /*
+ * We now can assume that the first and last
+ * characters are different from any delimiters,
+ * so we don't have to care about this.
+ */
+ count = 0;
+ while ((q=strchr(p, LIST_DELIMITER))) {
+ result[count] = (char *) malloc((q - p + 1) * sizeof(char));
+ if (result[count] == NULL) {
+ printf ("Sorry, can't get enough memory, exiting.\n");
+ exit(0); /* safe exit, because only called during startup */
+ }
+ strncpy(result[count], p, q - p);
+ result[count][q - p] = '\0';
+ p = q; p++;
+ count++;
+ }
+ if ((result[count] = \
+ (char *)malloc(sizeof(char) * strlen(p) + 1)) == NULL) {
+ printf ("Sorry, can't get enough memory, exiting.\n");
+ exit(0); /* safe exit, because only called during startup */
+ }
+ strcpy(result[count],p);
+ result[++count] = NULL;
+
+#if 0
+ count=0;
+ while (result[count])
+ dbgprintf("#%d: %s\n", count, StripDomains[count++]);
+#endif
+ return result;
+}
+
+
+void untty(void)
+#ifdef HAVE_SETSID
+{
+ if ( !Debug ) {
+ setsid();
+ }
+ return;
+}
+#else
+{
+ int i;
+
+ if ( !Debug ) {
+ i = open(_PATH_TTY, O_RDWR);
+ if (i >= 0) {
+# if !defined(__hpux)
+ (void) ioctl(i, (int) TIOCNOTTY, (char *)0);
+# else
+ /* TODO: we need to implement something for HP UX! -- rgerhards, 2008-03-04 */
+ /* actually, HP UX should have setsid, so the code directly above should
+ * trigger. So the actual question is why it doesn't do that...
+ */
+# endif
+ (void) close(i);
+ }
+ }
+}
+#endif
+
+
+/* Take a raw input line, decode the message, and print the message
+ * on the appropriate log files.
+ * rgerhards 2004-11-08: Please note
+ * that this function does only a partial decoding. At best, it splits
+ * the PRI part. No further decode happens. The rest is done in
+ * logmsg().
+ * Added the iSource parameter so that we know if we have to parse
+ * HOSTNAME or not. rgerhards 2004-11-16.
+ * changed parameter iSource to bParseHost. For details, see comment in
+ * printchopped(). rgerhards 2005-10-06
+ * rgerhards: 2008-03-06: added "flags" to allow an input module to specify
+ * flags, most importantly to request ignoring the messages' timestamp.
+ *
+ * rgerhards, 2008-03-19:
+ * I added an additional calling parameter to permit specifying the flow
+ * control capability of the source.
+ *
+ * rgerhards, 2008-05-16:
+ * I added an additional calling parameter (hnameIP) to enable specifying the IP
+ * of a remote host.
+ */
+rsRetVal printline(uchar *hname, uchar *hnameIP, uchar *msg, int bParseHost, int flags, flowControl_t flowCtlType)
+{
+ DEFiRet;
+ register uchar *p;
+ int pri;
+ msg_t *pMsg;
+
+ /* Now it is time to create the message object (rgerhards) */
+ CHKiRet(msgConstruct(&pMsg));
+ MsgSetFlowControlType(pMsg, flowCtlType);
+ MsgSetRawMsg(pMsg, (char*)msg);
+
+ pMsg->bParseHOSTNAME = bParseHost;
+ /* test for special codes */
+ pri = DEFUPRI;
+ p = msg;
+ if (*p == '<') {
+ pri = 0;
+ while (isdigit((int) *++p))
+ {
+ pri = 10 * pri + (*p - '0');
+ }
+ if (*p == '>')
+ ++p;
+ }
+ if (pri &~ (LOG_FACMASK|LOG_PRIMASK))
+ pri = DEFUPRI;
+ pMsg->iFacility = LOG_FAC(pri);
+ pMsg->iSeverity = LOG_PRI(pri);
+
+ /* Now we look at the HOSTNAME. That is a bit complicated...
+ * If we have a locally received message, it does NOT
+ * contain any hostname information in the message itself.
+ * As such, the HOSTNAME is the same as the system that
+ * the message was received from (that, for obvious reasons,
+ * being the local host). rgerhards 2004-11-16
+ */
+ if(bParseHost == 0)
+ MsgSetHOSTNAME(pMsg, (char*)hname);
+ MsgSetRcvFrom(pMsg, (char*)hname);
+ CHKiRet(MsgSetRcvFromIP(pMsg, hnameIP));
+
+ /* rgerhards 2004-11-19: well, well... we've now seen that we
+ * have the "hostname problem" also with the traditional Unix
+ * message. As we like to emulate it, we need to add the hostname
+ * to it.
+ */
+ if(MsgSetUxTradMsg(pMsg, (char*)p) != 0)
+ ABORT_FINALIZE(RS_RET_ERR);
+
+ logmsg(pMsg, flags);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This takes a received message that must be decoded and submits it to
+ * the main message queue. The function calls the necessary parser.
+ *
+ * rgerhards, 2006-11-30: I have greatly changed this function. Formerly,
+ * it tried to reassemble multi-part messages, which is a legacy stock
+ * sysklogd concept. In essence, that was that messages not ending with
+ * \0 were glued together. As far as I can see, this is a sysklogd
+ * specific feature and, from looking at the code, seems to be used
+ * pretty seldom (if at all). I remove this now, not the least because it is totally
+ * incompatible with upcoming IETF syslog standards. If you experience
+ * strange behaviour with messages beeing split across multiple lines,
+ * this function here might be the place to look at.
+ *
+ * Some previous history worth noting:
+ * I added the "iSource" parameter. This is needed to distinguish between
+ * messages that have a hostname in them (received from the internet) and
+ * those that do not have (most prominently /dev/log). rgerhards 2004-11-16
+ * And now I removed the "iSource" parameter and changed it to be "bParseHost",
+ * because all that it actually controls is whether the host is parsed or not.
+ * For rfc3195 support, we needed to modify the algo for host parsing, so we can
+ * no longer rely just on the source (rfc3195d forwarded messages arrive via
+ * unix domain sockets but contain the hostname). rgerhards, 2005-10-06
+ *
+ * rgerhards, 2008-02-18:
+ * This function was previously called "printchopped"() and has been renamed
+ * as part of the effort to create a clean internal message submission interface.
+ * It also has been adopted to our usual calling interface, but currently does
+ * not provide any useful return states. But we now have the hook and things can
+ * improve in the future. <-- TODO!
+ *
+ * rgerhards, 2008-03-19:
+ * I added an additional calling parameter to permit specifying the flow
+ * control capability of the source.
+ *
+ * rgerhards, 2008-05-16:
+ * I added an additional calling parameter (hnameIP) to enable specifying the IP
+ * of a remote host.
+ */
+rsRetVal
+parseAndSubmitMessage(uchar *hname, uchar *hnameIP, uchar *msg, int len, int bParseHost, int flags, flowControl_t flowCtlType)
+{
+ DEFiRet;
+ register int iMsg;
+ uchar *pMsg;
+ uchar *pData;
+ uchar *pEnd;
+ uchar tmpline[MAXLINE + 1];
+# ifdef USE_NETZIP
+ uchar deflateBuf[MAXLINE + 1];
+ uLongf iLenDefBuf;
+# endif
+
+ assert(hname != NULL);
+ assert(hnameIP != NULL);
+ assert(msg != NULL);
+ assert(len >= 0);
+
+ /* we first 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
+ */
+ if(*(msg + len - 1) == '\0') {
+ dbgprintf("dropped NUL at very end of message\n");
+ len--;
+ }
+
+ /* then we check if we need to drop trailing LFs, which often make
+ * their way into syslog messages unintentionally. In order to remain
+ * compatible to recent IETF developments, we allow the user to
+ * turn on/off this handling. rgerhards, 2007-07-23
+ */
+ if(bDropTrailingLF && *(msg + len - 1) == '\n') {
+ dbgprintf("dropped LF at very end of message (DropTrailingLF is set)\n");
+ len--;
+ }
+
+ iMsg = 0; /* initialize receiving buffer index */
+ pMsg = tmpline; /* set receiving buffer pointer */
+ pData = msg; /* set source buffer pointer */
+ pEnd = msg + len; /* this is one off, which is intensional */
+
+# ifdef USE_NETZIP
+ /* we first need to check if we have a compressed record. If so,
+ * we must decompress it.
+ */
+ if(len > 0 && *msg == 'z') { /* compressed data present? (do NOT change order if conditions!) */
+ /* we have compressed data, so let's deflate it. We support a maximum
+ * message size of MAXLINE. 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 = MAXLINE;
+ ret = uncompress((uchar *) deflateBuf, &iLenDefBuf, (uchar *) msg+1, len-1);
+ dbgprintf("Compressed message uncompressed with status %d, length: new %ld, old %d.\n",
+ ret, (long) iLenDefBuf, len-1);
+ /* Now check if the uncompression worked. If not, there is not much we can do. In
+ * that case, we log an error message but ignore the message itself. Storing the
+ * compressed text is dangerous, as it contains control characters. So we do
+ * not do this. If someone would like to have a copy, this code here could be
+ * modified to do a hex-dump of the buffer in question. We do not include
+ * this functionality right now.
+ * rgerhards, 2006-12-07
+ */
+ if(ret != Z_OK) {
+ errmsg.LogError(0, NO_ERRCODE, "Uncompression of a message failed with return code %d "
+ "- enable debug logging if you need further information. "
+ "Message ignored.", ret);
+ FINALIZE; /* unconditional exit, nothing left to do... */
+ }
+ pData = deflateBuf;
+ pEnd = deflateBuf + iLenDefBuf;
+ }
+# else /* ifdef USE_NETZIP */
+ /* in this case, we still need to check if the message is compressed. If so, we must
+ * tell the user we can not accept it.
+ */
+ if(len > 0 && *msg == 'z') {
+ errmsg.LogError(0, NO_ERRCODE, "Received a compressed message, but rsyslogd does not have compression "
+ "support enabled. The message will be ignored.");
+ FINALIZE;
+ }
+# endif /* ifdef USE_NETZIP */
+
+ while(pData < pEnd) {
+ if(iMsg >= MAXLINE) {
+ /* emergency, we now need to flush, no matter if
+ * we are at end of message or not...
+ */
+ if(iMsg == MAXLINE) {
+ *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */
+ printline(hname, hnameIP, tmpline, bParseHost, flags, flowCtlType);
+ } else {
+ /* This case in theory never can happen. If it happens, we have
+ * a logic error. I am checking for it, because if I would not,
+ * we would address memory invalidly with the code above. I
+ * do not care much about this case, just a debug log entry
+ * (I couldn't do any more smart things anyway...).
+ * rgerhards, 2007-9-20
+ */
+ dbgprintf("internal error: iMsg > MAXLINE in printchopped()\n");
+ }
+ FINALIZE; /* in this case, we are done... nothing left we can do */
+ }
+ if(*pData == '\0') { /* guard against \0 characters... */
+ /* changed to the sequence (somewhat) proposed in
+ * draft-ietf-syslog-protocol-19. rgerhards, 2006-11-30
+ */
+ if(iMsg + 3 < MAXLINE) { /* do we have space? */
+ *(pMsg + iMsg++) = cCCEscapeChar;
+ *(pMsg + iMsg++) = '0';
+ *(pMsg + iMsg++) = '0';
+ *(pMsg + iMsg++) = '0';
+ } /* if we do not have space, we simply ignore the '\0'... */
+ /* log an error? Very questionable... rgerhards, 2006-11-30 */
+ /* decided: we do not log an error, it won't help... rger, 2007-06-21 */
+ ++pData;
+ } else if(bEscapeCCOnRcv && iscntrl((int) *pData)) {
+ /* 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
+ * Note: sysklogd logs octal values only for DEL and CCs above 127.
+ * For others, it logs ^n where n is the control char converted to an
+ * alphabet character. We like consistency and thus escape it to octal
+ * in all cases. If someone complains, we may change the mode. At least
+ * we known now what's going on.
+ * rgerhards, 2007-07-17
+ */
+ if(iMsg + 3 < MAXLINE) { /* do we have space? */
+ *(pMsg + iMsg++) = cCCEscapeChar;
+ *(pMsg + iMsg++) = '0' + ((*pData & 0300) >> 6);
+ *(pMsg + iMsg++) = '0' + ((*pData & 0070) >> 3);
+ *(pMsg + iMsg++) = '0' + ((*pData & 0007));
+ } /* again, if we do not have space, we ignore the char - see comment at '\0' */
+ ++pData;
+ } else {
+ *(pMsg + iMsg++) = *pData++;
+ }
+ }
+
+ *(pMsg + iMsg) = '\0'; /* space *is* reserved for this! */
+
+ /* typically, we should end up here! */
+ printline(hname, hnameIP, tmpline, bParseHost, flags, flowCtlType);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* this is a special function used to submit an error message. This
+ * function is also passed to the runtime library as the generic error
+ * message handler. -- rgerhards, 2008-04-17
+ */
+rsRetVal
+submitErrMsg(int iErr, uchar *msg)
+{
+ DEFiRet;
+ iRet = logmsgInternal(iErr, LOG_SYSLOG|LOG_ERR, msg, ADDDATE);
+ RETiRet;
+}
+
+/* rgerhards 2004-11-09: the following is a function that can be used
+ * to log a message orginating from the syslogd itself. In sysklogd code,
+ * this is done by simply calling logmsg(). However, logmsg() is changed in
+ * rsyslog so that it takes a msg "object". So it can no longer be called
+ * directly. This method here solves the need. It provides an interface that
+ * allows to construct a locally-generated message. Please note that this
+ * function here probably is only an interim solution and that we need to
+ * think on the best way to do this.
+ */
+rsRetVal
+logmsgInternal(int iErr, int pri, uchar *msg, int flags)
+{
+ uchar pszTag[33];
+ msg_t *pMsg;
+ DEFiRet;
+
+ CHKiRet(msgConstruct(&pMsg));
+ MsgSetUxTradMsg(pMsg, (char*)msg);
+ MsgSetRawMsg(pMsg, (char*)msg);
+ MsgSetHOSTNAME(pMsg, (char*)glbl.GetLocalHostName());
+ MsgSetRcvFrom(pMsg, (char*)glbl.GetLocalHostName());
+ MsgSetRcvFromIP(pMsg, (uchar*)"127.0.0.1");
+ /* check if we have an error code associated and, if so,
+ * adjust the tag. -- r5gerhards, 2008-06-27
+ */
+ if(iErr == NO_ERRCODE) {
+ MsgSetTAG(pMsg, "rsyslogd:");
+ } else {
+ snprintf((char*)pszTag, sizeof(pszTag), "rsyslogd%d:", iErr);
+ pszTag[32] = '\0'; /* just to make sure... */
+ MsgSetTAG(pMsg, (char*)pszTag);
+ }
+ pMsg->iFacility = LOG_FAC(pri);
+ pMsg->iSeverity = LOG_PRI(pri);
+ pMsg->bParseHOSTNAME = 0;
+ datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */
+ flags |= INTERNAL_MSG;
+
+ if(bHaveMainQueue == 0) { /* not yet in queued mode */
+ iminternalAddMsg(pri, pMsg, flags);
+ } else {
+ /* we have the queue, so we can simply provide the
+ * message to the queue engine.
+ */
+ logmsg(pMsg, flags);
+ }
+finalize_it:
+ RETiRet;
+}
+
+/* This functions looks at the given message and checks if it matches the
+ * provided filter condition. If so, it returns true, else it returns
+ * false. This is a helper to logmsg() and meant to drive the decision
+ * process if a message is to be processed or not. As I expect this
+ * decision code to grow more complex over time AND logmsg() is already
+ * a very lengthy function, I thought a separate function is more appropriate.
+ * 2005-09-19 rgerhards
+ * 2008-02-25 rgerhards: changed interface, now utilizes iRet, bProcessMsg
+ * returns is message should be procesed.
+ */
+static rsRetVal shouldProcessThisMessage(selector_t *f, msg_t *pMsg, int *bProcessMsg)
+{
+ DEFiRet;
+ unsigned short pbMustBeFreed;
+ char *pszPropVal;
+ int bRet = 0;
+ vm_t *pVM = NULL;
+ var_t *pResult = NULL;
+
+ assert(f != NULL);
+ assert(pMsg != NULL);
+
+ /* we first have a look at the global, BSD-style block filters (for tag
+ * and host). Only if they match, we evaluate the actual filter.
+ * rgerhards, 2005-10-18
+ */
+ if(f->eHostnameCmpMode == HN_NO_COMP) {
+ /* EMPTY BY INTENSION - we check this value first, because
+ * it is the one most often used, so this saves us time!
+ */
+ } else if(f->eHostnameCmpMode == HN_COMP_MATCH) {
+ if(rsCStrSzStrCmp(f->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) {
+ /* not equal, so we are already done... */
+ dbgprintf("hostname filter '+%s' does not match '%s'\n",
+ rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg));
+ FINALIZE;
+ }
+ } else { /* must be -hostname */
+ if(!rsCStrSzStrCmp(f->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) {
+ /* not equal, so we are already done... */
+ dbgprintf("hostname filter '-%s' does not match '%s'\n",
+ rsCStrGetSzStrNoNULL(f->pCSHostnameComp), getHOSTNAME(pMsg));
+ FINALIZE;
+ }
+ }
+
+ if(f->pCSProgNameComp != NULL) {
+ int bInv = 0, bEqv = 0, offset = 0;
+ if(*(rsCStrGetSzStrNoNULL(f->pCSProgNameComp)) == '-') {
+ if(*(rsCStrGetSzStrNoNULL(f->pCSProgNameComp) + 1) == '-')
+ offset = 1;
+ else {
+ bInv = 1;
+ offset = 1;
+ }
+ }
+ if(!rsCStrOffsetSzStrCmp(f->pCSProgNameComp, offset, (uchar*) getProgramName(pMsg), getProgramNameLen(pMsg)))
+ bEqv = 1;
+
+ if((!bEqv && !bInv) || (bEqv && bInv)) {
+ /* not equal or inverted selection, so we are already done... */
+ dbgprintf("programname filter '%s' does not match '%s'\n",
+ rsCStrGetSzStrNoNULL(f->pCSProgNameComp), getProgramName(pMsg));
+ FINALIZE;
+ }
+ }
+
+ /* done with the BSD-style block filters */
+
+ if(f->f_filter_type == FILTER_PRI) {
+ /* skip messages that are incorrect priority */
+ if ( (f->f_filterData.f_pmask[pMsg->iFacility] == TABLE_NOPRI) || \
+ ((f->f_filterData.f_pmask[pMsg->iFacility] & (1<<pMsg->iSeverity)) == 0) )
+ bRet = 0;
+ else
+ bRet = 1;
+ } else if(f->f_filter_type == FILTER_EXPR) {
+ CHKiRet(vm.Construct(&pVM));
+ CHKiRet(vm.ConstructFinalize(pVM));
+ CHKiRet(vm.SetMsg(pVM, pMsg));
+ CHKiRet(vm.ExecProg(pVM, f->f_filterData.f_expr->pVmprg));
+ CHKiRet(vm.PopBoolFromStack(pVM, &pResult));
+ dbgprintf("result of expression evaluation: %lld\n", pResult->val.num);
+ /* VM is destructed on function exit */
+ bRet = (pResult->val.num) ? 1 : 0;
+ } else {
+ assert(f->f_filter_type == FILTER_PROP); /* assert() just in case... */
+ pszPropVal = MsgGetProp(pMsg, NULL, f->f_filterData.prop.pCSPropName, &pbMustBeFreed);
+
+ /* Now do the compares (short list currently ;)) */
+ switch(f->f_filterData.prop.operation ) {
+ case FIOP_CONTAINS:
+ if(rsCStrLocateInSzStr(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal) != -1)
+ bRet = 1;
+ break;
+ case FIOP_ISEQUAL:
+ if(rsCStrSzStrCmp(f->f_filterData.prop.pCSCompValue,
+ (uchar*) pszPropVal, strlen(pszPropVal)) == 0)
+ bRet = 1; /* process message! */
+ break;
+ case FIOP_STARTSWITH:
+ if(rsCStrSzStrStartsWithCStr(f->f_filterData.prop.pCSCompValue,
+ (uchar*) pszPropVal, strlen(pszPropVal)) == 0)
+ bRet = 1; /* process message! */
+ break;
+ case FIOP_REGEX:
+ if(rsCStrSzStrMatchRegex(f->f_filterData.prop.pCSCompValue,
+ (unsigned char*) pszPropVal) == 0)
+ bRet = 1;
+ break;
+ default:
+ /* here, it handles NOP (for performance reasons) */
+ assert(f->f_filterData.prop.operation == FIOP_NOP);
+ bRet = 1; /* as good as any other default ;) */
+ break;
+ }
+
+ /* now check if the value must be negated */
+ if(f->f_filterData.prop.isNegated)
+ bRet = (bRet == 1) ? 0 : 1;
+
+ if(Debug) {
+ dbgprintf("Filter: check for property '%s' (value '%s') ",
+ rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName),
+ pszPropVal);
+ if(f->f_filterData.prop.isNegated)
+ dbgprintf("NOT ");
+ dbgprintf("%s '%s': %s\n",
+ getFIOPName(f->f_filterData.prop.operation),
+ rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue),
+ bRet ? "TRUE" : "FALSE");
+ }
+
+ /* cleanup */
+ if(pbMustBeFreed)
+ free(pszPropVal);
+ }
+
+finalize_it:
+ /* destruct in any case, not just on error, but it makes error handling much easier */
+ if(pVM != NULL)
+ vm.Destruct(&pVM);
+
+ if(pResult != NULL)
+ var.Destruct(&pResult);
+
+ *bProcessMsg = bRet;
+ RETiRet;
+}
+
+
+/* helper to processMsg(), used to call the configured actions. It is
+ * executed from within llExecFunc() of the action list.
+ * rgerhards, 2007-08-02
+ */
+typedef struct processMsgDoActions_s {
+ int bPrevWasSuspended; /* was the previous action suspended? */
+ msg_t *pMsg;
+} processMsgDoActions_t;
+DEFFUNC_llExecFunc(processMsgDoActions)
+{
+ DEFiRet;
+ rsRetVal iRetMod; /* return value of module - we do not always pass that back */
+ action_t *pAction = (action_t*) pData;
+ processMsgDoActions_t *pDoActData = (processMsgDoActions_t*) pParam;
+
+ assert(pAction != NULL);
+
+ if((pAction->bExecWhenPrevSusp == 1) && (pDoActData->bPrevWasSuspended == 0)) {
+ dbgprintf("not calling action because the previous one is not suspended\n");
+ ABORT_FINALIZE(RS_RET_OK);
+ }
+
+ iRetMod = actionCallAction(pAction, pDoActData->pMsg);
+ if(iRetMod == RS_RET_DISCARDMSG) {
+ ABORT_FINALIZE(RS_RET_DISCARDMSG);
+ } else if(iRetMod == RS_RET_SUSPENDED) {
+ /* indicate suspension for next module to be called */
+ pDoActData->bPrevWasSuspended = 1;
+ } else {
+ pDoActData->bPrevWasSuspended = 0;
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* Process (consume) a received message. Calls the actions configured.
+ * rgerhards, 2005-10-13
+ */
+static void
+processMsg(msg_t *pMsg)
+{
+ selector_t *f;
+ int bContinue;
+ int bProcessMsg;
+ processMsgDoActions_t DoActData;
+ rsRetVal iRet;
+
+ BEGINfunc
+ assert(pMsg != NULL);
+
+ /* log the message to the particular outputs */
+
+ bContinue = 1;
+ for (f = Files; f != NULL && bContinue ; f = f->f_next) {
+ /* first check the filters... */
+ iRet = shouldProcessThisMessage(f, pMsg, &bProcessMsg);
+ if(!bProcessMsg) {
+ continue;
+ }
+
+ /* ok -- from here, we have action-specific code, nothing really selector-specific -- rger 2007-08-01 */
+ DoActData.pMsg = pMsg;
+ DoActData.bPrevWasSuspended = 0;
+ if(llExecFunc(&f->llActList, processMsgDoActions, (void*)&DoActData) == RS_RET_DISCARDMSG)
+ bContinue = 0;
+ }
+ ENDfunc
+}
+
+
+/* The consumer of dequeued messages. This function is called by the
+ * queue engine on dequeueing of a message. It runs on a SEPARATE
+ * THREAD.
+ * NOTE: Having more than one worker requires guarding of some
+ * message object structures and potentially others - need to be checked
+ * before we support multiple worker threads on the message queue.
+ * Please note: the message object is destructed by the queue itself!
+ */
+static rsRetVal
+msgConsumer(void __attribute__((unused)) *notNeeded, void *pUsr)
+{
+ DEFiRet;
+ msg_t *pMsg = (msg_t*) pUsr;
+
+ assert(pMsg != NULL);
+
+ processMsg(pMsg);
+ msgDestruct(&pMsg);
+
+ RETiRet;
+}
+
+
+/* Helper to parseRFCSyslogMsg. This function parses a field up to
+ * (and including) the SP character after it. The field contents is
+ * returned in a caller-provided buffer. The parsepointer is advanced
+ * to after the terminating SP. The caller must ensure that the
+ * provided buffer is large enough to hold the to be extracted value.
+ * Returns 0 if everything is fine or 1 if either the field is not
+ * SP-terminated or any other error occurs.
+ * rger, 2005-11-24
+ */
+static int parseRFCField(char **pp2parse, char *pResult)
+{
+ char *p2parse;
+ int iRet = 0;
+
+ assert(pp2parse != NULL);
+ assert(*pp2parse != NULL);
+ assert(pResult != NULL);
+
+ p2parse = *pp2parse;
+
+ /* this is the actual parsing loop */
+ while(*p2parse && *p2parse != ' ') {
+ *pResult++ = *p2parse++;
+ }
+
+ if(*p2parse == ' ')
+ ++p2parse; /* eat SP, but only if not at end of string */
+ else
+ iRet = 1; /* there MUST be an SP! */
+ *pResult = '\0';
+
+ /* set the new parse pointer */
+ *pp2parse = p2parse;
+ return 0;
+}
+
+
+/* Helper to parseRFCSyslogMsg. This function parses the structured
+ * data field of a message. It does NOT parse inside structured data,
+ * just gets the field as whole. Parsing the single entities is left
+ * to other functions. The parsepointer is advanced
+ * to after the terminating SP. The caller must ensure that the
+ * provided buffer is large enough to hold the to be extracted value.
+ * Returns 0 if everything is fine or 1 if either the field is not
+ * SP-terminated or any other error occurs.
+ * rger, 2005-11-24
+ */
+static int parseRFCStructuredData(char **pp2parse, char *pResult)
+{
+ char *p2parse;
+ int bCont = 1;
+ int iRet = 0;
+
+ assert(pp2parse != NULL);
+ assert(*pp2parse != NULL);
+ assert(pResult != NULL);
+
+ p2parse = *pp2parse;
+
+ /* this is the actual parsing loop
+ * Remeber: structured data starts with [ and includes any characters
+ * until the first ] followed by a SP. There may be spaces inside
+ * structured data. There may also be \] inside the structured data, which
+ * do NOT terminate an element.
+ */
+ if(*p2parse != '[')
+ return 1; /* this is NOT structured data! */
+
+ while(bCont) {
+ if(*p2parse == '\0') {
+ iRet = 1; /* this is not valid! */
+ bCont = 0;
+ } else if(*p2parse == '\\' && *(p2parse+1) == ']') {
+ /* this is escaped, need to copy both */
+ *pResult++ = *p2parse++;
+ *pResult++ = *p2parse++;
+ } else if(*p2parse == ']' && *(p2parse+1) == ' ') {
+ /* found end, just need to copy the ] and eat the SP */
+ *pResult++ = *p2parse;
+ p2parse += 2;
+ bCont = 0;
+ } else {
+ *pResult++ = *p2parse++;
+ }
+ }
+
+ if(*p2parse == ' ')
+ ++p2parse; /* eat SP, but only if not at end of string */
+ else
+ iRet = 1; /* there MUST be an SP! */
+ *pResult = '\0';
+
+ /* set the new parse pointer */
+ *pp2parse = p2parse;
+ return 0;
+}
+
+/* parse a RFC-formatted syslog message. This function returns
+ * 0 if processing of the message shall continue and 1 if something
+ * went wrong and this messe should be ignored. This function has been
+ * implemented in the effort to support syslog-protocol. Please note that
+ * the name (parse *RFC*) stems from the hope that syslog-protocol will
+ * some time become an RFC. Do not confuse this with informational
+ * RFC 3164 (which is legacy syslog).
+ *
+ * currently supported format:
+ *
+ * <PRI>VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s SP MSG
+ *
+ * <PRI> is already stripped when this function is entered. VERSION already
+ * has been confirmed to be "1", but has NOT been stripped from the message.
+ *
+ * rger, 2005-11-24
+ */
+static int parseRFCSyslogMsg(msg_t *pMsg, int flags)
+{
+ char *p2parse;
+ char *pBuf;
+ int bContParse = 1;
+
+ assert(pMsg != NULL);
+ assert(pMsg->pszUxTradMsg != NULL);
+ p2parse = (char*) pMsg->pszUxTradMsg;
+
+ /* do a sanity check on the version and eat it */
+ assert(p2parse[0] == '1' && p2parse[1] == ' ');
+ p2parse += 2;
+
+ /* Now get us some memory we can use as a work buffer while parsing.
+ * We simply allocated a buffer sufficiently large to hold all of the
+ * message, so we can not run into any troubles. I think this is
+ * more wise then to use individual buffers.
+ */
+ if((pBuf = malloc(sizeof(char)* strlen(p2parse) + 1)) == NULL)
+ return 1;
+
+ /* IMPORTANT NOTE:
+ * Validation is not actually done below nor are any errors handled. I have
+ * NOT included this for the current proof of concept. However, it is strongly
+ * advisable to add it when this code actually goes into production.
+ * rgerhards, 2005-11-24
+ */
+
+ /* TIMESTAMP */
+ if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == FALSE) {
+ dbgprintf("no TIMESTAMP detected!\n");
+ bContParse = 0;
+ flags |= ADDDATE;
+ }
+
+ if (flags & ADDDATE) {
+ datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */
+ }
+
+ /* HOSTNAME */
+ if(bContParse) {
+ parseRFCField(&p2parse, pBuf);
+ MsgSetHOSTNAME(pMsg, pBuf);
+ } else {
+ /* we can not parse, so we get the system we
+ * received the data from.
+ */
+ MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg));
+ }
+
+ /* APP-NAME */
+ if(bContParse) {
+ parseRFCField(&p2parse, pBuf);
+ MsgSetAPPNAME(pMsg, pBuf);
+ }
+
+ /* PROCID */
+ if(bContParse) {
+ parseRFCField(&p2parse, pBuf);
+ MsgSetPROCID(pMsg, pBuf);
+ }
+
+ /* MSGID */
+ if(bContParse) {
+ parseRFCField(&p2parse, pBuf);
+ MsgSetMSGID(pMsg, pBuf);
+ }
+
+ /* STRUCTURED-DATA */
+ if(bContParse) {
+ parseRFCStructuredData(&p2parse, pBuf);
+ MsgSetStructuredData(pMsg, pBuf);
+ }
+
+ /* MSG */
+ MsgSetMSG(pMsg, p2parse);
+
+ free(pBuf);
+ return 0; /* all ok */
+}
+
+
+/* parse a legay-formatted syslog message. This function returns
+ * 0 if processing of the message shall continue and 1 if something
+ * went wrong and this messe should be ignored. This function has been
+ * implemented in the effort to support syslog-protocol.
+ * rger, 2005-11-24
+ * As of 2006-01-10, I am removing the logic to continue parsing only
+ * when a valid TIMESTAMP is detected. Validity of other fields already
+ * is ignored. This is due to the fact that the parser has grown smarter
+ * and is now more able to understand different dialects of the syslog
+ * message format. I do not expect any bad side effects of this change,
+ * but I thought I log it in this comment.
+ * rgerhards, 2006-01-10
+ */
+static int parseLegacySyslogMsg(msg_t *pMsg, int flags)
+{
+ char *p2parse;
+ char *pBuf;
+ char *pWork;
+ cstr_t *pStrB;
+ int iCnt;
+ int bTAGCharDetected;
+
+ assert(pMsg != NULL);
+ assert(pMsg->pszUxTradMsg != NULL);
+ p2parse = (char*) pMsg->pszUxTradMsg;
+
+ /* Check to see if msg contains a timestamp. We stary trying with a
+ * high-precision one...
+ */
+ if(datetime.ParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == TRUE) {
+ /* we are done - parse pointer is moved by ParseTIMESTAMP3339 */;
+ } else if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse) == TRUE) {
+ p2parse += 16;
+ } else if(*p2parse == ' ') { /* try to see if it is slighly malformed - HP procurve seems to do that sometimes */
+ if(datetime.ParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse+1) == TRUE) {
+ /* indeed, we got it! */
+ p2parse += 17;
+ } else {
+ flags |= ADDDATE;
+ }
+ } else {
+ flags |= ADDDATE;
+ }
+
+ /* here we need to check if the timestamp is valid. If it is not,
+ * we can not continue to parse but must treat the rest as the
+ * MSG part of the message (as of RFC 3164).
+ * rgerhards 2004-12-03
+ */
+ if(flags & ADDDATE) {
+ datetime.getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */
+ }
+
+ /* rgerhards, 2006-03-13: next, we parse the hostname and tag. But we
+ * do this only when the user has not forbidden this. I now introduce some
+ * code that allows a user to configure rsyslogd to treat the rest of the
+ * message as MSG part completely. In this case, the hostname will be the
+ * machine that we received the message from and the tag will be empty. This
+ * is meant to be an interim solution, but for now it is in the code.
+ */
+ if(bParseHOSTNAMEandTAG && !(flags & INTERNAL_MSG)) {
+ /* parse HOSTNAME - but only if this is network-received!
+ * rger, 2005-11-14: we still have a problem with BSD messages. These messages
+ * do NOT include a host name. In most cases, this leads to the TAG to be treated
+ * as hostname and the first word of the message as the TAG. Clearly, this is not
+ * of advantage ;) I think I have now found a way to handle this situation: there
+ * are certain characters which are frequently used in TAG (e.g. ':'), which are
+ * *invalid* in host names. So while parsing the hostname, I check for these characters.
+ * If I find them, I set a simple flag but continue. After parsing, I check the flag.
+ * If it was set, then we most probably do not have a hostname but a TAG. Thus, I change
+ * the fields. I think this logic shall work with any type of syslog message.
+ */
+ bTAGCharDetected = 0;
+ if(pMsg->bParseHOSTNAME) {
+ /* TODO: quick and dirty memory allocation */
+ /* the memory allocated is far too much in most cases. But on the plus side,
+ * it is quite fast... - rgerhards, 2007-09-20
+ */
+ if((pBuf = malloc(sizeof(char)* (strlen(p2parse) +1))) == NULL)
+ return 1;
+ pWork = pBuf;
+ /* this is the actual parsing loop */
+ while(*p2parse && *p2parse != ' ' && *p2parse != ':') {
+ if(*p2parse == '[' || *p2parse == ']' || *p2parse == '/')
+ bTAGCharDetected = 1;
+ *pWork++ = *p2parse++;
+ }
+ /* we need to handle ':' seperately, because it terminates the
+ * TAG - so we also need to terminate the parser here!
+ * rgerhards, 2007-09-10 *p2parse points to a valid address here in
+ * any case. We can reach this point only if we are at end of string,
+ * or we have a ':' or ' '. What the if below does is check if we are
+ * not at end of string and, if so, advance the parse pointer. If we
+ * are already at end of string, *p2parse is equal to '\0', neither if
+ * will be true and the parse pointer remain as is. This is perfectly
+ * well.
+ */
+ if(*p2parse == ':') {
+ bTAGCharDetected = 1;
+ /* We will move hostname to tag, so preserve ':' (otherwise we
+ * will needlessly change the message format) */
+ *pWork++ = *p2parse++;
+ } else if(*p2parse == ' ')
+ ++p2parse;
+ *pWork = '\0';
+ MsgAssignHOSTNAME(pMsg, pBuf);
+ }
+ /* check if we seem to have a TAG */
+ if(bTAGCharDetected) {
+ /* indeed, this smells like a TAG, so lets use it for this. We take
+ * the HOSTNAME from the sender system instead.
+ */
+ dbgprintf("HOSTNAME contains invalid characters, assuming it to be a TAG.\n");
+ moveHOSTNAMEtoTAG(pMsg);
+ MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg));
+ }
+
+ /* now parse TAG - that should be present in message from all sources.
+ * This code is somewhat not compliant with RFC 3164. As of 3164,
+ * the TAG field is ended by any non-alphanumeric character. In
+ * practice, however, the TAG often contains dashes and other things,
+ * which would end the TAG. So it is not desirable. As such, we only
+ * accept colon and SP to be terminators. Even there is a slight difference:
+ * a colon is PART of the TAG, while a SP is NOT part of the tag
+ * (it is CONTENT). Starting 2008-04-04, we have removed the 32 character
+ * size limit (from RFC3164) on the tag. This had bad effects on existing
+ * envrionments, as sysklogd didn't obey it either (probably another bug
+ * in RFC3164...). We now receive the full size, but will modify the
+ * outputs so that only 32 characters max are used by default.
+ */
+ /* The following code in general is quick & dirty - I need to get
+ * it going for a test, rgerhards 2004-11-16 */
+ /* lol.. we tried to solve it, just to remind ourselfs that 32 octets
+ * is the max size ;) we need to shuffle the code again... Just for
+ * the records: the code is currently clean, but we could optimize it! */
+ if(!bTAGCharDetected) {
+ uchar *pszTAG;
+ if(rsCStrConstruct(&pStrB) != RS_RET_OK)
+ return 1;
+ rsCStrSetAllocIncrement(pStrB, 33);
+ pWork = pBuf;
+ iCnt = 0;
+ while(*p2parse && *p2parse != ':' && *p2parse != ' ') {
+ rsCStrAppendChar(pStrB, *p2parse++);
+ ++iCnt;
+ }
+ if(*p2parse == ':') {
+ ++p2parse;
+ rsCStrAppendChar(pStrB, ':');
+ }
+ rsCStrFinish(pStrB);
+
+ rsCStrConvSzStrAndDestruct(pStrB, &pszTAG, 1);
+ if(pszTAG == NULL)
+ { /* rger, 2005-11-10: no TAG found - this implies that what
+ * we have considered to be the HOSTNAME is most probably the
+ * TAG. We consider it so probable, that we now adjust it
+ * that way. So we pick up the previously set hostname, assign
+ * it to tag and use the sender system (from IP stack) as
+ * the hostname. This situation is the standard case with
+ * stock BSD syslogd.
+ */
+ dbgprintf("No TAG in message, assuming that HOSTNAME is missing.\n");
+ moveHOSTNAMEtoTAG(pMsg);
+ MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg));
+ } else { /* we have a TAG, so we can happily set it ;) */
+ MsgAssignTAG(pMsg, pszTAG);
+ }
+ } else {
+ /* we have no TAG, so we ... */
+ /*DO NOTHING*/;
+ }
+ } else {
+ /* we enter this code area when the user has instructed rsyslog NOT
+ * to parse HOSTNAME and TAG - rgerhards, 2006-03-13
+ */
+ if(!(flags & INTERNAL_MSG))
+ {
+ dbgprintf("HOSTNAME and TAG not parsed by user configuraton.\n");
+ MsgSetHOSTNAME(pMsg, getRcvFrom(pMsg));
+ }
+ }
+
+ /* The rest is the actual MSG */
+ MsgSetMSG(pMsg, p2parse);
+
+ return 0; /* all ok */
+}
+
+
+/* submit a fully created message to the main message queue. The message is
+ * fully processed and parsed, so no parsing at all happens. This is primarily
+ * a hook to prevent the need for callers to know about the main message queue
+ * (which may change in the future as we will probably have multiple rule
+ * sets and thus queues...).
+ * rgerhards, 2008-02-13
+ */
+rsRetVal
+submitMsg(msg_t *pMsg)
+{
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pMsg, msg);
+
+ MsgPrepareEnqueue(pMsg);
+ queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg);
+
+ RETiRet;
+}
+
+
+/* Log a message to the appropriate log files, users, etc. based on
+ * the priority.
+ * rgerhards 2004-11-08: actually, this also decodes all but the PRI part.
+ * rgerhards 2004-11-09: ... but only, if syslogd could properly be initialized
+ * if not, we use emergency logging to the console and in
+ * this case, no further decoding happens.
+ * changed to no longer receive a plain message but a msg object instead.
+ * rgerhards-2004-11-16: OK, we are now up to another change... This method
+ * actually needs to PARSE the message. How exactly this needs to happen depends on
+ * a number of things. Most importantly, it depends on the source. For example,
+ * locally received messages (SOURCE_UNIXAF) do NOT have a hostname in them. So
+ * we need to treat them differntly form network-received messages which have.
+ * Well, actually not all network-received message really have a hostname. We
+ * can just hope they do, but we can not be sure. So this method tries to find
+ * whatever can be found in the message and uses that... Obviously, there is some
+ * potential for misinterpretation, which we simply can not solve under the
+ * circumstances given.
+ */
+void
+logmsg(msg_t *pMsg, int flags)
+{
+ char *msg;
+
+ BEGINfunc
+ assert(pMsg != NULL);
+ assert(pMsg->pszUxTradMsg != NULL);
+ msg = (char*) pMsg->pszUxTradMsg;
+ dbgprintf("logmsg: flags %x, pri %s, from '%s', msg %s\n", flags, getPRI(pMsg), getRcvFrom(pMsg), msg);
+
+ /* rger 2005-11-24 (happy thanksgiving!): we now need to check if we have
+ * a traditional syslog message or one formatted according to syslog-protocol.
+ * We need to apply different parsers depending on that. We use the
+ * -protocol VERSION field for the detection.
+ */
+ if(msg[0] == '1' && msg[1] == ' ') {
+ dbgprintf("Message has syslog-protocol format.\n");
+ setProtocolVersion(pMsg, 1);
+ if(parseRFCSyslogMsg(pMsg, flags) == 1) {
+ msgDestruct(&pMsg);
+ return;
+ }
+ } else { /* we have legacy syslog */
+ dbgprintf("Message has legacy syslog format.\n");
+ setProtocolVersion(pMsg, 0);
+ if(parseLegacySyslogMsg(pMsg, flags) == 1) {
+ msgDestruct(&pMsg);
+ return;
+ }
+ }
+
+ /* ---------------------- END PARSING ---------------- */
+
+ /* now submit the message to the main queue - then we are done */
+ pMsg->msgFlags = flags;
+ MsgPrepareEnqueue(pMsg);
+ queueEnqObj(pMsgQueue, pMsg->flowCtlType, (void*) pMsg);
+ ENDfunc
+}
+
+
+static void
+reapchild()
+{
+ int saved_errno = errno;
+ struct sigaction sigAct;
+
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = reapchild;
+ sigaction(SIGCHLD, &sigAct, NULL); /* reset signal handler -ASP */
+
+ while(waitpid(-1, NULL, WNOHANG) > 0);
+ errno = saved_errno;
+}
+
+
+/* helper to doFlushRptdMsgs() to flush the individual action links via llExecFunc
+ * rgerhards, 2007-08-02
+ */
+DEFFUNC_llExecFunc(flushRptdMsgsActions)
+{
+ action_t *pAction = (action_t*) pData;
+
+ assert(pAction != NULL);
+
+ BEGINfunc
+ LockObj(pAction);
+ if (pAction->f_prevcount && time(NULL) >= REPEATTIME(pAction)) {
+ dbgprintf("flush %s: repeated %d times, %d sec.\n",
+ module.GetStateName(pAction->pMod), pAction->f_prevcount,
+ repeatinterval[pAction->f_repeatcount]);
+ actionWriteToAction(pAction);
+ BACKOFF(pAction);
+ }
+ UnlockObj(pAction);
+
+ ENDfunc
+ return RS_RET_OK; /* we ignore errors, we can not do anything either way */
+}
+
+
+/* This method flushes reapeat messages.
+ */
+static void
+doFlushRptdMsgs(void)
+{
+ register selector_t *f;
+
+ /* see if we need to flush any "message repeated n times"...
+ * Note that this interferes with objects running on other threads.
+ * We are using appropriate locking inside the function to handle that.
+ */
+ for (f = Files; f != NULL ; f = f->f_next) {
+ llExecFunc(&f->llActList, flushRptdMsgsActions, NULL);
+ }
+}
+
+
+static void debug_switch()
+{
+ struct sigaction sigAct;
+
+ if(debugging_on == 0) {
+ debugging_on = 1;
+ dbgprintf("Switching debugging_on to true\n");
+ } else {
+ dbgprintf("Switching debugging_on to false\n");
+ debugging_on = 0;
+ }
+
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = debug_switch;
+ sigaction(SIGUSR1, &sigAct, NULL);
+}
+
+
+void legacyOptsEnq(uchar *line)
+{
+ legacyOptsLL_t *pNew;
+
+ pNew = malloc(sizeof(legacyOptsLL_t));
+ if(line == NULL)
+ pNew->line = NULL;
+ else
+ pNew->line = (uchar *) strdup((char *) line);
+ pNew->next = NULL;
+
+ if(pLegacyOptsLL == NULL)
+ pLegacyOptsLL = pNew;
+ else {
+ legacyOptsLL_t *pThis = pLegacyOptsLL;
+
+ while(pThis->next != NULL)
+ pThis = pThis->next;
+ pThis->next = pNew;
+ }
+}
+
+
+void legacyOptsFree(void)
+{
+ legacyOptsLL_t *pThis = pLegacyOptsLL, *pNext;
+
+ while(pThis != NULL) {
+ if(pThis->line != NULL)
+ free(pThis->line);
+ pNext = pThis->next;
+ free(pThis);
+ pThis = pNext;
+ }
+}
+
+
+void legacyOptsHook(void)
+{
+ legacyOptsLL_t *pThis = pLegacyOptsLL;
+
+ while(pThis != NULL) {
+ if(pThis->line != NULL) {
+ errno = 0;
+ errmsg.LogError(0, NO_ERRCODE, "Warning: backward compatibility layer added to following "
+ "directive to rsyslog.conf: %s", pThis->line);
+ conf.cfsysline(pThis->line);
+ }
+ pThis = pThis->next;
+ }
+}
+
+
+void legacyOptsParseTCP(char ch, char *arg)
+{
+ register int i;
+ register char *pArg = arg;
+ static char conflict = '\0';
+
+ if((conflict == 'g' && ch == 't') || (conflict == 't' && ch == 'g')) {
+ fprintf(stderr, "rsyslog: If you want to use both -g and -t, use directives instead, -%c ignored.\n", ch);
+ return;
+ } else
+ conflict = ch;
+
+ /* extract port */
+ i = 0;
+ while(isdigit((int) *pArg))
+ i = i * 10 + *pArg++ - '0';
+
+ /* number of sessions */
+ if(*pArg == '\0' || *pArg == ',') {
+ if(ch == 't')
+ legacyOptsEnq((uchar *) "ModLoad imtcp");
+ else if(ch == 'g')
+ legacyOptsEnq((uchar *) "ModLoad imgssapi");
+
+ if(i >= 0 && i <= 65535) {
+ uchar line[30];
+
+ if(ch == 't') {
+ snprintf((char *) line, sizeof(line), "InputTCPServerRun %d", i);
+ } else if(ch == 'g') {
+ snprintf((char *) line, sizeof(line), "InputGSSServerRun %d", i);
+ }
+ legacyOptsEnq(line);
+ } else {
+ if(ch == 't') {
+ fprintf(stderr, "rsyslogd: Invalid TCP listen port %d - changed to 514.\n", i);
+ legacyOptsEnq((uchar *) "InputTCPServerRun 514");
+ } else if(ch == 'g') {
+ fprintf(stderr, "rsyslogd: Invalid GSS listen port %d - changed to 514.\n", i);
+ legacyOptsEnq((uchar *) "InputGSSServerRun 514");
+ }
+ }
+
+ if(*pArg == ',') {
+ ++pArg;
+ while(isspace((int) *pArg))
+ ++pArg;
+ i = 0;
+ while(isdigit((int) *pArg)) {
+ i = i * 10 + *pArg++ - '0';
+ }
+ if(i > 0) {
+ uchar line[30];
+
+ snprintf((char *) line, sizeof(line), "InputTCPMaxSessions %d", i);
+ legacyOptsEnq(line);
+ } else {
+ if(ch == 't') {
+ fprintf(stderr, "rsyslogd: TCP session max configured "
+ "to %d [-t %s] - changing to 1.\n", i, arg);
+ legacyOptsEnq((uchar *) "InputTCPMaxSessions 1");
+ } else if (ch == 'g') {
+ fprintf(stderr, "rsyslogd: GSS session max configured "
+ "to %d [-g %s] - changing to 1.\n", i, arg);
+ legacyOptsEnq((uchar *) "InputTCPMaxSessions 1");
+ }
+ }
+ }
+ } else
+ fprintf(stderr, "rsyslogd: Invalid -t %s command line option.\n", arg);
+}
+
+
+/* doDie() is a signal handler. If called, it sets the bFinished variable
+ * to indicate the program should terminate. However, it does not terminate
+ * it itself, because that causes issues with multi-threading. The actual
+ * termination is then done on the main thread. This solution might introduce
+ * a minimal delay, but it is much cleaner than the approach of doing everything
+ * inside the signal handler.
+ * rgerhards, 2005-10-26
+ */
+static void doDie(int sig)
+{
+ static int iRetries = 0; /* debug aid */
+ printf("DoDie called.\n");
+ if(iRetries++ == 4) {
+ printf("DoDie called 5 times - unconditional exit\n");
+ abort();
+ }
+ bFinished = sig;
+}
+
+
+/* This function frees all dynamically allocated memory for program termination.
+ * It must be called only immediately before exit(). It is primarily an aid
+ * for memory debuggers, which prevents cluttered outupt.
+ * rgerhards, 2008-03-20
+ */
+static void
+freeAllDynMemForTermination(void)
+{
+ if(pszMainMsgQFName != NULL)
+ free(pszMainMsgQFName);
+ if(pModDir != NULL)
+ free(pModDir);
+}
+
+
+/* die() is called when the program shall end. This typically only occurs
+ * during sigterm or during the initialization.
+ * As die() is intended to shutdown rsyslogd, it is
+ * safe to call exit() here. Just make sure that die() itself is not called
+ * at inapropriate places. As a general rule of thumb, it is a bad idea to add
+ * any calls to die() in new code!
+ * rgerhards, 2005-10-24
+ */
+static void
+die(int sig)
+{
+ char buf[256];
+
+ dbgprintf("exiting on signal %d\n", sig);
+
+ /* IMPORTANT: we should close the inputs first, and THEN send our termination
+ * message. If we do it the other way around, logmsgInternal() may block on
+ * a full queue and the inputs still fill up that queue. Depending on the
+ * scheduling order, we may end up with logmsgInternal being held for a quite
+ * long time. When the inputs are terminated first, that should not happen
+ * because the queue is drained in parallel. The situation could only become
+ * an issue with extremely long running actions in a queue full environment.
+ * However, such actions are at least considered poorly written, if not
+ * outright wrong. So we do not care about this very remote problem.
+ * rgerhards, 2008-01-11
+ */
+
+ /* close the inputs */
+ dbgprintf("Terminating input threads...\n");
+ thrdTerminateAll(); /* TODO: inputs only, please */
+
+ /* and THEN send the termination log message (see long comment above) */
+ if (sig) {
+ (void) snprintf(buf, sizeof(buf) / sizeof(char),
+ " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \
+ "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"]" " exiting on signal %d.",
+ (int) myPid, sig);
+ errno = 0;
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)buf, ADDDATE);
+ }
+
+ /* drain queue (if configured so) and stop main queue worker thread pool */
+ dbgprintf("Terminating main queue...\n");
+ queueDestruct(&pMsgQueue);
+ pMsgQueue = NULL;
+
+ /* Free ressources and close connections. This includes flushing any remaining
+ * repeated msgs.
+ */
+ dbgprintf("Terminating outputs...\n");
+ freeSelectors();
+
+ dbgprintf("all primary multi-thread sources have been terminated - now doing aux cleanup...\n");
+ /* rger 2005-02-22
+ * now clean up the in-memory structures. OK, the OS
+ * would also take care of that, but if we do it
+ * ourselfs, this makes finding memory leaks a lot
+ * easier.
+ */
+ tplDeleteAll();
+
+ remove_pid(PidFile);
+
+ /* de-init some modules */
+ modExitIminternal();
+
+ /*dbgPrintAllDebugInfo(); / * this is the last spot where this can be done - below output modules are unloaded! */
+
+ /* the following line cleans up CfSysLineHandlers that were not based on loadable
+ * modules. As such, they are not yet cleared.
+ */
+ unregCfSysLineHdlrs();
+
+ legacyOptsFree();
+
+ /* terminate the remaining classes */
+ GlobalClassExit();
+
+ /* TODO: this would also be the right place to de-init the builtin output modules. We
+ * do not currently do that, because the module interface does not allow for
+ * it. This will come some time later (it's essential with loadable modules).
+ * For the time being, this is a memory leak on exit, but as the process is
+ * terminated, we do not really bother about it.
+ * rgerhards, 2007-08-03
+ * I have added some code now, but all that mod init/de-init should be moved to
+ * init, so that modules are unloaded and reloaded on HUP to. Eventually it should go
+ * into freeSelectors() - but that needs to be seen. -- rgerhards, 2007-08-09
+ */
+ module.UnloadAndDestructAll(eMOD_LINK_ALL);
+
+ dbgprintf("Clean shutdown completed, bye\n");
+ /* dbgClassExit MUST be the last one, because it de-inits the debug system */
+ dbgClassExit();
+
+ /* free all remaining memory blocks - this is not absolutely necessary, but helps
+ * us keep memory debugger logs clean and this is in aid in developing. It doesn't
+ * cost much time, so we do it always. -- rgerhards, 2008-03-20
+ */
+ freeAllDynMemForTermination();
+ /* NO CODE HERE - feeelAllDynMemForTermination() must be the last thing before exit()! */
+ exit(0); /* "good" exit, this is the terminator function for rsyslog [die()] */
+}
+
+/*
+ * Signal handler to terminate the parent process.
+ * rgerhards, 2005-10-24: this is only called during forking of the
+ * detached syslogd. I consider this method to be safe.
+ */
+static void doexit()
+{
+ exit(0); /* "good" exit, only during child-creation */
+}
+
+
+/* set the action resume interval
+ */
+static rsRetVal setActionResumeInterval(void __attribute__((unused)) *pVal, int iNewVal)
+{
+ return actionSetGlobalResumeInterval(iNewVal);
+}
+
+
+/* set the processes umask (upon configuration request)
+ */
+static rsRetVal setUmask(void __attribute__((unused)) *pVal, int iUmask)
+{
+ umask(iUmask);
+ dbgprintf("umask set to 0%3.3o.\n", iUmask);
+
+ return RS_RET_OK;
+}
+
+
+/* helper to freeSelectors(), used with llExecFunc() to flush
+ * pending output. -- rgerhards, 2007-08-02
+ * We do not need to lock the action object here as the processing
+ * queue is already empty and no other threads are running when
+ * we call this function. -- rgerhards, 2007-12-12
+ */
+DEFFUNC_llExecFunc(freeSelectorsActions)
+{
+ action_t *pAction = (action_t*) pData;
+
+ assert(pAction != NULL);
+
+ /* flush any pending output */
+ if(pAction->f_prevcount) {
+ actionWriteToAction(pAction);
+ }
+
+ return RS_RET_OK; /* never fails ;) */
+}
+
+
+/* Close all open log files and free selector descriptor array.
+ */
+static void freeSelectors(void)
+{
+ selector_t *f;
+ selector_t *fPrev;
+
+ if(Files != NULL) {
+ dbgprintf("Freeing log structures.\n");
+
+ for(f = Files ; f != NULL ; f = f->f_next) {
+ llExecFunc(&f->llActList, freeSelectorsActions, NULL);
+ }
+
+ /* actions flushed and ready for destruction - so do that... */
+ f = Files;
+ while (f != NULL) {
+ fPrev = f;
+ f = f->f_next;
+ selectorDestruct(fPrev);
+ }
+
+ /* Reflect the deletion of the selectors linked list. */
+ Files = NULL;
+ bHaveMainQueue = 0;
+ }
+}
+
+
+/* helper to dbPrintInitInfo, to print out all actions via
+ * the llExecFunc() facility.
+ * rgerhards, 2007-08-02
+ */
+DEFFUNC_llExecFunc(dbgPrintInitInfoAction)
+{
+ DEFiRet;
+ iRet = actionDbgPrint((action_t*) pData);
+ dbgprintf("\n");
+
+ RETiRet;
+}
+
+/* print debug information as part of init(). This pretty much
+ * outputs the whole config of rsyslogd. I've moved this code
+ * out of init() to clean it somewhat up.
+ * rgerhards, 2007-07-31
+ */
+static void dbgPrintInitInfo(void)
+{
+ register selector_t *f;
+ int iSelNbr = 1;
+ int i;
+
+ dbgprintf("\nActive selectors:\n");
+ for (f = Files; f != NULL ; f = f->f_next) {
+ dbgprintf("Selector %d:\n", iSelNbr++);
+ if(f->pCSProgNameComp != NULL)
+ dbgprintf("tag: '%s'\n", rsCStrGetSzStrNoNULL(f->pCSProgNameComp));
+ if(f->eHostnameCmpMode != HN_NO_COMP)
+ dbgprintf("hostname: %s '%s'\n",
+ f->eHostnameCmpMode == HN_COMP_MATCH ?
+ "only" : "allbut",
+ rsCStrGetSzStrNoNULL(f->pCSHostnameComp));
+ if(f->f_filter_type == FILTER_PRI) {
+ for (i = 0; i <= LOG_NFACILITIES; i++)
+ if (f->f_filterData.f_pmask[i] == TABLE_NOPRI)
+ dbgprintf(" X ");
+ else
+ dbgprintf("%2X ", f->f_filterData.f_pmask[i]);
+ } else if(f->f_filter_type == FILTER_EXPR) {
+ dbgprintf("EXPRESSION-BASED Filter: can currently not be displayed");
+ } else {
+ dbgprintf("PROPERTY-BASED Filter:\n");
+ dbgprintf("\tProperty.: '%s'\n",
+ rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName));
+ dbgprintf("\tOperation: ");
+ if(f->f_filterData.prop.isNegated)
+ dbgprintf("NOT ");
+ dbgprintf("'%s'\n", getFIOPName(f->f_filterData.prop.operation));
+ dbgprintf("\tValue....: '%s'\n",
+ rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue));
+ dbgprintf("\tAction...: ");
+ }
+
+ dbgprintf("\nActions:\n");
+ llExecFunc(&f->llActList, dbgPrintInitInfoAction, NULL); /* actions */
+
+ dbgprintf("\n");
+ }
+ dbgprintf("\n");
+ if(bDebugPrintTemplateList)
+ tplPrintList();
+ if(bDebugPrintModuleList)
+ module.PrintList();
+ ochPrintList();
+
+ if(bDebugPrintCfSysLineHandlerList)
+ dbgPrintCfSysLineHandlers();
+
+ dbgprintf("Messages with malicious PTR DNS Records are %sdropped.\n",
+ glbl.GetDropMalPTRMsgs() ? "" : "not ");
+
+ dbgprintf("Control characters are %sreplaced upon reception.\n",
+ bEscapeCCOnRcv? "" : "not ");
+
+ if(bEscapeCCOnRcv)
+ dbgprintf("Control character escape sequence prefix is '%c'.\n",
+ cCCEscapeChar);
+
+ dbgprintf("Main queue size %d messages.\n", iMainMsgQueueSize);
+ dbgprintf("Main queue worker threads: %d, Perists every %d updates.\n",
+ iMainMsgQueueNumWorkers, iMainMsgQPersistUpdCnt);
+ dbgprintf("Main queue timeouts: shutdown: %d, action completion shutdown: %d, enq: %d\n",
+ iMainMsgQtoQShutdown, iMainMsgQtoActShutdown, iMainMsgQtoEnq);
+ dbgprintf("Main queue watermarks: high: %d, low: %d, discard: %d, discard-severity: %d\n",
+ iMainMsgQHighWtrMark, iMainMsgQLowWtrMark, iMainMsgQDiscardMark, iMainMsgQDiscardSeverity);
+ dbgprintf("Main queue save on shutdown %d, max disk space allowed %lld\n",
+ bMainMsgQSaveOnShutdown, iMainMsgQueMaxDiskSpace);
+ /* TODO: add
+ iActionRetryCount = 0;
+ iActionRetryInterval = 30000;
+ static int iMainMsgQtoWrkShutdown = 60000;
+ static int iMainMsgQtoWrkMinMsgs = 100;
+ static int iMainMsgQbSaveOnShutdown = 1;
+ iMainMsgQueMaxDiskSpace = 0;
+ setQPROP(queueSettoWrkShutdown, "$MainMsgQueueTimeoutWorkerThreadShutdown", 5000);
+ setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", 100);
+ setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", 1);
+ */
+ dbgprintf("Work Directory: '%s'.\n", glbl.GetWorkDir());
+}
+
+
+/* Start the input modules. This function will probably undergo big changes
+ * while we implement the input module interface. For now, it does the most
+ * important thing to get at least my poor initial input modules up and
+ * running. Almost no config option is taken.
+ * rgerhards, 2007-12-14
+ */
+static rsRetVal
+startInputModules(void)
+{
+ DEFiRet;
+ modInfo_t *pMod;
+
+ /* loop through all modules and activate them (brr...) */
+ pMod = module.GetNxtType(NULL, eMOD_IN);
+ while(pMod != NULL) {
+ if((iRet = pMod->mod.im.willRun()) == RS_RET_OK) {
+ /* activate here */
+ thrdCreate(pMod->mod.im.runInput, pMod->mod.im.afterRun);
+ } else {
+ dbgprintf("module %lx will not run, iRet %d\n", (unsigned long) pMod, iRet);
+ }
+ pMod = module.GetNxtType(pMod, eMOD_IN);
+ }
+
+ ENDfunc
+ return RS_RET_OK; /* intentional: we do not care about module errors */
+}
+
+
+/* INIT -- Initialize syslogd from configuration table
+ * init() is called at initial startup AND each time syslogd is HUPed
+ */
+static void
+init(void)
+{
+ DEFiRet;
+ char cbuf[BUFSIZ];
+ char bufStartUpMsg[512];
+ struct sigaction sigAct;
+
+ thrdTerminateAll(); /* stop all running input threads - TODO: reconsider location! */
+
+ /* initialize some static variables */
+ pDfltHostnameCmp = NULL;
+ pDfltProgNameCmp = NULL;
+ eDfltHostnameCmpMode = HN_NO_COMP;
+
+ dbgprintf("rsyslog %s - called init()\n", VERSION);
+
+ /* delete the message queue, which also flushes all messages left over */
+ if(pMsgQueue != NULL) {
+ dbgprintf("deleting main message queue\n");
+ queueDestruct(&pMsgQueue); /* delete pThis here! */
+ pMsgQueue = NULL;
+ }
+
+ /* Close all open log files and free log descriptor array. This also frees
+ * all output-modules instance data.
+ */
+ freeSelectors();
+
+ /* Unload all non-static modules */
+ dbgprintf("Unloading non-static modules.\n");
+ module.UnloadAndDestructAll(eMOD_LINK_DYNAMIC_LOADED);
+
+ dbgprintf("Clearing templates.\n");
+ tplDeleteNew();
+
+ /* re-setting values to defaults (where applicable) */
+ /* once we have loadable modules, we must re-visit this code. The reason is
+ * that config variables are not re-set, because the module is not yet loaded. On
+ * the other hand, that doesn't matter, because the module got unloaded and is then
+ * re-loaded, so the variables should be re-set via that way. And this is exactly how
+ * it works. Loadable module's variables are initialized on load, the rest here.
+ * rgerhards, 2008-04-28
+ */
+ conf.cfsysline((uchar*)"ResetConfigVariables");
+
+ /* open the configuration file */
+ if((iRet = conf.processConfFile(ConfFile)) != RS_RET_OK) {
+ /* rgerhards: this code is executed to set defaults when the
+ * config file could not be opened. We might think about
+ * abandoning the run in this case - but this, too, is not
+ * very clever... So we stick with what we have.
+ * We ignore any errors while doing this - we would be lost anyhow...
+ */
+ selector_t *f = NULL;
+ char szTTYNameBuf[_POSIX_TTY_NAME_MAX+1]; /* +1 for NULL character */
+ dbgprintf("primary config file could not be opened - using emergency definitions.\n");
+ conf.cfline((uchar*)"*.ERR\t" _PATH_CONSOLE, &f);
+ conf.cfline((uchar*)"*.PANIC\t*", &f);
+ if(ttyname_r(0, szTTYNameBuf, sizeof(szTTYNameBuf)) == 0) {
+ snprintf(cbuf,sizeof(cbuf), "*.*\t%s", szTTYNameBuf);
+ conf.cfline((uchar*)cbuf, &f);
+ }
+ selectorAddList(f);
+ }
+
+ legacyOptsHook();
+
+ /* we are now done with reading the configuration. This is the right time to
+ * free some objects that were just needed for loading it. rgerhards 2005-10-19
+ */
+ if(pDfltHostnameCmp != NULL) {
+ rsCStrDestruct(&pDfltHostnameCmp);
+ }
+
+ if(pDfltProgNameCmp != NULL) {
+ rsCStrDestruct(&pDfltProgNameCmp);
+ }
+
+ /* some checks */
+ if(iMainMsgQueueNumWorkers < 1) {
+ errmsg.LogError(0, NO_ERRCODE, "$MainMsgQueueNumWorkers must be at least 1! Set to 1.\n");
+ iMainMsgQueueNumWorkers = 1;
+ }
+
+ if(MainMsgQueType == QUEUETYPE_DISK) {
+ errno = 0; /* for logerror! */
+ if(glbl.GetWorkDir() == NULL) {
+ errmsg.LogError(0, NO_ERRCODE, "No $WorkDirectory specified - can not run main message queue in 'disk' mode. "
+ "Using 'FixedArray' instead.\n");
+ MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ }
+ if(pszMainMsgQFName == NULL) {
+ errmsg.LogError(0, NO_ERRCODE, "No $MainMsgQueueFileName specified - can not run main message queue in "
+ "'disk' mode. Using 'FixedArray' instead.\n");
+ MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ }
+ }
+
+ /* switch the message object to threaded operation, if necessary */
+ if(MainMsgQueType == QUEUETYPE_DIRECT || iMainMsgQueueNumWorkers > 1) {
+ MsgEnableThreadSafety();
+ }
+
+ /* create message queue */
+ CHKiRet_Hdlr(queueConstruct(&pMsgQueue, MainMsgQueType, iMainMsgQueueNumWorkers, iMainMsgQueueSize, msgConsumer)) {
+ /* 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);
+ exit(1);
+ }
+ /* name our main queue object (it's not fatal if it fails...) */
+ obj.SetName((obj_t*) pMsgQueue, (uchar*) "main queue");
+
+ /* ... set some properties ... */
+# define setQPROP(func, directive, data) \
+ CHKiRet_Hdlr(func(pMsgQueue, data)) { \
+ errmsg.LogError(0, NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \
+ }
+# define setQPROPstr(func, directive, data) \
+ CHKiRet_Hdlr(func(pMsgQueue, data, (data == NULL)? 0 : strlen((char*) data))) { \
+ errmsg.LogError(0, NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, running with default setting", iRet); \
+ }
+
+ setQPROP(queueSetMaxFileSize, "$MainMsgQueueFileSize", iMainMsgQueMaxFileSize);
+ setQPROP(queueSetsizeOnDiskMax, "$MainMsgQueueMaxDiskSpace", iMainMsgQueMaxDiskSpace);
+ setQPROPstr(queueSetFilePrefix, "$MainMsgQueueFileName", pszMainMsgQFName);
+ setQPROP(queueSetiPersistUpdCnt, "$MainMsgQueueCheckpointInterval", iMainMsgQPersistUpdCnt);
+ setQPROP(queueSettoQShutdown, "$MainMsgQueueTimeoutShutdown", iMainMsgQtoQShutdown );
+ setQPROP(queueSettoActShutdown, "$MainMsgQueueTimeoutActionCompletion", iMainMsgQtoActShutdown);
+ setQPROP(queueSettoWrkShutdown, "$MainMsgQueueWorkerTimeoutThreadShutdown", iMainMsgQtoWrkShutdown);
+ setQPROP(queueSettoEnq, "$MainMsgQueueTimeoutEnqueue", iMainMsgQtoEnq);
+ setQPROP(queueSetiHighWtrMrk, "$MainMsgQueueHighWaterMark", iMainMsgQHighWtrMark);
+ setQPROP(queueSetiLowWtrMrk, "$MainMsgQueueLowWaterMark", iMainMsgQLowWtrMark);
+ setQPROP(queueSetiDiscardMrk, "$MainMsgQueueDiscardMark", iMainMsgQDiscardMark);
+ setQPROP(queueSetiDiscardSeverity, "$MainMsgQueueDiscardSeverity", iMainMsgQDiscardSeverity);
+ setQPROP(queueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages", iMainMsgQWrkMinMsgs);
+ setQPROP(queueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown", bMainMsgQSaveOnShutdown);
+ setQPROP(queueSetiDeqSlowdown, "$MainMsgQueueDequeueSlowdown", iMainMsgQDeqSlowdown);
+ setQPROP(queueSetiDeqtWinFromHr, "$MainMsgQueueDequeueTimeBegin", iMainMsgQueueDeqtWinFromHr);
+ setQPROP(queueSetiDeqtWinToHr, "$MainMsgQueueDequeueTimeEnd", iMainMsgQueueDeqtWinToHr);
+
+# undef setQPROP
+# undef setQPROPstr
+
+ /* ... and finally start the queue! */
+ CHKiRet_Hdlr(queueStart(pMsgQueue)) {
+ /* no queue is fatal, we need to give up in that case... */
+ fprintf(stderr, "fatal error %d: could not start message queue - rsyslogd can not run!\n", iRet);
+ exit(1);
+ }
+
+ bHaveMainQueue = (MainMsgQueType == QUEUETYPE_DIRECT) ? 0 : 1;
+ dbgprintf("Main processing queue is initialized and running\n");
+
+ /* the output part and the queue is now ready to run. So it is a good time
+ * to start 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
+ */
+ startInputModules();
+
+ if(Debug) {
+ dbgPrintInitInfo();
+ }
+
+ /* we now generate the startup message. It now includes everything to
+ * identify this instance. -- rgerhards, 2005-08-17
+ */
+ snprintf(bufStartUpMsg, sizeof(bufStartUpMsg)/sizeof(char),
+ " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \
+ "\" x-pid=\"%d\" x-info=\"http://www.rsyslog.com\"] restart",
+ (int) myPid);
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)bufStartUpMsg, ADDDATE);
+
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = sighup_handler;
+ sigaction(SIGHUP, &sigAct, NULL);
+
+ dbgprintf(" (re)started.\n");
+ ENDfunc
+}
+
+
+/* add a completely-processed selector (after config line parsing) to
+ * the linked list of selectors. We now need to check
+ * if it has any actions associated and, if so, link it to the linked
+ * list. If it has nothing associated with it, we can simply discard
+ * it.
+ * We have one special case during initialization: then, the current
+ * selector is NULL, which means we do not need to care about it at
+ * all. -- rgerhards, 2007-08-01
+ */
+rsRetVal
+selectorAddList(selector_t *f)
+{
+ DEFiRet;
+ int iActionCnt;
+
+ static selector_t *nextp = NULL; /* TODO: make this go away (see comment below) */
+
+ if(f != NULL) {
+ CHKiRet(llGetNumElts(&f->llActList, &iActionCnt));
+ if(iActionCnt == 0) {
+ errmsg.LogError(0, NO_ERRCODE, "warning: selector line without actions will be discarded");
+ selectorDestruct(f);
+ } else {
+ /* successfully created an entry */
+ dbgprintf("selector line successfully processed\n");
+ /* TODO: we should use the linked list class for the selector list, else we need to add globals
+ * ... well nextp could be added temporarily...
+ * Thanks to varmojfekoj for having the idea to just use "Files" to make this
+ * code work. I had actually forgotten to fix the code here before moving to 1.18.0.
+ * And, of course, I also did not migrate the selector_t structure to the linked list class.
+ * However, that should still be one of the very next things to happen.
+ * rgerhards, 2007-08-06
+ */
+ if(Files == NULL) {
+ Files = f;
+ } else {
+ nextp->f_next = f;
+ }
+ nextp = f;
+ }
+ }
+
+finalize_it:
+ 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")) {
+ MainMsgQueType = QUEUETYPE_FIXED_ARRAY;
+ dbgprintf("main message queue type set to FIXED_ARRAY\n");
+ } else if (!strcasecmp((char *) pszType, "linkedlist")) {
+ MainMsgQueType = QUEUETYPE_LINKEDLIST;
+ dbgprintf("main message queue type set to LINKEDLIST\n");
+ } else if (!strcasecmp((char *) pszType, "disk")) {
+ MainMsgQueType = QUEUETYPE_DISK;
+ dbgprintf("main message queue type set to DISK\n");
+ } else if (!strcasecmp((char *) pszType, "direct")) {
+ MainMsgQueType = QUEUETYPE_DIRECT;
+ dbgprintf("main message queue type set to DIRECT (no queueing at all)\n");
+ } else {
+ errmsg.LogError(0, RS_RET_INVALID_PARAMS, "unknown mainmessagequeuetype parameter: %s", (char *) pszType);
+ iRet = RS_RET_INVALID_PARAMS;
+ }
+ free(pszType); /* no longer needed */
+
+ RETiRet;
+}
+
+
+/*
+ * The following function is resposible for handling a SIGHUP signal. Since
+ * we are now doing mallocs/free as part of init we had better not being
+ * doing this during a signal handler. Instead this function simply sets
+ * a flag variable which will tell the main loop to go through a restart.
+ */
+void sighup_handler()
+{
+ struct sigaction sigAct;
+
+ restart = 1;
+
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = sighup_handler;
+ sigaction(SIGHUP, &sigAct, NULL);
+
+ return;
+}
+
+
+/* this function pulls all internal messages from the buffer
+ * and puts them into the processing engine.
+ * We can only do limited error handling, as this would not
+ * really help us. TODO: add error messages?
+ * rgerhards, 2007-08-03
+ */
+static void processImInternal(void)
+{
+ int iPri;
+ int iFlags;
+ msg_t *pMsg;
+
+ while(iminternalRemoveMsg(&iPri, &pMsg, &iFlags) == RS_RET_OK) {
+ logmsg(pMsg, iFlags);
+ }
+}
+
+
+/* This is the main processing loop. It is called after successful initialization.
+ * When it returns, the syslogd terminates.
+ * Its sole function is to provide some housekeeping things. The real work is done
+ * by the other threads spawned.
+ */
+static void
+mainloop(void)
+{
+ struct timeval tvSelectTimeout;
+
+ BEGINfunc
+ /* first check if we have any internal messages queued and spit them out. We used
+ * to do that on any loop iteration, but that is no longer necessry. The reason
+ * is that once we reach this point here, we always run on multiple threads and
+ * thus the main queue is properly initialized. -- rgerhards, 2008-06-09
+ */
+ processImInternal();
+
+ while(!bFinished){
+ /* this is now just a wait - please note that we do use a near-"eternal"
+ * timeout of 1 day if we do not have repeated message reduction turned on
+ * (which it is not by default). This enables us to help safe the environment
+ * by not unnecessarily awaking rsyslog on a regular tick (just think
+ * powertop, for example). In that case, we primarily wait for a signal,
+ * but a once-a-day wakeup should be quite acceptable. -- rgerhards, 2008-06-09
+ */
+ tvSelectTimeout.tv_sec = (bReduceRepeatMsgs == 1) ? TIMERINTVL : 86400 /*1 day*/;
+ tvSelectTimeout.tv_usec = 0;
+ select(1, NULL, NULL, NULL, &tvSelectTimeout);
+ if(bFinished)
+ break; /* exit as quickly as possible - see long comment below */
+
+ /* If we received a HUP signal, we call doFlushRptdMsgs() a bit early. This
+ * doesn't matter, because doFlushRptdMsgs() checks timestamps. What may happen,
+ * however, is that the too-early call may lead to a bit too-late output
+ * of "last message repeated n times" messages. But that is quite acceptable.
+ * rgerhards, 2007-12-21
+ * ... and just to explain, we flush here because that is exactly what the mainloop
+ * shall do - provide a periodic interval in which not-yet-flushed messages will
+ * be flushed. Be careful, there is a potential race condition: doFlushRptdMsgs()
+ * needs to aquire a lock on the action objects. If, however, long-running consumers
+ * cause the main queue worker threads to lock them for a long time, we may receive
+ * a starvation condition, resulting in the mainloop being held on lock for an extended
+ * period of time. That, in turn, could lead to unresponsiveness to termination
+ * requests. It is especially important that the bFinished flag is checked before
+ * doFlushRptdMsgs() is called (I know because I ran into that situation). I am
+ * not yet sure if the remaining probability window of a termination-related
+ * problem is large enough to justify changing the code - I would consider it
+ * extremely unlikely that the problem ever occurs in practice. Fixing it would
+ * require not only a lot of effort but would cost considerable performance. So
+ * for the time being, I think the remaining risk can be accepted.
+ * rgerhards, 2008-01-10
+ */
+ if(bReduceRepeatMsgs == 1)
+ doFlushRptdMsgs();
+
+ if(restart) {
+ dbgprintf("\nReceived SIGHUP, reloading rsyslogd.\n");
+ /* main queue is stopped as part of init() */
+ init();
+ restart = 0;
+ continue;
+ }
+ }
+ ENDfunc
+}
+
+/* If user is not root, prints warnings or even exits
+ * TODO: check all dynafiles for write permission
+ * ... but it is probably better to wait here until we have
+ * a module interface - rgerhards, 2007-07-23
+ */
+static void checkPermissions()
+{
+#if 0
+ /* TODO: this function must either be redone or removed - now with the input modules,
+ * there is no such simple check we can do. What we can check, however, is if there is
+ * any input module active and terminate, if not. -- rgerhards, 2007-12-26
+ */
+ /* we are not root */
+ if (geteuid() != 0)
+ {
+ fputs("WARNING: Local messages will not be logged! If you want to log them, run rsyslog as root.\n",stderr);
+#ifdef SYSLOG_INET
+ /* udp enabled and port number less than or equal to 1024 */
+ if ( AcceptRemote && (atoi(LogPort) <= 1024) )
+ fprintf(stderr, "WARNING: Will not listen on UDP port %s. Use port number higher than 1024 or run rsyslog as root!\n", LogPort);
+
+ /* tcp enabled and port number less or equal to 1024 */
+ if( bEnableTCP && (atoi(TCPLstnPort) <= 1024) )
+ fprintf(stderr, "WARNING: Will not listen on TCP port %s. Use port number higher than 1024 or run rsyslog as root!\n", TCPLstnPort);
+
+ /* Neither explicit high UDP port nor explicit high TCP port.
+ * It is useless to run anymore */
+ if( !(AcceptRemote && (atoi(LogPort) > 1024)) && !( bEnableTCP && (atoi(TCPLstnPort) > 1024)) )
+ {
+#endif
+ fprintf(stderr, "ERROR: Nothing to log, no reason to run. Please run rsyslog as root.\n");
+ exit(EXIT_FAILURE);
+#ifdef SYSLOG_INET
+ }
+#endif
+ }
+#endif
+}
+
+
+/* load build-in modules
+ * very first version begun on 2007-07-23 by rgerhards
+ */
+static rsRetVal loadBuildInModules(void)
+{
+ DEFiRet;
+
+ if((iRet = module.doModInit(modInitFile, (uchar*) "builtin-file", NULL)) != RS_RET_OK) {
+ RETiRet;
+ }
+#ifdef SYSLOG_INET
+ if((iRet = module.doModInit(modInitFwd, (uchar*) "builtin-fwd", NULL)) != RS_RET_OK) {
+ RETiRet;
+ }
+#endif
+ if((iRet = module.doModInit(modInitShell, (uchar*) "builtin-shell", NULL)) != RS_RET_OK) {
+ RETiRet;
+ }
+ if((iRet = module.doModInit(modInitDiscard, (uchar*) "builtin-discard", NULL)) != RS_RET_OK) {
+ RETiRet;
+ }
+
+ /* 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 time 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_.]
+ */
+ if((iRet = module.doModInit(modInitUsrMsg, (uchar*) "builtin-usrmsg", NULL)) != RS_RET_OK)
+ RETiRet;
+
+ /* ok, initialization of the command handler probably does not 100% belong right in
+ * this space here. However, with the current design, this is actually quite a good
+ * place to put it. We might decide to shuffle it around later, but for the time
+ * being, the code has found its home here. A not-just-sideeffect of this decision
+ * is that rsyslog will terminate if we can not register our built-in config commands.
+ * This, I think, is the right thing to do. -- rgerhards, 2007-07-31
+ */
+// CHKiRet(regCfSysLineHdlr((uchar *)"workdirectory", 0, eCmdHdlrGetWord, NULL, &pszWorkDir, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeretrycount", 0, eCmdHdlrInt, NULL, &glbliActionResumeRetryCount, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuefilename", 0, eCmdHdlrGetWord, NULL, &pszMainMsgQFName, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesize", 0, eCmdHdlrInt, NULL, &iMainMsgQueueSize, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuehighwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQHighWtrMark, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuelowwatermark", 0, eCmdHdlrInt, NULL, &iMainMsgQLowWtrMark, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardmark", 0, eCmdHdlrInt, NULL, &iMainMsgQDiscardMark, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuediscardseverity", 0, eCmdHdlrSeverity, NULL, &iMainMsgQDiscardSeverity, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuecheckpointinterval", 0, eCmdHdlrInt, NULL, &iMainMsgQPersistUpdCnt, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetype", 0, eCmdHdlrGetWord, setMainMsgQueType, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreads", 0, eCmdHdlrInt, NULL, &iMainMsgQueueNumWorkers, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoQShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutactioncompletion", 0, eCmdHdlrInt, NULL, &iMainMsgQtoActShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuetimeoutenqueue", 0, eCmdHdlrInt, NULL, &iMainMsgQtoEnq, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworketimeoutrthreadshutdown", 0, eCmdHdlrInt, NULL, &iMainMsgQtoWrkShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeueslowdown", 0, eCmdHdlrInt, NULL, &iMainMsgQDeqSlowdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueueworkerthreadminimummessages", 0, eCmdHdlrInt, NULL, &iMainMsgQWrkMinMsgs, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxfilesize", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxFileSize, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuemaxdiskspace", 0, eCmdHdlrSize, NULL, &iMainMsgQueMaxDiskSpace, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuesaveonshutdown", 0, eCmdHdlrBinary, NULL, &bMainMsgQSaveOnShutdown, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimebegin", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinFromHr, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"mainmsgqueuedequeuetimeend", 0, eCmdHdlrInt, NULL, &iMainMsgQueueDeqtWinToHr, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgreduction", 0, eCmdHdlrBinary, NULL, &bReduceRepeatMsgs, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlywhenpreviousissuspended", 0, eCmdHdlrBinary, NULL, &bActExecWhenPrevSusp, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlyonceeveryinterval", 0, eCmdHdlrInt, NULL, &iActExecOnceInterval, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"actionresumeinterval", 0, eCmdHdlrInt, setActionResumeInterval, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"controlcharacterescapeprefix", 0, eCmdHdlrGetChar, NULL, &cCCEscapeChar, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"escapecontrolcharactersonreceive", 0, eCmdHdlrBinary, NULL, &bEscapeCCOnRcv, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary, NULL, &bDropTrailingLF, NULL));
+ 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));
+ CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler, conf.doModLoad, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"includeconfig", 0, eCmdHdlrCustomHandler, conf.doIncludeLine, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"umask", 0, eCmdHdlrFileCreateMode, setUmask, NULL, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"debugprinttemplatelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintTemplateList, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"debugprintmodulelist", 0, eCmdHdlrBinary, NULL, &bDebugPrintModuleList, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"debugprintcfsyslinehandlerlist", 0, eCmdHdlrBinary,
+ NULL, &bDebugPrintCfSysLineHandlerList, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"moddir", 0, eCmdHdlrGetWord, NULL, &pModDir, NULL));
+ CHKiRet(regCfSysLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL, NULL));
+
+ /* now add other modules handlers (we should work on that to be able to do it in ClassInit(), but so far
+ * that is not possible). -- rgerhards, 2008-01-28
+ */
+ CHKiRet(actionAddCfSysLineHdrl());
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* print version and compile-time setting information.
+ */
+static void printVersion(void)
+{
+ printf("rsyslogd %s, ", VERSION);
+ printf("compiled with:\n");
+#ifdef FEATURE_REGEXP
+ printf("\tFEATURE_REGEXP:\t\t\t\tYes\n");
+#else
+ printf("\tFEATURE_REGEXP:\t\t\t\tNo\n");
+#endif
+#ifndef NOLARGEFILE
+ printf("\tFEATURE_LARGEFILE:\t\t\tYes\n");
+#else
+ printf("\tFEATURE_LARGEFILE:\t\t\tNo\n");
+#endif
+#ifdef USE_NETZIP
+ printf("\tFEATURE_NETZIP (message compression):\tYes\n");
+#else
+ printf("\tFEATURE_NETZIP (message compression):\tNo\n");
+#endif
+#if defined(SYSLOG_INET) && defined(USE_GSSAPI)
+ printf("\tGSSAPI Kerberos 5 support:\t\tYes\n");
+#else
+ printf("\tGSSAPI Kerberos 5 support:\t\tNo\n");
+#endif
+#ifndef NDEBUG
+ printf("\tFEATURE_DEBUG (debug build, slow code):\tYes\n");
+#else
+ printf("\tFEATURE_DEBUG (debug build, slow code):\tNo\n");
+#endif
+#ifdef RTINST
+ printf("\tRuntime Instrumentation (slow code):\tYes\n");
+#else
+ printf("\tRuntime Instrumentation (slow code):\tNo\n");
+#endif
+ printf("\nSee http://www.rsyslog.com for more information.\n");
+}
+
+
+/* This function is called after initial initalization. It is used to
+ * move code out of the too-long main() function.
+ * rgerhards, 2007-10-17
+ */
+static void mainThread()
+{
+ BEGINfunc
+ uchar *pTmp;
+
+ /* Note: signals MUST be processed by the thread this code is running in. The reason
+ * is that we need to interrupt the select() system call. -- rgerhards, 2007-10-17
+ */
+
+ /* initialize the build-in templates */
+ pTmp = template_DebugFormat;
+ tplAddLine("RSYSLOG_DebugFormat", &pTmp);
+ pTmp = template_SyslogProtocol23Format;
+ tplAddLine("RSYSLOG_SyslogProtocol23Format", &pTmp);
+ pTmp = template_FileFormat; /* new format for files with high-precision stamp */
+ tplAddLine("RSYSLOG_FileFormat", &pTmp);
+ pTmp = template_TraditionalFileFormat;
+ tplAddLine("RSYSLOG_TraditionalFileFormat", &pTmp);
+ pTmp = template_WallFmt;
+ tplAddLine(" WallFmt", &pTmp);
+ pTmp = template_ForwardFormat;
+ tplAddLine("RSYSLOG_ForwardFormat", &pTmp);
+ pTmp = template_TraditionalForwardFormat;
+ tplAddLine("RSYSLOG_TraditionalForwardFormat", &pTmp);
+ pTmp = template_StdUsrMsgFmt;
+ tplAddLine(" StdUsrMsgFmt", &pTmp);
+ pTmp = template_StdDBFmt;
+ tplAddLine(" StdDBFmt", &pTmp);
+ pTmp = template_StdPgSQLFmt;
+ tplLastStaticInit(tplAddLine(" StdPgSQLFmt", &pTmp));
+
+ init();
+ if(Debug) {
+ dbgprintf("Debugging enabled, SIGUSR1 to turn off debugging.\n");
+ debugging_on = 1;
+ }
+ /* Send a signal to the parent so it can terminate.
+ */
+ if (myPid != ppid)
+ kill (ppid, SIGTERM);
+
+ /* END OF INTIALIZATION
+ * ... but keep in mind that we might do a restart and thus init() might
+ * be called again. If that happens, we must shut down the worker thread,
+ * do the init() and then restart things.
+ * rgerhards, 2005-10-24
+ */
+ dbgprintf("initialization completed, transitioning to regular run mode\n");
+
+ mainloop();
+ ENDfunc
+}
+
+
+/* Method to initialize all global classes and use the objects that we need.
+ * rgerhards, 2008-01-04
+ * rgerhards, 2008-04-16: the actual initialization is now carried out by the runtime
+ */
+static rsRetVal
+InitGlobalClasses(void)
+{
+ DEFiRet;
+ char *pErrObj; /* tells us which object failed if that happens (useful for troubleshooting!) */
+
+ /* Intialize the runtime system */
+ pErrObj = "rsyslog runtime"; /* set in case the runtime errors before setting an object */
+ CHKiRet(rsrtInit(&pErrObj, &obj));
+ CHKiRet(rsrtSetErrLogger(submitErrMsg)); /* set out error handler */
+
+ /* Now tell the system which classes we need ourselfs */
+ pErrObj = "glbl";
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ pErrObj = "errmsg";
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ pErrObj = "module";
+ CHKiRet(objUse(module, CORE_COMPONENT));
+ pErrObj = "var";
+ CHKiRet(objUse(var, CORE_COMPONENT));
+ pErrObj = "datetime";
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ pErrObj = "vm";
+ CHKiRet(objUse(vm, CORE_COMPONENT));
+ pErrObj = "expr";
+ CHKiRet(objUse(expr, CORE_COMPONENT));
+ pErrObj = "conf";
+ CHKiRet(objUse(conf, CORE_COMPONENT));
+
+ /* intialize some dummy classes that are not part of the runtime */
+ pErrObj = "action";
+ CHKiRet(actionClassInit());
+ pErrObj = "template";
+ CHKiRet(templateInit());
+
+ /* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */
+ pErrObj = "net";
+ CHKiRet(objUse(net, LM_NET_FILENAME));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ /* we know we are inside the init sequence, so we can safely emit
+ * messages to stderr. -- rgerhards, 2008-04-02
+ */
+ fprintf(stderr, "Error during class init for object '%s' - failing...\n", pErrObj);
+ }
+
+ RETiRet;
+}
+
+
+/* Method to exit all global classes. We do not do any error checking here,
+ * because that wouldn't help us at all. So better try to deinit blindly
+ * as much as succeeds (which usually means everything will). We just must
+ * be careful to do the de-init in the opposite order of the init, because
+ * of the dependencies. However, its not as important this time, because
+ * we have reference counting.
+ * rgerhards, 2008-03-10
+ */
+static rsRetVal
+GlobalClassExit(void)
+{
+ DEFiRet;
+
+ /* first, release everything we used ourself */
+ objRelease(net, LM_NET_FILENAME);/* TODO: the dependency on net shall go away! -- rgerhards, 2008-03-07 */
+ objRelease(conf, CORE_COMPONENT);
+ objRelease(expr, CORE_COMPONENT);
+ objRelease(vm, CORE_COMPONENT);
+ objRelease(var, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+
+ /* TODO: implement the rest of the deinit */
+#if 0
+ CHKiRet(datetimeClassInit(NULL));
+ CHKiRet(msgClassInit(NULL));
+ CHKiRet(strmClassInit(NULL));
+ CHKiRet(wtiClassInit(NULL));
+ CHKiRet(wtpClassInit(NULL));
+ CHKiRet(queueClassInit(NULL));
+ CHKiRet(vmstkClassInit(NULL));
+ CHKiRet(sysvarClassInit(NULL));
+ CHKiRet(vmClassInit(NULL));
+ CHKiRet(vmopClassInit(NULL));
+ CHKiRet(vmprgClassInit(NULL));
+ CHKiRet(ctok_tokenClassInit(NULL));
+ CHKiRet(ctokClassInit(NULL));
+ CHKiRet(exprClassInit(NULL));
+
+ /* dummy "classes" */
+ CHKiRet(actionClassInit());
+ CHKiRet(templateInit());
+#endif
+ /* dummy "classes */
+ strExit();
+
+#if 0
+ CHKiRet(objGetObjInterface(&obj)); /* this provides the root pointer for all other queries */
+ /* the following classes were intialized by objClassInit() */
+ CHKiRet(objUse(errmsg, CORE_COMPONENT));
+ CHKiRet(objUse(module, CORE_COMPONENT));
+#endif
+ rsrtExit(); /* *THIS* *MUST/SHOULD?* always be the first class initilizer being called (except debug)! */
+
+ RETiRet;
+}
+
+
+/* some support for command line option parsing. Any non-trivial options must be
+ * buffered until the complete command line has been parsed. This is necessary to
+ * prevent dependencies between the options. That, in turn, means we need to have
+ * something that is capable of buffering options and there values. The follwing
+ * functions handle that.
+ * rgerhards, 2008-04-04
+ */
+typedef struct bufOpt {
+ struct bufOpt *pNext;
+ char optchar;
+ char *arg;
+} bufOpt_t;
+static bufOpt_t *bufOptRoot = NULL;
+static bufOpt_t *bufOptLast = NULL;
+
+/* add option buffer */
+static rsRetVal
+bufOptAdd(char opt, char *arg)
+{
+ DEFiRet;
+ bufOpt_t *pBuf;
+
+ if((pBuf = malloc(sizeof(bufOpt_t))) == NULL)
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+
+ pBuf->optchar = opt;
+ pBuf->arg = arg;
+ pBuf->pNext = NULL;
+
+ if(bufOptLast == NULL) {
+ bufOptRoot = pBuf; /* then there is also no root! */
+ } else {
+ bufOptLast->pNext = pBuf;
+ }
+ bufOptLast = pBuf;
+
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* remove option buffer from top of list, return values and destruct buffer itself.
+ * returns RS_RET_END_OF_LINKEDLIST when no more options are present.
+ * (we use int *opt instead of char *opt to keep consistent with getopt())
+ */
+static rsRetVal
+bufOptRemove(int *opt, char **arg)
+{
+ DEFiRet;
+ bufOpt_t *pBuf;
+
+ if(bufOptRoot == NULL)
+ ABORT_FINALIZE(RS_RET_END_OF_LINKEDLIST);
+ pBuf = bufOptRoot;
+
+ *opt = pBuf->optchar;
+ *arg = pBuf->arg;
+
+ bufOptRoot = pBuf->pNext;
+ free(pBuf);
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* This is the main entry point into rsyslogd. Over time, we should try to
+ * modularize it a bit more...
+ */
+int realMain(int argc, char **argv)
+{
+ DEFiRet;
+
+ register int i;
+ register uchar *p;
+ int num_fds;
+ int ch;
+ struct hostent *hent;
+ extern int optind;
+ extern char *optarg;
+ struct sigaction sigAct;
+ int bEOptionWasGiven = 0;
+ int bImUxSockLoaded = 0; /* already generated a $ModLoad imuxsock? */
+ char *arg; /* for command line option processing */
+ uchar legacyConfLine[80];
+ uchar *LocalHostName;
+ uchar *LocalDomain;
+
+ /* first, parse the command line options. We do not carry out any actual work, just
+ * see what we should do. This relieves us from certain anomalies and we can process
+ * the parameters down below in the correct order. For example, we must know the
+ * value of -M before we can do the init, but at the same time we need to have
+ * the base classes init before we can process most of the options. Now, with the
+ * split of functionality, this is no longer a problem. Thanks to varmofekoj for
+ * suggesting this algo.
+ * Note: where we just need to set some flags and can do so without knowledge
+ * of other options, we do this during the inital option processing. With later
+ * versions (if a dependency on -c option is introduced), we must move that code
+ * to other places, but I think it is quite appropriate and saves code to do this
+ * only when actually neeeded.
+ * rgerhards, 2008-04-04
+ */
+ while ((ch = getopt(argc, argv, "46aAc:def:g:hi:l:m:M:nopqQr::s:t:u:vwx")) != EOF) {
+ switch((char)ch) {
+ case '4':
+ case '6':
+ case 'A':
+ case 'a':
+ case 'f': /* configuration file */
+ case 'h':
+ case 'i': /* pid file name */
+ case 'l':
+ case 'm': /* mark interval */
+ case 'n': /* don't fork */
+ case 'o':
+ case 'p':
+ case 'q': /* add hostname if DNS resolving has failed */
+ case 'Q': /* dont resolve hostnames in ACL to IPs */
+ case 's':
+ case 'u': /* misc user settings */
+ case 'w': /* disable disallowed host warnings */
+ case 'x': /* disable dns for remote messages */
+ CHKiRet(bufOptAdd(ch, optarg));
+ break;
+ case 'c': /* compatibility mode */
+ iCompatibilityMode = atoi(optarg);
+ break;
+ case 'd': /* debug - must be handled now, so that debug is active during init! */
+ Debug = 1;
+ break;
+ case 'e': /* log every message (no repeat message supression) */
+ fprintf(stderr, "note: -e option is no longer supported, every message is now logged by default\n");
+ bEOptionWasGiven = 1;
+ break;
+ case 'g': /* enable tcp gssapi logging */
+#if defined(SYSLOG_INET) && defined(USE_GSSAPI)
+ CHKiRet(bufOptAdd('g', optarg));
+#else
+ fprintf(stderr, "rsyslogd: -g not valid - not compiled with gssapi support");
+#endif
+ break;
+ case 'M': /* default module load path -- this MUST be carried out immediately! */
+ glblModPath = (uchar*) optarg;
+ break;
+ case 'r': /* accept remote messages */
+#ifdef SYSLOG_INET
+ CHKiRet(bufOptAdd(ch, optarg));
+#else
+ fprintf(stderr, "rsyslogd: -r not valid - not compiled with network support\n");
+#endif
+ break;
+ case 't': /* enable tcp logging */
+#ifdef SYSLOG_INET
+ CHKiRet(bufOptAdd(ch, optarg));
+#else
+ fprintf(stderr, "rsyslogd: -t not valid - not compiled with network support\n");
+#endif
+ break;
+ case 'v': /* MUST be carried out immediately! */
+ printVersion();
+ exit(0); /* exit for -v option - so this is a "good one" */
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ if ((argc -= optind))
+ usage();
+
+ dbgprintf("rsyslogd %s startup, compatibility mode %d, module path '%s'\n",
+ VERSION, iCompatibilityMode, glblModPath == NULL ? "" : (char*)glblModPath);
+
+ /* we are done with the initial option parsing and processing. Now we init the system. */
+
+ ppid = getpid();
+
+ if(chdir ("/") != 0)
+ fprintf(stderr, "Can not do 'cd /' - still trying to run\n");
+
+ CHKiRet_Hdlr(InitGlobalClasses()) {
+ fprintf(stderr, "rsyslogd initializiation failed - global classes could not be initialized.\n"
+ "Did you do a \"make install\"?\n"
+ "Suggested action: run rsyslogd with -d -n options to see what exactly "
+ "fails.\n");
+ FINALIZE;
+ }
+
+ /* doing some core initializations */
+
+ /* get our host and domain names - we need to do this early as we may emit
+ * error log messages, which need the correct hostname. -- rgerhards, 2008-04-04
+ */
+ net.getLocalHostname(&LocalHostName);
+ if((p = (uchar*)strchr((char*)LocalHostName, '.'))) {
+ *p++ = '\0';
+ LocalDomain = p;
+ } else {
+ LocalDomain = (uchar*)"";
+
+ /* It's not clearly defined whether gethostname()
+ * should return the simple hostname or the fqdn. A
+ * good piece of software should be aware of both and
+ * we want to distribute good software. Joey
+ *
+ * Good software also always checks its return values...
+ * If syslogd starts up before DNS is up & /etc/hosts
+ * doesn't have LocalHostName listed, gethostbyname will
+ * return NULL.
+ */
+ /* TODO: gethostbyname() is not thread-safe, but replacing it is
+ * not urgent as we do not run on multiple threads here. rgerhards, 2007-09-25
+ */
+ hent = gethostbyname((char*)LocalHostName);
+ if(hent) {
+ free(LocalHostName);
+ CHKmalloc(LocalHostName = (uchar*)strdup(hent->h_name));
+
+ if((p = (uchar*)strchr((char*)LocalHostName, '.')))
+ {
+ *p++ = '\0';
+ LocalDomain = p;
+ }
+ }
+ }
+
+ /* Convert to lower case to recognize the correct domain laterly */
+ for(p = LocalDomain ; *p ; p++)
+ *p = (char)tolower((int)*p);
+
+ /* we now have our hostname and can set it inside the global vars.
+ * TODO: think if all of this would better be a runtime function
+ * rgerhards, 2008-04-17
+ */
+ glbl.SetLocalHostName(LocalHostName);
+ glbl.SetLocalDomain(LocalDomain);
+
+ /* initialize the objects */
+ if((iRet = modInitIminternal()) != RS_RET_OK) {
+ fprintf(stderr, "fatal error: could not initialize errbuf object (error code %d).\n",
+ iRet);
+ exit(1); /* "good" exit, leaving at init for fatal error */
+ }
+
+ if((iRet = loadBuildInModules()) != RS_RET_OK) {
+ fprintf(stderr, "fatal error: could not activate built-in modules. Error code %d.\n",
+ iRet);
+ exit(1); /* "good" exit, leaving at init for fatal error */
+ }
+
+ /* END core initializations - we now come back to carrying out command line options*/
+
+ while((iRet = bufOptRemove(&ch, &arg)) == RS_RET_OK) {
+ dbgprintf("deque option %c, optarg '%s'\n", ch, arg);
+ switch((char)ch) {
+ case '4':
+ glbl.SetDefPFFamily(PF_INET);
+ break;
+ case '6':
+ glbl.SetDefPFFamily(PF_INET6);
+ break;
+ case 'A':
+ send_to_all++;
+ break;
+ case 'a':
+ if(iCompatibilityMode < 3) {
+ if(!bImUxSockLoaded) {
+ legacyOptsEnq((uchar *) "ModLoad imuxsock");
+ bImUxSockLoaded = 1;
+ }
+ snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "addunixlistensocket %s", arg);
+ legacyOptsEnq(legacyConfLine);
+ } else {
+ fprintf(stderr, "error -a is no longer supported, use module imuxsock instead");
+ }
+ break;
+ case 'f': /* configuration file */
+ ConfFile = (uchar*) arg;
+ break;
+ case 'g': /* enable tcp gssapi logging */
+ if(iCompatibilityMode < 3) {
+ legacyOptsParseTCP(ch, arg);
+ } else
+ fprintf(stderr, "-g option only supported in compatibility modes 0 to 2 - ignored\n");
+ break;
+ case 'h':
+ if(iCompatibilityMode < 3) {
+ errmsg.LogError(0, NO_ERRCODE, "WARNING: -h option is no longer supported - ignored");
+ } else {
+ usage(); /* for v3 and above, it simply is an error */
+ }
+ break;
+ case 'i': /* pid file name */
+ PidFile = arg;
+ break;
+ case 'l':
+ if(glbl.GetLocalHosts() != NULL) {
+ fprintf (stderr, "rsyslogd: Only one -l argument allowed, the first one is taken.\n");
+ } else {
+ glbl.SetLocalHosts(crunch_list(arg));
+ }
+ break;
+ case 'm': /* mark interval */
+ if(iCompatibilityMode < 3) {
+ MarkInterval = atoi(arg) * 60;
+ } else
+ fprintf(stderr,
+ "-m option only supported in compatibility modes 0 to 2 - ignored\n");
+ break;
+ case 'n': /* don't fork */
+ NoFork = 1;
+ break;
+ case 'o':
+ if(iCompatibilityMode < 3) {
+ if(!bImUxSockLoaded) {
+ legacyOptsEnq((uchar *) "ModLoad imuxsock");
+ bImUxSockLoaded = 1;
+ }
+ legacyOptsEnq((uchar *) "OmitLocalLogging");
+ } else {
+ fprintf(stderr, "error -o is no longer supported, use module imuxsock instead");
+ }
+ break;
+ case 'p':
+ if(iCompatibilityMode < 3) {
+ if(!bImUxSockLoaded) {
+ legacyOptsEnq((uchar *) "ModLoad imuxsock");
+ bImUxSockLoaded = 1;
+ }
+ snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "SystemLogSocketName %s", arg);
+ legacyOptsEnq(legacyConfLine);
+ } else {
+ fprintf(stderr, "error -p is no longer supported, use module imuxsock instead");
+ }
+ case 'q': /* add hostname if DNS resolving has failed */
+ *net.pACLAddHostnameOnFail = 1;
+ break;
+ case 'Q': /* dont resolve hostnames in ACL to IPs */
+ *net.pACLDontResolve = 1;
+ break;
+ case 'r': /* accept remote messages */
+ if(iCompatibilityMode < 3) {
+ legacyOptsEnq((uchar *) "ModLoad imudp");
+ snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "UDPServerRun %s", arg);
+ legacyOptsEnq(legacyConfLine);
+ } else
+ fprintf(stderr, "-r option only supported in compatibility modes 0 to 2 - ignored\n");
+ break;
+ case 's':
+ if(glbl.GetStripDomains() != NULL) {
+ fprintf (stderr, "rsyslogd: Only one -s argument allowed, the first one is taken.\n");
+ } else {
+ glbl.SetStripDomains(crunch_list(arg));
+ }
+ break;
+ case 't': /* enable tcp logging */
+ if(iCompatibilityMode < 3) {
+ legacyOptsParseTCP(ch, arg);
+ } else
+ fprintf(stderr, "-t option only supported in compatibility modes 0 to 2 - ignored\n");
+ break;
+ case 'u': /* misc user settings */
+ if(atoi(arg) == 1)
+ bParseHOSTNAMEandTAG = 0;
+ break;
+ case 'w': /* disable disallowed host warnigs */
+ glbl.SetOption_DisallowWarning(0);
+ break;
+ case 'x': /* disable dns for remote messages */
+ glbl.SetDisableDNS(1);
+ break;
+ case '?':
+ default:
+ usage();
+ }
+ }
+
+ if(iRet != RS_RET_END_OF_LINKEDLIST)
+ FINALIZE;
+
+ /* process compatibility mode settings */
+ if(iCompatibilityMode < 3) {
+ errmsg.LogError(0, NO_ERRCODE, "WARNING: rsyslogd is running in compatibility mode. Automatically "
+ "generated config directives may interfer with your rsyslog.conf settings. "
+ "We suggest upgrading your config and adding -c3 as the first "
+ "rsyslogd option.");
+ if(MarkInterval > 0) {
+ legacyOptsEnq((uchar *) "ModLoad immark");
+ snprintf((char *) legacyConfLine, sizeof(legacyConfLine), "MarkMessagePeriod %d", MarkInterval);
+ legacyOptsEnq(legacyConfLine);
+ }
+ if(!bImUxSockLoaded) {
+ legacyOptsEnq((uchar *) "ModLoad imuxsock");
+ }
+ }
+
+ if(bEOptionWasGiven && iCompatibilityMode < 3) {
+ errmsg.LogError(0, NO_ERRCODE, "WARNING: \"message repeated n times\" feature MUST be turned on in "
+ "rsyslog.conf - CURRENTLY EVERY MESSAGE WILL BE LOGGED. Visit "
+ "http://www.rsyslog.com/rptdmsgreduction to learn "
+ "more and cast your vote if you want us to keep this feature.");
+ }
+
+ checkPermissions();
+ thrdInit();
+
+ if( !(Debug || NoFork) )
+ {
+ dbgprintf("Checking pidfile.\n");
+ if (!check_pid(PidFile))
+ {
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = doexit;
+ sigaction(SIGTERM, &sigAct, NULL);
+
+ if (fork()) {
+ /*
+ * Parent process
+ */
+ sleep(300);
+ /*
+ * Not reached unless something major went wrong. 5
+ * minutes should be a fair amount of time to wait.
+ * Please note that this procedure is important since
+ * the father must not exit before syslogd isn't
+ * initialized or the klogd won't be able to flush its
+ * logs. -Joey
+ */
+ exit(1); /* "good" exit - after forking, not diasabling anything */
+ }
+ num_fds = getdtablesize();
+ for (i= 0; i < num_fds; i++)
+ (void) close(i);
+ untty();
+ }
+ else
+ {
+ fputs(" Already running.\n", stderr);
+ exit(1); /* "good" exit, done if syslogd is already running */
+ }
+ }
+ else
+ debugging_on = 1;
+
+ /* tuck my process id away */
+ dbgprintf("Writing pidfile %s.\n", PidFile);
+ if (!check_pid(PidFile))
+ {
+ if (!write_pid(PidFile))
+ {
+ fputs("Can't write pid.\n", stderr);
+ exit(1); /* exit during startup - questionable */
+ }
+ }
+ else
+ {
+ fputs("Pidfile (and pid) already exist.\n", stderr);
+ exit(1); /* exit during startup - questionable */
+ }
+ myPid = getpid(); /* save our pid for further testing (also used for messages) */
+
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+
+ sigAct.sa_handler = sigsegvHdlr;
+ sigaction(SIGSEGV, &sigAct, NULL);
+ sigAct.sa_handler = sigsegvHdlr;
+ sigaction(SIGABRT, &sigAct, NULL);
+ sigAct.sa_handler = doDie;
+ sigaction(SIGTERM, &sigAct, NULL);
+ sigAct.sa_handler = Debug ? doDie : SIG_IGN;
+ sigaction(SIGINT, &sigAct, NULL);
+ sigaction(SIGQUIT, &sigAct, NULL);
+ sigAct.sa_handler = reapchild;
+ sigaction(SIGCHLD, &sigAct, NULL);
+ sigAct.sa_handler = Debug ? debug_switch : SIG_IGN;
+ sigaction(SIGUSR1, &sigAct, NULL);
+ sigAct.sa_handler = SIG_IGN;
+ sigaction(SIGPIPE, &sigAct, NULL);
+ sigaction(SIGXFSZ, &sigAct, NULL); /* do not abort if 2gig file limit is hit */
+
+ mainThread();
+
+ /* do any de-init's that need to be done AFTER this comment */
+
+ die(bFinished);
+
+ thrdExit();
+
+finalize_it:
+ if(iRet != RS_RET_OK)
+ fprintf(stderr, "rsyslogd run failed with error %d\n(see rsyslog.h "
+ "or http://www.rsyslog.com/errcode to learn what that number means)\n", iRet);
+
+ ENDfunc
+ return 0;
+}
+
+
+/* This is the main entry point into rsyslogd. This must be a function in its own
+ * right in order to intialize the debug system in a portable way (otherwise we would
+ * need to have a statement before variable definitions.
+ * rgerhards, 20080-01-28
+ */
+int main(int argc, char **argv)
+{
+ dbgClassInit();
+ return realMain(argc, argv);
+}
+
+/* vim:set ai:
+ */
diff --git a/tools/syslogd.h b/tools/syslogd.h
new file mode 100644
index 00000000..e866a16b
--- /dev/null
+++ b/tools/syslogd.h
@@ -0,0 +1,99 @@
+/* common header for syslogd
+ * Copyright 2007 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.
+ */
+#ifndef SYSLOGD_H_INCLUDED
+#define SYSLOGD_H_INCLUDED 1
+
+#include "syslogd-types.h"
+#include "objomsr.h"
+#include "modules.h"
+#include "template.h"
+#include "action.h"
+#include "linkedlist.h"
+#include "expr.h"
+
+
+#ifndef _PATH_CONSOLE
+#define _PATH_CONSOLE "/dev/console"
+#endif
+
+
+/* This structure represents the files that will have log
+ * copies printed.
+ * RGerhards 2004-11-08: Each instance of the filed structure
+ * describes what I call an "output channel". This is important
+ * to mention as we now allow database connections to be
+ * present in the filed structure. If helps immensely, if we
+ * think of it as the abstraction of an output channel.
+ * rgerhards, 2005-10-26: The structure below provides ample
+ * opportunity for non-thread-safety. Each of the variable
+ * accesses must be carefully evaluated, many of them probably
+ * be guarded by mutexes. But beware of deadlocks...
+ * rgerhards, 2007-08-01: as you can see, the structure has shrunk pretty much. I will
+ * remove some of the comments some time. It's still the structure that controls much
+ * of the processing that goes on in syslogd, but it now has lots of helpers.
+ */
+struct filed {
+ struct filed *f_next; /* next in linked list */
+ /* filter properties */
+ enum {
+ FILTER_PRI = 0, /* traditional PRI based filer */
+ FILTER_PROP = 1, /* extended filter, property based */
+ FILTER_EXPR = 2 /* extended filter, expression based */
+ } f_filter_type;
+ EHostnameCmpMode eHostnameCmpMode;
+ cstr_t *pCSHostnameComp; /* hostname to check */
+ cstr_t *pCSProgNameComp; /* tag to check or NULL, if not to be checked */
+ union {
+ u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */
+ struct {
+ cstr_t *pCSPropName;
+ 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 regular expression? */
+ } operation;
+ cstr_t *pCSCompValue; /* value to "compare" against */
+ char isNegated; /* actually a boolean ;) */
+ } prop;
+ expr_t *f_expr; /* expression object */
+ } f_filterData;
+
+ linkedList_t llActList; /* list of configured actions */
+};
+
+
+#include "net.h" /* TODO: remove when you remoe isAllowedSender from here! */
+void untty(void);
+rsRetVal selectorConstruct(selector_t **ppThis);
+rsRetVal selectorDestruct(void *pVal);
+rsRetVal selectorAddList(selector_t *f);
+/* the following prototypes should go away once we have an input
+ * module interface -- rgerhards, 2007-12-12
+ */
+void logmsg(msg_t *pMsg, int flags);
+extern int NoHops;
+extern int send_to_all;
+extern int Debug;
+#include "dirty.h"
+
+#endif /* #ifndef SYSLOGD_H_INCLUDED */