/*
** Copyright 1998 - 2006 S. Varshavchik.
** See COPYING for distribution information.
*/

#if	HAVE_CONFIG_H
#include	"config.h"
#endif
#include	<string.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<sys/types.h>
#include	<sys/wait.h>
#include	<signal.h>
#include	<errno.h>
#include	<fcntl.h>
#include	<pwd.h>
#include	<grp.h>
#if	HAVE_UNISTD_H
#include	<unistd.h>
#endif
#if	HAVE_SYSLOG_H
#include	<syslog.h>
#else
#undef	TESTMODE
#define	TESTMODE	1
#endif
#include	"liblock/config.h"
#include	"liblock/liblock.h"
#include	"numlib/numlib.h"


static int do_logger(const char *name, int facility, FILE *f)
{
char	buf[512];
char	*p;
int	c;

#if	TESTMODE

#else
	openlog(name, 0

#ifdef	LOG_NDELAY
			| LOG_NDELAY
#else
			| LOG_NOWAIT
#endif

			, facility);
#endif
	if (chdir("/") < 0)
	{
		perror("chdir(\"/\")");
		exit(1);
	}

	while (fgets(buf, sizeof(buf), f))
	{
		if ((p=strchr(buf, '\n')) != 0)	*p=0;
		else
			while ((c=getchar()) != EOF && c != '\n')
				;

#if	TESTMODE
		fprintf(stderr, "%s: %s\n", name, buf);
#else
		c=LOG_INFO;
		if (strncmp(buf, "ERR:", 4) == 0)
		{
			c=LOG_ERR;
			p=buf+4;
		}
		else if (strncmp(buf, "WARN:", 5) == 0)
		{
			c=LOG_WARNING;
			p=buf+5;
		}
		else if (strncmp(buf, "ALERT:", 6) == 0)
		{
			c=LOG_ALERT;
			p=buf+6;
		}
		else if (strncmp(buf, "CRIT:", 5) == 0)
		{
			c=LOG_CRIT;
			p=buf+5;
		}
		else if (strncmp(buf, "DEBUG:", 6) == 0)
		{
			c=LOG_DEBUG;
			p=buf+6;
		}
		else if (strncmp(buf, "INFO:", 5) == 0)
			p=buf+5;
		else	p=buf;

		while (*p == ' ')
			++p;

		syslog(c, "%s", p);
#endif
	}
	return (0);
}

static const char *namearg=0;
static const char *pidarg=0;
static char *lockfilename=0;

struct lognames
{
	char *name;
	int value;
};

static struct lognames facilitynames[] =
{
#ifdef LOG_AUTH
	{ "auth",	LOG_AUTH },
#endif
#ifdef LOG_AUTHPRIV
	{ "authpriv",	LOG_AUTHPRIV },
#endif
#ifdef LOG_CONSOLE
	{ "console",	LOG_CONSOLE },
#endif
#ifdef LOG_CRON
	{ "cron",	LOG_CRON },
#endif
#ifdef LOG_DAEMON
	{ "daemon",	LOG_DAEMON },
#endif
#ifdef LOG_FTP
	{ "ftp",	LOG_FTP },
#endif
#ifdef LOG_KERN
	{ "kern",	LOG_KERN },
#endif
#ifdef LOG_LPR
	{ "lpr",	LOG_LPR },
#endif
#ifdef LOG_MAIL
	{ "mail",	LOG_MAIL },
#endif
#ifdef LOG_AUTHPRIV
	{ "news",	LOG_NEWS },
#endif
#ifdef LOG_SECURITY
	{ "security",	LOG_SECURITY },
#endif
#ifdef LOG_USER
	{ "user",	LOG_USER },
#endif
#ifdef LOG_UUCP
	{ "uucp",	LOG_UUCP },
#endif
#ifdef LOG_LOCAL0
	{ "local0",	LOG_LOCAL0 },
#endif
#ifdef LOG_LOCAL1
	{ "local1",	LOG_LOCAL1 },
#endif
#ifdef LOG_LOCAL2
	{ "local2",	LOG_LOCAL2 },
#endif
#ifdef LOG_LOCAL3
	{ "local3",	LOG_LOCAL3 },
#endif
#ifdef LOG_LOCAL4
	{ "local4",	LOG_LOCAL4 },
#endif
#ifdef LOG_LOCAL5
	{ "local5",	LOG_LOCAL5 },
#endif
#ifdef LOG_LOCAL6
	{ "local6",	LOG_LOCAL6 },
#endif
#ifdef LOG_LOCAL7
	{ "local7",	LOG_LOCAL7 },
#endif
	{ 0,		0 }
};

