/* imrelp.c
 *
 * This is the implementation of the RELP input module.
 *
 * File begun on 2008-03-13 by RGerhards
 *
 * Copyright 2008-2019 Adiscon GmbH.
 *
 * This file is part of rsyslog.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *       -or-
 *       see COPYING.ASL20 in the source distribution
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "config.h"
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdarg.h>
#include <ctype.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <librelp.h>
#include "rsyslog.h"
#include "dirty.h"
#include "errmsg.h"
#include "cfsysline.h"
#include "module-template.h"
#include "net.h"
#include "msg.h"
#include "unicode-helper.h"
#include "prop.h"
#include "ruleset.h"
#include "glbl.h"
#include "statsobj.h"
#include "srUtils.h"
#include "parserif.h"
#include "ratelimit.h"

MODULE_TYPE_INPUT;
MODULE_TYPE_NOKEEP;
MODULE_CNFNAME("imrelp")

/* static data */
DEF_IMOD_STATIC_DATA;
DEFobjCurrIf(net) DEFobjCurrIf(prop) DEFobjCurrIf(ruleset) DEFobjCurrIf(glbl) DEFobjCurrIf(statsobj)

    /* forward definitions */
    static rsRetVal resetConfigVariables(uchar __attribute__((unused)) * pp, void __attribute__((unused)) * pVal);


/* Module static data */
/* config vars for legacy config system */
static relpEngine_t *pRelpEngine; /* our relp engine */

/* config settings */
typedef struct configSettings_s {
    uchar *pszBindRuleset; /* name of Ruleset to bind to */
} configSettings_t;
static configSettings_t cs;

struct instanceConf_s {
    uchar *pszBindPort; /* port to bind to */
    uchar *pszBindAddr; /* address to bind to */
    uchar *pszBindRuleset; /* name of ruleset to bind to */
    uchar *pszInputName; /* value for inputname property */
    prop_t *pInputName; /* InputName in property format for fast access */
    ruleset_t *pBindRuleset; /* ruleset to bind listener to */
    sbool bKeepAlive; /* support keep-alive packets */
    sbool bEnableTLS;
    sbool bEnableTLSZip;
    sbool bEnableLstn; /* flag to permit disabling of listener in error case */
    int dhBits;
    size_t maxDataSize;
    int oversizeMode;
    uchar *pristring; /* GnuTLS priority string (NULL if not to be provided) */
    uchar *authmode; /* TLS auth mode */
    uchar *caCertFile;
    uchar *myCertFile;
    uchar *myPrivKeyFile;
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
    uchar *tlscfgcmd;
#endif
    int iKeepAliveIntvl;
    int iKeepAliveProbes;
    int iKeepAliveTime;
    int ratelimitInterval;
    int ratelimitBurst;
    uchar *pszRatelimitName;
    flowControl_t flowCtlType;
    struct {
        int nmemb;
        uchar **name;
    } permittedPeers;

    struct instanceConf_s *next;
    /* with librelp, this module does not have any own specific session
     * or listener active data item. As a "work-around", we keep some
     * data items inside the configuration object. To keep things
     * decently clean, we put them all into their dedicated struct. So
     * it is easy to judge what is actual configuration and what is
     * dynamic runtime data. -- rgerhards, 2013-06-18
     */
    struct {
        statsobj_t *stats; /* listener stats */
        STATSCOUNTER_DEF(ctrSubmit, mutCtrSubmit)
        ratelimit_t *ratelimiter;
    } data;
};


struct modConfData_s {
    rsconf_t *pConf; /* our overall config object */
    instanceConf_t *root, *tail;
    const char *tlslib;
    uchar *pszBindRuleset; /* default name of Ruleset to bind to */
};

static modConfData_t *loadModConf = NULL; /* modConf ptr to use for the current load process */
static modConfData_t *runModConf = NULL; /* modConf ptr to use for the current load process */

/* module-global parameters */
static struct cnfparamdescr modpdescr[] = {{"ruleset", eCmdHdlrGetWord, 0}, {"tls.tlslib", eCmdHdlrString, 0}};
static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, sizeof(modpdescr) / sizeof(struct cnfparamdescr), modpdescr};

/* input instance parameters */
static struct cnfparamdescr inppdescr[] = {{"port", eCmdHdlrString, CNFPARAM_REQUIRED},
                                           {"address", eCmdHdlrString, 0},
                                           {"name", eCmdHdlrString, 0},
                                           {"ruleset", eCmdHdlrString, 0},
                                           {"keepalive", eCmdHdlrBinary, 0},
                                           {"keepalive.probes", eCmdHdlrInt, 0},
                                           {"keepalive.time", eCmdHdlrInt, 0},
                                           {"keepalive.interval", eCmdHdlrInt, 0},
                                           {"maxdatasize", eCmdHdlrSize, 0},
                                           {"oversizemode", eCmdHdlrString, 0},
                                           {"flowcontrol", eCmdHdlrGetWord, 0},
                                           {"ratelimit.interval", eCmdHdlrInt, 0},
                                           {"ratelimit.burst", eCmdHdlrInt, 0},
                                           {"ratelimit.name", eCmdHdlrString, 0},
                                           {"tls", eCmdHdlrBinary, 0},
                                           {"tls.permittedpeer", eCmdHdlrArray, 0},
                                           {"tls.authmode", eCmdHdlrString, 0},
                                           {"tls.dhbits", eCmdHdlrInt, 0},
                                           {"tls.prioritystring", eCmdHdlrString, 0},
                                           {"tls.cacert", eCmdHdlrString, 0},
                                           {"tls.mycert", eCmdHdlrString, 0},
                                           {"tls.myprivkey", eCmdHdlrString, 0},
                                           {"tls.tlscfgcmd", eCmdHdlrString, 0},
                                           {"tls.compression", eCmdHdlrBinary, 0}};
