/* improg.c
 * This input plugin enables rsyslog to execute a program and
 * receive from it the message stream as standard input.
 * One message per line with a maximum size
 *
 * NOTE: read comments in module-template.h for more specifics!
 *
 * File begun on 2009-04-01 by RGerhards
 * File copied and adjust from improg.c on 2019-02-07 by Ph. Duveau
 *
 * Copyright 2009-2018 Adiscon GmbH.
 *
 * This file is contribution 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 <stdio.h>
#include <syslog.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <signal.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <pthread.h>
#include <poll.h>

/* Very strange error on solaris 11 LOG_CRON already defined here.
 * It is redefined in rsyslog.h
 * The error also appeared with module omprog but the warning is
 * accepted without generated an error
 */
#ifdef LOG_CRON
#undef LOG_CRON
#endif

#include "rsyslog.h"
#include "conf.h"
#include "syslogd-types.h"
#include "srUtils.h"
#include "template.h"
#include "module-template.h"
#include "unicode-helper.h"
#include "errmsg.h"
#include "cfsysline.h"
#include "glbl.h"
#include "prop.h"
#include "ruleset.h"
#include "ratelimit.h"
#include "stringbuf.h"

MODULE_TYPE_INPUT
MODULE_TYPE_NOKEEP
MODULE_CNFNAME("improg")

struct instanceConf_s {
	uchar *pszBinary;    /* name of external program to call */
	char **aParams;     /* optional parameters to pass to external program */
	int iParams;      /* holds the count of parameters if set */
	uchar *pszTag;
	size_t lenTag;
	int iFacility;
	int iSeverity;
	int bConfirmMessages; /* does the program provide feedback via stdout? */
	int bSignalOnClose;   /* should send SIGTERM to program before closing pipe? */
	long lCloseTimeout;   /* how long to wait for program to terminate after closing pipe (ms) */
	int bKillUnresponsive;  /* should send SIGKILL if closeTimeout is reached? */
	cstr_t *ppCStr;
	int bIsRunning;   /* is program currently running? 0-no, 1-yes */
	pid_t pid;      /* pid of currently running child process */
	int fdPipeToChild;    /* fd for sending data to the program */
	int fdPipeFromChild;   /* fd for receiving messages from the program, or -1 */
	uchar *pszBindRuleset;
	ruleset_t *pBindRuleset;  /* ruleset to bind listener to (use system default if unspecified) */
	ratelimit_t *ratelimiter;
	struct instanceConf_s *next;
	struct instanceConf_s *prev;
};

/* config variables */
struct modConfData_s {
	rsconf_t *pConf;  /* our overall config object */

};

/* internal structures
 */
DEF_OMOD_STATIC_DATA
DEFobjCurrIf(glbl)
DEFobjCurrIf(prop)
DEFobjCurrIf(ruleset)

static prop_t *pInputName = NULL;

#define NO_HUP_FORWARD -1 /* indicates that HUP should NOT be forwarded */
#define DEFAULT_CONFIRM_TIMEOUT_MS 10000
#define DEFAULT_CLOSE_TIMEOUT_MS 5000
#define RESPONSE_LINE_BUFFER_SIZE 4096
#define OUTPUT_CAPTURE_BUFFER_SIZE 4096
#define MAX_FD_TO_CLOSE 65535

static instanceConf_t *confRoot = NULL;
static fd_set rfds;
static int nfds = 0;

extern char **environ; /* POSIX environment ptr, by std not in a header... (see man 7 environ) */

static inline void
std_checkRuleset_genErrMsg(__attribute__((unused)) modConfData_t *modConf, instanceConf_t *pInst)
{
	LogError(0, NO_ERRCODE, "improg: ruleset '%s' for binary %s not found - "
			"using default ruleset instead", pInst->pszBindRuleset,
			pInst->pszBinary);
}

#include "im-helper.h" /* must be included AFTER the type definitions! */

