#!/usr/bin/perl

# Copyright (c) 2004, Branko F. Gracnar
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# + Redistributions of source code must retain the above copyright notice,
#   this list of conditions and the following disclaimer.
# 
# + Redistributions in binary form must reproduce the above copyright notice,
#   this list of conditions and the following disclaimer in the documentation
#   and/or other materials provided with the distribution.
#
# + Neither the name of the Branko F. Gracnar nor the names of its contributors
#    may be used to endorse or promote products derived from this software without
#    specific prior written permission.
#
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

BEGIN {
	use strict;
	use File::Basename;
	use Cwd;
	use vars qw($YAA_DEFAULT_CONFIG_FILE $YAA_MODULE_DIR);

	# determine platform
	use constant YAA_SLASH => ((($^O !~ /win/i) ? "/" : "\\"));
	use constant DEBUG => ((defined $ENV{YAA_DEBUG}) ? 1 : 0);

	# we need to determinate this script's directory
	my $ok = 0;
	my $module_dir = undef;

	# @INC
	foreach (@INC) {
		my $f = $_ . YAA_SLASH . "Yaa.pm";
		if (-f $f && -r $f) {
			$ok = 1;
			$module_dir = $_;
			last;
		}
	}

	unless ($ok) {	
		# ./
		if (substr($0, 0, 2) eq "." . YAA_SLASH) {
			$module_dir = getcwd() . YAA_SLASH . ".." . YAA_SLASH . "lib";
		}
		# /path/to
		elsif (substr($0, 0, 1) eq YAA_SLASH || substr($0, 0, 3) =~ m/^(\w{1}):/) {
			$module_dir = dirname($0) . YAA_SLASH . ".." . YAA_SLASH . "lib";
		}
		# $PATH
		else {
			my $path_delimiter = (YAA_SLASH eq '/') ? ':' : ';';
			foreach my $d (split(/$path_delimiter/, $ENV{PATH})) {
				if (-e $d . YAA_SLASH . $0 && -f $d . YAA_SLASH . $0 && -x $d . YAA_SLASH . $0) {
					$module_dir = $d . YAA_SLASH . ".." . YAA_SLASH . "lib";
					last;
				}
			}
		}

		# No module path defined yet? Weird. Let's try one more thing
		if (! defined $module_dir) {
			my $str = getcwd() . YAA_SLASH . ".." . YAA_SLASH . "lib" . YAA_SLASH . "Yaa.pm";
			$module_dir = getcwd() . YAA_SLASH . ".." . YAA_SLASH . "lib" if (-f $str && -r $str && ! -z $str);
		}
	}

	# any success?
	unless (defined $module_dir) {
		print STDERR "Unable to determinate Yaa module directory.\n";
		exit 1;	
	}

	$YAA_DEFAULT_CONFIG_FILE = $module_dir . YAA_SLASH . ".." . YAA_SLASH . "conf" . YAA_SLASH . "yaa.conf";
	$YAA_MODULE_DIR = $module_dir;

	# push dir into @INC
	unshift(@INC, $module_dir);
}

# standard used modules
use strict;
use IO::File; 
use Getopt::Long;
use File::Basename;
use Term::ANSIColor qw(:constants);

# constants
use constant YAA_SCRIPT_VERSION => '0.3.1';

# Yaa! modules
use Yaa;
use Yaa::LookupMap;
use Yaa::Autoresponse;
use Yaa::Log;

use vars qw(
	$chroot $user $group
	$daemon $daemon_background $daemon_lockfile $daemon_pidfile $daemon_min_servers $daemon_max_servers $daemon_min_spare_servers $daemon_max_spare_servers $daemon_protocol $daemon_listen_host $daemon_listen_port $daemon_tcpserver_loglevel
	$db_dir $duration_interval
	$mail_sending_method $sendmail_path $smtp_server $smtp_auth $smtp_username $smtp_password
	$logging $log_syslog $log_syslog_facility $log_syslog_priority $log_file $log_file_filename $log_stderr $log_time_format
	$lookup_maps $lookup_map_query_order
	@extra_modules
);

