/* mmsnmptrapd.c
 * This is a message modification module. It takes messages generated
 * from snmptrapd and modifies them so that the look like they
 * originated from the real originator.
 *
 * NOTE: read comments in module-template.h for details on the calling interface!
 *
 * File begun on 2011-05-05 by RGerhards
 *
 * Copyright 2011 Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of rsyslog.
 *
 * Rsyslog is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Rsyslog is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Rsyslog.  If not, see <http://www.gnu.org/licenses/>.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
 */
#include "config.h"
#include "rsyslog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <ctype.h>
#include "conf.h"
#include "msg.h"
#include "syslogd-types.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"
#include "cfsysline.h"
#include "unicode-helper.h"
#include "dirty.h"

MODULE_TYPE_OUTPUT
MODULE_TYPE_NOKEEP
MODULE_CNFNAME("mmsnmptrapd")

static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal);

/* static data */
DEFobjCurrIf(errmsg);

/* internal structures
 */
DEF_OMOD_STATIC_DATA

struct severMap_s {
	uchar *name;
	int code;
	struct severMap_s *next;
};

typedef struct _instanceData {
	uchar *pszTagName;
	uchar *pszTagID;	/* chaced: name plus trailing shlash (for compares) */
	int lenTagID;		/* cached length of tag ID, for performance reasons */
	struct severMap_s *severMap;
} instanceData;

typedef struct configSettings_s {
	uchar *pszTagName;	/**< name of tag start value that indicates snmptrapd initiated message */
	uchar *pszSeverityMapping; /**< severitystring to numerical code mapping for snmptrapd string */
} configSettings_t;
static configSettings_t cs;

BEGINinitConfVars		/* (re)set config variables to default values */
CODESTARTinitConfVars 
	cs.pszTagName = NULL;
	cs.pszSeverityMapping = NULL;
	resetConfigVariables(NULL, NULL);
ENDinitConfVars


BEGINcreateInstance
CODESTARTcreateInstance
ENDcreateInstance


BEGINisCompatibleWithFeature
CODESTARTisCompatibleWithFeature
ENDisCompatibleWithFeature


BEGINfreeInstance
	struct severMap_s *node, *nodeDel;
CODESTARTfreeInstance
	for(node = pData->severMap ; node != NULL ; ) {
		nodeDel = node;
		node = node->next;
		free(nodeDel->name);
		free(nodeDel);
	}
	free(pData->pszTagName);
	free(pData->pszTagID);
ENDfreeInstance


BEGINdbgPrintInstInfo
CODESTARTdbgPrintInstInfo
	dbgprintf("mmsnmptrapd\n");
ENDdbgPrintInstInfo


BEGINtryResume
CODESTARTtryResume
ENDtryResume


/* check if a string is numeric (int) */
static inline int
isNumeric(uchar *str)
{
	int r = 1;
	if(*str == '-' || *str == '+')
		++str;
	while(*str) {
		if(!isdigit(*str)) {
			r = 0;
			goto done;
		}
		++str;
	}
done:
	return r;
}

/* get a substring delimited by a character (or end of string). The
 * string is trimmed, that is leading and trailing spaces are removed.
 * The caller must provide a buffer which shall receive the substring.
 * String length is returned as result. The input string is updated
 * on exit, so that it may be used for another query starting at that
 * position.
 */
static int
getSubstring(uchar **psrc, uchar delim, uchar *dst, int lenDst)
{
	uchar *dstwrk = dst;
	uchar *src = *psrc;
	while(*src && isspace(*src)) {
		++src;	/* trim leading spaces */
	}
	while(*src && *src != delim && --lenDst > 0) {
		*dstwrk++ = *src++;
	}
	dstwrk--;
	while(dstwrk > dst && isspace(*dst))
		--dstwrk; /* trim trailing spaces */
	*++dstwrk = '\0';
	
	/* final results */
	if(*src == delim)
		++src;
	*psrc = src;
	return(dstwrk - dst);
}


/* get string up to the next SP or '/'. Stops at max size.
 * dst, lenDst (receive buffer) must be given. lenDst is
 * max length on entry and actual length on exit.
 */
static int
getTagComponent(uchar *tag, uchar *dst, int *lenDst)
{
	int end = *lenDst - 1; /* -1 for NUL-char! */
	int i;

	i = 0;
	if(tag[i] != '/')
		goto done;
	++tag;
	while(i < end && tag[i] != '\0' && tag[i] != ' ' && tag[i] != '/') {
		dst[i] = tag[i];
		++i;
	}
	dst[i] = '\0';
dbgprintf("XXXX: getTagComponent dst on output: '%s', len %d\n", dst, i);
	*lenDst = i;
done:
	return i;
}


