diff options
Diffstat (limited to '')
-rw-r--r-- | runtime/ruleset.c | 1194 |
1 files changed, 1194 insertions, 0 deletions
diff --git a/runtime/ruleset.c b/runtime/ruleset.c new file mode 100644 index 0000000..c137ee3 --- /dev/null +++ b/runtime/ruleset.c @@ -0,0 +1,1194 @@ +/* ruleset.c - rsyslog's ruleset object + * + * We have a two-way structure of linked lists: one config-specifc linked list + * (conf->rulesets.llRulesets) hold alls rule sets that we know. Included in each + * list is a list of rules (which contain a list of actions, but that's + * a different story). + * + * Usually, only a single rule set is executed. However, there exist some + * situations where all rules must be iterated over, for example on HUP. Thus, + * we also provide interfaces to do that. + * + * Module begun 2009-06-10 by Rainer Gerhards + * + * Copyright 2009-2021 Rainer Gerhards and Adiscon GmbH. + * + * This file is part of the rsyslog runtime library. + * + * 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 <stdlib.h> +#include <assert.h> +#include <ctype.h> + +#include "rsyslog.h" +#include "obj.h" +#include "cfsysline.h" +#include "msg.h" +#include "ruleset.h" +#include "errmsg.h" +#include "parser.h" +#include "batch.h" +#include "unicode-helper.h" +#include "rsconf.h" +#include "action.h" +#include "rainerscript.h" +#include "srUtils.h" +#include "modules.h" +#include "wti.h" +#include "dirty.h" /* for main ruleset queue creation */ + + +/* static data */ +DEFobjStaticHelpers +DEFobjCurrIf(parser) + +/* tables for interfacing with the v6 config system (as far as we need to) */ +static struct cnfparamdescr rspdescr[] = { + { "name", eCmdHdlrString, CNFPARAM_REQUIRED }, + { "parser", eCmdHdlrArray, 0 } +}; +static struct cnfparamblk rspblk = + { CNFPARAMBLK_VERSION, + sizeof(rspdescr)/sizeof(struct cnfparamdescr), + rspdescr + }; + +/* forward definitions */ +static rsRetVal processBatch(batch_t *pBatch, wti_t *pWti); +static rsRetVal scriptExec(struct cnfstmt *root, smsg_t *pMsg, wti_t *pWti); + + +/* ---------- linked-list key handling functions (ruleset) ---------- */ + +/* destructor for linked list keys. + */ +rsRetVal +rulesetKeyDestruct(void __attribute__((unused)) *pData) +{ + free(pData); + return RS_RET_OK; +} +/* ---------- END linked-list key handling functions (ruleset) ---------- */ + + +/* iterate over all actions in a script (stmt subtree) */ +static void +scriptIterateAllActions(struct cnfstmt *root, rsRetVal (*pFunc)(void*, void*), void* pParam) +{ + struct cnfstmt *stmt; + for(stmt = root ; stmt != NULL ; stmt = stmt->next) { + switch(stmt->nodetype) { + case S_NOP: + case S_STOP: + case S_SET: + case S_UNSET: + case S_CALL_INDIRECT: + case S_CALL:/* call does not need to do anything - done in called ruleset! */ + break; + case S_ACT: + DBGPRINTF("iterateAllActions calling into action %p\n", stmt->d.act); + pFunc(stmt->d.act, pParam); + break; + case S_IF: + if(stmt->d.s_if.t_then != NULL) + scriptIterateAllActions(stmt->d.s_if.t_then, + pFunc, pParam); + if(stmt->d.s_if.t_else != NULL) + scriptIterateAllActions(stmt->d.s_if.t_else, + pFunc, pParam); + break; + case S_FOREACH: + if(stmt->d.s_foreach.body != NULL) + scriptIterateAllActions(stmt->d.s_foreach.body, + pFunc, pParam); + break; + case S_PRIFILT: + if(stmt->d.s_prifilt.t_then != NULL) + scriptIterateAllActions(stmt->d.s_prifilt.t_then, + pFunc, pParam); + if(stmt->d.s_prifilt.t_else != NULL) + scriptIterateAllActions(stmt->d.s_prifilt.t_else, + pFunc, pParam); + break; + case S_PROPFILT: + scriptIterateAllActions(stmt->d.s_propfilt.t_then, + pFunc, pParam); + break; + case S_RELOAD_LOOKUP_TABLE: /* this is a NOP */ + break; + default: + dbgprintf("error: unknown stmt type %u during iterateAll\n", + (unsigned) stmt->nodetype); + #ifndef NDEBUG + fprintf(stderr, "error: unknown stmt type %u during iterateAll\n", + (unsigned) stmt->nodetype); + #endif + assert(0); /* abort under debugging */ + break; + } + } +} + +/* driver to iterate over all of this ruleset actions */ +typedef struct iterateAllActions_s { + rsRetVal (*pFunc)(void*, void*); + void *pParam; +} iterateAllActions_t; +/* driver to iterate over all actions */ +DEFFUNC_llExecFunc(doIterateAllActions) +{ + DEFiRet; + ruleset_t* pThis = (ruleset_t*) pData; + iterateAllActions_t *pMyParam = (iterateAllActions_t*) pParam; + scriptIterateAllActions(pThis->root, pMyParam->pFunc, pMyParam->pParam); + RETiRet; +} +/* iterate over ALL actions present in the WHOLE system. + * this is often needed, for example when HUP processing + * must be done or a shutdown is pending. + */ +static rsRetVal +iterateAllActions(rsconf_t *conf, rsRetVal (*pFunc)(void*, void*), void* pParam) +{ + iterateAllActions_t params; + DEFiRet; + assert(pFunc != NULL); + + params.pFunc = pFunc; + params.pParam = pParam; + CHKiRet(llExecFunc(&(conf->rulesets.llRulesets), doIterateAllActions, ¶ms)); + +finalize_it: + RETiRet; +} + +/* driver to iterate over all rulesets */ +DEFFUNC_llExecFunc(doActivateRulesetQueues) +{ + DEFiRet; + ruleset_t* pThis = (ruleset_t*) pData; + dbgprintf("Activating Ruleset Queue[%p] for Ruleset %s\n", + pThis->pQueue, pThis->pszName); + if(pThis->pQueue != NULL) + startMainQueue(runConf, pThis->pQueue); + RETiRet; +} +/* activate all ruleset queues */ +rsRetVal +activateRulesetQueues(void) +{ + llExecFunc(&(runConf->rulesets.llRulesets), doActivateRulesetQueues, NULL); + return RS_RET_OK; +} + + +static rsRetVal +execAct(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti) +{ + DEFiRet; + if(stmt->d.act->bDisabled) { + DBGPRINTF("action %d died, do NOT execute\n", stmt->d.act->iActionNbr); + FINALIZE; + } + + DBGPRINTF("executing action %d\n", stmt->d.act->iActionNbr); + stmt->d.act->submitToActQ(stmt->d.act, pWti, pMsg); + if(iRet != RS_RET_DISCARDMSG) { + /* note: we ignore the error code here, as we do NEVER want to + * stop script execution due to action return code + */ + iRet = RS_RET_OK; + } +finalize_it: + RETiRet; +} + +static rsRetVal ATTR_NONNULL() +execSet(const struct cnfstmt *const stmt, + smsg_t *const pMsg, + wti_t *const __restrict__ pWti) +{ + struct svar result; + DEFiRet; + cnfexprEval(stmt->d.s_set.expr, &result, pMsg, pWti); + msgSetJSONFromVar(pMsg, stmt->d.s_set.varname, &result, stmt->d.s_set.force_reset); + varDelete(&result); + RETiRet; +} + +static rsRetVal +execUnset(struct cnfstmt *stmt, smsg_t *pMsg) +{ + DEFiRet; + msgDelJSON(pMsg, stmt->d.s_unset.varname); + RETiRet; +} + +static rsRetVal +execCallIndirect(struct cnfstmt *const __restrict__ stmt, + smsg_t *pMsg, + wti_t *const __restrict__ pWti) +{ + ruleset_t *pRuleset; + struct svar result; + int bMustFree; /* dummy parameter */ + DEFiRet; + + assert(stmt->d.s_call_ind.expr != NULL); + + cnfexprEval(stmt->d.s_call_ind.expr, &result, pMsg, pWti); + uchar *const rsName = (uchar*) var2CString(&result, &bMustFree); + const rsRetVal localRet = rulesetGetRuleset(runConf, &pRuleset, rsName); + if(localRet != RS_RET_OK) { + /* in that case, we accept that a NOP will "survive" */ + LogError(0, RS_RET_RULESET_NOT_FOUND, "error: CALL_INDIRECT: " + "ruleset '%s' cannot be found, treating as NOP\n", rsName); + FINALIZE; + } + DBGPRINTF("CALL_INDIRECT obtained ruleset ptr %p for ruleset '%s' [hasQueue:%d]\n", + pRuleset, rsName, rulesetHasQueue(pRuleset)); + if(rulesetHasQueue(pRuleset)) { + CHKmalloc(pMsg = MsgDup((smsg_t*) pMsg)); + DBGPRINTF("CALL_INDIRECT: forwarding message to async ruleset %p\n", + pRuleset->pQueue); + MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY); + MsgSetRuleset(pMsg, pRuleset); + /* Note: we intentionally use submitMsg2() here, as we process messages + * that were already run through the rate-limiter. + */ + submitMsg2(pMsg); + } else { + CHKiRet(scriptExec(pRuleset->root, pMsg, pWti)); + } +finalize_it: + varDelete(&result); + free(rsName); + RETiRet; +} + +static rsRetVal +execCall(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti) +{ + DEFiRet; + if(stmt->d.s_call.ruleset == NULL) { + CHKiRet(scriptExec(stmt->d.s_call.stmt, pMsg, pWti)); + } else { + CHKmalloc(pMsg = MsgDup((smsg_t*) pMsg)); + DBGPRINTF("CALL: forwarding message to async ruleset %p\n", + stmt->d.s_call.ruleset->pQueue); + MsgSetFlowControlType(pMsg, eFLOWCTL_NO_DELAY); + MsgSetRuleset(pMsg, stmt->d.s_call.ruleset); + /* Note: we intentionally use submitMsg2() here, as we process messages + * that were already run through the rate-limiter. + */ + submitMsg2(pMsg); + } +finalize_it: + RETiRet; +} + +static rsRetVal +execIf(struct cnfstmt *const stmt, smsg_t *const pMsg, wti_t *const pWti) +{ + sbool bRet; + DEFiRet; + bRet = cnfexprEvalBool(stmt->d.s_if.expr, pMsg, pWti); + DBGPRINTF("if condition result is %d\n", bRet); + if(bRet) { + if(stmt->d.s_if.t_then != NULL) + CHKiRet(scriptExec(stmt->d.s_if.t_then, pMsg, pWti)); + } else { + if(stmt->d.s_if.t_else != NULL) + CHKiRet(scriptExec(stmt->d.s_if.t_else, pMsg, pWti)); + } +finalize_it: + RETiRet; +} + +static rsRetVal +invokeForeachBodyWith(struct cnfstmt *stmt, json_object *o, smsg_t *pMsg, wti_t *pWti) { + struct svar v; + v.datatype = 'J'; + v.d.json = o; + DEFiRet; + CHKiRet(msgSetJSONFromVar(pMsg, (uchar*)stmt->d.s_foreach.iter->var, &v, 1)); + CHKiRet(scriptExec(stmt->d.s_foreach.body, pMsg, pWti)); +finalize_it: + RETiRet; +} + +static rsRetVal +callForeachArray(struct cnfstmt *stmt, json_object *arr, smsg_t *pMsg, wti_t *pWti) { + DEFiRet; + int len = json_object_array_length(arr); + json_object *curr; + for (int i = 0; i < len; i++) { + curr = json_object_array_get_idx(arr, i); + CHKiRet(invokeForeachBodyWith(stmt, curr, pMsg, pWti)); + } +finalize_it: + RETiRet; +} + + +static rsRetVal +callForeachObject(struct cnfstmt *stmt, json_object *arr, smsg_t *pMsg, wti_t *pWti) { + json_object *entry = NULL; + json_object *key = NULL; + const char **keys = NULL; + json_object *curr = NULL; + const char **curr_key; + struct json_object_iterator it; + struct json_object_iterator itEnd; + DEFiRet; + + int len = json_object_object_length(arr); + CHKmalloc(keys = calloc(len, sizeof(char*))); + curr_key = keys; + it = json_object_iter_begin(arr); + itEnd = json_object_iter_end(arr); + while (!json_object_iter_equal(&it, &itEnd)) { + *curr_key = json_object_iter_peek_name(&it); + curr_key++; + json_object_iter_next(&it); + } + CHKmalloc(entry = json_object_new_object()); + for (int i = 0; i < len; i++) { + if (json_object_object_get_ex(arr, keys[i], &curr)) { + CHKmalloc(key = json_object_new_string(keys[i])); + json_object_object_add(entry, "key", key); + key = NULL; + json_object_object_add(entry, "value", json_object_get(curr)); + CHKiRet(invokeForeachBodyWith(stmt, entry, pMsg, pWti)); + } + } +finalize_it: + if (keys != NULL) free(keys); + if (entry != NULL) json_object_put(entry); + /* "fix" Coverity scan issue CID 185393: key currently can NOT be NULL + * However, instead of just removing the + * if (key != NULL) json_object_put(key); + * we put an assertion in its place. + */ + assert(key == NULL); + + RETiRet; +} + +static rsRetVal ATTR_NONNULL() +execForeach(struct cnfstmt *const stmt, smsg_t *const pMsg, wti_t *const pWti) +{ + json_object *arr = NULL; + DEFiRet; + + /* arr can either be an array or an associative-array (obj) */ + arr = cnfexprEvalCollection(stmt->d.s_foreach.iter->collection, pMsg, pWti); + + if (arr == NULL) { + DBGPRINTF("foreach loop skipped, as object to iterate upon is empty\n"); + FINALIZE; + } else if (json_object_is_type(arr, json_type_array) && json_object_array_length(arr) > 0) { + CHKiRet(callForeachArray(stmt, arr, pMsg, pWti)); + } else if (json_object_is_type(arr, json_type_object) && json_object_object_length(arr) > 0) { + CHKiRet(callForeachObject(stmt, arr, pMsg, pWti)); + } else { + DBGPRINTF("foreach loop skipped, as object to iterate upon is empty or is not an array\n"); + FINALIZE; + } + CHKiRet(msgDelJSON(pMsg, (uchar*)stmt->d.s_foreach.iter->var)); + +finalize_it: + if (arr != NULL) json_object_put(arr); + + RETiRet; +} + +static rsRetVal +execPRIFILT(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti) +{ + int bRet; + DEFiRet; + if( (stmt->d.s_prifilt.pmask[pMsg->iFacility] == TABLE_NOPRI) || + ((stmt->d.s_prifilt.pmask[pMsg->iFacility] + & (1<<pMsg->iSeverity)) == 0) ) + bRet = 0; + else + bRet = 1; + + DBGPRINTF("PRIFILT condition result is %d\n", bRet); + if(bRet) { + if(stmt->d.s_prifilt.t_then != NULL) + CHKiRet(scriptExec(stmt->d.s_prifilt.t_then, pMsg, pWti)); + } else { + if(stmt->d.s_prifilt.t_else != NULL) + CHKiRet(scriptExec(stmt->d.s_prifilt.t_else, pMsg, pWti)); + } +finalize_it: + RETiRet; +} + + +/* helper to execPROPFILT(), as the evaluation itself is quite lengthy */ +static int +evalPROPFILT(struct cnfstmt *stmt, smsg_t *pMsg) +{ + unsigned short pbMustBeFreed; + uchar *pszPropVal; + int bRet = 0; + rs_size_t propLen; + + if(stmt->d.s_propfilt.prop.id == PROP_INVALID) + goto done; + + pszPropVal = MsgGetProp(pMsg, NULL, &stmt->d.s_propfilt.prop, + &propLen, &pbMustBeFreed, NULL); + + /* Now do the compares (short list currently ;)) */ + switch(stmt->d.s_propfilt.operation ) { + case FIOP_CONTAINS: + if(rsCStrLocateInSzStr(stmt->d.s_propfilt.pCSCompValue, (uchar*) pszPropVal) != -1) + bRet = 1; + break; + case FIOP_ISEMPTY: + if(propLen == 0) + bRet = 1; /* process message! */ + break; + case FIOP_ISEQUAL: + if(rsCStrSzStrCmp(stmt->d.s_propfilt.pCSCompValue, + pszPropVal, propLen) == 0) + bRet = 1; /* process message! */ + break; + case FIOP_STARTSWITH: + if(rsCStrSzStrStartsWithCStr(stmt->d.s_propfilt.pCSCompValue, + pszPropVal, propLen) == 0) + bRet = 1; /* process message! */ + break; + case FIOP_REGEX: + if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue, + (unsigned char*) pszPropVal, 0, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK) + bRet = 1; + break; + case FIOP_EREREGEX: + if(rsCStrSzStrMatchRegex(stmt->d.s_propfilt.pCSCompValue, + (unsigned char*) pszPropVal, 1, &stmt->d.s_propfilt.regex_cache) == RS_RET_OK) + bRet = 1; + break; + case FIOP_NOP: + default: + /* here, it handles NOP (for performance reasons) */ + assert(stmt->d.s_propfilt.operation == FIOP_NOP); + bRet = 1; /* as good as any other default ;) */ + break; + } + + /* now check if the value must be negated */ + if(stmt->d.s_propfilt.isNegated) + bRet = (bRet == 1) ? 0 : 1; + + if(Debug) { + if(stmt->d.s_propfilt.prop.id == PROP_CEE) { + DBGPRINTF("Filter: check for CEE property '%s' (value '%s') ", + stmt->d.s_propfilt.prop.name, pszPropVal); + } else if(stmt->d.s_propfilt.prop.id == PROP_LOCAL_VAR) { + DBGPRINTF("Filter: check for local var '%s' (value '%s') ", + stmt->d.s_propfilt.prop.name, pszPropVal); + } else if(stmt->d.s_propfilt.prop.id == PROP_GLOBAL_VAR) { + DBGPRINTF("Filter: check for global var '%s' (value '%s') ", + stmt->d.s_propfilt.prop.name, pszPropVal); + } else { + DBGPRINTF("Filter: check for property '%s' (value '%s') ", + propIDToName(stmt->d.s_propfilt.prop.id), pszPropVal); + } + if(stmt->d.s_propfilt.isNegated) + DBGPRINTF("NOT "); + if(stmt->d.s_propfilt.operation == FIOP_ISEMPTY) { + DBGPRINTF("%s : %s\n", + getFIOPName(stmt->d.s_propfilt.operation), + bRet ? "TRUE" : "FALSE"); + } else { + DBGPRINTF("%s '%s': %s\n", + getFIOPName(stmt->d.s_propfilt.operation), + rsCStrGetSzStrNoNULL(stmt->d.s_propfilt.pCSCompValue), + bRet ? "TRUE" : "FALSE"); + } + } + + /* cleanup */ + if(pbMustBeFreed) + free(pszPropVal); +done: + return bRet; +} + +static rsRetVal +execPROPFILT(struct cnfstmt *stmt, smsg_t *pMsg, wti_t *pWti) +{ + sbool bRet; + DEFiRet; + + bRet = evalPROPFILT(stmt, pMsg); + DBGPRINTF("PROPFILT condition result is %d\n", bRet); + if(bRet) + CHKiRet(scriptExec(stmt->d.s_propfilt.t_then, pMsg, pWti)); +finalize_it: + RETiRet; +} + +static rsRetVal ATTR_NONNULL() +execReloadLookupTable(struct cnfstmt *stmt) +{ + assert(stmt != NULL); + lookup_ref_t *t; + DEFiRet; + t = stmt->d.s_reload_lookup_table.table; + if (t == NULL) { + ABORT_FINALIZE(RS_RET_NONE); + } + + iRet = lookupReload(t, stmt->d.s_reload_lookup_table.stub_value); + /* Note that reload dispatched above is performed asynchronously, + on a different thread. So rsRetVal it returns means it was triggered + successfully, and not that it was reloaded successfully. */ + +finalize_it: + RETiRet; +} + +/* The rainerscript execution engine. It is debatable if that would be better + * contained in grammer/rainerscript.c, HOWEVER, that file focusses primarily + * on the parsing and object creation part. So as an actual executor, it is + * better suited here. + * rgerhards, 2012-09-04 + */ +static rsRetVal ATTR_NONNULL(2, 3) +scriptExec(struct cnfstmt *const root, smsg_t *const pMsg, wti_t *const pWti) +{ + struct cnfstmt *stmt; + DEFiRet; + + for(stmt = root ; stmt != NULL ; stmt = stmt->next) { + if(*pWti->pbShutdownImmediate) { + DBGPRINTF("scriptExec: ShutdownImmediate set, " + "force terminating\n"); + ABORT_FINALIZE(RS_RET_FORCE_TERM); + } + if(Debug) { + cnfstmtPrintOnly(stmt, 2, 0); + } + switch(stmt->nodetype) { + case S_NOP: + break; + case S_STOP: + ABORT_FINALIZE(RS_RET_DISCARDMSG); + break; + case S_ACT: + CHKiRet(execAct(stmt, pMsg, pWti)); + break; + case S_SET: + CHKiRet(execSet(stmt, pMsg, pWti)); + break; + case S_UNSET: + CHKiRet(execUnset(stmt, pMsg)); + break; + case S_CALL: + CHKiRet(execCall(stmt, pMsg, pWti)); + break; + case S_CALL_INDIRECT: + CHKiRet(execCallIndirect(stmt, pMsg, pWti)); + break; + case S_IF: + CHKiRet(execIf(stmt, pMsg, pWti)); + break; + case S_FOREACH: + CHKiRet(execForeach(stmt, pMsg, pWti)); + break; + case S_PRIFILT: + CHKiRet(execPRIFILT(stmt, pMsg, pWti)); + break; + case S_PROPFILT: + CHKiRet(execPROPFILT(stmt, pMsg, pWti)); + break; + case S_RELOAD_LOOKUP_TABLE: + CHKiRet(execReloadLookupTable(stmt)); + break; + default: + dbgprintf("error: unknown stmt type %u during exec\n", + (unsigned) stmt->nodetype); + break; + } + } +finalize_it: + RETiRet; +} + + +/* Process (consume) a batch of messages. Calls the actions configured. + * This is called by MAIN queues. + */ +static rsRetVal +processBatch(batch_t *pBatch, wti_t *pWti) +{ + int i; + smsg_t *pMsg; + ruleset_t *pRuleset; + rsRetVal localRet; + DEFiRet; + + DBGPRINTF("processBATCH: batch of %d elements must be processed\n", pBatch->nElem); + + wtiResetExecState(pWti, pBatch); + + /* execution phase */ + for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pWti->pbShutdownImmediate) ; ++i) { + pMsg = pBatch->pElem[i].pMsg; + DBGPRINTF("processBATCH: next msg %d: %.128s\n", i, pMsg->pszRawMsg); + pRuleset = (pMsg->pRuleset == NULL) ? runConf->rulesets.pDflt : pMsg->pRuleset; + localRet = scriptExec(pRuleset->root, pMsg, pWti); + /* the most important case here is that processing may be aborted + * due to pbShutdownImmediate, in which case we MUST NOT flag this + * message as committed. If we would do so, the message would + * potentially be lost. + */ + if(localRet == RS_RET_OK) + batchSetElemState(pBatch, i, BATCH_STATE_COMM); + else if(localRet == RS_RET_SUSPENDED) + --i; + } + + /* commit phase */ + DBGPRINTF("END batch execution phase, entering to commit phase " + "[processed %d of %d messages]\n", i, batchNumMsgs(pBatch)); + actionCommitAllDirect(pWti); + + DBGPRINTF("processBATCH: batch of %d elements has been processed\n", pBatch->nElem); + RETiRet; +} + + +/* return the ruleset-assigned parser list. NULL means use the default + * parser list. + * rgerhards, 2009-11-04 + */ +static parserList_t* +GetParserList(rsconf_t *conf, smsg_t *pMsg) +{ + return (pMsg->pRuleset == NULL) ? conf->rulesets.pDflt->pParserLst : pMsg->pRuleset->pParserLst; +} + + +/* Add a script block to the current ruleset */ +static void ATTR_NONNULL(1) +addScript(ruleset_t *const pThis, struct cnfstmt *const script) +{ + if(script == NULL) /* happens for include() */ + return; + if(pThis->last == NULL) + pThis->root = pThis->last = script; + else { + pThis->last->next = script; + pThis->last = script; + } +} + + +/* set name for ruleset */ +static rsRetVal rulesetSetName(ruleset_t *pThis, uchar *pszName) +{ + DEFiRet; + free(pThis->pszName); + CHKmalloc(pThis->pszName = ustrdup(pszName)); + +finalize_it: + RETiRet; +} + + +/* get current ruleset + * We use a non-standard calling interface, as nothing can go wrong and it + * is really much more natural to return the pointer directly. + */ +static ruleset_t* +GetCurrent(rsconf_t *conf) +{ + return conf->rulesets.pCurr; +} + + +/* get main queue associated with ruleset. If no ruleset-specifc main queue + * is set, the primary main message queue is returned. + * We use a non-standard calling interface, as nothing can go wrong and it + * is really much more natural to return the pointer directly. + */ +static qqueue_t* +GetRulesetQueue(ruleset_t *pThis) +{ + ISOBJ_TYPE_assert(pThis, ruleset); + return (pThis->pQueue == NULL) ? runConf->pMsgQueue : pThis->pQueue; +} + + +/* Find the ruleset with the given name and return a pointer to its object. + */ +rsRetVal +rulesetGetRuleset(rsconf_t *conf, ruleset_t **ppRuleset, uchar *pszName) +{ + DEFiRet; + assert(ppRuleset != NULL); + assert(pszName != NULL); + + CHKiRet(llFind(&(conf->rulesets.llRulesets), pszName, (void*) ppRuleset)); + +finalize_it: + RETiRet; +} + + +/* Set a new default rule set. If the default can not be found, no change happens. + */ +static rsRetVal +SetDefaultRuleset(rsconf_t *conf, uchar *pszName) +{ + ruleset_t *pRuleset; + DEFiRet; + assert(pszName != NULL); + + CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName)); + conf->rulesets.pDflt = pRuleset; + DBGPRINTF("default rule set changed to %p: '%s'\n", pRuleset, pszName); + +finalize_it: + RETiRet; +} + + +/* Set a new current rule set. If the ruleset can not be found, no change happens */ +static rsRetVal +SetCurrRuleset(rsconf_t *conf, uchar *pszName) +{ + ruleset_t *pRuleset; + DEFiRet; + assert(pszName != NULL); + + CHKiRet(rulesetGetRuleset(conf, &pRuleset, pszName)); + conf->rulesets.pCurr = pRuleset; + DBGPRINTF("current rule set changed to %p: '%s'\n", pRuleset, pszName); + +finalize_it: + RETiRet; +} + + +/* Standard-Constructor + */ +BEGINobjConstruct(ruleset) /* be sure to specify the object type also in END macro! */ + pThis->root = NULL; + pThis->last = NULL; +ENDobjConstruct(ruleset) + + +/* ConstructionFinalizer + * This also adds the rule set to the list of all known rulesets. + */ +static rsRetVal +rulesetConstructFinalize(rsconf_t *conf, ruleset_t *pThis) +{ + uchar *keyName; + DEFiRet; + ISOBJ_TYPE_assert(pThis, ruleset); + + /* we must duplicate our name, as the key destructer would also + * free it, resulting in a double-free. It's also cleaner to have + * two separate copies. + */ + CHKmalloc(keyName = ustrdup(pThis->pszName)); + CHKiRet(llAppend(&(conf->rulesets.llRulesets), keyName, pThis)); + + /* and also the default, if so far none has been set */ + if(conf->rulesets.pDflt == NULL) + conf->rulesets.pDflt = pThis; + +finalize_it: + RETiRet; +} + + +/* destructor for the ruleset object */ +BEGINobjDestruct(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDestruct(ruleset) + DBGPRINTF("destructing ruleset %p, name %p\n", pThis, pThis->pszName); + if(pThis->pQueue != NULL) { + qqueueDestruct(&pThis->pQueue); + } + if(pThis->pParserLst != NULL) { + parser.DestructParserList(&pThis->pParserLst); + } + free(pThis->pszName); +ENDobjDestruct(ruleset) + + +/* helper for Destructor, shut down queue workers */ +DEFFUNC_llExecFunc(doShutdownQueueWorkers) +{ + DEFiRet; + ruleset_t *const pThis = (ruleset_t*) pData; + DBGPRINTF("shutting down queue workers for ruleset %p, name %s, queue %p\n", + pThis, pThis->pszName, pThis->pQueue); + ISOBJ_TYPE_assert(pThis, ruleset); + if(pThis->pQueue != NULL) { + qqueueShutdownWorkers(pThis->pQueue); + } + RETiRet; +} +/* helper for Destructor, shut down actions (cnfstmt's in general) */ +DEFFUNC_llExecFunc(doDestructCnfStmt) +{ + DEFiRet; + ruleset_t *const pThis = (ruleset_t*) pData; + DBGPRINTF("shutting down actions and conf stmts for ruleset %p, name %s\n", + pThis, pThis->pszName); + ISOBJ_TYPE_assert(pThis, ruleset); + cnfstmtDestructLst(pThis->root); + RETiRet; +} +/* destruct ALL rule sets that reside in the system. This must + * be callable before unloading this module as the module may + * not be unloaded before unload of the actions is required. This is + * kind of a left-over from previous logic and may be optimized one + * everything runs stable again. -- rgerhards, 2009-06-10 + */ +static rsRetVal +destructAllActions(rsconf_t *conf) +{ + DEFiRet; + + DBGPRINTF("rulesetDestructAllActions\n"); + /* we first need to stop all queue workers, else we + * may run into trouble with "call" statements calling + * into then-destroyed rulesets. + * see: https://github.com/rsyslog/rsyslog/issues/1122 + */ + DBGPRINTF("destructAllActions: queue shutdown\n"); + llExecFunc(&(conf->rulesets.llRulesets), doShutdownQueueWorkers, NULL); + DBGPRINTF("destructAllActions: action and conf stmt shutdown\n"); + llExecFunc(&(conf->rulesets.llRulesets), doDestructCnfStmt, NULL); + + CHKiRet(llDestroy(&(conf->rulesets.llRulesets))); + CHKiRet(llInit(&(conf->rulesets.llRulesets), rulesetDestructForLinkedList, + rulesetKeyDestruct, strcasecmp)); + conf->rulesets.pDflt = NULL; + +finalize_it: + RETiRet; +} + +/* this is a special destructor for the linkedList class. LinkedList does NOT + * provide a pointer to the pointer, but rather the raw pointer itself. So we + * must map this, otherwise the destructor will abort. + */ +rsRetVal +rulesetDestructForLinkedList(void *pData) +{ + ruleset_t *pThis = (ruleset_t*) pData; + return rulesetDestruct(&pThis); +} + +/* debugprint for the ruleset object */ +BEGINobjDebugPrint(ruleset) /* be sure to specify the object type also in END and CODESTART macros! */ +CODESTARTobjDebugPrint(ruleset) + dbgoprint((obj_t*) pThis, "rsyslog ruleset %s:\n", pThis->pszName); + cnfstmtPrint(pThis->root, 0); + dbgoprint((obj_t*) pThis, "ruleset %s assigned parser list:\n", pThis->pszName); + printParserList(pThis->pParserLst); +ENDobjDebugPrint(ruleset) + + +/* helper for debugPrintAll(), prints a single ruleset */ +DEFFUNC_llExecFunc(doDebugPrintAll) +{ + return rulesetDebugPrint((ruleset_t*) pData); +} +/* debug print all rulesets + */ +static rsRetVal +debugPrintAll(rsconf_t *conf) +{ + DEFiRet; + dbgprintf("All Rulesets:\n"); + llExecFunc(&(conf->rulesets.llRulesets), doDebugPrintAll, NULL); + dbgprintf("End of Rulesets.\n"); + RETiRet; +} + +struct cnfstmt * removeNOPs(struct cnfstmt *root); +static void +rulesetOptimize(ruleset_t *pRuleset) +{ + if(Debug) { + dbgprintf("ruleset '%s' before optimization:\n", + pRuleset->pszName); + rulesetDebugPrint((ruleset_t*) pRuleset); + } + pRuleset->root = cnfstmtOptimize(pRuleset->root); + if(Debug) { + dbgprintf("ruleset '%s' after optimization:\n", + pRuleset->pszName); + rulesetDebugPrint((ruleset_t*) pRuleset); + } +} + +/* helper for rulsetOptimizeAll(), optimizes a single ruleset */ +DEFFUNC_llExecFunc(doRulesetOptimizeAll) +{ + rulesetOptimize((ruleset_t*) pData); + return RS_RET_OK; +} +/* optimize all rulesets + */ +rsRetVal +rulesetOptimizeAll(rsconf_t *conf) +{ + DEFiRet; + dbgprintf("begin ruleset optimization phase\n"); + llExecFunc(&(conf->rulesets.llRulesets), doRulesetOptimizeAll, NULL); + dbgprintf("ruleset optimization phase finished.\n"); + RETiRet; +} + + +/* Create a ruleset-specific "main" queue for this ruleset. If one is already + * defined, an error message is emitted but nothing else is done. + * Note: we use the main message queue parameters for queue creation and access + * syslogd.c directly to obtain these. This is far from being perfect, but + * considered acceptable for the time being. + * rgerhards, 2009-10-27 + */ +static rsRetVal +doRulesetCreateQueue(rsconf_t *conf, int *pNewVal) +{ + uchar *rsname; + DEFiRet; + + if(conf->rulesets.pCurr == NULL) { + LogError(0, RS_RET_NO_CURR_RULESET, "error: currently no specific ruleset specified, thus a " + "queue can not be added to it"); + ABORT_FINALIZE(RS_RET_NO_CURR_RULESET); + } + + if(conf->rulesets.pCurr->pQueue != NULL) { + LogError(0, RS_RET_RULES_QUEUE_EXISTS, "error: ruleset already has a main queue, can not " + "add another one"); + ABORT_FINALIZE(RS_RET_RULES_QUEUE_EXISTS); + } + + if(pNewVal == 0) + FINALIZE; /* if it is turned off, we do not need to change anything ;) */ + + rsname = (conf->rulesets.pCurr->pszName == NULL) ? (uchar*) "[ruleset]" : conf->rulesets.pCurr->pszName; + DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s'\n", rsname); + CHKiRet(createMainQueue(&conf->rulesets.pCurr->pQueue, rsname, NULL)); + +finalize_it: + RETiRet; +} + +static rsRetVal +rulesetCreateQueue(void __attribute__((unused)) *pVal, int *pNewVal) +{ + return doRulesetCreateQueue(ourConf, pNewVal); +} + +/* Add a ruleset specific parser to the ruleset. Note that adding the first + * parser automatically disables the default parsers. If they are needed as well, + * the must be added via explicit config directives. + * Note: this is the only spot in the code that requires the parser object. In order + * to solve some class init bootstrap sequence problems, we get the object handle here + * instead of during module initialization. Note that objUse() is capable of being + * called multiple times. + * rgerhards, 2009-11-04 + */ +static rsRetVal +doRulesetAddParser(ruleset_t *pRuleset, uchar *pName) +{ + parser_t *pParser; + DEFiRet; + + CHKiRet(objUse(parser, CORE_COMPONENT)); + iRet = parser.FindParser(loadConf->parsers.pParsLstRoot, &pParser, pName); + if(iRet == RS_RET_PARSER_NOT_FOUND) { + LogError(0, RS_RET_PARSER_NOT_FOUND, "error: parser '%s' unknown at this time " + "(maybe defined too late in rsyslog.conf?)", pName); + ABORT_FINALIZE(RS_RET_NO_CURR_RULESET); + } else if(iRet != RS_RET_OK) { + LogError(0, iRet, "error trying to find parser '%s'\n", pName); + FINALIZE; + } + + CHKiRet(parser.AddParserToList(&pRuleset->pParserLst, pParser)); + + DBGPRINTF("added parser '%s' to ruleset '%s'\n", pName, pRuleset->pszName); + +finalize_it: + free(pName); /* no longer needed */ + + RETiRet; +} + +static rsRetVal +rulesetAddParser(void __attribute__((unused)) *pVal, uchar *pName) +{ + return doRulesetAddParser(loadConf->rulesets.pCurr, pName); +} + + +/* Process ruleset() objects */ +rsRetVal +rulesetProcessCnf(struct cnfobj *o) +{ + struct cnfparamvals *pvals; + rsRetVal localRet; + uchar *rsName = NULL; + uchar *parserName; + int nameIdx, parserIdx; + ruleset_t *pRuleset; + struct cnfarray *ar; + int i; + int qtype; + uchar *rsname; + DEFiRet; + + pvals = nvlstGetParams(o->nvlst, &rspblk, NULL); + if(pvals == NULL) { + ABORT_FINALIZE(RS_RET_CONFIG_ERROR); + } + DBGPRINTF("ruleset param blk after rulesetProcessCnf:\n"); + cnfparamsPrint(&rspblk, pvals); + nameIdx = cnfparamGetIdx(&rspblk, "name"); + rsName = (uchar*)es_str2cstr(pvals[nameIdx].val.d.estr, NULL); + + localRet = rulesetGetRuleset(loadConf, &pRuleset, rsName); + if(localRet == RS_RET_OK) { + LogError(0, RS_RET_RULESET_EXISTS, + "error: ruleset '%s' specified more than once", + rsName); + cnfstmtDestructLst(o->script); + ABORT_FINALIZE(RS_RET_RULESET_EXISTS); + } else if(localRet != RS_RET_NOT_FOUND) { + ABORT_FINALIZE(localRet); + } + + CHKiRet(rulesetConstruct(&pRuleset)); + if((localRet = rulesetSetName(pRuleset, rsName)) != RS_RET_OK) { + rulesetDestruct(&pRuleset); + ABORT_FINALIZE(localRet); + } + if((localRet = rulesetConstructFinalize(loadConf, pRuleset)) != RS_RET_OK) { + rulesetDestruct(&pRuleset); + ABORT_FINALIZE(localRet); + } + addScript(pRuleset, o->script); + + /* we have only two params, so we do NOT do the usual param loop */ + parserIdx = cnfparamGetIdx(&rspblk, "parser"); + if(parserIdx != -1 && pvals[parserIdx].bUsed) { + ar = pvals[parserIdx].val.d.ar; + for(i = 0 ; i < ar->nmemb ; ++i) { + parserName = (uchar*)es_str2cstr(ar->arr[i], NULL); + doRulesetAddParser(pRuleset, parserName); + /* note parserName is freed in doRulesetAddParser()! */ + } + } + + /* pick up ruleset queue parameters */ + if(queueCnfParamsSet(o->nvlst)) { + if(pRuleset->pszName == NULL) { + rsname = (uchar*) "[ruleset]"; + qtype = pRuleset->pQueue->qType; + } else { + rsname = pRuleset->pszName; + qtype = 3; + } + DBGPRINTF("adding a ruleset-specific \"main\" queue for ruleset '%s', mode %d\n", + rsname, qtype); + CHKiRet(createMainQueue(&pRuleset->pQueue, rsname, o->nvlst)); + } + +finalize_it: + free(rsName); + cnfparamvalsDestruct(pvals, &rspblk); + RETiRet; +} + + +/* queryInterface function + * rgerhards, 2008-02-21 + */ +BEGINobjQueryInterface(ruleset) +CODESTARTobjQueryInterface(ruleset) + if(pIf->ifVersion != rulesetCURR_IF_VERSION) { /* check for current version, increment on each change */ + ABORT_FINALIZE(RS_RET_INTERFACE_NOT_SUPPORTED); + } + + /* ok, we have the right interface, so let's fill it + * Please note that we may also do some backwards-compatibility + * work here (if we can support an older interface version - that, + * of course, also affects the "if" above). + */ + pIf->Construct = rulesetConstruct; + pIf->ConstructFinalize = rulesetConstructFinalize; + pIf->Destruct = rulesetDestruct; + pIf->DebugPrint = rulesetDebugPrint; + + pIf->IterateAllActions = iterateAllActions; + pIf->DestructAllActions = destructAllActions; + pIf->AddScript = addScript; + pIf->ProcessBatch = processBatch; + pIf->SetName = rulesetSetName; + pIf->DebugPrintAll = debugPrintAll; + pIf->GetCurrent = GetCurrent; + pIf->GetRuleset = rulesetGetRuleset; + pIf->SetDefaultRuleset = SetDefaultRuleset; + pIf->SetCurrRuleset = SetCurrRuleset; + pIf->GetRulesetQueue = GetRulesetQueue; + pIf->GetParserList = GetParserList; +finalize_it: +ENDobjQueryInterface(ruleset) + + +/* Exit the ruleset class. + * rgerhards, 2009-04-06 + */ +BEGINObjClassExit(ruleset, OBJ_IS_CORE_MODULE) /* class, version */ + objRelease(parser, CORE_COMPONENT); +ENDObjClassExit(ruleset) + + +/* Initialize the ruleset class. Must be called as the very first method + * before anything else is called inside this class. + * rgerhards, 2008-02-19 + */ +BEGINObjClassInit(ruleset, 1, OBJ_IS_CORE_MODULE) /* class, version */ + /* request objects we use */ + + /* set our own handlers */ + OBJSetMethodHandler(objMethod_DEBUGPRINT, rulesetDebugPrint); + OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, rulesetConstructFinalize); + + /* config file handlers */ + CHKiRet(regCfSysLineHdlr((uchar *)"rulesetparser", 0, eCmdHdlrGetWord, rulesetAddParser, NULL, NULL)); + CHKiRet(regCfSysLineHdlr((uchar *)"rulesetcreatemainqueue", 0, eCmdHdlrBinary, rulesetCreateQueue, + NULL, NULL)); +ENDObjClassInit(ruleset) |