/* tables for interfacing with the v6 config system */
/* action (instance) parameters */
static struct cnfparamdescr inppdescr[] = {
	{ "binary", eCmdHdlrString, CNFPARAM_REQUIRED },
	{ "tag", eCmdHdlrString, CNFPARAM_REQUIRED },
	{ "severity", eCmdHdlrSeverity, 0 },
	{ "facility", eCmdHdlrFacility, 0 },
	{ "ruleset", eCmdHdlrString, 0 },
	{ "confirmmessages", eCmdHdlrBinary, 0 },
	{ "signalonclose", eCmdHdlrBinary, 0 },
	{ "closetimeout", eCmdHdlrInt, 0 },
	{ "killunresponsive", eCmdHdlrBinary, 0 }
};
static struct cnfparamblk inppblk =
	{ CNFPARAMBLK_VERSION,
		sizeof(inppdescr)/sizeof(struct cnfparamdescr),
		inppdescr
	};

/* execute the external program (must be called in child context after fork).
 */
static __attribute__((noreturn)) void execBinary(const instanceConf_t *pInst, int pipeToParent, int pipeFromParent)
{
	int maxFd, fd, sigNum;
	struct sigaction sigAct;
	sigset_t sigSet;
	char errStr[1024];

	if(dup2(pipeToParent, STDOUT_FILENO) == -1) {
		goto failed;
	}

	if(pipeFromParent != -1) {
		if(dup2(pipeFromParent, STDIN_FILENO) == -1) {
			goto failed;
		}
	}

	/* close the file handles the child process doesn't need (all above STDERR).
	 * The following way is simple and portable, though not perfect.
	 * See https://stackoverflow.com/a/918469 for alternatives.
	 */
	maxFd = sysconf(_SC_OPEN_MAX);
	if(maxFd < 0 || maxFd > MAX_FD_TO_CLOSE) {
		maxFd = MAX_FD_TO_CLOSE;
	}
	# ifdef VALGRIND
	else {
		maxFd -= 10;
	}
	# endif
	for(fd = STDERR_FILENO + 1 ; fd <= maxFd ; ++fd) {
		close(fd);
	}

	/* reset signal handlers to default */
	memset(&sigAct, 0, sizeof(sigAct));
	sigemptyset(&sigAct.sa_mask);
	sigAct.sa_handler = SIG_DFL;
	for(sigNum = 1 ; sigNum < NSIG ; ++sigNum) {
		sigaction(sigNum, &sigAct, NULL);
	}

	/* we need to block SIGINT, otherwise our program is cancelled when we are
	 * stopped in debug mode.
	 */
	sigAct.sa_handler = SIG_IGN;
	sigaction(SIGINT, &sigAct, NULL);
	sigemptyset(&sigSet);
	sigprocmask(SIG_SETMASK, &sigSet, NULL);

	alarm(0);

	/* finally exec program */
	execve((char*)pInst->pszBinary, pInst->aParams, environ);

failed:
	/* an error occurred: log it and exit the child process. We use the
	 * 'syslog' system call to log the error (we cannot use LogMsg/LogError,
	 * since these functions add directly to the rsyslog input queue).
	 */
	rs_strerror_r(errno, errStr, sizeof(errStr));
	DBGPRINTF("improg: failed to execute program '%s': %s\n",
			pInst->pszBinary, errStr);
	openlog("rsyslogd", 0, LOG_SYSLOG);
	syslog(LOG_ERR, "improg: failed to execute program '%s': %s\n",
			pInst->pszBinary, errStr);
	/* let's print the error to stderr for test bench purposes */
	fprintf(stderr, "improg: failed to execute program '%s': %s\n",
			pInst->pszBinary, errStr);
	exit(1);
}

/* creates a pipe and starts program, uses pipe as stdin for program.
 * rgerhards, 2009-04-01
 */
