diff options
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | msg.c | 507 | ||||
-rw-r--r-- | msg.h | 3 | ||||
-rw-r--r-- | omfile.c | 437 | ||||
-rw-r--r-- | omfile.h | 35 | ||||
-rw-r--r-- | omfwd.c | 1 | ||||
-rw-r--r-- | omshell.c | 2 | ||||
-rw-r--r-- | omshell.h | 2 | ||||
-rw-r--r-- | syslogd.c | 1098 | ||||
-rw-r--r-- | syslogd.h | 9 | ||||
-rw-r--r-- | template.c | 211 | ||||
-rw-r--r-- | template.h | 5 |
12 files changed, 1219 insertions, 1093 deletions
diff --git a/Makefile.am b/Makefile.am index d098191b..1bb3ca0a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -7,7 +7,7 @@ rfc3195d_SOURCES=rfc3195d.c rsyslog.h man_MANS = rfc3195d.8 rklogd.8 rsyslogd.8 rsyslog.conf.5 -rsyslogd_SOURCES=syslogd.c pidfile.c template.c outchannel.c stringbuf.c srUtils.c parse.c syslogd-types.h template.h outchannel.h syslogd.h stringbuf.h parse.h srUtils.h liblogging-stub.h net.c net.h msg.c msg.h omshell.c omshell.h omusrmsg.c omusrmsg.h ommysql.c ommysql.h omfwd.c omfwd.h tcpsyslog.c tcpsyslog.h +rsyslogd_SOURCES=syslogd.c pidfile.c template.c outchannel.c stringbuf.c srUtils.c parse.c syslogd-types.h template.h outchannel.h syslogd.h stringbuf.h parse.h srUtils.h liblogging-stub.h net.c net.h msg.c msg.h omshell.c omshell.h omusrmsg.c omusrmsg.h ommysql.c ommysql.h omfwd.c omfwd.h tcpsyslog.c tcpsyslog.h omfile.h omfile.c rsyslogd_CPPFLAGS=$(mysql_includes) rsyslogd_LDADD=$(mysql_libs) $(zlib_libs) $(pthreads_libs) @@ -37,6 +37,7 @@ #include "rsyslog.h" #include "syslogd.h" #include "srUtils.h" +#include "template.h" #include "msg.h" /* The following functions will support advanced output module @@ -1131,6 +1132,512 @@ void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg) } +/* Decode a priority into textual information like auth.emerg. + * The variable pRes must point to a user-supplied buffer and + * pResLen must contain its size. The pointer to the buffer + * is also returned, what makes this functiona suitable for + * use in printf-like functions. + * Note: a buffer size of 20 characters is always sufficient. + * Interface to this function changed 2007-06-15 by RGerhards + */ +char *textpri(char *pRes, size_t pResLen, int pri) +{ + syslogCODE *c_pri, *c_fac; + + assert(pRes != NULL); + assert(pResLen > 0); + + for (c_fac = rs_facilitynames; c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri)<<3); c_fac++); + for (c_pri = rs_prioritynames; c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++); + + snprintf (pRes, pResLen, "%s.%s<%d>", c_fac->c_name, c_pri->c_name, pri); + + return pRes; +} + + +/* This function returns the current date in different + * variants. It is used to construct the $NOW series of + * system properties. The returned buffer must be freed + * by the caller when no longer needed. If the function + * can not allocate memory, it returns a NULL pointer. + * Added 2007-07-10 rgerhards + */ +typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_MINUTE } eNOWType; +#define tmpBUFSIZE 16 /* size of formatting buffer */ +static uchar *getNOW(eNOWType eNow) +{ + uchar *pBuf; + struct syslogTime t; + + if((pBuf = (uchar*) malloc(sizeof(uchar) * tmpBUFSIZE)) == NULL) { + glblHadMemShortage = 1; + return NULL; + } + + getCurrTime(&t); + switch(eNow) { + case NOW_NOW: + snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); + break; + case NOW_YEAR: + snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d", t.year); + break; + case NOW_MONTH: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.month); + break; + case NOW_DAY: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.day); + break; + case NOW_HOUR: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.hour); + break; + case NOW_MINUTE: + snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute); + break; + } + + return(pBuf); +} +#undef tmpBUFSIZE /* clean up */ + + +/* This function returns a string-representation of the + * requested message property. This is a generic function used + * to abstract properties so that these can be easier + * queried. Returns NULL if property could not be found. + * Actually, this function is a big if..elseif. What it does + * is simply to map property names (from MonitorWare) to the + * message object data fields. + * + * In case we need string forms of propertis we do not + * yet have in string form, we do a memory allocation that + * is sufficiently large (in all cases). Once the string + * form has been obtained, it is saved until the Msg object + * is finally destroyed. This is so that we save the processing + * time in the (likely) case that this property is requested + * again. It also saves us a lot of dynamic memory management + * issues in the upper layers, because we so can guarantee that + * the buffer will remain static AND available during the lifetime + * of the object. Please note that both the max size allocation as + * well as keeping things in memory might like look like a + * waste of memory (some might say it actually is...) - we + * deliberately accept this because performance is more important + * to us ;) + * rgerhards 2004-11-18 + * Parameter "bMustBeFreed" is set by this function. It tells the + * caller whether or not the string returned must be freed by the + * caller itself. It is is 0, the caller MUST NOT free it. If it is + * 1, the caller MUST free 1. Handling this wrongly leads to either + * a memory leak of a program abort (do to double-frees or frees on + * the constant memory pool). So be careful to do it right. + * rgerhards 2004-11-23 + * regular expression support contributed by Andres Riancho merged + * on 2005-09-13 + * changed so that it now an be called without a template entry (NULL). + * In this case, only the (unmodified) property is returned. This will + * be used in selector line processing. + * rgerhards 2005-09-15 + */ +char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + rsCStrObj *pCSPropName, unsigned short *pbMustBeFreed) +{ + char *pName; + char *pRes; /* result pointer */ + char *pBufStart; + char *pBuf; + int iLen; + +#ifdef FEATURE_REGEXP + /* Variables necessary for regular expression matching */ + size_t nmatch = 2; + regmatch_t pmatch[2]; +#endif + + assert(pMsg != NULL); + assert(pbMustBeFreed != NULL); + + if(pCSPropName == NULL) { + assert(pTpe != NULL); + pName = pTpe->data.field.pPropRepl; + } else { + pName = (char*) rsCStrGetSzStr(pCSPropName); + } + *pbMustBeFreed = 0; + + /* sometimes there are aliases to the original MonitoWare + * property names. These come after || in the ifs below. */ + if(!strcmp(pName, "msg")) { + pRes = getMSG(pMsg); + } else if(!strcmp(pName, "rawmsg")) { + pRes = getRawMsg(pMsg); + } else if(!strcmp(pName, "UxTradMsg")) { + pRes = getUxTradMsg(pMsg); + } else if(!strcmp(pName, "FROMHOST")) { + pRes = getRcvFrom(pMsg); + } else if(!strcmp(pName, "source") + || !strcmp(pName, "HOSTNAME")) { + pRes = getHOSTNAME(pMsg); + } else if(!strcmp(pName, "syslogtag")) { + pRes = getTAG(pMsg); + } else if(!strcmp(pName, "PRI")) { + pRes = getPRI(pMsg); + } else if(!strcmp(pName, "PRI-text")) { + pBuf = malloc(20 * sizeof(char)); + if(pBuf == NULL) { + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } else { + *pbMustBeFreed = 1; + pRes = textpri(pBuf, 20, getPRIi(pMsg)); + } + } else if(!strcmp(pName, "iut")) { + pRes = "1"; /* always 1 for syslog messages (a MonitorWare thing;)) */ + } else if(!strcmp(pName, "syslogfacility")) { + pRes = getFacility(pMsg); + } else if(!strcmp(pName, "syslogfacility-text")) { + pRes = getFacilityStr(pMsg); + } else if(!strcmp(pName, "syslogseverity") || !strcmp(pName, "syslogpriority")) { + pRes = getSeverity(pMsg); + } else if(!strcmp(pName, "syslogseverity-text") || !strcmp(pName, "syslogpriority-text")) { + pRes = getSeverityStr(pMsg); + } else if(!strcmp(pName, "timegenerated")) { + pRes = getTimeGenerated(pMsg, pTpe->data.field.eDateFormat); + } else if(!strcmp(pName, "timereported") + || !strcmp(pName, "TIMESTAMP")) { + pRes = getTimeReported(pMsg, pTpe->data.field.eDateFormat); + } else if(!strcmp(pName, "programname")) { + pRes = getProgramName(pMsg); + } else if(!strcmp(pName, "PROTOCOL-VERSION")) { + pRes = getProtocolVersionString(pMsg); + } else if(!strcmp(pName, "STRUCTURED-DATA")) { + pRes = getStructuredData(pMsg); + } else if(!strcmp(pName, "APP-NAME")) { + pRes = getAPPNAME(pMsg); + } else if(!strcmp(pName, "PROCID")) { + pRes = getPROCID(pMsg); + } else if(!strcmp(pName, "MSGID")) { + pRes = getMSGID(pMsg); + /* here start system properties (those, that do not relate to the message itself */ + } else if(!strcmp(pName, "$NOW")) { + if((pRes = (char*) getNOW(NOW_NOW)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp(pName, "$YEAR")) { + if((pRes = (char*) getNOW(NOW_YEAR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp(pName, "$MONTH")) { + if((pRes = (char*) getNOW(NOW_MONTH)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp(pName, "$DAY")) { + if((pRes = (char*) getNOW(NOW_DAY)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp(pName, "$HOUR")) { + if((pRes = (char*) getNOW(NOW_HOUR)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else if(!strcmp(pName, "$MINUTE")) { + if((pRes = (char*) getNOW(NOW_MINUTE)) == NULL) { + return "***OUT OF MEMORY***"; + } else + *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ + } else { + /* there is no point in continuing, we may even otherwise render the + * error message unreadable. rgerhards, 2007-07-10 + */ + return "**INVALID PROPERTY NAME**"; + } + + /* If we did not receive a template pointer, we are already done... */ + if(pTpe == NULL) { + return pRes; + } + + /* Now check if we need to make "temporary" transformations (these + * are transformations that do not go back into the message - + * memory must be allocated for them!). + */ + + /* substring extraction */ + /* first we check if we need to extract by field number + * rgerhards, 2005-12-22 + */ + if(pTpe->data.field.has_fields == 1) { + size_t iCurrFld; + char *pFld; + char *pFldEnd; + /* first, skip to the field in question. The field separator + * is always one character and is stored in the template entry. + */ + iCurrFld = 1; + pFld = pRes; + while(*pFld && iCurrFld < pTpe->data.field.iToPos) { + /* skip fields until the requested field or end of string is found */ + while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim) + ++pFld; /* skip to field terminator */ + if(*pFld == pTpe->data.field.field_delim) { + ++pFld; /* eat it */ + ++iCurrFld; + } + } + dprintf("field requested %d, field found %d\n", pTpe->data.field.iToPos, iCurrFld); + + if(iCurrFld == pTpe->data.field.iToPos) { + /* field found, now extract it */ + /* first of all, we need to find the end */ + pFldEnd = pFld; + while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim) + ++pFldEnd; + --pFldEnd; /* we are already at the delimiter - so we need to + * step back a little not to copy it as part of the field. */ + /* we got our end pointer, now do the copy */ + /* TODO: code copied from below, this is a candidate for a separate function */ + iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + /* now copy */ + memcpy(pBuf, pFld, iLen); + pBuf[iLen] = '\0'; /* terminate it */ + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; + if(*(pFldEnd+1) != '\0') + ++pFldEnd; /* OK, skip again over delimiter char */ + } else { + /* field not found, return error */ + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**FIELD NOT FOUND**"; + } + } else if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { + /* we need to obtain a private copy */ + int iFrom, iTo; + iFrom = pTpe->data.field.iFromPos; + iTo = pTpe->data.field.iToPos; + /* need to zero-base to and from (they are 1-based!) */ + if(iFrom > 0) + --iFrom; + if(iTo > 0) + --iTo; + iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */ + pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); + if(pBuf == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + if(iFrom) { + /* skip to the start of the substring (can't do pointer arithmetic + * because the whole string might be smaller!!) + */ + // ++iFrom; /* nbr of chars to skip! */ + while(*pRes && iFrom) { + --iFrom; + ++pRes; + } + } + /* OK, we are at the begin - now let's copy... */ + while(*pRes && iLen) { + *pBuf++ = *pRes; + ++pRes; + --iLen; + } + *pBuf = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBufStart; + *pbMustBeFreed = 1; +#ifdef FEATURE_REGEXP + } else { + /* Check for regular expressions */ + if (pTpe->data.field.has_regex != 0) { + if (pTpe->data.field.has_regex == 2) + /* Could not compile regex before! */ + return + "**NO MATCH** **BAD REGULAR EXPRESSION**"; + + dprintf("debug: String to match for regex is: %s\n", + pRes); + + if (0 != regexec(&pTpe->data.field.re, pRes, nmatch, + pmatch, 0)) { + /* we got no match! */ + return "**NO MATCH**"; + } else { + /* Match! */ + /* I need to malloc pB */ + int iLenBuf; + char *pB; + + iLenBuf = pmatch[1].rm_eo - pmatch[1].rm_so; + pB = (char *) malloc((iLenBuf + 1) * sizeof(char)); + + if (pB == NULL) { + if (*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY ALLOCATING pBuf**"; + } + + /* Lets copy the matched substring to the buffer */ + memcpy(pB, pRes + pmatch[1].rm_so, iLenBuf); + pB[iLenBuf] = '\0';/* terminate string, did not happen before */ + + if (*pbMustBeFreed == 1) + free(pRes); + pRes = pB; + *pbMustBeFreed = 1; + } + } +#endif /* #ifdef FEATURE_REGEXP */ + } + + /* case conversations (should go after substring, because so we are able to + * work on the smallest possible buffer). + */ + if(pTpe->data.field.eCaseConv != tplCaseConvNo) { + /* we need to obtain a private copy */ + int iBufLen = strlen(pRes); + char *pBStart; + char *pB; + pBStart = pB = malloc((iBufLen + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + while(*pRes) { + *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ? + toupper(*pRes) : tolower(*pRes); + /* currently only these two exist */ + ++pRes; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + + /* now do control character dropping/escaping/replacement + * Only one of these can be used. If multiple options are given, the + * result is random (though currently there obviously is an order of + * preferrence, see code below. But this is NOT guaranteed. + * RGerhards, 2006-11-17 + */ + if(pTpe->data.field.options.bDropCC) { + char *pSrc = pRes; + char *pDst = pRes; + + while(*pSrc) { + if(!iscntrl((int) *pSrc)) + *pDst++ = *pSrc; + ++pSrc; + } + *pDst = '\0'; + } else if(pTpe->data.field.options.bSpaceCC) { + char *pB = pRes; + while(*pB) { + if(iscntrl((int) *pB)) + *pB = ' '; + ++pB; + } + } else if(pTpe->data.field.options.bEscapeCC) { + /* we must first count how many control charactes are + * present, because we need this to compute the new string + * buffer length. While doing so, we also compute the string + * length. + */ + int iNumCC = 0; + int iLenBuf = 0; + char *pB; + + for(pB = pRes ; *pB ; ++pB) { + ++iLenBuf; + if(iscntrl((int) *pB)) + ++iNumCC; + } + + if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */ + /* OK, let's do the escaping... */ + char *pBStart; + char szCCEsc[8]; /* buffer for escape sequence */ + int i; + + iLenBuf += iNumCC * 4; + pBStart = pB = malloc((iLenBuf + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + while(*pRes) { + if(iscntrl((int) *pRes)) { + snprintf(szCCEsc, sizeof(szCCEsc), "#%3.3d", *pRes); + for(i = 0 ; i < 4 ; ++i) + *pB++ = szCCEsc[i]; + } else { + *pB++ = *pRes; + } + ++pRes; + } + *pB = '\0'; + if(*pbMustBeFreed == 1) + free(pRes); + pRes = pBStart; + *pbMustBeFreed = 1; + } + } + + /* Now drop last LF if present (pls note that this must not be done + * if bEscapeCC was set! + */ + if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) { + int iLn = strlen(pRes); + char *pB; + if(*(pRes + iLn - 1) == '\n') { + /* we have a LF! */ + /* check if we need to obtain a private copy */ + if(pbMustBeFreed == 0) { + /* ok, original copy, need a private one */ + pB = malloc((iLn + 1) * sizeof(char)); + if(pB == NULL) { + if(*pbMustBeFreed == 1) + free(pRes); + *pbMustBeFreed = 0; + return "**OUT OF MEMORY**"; + } + memcpy(pB, pRes, iLn - 1); + pRes = pB; + *pbMustBeFreed = 1; + } + *(pRes + iLn - 1) = '\0'; /* drop LF ;) */ + } + } + + /*dprintf("MsgGetProp(\"%s\"): \"%s\"\n", pName, pRes); only for verbose debug logging */ + return(pRes); +} + + /* * vi:set ai: */ @@ -145,6 +145,9 @@ void MsgSetMSG(msg_t *pMsg, char* pszMSG); void MsgSetRawMsg(msg_t *pMsg, char* pszRawMsg); void moveHOSTNAMEtoTAG(msg_t *pM); char *getMSGID(msg_t *pM); +char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, + rsCStrObj *pCSPropName, unsigned short *pbMustBeFreed); +char *textpri(char *pRes, size_t pResLen, int pri); #endif /* #ifndef MSG_H_INCLUDED */ /* diff --git a/omfile.c b/omfile.c new file mode 100644 index 00000000..f2d7814a --- /dev/null +++ b/omfile.c @@ -0,0 +1,437 @@ +/* omfile.c + * This is the implementation of the build-in file output module. + * + * Handles: F_CONSOLE, F_TTY, F_FILE, F_PIPE + * + * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) + * This file is under development and has not yet arrived at being fully + * self-contained and a real object. So far, it is mostly an excerpt + * of the "old" message code without any modifications. However, it + * helps to have things at the right place one we go to the meat of it. + * + * Copyright 2007 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 <stdio.h> +#include <stdarg.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <assert.h> +#include <errno.h> + +#include <unistd.h> +#include <sys/file.h> +//#include <sys/param.h> + +#include "rsyslog.h" +#include "syslogd.h" +#include "syslogd-types.h" +#include "srUtils.h" +#include "template.h" +#include "omfile.h" + + +/* rgerhards 2005-06-21: Try to resolve a size limit + * situation. This first runs the command, and then + * checks if we are still above the treshold. + * returns 0 if ok, 1 otherwise + * TODO: consider moving the initial check in here, too + */ +int resolveFileSizeLimit(selector_t *f) +{ + uchar *pParams; + uchar *pCmd; + uchar *p; + off_t actualFileSize; + assert(f != NULL); + + if(f->f_un.f_file.f_sizeLimitCmd == NULL) + return 1; /* nothing we can do in this case... */ + + /* the execProg() below is probably not great, but at least is is + * fairly secure now. Once we change the way file size limits are + * handled, we should also revisit how this command is run (and + * with which parameters). rgerhards, 2007-07-20 + */ + /* we first check if we have command line parameters. We assume this, + * when we have a space in the program name. If we find it, everything after + * the space is treated as a single argument. + */ + if((pCmd = (uchar*)strdup((char*)f->f_un.f_file.f_sizeLimitCmd)) == NULL) { + /* there is not much we can do - we make syslogd close the file in this case */ + glblHadMemShortage = 1; + return 1; + } + + for(p = pCmd ; *p && *p != ' ' ; ++p) { + /* JUST SKIP */ + } + + if(*p == ' ') { + *p = '\0'; /* pretend string-end */ + pParams = p+1; + } else + pParams = NULL; + + execProg(pCmd, 1, pParams); + + f->f_file = open(f->f_un.f_file.f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + f->f_un.f_file.fCreateMode); + + actualFileSize = lseek(f->f_file, 0, SEEK_END); + if(actualFileSize >= f->f_un.f_file.f_sizeLimit) { + /* OK, it didn't work out... */ + return 1; + } + + return 0; +} + + +/* This function deletes an entry from the dynamic file name + * cache. A pointer to the cache must be passed in as well + * as the index of the to-be-deleted entry. This index may + * point to an unallocated entry, in whcih case the + * function immediately returns. Parameter bFreeEntry is 1 + * if the entry should be free()ed and 0 if not. + */ +static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry) +{ + assert(pCache != NULL); + + if(pCache[iEntry] == NULL) + return; + + dprintf("Removed entry %d for file '%s' from dynaCache.\n", iEntry, + pCache[iEntry]->pName == NULL ? "[OPEN FAILED]" : (char*)pCache[iEntry]->pName); + /* if the name is NULL, this is an improperly initilized entry which + * needs to be discarded. In this case, neither the file is to be closed + * not the name to be freed. + */ + if(pCache[iEntry]->pName != NULL) { + close(pCache[iEntry]->fd); + free(pCache[iEntry]->pName); + pCache[iEntry]->pName = NULL; + } + + if(bFreeEntry) { + free(pCache[iEntry]); + pCache[iEntry] = NULL; + } +} + + +/* This function frees the dynamic file name cache. + */ +static void dynaFileFreeCache(selector_t *f) +{ + register int i; + assert(f != NULL); + + for(i = 0 ; i < f->f_un.f_file.iCurrCacheSize ; ++i) { + dynaFileDelCacheEntry(f->f_un.f_file.dynCache, i, 1); + } + + free(f->f_un.f_file.dynCache); +} + + +/* This function handles dynamic file names. It generates a new one + * based on the current message, checks if that file is already open + * and, if not, does everything needed to switch to the new one. + * Function returns 0 if all went well and non-zero otherwise. + * On successful return f->f_file must point to the correct file to + * be written. + * This is a helper to writeFile(). rgerhards, 2007-07-03 + */ +static int prepareDynFile(selector_t *f) +{ + uchar *newFileName; + time_t ttOldest; /* timestamp of oldest element */ + int iOldest; + int i; + int iFirstFree; + dynaFileCacheEntry **pCache; + + assert(f != NULL); + if((newFileName = tplToString(f->f_un.f_file.pTpl, f->f_pMsg)) == NULL) { + /* memory shortage - there is nothing we can do to resolve it. + * We silently ignore it, this is probably the best we can do. + */ + glblHadMemShortage = TRUE; + dprintf("prepareDynfile(): could not create file name, discarding this request\n"); + return -1; + } + + pCache = f->f_un.f_file.dynCache; + + /* first check, if we still have the current file + * I *hope* this will be a performance enhancement. + */ + if( (f->f_un.f_file.iCurrElt != -1) + && !strcmp((char*) newFileName, + (char*) pCache[f->f_un.f_file.iCurrElt])) { + /* great, we are all set */ + free(newFileName); + pCache[f->f_un.f_file.iCurrElt]->lastUsed = time(NULL); /* update timestamp for LRU */ + return 0; + } + + /* ok, no luck. Now let's search the table if we find a matching spot. + * While doing so, we also prepare for creation of a new one. + */ + iFirstFree = -1; /* not yet found */ + iOldest = 0; /* we assume the first element to be the oldest - that will change as we loop */ + ttOldest = time(NULL) + 1; /* there must always be an older one */ + for(i = 0 ; i < f->f_un.f_file.iCurrCacheSize ; ++i) { + if(pCache[i] == NULL) { + if(iFirstFree == -1) + iFirstFree = i; + } else { /* got an element, let's see if it matches */ + if(!strcmp((char*) newFileName, (char*) pCache[i]->pName)) { + /* we found our element! */ + f->f_file = pCache[i]->fd; + f->f_un.f_file.iCurrElt = i; + free(newFileName); + pCache[i]->lastUsed = time(NULL); /* update timestamp for LRU */ + return 0; + } + /* did not find it - so lets keep track of the counters for LRU */ + if(pCache[i]->lastUsed < ttOldest) { + ttOldest = pCache[i]->lastUsed; + iOldest = i; + } + } + } + + /* we have not found an entry */ + if(iFirstFree == -1 && (f->f_un.f_file.iCurrCacheSize < f->f_un.f_file.iDynaFileCacheSize)) { + /* there is space left, so set it to that index */ + iFirstFree = f->f_un.f_file.iCurrCacheSize++; + } + + if(iFirstFree == -1) { + dynaFileDelCacheEntry(pCache, iOldest, 0); + iFirstFree = iOldest; /* this one *is* now free ;) */ + } else { + /* we need to allocate memory for the cache structure */ + pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry)); + if(pCache[iFirstFree] == NULL) { + glblHadMemShortage = TRUE; + dprintf("prepareDynfile(): could not alloc mem, discarding this request\n"); + free(newFileName); + return -1; + } + } + + /* Ok, we finally can open the file */ + if(access((char*)newFileName, F_OK) == 0) { + /* file already exists */ + f->f_file = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + f->f_un.f_file.fCreateMode); + } else { + /* file does not exist, create it (and eventually parent directories */ + if(f->f_un.f_file.bCreateDirs) { + /* we fist need to create parent dirs if they are missing + * We do not report any errors here ourselfs but let the code + * fall through to error handler below. + */ + if(makeFileParentDirs(newFileName, strlen((char*)newFileName), + f->f_un.f_file.fDirCreateMode, f->f_un.f_file.dirUID, + f->f_un.f_file.dirGID, f->f_un.f_file.bFailOnChown) == 0) { + f->f_file = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, + f->f_un.f_file.fCreateMode); + if(f->f_file != -1) { + /* check and set uid/gid */ + if(f->f_un.f_file.fileUID != (uid_t)-1 || f->f_un.f_file.fileGID != (gid_t) -1) { + /* we need to set owner/group */ + if(fchown(f->f_file, f->f_un.f_file.fileUID, + f->f_un.f_file.fileGID) != 0) { + if(f->f_un.f_file.bFailOnChown) { + int eSave = errno; + close(f->f_file); + f->f_file = -1; + errno = eSave; + } + /* we will silently ignore the chown() failure + * if configured to do so. + */ + } + } + } + } + } + } + + /* file is either open now or an error state set */ + if(f->f_file == -1) { + /* do not report anything if the message is an internally-generated + * message. Otherwise, we could run into a never-ending loop. The bad + * news is that we also lose errors on startup messages, but so it is. + */ + if(f->f_pMsg->msgFlags & INTERNAL_MSG) + dprintf("Could not open dynaFile, discarding message\n"); + else + logerrorSz("Could not open dynamic file '%s' - discarding message", (char*)newFileName); + free(newFileName); + dynaFileDelCacheEntry(pCache, iFirstFree, 1); + return -1; + } + + pCache[iFirstFree]->fd = f->f_file; + pCache[iFirstFree]->pName = newFileName; + pCache[iFirstFree]->lastUsed = time(NULL); + f->f_un.f_file.iCurrElt = iFirstFree; + dprintf("Added new entry %d for file cache, file '%s'.\n", + iFirstFree, newFileName); + + return 0; +} + + +/* rgerhards 2004-11-11: write to a file output. This + * will be called for all outputs using file semantics, + * for example also for pipes. + */ +void writeFile(selector_t *f) +{ + off_t actualFileSize; + + assert(f != NULL); + + /* first check if we have a dynamic file name and, if so, + * check if it still is ok or a new file needs to be created + */ + if(f->f_un.f_file.bDynamicName) { + if(prepareDynFile(f) != 0) + return; + } + + /* create the message based on format specified */ + iovCreate(f); +again: + /* check if we have a file size limit and, if so, + * obey to it. + */ + if(f->f_un.f_file.f_sizeLimit != 0) { + actualFileSize = lseek(f->f_file, 0, SEEK_END); + if(actualFileSize >= f->f_un.f_file.f_sizeLimit) { + char errMsg[256]; + /* for now, we simply disable a file once it is + * beyond the maximum size. This is better than having + * us aborted by the OS... rgerhards 2005-06-21 + */ + (void) close(f->f_file); + /* try to resolve the situation */ + if(resolveFileSizeLimit(f) != 0) { + /* didn't work out, so disable... */ + f->f_type = F_UNUSED; + snprintf(errMsg, sizeof(errMsg), + "no longer writing to file %s; grown beyond configured file size of %lld bytes, actual size %lld - configured command did not resolve situation", + f->f_un.f_file.f_fname, (long long) f->f_un.f_file.f_sizeLimit, (long long) actualFileSize); + errno = 0; + logerror(errMsg); + return; + } else { + snprintf(errMsg, sizeof(errMsg), + "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation", + f->f_un.f_file.f_fname, (long long) f->f_un.f_file.f_sizeLimit, (long long) actualFileSize); + errno = 0; + logerror(errMsg); + } + } + } + + if (writev(f->f_file, f->f_iov, f->f_iIovUsed) < 0) { + int e = errno; + + /* If a named pipe is full, just ignore it for now + - mrn 24 May 96 */ + if (f->f_type == F_PIPE && e == EAGAIN) + return; + + /* If the filesystem is filled up, just ignore + * it for now and continue writing when possible + * based on patch for sysklogd by Martin Schulze on 2007-05-24 + */ + if (f->f_type == F_FILE && e == ENOSPC) + return; + + (void) close(f->f_file); + /* + * Check for EBADF on TTY's due to vhangup() + * Linux uses EIO instead (mrn 12 May 96) + */ + if ((f->f_type == F_TTY || f->f_type == F_CONSOLE) +#ifdef linux + && e == EIO) { +#else + && e == EBADF) { +#endif + f->f_file = open(f->f_un.f_file.f_fname, O_WRONLY|O_APPEND|O_NOCTTY); + if (f->f_file < 0) { + f->f_type = F_UNUSED; + logerror(f->f_un.f_file.f_fname); + } else { + untty(); + goto again; + } + } else { + f->f_type = F_UNUSED; + errno = e; + logerror(f->f_un.f_file.f_fname); + } + } else if (f->f_flags & SYNC_FILE) + fsync(f->f_file); +} + + +/* free an instance + * returns 0 if it succeeds, something else otherwise + */ +int freeInstanceFile(selector_t *f) +{ + assert(f != NULL); + if(f->f_un.f_file.bDynamicName) { + dynaFileFreeCache(f); + } else + close(f->f_file); + return 0; +} + + +/* call the shell action + * returns 0 if it succeeds, something else otherwise + */ +int doActionFile(selector_t *f) +{ + assert(f != NULL); + + /* f->f_file == -1 is an indicator that the we couldn't + * open the file at startup. For dynaFiles, this is ok, + * all others are doomed. + */ + if(f->f_un.f_file.bDynamicName || (f->f_file != -1)) + writeFile(f); + return 0; +} +/* + * vi:set ai: + */ diff --git a/omfile.h b/omfile.h new file mode 100644 index 00000000..330ee4c1 --- /dev/null +++ b/omfile.h @@ -0,0 +1,35 @@ +/* omfile.h + * These are the definitions for the build-in file output module. + * + * File begun on 2007-07-21 by RGerhards (extracted from syslogd.c) + * + * Copyright 2007 Rainer Gerhards and Adiscon GmbH. + * + * This 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. + */ +#ifndef OMFILE_H_INCLUDED +#define OMFILE_H_INCLUDED 1 + +/* prototypes */ + +int doActionFile(selector_t *f); +int freeInstanceFile(selector_t *f); + +#endif /* #ifndef OMFILE_H_INCLUDED */ +/* + * vi:set ai: + */ @@ -43,6 +43,7 @@ #include "srUtils.h" #include "net.h" #include "omfwd.h" +#include "template.h" #include "msg.h" #include "tcpsyslog.h" @@ -43,7 +43,7 @@ /* call the shell action * returns 0 if it succeeds, something else otherwise */ -int doActionShell(selector_t *f, time_t now) +int doActionShell(selector_t *f) { uchar *psz; @@ -26,7 +26,7 @@ /* prototypes */ -int doActionShell(selector_t *f, time_t now); +int doActionShell(selector_t *f); #endif /* #ifndef ACTSHELL_H_INCLUDED */ /* @@ -205,20 +205,22 @@ #endif #include "rsyslog.h" +#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 "stringbuf.h" #include "parse.h" -#include "srUtils.h" #include "msg.h" #include "tcpsyslog.h" #include "omshell.h" #include "omusrmsg.h" #include "ommysql.h" #include "omfwd.h" +#include "omfile.h" /* We define our own set of syslog defintions so that we * do not need to rely on (possibly different) implementations. @@ -386,13 +388,6 @@ int funix[MAXFUNIX] = { -1, }; /* read-only after startup */ #define TABLE_ALLPRI 0xFF /* Value to indicate all priorities in f_pmask */ #define LOG_MARK LOG_MAKEPRI(LOG_NFACILITIES, 0) /* mark "facility" */ -/* Flags to logmsg(). - */ -#define INTERNAL_MSG 0x001 /* msg generated by logmsgInternal() --> special handling */ -#define SYNC_FILE 0x002 /* do fsync on file after printing */ -#define ADDDATE 0x004 /* add a date to the message */ -#define MARK 0x008 /* this message is a mark */ - /* This table lists the directive lines: */ static const char *directive_name_list[] = { @@ -1606,512 +1601,6 @@ void getCurrTime(struct syslogTime *t) t->OffsetHour = lBias / 3600; t->OffsetMinute = lBias % 3600; } - -/* Decode a priority into textual information like auth.emerg. - * The variable pRes must point to a user-supplied buffer and - * pResLen must contain its size. The pointer to the buffer - * is also returned, what makes this functiona suitable for - * use in printf-like functions. - * Note: a buffer size of 20 characters is always sufficient. - * Interface to this function changed 2007-06-15 by RGerhards - */ -char *textpri(char *pRes, size_t pResLen, int pri) -{ - syslogCODE *c_pri, *c_fac; - - assert(pRes != NULL); - assert(pResLen > 0); - - for (c_fac = rs_facilitynames; c_fac->c_name && !(c_fac->c_val == LOG_FAC(pri)<<3); c_fac++); - for (c_pri = rs_prioritynames; c_pri->c_name && !(c_pri->c_val == LOG_PRI(pri)); c_pri++); - - snprintf (pRes, pResLen, "%s.%s<%d>", c_fac->c_name, c_pri->c_name, pri); - - return pRes; -} - - -/* This function returns the current date in different - * variants. It is used to construct the $NOW series of - * system properties. The returned buffer must be freed - * by the caller when no longer needed. If the function - * can not allocate memory, it returns a NULL pointer. - * Added 2007-07-10 rgerhards - */ -typedef enum ENOWType { NOW_NOW, NOW_YEAR, NOW_MONTH, NOW_DAY, NOW_HOUR, NOW_MINUTE } eNOWType; -#define tmpBUFSIZE 16 /* size of formatting buffer */ -static uchar *getNOW(eNOWType eNow) -{ - uchar *pBuf; - struct syslogTime t; - - if((pBuf = (uchar*) malloc(sizeof(uchar) * tmpBUFSIZE)) == NULL) { - glblHadMemShortage = 1; - return NULL; - } - - getCurrTime(&t); - switch(eNow) { - case NOW_NOW: - snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d-%2.2d-%2.2d", t.year, t.month, t.day); - break; - case NOW_YEAR: - snprintf((char*) pBuf, tmpBUFSIZE, "%4.4d", t.year); - break; - case NOW_MONTH: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.month); - break; - case NOW_DAY: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.day); - break; - case NOW_HOUR: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.hour); - break; - case NOW_MINUTE: - snprintf((char*) pBuf, tmpBUFSIZE, "%2.2d", t.minute); - break; - } - - return(pBuf); -} -#undef tmpBUFSIZE /* clean up */ - - -/* This function returns a string-representation of the - * requested message property. This is a generic function used - * to abstract properties so that these can be easier - * queried. Returns NULL if property could not be found. - * Actually, this function is a big if..elseif. What it does - * is simply to map property names (from MonitorWare) to the - * message object data fields. - * - * In case we need string forms of propertis we do not - * yet have in string form, we do a memory allocation that - * is sufficiently large (in all cases). Once the string - * form has been obtained, it is saved until the Msg object - * is finally destroyed. This is so that we save the processing - * time in the (likely) case that this property is requested - * again. It also saves us a lot of dynamic memory management - * issues in the upper layers, because we so can guarantee that - * the buffer will remain static AND available during the lifetime - * of the object. Please note that both the max size allocation as - * well as keeping things in memory might like look like a - * waste of memory (some might say it actually is...) - we - * deliberately accept this because performance is more important - * to us ;) - * rgerhards 2004-11-18 - * Parameter "bMustBeFreed" is set by this function. It tells the - * caller whether or not the string returned must be freed by the - * caller itself. It is is 0, the caller MUST NOT free it. If it is - * 1, the caller MUST free 1. Handling this wrongly leads to either - * a memory leak of a program abort (do to double-frees or frees on - * the constant memory pool). So be careful to do it right. - * rgerhards 2004-11-23 - * regular expression support contributed by Andres Riancho merged - * on 2005-09-13 - * changed so that it now an be called without a template entry (NULL). - * In this case, only the (unmodified) property is returned. This will - * be used in selector line processing. - * rgerhards 2005-09-15 - */ -static char *MsgGetProp(msg_t *pMsg, struct templateEntry *pTpe, - rsCStrObj *pCSPropName, unsigned short *pbMustBeFreed) -{ - char *pName; - char *pRes; /* result pointer */ - char *pBufStart; - char *pBuf; - int iLen; - -#ifdef FEATURE_REGEXP - /* Variables necessary for regular expression matching */ - size_t nmatch = 2; - regmatch_t pmatch[2]; -#endif - - assert(pMsg != NULL); - assert(pbMustBeFreed != NULL); - - if(pCSPropName == NULL) { - assert(pTpe != NULL); - pName = pTpe->data.field.pPropRepl; - } else { - pName = (char*) rsCStrGetSzStr(pCSPropName); - } - *pbMustBeFreed = 0; - - /* sometimes there are aliases to the original MonitoWare - * property names. These come after || in the ifs below. */ - if(!strcmp(pName, "msg")) { - pRes = getMSG(pMsg); - } else if(!strcmp(pName, "rawmsg")) { - pRes = getRawMsg(pMsg); - } else if(!strcmp(pName, "UxTradMsg")) { - pRes = getUxTradMsg(pMsg); - } else if(!strcmp(pName, "FROMHOST")) { - pRes = getRcvFrom(pMsg); - } else if(!strcmp(pName, "source") - || !strcmp(pName, "HOSTNAME")) { - pRes = getHOSTNAME(pMsg); - } else if(!strcmp(pName, "syslogtag")) { - pRes = getTAG(pMsg); - } else if(!strcmp(pName, "PRI")) { - pRes = getPRI(pMsg); - } else if(!strcmp(pName, "PRI-text")) { - pBuf = malloc(20 * sizeof(char)); - if(pBuf == NULL) { - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } else { - *pbMustBeFreed = 1; - pRes = textpri(pBuf, 20, getPRIi(pMsg)); - } - } else if(!strcmp(pName, "iut")) { - pRes = "1"; /* always 1 for syslog messages (a MonitorWare thing;)) */ - } else if(!strcmp(pName, "syslogfacility")) { - pRes = getFacility(pMsg); - } else if(!strcmp(pName, "syslogfacility-text")) { - pRes = getFacilityStr(pMsg); - } else if(!strcmp(pName, "syslogseverity") || !strcmp(pName, "syslogpriority")) { - pRes = getSeverity(pMsg); - } else if(!strcmp(pName, "syslogseverity-text") || !strcmp(pName, "syslogpriority-text")) { - pRes = getSeverityStr(pMsg); - } else if(!strcmp(pName, "timegenerated")) { - pRes = getTimeGenerated(pMsg, pTpe->data.field.eDateFormat); - } else if(!strcmp(pName, "timereported") - || !strcmp(pName, "TIMESTAMP")) { - pRes = getTimeReported(pMsg, pTpe->data.field.eDateFormat); - } else if(!strcmp(pName, "programname")) { - pRes = getProgramName(pMsg); - } else if(!strcmp(pName, "PROTOCOL-VERSION")) { - pRes = getProtocolVersionString(pMsg); - } else if(!strcmp(pName, "STRUCTURED-DATA")) { - pRes = getStructuredData(pMsg); - } else if(!strcmp(pName, "APP-NAME")) { - pRes = getAPPNAME(pMsg); - } else if(!strcmp(pName, "PROCID")) { - pRes = getPROCID(pMsg); - } else if(!strcmp(pName, "MSGID")) { - pRes = getMSGID(pMsg); - /* here start system properties (those, that do not relate to the message itself */ - } else if(!strcmp(pName, "$NOW")) { - if((pRes = (char*) getNOW(NOW_NOW)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp(pName, "$YEAR")) { - if((pRes = (char*) getNOW(NOW_YEAR)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp(pName, "$MONTH")) { - if((pRes = (char*) getNOW(NOW_MONTH)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp(pName, "$DAY")) { - if((pRes = (char*) getNOW(NOW_DAY)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp(pName, "$HOUR")) { - if((pRes = (char*) getNOW(NOW_HOUR)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else if(!strcmp(pName, "$MINUTE")) { - if((pRes = (char*) getNOW(NOW_MINUTE)) == NULL) { - return "***OUT OF MEMORY***"; - } else - *pbMustBeFreed = 1; /* all of these functions allocate dyn. memory */ - } else { - /* there is no point in continuing, we may even otherwise render the - * error message unreadable. rgerhards, 2007-07-10 - */ - return "**INVALID PROPERTY NAME**"; - } - - /* If we did not receive a template pointer, we are already done... */ - if(pTpe == NULL) { - return pRes; - } - - /* Now check if we need to make "temporary" transformations (these - * are transformations that do not go back into the message - - * memory must be allocated for them!). - */ - - /* substring extraction */ - /* first we check if we need to extract by field number - * rgerhards, 2005-12-22 - */ - if(pTpe->data.field.has_fields == 1) { - size_t iCurrFld; - char *pFld; - char *pFldEnd; - /* first, skip to the field in question. The field separator - * is always one character and is stored in the template entry. - */ - iCurrFld = 1; - pFld = pRes; - while(*pFld && iCurrFld < pTpe->data.field.iToPos) { - /* skip fields until the requested field or end of string is found */ - while(*pFld && (uchar) *pFld != pTpe->data.field.field_delim) - ++pFld; /* skip to field terminator */ - if(*pFld == pTpe->data.field.field_delim) { - ++pFld; /* eat it */ - ++iCurrFld; - } - } - dprintf("field requested %d, field found %d\n", pTpe->data.field.iToPos, iCurrFld); - - if(iCurrFld == pTpe->data.field.iToPos) { - /* field found, now extract it */ - /* first of all, we need to find the end */ - pFldEnd = pFld; - while(*pFldEnd && *pFldEnd != pTpe->data.field.field_delim) - ++pFldEnd; - --pFldEnd; /* we are already at the delimiter - so we need to - * step back a little not to copy it as part of the field. */ - /* we got our end pointer, now do the copy */ - /* TODO: code copied from below, this is a candidate for a separate function */ - iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */ - pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); - if(pBuf == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - /* now copy */ - memcpy(pBuf, pFld, iLen); - pBuf[iLen] = '\0'; /* terminate it */ - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBufStart; - *pbMustBeFreed = 1; - if(*(pFldEnd+1) != '\0') - ++pFldEnd; /* OK, skip again over delimiter char */ - } else { - /* field not found, return error */ - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**FIELD NOT FOUND**"; - } - } else if(pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { - /* we need to obtain a private copy */ - int iFrom, iTo; - iFrom = pTpe->data.field.iFromPos; - iTo = pTpe->data.field.iToPos; - /* need to zero-base to and from (they are 1-based!) */ - if(iFrom > 0) - --iFrom; - if(iTo > 0) - --iTo; - iLen = iTo - iFrom + 1; /* the +1 is for an actual char, NOT \0! */ - pBufStart = pBuf = malloc((iLen + 1) * sizeof(char)); - if(pBuf == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - if(iFrom) { - /* skip to the start of the substring (can't do pointer arithmetic - * because the whole string might be smaller!!) - */ - // ++iFrom; /* nbr of chars to skip! */ - while(*pRes && iFrom) { - --iFrom; - ++pRes; - } - } - /* OK, we are at the begin - now let's copy... */ - while(*pRes && iLen) { - *pBuf++ = *pRes; - ++pRes; - --iLen; - } - *pBuf = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBufStart; - *pbMustBeFreed = 1; -#ifdef FEATURE_REGEXP - } else { - /* Check for regular expressions */ - if (pTpe->data.field.has_regex != 0) { - if (pTpe->data.field.has_regex == 2) - /* Could not compile regex before! */ - return - "**NO MATCH** **BAD REGULAR EXPRESSION**"; - - dprintf("debug: String to match for regex is: %s\n", - pRes); - - if (0 != regexec(&pTpe->data.field.re, pRes, nmatch, - pmatch, 0)) { - /* we got no match! */ - return "**NO MATCH**"; - } else { - /* Match! */ - /* I need to malloc pB */ - int iLenBuf; - char *pB; - - iLenBuf = pmatch[1].rm_eo - pmatch[1].rm_so; - pB = (char *) malloc((iLenBuf + 1) * sizeof(char)); - - if (pB == NULL) { - if (*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY ALLOCATING pBuf**"; - } - - /* Lets copy the matched substring to the buffer */ - memcpy(pB, pRes + pmatch[1].rm_so, iLenBuf); - pB[iLenBuf] = '\0';/* terminate string, did not happen before */ - - if (*pbMustBeFreed == 1) - free(pRes); - pRes = pB; - *pbMustBeFreed = 1; - } - } -#endif /* #ifdef FEATURE_REGEXP */ - } - - /* case conversations (should go after substring, because so we are able to - * work on the smallest possible buffer). - */ - if(pTpe->data.field.eCaseConv != tplCaseConvNo) { - /* we need to obtain a private copy */ - int iBufLen = strlen(pRes); - char *pBStart; - char *pB; - pBStart = pB = malloc((iBufLen + 1) * sizeof(char)); - if(pB == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - while(*pRes) { - *pB++ = (pTpe->data.field.eCaseConv == tplCaseConvUpper) ? - toupper(*pRes) : tolower(*pRes); - /* currently only these two exist */ - ++pRes; - } - *pB = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBStart; - *pbMustBeFreed = 1; - } - - /* now do control character dropping/escaping/replacement - * Only one of these can be used. If multiple options are given, the - * result is random (though currently there obviously is an order of - * preferrence, see code below. But this is NOT guaranteed. - * RGerhards, 2006-11-17 - */ - if(pTpe->data.field.options.bDropCC) { - char *pSrc = pRes; - char *pDst = pRes; - - while(*pSrc) { - if(!iscntrl((int) *pSrc)) - *pDst++ = *pSrc; - ++pSrc; - } - *pDst = '\0'; - } else if(pTpe->data.field.options.bSpaceCC) { - char *pB = pRes; - while(*pB) { - if(iscntrl((int) *pB)) - *pB = ' '; - ++pB; - } - } else if(pTpe->data.field.options.bEscapeCC) { - /* we must first count how many control charactes are - * present, because we need this to compute the new string - * buffer length. While doing so, we also compute the string - * length. - */ - int iNumCC = 0; - int iLenBuf = 0; - char *pB; - - for(pB = pRes ; *pB ; ++pB) { - ++iLenBuf; - if(iscntrl((int) *pB)) - ++iNumCC; - } - - if(iNumCC > 0) { /* if 0, there is nothing to escape, so we are done */ - /* OK, let's do the escaping... */ - char *pBStart; - char szCCEsc[8]; /* buffer for escape sequence */ - int i; - - iLenBuf += iNumCC * 4; - pBStart = pB = malloc((iLenBuf + 1) * sizeof(char)); - if(pB == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - while(*pRes) { - if(iscntrl((int) *pRes)) { - snprintf(szCCEsc, sizeof(szCCEsc), "#%3.3d", *pRes); - for(i = 0 ; i < 4 ; ++i) - *pB++ = szCCEsc[i]; - } else { - *pB++ = *pRes; - } - ++pRes; - } - *pB = '\0'; - if(*pbMustBeFreed == 1) - free(pRes); - pRes = pBStart; - *pbMustBeFreed = 1; - } - } - - /* Now drop last LF if present (pls note that this must not be done - * if bEscapeCC was set! - */ - if(pTpe->data.field.options.bDropLastLF && !pTpe->data.field.options.bEscapeCC) { - int iLn = strlen(pRes); - char *pB; - if(*(pRes + iLn - 1) == '\n') { - /* we have a LF! */ - /* check if we need to obtain a private copy */ - if(pbMustBeFreed == 0) { - /* ok, original copy, need a private one */ - pB = malloc((iLn + 1) * sizeof(char)); - if(pB == NULL) { - if(*pbMustBeFreed == 1) - free(pRes); - *pbMustBeFreed = 0; - return "**OUT OF MEMORY**"; - } - memcpy(pB, pRes, iLn - 1); - pRes = pB; - *pbMustBeFreed = 1; - } - *(pRes + iLn - 1) = '\0'; /* drop LF ;) */ - } - } - - /*dprintf("MsgGetProp(\"%s\"): \"%s\"\n", pName, pRes); only for verbose debug logging */ - return(pRes); -} - /* rgerhards 2004-11-09: end of helper routines. On to the * "real" code ;) */ @@ -2384,7 +1873,7 @@ static char **crunch_list(char *list) } -static void untty() +void untty(void) #ifdef HAVE_SETSID { if ( !Debug ) { @@ -3582,129 +3071,6 @@ void logmsg(int pri, msg_t *pMsg, int flags) } -/* Helper to doSQLEscape. This is called if doSQLEscape - * runs out of memory allocating the escaped string. - * Then we are in trouble. We can - * NOT simply return the unmodified string because this - * may cause SQL injection. But we also can not simply - * abort the run, this would be a DoS. I think an appropriate - * measure is to remove the dangerous \' characters. We - * replace them by \", which will break the message and - * signatures eventually present - but this is the - * best thing we can do now (or does anybody - * have a better idea?). rgerhards 2004-11-23 - * added support for "escapeMode" (so doSQLEscape for details). - * if mode = 1, then backslashes are changed to slashes. - * rgerhards 2005-09-22 - */ -void doSQLEmergencyEscape(register char *p, int escapeMode) -{ - while(*p) { - if(*p == '\'') - *p = '"'; - else if((escapeMode == 1) && (*p == '\\')) - *p = '/'; - ++p; - } -} - - -/* SQL-Escape a string. Single quotes are found and - * replaced by two of them. A new buffer is allocated - * for the provided string and the provided buffer is - * freed. The length is updated. Parameter pbMustBeFreed - * is set to 1 if a new buffer is allocated. Otherwise, - * it is left untouched. - * -- - * We just discovered a security issue. MySQL is so - * "smart" to not only support the standard SQL mechanism - * for escaping quotes, but to also provide its own (using - * c-type syntax with backslashes). As such, it is actually - * possible to do sql injection via rsyslogd. The cure is now - * to escape backslashes, too. As we have found on the web, some - * other databases seem to be similar "smart" (why do we have standards - * at all if they are violated without any need???). Even better, MySQL's - * smartness depends on config settings. So we add a new option to this - * function that allows the caller to select if they want to standard or - * "smart" encoding ;) - * new parameter escapeMode is 0 - standard sql, 1 - "smart" engines - * 2005-09-22 rgerhards - */ -void doSQLEscape(char **pp, size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode) -{ - char *p; - int iLen; - rsCStrObj *pStrB; - uchar *pszGenerated; - - assert(pp != NULL); - assert(*pp != NULL); - assert(pLen != NULL); - assert(pbMustBeFreed != NULL); - - /* first check if we need to do anything at all... */ - if(escapeMode == 0) - for(p = *pp ; *p && *p != '\'' ; ++p) - ; - else - for(p = *pp ; *p && *p != '\'' && *p != '\\' ; ++p) - ; - /* when we get out of the loop, we are either at the - * string terminator or the first \'. */ - if(*p == '\0') - return; /* nothing to do in this case! */ - - p = *pp; - iLen = *pLen; - if((pStrB = rsCStrConstruct()) == NULL) { - /* oops - no mem ... Do emergency... */ - doSQLEmergencyEscape(p, escapeMode); - return; - } - - while(*p) { - if(*p == '\'') { - if(rsCStrAppendChar(pStrB, (escapeMode == 0) ? '\'' : '\\') != RS_RET_OK) { - doSQLEmergencyEscape(*pp, escapeMode); - rsCStrFinish(pStrB); - if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) != NULL) - free(pszGenerated); - return; - } - iLen++; /* reflect the extra character */ - } else if((escapeMode == 1) && (*p == '\\')) { - if(rsCStrAppendChar(pStrB, '\\') != RS_RET_OK) { - doSQLEmergencyEscape(*pp, escapeMode); - rsCStrFinish(pStrB); - if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) != NULL) - free(pszGenerated); - return; - } - iLen++; /* reflect the extra character */ - } - if(rsCStrAppendChar(pStrB, *p) != RS_RET_OK) { - doSQLEmergencyEscape(*pp, escapeMode); - rsCStrFinish(pStrB); - if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) != NULL) - free(pszGenerated); - return; - } - ++p; - } - rsCStrFinish(pStrB); - if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) == NULL) { - doSQLEmergencyEscape(*pp, escapeMode); - return; - } - - if(*pbMustBeFreed) - free(*pp); /* discard previous value */ - - *pp = (char*) pszGenerated; - *pLen = iLen; - *pbMustBeFreed = 1; -} - /* create a string from the provided iovec. This can * be called by all functions who need the template @@ -3860,446 +3226,6 @@ void iovCreate(selector_t *f) return; } -/* This functions converts a template into a string. It should - * actually be in template.c, but this requires larger re-structuring - * of the code (because all the property-access functions are static - * to this module). I have placed it next to the iov*() functions, as - * it is somewhat similiar in what it does. - * - * The function takes a pointer to a template and a pointer to a msg object. - * It the creates a string based on the template definition. A pointer - * to that string is returned to the caller. The caller MUST FREE that - * pointer when it is no longer needed. If the function fails, NULL - * is returned. - * If memory allocation fails in this function, we silently return - * NULL. The reason is that we can not do anything against it. And - * if we raise an alert, the memory situation might become even - * worse. So we prefer to let the caller deal with it. - * rgerhards, 2007-07-03 - */ -static uchar *tplToString(struct template *pTpl, msg_t *pMsg) -{ - struct templateEntry *pTpe; - rsCStrObj *pCStr; - unsigned short bMustBeFreed; - char *pVal; - size_t iLenVal; - rsRetVal iRet; - - assert(pTpl != NULL); - assert(pMsg != NULL); - - /* loop through the template. We obtain one value - * and copy it over to our dynamic string buffer. Then, we - * free the obtained value (if requested). We continue this - * loop until we got hold of all values. - */ - if((pCStr = rsCStrConstruct()) == NULL) { - dprintf("memory shortage, tplToString failed\n"); - return NULL; - } - - pTpe = pTpl->pEntryRoot; - while(pTpe != NULL) { - if(pTpe->eEntryType == CONSTANT) { - if((iRet = rsCStrAppendStrWithLen(pCStr, - (uchar *) pTpe->data.constant.pConstant, - pTpe->data.constant.iLenConstant) - ) != RS_RET_OK) { - dprintf("error %d during tplToString()\n", iRet); - /* it does not make sense to continue now */ - rsCStrDestruct(pCStr); - return NULL; - } - } else if(pTpe->eEntryType == FIELD) { - pVal = (char*) MsgGetProp(pMsg, pTpe, NULL, &bMustBeFreed); - iLenVal = strlen(pVal); - /* we now need to check if we should use SQL option. In this case, - * we must go over the generated string and escape '\'' characters. - * rgerhards, 2005-09-22: the option values below look somewhat misplaced, - * but they are handled in this way because of legacy (don't break any - * existing thing). - */ - if(pTpl->optFormatForSQL == 1) - doSQLEscape(&pVal, &iLenVal, &bMustBeFreed, 1); - else if(pTpl->optFormatForSQL == 2) - doSQLEscape(&pVal, &iLenVal, &bMustBeFreed, 0); - /* value extracted, so lets copy */ - if((iRet = rsCStrAppendStrWithLen(pCStr, (uchar*) pVal, iLenVal)) != RS_RET_OK) { - dprintf("error %d during tplToString()\n", iRet); - /* it does not make sense to continue now */ - rsCStrDestruct(pCStr); - if(bMustBeFreed) - free(pVal); - return NULL; - } - if(bMustBeFreed) - free(pVal); - } - pTpe = pTpe->pNext; - } - - /* we are done with the template, now let's convert the result into a - * "real" (usable) string and discard the helper structures. - */ - rsCStrFinish(pCStr); - return rsCStrConvSzStrAndDestruct(pCStr); -} - - -/* rgerhards 2005-06-21: Try to resolve a size limit - * situation. This first runs the command, and then - * checks if we are still above the treshold. - * returns 0 if ok, 1 otherwise - * TODO: consider moving the initial check in here, too - */ -int resolveFileSizeLimit(selector_t *f) -{ - uchar *pParams; - uchar *pCmd; - uchar *p; - off_t actualFileSize; - assert(f != NULL); - - if(f->f_un.f_file.f_sizeLimitCmd == NULL) - return 1; /* nothing we can do in this case... */ - - /* the execProg() below is probably not great, but at least is is - * fairly secure now. Once we change the way file size limits are - * handled, we should also revisit how this command is run (and - * with which parameters). rgerhards, 2007-07-20 - */ - /* we first check if we have command line parameters. We assume this, - * when we have a space in the program name. If we find it, everything after - * the space is treated as a single argument. - */ - if((pCmd = (uchar*)strdup((char*)f->f_un.f_file.f_sizeLimitCmd)) == NULL) { - /* there is not much we can do - we make syslogd close the file in this case */ - glblHadMemShortage = 1; - return 1; - } - - for(p = pCmd ; *p && *p != ' ' ; ++p) { - /* JUST SKIP */ - } - - if(*p == ' ') { - *p = '\0'; /* pretend string-end */ - pParams = p+1; - } else - pParams = NULL; - - execProg(pCmd, 1, pParams); - - f->f_file = open(f->f_un.f_file.f_fname, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - f->f_un.f_file.fCreateMode); - - actualFileSize = lseek(f->f_file, 0, SEEK_END); - if(actualFileSize >= f->f_un.f_file.f_sizeLimit) { - /* OK, it didn't work out... */ - return 1; - } - - return 0; -} - - -/* This function deletes an entry from the dynamic file name - * cache. A pointer to the cache must be passed in as well - * as the index of the to-be-deleted entry. This index may - * point to an unallocated entry, in whcih case the - * function immediately returns. Parameter bFreeEntry is 1 - * if the entry should be free()ed and 0 if not. - */ -static void dynaFileDelCacheEntry(dynaFileCacheEntry **pCache, int iEntry, int bFreeEntry) -{ - assert(pCache != NULL); - - if(pCache[iEntry] == NULL) - return; - - dprintf("Removed entry %d for file '%s' from dynaCache.\n", iEntry, - pCache[iEntry]->pName == NULL ? "[OPEN FAILED]" : (char*)pCache[iEntry]->pName); - /* if the name is NULL, this is an improperly initilized entry which - * needs to be discarded. In this case, neither the file is to be closed - * not the name to be freed. - */ - if(pCache[iEntry]->pName != NULL) { - close(pCache[iEntry]->fd); - free(pCache[iEntry]->pName); - pCache[iEntry]->pName = NULL; - } - - if(bFreeEntry) { - free(pCache[iEntry]); - pCache[iEntry] = NULL; - } -} - - -/* This function frees the dynamic file name cache. - */ -static void dynaFileFreeCache(selector_t *f) -{ - register int i; - assert(f != NULL); - - for(i = 0 ; i < f->f_un.f_file.iCurrCacheSize ; ++i) { - dynaFileDelCacheEntry(f->f_un.f_file.dynCache, i, 1); - } - - free(f->f_un.f_file.dynCache); -} - - -/* This function handles dynamic file names. It generates a new one - * based on the current message, checks if that file is already open - * and, if not, does everything needed to switch to the new one. - * Function returns 0 if all went well and non-zero otherwise. - * On successful return f->f_file must point to the correct file to - * be written. - * This is a helper to writeFile(). rgerhards, 2007-07-03 - */ -static int prepareDynFile(selector_t *f) -{ - uchar *newFileName; - time_t ttOldest; /* timestamp of oldest element */ - int iOldest; - int i; - int iFirstFree; - dynaFileCacheEntry **pCache; - - assert(f != NULL); - if((newFileName = tplToString(f->f_un.f_file.pTpl, f->f_pMsg)) == NULL) { - /* memory shortage - there is nothing we can do to resolve it. - * We silently ignore it, this is probably the best we can do. - */ - glblHadMemShortage = TRUE; - dprintf("prepareDynfile(): could not create file name, discarding this request\n"); - return -1; - } - - pCache = f->f_un.f_file.dynCache; - - /* first check, if we still have the current file - * I *hope* this will be a performance enhancement. - */ - if( (f->f_un.f_file.iCurrElt != -1) - && !strcmp((char*) newFileName, - (char*) pCache[f->f_un.f_file.iCurrElt])) { - /* great, we are all set */ - free(newFileName); - pCache[f->f_un.f_file.iCurrElt]->lastUsed = time(NULL); /* update timestamp for LRU */ - return 0; - } - - /* ok, no luck. Now let's search the table if we find a matching spot. - * While doing so, we also prepare for creation of a new one. - */ - iFirstFree = -1; /* not yet found */ - iOldest = 0; /* we assume the first element to be the oldest - that will change as we loop */ - ttOldest = time(NULL) + 1; /* there must always be an older one */ - for(i = 0 ; i < f->f_un.f_file.iCurrCacheSize ; ++i) { - if(pCache[i] == NULL) { - if(iFirstFree == -1) - iFirstFree = i; - } else { /* got an element, let's see if it matches */ - if(!strcmp((char*) newFileName, (char*) pCache[i]->pName)) { - /* we found our element! */ - f->f_file = pCache[i]->fd; - f->f_un.f_file.iCurrElt = i; - free(newFileName); - pCache[i]->lastUsed = time(NULL); /* update timestamp for LRU */ - return 0; - } - /* did not find it - so lets keep track of the counters for LRU */ - if(pCache[i]->lastUsed < ttOldest) { - ttOldest = pCache[i]->lastUsed; - iOldest = i; - } - } - } - - /* we have not found an entry */ - if(iFirstFree == -1 && (f->f_un.f_file.iCurrCacheSize < f->f_un.f_file.iDynaFileCacheSize)) { - /* there is space left, so set it to that index */ - iFirstFree = f->f_un.f_file.iCurrCacheSize++; - } - - if(iFirstFree == -1) { - dynaFileDelCacheEntry(pCache, iOldest, 0); - iFirstFree = iOldest; /* this one *is* now free ;) */ - } else { - /* we need to allocate memory for the cache structure */ - pCache[iFirstFree] = (dynaFileCacheEntry*) calloc(1, sizeof(dynaFileCacheEntry)); - if(pCache[iFirstFree] == NULL) { - glblHadMemShortage = TRUE; - dprintf("prepareDynfile(): could not alloc mem, discarding this request\n"); - free(newFileName); - return -1; - } - } - - /* Ok, we finally can open the file */ - if(access((char*)newFileName, F_OK) == 0) { - /* file already exists */ - f->f_file = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - f->f_un.f_file.fCreateMode); - } else { - /* file does not exist, create it (and eventually parent directories */ - if(f->f_un.f_file.bCreateDirs) { - /* we fist need to create parent dirs if they are missing - * We do not report any errors here ourselfs but let the code - * fall through to error handler below. - */ - if(makeFileParentDirs(newFileName, strlen((char*)newFileName), - f->f_un.f_file.fDirCreateMode, f->f_un.f_file.dirUID, - f->f_un.f_file.dirGID, f->f_un.f_file.bFailOnChown) == 0) { - f->f_file = open((char*) newFileName, O_WRONLY|O_APPEND|O_CREAT|O_NOCTTY, - f->f_un.f_file.fCreateMode); - if(f->f_file != -1) { - /* check and set uid/gid */ - if(f->f_un.f_file.fileUID != (uid_t)-1 || f->f_un.f_file.fileGID != (gid_t) -1) { - /* we need to set owner/group */ - if(fchown(f->f_file, f->f_un.f_file.fileUID, - f->f_un.f_file.fileGID) != 0) { - if(f->f_un.f_file.bFailOnChown) { - int eSave = errno; - close(f->f_file); - f->f_file = -1; - errno = eSave; - } - /* we will silently ignore the chown() failure - * if configured to do so. - */ - } - } - } - } - } - } - - /* file is either open now or an error state set */ - if(f->f_file == -1) { - /* do not report anything if the message is an internally-generated - * message. Otherwise, we could run into a never-ending loop. The bad - * news is that we also lose errors on startup messages, but so it is. - */ - if(f->f_pMsg->msgFlags & INTERNAL_MSG) - dprintf("Could not open dynaFile, discarding message\n"); - else - logerrorSz("Could not open dynamic file '%s' - discarding message", (char*)newFileName); - free(newFileName); - dynaFileDelCacheEntry(pCache, iFirstFree, 1); - return -1; - } - - pCache[iFirstFree]->fd = f->f_file; - pCache[iFirstFree]->pName = newFileName; - pCache[iFirstFree]->lastUsed = time(NULL); - f->f_un.f_file.iCurrElt = iFirstFree; - dprintf("Added new entry %d for file cache, file '%s'.\n", - iFirstFree, newFileName); - - return 0; -} - - -/* rgerhards 2004-11-11: write to a file output. This - * will be called for all outputs using file semantics, - * for example also for pipes. - */ -void writeFile(selector_t *f) -{ - off_t actualFileSize; - - assert(f != NULL); - - /* first check if we have a dynamic file name and, if so, - * check if it still is ok or a new file needs to be created - */ - if(f->f_un.f_file.bDynamicName) { - if(prepareDynFile(f) != 0) - return; - } - - /* create the message based on format specified */ - iovCreate(f); -again: - /* check if we have a file size limit and, if so, - * obey to it. - */ - if(f->f_un.f_file.f_sizeLimit != 0) { - actualFileSize = lseek(f->f_file, 0, SEEK_END); - if(actualFileSize >= f->f_un.f_file.f_sizeLimit) { - char errMsg[256]; - /* for now, we simply disable a file once it is - * beyond the maximum size. This is better than having - * us aborted by the OS... rgerhards 2005-06-21 - */ - (void) close(f->f_file); - /* try to resolve the situation */ - if(resolveFileSizeLimit(f) != 0) { - /* didn't work out, so disable... */ - f->f_type = F_UNUSED; - snprintf(errMsg, sizeof(errMsg), - "no longer writing to file %s; grown beyond configured file size of %lld bytes, actual size %lld - configured command did not resolve situation", - f->f_un.f_file.f_fname, (long long) f->f_un.f_file.f_sizeLimit, (long long) actualFileSize); - errno = 0; - logerror(errMsg); - return; - } else { - snprintf(errMsg, sizeof(errMsg), - "file %s had grown beyond configured file size of %lld bytes, actual size was %lld - configured command resolved situation", - f->f_un.f_file.f_fname, (long long) f->f_un.f_file.f_sizeLimit, (long long) actualFileSize); - errno = 0; - logerror(errMsg); - } - } - } - - if (writev(f->f_file, f->f_iov, f->f_iIovUsed) < 0) { - int e = errno; - - /* If a named pipe is full, just ignore it for now - - mrn 24 May 96 */ - if (f->f_type == F_PIPE && e == EAGAIN) - return; - - /* If the filesystem is filled up, just ignore - * it for now and continue writing when possible - * based on patch for sysklogd by Martin Schulze on 2007-05-24 - */ - if (f->f_type == F_FILE && e == ENOSPC) - return; - - (void) close(f->f_file); - /* - * Check for EBADF on TTY's due to vhangup() - * Linux uses EIO instead (mrn 12 May 96) - */ - if ((f->f_type == F_TTY || f->f_type == F_CONSOLE) -#ifdef linux - && e == EIO) { -#else - && e == EBADF) { -#endif - f->f_file = open(f->f_un.f_file.f_fname, O_WRONLY|O_APPEND|O_NOCTTY); - if (f->f_file < 0) { - f->f_type = F_UNUSED; - logerror(f->f_un.f_file.f_fname); - } else { - untty(); - goto again; - } - } else { - f->f_type = F_UNUSED; - errno = e; - logerror(f->f_un.f_file.f_fname); - } - } else if (f->f_flags & SYNC_FILE) - fsync(f->f_file); -} /* rgerhards 2004-11-09: fprintlog() is the actual driver for * the output channel. It receives the channel description (f) as @@ -4373,12 +3299,7 @@ void fprintlog(register selector_t *f) case F_PIPE: printf(" (%s)\n", f->f_un.f_file.f_fname); f->f_time = now; /* we need this for message repeation processing */ - /* f->f_file == -1 is an indicator that the we couldn't - * open the file at startup. For dynaFiles, this is ok, - * all others are doomed. - */ - if(f->f_un.f_file.bDynamicName || (f->f_file != -1)) - writeFile(f); + doActionFile(f); break; case F_USERS: @@ -4396,7 +3317,7 @@ void fprintlog(register selector_t *f) case F_SHELL: /* shell support by bkalkbrenner 2005-09-20 */ f->f_time = now; - doActionShell(f, now); + doActionShell(f); break; } /* switch */ @@ -5288,10 +4209,7 @@ static void init() case F_PIPE: case F_TTY: case F_CONSOLE: - if(f->f_un.f_file.bDynamicName) { - dynaFileFreeCache(f); - } else - close(f->f_file); + freeInstanceFile(f); break; case F_FORW: freeaddrinfo(f->f_un.f_forw.f_addr); @@ -56,6 +56,14 @@ * rgerhards, 2005-07-26 */ #endif + +/* Flags to logmsg(). + */ +#define INTERNAL_MSG 0x001 /* msg generated by logmsgInternal() --> special handling */ +#define SYNC_FILE 0x002 /* do fsync on file after printing */ +#define ADDDATE 0x004 /* add a date to the message */ +#define MARK 0x008 /* this message is a mark */ + #if defined(__GLIBC__) #define dprintf mydprintf #endif /* __GLIBC__ */ @@ -75,6 +83,7 @@ int formatTimestamp3339(struct syslogTime *ts, char* pBuf, size_t iLenBuf); int formatTimestamp3164(struct syslogTime *ts, char* pBuf, size_t iLenBuf); void iovCreate(selector_t *f); char *iovAsString(selector_t *f); +void untty(void); extern int glblHadMemShortage; /* indicates if we had memory shortage some time during the run */ extern syslogCODE rs_prioritynames[]; @@ -16,13 +16,224 @@ #include <assert.h> #include "rsyslog.h" #include "stringbuf.h" +#include "syslogd-types.h" #include "template.h" +#include "msg.h" #include "syslogd.h" static struct template *tplRoot = NULL; /* the root of the template list */ static struct template *tplLast = NULL; /* points to the last element of the template list */ static struct template *tplLastStatic = NULL; /* last static element of the template list */ +/* This functions converts a template into a string. It should + * actually be in template.c, but this requires larger re-structuring + * of the code (because all the property-access functions are static + * to this module). I have placed it next to the iov*() functions, as + * it is somewhat similiar in what it does. + * + * The function takes a pointer to a template and a pointer to a msg object. + * It the creates a string based on the template definition. A pointer + * to that string is returned to the caller. The caller MUST FREE that + * pointer when it is no longer needed. If the function fails, NULL + * is returned. + * If memory allocation fails in this function, we silently return + * NULL. The reason is that we can not do anything against it. And + * if we raise an alert, the memory situation might become even + * worse. So we prefer to let the caller deal with it. + * rgerhards, 2007-07-03 + */ +uchar *tplToString(struct template *pTpl, msg_t *pMsg) +{ + struct templateEntry *pTpe; + rsCStrObj *pCStr; + unsigned short bMustBeFreed; + char *pVal; + size_t iLenVal; + rsRetVal iRet; + + assert(pTpl != NULL); + assert(pMsg != NULL); + + /* loop through the template. We obtain one value + * and copy it over to our dynamic string buffer. Then, we + * free the obtained value (if requested). We continue this + * loop until we got hold of all values. + */ + if((pCStr = rsCStrConstruct()) == NULL) { + dprintf("memory shortage, tplToString failed\n"); + return NULL; + } + + pTpe = pTpl->pEntryRoot; + while(pTpe != NULL) { + if(pTpe->eEntryType == CONSTANT) { + if((iRet = rsCStrAppendStrWithLen(pCStr, + (uchar *) pTpe->data.constant.pConstant, + pTpe->data.constant.iLenConstant) + ) != RS_RET_OK) { + dprintf("error %d during tplToString()\n", iRet); + /* it does not make sense to continue now */ + rsCStrDestruct(pCStr); + return NULL; + } + } else if(pTpe->eEntryType == FIELD) { + pVal = (char*) MsgGetProp(pMsg, pTpe, NULL, &bMustBeFreed); + iLenVal = strlen(pVal); + /* we now need to check if we should use SQL option. In this case, + * we must go over the generated string and escape '\'' characters. + * rgerhards, 2005-09-22: the option values below look somewhat misplaced, + * but they are handled in this way because of legacy (don't break any + * existing thing). + */ + if(pTpl->optFormatForSQL == 1) + doSQLEscape(&pVal, &iLenVal, &bMustBeFreed, 1); + else if(pTpl->optFormatForSQL == 2) + doSQLEscape(&pVal, &iLenVal, &bMustBeFreed, 0); + /* value extracted, so lets copy */ + if((iRet = rsCStrAppendStrWithLen(pCStr, (uchar*) pVal, iLenVal)) != RS_RET_OK) { + dprintf("error %d during tplToString()\n", iRet); + /* it does not make sense to continue now */ + rsCStrDestruct(pCStr); + if(bMustBeFreed) + free(pVal); + return NULL; + } + if(bMustBeFreed) + free(pVal); + } + pTpe = pTpe->pNext; + } + + /* we are done with the template, now let's convert the result into a + * "real" (usable) string and discard the helper structures. + */ + rsCStrFinish(pCStr); + return rsCStrConvSzStrAndDestruct(pCStr); +} + +/* Helper to doSQLEscape. This is called if doSQLEscape + * runs out of memory allocating the escaped string. + * Then we are in trouble. We can + * NOT simply return the unmodified string because this + * may cause SQL injection. But we also can not simply + * abort the run, this would be a DoS. I think an appropriate + * measure is to remove the dangerous \' characters. We + * replace them by \", which will break the message and + * signatures eventually present - but this is the + * best thing we can do now (or does anybody + * have a better idea?). rgerhards 2004-11-23 + * added support for "escapeMode" (so doSQLEscape for details). + * if mode = 1, then backslashes are changed to slashes. + * rgerhards 2005-09-22 + */ +static void doSQLEmergencyEscape(register char *p, int escapeMode) +{ + while(*p) { + if(*p == '\'') + *p = '"'; + else if((escapeMode == 1) && (*p == '\\')) + *p = '/'; + ++p; + } +} + + +/* SQL-Escape a string. Single quotes are found and + * replaced by two of them. A new buffer is allocated + * for the provided string and the provided buffer is + * freed. The length is updated. Parameter pbMustBeFreed + * is set to 1 if a new buffer is allocated. Otherwise, + * it is left untouched. + * -- + * We just discovered a security issue. MySQL is so + * "smart" to not only support the standard SQL mechanism + * for escaping quotes, but to also provide its own (using + * c-type syntax with backslashes). As such, it is actually + * possible to do sql injection via rsyslogd. The cure is now + * to escape backslashes, too. As we have found on the web, some + * other databases seem to be similar "smart" (why do we have standards + * at all if they are violated without any need???). Even better, MySQL's + * smartness depends on config settings. So we add a new option to this + * function that allows the caller to select if they want to standard or + * "smart" encoding ;) + * new parameter escapeMode is 0 - standard sql, 1 - "smart" engines + * 2005-09-22 rgerhards + */ +void doSQLEscape(char **pp, size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode) +{ + char *p; + int iLen; + rsCStrObj *pStrB; + uchar *pszGenerated; + + assert(pp != NULL); + assert(*pp != NULL); + assert(pLen != NULL); + assert(pbMustBeFreed != NULL); + + /* first check if we need to do anything at all... */ + if(escapeMode == 0) + for(p = *pp ; *p && *p != '\'' ; ++p) + ; + else + for(p = *pp ; *p && *p != '\'' && *p != '\\' ; ++p) + ; + /* when we get out of the loop, we are either at the + * string terminator or the first \'. */ + if(*p == '\0') + return; /* nothing to do in this case! */ + + p = *pp; + iLen = *pLen; + if((pStrB = rsCStrConstruct()) == NULL) { + /* oops - no mem ... Do emergency... */ + doSQLEmergencyEscape(p, escapeMode); + return; + } + + while(*p) { + if(*p == '\'') { + if(rsCStrAppendChar(pStrB, (escapeMode == 0) ? '\'' : '\\') != RS_RET_OK) { + doSQLEmergencyEscape(*pp, escapeMode); + rsCStrFinish(pStrB); + if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) != NULL) + free(pszGenerated); + return; + } + iLen++; /* reflect the extra character */ + } else if((escapeMode == 1) && (*p == '\\')) { + if(rsCStrAppendChar(pStrB, '\\') != RS_RET_OK) { + doSQLEmergencyEscape(*pp, escapeMode); + rsCStrFinish(pStrB); + if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) != NULL) + free(pszGenerated); + return; + } + iLen++; /* reflect the extra character */ + } + if(rsCStrAppendChar(pStrB, *p) != RS_RET_OK) { + doSQLEmergencyEscape(*pp, escapeMode); + rsCStrFinish(pStrB); + if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) != NULL) + free(pszGenerated); + return; + } + ++p; + } + rsCStrFinish(pStrB); + if((pszGenerated = rsCStrConvSzStrAndDestruct(pStrB)) == NULL) { + doSQLEmergencyEscape(*pp, escapeMode); + return; + } + + if(*pbMustBeFreed) + free(*pp); /* discard previous value */ + + *pp = (char*) pszGenerated; + *pLen = iLen; + *pbMustBeFreed = 1; +} + /* Constructs a template entry object. Returns pointer to it * or NULL (if it fails). Pointer to associated template list entry * must be provided. @@ -7,6 +7,7 @@ #ifndef TEMPLATE_H_INCLUDED #define TEMPLATE_H_INCLUDED 1 + #ifdef FEATURE_REGEXP /* Include regular expressions */ #include <regex.h> @@ -34,6 +35,8 @@ enum tplFormatTypes { tplFmtDefault = 0, tplFmtMySQLDate = 1, tplFmtRFC3164Date = 2, tplFmtRFC3339Date = 3 }; enum tplFormatCaseConvTypes { tplCaseConvNo = 0, tplCaseConvUpper = 1, tplCaseConvLower = 2 }; +#include "msg.h" + /* a specific parse entry */ struct templateEntry { struct templateEntry *pNext; @@ -73,6 +76,8 @@ void tplDeleteAll(void); void tplDeleteNew(void); void tplPrintList(void); void tplLastStaticInit(struct template *tpl); +uchar *tplToString(struct template *pTpl, msg_t *pMsg); +void doSQLEscape(char **pp, size_t *pLen, unsigned short *pbMustBeFreed, int escapeMode); #endif /* #ifndef TEMPLATE_H_INCLUDED */ /* |