/** 
 * rfc3195d.c
 * This is an RFC 3195 listener. All data received is forwarded to
 * local UNIX domain socket, where it can be picked up by a
 * syslog daemon (like rsyslogd ;)).
 *
 * \author  Rainer Gerhards <rgerhards@adiscon.com>
 *
 * Copyright 2003-2005 Rainer Gerhards and Adiscon GmbH.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
 */
#include "config.h"

#include <stdio.h>
#ifndef FEATURE_RFC3195
/* this is a trick: if RFC3195 is not to be supported, we just do an
 * error message.
 */
int main()
{
	fprintf(stderr, "error: not compiled with FEATURE_RFC3195 - terminating.\n");
	return(1);
}
#else
#include <unistd.h>
#include <signal.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include "rsyslog.h"
#include "liblogging.h"
#include "srAPI.h"
#include "syslogmessage.h"

/* configurable params! */
static char* pPathLogname = "/dev/log3195";
static char *PidFile;
static int NoFork = 0;
static int Debug = 0;
static int listenPort = 601;

/* we use a global API object below, because this listener is
 * not very complex. As such, this hack should not harm anything.
 * rgerhards, 2005-10-12
 */
static srAPIObj* pAPI;

static int	LogFile = -1;		/* fd for log */
static int	connected;		/* have done connect */
static struct sockaddr SyslogAddr;	/* AF_UNIX address of local logger */

/* small usage info */
static int usage()
{
	/* The following usage line is what we intend to have - it
	 * is commented out as a reminder. The one below is what we
	 * currently actually do...
	 fprintf(stderr, "usage: rfc3195d [-dv] [-i pidfile] [-n] [-p path]\n");
	 */
	fprintf(stderr, "usage: rfc3195d [-dv] [-r port] [-p path]\n");
	exit(1);
}

/* CLOSELOG -- close the system log
 */
static void closelog(void)
{
	close(LogFile);
	LogFile = -1;
	connected = 0;
}

/* OPENLOG -- open system log
 */
static void openlog()
{
	if (LogFile == -1) {
		SyslogAddr.sa_family = AF_UNIX;
		strncpy(SyslogAddr.sa_data, pPathLogname,
		    sizeof(SyslogAddr.sa_data));
		LogFile = socket(AF_UNIX, SOCK_DGRAM, 0);
		if(LogFile < 0) {
			char errStr[1024];
			printf("error opening '%s': %s\n", 
			       pPathLogname, strerror_r(errno, errStr, sizeof(errStr)));
		}
	}
	if (LogFile != -1 && !connected &&
	    connect(LogFile, &SyslogAddr, sizeof(SyslogAddr.sa_family)+
			strlen(SyslogAddr.sa_data)) != -1)
		connected = 1;
	else {
		char errStr[1024];
		printf("error connecting '%s': %s\n", 
		       pPathLogname, strerror_r(errno, errStr, sizeof(errStr)));
	}
}


/* This method is called when a message has been fully received.
 * It passes the received message to the specified unix domain
 * socket. Please note that this callback is synchronous, thus
 * liblogging will be on hold until it returns. This is important
 * to note because in an error case we might stay in this code
 * for an extended amount of time. So far, we think this is the
 * best solution, but real-world experience might tell us a
 * different truth ;)
 * rgerhards 2005-10-12
 */
