/* omrelp.c
 *
 * This is the implementation of the RELP output module.
 *
 * Note that when multiple action workers are activated, we currently
 * also create multiple actions. This may be the source of some mild
 * message loss (!) if the worker instance is shut down while the
 * connection to the remote system is in retry state.
 * TODO: think if we should implement a mode where we do NOT
 *       support multiple action worker instances. This would be
 *       slower, but not have this loss opportunity. But it should
 *       definitely be optional and by default off due to the
 *       performance implications (and given the fact that message
 *       loss is pretty unlikely in usual cases).
 *
 *
 * 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 "rsyslog.h"
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <ctype.h>
#include <librelp.h>
#include "conf.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "cfsysline.h"
#include "module-template.h"
#include "glbl.h"
#include "errmsg.h"
#include "debug.h"
#include "parserif.h"
#include "unicode-helper.h"
#include "atomic.h"

#ifndef RELP_DFLT_PT
    #define RELP_DFLT_PT "514"
#endif

MODULE_TYPE_OUTPUT;
MODULE_TYPE_NOKEEP;
MODULE_CNFNAME("omrelp")

/* internal structures
 */
DEF_OMOD_STATIC_DATA;
DEFobjCurrIf(glbl)

#define DFLT_ENABLE_TLS 0
#define DFLT_ENABLE_TLSZIP 0
#define DFLT_TLS_PERMFAIL_DISABLES_ACTION 1

    static relpEngine_t *pRelpEngine; /* our relp engine */

typedef struct _instanceData {
    uchar *target;
    uchar *port;
    int sizeWindow; /**< the RELP window size - 0=use default */
    unsigned timeout;
    int connTimeout;
    unsigned rebindInterval;
    sbool bEnableTLS;
    sbool bEnableTLSZip;
    int bHadAuthFail; /**< set on TLS auth failure */
    DEF_ATOMIC_HELPER_MUT(mutHadAuthFail);
    sbool bTlsPermFailDisablesAction;
    uchar *pristring; /* GnuTLS priority string (NULL if not to be provided) */
    uchar *authmode;
    uchar *caCertFile;
    uchar *myCertFile;
    uchar *myPrivKeyFile;
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
    uchar *tlscfgcmd;
#endif
    uchar *tplName;
    uchar *localClientIP;
    sbool bKeepAlive; /* support keep-alive packets */
    int iKeepAliveProbes;
    int iKeepAliveTime;
    int iKeepAliveIntvl;
    struct {
        int nmemb;
        uchar **name;
    } permittedPeers;
} instanceData;

typedef struct wrkrInstanceData {
    instanceData *pData;
    int bInitialConnect; /* is this the initial connection request of our module? (0-no, 1-yes) */
    int bIsConnected; /* currently connected to server? 0 - no, 1 - yes */
    int bIsSuspended; /* currently suspended (than no more error messages) */
    relpClt_t *pRelpClt; /* relp client for this instance */
    unsigned nSent; /* number msgs sent - for rebind support */
} wrkrInstanceData_t;

typedef struct configSettings_s {
    EMPTY_STRUCT
} configSettings_t;
static configSettings_t __attribute__((unused)) cs;

static rsRetVal doCreateRelpClient(instanceData *pData, relpClt_t **pRelpClt);

struct modConfData_s {
    rsconf_t *pConf; /* our overall config object */
    const char *tlslib;
};

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 exec process */

