/* mmnormalize.c
 * This is a message modification module. It normalizes the input message with
 * the help of liblognorm. The message's JSON variables are updated.
 *
 * NOTE: read comments in module-template.h for details on the calling interface!
 *
 * File begun on 2010-01-01 by RGerhards
 *
 * Copyright 2010-2015 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 <libestr.h>
#include <json.h>
#include <liblognorm.h>
#include "conf.h"
#include "syslogd-types.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"
#include "cfsysline.h"
#include "dirty.h"
#include "unicode-helper.h"

MODULE_TYPE_OUTPUT
MODULE_TYPE_NOKEEP
MODULE_CNFNAME("mmnormalize")

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

/* static data */
DEFobjCurrIf(errmsg);

/* internal structures
 */
DEF_OMOD_STATIC_DATA

static struct cnfparamdescr modpdescr[] = {
	{ "allowregex", eCmdHdlrBinary, 0 }
};

static struct cnfparamblk modpblk = {
	CNFPARAMBLK_VERSION,
	sizeof(modpdescr)/sizeof(struct cnfparamdescr),
	modpdescr
};

typedef struct _instanceData {
	sbool bUseRawMsg;	/**< use %rawmsg% instead of %msg% */
	uchar 	*rulebase;	/**< name of rulebase to use */
	ln_ctx ctxln;		/**< context to be used for liblognorm */
	char *pszPath;		/**< path of normalized data */
	msgPropDescr_t *varDescr;/**< name of variable to use */
} instanceData;

typedef struct wrkrInstanceData {
	instanceData *pData;
} wrkrInstanceData_t;

typedef struct configSettings_s {
	uchar *rulebase;		/**< name of normalization rulebase to use */
	int bUseRawMsg;	/**< use %rawmsg% instead of %msg% */
} configSettings_t;
static configSettings_t cs;

/* tables for interfacing with the v6 config system */
/* action (instance) parameters */
static struct cnfparamdescr actpdescr[] = {
	{ "rulebase", eCmdHdlrGetWord, 1 },
	{ "path", eCmdHdlrGetWord, 0 },
	{ "userawmsg", eCmdHdlrBinary, 0 },
	{ "variable", eCmdHdlrGetWord, 0 }
};
static struct cnfparamblk actpblk =
	{ CNFPARAMBLK_VERSION,
	  sizeof(actpdescr)/sizeof(struct cnfparamdescr),
	  actpdescr
	};

struct modConfData_s {
	rsconf_t *pConf;	/* our overall config object */
	int allow_regex;
};

static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */
static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current exec process */

/* callback for liblognorm error messages */
static void
errCallBack(void __attribute__((unused)) *cookie, const char *msg,
	    size_t __attribute__((unused)) lenMsg)
{
	errmsg.LogError(0, RS_RET_ERR_LIBLOGNORM, "liblognorm error: %s", msg);
}

/* to be called to build the liblognorm part of the instance ONCE ALL PARAMETERS ARE CORRECT
 * (and set within pData!).
 */
static rsRetVal
buildInstance(instanceData *pData)
{
	DEFiRet;
	if((pData->ctxln = ln_initCtx()) == NULL) {
		errmsg.LogError(0, RS_RET_ERR_LIBLOGNORM_INIT, "error: could not initialize "
				"liblognorm ctx, cannot activate action");
		ABORT_FINALIZE(RS_RET_ERR_LIBLOGNORM_INIT);
	}
	ln_setCtxOpts(pData->ctxln, loadModConf->allow_regex);
	ln_setErrMsgCB(pData->ctxln, errCallBack, NULL);
	if(ln_loadSamples(pData->ctxln, (char*) pData->rulebase) != 0) {
		errmsg.LogError(0, RS_RET_NO_RULEBASE, "error: normalization rulebase '%s' "
				"could not be loaded cannot activate action", pData->rulebase);
		ln_exitCtx(pData->ctxln);
		ABORT_FINALIZE(RS_RET_ERR_LIBLOGNORM_SAMPDB_LOAD);
	}
finalize_it:
	RETiRet;
}


BEGINinitConfVars		/* (re)set config variables to default values */
CODESTARTinitConfVars 
	resetConfigVariables(NULL, NULL);
ENDinitConfVars


BEGINcreateInstance
CODESTARTcreateInstance
ENDcreateInstance


BEGINcreateWrkrInstance
CODESTARTcreateWrkrInstance
ENDcreateWrkrInstance


