/* cfsysline.c
 * Implementation of the configuration system line object.
 *
 * File begun on 2007-07-30 by RGerhards
 *
 * Copyright (C) 2007-2020 Adiscon GmbH.
 *
 * This file is part of rsyslog.
 *
 * This file is part of the rsyslog runtime library.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       -or-
 *       see COPYING.ASL20 in the source distribution
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>

#include "rsyslog.h"
#include "cfsysline.h"
#include "obj.h"
#include "conf.h"
#include "errmsg.h"
#include "srUtils.h"
#include "unicode-helper.h"
#include "rsconf.h"
#include "parserif.h"


/* static data */
DEFobjCurrIf(obj)

    linkedList_t llCmdList; /* this is NOT a pointer - no typo here ;) */

/* --------------- START functions for handling canned syntaxes --------------- */


/* parse a character from the config line
 * added 2007-07-17 by rgerhards
 * TODO: enhance this function to handle different classes of characters
 * HINT: check if char is ' and, if so, use 'c' where c may also be things
 * like \t etc.
 */
static rsRetVal doGetChar(uchar **pp, rsRetVal (*pSetHdlr)(void *, uchar *), void *pVal) {
    DEFiRet;

    assert(pp != NULL);
    assert(*pp != NULL);

    skipWhiteSpace(pp); /* skip over any whitespace */

    /* if we are not at a '\0', we have our new char - no validity checks here... */
    if (**pp == '\0') {
        LogError(0, RS_RET_NOT_FOUND, "No character available");
        iRet = RS_RET_NOT_FOUND;
    } else {
        if (pSetHdlr == NULL) {
            /* we should set value directly to var */
            *((uchar *)pVal) = **pp;
        } else {
            /* we set value via a set function */
            CHKiRet(pSetHdlr(pVal, *pp));
        }
        ++(*pp); /* eat processed char */
    }

finalize_it:
    RETiRet;
}


/* Parse a number from the configuration line. This is more or less
 * a shell to call the custom handler.
 * rgerhards, 2007-07-31
 */
static rsRetVal doCustomHdlr(uchar **pp, rsRetVal (*pSetHdlr)(uchar **, void *), void *pVal) {
    DEFiRet;

    assert(pp != NULL);
    assert(*pp != NULL);

    CHKiRet(pSetHdlr(pp, pVal));

finalize_it:
    RETiRet;
}


/* Parse a number from the configuration line. This functions just parses
 * the number and does NOT call any handlers or set any values. It is just
 * for INTERNAL USE by other parse functions!
 * rgerhards, 2008-01-08
 */
static rsRetVal parseIntVal(uchar **pp, int64 *pVal) {
    DEFiRet;
    uchar *p;
    int64 i;
    int bWasNegative;

    assert(pp != NULL);
    assert(*pp != NULL);
    assert(pVal != NULL);

    skipWhiteSpace(pp); /* skip over any whitespace */
    p = *pp;

    if (*p == '-') {
        bWasNegative = 1;
        ++p; /* eat it */
    } else {
        bWasNegative = 0;
    }

    if (!isdigit((int)*p)) {
        errno = 0;
        LogError(0, RS_RET_INVALID_INT, "invalid number");
        ABORT_FINALIZE(RS_RET_INVALID_INT);
    }

    /* pull value */
    for (i = 0; *p && (isdigit((int)*p) || *p == '.' || *p == ','); ++p) {
        if (isdigit((int)*p)) {
            i = i * 10 + *p - '0';
        }
    }

    if (bWasNegative) i *= -1;

    *pVal = i;
    *pp = p;

finalize_it:
    RETiRet;
}


/* Parse a size from the configuration line. This is basically an integer
 * syntax, but modifiers may be added after the integer (e.g. 1k to mean
 * 1024). The size must immediately follow the number. Note that the
 * param value must be int64!
 * rgerhards, 2008-01-09
 */
