#!/usr/bin/perl -w
#
# ichatserver_init_tool : tool for initializing the iChat Server database and initializing jabberd config files
#
# Apple Inc. (c) 2007 - All rights reserved.
#

use Getopt::Std;

$g_my_name = "ichatserver_init_tool";
$g_jabber_var_dir = "/private/var/jabberd";
$g_db_dir = "$g_jabber_var_dir/sqlite";
$g_db_file = "$g_db_dir/jabberd2.db";
$g_db_setup_file = "$g_jabber_var_dir/db-setup.sqlite";
$g_conf_dir = "/private/etc/jabberd";
$g_tmp_dir = "$g_jabber_var_dir/tmp";
$g_pid_dir = "/private/var/run/jabberd";
$g_spool_dir = "/private/var/spool";
$g_log_dir = "$g_jabber_var_dir/log";
$g_logfile = "$g_log_dir/$g_my_name.log";
$g_sqlite3_path = "/usr/bin/sqlite3";
$g_jabber_user = 'jabber';
$g_jabber_group = 'jabber';
$g_admin_group = 'admin';
$g_jabber_uid = getpwnam($g_jabber_user);
$g_jabber_gid = getgrnam($g_jabber_group);
$g_admin_gid = getgrnam($g_admin_group);
$g_db_update_file = "$g_jabber_var_dir/db_update_10.5.5.sqlite";

$DEBUG = 0;
$VERBOSE = 1;
$LOGGING = 1;

sub _usage() {
	print "$g_my_name: tool for initializing the iChat Server sqlite database and initializing jabberd config files\n";
	print "Usage:\n";
	print "$g_my_name: [-d]\n";
	print "Options:\n";
	print "		-d: 	debug mode\n";
	print "		-i: 	install db, create jabberd pid dir, and configure config files (set hostname and router random router passwords)\n";
	print "		-s: 	install sqlite database only\n";
	print "		-c: 	configure config files (set hostname and router random router passwords) only\n";
	print "\n";
}

sub _timestamp()
{
    my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
    localtime(time);
    $year += 1900;
    $mon  += 1;
    if ( $hour =~ /^\d$/ ) { $hour = "0" . $hour; }
    if ( $min  =~ /^\d$/ ) { $min  = "0" . $min; }
    if ( $sec  =~ /^\d$/ ) { $sec  = "0" . $sec; }

    my $ret = $year."-".$mon."-".$mday."-$hour:$min:$sec";
    return $ret;
}

sub _log() {
	my $t = &_timestamp;
	if ($LOGGING) {
		open(LOG, ">>$g_logfile") || die "ERROR: could not open log file at $g_logfile: $!";
		print LOG "$t: $_[0]\n";
	}
	if ($VERBOSE) { print "$t: $_[0]\n"; }
}

sub _bail() {
	&_log($_[0]);
	exit 1;
}