print STDERR basename(__FILE__), ", line ", __LINE__, ": startup version ", YAA_SCRIPT_VERSION, " with debugging enabled.\n" if (DEBUG);

######################################################
#                  CONFIG PARAMS                     #
######################################################

my $config_file = $YAA_DEFAULT_CONFIG_FILE;			# default config file
my $message_file = undef;							# file to read message from in nondaemon mode

$chroot = undef;									# chroot before processing?
$user = undef;										# change uid before processing?
$group = undef;										# change gid before processing?

# daemon settings
$daemon = 0;										# operate as daemon?
$daemon_background = 1;								# should we fork into background when operating as daemon?
$daemon_lockfile = "/var/lock/yaa.lock";			# daemon lock file
$daemon_pidfile = "/var/run/yaa.pid";				# daemon pid file
$daemon_min_servers = 1,							# minimum number of running child processes
$daemon_max_servers = 2,							# maximum number of running child processes
$daemon_min_spare_servers = 1;						# minimum number of spare child processes
$daemon_max_spare_servers = 1;						# maximum number of spare child processes
$daemon_protocol = 'SMTP';							# default daemon communication protocol when running as daemon
$daemon_tcpserver_loglevel = 0;
$daemon_listen_host = "localhost";					# daemon listen ip address
$daemon_listen_port = 40000;

# Yaa object general settings
$db_dir = "/tmp";									# directory to store autoresponse timeframe database
$duration_interval = 7200;							# autoresponse timeframe timeout

# Yaa::Autoresponder settings
$mail_sending_method = "smtp";
$sendmail_path = my_which('sendmail');
$smtp_server = "localhost";
$smtp_auth = 0;
$smtp_username = undef;
$smtp_password = undef;

# logging subsystem settings
$logging = 1;
$log_syslog = 1;
$log_syslog_facility = "mail";
$log_syslog_priority = "info";
$log_file = 0;
$log_file_filename = "/tmp/yaa.log";
$log_stderr = 0;
$log_time_format = "[%a, %b %e %T %Y]: ";

$lookup_maps = {};									# lookup map configuration hash
$lookup_map_query_order = {};						# lookup map query order configuration hash

@extra_modules = qw();								# Load additional modules

######################################################
#              GLOBAL RUNTIME VARS                   #
######################################################

my $error = undef;
my $yaa = undef;									# Yaa object

# try to load config file
parse_config_file($config_file);

######################################################
#                  SUBRUTINES                        #
######################################################

sub show_doc {
	my $perldoc = my_which("perldoc");
	unless (defined $perldoc) {
		print STDERR "Unable to find perldoc in \$PATH.\n";
		exit 1;
	}
	$ENV{PERL5LIB} = $YAA_MODULE_DIR;
	return (! system($perldoc, $_[0]));
}

sub show_map_doc {
	return show_doc("Yaa::LookupMap::Driver::" . $_[0]);
}

sub daemon_stop {
	my $pid = daemon_get_pid();
	if (! defined $pid) {
		return 0;
	}
	elsif (kill(15, $pid) < 1) {
		$error = "Unable to stop process $pid: $!\n";
		return 0;
	}
	return 1;
}

sub daemon_action {
	my ($action) = @_;
	
	unless ($daemon) {
		print STDERR "Daemon mode is not set in configuration file or command line options.\n";
		exit 1;
	}
	
	if ($action eq 'stop') {
		unless (daemon_stop()) {
			print STDERR "Unable to stop Yaa!: ", $error, "\n";
			exit 1;
		}
		exit 0;
	}
	elsif ($action eq 'start') {
		if (daemon_status()) {
			print STDERR "Yaa is already running: ", lc($error), "\n";
			exit 1;
		}
		# just let the script continue
	}
	elsif ($action eq 'status') {
		my $result = daemon_status();
		my $fh = (($result) ? *STDOUT : *STDERR);
		print $fh $error, "\n";
		exit (! $result);
	}
	else {
		print STDERR "$action: unknown daemon action.\n";
		exit 1;
	}
	return 1;
}