static int hup_restart = 0;
static int respawn = 0;
static int child_pid = -1;

static void sighup(int n)
{
	if (child_pid > 0)
	{
		/* The child may respond to HUP by dying. If so it will
		   close its stderr and do_logger will terminate; at that
		   point we need to restart it */
		hup_restart = 1;
		kill (child_pid, SIGHUP);
	}
	signal(SIGHUP, sighup);
}

static void sigalrm(int n)
{
	if (child_pid > 0) kill(child_pid, SIGKILL);
}

static void sigterm(int n)
{
	if (child_pid > 0)
	{
		hup_restart = 0;
		respawn = 0;
		kill(child_pid, SIGTERM);
		signal(SIGALRM, sigalrm); /* kill after 8 secs */
		alarm(8);
	}
	else exit(0);
}

static void checkfd(int actual, int exp)
{
	if (actual == exp) return;
	fprintf(stderr, "Bad fd, got %d, expected %d\n", actual, exp);
	if (actual < 0) perror("error");
	exit(1);
}

static int isid(const char *p)
{
	while (*p)
	{
		if (*p < '0' || *p > '9')	return (0);
		++p;
	}
	return (1);
}

static void setuidgid(const char *userarg,
		      const char *grouparg)
{
	if (grouparg)
	{
	gid_t gid = 0;
	struct group *gr;

		if (isid(grouparg))
			gid=atoi(grouparg);
		else if ((gr=getgrnam(grouparg)) == 0)
		{
			fprintf(stderr, "Group not found: %s\n", grouparg);
			exit(1);
		}
		else	gid=gr->gr_gid;

		libmail_changegroup(gid);
	}

	if (userarg)
	{
	uid_t	uid;

		if (isid(userarg))
		{
			uid=atoi(userarg);
			libmail_changeuidgid(uid, getgid());
		}
		else
		{
		gid_t	g=getgid(), *gp=0;

			if (grouparg)	gp= &g;
			libmail_changeusername(userarg, gp);
		}
	}
}


static void startchild(char **argv, const char *userarg, const char *grouparg)
{
pid_t p;
int pipefd[2];
int tmp;

	signal(SIGTERM, sigterm);
	signal(SIGHUP, sighup);

	/* Make sure the pipefds are at least 3 and 4. If we have an open
	   stderr then keep it around for debugging purposes. */
	close(0);
	checkfd(open("/dev/null", O_RDWR), 0);
	close(1);
	checkfd(dup(0), 1);
	if ((tmp = dup(0)) > 2) close(tmp);

	if (pipe(pipefd) < 0)
	{
		perror("pipe");
		exit(1);
	}
	p = fork();
	if (p < 0)
	{
		perror("fork");
		exit(1);
	}
	if (p == 0)
	{

		close(pipefd[0]);
		close(2);
		checkfd(dup(pipefd[1]), 2);
		close(pipefd[1]);
		setuidgid(userarg, grouparg);
		execvp(argv[0], argv);
		perror("exec");
		exit(1);
	}

	// We can close stderr now

	close(2);
	checkfd(dup(0), 2);


	close(pipefd[1]);
	close(0);
	checkfd(dup(pipefd[0]), 0);
	close(pipefd[0]);
	child_pid = p;
}
/*
 * Note that we now support several modes of operation:
 *
 * (1) standalone logger, just collect messages on stdin and feed to syslog
 *   courierlogger foo
 *   courierlogger -name=foo
 *
 * (2) run a child process, collect its stderr messages and feed to syslog
 *   courierlogger [-name=foo] foo arg1 arg2
 *
 * (3) start a detached daemon with a child process
 *   courierlogger [-name=foo] -pid=/var/run/foo.pid -start foo arg1 arg2
 *
 * (4) stop or restart a detached daemon
 *   courierlogger -pid=/var/run/foo.pid -stop      (or receive a SIGTERM)
 *   courierlogger -pid=/var/run/foo.pid -restart   (or receive a SIGHUP)
 */

