/* rainerscript.c - routines to support RainerScript config language
 *
 * Module begun 2011-07-01 by Rainer Gerhards
 *
 * Copyright 2011-2025 Rainer Gerhards and Others.
 *
 * 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 <http://www.gnu.org/licenses/>.
 *
 * 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h>
#include <limits.h>
#include <glob.h>
#include <errno.h>
#include <pwd.h>
#include <grp.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <libestr.h>
#include <time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include "rsyslog.h"
#include "rainerscript.h"
#include "conf.h"
#include "parserif.h"
#include "parse.h"
#include "rsconf.h"
#include "grammar.h"
#include "queue.h"
#include "srUtils.h"
#include "regexp.h"
#include "datetime.h"
#include "obj.h"
#include "modules.h"
#include "ruleset.h"
#include "msg.h"
#include "wti.h"
#include "unicode-helper.h"
#include "errmsg.h"
#include "glbl.h"
#ifdef HAVE_LIBYAML
    #include "yamlconf.h"
#endif

extern int yylineno;

PRAGMA_IGNORE_Wswitch_enum

    DEFobjCurrIf(obj) DEFobjCurrIf(regexp) DEFobjCurrIf(datetime)

        struct cnfexpr *cnfexprOptimize(struct cnfexpr *expr);
static void cnfstmtOptimizePRIFilt(struct cnfstmt *stmt);
static void cnfarrayPrint(struct cnfarray *ar, int indent);
struct cnffunc *cnffuncNew_prifilt(int fac);

static struct cnfparamdescr incpdescr[] = {
    {"file", eCmdHdlrString, 0}, {"text", eCmdHdlrString, 0}, {"mode", eCmdHdlrGetWord, 0}};
static struct cnfparamblk incpblk = {CNFPARAMBLK_VERSION, sizeof(incpdescr) / sizeof(struct cnfparamdescr), incpdescr};

/* debug support: convert token to a human-readable string. Note that
 * this function only supports a single thread due to a static buffer.
 * This is deemed a solid solution, as it is intended to be used during
 * startup, only.
 * NOTE: This function MUST be updated if new tokens are defined in the
 *       grammar.
 */
static const char *tokenToString(const int token) {
    const char *tokstr;
    static char tokbuf[512];

    switch (token) {
        case NAME:
            tokstr = "NAME";
            break;
        case FUNC:
            tokstr = "FUNC";
            break;
        case BEGINOBJ:
            tokstr = "BEGINOBJ";
            break;
        case ENDOBJ:
            tokstr = "ENDOBJ";
            break;
        case BEGIN_ACTION:
            tokstr = "BEGIN_ACTION";
            break;
        case BEGIN_PROPERTY:
            tokstr = "BEGIN_PROPERTY";
            break;
        case BEGIN_CONSTANT:
            tokstr = "BEGIN_CONSTANT";
            break;
        case BEGIN_TPL:
            tokstr = "BEGIN_TPL";
            break;
        case BEGIN_RULESET:
            tokstr = "BEGIN_RULESET";
            break;
        case STOP:
            tokstr = "STOP";
            break;
        case SET:
            tokstr = "SET";
            break;
        case UNSET:
            tokstr = "UNSET";
            break;
        case CONTINUE:
            tokstr = "CONTINUE";
            break;
        case CALL:
            tokstr = "CALL";
            break;
        case LEGACY_ACTION:
            tokstr = "LEGACY_ACTION";
            break;
        case LEGACY_RULESET:
            tokstr = "LEGACY_RULESET";
            break;
        case PRIFILT:
            tokstr = "PRIFILT";
            break;
        case PROPFILT:
            tokstr = "PROPFILT";
            break;
        case IF:
            tokstr = "IF";
            break;
        case THEN:
            tokstr = "THEN";
            break;
        case ELSE:
            tokstr = "ELSE";
            break;
        case OR:
            tokstr = "OR";
            break;
        case AND:
            tokstr = "AND";
            break;
        case NOT:
            tokstr = "NOT";
            break;
        case VAR:
            tokstr = "VAR";
            break;
        case STRING:
            tokstr = "STRING";
            break;
        case NUMBER:
            tokstr = "NUMBER";
            break;
        case CMP_EQ:
            tokstr = "CMP_EQ";
            break;
        case CMP_NE:
            tokstr = "CMP_NE";
            break;
        case CMP_LE:
            tokstr = "CMP_LE";
            break;
        case CMP_GE:
            tokstr = "CMP_GE";
            break;
        case CMP_LT:
            tokstr = "CMP_LT";
            break;
        case CMP_GT:
            tokstr = "CMP_GT";
            break;
        case CMP_CONTAINS:
            tokstr = "CMP_CONTAINS";
            break;
        case CMP_CONTAINSI:
            tokstr = "CMP_CONTAINSI";
            break;
        case CMP_STARTSWITH:
            tokstr = "CMP_STARTSWITH";
            break;
        case CMP_STARTSWITHI:
            tokstr = "CMP_STARTSWITHI";
            break;
        case CMP_ENDSWITH:
            tokstr = "CMP_ENDSWITH";
            break;
        case UMINUS:
            tokstr = "UMINUS";
            break;
        case '&':
            tokstr = "&";
            break;
        case '+':
            tokstr = "+";
            break;
        case '-':
            tokstr = "-";
            break;
        case '*':
            tokstr = "*";
            break;
        case '/':
            tokstr = "/";
            break;
        case '%':
            tokstr = "%";
            break;
        case 'M':
            tokstr = "M";
            break;
        case 'N':
            tokstr = "N";
            break;
        case 'S':
            tokstr = "S";
            break;
        case 'V':
            tokstr = "V";
            break;
        case 'F':
            tokstr = "F";
            break;
        case 'A':
            tokstr = "A";
            break;
        case S_FUNC_EXISTS:
            tokstr = "exists()";
            break;
        default:
            snprintf(tokbuf, sizeof(tokbuf), "%c[%d]", token, token);
            tokstr = tokbuf;
            break;
    }
    return tokstr;
}


const char *getFIOPName(const unsigned iFIOP) {
    const char *pRet;
    switch (iFIOP) {
        case FIOP_CONTAINS:
            pRet = "contains";
            break;
        case FIOP_ISEQUAL:
            pRet = "isequal";
            break;
        case FIOP_STARTSWITH:
            pRet = "startswith";
            break;
        case FIOP_ENDSWITH:
            pRet = "endswith";
            break;
        case FIOP_REGEX:
            pRet = "regex";
            break;
        case FIOP_EREREGEX:
            pRet = "ereregex";
            break;
        case FIOP_ISEMPTY:
            pRet = "isempty";
            break;
        default:
            pRet = "NOP";
            break;
    }
    return pRet;
}

const char *cnfFiltType2str(const enum cnfFiltType filttype) {
    switch (filttype) {
        case CNFFILT_NONE:
            return ("filter:none");
        case CNFFILT_PRI:
            return ("filter:pri");
        case CNFFILT_PROP:
            return ("filter:prop");
        case CNFFILT_SCRIPT:
            return ("filter:script");
        default:
            return ("error:invalid_filter_type"); /* should never be reached */
    }
}

const char *cnfobjType2str(const enum cnfobjType ot) {
    switch (ot) {
        case CNFOBJ_ACTION:
            return "action";
            break;
        case CNFOBJ_RULESET:
            return "ruleset";
            break;
        case CNFOBJ_GLOBAL:
            return "global";
            break;
        case CNFOBJ_INPUT:
            return "input";
            break;
        case CNFOBJ_MODULE:
            return "module";
            break;
        case CNFOBJ_TPL:
            return "template";
            break;
        case CNFOBJ_PROPERTY:
            return "property";
            break;
        case CNFOBJ_CONSTANT:
            return "constant";
            break;
        case CNFOBJ_MAINQ:
            return "main_queue";
        case CNFOBJ_LOOKUP_TABLE:
            return "lookup_table";
        case CNFOBJ_PARSER:
            return "parser";
            break;
        case CNFOBJ_TIMEZONE:
            return "timezone";
            break;
        case CNFOBJ_DYN_STATS:
            return "dyn_stats";
            break;
        case CNFOBJ_PERCTILE_STATS:
            return "perctile_stats";
            break;
        default:
            return "error: invalid cnfobjType";
    }
}

/* This function takes the filter part of a property
 * based filter and decodes it. It processes the line up to the beginning
 * of the action part.
 */
static rsRetVal DecodePropFilter(uchar *pline, struct cnfstmt *stmt) {
    rsParsObj *pPars = NULL;
    cstr_t *pCSCompOp = NULL;
    cstr_t *pCSPropName = NULL;
    int iOffset; /* for compare operations */
    DEFiRet;

    assert(pline != NULL);

    DBGPRINTF("Decoding property-based filter '%s'\n", pline);

    /* create parser object starting with line string without leading colon */
    if ((iRet = rsParsConstructFromSz(&pPars, pline + 1)) != RS_RET_OK) {
        parser_errmsg("error %d constructing parser object", iRet);
        FINALIZE;
    }

    /* read property */
    iRet = parsDelimCStr(pPars, &pCSPropName, ',', 1, 1, 1);
    if (iRet != RS_RET_OK) {
        parser_errmsg("error %d parsing filter property", iRet);
        FINALIZE;
    }
    CHKiRet(msgPropDescrFill(&stmt->d.s_propfilt.prop, cstrGetSzStrNoNULL(pCSPropName), cstrLen(pCSPropName)));

    /* read operation */
    iRet = parsDelimCStr(pPars, &pCSCompOp, ',', 1, 1, 1);
    if (iRet != RS_RET_OK) {
        parser_errmsg("error %d compare operation property - ignoring selector", iRet);
        FINALIZE;
    }

    /* we now first check if the condition is to be negated. To do so, we first
     * must make sure we have at least one char in the param and then check the
     * first one.
     * rgerhards, 2005-09-26
     */
    if (rsCStrLen(pCSCompOp) > 0) {
        if (*rsCStrGetBufBeg(pCSCompOp) == '!') {
            stmt->d.s_propfilt.isNegated = 1;
            iOffset = 1; /* ignore '!' */
        } else {
            stmt->d.s_propfilt.isNegated = 0;
            iOffset = 0;
        }
    } else {
        stmt->d.s_propfilt.isNegated = 0;
        iOffset = 0;
    }

    if (!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar *)"contains", 8)) {
        stmt->d.s_propfilt.operation = FIOP_CONTAINS;
    } else if (!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar *)"isequal", 7)) {
        stmt->d.s_propfilt.operation = FIOP_ISEQUAL;
    } else if (!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar *)"isempty", 7)) {
        stmt->d.s_propfilt.operation = FIOP_ISEMPTY;
    } else if (!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar *)"startswith", 10)) {
        stmt->d.s_propfilt.operation = FIOP_STARTSWITH;
    } else if (!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (uchar *)"endswith", 8)) {
        stmt->d.s_propfilt.operation = FIOP_ENDSWITH;
    } else if (!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char *)"regex", 5)) {
        stmt->d.s_propfilt.operation = FIOP_REGEX;
    } else if (!rsCStrOffsetSzStrCmp(pCSCompOp, iOffset, (unsigned char *)"ereregex", 8)) {
        stmt->d.s_propfilt.operation = FIOP_EREREGEX;
    } else {
        parser_errmsg("error: invalid compare operation '%s'", (char *)rsCStrGetSzStrNoNULL(pCSCompOp));
        ABORT_FINALIZE(RS_RET_ERR);
    }

    if (stmt->d.s_propfilt.operation != FIOP_ISEMPTY) {
        /* read compare value */
        iRet = parsQuotedCStr(pPars, &stmt->d.s_propfilt.pCSCompValue);
        if (iRet != RS_RET_OK) {
            parser_errmsg("error %d compare value property", iRet);
            FINALIZE;
        }
    }

finalize_it:
    if (pPars != NULL) rsParsDestruct(pPars);
    if (pCSCompOp != NULL) rsCStrDestruct(&pCSCompOp);
    if (pCSPropName != NULL) cstrDestruct(&pCSPropName);
    RETiRet;
}

static void prifiltInvert(struct funcData_prifilt *__restrict__ const prifilt) {
    int i;
    for (i = 0; i < LOG_NFACILITIES + 1; ++i) {
        prifilt->pmask[i] = ~prifilt->pmask[i];
    }
}

/* set prifilt so that it matches for some severities, sev is its numerical
 * value. Mode is one of the compop tokens CMP_EQ, CMP_LT, CMP_LE, CMP_GT,
 * CMP_GE, CMP_NE.
 */
static void prifiltSetSeverity(struct funcData_prifilt *prifilt, int sev, int mode) {
    static int lessthanmasks[] = {0x00, 0x01, 0x03, 0x07, 0x0f, 0x1f, 0x3f, 0x7f, 0xff};
    int i;
    for (i = 0; i < LOG_NFACILITIES + 1; ++i) {
        if (mode == CMP_EQ || mode == CMP_NE)
            prifilt->pmask[i] = 1 << sev;
        else if (mode == CMP_LT)
            prifilt->pmask[i] = lessthanmasks[sev];
        else if (mode == CMP_LE)
            prifilt->pmask[i] = lessthanmasks[sev + 1];
        else if (mode == CMP_GT)
            prifilt->pmask[i] = ~lessthanmasks[sev + 1];
        else if (mode == CMP_GE)
            prifilt->pmask[i] = ~lessthanmasks[sev];
        else
            DBGPRINTF("prifiltSetSeverity: program error, invalid mode %s\n", tokenToString(mode));
    }
    if (mode == CMP_NE) prifiltInvert(prifilt);
}

/* set prifilt so that it matches for some facilities, fac is its numerical
 * value. Mode is one of the compop tokens CMP_EQ, CMP_LT, CMP_LE, CMP_GT,
 * CMP_GE, CMP_NE. For the given facilities, all severities are enabled.
 * NOTE: fac MUST be in the range 0..24 (not multiplied by 8)!
 */
static void prifiltSetFacility(struct funcData_prifilt *__restrict__ const prifilt, const int fac, const int mode) {
    int i;

    memset(prifilt->pmask, 0, sizeof(prifilt->pmask));
    switch (mode) {
        case CMP_EQ:
            prifilt->pmask[fac] = TABLE_ALLPRI;
            break;
        case CMP_NE:
            prifilt->pmask[fac] = TABLE_ALLPRI;
            prifiltInvert(prifilt);
            break;
        case CMP_LT:
            for (i = 0; i < fac; ++i) prifilt->pmask[i] = TABLE_ALLPRI;
            break;
        case CMP_LE:
            for (i = 0; i < fac + 1; ++i) prifilt->pmask[i] = TABLE_ALLPRI;
            break;
        case CMP_GE:
            for (i = fac; i < LOG_NFACILITIES + 1; ++i) prifilt->pmask[i] = TABLE_ALLPRI;
            break;
        case CMP_GT:
            for (i = fac + 1; i < LOG_NFACILITIES + 1; ++i) prifilt->pmask[i] = TABLE_ALLPRI;
            break;
        default:
            break;
    }
}

/* combine a prifilt with AND/OR (the respective token values are
 * used to keep things simple).
 */
static void prifiltCombine(struct funcData_prifilt *__restrict__ const prifilt,
                           struct funcData_prifilt *__restrict__ const prifilt2,
                           const int mode) {
    int i;
    for (i = 0; i < LOG_NFACILITIES + 1; ++i) {
        if (mode == AND)
            prifilt->pmask[i] = prifilt->pmask[i] & prifilt2->pmask[i];
        else
            prifilt->pmask[i] = prifilt->pmask[i] | prifilt2->pmask[i];
    }
}


void readConfFile(FILE *const fp, es_str_t **str) {
    char ln[10240];
    char buf[512];
    int lenBuf;
    int bWriteLineno = 0;
    int len, i;
    int start; /* start index of to be submitted text */
    int bContLine = 0;
    int lineno = 0;

    *str = es_newStr(4096);

    while (fgets(ln, sizeof(ln), fp) != NULL) {
        ++lineno;
        if (bWriteLineno) {
            bWriteLineno = 0;
            lenBuf = snprintf(buf, sizeof(buf), "PreprocFileLineNumber(%d)\n", lineno);
            es_addBuf(str, buf, lenBuf);
        }
        len = strlen(ln);
        /* if we are continuation line, we need to drop leading WS */
        if (bContLine) {
            for (start = 0; start < len && isspace(ln[start]); ++start) /* JUST SCAN */
                ;
        } else {
            start = 0;
        }
        for (i = len - 1; i >= start && isspace(ln[i]); --i) /* JUST SCAN */
            ;
        if (i >= 0) {
            if (ln[i] == '\\') {
                --i;
                bContLine = 1;
            } else {
                if (bContLine) /* write line number if we had cont line */
                    bWriteLineno = 1;
                bContLine = 0;
            }
            /* add relevant data to buffer */
            es_addBuf(str, ln + start, i + 1 - start);
        }
        if (!bContLine) es_addChar(str, '\n');
    }
    /* indicate end of buffer to flex */
    es_addChar(str, '\0');
    es_addChar(str, '\0');
}

/* comparison function for qsort() and bsearch() string array compare */
static int qs_arrcmp(const void *s1, const void *s2) {
    return es_strcmp(*((es_str_t **)s1), *((es_str_t **)s2));
}


struct objlst *objlstNew(struct cnfobj *o) {
    struct objlst *lst;

    if ((lst = malloc(sizeof(struct objlst))) != NULL) {
        lst->next = NULL;
        lst->obj = o;
    }
    cnfobjPrint(o);

    return lst;
}

/* add object to end of object list, always returns pointer to root object */
struct objlst *objlstAdd(struct objlst *root, struct cnfobj *o) {
    struct objlst *l;
    struct objlst *newl;

    newl = objlstNew(o);
    if (root == 0) {
        root = newl;
    } else { /* find last, linear search ok, as only during config phase */
        for (l = root; l->next != NULL; l = l->next);
        l->next = newl;
    }
    return root;
}

/* add stmt to current script, always return root stmt pointer */
struct cnfstmt *scriptAddStmt(struct cnfstmt *root, struct cnfstmt *s) {
    struct cnfstmt *l;

    if (root == NULL) {
        root = s;
    } else { /* find last, linear search ok, as only during config phase */
        for (l = root; l->next != NULL; l = l->next);
        l->next = s;
    }
    return root;
}

void objlstDestruct(struct objlst *lst) {
    struct objlst *toDel;

    while (lst != NULL) {
        toDel = lst;
        lst = lst->next;
        cnfobjDestruct(toDel->obj);
        free(toDel);
    }
}

void objlstPrint(struct objlst *lst) {
    dbgprintf("objlst %p:\n", lst);
    while (lst != NULL) {
        cnfobjPrint(lst->obj);
        lst = lst->next;
    }
}

struct nvlst *ATTR_NONNULL(1) nvlstNewStr(es_str_t *const value) {
    struct nvlst *lst;

    if ((lst = malloc(sizeof(struct nvlst))) != NULL) {
        lst->next = NULL;
        lst->val.datatype = 'S';
        lst->val.d.estr = value;
        lst->bUsed = 0;
    }

    return lst;
}

struct nvlst *ATTR_NONNULL(1) nvlstNewStrBackticks(es_str_t *const value) {
    es_str_t *val = NULL;
    const char *realval;

    char *const param = es_str2cstr(value, NULL);
    if (param == NULL) goto done;

    if (strncmp(param, "echo $", sizeof("echo $") - 1) != 0) {
        parser_errmsg(
            "invalid backtick parameter `%s` currently "
            "only `echo $<var>` is supported - replaced by "
            "empty strong (\"\")",
            param);
        realval = NULL;
    } else {
        size_t i;
        const size_t len = strlen(param);
        for (i = len - 1; isspace(param[i]); --i) {
            ; /* just go down */
        }
        if (i > 6 && i < len - 1) {
            param[i + 1] = '\0';
        }
        realval = getenv(param + 6);
    }

    free((void *)param);
    if (realval == NULL) {
        realval = "";
    }
    val = es_newStrFromCStr(realval, strlen(realval));
    es_deleteStr(value);

done:
    return (val == NULL) ? NULL : nvlstNewStr(val);
}

struct nvlst *nvlstNewArray(struct cnfarray *ar) {
    struct nvlst *lst;

    if ((lst = malloc(sizeof(struct nvlst))) != NULL) {
        lst->next = NULL;
        lst->val.datatype = 'A';
        lst->val.d.ar = ar;
        lst->bUsed = 0;
    }

    return lst;
}

struct nvlst *nvlstSetName(struct nvlst *lst, es_str_t *name) {
    lst->name = name;
    return lst;
}

void nvlstDestruct(struct nvlst *lst) {
    struct nvlst *toDel;

    while (lst != NULL) {
        toDel = lst;
        lst = lst->next;
        es_deleteStr(toDel->name);
        varDelete(&toDel->val);
        free(toDel);
    }
}

void nvlstPrint(struct nvlst *lst) {
    char *name, *value;
    dbgprintf("nvlst %p:\n", lst);
    while (lst != NULL) {
        name = es_str2cstr(lst->name, NULL);
        switch (lst->val.datatype) {
            case 'A':
                dbgprintf("\tname: '%s':\n", name);
                cnfarrayPrint(lst->val.d.ar, 5);
                break;
            case 'S':
                value = es_str2cstr(lst->val.d.estr, NULL);
                dbgprintf("\tname: '%s', value '%s'\n", name, value);
                free(value);
                break;
            default:
                dbgprintf("nvlstPrint: unknown type '%s'\n", tokenToString(lst->val.datatype));
                break;
        }
        free(name);
        lst = lst->next;
    }
}

/* find a name starting at node lst. Returns node with this
 * name or NULL, if none found.
 */
struct nvlst *nvlstFindName(struct nvlst *lst, es_str_t *name) {
    while (lst != NULL && es_strcmp(lst->name, name)) lst = lst->next;
    return lst;
}


/* find a name starting at node lst. Same as nvlstFindName, but
 * for classical C strings. This is useful because the config system
 * uses C string constants.
 */
static struct nvlst *nvlstFindNameCStr(struct nvlst *lst, const char *const __restrict__ name) {
    es_size_t lenName = strlen(name);
    while (lst != NULL && es_strcasebufcmp(lst->name, (uchar *)name, lenName)) lst = lst->next;
    return lst;
}

/* check if the nvlst is disabled, and mark config.enabled directive
 * as used if it is not. Returns 1 if block is disabled, 0 otherwise.
 */
int nvlstChkDisabled(struct nvlst *lst) {
    struct nvlst *valnode;

    if ((valnode = nvlstFindNameCStr(lst, "config.enabled")) != NULL) {
        valnode->bUsed = 1;
        if (es_strbufcmp(valnode->val.d.estr, (unsigned char *)"on", 2)) {
            return 1;
        }
    }
    return 0;
}


/* check if there are duplicate names inside a nvlst and emit
 * an error message, if so.
 */
static void nvlstChkDupes(struct nvlst *lst) {
    char *cstr;

    while (lst != NULL) {
        if (nvlstFindName(lst->next, lst->name) != NULL) {
            cstr = es_str2cstr(lst->name, NULL);
            parser_errmsg(
                "duplicate parameter '%s' -- "
                "interpretation is ambiguous, one value "
                "will be randomly selected. Fix this problem.",
                cstr);
            free(cstr);
        }
        lst = lst->next;
    }
}


/* check for unused params and emit error message is found. This must
 * be called after all config params have been pulled from the object
 * (otherwise the flags are not correctly set).
 */
void nvlstChkUnused(struct nvlst *lst) {
    char *cstr;

    while (lst != NULL) {
        if (!lst->bUsed) {
            cstr = es_str2cstr(lst->name, NULL);
            parser_errmsg(
                "parameter '%s' not known -- "
                "typo in config file?",
                cstr);
            free(cstr);
        }
        lst = lst->next;
    }
}


static int doGetSize(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    unsigned char *c;
    es_size_t i;
    long long n;
    int r;
    c = es_getBufAddr(valnode->val.d.estr);
    n = 0;
    i = 0;
    while (i < es_strlen(valnode->val.d.estr) && isdigit(*c)) {
        n = 10 * n + *c - '0';
        ++i;
        ++c;
    }
    if (i < es_strlen(valnode->val.d.estr)) {
        ++i;
        switch (*c) {
            /* traditional binary-based definitions */
            case 'k':
                n *= 1024;
                break;
            case 'm':
                n *= 1024 * 1024;
                break;
            case 'g':
                n *= 1024 * 1024 * 1024;
                break;
            case 't':
                n *= (int64)1024 * 1024 * 1024 * 1024;
                break; /* tera */
            case 'p':
                n *= (int64)1024 * 1024 * 1024 * 1024 * 1024;
                break; /* peta */
            case 'e':
                n *= (int64)1024 * 1024 * 1024 * 1024 * 1024 * 1024;
                break; /* exa */
            /* and now the "new" 1000-based definitions */
            case 'K':
                n *= 1000;
                break;
            case 'M':
                n *= 1000000;
                break;
            case 'G':
                n *= 1000000000;
                break;
                /* we need to use the multiplication below because otherwise
                 * the compiler gets an error during constant parsing */
            case 'T':
                n *= (int64)1000 * 1000000000;
                break; /* tera */
            case 'P':
                n *= (int64)1000000 * 1000000000;
                break; /* peta */
            case 'E':
                n *= (int64)1000000000 * 1000000000;
                break; /* exa */
            default:
                --i;
                break; /* indicates error */
        }
    }
    if (i == es_strlen(valnode->val.d.estr)) {
        val->val.datatype = 'N';
        val->val.d.n = n;
        r = 1;
    } else {
        parser_errmsg("parameter '%s' does not contain a valid size", param->name);
        r = 0;
    }
    return r;
}


static int doGetBinary(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    int r = 1;
    val->val.datatype = 'N';
    if (!es_strbufcmp(valnode->val.d.estr, (unsigned char *)"on", 2)) {
        val->val.d.n = 1;
    } else if (!es_strbufcmp(valnode->val.d.estr, (unsigned char *)"off", 3)) {
        val->val.d.n = 0;
    } else {
        parser_errmsg(
            "parameter '%s' must be \"on\" or \"off\" but "
            "is neither. Results unpredictable.",
            param->name);
        val->val.d.n = 0;
        r = 0;
    }
    return r;
}

static int doGetQueueType(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    char *cstr;
    int r = 1;
    if (!es_strcasebufcmp(valnode->val.d.estr, (uchar *)"fixedarray", 10)) {
        val->val.d.n = QUEUETYPE_FIXED_ARRAY;
    } else if (!es_strcasebufcmp(valnode->val.d.estr, (uchar *)"linkedlist", 10)) {
        val->val.d.n = QUEUETYPE_LINKEDLIST;
    } else if (!es_strcasebufcmp(valnode->val.d.estr, (uchar *)"disk", 4)) {
        val->val.d.n = QUEUETYPE_DISK;
    } else if (!es_strcasebufcmp(valnode->val.d.estr, (uchar *)"direct", 6)) {
        val->val.d.n = QUEUETYPE_DIRECT;
    } else if (!es_strcasebufcmp(valnode->val.d.estr, (uchar *)"segmenteddisk", 13)) {
        val->val.d.n = QUEUETYPE_SEGMENTED_DISK;
    } else {
        cstr = es_str2cstr(valnode->val.d.estr, NULL);
        if (cstr == NULL) {
            parser_errmsg("param '%s': unknown queue type", param->name);
        } else {
            parser_errmsg("param '%s': unknown queue type: '%s'", param->name, cstr);
            free(cstr);
        }
        r = 0;
    }
    val->val.datatype = 'N';
    return r;
}


/* A file create-mode must be a four-digit octal number
 * starting with '0'.
 */