sub daemon_get_pid {
	my $f = IO::File->new($daemon_pidfile, "r");
	
	unless ($f) {
		$error = "Cannot open pidfile " . $daemon_pidfile . " for reading: " .  $!;
		return undef;
	}

	chomp(my $line = $f->getline);
	$f = undef;
	return $line;
}

sub daemon_status {
	my $pid = daemon_get_pid();
	if (! defined $pid) {
		$error = "Unable to obtain daemon status: $error";
		return 0;
	}
	if (kill(0, $pid)) {
		$error = "Running as pid $pid.";
		return 1;
	} else {
		$error = "Process is not running (pid file found).";
		return 0;
	}
}

sub parse_config_file {
	my ($file) = @_;
	unless (my $result = do $file) {
		if ($@) {
			$error = "Syntax error in file " . $file . ": " . $@;
		}
		elsif (! defined $result) {
			$error = "Couldn't open file " . $file . ": " . $!;
		}
		elsif (! $result) {
			$error = "Couldn't run config file " . $file;
		}
		# return error		
		return 0;
	}
	return 1;
}

sub chroot_check {
	my ($dir) = @_;

	if (! -e $dir) {
		$error = "Chroot sandbox " . $dir . " does not exist.";
	}
	elsif (! -d $dir) {
		$error = "Chroot sandbox " . $dir . " is not a directory.";
	}
	elsif (! -r $dir) {
		$error = "Chroot sandbox " . $dir ." is not readable.";
	}
	elsif (! -w $dir) {
		$error = "Chroot sandbox " . $dir ." is not writeable.";
	}
	elsif ($> != 0) {
		$error = "Unable to chroot when not running as superuser.";
	}
	else {
		# return success
		return 1;
	}
	
	# return failure
	return 0;
}

sub help_boolean {
	my ($var, $no_newline);
	my $str = (($var) ? "YES" : "NO");
	$str .= "\n" unless (defined $no_newline);
	
	return $str;
}

