/* 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 #include #include #include #include #include #include #include #include #include #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