/***************************************************************************
 *
 * Copyright (c) 1999 Balázs Scheidler
 * Copyright (c) 1999-2001 BalaBit IT Ltd.
 * 
 * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Inspired by nsyslog, originally written by Darren Reed.
 *
 * $Id: main.c,v 1.49.4.3 2006/03/13 23:34:13 bazsi Exp $
 *
 ***************************************************************************/

#include "syslog-ng.h"
#include "cfgfile.h"
#include "afinter.h"
#include "format.h"
#include "werror.h"
#include "queue.h"
#include "pkt_buffer.h"

#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <getopt.h>
#include <grp.h>
#include <pwd.h>

#if HAVE_ARPA_NAMESER_H
#include <arpa/nameser.h>
#endif

#include <resolv.h>

#include "main.c.x"

static char cfgfilename[128] = PATH_SYSLOG_NG_CONF;
static char pidfilename[128] = PATH_PIDFILE;
extern int linenum;

/* CLASS:
   (class
     (name reread_config)
     (super callback)
     (vars
       (backend object syslog_backend)
       (state simple int)))
*/

static void do_reread_config(struct callback *c)
{
	CAST(reread_config, self, c);

	switch (self->state) {
	case -1:
		self->backend->super.reloading = 1;
		self->state++;
		break;
	case 0:
	case 2:
		self->backend->persistent = make_persistent_config();
		CONFIG_DESTROY(self->backend->configuration, self->backend->persistent);
		self->state++;
		self->backend->oldconfig = self->backend->configuration;
		self->backend->configuration = self->backend->newconfig;
		io_callout(&self->backend->super, 0, c);
		break;
	case 1:
		if (CONFIG_INIT(self->backend->configuration, self->backend->persistent)) {
			self->backend->newconfig = NULL;
			self->backend->oldconfig = NULL;
			kill_persistent_config(self->backend->persistent);
			self->backend->persistent = NULL;
			self->backend->super.reloading = 0;
			if (!debug_flag) {
				set_internal_cfg(self->backend->configuration);
				set_error_internal();
			}
			notice("new configuration initialized\n");
		}
		else {
			/* initializing the new configuration was not 
			   successful, try to revert to the old one */
			self->state++;
			self->backend->newconfig = self->backend->oldconfig;
			self->backend->oldconfig = NULL;
			io_callout(&self->backend->super, 0, c);
		}
		break;
	case 3:
		if (CONFIG_INIT(self->backend->configuration, self->backend->persistent)) {
			kill_persistent_config(self->backend->persistent);
			self->backend->persistent = NULL;
			self->backend->super.reloading = 0;
			if (!debug_flag) {
				set_internal_cfg(self->backend->configuration);
				set_error_internal();
			}
			notice("Initializing new configuration failed, reverting to old config.\n");
		}
		else {
			/* this is a fatal error, but no stderr & no log output */
			exit(127);
		}
		break;
	}
}


/* CLASS:
   (class
     (name report_stats)
     (super callback)
     (vars
       (timeout simple int)
       (backend object syslog_backend)))
*/

static void do_report_stats(struct callback *c)
{
	CAST(report_stats, self, c);
	
	notice("STATS: dropped %i\n", pktbuf_dropped_pkts);
	pktbuf_dropped_pkts = 0;
	io_callout(&self->backend->super, self->timeout, &self->super);
}

static void do_flush_wakeup(struct callback *c)
{
	;
}

/* CLASS:
   (class
     (name syslog_backend)
     (super io_backend)
     (vars
       (newconfig object syslog_config)
       (oldconfig object syslog_config)
       (persistent object persistent_config)
       (configuration object syslog_config)))
*/

static int sighuprecvd = 0;

void sig_hup(int signo)
{
	sighuprecvd = 1;
	signal(SIGHUP, sig_hup);
}

static int sigtermrecvd = 0;

void sig_term(int signo)
{
	sigtermrecvd = 1;
	signal(SIGTERM, sig_term);
}

void sig_child(int signo)
{
	while (waitpid(-1, NULL, WNOHANG) > 0)
		;
	signal(SIGCHLD, sig_child);
}