static int doGetFileCreateMode(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    int fmtOK = 0;
    char *cstr;
    uchar *c;
    const int len_val = es_strlen(valnode->val.d.estr);

    if (len_val >= 4) {
        c = es_getBufAddr(valnode->val.d.estr);
        if ((c[0] == '0') && (c[1] >= '0' && c[1] <= '7') && (c[2] >= '0' && c[2] <= '7') &&
            (c[3] >= '0' && c[3] <= '7')) {
            if (len_val == 5) {
                if (c[4] >= '0' && c[4] <= '7') {
                    fmtOK = 1;
                }
            } else {
                fmtOK = 1;
            }
        }
    }

    if (fmtOK) {
        val->val.datatype = 'N';
        val->val.d.n = (c[1] - '0') * 64 + (c[2] - '0') * 8 + (c[3] - '0');
        if (len_val == 5) {
            val->val.d.n = val->val.d.n * 8 + (c[4] - '0');
        }
    } else {
        cstr = es_str2cstr(valnode->val.d.estr, NULL);
        parser_errmsg(
            "file modes need to be specified as "
            "4- or 5-digit octal numbers starting with '0' -"
            "parameter '%s=\"%s\"' is not a file mode",
            param->name, cstr);
        free(cstr);
    }
    return fmtOK;
}

static int doGetGID(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    char *cstr;
    int r;
    struct group *resultBuf = NULL;
    struct group wrkBuf;
    char *stringBuf = NULL;
    size_t bufSize = 1024;
    int e;

    cstr = es_str2cstr(valnode->val.d.estr, NULL);
    do {
        char *p;

        /* Increase bufsize and try again.*/
        bufSize *= 2;
        p = realloc(stringBuf, bufSize);
        if (!p) {
            e = ENOMEM;
            break;
        }
        stringBuf = p;
        e = getgrnam_r(cstr, &wrkBuf, stringBuf, bufSize, &resultBuf);
    } while (!resultBuf && (e == ERANGE));

    if (resultBuf == NULL) {
        if (e != 0) {
            if (loadConf->globals.abortOnIDResolutionFail) {
                fprintf(stderr,
                        "parameter '%s': error to "
                        "obtaining group id for '%s'",
                        param->name, cstr);
                exit(1); /* good exit */
            } else {
                LogError(e, RS_RET_ERR,
                         "parameter '%s': error to "
                         "obtaining group id for '%s'",
                         param->name, cstr);
            }
        }
        parser_errmsg(
            "parameter '%s': ID for group %s could not "
            "be found",
            param->name, cstr);
        r = 0;
    } else {
        val->val.datatype = 'N';
        val->val.d.n = resultBuf->gr_gid;
        DBGPRINTF("param '%s': uid %d obtained for group '%s'\n", param->name, (int)resultBuf->gr_gid, cstr);
        r = 1;
    }
    free(stringBuf);
    free(cstr);
    return r;
}

static int doGetUID(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    char *cstr;
    int r;
    struct passwd *resultBuf;
    struct passwd wrkBuf;
    char stringBuf[2048]; /* 2048 has been proven to be large enough */
    char errStr[1024];

    cstr = es_str2cstr(valnode->val.d.estr, NULL);
    const int err_no = getpwnam_r(cstr, &wrkBuf, stringBuf, sizeof(stringBuf), &resultBuf);
    if (resultBuf == NULL) {
        rs_strerror_r((err_no == 0) ? ENOENT : errno, errStr, sizeof(errStr));
        if (loadConf->globals.abortOnIDResolutionFail) {
            fprintf(stderr,
                    "parameter '%s': ID for user '%s' could not "
                    "be found: %s",
                    param->name, cstr, errStr);
            exit(1); /* good exit */
        } else {
            LogError(err_no, RS_RET_ERR,
                     "parameter '%s': ID for user '%s' could not "
                     "be found: %s",
                     param->name, cstr, errStr);
            parser_errmsg(
                "parameter '%s': ID for user '%s' could not "
                "be found: %s",
                param->name, cstr, errStr);
        }

        r = 0;
    } else {
        val->val.datatype = 'N';
        val->val.d.n = resultBuf->pw_uid;
        DBGPRINTF("param '%s': uid %d obtained for user '%s'\n", param->name, (int)resultBuf->pw_uid, cstr);
        r = 1;
    }
    free(cstr);
    return r;
}

/* note: we support all integer formats that es_str2num support,
 * so hex and octal representations are also valid.
 */
static int doGetInt(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    long long n;
    int bSuccess;

    n = es_str2num(valnode->val.d.estr, &bSuccess);
    if (!bSuccess) {
        parser_errmsg("parameter '%s' is not a proper number", param->name);
    }
    val->val.datatype = 'N';
    val->val.d.n = n;
    return bSuccess;
}

static int doGetNonNegInt(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    int bSuccess;

    if ((bSuccess = doGetInt(valnode, param, val))) {
        if (val->val.d.n < 0) {
            parser_errmsg("parameter '%s' cannot be less than zero (was %lld)", param->name, val->val.d.n);
            bSuccess = 0;
        }
    }
    return bSuccess;
}

static int doGetPositiveInt(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    int bSuccess;

    if ((bSuccess = doGetInt(valnode, param, val))) {
        if (val->val.d.n < 1) {
            parser_errmsg("parameter '%s' cannot be less than one (was %lld)", param->name, val->val.d.n);
            bSuccess = 0;
        }
    }
    return bSuccess;
}

static int doGetWord(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    es_size_t i;
    int r = 1;
    unsigned char *c;

    val->val.datatype = 'S';
    val->val.d.estr = es_newStr(32);
    c = es_getBufAddr(valnode->val.d.estr);
    for (i = 0; i < es_strlen(valnode->val.d.estr) && !isspace(c[i]); ++i) {
        es_addChar(&val->val.d.estr, c[i]);
    }
    if (i != es_strlen(valnode->val.d.estr)) {
        parser_errmsg(
            "parameter '%s' contains whitespace, which is not "
            "permitted",
            param->name);
        r = 0;
    }
    return r;
}

static int doGetArray(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    int r = 1;

    switch (valnode->val.datatype) {
        case 'S':
            /* a constant string is assumed to be a single-element array */
            val->val.datatype = 'A';
            val->val.d.ar = cnfarrayNew(es_strdup(valnode->val.d.estr));
            break;
        case 'A':
            val->val.datatype = 'A';
            val->val.d.ar = cnfarrayDup(valnode->val.d.ar);
            break;
        default:
            parser_errmsg(
                "parameter '%s' must be an array, but is a "
                "different datatype",
                param->name);
            r = 0;
            break;
    }
    return r;
}

static int doGetChar(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    int r = 1;
    if (es_strlen(valnode->val.d.estr) != 1) {
        parser_errmsg(
            "parameter '%s' must contain exactly one character "
            "but contains %d - cannot be processed",
            param->name, es_strlen(valnode->val.d.estr));
        r = 0;
    }
    val->val.datatype = 'S';
    val->val.d.estr = es_strdup(valnode->val.d.estr);
    return r;
}

/* get a single parameter according to its definition. Helper to
 * nvlstGetParams. returns 1 if success, 0 otherwise
 */
static int nvlstGetParam(struct nvlst *valnode, struct cnfparamdescr *param, struct cnfparamvals *val) {
    uchar *cstr;
    int r;

    DBGPRINTF("nvlstGetParam: name '%s', type %d, valnode->bUsed %d\n", param->name, (int)param->type, valnode->bUsed);
    if (valnode->val.datatype != 'S' && param->type != eCmdHdlrArray) {
        parser_errmsg(
            "parameter '%s' is not a string, which is not "
            "permitted",
            param->name);
        r = 0;
        goto done;
    }
    valnode->bUsed = 1;
    val->bUsed = 1;
    switch (param->type) {
        case eCmdHdlrQueueType:
            r = doGetQueueType(valnode, param, val);
            break;
        case eCmdHdlrUID:
            r = doGetUID(valnode, param, val);
            break;
        case eCmdHdlrGID:
            r = doGetGID(valnode, param, val);
            break;
        case eCmdHdlrBinary:
            r = doGetBinary(valnode, param, val);
            break;
        case eCmdHdlrFileCreateMode:
            r = doGetFileCreateMode(valnode, param, val);
            break;
        case eCmdHdlrInt:
            r = doGetInt(valnode, param, val);
            break;
        case eCmdHdlrNonNegInt:
            r = doGetNonNegInt(valnode, param, val);
            break;
        case eCmdHdlrPositiveInt:
            r = doGetPositiveInt(valnode, param, val);
            break;
        case eCmdHdlrSize:
            r = doGetSize(valnode, param, val);
            break;
        case eCmdHdlrGetChar:
            r = doGetChar(valnode, param, val);
            break;
        case eCmdHdlrFacility:
            cstr = (uchar *)es_str2cstr(valnode->val.d.estr, NULL);
            val->val.datatype = 'N';
            val->val.d.n = decodeSyslogName(cstr, syslogFacNames);
            free(cstr);
            r = 1;
            break;
        case eCmdHdlrSeverity:
            cstr = (uchar *)es_str2cstr(valnode->val.d.estr, NULL);
            val->val.datatype = 'N';
            val->val.d.n = decodeSyslogName(cstr, syslogPriNames);
            free(cstr);
            r = 1;
            break;
        case eCmdHdlrGetWord:
            r = doGetWord(valnode, param, val);
            break;
        case eCmdHdlrString:
            val->val.datatype = 'S';
            val->val.d.estr = es_strdup(valnode->val.d.estr);
            r = 1;
            break;
        case eCmdHdlrArray:
            r = doGetArray(valnode, param, val);
            break;
        case eCmdHdlrGoneAway:
            parser_errmsg("parameter '%s' is no longer supported", param->name);
            r = 1; /* this *is* valid! */
            break;
        default:
            DBGPRINTF("error: invalid param type\n");
            r = 0;
            break;
    }
done:
    return r;
}


/* obtain conf params from an nvlst and emit error messages if
 * necessary. If an already-existing param value is passed, that is
 * used. If NULL is passed instead, a new one is allocated. In that case,
 * it is the caller's duty to free it when no longer needed.
 * NULL is returned on error, otherwise a pointer to the vals array.
 */
struct cnfparamvals *ATTR_NONNULL(2)
    nvlstGetParams(struct nvlst *lst, struct cnfparamblk *params, struct cnfparamvals *vals) {
#ifndef __clang_analyzer__ /* I give up on this one - let Coverity do the work */
    int i;
    int bValsWasNULL;
    int bInError = 0;
    struct nvlst *valnode;
    struct cnfparamdescr *param;

    if (params->version != CNFPARAMBLK_VERSION) {
        DBGPRINTF(
            "nvlstGetParams: invalid param block version "
            "%d, expected %d\n",
            params->version, CNFPARAMBLK_VERSION);
        return NULL;
    }

    if (vals == NULL) {
        bValsWasNULL = 1;
        if ((vals = calloc(params->nParams, sizeof(struct cnfparamvals))) == NULL) return NULL;
    } else {
        bValsWasNULL = 0;
    }

    for (i = 0; i < params->nParams; ++i) {
        param = params->descr + i;
        if ((valnode = nvlstFindNameCStr(lst, param->name)) == NULL) {
            if (param->flags & CNFPARAM_REQUIRED) {
                parser_errmsg(
                    "parameter '%s' required but not specified - "
                    "fix config",
                    param->name);
                bInError = 1;
            }
            continue;
        }
        if (param->flags & CNFPARAM_DEPRECATED) {
            parser_errmsg(
                "parameter '%s' deprecated but accepted, consider "
                "removing or replacing it",
                param->name);
        }
        if (vals[i].bUsed) {
            parser_errmsg(
                "parameter '%s' specified more than once - "
                "one instance is ignored. Fix config",
                param->name);
            continue;
        }
        if (!nvlstGetParam(valnode, param, vals + i)) {
            bInError = 1;
        }
    }

    /* done parameter processing */
    if (bInError) {
        if (bValsWasNULL) cnfparamvalsDestruct(vals, params);
        vals = NULL;
    }

    return vals;
#else
    return NULL;
#endif
}


/* check if at least one cnfparamval is actually set
 * returns 1 if so, 0 otherwise
 */
int cnfparamvalsIsSet(struct cnfparamblk *params, struct cnfparamvals *vals) {
    int i;

    if (vals == NULL) return 0;
    if (params->version != CNFPARAMBLK_VERSION) {
        DBGPRINTF(
            "nvlstGetParams: invalid param block version "
            "%d, expected %d\n",
            params->version, CNFPARAMBLK_VERSION);
        return 0;
    }
    for (i = 0; i < params->nParams; ++i) {
        if (vals[i].bUsed) return 1;
    }
    return 0;
}


void cnfparamsPrint(const struct cnfparamblk *params, const struct cnfparamvals *vals) {
    int i;
    char *cstr;

    if (!Debug) return;

    for (i = 0; i < params->nParams; ++i) {
        dbgprintf("%s: ", params->descr[i].name);
        if (vals[i].bUsed) {
            // TODO: other types!
            switch (vals[i].val.datatype) {
                case 'S':
                    cstr = es_str2cstr(vals[i].val.d.estr, NULL);
                    dbgprintf(" '%s'", cstr);
                    free(cstr);
                    break;
                case 'A':
                    cnfarrayPrint(vals[i].val.d.ar, 0);
                    break;
                case 'N':
                    dbgprintf("%lld", vals[i].val.d.n);
                    break;
                default:
                    dbgprintf("(unsupported datatype %c)", vals[i].val.datatype);
            }
        } else {
            dbgprintf("(unset)");
        }
        dbgprintf("\n");
    }
}

struct cnfobj *cnfobjNew(enum cnfobjType objType, struct nvlst *lst) {
    struct cnfobj *o;

    if ((o = malloc(sizeof(struct cnfobj))) != NULL) {
        nvlstChkDupes(lst);
        o->objType = objType;
        o->nvlst = lst;
        o->subobjs = NULL;
        o->script = NULL;
    }

    return o;
}

void cnfobjDestruct(struct cnfobj *o) {
    if (o != NULL) {
        nvlstDestruct(o->nvlst);
        objlstDestruct(o->subobjs);
        free(o);
    }
}

void cnfobjPrint(struct cnfobj *o) {
    dbgprintf("obj: '%s'\n", cnfobjType2str(o->objType));
    nvlstPrint(o->nvlst);
}


struct cnfexpr *cnfexprNew(unsigned nodetype, struct cnfexpr *l, struct cnfexpr *r) {
    struct cnfexpr *expr;

    /* Warn on bare constant AND/OR operands as written in the config, e.g.
     * `$msg contains "a" or "b"` (issue #1046). This must happen here, at
     * construction time, rather than in the optimizer: constFoldCmp() later
     * reduces legitimate constant comparisons (typically backtick-expanded
     * environment variables, e.g. `echo $FLAG` == "on") to constant operands,
     * which must not warn. At this point such an operand is still a comparison
     * node, and cnfcurrfn/yylineno still point at the offending expression, so
     * the reported file and line are accurate.
     */
    if (nodetype == AND || nodetype == OR) {
        if (l != NULL && (l->nodetype == 'N' || l->nodetype == 'S')) {
            parser_warnmsg("boolean operator '%s' has constant left operand; did you mean to repeat the comparison?",
                           tokenToString(nodetype));
        }
        if (r != NULL && (r->nodetype == 'N' || r->nodetype == 'S')) {
            parser_warnmsg("boolean operator '%s' has constant right operand; did you mean to repeat the comparison?",
                           tokenToString(nodetype));
        }
    }

    /* optimize some constructs during parsing */
    if (nodetype == 'M' && r->nodetype == 'N') {
        ((struct cnfnumval *)r)->val *= -1;
        expr = r;
        goto done;
    }

    if ((expr = malloc(sizeof(struct cnfexpr))) != NULL) {
        expr->nodetype = nodetype;
        expr->l = l;
        expr->r = r;
    }
done:
    return expr;
}


static int64_t str2num(es_str_t *s, int *bSuccess) {
    size_t i;
    int neg;
    int64_t num = 0;
    const uchar *const c = es_getBufAddr(s);

    if (s->lenStr == 0) {
        DBGPRINTF("rainerscript: str2num: strlen == 0; invalid input (no string)\n");
        if (bSuccess != NULL) {
            *bSuccess = 1;
        }
        goto done;
    }
    if (c[0] == '-') {
        neg = -1;
        i = 1;
    } else {
        neg = 1;
        i = 0;
    }
    while (i < s->lenStr && isdigit(c[i])) {
        num = num * 10 + c[i] - '0';
        ++i;
    }
    num *= neg;
    if (bSuccess != NULL) *bSuccess = (i == s->lenStr) ? 1 : 0;
done:
    return num;
}

/* We support decimal integers. Unfortunately, previous versions
 * said they support oct and hex, but that wasn't really the case.
 * Everything based on JSON was just dec-converted. As this was/is
 * the norm, we fix that inconsistency. Luckly, oct and hex support
 * was never documented.
 * rgerhards, 2015-11-12
 */
long long var2Number(struct svar *r, int *bSuccess) {
    long long n = 0;
    if (r->datatype == 'S') {
        n = str2num(r->d.estr, bSuccess);
    } else {
        if (r->datatype == 'J') {
            n = (r->d.json == NULL) ? 0 : json_object_get_int64(r->d.json);
        } else {
            n = r->d.n;
        }
        if (bSuccess != NULL) *bSuccess = 1;
    }
    return n;
}

/* ensure that retval is a string
 */
static es_str_t *var2String(struct svar *__restrict__ const r, int *__restrict__ const bMustFree) {
    es_str_t *estr;
    const char *cstr;
    size_t lenstr;
    if (r->datatype == 'N') {
        *bMustFree = 1;
        estr = es_newStrFromNumber(r->d.n);
    } else if (r->datatype == 'J') {
        *bMustFree = 1;
        if (r->d.json == NULL) {
            cstr = "", lenstr = 0;
        } else {
            cstr = (char *)json_object_get_string(r->d.json);
            if (json_object_get_type(r->d.json) == json_type_string) {
                lenstr = json_object_get_string_len(r->d.json);
            } else {
                lenstr = strlen(cstr);
            }
#if SIZE_MAX > UINT_MAX
            if (lenstr > (size_t)UINT_MAX) {
                cstr = "", lenstr = 0;
            }
#endif
        }
        estr = es_newStrFromCStr(cstr, (es_size_t)lenstr);
    } else {
        *bMustFree = 0;
        estr = r->d.estr;
    }
    return estr;
}

/**
 * Convert a script value to a newly allocated C string.
 *
 * This function is intentionally a C-string boundary, not a byte-string
 * preserving conversion. Received messages are sanitized before script
 * evaluation and config literals are parsed through C-string based lexer paths.
 * Internally, however, some length-aware script functions may produce string
 * values that contain embedded NUL bytes. Those bytes are escaped as "#000",
 * matching rsyslog's established message-content convention, so callers do not
 * silently operate on a truncated prefix. Callers that need to preserve such
 * bytes must use var2String() and length-aware APIs instead.
 *
 * We deliberately do not scan for embedded NUL bytes here: this routine is on
 * hot script evaluation paths, and the libestr conversion already performs the
 * required escape while callers opt into C-string semantics by using this
 * helper.
 */
uchar *var2CString(struct svar *__restrict__ const r, int *__restrict__ const bMustFree) {
    uchar *cstr;
    es_str_t *estr;
    estr = var2String(r, bMustFree);
    cstr = (uchar *)es_str2cstr(estr, "#000");
    if (*bMustFree) es_deleteStr(estr);
    *bMustFree = 1;
    return cstr;
}

/* frees struct svar members, but not the struct itself. This is because
 * it usually is allocated on the stack. Callers why dynamically allocate
 * struct svar need to free the struct themselfes!
 */

int SKIP_NOTHING = 0x0;
int SKIP_STRING = 0x1;

static void varFreeMembersSelectively(const struct svar *r, const int skipMask) {
    if (r->datatype == 'J') {
        json_object_put(r->d.json);
    } else if (!(skipMask & SKIP_STRING) && (r->datatype == 'S')) {
        es_deleteStr(r->d.estr);
    }
}

void varFreeMembers(const struct svar *r) {
    varFreeMembersSelectively(r, SKIP_NOTHING);
}


static rsRetVal doExtractFieldByChar(uchar *str, uchar delim, const int matchnbr, uchar **resstr) {
    int iCurrFld;
    int allocLen;
    int iLen;
    uchar *pBuf;
    uchar *pFld;
    uchar *pFldEnd;
    DEFiRet;

    /* first, skip to the field in question */
    iCurrFld = 1;
    pFld = str;
    while (*pFld && iCurrFld < matchnbr) {
        /* skip fields until the requested field or end of string is found */
        while (*pFld && (uchar)*pFld != delim) ++pFld; /* skip to field terminator */
        if (*pFld == delim) {
            ++pFld; /* eat it */
            ++iCurrFld;
        }
    }
    DBGPRINTF("field() field requested %d, field found %d\n", matchnbr, iCurrFld);

    if (iCurrFld == matchnbr) {
        /* field found, now extract it */
        /* first of all, we need to find the end */
        pFldEnd = pFld;
        while (*pFldEnd && *pFldEnd != delim) ++pFldEnd;
        --pFldEnd; /* we are already at the delimiter - so we need to
                    * step back a little not to copy it as part of the field. */
        /* we got our end pointer, now do the copy */
        iLen = pFldEnd - pFld + 1; /* the +1 is for an actual char, NOT \0! */
        allocLen = iLen + 1;
#ifdef VALGRIND
        allocLen += (3 - (iLen % 4));
        /*older versions of valgrind have a problem with strlen inspecting 4-bytes at a time*/
#endif
        CHKmalloc(pBuf = malloc(allocLen));
        /* now copy */
        memcpy(pBuf, pFld, iLen);
        pBuf[iLen] = '\0'; /* terminate it */
        *resstr = pBuf;
    } else {
        ABORT_FINALIZE(RS_RET_FIELD_NOT_FOUND);
    }
finalize_it:
    RETiRet;
}


static rsRetVal doExtractFieldByStr(
    uchar *str, char *delim, const rs_size_t lenDelim, const int matchnbr, uchar **resstr) {
    int iCurrFld;
    int iLen;
    uchar *pBuf;
    uchar *pFld;
    uchar *pFldEnd;
    DEFiRet;

    if (str == NULL || delim == NULL) ABORT_FINALIZE(RS_RET_FIELD_NOT_FOUND);

    /* first, skip to the field in question */
    iCurrFld = 1;
    pFld = str;
    while (pFld != NULL && iCurrFld < matchnbr) {
        if ((pFld = (uchar *)strstr((char *)pFld, delim)) != NULL) {
            pFld += lenDelim;
            ++iCurrFld;
        }
    }
    DBGPRINTF("field() field requested %d, field found %d\n", matchnbr, iCurrFld);

    if (iCurrFld == matchnbr) {
        /* field found, now extract it */
        /* first of all, we need to find the end */
        pFldEnd = (uchar *)strstr((char *)pFld, delim);
        if (pFldEnd == NULL) {
            iLen = strlen((char *)pFld);
        } else { /* found delmiter!  Note that pFldEnd *is* already on
                  * the first delmi char, we don't need that. */
            iLen = pFldEnd - pFld;
        }
        /* we got our end pointer, now do the copy */
        CHKmalloc(pBuf = malloc(iLen + 1));
        /* now copy */
        memcpy(pBuf, pFld, iLen);
        pBuf[iLen] = '\0'; /* terminate it */
        *resstr = pBuf;
    } else {
        ABORT_FINALIZE(RS_RET_FIELD_NOT_FOUND);
    }
finalize_it:
    RETiRet;
}

static void doFunc_re_extract(struct cnffunc *func, struct svar *ret, void *usrptr, wti_t *const pWti) {
    size_t submatchnbr;
    short matchnbr;
    regmatch_t pmatch[50];
    int bMustFree;
    es_str_t *estr = NULL; /* init just to keep compiler happy */
    char *str;
    struct svar r[CNFFUNC_MAX_ARGS];
    int iLenBuf;
    unsigned iOffs;
    short iTry = 0;
    uchar bFound = 0;
    iOffs = 0;
    sbool bHadNoMatch = 0;

    cnfexprEval(func->expr[0], &r[0], usrptr, pWti);
    /* search string is already part of the compiled regex, so we don't
     * need it here!
     */
    cnfexprEval(func->expr[2], &r[2], usrptr, pWti);
    cnfexprEval(func->expr[3], &r[3], usrptr, pWti);
    str = (char *)var2CString(&r[0], &bMustFree);
    matchnbr = (short)var2Number(&r[2], NULL);
    submatchnbr = (size_t)var2Number(&r[3], NULL);
    if (submatchnbr >= sizeof(pmatch) / sizeof(regmatch_t)) {
        DBGPRINTF("re_extract() submatch %zd is too large\n", submatchnbr);
        bHadNoMatch = 1;
        goto finalize_it;
    }

    /* first see if we find a match, iterating through the series of
     * potential matches over the string.
     */
    while (!bFound) {
        int iREstat;
        iREstat = regexp.regexec(func->funcdata, (char *)(str + iOffs), submatchnbr + 1, pmatch, 0);
        DBGPRINTF("re_extract: regexec return is %d\n", iREstat);
        if (iREstat == 0) {
            if (pmatch[0].rm_so == -1) {
                DBGPRINTF("oops ... start offset of successful regexec is -1\n");
                break;
            }
            if (iTry == matchnbr) {
                bFound = 1;
            } else {
                DBGPRINTF("re_extract: regex found at offset %d, new offset %d, tries %d\n", iOffs,
                          (int)(iOffs + pmatch[0].rm_eo), iTry);
                if (pmatch[0].rm_eo == 0) {
                    if (str[iOffs] == '\0') {
                        break;
                    }
                    iOffs++;
                } else {
                    iOffs += pmatch[0].rm_eo;
                }
                ++iTry;
            }
        } else {
            break;
        }
    }
    DBGPRINTF("re_extract: regex: end search, found %d\n", bFound);
    if (!bFound) {
        bHadNoMatch = 1;
        goto finalize_it;
    } else {
        /* Match- but did it match the one we wanted? */
        /* we got no match! */
        if (pmatch[submatchnbr].rm_so == -1) {
            bHadNoMatch = 1;
            goto finalize_it;
        }
        /* OK, we have a usable match - we now need to malloc pB */
        iLenBuf = pmatch[submatchnbr].rm_eo - pmatch[submatchnbr].rm_so;
        estr = es_newStrFromBuf(str + iOffs + pmatch[submatchnbr].rm_so, iLenBuf);
    }

finalize_it:
    if (bMustFree) free(str);
    varFreeMembers(&r[0]);
    varFreeMembers(&r[2]);
    varFreeMembers(&r[3]);

    if (bHadNoMatch) {
        cnfexprEval(func->expr[4], &r[4], usrptr, pWti);
        estr = var2String(&r[4], &bMustFree);
        varFreeMembersSelectively(&r[4], SKIP_STRING);
        /* Note that we do NOT free the string that was returned/created
         * for r[4]. We pass it to the caller, which in turn frees it.
         * This saves us doing one unnecessary memory alloc & write.
         */
    }
    ret->datatype = 'S';
    ret->d.estr = estr;
    return;
}


/* note that we do not need to evaluate any parameters, as the template pointer
 * is set during initialization().
 * TODO: think if we can keep our buffer; but that may not be trival thinking about
 *       multiple threads.
 */
static void doFunc_exec_template(struct cnffunc *__restrict__ const func,
                                 struct svar *__restrict__ const ret,
                                 void *const usrptr,
                                 wti_t *const pWti __attribute__((unused))) {
    smsg_t *const pMsg = (smsg_t *)usrptr;
    rsRetVal localRet;
    actWrkrIParams_t iparam;

    wtiInitIParam(&iparam);
    localRet = tplToString(func->funcdata, pMsg, &iparam, NULL);
    if (localRet == RS_RET_OK) {
        ret->d.estr = es_newStrFromCStr((char *)iparam.param, iparam.lenStr);
    } else {
        ret->d.estr = es_newStrFromCStr("", 0);
    }
    ret->datatype = 'S';
    free(iparam.param);

    return;
}