sub help {
	print STDERR BOLD, "Usage: ", RESET, basename($0), " [OPTIONS] [-a ACTION]\n\n";

	print STDERR BOLD, "General options\n", RESET;
	print STDERR "  -c  --config-file=FILE          Read configuration settings from FILE\n";
	print STDERR "                                  Default: ", $config_file, "\n";
	print STDERR "  -f  --message-file=FILE         File, which contains message to process.\n";
	print STDERR "                                  If this option is omitted (default) or\n";
	print STDERR "                                  it's value is '-', then message will\n";
	print STDERR "                                  be read from stdin.\n\n";
	print STDERR "      --database-dir=DIR          Set DIR as yaa timeframe database directory.\n";


	print STDERR BOLD, "\nMail sending options:\n\n", RESET;
	print STDERR "      --sendmail-path=FILE        Sendmail binary file.\n";
	print STDERR "      --smtp-server=NAME          Use NAME as smtp server.\n";
	print STDERR "      --smtp-username=NAME        Use NAME as username for SMTP auth\n";
	print STDERR "      --smtp-password=PASS        Use PASS as password for SMTP auth\n";
	print STDERR "      --send-method=METHOD        Use METHOD (\"sendmail\" or \"smtp\")\n";
	print STDERR "                                  for sending autoresponses and forwards.\n\n";
	
	print STDERR BOLD, "\nSecurity related options:\n", RESET;
	print STDERR "      --chroot-dir=DIR            After becoming a daemon, chroot to DIR\n";
	print STDERR "                                  Default: ", (($chroot) ? $chroot : "don't chroot"), "\n";
	print STDERR "  -u  --user=USER                 Run as specified user\n";
	print STDERR "                                  Default: ", ((defined $user) ? $user : "don't set user id."), "\n";
	print STDERR "  -g  --group=GROUP               Run as specified group\n";
	print STDERR "                                  Default: ", ((defined $group) ? $group : "don't set group id."), "\n";


	print STDERR BOLD, "\nDaemon mode options:\n", RESET;
	print STDERR "      --daemon                    Operate as daemon.\n";
	print STDERR "                                  Default: ", (($daemon) ? "YES" : "NO"), "\n";
	print STDERR "  -F  --foreground                Don't fork to background if running as daemon.\n";
	print STDERR "                                  Default: ", (($daemon_background) ? "NO" : "YES"), "\n";
	print STDERR "      --daemon-lockfile=FILE      File used to implement connection mutex.\n";
	print STDERR "                                  Default: $daemon_lockfile\n";
	print STDERR "      --daemon-pid-file=FILE      Daemon pid file\n";
	print STDERR "                                  Default: $daemon_pidfile\n";
	print STDERR "      --daemon-min-servers=NUM    Minimum number of daemons. Default: $daemon_min_servers\n";
	print STDERR "      --daemon-max-servers=NUM    Maximum number of running daemons. Default: $daemon_max_servers\n";
	print STDERR "      --daemon-min-spares=NUM     Minimum number of running spare daemons.\n";
	print STDERR "                                  Default: $daemon_min_spare_servers\n";
	print STDERR "      --daemon-max-spares=NUM     Maximum number of running spare daemons.\n";
	print STDERR "                                  Default: $daemon_max_spare_servers\n";
	print STDERR "      --daemon-protocol=NAME      Set daemon transport protocol.\n";
	print STDERR "                                  To get available protocols, run this script\n";
	print STDERR "                                  with --list-transport-protocols switch\n\n";
	print STDERR "      --listen-port=NUM           Add NUM as TCP listen port\n";
	print STDERR "      --listen-host=NAME          Bind listening ports to host or ip address NAME\n";
	print STDERR "  -a  --action=NAME               Perform daemon action: {start|stop|status}\n";

	print STDERR BOLD, "\nLogging options:\n", RESET;
	print STDERR "  -l  --logging                   Turn on logging. Default: ", help_boolean($logging);
	print STDERR "      --log-syslog                Turn on syslog based logging. Default: ", help_boolean($log_syslog);
	print STDERR "      --log-syslog-facility=NAME  Syslog facility. Default: $log_syslog_facility\n";
	print STDERR "      --log-syslog-priority=NAME  Syslog priority. Default: $log_syslog_priority\n";
	print STDERR "      --log-file                  Turn on file based logging. Default: ", help_boolean($log_file);
	print STDERR "      --log-file-path=PATH        File used to write log. Default: $log_file_filename\n";
	print STDERR "      --log-stderr                Turn on logging to stderr. Default: ", help_boolean($log_stderr);
	print STDERR "      --log-timeformat=FORMAT     Set custom logfile time format.\n";
	print STDERR "                                  See strftime(3) for details.\n";

	print STDERR BOLD, "\nOther options:\n\n", RESET;
	print STDERR "      --list-transport-protocols  Prints out list of supported daemon transport protocols.\n";
	print STDERR "      --list-lookup-map-drivers   Prints out list of supported lookup map drivers.\n";
	print STDERR "      --show-lookup-map-doc=NAME  Prints out NAME lookup map driver documentation.\n";
	print STDERR "  -V  --version                   Print out script version and exit.\n";
	print STDERR "  -h  --help                      This help message.\n";
	print STDERR "\n";
}

# the following sub was written by Mark Martinec <mark.martinec@ijs.si>
# in his excellent software, called amavisd-new
sub fetch_modules {
    my($reason, @modules) = @_;
    my @missing;

	foreach my $mod (@modules) {
		$_ = $mod;
		$_ .= /^auto::/ ? '.al' : '.pm'; s[::][/]g;
		eval {require $_} or push(@missing, $mod);
	}

	if (@missing) {
		die "ERROR: MISSING $reason:\n" . join('', map {"  $_\n"} @missing);
	}

	return 1;
}