static rsRetVal doGetSize(uchar **pp, rsRetVal (*pSetHdlr)(void *, int64), void *pVal) {
    DEFiRet;
    int64 i;

    assert(pp != NULL);
    assert(*pp != NULL);

    CHKiRet(parseIntVal(pp, &i));

    /* we now check if the next character is one of our known modifiers.
     * If so, we accept it as such. If not, we leave it alone. tera and
     * above does not make any sense as that is above a 32-bit int value.
     */
    switch (**pp) {
        /* traditional binary-based definitions */
        case 'k':
            i *= 1024;
            ++(*pp);
            break;
        case 'm':
            i *= 1024 * 1024;
            ++(*pp);
            break;
        case 'g':
            i *= 1024 * 1024 * 1024;
            ++(*pp);
            break;
        case 't':
            i *= (int64)1024 * 1024 * 1024 * 1024;
            ++(*pp);
            break; /* tera */
        case 'p':
            i *= (int64)1024 * 1024 * 1024 * 1024 * 1024;
            ++(*pp);
            break; /* peta */
        case 'e':
            i *= (int64)1024 * 1024 * 1024 * 1024 * 1024 * 1024;
            ++(*pp);
            break; /* exa */
        /* and now the "new" 1000-based definitions */
        case 'K':
            i *= 1000;
            ++(*pp);
            break;
        case 'M':
            i *= 1000000;
            ++(*pp);
            break;
        case 'G':
            i *= 1000000000;
            ++(*pp);
            break;
            /* we need to use the multiplication below because otherwise
             * the compiler gets an error during constant parsing */
        case 'T':
            i *= (int64)1000 * 1000000000;
            ++(*pp);
            break; /* tera */
        case 'P':
            i *= (int64)1000000 * 1000000000;
            ++(*pp);
            break; /* peta */
        case 'E':
            i *= (int64)1000000000 * 1000000000;
            ++(*pp);
            break; /* exa */
        default:
            // No action needed for other cases
            break;
    }

    /* done */
    if (pSetHdlr == NULL) {
        /* we should set value directly to var */
        *((int64 *)pVal) = i;
    } else {
        /* we set value via a set function */
        CHKiRet(pSetHdlr(pVal, i));
    }

finalize_it:
    RETiRet;
}


/* Parse a number from the configuration line.
 * rgerhards, 2007-07-31
 */
static rsRetVal doGetInt(uchar **pp, rsRetVal (*pSetHdlr)(void *, uid_t), void *pVal) {
    uchar *p;
    DEFiRet;
    int64 i;

    assert(pp != NULL);
    assert(*pp != NULL);

    CHKiRet(doGetSize(pp, NULL, &i));
    p = *pp;
    if (i > 2147483648ll) { /*2^31*/
        LogError(0, RS_RET_INVALID_VALUE, "value %lld too large for integer argument.", i);
        ABORT_FINALIZE(RS_RET_INVALID_VALUE);
    }

    if (pSetHdlr == NULL) {
        /* we should set value directly to var */
        *((int *)pVal) = (int)i;
    } else {
        /* we set value via a set function */
        CHKiRet(pSetHdlr(pVal, (int)i));
    }

    *pp = p;

finalize_it:
    RETiRet;
}


static rsRetVal doGetNonNegInt(uchar **pp, rsRetVal (*pSetHdlr)(void *, uid_t), void *pVal) {
    int val;
    DEFiRet;

    CHKiRet(doGetInt(pp, NULL, &val));
    if (val < 0) {
        LogError(0, RS_RET_INVALID_VALUE, "value %d must not be less than zero.", val);
        ABORT_FINALIZE(RS_RET_INVALID_VALUE);
    }

    if (pSetHdlr == NULL) {
        *((int *)pVal) = val;
    } else {
        CHKiRet(pSetHdlr(pVal, val));
    }

finalize_it:
    RETiRet;
}


static rsRetVal doGetPositiveInt(uchar **pp, rsRetVal (*pSetHdlr)(void *, uid_t), void *pVal) {
    int val;
    DEFiRet;

    CHKiRet(doGetInt(pp, NULL, &val));
    if (val < 1) {
        LogError(0, RS_RET_INVALID_VALUE, "value %d must be greater than zero.", val);
        ABORT_FINALIZE(RS_RET_INVALID_VALUE);
    }

    if (pSetHdlr == NULL) {
        *((int *)pVal) = val;
    } else {
        CHKiRet(pSetHdlr(pVal, val));
    }

finalize_it:
    RETiRet;
}


/* Parse and interpret a $FileCreateMode and $umask line. This function
 * pulls the creation mode and, if successful, stores it
 * into the global variable so that the rest of rsyslogd
 * opens files with that mode. Any previous value will be
 * overwritten.
 * HINT: if we store the creation mode in selector_t, we
 * can even specify multiple modes simply be virtue of
 * being placed in the right section of rsyslog.conf
 * rgerhards, 2007-07-4 (happy independence day to my US friends!)
 * Parameter **pp has a pointer to the current config line.
 * On exit, it will be updated to the processed position.
 */