static es_str_t *doFuncReplace(struct svar *__restrict__ const operandVal,
                               struct svar *__restrict__ const findVal,
                               struct svar *__restrict__ const replaceWithVal) {
    int freeOperand, freeFind, freeReplacement;
    es_str_t *str = var2String(operandVal, &freeOperand);
    es_str_t *findStr = var2String(findVal, &freeFind);
    es_str_t *replaceWithStr = var2String(replaceWithVal, &freeReplacement);
    uchar *find = es_getBufAddr(findStr);
    uchar *replaceWith = es_getBufAddr(replaceWithStr);
    uint lfind = es_strlen(findStr);
    uint lReplaceWith = es_strlen(replaceWithStr);
    uint lSrc = es_strlen(str);
    uint lDst = 0;
    uchar *src_buff = es_getBufAddr(str);
    uint i, j;
    for (i = j = 0; i <= lSrc; i++, lDst++) {
        if (j == lfind) {
            lDst = lDst - lfind + lReplaceWith;
            j = 0;
            if (lfind == 0) break;
        }
        if (i == lSrc) break;
        if (src_buff[i] == find[j]) {
            j++;
        } else if (j > 0) {
            i -= (j - 1);
            lDst -= (j - 1);
            j = 0;
        }
    }
    es_str_t *res = es_newStr(lDst);
    unsigned char *dest = es_getBufAddr(res);
    uint k, s;
    for (i = j = s = 0; i <= lSrc; i++, s++) {
        if (j == lfind) {
            s -= j;
            for (k = 0; k < lReplaceWith; k++, s++) dest[s] = replaceWith[k];
            j = 0;
            if (lfind == 0) break;
        }
        if (i == lSrc) break;
        if (src_buff[i] == find[j]) {
            j++;
        } else {
            if (j > 0) {
                i -= j;
                s -= j;
                j = 0;
            }
            dest[s] = src_buff[i];
        }
    }
    if (j > 0) {
        for (k = 1; k <= j; k++) dest[s - k] = src_buff[i - k];
    }
    res->lenStr = lDst;
    if (freeOperand) es_deleteStr(str);
    if (freeFind) es_deleteStr(findStr);
    if (freeReplacement) es_deleteStr(replaceWithStr);
    return res;
}


static void ATTR_NONNULL() doFunc_parse_json(struct cnffunc *__restrict__ const func,
                                             struct svar *__restrict__ const ret,
                                             void *const usrptr,
                                             wti_t *const pWti) {
    struct svar srcVal[2];
    int bMustFree;
    int bMustFree2;
    smsg_t *const pMsg = (smsg_t *)usrptr;
    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
    char *jsontext = (char *)var2CString(&srcVal[0], &bMustFree);
    char *container = (char *)var2CString(&srcVal[1], &bMustFree2);
    struct json_object *json;

    int retVal;
    assert(jsontext != NULL);
    assert(container != NULL);
    assert(pMsg != NULL);

    struct json_tokener *const tokener = json_tokener_new();
    if (tokener == NULL) {
        retVal = 1;
        goto finalize_it;
    }
    json = json_tokener_parse_ex(tokener, jsontext, strlen(jsontext));
    if (json == NULL) {
        retVal = RS_SCRIPT_EINVAL;
    } else {
        /* Check for trailing garbage */
        int i = tokener->char_offset;
        while (jsontext[i] != '\0' && isspace((uchar)jsontext[i])) {
            i++;
        }
        if (jsontext[i] != '\0') {
            json_object_put(json);
            json = NULL;
            retVal = RS_SCRIPT_EINVAL;
        } else {
            size_t off = (*container == '$') ? 1 : 0;
            msgAddJSON(pMsg, (uchar *)container + off, json, 0, 0);
            retVal = RS_SCRIPT_EOK;
        }
    }
    wtiSetScriptErrno(pWti, retVal);
    json_tokener_free(tokener);


finalize_it:
    ret->datatype = 'N';
    ret->d.n = retVal;

    if (bMustFree) {
        free(jsontext);
    }
    if (bMustFree2) {
        free(container);
    }
    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);
}


static void ATTR_NONNULL() doFunc_get_property(struct cnffunc *__restrict__ const func,
                                               struct svar *__restrict__ const ret,
                                               void *const usrptr,
                                               wti_t *const pWti) {
    int retVal = RS_SCRIPT_EOK;
    int bMustFree = 0;
    char *expr = NULL;
    struct svar srcVal[2] = {{.d = {0}, .datatype = 0}};
    struct json_object *json = NULL;

    /* ignore string literals */
    if (func->expr[0]->nodetype == 'S') {
        retVal = RS_SCRIPT_EINVAL;
        FINALIZE;
    }

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
    DBGPRINTF("srcval[0] datatype: %c\n", srcVal[0].datatype);
    DBGPRINTF("srcval[1] datatype: %c\n", srcVal[1].datatype);

    switch (srcVal[0].datatype) {
        case 'J': {
            json = srcVal[0].d.json;
            break;
        }
        case 'S': {
            ret->d.estr = es_strdup(srcVal[0].d.estr);
            ret->datatype = 'S';
            FINALIZE;
            break;
        }
        default: {
            ret->d.estr = es_newStrFromCStr("", 1);
            ret->datatype = 'S';
            FINALIZE;
            break;
        }
    }

    switch (json_object_get_type(json)) {
        case json_type_object: {
            expr = (char *)var2CString(&srcVal[1], &bMustFree);
            if (expr && expr[0] == '\0') {
                ret->d.json = json_object_get(json);
                ret->datatype = 'J';
                break;
            }
            if (expr && !json_object_object_get_ex(json, (char *)expr, &ret->d.json)) {
                retVal = RS_SCRIPT_EINVAL;
                FINALIZE;
            }
            if (ret->d.json) {
                ret->d.json = json_object_get(ret->d.json);
                ret->datatype = 'J';
            } else {
                ret->d.estr = es_newStrFromCStr("", 1);
                ret->datatype = 'S';
            }
            break;
        }
        case json_type_array: {
            int success = 0;
            long long index = var2Number(&srcVal[1], &success);
            if (!success || index < 0
#if LLONG_MAX > SIZE_MAX
                || (unsigned long long)index > SIZE_MAX
#endif
                || (size_t)index >= (size_t)json_object_array_length(json)) {
                retVal = RS_SCRIPT_EINVAL;
                FINALIZE;
            }
            ret->d.json = json_object_array_get_idx(json, index);
            if (ret->d.json) {
                ret->d.json = json_object_get(ret->d.json);
                ret->datatype = 'J';
            } else {
                ret->d.estr = es_newStrFromCStr("", 1);
                ret->datatype = 'S';
            }
            break;
        }
        case json_type_boolean:
        case json_type_int: {
            ret->d.n = json_object_get_int64(json);
            ret->datatype = 'N';
            break;
        }
        case json_type_double: {
            ret->d.n = json_object_get_double(json);
            ret->datatype = 'N';
            break;
        }
        case json_type_string: {
            ret->d.estr = es_newStrFromCStr(json_object_get_string(json), json_object_get_string_len(json));
            ret->datatype = 'S';
            break;
        }
        case json_type_null: {
            ret->datatype = 'S';
            ret->d.estr = es_newStrFromCStr("", 1);
            break;
        }
        default:
            LogError(0, RS_RET_INTERNAL_ERROR, "Warning - unhandled json type(%d) !!!!\n", json_object_get_type(json));
            retVal = RS_SCRIPT_EINVAL;
            break;
    }

finalize_it:
    wtiSetScriptErrno(pWti, retVal);

    if (retVal != RS_SCRIPT_EOK) {
        ret->datatype = 'S';
        ret->d.estr = es_newStrFromCStr("", 1);
    }
    if (bMustFree) {
        free(expr);
    }
    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);
}

static unsigned long randomNumberMagnitude(void) {
    const long n = randomNumber();
    if (n >= 0) return (unsigned long)n;
    return (unsigned long)(-(n + 1)) + 1;
}

