/* mmrfc5424addhmac.c
 * custom module: add hmac to RFC5424 messages
 *
 * Note on important design decision: This module is fully self-contained.
 * Most importantly, it does not rely on mmpstrucdata to populate the
 * structured data portion of the messages JSON. There are two reasons
 * for this:
 * 1. robustness
 *    - this guard against misconfiguration
 *    - it permits us to be more liberal in regard to malformed
 *      structured data
 *    - it permits us to handle border-cases (like duplicate 
 *      SD-IDs) with much less complexity
 * 2. performance
 *    With being "on the spot" of what we need we can reduce memory
 *    reads and writes. This is a considerable save if the JSON representation
 *    is not otherwise needed.
 *
 * Note that the recommended calling sequence if both of these modules
 * are used is
 *
 * 1. mmrfc5424addhmac
 * 2. mmpstrucdata
 *
 * This sequence permits mmpstrucdata to pick up the modifications we
 * made in this module here.
 *
 * Copyright 2013 Adiscon GmbH.
 *
 * 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 <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <stdint.h>
#include <openssl/hmac.h>
#include "conf.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "template.h"
#include "module-template.h"
#include "errmsg.h"

MODULE_TYPE_OUTPUT
MODULE_TYPE_NOKEEP
MODULE_CNFNAME("mmrfc5424addhmac")


DEFobjCurrIf(errmsg);
DEF_OMOD_STATIC_DATA

/* config variables */

typedef struct _instanceData {
	uchar *key;
	int16_t keylen;	/* cached length of key, to avoid recomputation */
	uchar *sdid;	/* SD-ID to be used to persist the hmac */
	int16_t sdidLen;
	const EVP_MD *algo;
} instanceData;

typedef struct wrkrInstanceData {
	instanceData *pData;
} wrkrInstanceData_t;

struct modConfData_s {
	rsconf_t *pConf;	/* our overall config object */
};
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 */


/* tables for interfacing with the v6 config system */
/* action (instance) parameters */
static struct cnfparamdescr actpdescr[] = {
	{ "key", eCmdHdlrString, 1 },
	{ "hashfunction", eCmdHdlrString, 1 },
	{ "sd_id", eCmdHdlrGetWord, 1 }
};
static struct cnfparamblk actpblk =
	{ CNFPARAMBLK_VERSION,
	  sizeof(actpdescr)/sizeof(struct cnfparamdescr),
	  actpdescr
	};

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

BEGINendCnfLoad
CODESTARTendCnfLoad
ENDendCnfLoad

BEGINcheckCnf
CODESTARTcheckCnf
ENDcheckCnf

BEGINactivateCnf
CODESTARTactivateCnf
	runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
CODESTARTfreeCnf
ENDfreeCnf


BEGINcreateInstance
CODESTARTcreateInstance
ENDcreateInstance


BEGINcreateWrkrInstance
CODESTARTcreateWrkrInstance
ENDcreateWrkrInstance


BEGINisCompatibleWithFeature
CODESTARTisCompatibleWithFeature
ENDisCompatibleWithFeature


BEGINfreeInstance
CODESTARTfreeInstance
ENDfreeInstance


BEGINfreeWrkrInstance
CODESTARTfreeWrkrInstance
ENDfreeWrkrInstance


static inline void
setInstParamDefaults(instanceData *pData)
{
	pData->key = NULL;
}

BEGINnewActInst
	struct cnfparamvals *pvals;
	char *ciphername;
	int i;
CODESTARTnewActInst
	DBGPRINTF("newActInst (mmrfc5424addhmac)\n");
	if((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) {
		ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
	}

	CODE_STD_STRING_REQUESTnewActInst(1)
	CHKiRet(OMSRsetEntry(*ppOMSR, 0, NULL, OMSR_TPL_AS_MSG));
	CHKiRet(createInstance(&pData));
	setInstParamDefaults(pData);

	for(i = 0 ; i < actpblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(actpblk.descr[i].name, "key")) {
			pData->key = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
			pData->keylen = es_strlen(pvals[i].val.d.estr);
		} else if(!strcmp(actpblk.descr[i].name, "hashfunction")) {
			ciphername = es_str2cstr(pvals[i].val.d.estr, NULL);
			pData->algo = EVP_get_digestbyname(ciphername);
			if(pData->algo == NULL) {
				errmsg.LogError(0, RS_RET_CRY_INVLD_ALGO,
					"hashFunction '%s' unknown to openssl - "
					"cannot continue", ciphername);
				free(ciphername);
				ABORT_FINALIZE(RS_RET_CRY_INVLD_ALGO);
			}
			free(ciphername);
		} else if(!strcmp(actpblk.descr[i].name, "sd_id")) {
			pData->sdid = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
			pData->sdidLen = es_strlen(pvals[i].val.d.estr);
		} else {
			dbgprintf("mmrfc5424addhmac: program error, non-handled "
			  "param '%s'\n", actpblk.descr[i].name);
		}
	}

CODE_STD_FINALIZERnewActInst
	cnfparamvalsDestruct(pvals, &actpblk);
ENDnewActInst


BEGINdbgPrintInstInfo
CODESTARTdbgPrintInstInfo
ENDdbgPrintInstInfo


BEGINtryResume
CODESTARTtryResume
ENDtryResume


/* turn the binary data in bin of length len into a
 * printable hex string. "print" must be 2*len+1 (for \0)
 */