/* tables for interfacing with the v6 config system */
/* module-global parameters */
static struct cnfparamdescr modpdescr[] = {{"tls.tlslib", eCmdHdlrString, 0}};
static struct cnfparamblk modpblk = {CNFPARAMBLK_VERSION, sizeof(modpdescr) / sizeof(struct cnfparamdescr), modpdescr};
/* action (instance) parameters */
static struct cnfparamdescr actpdescr[] = {{"target", eCmdHdlrGetWord, 1},
                                           {"tls", eCmdHdlrBinary, 0},
                                           {"tls.compression", eCmdHdlrBinary, 0},
                                           {"tls.prioritystring", eCmdHdlrString, 0},
                                           {"tls.cacert", eCmdHdlrString, 0},
                                           {"tls.mycert", eCmdHdlrString, 0},
                                           {"tls.myprivkey", eCmdHdlrString, 0},
                                           {"tls.authmode", eCmdHdlrString, 0},
                                           {"tls.tlscfgcmd", eCmdHdlrString, 0},
                                           {"tls.permittedpeer", eCmdHdlrArray, 0},
                                           {"tls.permanentfailuredisablesaction", eCmdHdlrBinary, 0},
                                           {"port", eCmdHdlrGetWord, 0},
                                           {"rebindinterval", eCmdHdlrInt, 0},
                                           {"windowsize", eCmdHdlrInt, 0},
                                           {"timeout", eCmdHdlrInt, 0},
                                           {"conn.timeout", eCmdHdlrInt, 0},
                                           {"keepalive", eCmdHdlrBinary, 0},
                                           {"keepalive.probes", eCmdHdlrInt, 0},
                                           {"keepalive.time", eCmdHdlrInt, 0},
                                           {"keepalive.interval", eCmdHdlrInt, 0},
                                           {"localclientip", eCmdHdlrGetWord, 0},
                                           {"template", eCmdHdlrGetWord, 0}};
static struct cnfparamblk actpblk = {CNFPARAMBLK_VERSION, sizeof(actpdescr) / sizeof(struct cnfparamdescr), actpdescr};

BEGINinitConfVars /* (re)set config variables to default values */
    CODESTARTinitConfVars;
ENDinitConfVars

/* We may change the implementation to try to lookup the port
 * if it is unspecified. So far, we use 514 as default (what probably
 * is not a really bright idea, but kept for backward compatibility).
 */

PRAGMA_DIAGNOSTIC_PUSH
PRAGMA_IGNORE_Wformat_nonliteral static void __attribute__((format(printf, 1, 2))) omrelp_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("omrelp.c", "%s", pszWriteBuf);
}
PRAGMA_DIAGNOSTIC_POP


static uchar *getRelpPt(instanceData *pData) {
    assert(pData != NULL);
    if (pData->port == NULL)
        return ((uchar *)RELP_DFLT_PT);
    else
        return (pData->port);
}

static void onErr(void *pUsr, char *objinfo, char *errmesg, __attribute__((unused)) relpRetVal errcode) {
    instanceData *pData = (instanceData *)pUsr;
    if (pData == NULL) {
        LogError(0, RS_RET_RELP_ERR, "omrelp: error '%s', object '%s' - action may not work as intended", errmesg,
                 objinfo);
        return;
    }
    LogError(0, RS_RET_RELP_ERR,
             "omrelp[%s:%s]: error '%s', object "
             " '%s' - action may not work as intended",
             pData->target, getRelpPt(pData), errmesg, objinfo);
}

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

static const char *authFailActionText(const instanceData *const pData) {
    return (pData != NULL && !pData->bTlsPermFailDisablesAction) ? "suspending action" : "DISABLING action";
}

static rsRetVal authFailActionRet(const instanceData *const pData) {
    return (pData != NULL && !pData->bTlsPermFailDisablesAction) ? RS_RET_SUSPENDED : RS_RET_DISABLE_ACTION;
}

static void onAuthErr(void *pUsr, char *authinfo, char *errmesg, __attribute__((unused)) relpRetVal errcode) {
    instanceData *pData = (instanceData *)pUsr;
    if (pData == NULL) {
        LogError(0, RS_RET_RELP_AUTH_FAIL, "omrelp: authentication error '%s', peer is '%s' - DISABLING action",
                 errmesg, authinfo);
        return;
    }
    LogError(0, RS_RET_RELP_AUTH_FAIL,
             "omrelp[%s:%s]: authentication error '%s', peer "
             "is '%s' - %s",
             pData->target, getRelpPt(pData), errmesg, authinfo, authFailActionText(pData));
    ATOMIC_STORE_32BIT(&pData->bHadAuthFail, &pData->mutHadAuthFail, 1);
}

static int relpRetIsAuthErr(const relpRetVal relpRet) {
    switch (relpRet) {
        case RELP_RET_AUTH_ERR_FP:
        case RELP_RET_AUTH_ERR_NAME:
        case RELP_RET_AUTH_NO_CERT:
        case RELP_RET_AUTH_CERT_INVL:
            return 1;
        default:
            return 0;
    }
}