static rsRetVal openPipe(instanceConf_t *pInst)
{
	int pipeFromChild[2] = { -1, -1 };
	int pipeToChild[2] = { -1, -1 };
	pid_t cpid;
	DEFiRet;

	/* if the 'confirmMessages' setting is enabled, open a pipe to send
		 message confirmations to the program */
	if(pInst->bConfirmMessages && pipe(pipeToChild) == -1) {
		ABORT_FINALIZE(RS_RET_ERR_CREAT_PIPE);
	}

	/* open a pipe to receive messages to the program */
	if(pipe(pipeFromChild) == -1) {
		ABORT_FINALIZE(RS_RET_ERR_CREAT_PIPE);
	}

	DBGPRINTF("improg: executing program '%s' with '%d' parameters\n",
						pInst->pszBinary, pInst->iParams);

	cpid = fork();
	if(cpid == -1) {
		ABORT_FINALIZE(RS_RET_ERR_FORK);
	}

	if(cpid == 0) {  /* we are now the child process: execute the program */
		/* close the pipe ends that the child doesn't need */
		close(pipeFromChild[0]);
		if(pipeToChild[1] != -1) {
			close(pipeToChild[1]);
		}

		execBinary(pInst, pipeFromChild[1], pipeToChild[0]);
		/* NO CODE HERE - WILL NEVER BE REACHED! */
	}

	DBGPRINTF("improg: child has pid %ld\n", (long int) cpid);

	/* close the pipe ends that the parent doesn't need */
	close(pipeFromChild[1]);
	if(pipeToChild[0] != -1) {
		close(pipeToChild[0]);
	}

	pInst->fdPipeToChild = pipeToChild[1];  /* we'll send messages confirmations to the program via this fd */
	pInst->fdPipeFromChild = pipeFromChild[0];  /* we'll receive message via this fd */

	FD_SET(pInst->fdPipeFromChild, &rfds); /* manage select read fd set */
	nfds = (nfds > pInst->fdPipeFromChild) ? nfds : pInst->fdPipeFromChild+1;

	pInst->pid = cpid;
	pInst->bIsRunning = 1;

finalize_it:
	if(iRet != RS_RET_OK) {
		if(pipeFromChild[0] != -1) {
			close(pipeFromChild[0]);
			close(pipeFromChild[1]);
		}
		if(pipeToChild[0] != -1) {
			close(pipeToChild[0]);
			close(pipeToChild[1]);
		}
	}
	RETiRet;
}

static void waitForChild(instanceConf_t *pInst)
{
	int status;
	int ret;
	long counter;

	counter = pInst->lCloseTimeout / 10;
	while ((ret = waitpid(pInst->pid, &status, WNOHANG)) == 0 && counter > 0) {
		srSleep(0, 10000);  /* 0 seconds, 10 milliseconds */
		--counter;
	}

	if (ret == 0) {  /* timeout reached */
		if (!pInst->bKillUnresponsive) {
			LogMsg(0, NO_ERRCODE, LOG_WARNING, "improg: program '%s' (pid %ld) did not terminate "
					"within timeout (%ld ms); ignoring it", pInst->pszBinary, (long int)pInst->pid,
					pInst->lCloseTimeout);
			return;
		}

		LogMsg(0, NO_ERRCODE, LOG_WARNING, "improg: program '%s' (pid %ld) did not terminate "
				"within timeout (%ld ms); killing it", pInst->pszBinary, (long int)pInst->pid,
				pInst->lCloseTimeout);
		if (kill(pInst->pid, SIGKILL) == -1) {
			LogError(errno, RS_RET_SYS_ERR, "improg: could not send SIGKILL to child process");
			return;
		}

		ret = waitpid(pInst->pid, &status, 0);
	}

	/* waitpid will fail with errno == ECHILD if the child process has already
		 been reaped by the rsyslogd main loop (see rsyslogd.c) */
	if(ret == pInst->pid) {
		glblReportChildProcessExit(runConf, pInst->pszBinary, pInst->pid, status);
	}
}

/* Send SIGTERM to child process if configured to do so, close pipe
 * and wait for child to terminate.
 */
static void terminateChild(instanceConf_t *pInst)
{
	if(pInst->bIsRunning) {

		if(pInst->fdPipeFromChild != -1) {
			close(pInst->fdPipeFromChild);
			FD_CLR(pInst->fdPipeFromChild, &rfds);
			pInst->fdPipeFromChild = -1;

		}

		if(pInst->fdPipeToChild != -1) {
			close(pInst->fdPipeToChild);
			pInst->fdPipeToChild = -1;
		}

		/* wait for the child AFTER closing the pipe, so it receives EOF */
		waitForChild(pInst);

		pInst->bIsRunning = 0;
	}
}

