rsyslog/plugins/pmnormalize/pmnormalize.c
Rainer Gerhards a82fcd0d9c
parser: add checkParserInst hook for pre-registration validation
Introduce a new lifecycle callback—`checkParserInst`—to perform
configuration sanity checks on parser instances immediately after they’re
created. This establishes a standardized validation point (similar to
`checkCnf` in other module types) without altering existing parser logic.

By wiring `checkParserInst` into:
- the module template (macros for definition and registration),
- the module loader (`doModInit`) with graceful fallback,
- the runtime configuration flow (`rsconf.c`) just after
  `newParserInst`,

and by providing empty stubs in all current parser modules (contrib and
plugins), we now have a clear, uniform spot to add parser-specific
validation rules in subsequent patches. This improves future
maintainability and robustness of parser configuration handling.
2025-07-31 08:26:50 +02:00

259 lines
8.2 KiB
C

/* pmnormalize.c
* This is a parser module for parsing incoming messages using liblognorm.
*
* File begun on 2017-03-03 by Pascal Withopf.
*
* Copyright 2014-2019 Adiscon GmbH.
*
* This file is part of rsyslog.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
* -or-
* see COPYING.ASL20 in the source distribution
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "config.h"
#include "rsyslog.h"
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <liblognorm.h>
#include <json.h>
#include "conf.h"
#include "syslogd-types.h"
#include "template.h"
#include "msg.h"
#include "module-template.h"
#include "glbl.h"
#include "errmsg.h"
#include "parser.h"
#include "datetime.h"
#include "unicode-helper.h"
MODULE_TYPE_PARSER
MODULE_TYPE_NOKEEP;
PARSER_NAME("rsyslog.pmnormalize")
MODULE_CNFNAME("pmnormalize")
/* internal structures */
DEF_PMOD_STATIC_DATA;
DEFobjCurrIf(glbl) DEFobjCurrIf(parser) DEFobjCurrIf(datetime)
/* parser instance parameters */
static struct cnfparamdescr parserpdescr[] = {
{"rulebase", eCmdHdlrGetWord, 0}, {"rule", eCmdHdlrArray, 0}, {"undefinedpropertyerror", eCmdHdlrBinary, 0}};
static struct cnfparamblk parserpblk = {CNFPARAMBLK_VERSION, sizeof(parserpdescr) / sizeof(struct cnfparamdescr),
parserpdescr};
struct instanceConf_s {
sbool undefPropErr;
char *rulebase;
char *rule;
ln_ctx ctxln; /*context to be used for liblognorm*/
char *pszPath; /*path of normalized data*/
};
BEGINisCompatibleWithFeature
CODESTARTisCompatibleWithFeature;
if (eFeat == sFEATUREAutomaticSanitazion) iRet = RS_RET_OK;
if (eFeat == sFEATUREAutomaticPRIParsing) iRet = RS_RET_OK;
ENDisCompatibleWithFeature
/* create input instance, set default parameters, and
* add it to the list of instances.
*/
static rsRetVal createInstance(instanceConf_t **pinst) {
instanceConf_t *inst;
DEFiRet;
CHKmalloc(inst = malloc(sizeof(instanceConf_t)));
inst->undefPropErr = 0;
inst->rulebase = NULL;
inst->rule = NULL;
inst->ctxln = NULL;
*pinst = inst;
finalize_it:
RETiRet;
}
/* callback for liblognorm error messages */
static void errCallBack(void __attribute__((unused)) * cookie, const char *msg, size_t __attribute__((unused)) lenMsg) {
LogError(0, RS_RET_ERR_LIBLOGNORM, "liblognorm error: %s", msg);
}
/* to be called to build the liblognorm part of the instance ONCE ALL PARAMETERS ARE CORRECT
* (and set within inst!).
*/
static rsRetVal buildInstance(instanceConf_t *inst) {
DEFiRet;
if ((inst->ctxln = ln_initCtx()) == NULL) {
LogError(0, RS_RET_ERR_LIBLOGNORM_INIT,
"error: could not initialize "
"liblognorm ctx, cannot activate action");
ABORT_FINALIZE(RS_RET_ERR_LIBLOGNORM_INIT);
}
ln_setErrMsgCB(inst->ctxln, errCallBack, NULL);
if (inst->rule != NULL && inst->rulebase == NULL) {
if (ln_loadSamplesFromString(inst->ctxln, inst->rule) != 0) {
LogError(0, RS_RET_NO_RULEBASE,
"error: normalization rules '%s' "
"could not be loaded, cannot activate action",
inst->rule);
ABORT_FINALIZE(RS_RET_ERR_LIBLOGNORM_SAMPDB_LOAD);
}
} else if (inst->rulebase != NULL && inst->rule == NULL) {
if (ln_loadSamples(inst->ctxln, (char *)inst->rulebase) != 0) {
LogError(0, RS_RET_NO_RULEBASE,
"error: normalization rulebase '%s' "
"could not be loaded, cannot activate action",
inst->rulebase);
ABORT_FINALIZE(RS_RET_ERR_LIBLOGNORM_SAMPDB_LOAD);
}
}
finalize_it:
RETiRet;
}
BEGINfreeParserInst
CODESTARTfreeParserInst;
dbgprintf("pmnormalize: free parser instance %p\n", pInst);
free(pInst->rulebase);
free(pInst->rule);
if (pInst->ctxln != NULL) {
ln_exitCtx(pInst->ctxln);
}
ENDfreeParserInst
BEGINcheckParserInst
CODESTARTcheckParserInst;
ENDcheckParserInst
BEGINnewParserInst
struct cnfparamvals *pvals = NULL;
int i;
CODESTARTnewParserInst;
DBGPRINTF("newParserInst (pmnormalize)\n");
inst = NULL;
CHKiRet(createInstance(&inst));
if (lst == NULL) FINALIZE; /* just set defaults, no param block! */
if ((pvals = nvlstGetParams(lst, &parserpblk, NULL)) == NULL) {
ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
}
if (Debug) {
dbgprintf("parser param blk in pmnormalize:\n");
cnfparamsPrint(&parserpblk, pvals);
}
for (i = 0; i < parserpblk.nParams; ++i) {
if (!pvals[i].bUsed) continue;
if (!strcmp(parserpblk.descr[i].name, "undefinedpropertyerror")) {
inst->undefPropErr = (int)pvals[i].val.d.n;
} else if (!strcmp(parserpblk.descr[i].name, "rulebase")) {
inst->rulebase = (char *)es_str2cstr(pvals[i].val.d.estr, NULL);
} else if (!strcmp(parserpblk.descr[i].name, "rule")) {
es_str_t *rules;
CHKmalloc(rules = es_newStr(128));
for (int j = 0; j < pvals[i].val.d.ar->nmemb; ++j) {
CHKiRet(es_addStr(&rules, pvals[i].val.d.ar->arr[j]));
CHKiRet(es_addChar(&rules, '\n'));
}
inst->rule = (char *)es_str2cstr(rules, NULL);
if (rules != NULL) es_deleteStr(rules);
} else {
LogError(0, RS_RET_INTERNAL_ERROR, "pmnormalize: program error, non-handled param '%s'",
parserpblk.descr[i].name);
}
}
if (!inst->rulebase && !inst->rule) {
LogError(0, RS_RET_CONFIG_ERROR,
"pmnormalize: you need to specify "
"either parameter 'rule' or 'rulebase'.");
ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
}
if (inst->rulebase && inst->rule) {
LogError(0, RS_RET_CONFIG_ERROR,
"pmnormalize: you need to specify "
"one of the parameters 'rule' and 'rulebase', but not both");
ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
}
iRet = buildInstance(inst);
finalize_it:
CODE_STD_FINALIZERnewParserInst if (lst != NULL) cnfparamvalsDestruct(pvals, &parserpblk);
if (iRet != RS_RET_OK && inst != NULL) freeParserInst(inst);
ENDnewParserInst
BEGINparse2
uchar *buf;
rs_size_t len;
int r;
struct json_object *json = NULL;
CODESTARTparse2;
DBGPRINTF("Message will now be parsed by pmnormalize\n");
/*Msg OffSet needs to be set*/
MsgSetMSGoffs(pMsg, 0);
getRawMsg(pMsg, &buf, &len);
r = ln_normalize(pInst->ctxln, (char *)buf, len, &json);
if (r != 0) {
DBGPRINTF("error %d during ln_normalize\n", r);
if (pInst->undefPropErr) {
LogError(0, RS_RET_ERR,
"error %d during ln_normalize; "
"json: %s\n",
r, fjson_object_to_json_string(json));
}
fjson_object_put(json);
ABORT_FINALIZE(RS_RET_COULD_NOT_PARSE);
} else {
iRet = MsgSetPropsViaJSON_Object(pMsg, json);
}
finalize_it:
ENDparse2
BEGINmodExit
CODESTARTmodExit;
/* release what we no longer need */
objRelease(glbl, CORE_COMPONENT);
objRelease(parser, CORE_COMPONENT);
objRelease(datetime, CORE_COMPONENT);
ENDmodExit
BEGINqueryEtryPt
CODESTARTqueryEtryPt;
CODEqueryEtryPt_STD_PMOD2_QUERIES;
CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES;
ENDqueryEtryPt
BEGINmodInit()
CODESTARTmodInit;
*ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
CODEmodInit_QueryRegCFSLineHdlr CHKiRet(objUse(glbl, CORE_COMPONENT));
CHKiRet(objUse(parser, CORE_COMPONENT));
CHKiRet(objUse(datetime, CORE_COMPONENT));
DBGPRINTF("pmnormalize parser init called\n");
ENDmodInit