static struct cnfparamblk inppblk = {CNFPARAMBLK_VERSION, sizeof(inppdescr) / sizeof(struct cnfparamdescr), inppdescr};

#include "im-helper.h" /* must be included AFTER the type definitions! */
static int bLegacyCnfModGlobalsPermitted; /* are legacy module-global config parameters permitted? */

/* ------------------------------ callbacks ------------------------------ */

PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_IGNORE_Wformat_nonliteral static void __attribute__((format(printf, 1, 2))) imrelp_dbgprintf(const char *fmt,
                                                                                                    ...) {
    va_list ap;
    char pszWriteBuf[32 * 1024 + 1];  // this function has to be able to
    /*generate a buffer longer than that of r_dbgprintf, so
    r_dbgprintf can properly truncate*/

    if (!(Debug && debugging_on)) {
        return;
    }

    va_start(ap, fmt);
    vsnprintf(pszWriteBuf, sizeof(pszWriteBuf), fmt, ap);
    va_end(ap);
    r_dbgprintf("imrelp.c", "%s", pszWriteBuf);
}
PRAGMA_DIAGNOSTIC_POP

static void onErr(void *pUsr, char *objinfo, char *errmesg, __attribute__((unused)) relpRetVal errcode) {
    instanceConf_t *inst = (instanceConf_t *)pUsr;
    LogError(0, RS_RET_RELP_AUTH_FAIL,
             "imrelp[%s]: error '%s', object "
             " '%s' - input may not work as intended",
             inst->pszBindPort, errmesg, objinfo);
}

static void onGenericErr(char *objinfo, char *errmesg, __attribute__((unused)) relpRetVal errcode) {
    LogError(0, RS_RET_RELP_ERR,
             "imrelp: librelp error '%s', object "
             " '%s' - input may not work as intended",
             errmesg, objinfo);
}

static void onAuthErr(void *pUsr, char *authinfo, char *errmesg, __attribute__((unused)) relpRetVal errcode) {
    instanceConf_t *inst = (instanceConf_t *)pUsr;
    LogError(0, RS_RET_RELP_AUTH_FAIL,
             "imrelp[%s]: authentication error '%s', peer "
             "is '%s'",
             inst->pszBindPort, errmesg, authinfo);
}

/* callback for receiving syslog messages. This function is invoked from the
 * RELP engine when a syslog message arrived. It must return a relpRetVal,
 * with anything else but RELP_RET_OK terminating the relp session. Please note
 * that RELP_RET_OK is equal to RS_RET_OK and the other libRELP error codes
 * are different from our rsRetVal. So we can simply use our own iRet system
 * to fulfill the requirement.
 * rgerhards, 2008-03-21
 * Note: librelp 1.0.0 is required in order to receive the IP address, otherwise
 * we will only see the hostname (twice). -- rgerhards, 2009-10-14
 */
static relpRetVal onSyslogRcv(void *pUsr, uchar *pHostname, uchar *pIP, uchar *msg, size_t lenMsg) {
    prop_t *pProp = NULL;
    smsg_t *pMsg;
    instanceConf_t *inst = (instanceConf_t *)pUsr;
    DEFiRet;

    CHKiRet(msgConstruct(&pMsg));
    MsgSetInputName(pMsg, inst->pInputName);
    MsgSetRawMsg(pMsg, (char *)msg, lenMsg);
    MsgSetFlowControlType(pMsg, inst->flowCtlType);
    MsgSetRuleset(pMsg, inst->pBindRuleset);
    pMsg->msgFlags = PARSE_HOSTNAME | NEEDS_PARSING;

    /* TODO: optimize this, we can store it inside the session */
    MsgSetRcvFromStr(pMsg, pHostname, ustrlen(pHostname), &pProp);
    CHKiRet(prop.Destruct(&pProp));
    CHKiRet(MsgSetRcvFromIPStr(pMsg, pIP, ustrlen(pIP), &pProp));
    CHKiRet(prop.Destruct(&pProp));
    if (inst->data.ratelimiter != NULL) {
        rsRetVal ratelimitRet = ratelimitAddMsg(inst->data.ratelimiter, NULL, pMsg);
        if (ratelimitRet == RS_RET_DISCARDMSG) {
            FINALIZE;
        }
        CHKiRet(ratelimitRet);
    } else {
        CHKiRet(submitMsg2(pMsg));
    }
    STATSCOUNTER_INC(inst->data.ctrSubmit, inst->data.mutCtrSubmit);

finalize_it:

    RETiRet;
}


/* ------------------------------ end callbacks ------------------------------ */

/* create input instance, set default parameters, and
 * add it to the list of instances.
 */