static rsRetVal startChild(instanceConf_t *pInst)
{
	DEFiRet;

	if (!pInst->bIsRunning)

	CHKiRet(openPipe(pInst));

finalize_it:
	if(iRet != RS_RET_OK && pInst->bIsRunning) {
		/* if initialization has failed, terminate program */
		terminateChild(pInst);
	}
	RETiRet;
}

static rsRetVal enqLine(instanceConf_t *const __restrict__ pInst)
{
	DEFiRet;
	smsg_t *pMsg;

	if(cstrLen(pInst->ppCStr) == 0) {
		/* we do not process empty lines */
		FINALIZE;
	}

	CHKiRet(msgConstruct(&pMsg));

	MsgSetMSGoffs(pMsg, 0);
	MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName()));
	MsgSetFlowControlType(pMsg, eFLOWCTL_FULL_DELAY);
	MsgSetInputName(pMsg, pInputName);
	MsgSetAPPNAME(pMsg, (const char*)pInst->pszTag);
	MsgSetTAG(pMsg, pInst->pszTag, pInst->lenTag);
	msgSetPRI(pMsg, pInst->iFacility | pInst->iSeverity);
	MsgSetRawMsg(pMsg, (const char*)rsCStrGetBufBeg(pInst->ppCStr), cstrLen(pInst->ppCStr));
	MsgSetRuleset(pMsg, pInst->pBindRuleset);

	ratelimitAddMsg(pInst->ratelimiter, NULL, pMsg);
finalize_it:
	RETiRet;
}

/* read line(s) from the external program and sent them when they are complete */
static rsRetVal readChild(instanceConf_t *const pInst){
	char c;
	int retval;

	while ((retval = read(pInst->fdPipeFromChild, &c, 1)) == 1) {
		if (c=='\n'){
			enqLine(pInst);
			/* if confirm required then send an ACK to the program */
			if (pInst->bConfirmMessages) {
				if (write(pInst->fdPipeToChild,"ACK\n",sizeof("ACK\n")-1) <= 0)
					LogMsg(0, NO_ERRCODE, LOG_WARNING, "improg: pipe to child seems to be closed.");
			}
			rsCStrTruncate(pInst->ppCStr, rsCStrLen(pInst->ppCStr));
		} else {
			cstrAppendChar(pInst->ppCStr, c);
		}
	}
	return (retval == 0) ? RS_RET_OK : RS_RET_IO_ERROR;
}

/* create input instance, set default parameters, and
 * add it to the list of instances.
 */
static rsRetVal ATTR_NONNULL(1) createInstance(instanceConf_t **const ppInst)
{
	instanceConf_t *pInst;
	DEFiRet;
	CHKmalloc(pInst = malloc(sizeof(instanceConf_t)));
	pInst->next = NULL;
	pInst->pszBindRuleset = NULL;
	pInst->pBindRuleset = NULL;
	pInst->ratelimiter = NULL;
	pInst->iSeverity = 5;
	pInst->iFacility = 128;

	pInst->pszTag = NULL;
	pInst->lenTag = 0;

	pInst->bIsRunning = 0;
	pInst->pid = -1;
	pInst->fdPipeToChild = -1;
	pInst->fdPipeFromChild = -1;

	pInst->pszBinary = NULL;
	pInst->aParams = NULL;
	pInst->iParams = 0;

	pInst->bConfirmMessages = 1;
	pInst->bSignalOnClose = 0;
	pInst->lCloseTimeout = 200;
	pInst->bKillUnresponsive = 1;

	*ppInst = pInst;
finalize_it:
	RETiRet;
}

/* This adds a new listener object to the bottom of the list, but
 * it does NOT initialize any data members except for the list
 * pointers themselves.
 */