static rsRetVal doFileCreateMode(uchar **pp, rsRetVal (*pSetHdlr)(void *, uid_t), void *pVal) {
    uchar *p;
    DEFiRet;
    int iVal;

    assert(pp != NULL);
    assert(*pp != NULL);

    skipWhiteSpace(pp); /* skip over any whitespace */
    p = *pp;

    /* for now, we parse and accept only octal numbers
     * Sequence of tests is important, we are using boolean shortcuts
     * to avoid addressing invalid memory!
     */
    if (!((*p == '0') && (*(p + 1) && *(p + 1) >= '0' && *(p + 1) <= '7') &&
          (*(p + 2) && *(p + 2) >= '0' && *(p + 2) <= '7') && (*(p + 3) && *(p + 3) >= '0' && *(p + 3) <= '7'))) {
        LogError(0, RS_RET_INVALID_VALUE, "value must be octal (e.g 0644).");
        ABORT_FINALIZE(RS_RET_INVALID_VALUE);
    }

    /*  we reach this code only if the octal number is ok - so we can now
     *  compute the value.
     */
    iVal = (*(p + 1) - '0') * 64 + (*(p + 2) - '0') * 8 + (*(p + 3) - '0');

    if (pSetHdlr == NULL) {
        /* we should set value directly to var */
        *((int *)pVal) = iVal;
    } else {
        /* we set value via a set function */
        CHKiRet(pSetHdlr(pVal, iVal));
    }

    p += 4; /* eat the octal number */
    *pp = p;

finalize_it:
    RETiRet;
}


/* Parse and interpret an on/off inside a config file line. This is most
 * often used for boolean options, but of course it may also be used
 * for other things. The passed-in pointer is updated to point to
 * the first unparsed character on exit. Function emits error messages
 * if the value is neither on or off. It returns 0 if the option is off,
 * 1 if it is on and another value if there was an error.
 * rgerhards, 2007-07-15
 */
static int doParseOnOffOption(uchar **pp) {
    uchar *pOptStart;
    uchar szOpt[32];

    assert(pp != NULL);
    assert(*pp != NULL);

    pOptStart = *pp;
    skipWhiteSpace(pp); /* skip over any whitespace */

    if (getSubString(pp, (char *)szOpt, sizeof(szOpt), ' ') != 0) {
        LogError(0, NO_ERRCODE, "Invalid $-configline - could not extract on/off option");
        return -1;
    }

    if (!strcmp((char *)szOpt, "on")) {
        return 1;
    } else if (!strcmp((char *)szOpt, "off")) {
        return 0;
    } else {
        LogError(0, NO_ERRCODE, "Option value must be on or off, but is '%s'", (char *)pOptStart);
        return -1;
    }
}


/* extract a groupname and return its gid.
 * rgerhards, 2007-07-17
 */
static rsRetVal doGetGID(uchar **pp, rsRetVal (*pSetHdlr)(void *, uid_t), void *pVal) {
    struct group *pgBuf = NULL;
    struct group gBuf;
    DEFiRet;
    uchar szName[256];
    int bufSize = 1024;
    char *stringBuf = NULL;
    int err;

    assert(pp != NULL);
    assert(*pp != NULL);

    if (getSubString(pp, (char *)szName, sizeof(szName), ' ') != 0) {
        if (loadConf->globals.abortOnIDResolutionFail) {
            fprintf(stderr, "could not extract group name: %s\n", (char *)szName);
            exit(1); /* good exit */
        } else {
            LogError(0, RS_RET_NOT_FOUND, "could not extract group name");
            ABORT_FINALIZE(RS_RET_NOT_FOUND);
        }
    }

    do {
        char *p;

        /* Increase bufsize and try again.*/
        bufSize *= 2;
        CHKmalloc(p = realloc(stringBuf, bufSize));
        stringBuf = p;
        err = getgrnam_r((char *)szName, &gBuf, stringBuf, bufSize, &pgBuf);
    } while ((pgBuf == NULL) && (err == ERANGE));

    if (pgBuf == NULL) {
        if (err != 0) {
            LogError(err, RS_RET_NOT_FOUND, "Query for group '%s' resulted in an error", szName);
        } else {
            LogError(0, RS_RET_NOT_FOUND, "ID for group '%s' could not be found", szName);
        }
        iRet = RS_RET_NOT_FOUND;
        if (loadConf->globals.abortOnIDResolutionFail) {
            fprintf(stderr, "ID for group '%s' could not be found or error\n", szName);
            exit(1); /* good exit */
        }
    } else {
        if (pSetHdlr == NULL) {
            /* we should set value directly to var */
            *((gid_t *)pVal) = pgBuf->gr_gid;
        } else {
            /* we set value via a set function */
            CHKiRet(pSetHdlr(pVal, pgBuf->gr_gid));
        }
        dbgprintf("gid %d obtained for group '%s'\n", (int)pgBuf->gr_gid, szName);
    }

    skipWhiteSpace(pp); /* skip over any whitespace */

finalize_it:
    free(stringBuf);
    RETiRet;
}


