#define IMMARK 0 /* this is an aid to allow commiting work in progress to cvs. will be removed * once we have a version that is ready to compile cleanly. If you don't * develop immark, make sure this is set to 0 (should I forget to do that * when I check in). * rgerhards, 2007-12-14 */ /** * \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. * * Please note that as of now, a lot of the code in this file stems * from the sysklogd project. To learn more over this project, please * visit * * http://www.infodrom.org/projects/sysklogd/ * * 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 I made quite some changes to the orignal package. * I expect to do even more changes - up * to a full rewrite - to meet my design goals, which among others * contain a (at least) dual-thread design with a memory buffer for * storing received bursts of data. This is also the reason why I * kind of "forked" a completely new branch of the package. My intension * is to do many changes and only this initial release will look * similar to sysklogd (well, one never knows...). * * As I have made a lot of modifications, please assume that all bugs * in this package are mine and not those of the sysklogd team. * * As of this writing, there already exist heavy * modifications to the orginal sysklogd package. I suggest to no * longer rely too much on code knowledge you eventually have with * sysklogd - rgerhards 2005-07-05 * The code is now almost completely different. Be careful! * rgerhards, 2006-11-30 * * 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 . * * A copy of the GPL can be found in the file "COPYING" in this distribution. * * This Project was intiated and is maintained by * Rainer Gerhards . 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 * \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-2007 Rainer Gerhards and Adiscon GmbH. * * 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-1307, USA. * * A copy of the GPL can be found in the file "COPYING" in this distribution. */ #include "config.h" #include "rsyslog.h" #ifdef __FreeBSD__ #define BSD #endif /* 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 * * I have removed syslogdPanic(). That function was supposed to be used * for logging in low-memory conditons. Ever since it was introduced, it * was a wrapper for dbgprintf(). A more intelligent choice was hard to * find. After all, if we are short on memory, doing anything fance will * again cause memory problems. I have now modified the code so that * those elements for which we do not get memory are simply discarded. * That might be a single property like the TAG, but it might also be * a complete message. The overall goal of this code change is to keep * rsyslogd up and running, while we sacrifice some messages to reach * that goal. It also keeps the code cleaner. A real out of memory * condition is highly unlikely. If it happens, there will probably be * much more trouble on the system in question. Anyhow - rsyslogd will * most probably be able to survive it and carry on with processing * once the situation has been resolved. */ #define DEFUPRI (LOG_USER|LOG_NOTICE) #define DEFSPRI (LOG_KERN|LOG_CRIT) #define TIMERINTVL 30 /* interval for checking flush, mark */ #define CONT_LINE 1 /* Allow continuation lines */ #ifdef MTRACE #include #endif #include #include #include #include #include #include #define GNU_SOURCE #include #include #include #include #include #include #ifdef __sun #include #else #include #endif #include #include #include #include #include #include #if HAVE_SYS_TIMESPEC_H # include #endif #include #include #include #include #include #include #ifndef __sun #endif #include #include #include #include "pidfile.h" #include #include #if HAVE_PATHS_H #include #endif /* handle some defines missing on more than one platform */ #ifndef SUN_LEN #define SUN_LEN(su) \ (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path)) #endif #include "srUtils.h" #include "stringbuf.h" #include "syslogd-types.h" #include "template.h" #include "outchannel.h" #include "syslogd.h" #include "net.h" /* struct NetAddr */ #include "sync.h" /* struct NetAddr */ #include "parse.h" #include "msg.h" #include "modules.h" #include "action.h" #include "tcpsyslog.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" #if IMMARK #include "plugins/immark/immark.h" #endif /* 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 LOG_MAKEPRI(fac, pri) (((fac) << 3) | (pri)) #define LOG_PRI(p) ((p) & LOG_PRIMASK) #define LOG_FAC(p) (((p) & LOG_FACMASK) >> 3) #define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ #define LOG_FTP (11<<3) /* ftp daemon */ #define INTERNAL_MARK LOG_MAKEPRI((LOG_NFACILITIES<<3), 0) #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 BSD #define _PATH_VARRUN "/var/run/" #endif #ifdef __sun #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(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_CONSOLE #define _PATH_CONSOLE "/dev/console" #endif #ifndef _PATH_TTY #define _PATH_TTY "/dev/tty" #endif #ifndef _PATH_LOG #ifdef BSD #define _PATH_LOG "/var/run/log" #else #define _PATH_LOG "/dev/log" #endif #endif /* IPv6 compatibility layer for older platforms * We need to handle a few things different if we are running * on an older platform which does not support all the glory * of IPv6. We try to limit toll on features and reliability, * but obviously it is better to run rsyslog on a platform that * supports everything... * rgerhards, 2007-06-22 */ #ifndef AI_NUMERICSERV # define AI_NUMERICSERV 0 #endif static uchar *ConfFile = (uchar*) _PATH_LOGCONF; /* read-only after startup */ static char *PidFile = _PATH_LOGPID; /* read-only after startup */ static uchar *pModDir = NULL; /* read-only after startup */ char ctty[] = _PATH_CONSOLE; /* this is read-only; used by omfile -- TODO: remove that dependency */ 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 debugging_on = 0; /* read-only, except on sig USR1 */ static int restart = 0; /* do restart (config read) - multithread safe */ static int bRequestDoMark = 0; /* do mark processing? (multithread safe) */ #define MAXFUNIX 20 int glblHadMemShortage = 0; /* indicates if we had memory shortage some time during the run */ int startIndexUxLocalSockets = 0; /* process funix from that index on (used to * suppress local logging. rgerhards 2005-08-01 * read-only after startup */ int funixParseHost[MAXFUNIX] = { 0, }; /* should parser parse host name? read-only after startup */ char *funixn[MAXFUNIX] = { _PATH_LOG }; /* read-only after startup */ int funix[MAXFUNIX] = { -1, }; /* read-only after startup */ #define INTERNAL_NOPRI 0x10 /* the "no priority" priority */ #define TABLE_NOPRI 0 /* Value to indicate no priority in f_pmask */ #define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */ #define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */ /* definitions used for doNameLine to differentiate between different command types * (with otherwise identical code). This is a left-over from the previous config * system. It stays, because it is still useful. So do not wonder why it looks * somewhat strange (at least its name). -- rgerhards, 2007-08-01 */ enum eDirective { DIR_TEMPLATE = 0, DIR_OUTCHANNEL = 1, DIR_ALLOWEDSENDER = 2}; /* The following global variables are used for building * tag and host selector lines during startup and config reload. * This is stored as a global variable pool because of its ease. It is * also fairly compatible with multi-threading as the stratup code must * be run in a single thread anyways. So there can be no race conditions. These * variables are no longer used once the configuration has been loaded (except, * of course, during a reload). rgerhards 2005-10-18 */ static EHostnameCmpMode eDfltHostnameCmpMode; static rsCStrObj *pDfltHostnameCmp; static rsCStrObj *pDfltProgNameCmp; /* supporting structures for multithreading */ int bRunningMultithreaded = 0; /* Is this program running in multithreaded mode? */ static pthread_t thrdWorker; static int bGlblDone = 0; /* END supporting structures for multithreading */ static int bParseHOSTNAMEandTAG = 1; /* global config var: should the hostname and tag be * parsed inside message - rgerhards, 2006-03-13 */ 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. */ int repeatinterval[] = { 30, 60 }; /* # of secs before flush */ #define MAXREPEAT ((int)((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)) #define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) #define BACKOFF(f) { if (++(f)->f_repeatcount > MAXREPEAT) \ (f)->f_repeatcount = MAXREPEAT; \ } #ifdef SYSLOG_INET union sockunion { struct sockinet { u_char si_len; u_char si_family; } su_si; struct sockaddr_in su_sin; struct sockaddr_in6 su_sin6; }; #endif #define LIST_DELIMITER ':' /* delimiter between two hosts */ struct filed *Files = NULL; /* read-only after init() (but beware of sigusr1!) */ struct code { char *c_name; int c_val; }; static struct code PriNames[] = { {"alert", LOG_ALERT}, {"crit", LOG_CRIT}, {"debug", LOG_DEBUG}, {"emerg", LOG_EMERG}, {"err", LOG_ERR}, {"error", LOG_ERR}, /* DEPRECATED */ {"info", LOG_INFO}, {"none", INTERNAL_NOPRI}, /* INTERNAL */ {"notice", LOG_NOTICE}, {"panic", LOG_EMERG}, /* DEPRECATED */ {"warn", LOG_WARNING}, /* DEPRECATED */ {"warning", LOG_WARNING}, {"*", TABLE_ALLPRI}, {NULL, -1} }; static struct code FacNames[] = { {"auth", LOG_AUTH}, {"authpriv", LOG_AUTHPRIV}, {"cron", LOG_CRON}, {"daemon", LOG_DAEMON}, {"kern", LOG_KERN}, {"lpr", LOG_LPR}, {"mail", LOG_MAIL}, {"mark", LOG_MARK}, /* INTERNAL */ {"news", LOG_NEWS}, {"security", LOG_AUTH}, /* DEPRECATED */ {"syslog", LOG_SYSLOG}, {"user", LOG_USER}, {"uucp", LOG_UUCP}, #if defined(LOG_FTP) {"ftp", LOG_FTP}, #endif {"local0", LOG_LOCAL0}, {"local1", LOG_LOCAL1}, {"local2", LOG_LOCAL2}, {"local3", LOG_LOCAL3}, {"local4", LOG_LOCAL4}, {"local5", LOG_LOCAL5}, {"local6", LOG_LOCAL6}, {"local7", LOG_LOCAL7}, {NULL, -1}, }; static pid_t ppid; /* This is a quick and dirty hack used for spliting main/startup thread */ /* global variables for config file state */ static int bDropTrailingLF = 1; /* drop trailing LF's on reception? */ int Debug; /* debug flag - read-only after startup */ 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? */ int bDropMalPTRMsgs = 0;/* Drop messages which have malicious PTR records during DNS lookup */ static uchar cCCEscapeChar = '\\';/* character to be used to start an escape sequence for control chars */ static int bEscapeCCOnRcv; /* escape control characters on reception: 0 - no, 1 - yes */ static int bReduceRepeatMsgs; /* reduce repeated message - 0 - no, 1 - yes */ static int bActExecWhenPrevSusp; /* execute action only when previous one was suspended? */ static int logEveryMsg = 0;/* no repeat message processing - read-only after startup * 0 - suppress duplicate messages * 1 - do NOT suppress duplicate messages */ /* end global config file state variables */ static unsigned int Forwarding = 0; static int nfunix = 1; /* number of Unix sockets open / read-only after startup */ char LocalHostName[MAXHOSTNAMELEN+1];/* our hostname - read-only after startup */ char *LocalDomain; /* our local domain name - read-only after startup */ int *finet = NULL; /* Internet datagram sockets, first element is nbr of elements * read-only after init(), but beware of restart! */ static char *LogPort = "514"; /* port number for INET connections */ static int MarkInterval = 20 * 60; /* interval between marks in seconds - read-only after startup */ int family = PF_UNSPEC; /* protocol family (IPv4, IPv6 or both), set via cmdline */ int send_to_all = 0; /* send message to all IPv4/IPv6 addresses */ static int MarkSeq = 0; /* mark sequence number - modified in domark() only */ static int NoFork = 0; /* don't fork - don't run in daemon mode - read-only after startup */ static int AcceptRemote = 0;/* receive messages that come via UDP - read-only after startup */ int ACLAddHostnameOnFail = 0; /* add hostname to acl when DNS resolving has failed */ int ACLDontResolve = 0; /* add hostname to acl instead of resolving it to IP(s) */ int DisableDNS = 0; /* don't look up IP addresses of remote messages */ char **StripDomains = NULL;/* these domains may be stripped before writing logs - r/o after s.u., never touched by init */ char **LocalHosts = NULL;/* these hosts are logged with their hostname - read-only after startup, never touched by init */ int NoHops = 1; /* Can we bounce syslog messages through an intermediate host. Read-only after startup */ static int Initialized = 0; /* set when we have initialized ourselves * rgerhards 2004-11-09: and by initialized, we mean that * the configuration file could be properly read AND the * syslog/udp port could be obtained (the later is debatable). * It is mainly a setting used for emergency logging: if * something really goes wild, we can not do as indicated in * the log file, but we still log messages to the system * console. This is probably the best that can be done in * such a case. * read-only after startup, but modified during restart */ extern int errno; /* 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 */ } f_filter_type; EHostnameCmpMode eHostnameCmpMode; rsCStrObj *pCSHostnameComp; /* hostname to check */ rsCStrObj *pCSProgNameComp; /* tag to check or NULL, if not to be checked */ union { u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */ struct { rsCStrObj *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; rsCStrObj *pCSCompValue; /* value to "compare" against */ char isNegated; /* actually a boolean ;) */ } prop; } f_filterData; linkedList_t llActList; /* list of configured actions */ }; typedef struct filed selector_t; /* new type name */ /* 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; bDebugPrintTemplateList = 1; bDebugPrintCfSysLineHandlerList = 1; bDebugPrintModuleList = 1; bEscapeCCOnRcv = 1; /* default is to escape control characters */ bReduceRepeatMsgs = (logEveryMsg == 1) ? 0 : 1; bDropMalPTRMsgs = 0; if(pModDir != NULL) { free(pModDir); pModDir = NULL; } iMainMsgQueueSize = 10000; #if defined(SYSLOG_INET) && defined(USE_GSSAPI) if (gss_listen_service_name != NULL) { free(gss_listen_service_name); gss_listen_service_name = NULL; } #endif return RS_RET_OK; } /* support for defining allowed TCP and UDP senders. We use the same * structure to implement this (a linked list), but we define two different * list roots, one for UDP and one for TCP. * rgerhards, 2005-09-26 */ #ifdef SYSLOG_INET /* All of the five below are read-only after startup */ static struct AllowedSenders *pAllowedSenders_UDP = NULL; /* the roots of the allowed sender */ struct AllowedSenders *pAllowedSenders_TCP = NULL; /* lists. If NULL, all senders are ok! */ static struct AllowedSenders *pLastAllowedSenders_UDP = NULL; /* and now the pointers to the last */ static struct AllowedSenders *pLastAllowedSenders_TCP = NULL; /* element in the respective list */ #endif /* #ifdef SYSLOG_INET */ int option_DisallowWarning = 1; /* complain if message from disallowed sender is received */ /* hardcoded standard templates (used for defaults) */ static uchar template_TraditionalFormat[] = "\"%TIMESTAMP% %HOSTNAME% %syslogtag%%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_StdFwdFmt[] = "\"<%PRI%>%TIMESTAMP% %HOSTNAME% %syslogtag%%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 */ static void *singleWorker(); /* REMOVEME later 2005-10-24 */ /* Function prototypes. */ static char **crunch_list(char *list); static void printline(char *hname, char *msg, int iSource); static void logmsg(int pri, msg_t*, int flags); static rsRetVal fprintlog(action_t *pAction); static void reapchild(); static void debug_switch(); static rsRetVal cfline(uchar *line, selector_t **pfCurr); static int decode(uchar *name, struct code *codetab); static void sighup_handler(); static void die(int sig); static void freeSelectors(void); static rsRetVal processConfFile(uchar *pConfFile); static rsRetVal selectorAddList(selector_t *f); static void processImInternal(void); /* Code for handling allowed/disallowed senders */ #ifdef SYSLOG_INET static inline void MaskIP6 (struct in6_addr *addr, uint8_t bits) { register uint8_t i; assert (addr != NULL); assert (bits <= 128); i = bits/32; if (bits%32) addr->s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); for (; i < (sizeof addr->s6_addr32)/4; i++) addr->s6_addr32[i] = 0; } static inline void MaskIP4 (struct in_addr *addr, uint8_t bits) { assert (addr != NULL); assert (bits <=32 ); addr->s_addr &= htonl(0xffffffff << (32 - bits)); } #define SIN(sa) ((struct sockaddr_in *)(sa)) #define SIN6(sa) ((struct sockaddr_in6 *)(sa)) /* This function adds an allowed sender entry to the ACL linked list. * In any case, a single entry is added. If an error occurs, the * function does its error reporting itself. All validity checks * must already have been done by the caller. * This is a helper to AddAllowedSender(). * rgerhards, 2007-07-17 */ static rsRetVal AddAllowedSenderEntry(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, struct NetAddr *iAllow, uint8_t iSignificantBits) { struct AllowedSenders *pEntry = NULL; assert(ppRoot != NULL); assert(ppLast != NULL); assert(iAllow != NULL); if((pEntry = (struct AllowedSenders*) calloc(1, sizeof(struct AllowedSenders))) == NULL) { glblHadMemShortage = 1; return RS_RET_OUT_OF_MEMORY; /* no options left :( */ } memcpy(&(pEntry->allowedSender), iAllow, sizeof (struct NetAddr)); pEntry->pNext = NULL; pEntry->SignificantBits = iSignificantBits; /* enqueue */ if(*ppRoot == NULL) { *ppRoot = pEntry; } else { (*ppLast)->pNext = pEntry; } *ppLast = pEntry; return RS_RET_OK; } /* function to clear the allowed sender structure in cases where * it must be freed (occurs most often when HUPed. * TODO: reconsider recursive implementation */ static void clearAllowedSenders (struct AllowedSenders *pAllow) { if (pAllow != NULL) { if (pAllow->pNext != NULL) clearAllowedSenders (pAllow->pNext); else { if (F_ISSET(pAllow->allowedSender.flags, ADDR_NAME)) free (pAllow->allowedSender.addr.HostWildcard); else free (pAllow->allowedSender.addr.NetAddr); free (pAllow); } } } /* function to add an allowed sender to the allowed sender list. The * root of the list is caller-provided, so it can be used for all * supported lists. The caller must provide a pointer to the root, * as it eventually needs to be updated. Also, a pointer to the * pointer to the last element must be provided (to speed up adding * list elements). * rgerhards, 2005-09-26 * If a hostname is given there are possible multiple entries * added (all addresses from that host). */ static rsRetVal AddAllowedSender(struct AllowedSenders **ppRoot, struct AllowedSenders **ppLast, struct NetAddr *iAllow, uint8_t iSignificantBits) { DEFiRet; assert(ppRoot != NULL); assert(ppLast != NULL); assert(iAllow != NULL); if (!F_ISSET(iAllow->flags, ADDR_NAME)) { if(iSignificantBits == 0) /* we handle this seperatly just to provide a better * error message. */ logerror("You can not specify 0 bits of the netmask, this would " "match ALL systems. If you really intend to do that, " "remove all $AllowedSender directives."); switch (iAllow->addr.NetAddr->sa_family) { case AF_INET: if((iSignificantBits < 1) || (iSignificantBits > 32)) { logerrorInt("Invalid bit number in IPv4 address - adjusted to 32", (int)iSignificantBits); iSignificantBits = 32; } MaskIP4 (&(SIN(iAllow->addr.NetAddr)->sin_addr), iSignificantBits); break; case AF_INET6: if((iSignificantBits < 1) || (iSignificantBits > 128)) { logerrorInt("Invalid bit number in IPv6 address - adjusted to 128", iSignificantBits); iSignificantBits = 128; } MaskIP6 (&(SIN6(iAllow->addr.NetAddr)->sin6_addr), iSignificantBits); break; default: /* rgerhards, 2007-07-16: We have an internal program error in this * case. However, there is not much we can do against it right now. Of * course, we could abort, but that would probably cause more harm * than good. So we continue to run. We simply do not add this line - the * worst thing that happens is that one host will not be allowed to * log. */ logerrorInt("Internal error caused AllowedSender to be ignored, AF = %d", iAllow->addr.NetAddr->sa_family); return RS_RET_ERR; } /* OK, entry constructed, now lets add it to the ACL list */ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); } else { /* we need to process a hostname ACL */ if (DisableDNS) { logerror ("Ignoring hostname based ACLs because DNS is disabled."); return RS_RET_OK; } if (!strchr (iAllow->addr.HostWildcard, '*') && !strchr (iAllow->addr.HostWildcard, '?') && ACLDontResolve == 0) { /* single host - in this case, we pull its IP addresses from DNS * and add IP-based ACLs. */ struct addrinfo hints, *res, *restmp; struct NetAddr allowIP; memset (&hints, 0, sizeof (struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; # ifdef AI_ADDRCONFIG /* seems not to be present on all systems */ hints.ai_flags = AI_ADDRCONFIG; # endif if (getaddrinfo (iAllow->addr.HostWildcard, NULL, &hints, &res) != 0) { logerrorSz("DNS error: Can't resolve \"%s\"", iAllow->addr.HostWildcard); if (ACLAddHostnameOnFail) { logerrorSz("Adding hostname \"%s\" to ACL as a wildcard entry.", iAllow->addr.HostWildcard); return AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); } else { logerrorSz("Hostname \"%s\" WON\'T be added to ACL.", iAllow->addr.HostWildcard); return RS_RET_NOENTRY; } } for (restmp = res ; res != NULL ; res = res->ai_next) { switch (res->ai_family) { case AF_INET: /* add IPv4 */ iSignificantBits = 32; allowIP.flags = 0; if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { glblHadMemShortage = 1; return RS_RET_OUT_OF_MEMORY; } memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits)) != RS_RET_OK) return(iRet); break; case AF_INET6: /* IPv6 - but need to check if it is a v6-mapped IPv4 */ if(IN6_IS_ADDR_V4MAPPED (&SIN6(res->ai_addr)->sin6_addr)) { /* extract & add IPv4 */ iSignificantBits = 32; allowIP.flags = 0; if((allowIP.addr.NetAddr = malloc(sizeof(struct sockaddr_in))) == NULL) { glblHadMemShortage = 1; return RS_RET_OUT_OF_MEMORY; } SIN(allowIP.addr.NetAddr)->sin_family = AF_INET; #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN SIN(allowIP.addr.NetAddr)->sin_len = sizeof (struct sockaddr_in); #endif SIN(allowIP.addr.NetAddr)->sin_port = 0; memcpy(&(SIN(allowIP.addr.NetAddr)->sin_addr.s_addr), &(SIN6(res->ai_addr)->sin6_addr.s6_addr32[3]), sizeof (struct sockaddr_in)); if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits)) != RS_RET_OK) return(iRet); } else { /* finally add IPv6 */ iSignificantBits = 128; allowIP.flags = 0; if((allowIP.addr.NetAddr = malloc(res->ai_addrlen)) == NULL) { glblHadMemShortage = 1; return RS_RET_OUT_OF_MEMORY; } memcpy(allowIP.addr.NetAddr, res->ai_addr, res->ai_addrlen); if((iRet = AddAllowedSenderEntry(ppRoot, ppLast, &allowIP, iSignificantBits)) != RS_RET_OK) return(iRet); } break; } } freeaddrinfo (restmp); } else { /* wildcards in hostname - we need to add a text-based ACL. * For this, we already have everything ready and just need * to pass it along... */ iRet = AddAllowedSenderEntry(ppRoot, ppLast, iAllow, iSignificantBits); } } return iRet; } #endif /* #ifdef SYSLOG_INET */ #ifdef SYSLOG_INET /* Print an allowed sender list. The caller must tell us which one. * iListToPrint = 1 means UDP, 2 means TCP * rgerhards, 2005-09-27 */ static void PrintAllowedSenders(int iListToPrint) { struct AllowedSenders *pSender; uchar szIP[64]; assert((iListToPrint == 1) || (iListToPrint == 2)); printf("\nAllowed %s Senders:\n", (iListToPrint == 1) ? "UDP" : "TCP"); pSender = (iListToPrint == 1) ? pAllowedSenders_UDP : pAllowedSenders_TCP; if(pSender == NULL) { printf("\tNo restrictions set.\n"); } else { while(pSender != NULL) { if (F_ISSET(pSender->allowedSender.flags, ADDR_NAME)) printf ("\t%s\n", pSender->allowedSender.addr.HostWildcard); else { if(getnameinfo (pSender->allowedSender.addr.NetAddr, SALEN(pSender->allowedSender.addr.NetAddr), (char*)szIP, 64, NULL, 0, NI_NUMERICHOST) == 0) { printf ("\t%s/%u\n", szIP, pSender->SignificantBits); } else { /* getnameinfo() failed - but as this is only a * debug function, we simply spit out an error and do * not care much about it. */ dbgprintf("\tERROR in getnameinfo() - something may be wrong " "- ignored for now\n"); } } pSender = pSender->pNext; } } } /* compares a host to an allowed sender list entry. Handles all subleties * including IPv4/v6 as well as domain name wildcards. * This is a helper to isAllowedSender. As it is only called once, it is * declared inline. * Returns 0 if they do not match, something else otherwise. * contributed 1007-07-16 by mildew@gmail.com */ static inline int MaskCmp(struct NetAddr *pAllow, uint8_t bits, struct sockaddr *pFrom, const char *pszFromHost) { assert(pAllow != NULL); assert(pFrom != NULL); if(F_ISSET(pAllow->flags, ADDR_NAME)) { dbgprintf("MaskCmp: host=\"%s\"; pattern=\"%s\"\n", pszFromHost, pAllow->addr.HostWildcard); return(fnmatch(pAllow->addr.HostWildcard, pszFromHost, FNM_NOESCAPE|FNM_CASEFOLD) == 0); } else {/* We need to compare an IP address */ switch (pFrom->sa_family) { case AF_INET: if (AF_INET == pAllow->addr.NetAddr->sa_family) return(( SIN(pFrom)->sin_addr.s_addr & htonl(0xffffffff << (32 - bits)) ) == SIN(pAllow->addr.NetAddr)->sin_addr.s_addr); else return 0; break; case AF_INET6: switch (pAllow->addr.NetAddr->sa_family) { case AF_INET6: { struct in6_addr ip, net; register uint8_t i; memcpy (&ip, &(SIN6(pFrom))->sin6_addr, sizeof (struct in6_addr)); memcpy (&net, &(SIN6(pAllow->addr.NetAddr))->sin6_addr, sizeof (struct in6_addr)); i = bits/32; if (bits % 32) ip.s6_addr32[i++] &= htonl(0xffffffff << (32 - (bits % 32))); for (; i < (sizeof ip.s6_addr32)/4; i++) ip.s6_addr32[i] = 0; return (memcmp (ip.s6_addr, net.s6_addr, sizeof ip.s6_addr) == 0 && (SIN6(pAllow->addr.NetAddr)->sin6_scope_id != 0 ? SIN6(pFrom)->sin6_scope_id == SIN6(pAllow->addr.NetAddr)->sin6_scope_id : 1)); } case AF_INET: { struct in6_addr *ip6 = &(SIN6(pFrom))->sin6_addr; struct in_addr *net = &(SIN(pAllow->addr.NetAddr))->sin_addr; if ((ip6->s6_addr32[3] & (u_int32_t) htonl((0xffffffff << (32 - bits)))) == net->s_addr && #if BYTE_ORDER == LITTLE_ENDIAN (ip6->s6_addr32[2] == (u_int32_t)0xffff0000) && #else (ip6->s6_addr32[2] == (u_int32_t)0x0000ffff) && #endif (ip6->s6_addr32[1] == 0) && (ip6->s6_addr32[0] == 0)) return 1; else return 0; } default: /* Unsupported AF */ return 0; } default: /* Unsupported AF */ return 0; } } } /* check if a sender is allowed. The root of the the allowed sender. * list must be proveded by the caller. As such, this function can be * used to check both UDP and TCP allowed sender lists. * returns 1, if the sender is allowed, 0 otherwise. * rgerhards, 2005-09-26 */ int isAllowedSender(struct AllowedSenders *pAllowRoot, struct sockaddr *pFrom, const char *pszFromHost) { struct AllowedSenders *pAllow; assert(pFrom != NULL); if(pAllowRoot == NULL) return 1; /* checking disabled, everything is valid! */ /* now we loop through the list of allowed senders. As soon as * we find a match, we return back (indicating allowed). We loop * until we are out of allowed senders. If so, we fall through the * loop and the function's terminal return statement will indicate * that the sender is disallowed. */ for(pAllow = pAllowRoot ; pAllow != NULL ; pAllow = pAllow->pNext) { if (MaskCmp (&(pAllow->allowedSender), pAllow->SignificantBits, pFrom, pszFromHost)) return 1; } dbgprintf("%s is not an allowed sender\n", pszFromHost); return 0; } #endif /* #ifdef SYSLOG_INET */ /* code to free all sockets within a socket table. * A socket table is a descriptor table where the zero * element has the count of elements. This is used for * listening sockets. The socket table itself is also * freed. * A POINTER to this structure must be provided, thus * double indirection! * rgerhards, 2007-06-28 */ void freeAllSockets(int **socks) { assert(socks != NULL); assert(*socks != NULL); while(**socks) { dbgprintf("Closing socket %d.\n", (*socks)[**socks]); close((*socks)[**socks]); (**socks)--; } free(*socks); socks = NULL; } /******************************************************************* * BEGIN CODE-LIBLOGGING * ******************************************************************* * Code in this section is borrowed from liblogging. This is an * interim solution. Once liblogging is fully integrated, this is * to be removed (see http://www.monitorware.com/liblogging for * more details. 2004-11-16 rgerhards * * Please note that the orginal liblogging code is modified so that * it fits into the context of the current version of syslogd.c. * * DO NOT PUT ANY OTHER CODE IN THIS BEGIN ... END BLOCK!!!! */ /** * Parse a 32 bit integer number from a string. * * \param ppsz Pointer to the Pointer to the string being parsed. It * must be positioned at the first digit. Will be updated * so that on return it points to the first character AFTER * the integer parsed. * \retval The number parsed. */ static int srSLMGParseInt32(char** ppsz) { int i; i = 0; while(isdigit((int) **ppsz)) { i = i * 10 + **ppsz - '0'; ++(*ppsz); } return i; } /** * Parse a TIMESTAMP-3339. * updates the parse pointer position. */ static int srSLMGParseTIMESTAMP3339(struct syslogTime *pTime, char** ppszTS) { char *pszTS = *ppszTS; assert(pTime != NULL); assert(ppszTS != NULL); assert(pszTS != NULL); pTime->year = srSLMGParseInt32(&pszTS); /* We take the liberty to accept slightly malformed timestamps e.g. in * the format of 2003-9-1T1:0:0. This doesn't hurt on receiving. Of course, * with the current state of affairs, we would never run into this code * here because at postion 11, there is no "T" in such cases ;) */ if(*pszTS++ != '-') return FALSE; pTime->month = srSLMGParseInt32(&pszTS); if(pTime->month < 1 || pTime->month > 12) return FALSE; if(*pszTS++ != '-') return FALSE; pTime->day = srSLMGParseInt32(&pszTS); if(pTime->day < 1 || pTime->day > 31) return FALSE; if(*pszTS++ != 'T') return FALSE; pTime->hour = srSLMGParseInt32(&pszTS); if(pTime->hour < 0 || pTime->hour > 23) return FALSE; if(*pszTS++ != ':') return FALSE; pTime->minute = srSLMGParseInt32(&pszTS); if(pTime->minute < 0 || pTime->minute > 59) return FALSE; if(*pszTS++ != ':') return FALSE; pTime->second = srSLMGParseInt32(&pszTS); if(pTime->second < 0 || pTime->second > 60) return FALSE; /* Now let's see if we have secfrac */ if(*pszTS == '.') { char *pszStart = ++pszTS; pTime->secfrac = srSLMGParseInt32(&pszTS); pTime->secfracPrecision = (int) (pszTS - pszStart); } else { pTime->secfracPrecision = 0; pTime->secfrac = 0; } /* check the timezone */ if(*pszTS == 'Z') { pszTS++; /* eat Z */ pTime->OffsetMode = 'Z'; pTime->OffsetHour = 0; pTime->OffsetMinute = 0; } else if((*pszTS == '+') || (*pszTS == '-')) { pTime->OffsetMode = *pszTS; pszTS++; pTime->OffsetHour = srSLMGParseInt32(&pszTS); if(pTime->OffsetHour < 0 || pTime->OffsetHour > 23) return FALSE; if(*pszTS++ != ':') return FALSE; pTime->OffsetMinute = srSLMGParseInt32(&pszTS); if(pTime->OffsetMinute < 0 || pTime->OffsetMinute > 59) return FALSE; } else /* there MUST be TZ information */ return FALSE; /* OK, we actually have a 3339 timestamp, so let's indicated this */ if(*pszTS == ' ') ++pszTS; else return FALSE; /* update parse pointer */ *ppszTS = pszTS; return TRUE; } /** * Parse a TIMESTAMP-3164. * Returns TRUE on parse OK, FALSE on parse error. */ static int srSLMGParseTIMESTAMP3164(struct syslogTime *pTime, char* pszTS) { assert(pTime != NULL); assert(pszTS != NULL); getCurrTime(pTime); /* obtain the current year and UTC offsets! */ /* If we look at the month (Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec), * we may see the following character sequences occur: * * J(an/u(n/l)), Feb, Ma(r/y), A(pr/ug), Sep, Oct, Nov, Dec * * We will use this for parsing, as it probably is the * fastest way to parse it. * * 2005-07-18, well sometimes it pays to be a bit more verbose, even in C... * Fixed a bug that lead to invalid detection of the data. The issue was that * we had an if(++pszTS == 'x') inside of some of the consturcts below. However, * there were also some elseifs (doing the same ++), which than obviously did not * check the orginal character but the next one. Now removed the ++ and put it * into the statements below. Was a really nasty bug... I didn't detect it before * june, when it first manifested. This also lead to invalid parsing of the rest * of the message, as the time stamp was not detected to be correct. - rgerhards */ switch(*pszTS++) { case 'J': if(*pszTS == 'a') { ++pszTS; if(*pszTS == 'n') { ++pszTS; pTime->month = 1; } else return FALSE; } else if(*pszTS == 'u') { ++pszTS; if(*pszTS == 'n') { ++pszTS; pTime->month = 6; } else if(*pszTS == 'l') { ++pszTS; pTime->month = 7; } else return FALSE; } else return FALSE; break; case 'F': if(*pszTS == 'e') { ++pszTS; if(*pszTS == 'b') { ++pszTS; pTime->month = 2; } else return FALSE; } else return FALSE; break; case 'M': if(*pszTS == 'a') { ++pszTS; if(*pszTS == 'r') { ++pszTS; pTime->month = 3; } else if(*pszTS == 'y') { ++pszTS; pTime->month = 5; } else return FALSE; } else return FALSE; break; case 'A': if(*pszTS == 'p') { ++pszTS; if(*pszTS == 'r') { ++pszTS; pTime->month = 4; } else return FALSE; } else if(*pszTS == 'u') { ++pszTS; if(*pszTS == 'g') { ++pszTS; pTime->month = 8; } else return FALSE; } else return FALSE; break; case 'S': if(*pszTS == 'e') { ++pszTS; if(*pszTS == 'p') { ++pszTS; pTime->month = 9; } else return FALSE; } else return FALSE; break; case 'O': if(*pszTS == 'c') { ++pszTS; if(*pszTS == 't') { ++pszTS; pTime->month = 10; } else return FALSE; } else return FALSE; break; case 'N': if(*pszTS == 'o') { ++pszTS; if(*pszTS == 'v') { ++pszTS; pTime->month = 11; } else return FALSE; } else return FALSE; break; case 'D': if(*pszTS == 'e') { ++pszTS; if(*pszTS == 'c') { ++pszTS; pTime->month = 12; } else return FALSE; } else return FALSE; break; default: return FALSE; } /* done month */ if(*pszTS++ != ' ') return FALSE; /* we accept a slightly malformed timestamp when receiving. This is * we accept one-digit days */ if(*pszTS == ' ') ++pszTS; pTime->day = srSLMGParseInt32(&pszTS); if(pTime->day < 1 || pTime->day > 31) return FALSE; if(*pszTS++ != ' ') return FALSE; pTime->hour = srSLMGParseInt32(&pszTS); if(pTime->hour < 0 || pTime->hour > 23) return FALSE; if(*pszTS++ != ':') return FALSE; pTime->minute = srSLMGParseInt32(&pszTS); if(pTime->minute < 0 || pTime->minute > 59) return FALSE; if(*pszTS++ != ':') return FALSE; pTime->second = srSLMGParseInt32(&pszTS); if(pTime->second < 0 || pTime->second > 60) return FALSE; if(*pszTS++ != ':') /* OK, we actually have a 3164 timestamp, so let's indicate this * and fill the rest of the properties. */ pTime->timeType = 1; pTime->secfracPrecision = 0; pTime->secfrac = 0; return TRUE; } /******************************************************************* * END CODE-LIBLOGGING * *******************************************************************/ /** * Format a syslogTimestamp into format required by MySQL. * We are using the 14 digits format. For example 20041111122600 * is interpreted as '2004-11-11 12:26:00'. * The caller must provide the timestamp as well as a character * buffer that will receive the resulting string. The function * returns the size of the timestamp written in bytes (without * the string terminator). If 0 is returend, an error occured. */ int formatTimestampToMySQL(struct syslogTime *ts, char* pDst, size_t iLenDst) { /* currently we do not consider localtime/utc. This may later be * added. If so, I recommend using a property replacer option * and/or a global configuration option. However, we should wait * on user requests for this feature before doing anything. * rgerhards, 2007-06-26 */ assert(ts != NULL); assert(pDst != NULL); if (iLenDst < 15) /* we need at least 14 bytes 14 digits for timestamp + '\n' */ return(0); return(snprintf(pDst, iLenDst, "%4.4d%2.2d%2.2d%2.2d%2.2d%2.2d", ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); } int formatTimestampToPgSQL(struct syslogTime *ts, char *pDst, size_t iLenDst) { /* see note in formatTimestampToMySQL, applies here as well */ assert(ts != NULL); assert(pDst != NULL); if (iLenDst < 21) /* we need 20 bytes + '\n' */ return(0); return(snprintf(pDst, iLenDst, "%4.4d-%2.2d-%2.2d %2.2d:%2.2d:%2.2d", ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second)); } /** * Format a syslogTimestamp to a RFC3339 timestamp string (as * specified in syslog-protocol). * The caller must provide the timestamp as well as a character * buffer that will receive the resulting string. The function * returns the size of the timestamp written in bytes (without * the string terminator). If 0 is returend, an error occured. */ int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf) { int iRet; char szTZ[7]; /* buffer for TZ information */ assert(ts != NULL); assert(pBuf != NULL); if(iLenBuf < 20) return(0); /* we NEED at least 20 bytes */ /* do TZ information first, this is easier to take care of "Z" zone in rfc3339 */ if(ts->OffsetMode == 'Z') { szTZ[0] = 'Z'; szTZ[1] = '\0'; } else { snprintf(szTZ, sizeof(szTZ) / sizeof(char), "%c%2.2d:%2.2d", ts->OffsetMode, ts->OffsetHour, ts->OffsetMinute); } if(ts->secfracPrecision > 0) { /* we now need to include fractional seconds. While doing so, we must look at * the precision specified. For example, if we have millisec precision (3 digits), a * secFrac value of 12 is not equivalent to ".12" but ".012". Obviously, this * is a huge difference ;). To avoid this, we first create a format string with * the specific precision and *then* use that format string to do the actual * formating (mmmmhhh... kind of self-modifying code... ;)). */ char szFmtStr[64]; /* be careful: there is ONE actual %d in the format string below ;) */ snprintf(szFmtStr, sizeof(szFmtStr), "%%04d-%%02d-%%02dT%%02d:%%02d:%%02d.%%0%dd%%s", ts->secfracPrecision); iRet = snprintf(pBuf, iLenBuf, szFmtStr, ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second, ts->secfrac, szTZ); } else iRet = snprintf(pBuf, iLenBuf, "%4.4d-%2.2d-%2.2dT%2.2d:%2.2d:%2.2d%s", ts->year, ts->month, ts->day, ts->hour, ts->minute, ts->second, szTZ); return(iRet); } /** * Format a syslogTimestamp to a RFC3164 timestamp sring. * The caller must provide the timestamp as well as a character * buffer that will receive the resulting string. The function * returns the size of the timestamp written in bytes (without * the string termnator). If 0 is returend, an error occured. */ int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf) { static char* monthNames[13] = {"ERR", "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; assert(ts != NULL); assert(pBuf != NULL); if(iLenBuf < 16) return(0); /* we NEED 16 bytes */ return(snprintf(pBuf, iLenBuf, "%s %2d %2.2d:%2.2d:%2.2d", monthNames[ts->month], ts->day, ts->hour, ts->minute, ts->second )); } /** * Format a syslogTimestamp to a text format. * The caller must provide the timestamp as well as a character * buffer that will receive the resulting string. The function * returns the size of the timestamp written in bytes (without * the string termnator). If 0 is returend, an error occured. */ #if 0 /* This method is currently not called, be we like to preserve it */ static int formatTimestamp(struct syslogTime *ts, char* pBuf, size_t iLenBuf) { assert(ts != NULL); assert(pBuf != NULL); if(ts->timeType == 1) { return(formatTimestamp3164(ts, pBuf, iLenBuf)); } if(ts->timeType == 2) { return(formatTimestamp3339(ts, pBuf, iLenBuf)); } return(0); } #endif /** * Get the current date/time in the best resolution the operating * system has to offer (well, actually at most down to the milli- * second level. * * The date and time is returned in separate fields as this is * most portable and removes the need for additional structures * (but I have to admit it is somewhat "bulky";)). * * Obviously, all caller-provided pointers must not be NULL... */ void getCurrTime(struct syslogTime *t) { struct timeval tp; struct tm *tm; struct tm tmBuf; long lBias; assert(t != NULL); gettimeofday(&tp, NULL); tm = localtime_r((time_t*) &(tp.tv_sec), &tmBuf); t->year = tm->tm_year + 1900; t->month = tm->tm_mon + 1; t->day = tm->tm_mday; t->hour = tm->tm_hour; t->minute = tm->tm_min; t->second = tm->tm_sec; t->secfrac = tp.tv_usec; t->secfracPrecision = 6; # if __sun /* Solaris uses a different method of exporting the time zone. * It is UTC - localtime, which is the opposite sign of mins east of GMT. */ lBias = -(daylight ? altzone : timezone); # else lBias = tm->tm_gmtoff; # endif if(lBias < 0) { t->OffsetMode = '-'; lBias *= -1; } else t->OffsetMode = '+'; t->OffsetHour = lBias / 3600; t->OffsetMinute = lBias % 3600; } /* rgerhards 2004-11-09: end of helper routines. On to the * "real" code ;) */ static int usage(void) { fprintf(stderr, "usage: rsyslogd [-46AdhqQvw] [-l hostlist] [-m markinterval] [-n] [-p path]\n" \ " [-s domainlist] [-r[port]] [-tport[,max-sessions]] [-f conffile] [-i pidfile] [-x]\n"); exit(1); /* "good" exit - done to terminate usage() */ } #ifdef SYSLOG_UNIXAF static int create_unix_socket(const char *path) { struct sockaddr_un sunx; int fd; char line[MAXLINE +1]; if (path[0] == '\0') return -1; (void) unlink(path); memset(&sunx, 0, sizeof(sunx)); sunx.sun_family = AF_UNIX; (void) strncpy(sunx.sun_path, path, sizeof(sunx.sun_path)); fd = socket(AF_UNIX, SOCK_DGRAM, 0); if (fd < 0 || bind(fd, (struct sockaddr *) &sunx, SUN_LEN(&sunx)) < 0 || chmod(path, 0666) < 0) { snprintf(line, sizeof(line), "cannot create %s", path); logerror(line); dbgprintf("cannot create %s (%d).\n", path, errno); close(fd); return -1; } return fd; } #endif #ifdef SYSLOG_INET /* closes the UDP listen sockets (if they exist) and frees * all dynamically assigned memory. */ static void closeUDPListenSockets() { register int i; if(finet != NULL) { for (i = 0; i < *finet; i++) close(finet[i+1]); free(finet); finet = NULL; } } /* creates the UDP listen sockets */ static int *create_udp_socket() { struct addrinfo hints, *res, *r; int error, maxs, *s, *socks, on = 1; int sockflags; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; hints.ai_family = family; hints.ai_socktype = SOCK_DGRAM; error = getaddrinfo(NULL, LogPort, &hints, &res); if(error) { logerror((char*) gai_strerror(error)); logerror("UDP message reception disabled due to error logged in last message.\n"); return NULL; } /* Count max number of sockets we may open */ for (maxs = 0, r = res; r != NULL ; r = r->ai_next, maxs++) /* EMPTY */; socks = malloc((maxs+1) * sizeof(int)); if (socks == NULL) { logerror("couldn't allocate memory for UDP sockets, suspending UDP message reception"); freeaddrinfo(res); return NULL; } *socks = 0; /* num of sockets counter at start of array */ s = socks + 1; for (r = res; r != NULL ; r = r->ai_next) { *s = socket(r->ai_family, r->ai_socktype, r->ai_protocol); if (*s < 0) { if(!(r->ai_family == PF_INET6 && errno == EAFNOSUPPORT)) logerror("create_udp_socket(), socket"); /* it is debatable if PF_INET with EAFNOSUPPORT should * also be ignored... */ continue; } # ifdef IPV6_V6ONLY if (r->ai_family == AF_INET6) { int ion = 1; if (setsockopt(*s, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&ion, sizeof (ion)) < 0) { logerror("setsockopt"); close(*s); *s = -1; continue; } } # endif /* if we have an error, we "just" suspend that socket. Eventually * other sockets will work. At the end of this function, we check * if we managed to open at least one socket. If not, we'll write * a "inet suspended" message and declare failure. Else we use * what we could obtain. * rgerhards, 2007-06-22 */ if (setsockopt(*s, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on)) < 0 ) { logerror("setsockopt(REUSEADDR)"); close(*s); *s = -1; continue; } /* We need to enable BSD compatibility. Otherwise an attacker * could flood our log files by sending us tons of ICMP errors. */ #ifndef BSD if (should_use_so_bsdcompat()) { if (setsockopt(*s, SOL_SOCKET, SO_BSDCOMPAT, (char *) &on, sizeof(on)) < 0) { logerror("setsockopt(BSDCOMPAT)"); close(*s); *s = -1; continue; } } #endif /* We must not block on the network socket, in case a packet * gets lost between select and recv, otherwise the process * will stall until the timeout, and other processes trying to * log will also stall. * Patch vom Colin Phipps to the original * sysklogd source. Applied to rsyslogd on 2005-10-19. */ if ((sockflags = fcntl(*s, F_GETFL)) != -1) { sockflags |= O_NONBLOCK; /* SETFL could fail too, so get it caught by the subsequent * error check. */ sockflags = fcntl(*s, F_SETFL, sockflags); } if (sockflags == -1) { logerror("fcntl(O_NONBLOCK)"); close(*s); *s = -1; continue; } /* rgerhards, 2007-06-22: if we run on a kernel that does not support * the IPV6_V6ONLY socket option, we need to use a work-around. On such * systems the IPv6 socket does also accept IPv4 sockets. So an IPv4 * socket can not listen on the same port as an IPv6 socket. The only * workaround is to ignore the "socket in use" error. This is what we * do if we have to. */ if( (bind(*s, r->ai_addr, r->ai_addrlen) < 0) # ifndef IPV6_V6ONLY && (errno != EADDRINUSE) # endif ) { logerror("bind"); close(*s); *s = -1; continue; } (*socks)++; s++; } if(res != NULL) freeaddrinfo(res); if(Debug && *socks != maxs) dbgprintf("We could initialize %d UDP listen sockets out of %d we received " "- this may or may not be an error indication.\n", *socks, maxs); if(*socks == 0) { logerror("No UDP listen socket could successfully be initialized, " "message reception via UDP disabled.\n"); /* we do NOT need to free any sockets, because there were none... */ free(socks); return(NULL); } return(socks); } #endif /* function to destruct a selector_t object * rgerhards, 2007-08-01 */ static 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); } llDestroy(&pThis->llActList); free(pThis); return RS_RET_OK; } /* function to construct a selector_t object * rgerhards, 2007-08-01 */ static rsRetVal selectorConstruct(selector_t **ppThis) { DEFiRet; selector_t *pThis; assert(ppThis != NULL); if((pThis = (selector_t*) calloc(1, sizeof(selector_t))) == NULL) { glblHadMemShortage = 1; 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; return iRet; } /* 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) { (void) ioctl(i, (int) TIOCNOTTY, (char *)0); (void) close(i); } } } #endif /* 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 */ void printchopped(char *hname, char *msg, int len, int fd, int bParseHost) { register int iMsg; char *pMsg; char *pData; char *pEnd; char tmpline[MAXLINE + 1]; # ifdef USE_NETZIP char deflateBuf[MAXLINE + 1]; uLongf iLenDefBuf; # endif assert(hname != NULL); assert(msg != NULL); assert(len >= 0); dbgprintf("Message length: %d, File descriptor: %d.\n", len, fd); /* 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 %d, old %d.\n", ret, 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) { logerrorInt("Uncompression of a message failed with return code %d " "- enable debug logging if you need further information. " "Message ignored.", ret); return; /* 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') { logerror("Received a compressed message, but rsyslogd does not have compression " "support enabled. The message will be ignored."); return; } # 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, tmpline, bParseHost); } 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"); } return; /* 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, tmpline, bParseHost); return; } /* 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 */ void printline(char *hname, char *msg, int bParseHost) { register char *p; int pri; msg_t *pMsg; /* Now it is time to create the message object (rgerhards) */ if((pMsg = MsgConstruct()) == NULL){ /* rgerhards, 2007-06-21: if we can not get memory, we discard this * message but continue to run (in the hope that things improve) */ glblHadMemShortage = 1; dbgprintf("Memory shortage in printline(): Could not construct Msg object.\n"); return; } MsgSetRawMsg(pMsg, 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, hname); MsgSetRcvFrom(pMsg, hname); /* 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, p) != 0) return; logmsg(pri, pMsg, SYNC_FILE); /* rgerhards 2004-11-11: * we are done with the message object. If it still is * stored somewhere, we can call discard anyhow. This * is handled via the reference count - see description * of msg_t for details. */ MsgDestruct(pMsg); return; } /* 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. */ void logmsgInternal(int pri, char *msg, int flags) { msg_t *pMsg; dbgprintf("logmsgInternal: msg passed: '%s'\n", msg); if((pMsg = MsgConstruct()) == NULL){ /* rgerhards 2004-11-09: calling panic might not be the * brightest idea - however, it is the best I currently have * (think a bit more about this). * rgehards, 2007-06-21: I have now thought a bit more about * it. If we are so low on memory, there is few we can do. calling * panic so far only write a debug line - this is seomthing we keep. * Other than that, however, we ignore the error and hope that * memory shortage will be resolved while we continue to run. In any * case, there is no valid point in aborting the syslogd for this * reason - that would be counter-productive. So we ignore the * to be logged message. */ glblHadMemShortage = 1; dbgprintf("Memory shortage in logmsgInternal: could not construct Msg object.\n"); return; } MsgSetUxTradMsg(pMsg, msg); MsgSetRawMsg(pMsg, msg); MsgSetHOSTNAME(pMsg, LocalHostName); MsgSetTAG(pMsg, "rsyslogd:"); pMsg->iFacility = LOG_FAC(pri); pMsg->iSeverity = LOG_PRI(pri); pMsg->bParseHOSTNAME = 0; getCurrTime(&(pMsg->tTIMESTAMP)); /* use the current time! */ flags |= INTERNAL_MSG; if(bRunningMultithreaded == 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(pri, pMsg, flags); MsgDestruct(pMsg); } } /* 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 */ int shouldProcessThisMessage(selector_t *f, msg_t *pMsg) { unsigned short pbMustBeFreed; char *pszPropVal; int iRet = 0; 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)); return 0; } } 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)); return 0; } } 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)); return 0; } } /* 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<iSeverity)) == 0) ) iRet = 0; else iRet = 1; } 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) iRet = 1; break; case FIOP_ISEQUAL: if(rsCStrSzStrCmp(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal, strlen(pszPropVal)) == 0) iRet = 1; /* process message! */ break; case FIOP_STARTSWITH: if(rsCStrSzStrStartsWithCStr(f->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal, strlen(pszPropVal)) == 0) iRet = 1; /* process message! */ break; case FIOP_REGEX: if(rsCStrSzStrMatchRegex(f->f_filterData.prop.pCSCompValue, (unsigned char*) pszPropVal) == 0) iRet = 1; break; default: /* here, it handles NOP (for performance reasons) */ assert(f->f_filterData.prop.operation == FIOP_NOP); iRet = 1; /* as good as any other default ;) */ break; } /* now check if the value must be negated */ if(f->f_filterData.prop.isNegated) iRet = (iRet == 1) ? 0 : 1; if(Debug) { printf("Filter: check for property '%s' (value '%s') ", rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName), pszPropVal); if(f->f_filterData.prop.isNegated) printf("NOT "); printf("%s '%s': %s\n", getFIOPName(f->f_filterData.prop.operation), rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue), iRet ? "TRUE" : "FALSE"); } /* cleanup */ if(pbMustBeFreed) free(pszPropVal); } return(iRet); } /* doEmergencyLoggin() * ... does exactly do that. It logs messages when the subsystem has not yet * been initialized. This almost always happens during initial startup or * during HUPing. -- rgerhards, 2007-07-25 * rgerhards, 2007-08-03: as of now, this can normally no longer happen. All * startup messages are now buffered until the system is ready to run. I leave * this minimal implementation here in in the very remote case that it might * be needed in the future or due to a program bug. Do *not* excpect this * code to be called. */ static void doEmergencyLogging(msg_t *pMsg) { assert(pMsg != NULL); fprintf(stderr, "rsyslog: %s\n", pMsg->pszMSG); } /* call the configured action. Does all necessary housekeeping. * rgerhards, 2007-08-01 */ static rsRetVal callAction(msg_t *pMsg, action_t *pAction) { DEFiRet; assert(pMsg != NULL); assert(pAction != NULL); /* Make sure nodbody else modifies/uses this action object. Right now, this * is important because of "message repeated n times" processing, later it will * become important when we (possibly) have multiple worker threads. * rgerhards, 2007-12-11 */ LockObj(pAction); /* first, we need to check if this is a disabled * entry. If so, we must not further process it. * rgerhards 2005-09-26 * In the future, disabled modules may be re-probed from time * to time. They are in a perfectly legal state, except that the * doAction method indicated that it wanted to be disabled - but * we do not consider this is a solution for eternity... So we * should check from time to time if affairs have improved. * rgerhards, 2007-07-24 */ if(pAction->bEnabled == 0) { ABORT_FINALIZE(RS_RET_OK); } if(actionIsSuspended(pAction)) { CHKiRet(actionTryResume(pAction)); } /* don't output marks to recently written files */ if ((pMsg->msgFlags & MARK) && (time(NULL) - pAction->f_time) < MarkInterval / 2) { ABORT_FINALIZE(RS_RET_OK); } /* suppress duplicate messages */ if ((pAction->f_ReduceRepeated == 1) && pAction->f_pMsg != NULL && (pMsg->msgFlags & MARK) == 0 && getMSGLen(pMsg) == getMSGLen(pAction->f_pMsg) && !strcmp(getMSG(pMsg), getMSG(pAction->f_pMsg)) && !strcmp(getHOSTNAME(pMsg), getHOSTNAME(pAction->f_pMsg)) && !strcmp(getPROCID(pMsg), getPROCID(pAction->f_pMsg)) && !strcmp(getAPPNAME(pMsg), getAPPNAME(pAction->f_pMsg))) { pAction->f_prevcount++; dbgprintf("msg repeated %d times, %ld sec of %d.\n", pAction->f_prevcount, time(NULL) - pAction->f_time, repeatinterval[pAction->f_repeatcount]); /* use current message, so we have the new timestamp (means we need to discard previous one) */ MsgDestruct(pAction->f_pMsg); pAction->f_pMsg = MsgAddRef(pMsg); /* If domark would have logged this by now, flush it now (so we don't hold * isolated messages), but back off so we'll flush less often in the future. */ if(time(NULL) > REPEATTIME(pAction)) { iRet = fprintlog(pAction); BACKOFF(pAction); } } else { /* new message, save it */ /* first check if we have a previous message stored * if so, emit and then discard it first */ if(pAction->f_pMsg != NULL) { if(pAction->f_prevcount > 0) fprintlog(pAction); /* we do not care about iRet above - I think it's right but if we have * some troubles, you know where to look at ;) -- rgerhards, 2007-08-01 */ MsgDestruct(pAction->f_pMsg); } pAction->f_pMsg = MsgAddRef(pMsg); /* call the output driver */ iRet = fprintlog(pAction); } finalize_it: UnlockObj(pAction); return iRet; } /* 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 = callAction(pDoActData->pMsg, pAction); 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: return iRet; } /* Process (consume) a received message. Calls the actions configured. * Can some time later run in its own thread. To aid this, the calling * parameters should be reduced to just pMsg. * See comment dated 2005-10-13 in logmsg() on multithreading. * rgerhards, 2005-10-13 */ static void processMsg(msg_t *pMsg) { selector_t *f; int bContinue; processMsgDoActions_t DoActData; assert(pMsg != NULL); /* log the message to the particular outputs */ if (!Initialized) { doEmergencyLogging(pMsg); return; } bContinue = 1; for (f = Files; f != NULL && bContinue ; f = f->f_next) { /* This is actually the "filter logic". Looks like we need * to improve it a little for complex selector line conditions. We * won't do that for now, but at least we now know where * to look at. * 2005-09-09 rgerhards * ok, we are now ready to move to something more advanced. Because * of this, I am moving the actual decision code to outside this function. * 2005-09-19 rgerhards */ if(!shouldProcessThisMessage(f, pMsg)) { 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; } } /* Start Threading-Related code */ /* shuts down the worker process. The worker will first finish * with the message queue. Control returns, when done. * This function is intended to be called during syslogd shutdown * AND restart (init()!). * rgerhards, 2005-10-25 */ static void stopWorker(void) { if(bRunningMultithreaded) { /* we could run single-threaded if there was an error * during startup. Then, we obviously do not need to * do anything to stop the worker ;) */ dbgprintf("Initiating worker thread shutdown sequence...\n"); /* We are now done with all messages, so we need to wake up the * worker thread and then wait for it to finish. */ bGlblDone = 1; /* It's actually not "not empty" below but awaking the worker. The worker * then finds out that it shall terminate and does so. */ pthread_cond_signal(pMsgQueue->notEmpty); pthread_join(thrdWorker, NULL); bRunningMultithreaded = 0; dbgprintf("Worker thread terminated.\n"); } } /* starts the worker thread. It must be made sure that the queue is * already existing and the worker is NOT already running. * rgerhards 2005-10-25 */ static void startWorker(void) { int i; if(pMsgQueue != NULL) { bGlblDone = 0; /* we are NOT done (else worker would immediately terminate) */ i = pthread_create(&thrdWorker, NULL, singleWorker, NULL); dbgprintf("Worker thread started with state %d.\n", i); bRunningMultithreaded = 1; } else { dbgprintf("message queue not existing, remaining single-threaded.\n"); } } /* The worker thread (so far, we have dual-threading, so only one * worker thread. Having more than one worker requires considerable * additional code review in regard to thread-safety. */ static void *singleWorker() { msgQueue *fifo = pMsgQueue; msg_t *pMsg; sigset_t sigSet; assert(fifo != NULL); sigfillset(&sigSet); pthread_sigmask(SIG_BLOCK, &sigSet, NULL); while(!bGlblDone || !fifo->empty) { pthread_mutex_lock(fifo->mut); while (fifo->empty && !bGlblDone) { dbgprintf("singleWorker: queue EMPTY, waiting for next message.\n"); pthread_cond_wait (fifo->notEmpty, fifo->mut); } if(!fifo->empty) { /* dequeue element (still protected from mutex) */ queueDel(fifo, (void*) &pMsg); assert(pMsg != NULL); pthread_mutex_unlock(fifo->mut); pthread_cond_signal (fifo->notFull); /* do actual processing (the lengthy part, runs in parallel) */ dbgprintf("Lone worker is running...\n"); processMsg(pMsg); MsgDestruct(pMsg); /* If you need a delay for testing, here do a */ /* sleep(1); */ } else { /* the mutex must be unlocked in any case (important for termination) */ pthread_mutex_unlock(fifo->mut); } if(debugging_on && bGlblDone && !fifo->empty) dbgprintf("Worker does not yet terminate because it still has messages to process.\n"); } dbgprintf("Worker thread terminates\n"); pthread_exit(0); } /* END threads-related code */ /* This method enqueues a message into the the message buffer. It also * the worker thread, so that the message will be processed. * See comment dated 2005-10-13 in logmsg() on multithreading. * rgerhards, 2005-10-24 */ static void enqueueMsg(msg_t *pMsg) { int iRet; msgQueue *fifo = pMsgQueue; struct timespec t; assert(pMsg != NULL); if(bRunningMultithreaded == 0) { /* multi-threading is not yet initialized, happens e.g. * during startup and restart. rgerhards, 2005-10-25 */ dbgprintf("enqueueMsg: not yet running on multiple threads\n"); processMsg(pMsg); } else { /* "normal" mode, threading initialized */ pthread_mutex_lock(fifo->mut); while (fifo->full) { dbgprintf("enqueueMsg: queue FULL.\n"); clock_gettime (CLOCK_REALTIME, &t); t.tv_sec += 2; if(pthread_cond_timedwait (fifo->notFull, fifo->mut, &t) != 0) { dbgprintf("enqueueMsg: cond timeout, dropping message!\n"); goto unlock; } } queueAdd(fifo, MsgAddRef(pMsg)); unlock: /* now activate the worker thread */ pthread_mutex_unlock(fifo->mut); iRet = pthread_cond_signal(fifo->notEmpty); dbgprintf("EnqueueMsg signaled condition (%d)\n", iRet); } } /* 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: * * VERSION SP TIMESTAMP SP HOSTNAME SP APP-NAME SP PROCID SP MSGID SP [SD-ID]s SP MSG * * 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(srSLMGParseTIMESTAMP3339(&(pMsg->tTIMESTAMP), &p2parse) == FALSE) { dbgprintf("no TIMESTAMP detected!\n"); bContParse = 0; flags |= ADDDATE; } if (flags & ADDDATE) { 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); 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; rsCStrObj *pStrB; int iCnt; int bTAGCharDetected; assert(pMsg != NULL); assert(pMsg->pszUxTradMsg != NULL); p2parse = (char*) pMsg->pszUxTradMsg; /* Check to see if msg contains a timestamp */ if(srSLMGParseTIMESTAMP3164(&(pMsg->tTIMESTAMP), p2parse) == TRUE) p2parse += 16; 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) { 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; ++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). Finally, we allow only up to 32 characters for * TAG, as it is specified in RFC 3164. */ /* 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((pStrB = rsCStrConstruct()) == NULL) return 1; rsCStrSetAllocIncrement(pStrB, 33); pWork = pBuf; iCnt = 0; while(*p2parse && *p2parse != ':' && *p2parse != ' ' && iCnt < 32) { 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 */ } /* * 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(int pri, msg_t *pMsg, int flags) { char *msg; char PRItext[20]; assert(pMsg != NULL); assert(pMsg->pszUxTradMsg != NULL); msg = (char*) pMsg->pszUxTradMsg; dbgprintf("logmsg: %s, flags %x, from '%s', msg %s\n", textpri(PRItext, sizeof(PRItext) / sizeof(char), pri), flags, 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) return; } else { /* we have legacy syslog */ dbgprintf("Message has legacy syslog format.\n"); setProtocolVersion(pMsg, 0); if(parseLegacySyslogMsg(pMsg, flags) == 1) return; } /* ---------------------- END PARSING ---------------- */ /* rgerhards, 2005-10-13: if we consider going multi-threaded, this * is probably the best point to split between a producer and a consumer * thread. In general, with the first multi-threaded approach, we should * NOT try to do more than have a single producer and consumer, at least * if both are from the current code base. The issue is that this code * was definitely not written with reentrancy in mind and uses a lot of * global variables. So it is very dangerous to simply go ahead and multi * thread it. However, I think there is a clear distinction between * producer (where data is received) and consumer (where the actions are). * It should be fairly safe to create a single thread for each and run them * concurrently, thightly coupled via an in-memory queue. Even with this * limited multithraeding, benefits are immediate: the lengthy actions * (database writes!) are de-coupled from the receivers, what should result * in less likely message loss (loss due to receiver overrun). It also allows * us to utilize 2-cpu systems, which will soon be common given the current * advances in multicore CPU hardware. So this is well worth trying. * Another plus of this two-thread-approach would be that it can easily be configured, * so if there are compatibility issues with the threading libs, we could simply * disable it (as a makefile feature). * There is one important thing to keep in mind when doing this basic * multithreading. The syslog/tcp message forwarder manipulates a structutre * that is used by the main thread, which actually sends the data. This * structure must be guarded by a mutex, else we will have race conditions and * some very bad things could happen. * * Additional consumer threads might be added relatively easy for new receivers, * e.g. if we decide to move RFC 3195 via liblogging natively into rsyslogd. * * To aid this functionality, I am moving the rest of the code (the actual * consumer) to its own method, now called "processMsg()". * * rgerhards, 2005-10-25: as of now, the dual-threading code is now in place. * It is an optional feature and even when enabled, rsyslogd will run single-threaded * if it gets any errors during thread creation. */ pMsg->msgFlags = flags; enqueueMsg(pMsg); } /* rgerhards 2004-11-09: fprintlog() is the actual driver for * the output channel. It receives the channel description (f) as * well as the message and outputs them according to the channel * semantics. The message is typically already contained in the * channel save buffer (f->f_prevline). This is not only the case * when a message was already repeated but also when a new message * arrived. * rgerhards 2007-08-01: interface changed to use action_t * rgerhards, 2007-12-11: please note: THIS METHOD MUST ONLY BE * CALLED AFTER THE CALLER HAS LOCKED THE pAction OBJECT! We do * not do this here. Failing to do so results in all kinds of * "interesting" problems! */ rsRetVal fprintlog(action_t *pAction) { msg_t *pMsgSave; /* to save current message pointer, necessary to restore it in case it needs to be updated (e.g. repeated msgs) */ DEFiRet; int i; pMsgSave = NULL; /* indicate message poiner not saved */ /* first check if this is a regular message or the repeation of * a previous message. If so, we need to change the message text * to "last message repeated n times" and then go ahead and write * it. Please note that we can not modify the message object, because * that would update it in other selectors as well. As such, we first * need to create a local copy of the message, which we than can update. * rgerhards, 2007-07-10 */ if(pAction->f_prevcount > 1) { msg_t *pMsg; uchar szRepMsg[64]; snprintf((char*)szRepMsg, sizeof(szRepMsg), "last message repeated %d times", pAction->f_prevcount); if((pMsg = MsgDup(pAction->f_pMsg)) == NULL) { /* it failed - nothing we can do against it... */ dbgprintf("Message duplication failed, dropping repeat message.\n"); return RS_RET_ERR; /* This return is OK. The finalizer frees strings, which are not * yet allocated. So we can not use the finalizer. */ } /* We now need to update the other message properties. * ... RAWMSG is a problem ... Please note that digital * signatures inside the message are also invalidated. */ getCurrTime(&(pMsg->tRcvdAt)); getCurrTime(&(pMsg->tTIMESTAMP)); MsgSetMSG(pMsg, (char*)szRepMsg); MsgSetRawMsg(pMsg, (char*)szRepMsg); pMsgSave = pAction->f_pMsg; /* save message pointer for later restoration */ pAction->f_pMsg = pMsg; /* use the new msg (pointer will be restored below) */ } dbgprintf("Called fprintlog, logging to %s", modGetStateName(pAction->pMod)); time(&pAction->f_time); /* we need this for message repeation processing */ /* When we reach this point, we have a valid, non-disabled action. * So let's execute it. -- rgerhards, 2007-07-24 */ /* here we must loop to process all requested strings */ for(i = 0 ; i < pAction->iNumTpls ; ++i) { CHKiRet(tplToString(pAction->ppTpl[i], pAction->f_pMsg, &pAction->ppMsgs[i])); } /* call configured action */ iRet = pAction->pMod->mod.om.doAction(pAction->ppMsgs, pAction->f_pMsg->msgFlags, pAction->pModData); if(iRet == RS_RET_DISABLE_ACTION) { dbgprintf("Action requested to be disabled, done that.\n"); pAction->bEnabled = 0; /* that's it... */ } if(iRet == RS_RET_SUSPENDED) { dbgprintf("Action requested to be suspended, done that.\n"); actionSuspend(pAction); } if(iRet == RS_RET_OK) pAction->f_prevcount = 0; /* message processed, so we start a new cycle */ finalize_it: /* cleanup */ for(i = 0 ; i < pAction->iNumTpls ; ++i) { if(pAction->ppMsgs[i] != NULL) { free(pAction->ppMsgs[i]); pAction->ppMsgs[i] = NULL; } } if(pMsgSave != NULL) { /* we had saved the original message pointer. That was * done because we needed to create a temporary one * (most often for "message repeated n time" handling). If so, * we need to restore the original one now, so that procesing * can continue as normal. We also need to discard the temporary * one, as we do not like memory leaks ;) Please note that the original * message object will be discarded by our callers, so this is nothing * of our business. rgerhards, 2007-07-10 */ MsgDestruct(pAction->f_pMsg); pAction->f_pMsg = pMsgSave; /* restore it */ } return iRet; } 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 domark to flush the individual action links via llExecFunc * rgerhards, 2007-08-02 */ DEFFUNC_llExecFunc(domarkActions) { action_t *pAction = (action_t*) pData; assert(pAction != NULL); LockObj(pAction); if (pAction->f_prevcount && time(NULL) >= REPEATTIME(pAction)) { dbgprintf("flush %s: repeated %d times, %d sec.\n", modGetStateName(pAction->pMod), pAction->f_prevcount, repeatinterval[pAction->f_repeatcount]); fprintlog(pAction); BACKOFF(pAction); } UnlockObj(pAction); return RS_RET_OK; /* we ignore errors, we can not do anything either way */ } /* This method writes mark messages and - some time later - flushes reapeat * messages. * This method was initially called by an alarm handler. As such, it could potentially * have race-conditons. For details, see * http://lkml.org/lkml/2005/3/26/37 * http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=301511 * I have now changed it so that the alarm handler only sets a global variable, telling * the main thread that it must do mark processing. So domark() is now called from the * main thread itself, which is the only thing to make sure rsyslogd will not do * strange things. The way it originally was seemed to work because mark occurs very * seldom. However, the code called was anything else but reentrant, so it was like * russian roulette. - rgerhards, 2005-10-20 * rgerhards, 2007-12-11: ... and it still is, if running multithreaded. Because in this * case we run concurrently to the actions... I have now fixed that by using synchronization * macros. */ static void domark(void) { register selector_t *f; if (MarkInterval > 0) { /* Commented out, will be replaced by immark - just leave it in place for the * time being so we know what happens. Remove once immark is done. * rgerhards, 2007-12-12 */ #if !IMMARK MarkSeq += TIMERINTVL; if (MarkSeq >= MarkInterval) { logmsgInternal(LOG_INFO, "-- MARK --", ADDDATE|MARK); MarkSeq = 0; } #endif /* see if we need to flush any "message repeated n times"... * Note that this interferes with objects running on another thread. * We are using appropriate locking inside the function to handle that. */ for (f = Files; f != NULL ; f = f->f_next) { llExecFunc(&f->llActList, domarkActions, NULL); } } } /* This is the alarm handler setting the global variable for * domark request. See domark() comments for further details. * rgerhards, 2005-10-20 */ static void domarkAlarmHdlr() { struct sigaction sigAct; bRequestDoMark = 1; /* request alarm */ memset(&sigAct, 0, sizeof (sigAct)); sigemptyset(&sigAct.sa_mask); sigAct.sa_handler = domarkAlarmHdlr; sigaction(SIGALRM, &sigAct, NULL); (void) alarm(TIMERINTVL); } static void debug_switch() { struct sigaction sigAct; dbgprintf("Switching debugging_on to %s\n", (debugging_on == 0) ? "true" : "false"); debugging_on = (debugging_on == 0) ? 1 : 0; memset(&sigAct, 0, sizeof (sigAct)); sigemptyset(&sigAct.sa_mask); sigAct.sa_handler = debug_switch; sigaction(SIGUSR1, &sigAct, NULL); } /* * Add a string to error message and send it to logerror() * The error message is passed to snprintf() and must be * correctly formatted for it (containing a single %s param). * rgerhards 2005-09-19 */ void logerrorSz(char *type, char *errMsg) { char buf[1024]; snprintf(buf, sizeof(buf), type, errMsg); buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ logerror(buf); return; } /* * Add an integer to error message and send it to logerror() * The error message is passed to snprintf() and must be * correctly formatted for it (containing a single %d param). * rgerhards 2005-09-19 */ void logerrorInt(char *type, int errCode) { char buf[1024]; snprintf(buf, sizeof(buf), type, errCode); buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ logerror(buf); return; } /* Print syslogd errors some place. */ void logerror(char *type) { char buf[1024]; char errStr[1024]; dbgprintf("Called logerr, msg: %s\n", type); if (errno == 0) snprintf(buf, sizeof(buf), "%s", type); else { strerror_r(errno, errStr, sizeof(errStr)); snprintf(buf, sizeof(buf), "%s: %s", type, errStr); } buf[sizeof(buf)/sizeof(char) - 1] = '\0'; /* just to be on the safe side... */ errno = 0; logmsgInternal(LOG_SYSLOG|LOG_ERR, buf, ADDDATE); return; } /* 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) { dbgprintf("DoDie called.\n"); bFinished = sig; } /* die() is called when the program shall end. This typically only occurs * during sigterm or during the initialization. If you search for places where * it is called, search for "die", not "die(", because the later will not find * setting of signal handlers! 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]; int i; if (sig) { dbgprintf(" exiting on signal %d\n", sig); (void) snprintf(buf, sizeof(buf) / sizeof(char), " [origin software=\"rsyslogd\" " "swVersion=\"" VERSION \ "\" x-pid=\"%d\"]" " exiting on signal %d.", (int) myPid, sig); errno = 0; logmsgInternal(LOG_SYSLOG|LOG_INFO, buf, ADDDATE); } /* Free ressources and close connections */ freeSelectors(); /* Worker threads are stopped by freeSelectors() */ queueDelete(pMsgQueue); /* delete fifo here! */ pMsgQueue = NULL; /* now clean up the listener part */ #ifdef SYSLOG_INET /* Close the UNIX sockets. */ for (i = 0; i < nfunix; i++) if (funix[i] != -1) close(funix[i]); /* Close the UDP inet socket. */ closeUDPListenSockets(); /* Close the TCP inet socket. */ if(sockTCPLstn != NULL && *sockTCPLstn) { deinit_tcp_listener(); } #endif /* Clean-up files. */ for (i = 0; i < nfunix; i++) if (funixn[i] && funix[i] != -1) (void)unlink(funixn[i]); /* 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); if(glblHadMemShortage) dbgprintf("Had memory shortage at least once during the run.\n"); /* de-init some modules */ modExitIminternal(); /* 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 */ modUnloadAndDestructAll(); /* the following line cleans up CfSysLineHandlers that were not based on loadable * modules. As such, they are not yet cleared. */ unregCfSysLineHdlrs(); /* clean up auxiliary data */ if(pModDir != NULL) free(pModDir); dbgprintf("Clean shutdown completed, bye.\n"); 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 */ } /* parse an allowed sender config line and add the allowed senders * (if the line is correct). * rgerhards, 2005-09-27 */ static rsRetVal addAllowedSenderLine(char* pName, uchar** ppRestOfConfLine) { #ifdef SYSLOG_INET struct AllowedSenders **ppRoot; struct AllowedSenders **ppLast; rsParsObj *pPars; rsRetVal iRet; struct NetAddr *uIP = NULL; int iBits; #endif assert(pName != NULL); assert(ppRestOfConfLine != NULL); assert(*ppRestOfConfLine != NULL); #ifndef SYSLOG_INET errno = 0; logerror("config file contains allowed sender list, but rsyslogd " "compiled without Internet support - line ignored"); return RS_RET_ERR; #else if(!strcasecmp(pName, "udp")) { ppRoot = &pAllowedSenders_UDP; ppLast = &pLastAllowedSenders_UDP; } else if(!strcasecmp(pName, "tcp")) { ppRoot = &pAllowedSenders_TCP; ppLast = &pLastAllowedSenders_TCP; } else { logerrorSz("Invalid protocol '%s' in allowed sender " "list, line ignored", pName); return RS_RET_ERR; } /* OK, we now know the protocol and have valid list pointers. * So let's process the entries. We are using the parse class * for this. */ /* create parser object starting with line string without leading colon */ if((iRet = rsParsConstructFromSz(&pPars, (uchar*) *ppRestOfConfLine) != RS_RET_OK)) { logerrorInt("Error %d constructing parser object - ignoring allowed sender list", iRet); return(iRet); } while(!parsIsAtEndOfParseString(pPars)) { if(parsPeekAtCharAtParsPtr(pPars) == '#') break; /* a comment-sign stops processing of line */ /* now parse a single IP address */ if((iRet = parsAddrWithBits(pPars, &uIP, &iBits)) != RS_RET_OK) { logerrorInt("Error %d parsing address in allowed sender" "list - ignoring.", iRet); rsParsDestruct(pPars); return(iRet); } if((iRet = AddAllowedSender(ppRoot, ppLast, uIP, iBits)) != RS_RET_OK) { if (iRet == RS_RET_NOENTRY) { logerrorInt("Error %d adding allowed sender entry " "- ignoring.", iRet); } else { logerrorInt("Error %d adding allowed sender entry " "- terminating, nothing more will be added.", iRet); rsParsDestruct(pPars); return(iRet); } } free (uIP); /* copy stored in AllowedSenders list */ } /* cleanup */ *ppRestOfConfLine += parsGetCurrentPosition(pPars); return rsParsDestruct(pPars); #endif /*#ifndef SYSLOG_INET */ } /* process a directory and include all of its files into * the current config file. There is no specific order of inclusion, * files are included in the order they are read from the directory. * The caller must have make sure that the provided parameter is * indeed a directory. * rgerhards, 2007-08-01 */ static rsRetVal doIncludeDirectory(uchar *pDirName) { DEFiRet; int iEntriesDone = 0; DIR *pDir; union { struct dirent d; char b[offsetof(struct dirent, d_name) + NAME_MAX + 1]; } u; struct dirent *res; size_t iDirNameLen; size_t iFileNameLen; uchar szFullFileName[MAXFNAME]; assert(pDirName != NULL); if((pDir = opendir((char*) pDirName)) == NULL) { logerror("error opening include directory"); ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); } /* prepare file name buffer */ iDirNameLen = strlen((char*) pDirName); memcpy(szFullFileName, pDirName, iDirNameLen); /* now read the directory */ iEntriesDone = 0; while(readdir_r(pDir, &u.d, &res) == 0) { if(res == NULL) break; /* this also indicates end of directory */ if(res->d_type != DT_REG) continue; /* we are not interested in special files */ if(res->d_name[0] == '.') continue; /* these files we are also not interested in */ ++iEntriesDone; /* construct filename */ iFileNameLen = strlen(res->d_name); if (iFileNameLen > NAME_MAX) iFileNameLen = NAME_MAX; memcpy(szFullFileName + iDirNameLen, res->d_name, iFileNameLen); *(szFullFileName + iDirNameLen + iFileNameLen) = '\0'; dbgprintf("including file '%s'\n", szFullFileName); processConfFile(szFullFileName); /* we deliberately ignore the iRet of processConfFile() - this is because * failure to process one file does not mean all files will fail. By ignoring, * we retry with the next file, which is the best thing we can do. -- rgerhards, 2007-08-01 */ } if(iEntriesDone == 0) { /* I just make it a debug output, because I can think of a lot of cases where it * makes sense not to have any files. E.g. a system maintainer may place a $Include * into the config file just in case, when additional modules be installed. When none * are installed, the directory will be empty, which is fine. -- rgerhards 2007-08-01 */ dbgprintf("warning: the include directory contained no files - this may be ok.\n"); } finalize_it: if(pDir != NULL) closedir(pDir); return iRet; } /* process a $include config line. That type of line requires * inclusion of another file. * rgerhards, 2007-08-01 */ static rsRetVal doIncludeLine(uchar **pp, __attribute__((unused)) void* pVal) { DEFiRet; uchar cfgFile[MAXFNAME]; assert(pp != NULL); assert(*pp != NULL); if(getSubString(pp, (char*) cfgFile, sizeof(cfgFile) / sizeof(uchar), ' ') != 0) { logerror("could not extract group name"); ABORT_FINALIZE(RS_RET_NOT_FOUND); } if(*(cfgFile+strlen((char*) cfgFile) - 1) == '/') { dbgprintf("requested to include directory '%s'\n", cfgFile); iRet = doIncludeDirectory(cfgFile); } else { dbgprintf("Requested to include config file '%s'\n", cfgFile); iRet = processConfFile(cfgFile); } finalize_it: return iRet; } /* process a $ModLoad config line. * As of now, it is a dummy, that will later evolve into the * loader for plug-ins. * rgerhards, 2007-07-21 * varmojfekoj added support for dynamically loadable modules on 2007-08-13 * rgerhards, 2007-09-25: please note that the non-threadsafe function dlerror() is * called below. This is ok because modules are currently only loaded during * configuration file processing, which is executed on a single thread. Should we * change that design at any stage (what is unlikely), we need to find a * replacement. */ static rsRetVal doModLoad(uchar **pp, __attribute__((unused)) void* pVal) { DEFiRet; uchar szName[512]; uchar szPath[512]; uchar errMsg[1024]; uchar *pModName; void *pModHdlr, *pModInit; assert(pp != NULL); assert(*pp != NULL); if(getSubString(pp, (char*) szName, sizeof(szName) / sizeof(uchar), ' ') != 0) { logerror("could not extract module name"); ABORT_FINALIZE(RS_RET_NOT_FOUND); } /* this below is a quick and dirty hack to provide compatibility with the * $ModLoad MySQL forward compatibility statement. TODO: clean this up * For the time being, it is clean enough, it just needs to be done * differently when we have a full design for loadable plug-ins. For the * time being, we just mangle the names a bit. * rgerhards, 2007-08-14 */ if(!strcmp((char*) szName, "MySQL")) pModName = (uchar*) "ommysql.so"; else pModName = szName; dbgprintf("Requested to load module '%s'\n", szName); if(*pModName == '/') { *szPath = '\0'; /* we do not need to append the path - its already in the module name */ } else { strncpy((char *) szPath, (pModDir == NULL) ? _PATH_MODDIR : (char*) pModDir, sizeof(szPath)); } strncat((char *) szPath, (char *) pModName, sizeof(szPath) - strlen((char*) szPath) - 1); if(!(pModHdlr = dlopen((char *) szPath, RTLD_NOW))) { snprintf((char *) errMsg, sizeof(errMsg), "could not load module '%s', dlopen: %s\n", szPath, dlerror()); errMsg[sizeof(errMsg)/sizeof(uchar) - 1] = '\0'; logerror((char *) errMsg); ABORT_FINALIZE(RS_RET_ERR); } if(!(pModInit = dlsym(pModHdlr, "modInit"))) { snprintf((char *) errMsg, sizeof(errMsg), "could not load module '%s', dlsym: %s\n", szPath, dlerror()); errMsg[sizeof(errMsg)/sizeof(uchar) - 1] = '\0'; logerror((char *) errMsg); dlclose(pModHdlr); ABORT_FINALIZE(RS_RET_ERR); } if((iRet = doModInit(pModInit, (uchar*) pModName, pModHdlr)) != RS_RET_OK) { snprintf((char *) errMsg, sizeof(errMsg), "could not load module '%s', rsyslog error %d\n", szPath, iRet); errMsg[sizeof(errMsg)/sizeof(uchar) - 1] = '\0'; logerror((char *) errMsg); dlclose(pModHdlr); ABORT_FINALIZE(RS_RET_ERR); } skipWhiteSpace(pp); /* skip over any whitespace */ finalize_it: return iRet; } /* parse and interpret a $-config line that starts with * a name (this is common code). It is parsed to the name * and then the proper sub-function is called to handle * the actual directive. * rgerhards 2004-11-17 * rgerhards 2005-06-21: previously only for templates, now * generalized. */ static rsRetVal doNameLine(uchar **pp, void* pVal) { DEFiRet; uchar *p; enum eDirective eDir; char szName[128]; assert(pp != NULL); p = *pp; assert(p != NULL); eDir = (enum eDirective) pVal; /* this time, it actually is NOT a pointer! */ if(getSubString(&p, szName, sizeof(szName) / sizeof(char), ',') != 0) { logerror("Invalid config line: could not extract name - line ignored"); ABORT_FINALIZE(RS_RET_NOT_FOUND); } if(*p == ',') ++p; /* comma was eaten */ /* we got the name - now we pass name & the rest of the string * to the subfunction. It makes no sense to do further * parsing here, as this is in close interaction with the * respective subsystem. rgerhards 2004-11-17 */ switch(eDir) { case DIR_TEMPLATE: tplAddLine(szName, &p); break; case DIR_OUTCHANNEL: ochAddLine(szName, &p); break; case DIR_ALLOWEDSENDER: addAllowedSenderLine(szName, &p); break; default:/* we do this to avoid compiler warning - not all * enum values call this function, so an incomplete list * is quite ok (but then we should not run into this code, * so at least we log a debug warning). */ dbgprintf("INTERNAL ERROR: doNameLine() called with invalid eDir %d.\n", eDir); break; } *pp = p; finalize_it: return iRet; } /* 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; } /* Parse and interpret a system-directive in the config line * A system directive is one that starts with a "$" sign. It offers * extended configuration parameters. * 2004-11-17 rgerhards */ rsRetVal cfsysline(uchar *p) { DEFiRet; uchar szCmd[64]; uchar errMsg[128]; /* for dynamic error messages */ assert(p != NULL); errno = 0; if(getSubString(&p, (char*) szCmd, sizeof(szCmd) / sizeof(uchar), ' ') != 0) { logerror("Invalid $-configline - could not extract command - line ignored\n"); ABORT_FINALIZE(RS_RET_NOT_FOUND); } /* we now try and see if we can find the command in the registered * list of cfsysline handlers. -- rgerhards, 2007-07-31 */ CHKiRet(processCfSysLineCommand(szCmd, &p)); /* now check if we have some extra characters left on the line - that * should not be the case. Whitespace is OK, but everything else should * trigger a warning (that may be an indication of undesired behaviour). * An exception, of course, are comments (starting with '#'). * rgerhards, 2007-07-04 */ skipWhiteSpace(&p); if(*p && *p != '#') { /* we have a non-whitespace, so let's complain */ snprintf((char*) errMsg, sizeof(errMsg)/sizeof(uchar), "error: extra characters in config line ignored: '%s'", p); errno = 0; logerror((char*) errMsg); } finalize_it: return iRet; } /* 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) { fprintlog(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"); /* just in case, we flush the emergency log. If error messages occur after * this stage, we loose them, but that's ok. With multi-threading, this can * never happen. -- rgerhards, 2007-08-03 */ processImInternal(); /* we first wait until all messages are processed (stopWorker() does * that. Then, we go one last time over all actions and flush any * pending "message repeated n times" messages. We must use this sequence * because otherwise we would flush at whatever message is currently being * processed without draining the queue. That would lead to invalid * results. -- rgerhards, 2007-12-12 */ stopWorker(); 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; Initialized = 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); printf("\n"); return iRet; } /* 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; printf("\nActive selectors:\n"); for (f = Files; f != NULL ; f = f->f_next) { printf("Selector %d:\n", iSelNbr++); if(f->pCSProgNameComp != NULL) printf("tag: '%s'\n", rsCStrGetSzStrNoNULL(f->pCSProgNameComp)); if(f->eHostnameCmpMode != HN_NO_COMP) printf("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) printf(" X "); else printf("%2X ", f->f_filterData.f_pmask[i]); } else { printf("PROPERTY-BASED Filter:\n"); printf("\tProperty.: '%s'\n", rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSPropName)); printf("\tOperation: "); if(f->f_filterData.prop.isNegated) printf("NOT "); printf("'%s'\n", getFIOPName(f->f_filterData.prop.operation)); printf("\tValue....: '%s'\n", rsCStrGetSzStrNoNULL(f->f_filterData.prop.pCSCompValue)); printf("\tAction...: "); } printf("\nActions:\n"); llExecFunc(&f->llActList, dbgPrintInitInfoAction, NULL); /* actions */ printf("\n"); } printf("\n"); if(bDebugPrintTemplateList) tplPrintList(); if(bDebugPrintModuleList) modPrintList(); ochPrintList(); if(bDebugPrintCfSysLineHandlerList) dbgPrintCfSysLineHandlers(); #ifdef SYSLOG_INET /* now the allowedSender lists: */ PrintAllowedSenders(1); /* UDP */ PrintAllowedSenders(2); /* TCP */ printf("\n"); #endif /* #ifdef SYSLOG_INET */ printf("Messages with malicious PTR DNS Records are %sdropped.\n", bDropMalPTRMsgs ? "" : "not "); printf("Control characters are %sreplaced upon reception.\n", bEscapeCCOnRcv? "" : "not "); if(bEscapeCCOnRcv) printf("Control character escape sequence prefix is '%c'.\n", cCCEscapeChar); printf("Main queue size %d messages.\n", iMainMsgQueueSize); } /* process a configuration file * started with code from init() by rgerhards on 2007-07-31 */ static rsRetVal processConfFile(uchar *pConfFile) { DEFiRet; int iLnNbr = 0; FILE *cf; selector_t *fCurr = NULL; uchar *p; #ifdef CONT_LINE uchar cbuf[BUFSIZ]; uchar *cline; #else uchar cline[BUFSIZ]; #endif assert(pConfFile != NULL); if((cf = fopen((char*)pConfFile, "r")) == NULL) { ABORT_FINALIZE(RS_RET_FOPEN_FAILURE); } /* Now process the file. */ #if CONT_LINE cline = cbuf; while (fgets((char*)cline, sizeof(cbuf) - (cline - cbuf), cf) != NULL) { #else while (fgets(cline, sizeof(cline), cf) != NULL) { #endif ++iLnNbr; /* drop LF - TODO: make it better, replace fgets(), but its clean as it is */ if(cline[strlen((char*)cline)-1] == '\n') { cline[strlen((char*)cline) -1] = '\0'; } /* check for end-of-section, comments, strip off trailing * spaces and newline character. */ p = cline; skipWhiteSpace(&p); if (*p == '\0' || *p == '#') continue; #if CONT_LINE strcpy((char*)cline, (char*)p); #endif for (p = (uchar*) strchr((char*)cline, '\0'); isspace((int) *--p);); #if CONT_LINE if (*p == '\\') { if ((p - cbuf) > BUFSIZ - 30) { /* Oops the buffer is full - what now? */ cline = cbuf; } else { *p = 0; cline = p; continue; } } else cline = cbuf; #endif *++p = '\0'; // TODO: check this /* we now have the complete line, and are positioned at the first non-whitespace * character. So let's process it */ #if CONT_LINE if(cfline(cbuf, &fCurr) != RS_RET_OK) { #else if(cfline((uchar*)cline, &fCurr) != RS_RET_OK) { #endif /* we log a message, but otherwise ignore the error. After all, the next * line can be correct. -- rgerhards, 2007-08-02 */ uchar szErrLoc[MAXFNAME + 64]; dbgprintf("config line NOT successfully processed\n"); snprintf((char*)szErrLoc, sizeof(szErrLoc) / sizeof(uchar), "%s, line %d", pConfFile, iLnNbr); logerrorSz("the last error occured in %s", (char*)szErrLoc); } } /* we probably have one selector left to be added - so let's do that now */ CHKiRet(selectorAddList(fCurr)); /* close the configuration file */ (void) fclose(cf); finalize_it: if(iRet != RS_RET_OK) { char errStr[1024]; if(fCurr != NULL) selectorDestruct(fCurr); strerror_r(errno, errStr, sizeof(errStr)); dbgprintf("error %d processing config file '%s'; os error (if any): %s\n", iRet, pConfFile, errStr); } return iRet; } /* 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 = modGetNxtType(NULL, eMOD_IN); while(pMod != NULL) { /* activate here */ pMod = modGetNxtType(pMod, eMOD_IN); } return iRet; } /* INIT -- Initialize syslogd from configuration table * init() is called at initial startup AND each time syslogd is HUPed */ static void init(void) { DEFiRet; register int i; #ifdef CONT_LINE char cbuf[BUFSIZ]; #else char cline[BUFSIZ]; #endif char bufStartUpMsg[512]; struct servent *sp; struct sigaction sigAct; /* initialize some static variables */ pDfltHostnameCmp = NULL; pDfltProgNameCmp = NULL; eDfltHostnameCmpMode = HN_NO_COMP; Forwarding = 0; #ifdef SYSLOG_INET if (restart) { if (pAllowedSenders_UDP != NULL) { clearAllowedSenders (pAllowedSenders_UDP); pAllowedSenders_UDP = NULL; } if (pAllowedSenders_TCP != NULL) { clearAllowedSenders (pAllowedSenders_TCP); pAllowedSenders_TCP = NULL; } } assert(pAllowedSenders_UDP == NULL && pAllowedSenders_TCP == NULL); #endif /* I was told by an IPv6 expert that calling getservbyname() seems to be * still valid, at least for the use case we have. So I re-enabled that * code. rgerhards, 2007-07-02 */ if(!strcmp(LogPort, "0")) { /* we shall use the default syslog/udp port, so let's * look it up. * NOTE: getservbyname() is not thread-safe, but this is OK as * it is called only during init, in single-threading mode. */ sp = getservbyname("syslog", "udp"); if (sp == NULL) { errno = 0; logerror("Could not find syslog/udp port in /etc/services. " "Now using IANA-assigned default of 514."); LogPort = "514"; } else { /* we can dynamically allocate memory here and do NOT need * to care about freeing it because even though init() is * called on each restart, the LogPort can never again be * "0". So we will only once run into this part of the code * here. rgerhards, 2007-07-02 * We save ourselfs the hassle of dynamic memory management * for the very same reason. */ static char defPort[8]; snprintf(defPort, sizeof(defPort), "%d", ntohs(sp->s_port)); LogPort = defPort; } } dbgprintf("rsyslog %s.\n", VERSION); dbgprintf("Called init.\n"); /* 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"); modUnloadAndDestructDynamic(); dbgprintf("Clearing templates.\n"); tplDeleteNew(); if(pMsgQueue != NULL) { dbgprintf("deleting message queue\n"); queueDelete(pMsgQueue); /* delete fifo here! */ pMsgQueue = NULL; } /* re-setting values to defaults (where applicable) */ /* TODO: 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. In any case, we should * think about the whole situation when we implement loadable plugins. * rgerhards, 2007-07-31 */ cfsysline((uchar*)"ResetConfigVariables"); /* open the configuration file */ if((iRet = 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"); cfline((uchar*)"*.ERR\t" _PATH_CONSOLE, &f); cfline((uchar*)"*.PANIC\t*", &f); if(ttyname_r(0, szTTYNameBuf, sizeof(szTTYNameBuf)) == 0) { snprintf(cbuf,sizeof(cbuf), "*.*\t%s", szTTYNameBuf); cfline((uchar*)cbuf, &f); } selectorAddList(f); } /* 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); pDfltHostnameCmp = NULL; } if(pDfltProgNameCmp != NULL) { rsCStrDestruct(pDfltProgNameCmp); pDfltProgNameCmp = NULL; } #ifdef SYSLOG_UNIXAF for (i = startIndexUxLocalSockets ; i < nfunix ; i++) { if (funix[i] != -1) /* Don't close the socket, preserve it instead close(funix[i]); */ continue; if ((funix[i] = create_unix_socket(funixn[i])) != -1) dbgprintf("Opened UNIX socket `%s' (fd %d).\n", funixn[i], funix[i]); } #endif #ifdef SYSLOG_INET /* I have moved initializing UDP sockets before the TCP sockets. This ensures * they are as soon ready for reception as possible. Of course, it is only a * very small window of exposure, but it doesn't hurt to limit the message * loss risk to as low as possible - especially if it costs nothing... * rgerhards, 2007-06-28 */ if(Forwarding || AcceptRemote) { if (finet == NULL) { if((finet = create_udp_socket()) != NULL) dbgprintf("Opened %d syslog UDP port(s).\n", *finet); } } else { /* this case can happen during HUP processing. */ closeUDPListenSockets(); } if (bEnableTCP) { if(sockTCPLstn == NULL) { /* even when doing a re-init, we do not shut down and * re-open the TCP socket. That would break existing TCP * session, which we do not desire. Should at some time arise * need to do that, I recommend controlling that via a * user-selectable option. rgerhards, 2007-06-21 */ # ifdef USE_GSSAPI if(bEnableTCP == 2) { if(TCPSessGSSInit()) { logerror("GSS-API initialization failed\n"); bEnableTCP = -1; } } # endif if((sockTCPLstn = create_tcp_socket()) != NULL) { dbgprintf("Opened %d syslog TCP port(s).\n", *sockTCPLstn); } } } #endif /* create message queue */ pMsgQueue = queueInit(); if(pMsgQueue == NULL) { errno = 0; /* TODO: check if that is possible without threads - I think we must give up... */ logerror("error: could not create message queue - running single-threaded!\n"); } startWorker(); Initialized = 1; /* the output part and the queue is now ready to run. So it is a good time * now 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-configInfo udpReception=\"%s\" " \ "udpPort=\"%s\" tcpReception=\"%s\" tcpPort=\"%s\"]" \ " restart", (int) myPid, #ifdef SYSLOG_INET AcceptRemote ? "Yes" : "No", LogPort, bEnableTCP ? "Yes" : "No", TCPLstnPort #else "No", "0", "No", "0" #endif /* #ifdef SYSLOG_INET */ ); logmsgInternal(LOG_SYSLOG|LOG_INFO, bufStartUpMsg, ADDDATE); memset(&sigAct, 0, sizeof (sigAct)); sigemptyset(&sigAct.sa_mask); sigAct.sa_handler = sighup_handler; sigaction(SIGHUP, &sigAct, NULL); dbgprintf(" (re)started.\n"); } /* Helper to cfline() and its helpers. Parses a template name * from an "action" line. Must be called with the Line pointer * pointing to the first character after the semicolon. * rgerhards 2004-11-19 * changed function to work with OMSR. -- rgerhards, 2007-07-27 * the default template is to be used when no template is specified. */ rsRetVal cflineParseTemplateName(uchar** pp, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts, uchar *dfltTplName) { uchar *p; uchar *tplName; DEFiRet; rsCStrObj *pStrB; assert(pp != NULL); assert(*pp != NULL); assert(pOMSR != NULL); p =*pp; /* a template must follow - search it and complain, if not found */ skipWhiteSpace(&p); if(*p == ';') ++p; /* eat it */ else if(*p != '\0' && *p != '#') { logerror("invalid character in selector line - ';template' expected"); iRet = RS_RET_ERR; goto finalize_it; } skipWhiteSpace(&p); /* go to begin of template name */ if(*p == '\0') { /* no template specified, use the default */ /* TODO: check NULL ptr */ tplName = (uchar*) strdup((char*)dfltTplName); } else { /* template specified, pick it up */ if((pStrB = rsCStrConstruct()) == NULL) { glblHadMemShortage = 1; iRet = RS_RET_OUT_OF_MEMORY; goto finalize_it; } /* now copy the string */ while(*p && *p != '#' && !isspace((int) *p)) { CHKiRet(rsCStrAppendChar(pStrB, *p)); ++p; } CHKiRet(rsCStrFinish(pStrB)); CHKiRet(rsCStrConvSzStrAndDestruct(pStrB, &tplName, 0)); } iRet = OMSRsetEntry(pOMSR, iEntry, tplName, iTplOpts); if(iRet != RS_RET_OK) goto finalize_it; finalize_it: *pp = p; return iRet; } /* Helper to cfline(). Parses a file name up until the first * comma and then looks for the template specifier. Tries * to find that template. * rgerhards 2004-11-18 * parameter pFileName must point to a buffer large enough * to hold the largest possible filename. * rgerhards, 2007-07-25 * updated to include OMSR pointer -- rgerhards, 2007-07-27 */ rsRetVal cflineParseFileName(uchar* p, uchar *pFileName, omodStringRequest_t *pOMSR, int iEntry, int iTplOpts) { register uchar *pName; int i; DEFiRet; assert(pOMSR != NULL); pName = pFileName; i = 1; /* we start at 1 so that we reseve space for the '\0'! */ while(*p && *p != ';' && i < MAXFNAME) { *pName++ = *p++; ++i; } *pName = '\0'; iRet = cflineParseTemplateName(&p, pOMSR, iEntry, iTplOpts, (uchar*) " TradFmt"); return iRet; } /* * Helper to cfline(). This function takes the filter part of a traditional, PRI * based line and decodes the PRIs given in the selector line. It processed the * line up to the beginning of the action part. A pointer to that beginnig is * passed back to the caller. * rgerhards 2005-09-15 */ static rsRetVal cflineProcessTradPRIFilter(uchar **pline, register selector_t *f) { uchar *p; register uchar *q; register int i, i2; uchar *bp; int pri; int singlpri = 0; int ignorepri = 0; uchar buf[MAXLINE]; uchar xbuf[200]; assert(pline != NULL); assert(*pline != NULL); assert(f != NULL); dbgprintf(" - traditional PRI filter\n"); errno = 0; /* keep strerror_r() stuff out of logerror messages */ f->f_filter_type = FILTER_PRI; /* Note: file structure is pre-initialized to zero because it was * created with calloc()! */ for (i = 0; i <= LOG_NFACILITIES; i++) { f->f_filterData.f_pmask[i] = TABLE_NOPRI; } /* scan through the list of selectors */ for (p = *pline; *p && *p != '\t' && *p != ' ';) { /* find the end of this facility name list */ for (q = p; *q && *q != '\t' && *q++ != '.'; ) continue; /* collect priority name */ for (bp = buf; *q && !strchr("\t ,;", *q); ) *bp++ = *q++; *bp = '\0'; /* skip cruft */ while (strchr(",;", *q)) q++; /* decode priority name */ if ( *buf == '!' ) { ignorepri = 1; for (bp=buf; *(bp+1); bp++) *bp=*(bp+1); *bp='\0'; } else { ignorepri = 0; } if ( *buf == '=' ) { singlpri = 1; pri = decode(&buf[1], PriNames); } else { singlpri = 0; pri = decode(buf, PriNames); } if (pri < 0) { snprintf((char*) xbuf, sizeof(xbuf), "unknown priority name \"%s\"", buf); logerror((char*) xbuf); return RS_RET_ERR; } /* scan facilities */ while (*p && !strchr("\t .;", *p)) { for (bp = buf; *p && !strchr("\t ,;.", *p); ) *bp++ = *p++; *bp = '\0'; if (*buf == '*') { for (i = 0; i <= LOG_NFACILITIES; i++) { if ( pri == INTERNAL_NOPRI ) { if ( ignorepri ) f->f_filterData.f_pmask[i] = TABLE_ALLPRI; else f->f_filterData.f_pmask[i] = TABLE_NOPRI; } else if ( singlpri ) { if ( ignorepri ) f->f_filterData.f_pmask[i] &= ~(1<f_filterData.f_pmask[i] |= (1<f_filterData.f_pmask[i] = TABLE_NOPRI; else f->f_filterData.f_pmask[i] = TABLE_ALLPRI; } else { if ( ignorepri ) for (i2= 0; i2 <= pri; ++i2) f->f_filterData.f_pmask[i] &= ~(1<f_filterData.f_pmask[i] |= (1<f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; else f->f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; } else if ( singlpri ) { if ( ignorepri ) f->f_filterData.f_pmask[i >> 3] &= ~(1<f_filterData.f_pmask[i >> 3] |= (1<f_filterData.f_pmask[i >> 3] = TABLE_NOPRI; else f->f_filterData.f_pmask[i >> 3] = TABLE_ALLPRI; } else { if ( ignorepri ) for (i2= 0; i2 <= pri; ++i2) f->f_filterData.f_pmask[i >> 3] &= ~(1<f_filterData.f_pmask[i >> 3] |= (1<f_filter_type = FILTER_PROP; /* create parser object starting with line string without leading colon */ if((iRet = rsParsConstructFromSz(&pPars, (*pline)+1)) != RS_RET_OK) { logerrorInt("Error %d constructing parser object - ignoring selector", iRet); return(iRet); } /* read property */ iRet = parsDelimCStr(pPars, &f->f_filterData.prop.pCSPropName, ',', 1, 1); if(iRet != RS_RET_OK) { logerrorInt("error %d parsing filter property - ignoring selector", iRet); rsParsDestruct(pPars); return(iRet); } /* read operation */ iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1); if(iRet != RS_RET_OK) { logerrorInt("error %d compare operation property - ignoring selector", iRet); rsParsDestruct(pPars); return(iRet); } /* we now first check if the condition is to be negated. To do so, we first * must make sure we have at least one char in the param and then check the * first one. * rgerhards, 2005-09-26 */ if(rsCStrLen(pCSCompOp) > 0) { if(*rsCStrGetBufBeg(pCSCompOp) == '!') { f->f_filterData.prop.isNegated = 1; iOffset = 1; /* ignore '!' */ } else { f->f_filterData.prop.isNegated = 0; iOffset = 0; } } else { f->f_filterData.prop.isNegated = 0; iOffset = 0; } if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "contains", 8)) { f->f_filterData.prop.operation = FIOP_CONTAINS; } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "isequal", 7)) { f->f_filterData.prop.operation = FIOP_ISEQUAL; } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar*) "startswith", 10)) { f->f_filterData.prop.operation = FIOP_STARTSWITH; } else if(!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char*) "regex", 5)) { f->f_filterData.prop.operation = FIOP_REGEX; } else { logerrorSz("error: invalid compare operation '%s' - ignoring selector", (char*) rsCStrGetSzStrNoNULL(pCSCompOp)); } rsCStrDestruct (pCSCompOp); /* no longer needed */ /* read compare value */ iRet = parsQuotedCStr(pPars, &f->f_filterData.prop.pCSCompValue); if(iRet != RS_RET_OK) { logerrorInt("error %d compare value property - ignoring selector", iRet); rsParsDestruct(pPars); return(iRet); } /* skip to action part */ if((iRet = parsSkipWhitespace(pPars)) != RS_RET_OK) { logerrorInt("error %d skipping to action part - ignoring selector", iRet); rsParsDestruct(pPars); return(iRet); } /* cleanup */ *pline = *pline + rsParsGetParsePointer(pPars) + 1; /* we are adding one for the skipped initial ":" */ return rsParsDestruct(pPars); } /* * Helper to cfline(). This function interprets a BSD host selector line * from the config file ("+/-hostname"). It stores it for further reference. * rgerhards 2005-10-19 */ static rsRetVal cflineProcessHostSelector(uchar **pline) { rsRetVal iRet; assert(pline != NULL); assert(*pline != NULL); assert(**pline == '-' || **pline == '+'); dbgprintf(" - host selector line\n"); /* check include/exclude setting */ if(**pline == '+') { eDfltHostnameCmpMode = HN_COMP_MATCH; } else { /* we do not check for '-', it must be, else we wouldn't be here */ eDfltHostnameCmpMode = HN_COMP_NOMATCH; } (*pline)++; /* eat + or - */ /* the below is somewhat of a quick hack, but it is efficient (this is * why it is in here. "+*" resets the tag selector with BSD syslog. We mimic * this, too. As it is easy to check that condition, we do not fire up a * parser process, just make sure we do not address beyond our space. * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 */ if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { dbgprintf("resetting BSD-like hostname filter\n"); eDfltHostnameCmpMode = HN_NO_COMP; if(pDfltHostnameCmp != NULL) { if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, NULL)) != RS_RET_OK) return(iRet); } } else { dbgprintf("setting BSD-like hostname filter to '%s'\n", *pline); if(pDfltHostnameCmp == NULL) { /* create string for parser */ if((iRet = rsCStrConstructFromszStr(&pDfltHostnameCmp, *pline)) != RS_RET_OK) return(iRet); } else { /* string objects exists, just update... */ if((iRet = rsCStrSetSzStr(pDfltHostnameCmp, *pline)) != RS_RET_OK) return(iRet); } } return RS_RET_OK; } /* * Helper to cfline(). This function interprets a BSD tag selector line * from the config file ("!tagname"). It stores it for further reference. * rgerhards 2005-10-18 */ static rsRetVal cflineProcessTagSelector(uchar **pline) { rsRetVal iRet; assert(pline != NULL); assert(*pline != NULL); assert(**pline == '!'); dbgprintf(" - programname selector line\n"); (*pline)++; /* eat '!' */ /* the below is somewhat of a quick hack, but it is efficient (this is * why it is in here. "!*" resets the tag selector with BSD syslog. We mimic * this, too. As it is easy to check that condition, we do not fire up a * parser process, just make sure we do not address beyond our space. * Order of conditions in the if-statement is vital! rgerhards 2005-10-18 */ if(**pline != '\0' && **pline == '*' && *(*pline+1) == '\0') { dbgprintf("resetting programname filter\n"); if(pDfltProgNameCmp != NULL) { if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, NULL)) != RS_RET_OK) return(iRet); } } else { dbgprintf("setting programname filter to '%s'\n", *pline); if(pDfltProgNameCmp == NULL) { /* create string for parser */ if((iRet = rsCStrConstructFromszStr(&pDfltProgNameCmp, *pline)) != RS_RET_OK) return(iRet); } else { /* string objects exists, just update... */ if((iRet = rsCStrSetSzStr(pDfltProgNameCmp, *pline)) != RS_RET_OK) return(iRet); } } return RS_RET_OK; } /* add an Action to the current selector * The pOMSR is freed, as it is not needed after this function. * Note: this function pulls global data that specifies action config state. * rgerhards, 2007-07-27 */ rsRetVal addAction(action_t **ppAction, modInfo_t *pMod, void *pModData, omodStringRequest_t *pOMSR, int bSuspended) { DEFiRet; int i; int iTplOpts; uchar *pTplName; action_t *pAction; char errMsg[512]; assert(ppAction != NULL); assert(pMod != NULL); assert(pOMSR != NULL); dbgprintf("Module %s processed this config line.\n", modGetName(pMod)); CHKiRet(actionConstruct(&pAction)); /* create action object first */ pAction->pMod = pMod; pAction->pModData = pModData; pAction->bExecWhenPrevSusp = bActExecWhenPrevSusp; /* check if we can obtain the template pointers - TODO: move to separat function? */ pAction->iNumTpls = OMSRgetEntryCount(pOMSR); assert(pAction->iNumTpls >= 0); /* only debug check because this "can not happen" */ /* please note: iNumTpls may validly be zero. This is the case if the module * does not request any templates. This sounds unlikely, but an actual example is * the discard action, which does not require a string. -- rgerhards, 2007-07-30 */ if(pAction->iNumTpls > 0) { /* we first need to create the template pointer array */ if((pAction->ppTpl = calloc(pAction->iNumTpls, sizeof(struct template *))) == NULL) { glblHadMemShortage = 1; ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } /* and now the array for doAction() message pointers */ if((pAction->ppMsgs = calloc(pAction->iNumTpls, sizeof(uchar *))) == NULL) { glblHadMemShortage = 1; ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } } for(i = 0 ; i < pAction->iNumTpls ; ++i) { CHKiRet(OMSRgetEntry(pOMSR, i, &pTplName, &iTplOpts)); /* Ok, we got everything, so it now is time to look up the * template (Hint: templates MUST be defined before they are * used!) */ if((pAction->ppTpl[i] = tplFind((char*)pTplName, strlen((char*)pTplName))) == NULL) { snprintf(errMsg, sizeof(errMsg) / sizeof(char), " Could not find template '%s' - action disabled\n", pTplName); errno = 0; logerror(errMsg); ABORT_FINALIZE(RS_RET_NOT_FOUND); } /* check required template options */ if( (iTplOpts & OMSR_RQD_TPL_OPT_SQL) && (pAction->ppTpl[i]->optFormatForSQL == 0)) { errno = 0; logerror("Action disabled. To use this action, you have to specify " "the SQL or stdSQL option in your template!\n"); ABORT_FINALIZE(RS_RET_RQD_TPLOPT_MISSING); } dbgprintf("template: '%s' assigned\n", pTplName); } pAction->pMod = pMod; pAction->pModData = pModData; /* now check if the module is compatible with select features */ if(pMod->isCompatibleWithFeature(sFEATURERepeatedMsgReduction) == RS_RET_OK) pAction->f_ReduceRepeated = bReduceRepeatMsgs; else { dbgprintf("module is incompatible with RepeatedMsgReduction - turned off\n"); pAction->f_ReduceRepeated = 0; } pAction->bEnabled = 1; /* action is enabled */ if(bSuspended) actionSuspend(pAction); *ppAction = pAction; /* finally store the action pointer */ finalize_it: if(iRet == RS_RET_OK) iRet = OMSRdestruct(pOMSR); else { /* do not overwrite error state! */ OMSRdestruct(pOMSR); if(pAction != NULL) actionDestruct(pAction); } return iRet; } /* read the filter part of a configuration line and store the filter * in the supplied selector_t * rgerhards, 2007-08-01 */ static rsRetVal cflineDoFilter(uchar **pp, selector_t *f) { DEFiRet; assert(pp != NULL); assert(f != NULL); /* check which filter we need to pull... */ switch(**pp) { case ':': iRet = cflineProcessPropFilter(pp, f); break; default: iRet = cflineProcessTradPRIFilter(pp, f); break; } /* we now check if there are some global (BSD-style) filter conditions * and, if so, we copy them over. rgerhards, 2005-10-18 */ if(pDfltProgNameCmp != NULL) if((iRet = rsCStrConstructFromCStr(&(f->pCSProgNameComp), pDfltProgNameCmp)) != RS_RET_OK) return(iRet); if(eDfltHostnameCmpMode != HN_NO_COMP) { f->eHostnameCmpMode = eDfltHostnameCmpMode; if((iRet = rsCStrConstructFromCStr(&(f->pCSHostnameComp), pDfltHostnameCmp)) != RS_RET_OK) return(iRet); } return iRet; } /* process the action part of a selector line * rgerhards, 2007-08-01 */ static rsRetVal cflineDoAction(uchar **p, action_t **ppAction) { DEFiRet; modInfo_t *pMod; omodStringRequest_t *pOMSR; action_t *pAction; void *pModData; assert(p != NULL); assert(ppAction != NULL); /* loop through all modules and see if one picks up the line */ pMod = modGetNxtType(NULL, eMOD_OUT); while(pMod != NULL) { iRet = pMod->mod.om.parseSelectorAct(p, &pModData, &pOMSR); dbgprintf("tried selector action for %s: %d\n", modGetName(pMod), iRet); if(iRet == RS_RET_OK || iRet == RS_RET_SUSPENDED) { if((iRet = addAction(&pAction, pMod, pModData, pOMSR, (iRet == RS_RET_SUSPENDED)? 1 : 0)) == RS_RET_OK) { /* now check if the module is compatible with select features */ if(pMod->isCompatibleWithFeature(sFEATURERepeatedMsgReduction) == RS_RET_OK) pAction->f_ReduceRepeated = bReduceRepeatMsgs; else { dbgprintf("module is incompatible with RepeatedMsgReduction - turned off\n"); pAction->f_ReduceRepeated = 0; } pAction->bEnabled = 1; /* action is enabled */ } break; } else if(iRet != RS_RET_CONFLINE_UNPROCESSED) { /* In this case, the module would have handled the config * line, but some error occured while doing so. This error should * already by reported by the module. We do not try any other * modules on this line, because we found the right one. * rgerhards, 2007-07-24 */ dbgprintf("error %d parsing config line\n", (int) iRet); break; } pMod = modGetNxtType(pMod, eMOD_OUT); } *ppAction = pAction; return iRet; } /* helper to selectorAddListCheckActions() * This is the fucntion to be executed by llExecFunc */ DEFFUNC_llExecFunc(selectorAddListCheckActionsChecker) { DEFiRet; action_t *pAction = (action_t *) pData; assert(pAction != NULL); if(pAction->pMod->needUDPSocket(pAction->pModData) == RS_RET_TRUE) { Forwarding++; } return iRet; } /* loop through a list of actions and perform necessary checks and * housekeeping. This function must only be called when the owning * selector_t looks valid and is not likely to be discarded. However, * if we do not return RS_RET_OK, the caller MUST discard the * owning selector_t. -- rgerhards, 2007-08-02 */ static rsRetVal selectorAddListCheckActions(selector_t *f) { DEFiRet; assert(f != NULL); CHKiRet(llExecFunc(&f->llActList, selectorAddListCheckActionsChecker, NULL)); finalize_it: return iRet; } /* 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 */ static 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) { logerror("warning: selector line without actions will be discarded"); selectorDestruct(f); } else { if((iRet = selectorAddListCheckActions(f)) != RS_RET_OK) { logerror("selector line will be discarded due to error in action(s)"); selectorDestruct(f); goto finalize_it; } /* 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: return iRet; } /* Process a configuration file line in traditional "filter selector" format */ static rsRetVal cflineClassic(uchar *p, selector_t **pfCurr) { DEFiRet; action_t *pAction; selector_t *fCurr; assert(pfCurr != NULL); fCurr = *pfCurr; /* lines starting with '&' have no new filters and just add * new actions to the currently processed selector. */ if(*p == '&') { ++p; /* eat '&' */ skipWhiteSpace(&p); /* on to command */ } else { /* we are finished with the current selector. So 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. In any case, we create a fresh selector for our new filter. * 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 */ CHKiRet(selectorAddList(fCurr)); CHKiRet(selectorConstruct(&fCurr)); /* create "fresh" selector */ CHKiRet(cflineDoFilter(&p, fCurr)); /* pull filters */ } CHKiRet(cflineDoAction(&p, &pAction)); CHKiRet(llAppend(&fCurr->llActList, NULL, (void*) pAction)); finalize_it: *pfCurr = fCurr; return iRet; } /* process a configuration line * I re-did this functon because it was desperately time to do so * rgerhards, 2007-08-01 */ static rsRetVal cfline(uchar *line, selector_t **pfCurr) { DEFiRet; assert(line != NULL); dbgprintf("cfline: '%s'\n", line); /* check type of line and call respective processing */ switch(*line) { case '!': iRet = cflineProcessTagSelector(&line); break; case '+': case '-': iRet = cflineProcessHostSelector(&line); break; case '$': ++line; /* eat '$' */ iRet = cfsysline(line); break; default: iRet = cflineClassic(line, pfCurr); break; } return iRet; } /* Decode a symbolic name to a numeric value */ int decode(uchar *name, struct code *codetab) { register struct code *c; register uchar *p; uchar buf[80]; assert(name != NULL); assert(codetab != NULL); dbgprintf("symbolic name: %s", name); if (isdigit((int) *name)) { dbgprintf("\n"); return (atoi((char*) name)); } strncpy((char*) buf, (char*) name, 79); for (p = buf; *p; p++) if (isupper((int) *p)) *p = tolower((int) *p); for (c = codetab; c->c_name; c++) if (!strcmp((char*) buf, (char*) c->c_name)) { dbgprintf(" ==> %d\n", c->c_val); return (c->c_val); } return (-1); } extern void dbgprintf(char *fmt, ...) __attribute__((format(printf,1, 2))); void dbgprintf(char *fmt, ...) { static int bWasNL = FALSE; va_list ap; if ( !(Debug && debugging_on) ) return; /* The bWasNL handler does not really work. It works if no thread * switching occurs during non-NL messages. Else, things are messed * up. Anyhow, it works well enough to provide useful help during * getting this up and running. It is questionable if the extra effort * is worth fixing it, giving the limited appliability. * rgerhards, 2005-10-25 * I have decided that it is not worth fixing it - especially as it works * pretty well. * rgerhards, 2007-06-15 */ if(bWasNL) { fprintf(stdout, "%8.8d: ", (unsigned int) pthread_self()); } bWasNL = (*(fmt + strlen(fmt) - 1) == '\n') ? TRUE : FALSE; va_start(ap, fmt); vfprintf(stdout, fmt, ap); va_end(ap); fflush(stdout); return; } /* * 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; } /** * getSubString * * Copy a string byte by byte until the occurrence * of a given separator. * * \param ppSrc Pointer to a pointer of the source array of characters. If a separator detected the Pointer points to the next char after the separator. Except if the end of the string is dedected ('\n'). Then it points to the terminator char. * \param pDst Pointer to the destination array of characters. Here the substing will be stored. * \param DstSize Maximum numbers of characters to store. * \param cSep Separator char. * \ret int Returns 0 if no error occured. */ int getSubString(uchar **ppSrc, char *pDst, size_t DstSize, char cSep) { uchar *pSrc = *ppSrc; int iErr = 0; /* 0 = no error, >0 = error */ while(*pSrc != cSep && *pSrc != '\n' && *pSrc != '\0' && DstSize>1) { *pDst++ = *(pSrc)++; DstSize--; } /* check if the Dst buffer was to small */ if (*pSrc != cSep && *pSrc != '\n' && *pSrc != '\0') { dbgprintf("in getSubString, error Src buffer > Dst buffer\n"); iErr = 1; } if (*pSrc == '\0' || *pSrc == '\n') /* this line was missing, causing ppSrc to be invalid when it * was returned in case of end-of-string. rgerhards 2005-07-29 */ *ppSrc = pSrc; else *ppSrc = pSrc+1; *pDst = '\0'; return iErr; } /* print out which socket we are listening on. This is only * a debug aid. rgerhards, 2007-07-02 */ static void debugListenInfo(int fd, char *type) { char *szFamily; int port; struct sockaddr sa; struct sockaddr_in *ipv4; struct sockaddr_in6 *ipv6; socklen_t saLen = sizeof(sa); if(getsockname(fd, &sa, &saLen) == 0) { switch(sa.sa_family) { case PF_INET: szFamily = "IPv4"; ipv4 = (struct sockaddr_in*) &sa; port = ntohs(ipv4->sin_port); break; case PF_INET6: szFamily = "IPv6"; ipv6 = (struct sockaddr_in6*) &sa; port = ntohs(ipv6->sin6_port); break; default: szFamily = "other"; port = -1; break; } dbgprintf("Listening on %s syslogd socket %d (%s/port %d).\n", type, fd, szFamily, port); return; } /* we can not obtain peer info. We are just providing * debug info, so this is no reason to break the program * or do any serious error reporting. */ dbgprintf("Listening on syslogd socket %d - could not obtain peer info.\n", fd); } /* 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(iPri, pMsg, iFlags); MsgDestruct(pMsg); } } /* helper function for mainloop(). This is used to add all module * writeFDsfor Select via llExecFunc(). * rgerhards, 2007-08-02 */ typedef struct selectHelperWriteFDSInfo_s { /* struct for pParam */ fd_set *pWritefds; int *pMaxfds; } selectHelperWriteFDSInfo_t; DEFFUNC_llExecFunc(mainloopAddModWriteFDSforSelect) { DEFiRet; action_t *pAction = (action_t*) pData; selectHelperWriteFDSInfo_t *pState = (selectHelperWriteFDSInfo_t*) pParam; short fdMod; assert(pAction != NULL); assert(pState != NULL); if(pAction->pMod->getWriteFDForSelect(pAction->pModData, &fdMod) == RS_RET_OK) { FD_SET(fdMod, pState->pWritefds); if(fdMod > *pState->pMaxfds) *pState->pMaxfds = fdMod; } return iRet; } /* helper function for mainloop(). This is used to call module action * handlers after select if a fd is writable. * HINT: when we change to the new threading model, this function * is probably no longer needed. * rgerhards, 2007-08-02 */ DEFFUNC_llExecFunc(mainloopCallWithWritableFDsActions) { DEFiRet; action_t *pAction = (action_t*) pData; selectHelperWriteFDSInfo_t *pState = (selectHelperWriteFDSInfo_t*) pParam; short fdMod; assert(pAction != NULL); assert(pState != NULL); if(pAction->pMod->getWriteFDForSelect(pAction->pModData, &fdMod) == RS_RET_OK) { if(FD_ISSET(fdMod, pState->pWritefds)) { if((iRet = pAction->pMod->onSelectReadyWrite(pAction->pModData)) != RS_RET_OK) { dbgprintf("error %d from onSelectReadyWrite() - continuing\n", iRet); } if(--(pState->pMaxfds) == 0) { ABORT_FINALIZE(RS_RET_FINISHED); /* all processed, nothing left to do */ } } } finalize_it: return iRet; } /* process the select() selector array after the successful select. * processing is completed as soon as all selectors needing attention * are processed. * rgerhards, 2007-08-08 */ static rsRetVal processSelectAfter(int maxfds, int nfds, fd_set *pReadfds, fd_set *pWritefds) { DEFiRet; rsRetVal iRetLL; int i; int fd; char line[MAXLINE +1]; selectHelperWriteFDSInfo_t writeFDSInfo; #ifdef SYSLOG_INET selector_t *f; struct sockaddr_storage frominet; socklen_t socklen; uchar fromHost[NI_MAXHOST]; uchar fromHostFQDN[NI_MAXHOST]; int iTCPSess; ssize_t l; #endif /* #ifdef SYSLOG_INET */ /* the following macro is used to decrement the number of to-be-probed * fds and abort this function when we are done with all. */ # define FDPROCESSED() if(--nfds == 0) { ABORT_FINALIZE(RS_RET_OK); } if (nfds < 0) { if (errno != EINTR) logerror("select"); dbgprintf("Select interrupted.\n"); ABORT_FINALIZE(RS_RET_OK); /* we are done in any case */ } if(debugging_on) { dbgprintf("\nSuccessful select, descriptor count = %d, Activity on: ", nfds); for (i = 0; i <= maxfds; ++i) if ( FD_ISSET(i, pReadfds) ) dbgprintf("%d ", i); dbgprintf(("\n")); } #ifdef SYSLOG_INET /* Now check the TCP send sockets. So far, we only see if they become * writable and then change their internal status. No real async * writing is currently done. This code will be replaced once liblogging * is used, thus we try not to focus too much on it. * * IMPORTANT: With the current code, the writefds must be checked first, * because the readfds might have messages to be forwarded, which * rely on the status setting that is done here! * rgerhards 2005-07-20 * * liblogging implementation will not happen as anticipated above. So * this code here will stay for quite a while. * rgerhards, 2006-12-07 */ writeFDSInfo.pWritefds = pWritefds; writeFDSInfo.pMaxfds = &nfds; for(f = Files; f != NULL ; f = f->f_next) { iRetLL = llExecFunc(&f->llActList, mainloopCallWithWritableFDsActions, &writeFDSInfo); if(iRetLL == RS_RET_FINISHED) { ABORT_FINALIZE(RS_RET_OK); /* we are done in this case */ } } #endif /* #ifdef SYSLOG_INET */ #ifdef SYSLOG_UNIXAF for (i = 0; i < nfunix; i++) { if ((fd = funix[i]) != -1 && FD_ISSET(fd, pReadfds)) { int iRcvd; iRcvd = recv(fd, line, MAXLINE - 1, 0); dbgprintf("Message from UNIX socket: #%d\n", fd); if (iRcvd > 0) { printchopped(LocalHostName, line, iRcvd, fd, funixParseHost[i]); } else if (iRcvd < 0 && errno != EINTR) { char errStr[1024]; strerror_r(errno, errStr, sizeof(errStr)); dbgprintf("UNIX socket error: %d = %s.\n", \ errno, errStr); logerror("recvfrom UNIX"); } FDPROCESSED(); } } #endif #ifdef SYSLOG_INET if (finet != NULL && AcceptRemote) { for (i = 0; i < *finet; i++) { if (FD_ISSET(finet[i+1], pReadfds)) { socklen = sizeof(frominet); memset(line, 0xff, sizeof(line)); // TODO: I think we need this for debug only - remove after bug hunt l = recvfrom(finet[i+1], line, MAXLINE - 1, 0, (struct sockaddr *)&frominet, &socklen); if (l > 0) { if(cvthname(&frominet, fromHost, fromHostFQDN) == RS_RET_OK) { dbgprintf("Message from inetd socket: #%d, host: %s\n", finet[i+1], fromHost); /* Here we check if a host is permitted to send us * syslog messages. If it isn't, we do not further * process the message but log a warning (if we are * configured to do this). * rgerhards, 2005-09-26 */ if(isAllowedSender(pAllowedSenders_UDP, (struct sockaddr *)&frominet, (char*)fromHostFQDN)) { printchopped((char*)fromHost, line, l, finet[i+1], 1); } else { if(option_DisallowWarning) { logerrorSz("UDP message from disallowed sender %s discarded", (char*)fromHost); } } } } else if (l < 0 && errno != EINTR && errno != EAGAIN) { char errStr[1024]; strerror_r(errno, errStr, sizeof(errStr)); dbgprintf("INET socket error: %d = %s.\n", errno, errStr); logerror("recvfrom inet"); /* should be harmless */ sleep(1); } FDPROCESSED(); } } } if(sockTCPLstn != NULL && *sockTCPLstn) { for (i = 0; i < *sockTCPLstn; i++) { if (FD_ISSET(sockTCPLstn[i+1], pReadfds)) { dbgprintf("New connect on TCP inetd socket: #%d\n", sockTCPLstn[i+1]); # ifdef USE_GSSAPI if(bEnableTCP == 2) TCPSessGSSAccept(sockTCPLstn[i+1]); else # endif TCPSessAccept(sockTCPLstn[i+1]); FDPROCESSED(); } } /* now check the sessions */ iTCPSess = TCPSessGetNxtSess(-1); while(iTCPSess != -1) { int fdSess; int state; fdSess = pTCPSessions[iTCPSess].sock; if(FD_ISSET(fdSess, pReadfds)) { char buf[MAXLINE]; dbgprintf("tcp session socket with new data: #%d\n", fdSess); /* Receive message */ # ifdef USE_GSSAPI if(bEnableTCP == 2) state = TCPSessGSSRecv(iTCPSess, buf, sizeof(buf)); else # endif state = recv(fdSess, buf, sizeof(buf), 0); if(state == 0) { # ifdef USE_GSSAPI if(bEnableTCP == 2) TCPSessGSSClose(iTCPSess); else { # endif /* process any incomplete frames left over */ TCPSessPrepareClose(iTCPSess); /* Session closed */ TCPSessClose(iTCPSess); # ifdef USE_GSSAPI } # endif } else if(state == -1) { logerrorInt("TCP session %d will be closed, error ignored\n", fdSess); # ifdef USE_GSSAPI if(bEnableTCP == 2) TCPSessGSSClose(iTCPSess); else # endif TCPSessClose(iTCPSess); } else { /* valid data received, process it! */ if(TCPSessDataRcvd(iTCPSess, buf, state) == 0) { /* in this case, something went awfully wrong. * We are instructed to terminate the session. */ logerrorInt("Tearing down TCP Session %d - see " "previous messages for reason(s)\n", iTCPSess); # ifdef USE_GSSAPI if(bEnableTCP == 2) TCPSessGSSClose(iTCPSess); else # endif TCPSessClose(iTCPSess); } } FDPROCESSED(); } iTCPSess = TCPSessGetNxtSess(iTCPSess); } } #endif finalize_it: return iRet; } /* This is the main processing loop. It is called after successful initialization. * When it returns, the syslogd terminates. */ static void mainloop(void) { fd_set readfds; int i; int maxfds; int nfds; #ifdef SYSLOG_INET selectHelperWriteFDSInfo_t writeFDSInfo; fd_set writefds; selector_t *f; int iTCPSess; #endif /* #ifdef SYSLOG_INET */ #ifdef BSD struct timeval tvSelectTimeout; #endif while(!bFinished){ errno = 0; maxfds = 0; FD_ZERO (&readfds); /* first check if we have any internal messages queued and spit them out */ processImInternal(); #ifdef SYSLOG_UNIXAF /* Add the Unix Domain Sockets to the list of read * descriptors. * rgerhards 2005-08-01: we must now check if there are * any local sockets to listen to at all. If the -o option * is given without -a, we do not need to listen at all.. */ /* Copy master connections */ for (i = startIndexUxLocalSockets; i < nfunix; i++) { if (funix[i] != -1) { FD_SET(funix[i], &readfds); if (funix[i]>maxfds) maxfds=funix[i]; } } #endif #ifdef SYSLOG_INET /* Add the UDP listen sockets to the list of read descriptors. */ if(finet != NULL && AcceptRemote) { for (i = 0; i < *finet; i++) { if (finet[i+1] != -1) { if(Debug) debugListenInfo(finet[i+1], "UDP"); FD_SET(finet[i+1], &readfds); if(finet[i+1]>maxfds) maxfds=finet[i+1]; } } } /* Add the TCP listen sockets to the list of read descriptors. */ if(sockTCPLstn != NULL && *sockTCPLstn) { for (i = 0; i < *sockTCPLstn; i++) { /* The if() below is theoretically not needed, but I leave it in * so that a socket may become unsuable during execution. That * feature is not yet supported by the current code base. */ if (sockTCPLstn[i+1] != -1) { if(Debug) debugListenInfo(sockTCPLstn[i+1], "TCP"); FD_SET(sockTCPLstn[i+1], &readfds); if(sockTCPLstn[i+1]>maxfds) maxfds=sockTCPLstn[i+1]; } } /* do the sessions */ iTCPSess = TCPSessGetNxtSess(-1); while(iTCPSess != -1) { int fdSess; fdSess = pTCPSessions[iTCPSess].sock; dbgprintf("Adding TCP Session %d\n", fdSess); FD_SET(fdSess, &readfds); if (fdSess>maxfds) maxfds=fdSess; /* now get next... */ iTCPSess = TCPSessGetNxtSess(iTCPSess); } } /* TODO: activate the code below only if we actually need to check * for outstanding writefds. */ if(1) { /* Now add the TCP output sockets to the writefds set. This implementation * is not optimal (performance-wise) and it should be replaced with something * better in the longer term. I've not yet done this, as this code is * scheduled to be replaced after the liblogging integration. * rgerhards 2005-07-20 */ FD_ZERO(&writefds); writeFDSInfo.pWritefds = &writefds; writeFDSInfo.pMaxfds = &maxfds; for (f = Files; f != NULL ; f = f->f_next) { llExecFunc(&f->llActList, mainloopAddModWriteFDSforSelect, &writeFDSInfo); } } #endif if ( debugging_on ) { dbgprintf("----------------------------------------\n"); dbgprintf("Calling select, active file descriptors (max %d): ", maxfds); for (nfds= 0; nfds <= maxfds; ++nfds) if ( FD_ISSET(nfds, &readfds) ) dbgprintf("%d ", nfds); dbgprintf("\n"); } #define MAIN_SELECT_TIMEVAL NULL #ifdef BSD /* There seems to be a problem with BSD and threads. When running on * multiple threads, a signal will not cause the select call to be * interrrupted. I am not sure if this is by design or an bug (some * information on the web let's me think it is a bug), but that really * does not matter. The issue with our code is that we will not gain * control when rsyslogd is terminated or huped. What I am doing now is * make the select call timeout after 10 seconds, so that we can check * the condition then. Obviously, this causes some sluggish behaviour and * also the loss of some (very few) cpu cycles. Both, I think, are * absolutely acceptable. * rgerhards, 2005-10-26 * TODO: I got some information: this seems to be expected signal() behaviour * we should investigate the use of sigaction() (see klogd.c for an sample). * rgerhards, 2007-06-22 * rgerhards, 2007-09-11: code has been converted to sigaction() now. We need * to re-check on BSD, I think the issue is now solved. */ tvSelectTimeout.tv_sec = 10; tvSelectTimeout.tv_usec = 0; # undef MAIN_SELECT_TIMEVAL # define MAIN_SELECT_TIMEVAL &tvSelectTimeout #endif #ifdef SYSLOG_INET #define MAIN_SELECT_WRITEFDS (fd_set *) &writefds #else #define MAIN_SELECT_WRITEFDS NULL #endif nfds = select(maxfds+1, (fd_set *) &readfds, MAIN_SELECT_WRITEFDS, (fd_set *) NULL, MAIN_SELECT_TIMEVAL); if(bRequestDoMark) { domark(); bRequestDoMark = 0; /* We do not use continue, because domark() is carried out * only when something else happened. */ } if(restart) { dbgprintf("\nReceived SIGHUP, reloading rsyslogd.\n"); /* worker thread is stopped as part of init() */ init(); restart = 0; continue; } if (nfds == 0) { dbgprintf("No select activity.\n"); continue; } processSelectAfter(maxfds, nfds, &readfds, MAIN_SELECT_WRITEFDS); #undef MAIN_SELECT_TIMEVAL #undef MAIN_SELECT_WRITEFDS } } /* 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() { /* 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 } } /* load build-in modules * very first version begun on 2007-07-23 by rgerhards */ static rsRetVal loadBuildInModules(void) { DEFiRet; if((iRet = doModInit(modInitFile, (uchar*) "builtin-file", NULL)) != RS_RET_OK) return iRet; #ifdef SYSLOG_INET if((iRet = doModInit(modInitFwd, (uchar*) "builtin-fwd", NULL)) != RS_RET_OK) return iRet; #endif if((iRet = doModInit(modInitShell, (uchar*) "builtin-shell", NULL)) != RS_RET_OK) return iRet; if((iRet = doModInit(modInitDiscard, (uchar*) "builtin-discard", NULL)) != RS_RET_OK) return iRet; /* 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 = doModInit(modInitUsrMsg, (uchar*) "builtin-usrmsg", NULL)) != RS_RET_OK) return iRet; /* 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 *)"mainmsgqueuesize", 0, eCmdHdlrInt, NULL, &iMainMsgQueueSize, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"repeatedmsgreduction", 0, eCmdHdlrBinary, NULL, &bReduceRepeatMsgs, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"actionexeconlywhenpreviousissuspended", 0, eCmdHdlrBinary, NULL, &bActExecWhenPrevSusp, 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 *)"dropmsgswithmaliciousdnsptrrecords", 0, eCmdHdlrBinary, NULL, &bDropMalPTRMsgs, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"droptrailinglfonreception", 0, eCmdHdlrBinary, NULL, &bDropTrailingLF, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"template", 0, eCmdHdlrCustomHandler, doNameLine, (void*)DIR_TEMPLATE, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"outchannel", 0, eCmdHdlrCustomHandler, doNameLine, (void*)DIR_OUTCHANNEL, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"allowedsender", 0, eCmdHdlrCustomHandler, doNameLine, (void*)DIR_ALLOWEDSENDER, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"modload", 0, eCmdHdlrCustomHandler, doModLoad, NULL, NULL)); CHKiRet(regCfSysLineHdlr((uchar *)"includeconfig", 0, eCmdHdlrCustomHandler, 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)); #if defined(SYSLOG_INET) && defined(USE_GSSAPI) CHKiRet(regCfSysLineHdlr((uchar *)"gsslistenservicename", 0, eCmdHdlrGetWord, NULL, &gss_listen_service_name, NULL)); #endif finalize_it: return iRet; } /* 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 #ifdef SYSLOG_INET printf("\tSYSLOG_INET (Internet/remote support):\tYes\n"); #else printf("\tSYSLOG_INET (Internet/remote support):\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 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() { DEFiRet; uchar *pTmp; /* 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 */ } 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 */ } /* 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 default templates * we use template names with a SP in front - these * can NOT be generated via the configuration file */ pTmp = template_TraditionalFormat; tplAddLine(" TradFmt", &pTmp); pTmp = template_WallFmt; tplAddLine(" WallFmt", &pTmp); pTmp = template_StdFwdFmt; tplAddLine(" StdFwdFmt", &pTmp); pTmp = template_StdUsrMsgFmt; tplAddLine(" StdUsrMsgFmt", &pTmp); pTmp = template_StdDBFmt; tplLastStaticInit(tplAddLine(" StdDBFmt", &pTmp)); pTmp = template_StdPgSQLFmt; tplLastStaticInit(tplAddLine(" StdPgSQLFmt", &pTmp)); dbgprintf("Starting.\n"); 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 */ mainloop(); } static void sigusr2Dummy(int sig) { dbgprintf("sigusr2Dummy called!\n"); } /* This is the main entry point into rsyslogd. Over time, we should try to * modularize it a bit more... */ int main(int argc, char **argv) { register int i; register char *p; int num_fds; int ch; struct hostent *hent; extern int optind; extern char *optarg; struct sigaction sigAct; #if IMMARK /* see comment for #if 0 below (towards end of function) */ pthread_t thrdMain; sigset_t sigSet; #endif #ifdef MTRACE mtrace(); /* this is a debug aid for leak detection - either remove * or put in conditional compilation. 2005-01-18 RGerhards */ #endif ppid = getpid(); if(chdir ("/") != 0) fprintf(stderr, "Can not do 'cd /' - still trying to run\n"); for (i = 1; i < MAXFUNIX; i++) { funixn[i] = ""; funix[i] = -1; } /* END core initializations */ while ((ch = getopt(argc, argv, "46Aa:dehi:f:g:l:m:nop:qQr::s:t:u:vwx")) != EOF) { switch((char)ch) { case '4': family = PF_INET; break; case '6': family = PF_INET6; break; case 'A': send_to_all++; break; case 'a': if (nfunix < MAXFUNIX) if(*optarg == ':') { funixParseHost[nfunix] = 1; funixn[nfunix++] = optarg+1; } else { funixParseHost[nfunix] = 0; funixn[nfunix++] = optarg; } else fprintf(stderr, "rsyslogd: Out of descriptors, ignoring %s\n", optarg); break; case 'd': /* debug */ Debug = 1; break; case 'e': /* log every message (no repeat message supression) */ logEveryMsg = 1; break; case 'f': /* configuration file */ ConfFile = (uchar*) optarg; break; case 'g': /* enable tcp gssapi logging */ #if defined(SYSLOG_INET) && defined(USE_GSSAPI) configureTCPListen(optarg); bEnableTCP = 2; #else fprintf(stderr, "rsyslogd: -g not valid - not compiled with gssapi support"); #endif break; case 'h': NoHops = 0; break; case 'i': /* pid file name */ PidFile = optarg; break; case 'l': if (LocalHosts) { fprintf (stderr, "rsyslogd: Only one -l argument allowed," \ "the first one is taken.\n"); } else { LocalHosts = crunch_list(optarg); } break; case 'm': /* mark interval */ MarkInterval = atoi(optarg) * 60; break; case 'n': /* don't fork */ NoFork = 1; break; case 'o': /* omit local logging (/dev/log) */ startIndexUxLocalSockets = 1; break; case 'p': /* path to regular log socket */ funixn[0] = optarg; break; case 'q': /* add hostname if DNS resolving has failed */ ACLAddHostnameOnFail = 1; break; case 'Q': /* dont resolve hostnames in ACL to IPs */ ACLDontResolve = 1; break; case 'r': /* accept remote messages */ #ifdef SYSLOG_INET AcceptRemote = 1; if(optarg == NULL) LogPort = "0"; else LogPort = optarg; #else fprintf(stderr, "rsyslogd: -r not valid - not compiled with network support"); #endif break; case 's': if (StripDomains) { fprintf (stderr, "rsyslogd: Only one -s argument allowed," \ "the first one is taken.\n"); } else { StripDomains = crunch_list(optarg); } break; case 't': /* enable tcp logging */ #ifdef SYSLOG_INET configureTCPListen(optarg); #else fprintf(stderr, "rsyslogd: -t not valid - not compiled with network support"); #endif break; case 'u': /* misc user settings */ if(atoi(optarg) == 1) bParseHOSTNAMEandTAG = 0; break; case 'v': printVersion(); exit(0); /* exit for -v option - so this is a "good one" */ case 'w': /* disable disallowed host warnigs */ option_DisallowWarning = 0; break; case 'x': /* disable dns for remote messages */ DisableDNS = 1; break; case '?': default: usage(); } } if ((argc -= optind)) usage(); checkPermissions(); 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 */ if ( !Debug ) { dbgprintf("Writing pidfile.\n"); 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 */ } } /* if ( !Debug ) */ myPid = getpid(); /* save our pid for further testing (also used for messages) */ gethostname(LocalHostName, sizeof(LocalHostName)); if ( (p = strchr(LocalHostName, '.')) ) { *p++ = '\0'; LocalDomain = p; } else { LocalDomain = ""; /* 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(LocalHostName); if(hent) { snprintf(LocalHostName, sizeof(LocalHostName), "%s", hent->h_name); if ( (p = strchr(LocalHostName, '.')) ) { *p++ = '\0'; LocalDomain = p; } } } /* Convert to lower case to recognize the correct domain laterly */ for (p = (char *)LocalDomain; *p ; p++) if (isupper((int) *p)) *p = (char)tolower((int)*p); memset(&sigAct, 0, sizeof (sigAct)); sigemptyset(&sigAct.sa_mask); 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 = domarkAlarmHdlr; sigaction(SIGALRM, &sigAct, NULL); sigAct.sa_handler = Debug ? debug_switch : SIG_IGN; sigaction(SIGUSR1, &sigAct, NULL); sigAct.sa_handler = sigusr2Dummy; sigaction(SIGUSR2, &sigAct, NULL); sigAct.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sigAct, NULL); sigaction(SIGXFSZ, &sigAct, NULL); /* do not abort if 2gig file limit is hit */ (void) alarm(TIMERINTVL); #if IMMARK i = pthread_create(&thrdMain, NULL, immark_runInput, NULL); dbgprintf("\"main\" thread started with state %d.\n", i); #endif mainThread(); #if 0 /* This commented-out code was once used to spawn a separate thread * for the mainThread(). This was initially done to solve a problem that not * really existed. Thus the code is now commented out. I do not remove it yet, * because there may be use for it in the not too distant future. If it is * still commented out in a year's time, that's a good indication it should * be removed! -- rgerhards, 2007-10-17 */ i = pthread_create(&thrdMain, NULL, mainThread, NULL); dbgprintf("\"main\" thread started with state %d.\n", i); /* we block all signals - they will be processed by the "main"-thread. This most * closely resembles previous behaviour. TODO: think about optimizing it, some * signals may better be delivered here. rgerhards, 2007-10-08 */ sigfillset(&sigSet); pthread_sigmask(SIG_BLOCK, &sigSet, NULL); /* see comment in mainThread on why we start thread and then immediately * do a blocking wait on it - it makese sense... ;) rgerhards, 2007-10-08 */ #endif /* do any de-init's that need to be done AFTER this comment */ #if IMMARK dbgprintf("waiting to join thrdMain\n"); pthread_kill(thrdMain, SIGUSR2); pthread_join(thrdMain, NULL); dbgprintf("joined thrdMain\n"); #endif dbgprintf("reaching die\n"); die(bFinished); return 0; } /* vi:set ai: */