static rsRetVal doCreateRelpClient(instanceData *pData, relpClt_t **pRelpClt) {
    int i;
    DEFiRet;

    if (relpEngineCltConstruct(pRelpEngine, pRelpClt) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
    if (relpCltSetTimeout(*pRelpClt, pData->timeout) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
    if (relpCltSetConnTimeout(*pRelpClt, pData->connTimeout) != RELP_RET_OK) {
        ABORT_FINALIZE(RS_RET_RELP_ERR);
    }
    if (relpCltSetWindowSize(*pRelpClt, pData->sizeWindow) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
#if defined(HAVE_RELPCLTSETKEEPALIVE)
    if (relpCltSetKeepAlive(*pRelpClt, pData->bKeepAlive, pData->iKeepAliveIntvl, pData->iKeepAliveProbes,
                            pData->iKeepAliveTime) != RELP_RET_OK)
        ABORT_FINALIZE(RS_RET_RELP_ERR);
#endif
    if (pData->bEnableTLS) {
        if (relpCltEnableTLS(*pRelpClt) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
        if (pData->bEnableTLSZip) {
            if (relpCltEnableTLSZip(*pRelpClt) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
        }
        if (relpCltSetGnuTLSPriString(*pRelpClt, (char *)pData->pristring) != RELP_RET_OK)
            ABORT_FINALIZE(RS_RET_RELP_ERR);


        if (relpCltSetAuthMode(*pRelpClt, (char *)pData->authmode) != RELP_RET_OK) {
            LogError(0, RS_RET_RELP_ERR, "omrelp: invalid auth mode '%s'\n", pData->authmode);
            ABORT_FINALIZE(RS_RET_RELP_ERR);
        }

        if (relpCltSetCACert(*pRelpClt, (char *)pData->caCertFile) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
        if (relpCltSetOwnCert(*pRelpClt, (char *)pData->myCertFile) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
        if (relpCltSetPrivKey(*pRelpClt, (char *)pData->myPrivKeyFile) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
        if (pData->tlscfgcmd != NULL) {
            if (relpCltSetTlsConfigCmd(*pRelpClt, (char *)pData->tlscfgcmd) != RELP_RET_OK)
                ABORT_FINALIZE(RS_RET_RELP_ERR);
        }
#endif
        for (i = 0; i < pData->permittedPeers.nmemb; ++i) {
            relpCltAddPermittedPeer(*pRelpClt, (char *)pData->permittedPeers.name[i]);
        }
    }
    if (pData->localClientIP != NULL) {
        if (relpCltSetClientIP(*pRelpClt, pData->localClientIP) != RELP_RET_OK) ABORT_FINALIZE(RS_RET_RELP_ERR);
    }
finalize_it:

    RETiRet;
}

BEGINendCnfLoad
    CODESTARTendCnfLoad;
    loadModConf = NULL;
    runModConf = pModConf;
ENDendCnfLoad

BEGINcheckCnf
    CODESTARTcheckCnf;
ENDcheckCnf

BEGINactivateCnf
    CODESTARTactivateCnf;
ENDactivateCnf

BEGINfreeCnf
    CODESTARTfreeCnf;
    free((void *)pModConf->tlslib);
ENDfreeCnf

BEGINcreateInstance
    CODESTARTcreateInstance;
    pData->sizeWindow = 0;
    pData->timeout = 90;
    pData->connTimeout = 10;
    pData->rebindInterval = 0;
    pData->bEnableTLS = DFLT_ENABLE_TLS;
    pData->bEnableTLSZip = DFLT_ENABLE_TLSZIP;
    pData->bHadAuthFail = 0;
    INIT_ATOMIC_HELPER_MUT(pData->mutHadAuthFail);
    pData->bTlsPermFailDisablesAction = DFLT_TLS_PERMFAIL_DISABLES_ACTION;
    pData->pristring = NULL;
    pData->authmode = NULL;
    pData->localClientIP = NULL;
    pData->caCertFile = NULL;
    pData->myCertFile = NULL;
    pData->myPrivKeyFile = NULL;
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
    pData->tlscfgcmd = NULL;
#endif
    pData->bKeepAlive = 0;
    pData->iKeepAliveProbes = 0;
    pData->iKeepAliveTime = 0;
    pData->iKeepAliveIntvl = 0;
    pData->permittedPeers.nmemb = 0;
ENDcreateInstance

BEGINcreateWrkrInstance
    CODESTARTcreateWrkrInstance;
    pWrkrData->pRelpClt = NULL;
    iRet = doCreateRelpClient(pWrkrData->pData, &pWrkrData->pRelpClt);
    if (relpCltSetUsrPtr(pWrkrData->pRelpClt, pWrkrData->pData) != RELP_RET_OK)
        LogError(0, RS_RET_NO_ERRCODE, "omrelp: error when creating relp client");
    pWrkrData->bInitialConnect = 1;
    pWrkrData->nSent = 0;
ENDcreateWrkrInstance

BEGINfreeInstance
    int i;
    CODESTARTfreeInstance;
    free(pData->target);
    free(pData->port);
    free(pData->tplName);
    free(pData->pristring);
    free(pData->authmode);
    free(pData->localClientIP);
    free(pData->caCertFile);
    free(pData->myCertFile);
    free(pData->myPrivKeyFile);
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
    free(pData->tlscfgcmd);
#endif
    if (pData->permittedPeers.name != NULL) {
        for (i = 0; i < pData->permittedPeers.nmemb; ++i) {
            free(pData->permittedPeers.name[i]);
        }
    }
    DESTROY_ATOMIC_HELPER_MUT(pData->mutHadAuthFail);
ENDfreeInstance

BEGINfreeWrkrInstance
    CODESTARTfreeWrkrInstance;
    if (pWrkrData->pRelpClt != NULL) relpEngineCltDestruct(pRelpEngine, &pWrkrData->pRelpClt);
ENDfreeWrkrInstance

static void setInstParamDefaults(instanceData *pData) {
    pData->target = NULL;
    pData->port = NULL;
    pData->tplName = NULL;
    pData->timeout = 90;
    pData->connTimeout = 10;
    pData->sizeWindow = 0;
    pData->rebindInterval = 0;
    pData->bEnableTLS = DFLT_ENABLE_TLS;
    pData->bEnableTLSZip = DFLT_ENABLE_TLSZIP;
    pData->bTlsPermFailDisablesAction = DFLT_TLS_PERMFAIL_DISABLES_ACTION;
    pData->pristring = NULL;
    pData->authmode = NULL;
    if (glbl.GetSourceIPofLocalClient() == NULL)
        pData->localClientIP = NULL;
    else
        pData->localClientIP = (uchar *)strdup((char *)glbl.GetSourceIPofLocalClient());
    pData->caCertFile = NULL;
    pData->myCertFile = NULL;
    pData->myPrivKeyFile = NULL;
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
    pData->tlscfgcmd = NULL;
#endif
    pData->bKeepAlive = 0;
    pData->iKeepAliveProbes = 0;
    pData->iKeepAliveTime = 0;
    pData->iKeepAliveIntvl = 0;
    pData->permittedPeers.name = NULL;
    pData->permittedPeers.nmemb = 0;
}

BEGINbeginCnfLoad
    CODESTARTbeginCnfLoad;
    loadModConf = pModConf;
    pModConf->pConf = pConf;
    pModConf->tlslib = NULL;
    /* create our relp engine */
    CHKiRet(relpEngineConstruct(&pRelpEngine));
    CHKiRet(relpEngineSetDbgprint(pRelpEngine, (void (*)(char *, ...))omrelp_dbgprintf));
    CHKiRet(relpEngineSetOnAuthErr(pRelpEngine, onAuthErr));
    CHKiRet(relpEngineSetOnGenericErr(pRelpEngine, onGenericErr));
    CHKiRet(relpEngineSetOnErr(pRelpEngine, onErr));
    CHKiRet(relpEngineSetEnableCmd(pRelpEngine, (uchar *)"syslog", eRelpCmdState_Required));
finalize_it:
ENDbeginCnfLoad

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

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

    for (i = 0; i < modpblk.nParams; ++i) {
        if (!pvals[i].bUsed) {
            continue;
        }
        if (!strcmp(modpblk.descr[i].name, "tls.tlslib")) {
#if defined(HAVE_RELPENGINESETTLSLIBBYNAME)
            CHKmalloc(loadModConf->tlslib = es_str2cstr(pvals[i].val.d.estr, NULL));
            if (relpEngineSetTLSLibByName(pRelpEngine, loadModConf->tlslib) != RELP_RET_OK) {
                LogMsg(0, RS_RET_CONF_PARAM_INVLD, LOG_WARNING,
                       "omrelp: tlslib '%s' not accepted as valid by librelp - using default", loadModConf->tlslib);
            }
#else
            LogError(0, RS_RET_NOT_IMPLEMENTED,
                     "omrelp warning: parameter tls.tlslib ignored - librelp does not support "
                     "this API call. Using whatever librelp was compiled with.");
#endif
        } else {
            dbgprintf(
                "imfile: program error, non-handled "
                "param '%s' in beginCnfLoad\n",
                modpblk.descr[i].name);
        }
    }
finalize_it:
    if (pvals != NULL) cnfparamvalsDestruct(pvals, &modpblk);
ENDsetModCnf

/** Warn in secure warn mode when an omrelp action is configured without TLS. */
static void warnIfPlainRelpActionConfigured(const instanceData *const pData) {
    if (!pData->bEnableTLS) {
        glblWarnIfInsecureDefault(loadModConf->pConf,
                                  "omrelp action uses tls=\"off\" (plain RELP without TLS); "
                                  "see https://docs.rsyslog.com/doc/faq/tls_mode0_disables_tls.html");
    } else if (pData->authmode != NULL && strcasecmp((const char *)pData->authmode, "anon") == 0) {
        glblWarnIfInsecureDefault(
            loadModConf->pConf,
            "omrelp 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)");
    }
}

BEGINnewActInst
    struct cnfparamvals *pvals;
    int i, j;
    FILE *fp;
    relpClt_t *pRelpClt = NULL;
    CODESTARTnewActInst;
    if ((pvals = nvlstGetParams(lst, &actpblk, NULL)) == NULL) {
        ABORT_FINALIZE(RS_RET_MISSING_CNFPARAMS);
    }

    CHKiRet(createInstance(&pData));
    setInstParamDefaults(pData);

    for (i = 0; i < actpblk.nParams; ++i) {
        if (!pvals[i].bUsed) continue;
        if (!strcmp(actpblk.descr[i].name, "target")) {
            CHKmalloc(pData->target = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(actpblk.descr[i].name, "port")) {
            CHKmalloc(pData->port = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(actpblk.descr[i].name, "template")) {
            CHKmalloc(pData->tplName = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(actpblk.descr[i].name, "localclientip")) {
            CHKmalloc(pData->localClientIP = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(actpblk.descr[i].name, "timeout")) {
            pData->timeout = (unsigned)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "conn.timeout")) {
            pData->connTimeout = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "keepalive")) {
            pData->bKeepAlive = (sbool)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "keepalive.probes")) {
            pData->iKeepAliveProbes = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "keepalive.time")) {
            pData->iKeepAliveTime = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "keepalive.interval")) {
            pData->iKeepAliveIntvl = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "rebindinterval")) {
            pData->rebindInterval = (unsigned)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "windowsize")) {
            pData->sizeWindow = (int)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "tls")) {
            pData->bEnableTLS = (unsigned)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "tls.compression")) {
            pData->bEnableTLSZip = (unsigned)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "tls.permanentfailuredisablesaction")) {
            pData->bTlsPermFailDisablesAction = (sbool)pvals[i].val.d.n;
        } else if (!strcmp(actpblk.descr[i].name, "tls.prioritystring")) {
            CHKmalloc(pData->pristring = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(actpblk.descr[i].name, "tls.cacert")) {
            CHKmalloc(pData->caCertFile = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
            fp = fopen((const char *)pData->caCertFile, "r");
            if (fp == NULL) {
                LogError(errno, RS_RET_NO_FILE_ACCESS, "error: certificate file %s couldn't be accessed",
                         pData->caCertFile);
            } else {
                fclose(fp);
            }
        } else if (!strcmp(actpblk.descr[i].name, "tls.mycert")) {
            CHKmalloc(pData->myCertFile = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
            fp = fopen((const char *)pData->myCertFile, "r");
            if (fp == NULL) {
                LogError(errno, RS_RET_NO_FILE_ACCESS, "error: certificate file %s couldn't be accessed",
                         pData->myCertFile);
            } else {
                fclose(fp);
            }
        } else if (!strcmp(actpblk.descr[i].name, "tls.myprivkey")) {
            CHKmalloc(pData->myPrivKeyFile = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
            fp = fopen((const char *)pData->myPrivKeyFile, "r");
            if (fp == NULL) {
                LogError(errno, RS_RET_NO_FILE_ACCESS, "error: certificate file %s couldn't be accessed",
                         pData->myPrivKeyFile);
            } else {
                fclose(fp);
            }
        } else if (!strcmp(actpblk.descr[i].name, "tls.tlscfgcmd")) {
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
            CHKmalloc(pData->tlscfgcmd = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
#else
            LogError(0, RS_RET_NOT_IMPLEMENTED,
                     "omrelp: 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(actpblk.descr[i].name, "tls.authmode")) {
            CHKmalloc(pData->authmode = (uchar *)es_str2cstr(pvals[i].val.d.estr, NULL));
        } else if (!strcmp(actpblk.descr[i].name, "tls.permittedpeer")) {
            pData->permittedPeers.nmemb = pvals[i].val.d.ar->nmemb;
            CHKmalloc(pData->permittedPeers.name = calloc(pData->permittedPeers.nmemb, sizeof(uchar *)));
            for (j = 0; j < pData->permittedPeers.nmemb; ++j) {
                CHKmalloc(pData->permittedPeers.name[j] = (uchar *)es_str2cstr(pvals[i].val.d.ar->arr[j], NULL));
            }
        } else {
            dbgprintf(
                "omrelp: program error, non-handled "
                "param '%s'\n",
                actpblk.descr[i].name);
        }
    }

    CODE_STD_STRING_REQUESTnewActInst(1);

    CHKiRet(OMSRsetEntry(*ppOMSR, 0,
                         (uchar *)strdup((pData->tplName == NULL) ? "RSYSLOG_ForwardFormat" : (char *)pData->tplName),
                         OMSR_NO_RQD_TPL_OPTS));

    warnIfPlainRelpActionConfigured(pData);

    iRet = doCreateRelpClient(pData, &pRelpClt);
    if (pRelpClt != NULL) relpEngineCltDestruct(pRelpEngine, &pRelpClt);

    CODE_STD_FINALIZERnewActInst;
    if (pvals != NULL) cnfparamvalsDestruct(pvals, &actpblk);
ENDnewActInst

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

BEGINSetShutdownImmdtPtr
    CODESTARTSetShutdownImmdtPtr;
    relpEngineSetShutdownImmdtPtr(pRelpEngine, pPtr);
    DBGPRINTF("omrelp: shutdownImmediate ptr now is %p\n", pPtr);
ENDSetShutdownImmdtPtr


BEGINdbgPrintInstInfo
    CODESTARTdbgPrintInstInfo;
    dbgprintf("RELP/%s", pData->target);
ENDdbgPrintInstInfo


/* try to connect to server
 * rgerhards, 2008-03-21
 */
static rsRetVal ATTR_NONNULL() doConnect(wrkrInstanceData_t *const pWrkrData) {
    DEFiRet;

    if (pWrkrData->bInitialConnect) {
        iRet = relpCltConnect(pWrkrData->pRelpClt, glbl.GetDefPFFamily(runModConf->pConf), getRelpPt(pWrkrData->pData),
                              pWrkrData->pData->target);
        if (iRet == RELP_RET_OK) pWrkrData->bInitialConnect = 0;
    } else {
        iRet = relpCltReconnect(pWrkrData->pRelpClt);
    }

    if (iRet == RELP_RET_OK) {
        pWrkrData->bIsConnected = 1;
        pWrkrData->bIsSuspended = 0;
        ATOMIC_STORE_32BIT(&pWrkrData->pData->bHadAuthFail, &pWrkrData->pData->mutHadAuthFail, 0);
    } else if (iRet == RELP_RET_ERR_NO_TLS) {
        LogError(0, iRet,
                 "omrelp: Could not connect, librelp does NOT "
                 "support TLS (most probably GnuTLS lib "
                 "is too old)!");
        FINALIZE;
    } else if (iRet == RELP_RET_ERR_NO_TLS_AUTH) {
        LogError(0, iRet,
                 "omrelp: 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.");
        FINALIZE;
    } else {
        if (relpRetIsAuthErr(iRet)) {
            if (ATOMIC_CAS(&pWrkrData->pData->bHadAuthFail, 0, 1, &pWrkrData->pData->mutHadAuthFail)) {
                LogError(0, RS_RET_RELP_AUTH_FAIL, "omrelp[%s:%s]: TLS authentication failed, librelp error %d - %s",
                         pWrkrData->pData->target, getRelpPt(pWrkrData->pData), iRet,
                         authFailActionText(pWrkrData->pData));
            }
            pWrkrData->bIsConnected = 0;
            pWrkrData->bIsSuspended = 1;
            ABORT_FINALIZE(authFailActionRet(pWrkrData->pData));
        }
        if (pWrkrData->bIsSuspended == 0) {
            LogError(0, RS_RET_RELP_ERR,
                     "omrelp: could not connect to "
                     "remote server, librelp error %d",
                     iRet);
        }
        pWrkrData->bIsConnected = 0;
        pWrkrData->bIsSuspended = 1;
        iRet = RS_RET_SUSPENDED;
    }

finalize_it:
    RETiRet;
}


BEGINtryResume
    CODESTARTtryResume;
    if (ATOMIC_LOAD_32BIT(&pWrkrData->pData->bHadAuthFail, &pWrkrData->pData->mutHadAuthFail) &&
        pWrkrData->pData->bTlsPermFailDisablesAction) {
        ABORT_FINALIZE(RS_RET_DISABLE_ACTION);
    }
    iRet = doConnect(pWrkrData);
finalize_it:
ENDtryResume

static rsRetVal doRebind(wrkrInstanceData_t *pWrkrData) {
    DEFiRet;
    DBGPRINTF("omrelp: destructing relp client due to rebindInterval\n");
    CHKiRet(relpEngineCltDestruct(pRelpEngine, &pWrkrData->pRelpClt));
    pWrkrData->bIsConnected = 0;
    CHKiRet(doCreateRelpClient(pWrkrData->pData, &pWrkrData->pRelpClt));
    if (relpCltSetUsrPtr(pWrkrData->pRelpClt, pWrkrData->pData) != RELP_RET_OK)
        LogError(0, RS_RET_NO_ERRCODE, "omrelp: error when creating relp client");
    pWrkrData->bInitialConnect = 1;
    pWrkrData->nSent = 0;
finalize_it:
    RETiRet;
}

BEGINbeginTransaction
    CODESTARTbeginTransaction;
    DBGPRINTF("omrelp: beginTransaction\n");
    if (!pWrkrData->bIsConnected) {
        CHKiRet(doConnect(pWrkrData));
    }
    relpCltHintBurstBegin(pWrkrData->pRelpClt);
finalize_it:
ENDbeginTransaction

BEGINdoAction
    uchar *pMsg; /* temporary buffering */
    size_t lenMsg;
    relpRetVal ret;
    instanceData *pData;
    CODESTARTdoAction;
    pData = pWrkrData->pData;
    dbgprintf(" %s:%s/RELP\n", pData->target, getRelpPt(pData));

    if (!pWrkrData->bIsConnected) {
        CHKiRet(doConnect(pWrkrData));
    }

    pMsg = ppString[0];
    lenMsg = strlen((char *)pMsg); /* TODO: don't we get this? */

    /* we need to truncate oversize msgs - no way around that... */
    if ((int)lenMsg > glbl.GetMaxLine(runModConf->pConf)) lenMsg = glbl.GetMaxLine(runModConf->pConf);

    /* forward */
    ret = relpCltSendSyslog(pWrkrData->pRelpClt, (uchar *)pMsg, lenMsg);
    if (ret != RELP_RET_OK) {
        LogError(0, RS_RET_RELP_ERR,
                 "librelp error %d%s forwarding "
                 "to server %s:%s - suspending\n",
                 ret, (ret == RELP_RET_SESSION_BROKEN) ? "[connection broken]" : "", pData->target, getRelpPt(pData));
        ABORT_FINALIZE(RS_RET_SUSPENDED);
    }

    if (pData->rebindInterval != 0 && (++pWrkrData->nSent >= pData->rebindInterval)) {
        doRebind(pWrkrData);
    }
finalize_it:
    if (ATOMIC_LOAD_32BIT(&pData->bHadAuthFail, &pData->mutHadAuthFail)) iRet = authFailActionRet(pData);
    if (iRet == RS_RET_OK) {
        /* we mimic non-commit, as otherwise our endTransaction handler
         * will not get called. While this is not 100% correct, the worst
         * that can happen is some message duplication, something that
         * rsyslog generally accepts and prefers over message loss.
         */
        iRet = RS_RET_PREVIOUS_COMMITTED;
    } else if (iRet == RS_RET_SUSPENDED) {
        pWrkrData->bIsSuspended = 1;
    }
ENDdoAction


BEGINendTransaction
    CODESTARTendTransaction;
    DBGPRINTF("omrelp: endTransaction, connected %d\n", pWrkrData->bIsConnected);
    if (pWrkrData->bIsConnected) {
        relpCltHintBurstEnd(pWrkrData->pRelpClt);
    }
ENDendTransaction

BEGINparseSelectorAct
    uchar *q;
    int i;
    int bErr;
    CODESTARTparseSelectorAct;
    CODE_STD_STRING_REQUESTparseSelectorAct(1) if (!strncmp((char *)p, ":omrelp:", sizeof(":omrelp:") - 1)) {
        p += sizeof(":omrelp:") - 1; /* eat indicator sequence (-1 because of '\0'!) */
    }
    else {
        ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
    }

    /* ok, if we reach this point, we have something for us */
    if ((iRet = createInstance(&pData)) != RS_RET_OK) FINALIZE;

    /* extract the host first (we do a trick - we replace the ';' or ':' with a '\0')
     * now skip to port and then template name. rgerhards 2005-07-06
     */
    if (*p == '[') { /* everything is hostname upto ']' */
        ++p; /* skip '[' */
        for (q = p; *p && *p != ']'; ++p) /* JUST SKIP */
            ;
        if (*p == ']') {
            *p = '\0'; /* trick to obtain hostname (later)! */
            ++p; /* eat it */
        }
    } else { /* traditional view of hostname */
        for (q = p; *p && *p != ';' && *p != ':' && *p != '#'; ++p) /* JUST SKIP */
            ;
    }

    pData->port = NULL;
    if (*p == ':') { /* process port */
        uchar *tmp;

        *p = '\0'; /* trick to obtain hostname (later)! */
        tmp = ++p;
        for (i = 0; *p && isdigit((int)*p); ++p, ++i) /* SKIP AND COUNT */
            ;
        pData->port = malloc(i + 1);
        if (pData->port == NULL) {
            LogError(0, NO_ERRCODE,
                     "Could not get memory to store relp port, "
                     "using default port, results may not be what you intend\n");
            /* we leave f_forw.port set to NULL, this is then handled by getRelpPt() */
        } else {
            memcpy(pData->port, tmp, i);
            *(pData->port + i) = '\0';
        }
    }

    /* now skip to template */
    bErr = 0;
    while (*p && *p != ';') {
        if (*p && *p != ';' && !isspace((int)*p)) {
            if (bErr == 0) { /* only 1 error msg! */
                bErr = 1;
                errno = 0;
                LogError(0, NO_ERRCODE,
                         "invalid selector line (port), probably not doing "
                         "what was intended");
            }
        }
        ++p;
    }

    if (*p == ';') {
        *p = '\0'; /* trick to obtain hostname (later)! */
        CHKmalloc(pData->target = ustrdup(q));
        *p = ';';
    } else {
        CHKmalloc(pData->target = ustrdup(q));
    }

    /* process template */
    CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, OMSR_NO_RQD_TPL_OPTS, (uchar *)"RSYSLOG_ForwardFormat"));

    CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


BEGINmodExit
    CODESTARTmodExit;
    relpEngineDestruct(&pRelpEngine);

    /* release what we no longer need */
    objRelease(glbl, CORE_COMPONENT);
ENDmodExit


BEGINqueryEtryPt
    CODESTARTqueryEtryPt;
    CODEqueryEtryPt_STD_OMOD_QUERIES;
    CODEqueryEtryPt_STD_OMOD8_QUERIES;
    CODEqueryEtryPt_STD_CONF2_QUERIES;
    CODEqueryEtryPt_STD_CONF2_CNFNAME_QUERIES;
    CODEqueryEtryPt_STD_CONF2_OMOD_QUERIES;
    CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES;
    CODEqueryEtryPt_TXIF_OMOD_QUERIES CODEqueryEtryPt_SetShutdownImmdtPtr
ENDqueryEtryPt


BEGINmodInit()
    CODESTARTmodInit;
    INITLegCnfVars;
    *ipIFVersProvided = CURR_MOD_IF_VERSION; /* we only support the current interface specification */
    CODEmodInit_QueryRegCFSLineHdlr
        /* tell which objects we need */
        CHKiRet(objUse(glbl, CORE_COMPONENT));
ENDmodInit
