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