/* lookup severity code based on provided severity
 * returns -1 if severity could not be found.
 */
static inline int
lookupSeverityCode(instanceData *pData, uchar *sever)
{
	struct severMap_s *node;
	int sevCode = -1;

	for(node = pData->severMap ; node != NULL ; node = node->next) {
		if(!ustrcmp(node->name, sever)) {
			sevCode = node->code;
			break;
		}
	}
	return sevCode;
}


BEGINdoAction
	int lenTAG;
	int lenSever;
	int lenHost;
	int sevCode;
	msg_t *pMsg;
	uchar *pszTag;
	uchar pszSever[512];
	uchar pszHost[512];
CODESTARTdoAction
	pMsg = (msg_t*) ppString[0];
	dbgprintf("XXXX: mmsnmptrapd called with pMsg %p\n", pMsg);
	getTAG(pMsg, &pszTag, &lenTAG);
	if(strncmp((char*)pszTag, (char*)pData->pszTagID, pData->lenTagID)) {
		DBGPRINTF("tag '%s' not matching, mmsnmptrapd ignoring this message\n",
			  pszTag);
		FINALIZE;
	}

	lenSever = sizeof(pszSever);
dbgprintf("XXXX: pszTag: '%s', lenID %d\n", pszTag, pData->lenTagID);
	getTagComponent(pszTag+pData->lenTagID-1, pszSever, &lenSever);
	lenHost = sizeof(pszHost);
	getTagComponent(pszTag+pData->lenTagID+lenSever, pszHost, &lenHost);
	dbgprintf("XXXX: mmsnmptrapd sever '%s'(%d), host '%s'(%d)\n", pszSever, lenSever, pszHost,lenHost);

	if(pszHost[lenHost-1] == ':') {
		pszHost[lenHost-1] = '\0';
		--lenHost;
	}
	sevCode = lookupSeverityCode(pData, pszSever);
dbgprintf("XXXX: severity for message is %d\n", sevCode);
	/* now apply new settings */
	MsgSetTAG(pMsg, pData->pszTagName, pData->lenTagID);
	MsgSetHOSTNAME(pMsg, pszHost, lenHost);
	if(sevCode != -1)
		pMsg->iSeverity = sevCode; /* we update like the parser does! */
finalize_it:
ENDdoAction


/* Build the severity mapping table based on user-provided configuration
 * settings.
 */
static inline rsRetVal
buildSeverityMapping(instanceData *pData)
{
	uchar pszSev[512];
	uchar pszSevCode[512];
	int sevCode;
	uchar *mapping;
	struct severMap_s *node;
	DEFiRet;

	mapping = cs.pszSeverityMapping;

	while(1) {	/* broken inside when all entries are processed */
		if(getSubstring(&mapping, '/', pszSev, sizeof(pszSev)) == 0) {
			FINALIZE;
		}
		if(getSubstring(&mapping, ',', pszSevCode, sizeof(pszSevCode)) == 0) {
			errmsg.LogError(0, RS_RET_ERR, "error: invalid severity mapping, cannot "
					"extract code. given: '%s'\n", cs.pszSeverityMapping);
			ABORT_FINALIZE(RS_RET_ERR);
		}
		sevCode = atoi((char*) pszSevCode);
		if(!isNumeric(pszSevCode))
			sevCode = -1;
		if(sevCode < 0 || sevCode > 7) {
			errmsg.LogError(0, RS_RET_ERR, "error: severity code %d outside of valid "
					"range 0..7 (was string '%s')\n", sevCode, pszSevCode);
			ABORT_FINALIZE(RS_RET_ERR);
		}
		CHKmalloc(node = MALLOC(sizeof(struct severMap_s)));
		CHKmalloc(node->name = ustrdup(pszSev));
		node->code = sevCode;
		/* we enqueue at the top, so the two lines below do all we need! */
		node->next = pData->severMap;
		pData->severMap = node;
		DBGPRINTF("mmsnmptrapd: severity string '%s' mapped to code %d\n",
			  pszSev, sevCode);
	}

finalize_it:
	RETiRet;
}


