/* omuxsock.c
 * This is the implementation of datgram unix domain socket forwarding.
 *
 * NOTE: read comments in module-template.h to understand how this file
 *       works!
 *
 * Copyright 2010-2016 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 <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <assert.h>
#include <errno.h>
#include <unistd.h>
#include "conf.h"
#include "srUtils.h"
#include "template.h"
#include "msg.h"
#include "cfsysline.h"
#include "module-template.h"
#include "glbl.h"
#include "errmsg.h"
#include "unicode-helper.h"

MODULE_TYPE_OUTPUT
MODULE_TYPE_NOKEEP
MODULE_CNFNAME("omuxsock")

/* internal structures
 */
DEF_OMOD_STATIC_DATA
DEFobjCurrIf(glbl)

#define INVLD_SOCK -1

typedef struct _instanceData {
	permittedPeers_t *pPermPeers;
	uchar *sockName;
	int sock;
	struct sockaddr_un addr;
} instanceData;


typedef struct wrkrInstanceData {
	instanceData *pData;
} wrkrInstanceData_t;

/* config data */
typedef struct configSettings_s {
	uchar *tplName; /* name of the default template to use */
	uchar *sockName; /* name of the default template to use */
} configSettings_t;
static configSettings_t cs;

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

struct modConfData_s {
	rsconf_t *pConf;	/* our overall config object */
	uchar 	*tplName;	/* default template */
};

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


static pthread_mutex_t mutDoAct = PTHREAD_MUTEX_INITIALIZER;

BEGINinitConfVars		/* (re)set config variables to default values */
CODESTARTinitConfVars
	cs.tplName = NULL;
	cs.sockName = NULL;
ENDinitConfVars


static rsRetVal doTryResume(instanceData *pData);


/* this function gets the default template. It coordinates action between
 * old-style and new-style configuration parts.
 */
static uchar*
getDfltTpl(void)
{
	if(loadModConf != NULL && loadModConf->tplName != NULL)
		return loadModConf->tplName;
	else if(cs.tplName == NULL)
		return (uchar*)"RSYSLOG_TraditionalForwardFormat";
	else
		return cs.tplName;
}

/* set the default template to be used
 * This is a module-global parameter, and as such needs special handling. It needs to
 * be coordinated with values set via the v2 config system (rsyslog v6+). What we do
 * is we do not permit this directive after the v2 config system has been used to set
 * the parameter.
 */
static rsRetVal
setLegacyDfltTpl(void __attribute__((unused)) *pVal, uchar* newVal)
{
	DEFiRet;

	if(loadModConf != NULL && loadModConf->tplName != NULL) {
		free(newVal);
		LogError(0, RS_RET_ERR, "omuxsock default template already set via module "
			"global parameter - can no longer be changed");
		ABORT_FINALIZE(RS_RET_ERR);
	}
	free(cs.tplName);
	cs.tplName = newVal;
finalize_it:
	RETiRet;
}


static rsRetVal
closeSocket(instanceData *pData)
{
	DEFiRet;
	if(pData->sock != INVLD_SOCK) {
		close(pData->sock);
		pData->sock = INVLD_SOCK;
	}
	RETiRet;
}




BEGINbeginCnfLoad
CODESTARTbeginCnfLoad
	loadModConf = pModConf;
	pModConf->pConf = pConf;
	pModConf->tplName = NULL;
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 omuxsock:\n");
		cnfparamsPrint(&modpblk, pvals);
	}

	for(i = 0 ; i < modpblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(modpblk.descr[i].name, "template")) {
			loadModConf->tplName = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
			if(cs.tplName != NULL) {
				LogError(0, RS_RET_DUP_PARAM, "omuxsock: default template "
						"was already set via legacy directive - may lead to inconsistent "
						"results.");
			}
		} else {
			dbgprintf("omuxsock: program error, non-handled "
			  "param '%s' in beginCnfLoad\n", modpblk.descr[i].name);
		}
	}
finalize_it:
	if(pvals != NULL)
		cnfparamvalsDestruct(pvals, &modpblk);
ENDsetModCnf

BEGINendCnfLoad
CODESTARTendCnfLoad
	loadModConf = NULL; /* done loading */
	/* free legacy config vars */
	free(cs.tplName);
	cs.tplName = NULL;
ENDendCnfLoad

BEGINcheckCnf
CODESTARTcheckCnf
ENDcheckCnf

BEGINactivateCnf
CODESTARTactivateCnf
	runModConf = pModConf;
ENDactivateCnf

BEGINfreeCnf
CODESTARTfreeCnf
	free(pModConf->tplName);
ENDfreeCnf

BEGINcreateInstance
CODESTARTcreateInstance
	pData->sock = INVLD_SOCK;
ENDcreateInstance

BEGINcreateWrkrInstance
CODESTARTcreateWrkrInstance
ENDcreateWrkrInstance


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


BEGINfreeInstance
CODESTARTfreeInstance
	/* final cleanup */
	closeSocket(pData);
	free(pData->sockName);
ENDfreeInstance

BEGINfreeWrkrInstance
CODESTARTfreeWrkrInstance
ENDfreeWrkrInstance


BEGINdbgPrintInstInfo
CODESTARTdbgPrintInstInfo
	DBGPRINTF("%s", pData->sockName);
ENDdbgPrintInstInfo


/* Send a message via UDP
 * rgehards, 2007-12-20
 */