static rsRetVal ATTR_NONNULL() lstnAdd(instanceConf_t *pInst)
{
	DEFiRet;
	CHKiRet(ratelimitNew(&pInst->ratelimiter, "improg", (char*)pInst->pszBinary));

	/* insert it at the begin of the list */
	pInst->prev = NULL;
	pInst->next = confRoot;

	if (confRoot != NULL)
		confRoot->prev = pInst;

	confRoot = pInst;

finalize_it:
	RETiRet;
}

/* delete a listener object */
static void ATTR_NONNULL(1) lstnFree(instanceConf_t *pInst)
{
	DBGPRINTF("lstnFree called for %s\n", pInst->pszBinary);
	if (pInst->ratelimiter != NULL)
		ratelimitDestruct(pInst->ratelimiter);
	if(pInst->pszBinary != NULL)
		free(pInst->pszBinary);
	if (pInst->pszTag)
		free(pInst->pszTag);
	if (pInst->pszBindRuleset != NULL)
		free(pInst->pszBindRuleset);
	if (pInst->aParams) {
		int i;
		for (i = 0;pInst->aParams[i]; i++)
			free(pInst->aParams[i]);
		free(pInst->aParams);
	}
	if (pInst->ppCStr)
		rsCStrDestruct(&pInst->ppCStr);
	free(pInst);
}

/* read  */
BEGINnewInpInst
	struct cnfparamvals *pvals;
	instanceConf_t *pInst = NULL;
	int i;
CODESTARTnewInpInst
	DBGPRINTF("newInpInst (improg)\n");

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

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

	CHKiRet(createInstance(&pInst));

	for(i = 0 ; i < inppblk.nParams ; ++i) {
		if(!pvals[i].bUsed)
			continue;
		if(!strcmp(inppblk.descr[i].name, "binary")) {
			CHKiRet(split_binary_parameters(&pInst->pszBinary, &pInst->aParams, &pInst->iParams,
				pvals[i].val.d.estr));
		} else if(!strcmp(inppblk.descr[i].name, "tag")) {
			pInst->pszTag = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
			pInst->lenTag = es_strlen(pvals[i].val.d.estr);
		} else if(!strcmp(inppblk.descr[i].name, "ruleset")) {
			pInst->pszBindRuleset = (uchar*)es_str2cstr(pvals[i].val.d.estr, NULL);
		} else if(!strcmp(inppblk.descr[i].name, "severity")) {
			pInst->iSeverity = pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "facility")) {
			pInst->iFacility = pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "confirmmessages")) {
			pInst->bConfirmMessages = pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "signalonclose")) {
			pInst->bSignalOnClose = pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "closetimeout")) {
			pInst->lCloseTimeout = pvals[i].val.d.n;
		} else if(!strcmp(inppblk.descr[i].name, "killunresponsive")) {
			pInst->bKillUnresponsive = pvals[i].val.d.n;
		} else {
			DBGPRINTF("program error, non-handled "
				"param '%s'\n", inppblk.descr[i].name);
		}
	}
	if(pInst->pszBinary == NULL) {
		LogError(0, RS_RET_FILE_NOT_SPECIFIED,
			"ulogbase is not configured - no input will be gathered");
		ABORT_FINALIZE(RS_RET_FILE_NOT_SPECIFIED);
	}

	CHKiRet(cstrConstruct(&pInst->ppCStr));

	if ((iRet = lstnAdd(pInst)) != RS_RET_OK) {
		ABORT_FINALIZE(iRet);
	}

finalize_it:
CODE_STD_FINALIZERnewInpInst
	if (pInst && iRet != RS_RET_OK)
		lstnFree(pInst);
	cnfparamvalsDestruct(pvals, &inppblk);
ENDnewInpInst

BEGINwillRun
CODESTARTwillRun
	/* we need to create the inputName property (only once during our lifetime) */
	CHKiRet(prop.Construct(&pInputName));
	CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("improg"), sizeof("improg") - 1));
	CHKiRet(prop.ConstructFinalize(pInputName));
finalize_it:
ENDwillRun

BEGINrunInput
	struct timeval tv;
	int retval;
	instanceConf_t *pInst;