/* extract a username and return its uid.
 * rgerhards, 2007-07-17
 */
static rsRetVal doGetUID(uchar **pp, rsRetVal (*pSetHdlr)(void *, uid_t), void *pVal) {
    struct passwd *ppwBuf;
    struct passwd pwBuf;
    DEFiRet;
    uchar szName[256];
    char stringBuf[2048]; /* I hope this is large enough... */

    assert(pp != NULL);
    assert(*pp != NULL);

    if (getSubString(pp, (char *)szName, sizeof(szName), ' ') != 0) {
        if (loadConf->globals.abortOnIDResolutionFail) {
            fprintf(stderr, "could not extract user name: %s\n", (char *)szName);
            exit(1); /* good exit */
        } else {
            LogError(0, RS_RET_NOT_FOUND, "could not extract user name");
            ABORT_FINALIZE(RS_RET_NOT_FOUND);
        }
    }

    getpwnam_r((char *)szName, &pwBuf, stringBuf, sizeof(stringBuf), &ppwBuf);

    if (ppwBuf == NULL) {
        if (loadConf->globals.abortOnIDResolutionFail) {
            fprintf(stderr, "ID for user '%s' could not be found or error\n", (char *)szName);
            exit(1); /* good exit */
        } else {
            LogError(0, RS_RET_NOT_FOUND, "ID for user '%s' could not be found or error", (char *)szName);
            iRet = RS_RET_NOT_FOUND;
        }
    } else {
        if (pSetHdlr == NULL) {
            /* we should set value directly to var */
            *((uid_t *)pVal) = ppwBuf->pw_uid;
        } else {
            /* we set value via a set function */
            CHKiRet(pSetHdlr(pVal, ppwBuf->pw_uid));
        }
        dbgprintf("uid %d obtained for user '%s'\n", (int)ppwBuf->pw_uid, szName);
    }

    skipWhiteSpace(pp); /* skip over any whitespace */

finalize_it:
    RETiRet;
}


/* Parse and process an binary cofig option. pVal must be
 * a pointer to an integer which is to receive the option
 * value.
 * rgerhards, 2007-07-15
 */
static rsRetVal doBinaryOptionLine(uchar **pp, rsRetVal (*pSetHdlr)(void *, int), void *pVal) {
    int iOption;
    DEFiRet;

    assert(pp != NULL);
    assert(*pp != NULL);

    if ((iOption = doParseOnOffOption(pp)) == -1) return RS_RET_ERR; /* nothing left to do */

    if (pSetHdlr == NULL) {
        /* we should set value directly to var */
        *((int *)pVal) = iOption;
    } else {
        /* we set value via a set function */
        CHKiRet(pSetHdlr(pVal, iOption));
    }

    skipWhiteSpace(pp); /* skip over any whitespace */

finalize_it:
    RETiRet;
}


/* parse a whitespace-delimited word from the provided string. This is a
 * helper function for a number of syntaxes. The parsed value is returned
 * in ppStrB (which must be provided by caller).
 * rgerhards, 2008-02-14
 */
static rsRetVal getWord(uchar **pp, cstr_t **ppStrB) {
    DEFiRet;
    uchar *p;

    assert(pp != NULL);
    assert(*pp != NULL);
    assert(ppStrB != NULL);

    CHKiRet(cstrConstruct(ppStrB));

    skipWhiteSpace(pp); /* skip over any whitespace */

    /* parse out the word */
    p = *pp;

    while (*p && !isspace((int)*p)) {
        CHKiRet(cstrAppendChar(*ppStrB, *p++));
    }
    cstrFinalize(*ppStrB);

    *pp = p;

finalize_it:
    RETiRet;
}


/* Parse and a word config line option. A word is a consequtive
 * sequence of non-whitespace characters. pVal must be
 * a pointer to a string which is to receive the option
 * value. The returned string must be freed by the caller.
 * rgerhards, 2007-09-07
 * To facilitate multiple instances of the same command line
 * directive, doGetWord() now checks if pVal is already a
 * non-NULL pointer. If so, we assume it was created by a previous
 * incarnation and is automatically freed. This happens only when
 * no custom handler is defined. If it is, the customer handler
 * must do the cleanup. I have checked and this was al also memory
 * leak with some code. Obviously, not a large one. -- rgerhards, 2007-12-20
 * Just to clarify: if pVal is parsed to a custom handler, this handler
 * is responsible for freeing pVal. -- rgerhards, 2008-03-20
 */
