summaryrefslogtreecommitdiffstats
path: root/contrib/pmsnare/pmsnare.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/pmsnare/pmsnare.c')
-rw-r--r--contrib/pmsnare/pmsnare.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/contrib/pmsnare/pmsnare.c b/contrib/pmsnare/pmsnare.c
new file mode 100644
index 0000000..22f17f2
--- /dev/null
+++ b/contrib/pmsnare/pmsnare.c
@@ -0,0 +1,444 @@
+/* pmsnare.c
+ *
+ * this detects logs sent by Snare and cleans them up so that they can be processed by the normal parser
+ *
+ * there are two variations of this, if the client is set to 'syslog' mode it sends
+ *
+ * <pri>timestamp<sp>hostname<sp>tag<tab>otherstuff
+ *
+ * if the client is not set to syslog it sends
+ *
+ * hostname<tab>tag<tab>otherstuff
+ *
+ * The tabs can be represented in different ways. This module will auto-detect the tab representation based on
+ * the global config settings, but they can be overridden for each instance in the config file if needed.
+ *
+ * ToDo, take advantage of items in the message itself to set more friendly information
+ * where the normal parser will find it by re-writing more of the message
+ *
+ * Interesting information includes:
+ *
+ * in the case of windows snare messages:
+ * the system hostname is field 12
+ * the severity is field 3 (criticality ranging form 0 to 4)
+ * the source of the log is field 4 and may be able to be mapped to facility
+ *
+ *
+ * created 2010-12-13 by David Lang based on pmlastmsg
+ * Modified 2017-05-29 by Shane Lawrence.
+ *
+ * This file is part of rsyslog.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * -or-
+ * see COPYING.ASL20 in the source distribution
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+#include "rsyslog.h"
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <ctype.h>
+#include "conf.h"
+#include "syslogd-types.h"
+#include "template.h"
+#include "msg.h"
+#include "module-template.h"
+#include "glbl.h"
+#include "errmsg.h"
+#include "parser.h"
+#include "datetime.h"
+#include "unicode-helper.h"
+#include "rsconf.h"
+
+MODULE_TYPE_PARSER
+MODULE_TYPE_NOKEEP
+PARSER_NAME("rsyslog.snare")
+MODULE_CNFNAME("pmsnare")
+
+/* internal structures
+ */
+DEF_PMOD_STATIC_DATA
+DEFobjCurrIf(glbl)
+DEFobjCurrIf(parser)
+DEFobjCurrIf(datetime)
+
+
+/* static data */
+static int bParseHOSTNAMEandTAG; /* cache for the equally-named global param - performance enhancement */
+
+/* Keep a list of parser instances so we can apply global settings after config is loaded. */
+typedef struct modInstances_s {
+ instanceConf_t *root;
+ instanceConf_t *tail;
+} modInstances_t;
+static modInstances_t *modInstances = NULL;
+
+struct modConfData_s {
+ rsconf_t *pConf; /* our overall config object */
+};
+static modConfData_t *modConf = NULL;
+
+/* Per-instance config settings. */
+static struct cnfparamdescr parserpdescr[] = {
+ { "parser.controlcharacterescapeprefix", eCmdHdlrGetChar, 0 },
+ { "parser.escapecontrolcharactersonreceive", eCmdHdlrBinary, 0 },
+ { "parser.escapecontrolcharactertab", eCmdHdlrBinary, 0},
+ { "parser.escapecontrolcharacterscstyle", eCmdHdlrBinary, 0 }
+};
+static struct cnfparamblk parserpblk = {
+ CNFPARAMBLK_VERSION,
+ sizeof(parserpdescr)/sizeof(struct cnfparamdescr),
+ parserpdescr
+};
+struct instanceConf_s {
+ int bEscapeCCOnRcv;
+ int bEscapeTab;
+ int bParserEscapeCCCStyle;
+ uchar cCCEscapeChar;
+ int tabLength;
+ char tabRepresentation[5];
+ struct instanceConf_s *next;
+};
+
+/* Creates the instance and adds it to the list of instances. */
+static rsRetVal createInstance(instanceConf_t **pinst) {
+ instanceConf_t *inst;
+ DEFiRet;
+ CHKmalloc(inst = malloc(sizeof(instanceConf_t)));
+ inst->next = NULL;
+ *pinst = inst;
+
+ /* Add to list of instances. */
+ if(modInstances == NULL) {
+ CHKmalloc(modInstances = malloc(sizeof(modInstances_t)));
+ modInstances->tail = modInstances->root = NULL;
+ }
+ if (modInstances->tail == NULL) {
+ modInstances->tail = modInstances->root = inst;
+ } else {
+ modInstances->tail->next = inst;
+ modInstances->tail = inst;
+ }
+
+ finalize_it:
+ RETiRet;
+}
+
+BEGINnewParserInst
+ struct cnfparamvals *pvals = NULL;
+ int i;
+CODESTARTnewParserInst
+ DBGPRINTF("newParserInst (pmsnare)\n");
+ inst = NULL;
+ CHKiRet(createInstance(&inst));
+
+ /* Mark these as unset so we know if they should be overridden later. */
+ inst->bEscapeCCOnRcv = -1;
+ inst->bEscapeTab = -1;
+ inst->bParserEscapeCCCStyle = -1;
+ inst->cCCEscapeChar = '\0';
+
+ /* If using the old config, just use global settings for each instance. */
+ if (lst == NULL)
+ FINALIZE;
+
+ /* If using the new config, process module settings for this instance. */
+ if((pvals = nvlstGetParams(lst, &parserpblk, NULL)) == NULL) {
+ ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
+ }
+
+ if(Debug) {
+ dbgprintf("pmsnare: parser param blk:\n");
+ cnfparamsPrint(&parserpblk, pvals);
+ }
+
+ for(i = 0 ; i < parserpblk.nParams ; ++i) {
+ if(!pvals[i].bUsed)
+ continue;
+ if(!strcmp(parserpblk.descr[i].name, "parser.escapecontrolcharactersonreceive")) {
+ inst->bEscapeCCOnRcv = pvals[i].val.d.n;
+ } else if(!strcmp(parserpblk.descr[i].name, "parser.escapecontrolcharactertab")) {
+ inst->bEscapeTab = pvals[i].val.d.n;
+ } else if(!strcmp(parserpblk.descr[i].name, "parser.escapecontrolcharacterscstyle")) {
+ inst->bParserEscapeCCCStyle = pvals[i].val.d.n;
+ } else if(!strcmp(parserpblk.descr[i].name, "parser.controlcharacterescapeprefix")) {
+ inst->cCCEscapeChar = (uchar) *es_str2cstr(pvals[i].val.d.estr, NULL);
+ } else {
+ dbgprintf("pmsnare: program error, non-handled param '%s'\n", parserpblk.descr[i].name);
+ }
+ }
+
+finalize_it:
+CODE_STD_FINALIZERnewParserInst
+ if(lst != NULL)
+ cnfparamvalsDestruct(pvals, &parserpblk);
+ if(iRet != RS_RET_OK)
+ free(inst);
+ENDnewParserInst
+
+BEGINfreeParserInst
+CODESTARTfreeParserInst
+ dbgprintf("pmsnare: free parser instance %p\n", pInst);
+ENDfreeParserInst
+
+BEGINisCompatibleWithFeature
+CODESTARTisCompatibleWithFeature
+ if(eFeat == sFEATUREAutomaticSanitazion)
+ iRet = RS_RET_OK;
+ if(eFeat == sFEATUREAutomaticPRIParsing)
+ iRet = RS_RET_OK;
+ENDisCompatibleWithFeature
+
+/* Interface with the global config. */
+BEGINbeginCnfLoad
+CODESTARTbeginCnfLoad
+ modConf = pModConf;
+ pModConf->pConf = pConf;
+ENDbeginCnfLoad
+
+BEGINsetModCnf
+CODESTARTsetModCnf
+ /* Could use module-globals here, but not global globals. */
+ (void) lst;
+ENDsetModCnf
+
+BEGINendCnfLoad
+ instanceConf_t *inst;
+CODESTARTendCnfLoad
+ dbgprintf("pmsnare: Begin endCnfLoad\n");
+ /* Loop through each parser instance and apply global settings to any option that hasn't been overridden.
+ * This can't be done any earlier because the config wasn't fully loaded until now. */
+ for(inst = modInstances->root; inst != NULL; inst = inst->next) {
+ if(inst->bEscapeCCOnRcv == -1)
+ inst->bEscapeCCOnRcv = glbl.GetParserEscapeControlCharactersOnReceive(modConf->pConf);
+ if(inst->bEscapeTab == -1)
+ inst->bEscapeTab = glbl.GetParserEscapeControlCharacterTab(modConf->pConf);
+ if(inst->bParserEscapeCCCStyle == -1)
+ inst->bParserEscapeCCCStyle = glbl.GetParserEscapeControlCharactersCStyle(modConf->pConf);
+ if(inst->cCCEscapeChar == '\0')
+ inst->cCCEscapeChar = glbl.GetParserControlCharacterEscapePrefix(modConf->pConf);
+
+ /* Determine tab representation. Possible options:
+ * "#011" escape on, escapetabs on, no change to prefix (default)
+ * "?011" prefix changed in config
+ * "\\t" C style
+ * '\t' escape turned off
+ */
+ if (inst->bEscapeCCOnRcv && inst->bEscapeTab) {
+ if (inst->bParserEscapeCCCStyle) {
+ strncpy(inst->tabRepresentation, "\\t", 5);
+ } else {
+ strncpy(inst->tabRepresentation, "#011", 5);
+ inst->tabRepresentation[0] = inst->cCCEscapeChar;
+ }
+ } else {
+ strncpy(inst->tabRepresentation, "\t", 5);
+ }
+ inst->tabLength=strlen(inst->tabRepresentation);
+ /* TODO: This debug message would be more useful if it told which Snare instance! */
+ dbgprintf("pmsnare: Snare parser will treat '%s' as tab.\n", inst->tabRepresentation);
+ }
+
+ assert(pModConf == modConf);
+ENDendCnfLoad
+
+BEGINcheckCnf
+CODESTARTcheckCnf
+ENDcheckCnf
+
+BEGINactivateCnf
+CODESTARTactivateCnf
+ENDactivateCnf
+
+BEGINfreeCnf
+ instanceConf_t *inst, *del;
+CODESTARTfreeCnf
+ for(inst = modInstances->root ; inst != NULL ; ) {
+ del = inst;
+ inst = inst->next;
+ free(del);
+ }
+ free(modInstances);
+ENDfreeCnf
+
+BEGINparse2
+ uchar *p2parse;
+ int lenMsg;
+ int snaremessage; /* 0 means not a snare message, otherwise it's the index of the tab after the tag */
+
+CODESTARTparse2
+ dbgprintf("Message will now be parsed by fix Snare parser.\n");
+ assert(pMsg != NULL);
+ assert(pMsg->pszRawMsg != NULL);
+
+ /* check if this message is of the type we handle in this (very limited) parser
+ *
+ * - Find out if the first separator is a tab.
+ * - If it is, see if the second word is one of our expected tags.
+ * - If so, flag as Snare and replace the first tab with space so that
+ * hostname and syslog tag are going to be parsed properly
+ * - Else not a snare message, abort.
+ * - Else assume valid 3164 timestamp, move over to the syslog tag.
+ * - See if syslog header is followed by tab and one of our expected tags.
+ * - If so, flag as Snare.
+ * - See if either type flagged as Snare.
+ * - If so, replace the tab with a space so that it will be parsed properly.
+ */
+
+ snaremessage=0;
+ /* note: offAfterPRI is already the number of PRI chars (do not add one!) */
+ lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI;
+ /* point to start of text, after PRI */
+ p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI;
+ dbgprintf("pmsnare: msg to look at: [%d]'%s'\n", lenMsg, p2parse);
+ if((unsigned) lenMsg < 30) {
+ /* too short, can not be "our" message */
+ dbgprintf("pmsnare: Message is too short to be Snare!\n");
+ ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE);
+ }
+
+ /* Find the first separator and check if it's a tab. */
+ while(lenMsg && *p2parse != ' ' && *p2parse != '\t' && *p2parse != pInst->tabRepresentation[0]) {
+ --lenMsg;
+ ++p2parse;
+ }
+ if ((lenMsg > pInst->tabLength) && (strncasecmp((char *)p2parse, pInst->tabRepresentation,
+ pInst->tabLength) == 0)) {
+ dbgprintf("pmsnare: tab separated message\n");
+ dbgprintf("pmsnare: tab [%d]'%s' msg at the first separator: [%d]'%s'\n",
+ pInst->tabLength, pInst->tabRepresentation, lenMsg, p2parse);
+
+ /* Look for the Snare tag. */
+ if(strncasecmp((char*)(p2parse + pInst->tabLength), "MSWinEventLog", 13) == 0) {
+ dbgprintf("Found a non-syslog Windows Snare message.\n");
+ snaremessage = p2parse - pMsg->pszRawMsg + pInst->tabLength + 13;
+ }
+ else if(strncasecmp((char*) (p2parse + pInst->tabLength), "LinuxKAudit", 11) == 0) {
+ dbgprintf("Found a non-syslog Linux Snare message.\n");
+ snaremessage = p2parse - pMsg->pszRawMsg + pInst->tabLength + 11;
+ } else {
+ /* Tab-separated but no Snare tag-> can't be Snare! */
+ ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE);
+ }
+
+ /* This is a non-syslog Snare message. Example:
+ * other.lab.home MSWinEventLog 1 Security 606129 Wed May 17 02:25:10 2017
+ */
+
+ /* Remove the tab between the hostname and Snare tag. */
+ *p2parse = ' ';
+ p2parse++;
+ lenMsg--;
+ lenMsg -= (pInst->tabLength-1); /* size of tab goes from tabLength to 1, so shorten
+ the message by the difference */
+ memmove(p2parse, p2parse+(pInst->tabLength-1), lenMsg);
+ /* move the message portion up to overwrite the tab */
+ *(p2parse + lenMsg) = '\0';
+ pMsg->iLenRawMsg -= (pInst->tabLength-1);
+ pMsg->iLenMSG -= (pInst->tabLength-1);
+ snaremessage -= (pInst->tabLength-1);
+ } else {
+ /* The first separator is not a tab. Look for a syslog Snare message. Example:
+ * <14>May 17 02:25:10 syslog.lab.home MSWinEventLog 1 Security 606129
+ Wed May 17 02:25:10 2017
+ */
+
+ /* go back to the beginning of the message */
+ lenMsg = pMsg->iLenRawMsg - pMsg->offAfterPRI;
+ /* offAfterPRI is already the number of PRI chars (do not add one!) */
+ p2parse = pMsg->pszRawMsg + pMsg->offAfterPRI;
+
+ /* skip over timestamp and space (15 chars + space). */
+ lenMsg -=16;
+ p2parse +=16;
+ /* skip over what should be the hostname and space */
+ while(lenMsg && *p2parse != ' ') {
+ --lenMsg;
+ ++p2parse;
+ }
+ if (lenMsg){
+ --lenMsg;
+ ++p2parse;
+ }
+ dbgprintf("pmsnare: tab [%d]'%s' msg after the timestamp and hostname: [%d]'%s'\n",
+ pInst->tabLength,pInst->tabRepresentation,lenMsg, p2parse);
+
+ /* Look for the Snare tag. */
+ if(lenMsg > 13 && strncasecmp((char*) p2parse, "MSWinEventLog", 13) == 0) {
+ dbgprintf("Found a syslog Windows Snare message.\n");
+ snaremessage = p2parse - pMsg->pszRawMsg + 13;
+ }
+ else if(lenMsg > 11 && strncasecmp((char*) p2parse, "LinuxKAudit", 11) == 0) {
+ dbgprintf("pmsnare: Found a syslog Linux Snare message.\n");
+ snaremessage = p2parse - pMsg->pszRawMsg + 11;
+ }
+ }
+
+ if(snaremessage) {
+ /* Skip to the end of the tag. */
+ p2parse = pMsg->pszRawMsg + snaremessage;
+ lenMsg = pMsg->iLenRawMsg - snaremessage;
+
+ /* Remove the tab after the tag. */
+ *p2parse = ' ';
+ p2parse++;
+ lenMsg--;
+ lenMsg -= (pInst->tabLength-1); /* size of tab goes from tabLength to 1, so shorten
+ the message by the difference */
+ memmove(p2parse, p2parse+(pInst->tabLength-1), lenMsg);
+ /* move the message portion up to overwrite the tab */
+ *(p2parse + lenMsg) = '\0';
+ pMsg->iLenRawMsg -= (pInst->tabLength-1);
+ pMsg->iLenMSG -= (pInst->tabLength-1);
+
+ DBGPRINTF("pmsnare: new message: [%d]'%s'\n", lenMsg, pMsg->pszRawMsg + pMsg->offAfterPRI);
+ }
+
+ ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE);
+
+finalize_it:
+ENDparse2
+
+BEGINmodExit
+CODESTARTmodExit
+ /* release what we no longer need */
+ objRelease(glbl, CORE_COMPONENT);
+ objRelease(parser, CORE_COMPONENT);
+ objRelease(datetime, CORE_COMPONENT);
+ENDmodExit
+
+BEGINqueryEtryPt
+CODESTARTqueryEtryPt
+CODEqueryEtryPt_STD_MOD_QUERIES
+CODEqueryEtryPt_STD_CONF2_QUERIES
+CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES
+CODEqueryEtryPt_STD_PMOD2_QUERIES
+CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES
+ENDqueryEtryPt
+
+BEGINmodInit()
+CODESTARTmodInit
+ *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
+CODEmodInit_QueryRegCFSLineHdlr
+ CHKiRet(objUse(glbl, CORE_COMPONENT));
+ CHKiRet(objUse(parser, CORE_COMPONENT));
+ CHKiRet(objUse(datetime, CORE_COMPONENT));
+
+ DBGPRINTF("snare parser init called, compiled with version %s\n", VERSION);
+ bParseHOSTNAMEandTAG = glbl.GetParseHOSTNAMEandTAG(loadConf);
+ /* cache value, is set only during rsyslogd option processing */
+ENDmodInit
+
+/* vim:set ai:
+ */