static void ATTR_NONNULL() doFunct_RandomGen(struct cnffunc *__restrict__ const func,
                                             struct svar *__restrict__ const ret,
                                             void *__restrict__ const usrptr,
                                             wti_t *__restrict__ const pWti) {
    int success = 0;
    struct svar srcVal;
    long long retVal;
    unsigned long x;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    long long max = var2Number(&srcVal, &success);
    if (!success) {
        DBGPRINTF(
            "rainerscript: random(max) didn't get a valid 'max' limit, defaulting random-number "
            "value to 0");
        retVal = 0;
        goto done;
    }
    if (max == 0) {
        DBGPRINTF("rainerscript: random(max) invalid, 'max' is zero, , defaulting random-number value to 0");
        retVal = 0;
        goto done;
    }
    if (max < 0) {
        max = (max == LLONG_MIN) ? LLONG_MAX : -max;
    }
    x = randomNumberMagnitude();
    if (max > MAX_RANDOM_NUMBER) {
        DBGPRINTF(
            "rainerscript: desired random-number range [0 - %lld] "
            "is wider than supported limit of [0 - %d)\n",
            max, MAX_RANDOM_NUMBER);
        LogError(0, RS_RET_VALUE_NOT_SUPPORTED,
                 "rainerscript: desired random-number range [0 - %lld] is wider than supported limit of [0 - %d)", max,
                 MAX_RANDOM_NUMBER);
    }

    retVal = (x % max);
done:
    ret->d.n = retVal;
    ret->datatype = 'N';
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_LTrim(struct cnffunc *__restrict__ const func,
                                         struct svar *__restrict__ const ret,
                                         void *__restrict__ const usrptr,
                                         wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    char *str = (char *)var2CString(&srcVal, &bMustFree);

    const int len = strlen(str);
    int i;
    es_str_t *estr = NULL;

    for (i = 0; i < len; i++) {
        if (str[i] != ' ') {
            break;
        }
    }

    estr = es_newStrFromCStr(str + i, len - i);

    ret->d.estr = estr;
    ret->datatype = 'S';
    varFreeMembers(&srcVal);
    if (bMustFree) free(str);
}

static void ATTR_NONNULL() doFunct_RTrim(struct cnffunc *__restrict__ const func,
                                         struct svar *__restrict__ const ret,
                                         void *__restrict__ const usrptr,
                                         wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    char *str = (char *)var2CString(&srcVal, &bMustFree);

    int len = strlen(str);
    int i;
    es_str_t *estr = NULL;

    for (i = (len - 1); i > 0; i--) {
        if (str[i] != ' ') {
            break;
        }
    }

    if (i > 0 || str[0] != ' ') {
        estr = es_newStrFromCStr(str, (i + 1));
    } else {
        estr = es_newStr(1);
    }

    ret->d.estr = estr;
    ret->datatype = 'S';
    varFreeMembers(&srcVal);
    if (bMustFree) free(str);
}

static void ATTR_NONNULL() doFunct_Getenv(struct cnffunc *__restrict__ const func,
                                          struct svar *__restrict__ const ret,
                                          void *__restrict__ const usrptr,
                                          wti_t *__restrict__ const pWti) {
    /* note: the optimizer shall have replaced calls to getenv()
     * with a constant argument to a single string (once obtained via
     * getenv()). So we do NOT need to check if there is just a
     * string following.
     */
    struct svar srcVal;
    char *envvar;
    es_str_t *estr;
    char *str;
    int bMustFree;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    estr = var2String(&srcVal, &bMustFree);
    str = (char *)es_str2cstr(estr, NULL);
    envvar = getenv(str);
    if (envvar == NULL) {
        ret->d.estr = es_newStr(0);
    } else {
        ret->d.estr = es_newStrFromCStr(envvar, strlen(envvar));
    }
    ret->datatype = 'S';
    if (bMustFree) {
        es_deleteStr(estr);
    }
    varFreeMembers(&srcVal);
    free(str);
}

static void ATTR_NONNULL() doFunct_ToLower(struct cnffunc *__restrict__ const func,
                                           struct svar *__restrict__ const ret,
                                           void *__restrict__ const usrptr,
                                           wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    es_str_t *estr;
    int bMustFree;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    estr = var2String(&srcVal, &bMustFree);
    if (!bMustFree) { /* let caller handle that M) */
        estr = es_strdup(estr);
    }
    es_tolower(estr);
    ret->datatype = 'S';
    ret->d.estr = estr;
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_ToUpper(struct cnffunc *__restrict__ const func,
                                           struct svar *__restrict__ const ret,
                                           void *__restrict__ const usrptr,
                                           wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    es_str_t *estr;
    int bMustFree;
    uchar *p;
    int i, len;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    estr = var2String(&srcVal, &bMustFree);
    if (!bMustFree) { /* let caller handle that M) */
        estr = es_strdup(estr);
    }
    p = es_getBufAddr(estr);
    len = es_strlen(estr);
    for (i = 0; i < len; ++i) {
        p[i] = toupper((int)p[i]);
    }
    ret->datatype = 'S';
    ret->d.estr = estr;
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_CStr(struct cnffunc *__restrict__ const func,
                                        struct svar *__restrict__ const ret,
                                        void *__restrict__ const usrptr,
                                        wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    es_str_t *estr;
    int bMustFree;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    estr = var2String(&srcVal, &bMustFree);
    if (!bMustFree) /* let caller handle that M) */
        estr = es_strdup(estr);
    ret->datatype = 'S';
    ret->d.estr = estr;
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_CNum(struct cnffunc *__restrict__ const func,
                                        struct svar *__restrict__ const ret,
                                        void *__restrict__ const usrptr,
                                        wti_t *__restrict__ const pWti) {
    struct svar srcVal;

    if (func->expr[0]->nodetype == 'N') {
        ret->d.n = ((struct cnfnumval *)func->expr[0])->val;
    } else if (func->expr[0]->nodetype == 'S') {
        ret->d.n = es_str2num(((struct cnfstringval *)func->expr[0])->estr, NULL);
    } else {
        cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
        ret->d.n = var2Number(&srcVal, NULL);
        varFreeMembers(&srcVal);
    }
    ret->datatype = 'N';
    DBGPRINTF("JSONorString: cnum node type %c result %d\n", func->expr[0]->nodetype, (int)ret->d.n);
}

static int rsyslogBoolTextToNumber(const uchar *const str) {
    const uchar *p = str;
    size_t len;

    if (str == NULL) {
        return 0;
    }
    while (isspace((int)*p)) {
        ++p;
    }
    len = ustrlen(p);
    while (len > 0 && isspace((int)p[len - 1])) {
        --len;
    }
    if (len == 0) {
        return 0;
    }
    if (len == 1 && p[0] == '0') {
        return 0;
    }
    if (len == 2 && strncasecmp((char *)p, "no", 2) == 0) {
        return 0;
    }
    if (len == 3 && strncasecmp((char *)p, "off", 3) == 0) {
        return 0;
    }
    if (len == 5 && strncasecmp((char *)p, "false", 5) == 0) {
        return 0;
    }
    return 1;
}

static void ATTR_NONNULL() doFunct_CBool(struct cnffunc *__restrict__ const func,
                                         struct svar *__restrict__ const ret,
                                         void *__restrict__ const usrptr,
                                         wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    uchar *str;
    int bMustFree;

    if (func->expr[0]->nodetype == 'N') {
        ret->d.n = (((struct cnfnumval *)func->expr[0])->val != 0);
    } else if (func->expr[0]->nodetype == 'S') {
        str = (uchar *)es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL);
        if (str == NULL) {
            ret->d.n = 0;
        } else {
            ret->d.n = rsyslogBoolTextToNumber(str);
            free(str);
        }
    } else {
        cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
        if (srcVal.datatype == 'N') {
            ret->d.n = (srcVal.d.n != 0);
        } else {
            str = var2CString(&srcVal, &bMustFree);
            ret->d.n = rsyslogBoolTextToNumber(str);
            if (bMustFree) free(str);
        }
        varFreeMembers(&srcVal);
    }
    ret->datatype = 'N';
    DBGPRINTF("JSONorString: cbool node type %c result %d\n", func->expr[0]->nodetype, (int)ret->d.n);
}

static void ATTR_NONNULL() doFunct_ReMatch(struct cnffunc *__restrict__ const func,
                                           struct svar *__restrict__ const ret,
                                           void *__restrict__ const usrptr,
                                           wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    char *str;
    int retval;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    str = (char *)var2CString(&srcVal, &bMustFree);
    retval = regexp.regexec(func->funcdata, str, 0, NULL, 0);
    if (retval == 0)
        ret->d.n = 1;
    else {
        ret->d.n = 0;
        if (retval != REG_NOMATCH) {
            DBGPRINTF("re_match: regexec returned error %d\n", retval);
        }
    }
    ret->datatype = 'N';
    if (bMustFree) {
        free(str);
    }
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_Ipv42num(struct cnffunc *__restrict__ const func,
                                            struct svar *__restrict__ const ret,
                                            void *__restrict__ const usrptr,
                                            wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    char *str;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    str = (char *)var2CString(&srcVal, &bMustFree);


    unsigned num[4] = {0, 0, 0, 0};
    long long value = -1;
    size_t len = strlen(str);
    int cyc = 0;
    int prevdot = 0;
    int startblank = 0;
    int endblank = 0;
    DBGPRINTF("rainerscript: (ipv42num) arg: '%s'\n", str);
    for (unsigned int i = 0; i < len; i++) {
        switch (str[i]) {
            case '0':
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
            case '8':
            case '9':
                if (endblank == 1) {
                    DBGPRINTF(
                        "rainerscript: (ipv42num) error: wrong IP-Address format "
                        "(invalid space(1))\n");
                    goto done;
                }
                prevdot = 0;
                startblank = 0;
                DBGPRINTF("rainerscript: (ipv42num) cycle: %d\n", cyc);
                num[cyc] = num[cyc] * 10 + (str[i] - '0');
                break;
            case ' ':
                prevdot = 0;
                if (i == 0 || startblank == 1) {
                    startblank = 1;
                    break;
                } else {
                    endblank = 1;
                    break;
                }
            case '.':
                if (endblank == 1) {
                    DBGPRINTF(
                        "rainerscript: (ipv42num) error: wrong IP-Address format "
                        "(inalid space(2))\n");
                    goto done;
                }
                startblank = 0;
                if (prevdot == 1) {
                    DBGPRINTF(
                        "rainerscript: (ipv42num) error: wrong IP-Address format "
                        "(two dots after one another)\n");
                    goto done;
                }
                prevdot = 1;
                cyc++;
                if (cyc > 3) {
                    DBGPRINTF(
                        "rainerscript: (ipv42num) error: wrong IP-Address format "
                        "(too many dots)\n");
                    goto done;
                }
                break;
            default:
                DBGPRINTF("rainerscript: (ipv42num) error: wrong IP-Address format (invalid charakter)\n");
                goto done;
        }
    }
    if (cyc != 3) {
        DBGPRINTF("rainerscript: (ipv42num) error: wrong IP-Address format (wrong number of dots)\n");
        goto done;
    }
    value = num[0] * 256 * 256 * 256 + num[1] * 256 * 256 + num[2] * 256 + num[3];
done:
    DBGPRINTF("rainerscript: (ipv42num): return value:'%lld'\n", value);
    ret->datatype = 'N';
    ret->d.n = value;
    varFreeMembers(&srcVal);
    if (bMustFree) free(str);
}

static void ATTR_NONNULL() doFunct_Int2Hex(struct cnffunc *__restrict__ const func,
                                           struct svar *__restrict__ const ret,
                                           void *__restrict__ const usrptr,
                                           wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int success = 0;
    char str[18];
    es_str_t *estr = NULL;
    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    long long num = var2Number(&srcVal, &success);

    if (!success) {
        DBGPRINTF("rainerscript: (int2hex) couldn't access number\n");
        estr = es_newStrFromCStr("NAN", strlen("NAN"));
        goto done;
    }

    snprintf(str, 18, "%llx", num);
    estr = es_newStrFromCStr(str, strlen(str));

done:
    ret->d.estr = estr;
    ret->datatype = 'S';
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_Replace(struct cnffunc *__restrict__ const func,
                                           struct svar *__restrict__ const ret,
                                           void *__restrict__ const usrptr,
                                           wti_t *__restrict__ const pWti) {
    struct svar srcVal[3];

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
    cnfexprEval(func->expr[2], &srcVal[2], usrptr, pWti);
    ret->d.estr = doFuncReplace(&srcVal[0], &srcVal[1], &srcVal[2]);
    ret->datatype = 'S';
    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);
    varFreeMembers(&srcVal[2]);
}

static void ATTR_NONNULL() doFunct_Wrap(struct cnffunc *__restrict__ const func,
                                        struct svar *__restrict__ const ret,
                                        void *__restrict__ const usrptr,
                                        wti_t *__restrict__ const pWti) {
    struct svar sourceVal;
    struct svar wrapperVal;
    struct svar escaperVal;
    int freeSource, freeWrapper;
    es_str_t *sourceStr;

    cnfexprEval(func->expr[0], &sourceVal, usrptr, pWti);
    cnfexprEval(func->expr[1], &wrapperVal, usrptr, pWti);
    if (func->nParams == 3) {
        cnfexprEval(func->expr[2], &escaperVal, usrptr, pWti);
        sourceStr = doFuncReplace(&sourceVal, &wrapperVal, &escaperVal);
        freeSource = 1;

    } else {
        sourceStr = var2String(&sourceVal, &freeSource);
    }
    es_str_t *wrapperStr = var2String(&wrapperVal, &freeWrapper);
    uchar *src = es_getBufAddr(sourceStr);
    uchar *wrapper = es_getBufAddr(wrapperStr);
    uint lWrapper = es_strlen(wrapperStr);
    uint lSrc = es_strlen(sourceStr);
    uint totalLen = lSrc + 2 * lWrapper;
    es_str_t *res = es_newStr(totalLen);
    uchar *resBuf = es_getBufAddr(res);
    memcpy(resBuf, wrapper, lWrapper);
    memcpy(resBuf + lWrapper, src, lSrc);
    memcpy(resBuf + lSrc + lWrapper, wrapper, lWrapper);
    res->lenStr = totalLen;
    if (freeSource) {
        es_deleteStr(sourceStr);
    }
    if (freeWrapper) {
        es_deleteStr(wrapperStr);
    }

    ret->d.estr = res;
    ret->datatype = 'S';
    varFreeMembers(&sourceVal);
    varFreeMembers(&wrapperVal);
    if (func->nParams == 3) varFreeMembers(&escaperVal);
}

static void ATTR_NONNULL() doFunct_StrLen(struct cnffunc *__restrict__ const func,
                                          struct svar *__restrict__ const ret,
                                          void *__restrict__ const usrptr,
                                          wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    es_str_t *estr;

    if (func->expr[0]->nodetype == 'S') {
        /* if we already have a string, we do not need to
         * do one more recursive call.
         */
        ret->d.n = es_strlen(((struct cnfstringval *)func->expr[0])->estr);
    } else {
        cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
        estr = var2String(&srcVal, &bMustFree);
        ret->d.n = es_strlen(estr);
        if (bMustFree) {
            es_deleteStr(estr);
        }
        varFreeMembers(&srcVal);
    }
    ret->datatype = 'N';
}

static void ATTR_NONNULL()
    doFunct_Substring(struct cnffunc *__restrict__ const func,
                      struct svar *__restrict__ const ret,
                      void *__restrict__ const usrptr,
                      wti_t *__restrict__ const pWti) {  // TODO: generalize parameter getter? jgerhards, 2018-02-26
    int bMustFree;
    struct svar srcVal[3];

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
    cnfexprEval(func->expr[2], &srcVal[2], usrptr, pWti);
    es_str_t *es = var2String(&srcVal[0], &bMustFree);
    const int lenSrcStr = es_strlen(es);
    long long startNum = var2Number(&srcVal[1], NULL);
    long long subStrLenNum = var2Number(&srcVal[2], NULL);
    int start;
    int subStrLen;
    if (startNum < 0) {
        start = 0;
    } else if (startNum > INT_MAX) {
        start = lenSrcStr;
    } else {
        start = (int)startNum;
    }
    if (subStrLenNum < INT_MIN) {
        subStrLen = INT_MIN;
    } else if (subStrLenNum > INT_MAX) {
        subStrLen = INT_MAX;
    } else {
        subStrLen = (int)subStrLenNum;
    }
    if (start >= lenSrcStr) {
        /* begin PAST the source string - ensure nothing is copied at all */
        start = subStrLen = 0;
    } else {
        if (subStrLen < 0) {
            subStrLen = lenSrcStr + subStrLen; /* "add" negative offset! */
            if (subStrLen < 0) {
                subStrLen = 0;
            }
        }
        if (subStrLen > (lenSrcStr - start)) {
            subStrLen = lenSrcStr - start;
        }
    }

    ret->datatype = 'S';
    ret->d.estr = es_newStrFromSubStr(es, (es_size_t)start, (es_size_t)subStrLen);
    if (bMustFree) es_deleteStr(es);
    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);
    varFreeMembers(&srcVal[2]);
}

static void ATTR_NONNULL() doFunct_Field(struct cnffunc *__restrict__ const func,
                                         struct svar *__restrict__ const ret,
                                         void *__restrict__ const usrptr,
                                         wti_t *__restrict__ const pWti) {
    struct svar srcVal[3];
    int bMustFree;
    char *str;
    uchar *resStr;
    int matchnbr;
    int delim;
    rsRetVal localRet;

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
    cnfexprEval(func->expr[2], &srcVal[2], usrptr, pWti);
    str = (char *)var2CString(&srcVal[0], &bMustFree);
    matchnbr = var2Number(&srcVal[2], NULL);
    if (srcVal[1].datatype == 'S') {
        char *delimstr;
        delimstr = (char *)es_str2cstr(srcVal[1].d.estr, NULL);
        localRet = doExtractFieldByStr((uchar *)str, delimstr, es_strlen(srcVal[1].d.estr), matchnbr, &resStr);
        free(delimstr);
    } else {
        delim = var2Number(&srcVal[1], NULL);
        localRet = doExtractFieldByChar((uchar *)str, (char)delim, matchnbr, &resStr);
    }
    if (localRet == RS_RET_OK) {
        ret->d.estr = es_newStrFromCStr((char *)resStr, strlen((char *)resStr));
        free(resStr);
    } else if (localRet == RS_RET_FIELD_NOT_FOUND) {
        ret->d.estr = es_newStrFromCStr("***FIELD NOT FOUND***", sizeof("***FIELD NOT FOUND***") - 1);
    } else {
        ret->d.estr =
            es_newStrFromCStr("***ERROR in field() FUNCTION***", sizeof("***ERROR in field() FUNCTION***") - 1);
    }
    ret->datatype = 'S';
    if (bMustFree) free(str);
    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);
    varFreeMembers(&srcVal[2]);
}

static void ATTR_NONNULL() doFunct_Prifilt(struct cnffunc *__restrict__ const func,
                                           struct svar *__restrict__ const ret,
                                           void *__restrict__ const usrptr,
                                           wti_t *const pWti __attribute__((unused))) {
    struct funcData_prifilt *pPrifilt;

    pPrifilt = (struct funcData_prifilt *)func->funcdata;
    if ((pPrifilt->pmask[((smsg_t *)usrptr)->iFacility] == TABLE_NOPRI) ||
        ((pPrifilt->pmask[((smsg_t *)usrptr)->iFacility] & (1 << ((smsg_t *)usrptr)->iSeverity)) == 0))
        ret->d.n = 0;
    else
        ret->d.n = 1;
    ret->datatype = 'N';
}

static void ATTR_NONNULL() doFunct_Lookup(struct cnffunc *__restrict__ const func,
                                          struct svar *__restrict__ const ret,
                                          void *__restrict__ const usrptr,
                                          wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    lookup_key_t key;
    uint8_t lookup_key_type;
    lookup_ref_t *lookup_table_ref;
    lookup_t *lookup_table;
    int bMustFree;

    ret->datatype = 'S';
    if (func->funcdata == NULL) {
        ret->d.estr = es_newStrFromCStr("TABLE-NOT-FOUND", sizeof("TABLE-NOT-FOUND") - 1);
        return;
    }
    cnfexprEval(func->expr[1], &srcVal, usrptr, pWti);
    lookup_table_ref = (lookup_ref_t *)func->funcdata;
    pthread_rwlock_rdlock(&lookup_table_ref->rwlock);
    lookup_table = lookup_table_ref->self;
    if (lookup_table != NULL) {
        lookup_key_type = lookup_table->key_type;
        bMustFree = 0;
        if (lookup_key_type == LOOKUP_KEY_TYPE_STRING) {
            key.k_str = (uchar *)var2CString(&srcVal, &bMustFree);
        } else if (lookup_key_type == LOOKUP_KEY_TYPE_UINT) {
            key.k_uint = var2Number(&srcVal, NULL);
        } else {
            DBGPRINTF("program error in %s:%d: lookup_key_type unknown\n", __FILE__, __LINE__);
            key.k_uint = 0;
        }
        ret->d.estr = lookupKeyLocked((lookup_ref_t *)func->funcdata, key);
        if (bMustFree) {
            free(key.k_str);
        }
    } else {
        ret->d.estr = es_newStrFromCStr("", 1);
    }
    pthread_rwlock_unlock(&lookup_table_ref->rwlock);
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_DynInc(struct cnffunc *__restrict__ const func,
                                          struct svar *__restrict__ const ret,
                                          void *__restrict__ const usrptr,
                                          wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    char *str;

    ret->datatype = 'N';
    if (func->funcdata == NULL) {
        ret->d.n = -1;
        return;
    }
    cnfexprEval(func->expr[1], &srcVal, usrptr, pWti);
    str = (char *)var2CString(&srcVal, &bMustFree);
    ret->d.n = dynstats_inc(func->funcdata, (uchar *)str);
    if (bMustFree) free(str);
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_FormatTime(struct cnffunc *__restrict__ const func,
                                              struct svar *__restrict__ const ret,
                                              void *__restrict__ const usrptr,
                                              wti_t *__restrict__ const pWti) {
    struct svar srcVal[2];
    int bMustFree;
    char *str;
    int retval;
    long long unixtime;
    enum { resMax = 64 };
    char result[resMax];
    char *formatstr = NULL;

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);

    unixtime = var2Number(&srcVal[0], &retval);

    // Make sure that the timestamp we got can fit into
    // time_t on older systems.
    if (sizeof(time_t) == sizeof(int)) {
        if (unixtime < INT_MIN || unixtime > INT_MAX) {
            LogMsg(0, RS_RET_VAL_OUT_OF_RANGE, LOG_WARNING,
                   "Timestamp value %lld is out of range for this system (time_t is "
                   "32bits)!\n",
                   unixtime);
            retval = 0;
        }
    }

    // We want the string form too so we can return it as the
    // default if we run into problems parsing the number.
    str = (char *)var2CString(&srcVal[0], &bMustFree);
    formatstr = (char *)es_str2cstr(srcVal[1].d.estr, NULL);

    ret->datatype = 'S';

    if (objUse(datetime, CORE_COMPONENT) != RS_RET_OK) {
        ret->d.estr = es_newStr(0);
    } else {
        if (!retval || datetime.formatUnixTimeFromTime_t(unixtime, formatstr, result, resMax) == -1) {
            const size_t src_len = strlen(str);
            const size_t copy_len = src_len < (size_t)(resMax - 1) ? src_len : (size_t)(resMax - 1);
            memcpy(result, str, copy_len);
            result[copy_len] = '\0';
        }
        ret->d.estr = es_newStrFromCStr(result, strlen(result));
    }

    if (bMustFree) {
        free(str);
    }
    free(formatstr);

    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);
}

/*
 * Uses the given (current) year/month to decide which year
 * the incoming month likely belongs in.
 *
 * cy - Current Year (actual)
 * cm - Current Month (actual)
 * im - "Incoming" Month
 */
static int estimateYear(int cy, int cm, int im) {
    im += 12;

    if ((im - cm) == 1) {
        if (cm == 12 && im == 13) return cy + 1;
    }

    if ((im - cm) > 13) return cy - 1;

    return cy;
}

static void ATTR_NONNULL() doFunct_ParseTime(struct cnffunc *__restrict__ const func,
                                             struct svar *__restrict__ const ret,
                                             void *__restrict__ const usrptr,
                                             wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    char *str = (char *)var2CString(&srcVal, &bMustFree);
    ret->datatype = 'N';
    ret->d.n = 0;
    wtiSetScriptErrno(pWti, RS_SCRIPT_EOK);

    if (objUse(datetime, CORE_COMPONENT) == RS_RET_OK) {
        struct syslogTime s;
        int len = strlen(str);
        uchar *pszTS = (uchar *)str;
        memset(&s, 0, sizeof(struct syslogTime));
        // Attempt to parse the date/time string
        if (datetime.ParseTIMESTAMP3339(&s, (uchar **)&pszTS, &len) == RS_RET_OK) {
            ret->d.n = datetime.syslogTime2time_t(&s);
            DBGPRINTF("parse_time: RFC3339 format found\n");
        } else if (datetime.ParseTIMESTAMP3164(&s, (uchar **)&pszTS, &len, NO_PARSE3164_TZSTRING,
                                               NO_PERMIT_YEAR_AFTER_TIME) == RS_RET_OK) {
            time_t t = time(NULL);
            struct tm tm;
            gmtime_r(&t, &tm);  // Get the current UTC date
            // Since properly formatted RFC 3164 timestamps do not have a YEAR
            // specified, we have to assume one that seems reasonable - SW.
            s.year = estimateYear(tm.tm_year + 1900, tm.tm_mon + 1, s.month);
            ret->d.n = datetime.syslogTime2time_t(&s);
            DBGPRINTF("parse_time: RFC3164 format found\n");
        } else {
            DBGPRINTF("parse_time: no valid format found\n");
            wtiSetScriptErrno(pWti, RS_SCRIPT_EINVAL);
        }
    }

    if (bMustFree) {
        free(str);
    }
    varFreeMembers(&srcVal);
}

static void ATTR_NONNULL() doFunct_ParseTimeLocalTz(struct cnffunc *__restrict__ const func,
                                                    struct svar *__restrict__ const ret,
                                                    void *__restrict__ const usrptr,
                                                    wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    char *str = (char *)var2CString(&srcVal, &bMustFree);
    ret->datatype = 'N';
    ret->d.n = 0;
    wtiSetScriptErrno(pWti, RS_SCRIPT_EOK);

    if (str != NULL) {
        if (objUse(datetime, CORE_COMPONENT) == RS_RET_OK) {
            struct syslogTime s;
            int len = strlen(str);
            uchar *pszTS = (uchar *)str;
            memset(&s, 0, sizeof(struct syslogTime));
            // Attempt to parse the date/time string
            if (datetime.ParseTIMESTAMP3339(&s, (uchar **)&pszTS, &len) == RS_RET_OK) {
                ret->d.n = datetime.syslogTime2time_tLocalTZ(&s);
                DBGPRINTF("parse_time: RFC3339 format found\n");
            } else if (datetime.ParseTIMESTAMP3164(&s, (uchar **)&pszTS, &len, NO_PARSE3164_TZSTRING,
                                                   NO_PERMIT_YEAR_AFTER_TIME) == RS_RET_OK) {
                time_t t = time(NULL);
                struct tm tm;
                gmtime_r(&t, &tm);  // Get the current UTC date
                // Since properly formatted RFC 3164 timestamps do not have a YEAR
                // specified, we have to assume one that seems reasonable - SW.
                s.year = estimateYear(tm.tm_year + 1900, tm.tm_mon + 1, s.month);
                ret->d.n = datetime.syslogTime2time_tLocalTZ(&s);
                DBGPRINTF("parse_time: RFC3164 format found\n");
            } else {
                DBGPRINTF("parse_time: no valid format found\n");
                wtiSetScriptErrno(pWti, RS_SCRIPT_EINVAL);
            }
        }
    } else {
        wtiSetScriptErrno(pWti, RS_SCRIPT_EINVAL);
    }

    if (bMustFree) {
        free(str);
    }
    varFreeMembers(&srcVal);
}

static int ATTR_NONNULL(1, 3, 4) doFunc_is_time(const char *__restrict__ const str,
                                                const char *__restrict__ const fmt,
                                                struct svar *__restrict__ const r,
                                                wti_t *pWti) {
    assert(str != NULL);
    assert(r != NULL);
    assert(pWti != NULL);

    int ret = 0;

    wtiSetScriptErrno(pWti, RS_SCRIPT_EOK);

    if (objUse(datetime, CORE_COMPONENT) == RS_RET_OK) {
        struct syslogTime s;
        int len = strlen(str);
        uchar *pszTS = (uchar *)str;

        int numFormats = 3;
        dateTimeFormat_t formats[] = {DATE_RFC3164, DATE_RFC3339, DATE_UNIX};
        dateTimeFormat_t pf[] = {DATE_INVALID};
        dateTimeFormat_t *p = formats;

        // Check if a format specifier was explicitly provided
        if (fmt != NULL) {
            numFormats = 1;
            *pf = getDateTimeFormatFromStr(fmt);
            p = pf;
        }

        // Enumerate format specifier options, looking for the first match
        for (int i = 0; i < numFormats; i++) {
            dateTimeFormat_t f = p[i];

            if (f == DATE_RFC3339) {
                if (datetime.ParseTIMESTAMP3339(&s, (uchar **)&pszTS, &len) == RS_RET_OK) {
                    DBGPRINTF("is_time: RFC3339 format found.\n");
                    ret = 1;
                    break;
                }
            } else if (f == DATE_RFC3164) {
                if (datetime.ParseTIMESTAMP3164(&s, (uchar **)&pszTS, &len, NO_PARSE3164_TZSTRING,
                                                NO_PERMIT_YEAR_AFTER_TIME) == RS_RET_OK) {
                    DBGPRINTF("is_time: RFC3164 format found.\n");
                    ret = 1;
                    break;
                }
            } else if (f == DATE_UNIX) {
                int result;
                var2Number(r, &result);

                if (result) {
                    DBGPRINTF("is_time: UNIX format found.\n");
                    ret = 1;
                    break;
                }
            } else {
                DBGPRINTF("is_time: %s is not a valid date/time format specifier!\n", fmt);
                break;
            }
        }
    }

    // If not a valid date/time string, set 'errno'
    if (ret == 0) {
        DBGPRINTF("is_time: Invalid date-time string: %s.\n", str);
        wtiSetScriptErrno(pWti, RS_SCRIPT_EINVAL);
    }

    return ret;
}

static void ATTR_NONNULL() doFunct_IsTime(struct cnffunc *__restrict__ const func,
                                          struct svar *__restrict__ const ret,
                                          void *__restrict__ const usrptr,
                                          wti_t *__restrict__ const pWti) {
    struct svar srcVal[2];
    int bMustFree;
    int bMustFree2;
    char *fmt = NULL;

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    char *str = (char *)var2CString(&srcVal[0], &bMustFree);

    bMustFree2 = 0;

    // Check if the optional 2nd parameter was provided
    if (func->nParams == 2) {
        cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
        fmt = (char *)var2CString(&srcVal[1], &bMustFree2);
    }

    ret->datatype = 'N';
    ret->d.n = doFunc_is_time(str, fmt, &srcVal[0], pWti);

    if (bMustFree) {
        free(str);
    }
    if (bMustFree2) {
        free(fmt);
    }
    varFreeMembers(&srcVal[0]);
    if (func->nParams == 2) {
        varFreeMembers(&srcVal[1]);
    }
}

static void ATTR_NONNULL() doFunct_ScriptError(struct cnffunc *const func __attribute__((unused)),
                                               struct svar *__restrict__ const ret,
                                               void *const usrptr __attribute__((unused)),
                                               wti_t *__restrict__ const pWti) {
    ret->datatype = 'N';
    ret->d.n = wtiGetScriptErrno(pWti);
    DBGPRINTF("script_error() is %d\n", (int)ret->d.n);
}

static void ATTR_NONNULL() doFunct_PreviousActionSuspended(struct cnffunc *const func __attribute__((unused)),
                                                           struct svar *__restrict__ const ret,
                                                           void *const usrptr __attribute__((unused)),
                                                           wti_t *__restrict__ const pWti) {
    ret->datatype = 'N';
    ret->d.n = wtiGetPrevWasSuspended(pWti);
    DBGPRINTF("previous_action_suspended() is %d\n", (int)ret->d.n);
}

static void ATTR_NONNULL() doFunct_num2ipv4(struct cnffunc *__restrict__ const func,
                                            struct svar *__restrict__ const ret,
                                            void *__restrict__ const usrptr,
                                            wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    int success = 0;
    long long num = var2Number(&srcVal, &success);
    varFreeMembers(&srcVal);

    int numip[4];
    char str[16];
    size_t len;
    DBGPRINTF("rainrescript: (num2ipv4) var2Number output: '%lld\n'", num);
    if (!success) {
        DBGPRINTF("rainerscript: (num2ipv4) couldn't access number\n");
        len = snprintf(str, 16, "-1");
        goto done;
    }
    if (num < 0 || num > 4294967295) {
        DBGPRINTF(
            "rainerscript: (num2ipv4) invalid number(too big/negative); does "
            "not represent IPv4 address\n");
        len = snprintf(str, 16, "-1");
        goto done;
    }
    for (int i = 0; i < 4; i++) {
        numip[i] = num % 256;
        num = num / 256;
    }
    DBGPRINTF("rainerscript: (num2ipv4) Numbers: 1:'%d' 2:'%d' 3:'%d' 4:'%d'\n", numip[0], numip[1], numip[2],
              numip[3]);
    len = snprintf(str, 16, "%d.%d.%d.%d", numip[3], numip[2], numip[1], numip[0]);
done:
    DBGPRINTF("rainerscript: (num2ipv4) ipv4-Address: %s, length: %zu\n", str, len);
    ret->d.estr = es_newStrFromCStr(str, len);
    ret->datatype = 'S';
}


/* Perform a function call. This has been moved out of cnfExprEval in order
 * to keep the code small and easier to maintain.
 */
static void ATTR_NONNULL() doFuncCall(struct cnffunc *__restrict__ const func,
                                      struct svar *__restrict__ const ret,
                                      void *__restrict__ const usrptr,
                                      wti_t *__restrict__ const pWti) {
    if (Debug) {
        char *fname = es_str2cstr(func->fname, NULL);
        DBGPRINTF("rainerscript: executing function id %s\n", fname);
        free(fname);
    }
    if (func->fPtr == NULL) {
        ret->datatype = 'N';
        ret->d.n = 0;
    } else {
        func->fPtr(func, ret, usrptr, pWti);
    }
}


/* Perform the special "exists()" function to check presence of a variable.
 */
static int ATTR_NONNULL()
    evalFuncExists(struct cnffuncexists *__restrict__ const fexists, void *__restrict__ const usrptr) {
    int r = 0;
    rsRetVal localRet;

    if (fexists->prop.id == PROP_CEE || fexists->prop.id == PROP_LOCAL_VAR || fexists->prop.id == PROP_GLOBAL_VAR) {
        localRet = msgCheckVarExists((smsg_t *)usrptr, &fexists->prop);
        if (localRet == RS_RET_OK) {
            r = 1;
        }
    }

    return r;
}

/* Base64 function to predict decoded length (for allocation) from Apache */
static ATTR_NONNULL() int base64_decode_len(const char *str) {
    size_t len;

    len = strlen(str);
    while (len && str[len - 1] == '=') {
        len--;
    }

    return len * 3 / 4;
}

/* The following code comes from
 * https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C_2 */
static const unsigned char base64_table[] = {
    66, 66, 66, 66, 66, 66, 66, 66, 66, 64, 64, 66, 66, 64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
    66, 66, 66, 64, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 62, 66, 66, 66, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61,
    66, 66, 66, 65, 66, 66, 66, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
    22, 23, 24, 25, 66, 66, 66, 66, 66, 66, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
    45, 46, 47, 48, 49, 50, 51, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66,
    66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66};

/* From https://en.wikibooks.org/wiki/Algorithm_Implementation/Miscellaneous/Base64#C_2
 * This code is public domain.
 * This solution has been optimized using pointer math and a look-up table.
 * This algorithm handles multiple encoding formats: with and without line breaks,
 * with and without whitespace,
 * and with and without padding characters.
 * */
static int base64_decode(char *in, size_t inLen, unsigned char *out, size_t *outLen) {
    char *end = in + inLen;
    char iter = 0;
    uint32_t buf = 0;
    size_t len = 0;

    while (in < end) {
        unsigned char c = base64_table[(unsigned char)*in++];

        switch (c) {
            case 64:
                continue; /* skip whitespace */
            case 66:
                return 1; /* invalid input, return error */
            case 65: /* = char, pad character, end of data */
                in = end;
                continue;
            default:
                buf = buf << 6 | c;
                iter++;  // increment the number of iteration
                /* If the buffer is full, split it into bytes */
                if (iter == 4) {
                    if ((len += 3) > *outLen) return 1; /* buffer overflow */
                    *(out++) = (buf >> 16) & 255;
                    *(out++) = (buf >> 8) & 255;
                    *(out++) = buf & 255;
                    buf = 0;
                    iter = 0;
                }
        }
    }

    if (iter == 3) {
        if ((len += 2) > *outLen) return 1; /* buffer overflow */
        *(out++) = (buf >> 10) & 255;
        *(out++) = (buf >> 2) & 255;
    } else if (iter == 2) {
        if (++len > *outLen) return 1; /* buffer overflow */
        *(out++) = (buf >> 4) & 255;
    }

    *outLen = len; /* modify to reflect the actual output size */
    return 0;
}

static void ATTR_NONNULL() doFunct_Base64Dec(struct cnffunc *__restrict__ const func,
                                             struct svar *__restrict__ const ret,
                                             void *__restrict__ const usrptr,
                                             wti_t *__restrict__ const pWti) {
    struct svar srcVal;
    int bMustFree;
    char *cstr = NULL;
    unsigned char *res = NULL;
    size_t len = 0;
    ret->d.estr = NULL;

    if (func->expr[0]->nodetype == 'S') {
        /* if we already have a string, we do not need to
         * do one more recursive call.
         */
        cstr = (char *)es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL);
        bMustFree = 1;
    } else {
        // Otherwise, retrieve the value from the specified value name
        cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
        cstr = (char *)var2CString(&srcVal, &bMustFree);
        varFreeMembers(&srcVal);
    }
    DBGPRINTF("rainerscript: (base64_dec) String to decode = '%s' \n", cstr);
    len = base64_decode_len((const char *)cstr);
    if (len == 0) {
        if (strlen(cstr) != 0) {
            DBGPRINTF("base64_dec: Failed to determine decoded payload length of '%s' \n", cstr);
        }
        goto finalize_it;
    }
    DBGPRINTF("rainerscript: (base64_dec) Predicted decoded data length : '%lu' \n", (long unsigned int)len);
    res = malloc(len + 1);
    if (res == NULL) goto finalize_it;
    if (base64_decode(cstr, strlen(cstr), res, &len)) {
        DBGPRINTF("rainerscript: (base64_dec) Failed to decode base64 data '%s'", cstr);
        goto finalize_it;
    }
    res[len] = '\0';
    // Beware, decoded data (for instance \x01\x02) will be ''
    DBGPRINTF("rainerscript: (base64_dec) Decoded data = '%.*s' \n", (int)len, res);
    ret->d.estr = es_newStrFromCStr((char *)res, len);

finalize_it:
    ret->datatype = 'S';
    if (ret->d.estr == NULL) {
        ret->d.estr = es_newStr(0);
    }
    free(res);
    if (bMustFree) {
        free(cstr);
    }
}

static void ATTR_NONNULL() doFunct_split(struct cnffunc *__restrict__ const func,
                                         struct svar *__restrict__ const ret,
                                         void *const usrptr,
                                         wti_t *const pWti) {
    struct svar srcVal[2];
    int bMustFree = 0;
    int bMustFree2 = 0;

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);

    char *inputStr = (char *)var2CString(&srcVal[0], &bMustFree);
    char *separator = (char *)var2CString(&srcVal[1], &bMustFree2);

    struct json_object *jsonArray = json_object_new_array();

    if (jsonArray == NULL) {
        goto done;
    }

    if (inputStr == NULL || separator == NULL) {
        json_object_put(jsonArray);
        jsonArray = NULL;
        goto done;
    }

    if (strlen(separator) == 0) {
        goto done;
    }

    char *workStr = strdup(inputStr);
    if (workStr == NULL) {
        json_object_put(jsonArray);
        jsonArray = NULL;
        goto done;
    }

    const size_t sepLen = strlen(separator);
    char *p = workStr;

    while (1) {
        char *next_sep = strstr(p, separator);
        if (next_sep != NULL) {
            *next_sep = '\0';
        }
        struct json_object *jsonStr = json_object_new_string(p);
        if (jsonStr == NULL) {
            json_object_put(jsonArray);
            jsonArray = NULL;
            break;
        }
        if (json_object_array_add(jsonArray, jsonStr) != 0) {
            json_object_put(jsonStr);
            json_object_put(jsonArray);
            jsonArray = NULL;
            break;
        }
        if (next_sep == NULL) {
            break;
        }
        p = next_sep + sepLen;
    }

    free(workStr);

done:
    ret->datatype = 'J';
    ret->d.json = jsonArray;

    if (bMustFree) free(inputStr);
    if (bMustFree2) free(separator);
    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);
}

/* Append s to *pOut with CEF header escaping:
 *   backslash  ->  \\
 *   pipe       ->  \|
 * Used for all seven pipe-delimited header fields. */
static void cef_escape_header(es_str_t **pOut, const char *s) {
    if (s == NULL) return;
    while (*s != '\0') {
        if (*s == '\\' || *s == '|') es_addChar(pOut, '\\');
        es_addChar(pOut, (unsigned char)*s);
        ++s;
    }
}

/* Append s to *pOut with CEF deviceEventClassId escaping.
 * The spec requires additional escaping beyond normal header fields:
 *   backslash  ->  \\
 *   pipe       ->  \|
 *   equals     ->  \=
 *   percent    ->  \%
 *   hash       ->  \#
 */
static void cef_escape_eventclassid(es_str_t **pOut, const char *s) {
    if (s == NULL) return;
    while (*s != '\0') {
        if (*s == '\\' || *s == '|' || *s == '=' || *s == '%' || *s == '#') es_addChar(pOut, '\\');
        es_addChar(pOut, (unsigned char)*s);
        ++s;
    }
}

/**
 * tocef(version, vendor, product, devversion, eventclassid, name, severity, extensions)
 *
 * Builds a CEF (Common Event Format) header string from eight positional
 * arguments and returns it as a RainerScript string variable.  The seven
 * header fields are automatically escaped per the CEF spec (backslash and
 * pipe are escaped with a leading backslash).  The extensions argument is
 * appended verbatim; callers are responsible for escaping extension values
 * (backslash -> \\, equals -> \=, newline -> \n, CR -> \r).
 *
 * Example:
 *   set $!cef = tocef("0", "MyVendor", "rsyslog", "1.0",
 *                     $syslogtag, $msg, "5",
 *                     "src=" & $fromhost-ip & " spt=" & $fromhost);
 *
 * Produces:
 *   CEF:0|MyVendor|rsyslog|1.0|<tag>|<msg>|5|src=... spt=...
 */
static void ATTR_NONNULL() doFunct_tocef(struct cnffunc *__restrict__ const func,
                                         struct svar *__restrict__ const ret,
                                         void *const usrptr,
                                         wti_t *const pWti) {
    struct svar srcVal[8];
    int bMustFree[8] = {0, 0, 0, 0, 0, 0, 0, 0};
    char *params[8] = {NULL};
    es_str_t *out = NULL;

    for (int i = 0; i < 8; ++i) {
        cnfexprEval(func->expr[i], &srcVal[i], usrptr, pWti);
        params[i] = (char *)var2CString(&srcVal[i], &bMustFree[i]);
    }

    out = es_newStr(256);
    if (out == NULL) goto done;

    es_addBufConstcstr(&out, "CEF:");
    cef_escape_header(&out, params[0] ? params[0] : "0"); /* version */
    es_addChar(&out, '|');
    cef_escape_header(&out, params[1] ? params[1] : ""); /* vendor */
    es_addChar(&out, '|');
    cef_escape_header(&out, params[2] ? params[2] : ""); /* product */
    es_addChar(&out, '|');
    cef_escape_header(&out, params[3] ? params[3] : ""); /* devversion */
    es_addChar(&out, '|');
    cef_escape_eventclassid(&out, params[4] ? params[4] : ""); /* eventclassid */
    es_addChar(&out, '|');
    cef_escape_header(&out, params[5] ? params[5] : ""); /* name */
    es_addChar(&out, '|');
    cef_escape_header(&out, params[6] ? params[6] : ""); /* severity */
    es_addChar(&out, '|');
    if (params[7] != NULL) /* extensions - verbatim */
        es_addBuf(&out, params[7], strlen(params[7]));

done:
    ret->datatype = 'S';
    ret->d.estr = (out != NULL) ? out : es_newStrFromCStr("", 0);

    for (int i = 0; i < 8; ++i) {
        if (bMustFree[i]) free(params[i]);
        varFreeMembers(&srcVal[i]);
    }
}

/**
 * cef_ext_escape(value) -> string
 *
 * Escapes a single CEF extension field VALUE per the CEF spec:
 *   backslash  ->  \\
 *   equals     ->  \=
 *   newline    ->  \n  (literal two characters)
 *   CR         ->  \r  (literal two characters)
 *
 * Use this to safely embed dynamic rsyslog property values inside a
 * pre-formed extension string passed to tocef():
 *
 *   set $!cef = tocef("0","V","P","1.0",$syslogtag,$msg,"5",
 *                     "src=" & $fromhost-ip &
 *                     " msg=" & cef_ext_escape($msg));
 */
static void ATTR_NONNULL() doFunct_cef_ext_escape(struct cnffunc *__restrict__ const func,
                                                  struct svar *__restrict__ const ret,
                                                  void *const usrptr,
                                                  wti_t *const pWti) {
    struct svar srcVal;
    int bMustFree = 0;
    es_str_t *out = NULL;

    cnfexprEval(func->expr[0], &srcVal, usrptr, pWti);
    char *const orig = (char *)var2CString(&srcVal, &bMustFree);
    const char *s = orig;

    out = es_newStr(64);
    if (out == NULL || s == NULL) goto done;

    while (*s != '\0') {
        if (*s == '\\') {
            es_addChar(&out, '\\');
            es_addChar(&out, '\\');
        } else if (*s == '=') {
            es_addChar(&out, '\\');
            es_addChar(&out, '=');
        } else if (*s == '\n') {
            es_addChar(&out, '\\');
            es_addChar(&out, 'n');
        } else if (*s == '\r') {
            es_addChar(&out, '\\');
            es_addChar(&out, 'r');
        } else {
            es_addChar(&out, (unsigned char)*s);
        }
        ++s;
    }

done:
    ret->datatype = 'S';
    ret->d.estr = (out != NULL) ? out : es_newStrFromCStr("", 0);
    if (bMustFree) free(orig);
    varFreeMembers(&srcVal);
}

/**
 * Applies a CIDR mask to an IPv6 address.
 *
 * @param[in,out] addr The IPv6 address to mask.
 * @param[in] bits The number of bits in the mask (0-128).
 */
static void mask_ip6(struct in6_addr *addr, int bits) {
    int i;
    for (i = 0; i < 16; i++) {
        if (bits >= 8) {
            bits -= 8;
        } else if (bits > 0) {
            addr->s6_addr[i] &= (0xFF << (8 - bits));
            bits = 0;
        } else {
            addr->s6_addr[i] = 0;
        }
    }
}

/**
 * Applies a CIDR mask to an IPv4 address.
 *
 * @param[in,out] addr The IPv4 address to mask.
 * @param[in] bits The number of bits in the mask (0-32).
 */
static void mask_ip4(struct in_addr *addr, int bits) {
    if (bits == 0) {
        addr->s_addr = 0;
    } else if (bits < 32) {
        uint32_t host_addr = ntohl(addr->s_addr);
        host_addr &= (0xFFFFFFFFu << (32 - bits));
        addr->s_addr = htonl(host_addr);
    }
}

/**
 * Checks if an IP address is in a given subnet (CIDR notation).
 *
 * @param[in] func The function object.
 * @param[out] ret The return value (1 if in subnet, 0 otherwise).
 * @param[in] usrptr User pointer (unused).
 * @param[in] pWti Worker thread instance.
 */