int main(int argc, char **argv)
{
int facility = LOG_DEST;
int daemon = 0;
int lockfd = -1;
const char *userarg = 0;
const char *grouparg = 0;
int droproot=0;

	if (argc == 2 && argv[1][0] != '-')
	{
		/* backwards-compatibility mode */
		close(1);
		close(2);
		checkfd(open("/dev/null", O_WRONLY), 1);
		checkfd(open("/dev/null", O_WRONLY), 2);
		do_logger(argv[1], facility, stdin);
		exit(0);
	}

	if (argc <= 1)
	{
		fprintf(stderr,

			"Usage: courierlogger [-name=name] [-pid=pidfile] [-facility=type]\n"
			"       [-start|-stop|-restart] [cmd [args...]]\n"
		);
		exit(1);
	}

	argv++, argc--;
	while (argc > 0 && argv[0][0]=='-')
	{
		if (strncmp(argv[0],"-pid=",5) == 0 && argv[0][5])
		{
			pidarg=&argv[0][5];
			lockfilename=malloc(strlen(pidarg)+sizeof(".lock"));
			if (!lockfilename)
			{
				perror("malloc");
				exit(1);
			}
			strcat(strcpy(lockfilename, pidarg), ".lock");
		}
		else
		if (strncmp(argv[0],"-name=",6) == 0 && argv[0][6])
			namearg=&argv[0][6];
		else if (strncmp(argv[0],"-user=",6) == 0)
			userarg=argv[0]+6;
		else if (strncmp(argv[0],"-group=",7) == 0)
			grouparg=argv[0]+7;
		else if (strcmp(argv[0], "-droproot") == 0)
			droproot=1;
		else
		if (strncmp(argv[0],"-facility=",10) == 0)
		{
		struct lognames *p = facilitynames;

			while (p->name && strcmp(p->name, &argv[0][10]))
				p++;
			if (p->name == 0)
			{
				fprintf(stderr, "Unknown facility name '%s'\n",
					&argv[0][10]);
				exit(1);
			}
			facility = p->value;
		}
		else
		if (strcmp(argv[0],"-start") == 0)
			daemon = 1;
		else
		if (strcmp(argv[0],"-stop") == 0)
			daemon = 2;
		else
		if (strcmp(argv[0],"-restart") == 0)
			daemon = 3;
		else
		if (strcmp(argv[0],"-respawn") == 0)
			respawn = 1;
		else
		{
			fprintf(stderr, "Unknown option '%s'\n", argv[0]);
			exit(1);
		}
		argv++, argc--;
	}

	if (daemon && !pidarg)
	{
		fprintf(stderr, "-pid argument required\n");
		exit(1);
	}

	if (!daemon && pidarg)
		daemon = 1; /* -start implied */

	if (!namearg && daemon != 2 && daemon != 3)
	{
		/* choose a default name based on the program we're running */
		if (argc <= 0 || !argv[0] || !argv[0][0])
		{
			fprintf(stderr, "-name option required for standalone logger\n");
			exit(1);
		}
		namearg = strrchr(argv[0],'/');
		namearg = namearg ? namearg+1 : argv[0];
	}

	switch (daemon)
	{
	case 1: /* start */
		if (argc <= 0 || !argv[0] || !argv[0][0])
		{
			fprintf(stderr, "-start must be followed by a command to execute\n");
			exit(1);
		}
		lockfd=ll_daemon_start(lockfilename);
		if (lockfd < 0)
		{
			perror("ll_daemon_start");
			exit(1);
		}
		startchild(argv, droproot ? userarg:NULL,
			   droproot ? grouparg:NULL);
		ll_daemon_started(pidarg, lockfd);
		break;
	case 2: /* stop */
		exit(ll_daemon_stop(lockfilename, pidarg));
	case 3: /* restart */
		exit(ll_daemon_restart(lockfilename, pidarg));
	default: /* run in foreground, with or without a child process */

		if (argc > 0)
			startchild(argv, droproot ? userarg:NULL,
				   droproot ? grouparg:NULL);
	}

	setuidgid(userarg, grouparg);

	while (1)
	{
	int waitstat;
	pid_t p2;
	FILE *f = fdopen(0, "r");

		do_logger(namearg, facility, f);
		fclose(f);
		if (child_pid < 0) break;
		while ((p2=wait(&waitstat)) != child_pid &&
			(p2 != -1 || errno != ECHILD))
			;
		if (hup_restart)
			hup_restart = 0;
		else if (respawn)
			sleep (5);
		else
			break;
		startchild(argv, NULL, NULL);
	}
	exit(0);
}
