diff options
Diffstat (limited to 'contrib/pmsnare/pmsnare.c')
-rw-r--r-- | contrib/pmsnare/pmsnare.c | 444 |
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: + */ |