/
githubmirror
/
rsyslog
Обзор
Документация
Войти
/
githubmirror
/
rsyslog
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
runtime/template.c
3 275 строк
128 KB
Rainer Gerhards
template: avoid new legacy octal escape
29 июн 2026, 19:13
29 июн 2026, 19:13
b7d76a3
Код
Авторство
О чём код?
/* This is the template processing code of rsyslog. * begun 2004-11-17 rgerhards * * Copyright 2004-2019 Rainer Gerhards and Adiscon * * 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. * * Note: there is a tiny bit of code left where I could not get any response * from the author if this code can be placed under ASL2.0. I have guarded this * with #ifdef STRICT_GPLV3. Only if that macro is defined, the code will be * compiled. Otherwise this feature is not present. The plan is to do a * different implementation in the future to get rid of this problem. * rgerhards, 2012-08-25 */ #include "config.h" #include "rsyslog.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <ctype.h> #include <assert.h> #include <limits.h> #include <stdint.h> #include <json.h> #include "stringbuf.h" #include "syslogd-types.h" #include "template.h" #include "msg.h" #include "dirty.h" #include "obj.h" #include "errmsg.h" #include "strgen.h" #include "rsconf.h" #include "msg.h" #include "parserif.h" #include "unicode-helper.h" /* states for lazily built JSON tree used in list templates with jsonf mode */ #define TPL_JSON_TREE_NOT_BUILT 0 #define TPL_JSON_TREE_BUILT 1 #define TPL_JSON_TREE_UNSUPPORTED 2 enum tplJsonNodeType { tplJsonNodeObject = 0, tplJsonNodeValue = 1 }; struct tplJsonNode { enum tplJsonNodeType type; uchar *name; size_t nameLen; struct templateEntry *pTpe; /* only valid for value nodes */ struct tplJsonNode *parent; struct tplJsonNode *firstChild; struct tplJsonNode *lastChild; struct tplJsonNode *nextSibling; }; static struct tplJsonNode *tplJsonNodeNew(enum tplJsonNodeType type, const uchar *name, size_t nameLen); static void tplJsonNodeAddChild(struct tplJsonNode *parent, struct tplJsonNode *child); static void tplJsonNodeFree(struct tplJsonNode *node); static rsRetVal tplJsonEnsureContainer( struct tplJsonNode *parent, const uchar *name, size_t nameLen, struct tplJsonNode **ppContainer, int *pUnsupported); static rsRetVal tplJsonAddValueNode( struct tplJsonNode *parent, struct templateEntry *pTpe, const uchar *name, size_t nameLen, int *pUnsupported); static rsRetVal tplJsonBuildTree(struct template *pTpl); static rsRetVal tplJsonRender(struct template *pTpl, smsg_t *pMsg, actWrkrIParams_t *iparam, struct syslogTime *ttNow); static rsRetVal tplJsonRenderChildren( const struct tplJsonNode *parent, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppDst, int *pNeedComma); static rsRetVal tplAddSize(size_t a, size_t b, size_t *sum); /** Returns true when at least one field lacks explicit secure path handling. */ static int tplNeedsDynafileSecureDefault(const struct template *const pTpl) { const struct templateEntry *pTpe; assert(pTpl != NULL); for (pTpe = pTpl->pEntryRoot; pTpe != NULL; pTpe = pTpe->pNext) { if (pTpe->eEntryType == FIELD && !pTpe->data.field.options.bSecPathDrop && !pTpe->data.field.options.bSecPathReplace) { return 1; } } return 0; } /** Applies the strict dynafile path default to fields without explicit mode. */ static void tplApplyDynafileSecureDefault(struct template *const pTpl) { struct templateEntry *pTpe; assert(pTpl != NULL); for (pTpe = pTpl->pEntryRoot; pTpe != NULL; pTpe = pTpe->pNext) { if (pTpe->eEntryType == FIELD && !pTpe->data.field.options.bSecPathDrop && !pTpe->data.field.options.bSecPathReplace) { pTpe->data.field.options.bSecPathReplace = 1; pTpe->bComplexProcessing = 1; } } pTpl->bAppliedDynafileSecureDefault = 1; } /** Records a template use and enforces compatibility.defaults.secure policy. */ void tplNoteUse(struct template *const pTpl, const int bDynafile) { assert(pTpl != NULL); if (bDynafile) { pTpl->bUsedAsDynafile = 1; } else { pTpl->bUsedAsNonDynafile = 1; } if (pTpl->bUsedAsDynafile && pTpl->bUsedAsNonDynafile && !pTpl->bWarnedDynafileMixedUse) { if (loadConf->globals.compatDefaultsSecure == COMPAT_DEFAULTS_SECURE_STRICT) { parser_warnmsg( "template '%s' is used both as an omfile dynafile template and for other output; " "strict secure dynafile defaults apply to the shared template, use separate templates " "if literal slashes must be preserved outside dynafile paths", pTpl->pszName); } else { parser_warnmsg( "template '%s' is used both as an omfile dynafile template and for other output; " "in strict mode, secure dynafile defaults apply to the shared template, use separate " "templates if literal slashes must be preserved outside dynafile paths", pTpl->pszName); } pTpl->bWarnedDynafileMixedUse = 1; } if (!bDynafile || !tplNeedsDynafileSecureDefault(pTpl)) { return; } if (loadConf->globals.compatDefaultsSecure == COMPAT_DEFAULTS_SECURE_STRICT) { if (!pTpl->bAppliedDynafileSecureDefault) { tplApplyDynafileSecureDefault(pTpl); } } else if (loadConf->globals.compatDefaultsSecure == COMPAT_DEFAULTS_SECURE_WARN && !pTpl->bWarnedDynafileSecureDefault) { parser_warnmsg( "backward-compatible insecure default in use: omfile dynafile template '%s' has " "fields without securepath handling; use global(compatibility.defaults.secure=\"strict\") " "to default them to secpath-replace", pTpl->pszName); pTpl->bWarnedDynafileSecureDefault = 1; } } static rsRetVal tplJsonRenderNode( const struct tplJsonNode *node, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppOut, int *pHasOutput); static rsRetVal tplJsonRenderValue( const struct tplJsonNode *node, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppOut, int *pHasOutput); static rsRetVal tplJsonRenderObject( const struct tplJsonNode *node, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppOut, int *pHasOutput); static void tplWarnDuplicateJsonKeys(struct template *pTpl); /** * \brief Allocate a fresh JSON tree node for dotted jsonf rendering. * * The node optionally owns a copy of \p name so descendants can be * addressed without depending on the template entry backing storage. * * \param type Node role (object container or scalar value). * \param name Optional name for the node. * \param nameLen Length of \p name in bytes (without terminator). * \return Newly allocated node or NULL on allocation failure. */ static struct tplJsonNode *tplJsonNodeNew(enum tplJsonNodeType type, const uchar *name, size_t nameLen) { struct tplJsonNode *node = calloc(1, sizeof(struct tplJsonNode)); if (node == NULL) return NULL; node->type = type; node->pTpe = NULL; if (name != NULL && nameLen > 0) { if (nameLen == SIZE_MAX) { free(node); return NULL; } node->name = malloc(nameLen + 1); if (node->name == NULL) { free(node); return NULL; } memcpy(node->name, name, nameLen); node->name[nameLen] = '\0'; node->nameLen = nameLen; } return node; } /** * \brief Link a child node to its parent while maintaining sibling order. * * The helper records both first and last child so breadth-first iteration * is cheap when walking the tree later during rendering. * * \param parent Parent object that will receive \p child. * \param child Node to link; ownership is transferred to \p parent. */ static void tplJsonNodeAddChild(struct tplJsonNode *parent, struct tplJsonNode *child) { if (parent == NULL || child == NULL) return; child->parent = parent; child->nextSibling = NULL; if (parent->firstChild == NULL) { parent->firstChild = parent->lastChild = child; } else { parent->lastChild->nextSibling = child; parent->lastChild = child; } } /** * \brief Recursively dispose a JSON node subtree. * * Frees all descendants, the node name buffer, and the node itself. * Safe to call with NULL. * * \param node Root of the subtree to release. */ static void tplJsonNodeFree(struct tplJsonNode *node) { if (node == NULL) return; struct tplJsonNode *child = node->firstChild; while (child != NULL) { struct tplJsonNode *next = child->nextSibling; tplJsonNodeFree(child); child = next; } free(node->name); free(node); } /** * \brief Ensure an object child exists for the given dotted segment. * * Creates an intermediate container when necessary and flags the template * as unsupported if the segment collides with an existing value node. * * \param parent Current node that should hold the container. * \param name Segment name to ensure. * \param nameLen Length of \p name. * \param ppContainer Out parameter receiving the container when found. * \param pUnsupported Set to 1 when the template cannot be expressed. * \retval RS_RET_OK Container exists or was created. * \retval RS_RET_OUT_OF_MEMORY Allocation failed. */ static rsRetVal tplJsonEnsureContainer(struct tplJsonNode *parent, const uchar *name, size_t nameLen, struct tplJsonNode **ppContainer, int *pUnsupported) { struct tplJsonNode *child; if (ppContainer != NULL) *ppContainer = NULL; if (pUnsupported != NULL) *pUnsupported = 0; if (parent == NULL) return RS_RET_ERR; for (child = parent->firstChild; child != NULL; child = child->nextSibling) { if (child->nameLen == nameLen && memcmp(child->name, name, nameLen) == 0) { if (child->type != tplJsonNodeObject) { if (pUnsupported != NULL) *pUnsupported = 1; return RS_RET_OK; } if (ppContainer != NULL) *ppContainer = child; return RS_RET_OK; } } child = tplJsonNodeNew(tplJsonNodeObject, name, nameLen); if (child == NULL) return RS_RET_OUT_OF_MEMORY; tplJsonNodeAddChild(parent, child); if (ppContainer != NULL) *ppContainer = child; return RS_RET_OK; } /** * \brief Attach a value node for a template entry at the current tree level. * * Rejects collisions where an existing object node already occupies the * desired name and propagates an unsupported flag to the caller. * * \param parent Object node receiving the value. * \param pTpe Template entry that supplies the value. * \param name Final path segment for the value. * \param nameLen Length of \p name. * \param pUnsupported Output flag marked when a collision is detected. * \retval RS_RET_OK Value node linked or collision recorded. * \retval RS_RET_OUT_OF_MEMORY Allocation failed. */ static rsRetVal tplJsonAddValueNode( struct tplJsonNode *parent, struct templateEntry *pTpe, const uchar *name, size_t nameLen, int *pUnsupported) { struct tplJsonNode *child; if (pUnsupported != NULL) *pUnsupported = 0; if (parent == NULL) return RS_RET_ERR; for (child = parent->firstChild; child != NULL; child = child->nextSibling) { if (child->nameLen == nameLen && memcmp(child->name, name, nameLen) == 0) { if (child->type == tplJsonNodeObject) { if (pUnsupported != NULL) *pUnsupported = 1; return RS_RET_OK; } child->pTpe = pTpe; return RS_RET_OK; } } child = tplJsonNodeNew(tplJsonNodeValue, name, nameLen); if (child == NULL) return RS_RET_OUT_OF_MEMORY; child->pTpe = pTpe; tplJsonNodeAddChild(parent, child); return RS_RET_OK; } /** * \brief Build the JSON tree representation for a jsonf list template. * * Walks dotted outname segments, creating containers as required and * recording which templates cannot be represented as structured JSON. * * \param pTpl Template to analyse; its tree fields are updated in place. * \retval RS_RET_OK on success or when jsonf is unsupported for the template. * \retval RS_RET_OUT_OF_MEMORY on allocation failure. */ static rsRetVal tplJsonBuildTree(struct template *pTpl) { struct tplJsonNode *root = NULL; struct templateEntry *pTpe; int unsupported = 0; DEFiRet; if (pTpl->bJsonTreeBuilt != TPL_JSON_TREE_NOT_BUILT) RETiRet; CHKmalloc(root = tplJsonNodeNew(tplJsonNodeObject, NULL, 0)); for (pTpe = pTpl->pEntryRoot; pTpe != NULL; pTpe = pTpe->pNext) { if (pTpe->fieldName == NULL || pTpe->lenFieldName == 0) { unsupported = 1; break; } if (pTpe->eEntryType == CONSTANT) { if (!pTpe->data.constant.bJSONf) { unsupported = 1; break; } } else if (pTpe->eEntryType == FIELD) { if (!pTpe->data.field.options.bJSONf && !pTpe->data.field.options.bJSONfr) { unsupported = 1; break; } } else { unsupported = 1; break; } struct tplJsonNode *parent = root; const uchar *segStart = pTpe->fieldName; const uchar *ptr = pTpe->fieldName; const uchar *end = pTpe->fieldName + pTpe->lenFieldName; while (ptr < end) { if (*ptr == '.') { if (ptr == segStart) { unsupported = 1; break; } struct tplJsonNode *container = NULL; const size_t segLen = (size_t)(ptr - segStart); CHKiRet(tplJsonEnsureContainer(parent, segStart, segLen, &container, &unsupported)); if (unsupported) break; parent = container; ++ptr; segStart = ptr; continue; } ++ptr; } if (unsupported) break; const size_t segLen = (size_t)(end - segStart); if (segLen == 0) { unsupported = 1; break; } CHKiRet(tplJsonAddValueNode(parent, pTpe, segStart, segLen, &unsupported)); if (unsupported) break; } if (unsupported) { tplJsonNodeFree(root); pTpl->pJsonRoot = NULL; pTpl->bJsonTreeBuilt = TPL_JSON_TREE_UNSUPPORTED; LogError(0, NO_ERRCODE, "template '%s' with option jsonftree has conflicting keys (e.g. 'a' and 'a.b'); " "falling back to flat JSON output.", pTpl->pszName != NULL ? pTpl->pszName : "<unnamed>"); } else { pTpl->pJsonRoot = root; pTpl->bJsonTreeBuilt = TPL_JSON_TREE_BUILT; } finalize_it: if (iRet != RS_RET_OK) { tplJsonNodeFree(root); } RETiRet; } /** * \brief Render a list template with jsonf option via the pre-built tree. * * Serialises the lazily cached tree into the caller-provided work buffer * producing canonical JSON with nested objects. * * \param pTpl Template to render; must already have a built tree. * \param pMsg Message supplying field data. * \param iparam Worker output buffer descriptor. * \param ttNow Timestamp context for field formatting. * \retval RS_RET_OK on success or an error code from subordinate lookups. */ static rsRetVal tplJsonRender(struct template *pTpl, smsg_t *pMsg, actWrkrIParams_t *iparam, struct syslogTime *ttNow) { es_str_t *out = NULL; char *rendered = NULL; int needComma = 0; DEFiRet; if (pTpl->pJsonRoot == NULL) RETiRet; CHKmalloc(out = es_newStr(128)); es_addChar(&out, '{'); CHKiRet(tplJsonRenderChildren(pTpl->pJsonRoot, pMsg, ttNow, &out, &needComma)); es_addBufConstcstr(&out, "}\n"); const int len = es_strlen(out); if (len < 0) { ABORT_FINALIZE(RS_RET_ERR); } size_t neededLen; CHKiRet(tplAddSize((size_t)len, 1, &neededLen)); if (neededLen > iparam->lenBuf) { CHKiRet(ExtendBuf(iparam, neededLen)); } CHKmalloc(rendered = es_str2cstr(out, NULL)); memcpy(iparam->param, rendered, (size_t)len); iparam->param[len] = '\0'; iparam->lenStr = len; finalize_it: if (rendered != NULL) free(rendered); if (out != NULL) es_deleteStr(out); RETiRet; } /** * \brief Render all children of a JSON node, inserting commas as needed. * * Recursively descends into the tree and appends emitted fragments to the * destination string buffer. * * \param parent Node whose children should be rendered. * \param pMsg Message that provides runtime values. * \param ttNow Timestamp context for property formatting. * \param ppDst Destination string buffer reference. * \param pNeedComma Tracks whether a comma should precede the next child. */ static rsRetVal tplJsonRenderChildren( const struct tplJsonNode *parent, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppDst, int *pNeedComma) { const struct tplJsonNode *child = parent->firstChild; es_str_t *childStr = NULL; int childHasOutput = 0; DEFiRet; while (child != NULL) { childStr = NULL; childHasOutput = 0; CHKiRet(tplJsonRenderNode(child, pMsg, ttNow, &childStr, &childHasOutput)); if (childHasOutput) { if (*pNeedComma) es_addBufConstcstr(ppDst, ", "); es_addStr(ppDst, childStr); *pNeedComma = 1; } if (childStr != NULL) { es_deleteStr(childStr); childStr = NULL; } child = child->nextSibling; } finalize_it: if (childStr != NULL) es_deleteStr(childStr); RETiRet; } /** * \brief Dispatch renderer for a node depending on its type. * * Value nodes are expanded into name/value pairs, while object nodes yield * nested JSON objects. * * \param node Node to render. * \param pMsg Message providing data. * \param ttNow Timestamp context. * \param ppOut Output string produced for this node. * \param pHasOutput Flag indicating whether a value was emitted. */ static rsRetVal tplJsonRenderNode( const struct tplJsonNode *node, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppOut, int *pHasOutput) { if (node->type == tplJsonNodeValue) { return tplJsonRenderValue(node, pMsg, ttNow, ppOut, pHasOutput); } return tplJsonRenderObject(node, pMsg, ttNow, ppOut, pHasOutput); } /** * \brief Render a scalar template entry into a JSON name/value fragment. * * Handles both constant and property-backed entries, including trimming any * legacy jsonf field prefixes and skipping empty expansions. * * \param node Value node describing the template entry. * \param pMsg Message context for property lookups. * \param ttNow Timestamp context. * \param ppOut Newly allocated string with the rendered fragment. * \param pHasOutput Set to 1 if the fragment contains data. */ static rsRetVal tplJsonRenderValue( const struct tplJsonNode *node, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppOut, int *pHasOutput) { uchar *pVal = NULL; rs_size_t lenVal = 0; unsigned short bMustBeFreed = 0; es_str_t *out = NULL; const uchar *valuePtr; size_t valueLen; const uchar *colon; DEFiRet; *ppOut = NULL; *pHasOutput = 0; if (node->pTpe == NULL || node->name == NULL || node->nameLen == 0) RETiRet; if (node->pTpe->eEntryType == CONSTANT) { pVal = node->pTpe->data.constant.pConstant; lenVal = node->pTpe->data.constant.iLenConstant; } else if (node->pTpe->eEntryType == FIELD) { pVal = MsgGetProp(pMsg, node->pTpe, &node->pTpe->data.field.msgProp, &lenVal, &bMustBeFreed, ttNow); } else { RETiRet; } if (pVal == NULL || lenVal <= 0) { FINALIZE; } valuePtr = pVal; valueLen = (size_t)lenVal; colon = memchr(pVal, ':', (size_t)lenVal); if (colon != NULL) { const uchar *keyStart = pVal; const uchar *keyEnd = colon; while (keyStart < colon && isspace((int)*keyStart)) ++keyStart; while (keyEnd > keyStart && isspace((int)*(keyEnd - 1))) --keyEnd; const size_t prefixLen = (size_t)(keyEnd - keyStart); int matchesKey = 0; if (prefixLen >= 2 && keyStart[0] == '"' && keyEnd[-1] == '"' && node->pTpe->fieldName != NULL) { if ((size_t)node->pTpe->lenFieldName == prefixLen - 2 && memcmp(keyStart + 1, node->pTpe->fieldName, node->pTpe->lenFieldName) == 0) { matchesKey = 1; } } if (matchesKey) { size_t offset = (size_t)((colon + 1) - pVal); if (offset >= (size_t)lenVal) { FINALIZE; } valuePtr = pVal + offset; valueLen = (size_t)lenVal - offset; } } if (node->pTpe->eEntryType == FIELD && node->pTpe->data.field.options.dataType == TPE_DATATYPE_NUMBER) { while (valueLen > 0 && isspace((int)*valuePtr)) { ++valuePtr; --valueLen; } while (valueLen > 0 && isspace((int)valuePtr[valueLen - 1])) --valueLen; if (node->pTpe->data.field.options.bOmitIfZero && valueLen == 1 && valuePtr[0] == '0') { FINALIZE; } } if (valueLen == 0) { FINALIZE; } size_t requiredLen; CHKiRet(tplAddSize(node->nameLen, valueLen, &requiredLen)); CHKiRet(tplAddSize(requiredLen, 4, &requiredLen)); if (requiredLen > (size_t)INT_MAX) { parser_errmsg("error: jsonf value to be rendered is too large"); ABORT_FINALIZE(RS_RET_ERR); } CHKmalloc(out = es_newStr((int)requiredLen)); es_addChar(&out, '"'); es_addBuf(&out, (const char *)node->name, node->nameLen); es_addBufConstcstr(&out, "\":"); es_addBuf(&out, (const char *)valuePtr, valueLen); *ppOut = out; out = NULL; *pHasOutput = 1; finalize_it: if (bMustBeFreed && pVal != NULL) free(pVal); if (out != NULL) es_deleteStr(out); RETiRet; } /** * \brief Render an object node and all nested children into JSON. * * Skips empty objects to preserve legacy behaviour while ensuring commas * and braces are placed consistently. * * \param node Object node to render. * \param pMsg Message supplying data for descendants. * \param ttNow Timestamp context. * \param ppOut Output string containing the rendered object. * \param pHasOutput Flag set when the object emitted any fields. */ static rsRetVal tplJsonRenderObject( const struct tplJsonNode *node, smsg_t *pMsg, struct syslogTime *ttNow, es_str_t **ppOut, int *pHasOutput) { es_str_t *out = NULL; es_str_t *childStr = NULL; int needComma = 0; const struct tplJsonNode *child; DEFiRet; *ppOut = NULL; *pHasOutput = 0; if (node->name == NULL || node->nameLen == 0) RETiRet; size_t requiredLen; CHKiRet(tplAddSize(node->nameLen, 5, &requiredLen)); if (requiredLen > (size_t)INT_MAX) { parser_errmsg("error: jsonf object key to be rendered is too large"); ABORT_FINALIZE(RS_RET_ERR); } CHKmalloc(out = es_newStr((int)requiredLen)); es_addChar(&out, '"'); es_addBuf(&out, (const char *)node->name, node->nameLen); es_addBufConstcstr(&out, "\": {"); child = node->firstChild; while (child != NULL) { childStr = NULL; int childHasOutput = 0; CHKiRet(tplJsonRenderNode(child, pMsg, ttNow, &childStr, &childHasOutput)); if (childHasOutput) { if (needComma) es_addBufConstcstr(&out, ", "); es_addStr(&out, childStr); needComma = 1; } if (childStr != NULL) { es_deleteStr(childStr); childStr = NULL; } child = child->nextSibling; } if (!needComma) { FINALIZE; } es_addChar(&out, '}'); *ppOut = out; out = NULL; *pHasOutput = 1; finalize_it: if (childStr != NULL) es_deleteStr(childStr); if (out != NULL) es_deleteStr(out); RETiRet; } static void tplWarnDuplicateJsonKeys(struct template *pTpl) { struct templateEntry *curr; if (pTpl == NULL) return; for (curr = pTpl->pEntryRoot; curr != NULL; curr = curr->pNext) { struct templateEntry *scan; int isJsonField = 0; if (curr->fieldName == NULL || curr->lenFieldName <= 0) continue; if (curr->eEntryType == CONSTANT) { isJsonField = curr->data.constant.bJSONf; } else if (curr->eEntryType == FIELD) { isJsonField = curr->data.field.options.bJSONf || curr->data.field.options.bJSONfr; } if (!isJsonField) continue; for (scan = pTpl->pEntryRoot; scan != curr; scan = scan->pNext) { int scanJsonField = 0; if (scan->fieldName == NULL || scan->lenFieldName <= 0) continue; if (scan->eEntryType == CONSTANT) { scanJsonField = scan->data.constant.bJSONf; } else if (scan->eEntryType == FIELD) { scanJsonField = scan->data.field.options.bJSONf || scan->data.field.options.bJSONfr; } if (!scanJsonField) continue; if (scan->lenFieldName == curr->lenFieldName && memcmp(scan->fieldName, curr->fieldName, curr->lenFieldName) == 0) { LogError(0, NO_ERRCODE, "template '%s': duplicate JSON key '%.*s' encountered; later definition takes precedence", pTpl->pszName != NULL ? pTpl->pszName : "<unnamed>", curr->lenFieldName, curr->fieldName); break; } } } } PRAGMA_IGNORE_Wswitch_enum /* static data */ DEFobjCurrIf(obj) DEFobjCurrIf(strgen) /* tables for interfacing with the v6 config system */ static struct cnfparamdescr cnfparamdescr[] = {{"name", eCmdHdlrString, 1}, {"type", eCmdHdlrString, 1}, {"string", eCmdHdlrString, 0}, {"plugin", eCmdHdlrString, 0}, {"subtree", eCmdHdlrString, 0}, {"option.stdsql", eCmdHdlrBinary, 0}, {"option.sql", eCmdHdlrBinary, 0}, {"option.json", eCmdHdlrBinary, 0}, {"option.jsonf", eCmdHdlrBinary, 0}, {"option.jsonftree", eCmdHdlrBinary, 0}, {"option.casesensitive", eCmdHdlrBinary, 0}}; static struct cnfparamblk pblk = {CNFPARAMBLK_VERSION, sizeof(cnfparamdescr) / sizeof(struct cnfparamdescr), cnfparamdescr}; static struct cnfparamdescr cnfparamdescrProperty[] = {{"name", eCmdHdlrString, 1}, {"outname", eCmdHdlrString, 0}, {"dateformat", eCmdHdlrString, 0}, {"date.inutc", eCmdHdlrBinary, 0}, {"compressspace", eCmdHdlrBinary, 0}, {"caseconversion", eCmdHdlrString, 0}, {"controlcharacters", eCmdHdlrString, 0}, {"securepath", eCmdHdlrString, 0}, {"format", eCmdHdlrString, 0}, {"position.from", eCmdHdlrInt, 0}, {"position.to", eCmdHdlrInt, 0}, {"position.relativetoend", eCmdHdlrBinary, 0}, {"field.number", eCmdHdlrInt, 0}, {"field.delimiter", eCmdHdlrInt, 0}, {"regex.expression", eCmdHdlrString, 0}, {"regex.type", eCmdHdlrString, 0}, {"regex.nomatchmode", eCmdHdlrString, 0}, {"regex.match", eCmdHdlrInt, 0}, {"regex.submatch", eCmdHdlrInt, 0}, {"droplastlf", eCmdHdlrBinary, 0}, {"fixedwidth", eCmdHdlrBinary, 0}, {"datatype", eCmdHdlrString, 0}, {"onempty", eCmdHdlrString, 0}, {"mandatory", eCmdHdlrBinary, 0}, {"omitifzero", eCmdHdlrBinary, 0}, {"spifno1stsp", eCmdHdlrBinary, 0}}; static struct cnfparamblk pblkProperty = { CNFPARAMBLK_VERSION, sizeof(cnfparamdescrProperty) / sizeof(struct cnfparamdescr), cnfparamdescrProperty}; static struct cnfparamdescr cnfparamdescrConstant[] = { {"value", eCmdHdlrString, 1}, {"format", eCmdHdlrString, 0}, {"outname", eCmdHdlrString, 0}}; static struct cnfparamblk pblkConstant = { CNFPARAMBLK_VERSION, sizeof(cnfparamdescrConstant) / sizeof(struct cnfparamdescr), cnfparamdescrConstant}; #ifdef FEATURE_REGEXP DEFobjCurrIf(regexp) static int bFirstRegexpErrmsg = 1; /**< did we already do a "can't load regexp" error message? */ static rsRetVal tplValidateRegexMatchSelector(struct template *const pTpl, const char *const paramName, const int selector) { if (selector < 0 || selector >= TPL_REGEX_MAX_MATCHES) { LogError(0, RS_RET_ERR, "template %s error: %s=%d is invalid (supported range 0..%d)", pTpl->pszName != NULL ? pTpl->pszName : "<unnamed>", paramName, selector, TPL_REGEX_MAX_MATCHES - 1); return RS_RET_ERR; } return RS_RET_OK; } #endif /* helper to tplToString and strgen's, extends buffer */ #define ALLOC_INC 128 rsRetVal ExtendBuf(actWrkrIParams_t *__restrict__ const iparam, const size_t iMinSize) { uchar *pNewBuf; size_t iNewSize; DEFiRet; if (iMinSize > SIZE_MAX - ALLOC_INC) { parser_errmsg("error: template output buffer size overflow"); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } iNewSize = (iMinSize / ALLOC_INC + 1) * ALLOC_INC; CHKmalloc(pNewBuf = (uchar *)realloc(iparam->param, iNewSize)); iparam->param = pNewBuf; iparam->lenBuf = iNewSize; finalize_it: RETiRet; } static rsRetVal tplAddSize(const size_t a, const size_t b, size_t *const sum) { if (a > SIZE_MAX - b) { parser_errmsg("error: template size calculation overflow"); return RS_RET_OUT_OF_MEMORY; } *sum = a + b; return RS_RET_OK; } static int tplParseNonNegativeInt(uchar **const pp, int *const out) { uchar *p = *pp; int val = 0; int overflow = 0; while (isdigit((int)*p)) { const int digit = *p++ - '0'; if (val > (INT_MAX - digit) / 10) { overflow = 1; val = INT_MAX; while (isdigit((int)*p)) ++p; break; } val = val * 10 + digit; } *pp = p; *out = val; return overflow; } /* This functions converts a template into a string. * * The function takes a pointer to a template and a pointer to a msg object * as well as a pointer to an output buffer and its size. Note that the output * buffer pointer may be NULL, size 0, in which case a new one is allocated. * The output buffer is grown as required. It is the caller's duty to free the * buffer when it is done. Note that it is advisable to reuse memory, as this * offers big performance improvements. * rewritten 2009-06-19 rgerhards */ rsRetVal tplToString(struct template *__restrict__ const pTpl, smsg_t *__restrict__ const pMsg, actWrkrIParams_t *__restrict__ const iparam, struct syslogTime *const ttNow) { DEFiRet; struct templateEntry *__restrict__ pTpe; size_t iBuf; unsigned short bMustBeFreed = 0; uchar *pVal; rs_size_t iLenVal = 0; int need_comma = 0; if (pTpl->pStrgen != NULL) { CHKiRet(pTpl->pStrgen(pMsg, iparam)); FINALIZE; } if (pTpl->bHaveSubtree) { /* only a single CEE subtree must be provided */ /* note: we could optimize the code below, however, this is * not worth the effort, as this passing mode is not expected * in subtree mode and so most probably only used for debug & test. */ getJSONPropVal(pMsg, &pTpl->subtree, &pVal, &iLenVal, &bMustBeFreed); if (iLenVal < 0) { ABORT_FINALIZE(RS_RET_ERR); } if ((size_t)iLenVal >= iparam->lenBuf) { /* we reserve one char for the final \0! */ size_t neededLen; CHKiRet(tplAddSize((size_t)iLenVal, 1, &neededLen)); CHKiRet(ExtendBuf(iparam, neededLen)); } memcpy(iparam->param, pVal, iLenVal + 1); iparam->lenStr = iLenVal; FINALIZE; } /* we have a "regular" template with template entries */ if (pTpl->optFormatEscape == JSONF && pTpl->bJsonTreeEnabled) { if (pTpl->bJsonTreeBuilt == TPL_JSON_TREE_NOT_BUILT) { CHKiRet(tplJsonBuildTree(pTpl)); } if (pTpl->bJsonTreeBuilt == TPL_JSON_TREE_BUILT && pTpl->pJsonRoot != NULL) { CHKiRet(tplJsonRender(pTpl, pMsg, iparam, ttNow)); FINALIZE; } } /* loop through the template. We obtain one value * and copy it over to our dynamic string buffer. Then, we * free the obtained value (if requested). We continue this * loop until we got hold of all values. */ pTpe = pTpl->pEntryRoot; iBuf = 0; const int isJsonFlat = (pTpl->optFormatEscape == JSONF && (!pTpl->bJsonTreeEnabled || pTpl->bJsonTreeBuilt == TPL_JSON_TREE_UNSUPPORTED)); if (isJsonFlat) { if (iparam->lenBuf < 2) /* we reserve one char for the final \0! */ CHKiRet(ExtendBuf(iparam, 2)); iBuf = 1; *iparam->param = '{'; } while (pTpe != NULL) { if (pTpe->eEntryType == CONSTANT) { pVal = (uchar *)pTpe->data.constant.pConstant; iLenVal = pTpe->data.constant.iLenConstant; bMustBeFreed = 0; } else if (pTpe->eEntryType == FIELD) { pVal = (uchar *)MsgGetProp(pMsg, pTpe, &pTpe->data.field.msgProp, &iLenVal, &bMustBeFreed, ttNow); if (pVal == NULL) { DBGPRINTF("template property evaluation returned NULL, using empty value\n"); pVal = UCHAR_CONSTANT(""); iLenVal = 0; bMustBeFreed = 0; } /* we now need to check if we should use SQL option. In this case, * we must go over the generated string and escape '\'' characters. * rgerhards, 2005-09-22: the option values below look somewhat misplaced, * but they are handled in this way because of legacy (don't break any * existing thing). */ if (pTpl->optFormatEscape == SQL_ESCAPE) doEscape(&pVal, &iLenVal, &bMustBeFreed, SQL_ESCAPE); else if (pTpl->optFormatEscape == JSON_ESCAPE) doEscape(&pVal, &iLenVal, &bMustBeFreed, JSON_ESCAPE); else if (pTpl->optFormatEscape == STDSQL_ESCAPE) doEscape(&pVal, &iLenVal, &bMustBeFreed, STDSQL_ESCAPE); } else { DBGPRINTF("TplToString: invalid entry type %d\n", pTpe->eEntryType); pVal = (uchar *)"*LOGIC ERROR*"; iLenVal = sizeof("*LOGIC ERROR*") - 1; bMustBeFreed = 0; } /* got source, now copy over */ const int isLastEntry = (pTpe->pNext == NULL); const size_t closingLen = (isJsonFlat && isLastEntry) ? 2 : 0; size_t requiredLen = closingLen; if (iLenVal > 0) { if (need_comma) CHKiRet(tplAddSize(requiredLen, 2, &requiredLen)); CHKiRet(tplAddSize(requiredLen, (size_t)iLenVal, &requiredLen)); } if (requiredLen > 0) { size_t neededLen; CHKiRet(tplAddSize(iBuf, requiredLen, &neededLen)); if (neededLen >= iparam->lenBuf) { CHKiRet(tplAddSize(neededLen, 1, &neededLen)); CHKiRet(ExtendBuf(iparam, neededLen)); } } if (iLenVal > 0) { /* may be zero depending on property */ if (need_comma) { memcpy(iparam->param + iBuf, ", ", 2); iBuf += 2; } memcpy(iparam->param + iBuf, pVal, iLenVal); iBuf += iLenVal; if (isJsonFlat) { need_comma = 1; } } if (closingLen > 0) { memcpy(iparam->param + iBuf, "}\n", 2); iBuf += 2; } if (bMustBeFreed) { free(pVal); bMustBeFreed = 0; } pTpe = pTpe->pNext; } if (iBuf == iparam->lenBuf) { /* in the weired case of an *empty* template, this can happen. * it is debatable if we should really fix it here or simply * forbid that case. However, performance toll is minimal, so * I tend to permit it. -- 2010-11-05 rgerhards */ CHKiRet(ExtendBuf(iparam, iBuf + 1)); } iparam->param[iBuf] = '\0'; iparam->lenStr = iBuf; finalize_it: if (bMustBeFreed) { free(pVal); bMustBeFreed = 0; } RETiRet; } /* This functions converts a template into a json object. * For further general details, see the very similar funtion * tpltoString(). * rgerhards, 2012-08-29 */ rsRetVal tplToJSON(struct template *pTpl, smsg_t *pMsg, struct json_object **pjson, struct syslogTime *ttNow) { struct templateEntry *pTpe; rs_size_t propLen; unsigned short bMustBeFreed; uchar *pVal; struct json_object *json, *jsonf; rsRetVal localRet; DEFiRet; if (pTpl->bHaveSubtree) { if (jsonFind(pMsg, &pTpl->subtree, pjson) != RS_RET_OK) *pjson = NULL; if (*pjson == NULL) { /* we need to have a root object! */ *pjson = json_object_new_object(); } else { json_object_get(*pjson); /* inc refcount */ } FINALIZE; } json = json_object_new_object(); for (pTpe = pTpl->pEntryRoot; pTpe != NULL; pTpe = pTpe->pNext) { if (pTpe->eEntryType == CONSTANT) { if (pTpe->fieldName == NULL) continue; jsonf = json_object_new_string((char *)pTpe->data.constant.pConstant); json_object_object_add(json, (char *)pTpe->fieldName, jsonf); } else if (pTpe->eEntryType == FIELD) { if (pTpe->data.field.msgProp.id == PROP_CEE || pTpe->data.field.msgProp.id == PROP_LOCAL_VAR || pTpe->data.field.msgProp.id == PROP_GLOBAL_VAR) { localRet = msgGetJSONPropJSON(pMsg, &pTpe->data.field.msgProp, &jsonf); if (localRet == RS_RET_OK) { json_object_object_add(json, (char *)pTpe->fieldName, json_object_get(jsonf)); } else { DBGPRINTF("tplToJSON: error %d looking up property %s\n", localRet, pTpe->fieldName); if (pTpe->data.field.options.bMandatory) { json_object_object_add(json, (char *)pTpe->fieldName, NULL); } } } else { pVal = (uchar *)MsgGetProp(pMsg, pTpe, &pTpe->data.field.msgProp, &propLen, &bMustBeFreed, ttNow); if (pTpe->data.field.options.bMandatory || propLen > 0) { if (propLen < 0 || propLen == INT_MAX) { ABORT_FINALIZE(RS_RET_ERR); } jsonf = json_object_new_string_len((char *)pVal, propLen + 1); json_object_object_add(json, (char *)pTpe->fieldName, jsonf); } if (bMustBeFreed) { /* json-c makes its own private copy! */ free(pVal); } } } } assert(iRet == RS_RET_OK); *pjson = json; finalize_it: RETiRet; } /* Helper to doEscape. This is called if doEscape * runs out of memory allocating the escaped string. * Then we are in trouble. We can * NOT simply return the unmodified string because this * may cause SQL injection. But we also can not simply * abort the run, this would be a DoS. I think an appropriate * measure is to remove the dangerous \' characters (SQL). We * replace them by \", which will break the message and * signatures eventually present - but this is the * best thing we can do now (or does anybody * have a better idea?). rgerhards 2004-11-23 * added support for escape mode (see doEscape for details). * if mode = SQL_ESCAPE, then backslashes are changed to slashes. * rgerhards 2005-09-22 */ static void doEmergencyEscape(register uchar *p, int mode) { while (*p) { if ((mode == SQL_ESCAPE || mode == STDSQL_ESCAPE) && *p == '\'') { *p = '"'; } else if (mode == JSON_ESCAPE) { if (*p == '"') { *p = '\''; } else if (*p == '\\') { *p = '/'; } } else if ((mode == SQL_ESCAPE) && *p == '\\') { *p = '/'; } ++p; } } /* SQL-Escape a string. Single quotes are found and * replaced by two of them. A new buffer is allocated * for the provided string and the provided buffer is * freed. The length is updated. Parameter pbMustBeFreed * is set to 1 if a new buffer is allocated. Otherwise, * it is left untouched. * -- * We just discovered a security issue. MySQL is so * "smart" to not only support the standard SQL mechanism * for escaping quotes, but to also provide its own (using * c-type syntax with backslashes). As such, it is actually * possible to do sql injection via rsyslogd. The cure is now * to escape backslashes, too. As we have found on the web, some * other databases seem to be similar "smart" (why do we have standards * at all if they are violated without any need???). Even better, MySQL's * smartness depends on config settings. So we add a new option to this * function that allows the caller to select if they want to standard or * "smart" encoding ;) * -- * Parameter "mode" is STDSQL_ESCAPE, SQL_ESCAPE "smart" SQL engines, or * JSON_ESCAPE for everyone requiring escaped JSON (e.g. ElasticSearch). * 2005-09-22 rgerhards */ rsRetVal doEscape(uchar **pp, rs_size_t *pLen, unsigned short *pbMustBeFreed, int mode) { DEFiRet; uchar *p = NULL; int iLen; cstr_t *pStrB = NULL; uchar *pszGenerated; assert(pp != NULL); assert(*pp != NULL); assert(pLen != NULL); assert(pbMustBeFreed != NULL); /* first check if we need to do anything at all... */ if (mode == STDSQL_ESCAPE) for (p = *pp; *p && *p != '\''; ++p); else if (mode == SQL_ESCAPE) for (p = *pp; *p && *p != '\'' && *p != '\\'; ++p); else if (mode == JSON_ESCAPE) for (p = *pp; *p && (*p == '"' || *p == '\\'); ++p); /* when we get out of the loop, we are either at the * string terminator or the first character to escape */ if (p && *p == '\0') FINALIZE; /* nothing to do in this case! */ p = *pp; iLen = *pLen; CHKiRet(cstrConstruct(&pStrB)); while (*p) { if ((mode == SQL_ESCAPE || mode == STDSQL_ESCAPE) && *p == '\'') { CHKiRet(cstrAppendChar(pStrB, (mode == STDSQL_ESCAPE) ? '\'' : '\\')); iLen++; /* reflect the extra character */ } else if ((mode == SQL_ESCAPE) && *p == '\\') { CHKiRet(cstrAppendChar(pStrB, '\\')); iLen++; /* reflect the extra character */ } else if ((mode == JSON_ESCAPE) && (*p == '"' || *p == '\\')) { CHKiRet(cstrAppendChar(pStrB, '\\')); iLen++; /* reflect the extra character */ } CHKiRet(cstrAppendChar(pStrB, *p)); ++p; } cstrFinalize(pStrB); CHKiRet(cstrConvSzStrAndDestruct(&pStrB, &pszGenerated, 0)); if (*pbMustBeFreed) free(*pp); /* discard previous value */ *pp = pszGenerated; *pLen = iLen; *pbMustBeFreed = 1; finalize_it: if (iRet != RS_RET_OK) { doEmergencyEscape(*pp, mode); if (pStrB != NULL) cstrDestruct(&pStrB); } RETiRet; } /* Constructs a template entry object. Returns pointer to it * or NULL (if it fails). Pointer to associated template list entry * must be provided. */ static struct templateEntry *tpeConstruct(struct template *pTpl) { struct templateEntry *pTpe; assert(pTpl != NULL); if ((pTpe = calloc(1, sizeof(struct templateEntry))) == NULL) return NULL; /* basic initialization is done via calloc() - need to * initialize only values != 0. */ if (pTpl->pEntryLast == NULL) { /* we are the first element! */ pTpl->pEntryRoot = pTpl->pEntryLast = pTpe; } else { pTpl->pEntryLast->pNext = pTpe; pTpl->pEntryLast = pTpe; } pTpl->tpenElements++; return (pTpe); } /* Helper function to apply case-sensitivity to templates. */ static void apply_case_sensitivity(struct template *pTpl) { if (pTpl->optCaseSensitive) return; struct templateEntry *pTpe; for (pTpe = pTpl->pEntryRoot; pTpe != NULL; pTpe = pTpe->pNext) { if (pTpe->eEntryType == FIELD) { if (pTpe->data.field.msgProp.id == PROP_CEE || pTpe->data.field.msgProp.id == PROP_LOCAL_VAR || pTpe->data.field.msgProp.id == PROP_GLOBAL_VAR) { uchar *p; p = pTpe->fieldName; for (; *p; ++p) *p = tolower(*p); p = pTpe->data.field.msgProp.name; for (; *p; ++p) *p = tolower(*p); } } } } /* Constructs a template list object. Returns pointer to it * or NULL (if it fails). */ static struct template *tplConstruct(rsconf_t *conf) { struct template *pTpl; if ((pTpl = calloc(1, sizeof(struct template))) == NULL) return NULL; /* basic initialisation is done via calloc() - need to * initialize only values != 0. */ if (conf->templates.last == NULL) { /* we are the first element! */ conf->templates.root = conf->templates.last = pTpl; } else { conf->templates.last->pNext = pTpl; conf->templates.last = pTpl; } return (pTpl); } /* helper to tplAddLine. Parses a constant and generates * the necessary structure. * Parameter "bDoEscapes" is to support legacy vs. v6+ config system. In * legacy, we must do escapes ourselves, whereas v6+ passes in already * escaped strings (which we are NOT permitted to further escape, this would * cause invalid result strings!). Note: if escapes are not permitted, * quotes (") are just a regular character and do NOT terminate the constant! */ static rsRetVal do_Constant(unsigned char **pp, struct template *pTpl, int bDoEscapes) { register unsigned char *p; cstr_t *pStrB; struct templateEntry *pTpe; int i; DEFiRet; assert(pp != NULL); assert(*pp != NULL); assert(pTpl != NULL); p = *pp; CHKiRet(cstrConstruct(&pStrB)); /* process the message and expand escapes * (additional escapes can be added here if needed) */ while (*p && *p != '%' && !(bDoEscapes && *p == '\"')) { if (bDoEscapes && *p == '\\') { switch (*++p) { case '\0': /* the best we can do - it's invalid anyhow... */ cstrAppendChar(pStrB, *p); break; case 'n': cstrAppendChar(pStrB, '\n'); ++p; break; case 'r': cstrAppendChar(pStrB, '\r'); ++p; break; case '\\': cstrAppendChar(pStrB, '\\'); ++p; break; case '%': cstrAppendChar(pStrB, '%'); ++p; break; case '0': /* numerical escape sequence */ case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': { uchar *numStart = p; if (tplParseNonNegativeInt(&numStart, &i)) { LogError(0, NO_ERRCODE, "template numeric escape value too large, using %d as substitute", UCHAR_MAX); i = UCHAR_MAX; } else if (i > UCHAR_MAX) { i = UCHAR_MAX; } p = numStart; cstrAppendChar(pStrB, i); break; } default: cstrAppendChar(pStrB, *p++); break; } } else cstrAppendChar(pStrB, *p++); } if ((pTpe = tpeConstruct(pTpl)) == NULL) { rsCStrDestruct(&pStrB); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } pTpe->eEntryType = CONSTANT; cstrFinalize(pStrB); /* We obtain the length from the counted string object * (before we delete it). Later we might take additional * benefit from the counted string object. * 2005-09-09 rgerhards */ pTpe->data.constant.iLenConstant = rsCStrLen(pStrB); CHKiRet(cstrConvSzStrAndDestruct(&pStrB, &pTpe->data.constant.pConstant, 0)); *pp = p; finalize_it: RETiRet; } /* Helper that checks to see if a property already has a format * type defined */ static int hasFormat(struct templateEntry *pTpe) { return (pTpe->data.field.options.bCSV || pTpe->data.field.options.bJSON || pTpe->data.field.options.bJSONf || pTpe->data.field.options.bJSONr); } /* Helper to do_Parameter(). This parses the formatting options * specified in a template variable. It returns the passed-in pointer * updated to the next processed character. */ static void doOptions(unsigned char **pp, struct templateEntry *pTpe) { register unsigned char *p; unsigned char Buf[64]; size_t i; assert(pp != NULL); assert(*pp != NULL); assert(pTpe != NULL); p = *pp; while (*p && *p != '%' && *p != ':') { /* outer loop - until end of options */ memset(Buf, 0, sizeof(Buf)); /* silence valgrind warnings */ i = 0; while ((i < sizeof(Buf) - 1) && *p && *p != '%' && *p != ':' && *p != ',') { /* inner loop - until end of ONE option */ Buf[i++] = tolower((int)*p); ++p; } Buf[i] = '\0'; /* terminate */ /* check if we need to skip oversize option */ while (*p && *p != '%' && *p != ':' && *p != ',') ++p; /* just skip */ if (*p == ',') ++p; /* eat ',' */ /* OK, we got the option, so now lets look what * it tells us... */ if (!strcmp((char *)Buf, "date-mysql")) { pTpe->data.field.eDateFormat = tplFmtMySQLDate; } else if (!strcmp((char *)Buf, "date-pgsql")) { pTpe->data.field.eDateFormat = tplFmtPgSQLDate; } else if (!strcmp((char *)Buf, "date-rfc3164")) { pTpe->data.field.eDateFormat = tplFmtRFC3164Date; } else if (!strcmp((char *)Buf, "date-rfc3164-buggyday")) { pTpe->data.field.eDateFormat = tplFmtRFC3164BuggyDate; } else if (!strcmp((char *)Buf, "date-rfc3339")) { pTpe->data.field.eDateFormat = tplFmtRFC3339Date; } else if (!strcmp((char *)Buf, "date-unixtimestamp")) { pTpe->data.field.eDateFormat = tplFmtUnixDate; } else if (!strcmp((char *)Buf, "date-subseconds")) { pTpe->data.field.eDateFormat = tplFmtSecFrac; } else if (!strcmp((char *)Buf, "date-wdayname")) { pTpe->data.field.eDateFormat = tplFmtWDayName; } else if (!strcmp((char *)Buf, "date-wday")) { pTpe->data.field.eDateFormat = tplFmtWDay; } else if (!strcmp((char *)Buf, "date-year")) { pTpe->data.field.eDateFormat = tplFmtYear; } else if (!strcmp((char *)Buf, "date-month")) { pTpe->data.field.eDateFormat = tplFmtMonth; } else if (!strcmp((char *)Buf, "date-day")) { pTpe->data.field.eDateFormat = tplFmtDay; } else if (!strcmp((char *)Buf, "date-hour")) { pTpe->data.field.eDateFormat = tplFmtHour; } else if (!strcmp((char *)Buf, "date-minute")) { pTpe->data.field.eDateFormat = tplFmtMinute; } else if (!strcmp((char *)Buf, "date-second")) { pTpe->data.field.eDateFormat = tplFmtSecond; } else if (!strcmp((char *)Buf, "date-tzoffshour")) { pTpe->data.field.eDateFormat = tplFmtTZOffsHour; } else if (!strcmp((char *)Buf, "date-tzoffsmin")) { pTpe->data.field.eDateFormat = tplFmtTZOffsMin; } else if (!strcmp((char *)Buf, "date-tzoffsdirection")) { pTpe->data.field.eDateFormat = tplFmtTZOffsDirection; } else if (!strcmp((char *)Buf, "date-ordinal")) { pTpe->data.field.eDateFormat = tplFmtOrdinal; } else if (!strcmp((char *)Buf, "date-week")) { pTpe->data.field.eDateFormat = tplFmtWeek; } else if (!strcmp((char *)Buf, "date-iso-week")) { pTpe->data.field.eDateFormat = tplFmtISOWeek; } else if (!strcmp((char *)Buf, "date-iso-week-year")) { pTpe->data.field.eDateFormat = tplFmtISOWeekYear; } else if (!strcmp((char *)Buf, "date-utc")) { pTpe->data.field.options.bDateInUTC = 1; } else if (!strcmp((char *)Buf, "lowercase")) { pTpe->data.field.eCaseConv = tplCaseConvLower; } else if (!strcmp((char *)Buf, "uppercase")) { pTpe->data.field.eCaseConv = tplCaseConvUpper; } else if (!strcmp((char *)Buf, "sp-if-no-1st-sp")) { pTpe->data.field.options.bSPIffNo1stSP = 1; } else if (!strcmp((char *)Buf, "compressspace")) { pTpe->data.field.options.bCompressSP = 1; } else if (!strcmp((char *)Buf, "escape-cc")) { pTpe->data.field.options.bEscapeCC = 1; pTpe->data.field.options.bEscapeCCOctal = 0; pTpe->data.field.options.bDropCC = 0; pTpe->data.field.options.bSpaceCC = 0; } else if (!strcmp((char *)Buf, "drop-cc")) { pTpe->data.field.options.bDropCC = 1; pTpe->data.field.options.bEscapeCC = 0; pTpe->data.field.options.bEscapeCCOctal = 0; pTpe->data.field.options.bSpaceCC = 0; } else if (!strcmp((char *)Buf, "space-cc")) { pTpe->data.field.options.bSpaceCC = 1; pTpe->data.field.options.bEscapeCC = 0; pTpe->data.field.options.bEscapeCCOctal = 0; pTpe->data.field.options.bDropCC = 0; } else if (!strcmp((char *)Buf, "drop-last-lf")) { pTpe->data.field.options.bDropLastLF = 1; } else if (!strcmp((char *)Buf, "secpath-drop")) { pTpe->data.field.options.bSecPathDrop = 1; } else if (!strcmp((char *)Buf, "secpath-replace")) { pTpe->data.field.options.bSecPathReplace = 1; } else if (!strcmp((char *)Buf, "pos-end-relative")) { pTpe->data.field.options.bFromPosEndRelative = 1; } else if (!strcmp((char *)Buf, "fixed-width")) { pTpe->data.field.options.bFixedWidth = 1; } else if (!strcmp((char *)Buf, "csv")) { if (hasFormat(pTpe)) { LogError(0, NO_ERRCODE, "error: can only specify " "one option out of (json, jsonf, jsonr, jsonfr, csv) - csv ignored"); } else { pTpe->data.field.options.bCSV = 1; } } else if (!strcmp((char *)Buf, "json")) { if (hasFormat(pTpe)) { LogError(0, NO_ERRCODE, "error: can only specify " "one option out of (json, jsonf, jsonr, jsonfr, csv) - json ignored"); } else { pTpe->data.field.options.bJSON = 1; } } else if (!strcmp((char *)Buf, "jsonf")) { if (hasFormat(pTpe)) { LogError(0, NO_ERRCODE, "error: can only specify " "one option out of (json, jsonf, jsonr, jsonfr, csv) - jsonf ignored"); } else { pTpe->data.field.options.bJSONf = 1; } } else if (!strcmp((char *)Buf, "jsonr")) { if (hasFormat(pTpe)) { LogError(0, NO_ERRCODE, "error: can only specify " "one option out of (json, jsonf, jsonr, jsonfr, csv) - jsonr ignored"); } else { pTpe->data.field.options.bJSONr = 1; } } else if (!strcmp((char *)Buf, "jsonfr")) { if (hasFormat(pTpe)) { LogError(0, NO_ERRCODE, "error: can only specify " "one option out of (json, jsonf, jsonr, jsonfr, csv) - jsonfr ignored"); } else { pTpe->data.field.options.bJSONfr = 1; } } else if (!strcmp((char *)Buf, "mandatory-field")) { pTpe->data.field.options.bMandatory = 1; } else { LogError(0, NO_ERRCODE, "template error: invalid field option '%s' " "specified - ignored", Buf); } } *pp = p; } /* helper to tplAddLine. Parses a parameter and generates * the necessary structure. */ static rsRetVal do_Parameter(uchar **pp, struct template *pTpl) { uchar *p; cstr_t *pStrProp = NULL; cstr_t *pStrField = NULL; struct templateEntry *pTpe; int iNum; /* to compute numbers */ #ifdef FEATURE_REGEXP /* APR: variables for regex */ rsRetVal iRetLocal; int longitud; unsigned char *regex_char; unsigned char *regex_end; #endif DEFiRet; assert(pp != NULL); assert(*pp != NULL); assert(pTpl != NULL); p = (uchar *)*pp; CHKiRet(cstrConstruct(&pStrProp)); CHKmalloc(pTpe = tpeConstruct(pTpl)); pTpe->eEntryType = FIELD; while (*p && *p != '%' && *p != ':') { cstrAppendChar(pStrProp, *p); ++p; } /* got the name */ cstrFinalize(pStrProp); CHKiRet(msgPropDescrFill(&pTpe->data.field.msgProp, cstrGetSzStrNoNULL(pStrProp), cstrLen(pStrProp))); /* Check frompos, if it has an R, then topos should be a regex */ if (*p == ':') { pTpe->bComplexProcessing = 1; ++p; /* eat ':' */ #ifdef FEATURE_REGEXP if (*p == 'R') { /* APR: R found! regex alarm ! :) */ ++p; /* eat ':' */ /* first come the regex type */ if (*p == ',') { ++p; /* eat ',' */ if (p[0] == 'B' && p[1] == 'R' && p[2] == 'E' && (p[3] == ',' || p[3] == ':')) { pTpe->data.field.typeRegex = TPL_REGEX_BRE; p += 3; /* eat indicator sequence */ } else if (p[0] == 'E' && p[1] == 'R' && p[2] == 'E' && (p[3] == ',' || p[3] == ':')) { pTpe->data.field.typeRegex = TPL_REGEX_ERE; p += 3; /* eat indicator sequence */ } else { LogError(0, NO_ERRCODE, "error: invalid regular expression " "type, rest of line %s", (char *)p); } } /* now check for submatch ID */ pTpe->data.field.iSubMatchToUse = 0; if (*p == ',') { /* in this case a number follows, which indicates which match * shall be used. This must be a single digit. */ ++p; /* eat ',' */ if (isdigit((int)*p)) { pTpe->data.field.iSubMatchToUse = *p - '0'; ++p; /* eat digit */ } } /* now pull what to do if we do not find a match */ if (*p == ',') { ++p; /* eat ',' */ if (p[0] == 'D' && p[1] == 'F' && p[2] == 'L' && p[3] == 'T' && (p[4] == ',' || p[4] == ':')) { pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_DFLTSTR; p += 4; /* eat indicator sequence */ } else if (p[0] == 'B' && p[1] == 'L' && p[2] == 'A' && p[3] == 'N' && p[4] == 'K' && (p[5] == ',' || p[5] == ':')) { pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_BLANK; p += 5; /* eat indicator sequence */ } else if (p[0] == 'F' && p[1] == 'I' && p[2] == 'E' && p[3] == 'L' && p[4] == 'D' && (p[5] == ',' || p[5] == ':')) { pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_WHOLE_FIELD; p += 5; /* eat indicator sequence */ } else if (p[0] == 'Z' && p[1] == 'E' && p[2] == 'R' && p[3] == 'O' && (p[4] == ',' || p[4] == ':')) { pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_ZERO; p += 4; /* eat indicator sequence */ } else if (p[0] == ',') { /* empty, use default */ pTpe->data.field.nomatchAction = TPL_REGEX_NOMATCH_USE_DFLTSTR; /* do NOT eat indicator sequence, as this was already eaten - the * comma itself is already part of the next field. */ } else { LogError(0, NO_ERRCODE, "template %s error: invalid regular " "expression type, rest of line %s", pTpl->pszName, (char *)p); } } /* now check for match ID */ pTpe->data.field.iMatchToUse = 0; if (*p == ',') { /* in this case a number follows, which indicates which match * shall be used. This must be a single digit. */ ++p; /* eat ',' */ if (isdigit((int)*p)) { pTpe->data.field.iMatchToUse = *p - '0'; ++p; /* eat digit */ } } if (*p != ':') { /* There is something more than an R , this is invalid ! */ /* Complain on extra characters */ LogError(0, NO_ERRCODE, "error: invalid character in frompos " "after \"R\", property: '%%%s'", (char *)*pp); } else { pTpe->data.field.has_regex = 1; dbgprintf("we have a regexp and use match #%d, submatch #%d\n", pTpe->data.field.iMatchToUse, pTpe->data.field.iSubMatchToUse); } } else { /* now we fall through the "regular" FromPos code */ #endif /* #ifdef FEATURE_REGEXP */ if (*p == 'F') { #ifdef STRICT_GPLV3 pTpe->data.field.field_expand = 0; #endif /* we have a field counter, so indicate it in the template */ ++p; /* eat 'F' */ if (*p == ':') { /* no delimiter specified, so use the default (HT) */ pTpe->data.field.has_fields = 1; pTpe->data.field.field_delim = 9; } else if (*p == ',') { ++p; /* eat ',' */ /* configured delimiter follows, so we need to obtain * it. Important: the following number must be the * **DECIMAL** ASCII value of the delimiter character. */ pTpe->data.field.has_fields = 1; if (!isdigit((int)*p)) { /* complain and use default */ LogError(0, NO_ERRCODE, "error: invalid character in " "frompos after \"F,\", property: '%%%s' - using 9 (HT) as field delimiter", (char *)*pp); pTpe->data.field.field_delim = 9; } else { if (tplParseNonNegativeInt(&p, &iNum)) { LogError(0, NO_ERRCODE, "error: delimiter value in template is too large - using 9 (HT) as substitute"); iNum = 9; } if (iNum < 0 || iNum > 255) { LogError(0, NO_ERRCODE, "error: non-USASCII delimiter " "character value %d in template - using 9 (HT) as substitute", iNum); pTpe->data.field.field_delim = 9; } else { pTpe->data.field.field_delim = iNum; #ifdef STRICT_GPLV3 if (*p == '+') { pTpe->data.field.field_expand = 1; p++; } #endif if (*p == ',') { /* real fromPos? */ ++p; if (tplParseNonNegativeInt(&p, &iNum)) { LogError(0, NO_ERRCODE, "error: frompos value in template is too large - using %d", INT_MAX); } pTpe->data.field.iFromPos = iNum; } else if (*p != ':') { parser_errmsg( "error: invalid character " "'%c' in frompos after \"F,\", property: '%s' " "be sure to use DECIMAL character " "codes!", *p, (char *)*pp); ABORT_FINALIZE(RS_RET_SYNTAX_ERROR); } } } } else { /* invalid character after F, so we need to reject * this. */ LogError(0, NO_ERRCODE, "error: invalid character in frompos " "after \"F\", property: '%%%s'", (char *)*pp); } } else { /* we now have a simple offset in frompos (the previously "normal" case) */ if (tplParseNonNegativeInt(&p, &iNum)) { LogError(0, NO_ERRCODE, "error: frompos value in template is too large - using %d", INT_MAX); } pTpe->data.field.iFromPos = iNum; /* skip to next known good */ while (*p && *p != '%' && *p != ':') { /* TODO: complain on extra characters */ dbgprintf("error: extra character in frompos: '%s'\n", p); ++p; } } #ifdef FEATURE_REGEXP } #endif /* #ifdef FEATURE_REGEXP */ } /* check topos (holds an regex if FromPos is "R"*/ if (*p == ':') { ++p; /* eat ':' */ #ifdef FEATURE_REGEXP if (pTpe->data.field.has_regex) { dbgprintf("debug: has regex \n"); /* APR 2005-09 I need the string that represent the regex */ /* The regex end is: "--end" */ /* TODO : this is hardcoded and cant be escaped, please change */ regex_end = (unsigned char *)strstr((char *)p, "--end"); if (regex_end == NULL) { dbgprintf("error: can not find regex end in: '%s'\n", p); pTpe->data.field.has_regex = 0; } else { /* We get here ONLY if the regex end was found */ longitud = regex_end - p; /* Malloc for the regex string */ regex_char = (unsigned char *)malloc(longitud + 1); if (regex_char == NULL) { dbgprintf("Could not allocate memory for template parameter!\n"); pTpe->data.field.has_regex = 0; ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } /* Get the regex string for compiling later */ memcpy(regex_char, p, longitud); regex_char[longitud] = '\0'; dbgprintf("debug: regex detected: '%s'\n", regex_char); /* Now i compile the regex */ /* Remember that the re is an attribute of the Template entry */ if ((iRetLocal = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { int iOptions; iOptions = (pTpe->data.field.typeRegex == TPL_REGEX_ERE) ? REG_EXTENDED : 0; int errcode; if ((errcode = regexp.regcomp(&(pTpe->data.field.re), (char *)regex_char, iOptions)) != 0) { char errbuff[512]; regexp.regerror(errcode, &(pTpe->data.field.re), errbuff, sizeof(errbuff)); DBGPRINTF("Template.c: Error in regular expression: %s\n", errbuff); pTpe->data.field.has_regex = 2; } } else { /* regexp object could not be loaded */ dbgprintf( "error %d trying to load regexp library - this may be desired " "and thus OK", iRetLocal); if (bFirstRegexpErrmsg) { /* prevent flood of messages, maybe even an endless loop! */ bFirstRegexpErrmsg = 0; LogError(0, NO_ERRCODE, "regexp library could not be loaded " "(error %d), regexp ignored", iRetLocal); } pTpe->data.field.has_regex = 2; } /* Finally we move the pointer to the end of the regex * so it aint parsed twice or something weird */ p = regex_end + 5 /*strlen("--end")*/; free(regex_char); } } else if (*p == '$') { /* shortcut for "end of message */ p++; /* eat '$' */ /* in this case, we do a quick, somewhat dirty but totally * legitimate trick: we simply use a topos that is higher than * potentially ever can happen. The code below checks that no copy * will occur after the end of string, so this is perfectly legal. * rgerhards, 2006-10-17 */ pTpe->data.field.iToPos = 9999999; } else { /* fallthrough to "regular" ToPos code */ #endif /* #ifdef FEATURE_REGEXP */ if (pTpe->data.field.has_fields == 1) { if (tplParseNonNegativeInt(&p, &iNum)) { LogError(0, NO_ERRCODE, "error: field number in template is too large - using %d", INT_MAX); } pTpe->data.field.iFieldNr = iNum; if (*p == ',') { /* get real toPos? */ ++p; if (tplParseNonNegativeInt(&p, &iNum)) { LogError(0, NO_ERRCODE, "error: topos value in template is too large - using %d", INT_MAX); } pTpe->data.field.iToPos = iNum; } } else { if (tplParseNonNegativeInt(&p, &iNum)) { LogError(0, NO_ERRCODE, "error: topos value in template is too large - using %d", INT_MAX); } pTpe->data.field.iToPos = iNum; } /* skip to next known good */ while (*p && *p != '%' && *p != ':') { /* TODO: complain on extra characters */ dbgprintf("error: extra character in frompos: '%s'\n", p); ++p; } #ifdef FEATURE_REGEXP } #endif /* #ifdef FEATURE_REGEXP */ } /* check options */ if (*p == ':') { ++p; /* eat ':' */ doOptions(&p, pTpe); } if (pTpe->data.field.options.bFromPosEndRelative) { if (pTpe->data.field.iToPos > pTpe->data.field.iFromPos) { iNum = pTpe->data.field.iToPos; pTpe->data.field.iToPos = pTpe->data.field.iFromPos; pTpe->data.field.iFromPos = iNum; } } else { if (pTpe->data.field.iToPos < pTpe->data.field.iFromPos) { iNum = pTpe->data.field.iToPos; pTpe->data.field.iToPos = pTpe->data.field.iFromPos; pTpe->data.field.iFromPos = iNum; } } /* check field name */ if (*p == ':') { ++p; /* eat ':' */ CHKiRet(cstrConstruct(&pStrField)); while (*p != ':' && *p != '%' && *p != '\0') { cstrAppendChar(pStrField, *p); ++p; } cstrFinalize(pStrField); } /* save field name - if none was given, use the property name instead */ if (pStrField == NULL) { if (pTpe->data.field.msgProp.id == PROP_CEE || pTpe->data.field.msgProp.id == PROP_LOCAL_VAR) { /* in CEE case, we remove "$!"/"$." from the fieldname - it's just our indicator */ pTpe->fieldName = ustrdup(cstrGetSzStrNoNULL(pStrProp) + 2); pTpe->lenFieldName = cstrLen(pStrProp) - 2; } else { pTpe->fieldName = ustrdup(cstrGetSzStrNoNULL(pStrProp)); pTpe->lenFieldName = cstrLen(pStrProp); } } else { pTpe->fieldName = ustrdup(cstrGetSzStrNoNULL(pStrField)); pTpe->lenFieldName = ustrlen(pTpe->fieldName); cstrDestruct(&pStrField); } if (pTpe->fieldName == NULL) { DBGPRINTF("template/do_Parameter: fieldName is NULL!\n"); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } if (*p) ++p; /* eat '%' */ *pp = p; finalize_it: if (pStrProp != NULL) cstrDestruct(&pStrProp); RETiRet; } /* Add a new entry for a template module. * returns pointer to new object if it succeeds, NULL otherwise. * rgerhards, 2010-05-31 */ static rsRetVal tplAddTplMod(struct template *pTpl, uchar **ppRestOfConfLine) { uchar *pSrc; uchar szMod[2048]; unsigned lenMod; strgen_t *pStrgen; DEFiRet; pSrc = *ppRestOfConfLine; lenMod = 0; while (*pSrc && !isspace(*pSrc) && lenMod < sizeof(szMod) - 1) { szMod[lenMod] = *pSrc++; lenMod++; } szMod[lenMod] = '\0'; *ppRestOfConfLine = pSrc; CHKiRet(strgen.FindStrgen(&pStrgen, szMod)); pTpl->pStrgen = pStrgen->pModule->mod.sm.strgen; DBGPRINTF("template bound to strgen '%s'\n", szMod); /* check if the name potentially contains some well-known options * Note: we have opted to let the name contain all options. This sounds * useful, because the strgen MUST actually implement a specific set * of options. Doing this via the name looks to the enduser as if the * regular syntax were used, and it make sure the strgen postively * acknowledged implementing the option. -- rgerhards, 2011-03-21 */ if (lenMod > 6 && !strcasecmp((char *)szMod + lenMod - 7, ",stdsql")) { pTpl->optFormatEscape = STDSQL_ESCAPE; DBGPRINTF("strgen supports the stdsql option\n"); } else if (lenMod > 3 && !strcasecmp((char *)szMod + lenMod - 4, ",sql")) { pTpl->optFormatEscape = SQL_ESCAPE; DBGPRINTF("strgen supports the sql option\n"); } else if (lenMod > 4 && !strcasecmp((char *)szMod + lenMod - 4, ",json")) { pTpl->optFormatEscape = JSON_ESCAPE; DBGPRINTF("strgen supports the json option\n"); } finalize_it: RETiRet; } /* Add a new template line * returns pointer to new object if it succeeds, NULL otherwise. */ struct template *tplAddLine(rsconf_t *conf, const char *pName, uchar **ppRestOfConfLine) { struct template *pTpl; unsigned char *p; int bDone; size_t i; rsRetVal localRet; assert(pName != NULL); assert(ppRestOfConfLine != NULL); if ((pTpl = tplConstruct(conf)) == NULL) return NULL; DBGPRINTF("tplAddLine processing template '%s'\n", pName); pTpl->iLenName = strlen(pName); pTpl->pszName = (char *)malloc(pTpl->iLenName + 1); if (pTpl->pszName == NULL) { dbgprintf("tplAddLine could not alloc memory for template name!"); pTpl->iLenName = 0; return NULL; /* I know - we create a memory leak here - but I deem * it acceptable as it is a) a very small leak b) very * unlikely to happen. rgerhards 2004-11-17 */ } memcpy(pTpl->pszName, pName, pTpl->iLenName + 1); /* now actually parse the line */ p = *ppRestOfConfLine; assert(p != NULL); while (isspace((int)*p)) /* skip whitespace */ ++p; switch (*p) { case '"': /* just continue */ break; case '=': *ppRestOfConfLine = p + 1; localRet = tplAddTplMod(pTpl, ppRestOfConfLine); if (localRet != RS_RET_OK) { LogError(0, localRet, "Template '%s': error %d defining template via strgen module", pTpl->pszName, localRet); /* we simply make the template defunct in this case by setting * its name to a zero-string. We do not free it, as this would * require additional code and causes only a very small memory * consumption. Memory is freed, however, in normal operation * and most importantly by HUPing syslogd. */ *pTpl->pszName = '\0'; } return NULL; default: dbgprintf("Template '%s' invalid, does not start with '\"'!\n", pTpl->pszName); /* we simply make the template defunct in this case by setting * its name to a zero-string. We do not free it, as this would * require additional code and causes only a very small memory * consumption. */ *pTpl->pszName = '\0'; return NULL; } ++p; /* we finally go to the actual template string - so let's have some fun... */ bDone = *p ? 0 : 1; while (!bDone) { switch (*p) { case '\0': bDone = 1; break; case '%': /* parameter */ ++p; /* eat '%' */ if (do_Parameter(&p, pTpl) != RS_RET_OK) { dbgprintf("tplAddLine error: parameter invalid"); return NULL; }; break; default: /* constant */ do_Constant(&p, pTpl, 1); break; } if (*p == '"') { /* end of template string? */ ++p; /* eat it! */ bDone = 1; } } /* we now have the template - let's look at the options (if any) * we process options until we reach the end of the string or * an error occurs - whichever is first. */ while (*p) { while (isspace((int)*p)) /* skip whitespace */ ++p; if (*p != ',') break; ++p; /* eat ',' */ while (isspace((int)*p)) /* skip whitespace */ ++p; /* read option word */ char optBuf[128] = {'\0'}; /* buffer for options - should be more than enough... */ i = 0; while ((i < (sizeof(optBuf) - 1)) && *p && *p != '=' && *p != ',' && *p != '\n') { optBuf[i++] = tolower((int)*p); ++p; } optBuf[i] = '\0'; if (*p == '\n') ++p; /* as of now, the no form is nonsense... but I do include * it anyhow... ;) rgerhards 2004-11-22 */ if (!strcmp(optBuf, "stdsql")) { pTpl->optFormatEscape = STDSQL_ESCAPE; } else if (!strcmp(optBuf, "json")) { pTpl->optFormatEscape = JSON_ESCAPE; } else if (!strcmp(optBuf, "sql")) { pTpl->optFormatEscape = SQL_ESCAPE; } else if (!strcmp(optBuf, "nosql")) { pTpl->optFormatEscape = NO_ESCAPE; } else if (!strcmp(optBuf, "casesensitive")) { pTpl->optCaseSensitive = 1; } else if (!strcmp(optBuf, "jsonf")) { pTpl->optFormatEscape = JSONF; pTpl->bJsonTreeEnabled = 0; } else if (!strcmp(optBuf, "jsonftree")) { pTpl->optFormatEscape = JSONF; pTpl->bJsonTreeEnabled = 1; } else { dbgprintf("Invalid option '%s' ignored.\n", optBuf); } } *ppRestOfConfLine = p; apply_case_sensitivity(pTpl); if (pTpl->optFormatEscape == JSONF) tplWarnDuplicateJsonKeys(pTpl); return (pTpl); } static rsRetVal createConstantTpe(struct template *pTpl, struct cnfobj *o) { struct templateEntry *pTpe; es_str_t *value = NULL; /* init just to keep compiler happy - mandatory parameter */ int i; int is_jsonf = 0; struct cnfparamvals *pvals = NULL; struct json_object *json = NULL; struct json_object *jval = NULL; uchar *outname = NULL; const char *sz = NULL; DEFiRet; /* pull params */ pvals = nvlstGetParams(o->nvlst, &pblkConstant, NULL); if (pvals == NULL) { parser_errmsg("error processing template parameters"); ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } cnfparamsPrint(&pblkConstant, pvals); for (i = 0; i < pblkConstant.nParams; ++i) { if (!pvals[i].bUsed) continue; if (!strcmp(pblkConstant.descr[i].name, "value")) { value = pvals[i].val.d.estr; } else if (!strcmp(pblkConstant.descr[i].name, "outname")) { CHKmalloc(outname = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(pblkConstant.descr[i].name, "format")) { if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"jsonf", sizeof("jsonf") - 1)) { is_jsonf = 1; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid format type '%s' for constant", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else { LogError(0, RS_RET_INTERNAL_ERROR, "template:constantTpe: program error, non-handled " "param '%s'\n", pblkConstant.descr[i].name); } } if (is_jsonf && outname == NULL) { parser_errmsg("constant set to format jsonf, but outname not specified - aborting"); ABORT_FINALIZE(RS_RET_ERR); } /* just double-check */ assert(value != NULL); if (value == NULL) { parser_errmsg("constant template entry is missing mandatory value parameter"); ABORT_FINALIZE(RS_RET_ERR); } /* apply */ CHKmalloc(pTpe = tpeConstruct(pTpl)); es_unescapeStr(value); pTpe->eEntryType = CONSTANT; pTpe->fieldName = outname; if (outname != NULL) pTpe->lenFieldName = ustrlen(outname); pTpe->data.constant.bJSONf = is_jsonf; if (is_jsonf) { json = json_object_new_object(); CHKmalloc(json); CHKmalloc(sz = es_str2cstr(value, NULL)); jval = json_object_new_string(sz); if (jval == NULL) { free((void *)sz); ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY); } free((void *)sz); json_object_object_add(json, (char *)outname, jval); const char *jsonStr = json_object_get_string(json); CHKmalloc(jsonStr); const size_t jsonLen = strlen(jsonStr); if (jsonLen < 2) { parser_errmsg("error: failed to serialize jsonf constant"); ABORT_FINALIZE(RS_RET_ERR); } const size_t fragmentLen = jsonLen - 2; uchar *fragment = (uchar *)strndup(jsonStr + 1, fragmentLen); CHKmalloc(fragment); size_t fragLen = ustrlen(fragment); while (fragLen > 0 && isspace((int)fragment[fragLen - 1])) { --fragLen; } fragment[fragLen] = '\0'; size_t lead = 0; while (lead < fragLen && isspace((int)fragment[lead])) ++lead; if (lead > 0) { memmove(fragment, fragment + lead, fragLen - lead + 1); fragLen -= lead; } pTpe->data.constant.pConstant = fragment; pTpe->data.constant.iLenConstant = fragLen; json_object_put(json); json = NULL; } else { pTpe->data.constant.iLenConstant = es_strlen(value); CHKmalloc(pTpe->data.constant.pConstant = (uchar *)es_str2cstr(value, NULL)); } finalize_it: if (json != NULL) json_object_put(json); if (pvals != NULL) cnfparamvalsDestruct(pvals, &pblkConstant); RETiRet; } static rsRetVal createPropertyTpe(struct template *pTpl, struct cnfobj *o) { struct templateEntry *pTpe; uchar *name = NULL; uchar *outname = NULL; int i; int droplastlf = 0; int spifno1stsp = 0; int mandatory = 0; int omitifzero = 0; int frompos = -1; int topos = 0; int topos_set = 0; int fieldnum = -1; int fielddelim = 9; /* default is HT (USACSII 9) */ int fixedwidth = 0; int re_matchToUse = 0; int re_submatchToUse = 0; int bComplexProcessing = 0; int bPosRelativeToEnd = 0; int bDateInUTC = 0; int bCompressSP = 0; unsigned dataType = TPE_DATATYPE_STRING; unsigned onEmpty = TPE_DATAEMPTY_KEEP; char *re_expr = NULL; struct cnfparamvals *pvals = NULL; enum { F_NONE, F_CSV, F_JSON, F_JSONF, F_JSONR, F_JSONFR } formatType = F_NONE; enum { CC_NONE, CC_ESCAPE, CC_ESCAPE_OCTAL, CC_SPACE, CC_DROP } controlchr = CC_NONE; enum { SP_NONE, SP_DROP, SP_REPLACE } secpath = SP_NONE; enum tplFormatCaseConvTypes caseconv = tplCaseConvNo; enum tplFormatTypes datefmt = tplFmtDefault; enum tplRegexType re_type = TPL_REGEX_BRE; enum tlpRegexNoMatchType re_nomatchType = TPL_REGEX_NOMATCH_USE_DFLTSTR; DEFiRet; /* pull params */ pvals = nvlstGetParams(o->nvlst, &pblkProperty, NULL); if (pvals == NULL) { parser_errmsg("error processing template entry config parameters"); ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } cnfparamsPrint(&pblkProperty, pvals); for (i = 0; i < pblkProperty.nParams; ++i) { if (!pvals[i].bUsed) continue; if (!strcmp(pblkProperty.descr[i].name, "name")) { CHKmalloc(name = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(pblkProperty.descr[i].name, "datatype")) { if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"string", sizeof("string") - 1)) { dataType = TPE_DATATYPE_STRING; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"number", sizeof("number") - 1)) { dataType = TPE_DATATYPE_NUMBER; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"bool", sizeof("bool") - 1)) { dataType = TPE_DATATYPE_BOOL; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"auto", sizeof("auto") - 1)) { dataType = TPE_DATATYPE_AUTO; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid dataType '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "onempty")) { if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"keep", sizeof("keep") - 1)) { onEmpty = TPE_DATAEMPTY_KEEP; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"skip", sizeof("skip") - 1)) { onEmpty = TPE_DATAEMPTY_SKIP; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"null", sizeof("null") - 1)) { onEmpty = TPE_DATAEMPTY_NULL; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid onEmpty value '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "droplastlf")) { droplastlf = pvals[i].val.d.n; bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "fixedwidth")) { fixedwidth = pvals[i].val.d.n; bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "mandatory")) { mandatory = pvals[i].val.d.n; } else if (!strcmp(pblkProperty.descr[i].name, "omitifzero")) { omitifzero = pvals[i].val.d.n; } else if (!strcmp(pblkProperty.descr[i].name, "spifno1stsp")) { spifno1stsp = pvals[i].val.d.n; bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "outname")) { CHKmalloc(outname = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(pblkProperty.descr[i].name, "position.from")) { frompos = pvals[i].val.d.n; bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "position.to")) { topos = pvals[i].val.d.n; topos_set = 1; bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "position.relativetoend")) { bPosRelativeToEnd = pvals[i].val.d.n; } else if (!strcmp(pblkProperty.descr[i].name, "field.number")) { fieldnum = pvals[i].val.d.n; bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "field.delimiter")) { fielddelim = pvals[i].val.d.n; bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "regex.expression")) { CHKmalloc(re_expr = es_str2cstr(pvals[i].val.d.estr, NULL)); bComplexProcessing = 1; } else if (!strcmp(pblkProperty.descr[i].name, "regex.type")) { bComplexProcessing = 1; if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"BRE", sizeof("BRE") - 1)) { re_type = TPL_REGEX_BRE; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"ERE", sizeof("ERE") - 1)) { re_type = TPL_REGEX_ERE; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid regex.type '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "regex.nomatchmode")) { bComplexProcessing = 1; if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"DFLT", sizeof("DFLT") - 1)) { re_nomatchType = TPL_REGEX_NOMATCH_USE_DFLTSTR; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"BLANK", sizeof("BLANK") - 1)) { re_nomatchType = TPL_REGEX_NOMATCH_USE_BLANK; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"FIELD", sizeof("FIELD") - 1)) { re_nomatchType = TPL_REGEX_NOMATCH_USE_WHOLE_FIELD; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"ZERO", sizeof("ZERO") - 1)) { re_nomatchType = TPL_REGEX_NOMATCH_USE_ZERO; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid format type '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "regex.match")) { bComplexProcessing = 1; re_matchToUse = pvals[i].val.d.n; } else if (!strcmp(pblkProperty.descr[i].name, "regex.submatch")) { bComplexProcessing = 1; re_submatchToUse = pvals[i].val.d.n; } else if (!strcmp(pblkProperty.descr[i].name, "format")) { bComplexProcessing = 1; if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"csv", sizeof("csv") - 1)) { formatType = F_CSV; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"json", sizeof("json") - 1)) { formatType = F_JSON; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"jsonf", sizeof("jsonf") - 1)) { formatType = F_JSONF; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"jsonr", sizeof("jsonr") - 1)) { formatType = F_JSONR; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"jsonfr", sizeof("jsonfr") - 1)) { formatType = F_JSONFR; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid format type '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "controlcharacters")) { bComplexProcessing = 1; if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"escape", sizeof("escape") - 1)) { controlchr = CC_ESCAPE; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"escape-octal", sizeof("escape-octal") - 1)) { controlchr = CC_ESCAPE_OCTAL; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"space", sizeof("space") - 1)) { controlchr = CC_SPACE; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"drop", sizeof("drop") - 1)) { controlchr = CC_DROP; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid controlcharacter mode '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "securepath")) { bComplexProcessing = 1; if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"drop", sizeof("drop") - 1)) { secpath = SP_DROP; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"replace", sizeof("replace") - 1)) { secpath = SP_REPLACE; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid securepath mode '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "caseconversion")) { bComplexProcessing = 1; if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"lower", sizeof("lower") - 1)) { caseconv = tplCaseConvLower; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"upper", sizeof("upper") - 1)) { caseconv = tplCaseConvUpper; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid caseconversion type '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblkProperty.descr[i].name, "compressspace")) { bComplexProcessing = 1; bCompressSP = pvals[i].val.d.n; } else if (!strcmp(pblkProperty.descr[i].name, "date.inutc")) { bDateInUTC = pvals[i].val.d.n; } else if (!strcmp(pblkProperty.descr[i].name, "dateformat")) { if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"mysql", sizeof("mysql") - 1)) { datefmt = tplFmtMySQLDate; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"pgsql", sizeof("pgsql") - 1)) { datefmt = tplFmtPgSQLDate; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"rfc3164", sizeof("rfc3164") - 1)) { datefmt = tplFmtRFC3164Date; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"rfc3164-buggyday", sizeof("rfc3164-buggyday") - 1)) { datefmt = tplFmtRFC3164BuggyDate; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"rfc3339", sizeof("rfc3339") - 1)) { datefmt = tplFmtRFC3339Date; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"unixtimestamp", sizeof("unixtimestamp") - 1)) { datefmt = tplFmtUnixDate; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"subseconds", sizeof("subseconds") - 1)) { datefmt = tplFmtSecFrac; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"wdayname", sizeof("wdayname") - 1)) { datefmt = tplFmtWDayName; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"wday", sizeof("wday") - 1)) { datefmt = tplFmtWDay; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"year", sizeof("year") - 1)) { datefmt = tplFmtYear; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"month", sizeof("month") - 1)) { datefmt = tplFmtMonth; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"day", sizeof("day") - 1)) { datefmt = tplFmtDay; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"hour", sizeof("hour") - 1)) { datefmt = tplFmtHour; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"minute", sizeof("minute") - 1)) { datefmt = tplFmtMinute; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"second", sizeof("second") - 1)) { datefmt = tplFmtSecond; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"tzoffshour", sizeof("tzoffshour") - 1)) { datefmt = tplFmtTZOffsHour; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"tzoffsmin", sizeof("tzoffsmin") - 1)) { datefmt = tplFmtTZOffsMin; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"tzoffsdirection", sizeof("tzoffsdirection") - 1)) { datefmt = tplFmtTZOffsDirection; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"ordinal", sizeof("ordinal") - 1)) { datefmt = tplFmtOrdinal; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"week", sizeof("week") - 1)) { datefmt = tplFmtWeek; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid date format '%s' for property", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else { dbgprintf( "template:propertyTpe: program error, non-handled " "param '%s'\n", pblkProperty.descr[i].name); } } if (name == NULL) { CHKmalloc(name = (uchar *)strdup("")); } if (outname == NULL) { /* we need to drop "$!" prefix, if present */ if (ustrlen(name) >= 2 && !strncmp((char *)name, "$!", 2)) outname = ustrdup(name + 2); else outname = ustrdup(name); } /* sanity check */ if (topos_set == 0 && frompos != -1) topos = 2000000000; /* large enough ;) */ if (frompos == -1 && topos_set != 0) frompos = 0; if (bPosRelativeToEnd) { if (topos > frompos) { LogError(0, RS_RET_ERR, "position.to=%d is higher than postion.from=%d " "in 'relativeToEnd' mode\n", topos, frompos); ABORT_FINALIZE(RS_RET_ERR); } } else { if ((topos >= 0) && (topos < frompos)) { LogError(0, RS_RET_ERR, "position.to=%d is lower than postion.from=%d\n", topos, frompos); ABORT_FINALIZE(RS_RET_ERR); } } if (fieldnum != -1 && re_expr != NULL) { LogError(0, RS_RET_ERR, "both field extraction and regex extraction " "specified - this is not possible, remove one"); ABORT_FINALIZE(RS_RET_ERR); } #ifdef FEATURE_REGEXP if (re_expr != NULL) { CHKiRet(tplValidateRegexMatchSelector(pTpl, "regex.match", re_matchToUse)); CHKiRet(tplValidateRegexMatchSelector(pTpl, "regex.submatch", re_submatchToUse)); } #endif /* apply */ CHKmalloc(pTpe = tpeConstruct(pTpl)); pTpe->eEntryType = FIELD; CHKiRet(msgPropDescrFill(&pTpe->data.field.msgProp, name, strlen((char *)name))); pTpe->data.field.options.bDropLastLF = droplastlf; pTpe->data.field.options.bSPIffNo1stSP = spifno1stsp; pTpe->data.field.options.bMandatory = mandatory; pTpe->data.field.options.bOmitIfZero = omitifzero; pTpe->data.field.options.bFixedWidth = fixedwidth; pTpe->data.field.options.dataType = dataType; pTpe->data.field.options.onEmpty = onEmpty; pTpe->data.field.eCaseConv = caseconv; switch (formatType) { case F_NONE: /* all set ;) */ break; case F_CSV: pTpe->data.field.options.bCSV = 1; break; case F_JSON: pTpe->data.field.options.bJSON = 1; break; case F_JSONF: pTpe->data.field.options.bJSONf = 1; break; case F_JSONR: pTpe->data.field.options.bJSONr = 1; break; case F_JSONFR: pTpe->data.field.options.bJSONfr = 1; break; default: // No action needed for other cases break; } switch (controlchr) { case CC_NONE: /* all set ;) */ break; case CC_ESCAPE: pTpe->data.field.options.bEscapeCC = 1; break; case CC_ESCAPE_OCTAL: pTpe->data.field.options.bEscapeCCOctal = 1; break; case CC_SPACE: pTpe->data.field.options.bSpaceCC = 1; break; case CC_DROP: pTpe->data.field.options.bDropCC = 1; break; default: // No action needed for other cases break; } switch (secpath) { case SP_NONE: /* all set ;) */ break; case SP_DROP: pTpe->data.field.options.bSecPathDrop = 1; break; case SP_REPLACE: pTpe->data.field.options.bSecPathReplace = 1; break; default: // No action needed for other cases break; } pTpe->fieldName = outname; if (outname != NULL) pTpe->lenFieldName = ustrlen(outname); outname = NULL; pTpe->bComplexProcessing = bComplexProcessing; pTpe->data.field.eDateFormat = datefmt; pTpe->data.field.options.bDateInUTC = bDateInUTC; pTpe->data.field.options.bCompressSP = bCompressSP; if (fieldnum != -1) { pTpe->data.field.has_fields = 1; pTpe->data.field.iFieldNr = fieldnum; pTpe->data.field.field_delim = fielddelim; } if (frompos != -1) { pTpe->data.field.iFromPos = frompos; pTpe->data.field.iToPos = topos; pTpe->data.field.options.bFromPosEndRelative = bPosRelativeToEnd; } if (re_expr != NULL) { rsRetVal iRetLocal; pTpe->data.field.typeRegex = re_type; pTpe->data.field.nomatchAction = re_nomatchType; pTpe->data.field.iMatchToUse = re_matchToUse; pTpe->data.field.iSubMatchToUse = re_submatchToUse; pTpe->data.field.has_regex = 1; if ((iRetLocal = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) { int iOptions; iOptions = (pTpe->data.field.typeRegex == TPL_REGEX_ERE) ? REG_EXTENDED : 0; if (regexp.regcomp(&(pTpe->data.field.re), (char *)re_expr, iOptions) != 0) { LogError(0, NO_ERRCODE, "error compiling regex '%s'", re_expr); pTpe->data.field.has_regex = 2; ABORT_FINALIZE(RS_RET_ERR); } } else { /* regexp object could not be loaded */ if (bFirstRegexpErrmsg) { /* prevent flood of messages, maybe even an endless loop! */ bFirstRegexpErrmsg = 0; LogError(0, NO_ERRCODE, "regexp library could not be loaded (error %d), " "regexp ignored", iRetLocal); } pTpe->data.field.has_regex = 2; ABORT_FINALIZE(RS_RET_ERR); } } finalize_it: if (pvals != NULL) cnfparamvalsDestruct(pvals, &pblkProperty); free(name); free(outname); free(re_expr); RETiRet; } /* create a template in list mode, is build from sub-objects */ static rsRetVal createListTpl(struct template *pTpl, struct cnfobj *o) { struct objlst *lst; DEFiRet; dbgprintf("create template from subobjs\n"); objlstPrint(o->subobjs); for (lst = o->subobjs; lst != NULL; lst = lst->next) { switch (lst->obj->objType) { case CNFOBJ_PROPERTY: CHKiRet(createPropertyTpe(pTpl, lst->obj)); break; case CNFOBJ_CONSTANT: CHKiRet(createConstantTpe(pTpl, lst->obj)); break; default: dbgprintf( "program error: invalid object type %d " "in createLstTpl\n", lst->obj->objType); break; } nvlstChkUnused(lst->obj->nvlst); } finalize_it: RETiRet; } /* Add a new template via the v6 config system. */ rsRetVal ATTR_NONNULL() tplProcessCnf(struct cnfobj *o) { struct template *pTpl = NULL; struct cnfparamvals *pvals = NULL; int lenName = 0; /* init just to keep compiler happy: mandatory parameter */ char *name = NULL; uchar *tplStr = NULL; uchar *plugin = NULL; uchar *p; msgPropDescr_t subtree; sbool bHaveSubtree = 0; enum { T_STRING, T_PLUGIN, T_LIST, T_SUBTREE } tplType = T_STRING; /* init just to keep compiler happy: mandatory parameter */ int i; int o_sql = 0, o_stdsql = 0, o_jsonf = 0, o_jsonftree = 0, o_json = 0, o_casesensitive = 0; /* options */ int numopts; rsRetVal localRet; uchar *cstr = NULL; DEFiRet; pvals = nvlstGetParams(o->nvlst, &pblk, NULL); if (pvals == NULL) { parser_errmsg("error processing template config parameters"); ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS); } cnfparamsPrint(&pblk, pvals); for (i = 0; i < pblk.nParams; ++i) { if (!pvals[i].bUsed) continue; if (!strcmp(pblk.descr[i].name, "name")) { lenName = es_strlen(pvals[i].val.d.estr); CHKmalloc(name = es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(pblk.descr[i].name, "type")) { if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"string", sizeof("string") - 1)) { tplType = T_STRING; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"plugin", sizeof("plugin") - 1)) { tplType = T_PLUGIN; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"list", sizeof("list") - 1)) { tplType = T_LIST; } else if (!es_strbufcmp(pvals[i].val.d.estr, (uchar *)"subtree", sizeof("subtree") - 1)) { tplType = T_SUBTREE; } else { uchar *typeStr; CHKmalloc(typeStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid template type '%s'", typeStr); free(typeStr); ABORT_FINALIZE(RS_RET_ERR); } } else if (!strcmp(pblk.descr[i].name, "string")) { CHKmalloc(tplStr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(pblk.descr[i].name, "subtree")) { uchar *st_str = es_getBufAddr(pvals[i].val.d.estr); if (st_str[0] != '$' || st_str[1] != '!') { CHKmalloc(cstr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); LogError(0, RS_RET_ERR, "invalid subtree " "parameter, variable must start with '$!' but " "var name is '%s'", cstr); ABORT_FINALIZE(RS_RET_ERR); } else { CHKmalloc(cstr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); CHKiRet(msgPropDescrFill(&subtree, (uchar *)cstr, ustrlen(cstr))); bHaveSubtree = 1; } } else if (!strcmp(pblk.descr[i].name, "plugin")) { CHKmalloc(plugin = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL)); } else if (!strcmp(pblk.descr[i].name, "option.stdsql")) { o_stdsql = pvals[i].val.d.n; } else if (!strcmp(pblk.descr[i].name, "option.sql")) { o_sql = pvals[i].val.d.n; } else if (!strcmp(pblk.descr[i].name, "option.json")) { o_json = pvals[i].val.d.n; } else if (!strcmp(pblk.descr[i].name, "option.jsonf")) { o_jsonf = pvals[i].val.d.n; } else if (!strcmp(pblk.descr[i].name, "option.jsonftree")) { o_jsonftree = pvals[i].val.d.n; } else if (!strcmp(pblk.descr[i].name, "option.casesensitive")) { o_casesensitive = pvals[i].val.d.n; } else { dbgprintf( "template: program error, non-handled " "param '%s'\n", pblk.descr[i].name); } } /* the following check is just for clang static anaylzer: this condition * cannot occur if all is setup well, because "name" is a required parameter * inside the param block and so the code should err out above. */ if (name == NULL) { DBGPRINTF("template/tplProcessConf: logic error name == NULL - pblk wrong?\n"); ABORT_FINALIZE(RS_RET_ERR); } /* do config sanity checks */ if (tplStr == NULL) { if (tplType == T_STRING) { LogError(0, RS_RET_ERR, "template '%s' of type string needs " "string parameter", name); ABORT_FINALIZE(RS_RET_ERR); } } else { if (tplType != T_STRING) { LogError(0, RS_RET_ERR, "template '%s' is not a string " "template but has a string specified - ignored", name); } } if (plugin == NULL) { if (tplType == T_PLUGIN) { LogError(0, RS_RET_ERR, "template '%s' of type plugin needs " "plugin parameter", name); ABORT_FINALIZE(RS_RET_ERR); } } else { if (tplType != T_PLUGIN) { LogError(0, RS_RET_ERR, "template '%s' is not a plugin " "template but has a plugin specified - ignored", name); } } if (!bHaveSubtree) { if (tplType == T_SUBTREE) { LogError(0, RS_RET_ERR, "template '%s' of type subtree needs " "subtree parameter", name); ABORT_FINALIZE(RS_RET_ERR); } } else { if (tplType != T_SUBTREE) { LogError(0, RS_RET_ERR, "template '%s' is not a subtree " "template but has a subtree specified - ignored", name); } } if (o->subobjs == NULL) { if (tplType == T_LIST) { LogError(0, RS_RET_ERR, "template '%s' of type list has " "no parameters specified", name); ABORT_FINALIZE(RS_RET_ERR); } } else { if (tplType != T_LIST) { LogError(0, RS_RET_ERR, "template '%s' is not a list " "template but has parameters specified - ignored", name); } } numopts = 0; if (o_sql) ++numopts; if (o_stdsql) ++numopts; if (o_json) ++numopts; if (o_jsonf) ++numopts; if (o_jsonftree) ++numopts; if (numopts > 1) { LogError(0, RS_RET_ERR, "template '%s' has multiple incompatible " "options of sql, stdsql, json, jsonf or jsonftree specified", name); ABORT_FINALIZE(RS_RET_ERR); } /* config ok */ if ((pTpl = tplConstruct(loadConf)) == NULL) { DBGPRINTF("template.c: tplConstruct failed!\n"); ABORT_FINALIZE(RS_RET_ERR); } pTpl->pszName = (char *)name; name = NULL; pTpl->iLenName = lenName; switch (tplType) { case T_STRING: p = tplStr; while (*p) { switch (*p) { case '%': /* parameter */ ++p; /* eat '%' */ CHKiRet(do_Parameter(&p, pTpl)); break; default: /* constant */ do_Constant(&p, pTpl, 0); break; } } break; case T_PLUGIN: p = plugin; /* TODO: the use of tplAddTplMod() can be improved! */ localRet = tplAddTplMod(pTpl, &p); if (localRet != RS_RET_OK) { LogError(0, localRet, "template '%s': error %d " "defining template via plugin (strgen) module", pTpl->pszName, localRet); ABORT_FINALIZE(localRet); } break; case T_LIST: CHKiRet(createListTpl(pTpl, o)); break; case T_SUBTREE: memcpy(&pTpl->subtree, &subtree, sizeof(msgPropDescr_t)); pTpl->bHaveSubtree = 1; break; default: // No action needed for other cases break; } pTpl->optFormatEscape = NO_ESCAPE; if (o_stdsql) pTpl->optFormatEscape = STDSQL_ESCAPE; else if (o_sql) pTpl->optFormatEscape = SQL_ESCAPE; else if (o_json) pTpl->optFormatEscape = JSON_ESCAPE; else if (o_jsonf || o_jsonftree) pTpl->optFormatEscape = JSONF; if (o_jsonftree) pTpl->bJsonTreeEnabled = 1; if (o_casesensitive) pTpl->optCaseSensitive = 1; apply_case_sensitivity(pTpl); if (pTpl->optFormatEscape == JSONF) tplWarnDuplicateJsonKeys(pTpl); finalize_it: if (iRet != RS_RET_OK && bHaveSubtree) { msgPropDescrDestruct(&subtree); } free(name); free(cstr); free(tplStr); free(plugin); if (pvals != NULL) cnfparamvalsDestruct(pvals, &pblk); if (iRet != RS_RET_OK) { if (pTpl != NULL) { /* we simply make the template defunct in this case by setting * its name to a zero-string. We do not free it, as this would * require additional code and causes only a very small memory * consumption. TODO: maybe in next iteration... */ *pTpl->pszName = '\0'; } } RETiRet; } /* Find a template object based on name. Search * currently is case-sensitive (should we change?). * returns pointer to template object if found and * NULL otherwise. * rgerhards 2004-11-17 */ struct template *tplFind(rsconf_t *conf, char *pName, int iLenName) { struct template *pTpl; assert(pName != NULL); pTpl = conf->templates.root; while (pTpl != NULL && !(pTpl->iLenName == iLenName && !strcmp(pTpl->pszName, pName))) { pTpl = pTpl->pNext; } return (pTpl); } /* Destroy the template structure. This is for de-initialization * at program end. Everything is deleted. * rgerhards 2005-02-22 * I have commented out dbgprintfs, because they are not needed for * "normal" debugging. Uncomment them, if they are needed. * rgerhards, 2007-07-05 */ void tplDeleteAll(rsconf_t *conf) { struct template *pTpl, *pTplDel; struct templateEntry *pTpe, *pTpeDel; pTpl = conf->templates.root; while (pTpl != NULL) { pTpe = pTpl->pEntryRoot; while (pTpe != NULL) { pTpeDel = pTpe; pTpe = pTpe->pNext; switch (pTpeDel->eEntryType) { case UNDEFINED: break; case CONSTANT: free(pTpeDel->data.constant.pConstant); break; case FIELD: /* check if we have a regexp and, if so, delete it */ #ifdef FEATURE_REGEXP if (pTpeDel->data.field.has_regex != 0) { if (objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { regexp.regfree(&(pTpeDel->data.field.re)); } } #endif msgPropDescrDestruct(&pTpeDel->data.field.msgProp); break; default: // No action needed for other cases break; } free(pTpeDel->fieldName); free(pTpeDel); } pTplDel = pTpl; pTpl = pTpl->pNext; free(pTplDel->pszName); if (pTplDel->bHaveSubtree) msgPropDescrDestruct(&pTplDel->subtree); tplJsonNodeFree(pTplDel->pJsonRoot); free(pTplDel); } } /* Destroy all templates obtained from conf file * preserving hardcoded ones. This is called from init(). */ void tplDeleteNew(rsconf_t *conf) { struct template *pTpl, *pTplDel; struct templateEntry *pTpe, *pTpeDel; if (conf->templates.root == NULL || conf->templates.lastStatic == NULL) return; pTpl = conf->templates.lastStatic->pNext; conf->templates.lastStatic->pNext = NULL; conf->templates.last = conf->templates.lastStatic; while (pTpl != NULL) { pTpe = pTpl->pEntryRoot; while (pTpe != NULL) { pTpeDel = pTpe; pTpe = pTpe->pNext; switch (pTpeDel->eEntryType) { case UNDEFINED: break; case CONSTANT: free(pTpeDel->data.constant.pConstant); break; case FIELD: #ifdef FEATURE_REGEXP /* check if we have a regexp and, if so, delete it */ if (pTpeDel->data.field.has_regex != 0) { if (objUse(regexp, LM_REGEXP_FILENAME) == RS_RET_OK) { regexp.regfree(&(pTpeDel->data.field.re)); } } #endif msgPropDescrDestruct(&pTpeDel->data.field.msgProp); break; default: // No action needed for other cases break; } free(pTpeDel->fieldName); free(pTpeDel); } pTplDel = pTpl; pTpl = pTpl->pNext; free(pTplDel->pszName); if (pTplDel->bHaveSubtree) msgPropDescrDestruct(&pTplDel->subtree); tplJsonNodeFree(pTplDel->pJsonRoot); free(pTplDel); } } /* Store the pointer to the last hardcoded template */ void tplLastStaticInit(rsconf_t *conf, struct template *tpl) { conf->templates.lastStatic = tpl; } /* Print the template structure. This is more or less a * debug or test aid, but anyhow I think it's worth it... */ void tplPrintList(rsconf_t *conf) { struct template *pTpl; struct templateEntry *pTpe; pTpl = conf->templates.root; while (pTpl != NULL) { dbgprintf("Template: Name='%s' ", pTpl->pszName == NULL ? "NULL" : pTpl->pszName); if (pTpl->optFormatEscape == SQL_ESCAPE) dbgprintf("[SQL-Format (MySQL)] "); else if (pTpl->optFormatEscape == JSON_ESCAPE) dbgprintf("[JSON-Escaped Format] "); else if (pTpl->optFormatEscape == STDSQL_ESCAPE) dbgprintf("[SQL-Format (standard SQL)] "); else if (pTpl->optFormatEscape == JSONF) dbgprintf("[JSON fields] "); if (pTpl->optCaseSensitive) dbgprintf("[Case Sensitive Vars] "); dbgprintf("\n"); pTpe = pTpl->pEntryRoot; while (pTpe != NULL) { dbgprintf("\tEntry(%lx): type %d, ", (unsigned long)pTpe, pTpe->eEntryType); switch (pTpe->eEntryType) { case UNDEFINED: dbgprintf("(UNDEFINED)"); break; case CONSTANT: dbgprintf("(CONSTANT), value: '%s'", pTpe->data.constant.pConstant); break; case FIELD: dbgprintf("(FIELD), value: '%d' ", pTpe->data.field.msgProp.id); if (pTpe->data.field.msgProp.id == PROP_CEE) { dbgprintf("[EE-Property: '%s'] ", pTpe->data.field.msgProp.name); } else if (pTpe->data.field.msgProp.id == PROP_LOCAL_VAR) { dbgprintf("[Local Var: '%s'] ", pTpe->data.field.msgProp.name); } switch (pTpe->data.field.eDateFormat) { case tplFmtDefault: break; case tplFmtMySQLDate: dbgprintf("[Format as MySQL-Date] "); break; case tplFmtPgSQLDate: dbgprintf("[Format as PgSQL-Date] "); break; case tplFmtRFC3164Date: dbgprintf("[Format as RFC3164-Date] "); break; case tplFmtRFC3339Date: dbgprintf("[Format as RFC3339-Date] "); break; case tplFmtUnixDate: dbgprintf("[Format as Unix timestamp] "); break; case tplFmtSecFrac: dbgprintf("[fractional seconds, only] "); break; case tplFmtRFC3164BuggyDate: dbgprintf("[Format as buggy RFC3164-Date] "); break; case tplFmtWDayName: dbgprintf("[Format as weekday name] "); break; case tplFmtYear: dbgprintf("[Format as year] "); break; case tplFmtMonth: dbgprintf("[Format as month] "); break; case tplFmtDay: dbgprintf("[Format as day] "); break; case tplFmtHour: dbgprintf("[Format as hour] "); break; case tplFmtMinute: dbgprintf("[Format as minute] "); break; case tplFmtSecond: dbgprintf("[Format as second] "); break; case tplFmtTZOffsHour: dbgprintf("[Format as offset hour] "); break; case tplFmtTZOffsMin: dbgprintf("[Format as offset minute] "); break; case tplFmtTZOffsDirection: dbgprintf("[Format as offset direction] "); break; case tplFmtWDay: dbgprintf("[Format as weekday] "); break; case tplFmtOrdinal: dbgprintf("[Format as ordinal] "); break; case tplFmtWeek: dbgprintf("[Format as week] "); break; default: dbgprintf("[UNKNOWN eDateFormat %d] ", pTpe->data.field.eDateFormat); } switch (pTpe->data.field.eCaseConv) { case tplCaseConvNo: break; case tplCaseConvLower: dbgprintf("[Converted to Lower Case] "); break; case tplCaseConvUpper: dbgprintf("[Converted to Upper Case] "); break; default: // No action needed for other cases break; } if (pTpe->data.field.options.bEscapeCC) { dbgprintf("[escape control-characters] "); } if (pTpe->data.field.options.bDropCC) { dbgprintf("[drop control-characters] "); } if (pTpe->data.field.options.bSpaceCC) { dbgprintf("[replace control-characters with space] "); } if (pTpe->data.field.options.bSecPathDrop) { dbgprintf("[slashes are dropped] "); } if (pTpe->data.field.options.bSecPathReplace) { dbgprintf("[slashes are replaced by '_'] "); } if (pTpe->data.field.options.bSPIffNo1stSP) { dbgprintf("[SP iff no first SP] "); } if (pTpe->data.field.options.bCSV) { dbgprintf("[format as CSV (RFC4180)]"); } if (pTpe->data.field.options.bJSON) { dbgprintf("[format as JSON] "); } if (pTpe->data.field.options.bJSONf) { dbgprintf("[format as JSON field] "); } if (pTpe->data.field.options.bJSONr) { dbgprintf("[format as JSON without re-escaping] "); } if (pTpe->data.field.options.bJSONfr) { dbgprintf("[format as JSON field without re-escaping] "); } if (pTpe->data.field.options.bMandatory) { dbgprintf("[mandatory field] "); } if (pTpe->data.field.options.bDropLastLF) { dbgprintf("[drop last LF in msg] "); } if (pTpe->data.field.has_fields == 1) { dbgprintf("[substring, field #%d only (delimiter %d)] ", pTpe->data.field.iFieldNr, pTpe->data.field.field_delim); } if (pTpe->data.field.iFromPos != 0 || pTpe->data.field.iToPos != 0) { dbgprintf("[substring, from character %d to %d] ", pTpe->data.field.iFromPos, pTpe->data.field.iToPos); } break; default: // No action needed for other cases break; } if (pTpe->bComplexProcessing) dbgprintf("[COMPLEX]"); dbgprintf("\n"); pTpe = pTpe->pNext; } pTpl = pTpl->pNext; /* done, go next */ } } int tplGetEntryCount(struct template *pTpl) { assert(pTpl != NULL); return (pTpl->tpenElements); } rsRetVal templateInit(void) { DEFiRet; CHKiRet(objGetObjInterface(&obj)); CHKiRet(objUse(strgen, CORE_COMPONENT)); finalize_it: RETiRet; }