static void ATTR_NONNULL() doFunct_is_in_subnet(struct cnffunc *__restrict__ const func,
                                                struct svar *__restrict__ const ret,
                                                void *__restrict__ const usrptr,
                                                wti_t *__restrict__ const pWti) {
    struct svar srcVal[2];
    int bMustFree1, bMustFree2;
    char *ip_str, *cidr_str;
    char *cidr_ip_part;
    char *cidr_mask_part;
    struct in_addr ip4, net4;
    struct in6_addr ip6, net6;
    int ip_family, net_family;
    int cidr_bits;
    int result = 0;
    char ip_buf[INET6_ADDRSTRLEN];

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);
    cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);

    ip_str = (char *)var2CString(&srcVal[0], &bMustFree1);
    cidr_str = (char *)var2CString(&srcVal[1], &bMustFree2);
    if (ip_str == NULL || cidr_str == NULL) {
        goto finalize_it;
    }

    /* 1. Parse IP address */
    if (inet_pton(AF_INET, ip_str, &ip4) == 1) {
        ip_family = AF_INET;
    } else if (inet_pton(AF_INET6, ip_str, &ip6) == 1) {
        ip_family = AF_INET6;
    } else {
        goto finalize_it; /* Invalid IP */
    }

    /* 2. Parse CIDR */
    cidr_mask_part = strchr(cidr_str, '/');
    if (cidr_mask_part == NULL) {
        goto finalize_it; /* Invalid CIDR format */
    }

    // Copy IP part to buffer to avoid modifying cidr_str
    size_t len = cidr_mask_part - cidr_str;
    if (len >= sizeof(ip_buf)) {
        goto finalize_it;  // Too long for IP address
    }
    memcpy(ip_buf, cidr_str, len);
    ip_buf[len] = '\0';
    cidr_ip_part = ip_buf;

    cidr_mask_part++;  // Skip '/'

    if (inet_pton(AF_INET, cidr_ip_part, &net4) == 1) {
        net_family = AF_INET;
    } else if (inet_pton(AF_INET6, cidr_ip_part, &net6) == 1) {
        net_family = AF_INET6;
    } else {
        goto finalize_it; /* Invalid Subnet IP */
    }

    /* 3. Check Family Match */
    if (ip_family != net_family) {
        goto finalize_it; /* mismatch */
    }

    /* 4. Parse Mask bits */
    char *endptr;
    errno = 0;
    long bits = strtol(cidr_mask_part, &endptr, 10);
    if (errno != 0 || endptr == cidr_mask_part || *endptr != '\0' || bits < 0) {
        goto finalize_it;
    }
    if (ip_family == AF_INET && bits > 32) goto finalize_it;
    if (ip_family == AF_INET6 && bits > 128) goto finalize_it;
    cidr_bits = (int)bits;

    /* 5. Mask and Compare */
    if (ip_family == AF_INET) {
        mask_ip4(&ip4, cidr_bits);
        mask_ip4(&net4, cidr_bits);
        if (ip4.s_addr == net4.s_addr) {
            result = 1;
        }
    } else {
        mask_ip6(&ip6, cidr_bits);
        mask_ip6(&net6, cidr_bits);
        if (memcmp(&ip6, &net6, sizeof(struct in6_addr)) == 0) {
            result = 1;
        }
    }

finalize_it:
    if (bMustFree1) free(ip_str);
    if (bMustFree2) free(cidr_str);
    varFreeMembers(&srcVal[0]);
    varFreeMembers(&srcVal[1]);

    ret->datatype = 'N';
    ret->d.n = result;
}

static void ATTR_NONNULL() doFunct_append_json(struct cnffunc *__restrict__ const func,
                                               struct svar *__restrict__ const ret,
                                               void *const usrptr,
                                               wti_t *const pWti) {
    struct svar srcVal[3];
    struct json_object *result = NULL;
    struct json_object *input = NULL;
    enum json_type type;
    char *key = NULL;
    int bMustFree = 0;
    int bMustFree2 = 0;
    int nParamsEvaluated = 0;

    memset(srcVal, 0, sizeof(srcVal));

    cnfexprEval(func->expr[0], &srcVal[0], usrptr, pWti);

    if (srcVal[0].datatype != 'J' || srcVal[0].d.json == NULL) {
        goto finalize_it;
    }

    input = srcVal[0].d.json;
    type = json_object_get_type(input);

    if (type == json_type_array) {
        cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
        nParamsEvaluated = 1;

        result = jsonDeepCopy(input);
        if (result == NULL) {
            goto finalize_it;
        }

        struct json_object *newElem = NULL;
        if (srcVal[1].datatype == 'J') {
            newElem = jsonDeepCopy(srcVal[1].d.json);
        } else if (srcVal[1].datatype == 'S') {
            char *str = (char *)var2CString(&srcVal[1], &bMustFree2);
            if (str != NULL) {
                newElem = json_object_new_string(str);
            }
            if (bMustFree2) free(str);
        } else if (srcVal[1].datatype == 'N') {
            newElem = json_object_new_int64(srcVal[1].d.n);
        }
        if (newElem != NULL) {
            if (json_object_array_add(result, newElem) != 0) {
                json_object_put(newElem);
                json_object_put(result);
                result = NULL;
            }
        }

    } else if (type == json_type_object && func->nParams >= 3) {
        cnfexprEval(func->expr[1], &srcVal[1], usrptr, pWti);
        cnfexprEval(func->expr[2], &srcVal[2], usrptr, pWti);
        nParamsEvaluated = 2;

        key = (char *)var2CString(&srcVal[1], &bMustFree);
        if (key == NULL) {
            goto finalize_it;
        }

        result = jsonDeepCopy(input);
        if (result == NULL) {
            goto finalize_it;
        }

        struct json_object *newVal = NULL;
        if (srcVal[2].datatype == 'J') {
            newVal = jsonDeepCopy(srcVal[2].d.json);
        } else if (srcVal[2].datatype == 'S') {
            char *str = (char *)var2CString(&srcVal[2], &bMustFree2);
            if (str != NULL) {
                newVal = json_object_new_string(str);
            }
            if (bMustFree2) free(str);
        } else if (srcVal[2].datatype == 'N') {
            newVal = json_object_new_int64(srcVal[2].d.n);
        }
        if (newVal != NULL) {
            json_object_object_add(result, key, newVal);
        }
    }

finalize_it:
    ret->datatype = 'J';
    ret->d.json = result;

    if (bMustFree) free(key);
    varFreeMembers(&srcVal[0]);

    if (nParamsEvaluated >= 1) varFreeMembers(&srcVal[1]);
    if (nParamsEvaluated >= 2) varFreeMembers(&srcVal[2]);
}

static void evalVar(struct cnfvar *__restrict__ const var,
                    void *__restrict__ const usrptr,
                    struct svar *__restrict__ const ret) {
    rs_size_t propLen;
    uchar *pszProp = NULL;
    unsigned short bMustBeFreed = 0;
    rsRetVal localRet;
    struct json_object *json;
    uchar *cstr = NULL;

    if (var->prop.id == PROP_CEE || var->prop.id == PROP_LOCAL_VAR || var->prop.id == PROP_GLOBAL_VAR) {
        localRet = msgGetJSONPropJSONorString((smsg_t *)usrptr, &var->prop, &json, &cstr);
        if (json != NULL) {
            assert(cstr == NULL);
            ret->datatype = 'J';
            ret->d.json = (localRet == RS_RET_OK) ? json : NULL;
            DBGPRINTF("rainerscript: (json) var %d:%s: '%s'\n", var->prop.id, var->prop.name,
                      (ret->d.json == NULL) ? "" : json_object_get_string(ret->d.json));
        } else { /* we have a string */
            DBGPRINTF("rainerscript: (json/string) var %d: '%s'\n", var->prop.id, cstr);
            ret->datatype = 'S';
            ret->d.estr = (localRet != RS_RET_OK || cstr == NULL)
                              ? es_newStr(1)
                              : es_newStrFromCStr((char *)cstr, strlen((char *)cstr));
        }
        free(cstr);
    } else {
        ret->datatype = 'S';
        pszProp = (uchar *)MsgGetProp((smsg_t *)usrptr, NULL, &var->prop, &propLen, &bMustBeFreed, NULL);
        ret->d.estr = es_newStrFromCStr((char *)pszProp, propLen);
        DBGPRINTF("rainerscript: (string) var %d: '%s'\n", var->prop.id, pszProp);
        if (bMustBeFreed) free(pszProp);
    }
}

/* perform a string comparision operation against a while array. Semantic is
 * that one one comparison is true, the whole construct is true.
 * TODO: we can obviously optimize this process. One idea is to
 * compile a regex, which should work faster than serial comparison.
 * Note: compiling a regex does NOT work at all. I experimented with that
 * and it was generally 5 to 10 times SLOWER than what we do here...
 */
static int evalStrArrayCmp(es_str_t *const estr_l, const struct cnfarray *__restrict__ const ar, const int cmpop) {
    int i;
    int r = 0;
    es_str_t **res;
    if (cmpop == CMP_EQ) {
        res = bsearch(&estr_l, ar->arr, ar->nmemb, sizeof(es_str_t *), qs_arrcmp);
        r = res != NULL;
    } else if (cmpop == CMP_NE) {
        res = bsearch(&estr_l, ar->arr, ar->nmemb, sizeof(es_str_t *), qs_arrcmp);
        r = res == NULL;
    } else {
        for (i = 0; (r == 0) && (i < ar->nmemb); ++i) {
            switch (cmpop) {
                case CMP_STARTSWITH:
                    r = es_strncmp(estr_l, ar->arr[i], es_strlen(ar->arr[i])) == 0;
                    break;
                case CMP_STARTSWITHI:
                    r = es_strncasecmp(estr_l, ar->arr[i], es_strlen(ar->arr[i])) == 0;
                    break;
                case CMP_ENDSWITH: {
                    const size_t len_l = es_strlen(estr_l);
                    const size_t len_suffix = es_strlen(ar->arr[i]);
                    r = len_l >= len_suffix && (memcmp(es_getBufAddr(estr_l) + len_l - len_suffix,
                                                       es_getBufAddr(ar->arr[i]), len_suffix) == 0);
                } break;
                case CMP_CONTAINS:
                    r = es_strContains(estr_l, ar->arr[i]) != -1;
                    break;
                case CMP_CONTAINSI:
                    r = es_strCaseContains(estr_l, ar->arr[i]) != -1;
                    break;
                default:
                    // We need to satisfy compiler which does not properly handle enum
                    break;
            }
        }
    }
    return r;
}

#define FREE_BOTH_RET   \
    varFreeMembers(&r); \
    varFreeMembers(&l)

#define COMP_NUM_BINOP(x)                                             \
    cnfexprEval(expr->l, &l, usrptr, pWti);                           \
    cnfexprEval(expr->r, &r, usrptr, pWti);                           \
    ret->datatype = 'N';                                              \
    ret->d.n = var2Number(&l, &convok_l) x var2Number(&r, &convok_r); \
    FREE_BOTH_RET

#define COMP_NUM_BINOP_DIV(x)                            \
    cnfexprEval(expr->l, &l, usrptr, pWti);              \
    cnfexprEval(expr->r, &r, usrptr, pWti);              \
    ret->datatype = 'N';                                 \
    if ((ret->d.n = var2Number(&r, &convok_r)) == 0) {   \
        /* division by zero */                           \
    } else {                                             \
        ret->d.n = var2Number(&l, &convok_l) x ret->d.n; \
    }                                                    \
    FREE_BOTH_RET

/* NOTE: array as right-hand argument MUST be handled by user */
#define PREP_TWO_STRINGS                                           \
    cnfexprEval(expr->l, &l, usrptr, pWti);                        \
    estr_l = var2String(&l, &bMustFree2);                          \
    if (expr->r->nodetype == 'S') {                                \
        estr_r = ((struct cnfstringval *)expr->r)->estr;           \
        bMustFree = 0;                                             \
    } else if (expr->r->nodetype != 'A') {                         \
        cnfexprEval(expr->r, &r, usrptr, pWti);                    \
        estr_r = var2String(&r, &bMustFree);                       \
    } else {                                                       \
        /* Note: this is not really necessary, but if we do not */ \
        /* do it, we get a very irritating compiler warning... */  \
        estr_r = NULL;                                             \
    }

#define FREE_TWO_STRINGS                                                          \
    if (bMustFree) es_deleteStr(estr_r);                                          \
    if (expr->r->nodetype != 'S' && expr->r->nodetype != 'A') varFreeMembers(&r); \
    if (bMustFree2) es_deleteStr(estr_l);                                         \
    varFreeMembers(&l)

/* helper to evaluate comparison in a strcmp() like manner. Result is
 * to be used for final truth value evaluation.
 */
static int eval_strcmp_like(const struct cnfexpr *__restrict__ const expr,
                            void *__restrict__ const usrptr,
                            wti_t *__restrict__ const pWti) {
    es_str_t *__restrict__ estr_r, *__restrict__ estr_l;
    int bMustFree, bMustFree2;
    int64_t n_r, n_l;
    int convok_r, convok_l;
    struct svar r = {{0}, 0}, l = {{0}, 0}; /* memory for subexpression results */
    int ret;

    cnfexprEval(expr->l, &l, usrptr, pWti);
    cnfexprEval(expr->r, &r, usrptr, pWti);
    n_l = var2Number(&l, &convok_l);
    if (convok_l) {
        n_r = var2Number(&r, &convok_r);
    }
    if (convok_l && convok_r) {
        ret = n_l - n_r;
    } else {
        estr_l = var2String(&l, &bMustFree);
        estr_r = var2String(&r, &bMustFree2);
        ret = es_strcmp(estr_l, estr_r);
        if (bMustFree) es_deleteStr(estr_l);
        if (bMustFree2) es_deleteStr(estr_r);
    }
    FREE_BOTH_RET;
    return ret;
}

/* evaluate an expression.
 * Note that we try to avoid malloc whenever possible (because of
 * the large overhead it has, especially on highly threaded programs).
 * As such, the each caller level must provide buffer space for the
 * result on its stack during recursion. This permits the callee to store
 * the return value without malloc. As the value is a somewhat larger
 * struct, we could otherwise not return it without malloc.
 * Note that we implement boolean shortcut operations. For our needs, there
 * simply is no case where full evaluation would make any sense at all.
 */
void ATTR_NONNULL() cnfexprEval(const struct cnfexpr *__restrict__ const expr,
                                struct svar *__restrict__ const ret,
                                void *__restrict__ const usrptr,
                                wti_t *__restrict__ const pWti) {
    struct svar r = {{0}, 0}, l = {{0}, 0}; /* memory for subexpression results */
    es_str_t *__restrict__ estr_r, *__restrict__ estr_l;
    int convok_r, convok_l;
    int bMustFree, bMustFree2;
    long long n_r, n_l;

    DBGPRINTF("eval expr %p, type '%s'\n", expr, tokenToString(expr->nodetype));
    switch (expr->nodetype) {
        /* note: comparison operations are extremely similar. The code can be copyied, only
         * places flagged with "CMP" need to be changed.
         */
        case CMP_EQ:
            /* this is optimized in regard to right param as a PoC for all compOps
             * So this is a NOT yet the copy template!
             */
            cnfexprEval(expr->l, &l, usrptr, pWti);
            ret->datatype = 'N';
            if (l.datatype == 'S') {
                if (expr->r->nodetype == 'S') {
                    ret->d.n = !es_strcmp(l.d.estr, ((struct cnfstringval *)expr->r)->estr); /*CMP*/
                } else if (expr->r->nodetype == 'A') {
                    ret->d.n = evalStrArrayCmp(l.d.estr, (struct cnfarray *)expr->r, CMP_EQ);
                } else {
                    cnfexprEval(expr->r, &r, usrptr, pWti);
                    if (r.datatype == 'S') {
                        ret->d.n = !es_strcmp(l.d.estr, r.d.estr); /*CMP*/
                    } else {
                        n_l = var2Number(&l, &convok_l);
                        if (convok_l) {
                            ret->d.n = (n_l == var2Number(&r, NULL)); /*CMP*/
                        } else {
                            estr_r = var2String(&r, &bMustFree);
                            ret->d.n = !es_strcmp(l.d.estr, estr_r); /*CMP*/
                            if (bMustFree) es_deleteStr(estr_r);
                        }
                    }
                    varFreeMembers(&r);
                }
            } else if (l.datatype == 'J') {
                estr_l = var2String(&l, &bMustFree);
                if (expr->r->nodetype == 'S') {
                    ret->d.n = !es_strcmp(estr_l, ((struct cnfstringval *)expr->r)->estr); /*CMP*/
                } else if (expr->r->nodetype == 'A') {
                    ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray *)expr->r, CMP_EQ);
                } else {
                    cnfexprEval(expr->r, &r, usrptr, pWti);
                    if (r.datatype == 'S') {
                        ret->d.n = !es_strcmp(estr_l, r.d.estr); /*CMP*/
                    } else {
                        n_l = var2Number(&l, &convok_l);
                        if (convok_l) {
                            ret->d.n = (n_l == var2Number(&r, NULL)); /*CMP*/
                        } else {
                            estr_r = var2String(&r, &bMustFree2);
                            ret->d.n = !es_strcmp(estr_l, estr_r); /*CMP*/
                            if (bMustFree2) es_deleteStr(estr_r);
                        }
                    }
                    varFreeMembers(&r);
                }
                if (bMustFree) es_deleteStr(estr_l);
            } else {
                cnfexprEval(expr->r, &r, usrptr, pWti);
                if (r.datatype == 'S') {
                    n_r = var2Number(&r, &convok_r);
                    if (convok_r) {
                        ret->d.n = (l.d.n == n_r); /*CMP*/
                    } else {
                        estr_l = var2String(&l, &bMustFree);
                        ret->d.n = !es_strcmp(r.d.estr, estr_l); /*CMP*/
                        if (bMustFree) es_deleteStr(estr_l);
                    }
                } else {
                    ret->d.n = (l.d.n == var2Number(&r, NULL)); /*CMP*/
                }
                varFreeMembers(&r);
            }
            varFreeMembers(&l);
            break;
        case CMP_NE:
            cnfexprEval(expr->l, &l, usrptr, pWti);
            ret->datatype = 'N';
            if (l.datatype == 'S') {
                if (expr->r->nodetype == 'S') {
                    ret->d.n = es_strcmp(l.d.estr, ((struct cnfstringval *)expr->r)->estr); /*CMP*/
                } else if (expr->r->nodetype == 'A') {
                    ret->d.n = evalStrArrayCmp(l.d.estr, (struct cnfarray *)expr->r, CMP_NE);
                } else {
                    cnfexprEval(expr->r, &r, usrptr, pWti);
                    if (r.datatype == 'S') {
                        ret->d.n = es_strcmp(l.d.estr, r.d.estr); /*CMP*/
                    } else {
                        n_l = var2Number(&l, &convok_l);
                        if (convok_l) {
                            ret->d.n = (n_l != var2Number(&r, NULL)); /*CMP*/
                        } else {
                            estr_r = var2String(&r, &bMustFree);
                            ret->d.n = es_strcmp(l.d.estr, estr_r); /*CMP*/
                            if (bMustFree) es_deleteStr(estr_r);
                        }
                    }
                    varFreeMembers(&r);
                }
            } else if (l.datatype == 'J') {
                estr_l = var2String(&l, &bMustFree);
                if (expr->r->nodetype == 'S') {
                    ret->d.n = es_strcmp(estr_l, ((struct cnfstringval *)expr->r)->estr); /*CMP*/
                } else if (expr->r->nodetype == 'A') {
                    ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray *)expr->r, CMP_NE);
                } else {
                    cnfexprEval(expr->r, &r, usrptr, pWti);
                    if (r.datatype == 'S') {
                        ret->d.n = es_strcmp(estr_l, r.d.estr); /*CMP*/
                    } else {
                        n_l = var2Number(&l, &convok_l);
                        if (convok_l) {
                            ret->d.n = (n_l != var2Number(&r, NULL)); /*CMP*/
                        } else {
                            estr_r = var2String(&r, &bMustFree2);
                            ret->d.n = es_strcmp(estr_l, estr_r); /*CMP*/
                            if (bMustFree2) es_deleteStr(estr_r);
                        }
                    }
                    varFreeMembers(&r);
                }
                if (bMustFree) es_deleteStr(estr_l);
            } else {
                cnfexprEval(expr->r, &r, usrptr, pWti);
                if (r.datatype == 'S') {
                    n_r = var2Number(&r, &convok_r);
                    if (convok_r) {
                        ret->d.n = (l.d.n != n_r); /*CMP*/
                    } else {
                        estr_l = var2String(&l, &bMustFree);
                        ret->d.n = es_strcmp(r.d.estr, estr_l); /*CMP*/
                        if (bMustFree) es_deleteStr(estr_l);
                    }
                } else {
                    ret->d.n = (l.d.n != var2Number(&r, NULL)); /*CMP*/
                }
                varFreeMembers(&r);
            }
            varFreeMembers(&l);
            break;
        case CMP_LE:
            ret->datatype = 'N';
            ret->d.n = eval_strcmp_like(expr, usrptr, pWti) <= 0;
            break;
        case CMP_GE:
            ret->datatype = 'N';
            ret->d.n = eval_strcmp_like(expr, usrptr, pWti) >= 0;
            break;
        case CMP_LT:
            ret->datatype = 'N';
            ret->d.n = eval_strcmp_like(expr, usrptr, pWti) < 0;
            break;
        case CMP_GT:
            ret->datatype = 'N';
            ret->d.n = eval_strcmp_like(expr, usrptr, pWti) > 0;
            break;
        case CMP_STARTSWITH:
            PREP_TWO_STRINGS;
            ret->datatype = 'N';
            if (expr->r->nodetype == 'A') {
                ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray *)expr->r, CMP_STARTSWITH);
                bMustFree = 0;
            } else {
                ret->d.n = es_strncmp(estr_l, estr_r, estr_r->lenStr) == 0;
            }
            FREE_TWO_STRINGS;
            break;
        case CMP_ENDSWITH:
            PREP_TWO_STRINGS;
            ret->datatype = 'N';
            if (expr->r->nodetype == 'A') {
                ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray *)expr->r, CMP_ENDSWITH);
                bMustFree = 0;
            } else {
                ret->d.n =
                    estr_l->lenStr >= estr_r->lenStr && (memcmp(es_getBufAddr(estr_l) + estr_l->lenStr - estr_r->lenStr,
                                                                es_getBufAddr(estr_r), estr_r->lenStr) == 0);
            }
            FREE_TWO_STRINGS;
            break;
        case CMP_STARTSWITHI:
            PREP_TWO_STRINGS;
            ret->datatype = 'N';
            if (expr->r->nodetype == 'A') {
                ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray *)expr->r, CMP_STARTSWITHI);
                bMustFree = 0;
            } else {
                ret->d.n = es_strncasecmp(estr_l, estr_r, estr_r->lenStr) == 0;
            }
            FREE_TWO_STRINGS;
            break;
        case CMP_CONTAINS:
            PREP_TWO_STRINGS;
            ret->datatype = 'N';
            if (expr->r->nodetype == 'A') {
                ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray *)expr->r, CMP_CONTAINS);
                bMustFree = 0;
            } else {
                ret->d.n = es_strContains(estr_l, estr_r) != -1;
            }
            FREE_TWO_STRINGS;
            break;
        case CMP_CONTAINSI:
            PREP_TWO_STRINGS;
            ret->datatype = 'N';
            if (expr->r->nodetype == 'A') {
                ret->d.n = evalStrArrayCmp(estr_l, (struct cnfarray *)expr->r, CMP_CONTAINSI);
                bMustFree = 0;
            } else {
                ret->d.n = es_strCaseContains(estr_l, estr_r) != -1;
            }
            FREE_TWO_STRINGS;
            break;
        case OR:
            cnfexprEval(expr->l, &l, usrptr, pWti);
            ret->datatype = 'N';
            if (var2Number(&l, &convok_l)) {
                ret->d.n = 1ll;
            } else {
                cnfexprEval(expr->r, &r, usrptr, pWti);
                if (var2Number(&r, &convok_r))
                    ret->d.n = 1ll;
                else
                    ret->d.n = 0ll;
                varFreeMembers(&r);
            }
            varFreeMembers(&l);
            break;
        case AND:
            cnfexprEval(expr->l, &l, usrptr, pWti);
            ret->datatype = 'N';
            if (var2Number(&l, &convok_l)) {
                cnfexprEval(expr->r, &r, usrptr, pWti);
                if (var2Number(&r, &convok_r))
                    ret->d.n = 1ll;
                else
                    ret->d.n = 0ll;
                varFreeMembers(&r);
            } else {
                ret->d.n = 0ll;
            }
            varFreeMembers(&l);
            break;
        case NOT:
            cnfexprEval(expr->r, &r, usrptr, pWti);
            ret->datatype = 'N';
            ret->d.n = !var2Number(&r, &convok_r);
            varFreeMembers(&r);
            break;
        case 'N':
            ret->datatype = 'N';
            ret->d.n = ((struct cnfnumval *)expr)->val;
            break;
        case 'S':
            ret->datatype = 'S';
            ret->d.estr = es_strdup(((struct cnfstringval *)expr)->estr);
            break;
        case 'A':
            /* if an array is used with "normal" operations, it just evaluates
             * to its first element.
             */
            ret->datatype = 'S';
            ret->d.estr = es_strdup(((struct cnfarray *)expr)->arr[0]);
            break;
        case 'V':
            evalVar((struct cnfvar *)expr, usrptr, ret);
            break;
        case '&':
            /* TODO: think about optimization, should be possible ;) */
            PREP_TWO_STRINGS;
            if (expr->r->nodetype == 'A') {
                estr_r = ((struct cnfarray *)expr->r)->arr[0];
                bMustFree = 0;
            }
            ret->datatype = 'S';
            ret->d.estr = es_strdup(estr_l);
            es_addStr(&ret->d.estr, estr_r);
            FREE_TWO_STRINGS;
            break;
        case '+':
            COMP_NUM_BINOP(+);
            break;
        case '-':
            COMP_NUM_BINOP(-);
            break;
        case '*':
            COMP_NUM_BINOP(*);
            break;
        case '/':
            COMP_NUM_BINOP_DIV(/);
            break;
        case '%':
            COMP_NUM_BINOP_DIV(%);
            break;
        case 'M':
            cnfexprEval(expr->r, &r, usrptr, pWti);
            ret->datatype = 'N';
            ret->d.n = -var2Number(&r, &convok_r);
            varFreeMembers(&r);
            break;
        case 'F':
            doFuncCall((struct cnffunc *)expr, ret, usrptr, pWti);
            break;
        case S_FUNC_EXISTS:
            ret->datatype = 'N';
            ret->d.n = evalFuncExists((struct cnffuncexists *)expr, usrptr);
            break;
        default:
            ret->datatype = 'N';
            ret->d.n = 0ll;
            DBGPRINTF("eval error: unknown nodetype %u['%c']\n", (unsigned)expr->nodetype, (char)expr->nodetype);
            assert(0); /* abort on debug builds, this must not happen! */
            break;
    }
    DBGPRINTF("eval expr %p, return datatype '%c':%d\n", expr, ret->datatype,
              (ret->datatype == 'N') ? (int)ret->d.n : 0);
}

//---------------------------------------------------------

void cnfarrayContentDestruct(struct cnfarray *ar) {
    unsigned short i;
    if (ar == NULL) {
        return;
    }
    for (i = 0; i < ar->nmemb; ++i) {
        es_deleteStr(ar->arr[i]);
    }
    free(ar->arr);
}

static void regex_destruct(struct cnffunc *func) {
    if (func->funcdata != NULL) {
        regexp.regfree(func->funcdata);
    }
}

static rsRetVal initFunc_dyn_stats(struct cnffunc *func) {
    uchar *cstr = NULL;
    DEFiRet;

    func->destructable_funcdata = 0;

    if (func->nParams != 2) {
        parser_errmsg("rsyslog logic error in line %d of file %s\n", __LINE__, __FILE__);
        FINALIZE;
    }

    func->funcdata = NULL;
    if (func->expr[0]->nodetype != 'S') {
        parser_errmsg(
            "dyn-stats bucket-name (param 1) of dyn-stats manipulating "
            "functions like dyn_inc must be a constant string");
        FINALIZE;
    }

    cstr = (uchar *)es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL);
    if ((func->funcdata = dynstats_findBucket(cstr)) == NULL) {
        parser_errmsg("dyn-stats bucket '%s' not found", cstr);
        FINALIZE;
    }