sub version {
	print basename($0), " version ", YAA_SCRIPT_VERSION, "\n";
}

sub my_which {
	my ($prog) = @_;
	foreach (split(/:/, $ENV{PATH})) {
		return $_ . "/" . $prog if (-x $_ . "/" . $prog);
	}
	return undef;
}

######################################################
#      int main (int argc, char *argv[])             #
######################################################

# try to parse config file
parse_config_file($config_file);
$error = undef;

# configure command line parser
Getopt::Long::Configure(
	"bundling"
);

# parse command line
my $getopt_result = GetOptions(
	'c|config-file=s' => sub { unless(parse_config_file($_[1])) { print STDERR $error, "\n"; exit 1; } },
	'f|message-file=s' => \ $message_file,

	'database-dir=s' => \ $db_dir,

	'sendmail-path=s' => \ $sendmail_path,
	'smtp-server=s' => $smtp_server,
	'smtp-username=s' => \ $smtp_username,
	'smtp-password=s' => \ $smtp_password,
	'send-method=s' => \ $mail_sending_method,
	'a|action=s' => sub { daemon_action($_[1]); },
	
	
	'chroot-dir=s' => \ $chroot,
	'u|user=s' => \ $user,
	'g|group=s' => \ $group,
	
	'daemon' => \ $daemon,
	"F|foreground" => sub { $daemon_background = 0; },
	'daemon_lockfile=s' => \ $daemon_lockfile,
	'daemon_pidfile=s' => \ $daemon_pidfile,
	'daemon-max-servers=i' => \ $daemon_max_servers,
	'daemon-min-servers=i' => \ $daemon_min_servers,
	'daemon-min-spares=i' => \ $daemon_min_spare_servers,
	'daemon-max-spares=i' => \ $daemon_max_spare_servers,
	'listen-port=s' => \ $daemon_listen_port,
	'listen-host=s' =>  \ $daemon_listen_host,
	
	'l|logging' => \ $logging,
	'log-syslog' => \ $log_syslog,
	'log-syslog-facility=s' => \ $log_syslog_facility,
	'log-syslog-priority=s' => \ $log_syslog_priority,
	'log-file' => \ $log_file,
	'log-file-path=s' => \ $log_file_filename,
	'log-stderr' =>  \ $log_stderr,
	'log-timeformat=s' => \ $log_time_format,
	
	'list-transport-protocols' => sub {
			require Yaa::Daemon;
			print join(", ", Yaa::Daemon->getAvailableProtocols()), "\n";
			exit 0;
		},
	'list-lookup-map-drivers' => sub {
			print join(", ", Yaa::LookupMap->getAvailableDrivers()), "\n";
			exit 0;
		},
	
	'V|version' => sub { version(); exit 0; },
	'h|?|help' => sub { help(); exit 0; },
	'doc=s' => sub { exit ! (show_doc($_[1])); },
	'show-lookup-map-doc=s' => sub { exit ! (show_map_doc($_[1])); },
	'<>' => sub { print STDERR "Invalid option: ", $_[0], "\n"; help(); exit 1; }
);

unless ($getopt_result) {
	help();
	exit 1;	
}

# check chroot directory if needed
if ($chroot && ! chroot_check($chroot)) {
	print STDERR $error, "\n";
	exit 1;
}

# we need to load additional perl modules for daemon mode operation
if ($daemon) {
	if ($mail_sending_method eq 'sendmail') {
		print STDERR "Using sendmail as mail sending method is disabled in daemon mode. Use smtp instead.\n";
		exit 1;
	}

	push(@extra_modules, "Yaa::Daemon");
	# perl version < 5.8.0?
	push(@extra_modules, 'auto::POSIX::setgid', 'auto::POSIX::setuid') if ($] < 5.008);
}

# load these scary modules
fetch_modules("Yaa modules", @extra_modules);