static inline void
hexify(uchar *bin, int len, uchar *print)
{
	static const char hexchars[16] =
	   {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
	int iSrc, iDst;

	for(iSrc = iDst = 0 ; iSrc < len ; ++iSrc) {
		print[iDst++] = hexchars[bin[iSrc]>>4];
		print[iDst++] = hexchars[bin[iSrc]&0x0f];
	}
	print[iDst] = '\0';
}


/* skip to end of current SD-ID. This function can be improved
 * in regard to fully parsing based on RFC5424, HOWEVER, this would
 * also reduce performance. So we consider the current implementation
 * to be superior.
 */
static inline void
skipSDID(uchar *sdbuf, int sdlen, int *rootIdx)
{
	int i;
	i = *rootIdx;
	while(i < sdlen) {
		if(sdbuf[i] == ']') {
			if(i > *rootIdx && sdbuf[i-1] == '\\') {
				; /* escaped, nothing to do! */
			} else {
				++i; /* eat ']' */
				break;
			}
		}
		++i;
	}
	*rootIdx = i;
}

static inline void
getSDID(uchar *sdbuf, int sdlen, int *rootIdx, uchar *sdid)
{
	int i, j;
	i = *rootIdx;
	j = 0;

	if(sdbuf[i] != '[') {
		++i;
		goto done;
	}
	
	++i;
	while(i < sdlen && sdbuf[i] != '=' && sdbuf[i] != ' '
	                && sdbuf[i] != ']' && sdbuf[i] != '"') {
		sdid[j++] = sdbuf[i++];
	}
done:
	sdid[j] = '\0';
	*rootIdx = i;
}

/* check if "our" hmac is already present */
static inline sbool
isHmacPresent(instanceData *pData, msg_t *pMsg)
{
	uchar *sdbuf;
	rs_size_t sdlen;
	sbool found;
	int i;
	uchar sdid[33]; /* RFC-based size limit */

	MsgGetStructuredData(pMsg, &sdbuf, &sdlen);
	found = 0;

	if(sdbuf[0] == '-') /* RFC: struc data is empty! */
		goto done;

	i = 0;
	while(i < sdlen && !found) {
		getSDID(sdbuf, sdlen, &i, sdid);
		if(!strcmp((char*)pData->sdid, (char*)sdid)) {
			found = 1;
			break;
		}
		skipSDID(sdbuf, sdlen, &i);
	}

done:
	return found;
}

static inline rsRetVal
hashMsg(instanceData *pData, msg_t *pMsg)
{
	uchar *pRawMsg;
	int lenRawMsg;
	uchar *sdbuf;
	rs_size_t sdlen;
	unsigned int hashlen;
	uchar hash[EVP_MAX_MD_SIZE];
	uchar hashPrintable[2*EVP_MAX_MD_SIZE+1];
	uchar newsd[64*1024];	/* we assume this is sufficient... */
	int lenNewsd;
	DEFiRet;

	MsgGetStructuredData(pMsg, &sdbuf, &sdlen);
	getRawMsg(pMsg, &pRawMsg, &lenRawMsg);
 	HMAC(pData->algo, pData->key, pData->keylen,
	     pRawMsg, lenRawMsg, hash, &hashlen);
	hexify(hash, hashlen, hashPrintable);
	lenNewsd = snprintf((char*)newsd, sizeof(newsd), "[%s hash=\"%s\"]",
		            (char*)pData->sdid, (char*)hashPrintable);
	MsgAddToStructuredData(pMsg, newsd, lenNewsd);
	RETiRet;
}


BEGINdoAction
	instanceData *pData = pWrkrData->pData;
	msg_t *pMsg;
CODESTARTdoAction
	pMsg = (msg_t*) ppString[0];
	if(   msgGetProtocolVersion(pMsg) == MSG_RFC5424_PROTOCOL
	   && !isHmacPresent(pData, pMsg)) {
		hashMsg(pData, pMsg);
	} else {
		if(Debug) {
			uchar *pRawMsg;
			int lenRawMsg;
			getRawMsg(pMsg, &pRawMsg, &lenRawMsg);
			dbgprintf("mmrfc5424addhmac: non-rfc5424 or HMAC already "
			          "present: %.256s\n", pRawMsg);
		}
	}
ENDdoAction


BEGINparseSelectorAct
CODESTARTparseSelectorAct
CODE_STD_STRING_REQUESTparseSelectorAct(1)
	if(strncmp((char*) p, ":mmrfc5424addhmac:", sizeof(":mmrfc5424addhmac:") - 1)) {
		errmsg.LogError(0, RS_RET_LEGA_ACT_NOT_SUPPORTED,
			"mmrfc5424addhmac supports only v6+ config format, use: "
			"action(type=\"mmrfc5424addhmac\" ...)");
	}
	ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


BEGINmodExit
CODESTARTmodExit
	objRelease(errmsg, CORE_COMPONENT);
	EVP_cleanup();
ENDmodExit


BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_OMOD_QUERIES
CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES
CODEqueryEtryPt_STD_OMOD8_QUERIES
CODEqueryEtryPt_STD_CONF2_QUERIES
ENDqueryEtryPt



BEGINmodInit()
CODESTARTmodInit
	*ipIFVersProvided = CURR_MOD_IF_VERSION;
CODEmodInit_QueryRegCFSLineHdlr
	DBGPRINTF("mmrfc5424addhmac: module compiled with rsyslog version %s.\n", VERSION);
	OpenSSL_add_all_digests();
	CHKiRet(objUse(errmsg, CORE_COMPONENT));
ENDmodInit