finalize_it:
    free(cstr);
    RETiRet;
}

static rsRetVal initFunc_perctile_obs(struct cnffunc *func) {
    uchar *cstr = NULL;
    DEFiRet;

    func->destructable_funcdata = 0;
    if (func->nParams != 3) {
        parser_errmsg("rsyslog logic error in line %d of file %s\n", __LINE__, __FILE__);
        FINALIZE;
    }

    func->funcdata = NULL;
    if (func->expr[0]->nodetype != 'S') {
        parser_errmsg(
            "percentile-stats bucket-name (param 1) of perctile-stats manipulating "
            "functions like percentile_observe must be a constant string");
        FINALIZE;
    }

    cstr = (uchar *)es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL);
    if ((func->funcdata = perctile_findBucket(cstr)) == NULL) {
        parser_errmsg("perctile-stats bucket '%s' not found", cstr);
        FINALIZE;
    }

finalize_it:
    free(cstr);
    RETiRet;
}

static void ATTR_NONNULL() doFunc_percentile_obs(struct cnffunc *__restrict__ const func,
                                                 struct svar *__restrict__ const ret,
                                                 void *__restrict__ const usrptr,
                                                 wti_t *__restrict__ const pWti) {
    uchar *cstr = NULL;
    struct svar srcVal;
    int bMustFree;

    ret->datatype = 'N';
    if (func->funcdata == NULL) {
        ret->d.n = -1;
        return;
    }

    cnfexprEval(func->expr[1], &srcVal, usrptr, pWti);
    cstr = (uchar *)var2CString(&srcVal, &bMustFree);

    int success = 0;
    struct svar srcVal2;
    long long retVal;
    cnfexprEval(func->expr[2], &srcVal2, usrptr, pWti);
    long long val = var2Number(&srcVal2, &success);
    if (!success) {
        char *cstr2 = es_str2cstr(srcVal2.d.estr, NULL);
        parser_errmsg("rainerscript: percentile_obs - didn't get a valid number: %s\n", cstr2);
        free(cstr2);
        retVal = 0;
        FINALIZE;
    }

    retVal = perctile_obs(func->funcdata, cstr, val);

finalize_it:
    if (bMustFree) {
        free(cstr);
    }
    varFreeMembers(&srcVal);
    varFreeMembers(&srcVal2);
    ret->d.n = retVal;
    ret->datatype = 'N';
}

static rsRetVal initFunc_re_match_generic(struct cnffunc *const func, const unsigned flags) {
    rsRetVal localRet;
    char *regex = NULL;
    regex_t *re;
    DEFiRet;

    if (func->nParams < 2) {
        parser_errmsg("rsyslog logic error in line %d of file %s\n", __LINE__, __FILE__);
        FINALIZE;
    }

    func->funcdata = NULL;
    if (func->expr[1]->nodetype != 'S') {
        parser_errmsg("param 2 of re_match/extract() must be a constant string");
        FINALIZE;
    }

    CHKmalloc(re = malloc(sizeof(regex_t)));
    func->funcdata = re;

    regex = es_str2cstr(((struct cnfstringval *)func->expr[1])->estr, NULL);

    if ((localRet = objUse(regexp, LM_REGEXP_FILENAME)) == RS_RET_OK) {
        int errcode;
        if ((errcode = regexp.regcomp(re, (char *)regex, REG_EXTENDED | flags)) != 0) {
            char errbuff[512];
            regexp.regerror(errcode, re, errbuff, sizeof(errbuff));
            parser_errmsg("cannot compile regex '%s': %s", regex, errbuff);
            ABORT_FINALIZE(RS_RET_ERR);
        }
    } else { /* regexp object could not be loaded */
        parser_errmsg("could not load regex support - regex ignored");
        ABORT_FINALIZE(localRet);
    }

finalize_it:
    free(regex);
    RETiRet;
}

static rsRetVal initFunc_re_match(struct cnffunc *func) {
    return initFunc_re_match_generic(func, 0);
}

static rsRetVal initFunc_re_match_i(struct cnffunc *func) {
    return initFunc_re_match_generic(func, REG_ICASE);
}

static rsRetVal initFunc_exec_template(struct cnffunc *func) {
    char *tplName = NULL;
    DEFiRet;

    func->destructable_funcdata = 0;

    if (func->nParams != 1) {
        parser_errmsg("rsyslog logic error in line %d of file %s\n", __LINE__, __FILE__);
        FINALIZE;
    }

    if (func->expr[0]->nodetype != 'S') {
        parser_errmsg("exec_template(): param 1 must be a constant string");
        FINALIZE;
    }

    tplName = es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL);
    func->funcdata = tplFind(loadConf, tplName, strlen(tplName));
    if (func->funcdata == NULL) {
        parser_errmsg("exec_template(): template '%s' could not be found", tplName);
        FINALIZE;
    }


finalize_it:
    free(tplName);
    RETiRet;
}

static rsRetVal initFunc_prifilt(struct cnffunc *func) {
    struct funcData_prifilt *pData;
    uchar *cstr;
    DEFiRet;

    if (func->nParams != 1) {
        parser_errmsg("rsyslog logic error in line %d of file %s\n", __LINE__, __FILE__);
        FINALIZE;
    }

    func->funcdata = NULL;
    if (func->expr[0]->nodetype != 'S') {
        parser_errmsg("param 1 of prifilt() must be a constant string");
        FINALIZE;
    }

    CHKmalloc(pData = calloc(1, sizeof(struct funcData_prifilt)));
    func->funcdata = pData;
    cstr = (uchar *)es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL);
    CHKiRet(DecodePRIFilter(cstr, pData->pmask));
    free(cstr);
finalize_it:
    RETiRet;
}

static rsRetVal resolveLookupTable(struct cnffunc *func) {
    uchar *cstr = NULL;
    char *fn_name = NULL;
    DEFiRet;

    func->destructable_funcdata = 0;

    if (func->nParams == 0) { /*we assume first arg is lookup-table-name*/
        parser_errmsg("rsyslog logic error in line %d of file %s\n", __LINE__, __FILE__);
        FINALIZE;
    }

    CHKmalloc(fn_name = es_str2cstr(func->fname, NULL));

    func->funcdata = NULL;
    if (func->expr[0]->nodetype != 'S') {
        parser_errmsg("table name (param 1) of %s() must be a constant string", fn_name);
        FINALIZE;
    }

    CHKmalloc(cstr = (uchar *)es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL));
    if ((func->funcdata = lookupFindTable(cstr)) == NULL) {
        parser_errmsg("lookup table '%s' not found (used in function: %s)", cstr, fn_name);
        FINALIZE;
    }

finalize_it:
    free(cstr);
    free(fn_name);
    RETiRet;
}

struct modListNode {
    int version;
    struct scriptFunct *modFcts;
    struct modListNode *next;
};

static struct modListNode *modListRoot = NULL;
static struct modListNode *modListLast = NULL;

static struct scriptFunct functions[] = {
    {"strlen", 1, 1, doFunct_StrLen, NULL, NULL},
    {"getenv", 1, 1, doFunct_Getenv, NULL, NULL},
    {"num2ipv4", 1, 1, doFunct_num2ipv4, NULL, NULL},
    {"int2hex", 1, 1, doFunct_Int2Hex, NULL, NULL},
    {"substring", 3, 3, doFunct_Substring, NULL, NULL},
    {"ltrim", 1, 1, doFunct_LTrim, NULL, NULL},
    {"rtrim", 1, 1, doFunct_RTrim, NULL, NULL},
    {"tolower", 1, 1, doFunct_ToLower, NULL, NULL},
    {"toupper", 1, 1, doFunct_ToUpper, NULL, NULL},
    {"cstr", 1, 1, doFunct_CStr, NULL, NULL},
    {"cnum", 1, 1, doFunct_CNum, NULL, NULL},
    {"cbool", 1, 1, doFunct_CBool, NULL, NULL},
    {"ip42num", 1, 1, doFunct_Ipv42num, NULL, NULL},
    {"ipv42num", 1, 1, doFunct_Ipv42num, NULL, NULL},
    {"re_match", 2, 2, doFunct_ReMatch, initFunc_re_match, regex_destruct},
    {"re_match_i", 2, 2, doFunct_ReMatch, initFunc_re_match_i, regex_destruct},
    {"re_extract", 5, 5, doFunc_re_extract, initFunc_re_match, regex_destruct},
    {"re_extract_i", 5, 5, doFunc_re_extract, initFunc_re_match_i, regex_destruct},
    {"field", 3, 3, doFunct_Field, NULL, NULL},
    {"exec_template", 1, 1, doFunc_exec_template, initFunc_exec_template, NULL},
    {"prifilt", 1, 1, doFunct_Prifilt, initFunc_prifilt, NULL},
    {"lookup", 2, 2, doFunct_Lookup, resolveLookupTable, NULL},
    {"dyn_inc", 2, 2, doFunct_DynInc, initFunc_dyn_stats, NULL},
    {"percentile_observe", 3, 3, doFunc_percentile_obs, initFunc_perctile_obs, NULL},
    {"replace", 3, 3, doFunct_Replace, NULL, NULL},
    {"wrap", 2, 3, doFunct_Wrap, NULL, NULL},
    {"random", 1, 1, doFunct_RandomGen, NULL, NULL},
    {"format_time", 2, 2, doFunct_FormatTime, NULL, NULL},
    {"parse_time", 1, 1, doFunct_ParseTime, NULL, NULL},
    {"parse_time_localtz", 1, 1, doFunct_ParseTimeLocalTz, NULL, NULL},
    {"is_time", 1, 2, doFunct_IsTime, NULL, NULL},
    {"parse_json", 2, 2, doFunc_parse_json, NULL, NULL},
    {"get_property", 2, 2, doFunc_get_property, NULL, NULL},
    {"script_error", 0, 0, doFunct_ScriptError, NULL, NULL},
    {"previous_action_suspended", 0, 0, doFunct_PreviousActionSuspended, NULL, NULL},
    {"b64_decode", 1, 1, doFunct_Base64Dec, NULL, NULL},
    {"split", 2, 2, doFunct_split, NULL, NULL},
    {"is_in_subnet", 2, 2, doFunct_is_in_subnet, NULL, NULL},
    {"append_json", 2, 3, doFunct_append_json, NULL, NULL},
    {"tocef", 8, 8, doFunct_tocef, NULL, NULL},
    {"cef_ext_escape", 1, 1, doFunct_cef_ext_escape, NULL, NULL},
    {NULL, 0, 0, NULL, NULL, NULL}  // last element to check end of array
};

static rscriptFuncPtr ATTR_NONNULL() extractFuncPtr(const struct scriptFunct *const funct, const unsigned int nParams) {
    rscriptFuncPtr retPtr = NULL;

    if (funct->minParams == funct->maxParams) {
        if (nParams == funct->maxParams) {
            retPtr = funct->fPtr;
        } else {
            parser_errmsg("number of parameters for %s() must be %hu but is %d.", funct->fname, funct->maxParams,
                          nParams);
        }
    } else {
        if (nParams < funct->minParams) {
            parser_errmsg("number of parameters for %s() must be at least %hu but is %d.", funct->fname,
                          funct->minParams, nParams);
        } else if (nParams > funct->maxParams) {
            parser_errmsg("number of parameters for %s() must be at most %hu but is %d.", funct->fname,
                          funct->maxParams, nParams);
        } else {
            retPtr = funct->fPtr;
        }
    }

    return retPtr;
}

static struct scriptFunct *ATTR_NONNULL() searchFunctArray(const char *const fname, struct scriptFunct *functArray) {
    struct scriptFunct *retPtr = NULL;
    int i = 0;
    while (functArray[i].fname != NULL) {
        if (!strcmp(fname, functArray[i].fname)) {
            retPtr = functArray + i;
            goto done;
        }
        i++;
    }
done:
    return retPtr;
}

static struct scriptFunct *ATTR_NONNULL() searchModList(const char *const fname) {
    struct modListNode *modListCurr = modListRoot;
    struct scriptFunct *foundFunct;

    do {
        foundFunct = searchFunctArray(fname, modListCurr->modFcts);
        if (foundFunct != NULL) {
            return foundFunct;
        }
        modListCurr = modListCurr->next;
    } while (modListCurr != NULL);
    return NULL;
}

static void cnffuncDestruct(struct cnffunc *func) {
    unsigned short i;

    for (i = 0; i < func->nParams; ++i) {
        cnfexprDestruct(func->expr[i]);
    }

    /* some functions require special destruction */
    char *cstr = es_str2cstr(func->fname, NULL);
    struct scriptFunct *foundFunc = searchModList(cstr);
    free(cstr);
    if (foundFunc && foundFunc->destruct != NULL) {
        foundFunc->destruct(func);
    }

    if (func->destructable_funcdata) {
        free(func->funcdata);
    }
    free(func->fname);
}

/* Destruct an expression and all sub-expressions contained in it.
 */
void cnfexprDestruct(struct cnfexpr *__restrict__ const expr) {
    if (expr == NULL) {
        /* this is valid and can happen during optimizer run! */
        DBGPRINTF("cnfexprDestruct got NULL ptr - valid, so doing nothing\n");
        return;
    }

    DBGPRINTF("cnfexprDestruct expr %p, type '%s'\n", expr, tokenToString(expr->nodetype));
    switch (expr->nodetype) {
        case CMP_NE:
        case CMP_EQ:
        case CMP_LE:
        case CMP_GE:
        case CMP_LT:
        case CMP_GT:
        case CMP_STARTSWITH:
        case CMP_ENDSWITH:
        case CMP_STARTSWITHI:
        case CMP_CONTAINS:
        case CMP_CONTAINSI:
        case OR:
        case AND:
        case '&':
        case '+':
        case '-':
        case '*':
        case '/':
        case '%': /* binary */
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
            break;
        case NOT:
        case 'M': /* unary */
            cnfexprDestruct(expr->r);
            break;
        case 'N':
            break;
        case 'S':
            es_deleteStr(((struct cnfstringval *)expr)->estr);
            break;
        case 'V':
            free(((struct cnfvar *)expr)->name);
            msgPropDescrDestruct(&(((struct cnfvar *)expr)->prop));
            break;
        case 'F':
            cnffuncDestruct((struct cnffunc *)expr);
            break;
        case 'A':
            cnfarrayContentDestruct((struct cnfarray *)expr);
            break;
        default:
            break;
    }
    free(expr);
}

//---- END


/* Evaluate an expression as a bool. This is added because expressions are
 * mostly used inside filters, and so this function is quite common and
 * important.
 */
int cnfexprEvalBool(struct cnfexpr *__restrict__ const expr, void *__restrict__ const usrptr, wti_t *const pWti) {
    int convok;
    struct svar ret;
    cnfexprEval(expr, &ret, usrptr, pWti);
    int retVal = var2Number(&ret, &convok);
    varFreeMembers(&ret);
    return retVal;
}

struct json_object *cnfexprEvalCollection(struct cnfexpr *__restrict__ const expr,
                                          void *__restrict__ const usrptr,
                                          wti_t *const pWti) {
    struct svar ret;
    void *retptr;
    cnfexprEval(expr, &ret, usrptr, pWti);
    if (ret.datatype == 'J') {
        retptr = ret.d.json; /*caller is supposed to free the returned json-object*/
    } else {
        retptr = NULL;
        varFreeMembers(&ret); /* we must free the element */
    }
    return retptr;
}

static void doIndent(int indent) {
    int i;
    for (i = 0; i < indent; ++i) dbgprintf("  ");
}

static void pmaskPrint(uchar *pmask, int indent) {
    int i;
    doIndent(indent);
    dbgprintf("pmask: ");
    for (i = 0; i <= LOG_NFACILITIES; i++)
        if (pmask[i] == TABLE_NOPRI)
            dbgprintf(" X ");
        else
            dbgprintf("%2X ", pmask[i]);
    dbgprintf("\n");
}

static void cnfarrayPrint(struct cnfarray *ar, int indent) {
    int i;
    doIndent(indent);
    dbgprintf("ARRAY:\n");
    if (ar == NULL) {
        doIndent(indent + 1);
        dbgprintf("(empty)\n");
        return;
    }
    for (i = 0; i < ar->nmemb; ++i) {
        doIndent(indent + 1);
        cstrPrint("string '", ar->arr[i]);
        dbgprintf("'\n");
    }
}