# IIIII-HAAAA... Time to create Yaa object
# daemon? scary daemon?
if ($daemon) {
	print STDERR basename(__FILE__), ", line ", __LINE__, ": creating Net::Server daemon object.\n" if (DEBUG);
	$yaa = Yaa::Daemon->factory(
		protocol => $daemon_protocol,
	);
	
	unless (defined $yaa) {
		print STDERR "Unable to create daemon version of Yaa object: ", $Yaa::Daemon::Error, "\n";
		exit 1;
	}
} else {
	$yaa = Yaa->new();
}

print STDERR basename(__FILE__), ", line ", __LINE__, ": setting Yaa object parameters.\n" if (DEBUG);
$yaa->setParams(
	db_dir => $db_dir,
	tf_duration_interval => $duration_interval
);

# initialize logging subsystem
print STDERR basename(__FILE__), ", line ", __LINE__, ": initializing logger subsystem.\n" if (DEBUG);
$yaa->setLogger(
	Yaa::Log->new(
		logging => $logging,
		log_syslog => $log_syslog,
		log_syslog_facility => $log_syslog_facility,
		log_syslog_priority => $log_syslog_priority,
		log_syslog_ident => basename($0),
		log_file => $log_file,
		log_file_filename => $log_file_filename,
		log_time_format => $log_time_format,
		log_stderr => ($daemon && $daemon_background) ? 0 : $log_stderr
	)
);

# initialize autoresponse object
print STDERR basename(__FILE__), ", line ", __LINE__, ": initializing autoresponse subsystem.\n" if (DEBUG);
$yaa->setAutoresponse(
	Yaa::Autoresponse->new(
		mail_sending_method => $mail_sending_method,
		sendmail_path => $sendmail_path,
		smtp_server => $smtp_server,
		smtp_auth => $smtp_auth,
		smtp_username => $smtp_username,
		smtp_password => $smtp_password
	)
);

# initialize lookup maps
my $i = 0;
foreach (keys %{$lookup_maps}) {
	print STDERR basename(__FILE__), ", line ", __LINE__, ": initializing lookup map '$_', driver '", $lookup_maps->{$_}->{driver}, "'\n" if (DEBUG);

	my $map = Yaa::LookupMap->factory(%{$lookup_maps->{$_}});
	unless (defined $map) {
		print STDERR basename(__FILE__), ", line ", __LINE__, ": lookup map initialization FAILED.\n" if (DEBUG);
		print STDERR "Unable to initialize lookup map '$_': ", $Yaa::LookupMap::Error, "\n";
		exit 1;
	}
	print STDERR basename(__FILE__), ", line ", __LINE__, ": lookup map initialization succeeded.\n" if (DEBUG);

	# assign lookup map
	unless ($yaa->addLookupMap($_, $map)) {
		print STDERR "Unable to add initialized lookup map '$_': ", $yaa->error, "\n";
		exit 1;
	}
	$i++;
}

unless ($i) {
	print STDERR "Cannot continue, becouse no lookup maps were defined.\n";
	exit 1;
}

# set lookup map query order
foreach (keys %{$lookup_map_query_order}) {
	print STDERR basename(__FILE__), ", line ", __LINE__, ": setting lookup map query order for attribute '$_'\n" if (DEBUG);
	
	unless ($yaa->setLookupMapQueryOrder($_, @{$lookup_map_query_order->{$_}})) {
		print STDERR basename(__FILE__), ", line ", __LINE__, ": setting lookup query order for attribute '$_' FAILED.\n" if (DEBUG);
		print STDERR "Unable to set lookup query order for attribute '$_': ", $yaa->error, "\n";
		exit 1;
	}
	
	print STDERR basename(__FILE__), ", line ", __LINE__, ": setting lookup query order for attribute '$_' succeeded.\n" if (DEBUG);
}