BEGINparseSelectorAct
CODESTARTparseSelectorAct
CODE_STD_STRING_REQUESTparseSelectorAct(1)
	/* first check if this config line is actually for us */
	if(strncmp((char*) p, ":mmsnmptrapd:", sizeof(":mmsnmptrapd:") - 1)) {
		ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
	}

	/* ok, if we reach this point, we have something for us */
	p += sizeof(":mmsnmptrapd:") - 1; /* eat indicator sequence  (-1 because of '\0'!) */
	CHKiRet(createInstance(&pData));

	/* check if a non-standard template is to be applied */
	if(*(p-1) == ';')
		--p;
	/* we call the function below because we need to call it via our interface definition. However,
	 * the format specified (if any) is always ignored.
	 */
	CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_TPL_AS_MSG, (uchar*) "RSYSLOG_FileFormat"));

	/* finally build the instance */
	if(cs.pszTagName == NULL) {
		pData->pszTagName = (uchar*) strdup("snmptrapd:");
		pData->pszTagID = (uchar*) strdup("snmptrapd/");
	} else {
		int lenTag = ustrlen(cs.pszTagName);
		/* new tag value (with colon at the end) */
		CHKmalloc(pData->pszTagName = MALLOC(lenTag + 2));
		memcpy(pData->pszTagName, cs.pszTagName, lenTag);
		memcpy(pData->pszTagName+lenTag, ":", 2);
		/* tag ID for comparisions */
		CHKmalloc(pData->pszTagID = MALLOC(lenTag + 2));
		memcpy(pData->pszTagID, cs.pszTagName, lenTag);
		memcpy(pData->pszTagID+lenTag, "/", 2);
		free(cs.pszTagName); /* no longer needed */
	}
	pData->lenTagID = ustrlen(pData->pszTagID);
	if(cs.pszSeverityMapping != NULL) {
		CHKiRet(buildSeverityMapping(pData));
	}

	/* all config vars auto-reset! */
	cs.pszTagName = NULL;
	free(cs.pszSeverityMapping);
	cs.pszSeverityMapping = NULL;
CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


BEGINmodExit
CODESTARTmodExit
	objRelease(errmsg, CORE_COMPONENT);
ENDmodExit


BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_OMOD_QUERIES
CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES 
ENDqueryEtryPt



/* Reset config variables for this module to default values.
 */
static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
{
	DEFiRet;
	free(cs.pszTagName);
	cs.pszTagName = NULL;
	free(cs.pszSeverityMapping);
	cs.pszSeverityMapping = NULL;
	RETiRet;
}


BEGINmodInit()
	rsRetVal localRet;
	rsRetVal (*pomsrGetSupportedTplOpts)(unsigned long *pOpts);
	unsigned long opts;
	int bMsgPassingSupported;
CODESTARTmodInit
INITLegCnfVars
	*ipIFVersProvided = CURR_MOD_IF_VERSION;
		/* we only support the current interface specification */
CODEmodInit_QueryRegCFSLineHdlr
	/* check if the rsyslog core supports parameter passing code */
	bMsgPassingSupported = 0;
	localRet = pHostQueryEtryPt((uchar*)"OMSRgetSupportedTplOpts",
			&pomsrGetSupportedTplOpts);
	if(localRet == RS_RET_OK) {
		/* found entry point, so let's see if core supports msg passing */
		CHKiRet((*pomsrGetSupportedTplOpts)(&opts));
		if(opts & OMSR_TPL_AS_MSG)
			bMsgPassingSupported = 1;
	} else if(localRet != RS_RET_ENTRY_POINT_NOT_FOUND) {
		ABORT_FINALIZE(localRet); /* Something else went wrong, not acceptable */
	}
	
	if(!bMsgPassingSupported) {
		DBGPRINTF("mmsnmptrapd: msg-passing is not supported by rsyslog core, "
			  "can not continue.\n");
		ABORT_FINALIZE(RS_RET_NO_MSG_PASSING);
	}

	CHKiRet(objUse(errmsg, CORE_COMPONENT));

	/* TODO: config vars ininit can be replaced by commented-out code above in v6 */
	cs.pszTagName = NULL;
	cs.pszSeverityMapping = NULL;
	
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmsnmptrapdtag", 0, eCmdHdlrGetWord,
				    NULL, &cs.pszTagName, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmsnmptrapdseveritymapping", 0, eCmdHdlrGetWord,
				    NULL, &cs.pszSeverityMapping, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler,
				    resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
ENDmodInit

/* vi:set ai:
 */
