summaryrefslogtreecommitdiffstats
path: root/tools/rsyslogd.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/rsyslogd.c2362
1 files changed, 2362 insertions, 0 deletions
diff --git a/tools/rsyslogd.c b/tools/rsyslogd.c
new file mode 100644
index 0000000..d27a2a7
--- /dev/null
+++ b/tools/rsyslogd.c
@@ -0,0 +1,2362 @@
+/* This is the main rsyslogd file.
+ * It contains code * that is known to be validly under ASL 2.0,
+ * because it was either written from scratch by me (rgerhards) or
+ * contributors who agreed to ASL 2.0.
+ *
+ * Copyright 2004-2023 Rainer Gerhards and Adiscon
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#include <signal.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <dirent.h>
+#include <unistd.h>
+#include <errno.h>
+#ifdef ENABLE_LIBLOGGING_STDLOG
+# include <liblogging/stdlog.h>
+#else
+# include <syslog.h>
+#endif
+#ifdef HAVE_LIBSYSTEMD
+# include <systemd/sd-daemon.h>
+#endif
+#ifdef ENABLE_LIBCAPNG
+ #include <cap-ng.h>
+#endif
+#if defined(HAVE_LINUX_CLOSE_RANGE_H)
+# include <linux/close_range.h>
+#endif
+
+#include "rsyslog.h"
+#include "wti.h"
+#include "ratelimit.h"
+#include "parser.h"
+#include "linkedlist.h"
+#include "ruleset.h"
+#include "action.h"
+#include "iminternal.h"
+#include "errmsg.h"
+#include "threads.h"
+#include "dnscache.h"
+#include "prop.h"
+#include "unicode-helper.h"
+#include "net.h"
+#include "glbl.h"
+#include "debug.h"
+#include "srUtils.h"
+#include "rsconf.h"
+#include "cfsysline.h"
+#include "datetime.h"
+#include "operatingstate.h"
+#include "dirty.h"
+#include "janitor.h"
+#include "parserif.h"
+
+/* some global vars we need to differentiate between environments,
+ * for TZ-related things see
+ * https://github.com/rsyslog/rsyslog/issues/2994
+ */
+static int runningInContainer = 0;
+#ifdef OS_LINUX
+static int emitTZWarning = 0;
+#else
+static int emitTZWarning = 1;
+#endif
+static pthread_t mainthread = 0;
+
+#if defined(_AIX)
+/* AIXPORT : start
+ * The following includes and declarations are for support of the System
+ * Resource Controller (SRC) .
+ */
+#include <sys/select.h>
+/* AIXPORT : start*/
+#define SRC_FD 13
+#define SRCMSG (sizeof(srcpacket))
+
+static void deinitAll(void);
+#include <spc.h>
+static struct srcreq srcpacket;
+int cont;
+struct srchdr *srchdr;
+char progname[128];
+
+
+/* Normally defined as locals in main
+ * But here since the functionality is split
+ * across multiple functions, we make it global
+ */
+static int rc;
+static socklen_t addrsz;
+static struct sockaddr srcaddr;
+int src_exists = TRUE;
+/* src end */
+
+/*
+ * SRC packet processing - .
+ */
+#define SRCMIN(a, b) (a < b) ? a : b
+void
+dosrcpacket(msgno, txt, len)
+ int msgno;
+ char *txt;
+ int len;
+{
+ struct srcrep reply;
+
+ reply.svrreply.rtncode = msgno;
+/* AIXPORT : srv was corrected to syslogd */
+ strcpy(reply.svrreply.objname, "syslogd");
+ snprintf(reply.svrreply.rtnmsg,
+ SRCMIN(sizeof(reply.svrreply.rtnmsg)-1, strlen(txt)), "%s", txt);
+ srchdr = srcrrqs((char *)&srcpacket);
+ srcsrpy(srchdr, (char *)&reply, len, cont);
+}
+
+#define AIX_SRC_EXISTS_IF if(!src_exists) {
+#define AIX_SRC_EXISTS_FI }
+
+static void aix_close_it(int i)
+{
+ if(src_exists) {
+ if(i != SRC_FD)
+ (void)close(i);
+ } else
+ close(i);
+}
+
+
+#else
+
+#define AIX_SRC_EXISTS_IF
+#define AIX_SRC_EXISTS_FI
+#define aix_close_it(x) close(x)
+#endif
+
+/* AIXPORT : end */
+
+
+DEFobjCurrIf(obj)
+DEFobjCurrIf(prop)
+DEFobjCurrIf(parser)
+DEFobjCurrIf(ruleset)
+DEFobjCurrIf(net)
+DEFobjCurrIf(rsconf)
+DEFobjCurrIf(module)
+DEFobjCurrIf(datetime)
+DEFobjCurrIf(glbl)
+
+extern int yydebug; /* interface to flex */
+
+
+/* forward definitions */
+void rsyslogd_submitErrMsg(const int severity, const int iErr, const uchar *msg);
+void rsyslogdDoDie(int sig);
+
+
+#ifndef PATH_PIDFILE
+#if defined(_AIX) /* AIXPORT : Add _AIX */
+# define PATH_PIDFILE "/etc/rsyslogd.pid"
+#else
+# define PATH_PIDFILE "/var/run/rsyslogd.pid"
+#endif /*_AIX*/
+#endif
+
+#ifndef PATH_CONFFILE
+# define PATH_CONFFILE "/etc/rsyslog.conf"
+#endif
+
+/* global data items */
+static pthread_mutex_t mutChildDied;
+static int bChildDied = 0;
+static pthread_mutex_t mutHadHUP;
+static int bHadHUP;
+static int doFork = 1; /* fork - run in daemon mode - read-only after startup */
+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.
+ */
+const char *PidFile = NULL;
+#define NO_PIDFILE "NONE"
+int iConfigVerify = 0; /* is this just a config verify run? */
+rsconf_t *ourConf = NULL; /* our config object */
+int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */
+ratelimit_t *dflt_ratelimiter = NULL; /* ratelimiter for submits without explicit one */
+uchar *ConfFile = (uchar*) PATH_CONFFILE;
+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.
+ */
+prop_t *pInternalInputName = NULL; /* there is only one global inputName for all internally-generated messages */
+ratelimit_t *internalMsg_ratelimiter = NULL; /* ratelimiter for rsyslog-own messages */
+int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */
+
+static struct queuefilenames_s {
+ struct queuefilenames_s *next;
+ uchar *name;
+} *queuefilenames = NULL;
+
+
+static __attribute__((noreturn)) void
+rsyslogd_usage(void)
+{
+ fprintf(stderr, "usage: rsyslogd [options]\n"
+ "use \"man rsyslogd\" for details. To run rsyslog "
+ "interactively, use \"rsyslogd -n\"\n"
+ "to run it in debug mode use \"rsyslogd -dn\"\n"
+ "For further information see https://www.rsyslog.com/doc/\n");
+ exit(1); /* "good" exit - done to terminate usage() */
+}
+
+#ifndef HAVE_SETSID
+extern void untty(void); /* in syslogd.c, GPLv3 */
+static int
+setsid(void)
+{
+ untty();
+ return 0;
+}
+#endif
+
+/* helper for imdiag. Returns if HUP processing has been requested or
+ * is not yet finished. We know this is racy, but imdiag handles this
+ * part by repeating operations. The mutex look is primarily to force
+ * a memory barrier, so that we have a change to see changes already
+ * written, but not present in the core's cache.
+ * 2023-07-26 Rainer Gerhards
+ */
+int
+get_bHadHUP(void)
+{
+ pthread_mutex_lock(&mutHadHUP);
+ const int ret = bHadHUP;
+ pthread_mutex_unlock(&mutHadHUP);
+ /* note: at this point ret can already be invalid */
+ return ret;
+}
+
+/* we need a pointer to the conf, because in early startup stage we
+ * need to use loadConf, later on runConf.
+ */
+rsRetVal
+queryLocalHostname(rsconf_t *const pConf)
+{
+ uchar *LocalHostName = NULL;
+ uchar *LocalDomain = NULL;
+ uchar *LocalFQDNName;
+ DEFiRet;
+
+ CHKiRet(net.getLocalHostname(pConf, &LocalFQDNName));
+ uchar *dot = (uchar*) strstr((char*)LocalFQDNName, ".");
+ if(dot == NULL) {
+ CHKmalloc(LocalHostName = (uchar*) strdup((char*)LocalFQDNName));
+ CHKmalloc(LocalDomain = (uchar*)strdup(""));
+ } else {
+ const size_t lenhn = dot - LocalFQDNName;
+ CHKmalloc(LocalHostName = (uchar*) strndup((char*) LocalFQDNName, lenhn));
+ CHKmalloc(LocalDomain = (uchar*) strdup((char*) dot+1));
+ }
+
+ glbl.SetLocalFQDNName(LocalFQDNName);
+ glbl.SetLocalHostName(LocalHostName);
+ glbl.SetLocalDomain(LocalDomain);
+ glbl.GenerateLocalHostNameProperty();
+ LocalHostName = NULL; /* handed over */
+ LocalDomain = NULL; /* handed over */
+
+finalize_it:
+ free(LocalHostName);
+ free(LocalDomain);
+ RETiRet;
+}
+
+static rsRetVal
+writePidFile(void)
+{
+ FILE *fp;
+ DEFiRet;
+
+ const char *tmpPidFile;
+
+ if(!strcmp(PidFile, NO_PIDFILE)) {
+ FINALIZE;
+ }
+ if(asprintf((char **)&tmpPidFile, "%s.tmp", PidFile) == -1) {
+ ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
+ }
+ if(tmpPidFile == NULL)
+ tmpPidFile = PidFile;
+ DBGPRINTF("rsyslogd: writing pidfile '%s'.\n", tmpPidFile);
+ if((fp = fopen((char*) tmpPidFile, "w")) == NULL) {
+ perror("rsyslogd: error writing pid file (creation stage)\n");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+ if(fprintf(fp, "%d", (int) glblGetOurPid()) < 0) {
+ LogError(errno, iRet, "rsyslog: error writing pid file");
+ }
+ fclose(fp);
+ if(tmpPidFile != PidFile) {
+ if(rename(tmpPidFile, PidFile) != 0) {
+ perror("rsyslogd: error writing pid file (rename stage)");
+ }
+ free((void*)tmpPidFile);
+ }
+finalize_it:
+ RETiRet;
+}
+
+static void
+clearPidFile(void)
+{
+ if(PidFile != NULL) {
+ if(strcmp(PidFile, NO_PIDFILE)) {
+ unlink(PidFile);
+ }
+ }
+}
+
+/* duplicate startup protection: check, based on pid file, if our instance
+ * is already running. This MUST be called before we write our own pid file.
+ */
+static rsRetVal
+checkStartupOK(void)
+{
+ FILE *fp = NULL;
+ DEFiRet;
+
+ DBGPRINTF("rsyslogd: checking if startup is ok, pidfile '%s'.\n", PidFile);
+
+ if(!strcmp(PidFile, NO_PIDFILE)) {
+ dbgprintf("no pid file shall be written, skipping check\n");
+ FINALIZE;
+ }
+
+ if((fp = fopen((char*) PidFile, "r")) == NULL)
+ FINALIZE; /* all well, no pid file yet */
+
+ int pf_pid;
+ if(fscanf(fp, "%d", &pf_pid) != 1) {
+ fprintf(stderr, "rsyslogd: error reading pid file, cannot start up\n");
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+ /* ok, we got a pid, let's check if the process is running */
+ const pid_t pid = (pid_t) pf_pid;
+ if(kill(pid, 0) == 0 || errno != ESRCH) {
+ fprintf(stderr, "rsyslogd: pidfile '%s' and pid %d already exist.\n"
+ "If you want to run multiple instances of rsyslog, you need "
+ "to specify\n"
+ "different pid files for them (-i option).\n",
+ PidFile, (int) getpid());
+ ABORT_FINALIZE(RS_RET_ERR);
+ }
+
+finalize_it:
+ if(fp != NULL)
+ fclose(fp);
+ RETiRet;
+}
+
+
+
+/* note: this function is specific to OS'es which provide
+ * the ability to read open file descriptors via /proc.
+ * returns 0 - success, something else otherwise
+ */
+static int
+close_unneeded_open_files(const char *const procdir,
+ const int beginClose, const int parentPipeFD)
+{
+ DIR *dir;
+ struct dirent *entry;
+
+ dir = opendir(procdir);
+ if (dir == NULL) {
+ dbgprintf("closes unneeded files: opendir failed for %s\n", procdir);
+ return 1;
+ }
+
+ while ((entry = readdir(dir)) != NULL) {
+ const int fd = atoi(entry->d_name);
+ if(fd >= beginClose && (((fd != dbgGetDbglogFd()) && (fd != parentPipeFD)))) {
+ close(fd);
+ }
+ }
+
+ closedir(dir);
+ return 0;
+}
+
+/* prepares the background processes (if auto-backbrounding) for
+ * operation.
+ */
+static void
+prepareBackground(const int parentPipeFD)
+{
+ DBGPRINTF("rsyslogd: in child, finalizing initialization\n");
+
+ dbgTimeoutToStderr = 0; /* we loose stderr when backgrounding! */
+ int r = setsid();
+ if(r == -1) {
+ char err[1024];
+ char em[2048];
+ rs_strerror_r(errno, err, sizeof(err));
+ snprintf(em, sizeof(em)-1, "rsyslog: error "
+ "auto-backgrounding: %s\n", err);
+ dbgprintf("%s\n", em);
+ fprintf(stderr, "%s", em);
+ }
+
+ int beginClose = 3;
+
+#ifdef HAVE_LIBSYSTEMD
+ /* running under systemd? Then we must make sure we "forward" any
+ * fds passed by it (adjust the pid).
+ */
+ if(sd_booted()) {
+ const char *lstnPid = getenv("LISTEN_PID");
+ if(lstnPid != NULL) {
+ char szBuf[64];
+ const int lstnPidI = atoi(lstnPid);
+ snprintf(szBuf, sizeof(szBuf), "%d", lstnPidI);
+ if(!strcmp(szBuf, lstnPid) && lstnPidI == getppid()) {
+ snprintf(szBuf, sizeof(szBuf), "%d", (int) getpid());
+ setenv("LISTEN_PID", szBuf, 1);
+ /* ensure we do not close what systemd provided */
+ const int nFds = sd_listen_fds(0);
+ if(nFds > 0) {
+ beginClose = SD_LISTEN_FDS_START + nFds;
+ }
+ }
+ }
+ }
+#endif
+
+ /* close unnecessary open files - first try to use /proc file system,
+ * if that is not possible iterate through all potentially open file
+ * descriptors. This can be lenghty, but in practice /proc should work
+ * for almost all current systems, and the fallback is primarily for
+ * Solaris and AIX, where we do expect a decent max numbers of fds.
+ */
+ close(0); /* always close stdin, we do not need it */
+
+ /* try Linux, Cygwin, NetBSD */
+ if(close_unneeded_open_files("/proc/self/fd", beginClose, parentPipeFD) != 0) {
+ /* try MacOS, FreeBSD */
+ if(close_unneeded_open_files("/proc/fd", beginClose, parentPipeFD) != 0) {
+ /* did not work out, so let's close everything... */
+ int endClose = (parentPipeFD > dbgGetDbglogFd()) ? parentPipeFD : dbgGetDbglogFd();
+ for(int i = beginClose ; i <= endClose ; ++i) {
+ if((i != dbgGetDbglogFd()) && (i != parentPipeFD)) {
+ aix_close_it(i); /* AIXPORT */
+ }
+ }
+ beginClose = endClose + 1;
+ endClose = getdtablesize();
+#if defined(HAVE_CLOSE_RANGE)
+ if(close_range(beginClose, endClose, 0) !=0) {
+ dbgprintf("errno %d after close_range(), fallback to loop\n", errno);
+#endif
+ for(int i = beginClose ; i <= endClose ; ++i) {
+ aix_close_it(i); /* AIXPORT */
+ }
+#if defined(HAVE_CLOSE_RANGE)
+ }
+#endif
+ }
+ }
+ seedRandomNumberForChild();
+}
+
+/* This is called when rsyslog is set to auto-background itself. If so, a child
+ * is forked and the parent waits until it is initialized.
+ * The parent never returns from this function, only this happens for the child.
+ * So if it returns, you know you are in the child.
+ * return: file descriptor to which the child needs to write an "OK" or error
+ * message.
+ */
+static int
+forkRsyslog(void)
+{
+ int pipefd[2];
+ pid_t cpid;
+ char err[1024];
+ char msgBuf[4096];
+
+ dbgprintf("rsyslogd: parent ready for forking\n");
+ if(pipe(pipefd) == -1) {
+ perror("error creating rsyslog \"fork pipe\" - terminating");
+ exit(1);
+ }
+ AIX_SRC_EXISTS_IF /* AIXPORT */
+ cpid = fork();
+ if(cpid == -1) {
+ perror("error forking rsyslogd process - terminating");
+ exit(1);
+ }
+ AIX_SRC_EXISTS_FI /* AIXPORT */
+
+ if(cpid == 0) {
+ prepareBackground(pipefd[1]);
+ close(pipefd[0]);
+ return pipefd[1];
+ }
+
+ /* we are now in the parent. All we need to do here is wait for the
+ * startup message, emit it (if necessary) and then terminate.
+ */
+ close(pipefd[1]);
+ dbgprintf("rsyslogd: parent waiting up to 60 seconds to read startup message\n");
+
+ fd_set rfds;
+ struct timeval tv;
+ int retval;
+
+ FD_ZERO(&rfds);
+ FD_SET(pipefd[0], &rfds);
+ tv.tv_sec = 60;
+ tv.tv_usec = 0;
+
+ retval = select(pipefd[0]+1, &rfds, NULL, NULL, &tv);
+ if(retval == -1)
+ rs_strerror_r(errno, err, sizeof(err));
+ else
+ strcpy(err, "OK");
+ dbgprintf("rsyslogd: select() returns %d: %s\n", retval, err);
+ if(retval == -1) {
+ fprintf(stderr,"rsyslog startup failure, select() failed: %s\n", err);
+ exit(1);
+ } else if(retval == 0) {
+ fprintf(stderr,"rsyslog startup failure, child did not "
+ "respond within startup timeout (60 seconds)\n");
+ exit(1);
+ }
+
+ int nRead = read(pipefd[0], msgBuf, sizeof(msgBuf));
+ if(nRead > 0) {
+ msgBuf[nRead] = '\0';
+ } else {
+ rs_strerror_r(errno, err, sizeof(err));
+ snprintf(msgBuf, sizeof(msgBuf)-1, "error reading \"fork pipe\": %s",
+ err);
+ }
+ if(strcmp(msgBuf, "OK")) {
+ dbgprintf("rsyslog parent startup failure: %s\n", msgBuf);
+ fprintf(stderr,"rsyslog startup failure: %s\n", msgBuf);
+ exit(1);
+ }
+ close(pipefd[0]);
+ dbgprintf("rsyslogd: parent terminates after successful child startup\n");
+ exit(0);
+}
+
+/* startup processing: this signals the waiting parent that the child is ready
+ * and the parent may terminate.
+ */
+static void
+tellChildReady(const int pipefd, const char *const msg)
+{
+ dbgprintf("rsyslogd: child signaling OK\n");
+ const int nWritten = write(pipefd, msg, strlen(msg));
+ dbgprintf("rsyslogd: child signalled OK, nWritten %d\n", (int) nWritten);
+ close(pipefd);
+ sleep(1);
+}
+
+/* print version and compile-time setting information */
+static void
+printVersion(void)
+{
+ printf("rsyslogd " VERSION " (aka %4d.%2.2d) compiled with:\n",
+ 2000 + VERSION_YEAR, VERSION_MONTH);
+ printf("\tPLATFORM:\t\t\t\t%s\n", PLATFORM_ID);
+ printf("\tPLATFORM (lsb_release -d):\t\t%s\n", PLATFORM_ID_LSB);
+#ifdef FEATURE_REGEXP
+ printf("\tFEATURE_REGEXP:\t\t\t\tYes\n");
+#else
+ printf("\tFEATURE_REGEXP:\t\t\t\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 HAVE_ATOMIC_BUILTINS
+ printf("\t32bit Atomic operations supported:\tYes\n");
+#else
+ printf("\t32bit Atomic operations supported:\tNo\n");
+#endif
+#ifdef HAVE_ATOMIC_BUILTINS64
+ printf("\t64bit Atomic operations supported:\tYes\n");
+#else
+ printf("\t64bit Atomic operations supported:\tNo\n");
+#endif
+#ifdef HAVE_JEMALLOC
+ printf("\tmemory allocator:\t\t\tjemalloc\n");
+#else
+ printf("\tmemory allocator:\t\t\tsystem default\n");
+#endif
+#ifdef RTINST
+ printf("\tRuntime Instrumentation (slow code):\tYes\n");
+#else
+ printf("\tRuntime Instrumentation (slow code):\tNo\n");
+#endif
+#ifdef USE_LIBUUID
+ printf("\tuuid support:\t\t\t\tYes\n");
+#else
+ printf("\tuuid support:\t\t\t\tNo\n");
+#endif
+#ifdef HAVE_LIBSYSTEMD
+ printf("\tsystemd support:\t\t\tYes\n");
+#else
+ printf("\tsystemd support:\t\t\tNo\n");
+#endif
+ /* we keep the following message to so that users don't need
+ * to wonder.
+ */
+ printf("\tConfig file:\t\t\t\t" PATH_CONFFILE "\n");
+ printf("\tPID file:\t\t\t\t" PATH_PIDFILE "%s\n", PATH_PIDFILE[0]!='/'?
+ "(relative to global workingdirectory)":"");
+ printf("\tNumber of Bits in RainerScript integers: 64\n");
+ printf("\nSee https://www.rsyslog.com for more information.\n");
+}
+
+static rsRetVal
+rsyslogd_InitStdRatelimiters(void)
+{
+ DEFiRet;
+ CHKiRet(ratelimitNew(&dflt_ratelimiter, "rsyslogd", "dflt"));
+ CHKiRet(ratelimitNew(&internalMsg_ratelimiter, "rsyslogd", "internal_messages"));
+ ratelimitSetThreadSafe(internalMsg_ratelimiter);
+ ratelimitSetLinuxLike(internalMsg_ratelimiter,
+ loadConf->globals.intMsgRateLimitItv, loadConf->globals.intMsgRateLimitBurst);
+ /* TODO: make internalMsg ratelimit settings configurable */
+finalize_it:
+ RETiRet;
+}
+
+
+/* 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
+rsyslogd_InitGlobalClasses(void)
+{
+ DEFiRet;
+ const 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));
+ rsrtSetErrLogger(rsyslogd_submitErrMsg);
+
+ /* Now tell the system which classes we need ourselfs */
+ pErrObj = "glbl";
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ pErrObj = "module";
+ CHKiRet(objUse(module, CORE_COMPONENT));
+ pErrObj = "datetime";
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+ pErrObj = "ruleset";
+ CHKiRet(objUse(ruleset, CORE_COMPONENT));
+ pErrObj = "prop";
+ CHKiRet(objUse(prop, CORE_COMPONENT));
+ pErrObj = "parser";
+ CHKiRet(objUse(parser, CORE_COMPONENT));
+ pErrObj = "rsconf";
+ CHKiRet(objUse(rsconf, 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));
+
+ dnscacheInit();
+ initRainerscript();
+ ratelimitModInit();
+
+ /* we need to create the inputName property (only once during our lifetime) */
+ CHKiRet(prop.Construct(&pInternalInputName));
+ CHKiRet(prop.SetString(pInternalInputName, UCHAR_CONSTANT("rsyslogd"), sizeof("rsyslogd") - 1));
+ CHKiRet(prop.ConstructFinalize(pInternalInputName));
+
+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);
+ fprintf(stderr, "rsyslogd initialization 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");
+ }
+
+ RETiRet;
+}
+
+/* preprocess a batch of messages, that is ready them for actual processing. This is done
+ * as a first stage and totally in parallel to any other worker active in the system. So
+ * it helps us keep up the overall concurrency level.
+ * rgerhards, 2010-06-09
+ */
+static rsRetVal
+preprocessBatch(batch_t *pBatch, int *pbShutdownImmediate) {
+ prop_t *ip;
+ prop_t *fqdn;
+ prop_t *localName;
+ int bIsPermitted;
+ smsg_t *pMsg;
+ int i;
+ rsRetVal localRet;
+ DEFiRet;
+
+ for(i = 0 ; i < pBatch->nElem && !*pbShutdownImmediate ; i++) {
+ pMsg = pBatch->pElem[i].pMsg;
+ if((pMsg->msgFlags & NEEDS_ACLCHK_U) != 0) {
+ DBGPRINTF("msgConsumer: UDP ACL must be checked for message (hostname-based)\n");
+ if(net.cvthname(pMsg->rcvFrom.pfrominet, &localName, &fqdn, &ip) != RS_RET_OK)
+ continue;
+ bIsPermitted = net.isAllowedSender2((uchar*)"UDP",
+ (struct sockaddr *)pMsg->rcvFrom.pfrominet, (char*)propGetSzStr(fqdn), 1);
+ if(!bIsPermitted) {
+ DBGPRINTF("Message from '%s' discarded, not a permitted sender host\n",
+ propGetSzStr(fqdn));
+ pBatch->eltState[i] = BATCH_STATE_DISC;
+ } else {
+ /* save some of the info we obtained */
+ MsgSetRcvFrom(pMsg, localName);
+ CHKiRet(MsgSetRcvFromIP(pMsg, ip));
+ pMsg->msgFlags &= ~NEEDS_ACLCHK_U;
+ }
+ }
+ if((pMsg->msgFlags & NEEDS_PARSING) != 0) {
+ if((localRet = parser.ParseMsg(pMsg)) != RS_RET_OK) {
+ DBGPRINTF("Message discarded, parsing error %d\n", localRet);
+ pBatch->eltState[i] = BATCH_STATE_DISC;
+ }
+ }
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* The consumer of dequeued messages. This function is called by the
+ * queue engine on dequeueing of a message. It runs on a SEPARATE
+ * THREAD. It receives an array of pointers, which it must iterate
+ * over. We do not do any further batching, as this is of no benefit
+ * for the main queue.
+ */
+static rsRetVal
+msgConsumer(void __attribute__((unused)) *notNeeded, batch_t *pBatch, wti_t *pWti)
+{
+ DEFiRet;
+ assert(pBatch != NULL);
+ preprocessBatch(pBatch, pWti->pbShutdownImmediate);
+ ruleset.ProcessBatch(pBatch, pWti);
+//TODO: the BATCH_STATE_COMM must be set somewhere down the road, but we
+//do not have this yet and so we emulate -- 2010-06-10
+int i;
+ for(i = 0 ; i < pBatch->nElem && !*pWti->pbShutdownImmediate ; i++) {
+ pBatch->eltState[i] = BATCH_STATE_COMM;
+ }
+ RETiRet;
+}
+
+
+/* create a main message queue, now also used for ruleset queues. This function
+ * needs to be moved to some other module, but it is considered acceptable for
+ * the time being (remember that we want to restructure config processing at large!).
+ * rgerhards, 2009-10-27
+ */
+rsRetVal createMainQueue(qqueue_t **ppQueue, uchar *pszQueueName, struct nvlst *lst)
+{
+ struct queuefilenames_s *qfn;
+ uchar *qfname = NULL;
+ static int qfn_renamenum = 0;
+ uchar qfrenamebuf[1024];
+ DEFiRet;
+
+ /* create message queue */
+ CHKiRet_Hdlr(qqueueConstruct(ppQueue, ourConf->globals.mainQ.MainMsgQueType,
+ ourConf->globals.mainQ.iMainMsgQueueNumWorkers, ourConf->globals.mainQ.iMainMsgQueueSize, msgConsumer)) {
+ /* no queue is fatal, we need to give up in that case... */
+ LogError(0, iRet, "could not create (ruleset) main message queue"); \
+ }
+ /* name our main queue object (it's not fatal if it fails...) */
+ obj.SetName((obj_t*) (*ppQueue), pszQueueName);
+
+ if(lst == NULL) { /* use legacy parameters? */
+ /* ... set some properties ... */
+ # define setQPROP(func, directive, data) \
+ CHKiRet_Hdlr(func(*ppQueue, data)) { \
+ LogError(0, NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, " \
+ "running with default setting", iRet); \
+ }
+ # define setQPROPstr(func, directive, data) \
+ CHKiRet_Hdlr(func(*ppQueue, data, (data == NULL)? 0 : strlen((char*) data))) { \
+ LogError(0, NO_ERRCODE, "Invalid " #directive ", error %d. Ignored, " \
+ "running with default setting", iRet); \
+ }
+
+ if(ourConf->globals.mainQ.pszMainMsgQFName != NULL) {
+ /* check if the queue file name is unique, else emit an error */
+ for(qfn = queuefilenames ; qfn != NULL ; qfn = qfn->next) {
+ dbgprintf("check queue file name '%s' vs '%s'\n", qfn->name,
+ ourConf->globals.mainQ.pszMainMsgQFName );
+ if(!ustrcmp(qfn->name, ourConf->globals.mainQ.pszMainMsgQFName)) {
+ snprintf((char*)qfrenamebuf, sizeof(qfrenamebuf), "%d-%s-%s",
+ ++qfn_renamenum, ourConf->globals.mainQ.pszMainMsgQFName,
+ (pszQueueName == NULL) ? "NONAME" : (char*)pszQueueName);
+ qfname = ustrdup(qfrenamebuf);
+ LogError(0, NO_ERRCODE, "Error: queue file name '%s' already in use "
+ " - using '%s' instead", ourConf->globals.mainQ.pszMainMsgQFName,
+ qfname);
+ break;
+ }
+ }
+ if(qfname == NULL)
+ qfname = ustrdup(ourConf->globals.mainQ.pszMainMsgQFName);
+ qfn = malloc(sizeof(struct queuefilenames_s));
+ qfn->name = qfname;
+ qfn->next = queuefilenames;
+ queuefilenames = qfn;
+ }
+
+ setQPROP(qqueueSetMaxFileSize, "$MainMsgQueueFileSize",
+ ourConf->globals.mainQ.iMainMsgQueMaxFileSize);
+ setQPROP(qqueueSetsizeOnDiskMax, "$MainMsgQueueMaxDiskSpace",
+ ourConf->globals.mainQ.iMainMsgQueMaxDiskSpace);
+ setQPROP(qqueueSetiDeqBatchSize, "$MainMsgQueueDequeueBatchSize",
+ ourConf->globals.mainQ.iMainMsgQueDeqBatchSize);
+ setQPROPstr(qqueueSetFilePrefix, "$MainMsgQueueFileName", qfname);
+ setQPROP(qqueueSetiPersistUpdCnt, "$MainMsgQueueCheckpointInterval",
+ ourConf->globals.mainQ.iMainMsgQPersistUpdCnt);
+ setQPROP(qqueueSetbSyncQueueFiles, "$MainMsgQueueSyncQueueFiles",
+ ourConf->globals.mainQ.bMainMsgQSyncQeueFiles);
+ setQPROP(qqueueSettoQShutdown, "$MainMsgQueueTimeoutShutdown",
+ ourConf->globals.mainQ.iMainMsgQtoQShutdown );
+ setQPROP(qqueueSettoActShutdown, "$MainMsgQueueTimeoutActionCompletion",
+ ourConf->globals.mainQ.iMainMsgQtoActShutdown);
+ setQPROP(qqueueSettoWrkShutdown, "$MainMsgQueueWorkerTimeoutThreadShutdown",
+ ourConf->globals.mainQ.iMainMsgQtoWrkShutdown);
+ setQPROP(qqueueSettoEnq, "$MainMsgQueueTimeoutEnqueue", ourConf->globals.mainQ.iMainMsgQtoEnq);
+ setQPROP(qqueueSetiHighWtrMrk, "$MainMsgQueueHighWaterMark",
+ ourConf->globals.mainQ.iMainMsgQHighWtrMark);
+ setQPROP(qqueueSetiLowWtrMrk, "$MainMsgQueueLowWaterMark",
+ ourConf->globals.mainQ.iMainMsgQLowWtrMark);
+ setQPROP(qqueueSetiDiscardMrk, "$MainMsgQueueDiscardMark",
+ ourConf->globals.mainQ.iMainMsgQDiscardMark);
+ setQPROP(qqueueSetiDiscardSeverity, "$MainMsgQueueDiscardSeverity",
+ ourConf->globals.mainQ.iMainMsgQDiscardSeverity);
+ setQPROP(qqueueSetiMinMsgsPerWrkr, "$MainMsgQueueWorkerThreadMinimumMessages",
+ ourConf->globals.mainQ.iMainMsgQWrkMinMsgs);
+ setQPROP(qqueueSetbSaveOnShutdown, "$MainMsgQueueSaveOnShutdown",
+ ourConf->globals.mainQ.bMainMsgQSaveOnShutdown);
+ setQPROP(qqueueSetiDeqSlowdown, "$MainMsgQueueDequeueSlowdown",
+ ourConf->globals.mainQ.iMainMsgQDeqSlowdown);
+ setQPROP(qqueueSetiDeqtWinFromHr, "$MainMsgQueueDequeueTimeBegin",
+ ourConf->globals.mainQ.iMainMsgQueueDeqtWinFromHr);
+ setQPROP(qqueueSetiDeqtWinToHr, "$MainMsgQueueDequeueTimeEnd",
+ ourConf->globals.mainQ.iMainMsgQueueDeqtWinToHr);
+
+ # undef setQPROP
+ # undef setQPROPstr
+ } else { /* use new style config! */
+ qqueueSetDefaultsRulesetQueue(*ppQueue);
+ qqueueApplyCnfParam(*ppQueue, lst);
+ }
+ qqueueCorrectParams(*ppQueue);
+
+ RETiRet;
+}
+
+rsRetVal
+startMainQueue(rsconf_t *cnf, qqueue_t *const pQueue)
+{
+ DEFiRet;
+ CHKiRet_Hdlr(qqueueStart(cnf, pQueue)) {
+ /* no queue is fatal, we need to give up in that case... */
+ LogError(0, iRet, "could not start (ruleset) main message queue");
+ if(runConf->globals.bAbortOnFailedQueueStartup) {
+ fprintf(stderr, "rsyslogd: could not start (ruleset) main message queue, "
+ "abortOnFailedQueueStartup is set, so we abort rsyslog now.\n");
+ fflush(stderr);
+ clearPidFile();
+ exit(1); /* "good" exit, this is intended here */
+ }
+ pQueue->qType = QUEUETYPE_DIRECT;
+ CHKiRet_Hdlr(qqueueStart(cnf, pQueue)) {
+ /* no queue is fatal, we need to give up in that case... */
+ LogError(0, iRet, "fatal error: could not even start queue in direct mode");
+ }
+ }
+ 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
+ */
+void
+rsyslogd_submitErrMsg(const int severity, const int iErr, const uchar *msg)
+{
+ if (glbl.GetGlobalInputTermState() == 1) {
+ /* After fork the stderr is unusable (dfltErrLogger uses is internally) */
+ if(!doFork)
+ dfltErrLogger(severity, iErr, msg);
+ } else {
+ logmsgInternal(iErr, LOG_SYSLOG|(severity & 0x07), msg, 0);
+ }
+}
+
+static inline rsRetVal
+submitMsgWithDfltRatelimiter(smsg_t *pMsg)
+{
+ return ratelimitAddMsg(dflt_ratelimiter, NULL, pMsg);
+}
+
+
+static void
+logmsgInternal_doWrite(smsg_t *pMsg)
+{
+ const int pri = getPRIi(pMsg);
+ if(pri % 8 <= runConf->globals.intMsgsSeverityFilter) {
+ if(runConf->globals.bProcessInternalMessages) {
+ submitMsg2(pMsg);
+ pMsg = NULL; /* msg obj handed over; do not destruct */
+ } else {
+ uchar *const msg = getMSG(pMsg);
+ #ifdef ENABLE_LIBLOGGING_STDLOG
+ /* the "emit only once" rate limiter is quick and dirty and not
+ * thread safe. However, that's no problem for the current intend
+ * and it is not justified to create more robust code for the
+ * functionality. -- rgerhards, 2018-05-14
+ */
+ static warnmsg_emitted = 0;
+ if(warnmsg_emitted == 0) {
+ stdlog_log(runConf->globals.stdlog_hdl, LOG_WARNING, "%s",
+ "RSYSLOG WARNING: liblogging-stdlog "
+ "functionality will go away soon. For details see "
+ "https://github.com/rsyslog/rsyslog/issues/2706");
+ warnmsg_emitted = 1;
+ }
+ stdlog_log(runConf->globals.stdlog_hdl, pri2sev(pri), "%s", (char*)msg);
+ #else
+ syslog(pri, "%s", msg);
+ #endif
+ }
+ }
+ if(pMsg != NULL) {
+ msgDestruct(&pMsg);
+ }
+}
+
+/* This function creates a log message object out of the provided
+ * message text and forwards it for logging.
+ */
+static rsRetVal
+logmsgInternalSubmit(const int iErr, const syslog_pri_t pri, const size_t lenMsg,
+ const char *__restrict__ const msg, int flags)
+{
+ uchar pszTag[33];
+ smsg_t *pMsg;
+ DEFiRet;
+
+ CHKiRet(msgConstruct(&pMsg));
+ MsgSetInputName(pMsg, pInternalInputName);
+ MsgSetRawMsg(pMsg, (char*)msg, lenMsg);
+ MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName()));
+ MsgSetRcvFrom(pMsg, glbl.GetLocalHostNameProp());
+ MsgSetRcvFromIP(pMsg, glbl.GetLocalHostIP());
+ MsgSetMSGoffs(pMsg, 0);
+ /* check if we have an error code associated and, if so,
+ * adjust the tag. -- rgerhards, 2008-06-27
+ */
+ if(iErr == NO_ERRCODE) {
+ MsgSetTAG(pMsg, UCHAR_CONSTANT("rsyslogd:"), sizeof("rsyslogd:") - 1);
+ } else {
+ size_t len = snprintf((char*)pszTag, sizeof(pszTag), "rsyslogd%d:", iErr);
+ pszTag[32] = '\0'; /* just to make sure... */
+ MsgSetTAG(pMsg, pszTag, len);
+ }
+ flags |= INTERNAL_MSG;
+ pMsg->msgFlags = flags;
+ msgSetPRI(pMsg, pri);
+
+ iminternalAddMsg(pMsg);
+finalize_it:
+ RETiRet;
+}
+
+
+
+/* rgerhards 2004-11-09: the following is a function that can be used
+ * to log a message orginating from the syslogd itself.
+ */
+rsRetVal
+logmsgInternal(int iErr, const syslog_pri_t pri, const uchar *const msg, int flags)
+{
+ size_t lenMsg;
+ unsigned i;
+ char *bufModMsg = NULL; /* buffer for modified message, should we need to modify */
+ DEFiRet;
+
+ /* we first do a path the remove control characters that may have accidently
+ * introduced (program error!). This costs performance, but we do not expect
+ * to be called very frequently in any case ;) -- rgerhards, 2013-12-19.
+ */
+ lenMsg = ustrlen(msg);
+ for(i = 0 ; i < lenMsg ; ++i) {
+ if(msg[i] < 0x20 || msg[i] == 0x7f) {
+ if(bufModMsg == NULL) {
+ CHKmalloc(bufModMsg = strdup((char*) msg));
+ }
+ bufModMsg[i] = ' ';
+ }
+ }
+
+ CHKiRet(logmsgInternalSubmit(iErr, pri, lenMsg,
+ (bufModMsg == NULL) ? (char*)msg : bufModMsg,
+ flags));
+
+ /* we now check if we should print internal messages out to stderr. This was
+ * suggested by HKS as a way to help people troubleshoot rsyslog configuration
+ * (by running it interactively. This makes an awful lot of sense, so I add
+ * it here. -- rgerhards, 2008-07-28
+ * Note that error messages can not be disabled during a config verify. This
+ * permits us to process unmodified config files which otherwise contain a
+ * supressor statement.
+ */
+ int emit_to_stderr = (ourConf == NULL) ? 1 : ourConf->globals.bErrMsgToStderr;
+ int emit_supress_msg = 0;
+ if(Debug == DEBUG_FULL || !doFork) {
+ emit_to_stderr = 1;
+ }
+ if(ourConf != NULL && ourConf->globals.maxErrMsgToStderr != -1) {
+ if(emit_to_stderr && ourConf->globals.maxErrMsgToStderr != -1 && ourConf->globals.maxErrMsgToStderr) {
+ --ourConf->globals.maxErrMsgToStderr;
+ if(ourConf->globals.maxErrMsgToStderr == 0)
+ emit_supress_msg = 1;
+ } else {
+ emit_to_stderr = 0;
+ }
+ }
+ if(emit_to_stderr || iConfigVerify) {
+ if(pri2sev(pri) == LOG_ERR)
+ fprintf(stderr, "rsyslogd: %s\n",
+ (bufModMsg == NULL) ? (char*)msg : bufModMsg);
+ }
+ if(emit_supress_msg) {
+ fprintf(stderr, "rsyslogd: configured max number of error messages "
+ "to stderr reached, further messages will not be output\n"
+ "Consider adjusting\n"
+ " global(errorMessagesToStderr.maxNumber=\"xx\")\n"
+ "if you want more.\n");
+ }
+
+finalize_it:
+ free(bufModMsg);
+ RETiRet;
+}
+
+rsRetVal
+submitMsg(smsg_t *pMsg)
+{
+ return submitMsgWithDfltRatelimiter(pMsg);
+}
+
+
+static rsRetVal ATTR_NONNULL()
+splitOversizeMessage(smsg_t *const pMsg)
+{
+ DEFiRet;
+ const char *rawmsg;
+ int nsegments;
+ int len_rawmsg;
+ const int maxlen = glblGetMaxLine(runConf);
+ ISOBJ_TYPE_assert(pMsg, msg);
+
+ getRawMsg(pMsg, (uchar**) &rawmsg, &len_rawmsg);
+ nsegments = len_rawmsg / maxlen;
+ const int len_last_segment = len_rawmsg % maxlen;
+ DBGPRINTF("splitting oversize message, size %d, segment size %d, "
+ "nsegments %d, bytes in last fragment %d\n",
+ len_rawmsg, maxlen, nsegments, len_last_segment);
+
+ smsg_t *pMsg_seg;
+
+ /* process full segments */
+ for(int i = 0 ; i < nsegments ; ++i) {
+ CHKmalloc(pMsg_seg = MsgDup(pMsg));
+ MsgSetRawMsg(pMsg_seg, rawmsg + (i * maxlen), maxlen);
+ submitMsg2(pMsg_seg);
+ }
+
+ /* if necessary, write partial last segment */
+ if(len_last_segment != 0) {
+ CHKmalloc(pMsg_seg = MsgDup(pMsg));
+ MsgSetRawMsg(pMsg_seg, rawmsg + (nsegments * maxlen), len_last_segment);
+ submitMsg2(pMsg_seg);
+ }
+
+finalize_it:
+ RETiRet;
+}
+
+
+/* submit a message to the main message queue. This is primarily
+ * a hook to prevent the need for callers to know about the main message queue
+ * rgerhards, 2008-02-13
+ */
+rsRetVal
+submitMsg2(smsg_t *pMsg)
+{
+ qqueue_t *pQueue;
+ ruleset_t *pRuleset;
+ DEFiRet;
+
+ ISOBJ_TYPE_assert(pMsg, msg);
+
+ if(getRawMsgLen(pMsg) > glblGetMaxLine(runConf)){
+ uchar *rawmsg;
+ int dummy;
+ getRawMsg(pMsg, &rawmsg, &dummy);
+ if(glblReportOversizeMessage(runConf)) {
+ LogMsg(0, RS_RET_OVERSIZE_MSG, LOG_WARNING,
+ "message too long (%d) with configured size %d, begin of "
+ "message is: %.80s",
+ getRawMsgLen(pMsg), glblGetMaxLine(runConf), rawmsg);
+ }
+ writeOversizeMessageLog(pMsg);
+ if(glblGetOversizeMsgInputMode(runConf) == glblOversizeMsgInputMode_Split) {
+ splitOversizeMessage(pMsg);
+ /* we have submitted the message segments recursively, so we
+ * can just deleted the original msg object and terminate.
+ */
+ msgDestruct(&pMsg);
+ FINALIZE;
+ } else if(glblGetOversizeMsgInputMode(runConf) == glblOversizeMsgInputMode_Truncate) {
+ MsgTruncateToMaxSize(pMsg);
+ } else {
+ /* in "accept" mode, we do nothing, simply because "accept" means
+ * to use as-is.
+ */
+ assert(glblGetOversizeMsgInputMode(runConf) == glblOversizeMsgInputMode_Accept);
+ }
+ }
+
+ pRuleset = MsgGetRuleset(pMsg);
+ pQueue = (pRuleset == NULL) ? runConf->pMsgQueue : ruleset.GetRulesetQueue(pRuleset);
+
+ /* if a plugin logs a message during shutdown, the queue may no longer exist */
+ if(pQueue == NULL) {
+ DBGPRINTF("submitMsg2() could not submit message - "
+ "queue does (no longer?) exist - ignored\n");
+ FINALIZE;
+ }
+
+ qqueueEnqMsg(pQueue, pMsg->flowCtlType, pMsg);
+
+finalize_it:
+ RETiRet;
+}
+
+/* submit multiple messages at once, very similar to submitMsg, just
+ * for multi_submit_t. All messages need to go into the SAME queue!
+ * rgerhards, 2009-06-16
+ */
+rsRetVal ATTR_NONNULL()
+multiSubmitMsg2(multi_submit_t *const pMultiSub)
+{
+ qqueue_t *pQueue;
+ ruleset_t *pRuleset;
+ DEFiRet;
+
+ if(pMultiSub->nElem == 0)
+ FINALIZE;
+
+ pRuleset = MsgGetRuleset(pMultiSub->ppMsgs[0]);
+ pQueue = (pRuleset == NULL) ? runConf->pMsgQueue : ruleset.GetRulesetQueue(pRuleset);
+
+ /* if a plugin logs a message during shutdown, the queue may no longer exist */
+ if(pQueue == NULL) {
+ DBGPRINTF("multiSubmitMsg() could not submit message - "
+ "queue does (no longer?) exist - ignored\n");
+ FINALIZE;
+ }
+
+ iRet = pQueue->MultiEnq(pQueue, pMultiSub);
+ pMultiSub->nElem = 0;
+
+finalize_it:
+ RETiRet;
+}
+rsRetVal
+multiSubmitMsg(multi_submit_t *pMultiSub) /* backward compat. level */
+{
+ return multiSubmitMsg2(pMultiSub);
+}
+
+
+/* flush multiSubmit, e.g. at end of read records */
+rsRetVal
+multiSubmitFlush(multi_submit_t *pMultiSub)
+{
+ DEFiRet;
+ if(pMultiSub->nElem > 0) {
+ iRet = multiSubmitMsg2(pMultiSub);
+ }
+ 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;
+}
+
+
+static void
+hdlr_sigttin_ou(void)
+{
+ /* this is just a dummy to care for our sigttin input
+ * module cancel interface and sigttou internal message
+ * notificaton/mainloop wakeup mechanism. The important
+ * point is that it actually does *NOTHING*.
+ */
+}
+
+static void
+hdlr_enable(int sig, void (*hdlr)())
+{
+ struct sigaction sigAct;
+ memset(&sigAct, 0, sizeof (sigAct));
+ sigemptyset(&sigAct.sa_mask);
+ sigAct.sa_handler = hdlr;
+ sigaction(sig, &sigAct, NULL);
+}
+
+static void
+hdlr_sighup(void)
+{
+ pthread_mutex_lock(&mutHadHUP);
+ bHadHUP = 1;
+ pthread_mutex_unlock(&mutHadHUP);
+ /* at least on FreeBSD we seem not to necessarily awake the main thread.
+ * So let's do it explicitely.
+ */
+ dbgprintf("awaking mainthread on HUP\n");
+ pthread_kill(mainthread, SIGTTIN);
+}
+
+static void
+hdlr_sigchld(void)
+{
+ pthread_mutex_lock(&mutChildDied);
+ bChildDied = 1;
+ pthread_mutex_unlock(&mutChildDied);
+}
+
+static void
+rsyslogdDebugSwitch(void)
+{
+ time_t tTime;
+ struct tm tp;
+
+ datetime.GetTime(&tTime);
+ localtime_r(&tTime, &tp);
+ if(debugging_on == 0) {
+ debugging_on = 1;
+ dbgprintf("\n");
+ dbgprintf("\n");
+ dbgprintf("********************************************************************************\n");
+ dbgprintf("Switching debugging_on to true at %2.2d:%2.2d:%2.2d\n",
+ tp.tm_hour, tp.tm_min, tp.tm_sec);
+ dbgprintf("********************************************************************************\n");
+ } else {
+ dbgprintf("********************************************************************************\n");
+ dbgprintf("Switching debugging_on to false at %2.2d:%2.2d:%2.2d\n",
+ tp.tm_hour, tp.tm_min, tp.tm_sec);
+ dbgprintf("********************************************************************************\n");
+ dbgprintf("\n");
+ dbgprintf("\n");
+ debugging_on = 0;
+ }
+}
+
+
+/* This is the main entry point into rsyslogd. Over time, we should try to
+ * modularize it a bit more...
+ *
+ * NOTE on stderr and stdout: they are kept open during a fork. Note that this
+ * may introduce subtle security issues: if we are in a jail, one may break out of
+ * it via these descriptors. But if I close them earlier, error messages will (once
+ * again) not be emitted to the user that starts the daemon. Given that the risk
+ * of a break-in is very low in the startup phase, we decide it is more important
+ * to emit error messages.
+ */
+static void
+initAll(int argc, char **argv)
+{
+ rsRetVal localRet;
+ int ch;
+ int iHelperUOpt;
+ int bChDirRoot = 1; /* change the current working directory to "/"? */
+ char *arg; /* for command line option processing */
+ char cwdbuf[128]; /* buffer to obtain/display current working directory */
+ int parentPipeFD = 0; /* fd of pipe to parent, if auto-backgrounding */
+ DEFiRet;
+
+ /* prepare internal signaling */
+ hdlr_enable(SIGTTIN, hdlr_sigttin_ou);
+ hdlr_enable(SIGTTOU, hdlr_sigttin_ou);
+
+ /* 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.
+ * rgerhards, 2008-04-04
+ */
+#if defined(_AIX)
+ while((ch = getopt(argc, argv, "46ACDdf:hi:M:nN:o:qQS:T:u:vwxR")) != EOF) {
+#else
+ while((ch = getopt(argc, argv, "46ACDdf:hi:M:nN:o:qQS:T:u:vwx")) != EOF) {
+#endif
+ switch((char)ch) {
+ case '4':
+ case '6':
+ case 'A':
+ case 'f': /* configuration file */
+ case 'i': /* pid file name */
+ case 'n': /* don't fork */
+ case 'N': /* enable config verify mode */
+ case 'q': /* add hostname if DNS resolving has failed */
+ case 'Q': /* dont resolve hostnames in ACL to IPs */
+ case 'S': /* Source IP for local client to be used on multihomed host */
+ case 'T': /* chroot on startup (primarily for testing) */
+ case 'u': /* misc user settings */
+ case 'w': /* disable disallowed host warnings */
+ case 'C':
+ case 'o': /* write output config file */
+ case 'x': /* disable dns for remote messages */
+ CHKiRet(bufOptAdd(ch, optarg));
+ break;
+#if defined(_AIX)
+ case 'R': /* This option is a no-op for AIX */
+ break;
+#endif
+ case 'd': /* debug - must be handled now, so that debug is active during init! */
+ debugging_on = 1;
+ Debug = 1;
+ yydebug = 1;
+ break;
+ case 'D': /* BISON debug */
+ yydebug = 1;
+ break;
+ case 'M': /* default module load path -- this MUST be carried out immediately! */
+ glblModPath = (uchar*) optarg;
+ break;
+ case 'v': /* MUST be carried out immediately! */
+ printVersion();
+ exit(0); /* exit for -v option - so this is a "good one" */
+ case 'h':
+ case '?':
+ default:
+ rsyslogd_usage();
+ }
+ }
+
+ if(argc - optind)
+ rsyslogd_usage();
+
+ DBGPRINTF("rsyslogd %s startup, module path '%s', cwd:%s\n",
+ VERSION, glblModPath == NULL ? "" : (char*)glblModPath,
+ getcwd(cwdbuf, sizeof(cwdbuf)));
+
+ /* we are done with the initial option parsing and processing. Now we init the system. */
+
+ CHKiRet(rsyslogd_InitGlobalClasses());
+
+ /* doing some core initializations */
+
+ 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 */
+ }
+
+ /* we now can emit error messages "the regular way" */
+
+ if(getenv("TZ") == NULL) {
+ const char *const tz =
+ (access("/etc/localtime", R_OK) == 0) ? "TZ=/etc/localtime" : "TZ=UTC";
+ putenv((char*)tz);
+ if(emitTZWarning) {
+ LogMsg(0, RS_RET_NO_TZ_SET, LOG_WARNING, "environment variable TZ is not "
+ "set, auto correcting this to %s", tz);
+ } else {
+ dbgprintf("environment variable TZ is not set, auto correcting this to %s\n", tz);
+ }
+ }
+
+ /* 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 == NULL) ? "" : arg);
+ switch((char)ch) {
+ case '4':
+ fprintf (stderr, "rsyslogd: the -4 command line option has gone away.\n"
+ "Please use the global(net.ipprotocol=\"ipv4-only\") "
+ "configuration parameter instead.\n");
+ break;
+ case '6':
+ fprintf (stderr, "rsyslogd: the -6 command line option will has gone away.\n"
+ "Please use the global(net.ipprotocol=\"ipv6-only\") "
+ "configuration parameter instead.\n");
+ break;
+ case 'A':
+ fprintf (stderr, "rsyslogd: the -A command line option will go away "
+ "soon.\n"
+ "Please use the omfwd parameter \"upd.sendToAll\" instead.\n");
+ send_to_all++;
+ break;
+ case 'S': /* Source IP for local client to be used on multihomed host */
+ fprintf (stderr, "rsyslogd: the -S command line option will go away "
+ "soon.\n"
+ "Please use the omrelp parameter \"localClientIP\" instead.\n");
+ if(glbl.GetSourceIPofLocalClient() != NULL) {
+ fprintf (stderr, "rsyslogd: Only one -S argument allowed, the first one is taken.\n");
+ } else {
+ glbl.SetSourceIPofLocalClient((uchar*)arg);
+ }
+ break;
+ case 'f': /* configuration file */
+ ConfFile = (uchar*) arg;
+ break;
+ case 'i': /* pid file name */
+ free((void*)PidFile);
+ PidFile = arg;
+ break;
+ case 'n': /* don't fork */
+ doFork = 0;
+ break;
+ case 'N': /* enable config verify mode */
+ iConfigVerify = (arg == NULL) ? 0 : atoi(arg);
+ break;
+ case 'o':
+ if(fp_rs_full_conf_output != NULL) {
+ fprintf(stderr, "warning: -o option given multiple times. Now "
+ "using value %s\n", (arg == NULL) ? "-" : arg);
+ fclose(fp_rs_full_conf_output);
+ fp_rs_full_conf_output = NULL;
+ }
+ if(arg == NULL || !strcmp(arg, "-")) {
+ fp_rs_full_conf_output = stdout;
+ } else {
+ fp_rs_full_conf_output = fopen(arg, "w");
+ }
+ if(fp_rs_full_conf_output == NULL) {
+ perror(arg);
+ fprintf (stderr, "rsyslogd: cannot open config output file %s - "
+ "-o option will be ignored\n", arg);
+ } else {
+ time_t tTime;
+ struct tm tp;
+ datetime.GetTime(&tTime);
+ localtime_r(&tTime, &tp);
+ fprintf(fp_rs_full_conf_output,
+ "## full conf created by rsyslog version %s at "
+ "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d ##\n",
+ VERSION, tp.tm_year + 1900, tp.tm_mon + 1, tp.tm_mday,
+ tp.tm_hour, tp.tm_min, tp.tm_sec);
+ }
+ break;
+ case 'q': /* add hostname if DNS resolving has failed */
+ fprintf (stderr, "rsyslogd: the -q command line option has gone away.\n"
+ "Please use the global(net.aclAddHostnameOnFail=\"on\") "
+ "configuration parameter instead.\n");
+ break;
+ case 'Q': /* dont resolve hostnames in ACL to IPs */
+ fprintf (stderr, "rsyslogd: the -Q command line option has gone away.\n"
+ "Please use the global(net.aclResolveHostname=\"off\") "
+ "configuration parameter instead.\n");
+ break;
+ case 'T':/* chroot() immediately at program startup, but only for testing, NOT security yet */
+ if(arg == NULL) {
+ /* note this case should already be handled by getopt,
+ * but we want to keep the static analyzer happy.
+ */
+ fprintf(stderr, "-T options needs a parameter\n");
+ exit(1);
+ }
+ if(chroot(arg) != 0) {
+ perror("chroot");
+ exit(1);
+ }
+ if(chdir("/") != 0) {
+ perror("chdir");
+ exit(1);
+ }
+ break;
+ case 'u': /* misc user settings */
+ iHelperUOpt = (arg == NULL) ? 0 : atoi(arg);
+ if(iHelperUOpt & 0x01) {
+ fprintf (stderr, "rsyslogd: the -u command line option has gone away.\n"
+ "For the 0x01 bit, please use the "
+ "global(parser.parseHostnameAndTag=\"off\") "
+ "configuration parameter instead.\n");
+ }
+ if(iHelperUOpt & 0x02) {
+ fprintf (stderr, "rsyslogd: the -u command line option will go away "
+ "soon.\n"
+ "For the 0x02 bit, please use the -C option instead.");
+ bChDirRoot = 0;
+ }
+ break;
+ case 'C':
+ bChDirRoot = 0;
+ break;
+ case 'w': /* disable disallowed host warnigs */
+ fprintf (stderr, "rsyslogd: the -w command line option has gone away.\n"
+ "Please use the global(net.permitWarning=\"off\") "
+ "configuration parameter instead.\n");
+ break;
+ case 'x': /* disable dns for remote messages */
+ fprintf (stderr, "rsyslogd: the -x command line option has gone away.\n"
+ "Please use the global(net.enableDNS=\"off\") "
+ "configuration parameter instead.\n");
+ break;
+ case 'h':
+ case '?':
+ default:
+ rsyslogd_usage();
+ }
+ }
+
+ if(iRet != RS_RET_END_OF_LINKEDLIST)
+ FINALIZE;
+
+ if(iConfigVerify) {
+ doFork = 0;
+ fprintf(stderr, "rsyslogd: version %s, config validation run (level %d), master config %s\n",
+ VERSION, iConfigVerify, ConfFile);
+ }
+
+ resetErrMsgsFlag();
+ localRet = rsconf.Load(&ourConf, ConfFile);
+
+#ifdef ENABLE_LIBCAPNG
+ if (loadConf->globals.bCapabilityDropEnabled) {
+ /*
+ * Drop capabilities to the necessary set
+ */
+ int capng_rc, capng_failed = 0;
+ typedef struct capabilities_s {
+ int capability; /* capability code */
+ const char *name; /* name of the capability to be displayed */
+ /* is the capability present that is needed by rsyslog? if so we do not drop it */
+ sbool present;
+ capng_type_t type;
+ } capabilities_t;
+
+ capabilities_t capabilities[] = {
+ #define CAP_FIELD(code, type) { code, #code, 0 , type}
+ CAP_FIELD(CAP_BLOCK_SUSPEND, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_NET_RAW, CAPNG_EFFECTIVE | CAPNG_PERMITTED ),
+ CAP_FIELD(CAP_CHOWN, CAPNG_EFFECTIVE | CAPNG_PERMITTED ),
+ CAP_FIELD(CAP_IPC_LOCK, CAPNG_EFFECTIVE | CAPNG_PERMITTED ),
+ CAP_FIELD(CAP_LEASE, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_NET_ADMIN, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_NET_BIND_SERVICE, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_DAC_OVERRIDE, CAPNG_EFFECTIVE | CAPNG_PERMITTED | CAPNG_BOUNDING_SET),
+ CAP_FIELD(CAP_SETGID, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_SETUID, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_SYS_ADMIN, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_SYS_CHROOT, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_SYS_RESOURCE, CAPNG_EFFECTIVE | CAPNG_PERMITTED),
+ CAP_FIELD(CAP_SYSLOG, CAPNG_EFFECTIVE | CAPNG_PERMITTED)
+ #undef CAP_FIELD
+ };
+
+ if (capng_have_capabilities(CAPNG_SELECT_CAPS) > CAPNG_NONE) {
+ /* Examine which capabilities are available to us, so we do not try to
+ drop something that is not present. We need to do this in two steps,
+ because capng_clear clears the capability set. In the second step,
+ we add back those caps, which were present before clearing the selected
+ posix capabilities set.
+ */
+ unsigned long caps_len = sizeof(capabilities) / sizeof(capabilities_t);
+ for (unsigned long i = 0; i < caps_len; i++) {
+ if (capng_have_capability(CAPNG_EFFECTIVE, capabilities[i].capability)) {
+ capabilities[i].present = 1;
+ }
+ }
+
+ capng_clear(CAPNG_SELECT_BOTH);
+
+ for (unsigned long i = 0; i < caps_len; i++) {
+ if (capabilities[i].present) {
+ DBGPRINTF("The %s capability is present, "
+ "will try to preserve it.\n", capabilities[i].name);
+ if ((capng_rc = capng_update(CAPNG_ADD, capabilities[i].type,
+ capabilities[i].capability)) != 0) {
+ LogError(0, RS_RET_LIBCAPNG_ERR,
+ "could not update the internal posix capabilities"
+ " settings based on the options passed to it,"
+ " capng_update=%d", capng_rc);
+ capng_failed = 1;
+ }
+ } else {
+ DBGPRINTF("The %s capability is not present, "
+ "will not try to preserve it.\n", capabilities[i].name);
+ }
+ }
+
+ if ((capng_rc = capng_apply(CAPNG_SELECT_BOTH)) != 0) {
+ LogError(0, RS_RET_LIBCAPNG_ERR,
+ "could not transfer the specified internal posix capabilities "
+ "settings to the kernel, capng_apply=%d", capng_rc);
+ capng_failed = 1;
+ }
+
+ if (capng_failed) {
+ DBGPRINTF("Capabilities were not dropped successfully.\n");
+ if (loadConf->globals.bAbortOnFailedLibcapngSetup) {
+ ABORT_FINALIZE(RS_RET_LIBCAPNG_ERR);
+ }
+ } else {
+ DBGPRINTF("Capabilities were dropped successfully\n");
+ }
+ } else {
+ DBGPRINTF("No capabilities to drop\n");
+ }
+ }
+#endif
+
+ if(fp_rs_full_conf_output != NULL) {
+ if(fp_rs_full_conf_output != stdout) {
+ fclose(fp_rs_full_conf_output);
+ }
+ fp_rs_full_conf_output = NULL;
+ }
+
+ /* check for "hard" errors that needs us to abort in any case */
+ if( (localRet == RS_RET_CONF_FILE_NOT_FOUND)
+ || (localRet == RS_RET_NO_ACTIONS) ) {
+ /* for extreme testing, we keep the ability to let rsyslog continue
+ * even on hard config errors. Note that this may lead to segfaults
+ * or other malfunction further down the road.
+ */
+ if((loadConf->globals.glblDevOptions & DEV_OPTION_KEEP_RUNNING_ON_HARD_CONF_ERROR) == 1) {
+ fprintf(stderr, "rsyslogd: NOTE: developer-only option set to keep rsyslog "
+ "running where it should abort - this can lead to "
+ "more problems later in the run.\n");
+ } else {
+ ABORT_FINALIZE(localRet);
+ }
+ }
+
+ glbl.GenerateLocalHostNameProperty();
+
+ if(hadErrMsgs()) {
+ if(loadConf->globals.bAbortOnUncleanConfig) {
+ fprintf(stderr, "rsyslogd: global(AbortOnUncleanConfig=\"on\") is set, and "
+ "config is not clean.\n"
+ "Check error log for details, fix errors and restart. As a last\n"
+ "resort, you may want to use global(AbortOnUncleanConfig=\"off\") \n"
+ "to permit a startup with a dirty config.\n");
+ exit(2);
+ }
+ if(iConfigVerify) {
+ /* a bit dirty, but useful... */
+ exit(1);
+ }
+ localRet = RS_RET_OK;
+ }
+ CHKiRet(localRet);
+
+ CHKiRet(rsyslogd_InitStdRatelimiters());
+
+ if(bChDirRoot) {
+ if(chdir("/") != 0)
+ fprintf(stderr, "Can not do 'cd /' - still trying to run\n");
+ }
+
+ if(iConfigVerify)
+ FINALIZE;
+ /* after this point, we are in a "real" startup */
+
+ thrdInit();
+ CHKiRet(checkStartupOK());
+ if(doFork) {
+ parentPipeFD = forkRsyslog();
+ }
+ glblSetOurPid(getpid());
+
+ hdlr_enable(SIGPIPE, SIG_IGN);
+ hdlr_enable(SIGXFSZ, SIG_IGN);
+ if(Debug || loadConf->globals.permitCtlC) {
+ hdlr_enable(SIGUSR1, rsyslogdDebugSwitch);
+ hdlr_enable(SIGINT, rsyslogdDoDie);
+ hdlr_enable(SIGQUIT, rsyslogdDoDie);
+ } else {
+ hdlr_enable(SIGUSR1, SIG_IGN);
+ hdlr_enable(SIGINT, SIG_IGN);
+ hdlr_enable(SIGQUIT, SIG_IGN);
+ }
+ hdlr_enable(SIGTERM, rsyslogdDoDie);
+ hdlr_enable(SIGCHLD, hdlr_sigchld);
+ hdlr_enable(SIGHUP, hdlr_sighup);
+
+ if(rsconfNeedDropPriv(loadConf)) {
+ /* need to write pid file early as we may loose permissions */
+ CHKiRet(writePidFile());
+ }
+
+ CHKiRet(rsconf.Activate(ourConf));
+
+ if(runConf->globals.bLogStatusMsgs) {
+ char bufStartUpMsg[512];
+ snprintf(bufStartUpMsg, sizeof(bufStartUpMsg),
+ "[origin software=\"rsyslogd\" " "swVersion=\"" VERSION \
+ "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"] start",
+ (int) glblGetOurPid());
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)bufStartUpMsg, 0);
+ }
+
+ if(!rsconfNeedDropPriv(runConf)) {
+ CHKiRet(writePidFile());
+ }
+
+ /* END OF INTIALIZATION */
+ DBGPRINTF("rsyslogd: initialization completed, transitioning to regular run mode\n");
+
+ if(doFork) {
+ tellChildReady(parentPipeFD, "OK");
+ stddbg = -1; /* turn off writing to fd 1 */
+ close(1);
+ close(2);
+ runConf->globals.bErrMsgToStderr = 0;
+ }
+
+finalize_it:
+ if(iRet == RS_RET_VALIDATION_RUN) {
+ fprintf(stderr, "rsyslogd: End of config validation run. Bye.\n");
+ exit(0);
+ } else if(iRet != RS_RET_OK) {
+ fprintf(stderr, "rsyslogd: run failed with error %d (see rsyslog.h "
+ "or try https://www.rsyslog.com/e/%d to learn what that number means)\n",
+ iRet, iRet*-1);
+ exit(1);
+ }
+
+}
+
+
+/* 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
+ */
+void
+processImInternal(void)
+{
+ smsg_t *pMsg;
+ smsg_t *repMsg;
+
+ while(iminternalRemoveMsg(&pMsg) == RS_RET_OK) {
+ rsRetVal localRet = ratelimitMsg(internalMsg_ratelimiter, pMsg, &repMsg);
+ if(repMsg != NULL) {
+ logmsgInternal_doWrite(repMsg);
+ }
+ if(localRet == RS_RET_OK) {
+ logmsgInternal_doWrite(pMsg);
+ }
+ }
+}
+
+
+/* This takes a received message that must be decoded and submits it to
+ * the main message queue. This is a legacy function which is being provided
+ * to aid older input plugins that do not support message creation via
+ * the new interfaces themselves. It is not recommended to use this
+ * function for new plugins. -- rgerhards, 2009-10-12
+ */
+rsRetVal
+parseAndSubmitMessage(const uchar *const hname, const uchar *const hnameIP, const uchar *const msg,
+ const int len, const int flags, const flowControl_t flowCtlType,
+ prop_t *const pInputName,
+ const struct syslogTime *const stTime,
+ const time_t ttGenTime,
+ ruleset_t *const pRuleset)
+{
+ prop_t *pProp = NULL;
+ smsg_t *pMsg = NULL;
+ DEFiRet;
+
+ /* we now create our own message object and submit it to the queue */
+ if(stTime == NULL) {
+ CHKiRet(msgConstruct(&pMsg));
+ } else {
+ CHKiRet(msgConstructWithTime(&pMsg, stTime, ttGenTime));
+ }
+ if(pInputName != NULL)
+ MsgSetInputName(pMsg, pInputName);
+ MsgSetRawMsg(pMsg, (char*)msg, len);
+ MsgSetFlowControlType(pMsg, flowCtlType);
+ MsgSetRuleset(pMsg, pRuleset);
+ pMsg->msgFlags = flags | NEEDS_PARSING;
+
+ MsgSetRcvFromStr(pMsg, hname, ustrlen(hname), &pProp);
+ CHKiRet(prop.Destruct(&pProp));
+ CHKiRet(MsgSetRcvFromIPStr(pMsg, hnameIP, ustrlen(hnameIP), &pProp));
+ CHKiRet(prop.Destruct(&pProp));
+ CHKiRet(submitMsg2(pMsg));
+
+finalize_it:
+ if(iRet != RS_RET_OK) {
+ DBGPRINTF("parseAndSubmitMessage() error, discarding msg: %s\n", msg);
+ if(pMsg != NULL) {
+ msgDestruct(&pMsg);
+ }
+ }
+ RETiRet;
+}
+
+
+/* helper to doHUP(), this "HUPs" each action. The necessary locking
+ * is done inside the action class and nothing we need to take care of.
+ * rgerhards, 2008-10-22
+ */
+DEFFUNC_llExecFunc(doHUPActions)
+{
+ actionCallHUPHdlr((action_t*) pData);
+ return RS_RET_OK; /* we ignore errors, we can not do anything either way */
+}
+
+
+/* This function processes a HUP after one has been detected. Note that this
+ * is *NOT* the sighup handler. The signal is recorded by the handler, that record
+ * detected inside the mainloop and then this function is called to do the
+ * real work. -- rgerhards, 2008-10-22
+ * Note: there is a VERY slim chance of a data race when the hostname is reset.
+ * We prefer to take this risk rather than sync all accesses, because to the best
+ * of my analysis it can not really hurt (the actual property is reference-counted)
+ * but the sync would require some extra CPU for *each* message processed.
+ * rgerhards, 2012-04-11
+ */
+static void
+doHUP(void)
+{
+ char buf[512];
+
+ DBGPRINTF("doHUP: doing modules\n");
+ if(ourConf->globals.bLogStatusMsgs) {
+ snprintf(buf, sizeof(buf),
+ "[origin software=\"rsyslogd\" " "swVersion=\"" VERSION
+ "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"] rsyslogd was HUPed",
+ (int) glblGetOurPid());
+ errno = 0;
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)buf, 0);
+ }
+
+ queryLocalHostname(runConf); /* re-read our name */
+ ruleset.IterateAllActions(ourConf, doHUPActions, NULL);
+ DBGPRINTF("doHUP: doing modules\n");
+ modDoHUP();
+ DBGPRINTF("doHUP: doing lookup tables\n");
+ lookupDoHUP();
+ DBGPRINTF("doHUP: doing errmsgs\n");
+ errmsgDoHUP();
+}
+
+/* rsyslogdDoDie() 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
+ * Note:
+ * - we do not call DBGPRINTF() as this may cause us to block in case something
+ * with the threading is wrong.
+ * - we do not really care about the return state of write(), but we need this
+ * strange check we do to silence compiler warnings (thanks, Ubuntu!)
+ */
+void
+rsyslogdDoDie(int sig)
+{
+# define MSG1 "DoDie called.\n"
+# define MSG2 "DoDie called 5 times - unconditional exit\n"
+ static int iRetries = 0; /* debug aid */
+ dbgprintf(MSG1);
+ if(Debug == DEBUG_FULL) {
+ if(write(1, MSG1, sizeof(MSG1) - 1) == -1) {
+ dbgprintf("%s:%d: write failed\n", __FILE__, __LINE__);
+ }
+ }
+ if(iRetries++ == 4) {
+ if(Debug == DEBUG_FULL) {
+ if(write(1, MSG2, sizeof(MSG2) - 1) == -1) {
+ dbgprintf("%s:%d: write failed\n", __FILE__, __LINE__);
+ }
+ }
+ abort();
+ }
+ bFinished = sig;
+ if(runConf->globals.debugOnShutdown) {
+ /* kind of hackish - set to 0, so that debug_swith will enable
+ * and AND emit the "start debug log" message.
+ */
+ debugging_on = 0;
+ rsyslogdDebugSwitch();
+ }
+# undef MSG1
+# undef MSG2
+ /* at least on FreeBSD we seem not to necessarily awake the main thread.
+ * So let's do it explicitely.
+ */
+ dbgprintf("awaking mainthread\n");
+ pthread_kill(mainthread, SIGTTIN);
+}
+
+
+static void
+wait_timeout(const sigset_t *sigmask)
+{
+ struct timespec tvSelectTimeout;
+
+ tvSelectTimeout.tv_sec = runConf->globals.janitorInterval * 60; /* interval is in minutes! */
+ tvSelectTimeout.tv_nsec = 0;
+
+#ifdef _AIX
+ if(!src_exists) {
+ /* it looks like select() is NOT interrupted by HUP, even though
+ * SA_RESTART is not given in the signal setup. As this code is
+ * not expected to be used in production (when running as a
+ * service under src control), we simply make a kind of
+ * "somewhat-busy-wait" algorithm. We compute our own
+ * timeout value, which we count down to zero. We do this
+ * in useful subsecond steps.
+ */
+ const long wait_period = 500000000; /* wait period in nanoseconds */
+ int timeout = runConf->globals.janitorInterval * 60 * (1000000000 / wait_period);
+
+ tvSelectTimeout.tv_sec = 0;
+ tvSelectTimeout.tv_nsec = wait_period;
+ do {
+ pthread_mutex_lock(&mutHadHUP);
+ if(bFinished || bHadHUP) {
+ pthread_mutex_unlock(&mutHadHUP);
+ break;
+ }
+ pthread_mutex_unlock(&mutHadHUP);
+ pselect(1, NULL, NULL, NULL, &tvSelectTimeout, sigmask);
+ } while(--timeout > 0);
+ } else {
+ char buf[256];
+ fd_set rfds;
+
+ FD_ZERO(&rfds);
+ FD_SET(SRC_FD, &rfds);
+ if(pselect(SRC_FD + 1, (fd_set *)&rfds, NULL, NULL, &tvSelectTimeout, sigmask))
+ {
+ if(FD_ISSET(SRC_FD, &rfds))
+ {
+ rc = recvfrom(SRC_FD, &srcpacket, SRCMSG, 0, &srcaddr, &addrsz);
+ if(rc < 0) {
+ if (errno != EINTR)
+ {
+ fprintf(stderr,"%s: ERROR: '%d' recvfrom\n", progname,errno);
+ exit(1); //TODO: this needs to be handled gracefully
+ } else { /* punt on short read */
+ return;
+ }
+
+ switch(srcpacket.subreq.action)
+ {
+ case START:
+ dosrcpacket(SRC_SUBMSG,"ERROR: rsyslogd does not support this "
+ "option.\n", sizeof(struct srcrep));
+ break;
+ case STOP:
+ if (srcpacket.subreq.object == SUBSYSTEM) {
+ dosrcpacket(SRC_OK,NULL,sizeof(struct srcrep));
+ (void) snprintf(buf, sizeof(buf) / sizeof(char), " [origin "
+ "software=\"rsyslogd\" " "swVersion=\"" VERSION \
+ "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"]"
+ " exiting due to stopsrc.",
+ (int) glblGetOurPid());
+ errno = 0;
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)buf, 0);
+ return ;
+ } else
+ dosrcpacket(SRC_SUBMSG,"ERROR: rsyslogd does not support "
+ "this option.\n",sizeof(struct srcrep));
+ break;
+ case REFRESH:
+ dosrcpacket(SRC_SUBMSG,"ERROR: rsyslogd does not support this "
+ "option.\n", sizeof(struct srcrep));
+ break;
+ default:
+ dosrcpacket(SRC_SUBICMD,NULL,sizeof(struct srcrep));
+ break;
+
+ }
+ }
+ }
+ }
+ }
+#else
+ pselect(0, NULL, NULL, NULL, &tvSelectTimeout, sigmask);
+#endif /* AIXPORT : SRC end */
+}
+
+
+static void
+reapChild(void)
+{
+ pid_t child;
+ do {
+ int status;
+ child = waitpid(-1, &status, WNOHANG);
+ if(child != -1 && child != 0) {
+ glblReportChildProcessExit(runConf, NULL, child, status);
+ }
+ } while(child > 0);
+}
+
+
+/* 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)
+{
+ time_t tTime;
+ sigset_t origmask;
+ sigset_t sigblockset;
+ int need_free_mutex;
+
+ sigemptyset(&sigblockset);
+ sigaddset(&sigblockset, SIGTERM);
+ sigaddset(&sigblockset, SIGCHLD);
+ sigaddset(&sigblockset, SIGHUP);
+
+ do {
+ pthread_sigmask(SIG_BLOCK, &sigblockset, &origmask);
+ pthread_mutex_lock(&mutChildDied);
+ need_free_mutex = 1;
+ if(bChildDied) {
+ bChildDied = 0;
+ pthread_mutex_unlock(&mutChildDied);
+ need_free_mutex = 0;
+ reapChild();
+ }
+ if(need_free_mutex) {
+ pthread_mutex_unlock(&mutChildDied);
+ }
+
+ pthread_mutex_lock(&mutHadHUP);
+ need_free_mutex = 1;
+ if(bHadHUP) {
+ need_free_mutex = 0;
+ pthread_mutex_unlock(&mutHadHUP);
+ doHUP();
+ pthread_mutex_lock(&mutHadHUP);
+ bHadHUP = 0;
+ pthread_mutex_unlock(&mutHadHUP);
+ }
+ if(need_free_mutex) {
+ pthread_mutex_unlock(&mutHadHUP);
+ }
+
+ processImInternal();
+
+ if(bFinished)
+ break; /* exit as quickly as possible */
+
+ wait_timeout(&origmask);
+ pthread_sigmask(SIG_UNBLOCK, &sigblockset, NULL);
+
+ janitorRun();
+
+ datetime.GetTime(&tTime);
+ checkGoneAwaySenders(tTime);
+
+ } while(!bFinished); /* end do ... while() */
+}
+
+/* Finalize and destruct all actions.
+ */
+static void
+rsyslogd_destructAllActions(void)
+{
+ ruleset.DestructAllActions(runConf);
+ PREFER_STORE_0_TO_INT(&bHaveMainQueue); /* flag that internal messages need to be temporarily stored */
+}
+
+
+/* de-initialize everything, make ready for termination */
+static void
+deinitAll(void)
+{
+ char buf[256];
+
+ DBGPRINTF("exiting on signal %d\n", bFinished);
+
+ /* 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");
+ glbl.SetGlobalInputTermination();
+
+ thrdTerminateAll();
+
+ /* and THEN send the termination log message (see long comment above) */
+ if(bFinished && runConf->globals.bLogStatusMsgs) {
+ (void) snprintf(buf, sizeof(buf),
+ "[origin software=\"rsyslogd\" " "swVersion=\"" VERSION \
+ "\" x-pid=\"%d\" x-info=\"https://www.rsyslog.com\"]" " exiting on signal %d.",
+ (int) glblGetOurPid(), bFinished);
+ errno = 0;
+ logmsgInternal(NO_ERRCODE, LOG_SYSLOG|LOG_INFO, (uchar*)buf, 0);
+ }
+ processImInternal(); /* make sure not-yet written internal messages are processed */
+ /* we sleep a couple of ms to give the queue a chance to pick up the late messages
+ * (including exit message); otherwise we have seen cases where the message did
+ * not make it to log files, even on idle systems.
+ */
+ srSleep(0, 50);
+
+ /* drain queue (if configured so) and stop main queue worker thread pool */
+ DBGPRINTF("Terminating main queue...\n");
+ qqueueDestruct(&runConf->pMsgQueue);
+ runConf->pMsgQueue = NULL;
+
+ /* Free ressources and close connections. This includes flushing any remaining
+ * repeated msgs.
+ */
+ DBGPRINTF("Terminating outputs...\n");
+ rsyslogd_destructAllActions();
+
+ DBGPRINTF("all primary multi-thread sources have been terminated - now doing aux cleanup...\n");
+
+ DBGPRINTF("destructing current config...\n");
+ rsconf.Destruct(&runConf);
+
+ modExitIminternal();
+
+ if(pInternalInputName != NULL)
+ prop.Destruct(&pInternalInputName);
+
+ /* the following line cleans up CfSysLineHandlers that were not based on loadable
+ * modules. As such, they are not yet cleared. */
+ unregCfSysLineHdlrs();
+
+ /* this is the last spot where this can be done - below output modules are unloaded! */
+
+ parserClassExit();
+ rsconfClassExit();
+ strExit();
+ ratelimitModExit();
+ dnscacheDeinit();
+ thrdExit();
+ objRelease(net, LM_NET_FILENAME);
+
+ module.UnloadAndDestructAll(eMOD_LINK_ALL);
+
+ rsrtExit(); /* runtime MUST always be deinitialized LAST (except for debug system) */
+ DBGPRINTF("Clean shutdown completed, bye\n");
+
+ errmsgExit();
+ /* dbgClassExit MUST be the last one, because it de-inits the debug system */
+ dbgClassExit();
+
+ /* NO CODE HERE - dbgClassExit() must be the last thing before exit()! */
+ clearPidFile();
+}
+
+/* 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)
+{
+#if defined(_AIX)
+ /* SRC support : fd 0 (stdin) must be the SRC socket
+ * startup. fd 0 is duped to a new descriptor so that stdin can be used
+ * internally by rsyslogd.
+ */
+
+ pthread_mutex_init(&mutHadHUP, NULL);
+ pthread_mutex_init(&mutChildDied, NULL);
+ strncpy(progname,argv[0], sizeof(progname)-1);
+ addrsz = sizeof(srcaddr);
+ if ((rc = getsockname(0, &srcaddr, &addrsz)) < 0) {
+ fprintf(stderr, "%s: continuing without SRC support\n", progname);
+ src_exists = FALSE;
+ }
+ if (src_exists)
+ if(dup2(0, SRC_FD) == -1) {
+ fprintf(stderr, "%s: dup2 failed exiting now...\n", progname);
+ /* In the unlikely event of dup2 failing we exit */
+ exit(-1);
+ }
+#endif
+
+ mainthread = pthread_self();
+ if((int) getpid() == 1) {
+ fprintf(stderr, "rsyslogd %s: running as pid 1, enabling "
+ "container-specific defaults, press ctl-c to "
+ "terminate rsyslog\n", VERSION);
+ PidFile = strdup("NONE"); /* disables pid file writing */
+ glblPermitCtlC = 1;
+ runningInContainer = 1;
+ emitTZWarning = 1;
+ } else {
+ /* "dynamic defaults" - non-container case */
+ PidFile = strdup(PATH_PIDFILE);
+ }
+ if(PidFile == NULL) {
+ fprintf(stderr, "rsyslogd: could not alloc memory for pid file "
+ "default name - aborting\n");
+ exit(1);
+ }
+
+ /* disable case-sensitive comparisons in variable subsystem: */
+ fjson_global_do_case_sensitive_comparison(0);
+
+ dbgClassInit();
+
+ initAll(argc, argv);
+#ifdef HAVE_LIBSYSTEMD
+ sd_notify(0, "READY=1");
+ dbgprintf("done signaling to systemd that we are ready!\n");
+#endif
+ DBGPRINTF("max message size: %d\n", glblGetMaxLine(runConf));
+ DBGPRINTF("----RSYSLOGD INITIALIZED\n");
+ LogMsg(0, RS_RET_OK, LOG_DEBUG, "rsyslogd fully started up and initialized "
+ "- begin actual processing");
+
+ mainloop();
+ LogMsg(0, RS_RET_OK, LOG_DEBUG, "rsyslogd shutting down");
+ deinitAll();
+ osf_close();
+ pthread_mutex_destroy(&mutChildDied);
+ pthread_mutex_destroy(&mutHadHUP);
+ return 0;
+}