static rsRetVal doGetWord(uchar **pp, rsRetVal (*pSetHdlr)(void *, uchar *), void *pVal) {
    DEFiRet;
    cstr_t *pStrB = NULL;
    uchar *pNewVal;

    assert(pp != NULL);
    assert(*pp != NULL);

    CHKiRet(getWord(pp, &pStrB));
    CHKiRet(cstrConvSzStrAndDestruct(&pStrB, &pNewVal, 0));

    DBGPRINTF("doGetWord: get newval '%s' (len %d), hdlr %p\n", pNewVal, (int)ustrlen(pNewVal), pSetHdlr);
    /* we got the word, now set it */
    if (pSetHdlr == NULL) {
        /* we should set value directly to var */
        if (*((uchar **)pVal) != NULL) free(*((uchar **)pVal)); /* free previous entry */
        *((uchar **)pVal) = pNewVal; /* set new one */
    } else {
        /* we set value via a set function */
        CHKiRet(pSetHdlr(pVal, pNewVal));
    }

    skipWhiteSpace(pp); /* skip over any whitespace */

finalize_it:
    if (iRet != RS_RET_OK) {
        if (pStrB != NULL) cstrDestruct(&pStrB);
    }

    RETiRet;
}


/* parse a syslog name from the string. This is the generic code that is
 * called by the facility/severity functions. Note that we do not check the
 * validity of numerical values, something that should probably change over
 * time (TODO). -- rgerhards, 2008-02-14
 */
static rsRetVal doSyslogName(uchar **pp, rsRetVal (*pSetHdlr)(void *, int), void *pVal, syslogName_t *pNameTable) {
    DEFiRet;
    cstr_t *pStrB;
    int iNewVal;

    assert(pp != NULL);
    assert(*pp != NULL);

    CHKiRet(getWord(pp, &pStrB)); /* get word */
    iNewVal = decodeSyslogName(cstrGetSzStrNoNULL(pStrB), pNameTable);

    if (pSetHdlr == NULL) {
        /* we should set value directly to var */
        *((int *)pVal) = iNewVal; /* set new one */
    } else {
        /* we set value via a set function */
        CHKiRet(pSetHdlr(pVal, iNewVal));
    }

    skipWhiteSpace(pp); /* skip over any whitespace */

finalize_it:
    if (pStrB != NULL) rsCStrDestruct(&pStrB);

    RETiRet;
}


/* Implements the facility syntax.
 * rgerhards, 2008-02-14
 */
static rsRetVal doFacility(uchar **pp, rsRetVal (*pSetHdlr)(void *, int), void *pVal) {
    DEFiRet;
    iRet = doSyslogName(pp, pSetHdlr, pVal, syslogFacNames);
    RETiRet;
}


static rsRetVal doGoneAway(__attribute__((unused)) uchar **pp,
                           __attribute__((unused)) rsRetVal (*pSetHdlr)(void *, int),
                           __attribute__((unused)) void *pVal) {
    parser_warnmsg("config directive is no longer supported -- ignored");
    return RS_RET_CMD_GONE_AWAY;
}

/* Implements the severity syntax.
 * rgerhards, 2008-02-14
 */
static rsRetVal doSeverity(uchar **pp, rsRetVal (*pSetHdlr)(void *, int), void *pVal) {
    DEFiRet;
    iRet = doSyslogName(pp, pSetHdlr, pVal, syslogPriNames);
    RETiRet;
}


/* --------------- END functions for handling canned syntaxes --------------- */

/* destructor for cslCmdHdlr
 * pThis is actually a cslCmdHdlr_t, but we do not cast it as all we currently
 * need to do is free it.
 */
static rsRetVal cslchDestruct(void *pThis) {
    assert(pThis != NULL);
    free(pThis);

    return RS_RET_OK;
}


/* constructor for cslCmdHdlr
 */
static rsRetVal cslchConstruct(cslCmdHdlr_t **ppThis) {
    cslCmdHdlr_t *pThis;
    DEFiRet;

    assert(ppThis != NULL);
    if ((pThis = calloc(1, sizeof(cslCmdHdlr_t))) == NULL) {
        ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
    }

finalize_it:
    *ppThis = pThis;
    RETiRet;
}

/* destructor for linked list keys. As we do not use any dynamic memory,
 * we simply return. However, this entry point must be defined for the
 * linkedList class to make sure we have not forgotten a destructor.
 * rgerhards, 2007-11-21
 */
