/* rule.c - rsyslog's rule object * * See file comment in rule.c for the overall structure of rule processing. * * Module begun 2009-06-10 by Rainer Gerhards * * Copyright 2009 Rainer Gerhards and Adiscon GmbH. * * This file is part of the rsyslog runtime library. * * The rsyslog runtime library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The rsyslog runtime library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the rsyslog runtime library. If not, see . * * A copy of the GPL can be found in the file "COPYING" in this distribution. * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution. */ #include "config.h" #include #include #include #include #include "rsyslog.h" #include "obj.h" #include "action.h" #include "rule.h" #include "errmsg.h" #include "vm.h" #include "var.h" #include "srUtils.h" #include "batch.h" #include "unicode-helper.h" /* static data */ DEFobjStaticHelpers DEFobjCurrIf(errmsg) DEFobjCurrIf(expr) DEFobjCurrIf(var) DEFobjCurrIf(vm) /* support for simple textual representation of FIOP names * rgerhards, 2005-09-27 */ static char* getFIOPName(unsigned iFIOP) { char *pRet; switch(iFIOP) { case FIOP_CONTAINS: pRet = "contains"; break; case FIOP_ISEQUAL: pRet = "isequal"; break; case FIOP_STARTSWITH: pRet = "startswith"; break; case FIOP_REGEX: pRet = "regex"; break; default: pRet = "NOP"; break; } return pRet; } /* iterate over all actions, this is often needed, for example when HUP processing * must be done or a shutdown is pending. */ static rsRetVal iterateAllActions(rule_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam) { return llExecFunc(&pThis->llActList, pFunc, pParam); } /* helper to processMsg(), used to call the configured actions. It is * executed from within llExecFunc() of the action list. * rgerhards, 2007-08-02 */ DEFFUNC_llExecFunc(processBatchDoActions) { DEFiRet; rsRetVal iRetMod; /* return value of module - we do not always pass that back */ action_t *pAction = (action_t*) pData; batch_t *pBatch = (batch_t*) pParam; DBGPRINTF("Processing next action\n"); iRetMod = pAction->submitToActQ(pAction, pBatch); RETiRet; } /* This functions looks at the given message and checks if it matches the * provided filter condition. */ static rsRetVal shouldProcessThisMessage(rule_t *pRule, msg_t *pMsg, sbool *bProcessMsg) { DEFiRet; unsigned short pbMustBeFreed; uchar *pszPropVal; int bRet = 0; size_t propLen; vm_t *pVM = NULL; var_t *pResult = NULL; ISOBJ_TYPE_assert(pRule, rule); assert(pMsg != NULL); /* we first have a look at the global, BSD-style block filters (for tag * and host). Only if they match, we evaluate the actual filter. * rgerhards, 2005-10-18 */ if(pRule->eHostnameCmpMode == HN_NO_COMP) { /* EMPTY BY INTENSION - we check this value first, because * it is the one most often used, so this saves us time! */ } else if(pRule->eHostnameCmpMode == HN_COMP_MATCH) { if(rsCStrSzStrCmp(pRule->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) { /* not equal, so we are already done... */ dbgprintf("hostname filter '+%s' does not match '%s'\n", rsCStrGetSzStrNoNULL(pRule->pCSHostnameComp), getHOSTNAME(pMsg)); FINALIZE; } } else { /* must be -hostname */ if(!rsCStrSzStrCmp(pRule->pCSHostnameComp, (uchar*) getHOSTNAME(pMsg), getHOSTNAMELen(pMsg))) { /* not equal, so we are already done... */ dbgprintf("hostname filter '-%s' does not match '%s'\n", rsCStrGetSzStrNoNULL(pRule->pCSHostnameComp), getHOSTNAME(pMsg)); FINALIZE; } } if(pRule->pCSProgNameComp != NULL) { int bInv = 0, bEqv = 0, offset = 0; if(*(rsCStrGetSzStrNoNULL(pRule->pCSProgNameComp)) == '-') { if(*(rsCStrGetSzStrNoNULL(pRule->pCSProgNameComp) + 1) == '-') offset = 1; else { bInv = 1; offset = 1; } } if(!rsCStrOffsetSzStrCmp(pRule->pCSProgNameComp, offset, (uchar*) getProgramName(pMsg, LOCK_MUTEX), getProgramNameLen(pMsg, LOCK_MUTEX))) bEqv = 1; if((!bEqv && !bInv) || (bEqv && bInv)) { /* not equal or inverted selection, so we are already done... */ DBGPRINTF("programname filter '%s' does not match '%s'\n", rsCStrGetSzStrNoNULL(pRule->pCSProgNameComp), getProgramName(pMsg, LOCK_MUTEX)); FINALIZE; } } /* done with the BSD-style block filters */ if(pRule->f_filter_type == FILTER_PRI) { /* skip messages that are incorrect priority */ dbgprintf("testing filter, f_pmask %d\n", pRule->f_filterData.f_pmask[pMsg->iFacility]); if ( (pRule->f_filterData.f_pmask[pMsg->iFacility] == TABLE_NOPRI) || \ ((pRule->f_filterData.f_pmask[pMsg->iFacility] & (1<iSeverity)) == 0) ) bRet = 0; else bRet = 1; } else if(pRule->f_filter_type == FILTER_EXPR) { CHKiRet(vm.Construct(&pVM)); CHKiRet(vm.ConstructFinalize(pVM)); CHKiRet(vm.SetMsg(pVM, pMsg)); CHKiRet(vm.ExecProg(pVM, pRule->f_filterData.f_expr->pVmprg)); CHKiRet(vm.PopBoolFromStack(pVM, &pResult)); dbgprintf("result of expression evaluation: %lld\n", pResult->val.num); /* VM is destructed on function exit */ bRet = (pResult->val.num) ? 1 : 0; } else { assert(pRule->f_filter_type == FILTER_PROP); /* assert() just in case... */ pszPropVal = MsgGetProp(pMsg, NULL, pRule->f_filterData.prop.propID, &propLen, &pbMustBeFreed); /* Now do the compares (short list currently ;)) */ switch(pRule->f_filterData.prop.operation ) { case FIOP_CONTAINS: if(rsCStrLocateInSzStr(pRule->f_filterData.prop.pCSCompValue, (uchar*) pszPropVal) != -1) bRet = 1; break; case FIOP_ISEQUAL: if(rsCStrSzStrCmp(pRule->f_filterData.prop.pCSCompValue, pszPropVal, ustrlen(pszPropVal)) == 0) bRet = 1; /* process message! */ break; case FIOP_STARTSWITH: if(rsCStrSzStrStartsWithCStr(pRule->f_filterData.prop.pCSCompValue, pszPropVal, ustrlen(pszPropVal)) == 0) bRet = 1; /* process message! */ break; case FIOP_REGEX: if(rsCStrSzStrMatchRegex(pRule->f_filterData.prop.pCSCompValue, (unsigned char*) pszPropVal, 0, &pRule->f_filterData.prop.regex_cache) == RS_RET_OK) bRet = 1; break; case FIOP_EREREGEX: if(rsCStrSzStrMatchRegex(pRule->f_filterData.prop.pCSCompValue, (unsigned char*) pszPropVal, 1, &pRule->f_filterData.prop.regex_cache) == RS_RET_OK) bRet = 1; break; default: /* here, it handles NOP (for performance reasons) */ assert(pRule->f_filterData.prop.operation == FIOP_NOP); bRet = 1; /* as good as any other default ;) */ break; } /* now check if the value must be negated */ if(pRule->f_filterData.prop.isNegated) bRet = (bRet == 1) ? 0 : 1; if(Debug) { dbgprintf("Filter: check for property '%s' (value '%s') ", propIDToName(pRule->f_filterData.prop.propID), pszPropVal); if(pRule->f_filterData.prop.isNegated) dbgprintf("NOT "); dbgprintf("%s '%s': %s\n", getFIOPName(pRule->f_filterData.prop.operation), rsCStrGetSzStrNoNULL(pRule->f_filterData.prop.pCSCompValue), bRet ? "TRUE" : "FALSE"); } /* cleanup */ if(pbMustBeFreed) free(pszPropVal); } finalize_it: /* destruct in any case, not just on error, but it makes error handling much easier */ if(pVM != NULL) vm.Destruct(&pVM); if(pResult != NULL) var.Destruct(&pResult); *bProcessMsg = bRet; RETiRet; } /* Process (consume) a batch of messages. Calls the actions configured. * rgerhards, 2005-10-13 */ static rsRetVal processBatch(rule_t *pThis, batch_t *pBatch) { int i; DEFiRet; ISOBJ_TYPE_assert(pThis, rule); assert(pBatch != NULL); /* first check the filters and reset status variables */ for(i = 0 ; i < batchNumMsgs(pBatch) && !*(pBatch->pbShutdownImmediate) ; ++i) { CHKiRet(shouldProcessThisMessage(pThis, (msg_t*)(pBatch->pElem[i].pUsrp), &(pBatch->pElem[i].bFilterOK))); // TODO: really abort on error? 2010-06-10 if(pBatch->pElem[i].bFilterOK) { /* re-init only when actually needed (cache write cost!) */ pBatch->pElem[i].bPrevWasSuspended = 0; } } CHKiRet(llExecFunc(&pThis->llActList, processBatchDoActions, pBatch)); finalize_it: RETiRet; } /* Standard-Constructor */ BEGINobjConstruct(rule) /* be sure to specify the object type also in END macro! */ ENDobjConstruct(rule) /* ConstructionFinalizer * rgerhards, 2008-01-09 */ static rsRetVal ruleConstructFinalize(rule_t *pThis) { DEFiRet; ISOBJ_TYPE_assert(pThis, rule); /* note: actionDestruct is from action.c API! */ CHKiRet(llInit(&pThis->llActList, actionDestruct, NULL, NULL)); finalize_it: RETiRet; } /* destructor for the rule object */ BEGINobjDestruct(rule) /* be sure to specify the object type also in END and CODESTART macros! */ CODESTARTobjDestruct(rule) if(pThis->pCSHostnameComp != NULL) rsCStrDestruct(&pThis->pCSHostnameComp); if(pThis->pCSProgNameComp != NULL) rsCStrDestruct(&pThis->pCSProgNameComp); if(pThis->f_filter_type == FILTER_PROP) { if(pThis->f_filterData.prop.pCSCompValue != NULL) rsCStrDestruct(&pThis->f_filterData.prop.pCSCompValue); if(pThis->f_filterData.prop.regex_cache != NULL) rsCStrRegexDestruct(&pThis->f_filterData.prop.regex_cache); } else if(pThis->f_filter_type == FILTER_EXPR) { if(pThis->f_filterData.f_expr != NULL) expr.Destruct(&pThis->f_filterData.f_expr); } llDestroy(&pThis->llActList); ENDobjDestruct(rule) /* set the associated ruleset */ static rsRetVal setAssRuleset(rule_t *pThis, ruleset_t *pRuleset) { DEFiRet; ISOBJ_TYPE_assert(pThis, rule); ISOBJ_TYPE_assert(pRuleset, ruleset); pThis->pRuleset = pRuleset; RETiRet; } /* get the associated ruleset (may be NULL if not set!) */ static ruleset_t* getAssRuleset(rule_t *pThis) { ISOBJ_TYPE_assert(pThis, rule); return pThis->pRuleset; } /* helper to DebugPrint, to print out all actions via * the llExecFunc() facility. */ DEFFUNC_llExecFunc(dbgPrintInitInfoAction) { DEFiRet; iRet = actionDbgPrint((action_t*) pData); dbgprintf("\n"); RETiRet; } /* debugprint for the rule object */ BEGINobjDebugPrint(rule) /* be sure to specify the object type also in END and CODESTART macros! */ int i; CODESTARTobjDebugPrint(rule) dbgoprint((obj_t*) pThis, "rsyslog rule:\n"); if(pThis->pCSProgNameComp != NULL) dbgprintf("tag: '%s'\n", rsCStrGetSzStrNoNULL(pThis->pCSProgNameComp)); if(pThis->eHostnameCmpMode != HN_NO_COMP) dbgprintf("hostname: %s '%s'\n", pThis->eHostnameCmpMode == HN_COMP_MATCH ? "only" : "allbut", rsCStrGetSzStrNoNULL(pThis->pCSHostnameComp)); if(pThis->f_filter_type == FILTER_PRI) { for (i = 0; i <= LOG_NFACILITIES; i++) if (pThis->f_filterData.f_pmask[i] == TABLE_NOPRI) dbgprintf(" X "); else dbgprintf("%2X ", pThis->f_filterData.f_pmask[i]); } else if(pThis->f_filter_type == FILTER_EXPR) { dbgprintf("EXPRESSION-BASED Filter: can currently not be displayed"); } else { dbgprintf("PROPERTY-BASED Filter:\n"); dbgprintf("\tProperty.: '%s'\n", propIDToName(pThis->f_filterData.prop.propID)); dbgprintf("\tOperation: "); if(pThis->f_filterData.prop.isNegated) dbgprintf("NOT "); dbgprintf("'%s'\n", getFIOPName(pThis->f_filterData.prop.operation)); dbgprintf("\tValue....: '%s'\n", rsCStrGetSzStrNoNULL(pThis->f_filterData.prop.pCSCompValue)); dbgprintf("\tAction...: "); } dbgprintf("\nActions:\n"); llExecFunc(&pThis->llActList, dbgPrintInitInfoAction, NULL); /* actions */ dbgprintf("\n"); ENDobjDebugPrint(rule) /* queryInterface function * rgerhards, 2008-02-21 */ BEGINobjQueryInterface(rule) CODESTARTobjQueryInterface(rule) if(pIf->ifVersion != ruleCURR_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 = ruleConstruct; pIf->ConstructFinalize = ruleConstructFinalize; pIf->Destruct = ruleDestruct; pIf->DebugPrint = ruleDebugPrint; pIf->IterateAllActions = iterateAllActions; pIf->ProcessBatch = processBatch; pIf->SetAssRuleset = setAssRuleset; pIf->GetAssRuleset = getAssRuleset; finalize_it: ENDobjQueryInterface(rule) /* Exit the rule class. * rgerhards, 2009-04-06 */ BEGINObjClassExit(rule, OBJ_IS_CORE_MODULE) /* class, version */ objRelease(errmsg, CORE_COMPONENT); objRelease(expr, CORE_COMPONENT); objRelease(var, CORE_COMPONENT); objRelease(vm, CORE_COMPONENT); ENDObjClassExit(rule) /* Initialize the rule class. Must be called as the very first method * before anything else is called inside this class. * rgerhards, 2008-02-19 */ BEGINObjClassInit(rule, 1, OBJ_IS_CORE_MODULE) /* class, version */ /* request objects we use */ CHKiRet(objUse(errmsg, CORE_COMPONENT)); CHKiRet(objUse(expr, CORE_COMPONENT)); CHKiRet(objUse(var, CORE_COMPONENT)); CHKiRet(objUse(vm, CORE_COMPONENT)); /* set our own handlers */ OBJSetMethodHandler(objMethod_DEBUGPRINT, ruleDebugPrint); OBJSetMethodHandler(objMethod_CONSTRUCTION_FINALIZER, ruleConstructFinalize); ENDObjClassInit(rule) /* vi:set ai: */