/* 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"

#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

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;
	sbool bHadAuthFail;	/**< set on auth failure, will cause retry to disable action */
	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;
	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 },
	{ "port", eCmdHdlrGetWord, 0 },
	{ "rebindinterval", eCmdHdlrInt, 0 },
	{ "windowsize", eCmdHdlrInt, 0 },
	{ "timeout", eCmdHdlrInt, 0 },
	{ "conn.timeout", 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)
{
	wrkrInstanceData_t *pWrkrData = (wrkrInstanceData_t*) pUsr;
	LogError(0, RS_RET_RELP_AUTH_FAIL, "omrelp[%s:%s]: error '%s', object "
			" '%s' - action may not work as intended",
			pWrkrData->pData->target, pWrkrData->pData->port, 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 void
onAuthErr(void *pUsr, char *authinfo, char* errmesg, __attribute__((unused)) relpRetVal errcode)
{
	instanceData *pData = ((wrkrInstanceData_t*) pUsr)->pData;
	LogError(0, RS_RET_RELP_AUTH_FAIL, "omrelp[%s:%s]: authentication error '%s', peer "
			"is '%s' - DISABLING action", pData->target, pData->port, errmesg, authinfo);
	pData->bHadAuthFail = 1;
}

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(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;
	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->permittedPeers.nmemb = 0;
ENDcreateInstance

BEGINcreateWrkrInstance
CODESTARTcreateWrkrInstance
	pWrkrData->pRelpClt = NULL;
	iRet = doCreateRelpClient(pWrkrData->pData, &pWrkrData->pRelpClt);
	if(relpCltSetUsrPtr(pWrkrData->pRelpClt, pWrkrData) != 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]);
		}
	}
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->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->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)
				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

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")) {
			pData->target = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(actpblk.descr[i].name, "port")) {
			pData->port = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(actpblk.descr[i].name, "template")) {
			pData->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(actpblk.descr[i].name, "localclientip")) {
			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, "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.prioritystring")) {
			pData->pristring = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(actpblk.descr[i].name, "tls.cacert")) {
			pData->caCertFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
			fp = fopen((const char*)pData->caCertFile, "r");
			if(fp == NULL) {
				char errStr[1024];
				rs_strerror_r(errno, errStr, sizeof(errStr));
				LogError(0, RS_RET_NO_FILE_ACCESS,
				"error: certificate file %s couldn't be accessed: %s\n",
				pData->caCertFile, errStr);
			} else {
				fclose(fp);
			}
		} else if(!strcmp(actpblk.descr[i].name, "tls.mycert")) {
			pData->myCertFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
			fp = fopen((const char*)pData->myCertFile, "r");
			if(fp == NULL) {
				char errStr[1024];
				rs_strerror_r(errno, errStr, sizeof(errStr));
				LogError(0, RS_RET_NO_FILE_ACCESS,
				"error: certificate file %s couldn't be accessed: %s\n",
				pData->myCertFile, errStr);
			} else {
				fclose(fp);
			}
		} else if(!strcmp(actpblk.descr[i].name, "tls.myprivkey")) {
			pData->myPrivKeyFile = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
			fp = fopen((const char*)pData->myPrivKeyFile, "r");
			if(fp == NULL) {
				char errStr[1024];
				rs_strerror_r(errno, errStr, sizeof(errStr));
				LogError(0, RS_RET_NO_FILE_ACCESS,
				"error: certificate file %s couldn't be accessed: %s\n",
				pData->myPrivKeyFile, errStr);
			} else {
				fclose(fp);
			}
		} else if(!strcmp(actpblk.descr[i].name, "tls.tlscfgcmd")) {
#if defined(HAVE_RELPENGINESETTLSCFGCMD)
			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")) {
			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 =
				malloc(sizeof(uchar*) * pData->permittedPeers.nmemb));
			for(j = 0 ; j <  pData->permittedPeers.nmemb ; ++j) {
				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));

	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;
	} 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(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(pWrkrData->pData->bHadAuthFail) {
		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) != 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(pData->bHadAuthFail)
		iRet = RS_RET_DISABLE_ACTION;
	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