# Generate a random password.
# arguments:
#	0:	length of password to create [required]
#
# returns:
#	on failure: empty string
#	on success: the password string
#	
sub random_password_gen() {
	if ($_[0] !~ /^\d+$/) {
		&_log("Invalid argument provided to random_password_gen().  Must be a valid non-negative integer.");
		return "";
	}
	my $length = $_[0];
	my $dev = "/dev/random";
	my $password = "";
	my $matchlist= q{ABCDEFGHIJKLMNOPQRSTUVWXYZJabcdefghijklmnopqrstuvwxyz0123456789`~!#$%^*()-=_+[]\\|;':",./?}.'{}';
	my $random_bits;
	&_log("Generating random password...");

	eval { 
		$random_bits = sprintf("%0.0f", log(length($matchlist) ** $length) / log(2)); 
	};
	$matchlist =~ s|([^A-Za-z0-9])|\\$1|g;

	if (! open(DEV, "<$dev")) {
		&_log("Can't open random device $dev ($!)");
		return "";
	}

	until ($password =~ /^.{$length}$/) {
		$_ = getc DEV;
		$password .= $_ if (m/[$matchlist]/);
	}

	&_log("Generated random password...");
	close(DEV);
	return $password;
}

# Create pid directory used by proxy and jabberd
sub init_pid_dir() {
	umask(000);
	if (! -e $g_pid_dir) {
		&_log("Creating pid directory $g_pid_dir...");
		if (! mkdir($g_pid_dir, 0775)) {
			&_log("Unable to create directory $g_pid_dir ($!)");
			return 1;
		}
		chown($g_jabber_uid, $g_jabber_gid, $g_pid_dir);
	} else {
		&_log("Directory already exists: $g_pid_dir");
	} 
	return 0;
}

# Create spool dir for mu-conference
sub init_muc_spool_dir() {
	umask(000);
	my $hostname = `hostname`;
	chomp($hostname);
	my $full_spool_path = $g_spool_dir."/conference.".$hostname;
	if (! -e $full_spool_path) {
        &_log("Creating MUC spool directory $full_spool_path...");
        if (! mkdir($full_spool_path, 0770)) {
            &_log("Unable to create directory $full_spool_path ($!)");
            return 1;
        }
	chown($g_jabber_uid, $g_jabber_gid, $full_spool_path);
    } else {
        &_log("Directory already exists: $full_spool_path");
    }
    return 0;
}

# Initialize sqlite database file for jabberd.
# returns:
#  0: success
#  1: error
#  2: database file already exists
#
sub init_db() {
	my $exec_string;
	umask(007);
	&_log("Creating SQLite database setup for jabberd...");
	if (-e "$g_db_file") {
		&_log("Already exists: $g_db_file");
		
		# If a SQL schema update file exists, update the database with it
		if (-e $g_db_update_file) {
			&_log("Found DB update file, executing SQL commands...");
			$exec_string = "\"$g_sqlite3_path\" \"$g_db_file\" < \"$g_db_update_file\"";
			$ret = system($exec_string);
			rename("$g_db_update_file", "$g_db_update_file.done");
		}
		return 2;
	}
	
	if (! -e $g_db_setup_file) {
		&_log("Cannot find: $g_db_setup_file");
		return 1;
	}

	if (! -d $g_db_dir) {
		&_log("$g_db_dir does not exist. Creating...");
		if (! mkdir($g_db_dir)) {
			&_log("Cannot create directory: $g_db_dir ... Aborting ($!)");
			return 1;
		}
	}

	if (! -e $g_sqlite3_path) {
		&_log("$g_sqlite3_path does not exist. Aborting.");
		return 1;
	}

	my $ret = chown($g_jabber_uid, $g_jabber_gid, $g_db_dir);
	unless ($ret) {
		&_log("Failed to set correct ownership on $g_db_dir ($!)");
		return 1;
	}
		
	$exec_string = "\"$g_sqlite3_path\" \"$g_db_file\" < \"$g_db_setup_file\"";
	$ret = system($exec_string);
	if ($ret != 0) {
		&_log("Execution failed with status $ret: $exec_string ($!)");
		return 1;
	}

	$ret = chown($g_jabber_uid, $g_jabber_gid, $g_db_file);
	unless ($ret) {
		&_log("Failed to set correct ownership on $g_db_file ($!)");
		return 1;
	}

	return 0;
}

sub init_configs() {
	my @lines;
	my $line;
	my @files = ("c2s.xml", 
		"sm.xml", 
		"s2s.xml",
		"resolver.xml",
		"router.xml",
		"router-users.xml",
		"muc-jcr.xml"
	);
	my $file;
	my $hostname = `hostname`;
	my $router_password = &random_password_gen(32);
	if ($router_password eq "") {
		&_log("random_password_gen failed, aborting");
		return 1;
	}
	chomp($hostname);
	umask(007);
	
	foreach $file (@files) {
		if (! open(FILE, "<$g_conf_dir/$file")) {
			&_log("Could not open file: $g_conf_dir/$file ($!)");
			return 1;
		}
		@lines = <FILE>;
		close(FILE);
		if (! open(NEW_FILE, ">$g_tmp_dir/$file.new")) {
			&_log("Could not open file: $g_tmp_dir/$file.new ($!)");
			return 1;
		}
		foreach $line (@lines) {
			if ($line =~ /\@HOSTNAME\@/) {
				&_log("Found hostname tag in $g_conf_dir/$file, replacing string with $hostname...");
				$line =~ s/\@HOSTNAME\@/$hostname/g;
			}
			if ($line =~ /\@ROUTERPASSWORD\@/) {
				&_log("Found password tag in $g_conf_dir/$file, replacing string...");
				$line =~ s/\@ROUTERPASSWORD\@/$router_password/g;
			}
			print NEW_FILE "$line";
		}
		close(NEW_FILE);
		my $ret = rename("$g_tmp_dir/$file.new", "$g_conf_dir/$file");
		unless ($ret) {
			&_log("rename failed with status $ret: ($!)");
			return 1;
		}

		# Set the ownership and permissions for the file
		$ret = chown($g_jabber_uid, $g_admin_gid, "$g_conf_dir/$file");
		unless ($ret) {
			&_log("chown failed with status $ret: ($!)");
		}

		$ret = chmod(0660, "$g_conf_dir/$file");
		unless ($ret) {
			&_log("chmod failed with status $ret: ($!)");
		}
	}
	return 0;
}

####### MAIN
umask(077);
getopts('disc?h', \%opts);
if (defined $opts{'d'}) {
	$DEBUG = 1;
}

if (defined $opts{'?'} || defined $opts{'h'}) {
    &_usage;
    exit 0;
}

if (defined $opts{'i'}) {
	$ret = &init_pid_dir;
	if ($ret > 0) {
		&_bail("Exiting because pid dir could not be created");
	}
	$ret = &init_muc_spool_dir;
	if ($ret > 0) {
		&_bail("Exiting because muc spool dir could not be created");
	}
	$ret = &init_db;
	if ($ret == 2) {
		&_log("Exiting since database already exists.  Use -c to init config files only.");
		exit 0;
	} elsif ($ret == 1) {
		&_bail("Aborting since init_db failed.");
	}
	$ret = &init_configs;
	if ($ret > 0) {
		&_bail("Aborting since init_configs failed.");
	}
} elsif (defined $opts{'s'}) {
	$ret = &init_db;
	if ($ret == 1) {
		&_bail("Aborting since init_db failed.");
	}
} elsif (defined $opts{'c'}) {
	$ret = &init_configs;
	if ($ret > 0) {
		&_bail("Aborting since init_configs failed.");
	}
} else {
	&_log("Unrecognized options, exiting");
	&_usage;
	exit 1;
}

&_log("Finished.");