static rsRetVal createInstance(instanceConf_t **pinst) {
    instanceConf_t *inst;
    DEFiRet;
    CHKmalloc(inst = malloc(sizeof(instanceConf_t)));
    inst->next = NULL;

    inst->pszBindPort = NULL;
    inst->pszBindAddr = NULL;
    inst->pszBindRuleset = NULL;
    inst->pszInputName = NULL;
    inst->pBindRuleset = NULL;
    inst->bKeepAlive = 0;
    inst->iKeepAliveIntvl = 0;
    inst->iKeepAliveProbes = 0;
    inst->iKeepAliveTime = 0;
    inst->ratelimitInterval = -1;
    inst->ratelimitBurst = -1;
    inst->pszRatelimitName = NULL;
    inst->bEnableTLS = 0;
    inst->bEnableTLSZip = 0;
    inst->bEnableLstn = 0;
    inst->dhBits = 0;
    inst->pristring = NULL;
    inst->authmode = NULL;
    inst->permittedPeers.nmemb = 0;
    inst->caCertFile = NULL;
    inst->myCertFile = NULL;
    inst->myPrivKeyFile = NULL;
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
    inst->tlscfgcmd = NULL;
#endif
    inst->maxDataSize = 0;
    inst->flowCtlType = eFLOWCTL_LIGHT_DELAY;
#ifdef HAVE_RELPSRVSETOVERSIZEMODE
    inst->oversizeMode = RELP_OVERSIZE_TRUNCATE;
#endif

    /* node created, let's add to config */
    if (loadModConf->tail == NULL) {
        loadModConf->tail = loadModConf->root = inst;
    } else {
        loadModConf->tail->next = inst;
        loadModConf->tail = inst;
    }

    *pinst = inst;
finalize_it:
    RETiRet;
}


/* function to generate an error message if the ruleset cannot be found */
static inline void std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *inst) {
    LogError(0, NO_ERRCODE,
             "imrelp[%s]: ruleset '%s' not found - "
             "using default ruleset instead",
             inst->pszBindPort, inst->pszBindRuleset);
}


/* This function is called when a new listener instance shall be added to
 * the current config object via the legacy config system. It just shuffles
 * all parameters to the listener in-memory instance.
 * rgerhards, 2011-05-04
 */