CODESTARTrunInput
	FD_ZERO(&rfds);

	for(pInst = confRoot ; pInst != NULL ; pInst = pInst->next) {
		startChild(pInst);
	}

	for(pInst = confRoot ; pInst != NULL ; pInst = pInst->next) {
		if (pInst->bIsRunning && pInst->fdPipeToChild > 0){
			if (write(pInst->fdPipeToChild, "START\n", sizeof("START\n")-1)  <= 0)
				LogMsg(0, NO_ERRCODE, LOG_WARNING, "improg: pipe to child seems to be closed.");
			DBGPRINTF("Sending START to %s\n", pInst->pszBinary);
		}
	}

	/* main module loop */
	tv.tv_usec = 1000;
	while (glbl.GetGlobalInputTermState() == 0)
	{
		fd_set temp;
		memcpy(&temp, &rfds, sizeof(fd_set));
		tv.tv_sec = 0;

		/* wait for external data or 0.1 second */
		retval = select(nfds, &temp, NULL, NULL, &tv);

		/* retval is the number of fd with data to read */
		while (retval>0) {
			for (pInst = confRoot; pInst != NULL; pInst = pInst->next) {
				if (FD_ISSET(pInst->fdPipeFromChild, &temp)) {
					DBGPRINTF("read child %s\n",pInst->pszBinary);
					readChild(pInst);
					retval--;
				}
			}
		}
		tv.tv_usec = 100000;
	}
	DBGPRINTF("terminating upon request of rsyslog core\n");
ENDrunInput

/* This function is called by the framework after runInput() has been terminated. It
 * shall free any resources and prepare the module for unload.
 * CODEqueryEtryPt_STD_IMOD_QUERIES
 */
BEGINafterRun
CODESTARTafterRun
	instanceConf_t *pInst = confRoot, *nextInst;
	confRoot = NULL;

	DBGPRINTF("afterRun\n");

	while(pInst != NULL) {
		nextInst = pInst->next;

		if (pInst->bIsRunning) {
			if (pInst->bSignalOnClose) {
				kill(pInst->pid, SIGTERM);
				LogMsg(0, NO_ERRCODE, LOG_INFO, "%s SIGTERM signaled.", pInst->aParams[0]);
			}
			if (pInst->fdPipeToChild > 0){
				if (write(pInst->fdPipeToChild, "STOP\n", strlen("STOP\n")) <= 0 &&
						!pInst->bSignalOnClose)
					LogMsg(0, NO_ERRCODE, LOG_WARNING,
							"improg: pipe to child seems to be closed.");
			}
			terminateChild(pInst);
		}

		lstnFree(pInst);

		pInst = nextInst;
	}

	if(pInputName != NULL)
		prop.Destruct(&pInputName);
ENDafterRun

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

BEGINbeginCnfLoad
CODESTARTbeginCnfLoad
	pModConf->pConf = pConf;
ENDbeginCnfLoad

BEGINendCnfLoad
CODESTARTendCnfLoad
ENDendCnfLoad

BEGINcheckCnf
instanceConf_t *pInst;
CODESTARTcheckCnf
	for(pInst = confRoot ; pInst != NULL ; pInst = pInst->next) {
		std_checkRuleset(pModConf , pInst);
	}
ENDcheckCnf

BEGINactivateCnf
CODESTARTactivateCnf
ENDactivateCnf

BEGINfreeCnf
CODESTARTfreeCnf
ENDfreeCnf


BEGINmodExit
CODESTARTmodExit
	objRelease(ruleset, CORE_COMPONENT);
	objRelease(glbl, CORE_COMPONENT);
	objRelease(prop, CORE_COMPONENT);
ENDmodExit

BEGINqueryEtryPt
CODESTARTqueryEtryPt
CODEqueryEtryPt_STD_IMOD_QUERIES
CODEqueryEtryPt_STD_CONF2_QUERIES
CODEqueryEtryPt_STD_CONF2_IMOD_QUERIES
CODEqueryEtryPt_IsCompatibleWithFeature_IF_OMOD_QUERIES
ENDqueryEtryPt

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