static rsRetVal sendMsg(instanceData *pData, char *msg, size_t len)
{
	DEFiRet;
	unsigned lenSent = 0;

	if(pData->sock == INVLD_SOCK) {
		CHKiRet(doTryResume(pData));
	}

	if(pData->sock != INVLD_SOCK) {
		lenSent = sendto(pData->sock, msg, len, 0, (const struct sockaddr *)&pData->addr,
			sizeof(pData->addr));
		if(lenSent != len) {
			int eno = errno;
			char errStr[1024];
			DBGPRINTF("omuxsock suspending: sendto(), socket %d, error: %d = %s.\n",
				pData->sock, eno, rs_strerror_r(eno, errStr, sizeof(errStr)));
		}
	}

finalize_it:
	RETiRet;
}


/* open socket to remote system
 */
static rsRetVal
openSocket(instanceData *pData)
{
	DEFiRet;
	assert(pData->sock == INVLD_SOCK);

	if((pData->sock = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
		char errStr[1024];
		int eno = errno;
		DBGPRINTF("error %d creating AF_UNIX/SOCK_DGRAM: %s.\n",
			eno, rs_strerror_r(eno, errStr, sizeof(errStr)));
		pData->sock = INVLD_SOCK;
		ABORT_FINALIZE(RS_RET_NO_SOCKET);

	}

	/* set up server address structure */
	memset(&pData->addr, 0, sizeof(pData->addr));
	pData->addr.sun_family = AF_UNIX;
	strncpy(pData->addr.sun_path, (char*)pData->sockName, sizeof(pData->addr.sun_path));
	pData->addr.sun_path[sizeof(pData->addr.sun_path)-1] = '\0';

finalize_it:
	if(iRet != RS_RET_OK) {
		closeSocket(pData);
	}
	RETiRet;
}



/* try to resume connection if it is not ready
 */
static rsRetVal doTryResume(instanceData *pData)
{
	DEFiRet;

	DBGPRINTF("omuxsock trying to resume\n");
	closeSocket(pData);
	iRet = openSocket(pData);

	if(iRet != RS_RET_OK) {
		iRet = RS_RET_SUSPENDED;
	}

	RETiRet;
}


BEGINtryResume
CODESTARTtryResume
	iRet = doTryResume(pWrkrData->pData);
ENDtryResume

BEGINdoAction
	char *psz = NULL; /* temporary buffering */
	register unsigned l;
	int iMaxLine;
CODESTARTdoAction
	pthread_mutex_lock(&mutDoAct);
	CHKiRet(doTryResume(pWrkrData->pData));

	iMaxLine = glbl.GetMaxLine(runModConf->pConf);

	DBGPRINTF(" omuxsock:%s\n", pWrkrData->pData->sockName);

	psz = (char*) ppString[0];
	l = strlen((char*) psz);
	if((int) l > iMaxLine)
		l = iMaxLine;

	CHKiRet(sendMsg(pWrkrData->pData, psz, l));

finalize_it:
	pthread_mutex_unlock(&mutDoAct);
ENDdoAction


BEGINparseSelectorAct
CODESTARTparseSelectorAct
CODE_STD_STRING_REQUESTparseSelectorAct(1)

	/* first check if this config line is actually for us */
	if(strncmp((char*) p, ":omuxsock:", sizeof(":omuxsock:") - 1)) {
		ABORT_FINALIZE(RS_RET_CONFLINE_UNPROCESSED);
	}

	/* ok, if we reach this point, we have something for us */
	p += sizeof(":omuxsock:") - 1; /* eat indicator sequence  (-1 because of '\0'!) */
	CHKiRet(createInstance(&pData));

	/* check if a non-standard template is to be applied */
	if(*(p-1) == ';')
		--p;
	CHKiRet(cflineParseTemplateName(&p, *ppOMSR, 0, 0, getDfltTpl()));

	if(cs.sockName == NULL) {
		LogError(0, RS_RET_NO_SOCK_CONFIGURED, "No output socket configured for omuxsock\n");
		ABORT_FINALIZE(RS_RET_NO_SOCK_CONFIGURED);
	}

	pData->sockName = cs.sockName;
	cs.sockName = NULL; /* pData is now owner and will fee it */

CODE_STD_FINALIZERparseSelectorAct
ENDparseSelectorAct


/* a common function to free our configuration variables - used both on exit
 * and on $ResetConfig processing. -- rgerhards, 2008-05-16
 */
static void
freeConfigVars(void)
{
	free(cs.tplName);
	cs.tplName = NULL;
	free(cs.sockName);
	cs.sockName = NULL;
}


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

	freeConfigVars();
ENDmodExit


BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_OMOD_QUERIES
CODEqueryEtryPt_STD_OMOD8_QUERIES
CODEqueryEtryPt_STD_CONF2_QUERIES
CODEqueryEtryPt_STD_CONF2_setModCnf_QUERIES
ENDqueryEtryPt


/* Reset config variables for this module to default values.
 * rgerhards, 2008-03-28
 */
static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal)
{
	freeConfigVars();
	return RS_RET_OK;
}


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

	CHKiRet(regCfSysLineHdlr((uchar *)"omuxsockdefaulttemplate", 0, eCmdHdlrGetWord, setLegacyDfltTpl,
		NULL, NULL));
	CHKiRet(regCfSysLineHdlr((uchar *)"omuxsocksocket", 0, eCmdHdlrGetWord, NULL, &cs.sockName, NULL));
	CHKiRet(omsdRegCFSLineHdlr((uchar *)"resetconfigvariables", 1, eCmdHdlrCustomHandler, resetConfigVariables,
	NULL, STD_LOADABLE_MODULE_ID));
ENDmodInit

/* vim:set ai:
 */