BEGINbeginCnfLoad
CODESTARTbeginCnfLoad
	loadModConf = pModConf;
	pModConf->pConf = pConf;
ENDbeginCnfLoad


BEGINendCnfLoad
CODESTARTendCnfLoad
	loadModConf = NULL; /* done loading */
	/* free legacy config vars */
	free(cs.rulebase);
	cs.rulebase = NULL;
ENDendCnfLoad

BEGINcheckCnf
CODESTARTcheckCnf
ENDcheckCnf

BEGINactivateCnf
CODESTARTactivateCnf
	runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
CODESTARTfreeCnf
ENDfreeCnf


BEGINisCompatibleWithFeature
CODESTARTisCompatibleWithFeature
ENDisCompatibleWithFeature


BEGINfreeInstance
CODESTARTfreeInstance
	free(pData->rulebase);
	ln_exitCtx(pData->ctxln);
	free(pData->pszPath);
	msgPropDescrDestruct(pData->varDescr);
	free(pData->varDescr);
ENDfreeInstance


BEGINfreeWrkrInstance
CODESTARTfreeWrkrInstance
ENDfreeWrkrInstance


BEGINdbgPrintInstInfo
CODESTARTdbgPrintInstInfo
	dbgprintf("mmnormalize\n");
	dbgprintf("\tvariable='%s'\n", pData->varDescr->name);
	dbgprintf("\trulebase='%s'\n", pData->rulebase);
	dbgprintf("\tpath='%s'\n", pData->pszPath);
	dbgprintf("\tbUseRawMsg='%d'\n", pData->bUseRawMsg);
ENDdbgPrintInstInfo


BEGINtryResume
CODESTARTtryResume
ENDtryResume

BEGINdoAction_NoStrings
	msg_t **ppMsg = (msg_t **) pMsgData;
	msg_t *pMsg = ppMsg[0];
	uchar *buf;
	rs_size_t len;
	int r;
	struct json_object *json = NULL;
	unsigned short freeBuf = 0;
CODESTARTdoAction
	if(pWrkrData->pData->bUseRawMsg) {
		getRawMsg(pMsg, &buf, &len);
	} else if (pWrkrData->pData->varDescr) {
		buf = MsgGetProp(pMsg, NULL, pWrkrData->pData->varDescr, &len, &freeBuf, NULL);
	} else {
		buf = getMSG(pMsg);
		len = getMSGLen(pMsg);
	}
	r = ln_normalize(pWrkrData->pData->ctxln, (char*)buf, len, &json);
	if (freeBuf) {
		free(buf);
		buf = NULL;
	}
	if(r != 0) {
		DBGPRINTF("error %d during ln_normalize\n", r);
		MsgSetParseSuccess(pMsg, 0);
	} else {
		MsgSetParseSuccess(pMsg, 1);
	}

	msgAddJSON(pMsg, (uchar*)pWrkrData->pData->pszPath + 1, json, 0, 0);

ENDdoAction


static inline void
setInstParamDefaults(instanceData *pData)
{
	pData->rulebase = NULL;
	pData->bUseRawMsg = 0;
	pData->pszPath = strdup("$!");
	pData->varDescr = NULL;
}

BEGINsetModCnf
	struct cnfparamvals *pvals = NULL;
	int i;
CODESTARTsetModCnf
	pvals = nvlstGetParams(lst, &modpblk, NULL);
	if(pvals == NULL) {
		errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "mmnormalize: error processing module "
						"config parameters missing [module(...)]");
		ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
	}
	
	if(Debug) {
		dbgprintf("module (global) param blk for mmnormalize:\n");
		cnfparamsPrint(&modpblk, pvals);
	}
	
	for(i = 0 ; i < modpblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(modpblk.descr[i].name, "allowregex")) {
			loadModConf->allow_regex = (int) pvals[i].val.d.n;
		} else {
			dbgprintf("mmnormalize: program error, non-handled "
					  "param '%s' in setModCnf\n", modpblk.descr[i].name);
		}
	}
	
finalize_it:
	if(pvals != NULL)
		cnfparamvalsDestruct(pvals, &modpblk);
ENDsetModCnf


BEGINnewActInst
	struct cnfparamvals *pvals;
	int i;
	int bDestructPValsOnExit;
	char *cstr;
	char *varName = NULL;