# this is it! time to fire up this little thingy
# are we big, scary daemon?
if ($daemon) {
	# set nice proctitle
	$0 = "Yaa! (parent)";

	# relocate STDERR if needed
	if ($daemon_background) {
		print STDERR "Debugging messages will now be sent to logger subsystem. Check syslog or custom log file.\n" if (DEBUG);
		tie *STDERR, $yaa;
	}

	# rewrite ports
	#my @ports;
	#foreach (@daemon_listen_ports) {
	#	push(@ports, $_);
	#}
	#foreach (@daemon_listen_sockets) {
	#	push(@ports, $_ . "|unix");
	#}
	#@ports = [ 40000, 40001 ];
	
	# run tha devil
	print STDERR basename(__FILE__), ", line ", __LINE__, ": starting daemon.\n" if (DEBUG);
	$yaa->run(
		host => ($daemon_listen_host) ? $daemon_listen_host : "*",
		# TODO setting multiple ports
		port => $daemon_listen_port,
		chroot => ($chroot) ? $chroot : undef,
		pid_file => $daemon_pidfile,
		lock_file => $daemon_lockfile,
		user => ($user) ? $user : undef,
		group => ($group) ? $group : undef,
		
		background => (($daemon_background > 0) ? $daemon_background : undef),
		# setsid => (($daemon_background > 0) ? $daemon_background : undef),
		
		child_communication => 0,
		log_level => $daemon_tcpserver_loglevel,

		min_servers => $daemon_min_servers,
		max_servers => $daemon_max_servers,
		min_spare_servers => $daemon_min_spare_servers,
		max_spare_servers => $daemon_max_spare_servers,
		
		max_requests => 1,
	);
	exit 0;
}

# set proctitle
$0 = "Yaa! (single message processing mode)";

# get uid, gid
my ($uid, $gid);
$uid = $gid = undef;
if (defined $user) {
	$uid = (getpwnam($user))[2];
	unless (defined $uid) {
		print STDERR "Cannot change effective user id to '", $user, "': User does not exist.\n";
		exit 1;
	}
}
if (defined $group) {
	$gid = (getgrnam($group))[2];
	unless (defined $gid) {
		print STDERR "Cannot change effective group id to '", $group, "': Group does not exist.\n";
		exit 1;
	}
}

# chroot, if requested
if (defined $chroot) {
	print STDERR basename(__FILE__), ", line ", __LINE__, ": chrooting to '$chroot': " if (DEBUG);
	unless (chroot($chroot)) {
		print "FAILED\n" if (DEBUG);
		print STDERR "Unable to chroot to ", $chroot, ": ", $!, "\n";
		exit 1;	
	}
	print "OK\n" if (DEBUG);

	# step into chroot
	chdir YAA_SLASH;
}

# set user & group id, if requested
if (defined $group) {
	# apply new privileges
	$) = $gid;

	print STDERR basename(__FILE__), ", line ", __LINE__, ": changing effective user id to ", $group, " gid $gid: " if (DEBUG);
	if ($!) {
		print STDERR "FAILED\n" if (DEBUG);
		print STDERR "Cannot change effective group id to '", $user, "': ", $!, "\n";
		exit 1;
	}
	print STDERR "OK\n" if (DEBUG);
}

if (defined $user) {
	# apply new privileges
	$> = $uid;
	print STDERR basename(__FILE__), ", line ", __LINE__, ": changing effective user id to ", $user, ", uid $uid: " if (DEBUG);
	if ($!) {
		print STDERR "FAILED\n" if (DEBUG);
		print STDERR "Cannot change effective user id to '", $user, "': ", $!, "\n";
		exit 1;
	}
	print STDERR "OK\n" if (DEBUG);
}

# process message
my $result;
if (! defined $message_file || $message_file eq '-') {
	print STDERR basename(__FILE__), ", line ", __LINE__, ": processing message from stdin.\n" if (DEBUG);
	$result = $yaa->processMessageByFileno(fileno(STDIN));
} else {
	print STDERR basename(__FILE__), ", line ", __LINE__, ": processing message file '$message_file'.\n" if (DEBUG);
	$result = $yaa->processMessageFile($message_file);
}

print STDERR "Error processing message: ", $yaa->error, "\n" unless ($result);

# that's it, folks!
exit (! $result);