static rsRetVal addInstance(void __attribute__((unused)) * pVal, uchar *pNewVal) {
    instanceConf_t *inst;
    DEFiRet;

    CHKiRet(createInstance(&inst));

    if (pNewVal == NULL || *pNewVal == '\0') {
        LogError(0, NO_ERRCODE, "imrelp: port number must be specified, listener ignored");
    }
    if ((pNewVal == NULL) || (*pNewVal == '\0')) {
        inst->pszBindPort = NULL;
    } else {
        CHKmalloc(inst->pszBindPort = ustrdup(pNewVal));
    }
    if ((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) {
        inst->pszBindRuleset = NULL;
    } else {
        CHKmalloc(inst->pszBindRuleset = ustrdup(cs.pszBindRuleset));
    }
    inst->pBindRuleset = NULL;

    inst->bEnableLstn = -1; /* all ok, ready to start up */
finalize_it:
    free(pNewVal);
    RETiRet;
}


static rsRetVal addListner(modConfData_t __attribute__((unused)) * modConf, instanceConf_t *inst) {
    relpSrv_t *pSrv;
    int relpRet;
    uchar statname[64];
    int i;
    DEFiRet;

    if (!inst->bEnableLstn) {
        DBGPRINTF("listener not started because it is disabled by config error\n");
        FINALIZE;
    }

    if (pRelpEngine == NULL) {
        CHKiRet(relpEngineConstruct(&pRelpEngine));
        CHKiRet(relpEngineSetDbgprint(pRelpEngine, (void (*)(char *, ...))imrelp_dbgprintf));
        CHKiRet(relpEngineSetFamily(pRelpEngine, glbl.GetDefPFFamily(runModConf->pConf)));
        CHKiRet(relpEngineSetEnableCmd(pRelpEngine, (uchar *)"syslog", eRelpCmdState_Required));
        CHKiRet(relpEngineSetSyslogRcv2(pRelpEngine, onSyslogRcv));
        CHKiRet(relpEngineSetOnErr(pRelpEngine, onErr));
        CHKiRet(relpEngineSetOnGenericErr(pRelpEngine, onGenericErr));
        CHKiRet(relpEngineSetOnAuthErr(pRelpEngine, onAuthErr));
        if (!glbl.GetDisableDNS(runModConf->pConf)) {
            CHKiRet(relpEngineSetDnsLookupMode(pRelpEngine, 1));
        }
#if defined(HAVE_RELPENGINESETTLSLIBBYNAME)
        if (modConf->tlslib != NULL) {
            if (relpEngineSetTLSLibByName(pRelpEngine, modConf->tlslib) != RELP_RET_OK) {
                LogMsg(0, RS_RET_CONF_PARAM_INVLD, LOG_WARNING,
                       "imrelp: tlslib '%s' not accepted as valid by librelp - using default", modConf->tlslib);
            }
        }
#endif
    }

    CHKiRet(relpEngineListnerConstruct(pRelpEngine, &pSrv));
    CHKiRet(relpSrvSetMaxDataSize(pSrv, inst->maxDataSize));
    CHKiRet(relpSrvSetLstnPort(pSrv, inst->pszBindPort));
#if defined(HAVE_RELPSRVSETLSTNADDR)
    CHKiRet(relpSrvSetLstnAddr(pSrv, inst->pszBindAddr));
#endif

#ifdef HAVE_RELPSRVSETOVERSIZEMODE
    CHKiRet(relpSrvSetOversizeMode(pSrv, inst->oversizeMode));
#endif
    inst->pszInputName = ustrdup((inst->pszInputName == NULL) ? UCHAR_CONSTANT("imrelp") : inst->pszInputName);
    CHKiRet(prop.Construct(&inst->pInputName));
    CHKiRet(prop.SetString(inst->pInputName, inst->pszInputName, ustrlen(inst->pszInputName)));
    CHKiRet(prop.ConstructFinalize(inst->pInputName));
    /* support statistics gathering */
    CHKiRet(statsobj.Construct(&(inst->data.stats)));
    snprintf((char *)statname, sizeof(statname), "%s(%s)", inst->pszInputName, inst->pszBindPort);
    statname[sizeof(statname) - 1] = '\0'; /* just to be on the save side... */
    CHKiRet(statsobj.SetName(inst->data.stats, statname));
    CHKiRet(statsobj.SetOrigin(inst->data.stats, (uchar *)"imrelp"));
    STATSCOUNTER_INIT(inst->data.ctrSubmit, inst->data.mutCtrSubmit);
    CHKiRet(statsobj.AddCounter(inst->data.stats, UCHAR_CONSTANT("submitted"), ctrType_IntCtr, CTR_FLAG_RESETTABLE,
                                &(inst->data.ctrSubmit)));
    CHKiRet(statsobj.ConstructFinalize(inst->data.stats));
    /* end stats counters */
    /* set up ratelimiter */
    inst->data.ratelimiter = NULL;
    if (inst->pszRatelimitName != NULL) {
        CHKiRet(ratelimitNewFromConfig(&inst->data.ratelimiter, runModConf->pConf, (char *)inst->pszRatelimitName,
                                       "imrelp", (char *)inst->pszBindPort));
    } else if (inst->ratelimitInterval > 0) {
        CHKiRet(ratelimitNew(&inst->data.ratelimiter, "imrelp", (char *)inst->pszBindPort));
        ratelimitSetLinuxLike(inst->data.ratelimiter, (unsigned)inst->ratelimitInterval,
                              (unsigned)inst->ratelimitBurst);
    }
    if (inst->data.ratelimiter != NULL) {
        ratelimitSetThreadSafe(inst->data.ratelimiter);
    }
    relpSrvSetUsrPtr(pSrv, inst);
    relpSrvSetKeepAlive(pSrv, inst->bKeepAlive, inst->iKeepAliveIntvl, inst->iKeepAliveProbes, inst->iKeepAliveTime);
    if (inst->bEnableTLS) {
        relpRet = relpSrvEnableTLS2(pSrv);
        if (relpRet == RELP_RET_ERR_NO_TLS) {
            LogError(0, RS_RET_RELP_NO_TLS,
                     "imrelp: could not activate relp TLS, librelp "
                     "does not support it (most probably GnuTLS lib "
                     "is too old)!");
            ABORT_FINALIZE(RS_RET_RELP_NO_TLS);
        } else if (relpRet == RELP_RET_ERR_NO_TLS_AUTH) {
            LogError(0, RS_RET_RELP_NO_TLS_AUTH,
                     "imrelp: could not activate relp TLS with "
                     "authentication, librelp does not support it "
                     "(most probably GnuTLS lib is too old)! "
                     "Note: anonymous TLS is probably supported.");
            ABORT_FINALIZE(RS_RET_RELP_NO_TLS_AUTH);
        } else if (relpRet != RELP_RET_OK) {
            LogError(0, RS_RET_RELP_ERR, "imrelp: could not activate relp TLS, code %d", relpRet);
            ABORT_FINALIZE(RS_RET_RELP_ERR);
        }
        if (inst->bEnableTLSZip) {
            relpSrvEnableTLSZip2(pSrv);
        }
        if (inst->dhBits) {
            relpSrvSetDHBits(pSrv, inst->dhBits);
        }
        relpSrvSetGnuTLSPriString(pSrv, (char *)inst->pristring);
        if (relpSrvSetAuthMode(pSrv, (char *)inst->authmode) != RELP_RET_OK) {
            LogError(0, RS_RET_RELP_ERR, "imrelp: invalid auth mode '%s'", inst->authmode);
            ABORT_FINALIZE(RS_RET_RELP_ERR);
        }
        if (relpSrvSetCACert(pSrv, (char *)inst->caCertFile) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
        if (relpSrvSetOwnCert(pSrv, (char *)inst->myCertFile) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
        if (relpSrvSetPrivKey(pSrv, (char *)inst->myPrivKeyFile) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
        if (inst->tlscfgcmd != NULL) {
            if (relpSrvSetTlsConfigCmd(pSrv, (char *)inst->tlscfgcmd) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
        }
#endif
        for (i = 0; i < inst->permittedPeers.nmemb; ++i) {
            relpSrvAddPermittedPeer(pSrv, (char *)inst->permittedPeers.name[i]);
        }
    }
    relpRet = relpEngineListnerConstructFinalize(pRelpEngine, pSrv);
    /* re-check error TLS error codes. librelp seems to emit them only
     * after finalize in some cases...
     */
    if (relpRet == RELP_RET_ERR_NO_TLS) {
        LogError(0, RS_RET_RELP_NO_TLS,
                 "imrelp: could not activate relp TLS listener, librelp "
                 "does not support it (most probably GnuTLS lib "
                 "is too old)!");
        ABORT_FINALIZE(RS_RET_RELP_NO_TLS);
    } else if (relpRet == RELP_RET_ERR_NO_TLS_AUTH) {
        LogError(0, RS_RET_RELP_NO_TLS_AUTH,
                 "imrelp: could not activate relp TLS listener with "
                 "authentication, librelp does not support it "
                 "(most probably GnuTLS lib is too old)! "
                 "Note: anonymous TLS is probably supported.");
        ABORT_FINALIZE(RS_RET_RELP_NO_TLS_AUTH);
    } else if (relpRet != RELP_RET_OK) {
        LogError(0, RS_RET_RELP_ERR, "imrelp: could not activate relp listener, code %d", relpRet);
        ABORT_FINALIZE(RS_RET_RELP_ERR);
    }

    DBGPRINTF("imrelp: max data size %zd\n", inst->maxDataSize);
    resetConfigVariables(NULL, NULL);

finalize_it:
    RETiRet;
}

/** Warn in secure warn mode when an imrelp listener is configured without TLS. */
static void warnIfPlainRelpListenerConfigured(const instanceConf_t *const inst) {
    if (!inst->bEnableTLS) {
        glblWarnIfInsecureDefault(loadModConf->pConf,
                                  "imrelp input uses tls=\"off\" (plain RELP without TLS); "
                                  "see https://docs.rsyslog.com/doc/faq/tls_mode0_disables_tls.html");
    } else if (inst->authmode != NULL && strcasecmp((const char *)inst->authmode, "anon") == 0) {
        glblWarnIfInsecureDefault(
            loadModConf->pConf,
            "imrelp uses tls.authmode=\"anon\"; peer identity is not authenticated, so MITM is possible "
            "(see https://docs.rsyslog.com/doc/faq/tls_anon_auth_mitm.html)");
    }
}


BEGINnewInpInst
    struct cnfparamvals *pvals;
    instanceConf_t *inst = NULL;
    int i, j;
    FILE *fp;
    CODESTARTnewInpInst;
    DBGPRINTF("newInpInst (imrelp)\n");

    if ((pvals = nvlstGetParams(lst, &inppblk, NULL)) == NULL) {
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }

    if (Debug) {
        dbgprintf("input param blk in imrelp:\n");
        cnfparamsPrint(&inppblk, pvals);
    }

    CHKiRet(createInstance(&inst));

    for (i = 0; i < inppblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;
        if (!strcmp(inppblk.descr[i].name, "port")) {
            CHKmalloc(inst->pszBindPort = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(inppblk.descr[i].name, "address")) {
#if defined(HAVE_RELPSRVSETLSTNADDR)
            CHKmalloc(inst->pszBindAddr = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
#else
            parser_errmsg(
                "imrelp: librelp does not support input parameter 'address'; "
                "it probably is too old (1.2.16 should be fine); ignoring setting now, "
                "listening on all interfaces");
#endif
        } else if (!strcmp(inppblk.descr[i].name, "name")) {
            CHKmalloc(inst->pszInputName = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(inppblk.descr[i].name, "ruleset")) {
            CHKmalloc(inst->pszBindRuleset = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(inppblk.descr[i].name, "maxdatasize")) {
            inst->maxDataSize = (size_t)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "flowcontrol")) {
            if (!es_strconstcmp(pvals[i].val.d.estr, "none")) {
                inst->flowCtlType = eFLOWCTL_NO_DELAY;
            } else if (!es_strconstcmp(pvals[i].val.d.estr, "light")) {
                inst->flowCtlType = eFLOWCTL_LIGHT_DELAY;
            } else if (!es_strconstcmp(pvals[i].val.d.estr, "full")) {
                inst->flowCtlType = eFLOWCTL_FULL_DELAY;
            } else {
                char *mode = NULL;
                CHKmalloc(mode = es_str2cstr(pvals[i].val.d.estr, NULL));
                parser_errmsg(
                    "imrelp: wrong flowcontrol parameter "
                    "value '%s', using default: 'light'; possible "
                    "values: 'no', 'light', 'full'\n",
                    mode);
                free((void *)mode);
            }
        } else if (!strcmp(inppblk.descr[i].name, "oversizemode")) {
#ifdef HAVE_RELPSRVSETOVERSIZEMODE
            char *mode;
            CHKmalloc(mode = es_str2cstr(pvals[i].val.d.estr, NULL));
            if (!strcmp(mode, "abort")) {
                inst->oversizeMode = RELP_OVERSIZE_ABORT;
            } else if (!strcmp(mode, "truncate")) {
                inst->oversizeMode = RELP_OVERSIZE_TRUNCATE;
            } else if (!strcmp(mode, "accept")) {
                inst->oversizeMode = RELP_OVERSIZE_ACCEPT;
            } else {
                parser_errmsg(
                    "imrelp: wrong oversizeMode parameter "
                    "value %s, using default: truncate\n",
                    mode);
                inst->oversizeMode = RELP_OVERSIZE_TRUNCATE;
            }
#else
            parser_errmsg(
                "imrelp: parameter oversizeMode is not available in "
                "this relp version and is therefore disabled.");
#endif
        } else if (!strcmp(inppblk.descr[i].name, "keepalive")) {
            inst->bKeepAlive = (sbool)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "keepalive.probes")) {
            inst->iKeepAliveProbes = (int)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "keepalive.time")) {
            inst->iKeepAliveTime = (int)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "keepalive.interval")) {
            inst->iKeepAliveIntvl = (int)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "ratelimit.interval")) {
            inst->ratelimitInterval = (int)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "ratelimit.burst")) {
            inst->ratelimitBurst = (int)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "ratelimit.name")) {
            CHKmalloc(inst->pszRatelimitName = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(inppblk.descr[i].name, "tls")) {
            inst->bEnableTLS = (unsigned)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "tls.dhbits")) {
            inst->dhBits = (unsigned)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "tls.prioritystring")) {
            CHKmalloc(inst->pristring = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(inppblk.descr[i].name, "tls.authmode")) {
            CHKmalloc(inst->authmode = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(inppblk.descr[i].name, "tls.compression")) {
            inst->bEnableTLSZip = (unsigned)pvals[i].val.d.n;
        } else if (!strcmp(inppblk.descr[i].name, "tls.cacert")) {
            CHKmalloc(inst->caCertFile = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
            fp = fopen((const char *)inst->caCertFile, "r");
            if (fp == NULL) {
                LogError(errno, RS_RET_NO_FILE_ACCESS, "error: certificate file %s couldn't be accessed",
                         inst->caCertFile);
            } else {
                fclose(fp);
            }
        } else if (!strcmp(inppblk.descr[i].name, "tls.mycert")) {
            CHKmalloc(inst->myCertFile = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
            fp = fopen((const char *)inst->myCertFile, "r");
            if (fp == NULL) {
                LogError(errno, RS_RET_NO_FILE_ACCESS, "error: certificate file %s couldn't be accessed",
                         inst->myCertFile);
            } else {
                fclose(fp);
            }
        } else if (!strcmp(inppblk.descr[i].name, "tls.myprivkey")) {
            CHKmalloc(inst->myPrivKeyFile = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
            fp = fopen((const char *)inst->myPrivKeyFile, "r");
            if (fp == NULL) {
                LogError(errno, RS_RET_NO_FILE_ACCESS, "error: certificate file %s couldn't be accessed",
                         inst->myPrivKeyFile);
            } else {
                fclose(fp);
            }
        } else if (!strcmp(inppblk.descr[i].name, "tls.tlscfgcmd")) {
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
            CHKmalloc(inst->tlscfgcmd = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
#else
            parser_errmsg(
                "imrelp: librelp does not support input parameter 'tls.tlscfgcmd'; "
                "it probably is too old (1.5.0 or higher should be fine); ignoring setting now.");
#endif
        } else if (!strcmp(inppblk.descr[i].name, "tls.permittedpeer")) {
            inst->permittedPeers.nmemb = pvals[i].val.d.ar->nmemb;
            CHKmalloc(inst->permittedPeers.name = malloc(sizeof(uchar *) * inst->permittedPeers.nmemb));
            for (j = 0; j < pvals[i].val.d.ar->nmemb; ++j) {
                inst->permittedPeers.name[j] = (uchar *)es_str2cstr(pvals[i].val.d.ar->arr[j], NULL);
            }
        } else {
            dbgprintf(
                "imrelp: program error, non-handled "
                "param '%s'\n",
                inppblk.descr[i].name);
        }
    }

    if (inst->myCertFile != NULL && inst->myPrivKeyFile == NULL) {
        LogError(0, RS_RET_ERR,
                 "imrelp: certificate file given but no corresponding "
                 "private key file - this is invalid, listener cannot be started");
        ABORT_FINALIZE(RS_RET_ERR);
    }
    if (inst->myCertFile == NULL && inst->myPrivKeyFile != NULL) {
        LogError(0, RS_RET_ERR,
                 "imrelp: private key file given but no corresponding "
                 "certificate file - this is invalid, listener cannot be started");
        ABORT_FINALIZE(RS_RET_ERR);
    }

    if (inst->pszRatelimitName != NULL) {
        if (inst->ratelimitInterval != -1 || inst->ratelimitBurst != -1) {
            LogError(0, RS_RET_INVALID_PARAMS,
                     "imrelp: ratelimit.name is mutually exclusive with "
                     "ratelimit.interval and ratelimit.burst - using ratelimit.name");
        }
        inst->ratelimitInterval = 0;
        inst->ratelimitBurst = 0;
    } else {
        if (inst->ratelimitInterval == -1) inst->ratelimitInterval = 0;
        if (inst->ratelimitBurst == -1) inst->ratelimitBurst = 10000;
    }

    warnIfPlainRelpListenerConfigured(inst);

    inst->bEnableLstn = -1; /* all ok, ready to start up */

finalize_it:
    CODE_STD_FINALIZERnewInpInst cnfparamvalsDestruct(pvals, &inppblk);
    if (iRet != RS_RET_OK) {
        if (inst != NULL) {
            free(inst->myCertFile);
            inst->myCertFile = NULL;
            free(inst->myPrivKeyFile);
            inst->myPrivKeyFile = NULL;
        }
    }
ENDnewInpInst


BEGINbeginCnfLoad
    CODESTARTbeginCnfLoad;
    loadModConf = pModConf;
    pModConf->pConf = pConf;
    pModConf->pszBindRuleset = NULL;
    pModConf->tlslib = NULL;
    /* init legacy config variables */
    cs.pszBindRuleset = NULL;
    bLegacyCnfModGlobalsPermitted = 1;
ENDbeginCnfLoad


BEGINsetModCnf
    struct cnfparamvals *pvals = NULL;
    int i;
    CODESTARTsetModCnf;
    pvals = nvlstGetParams(lst, &modpblk, NULL);
    if (pvals == NULL) {
        LogError(0, RS_RET_MISSING_CNFPARAMS,
                 "error processing module "
                 "config parameters [module(...)]");
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }

    if (Debug) {
        dbgprintf("module (global) param blk for imrelp:\n");
        cnfparamsPrint(&modpblk, pvals);
    }

    for (i = 0; i < modpblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;
        if (!strcmp(modpblk.descr[i].name, "ruleset")) {
            CHKmalloc(loadModConf->pszBindRuleset = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(modpblk.descr[i].name, "tls.tlslib")) {
#if defined(HAVE_RELPENGINESETTLSLIBBYNAME)
            CHKmalloc(loadModConf->tlslib = es_str2cstr(pvals[i].val.d.estr, NULL));
#else
            LogError(0, RS_RET_NOT_IMPLEMENTED,
                     "imrelp warning: parameter tls.tlslib ignored - librelp does not support "
                     "this API call. Using whatever librelp was compiled with.");
#endif
        } else {
            dbgprintf(
                "imrelp: program error, non-handled "
                "param '%s' in beginCnfLoad\n",
                modpblk.descr[i].name);
        }
    }
    /* remove all of our legacy module handlers, as they can not used in addition
     * the the new-style config method.
     */
    bLegacyCnfModGlobalsPermitted = 0;
finalize_it:
    if (pvals != NULL) cnfparamvalsDestruct(pvals, &modpblk);
ENDsetModCnf

BEGINendCnfLoad
    CODESTARTendCnfLoad;
    if (loadModConf->pszBindRuleset == NULL) {
        if ((cs.pszBindRuleset == NULL) || (cs.pszBindRuleset[0] == '\0')) {
            loadModConf->pszBindRuleset = NULL;
        } else {
            CHKmalloc(loadModConf->pszBindRuleset = ustrdup(cs.pszBindRuleset));
        }
    } else {
        if ((cs.pszBindRuleset != NULL) && (cs.pszBindRuleset[0] != '\0')) {
            LogError(0, RS_RET_DUP_PARAM,
                     "imrelp: ruleset "
                     "set via legacy directive ignored");
        }
    }
finalize_it:
    free(cs.pszBindRuleset);
    cs.pszBindRuleset = NULL;
    loadModConf = NULL; /* done loading */
ENDendCnfLoad

BEGINcheckCnf
    instanceConf_t *inst;
    size_t maxMessageSize;
    CODESTARTcheckCnf;
    for (inst = pModConf->root; inst != NULL; inst = inst->next) {
        if (inst->pszBindRuleset == NULL && pModConf->pszBindRuleset != NULL) {
            CHKmalloc(inst->pszBindRuleset = ustrdup(pModConf->pszBindRuleset));
        }
        std_checkRuleset(pModConf, inst);


        if (inst->maxDataSize == 0) {
            /* We set default value for maxDataSize here because
             * otherwise the maxMessageSize isn't set.
             */
            inst->maxDataSize = glbl.GetMaxLine(loadConf);
        }
        maxMessageSize = (size_t)glbl.GetMaxLine(loadConf);
        if (inst->maxDataSize < maxMessageSize) {
            LogError(0, RS_RET_INVALID_PARAMS,
                     "error: "
                     "maxDataSize (%zu) is smaller than global parameter "
                     "maxMessageSize (%zu) - global parameter will be used.",
                     inst->maxDataSize, maxMessageSize);
            inst->maxDataSize = maxMessageSize;
        }
    }

finalize_it:
ENDcheckCnf


BEGINactivateCnfPrePrivDrop
    instanceConf_t *inst;
    CODESTARTactivateCnfPrePrivDrop;
    runModConf = pModConf;
    for (inst = runModConf->root; inst != NULL; inst = inst->next) {
        addListner(pModConf, inst);
    }
    if (pRelpEngine == NULL) {
        LogError(0, RS_RET_NO_LSTN_DEFINED, "imrelp: no RELP listener defined, module can not run.");
        ABORT_FINALIZE(RS_RET_NO_RUN);
    }
finalize_it:
ENDactivateCnfPrePrivDrop

BEGINactivateCnf
    CODESTARTactivateCnf;
ENDactivateCnf


BEGINfreeCnf
    instanceConf_t *inst, *del;
    int i;
    CODESTARTfreeCnf;
    if (pRelpEngine != NULL) iRet = relpEngineDestruct(&pRelpEngine);

    for (inst = pModConf->root; inst != NULL;) {
        free(inst->pszBindPort);
        if (inst->pszBindAddr != NULL) {
            free(inst->pszBindAddr);
        }
        free(inst->pszBindRuleset);
        free(inst->pszInputName);
        free(inst->pristring);
        free(inst->authmode);
        for (i = 0; i < inst->permittedPeers.nmemb; ++i) {
            free(inst->permittedPeers.name[i]);
        }
        if (inst->bEnableLstn) {
            prop.Destruct(&inst->pInputName);
            statsobj.Destruct(&(inst->data.stats));
            if (inst->data.ratelimiter != NULL) ratelimitDestruct(inst->data.ratelimiter);
        }
        free(inst->pszRatelimitName);
        del = inst;
        inst = inst->next;
        free(del);
    }
    free(pModConf->pszBindRuleset);
ENDfreeCnf

/* This is used to terminate the plugin. Note that the signal handler blocks
 * other activity on the thread. As such, it is safe to request the stop. When
 * we terminate, relpEngine is called, and it's select() loop interrupted. But
 * only *after this function is done*. So we do not have a race!
 */
static void imrelpDoSIGTTIN(int __attribute__((unused)) sig) {
    const int saved_errno = errno;
    const int bTerminate = PREFER_LOAD_INT(&bTerminateInputsSigSafe);
    if (bTerminate && (pRelpEngine != NULL)) {
        relpEngineSetStop(pRelpEngine);
    }
    errno = saved_errno;
}


/* This function is called to gather input.
 */
BEGINrunInput
    sigset_t sigSet;
    struct sigaction sigAct;
    CODESTARTrunInput;
    /* we want to support non-cancel input termination. To do so, we must signal librelp
     * when to stop. As we run on the same thread, we need to register as SIGTTIN handler,
     * which will be used to put the terminating condition into librelp.
     */
    sigfillset(&sigSet);
    pthread_sigmask(SIG_BLOCK, &sigSet, NULL);
    sigemptyset(&sigSet);
    sigaddset(&sigSet, SIGTTIN);
    pthread_sigmask(SIG_UNBLOCK, &sigSet, NULL);
    memset(&sigAct, 0, sizeof(sigAct));
    sigemptyset(&sigAct.sa_mask);
    sigAct.sa_handler = imrelpDoSIGTTIN;
    sigaction(SIGTTIN, &sigAct, NULL);

    iRet = relpEngineRun(pRelpEngine);
ENDrunInput


BEGINwillRun
    CODESTARTwillRun;
ENDwillRun


BEGINafterRun
    CODESTARTafterRun;
    /* do cleanup here */
ENDafterRun


BEGINmodExit
    CODESTARTmodExit;
    struct sigaction newAct;
    memset(&newAct, 0, sizeof(newAct));
    sigemptyset(&newAct.sa_mask);
    newAct.sa_handler = SIG_IGN;
    sigaction(SIGTTIN, &newAct, NULL);

    /* release objects we used */
    objRelease(statsobj, CORE_COMPONENT);
    objRelease(ruleset, CORE_COMPONENT);
    objRelease(glbl, CORE_COMPONENT);
    objRelease(prop, CORE_COMPONENT);
    objRelease(net, LM_NET_FILENAME);
ENDmodExit


static rsRetVal resetConfigVariables(uchar __attribute__((unused)) * pp, void __attribute__((unused)) * pVal) {
    free(cs.pszBindRuleset);
    cs.pszBindRuleset = NULL;
    return RS_RET_OK;
}


BEGINisCompatibleWithFeature
    CODESTARTisCompatibleWithFeature;
    if (eFeat == sFEATURENonCancelInputTermination) iRet = RS_RET_OK;
ENDisCompatibleWithFeature


BEGINqueryEtryPt
    CODESTARTqueryEtryPt;
    CODEqueryEtryPt_STD_IMOD_QUERIES;
    CODEqueryEtryPt_STD_CONF2_QUERIES;
    CODEqueryEtryPt_STD_CONF2_PREPRIVDROP_QUERIES;
    CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES;
    CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES;
    CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES;
ENDqueryEtryPt


BEGINmodInit()
    CODESTARTmodInit;
    *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
    CODEmodInit_QueryRegCFSLineHdlr pRelpEngine = NULL;
    /* request objects we use */
    CHKiRet(objUse(glbl, CORE_COMPONENT));
    CHKiRet(objUse(prop, CORE_COMPONENT));
    CHKiRet(objUse(net, LM_NET_FILENAME));
    CHKiRet(objUse(ruleset, CORE_COMPONENT));
    CHKiRet(objUse(statsobj, CORE_COMPONENT));

#ifndef HAVE_RELPSRVSETOVERSIZEMODE
    LogMsg(0, RS_RET_OK_WARN, LOG_WARNING,
           "imrelp: librelp too old, oversizemode "
           "defaults to \"abort\"");
#endif

    /* register config file handlers */
    CHKiRet(regCfSysLineHdlr2((uchar *)"inputrelpserverbindruleset", 0, eCmdHdlrGetWord, NULL, &cs.pszBindRuleset,
                              STD_LOADABLE_MODULE_ID, &bLegacyCnfModGlobalsPermitted));
    CHKiRet(omsdRegCFSLineHdlr((uchar *)"inputrelpserverrun", 0, eCmdHdlrGetWord, addInstance, NULL,
                               STD_LOADABLE_MODULE_ID));
    CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables, NULL,
                               STD_LOADABLE_MODULE_ID));
ENDmodInit