CODESTARTnewActInst
	DBGPRINTF("newActInst (mmnormalize)\n");

	bDestructPValsOnExit = 0;
	pvals = nvlstGetParams(lst, &actpblk, NULL);
	if(pvals == NULL) {
		errmsg.LogError(0, RS_RET_MISSING_CNFPARAMS, "mmnormalize: error reading "
				"config parameters");
		ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
	}
	bDestructPValsOnExit = 1;

	if(Debug) {
		dbgprintf("action param blk in mmnormalize:\n");
		cnfparamsPrint(&actpblk, pvals);
	}

	CHKiRet(createInstance(&pData));
	setInstParamDefaults(pData);

	for(i = 0 ; i < actpblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(actpblk.descr[i].name, "rulebase")) {
			pData->rulebase = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(actpblk.descr[i].name, "userawmsg")) {
			pData->bUseRawMsg = (int) pvals[i].val.d.n;
		} else if(!strcmp(actpblk.descr[i].name, "variable")) {
			varName = es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(actpblk.descr[i].name, "path")) {
			cstr = es_str2cstr(pvals[i].val.d.estr, NULL);
			if (strlen(cstr) < 2) {
				errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED,
						"mmnormalize: valid path name should be at least "
						"2 symbols long, got %s",	cstr);
				free(cstr);
			} else if (cstr[0] != '$') {
				errmsg.LogError(0, RS_RET_VALUE_NOT_SUPPORTED,
						"mmnormalize: valid path name should start with $,"
						"got %s", cstr);
				free(cstr);
			} else {
				free(pData->pszPath);
				pData->pszPath = cstr;
			}
			continue;
		} else {
			DBGPRINTF("mmnormalize: program error, non-handled "
			  "param '%s'\n", actpblk.descr[i].name);
		}
	}

	if (varName) {
		if(pData->bUseRawMsg) {
			errmsg.LogError(0, RS_RET_CONFIG_ERROR,
			                "mmnormalize: 'variable' param can't be used with 'useRawMsg'. "
			                "Ignoring 'variable', will use raw message.");
		} else {
			CHKmalloc(pData->varDescr = MALLOC(sizeof(msgPropDescr_t)));
			CHKiRet(msgPropDescrFill(pData->varDescr, (uchar*) varName, strlen(varName)));
		}
		free(varName);
		varName = NULL;
	}

	CODE_STD_STRING_REQUESTnewActInst(1)
	CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG));
	iRet = buildInstance(pData);
CODE_STD_FINALIZERnewActInst
	if(bDestructPValsOnExit)
		cnfparamvalsDestruct(pvals, &actpblk);
ENDnewActInst


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

	if(cs.rulebase == NULL) {
		errmsg.LogError(0, RS_RET_NO_RULEBASE, "error: no normalization rulebase was specified, use "
				"$MMNormalizeSampleDB directive first!");
		ABORT_FINALIZE(RS_RET_NO_RULEBASE);
	}

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

	pData->rulebase = cs.rulebase;
	pData->bUseRawMsg = cs.bUseRawMsg;
	pData->pszPath = strdup("$!"); /* old interface does not support this feature */
	/* all config vars auto-reset! */
	cs.bUseRawMsg = 0;
	cs.rulebase = NULL; /* we used it up! */

	/* 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"));
	CHKiRet(buildInstance(pData));
CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


BEGINmodExit
CODESTARTmodExit
	objRelease(errmsg, CORE_COMPONENT);
ENDmodExit


BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_OMOD_QUERIES
CODEqueryEtryPt_STD_OMOD8_QUERIES
CODEqueryEtryPt_STD_CONF2_QUERIES
CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES
CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES
ENDqueryEtryPt



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

/* set the rulebase name */
static rsRetVal
setRuleBase(void __attribute__((unused)) *pVal, uchar *pszName)
{
	DEFiRet;
	cs.rulebase = pszName;
	pszName = 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
	DBGPRINTF("mmnormalize: module compiled with rsyslog version %s.\n", VERSION);
	/* 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("mmnormalize: msg-passing is not supported by rsyslog core, "
			  "can not continue.\n");
		ABORT_FINALIZE(RS_RET_NO_MSG_PASSING);
	}

	CHKiRet(objUse(errmsg, CORE_COMPONENT));
	
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmnormalizerulebase", 0, eCmdHdlrGetWord,
				    setRuleBase, NULL, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"mmnormalizeuserawmsg", 0, eCmdHdlrBinary,
				    NULL, &cs.bUseRawMsg, STD_LOADABLE_MODULE_ID));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler,
				    resetConfigVariables, NULL, STD_LOADABLE_MODULE_ID));
ENDmodInit

/* vi:set ai:
 */