int main_loop(struct syslog_backend *backend)
{
	static struct report_stats s = 
		{ { STATIC_HEADER, do_report_stats }, 0, NULL };
	static struct reread_config r = 
		{ { STATIC_HEADER, do_reread_config }, NULL, 0 };

	int exit_main_loop = 0, reload_config = 0;

	signal(SIGPIPE, SIG_IGN);
	signal(SIGHUP, sig_hup);
	signal(SIGTERM, sig_term);
	signal(SIGCHLD, sig_child);
	
	s.timeout = backend->configuration->stats_freq;
	s.backend = backend;
	r.backend = backend;
	if (s.timeout > 0)
		io_callout(&backend->super, s.timeout, &s.super);

	while (io_iter(&backend->super)) {
		
		if (exit_main_loop) {
			CONFIG_DESTROY(backend->configuration, 0);
			break;
		}
		if (reload_config && r.state == 0) {
		        /* reload initiated and callout started by SIGHUP finished, e.g. r.state == 0 */
			backend->newconfig = make_syslog_config(cfgfilename, &backend->super);
			if (backend->newconfig) {
				
				io_callout_flush(&backend->super);

				s.timeout = backend->configuration->stats_freq;
				if (s.timeout > 0)
					io_callout(&backend->super, s.timeout, &s.super);
				
				r.state = 0;
				io_callout(&backend->super, 0, &r.super);
			}
			else {
				backend->super.reloading = 0;
				notice("Syntax error reading config file (line %i), using old configuration\n", linenum);
			}
			reload_config = 0;
		}

		if (sigtermrecvd) {
			static struct callback wakeup = 
				{ STATIC_HEADER, do_flush_wakeup };
			
			notice("syslog-ng version %z going down\n", VERSION);
			exit_main_loop = 1;
			io_callout(&backend->super, 1, &wakeup);
		}
		if (sighuprecvd) {
			sighuprecvd = 0;
			if (!backend->super.reloading && !reload_config) {
#if HAVE_RES_INIT
				res_init();
#endif
				notice("SIGHUP received, restarting syslog-ng\n");
				if (!debug_flag) {
					set_error_stream(2, 0);
					set_internal_cfg(NULL);
				}
				reload_config = 1;
				r.backend = backend;
				r.state = -1;
				/* wait a second to flush buffers */
				io_callout(&backend->super, 1, &r.super);
			}
		}

		if (backend->configuration->time_sleep > 0)
		{
			struct timespec ts;

			ts.tv_sec = 0;
			ts.tv_nsec = backend->configuration->time_sleep * 1E6;

			nanosleep(&ts, NULL);
		}
	}
	if (!exit_main_loop) {
		notice("No input/output channels opened, exiting...\n");
		CONFIG_DESTROY(backend->configuration, 0);
	}
	return 0;
}

int go_background()
{
	pid_t pid;
	int wakeup_pipe[2];
	char buf[1];
	
	if (pipe(wakeup_pipe) < 0) {
		werror("Error in pipe(), (%z)\n", strerror(errno));
		return -1;
	}
	pid = fork();
	if (pid == 0) {
		int fd;
		
		close(wakeup_pipe[0]);
		fd = open(pidfilename, O_CREAT | O_WRONLY | O_NOCTTY | O_TRUNC, 0600);
		if (fd != -1) {
			struct ol_string *pid_s = c_format("%i\n", getpid());
			write(fd, pid_s->data, pid_s->length);
			ol_string_free(pid_s);
			close(fd);
		}
		return wakeup_pipe[1];
	}
	else if (pid == -1) {
		werror("Cannot fork(), (%z)\n", strerror(errno));
		exit(1);
	}
	close(wakeup_pipe[1]);
	if (read(wakeup_pipe[0], buf, sizeof(buf)) == 1 && buf[0] == 0) {
		exit(0);
	}
	else {
		exit(1);
	}
	exit(0);
}

void setup_fds(void)
{
	int nullfd;
	
	nullfd = open("/dev/null", O_RDWR);
	dup2(nullfd, 0);
	dup2(nullfd, 1);
	dup2(nullfd, 2);
	if (nullfd != 0 && nullfd != 1 && nullfd != 2)
		close(nullfd);
}

int resolve_user_group(char *arg, uid_t *uid, gid_t *gid)
{
	char *user, *group;
	struct passwd *pw;
	struct group *gr;
	
	*uid = 0;
	user = strtok(arg, ":.");
	group = strtok(NULL, "");
	if (user) {
		pw = getpwnam(user);
		if (pw) {
			*uid = pw->pw_uid;
		}
		else {
			*uid = atoi(user);
		}
		if (*uid == 0)
			return 0;
	}
	else {
		return 0;
	}
	if (group && gid) {
		*gid = 0;
		gr = getgrnam(group);
		if (gr) {
			*gid = gr->gr_gid;
		}
		else {
			*gid = atoi(group); 
		}
		if (*gid == 0)
			return 0;
	}
	return 1;
}

int resolve_group(char *group, gid_t *gid)
{
	struct group *gr;

	*gid = 0;
	gr = getgrnam(group);
	if (gr) {
		*gid = gr->gr_gid;
	}
	else {
		*gid = atoi(group); 
	}
	if (*gid == 0)
		return 0;
	return 1;
}