static rsRetVal cslchKeyDestruct(void __attribute__((unused)) * pData) {
    return RS_RET_OK;
}


/* Key compare operation for linked list class. This compares two
 * owner cookies (void *).
 * rgerhards, 2007-11-21
 */
static int cslchKeyCompare(void *pKey1, void *pKey2) {
    if (pKey1 == pKey2)
        return 0;
    else if (pKey1 < pKey2)
        return -1;
    else
        return 1;
}


/* set data members for this object
 */
static rsRetVal cslchSetEntry(
    cslCmdHdlr_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, int *permitted) {
    assert(pThis != NULL);
    assert(eType != eCmdHdlrInvalid);

    pThis->eType = eType;
    pThis->cslCmdHdlr = pHdlr;
    pThis->pData = pData;
    pThis->permitted = permitted;

    return RS_RET_OK;
}


/* call the specified handler
 */
static rsRetVal cslchCallHdlr(cslCmdHdlr_t *pThis, uchar **ppConfLine) {
    DEFiRet;
    assert(pThis != NULL);
    assert(ppConfLine != NULL);

    switch (pThis->eType) {
        case eCmdHdlrCustomHandler:
            CHKiRet(doCustomHdlr(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrUID:
            CHKiRet(doGetUID(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrGID:
            CHKiRet(doGetGID(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrBinary:
            CHKiRet(doBinaryOptionLine(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrFileCreateMode:
            CHKiRet(doFileCreateMode(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrInt:
            CHKiRet(doGetInt(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrNonNegInt:
            CHKiRet(doGetNonNegInt(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrPositiveInt:
            CHKiRet(doGetPositiveInt(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrSize:
            CHKiRet(doGetSize(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrGetChar:
            CHKiRet(doGetChar(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrFacility:
            CHKiRet(doFacility(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrSeverity:
            CHKiRet(doSeverity(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrGetWord:
            CHKiRet(doGetWord(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        case eCmdHdlrGoneAway:
            CHKiRet(doGoneAway(ppConfLine, pThis->cslCmdHdlr, pThis->pData));
            break;
        /* some non-legacy handler (used in v6+ solely) */
        case eCmdHdlrInvalid:
        case eCmdHdlrString:
        case eCmdHdlrArray:
        case eCmdHdlrQueueType:
        default:
            dbgprintf("error: command handler type %d not implemented in legacy system\n", pThis->eType);
            iRet = RS_RET_NOT_IMPLEMENTED;
            goto finalize_it;
    }

finalize_it:
    RETiRet;
}


/* ---------------------------------------------------------------------- *
 * now come the handlers for cslCmd_t
 * ---------------------------------------------------------------------- */

/* destructor for a cslCmd list key (a string as of now)
 */
static rsRetVal cslcKeyDestruct(void *pData) {
    free(pData); /* we do not need to cast as all we do is free it anyway... */
    return RS_RET_OK;
}

/* destructor for cslCmd
 */
static rsRetVal cslcDestruct(void *pData) {
    cslCmd_t *pThis = (cslCmd_t *)pData;

    assert(pThis != NULL);

    llDestroy(&pThis->llCmdHdlrs);
    free(pThis);

    return RS_RET_OK;
}


/* constructor for cslCmd
 */
static rsRetVal cslcConstruct(cslCmd_t **ppThis, int bChainingPermitted) {
    cslCmd_t *pThis;
    DEFiRet;

    assert(ppThis != NULL);
    if ((pThis = calloc(1, sizeof(cslCmd_t))) == NULL) {
        ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
    }

    pThis->bChainingPermitted = bChainingPermitted;

    CHKiRet(llInit(&pThis->llCmdHdlrs, cslchDestruct, cslchKeyDestruct, (int (*)(void *, void *))cslchKeyCompare));

finalize_it:
    *ppThis = pThis;
    RETiRet;
}


/* add a handler entry to a known command
 */
static rsRetVal cslcAddHdlr(
    cslCmd_t *pThis, ecslCmdHdrlType eType, rsRetVal (*pHdlr)(), void *pData, void *pOwnerCookie, int *permitted) {
    DEFiRet;
    cslCmdHdlr_t *pCmdHdlr = NULL;

    assert(pThis != NULL);

    CHKiRet(cslchConstruct(&pCmdHdlr));
    CHKiRet(cslchSetEntry(pCmdHdlr, eType, pHdlr, pData, permitted));
    CHKiRet(llAppend(&pThis->llCmdHdlrs, pOwnerCookie, pCmdHdlr));

finalize_it:
    if (iRet != RS_RET_OK) {
        if (pHdlr != NULL) cslchDestruct(pCmdHdlr);
    }

    RETiRet;
}


/* function that registers cfsysline handlers.
 * The supplied pCmdName is copied and a new buffer is allocated. This
 * buffer is automatically destroyed when the element is freed, the
 * caller does not need to take care of that. The caller must, however,
 * free pCmdName if he allocated it dynamically! -- rgerhards, 2007-08-09
 * Parameter permitted has been added to support the v2 config system. With it,
 * we can tell the legacy system (us here!) to check if a config directive is
 * still permitted. For example, the v2 system will disable module global
 * parameters if the are supplied via the native v2 callbacks. In order not
 * to break exisiting modules, we have renamed the rgCfSysLinHdlr routine to
 * version 2 and added a new one with the original name. It just calls the
 * v2 function and supplies a "don't care (NULL)" pointer as this argument.
 * rgerhards, 2012-06-26
 */
rsRetVal regCfSysLineHdlr2(const uchar *pCmdName,
                           int bChainingPermitted,
                           ecslCmdHdrlType eType,
                           rsRetVal (*pHdlr)(),
                           void *pData,
                           void *pOwnerCookie,
                           int *permitted) {
    DEFiRet;
    cslCmd_t *pThis;
    uchar *pMyCmdName;

    iRet = llFind(&llCmdList, (void *)pCmdName, (void *)&pThis);
    if (iRet == RS_RET_NOT_FOUND) {
        /* new command */
        CHKiRet(cslcConstruct(&pThis, bChainingPermitted));
        CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) {
            cslcDestruct(pThis);
            FINALIZE;
        }
        /* important: add to list, AFTER everything else is OK. Else
         * we mess up things in the error case.
         */
        if ((pMyCmdName = (uchar *)strdup((char *)pCmdName)) == NULL) {
            cslcDestruct(pThis);
            ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
        }
        CHKiRet_Hdlr(llAppend(&llCmdList, pMyCmdName, (void *)pThis)) {
            cslcDestruct(pThis);
            FINALIZE;
        }
    } else {
        /* command already exists, are we allowed to chain? */
        if (pThis->bChainingPermitted == 0 || bChainingPermitted == 0) {
            ABORT_FINALIZE(RS_RET_CHAIN_NOT_PERMITTED);
        }
        CHKiRet_Hdlr(cslcAddHdlr(pThis, eType, pHdlr, pData, pOwnerCookie, permitted)) {
            cslcDestruct(pThis);
            FINALIZE;
        }
    }

finalize_it:
    RETiRet;
}

rsRetVal regCfSysLineHdlr(const uchar *pCmdName,
                          int bChainingPermitted,
                          ecslCmdHdrlType eType,
                          rsRetVal (*pHdlr)(),
                          void *pData,
                          void *pOwnerCookie) {
    DEFiRet;
    iRet = regCfSysLineHdlr2(pCmdName, bChainingPermitted, eType, pHdlr, pData, pOwnerCookie, NULL);
    RETiRet;
}


rsRetVal unregCfSysLineHdlrs(void) {
    return llDestroy(&llCmdList);
}


/* helper function for unregCfSysLineHdlrs4Owner(). This is used to see if there is
 * a handler of this owner inside the element and, if so, remove it. Please note that
 * it keeps track of a pointer to the last linked list entry, as this is needed to
 * remove an entry from the list.
 * rgerhards, 2007-11-21
 */
DEFFUNC_llExecFunc(unregHdlrsHeadExec) {
    DEFiRet;
    cslCmd_t *pListHdr = (cslCmd_t *)pData;
    int iNumElts;

    /* first find element */
    CHKiRet(llFindAndDelete(&(pListHdr->llCmdHdlrs), pParam));

    /* now go back and check how many elements are left */
    CHKiRet(llGetNumElts(&(pListHdr->llCmdHdlrs), &iNumElts));

    if (iNumElts == 0) {
        /* nothing left in header, so request to delete it */
        iRet = RS_RET_OK_DELETE_LISTENTRY;
    }

finalize_it:
    RETiRet;
}
/* unregister and destroy cfSysLineHandlers for a specific owner. This method is
 * most importantly used before unloading a loadable module providing some handlers.
 * The full list of handlers is searched. If the to-be removed handler was the only
 * handler for a directive name, the directive header, too, is deleted.
 * rgerhards, 2007-11-21
 */
rsRetVal unregCfSysLineHdlrs4Owner(void *pOwnerCookie) {
    DEFiRet;
    /* we need to walk through all directive names, as the linked list
     * class does not provide a way to just search the lower-level handlers.
     */
    iRet = llExecFunc(&llCmdList, unregHdlrsHeadExec, pOwnerCookie);
    if (iRet == RS_RET_NOT_FOUND) {
        /* It is not considered an error if a module had no
           hanlers registered. */
        iRet = RS_RET_OK;
    }

    RETiRet;
}


/* process a cfsysline command (based on handler structure)
 * param "p" is a pointer to the command line after the command. Should be
 * updated.
 */
rsRetVal processCfSysLineCommand(uchar *pCmdName, uchar **p) {
    DEFiRet;
    rsRetVal iRetLL; /* for linked list handling */
    cslCmd_t *pCmd;
    cslCmdHdlr_t *pCmdHdlr;
    linkedListCookie_t llCookieCmdHdlr;
    uchar *pHdlrP; /* the handler's private p (else we could only call one handler) */
    int bWasOnceOK; /* was the result of an handler at least once RS_RET_OK? */
    uchar *pOKp = NULL; /* returned conf line pointer when it was OK */

    iRet = llFind(&llCmdList, (void *)pCmdName, (void *)&pCmd);

    if (iRet == RS_RET_NOT_FOUND) {
        LogError(0, RS_RET_NOT_FOUND,
                 "invalid or yet-unknown config file command '%s' - "
                 "have you forgotten to load a module?",
                 pCmdName);
    }

    if (iRet != RS_RET_OK) goto finalize_it;

    llCookieCmdHdlr = NULL;
    bWasOnceOK = 0;
    while ((iRetLL = llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void *)&pCmdHdlr)) == RS_RET_OK) {
        /* for the time being, we ignore errors during handlers. The
         * reason is that handlers are independent. An error in one
         * handler does not necessarily mean that another one will
         * fail, too. Later, we might add a config variable to control
         * this behaviour (but I am not sure if that is really
         * necessary). -- rgerhards, 2007-07-31
         */
        pHdlrP = *p;
        if (pCmdHdlr->permitted != NULL && !*(pCmdHdlr->permitted)) {
            LogError(0, RS_RET_PARAM_NOT_PERMITTED,
                     "command '%s' is currently not "
                     "permitted - did you already set it via a RainerScript command (v6+ config)?",
                     pCmdName);
            ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
        } else if ((iRet = cslchCallHdlr(pCmdHdlr, &pHdlrP)) == RS_RET_OK) {
            bWasOnceOK = 1;
            pOKp = pHdlrP;
        }
    }

    if (bWasOnceOK == 1) {
        *p = pOKp;
        iRet = RS_RET_OK;
    }

    if (iRetLL != RS_RET_END_OF_LINKEDLIST) iRet = iRetLL;

finalize_it:
    RETiRet;
}


/* debug print the command handler structure
 */
void dbgPrintCfSysLineHandlers(void) {
    cslCmd_t *pCmd;
    cslCmdHdlr_t *pCmdHdlr;
    linkedListCookie_t llCookieCmd;
    linkedListCookie_t llCookieCmdHdlr;
    uchar *pKey;

    dbgprintf("System Line Configuration Commands:\n");
    llCookieCmd = NULL;
    while (llGetNextElt(&llCmdList, &llCookieCmd, (void *)&pCmd) == RS_RET_OK) {
        llGetKey(llCookieCmd, (void *)&pKey); /* TODO: using the cookie is NOT clean! */
        dbgprintf("\tCommand '%s':\n", pKey);
        llCookieCmdHdlr = NULL;
        while (llGetNextElt(&pCmd->llCmdHdlrs, &llCookieCmdHdlr, (void *)&pCmdHdlr) == RS_RET_OK) {
            dbgprintf("\t\ttype : %d\n", pCmdHdlr->eType);
            dbgprintf("\t\tpData: 0x%lx\n", (unsigned long)pCmdHdlr->pData);
            dbgprintf("\t\tHdlr : 0x%lx\n", (unsigned long)pCmdHdlr->cslCmdHdlr);
            dbgprintf("\t\tOwner: 0x%lx\n", (unsigned long)llCookieCmdHdlr->pKey);
            dbgprintf("\n");
        }
    }
    dbgprintf("\n");
}


rsRetVal cfsyslineInit(void) {
    DEFiRet;
    CHKiRet(objGetObjInterface(&obj));

    CHKiRet(llInit(&llCmdList, cslcDestruct, cslcKeyDestruct, (int (*)(void *, void *))strcasecmp));

finalize_it:
    RETiRet;
}