void OnReceive(srAPIObj* pAPI, srSLMGObj* pSLMG)
{
	unsigned char *pszRawMsg;
	int iRetries; /* number of retries connecting to log socket */
	int iSleep;
	int iWriteOffset;
	ssize_t nToWrite;
	ssize_t nWritten;

	srSLMGGetRawMSG(pSLMG, &pszRawMsg);

	/* we need to loop writing the message. At least in
	 * theory, a single write might not send all data to the
	 * syslogd. So we need to continue until everything is written.
	 * Also, we need to check if there are any socket erros, in 
	 * which case we reconect. We will re-try indefinitely, if this
	 * is not acceptable, you need to change the code.
	 * rgerhards 2005-10-12
	 */
	iRetries = 0;
	nToWrite = strlen(pszRawMsg);
	iWriteOffset = 0;
	while(nToWrite != 0) {
		if(LogFile < 0 || !connected)
			openlog();
		if(LogFile < 0 || !connected) {
			/* still not connected, retry */
			if(iRetries > 0) {
				iSleep = (iRetries < 30) ? iRetries : 30;
				/* we sleep a little to prevent a thight loop */
				if(Debug)
					printf("multiple retries connecting to log socket"
					       " - doing sleep(%d)\n", iSleep);
				sleep(iSleep);
			}
			++iRetries;
		} else {
			nWritten = write(LogFile, pszRawMsg, strlen(pszRawMsg));
			if(nWritten < 0) {
				/* error, recover! */
				char errStr[1024];
				printf("error writing to domain socket: %s\r\n", strerror_r(errno, errStr, sizeof(errStr)));
				closelog();
			} else {
				/* prepare for (potential) next write */
				nToWrite -= nWritten;
				iWriteOffset += nWritten;
			}
		}
	}

	if(Debug) {
		static int largest = 0;
		int sz = strlen(pszRawMsg);
		if(sz > largest)
			largest = sz;
		printf("Msg(%d/%d):%s\n\n", largest, sz, pszRawMsg);
	}
}


/* As we are single-threaded in this example, we need
 * one way to shut down the listener running on this
 * single thread. We use SIG_INT to do so - it effectively
 * provides a short-lived second thread ;-)
 */
void doShutdown(int i)
{
	printf("Shutting down rfc3195d. Be patient, this can take up to 30 seconds...\n");
	srAPIShutdownListener(pAPI);
}


/* on the the real program ;) */
int main(int argc, char* argv[])
{
	srRetVal iRet;
	int ch;
	struct sigaction sigAct;

	while ((ch = getopt(argc, argv, "di:np:r:v")) != EOF)
		switch((char)ch) {
		case 'd':		/* debug */
			Debug = 1;
			break;
		case 'i':		/* pid file name */
			PidFile = optarg;
			break;
		case 'n':		/* don't fork */
			NoFork = 1;
			break;
		case 'p':		/* path to regular log socket */
			pPathLogname = optarg;
			break;
		case 'r':		/* listen port */
			listenPort = atoi(optarg);
			if(listenPort < 1 || listenPort > 65535) {
				printf("Error: invalid listen port '%s', using 601 instead\n",
					optarg);
				listenPort = 601;
			}
			break;
		case 'v':
			printf("rfc3195d %s.%s (using liblogging version %d.%d.%d).\n",
			       VERSION, PATCHLEVEL,
			       LIBLOGGING_VERSION_MAJOR, LIBLOGGING_VERSION_MINOR,
			       LIBLOGGING_VERSION_SUBMINOR);
			printf("See http://www.rsyslog.com for more information.\n");
			exit(0);
		case '?':
		default:
			usage();
		}
	if ((argc -= optind))
		usage();

	memset(&sigAct, 0, sizeof(sigAct));
	sigemptyset(&sigAct.sa_mask);
	sigAct.sa_handler = doShutdown;
	sigaction(SIGUSR1, &sigAct, NULL);
	sigaction(SIGTERM, &sigAct, NULL);

	if(!Debug)
	{
		sigAct.sa_handler = SIG_IGN;
		sigaction(SIGINT, &sigAct, NULL);
	}

	if((pAPI = srAPIInitLib()) == NULL)
	{
		printf("Error initializing liblogging - aborting!\n");
		exit(1);
	}

	if((iRet = srAPISetOption(pAPI, srOPTION_BEEP_LISTENPORT, listenPort)) != SR_RET_OK)
	{
		printf("Error %d setting listen port - aborting\n", iRet);
		exit(100);
	}

	if((iRet = srAPISetupListener(pAPI, OnReceive)) != SR_RET_OK)
	{
		printf("Error %d setting up listener - aborting\n", iRet);
		exit(101);
	}

	/* now move the listener to running state. Control will only
	 * return after SIGUSR1.
	 */
	if((iRet = srAPIRunListener(pAPI)) != SR_RET_OK)
	{
		printf("Error %d running the listener - aborting\n", iRet);
		exit(102);
	}

	/** control will reach this point after shutdown */

	srAPIExitLib(pAPI);
	return 0;
}
#endif /* #ifndef FEATURE_RFC3195 - main wrapper */

/*
 * vi:set ai:
 */


syntax highlighted by Code2HTML, v. 0.9.1