void cnfexprPrint(struct cnfexpr *expr, int indent) {
    struct cnffunc *func;
    char *fname;
    int i;

    switch (expr->nodetype) {
        case CMP_EQ:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("==\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_NE:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("!=\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_LE:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("<=\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_GE:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf(">=\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_LT:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("<\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_GT:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf(">\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_CONTAINS:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("CONTAINS\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_CONTAINSI:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("CONTAINS_I\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_STARTSWITH:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("STARTSWITH\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_ENDSWITH:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("ENDSWITH\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case CMP_STARTSWITHI:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("STARTSWITH_I\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case OR:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("OR\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case AND:
            cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("AND\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case NOT:
            doIndent(indent);
            dbgprintf("NOT\n");
            cnfexprPrint(expr->r, indent + 1);
            break;
        case S_FUNC_EXISTS:
            doIndent(indent);
            dbgprintf("exists(%s)\n", ((struct cnffuncexists *)expr)->varname);
            break;
        case 'S':
            doIndent(indent);
            cstrPrint("string '", ((struct cnfstringval *)expr)->estr);
            dbgprintf("'\n");
            break;
        case 'A':
            cnfarrayPrint((struct cnfarray *)expr, indent);
            break;
        case 'N':
            doIndent(indent);
            dbgprintf("%lld\n", ((struct cnfnumval *)expr)->val);
            break;
        case 'V':
            doIndent(indent);
            dbgprintf("var '%s'\n", ((struct cnfvar *)expr)->name);
            break;
        case 'F':
            doIndent(indent);
            func = (struct cnffunc *)expr;
            cstrPrint("function '", func->fname);
            fname = es_str2cstr(func->fname, NULL);
            dbgprintf("' (name:%s, params:%hu)\n", fname, func->nParams);
            free(fname);
            if (func->fPtr == doFunct_Prifilt) {
                struct funcData_prifilt *pD;
                pD = (struct funcData_prifilt *)func->funcdata;
                pmaskPrint(pD->pmask, indent + 1);
            }
            for (i = 0; i < func->nParams; ++i) {
                cnfexprPrint(func->expr[i], indent + 1);
            }
            break;
        case '&':
        case '+':
        case '-':
        case '*':
        case '/':
        case '%':
        case 'M':
            if (expr->l != NULL) cnfexprPrint(expr->l, indent + 1);
            doIndent(indent);
            dbgprintf("%c\n", (char)expr->nodetype);
            cnfexprPrint(expr->r, indent + 1);
            break;
        default:
            dbgprintf("error: unknown nodetype %u['%c']\n", (unsigned)expr->nodetype, (char)expr->nodetype);
            assert(0); /* abort on debug builds, this must not happen! */
            break;
    }
}

/* print only the given stmt
 * if "subtree" equals 1, the full statement subtree is printed, else
 * really only the statement.
 */
void cnfstmtPrintOnly(struct cnfstmt *stmt, int indent, sbool subtree) {
    char *cstr;
    switch (stmt->nodetype) {
        case S_NOP:
            doIndent(indent);
            dbgprintf("NOP\n");
            break;
        case S_STOP:
            doIndent(indent);
            dbgprintf("STOP\n");
            break;
        case S_CALL:
            cstr = es_str2cstr(stmt->d.s_call.name, NULL);
            doIndent(indent);
            dbgprintf("CALL [%s, queue:%d]\n", cstr, stmt->d.s_call.ruleset == NULL ? 0 : 1);
            free(cstr);
            break;
        case S_CALL_INDIRECT:
            doIndent(indent);
            dbgprintf("CALL_INDIRECT\n");
            cnfexprPrint(stmt->d.s_call_ind.expr, indent + 1);
            break;
        case S_ACT:
            doIndent(indent);
            dbgprintf("ACTION %d [%s:%s]\n", stmt->d.act->iActionNbr, modGetName(stmt->d.act->pMod), stmt->printable);
            break;
        case S_IF:
            doIndent(indent);
            dbgprintf("IF\n");
            cnfexprPrint(stmt->d.s_if.expr, indent + 1);
            if (subtree) {
                doIndent(indent);
                dbgprintf("THEN\n");
                cnfstmtPrint(stmt->d.s_if.t_then, indent + 1);
                if (stmt->d.s_if.t_else != NULL) {
                    doIndent(indent);
                    dbgprintf("ELSE\n");
                    cnfstmtPrint(stmt->d.s_if.t_else, indent + 1);
                }
                doIndent(indent);
                dbgprintf("END IF\n");
            }
            break;
        case S_FOREACH:
            doIndent(indent);
            dbgprintf("FOREACH %s IN\n", stmt->d.s_foreach.iter->var);
            cnfexprPrint(stmt->d.s_foreach.iter->collection, indent + 1);
            if (subtree) {
                doIndent(indent);
                dbgprintf("DO\n");
                cnfstmtPrint(stmt->d.s_foreach.body, indent + 1);
                doIndent(indent);
                dbgprintf("END FOREACH\n");
            }
            break;
        case S_SET:
            doIndent(indent);
            dbgprintf("SET %s =\n", stmt->d.s_set.varname);
            cnfexprPrint(stmt->d.s_set.expr, indent + 1);
            doIndent(indent);
            dbgprintf("END SET\n");
            break;
        case S_UNSET:
            doIndent(indent);
            dbgprintf("UNSET %s\n", stmt->d.s_unset.varname);
            break;
        case S_RELOAD_LOOKUP_TABLE:
            doIndent(indent);
            dbgprintf("RELOAD_LOOKUP_TABLE table(%s) (stub with '%s' on error)",
                      stmt->d.s_reload_lookup_table.table_name, stmt->d.s_reload_lookup_table.stub_value);
            break;
        case S_PRIFILT:
            doIndent(indent);
            dbgprintf("PRIFILT '%s'\n", stmt->printable);
            pmaskPrint(stmt->d.s_prifilt.pmask, indent);
            if (subtree) {
                cnfstmtPrint(stmt->d.s_prifilt.t_then, indent + 1);
                if (stmt->d.s_prifilt.t_else != NULL) {
                    doIndent(indent);
                    dbgprintf("ELSE\n");
                    cnfstmtPrint(stmt->d.s_prifilt.t_else, indent + 1);
                }
                doIndent(indent);
                dbgprintf("END PRIFILT\n");
            }
            break;
        case S_PROPFILT:
            doIndent(indent);
            dbgprintf("PROPFILT\n");
            doIndent(indent);
            dbgprintf("\tProperty.: '%s'\n", propIDToName(stmt->d.s_propfilt.prop.id));
            if (stmt->d.s_propfilt.prop.id == PROP_CEE || stmt->d.s_propfilt.prop.id == PROP_LOCAL_VAR ||
                stmt->d.s_propfilt.prop.id == PROP_GLOBAL_VAR) {
                doIndent(indent);
                dbgprintf("\tCEE-Prop.: '%s'\n", stmt->d.s_propfilt.prop.name);
            }
            doIndent(indent);
            dbgprintf("\tOperation: ");
            if (stmt->d.s_propfilt.isNegated) dbgprintf("NOT ");
            dbgprintf("'%s'\n", getFIOPName(stmt->d.s_propfilt.operation));
            if (stmt->d.s_propfilt.pCSCompValue != NULL) {
                doIndent(indent);
                dbgprintf("\tValue....: '%s'\n", rsCStrGetSzStrNoNULL(stmt->d.s_propfilt.pCSCompValue));
            }
            if (subtree) {
                doIndent(indent);
                dbgprintf("THEN\n");
                cnfstmtPrint(stmt->d.s_propfilt.t_then, indent + 1);
                doIndent(indent);
                dbgprintf("END PROPFILT\n");
            }
            break;
        default:
            dbgprintf("error: unknown stmt type %u\n", (unsigned)stmt->nodetype);
            break;
    }
}
void cnfstmtPrint(struct cnfstmt *root, int indent) {
    struct cnfstmt *stmt;
    for (stmt = root; stmt != NULL; stmt = stmt->next) {
        cnfstmtPrintOnly(stmt, indent, 1);
    }
}

struct cnfnumval *cnfnumvalNew(const long long val) {
    struct cnfnumval *numval;
    if ((numval = malloc(sizeof(struct cnfnumval))) != NULL) {
        numval->nodetype = 'N';
        numval->val = val;
    }
    return numval;
}

struct cnfstringval *cnfstringvalNew(es_str_t *const estr) {
    struct cnfstringval *strval;
    if ((strval = malloc(sizeof(struct cnfstringval))) != NULL) {
        strval->nodetype = 'S';
        strval->estr = estr;
    }
    return strval;
}

/* creates array AND adds first element to it */
struct cnfarray *cnfarrayNew(es_str_t *val) {
    struct cnfarray *ar;
    if ((ar = malloc(sizeof(struct cnfarray))) != NULL) {
        ar->nodetype = 'A';
        ar->nmemb = 0;
        ar->arr = NULL;
        if (val == NULL) {
            goto done;
        }
        ar->nmemb = 1;
        if ((ar->arr = malloc(sizeof(es_str_t *))) == NULL) {
            free(ar);
            ar = NULL;
            goto done;
        }
        ar->arr[0] = val;
    }
done:
    return ar;
}

struct cnfarray *cnfarrayAdd(struct cnfarray *__restrict__ const ar, es_str_t *__restrict__ val) {
    es_str_t **newptr;
    if ((newptr = realloc(ar->arr, (ar->nmemb + 1) * sizeof(es_str_t *))) == NULL) {
        DBGPRINTF("cnfarrayAdd: realloc failed, item ignored, ar->arr=%p\n", ar->arr);
        goto done;
    } else {
        ar->arr = newptr;
        ar->arr[ar->nmemb] = val;
        ar->nmemb++;
    }
done:
    return ar;
}

/* duplicate an array (deep copy) */
struct cnfarray *cnfarrayDup(struct cnfarray *old) {
    int i;
    struct cnfarray *ar;
    if (old == NULL || old->nmemb == 0) {
        return cnfarrayNew(NULL);
    }
    ar = cnfarrayNew(es_strdup(old->arr[0]));
    for (i = 1; i < old->nmemb; ++i) {
        cnfarrayAdd(ar, es_strdup(old->arr[i]));
    }
    return ar;
}

struct cnfvar *cnfvarNew(char *name) {
    struct cnfvar *var;
    if ((var = malloc(sizeof(struct cnfvar))) != NULL) {
        var->nodetype = 'V';
        var->name = name;
        msgPropDescrFill(&var->prop, (uchar *)var->name, strlen(var->name));
    }
    return var;
}

struct cnfstmt *cnfstmtNew(unsigned s_type) {
    struct cnfstmt *cnfstmt;
    if ((cnfstmt = malloc(sizeof(struct cnfstmt))) != NULL) {
        cnfstmt->nodetype = s_type;
        cnfstmt->printable = NULL;
        cnfstmt->next = NULL;
    }
    return cnfstmt;
}

/* This function disables a cnfstmt by setting it to NOP. This is
 * useful when we detect errors late in the parsing processing, where
 * we need to return a valid cnfstmt. The optimizer later removes the
 * NOPs, so all is well.
 * NOTE: this call assumes that no dynamic data structures have been
 * allocated. If so, these MUST be freed before calling cnfstmtDisable().
 */
static void cnfstmtDisable(struct cnfstmt *cnfstmt) {
    cnfstmt->nodetype = S_NOP;
}

static void cnfIteratorDestruct(struct cnfitr *itr);

/* delete a single stmt */
static void cnfstmtDestruct(struct cnfstmt *stmt) {
    switch (stmt->nodetype) {
        case S_NOP:
        case S_STOP:
            break;
        case S_CALL:
            es_deleteStr(stmt->d.s_call.name);
            break;
        case S_CALL_INDIRECT:
            cnfexprDestruct(stmt->d.s_call_ind.expr);
            break;
        case S_ACT:
            actionDestruct(stmt->d.act);
            break;
        case S_IF:
            cnfexprDestruct(stmt->d.s_if.expr);
            if (stmt->d.s_if.t_then != NULL) {
                cnfstmtDestructLst(stmt->d.s_if.t_then);
            }
            if (stmt->d.s_if.t_else != NULL) {
                cnfstmtDestructLst(stmt->d.s_if.t_else);
            }
            break;
        case S_FOREACH:
            cnfIteratorDestruct(stmt->d.s_foreach.iter);
            cnfstmtDestructLst(stmt->d.s_foreach.body);
            break;
        case S_SET:
            free(stmt->d.s_set.varname);
            cnfexprDestruct(stmt->d.s_set.expr);
            break;
        case S_UNSET:
            free(stmt->d.s_set.varname);
            break;
        case S_PRIFILT:
            cnfstmtDestructLst(stmt->d.s_prifilt.t_then);
            cnfstmtDestructLst(stmt->d.s_prifilt.t_else);
            break;
        case S_PROPFILT:
            msgPropDescrDestruct(&stmt->d.s_propfilt.prop);
            if (stmt->d.s_propfilt.regex_cache != NULL) rsCStrRegexDestruct(&stmt->d.s_propfilt.regex_cache);
            if (stmt->d.s_propfilt.pCSCompValue != NULL) cstrDestruct(&stmt->d.s_propfilt.pCSCompValue);
            cnfstmtDestructLst(stmt->d.s_propfilt.t_then);
            break;
        case S_RELOAD_LOOKUP_TABLE:
            if (stmt->d.s_reload_lookup_table.table_name != NULL) {
                free(stmt->d.s_reload_lookup_table.table_name);
            }
            if (stmt->d.s_reload_lookup_table.stub_value != NULL) {
                free(stmt->d.s_reload_lookup_table.stub_value);
            }
            break;
        default:
            DBGPRINTF("error: unknown stmt type during destruct %u\n", (unsigned)stmt->nodetype);
            break;
    }
    free(stmt->printable);
    free(stmt);
}

/* delete a stmt and all others following it */
void cnfstmtDestructLst(struct cnfstmt *root) {
    struct cnfstmt *stmt, *todel;
    for (stmt = root; stmt != NULL;) {
        todel = stmt;
        stmt = stmt->next;
        cnfstmtDestruct(todel);
    }
}

struct cnfitr *cnfNewIterator(char *var, struct cnfexpr *collection) {
    struct cnfitr *itr;
    if ((itr = malloc(sizeof(struct cnfitr))) != NULL) {
        itr->var = var;
        itr->collection = collection;
    }
    return itr;
}

static void cnfIteratorDestruct(struct cnfitr *itr) {
    free(itr->var);
    if (itr->collection != NULL) cnfexprDestruct(itr->collection);
    free(itr);
}

struct cnfstmt *cnfstmtNewSet(char *var, struct cnfexpr *expr, int force_reset) {
    propid_t propid;
    struct cnfstmt *cnfstmt;
    if ((cnfstmt = cnfstmtNew(S_SET)) != NULL) {
        if (propNameToID((uchar *)var, &propid) == RS_RET_OK &&
            (propid == PROP_CEE || propid == PROP_LOCAL_VAR || propid == PROP_GLOBAL_VAR)) {
            cnfstmt->d.s_set.varname = (uchar *)var;
            cnfstmt->d.s_set.expr = expr;
            cnfstmt->d.s_set.force_reset = force_reset;
        } else {
            parser_errmsg("invalid variable '%s' in set statement.", var);
            free(var);
            cnfstmtDisable(cnfstmt);
        }
    }
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewCall(es_str_t *name) {
    struct cnfstmt *cnfstmt;
    if ((cnfstmt = cnfstmtNew(S_CALL)) != NULL) {
        cnfstmt->d.s_call.name = name;
        cnfstmt->d.s_call.ruleset = NULL;
    }
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewReloadLookupTable(struct cnffparamlst *fparams) {
    int nParams;
    struct cnffparamlst *param, *nxt;
    struct cnfstmt *cnfstmt;
    uint8_t failed = 0;
    if ((cnfstmt = cnfstmtNew(S_RELOAD_LOOKUP_TABLE)) != NULL) {
        nParams = 0;
        for (param = fparams; param != NULL; param = param->next) {
            ++nParams;
        }
        cnfstmt->d.s_reload_lookup_table.table_name = cnfstmt->d.s_reload_lookup_table.stub_value = NULL;
        switch (nParams) {
            case 2:
                param = fparams->next;
                if (param->expr->nodetype != 'S') {
                    parser_errmsg(
                        "statement ignored: reload_lookup_table(table_name, "
                        "optional:stub_value_in_case_reload_fails) "
                        "expects a litteral string for second argument\n");
                    failed = 1;
                }
                if ((cnfstmt->d.s_reload_lookup_table.stub_value =
                         (uchar *)es_str2cstr(((struct cnfstringval *)param->expr)->estr, NULL)) == NULL) {
                    parser_errmsg(
                        "statement ignored: reload_lookup_table statement "
                        "failed to allocate memory for lookup-table stub-value\n");
                    failed = 1;
                }
                CASE_FALLTHROUGH
            case 1:
                param = fparams;
                if (param->expr->nodetype != 'S') {
                    parser_errmsg(
                        "statement ignored: reload_lookup_table(table_name, "
                        "optional:stub_value_in_case_reload_fails) "
                        "expects a litteral string for first argument\n");
                    failed = 1;
                }
                if ((cnfstmt->d.s_reload_lookup_table.table_name =
                         (uchar *)es_str2cstr(((struct cnfstringval *)param->expr)->estr, NULL)) == NULL) {
                    parser_errmsg(
                        "statement ignored: reload_lookup_table statement "
                        "failed to allocate memory for lookup-table name\n");
                    failed = 1;
                }
                break;
            default:
                parser_errmsg(
                    "statement ignored: reload_lookup_table(table_name, optional:"
                    "stub_value_in_case_reload_fails) "
                    "expected 1 or 2 arguments, but found '%d'\n",
                    nParams);
                failed = 1;
        }
    }
    param = fparams;
    while (param != NULL) {
        nxt = param->next;
        if (param->expr != NULL) cnfexprDestruct(param->expr);
        free(param);
        param = nxt;
    }
    if (failed) {
        cnfstmt->nodetype = S_NOP;
        if (cnfstmt->d.s_reload_lookup_table.table_name != NULL) {
            free(cnfstmt->d.s_reload_lookup_table.table_name);
        }
        if (cnfstmt->d.s_reload_lookup_table.stub_value != NULL) {
            free(cnfstmt->d.s_reload_lookup_table.stub_value);
        }
    }
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewUnset(char *var) {
    propid_t propid;
    struct cnfstmt *cnfstmt;
    if ((cnfstmt = cnfstmtNew(S_UNSET)) != NULL) {
        if (propNameToID((uchar *)var, &propid) == RS_RET_OK &&
            (propid == PROP_CEE || propid == PROP_LOCAL_VAR || propid == PROP_GLOBAL_VAR)) {
            cnfstmt->d.s_unset.varname = (uchar *)var;
        } else {
            parser_errmsg("invalid variable '%s' in unset statement.", var);
            free(var);
            cnfstmtDisable(cnfstmt);
        }
    }
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewContinue(void) {
    struct cnfstmt *cnfstmt = cnfstmtNew(S_NOP);
    if (cnfstmt != NULL) {
        cnfstmt->printable = (uchar *)strdup("continue");
    }
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewPRIFILT(char *prifilt, struct cnfstmt *t_then) {
    struct cnfstmt *cnfstmt;
    if ((cnfstmt = cnfstmtNew(S_PRIFILT)) != NULL) {
        cnfstmt->printable = (uchar *)prifilt;
        cnfstmt->d.s_prifilt.t_then = t_then;
        cnfstmt->d.s_prifilt.t_else = NULL;
        if (glblPermitSyslogdConfigFilter(loadConf, prifilt)) {
            DecodePRIFilter((uchar *)prifilt, cnfstmt->d.s_prifilt.pmask);
        } else {
            free(cnfstmt->printable);
            cnfstmt->printable = NULL;
            cnfstmt->nodetype = S_NOP;
            cnfstmtDestructLst(t_then);
            cnfstmt->d.s_prifilt.t_then = NULL;
        }
    }
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewPROPFILT(char *propfilt, struct cnfstmt *t_then) {
    struct cnfstmt *cnfstmt;
    if ((cnfstmt = cnfstmtNew(S_PROPFILT)) != NULL) {
        cnfstmt->printable = (uchar *)propfilt;
        cnfstmt->d.s_propfilt.t_then = t_then;
        cnfstmt->d.s_propfilt.regex_cache = NULL;
        cnfstmt->d.s_propfilt.pCSCompValue = NULL;
        if (!glblPermitPropertyConfigFilter(loadConf, propfilt)) {
            free(cnfstmt->printable);
            cnfstmt->printable = NULL;
            cnfstmt->nodetype = S_NOP;
            cnfstmtDestructLst(t_then);
            cnfstmt->d.s_propfilt.t_then = NULL;
        } else if (DecodePropFilter((uchar *)propfilt, cnfstmt) != RS_RET_OK) {
            cnfstmt->nodetype = S_NOP; /* disable action! */
            cnfstmtDestructLst(t_then); /* we do no longer need this */
        }
    }
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewAct(struct nvlst *lst) {
    struct cnfstmt *cnfstmt;
    char namebuf[256];
    rsRetVal localRet;
    if ((cnfstmt = cnfstmtNew(S_ACT)) == NULL) {
        goto done;
    }
    if (nvlstChkDisabled(lst)) {
        /* Do not instantiate a disabled action: skip actionNewInst() entirely so
         * the module's instance is never created and its checkCnf/newActInst side
         * effects (e.g. omelasticsearch probing the server) never run - matching
         * how disabled includes and input objects are skipped. The statement is
         * turned into a NOP the optimizer later removes.
         */
        dbgprintf("action disabled by configuration\n");
        cnfstmtDisable(cnfstmt);
        goto done;
    }
    localRet = actionNewInst(lst, &cnfstmt->d.act);
    if (localRet == RS_RET_OK_WARN) {
        parser_errmsg("warnings occurred in file '%s' around line %d", cnfcurrfn, yylineno);
    } else if (localRet != RS_RET_OK) {
        parser_errmsg("errors occurred in file '%s' around line %d", cnfcurrfn, yylineno);
        cnfstmt->nodetype = S_NOP; /* disable action! */
        goto done;
    }
    snprintf(namebuf, sizeof(namebuf) - 1, "action(type=\"%s\" ...)", modGetName(cnfstmt->d.act->pMod));
    namebuf[255] = '\0'; /* be on safe side */
    cnfstmt->printable = (uchar *)strdup(namebuf);
    nvlstChkUnused(lst);
done:
    /* Single cleanup point: cnfstmtNewAct owns lst on every path (the module only
     * reads it, and addAction keeps an independent clone as pSyntaxLst), so one
     * nvlstDestruct() frees it exactly once. This also covers the early
     * error / cnfstmtNew()-failure returns that previously leaked lst.
     */
    nvlstDestruct(lst);
    return cnfstmt;
}

struct cnfstmt *cnfstmtNewLegaAct(char *actline) {
    struct cnfstmt *cnfstmt;
    rsRetVal localRet;
    if ((cnfstmt = cnfstmtNew(S_ACT)) == NULL) goto done;
    cnfstmt->printable = (uchar *)strdup((char *)actline);
    localRet = cflineDoAction(loadConf, (uchar **)&actline, &cnfstmt->d.act);
    if (localRet != RS_RET_OK) {
        parser_errmsg("%s occurred in file '%s' around line %d", (localRet == RS_RET_OK_WARN) ? "warnings" : "errors",
                      cnfcurrfn, yylineno);
        if (localRet != RS_RET_OK_WARN) {
            cnfstmt->nodetype = S_NOP; /* disable action! */
            goto done;
        }
    }
done:
    return cnfstmt;
}


/* returns 1 if the two expressions are constants, 0 otherwise
 * if both are constants, the expression subtrees are destructed
 * (this is an aid for constant folding optimizing)
 */
static int getConstNumber(struct cnfexpr *expr, long long *l, long long *r) {
    int ret = 0;
    cnfexprOptimize(expr->l);
    cnfexprOptimize(expr->r);
    if (expr->l->nodetype == 'N') {
        if (expr->r->nodetype == 'N') {
            ret = 1;
            *l = ((struct cnfnumval *)expr->l)->val;
            *r = ((struct cnfnumval *)expr->r)->val;
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
        } else if (expr->r->nodetype == 'S') {
            ret = 1;
            *l = ((struct cnfnumval *)expr->l)->val;
            *r = es_str2num(((struct cnfstringval *)expr->r)->estr, NULL);
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
        }
    } else if (expr->l->nodetype == 'S') {
        if (expr->r->nodetype == 'N') {
            ret = 1;
            *l = es_str2num(((struct cnfstringval *)expr->l)->estr, NULL);
            *r = ((struct cnfnumval *)expr->r)->val;
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
        } else if (expr->r->nodetype == 'S') {
            ret = 1;
            *l = es_str2num(((struct cnfstringval *)expr->l)->estr, NULL);
            *r = es_str2num(((struct cnfstringval *)expr->r)->estr, NULL);
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
        }
    }
    return ret;
}


/* constant folding for string concatenation */
static void constFoldConcat(struct cnfexpr *expr) {
    es_str_t *estr;
    cnfexprOptimize(expr->l);
    cnfexprOptimize(expr->r);
    if (expr->l->nodetype == 'S') {
        if (expr->r->nodetype == 'S') {
            estr = ((struct cnfstringval *)expr->l)->estr;
            ((struct cnfstringval *)expr->l)->estr = NULL;
            es_addStr(&estr, ((struct cnfstringval *)expr->r)->estr);
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
            expr->nodetype = 'S';
            ((struct cnfstringval *)expr)->estr = estr;
        } else if (expr->r->nodetype == 'N') {
            es_str_t *numstr;
            estr = ((struct cnfstringval *)expr->l)->estr;
            ((struct cnfstringval *)expr->l)->estr = NULL;
            numstr = es_newStrFromNumber(((struct cnfnumval *)expr->r)->val);
            es_addStr(&estr, numstr);
            es_deleteStr(numstr);
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
            expr->nodetype = 'S';
            ((struct cnfstringval *)expr)->estr = estr;
        }
    } else if (expr->l->nodetype == 'N') {
        if (expr->r->nodetype == 'S') {
            estr = es_newStrFromNumber(((struct cnfnumval *)expr->l)->val);
            es_addStr(&estr, ((struct cnfstringval *)expr->r)->estr);
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
            expr->nodetype = 'S';
            ((struct cnfstringval *)expr)->estr = estr;
        } else if (expr->r->nodetype == 'N') {
            es_str_t *numstr;
            estr = es_newStrFromNumber(((struct cnfnumval *)expr->l)->val);
            numstr = es_newStrFromNumber(((struct cnfnumval *)expr->r)->val);
            es_addStr(&estr, numstr);
            es_deleteStr(numstr);
            cnfexprDestruct(expr->l);
            cnfexprDestruct(expr->r);
            expr->nodetype = 'S';
            ((struct cnfstringval *)expr)->estr = estr;
        }
    }
}

/* constant folding for literal string/number comparisons */
static int constFoldCmp(struct cnfexpr *expr) {
    long long ln, rn;
    int convok_l, convok_r;
    int folded = 0;
    int result = 0;

    if (expr->l->nodetype == 'S' && expr->r->nodetype == 'S') {
        result = es_strcmp(((struct cnfstringval *)expr->l)->estr, ((struct cnfstringval *)expr->r)->estr);
        if (expr->nodetype == CMP_EQ)
            result = !result;
        else
            result = !!result;
        folded = 1;
    } else if (expr->l->nodetype == 'N' && expr->r->nodetype == 'N') {
        ln = ((struct cnfnumval *)expr->l)->val;
        rn = ((struct cnfnumval *)expr->r)->val;
        result = (expr->nodetype == CMP_EQ) ? (ln == rn) : (ln != rn);
        folded = 1;
    } else if (expr->l->nodetype == 'S' && expr->r->nodetype == 'N') {
        ln = str2num(((struct cnfstringval *)expr->l)->estr, &convok_l);
        if (convok_l) {
            rn = ((struct cnfnumval *)expr->r)->val;
            result = (expr->nodetype == CMP_EQ) ? (ln == rn) : (ln != rn);
            folded = 1;
        } else {
            es_str_t *numstr = es_newStrFromNumber(((struct cnfnumval *)expr->r)->val);
            if (numstr != NULL) {
                result = es_strcmp(((struct cnfstringval *)expr->l)->estr, numstr);
                es_deleteStr(numstr);
                if (expr->nodetype == CMP_EQ)
                    result = !result;
                else
                    result = !!result;
                folded = 1;
            }
        }
    } else if (expr->l->nodetype == 'N' && expr->r->nodetype == 'S') {
        rn = str2num(((struct cnfstringval *)expr->r)->estr, &convok_r);
        if (convok_r) {
            ln = ((struct cnfnumval *)expr->l)->val;
            result = (expr->nodetype == CMP_EQ) ? (ln == rn) : (ln != rn);
            folded = 1;
        } else {
            es_str_t *numstr = es_newStrFromNumber(((struct cnfnumval *)expr->l)->val);
            if (numstr != NULL) {
                result = es_strcmp(((struct cnfstringval *)expr->r)->estr, numstr);
                es_deleteStr(numstr);
                if (expr->nodetype == CMP_EQ)
                    result = !result;
                else
                    result = !!result;
                folded = 1;
            }
        }
    }

    if (folded) {
        cnfexprDestruct(expr->l);
        cnfexprDestruct(expr->r);
        expr->nodetype = 'N';
        ((struct cnfnumval *)expr)->val = result;
    }
    return folded;
}


/* optimize comparisons with syslog severity/facility. This is a special
 * handler as the numerical values also support GT, LT, etc ops.
 */
static struct cnfexpr *cnfexprOptimize_CMP_severity_facility(struct cnfexpr *expr) {
    struct cnffunc *func;

    if (expr->l->nodetype != 'V') FINALIZE;

    if (!strcmp("syslogseverity", ((struct cnfvar *)expr->l)->name)) {
        if (expr->r->nodetype == 'N') {
            int sev = (int)((struct cnfnumval *)expr->r)->val;
            if (sev >= 0 && sev <= 7) {
                DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n");
                func = cnffuncNew_prifilt(0); /* fac is irrelevant, set below... */
                prifiltSetSeverity(func->funcdata, sev, expr->nodetype);
                cnfexprDestruct(expr);
                expr = (struct cnfexpr *)func;
            } else {
                parser_errmsg(
                    "invalid syslogseverity %d, expression will always "
                    "evaluate to FALSE",
                    sev);
            }
        }
    } else if (!strcmp("syslogfacility", ((struct cnfvar *)expr->l)->name)) {
        if (expr->r->nodetype == 'N') {
            int fac = (int)((struct cnfnumval *)expr->r)->val;
            if (fac >= 0 && fac <= 24) {
                DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n");
                func = cnffuncNew_prifilt(0); /* fac is irrelevant, set below... */
                prifiltSetFacility(func->funcdata, fac, expr->nodetype);
                cnfexprDestruct(expr);
                expr = (struct cnfexpr *)func;
            } else {
                parser_errmsg(
                    "invalid syslogfacility %d, expression will always "
                    "evaluate to FALSE",
                    fac);
            }
        }
    }
finalize_it:
    return expr;
}

/* optimize a comparison with a variable as left-hand operand
 * NOTE: Currently support CMP_EQ, CMP_NE only and code NEEDS
 *       TO BE CHANGED fgr other comparisons!
 */
static struct cnfexpr *cnfexprOptimize_CMP_var(struct cnfexpr *expr) {
    struct cnffunc *func;

    if (!strcmp("syslogfacility-text", ((struct cnfvar *)expr->l)->name)) {
        if (expr->r->nodetype == 'S') {
            char *cstr = es_str2cstr(((struct cnfstringval *)expr->r)->estr, NULL);
            int fac = decodeSyslogName((uchar *)cstr, syslogFacNames);
            if (fac == -1) {
                parser_errmsg(
                    "invalid facility '%s', expression will always "
                    "evaluate to FALSE",
                    cstr);
            } else {
                /* we can actually optimize! */
                DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n");
                func = cnffuncNew_prifilt(fac);
                if (expr->nodetype == CMP_NE) prifiltInvert(func->funcdata);
                cnfexprDestruct(expr);
                expr = (struct cnfexpr *)func;
            }
            free(cstr);
        }
    } else if (!strcmp("syslogseverity-text", ((struct cnfvar *)expr->l)->name)) {
        if (expr->r->nodetype == 'S') {
            char *cstr = es_str2cstr(((struct cnfstringval *)expr->r)->estr, NULL);
            int sev = decodeSyslogName((uchar *)cstr, syslogPriNames);
            if (sev == -1) {
                parser_errmsg(
                    "invalid syslogseverity '%s', expression will always "
                    "evaluate to FALSE",
                    cstr);
            } else {
                /* we can acutally optimize! */
                DBGPRINTF("optimizer: change comparison OP to FUNC prifilt()\n");
                func = cnffuncNew_prifilt(0);
                prifiltSetSeverity(func->funcdata, sev, expr->nodetype);
                cnfexprDestruct(expr);
                expr = (struct cnfexpr *)func;
            }
            free(cstr);
        }
    } else {
        expr = cnfexprOptimize_CMP_severity_facility(expr);
    }
    return expr;
}

static struct cnfexpr *cnfexprOptimize_NOT(struct cnfexpr *expr) {
    struct cnffunc *func;

    if (expr->r->nodetype == 'F') {
        func = (struct cnffunc *)expr->r;
        if (func->fPtr == doFunct_Prifilt) {
            DBGPRINTF("optimize NOT prifilt() to inverted prifilt()\n");
            expr->r = NULL;
            cnfexprDestruct(expr);
            prifiltInvert(func->funcdata);
            expr = (struct cnfexpr *)func;
        }
    }
    return expr;
}

static struct cnfexpr *cnfexprOptimize_AND_OR(struct cnfexpr *expr) {
    struct cnffunc *funcl, *funcr;

    /* the constant-operand warning is emitted at construction time in
     * cnfexprNew(); see the comment there for why it cannot live here.
     */
    if (expr->l->nodetype == 'F') {
        if (expr->r->nodetype == 'F') {
            funcl = (struct cnffunc *)expr->l;
            funcr = (struct cnffunc *)expr->r;
            if (funcl->fPtr == doFunct_Prifilt && funcr->fPtr == doFunct_Prifilt) {
                DBGPRINTF("optimize combine AND/OR prifilt()\n");
                expr->l = NULL;
                prifiltCombine(funcl->funcdata, funcr->funcdata, expr->nodetype);
                cnfexprDestruct(expr);
                expr = (struct cnfexpr *)funcl;
            }
        }
    }
    return expr;
}


/* optimize array for EQ/NEQ comparisons. We sort the array in
 * this case so that we can apply binary search later on.
 */
static inline void cnfexprOptimize_CMPEQ_arr(struct cnfarray *arr) {
    DBGPRINTF("optimizer: sorting array of %d members for CMP_EQ/NEQ comparison\n", arr->nmemb);
    qsort(arr->arr, arr->nmemb, sizeof(es_str_t *), qs_arrcmp);
}


/* (recursively) optimize an expression */
struct cnfexpr *cnfexprOptimize(struct cnfexpr *expr) {
    long long ln, rn;
    struct cnfexpr *exprswap;

    DBGPRINTF("optimize expr %p, type '%s'\n", expr, tokenToString(expr->nodetype));
    switch (expr->nodetype) {
        case '&':
            constFoldConcat(expr);
            break;
        case '+':
            if (getConstNumber(expr, &ln, &rn)) {
                expr->nodetype = 'N';
                ((struct cnfnumval *)expr)->val = ln + rn;
            }
            break;
        case '-':
            if (getConstNumber(expr, &ln, &rn)) {
                expr->nodetype = 'N';
                ((struct cnfnumval *)expr)->val = ln - rn;
            }
            break;
        case '*':
            if (getConstNumber(expr, &ln, &rn)) {
                expr->nodetype = 'N';
                ((struct cnfnumval *)expr)->val = ln * rn;
            }
            break;
        case '/':
            if (getConstNumber(expr, &ln, &rn)) {
                expr->nodetype = 'N';
                if (rn == 0) {
                    /* division by zero */
                    ((struct cnfnumval *)expr)->val = 0;
                } else {
                    ((struct cnfnumval *)expr)->val = ln / rn;
                }
            }
            break;
        case '%':
            if (getConstNumber(expr, &ln, &rn)) {
                expr->nodetype = 'N';
                if (rn == 0) {
                    /* division by zero */
                    ((struct cnfnumval *)expr)->val = 0;
                } else {
                    ((struct cnfnumval *)expr)->val = ln % rn;
                }
            }
            break;
        case CMP_NE:
        case CMP_EQ:
            expr->l = cnfexprOptimize(expr->l);
            expr->r = cnfexprOptimize(expr->r);
            if (constFoldCmp(expr)) break;
            if (expr->l->nodetype == 'A') {
                if (expr->r->nodetype == 'A') {
                    parser_errmsg(
                        "warning: '==' or '<>' "
                        "comparison of two constant string "
                        "arrays makes no sense");
                } else { /* swap for simpler execution step */
                    exprswap = expr->l;
                    expr->l = expr->r;
                    expr->r = exprswap;
                }
            }
            if (expr->r->nodetype == 'A') {
                cnfexprOptimize_CMPEQ_arr((struct cnfarray *)expr->r);
            }
            /* This should be evaluated last because it may change expr
             * to a function.
             */
            if (expr->l->nodetype == 'V') {
                expr = cnfexprOptimize_CMP_var(expr);
            }
            break;
        case CMP_LE:
        case CMP_GE:
        case CMP_LT:
        case CMP_GT:
            expr->l = cnfexprOptimize(expr->l);
            expr->r = cnfexprOptimize(expr->r);
            expr = cnfexprOptimize_CMP_severity_facility(expr);
            break;
        case CMP_CONTAINS:
        case CMP_CONTAINSI:
        case CMP_STARTSWITH:
        case CMP_ENDSWITH:
        case CMP_STARTSWITHI:
            expr->l = cnfexprOptimize(expr->l);
            expr->r = cnfexprOptimize(expr->r);
            break;
        case AND:
        case OR:
            expr->l = cnfexprOptimize(expr->l);
            expr->r = cnfexprOptimize(expr->r);
            expr = cnfexprOptimize_AND_OR(expr);
            break;
        case NOT:
            expr->r = cnfexprOptimize(expr->r);
            expr = cnfexprOptimize_NOT(expr);
            break;
        default: /* nodetypes we cannot optimize */
            break;
    }
    return expr;
}

/* removes NOPs from a statement list and returns the
 * first non-NOP entry.
 */
static struct cnfstmt *removeNOPs(struct cnfstmt *const root) {
    struct cnfstmt *stmt, *toDel, *prevstmt = NULL;
    struct cnfstmt *newRoot = NULL;

    if (root == NULL) goto done;
    stmt = root;
    while (stmt != NULL) {
        if (stmt->nodetype == S_NOP) {
            if (prevstmt != NULL) /* end chain, is rebuild if more non-NOPs follow */
                prevstmt->next = NULL;
            toDel = stmt;
            stmt = stmt->next;
            cnfstmtDestruct(toDel);
        } else {
            if (newRoot == NULL) newRoot = stmt;
            if (prevstmt != NULL) prevstmt->next = stmt;
            prevstmt = stmt;
            stmt = stmt->next;
        }
    }
done:
    return newRoot;
}

static void cnfstmtOptimizeForeach(struct cnfstmt *stmt) {
    stmt->d.s_foreach.iter->collection = cnfexprOptimize(stmt->d.s_foreach.iter->collection);
    stmt->d.s_foreach.body = cnfstmtOptimize(stmt->d.s_foreach.body);
}


static void cnfstmtOptimizeIf(struct cnfstmt *stmt) {
    struct cnfstmt *t_then, *t_else;
    struct cnfexpr *expr;
    struct cnffunc *func;
    struct funcData_prifilt *prifilt;

    assert(stmt->nodetype == S_IF);
    expr = stmt->d.s_if.expr = cnfexprOptimize(stmt->d.s_if.expr);
    stmt->d.s_if.t_then = cnfstmtOptimize(stmt->d.s_if.t_then);
    stmt->d.s_if.t_else = cnfstmtOptimize(stmt->d.s_if.t_else);

    if (stmt->d.s_if.t_then == NULL && stmt->d.s_if.t_else == NULL) {
        /* pointless if, probably constructed by config mgmt system */
        DBGPRINTF("optimizer: if with both empty then and else - remove\n");
        cnfexprDestruct(stmt->d.s_if.expr);
        /* set to NOP, this will be removed in later stage */
        stmt->nodetype = S_NOP;
        goto done;
    }

    assert(stmt->nodetype == S_IF);
    if (stmt->d.s_if.expr->nodetype == 'N' && ((struct cnfnumval *)stmt->d.s_if.expr)->val == 0 &&
        stmt->d.s_if.t_else == NULL) {
        DBGPRINTF("optimizer: if with constant false expression and no else - remove\n");
        cnfexprDestruct(stmt->d.s_if.expr);
        cnfstmtDestructLst(stmt->d.s_if.t_then);
        stmt->d.s_if.expr = NULL;
        stmt->d.s_if.t_then = NULL;
        stmt->nodetype = S_NOP;
        goto done;
    }

    assert(stmt->nodetype == S_IF);
    if (stmt->d.s_if.expr->nodetype == 'F') {
        func = (struct cnffunc *)expr;
        if (func->fPtr == doFunct_Prifilt) {
            DBGPRINTF("optimizer: change IF to PRIFILT\n");
            t_then = stmt->d.s_if.t_then;
            t_else = stmt->d.s_if.t_else;
            stmt->nodetype = S_PRIFILT;
            prifilt = (struct funcData_prifilt *)func->funcdata;
            memcpy(stmt->d.s_prifilt.pmask, prifilt->pmask, sizeof(prifilt->pmask));
            stmt->d.s_prifilt.t_then = t_then;
            stmt->d.s_prifilt.t_else = t_else;
            if (func->nParams == 0)
                stmt->printable = (uchar *)strdup("[Optimizer Result]");
            else
                stmt->printable = (uchar *)es_str2cstr(((struct cnfstringval *)func->expr[0])->estr, NULL);
            cnfexprDestruct(expr);
            cnfstmtOptimizePRIFilt(stmt);
        }
    }
done:
    return;
}

static void cnfstmtOptimizeAct(struct cnfstmt *stmt) {
    action_t *pAct;

    pAct = stmt->d.act;
    if (!strcmp((char *)modGetName(pAct->pMod), "builtin:omdiscard")) {
        DBGPRINTF("optimizer: replacing omdiscard by STOP\n");
        actionDestruct(stmt->d.act);
        stmt->nodetype = S_STOP;
    }
}

static void cnfstmtOptimizePRIFilt(struct cnfstmt *stmt) {
    int i;
    int isAlways = 1;
    struct cnfstmt *subroot, *last;

    stmt->d.s_prifilt.t_then = cnfstmtOptimize(stmt->d.s_prifilt.t_then);

    for (i = 0; i <= LOG_NFACILITIES; i++)
        if (stmt->d.s_prifilt.pmask[i] != 0xff) {
            isAlways = 0;
            break;
        }
    if (!isAlways) goto done;

    DBGPRINTF("optimizer: removing always-true PRIFILT %p\n", stmt);
    if (stmt->d.s_prifilt.t_else != NULL) {
        parser_errmsg("error: always-true PRI filter has else part!\n");
        cnfstmtDestructLst(stmt->d.s_prifilt.t_else);
    }
    free(stmt->printable);
    stmt->printable = NULL;
    subroot = stmt->d.s_prifilt.t_then;
    if (subroot == NULL) {
        /* very strange, we set it to NOP, best we can do
         * This case is NOT expected in practice
         */
        stmt->nodetype = S_NOP;
        goto done;
    }
    for (last = subroot; last->next != NULL; last = last->next) /* find last node in subtree */
        ;
    last->next = stmt->next;
    memcpy(stmt, subroot, sizeof(struct cnfstmt));
    free(subroot);

done:
    return;
}

static void cnfstmtOptimizeReloadLookupTable(struct cnfstmt *stmt) {
    if ((stmt->d.s_reload_lookup_table.table = lookupFindTable(stmt->d.s_reload_lookup_table.table_name)) == NULL) {
        parser_errmsg("lookup table '%s' not found\n", stmt->d.s_reload_lookup_table.table_name);
    }
}

/* we abuse "optimize" a bit. Actually, we obtain a ruleset pointer, as
 * all rulesets are only known later in the process (now!).
 */
static void cnfstmtOptimizeCall(struct cnfstmt *stmt) {
    ruleset_t *pRuleset;
    rsRetVal localRet;
    uchar *rsName;

    rsName = (uchar *)es_str2cstr(stmt->d.s_call.name, NULL);
    localRet = rulesetGetRuleset(loadConf, &pRuleset, rsName);
    if (localRet != RS_RET_OK) {
        /* in that case, we accept that a NOP will "survive" */
        parser_errmsg("ruleset '%s' cannot be found\n", rsName);
        es_deleteStr(stmt->d.s_call.name);
        stmt->nodetype = S_NOP;
        goto done;
    }
    DBGPRINTF("CALL obtained ruleset ptr %p for ruleset '%s' [hasQueue:%d]\n", pRuleset, rsName,
              rulesetHasQueue(pRuleset));
    if (rulesetHasQueue(pRuleset)) {
        stmt->d.s_call.ruleset = pRuleset;
    } else {
        stmt->d.s_call.ruleset = NULL;
        stmt->d.s_call.stmt = pRuleset->root;
    }
done:
    free(rsName);
    return;
}
/* (recursively) optimize a statement */
struct cnfstmt *cnfstmtOptimize(struct cnfstmt *root) {
    struct cnfstmt *stmt;
    if (root == NULL) goto done;
    for (stmt = root; stmt != NULL; stmt = stmt->next) {
        DBGPRINTF("optimizing cnfstmt type %d\n", (int)stmt->nodetype);
        switch (stmt->nodetype) {
            case S_IF:
                cnfstmtOptimizeIf(stmt);
                break;
            case S_FOREACH:
                cnfstmtOptimizeForeach(stmt);
                break;
            case S_PRIFILT:
                cnfstmtOptimizePRIFilt(stmt);
                break;
            case S_PROPFILT:
                stmt->d.s_propfilt.t_then = cnfstmtOptimize(stmt->d.s_propfilt.t_then);
                break;
            case S_SET:
                stmt->d.s_set.expr = cnfexprOptimize(stmt->d.s_set.expr);
                break;
            case S_ACT:
                cnfstmtOptimizeAct(stmt);
                break;
            case S_CALL:
                cnfstmtOptimizeCall(stmt);
                break;
            case S_CALL_INDIRECT:
                stmt->d.s_call_ind.expr = cnfexprOptimize(stmt->d.s_call_ind.expr);
                break;
            case S_STOP:
                if (stmt->next != NULL) parser_warnmsg("STOP is followed by unreachable statements!\n");
                break;
            case S_UNSET: /* nothing to do */
                break;
            case S_RELOAD_LOOKUP_TABLE:
                cnfstmtOptimizeReloadLookupTable(stmt);
                break;
            case S_NOP:
                /* NOPs are normal optimizer intermediates, for example when
                 * a user intentionally writes "continue" inside an if branch.
                 * removeNOPs() drops them after this traversal. */
                break;
            default:
                LogError(0, RS_RET_INTERNAL_ERROR, "internal error: unknown stmt type %u during optimizer run\n",
                         (unsigned)stmt->nodetype);
                break;
        }
    }
    root = removeNOPs(root);
done:
    return root;
}


struct cnffparamlst *cnffparamlstNew(struct cnfexpr *expr, struct cnffparamlst *next) {
    struct cnffparamlst *lst;
    if ((lst = malloc(sizeof(struct cnffparamlst))) != NULL) {
        lst->nodetype = 'P';
        lst->expr = expr;
        lst->next = next;
    }
    return lst;
}

/* Obtain function id from name AND number of params. Issues the
 * relevant error messages if errors are detected.
 */
static rscriptFuncPtr funcName2Ptr(char *const fname, const unsigned short nParams) {
    struct scriptFunct *foundFunc = searchModList(fname);
    if (foundFunc == NULL) {
        parser_errmsg("function '%s' not found", fname);
        return NULL;
    } else {
        return extractFuncPtr(foundFunc, nParams);
    }
}

rsRetVal addMod2List(const int __attribute__((unused)) version, struct scriptFunct *functArray)
/*version currently not used, might be needed later for versin check*/
{
    DEFiRet;
    int i;
    struct modListNode *newNode;
    CHKmalloc(newNode = (struct modListNode *)malloc(sizeof(struct modListNode)));
    newNode->version = 1;
    newNode->next = NULL;

    i = 0;
    while (functArray[i].fname != NULL) {
        if (searchModList(functArray[i].fname) != NULL) {
            parser_errmsg("function %s defined multiple times, second time will be ignored", functArray[i].fname);
        }
        i++;
    }
    newNode->modFcts = functArray;

    modListLast->next = newNode;
    modListLast = newNode;
finalize_it:
    RETiRet;
}


struct cnffunc *cnffuncNew(es_str_t *fname, struct cnffparamlst *paramlst) {
    struct cnffunc *func;
    struct cnffparamlst *param, *toDel;
    unsigned short i;
    unsigned short nParams;
    char *cstr;

    /* we first need to find out how many params we have */
    nParams = 0;
    for (param = paramlst; param != NULL; param = param->next) ++nParams;
    if ((func = malloc(sizeof(struct cnffunc) + (nParams * sizeof(struct cnfexp *)))) != NULL) {
        func->nodetype = 'F';
        func->fname = fname;
        func->nParams = nParams;
        func->funcdata = NULL;
        func->destructable_funcdata = 1;
        cstr = es_str2cstr(fname, NULL);
        func->fPtr = funcName2Ptr(cstr, nParams);

        /* parse error if we have an unknown function */
        if (func->fPtr == NULL) {
            parser_errmsg("Invalid function %s", cstr);
        }

        /* shuffle params over to array (access speed!) */
        param = paramlst;
        for (i = 0; i < nParams; ++i) {
            func->expr[i] = param->expr;
            toDel = param;
            param = param->next;
            free(toDel);
        }
        /* some functions require special initialization */
        struct scriptFunct *foundFunc = searchModList(cstr);
        if (foundFunc && foundFunc->initFunc != NULL) {
            foundFunc->initFunc(func);
        }
        free(cstr);
    }
    return func;
}


/* A special function to create a prifilt() expression during optimization
 * phase.
 */
struct cnffunc *cnffuncNew_prifilt(int fac) {
    struct cnffunc *func;

    fac >>= 3;
    if (fac >= LOG_NFACILITIES + 1 || fac < 0) return NULL;

    if ((func = malloc(sizeof(struct cnffunc))) != NULL) {
        if ((func->funcdata = calloc(1, sizeof(struct funcData_prifilt))) == NULL) {
            free(func);
            return NULL;
        }
        func->nodetype = 'F';
        func->fname = es_newStrFromCStr("prifilt", sizeof("prifilt") - 1);
        func->nParams = 0;
        func->fPtr = doFunct_Prifilt;
        func->destructable_funcdata = 1;
        ((struct funcData_prifilt *)func->funcdata)->pmask[fac] = TABLE_ALLPRI;
    }
    return func;
}


/* The check-if-variable exists "exists($!var)" is a special beast and as such
 * also needs special code (we must not evaluate the var but need its name).
 */
struct cnffuncexists *ATTR_NONNULL() cnffuncexistsNew(const char *const varname) {
    struct cnffuncexists *f_exists;

    if ((f_exists = malloc(sizeof(struct cnffuncexists))) != NULL) {
        f_exists->nodetype = S_FUNC_EXISTS;
        f_exists->varname = varname;
        msgPropDescrFill(&f_exists->prop, (uchar *)varname, strlen(varname));
    }
    return f_exists;
}


/* returns 0 if everything is OK and config parsing shall continue,
 * and 1 if things are so wrong that config parsing shall be aborted.
 */
int ATTR_NONNULL() cnfDoInclude(const char *const name, const int optional) {
    char *cfgFile;
    const char *finalName;
    int i;
    int result;
    glob_t cfgFiles = {0};
    int ret = 0;
    struct stat fileInfo;
    struct stat linkInfo;
    char errStr[1024];
    char nameBuf[MAXFNAME + 1];
    char cwdBuf[MAXFNAME + 1];

    DBGPRINTF("cnfDoInclude: file: '%s', optional: %d\n", name, optional);
    finalName = name;
    if (stat(name, &fileInfo) == 0) {
        /* stat usually fails if we have a wildcard - so this does NOT indicate error! */
        if (S_ISDIR(fileInfo.st_mode)) {
            /* if we have a directory, we need to add "*" to get its files */
            snprintf(nameBuf, sizeof(nameBuf), "%s*", name);
            finalName = nameBuf;
        }
    }

/* Use GLOB_MARK to append a trailing slash for directories. */
/* Use GLOB_NOMAGIC to detect wildcards that match nothing. */
#ifdef HAVE_GLOB_NOMAGIC
    result = glob(finalName, GLOB_MARK | GLOB_NOMAGIC, NULL, &cfgFiles);
    if (result == GLOB_NOMATCH) {
#else
    result = glob(finalName, GLOB_MARK, NULL, &cfgFiles);
    if (result == GLOB_NOMATCH && containsGlobWildcard((char *)finalName)) {
#endif /* HAVE_GLOB_NOMAGIC */
        goto done;
    }

    if (result == GLOB_NOSPACE || result == GLOB_ABORTED) {
        if (optional == 0) {
            rs_strerror_r(errno, errStr, sizeof(errStr));
            if (getcwd(cwdBuf, sizeof(cwdBuf)) == NULL) RS_COPY_LITERAL(cwdBuf, "??getcwd() failed??");
            parser_errmsg(
                "error accessing config file or directory '%s' "
                "[cwd:%s]: %s",
                finalName, cwdBuf, errStr);
            ret = 1;
        }
        goto done;
    }

    /* note: bison "stacks" the files, so we need to submit them
     * in reverse order to the *stack* in order to get the proper
     * parsing order. Also see
     * http://bugzilla.adiscon.com/show_bug.cgi?id=411
     */
    for (i = cfgFiles.gl_pathc - 1; i >= 0; i--) {
        cfgFile = cfgFiles.gl_pathv[i];
        if (lstat(cfgFile, &linkInfo) != 0) {
            if (optional == 0) {
                rs_strerror_r(errno, errStr, sizeof(errStr));
                if (getcwd(cwdBuf, sizeof(cwdBuf)) == NULL) RS_COPY_LITERAL(cwdBuf, "??getcwd() failed??");
                parser_errmsg(
                    "error accessing config file or directory '%s' "
                    "[cwd: %s]: %s",
                    cfgFile, cwdBuf, errStr);
                ret = 1;
                goto done;
            }
            continue;
        }

        if (S_ISLNK(linkInfo.st_mode)) {
            if (stat(cfgFile, &fileInfo) != 0) {
                if (optional == 0) {
                    rs_strerror_r(errno, errStr, sizeof(errStr));
                    if (getcwd(cwdBuf, sizeof(cwdBuf)) == NULL) RS_COPY_LITERAL(cwdBuf, "??getcwd() failed??");
                    parser_errmsg(
                        "error accessing config file or directory '%s' "
                        "[cwd: %s]: %s",
                        cfgFile, cwdBuf, errStr);
                    ret = 1;
                    goto done;
                }
                continue;
            }
        } else {
            fileInfo = linkInfo;
        }

        if (S_ISREG(fileInfo.st_mode)) { /* config file */
            DBGPRINTF("requested to include config file '%s'\n", cfgFile);
            /* Route .yaml / .yml files to the YAML loader */
            const char *ext = strrchr(cfgFile, '.');
            int is_yaml = (ext != NULL && (!strcmp(ext, ".yaml") || !strcmp(ext, ".yml")));
#ifdef HAVE_LIBYAML
            if (is_yaml) {
                if (yamlconf_load(cfgFile) != RS_RET_OK) ret = 1;
            } else {
                cnfSetLexFile(cfgFile);
            }
#else
            if (is_yaml) {
                LogError(0, RS_RET_ERR,
                         "YAML include file '%s' requested but rsyslog was "
                         "built without libyaml support",
                         cfgFile);
                ret = 1; /* treat as hard failure — config is incomplete */
            } else {
                cnfSetLexFile(cfgFile);
            }
#endif
        } else if (S_ISDIR(fileInfo.st_mode)) { /* config directory */
            DBGPRINTF("requested to include directory '%s'\n", cfgFile);
            cnfDoInclude(cfgFile, optional);
        } else {
            DBGPRINTF("warning: unable to process IncludeConfig directive '%s'\n", cfgFile);
        }
    }

done:
    globfree(&cfgFiles);
    return ret;
}


/* Process include() objects */
void includeProcessCnf(struct nvlst *const lst) {
    struct cnfparamvals *pvals = NULL;
    const char *inc_file = NULL;
    const char *text = NULL;
    int optional = 0;
    int abort_if_missing = 0;
    int i;

    if (lst == NULL) {
        parser_errmsg(
            "include() must have either 'file' or 'text' "
            "parameter - ignored");
        goto done;
    }

    if (nvlstChkDisabled(lst)) {
        DBGPRINTF("include statement disabled\n");
        goto done;
    }

    pvals = nvlstGetParams(lst, &incpblk, NULL);
    if (pvals == NULL) {
        goto done;
    }
    DBGPRINTF("include param blk after includeProcessCnf:\n");
    cnfparamsPrint(&incpblk, pvals);
    for (i = 0; i < incpblk.nParams; ++i) {
        if (!pvals[i].bUsed) {
            continue;
        }

        if (!strcmp(incpblk.descr[i].name, "file")) {
            inc_file = es_str2cstr(pvals[i].val.d.estr, NULL);
        } else if (!strcmp(incpblk.descr[i].name, "text")) {
            text = es_str2cstr(pvals[i].val.d.estr, NULL);
        } else if (!strcmp(incpblk.descr[i].name, "mode")) {
            char *const md = es_str2cstr(pvals[i].val.d.estr, NULL);
            if (!strcmp(md, "abort-if-missing")) {
                optional = 0;
                abort_if_missing = 1;
            } else if (!strcmp(md, "required")) {
                optional = 0;
            } else if (!strcmp(md, "optional")) {
                optional = 1;
            } else {
                parser_errmsg("invalid 'mode' parameter: '%s' - ignored", md);
            }
            free((void *)md);
        } else {
            LogError(0, RS_RET_INTERNAL_ERROR,
                     "rainerscript/include: program error, non-handled inclpblk "
                     "param '%s' in includeProcessCnf()",
                     incpblk.descr[i].name);
        }
    }

    if (text != NULL && inc_file != NULL) {
        parser_errmsg(
            "include() must have either 'file' or 'text' "
            "parameter, but both are set - ignored");
        goto done;
    }

    if (inc_file != NULL) {
        if (cnfDoInclude(inc_file, optional) != 0 && abort_if_missing) {
            fprintf(stderr,
                    "include file '%s' mode is set to abort-if-missing "
                    "and the file is indeed missing - thus aborting rsyslog\n",
                    inc_file);
            exit(1); /* "good exit" - during config processing, requested by user */
        }
    } else if (text != NULL) {
        es_str_t *estr = es_newStrFromCStr((char *)text, strlen(text));
        /* lex needs 2 \0 bytes as terminator indication (wtf ;-)) */
        es_addChar(&estr, '\0');
        es_addChar(&estr, '\0');
        cnfAddConfigBuffer(estr, "text");
    } else {
        parser_errmsg(
            "include must have either 'file' or 'text' "
            "parameter - ignored");
        goto done;
    }

done:
    free((void *)text);
    free((void *)inc_file);
    nvlstDestruct(lst);
    if (pvals != NULL) cnfparamvalsDestruct(pvals, &incpblk);
    return;
}


void varDelete(const struct svar *v) {
    switch (v->datatype) {
        case 'S':
        case 'J':
            varFreeMembers(v);
            break;
        case 'A':
            cnfarrayContentDestruct(v->d.ar);
            free(v->d.ar);
            break;
        default:
            break;
    }
}

void cnfparamvalsDestruct(const struct cnfparamvals *paramvals, const struct cnfparamblk *blk) {
    int i;
    if (paramvals == NULL) return;
    for (i = 0; i < blk->nParams; ++i) {
        if (paramvals[i].bUsed) {
            varDelete(&paramvals[i].val);
        }
    }
    free((void *)paramvals);
}

/* find the index (or -1!) for a config param by name. This is used to
 * address the parameter array. Of course, we could use with static
 * indices, but that would create some extra bug potential. So we
 * resort to names. As we do this only during the initial config parsing
 * stage the (considerable!) extra overhead is OK. -- rgerhards, 2011-07-19
 */
int cnfparamGetIdx(struct cnfparamblk *params, const char *name) {
    int i;
    for (i = 0; i < params->nParams; ++i)
        if (!strcmp(params->descr[i].name, name)) break;
    if (i == params->nParams) i = -1; /* not found */
    return i;
}


void cstrPrint(const char *text, es_str_t *estr) {
    char *str;
    str = es_str2cstr(estr, NULL);
    dbgprintf("%s%s", text, str);
    free(str);
}

char *rmLeadingSpace(char *s) {
    char *p;
    for (p = s; *p && isspace(*p); ++p);
    return (p);
}

/* init must be called once before any parsing of the script files start */
rsRetVal initRainerscript(void) {
    DEFiRet;
    CHKmalloc(modListRoot = (struct modListNode *)malloc(sizeof(struct modListNode)));
    modListRoot->version = 1;
    modListRoot->modFcts = functions;
    modListRoot->next = NULL;
    modListLast = modListRoot;
    iRet = objGetObjInterface(&obj);
finalize_it:
    RETiRet;
}

/* we need a function to check for octal digits */
static inline int isodigit(uchar c) {
    return (c >= '0' && c <= '7');
}

/**
 * Get numerical value of a hex digit. This is a helper function.
 * @param[in] c a character containing 0..9, A..Z, a..z anything else
 * is an (undetected) error.
 */
static int hexDigitVal(char c) {
    int r;
    if (c < 'A')
        r = c - '0';
    else if (c < 'a')
        r = c - 'A' + 10;
    else
        r = c - 'a' + 10;
    return r;
}

/* Handle the actual unescaping.
 * a helper to unescapeStr(), to help make the function easier to read.
 */
static void doUnescape(unsigned char *c, int len, int *iSrc, int iDst) {
    if (c[*iSrc] == '\\') {
        if (++(*iSrc) == len) {
            /* error, incomplete escape, treat as single char */
            c[iDst] = '\\';
        }
        /* regular case, unescape */
        switch (c[*iSrc]) {
            case 'a':
                c[iDst] = '\007';
                break;
            case 'b':
                c[iDst] = '\b';
                break;
            case 'f':
                c[iDst] = '\014';
                break;
            case 'n':
                c[iDst] = '\n';
                break;
            case 'r':
                c[iDst] = '\r';
                break;
            case 't':
                c[iDst] = '\t';
                break;
            case '\'':
                c[iDst] = '\'';
                break;
            case '"':
                c[iDst] = '"';
                break;
            case '?':
                c[iDst] = '?';
                break;
            case '$':
                c[iDst] = '$';
                break;
            case '\\':
                c[iDst] = '\\';
                break;
            case 'x':
                if ((*iSrc) + 2 >= len || !isxdigit(c[(*iSrc) + 1]) || !isxdigit(c[(*iSrc) + 2])) {
                    /* error, incomplete escape, use as is */
                    c[iDst] = '\\';
                    --(*iSrc);
                }
                c[iDst] = (hexDigitVal(c[(*iSrc) + 1]) << 4) + hexDigitVal(c[(*iSrc) + 2]);
                *iSrc += 2;
                break;
            case '0': /* octal escape */
            case '1':
            case '2':
            case '3':
            case '4':
            case '5':
            case '6':
            case '7':
                if ((*iSrc) + 2 >= len || !isodigit(c[(*iSrc) + 1]) || !isodigit(c[(*iSrc) + 2])) {
                    /* error, incomplete escape, use as is */
                    c[iDst] = '\\';
                    --(*iSrc);
                }
                c[iDst] = ((c[(*iSrc)] - '0') << 6) + ((c[(*iSrc) + 1] - '0') << 3) + (c[(*iSrc) + 2] - '0');
                *iSrc += 2;
                break;
            default:
                /* error, incomplete escape, indicate by '?' */
                c[iDst] = '?';
                break;
        }
    } else {
        /* regular character */
        c[iDst] = c[*iSrc];
    }
}

void unescapeStr(uchar *s, int len) {
    int iSrc, iDst;
    assert(s != NULL);

    /* scan for first escape sequence (if we are luky, there is none!) */
    iSrc = 0;
    while (iSrc < len && s[iSrc] != '\\') ++iSrc;
    /* now we have a sequence or end of string. In any case, we process
     * all remaining characters (maybe 0!) and unescape.
     */
    if (iSrc != len) {
        iDst = iSrc;
        while (iSrc < len) {
            doUnescape(s, len, &iSrc, iDst);
            ++iSrc;
            ++iDst;
        }
        s[iDst] = '\0';
    }
}

const char *tokenval2str(const int tok) {
    if (tok < 256) return "";
    switch (tok) {
        case NAME:
            return "NAME";
        case FUNC:
            return "FUNC";
        case BEGINOBJ:
            return "BEGINOBJ";
        case ENDOBJ:
            return "ENDOBJ";
        case BEGIN_ACTION:
            return "BEGIN_ACTION";
        case BEGIN_PROPERTY:
            return "BEGIN_PROPERTY";
        case BEGIN_CONSTANT:
            return "BEGIN_CONSTANT";
        case BEGIN_TPL:
            return "BEGIN_TPL";
        case BEGIN_INCLUDE:
            return "BEGIN_INCLUDE";
        case BEGIN_RULESET:
            return "BEGIN_RULESET";
        case STOP:
            return "STOP";
        case SET:
            return "SET";
        case UNSET:
            return "UNSET";
        case CONTINUE:
            return "CONTINUE";
        case CALL:
            return "CALL";
        case LEGACY_ACTION:
            return "LEGACY_ACTION";
        case LEGACY_RULESET:
            return "LEGACY_RULESET";
        case PRIFILT:
            return "PRIFILT";
        case PROPFILT:
            return "PROPFILT";
        case BSD_TAG_SELECTOR:
            return "BSD_TAG_SELECTOR";
        case BSD_HOST_SELECTOR:
            return "BSD_HOST_SELECTOR";
        case IF:
            return "IF";
        case THEN:
            return "THEN";
        case ELSE:
            return "ELSE";
        case OR:
            return "OR";
        case AND:
            return "AND";
        case NOT:
            return "NOT";
        case VAR:
            return "VAR";
        case STRING:
            return "STRING";
        case NUMBER:
            return "NUMBER";
        case CMP_EQ:
            return "CMP_EQ";
        case CMP_NE:
            return "CMP_NE";
        case CMP_LE:
            return "CMP_LE";
        case CMP_GE:
            return "CMP_GE";
        case CMP_LT:
            return "CMP_LT";
        case CMP_GT:
            return "CMP_GT";
        case CMP_CONTAINS:
            return "CMP_CONTAINS";
        case CMP_CONTAINSI:
            return "CMP_CONTAINSI";
        case CMP_STARTSWITH:
            return "CMP_STARTSWITH";
        case CMP_STARTSWITHI:
            return "CMP_STARTSWITHI";
        case CMP_ENDSWITH:
            return "CMP_ENDSWITH";
        case UMINUS:
            return "UMINUS";
        default:
            return "UNKNOWN TOKEN";
    }
}