void usage(void)
{
	werror("Usage: syslog-ng [options]\n"
	       "Accept and manage system log messages\n\n"
	       "Options:\n"
	       "  -s, --syntax-only                Only read and parse config file\n"
	       "  -d, --debug                      Turn on debugging messages\n"
	       "  -v, --verbose                    Be a bit more verbose\n"
	       "  -F, --foreground                 Don't fork into background\n"
	       "  -f <fname>, --cfgfile=<fname>    Set config file name, default=" PATH_SYSLOG_NG_CONF "\n"
	       "  -V, --version                    Display version number (" PACKAGE " " VERSION ")\n"
	       "  -p <fname>, --pidfile=<fname>    Set pid file name, default=" PATH_PIDFILE "\n"
               "  -C <dir>, --chroot=<dir>         Chroot to directory\n"
               "  -u <user>, --user=<user>         Switch to user\n"
               "  -g <group>, --group=<group>      Switch to group\n"
#ifdef YYDEBUG
	       "  -y, --yydebug                    Turn on yacc debug messages\n"
#endif
		);

	exit(0);
}

#ifdef YYDEBUG
extern int yydebug;
#endif

int main(int argc, char **argv)
{
	int do_fork = 1;
	int syntax_only = 0;
	int opt;
	int wakeup_fd = -1;
	char *chroot_dir = NULL;
	uid_t uid = 0;
	gid_t gid = 0;

	NEW(syslog_backend, backend);
	struct option syslog_ng_options[] = {
		{ "cfgfile", required_argument, NULL, 'f' },
		{ "pidfile", required_argument, NULL, 'p' },
		{ "debug", no_argument, NULL, 'd' },
		{ "syntax-only", no_argument, NULL, 's' },
		{ "verbose", no_argument, NULL, 'v' },
		{ "foreground", no_argument, NULL, 'F' },
		{ "help", no_argument, NULL, 'h' },
		{ "version", no_argument, NULL, 'V' },
                { "chroot", required_argument, NULL, 'C' },
                { "user", required_argument, NULL, 'u' },
                { "group", required_argument, NULL, 'g' },
#ifdef YYDEBUG
		{ "yydebug", no_argument, NULL, 'y' },
#endif
		{ NULL, 0, NULL, 0 }
	};
#ifdef HAVE_MALLOPT
        mallopt(M_TOP_PAD, 0x10000);
#endif
	gc_idle_threshold = 100;
	gc_busy_threshold = 3000;
	
	tzset();

	while ((opt = getopt_long(argc, argv, "sFf:p:dvhyVC:u:g:", syslog_ng_options, NULL)) != -1) {
		switch (opt) {
		case 'f':
			strncpy(cfgfilename, optarg, sizeof(cfgfilename));
			break;
		case 'p':
			strncpy(pidfilename, optarg, sizeof(pidfilename));
			break;
		case 's':
			syntax_only = 1;
			break;
		case 'd':
			debug_flag++;
			break;
		case 'v':
			verbose_flag++;
			break;
		case 'F':
			do_fork = 0;
			break;
		case 'V':
			werror(PACKAGE " " VERSION "\n");
			return 0;
		case 'C':
			chroot_dir = optarg;
			break;
		case 'u':
			if (!resolve_user_group(optarg, &uid, gid ? NULL : &gid))
				usage();
			break;
		case 'g':
			if (!resolve_group(optarg, &gid))
				usage();
			break;
#ifdef YYDEBUG
		case 'y':
			yydebug = 1;
			break;
#endif
		case 'h':
		default:
			usage();
			break;
		}
	}

	init_backend(&backend->super);
	
	backend->configuration = make_syslog_config(cfgfilename, &backend->super);
	if (!backend->configuration) {
		werror("Parse error reading configuration file, exiting. (line %i)\n", linenum);
		return 1;
	}
	if (syntax_only) {
		return 0;
	}
	if (debug_flag) {
		do_fork = 0;
	}
	if (do_fork) {
		wakeup_fd = go_background();
	}
	if (!CONFIG_INIT(backend->configuration, NULL)) {
		char res = 1;
		
		werror("Error initializing configuration, exiting.\n");
		if (wakeup_fd != -1) {
			write(wakeup_fd, &res, 1);
			close(wakeup_fd);
		}
		return 2;
	}
	if (chroot_dir) {
		if (chdir(chroot_dir) < 0) {
			werror("Error chdiring, exiting.\n");
			return 3;
		}
		if (chroot(".") < 0) {
			werror("Error chrooting, exiting.\n");
			return 3;
		}
	}
	
	if (uid || gid) {
		if (gid && setgid(gid) < 0) {
			werror("Error in setgid(), exiting.\n");
			return 4;
		}
		if (setgroups(0, NULL) < 0) {
			werror("Error in setgroups(), exiting.\n");
			return 4;
		}
		if (uid && setuid(uid) < 0) {
			werror("Error setuid(), exiting.\n");
			return 4;
		}
	}

	if (!debug_flag) {
		set_internal_cfg(backend->configuration);
		set_error_internal();
	}
	else {
		set_error_stream(2, 0);
	}

	if (do_fork) {
		setup_fds();
		setsid();
	}
	if (wakeup_fd != -1) {
		char res = 0;

		write(wakeup_fd, &res, 1);
		close(wakeup_fd);
	}
	notice("syslog-ng version %z starting\n", VERSION);
	return main_loop(backend);
}


syntax highlighted by Code2HTML, v. 0.9.1