/* 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:
 */
