#!/usr/bin/perl

#################################   Constants  #################################
$PBUDDY = "/usr/libexec/PlistBuddy";
$SQLITE = "/usr/bin/sqlite3";
$SERVER_ADMIN="/usr/sbin/serveradmin";
$TAR = "/usr/bin/gnutar";
$SBS_CONF = "/private/etc/server_backup";
$MKTEMP_PATH = "/usr/bin/mktemp";
$JABBER_USER = "_jabber";
$ServiceConf = "75-iChatServer.plist";
$servermgrJabberConfigPath = "/Library/Preferences/com.apple.ichatserver.plist";
$ServiceName = "iChatServer";
$tmpPath = "/private/var/jabberd/tmp";
%$BigList="";
@arrayServerAdminPrefs="";
%dbinfo = {};

# Enum for deciding what to do with output
($OUTPUT_NONE, $OUTPUT_DEBUG, $OUTPUT_VERBOSE, 
	$OUTPUT_FUNCLOG, $OUTPUT_PRINT) = (0..4);

$LOG_PATH = &_get_log_path(@ARGV);   # global, for service-specific log path

if($ENV{VERBOSE} eq 1) {$VERBOSE = '1';}
if($ENV{DEBUG} eq 1) {$DEBUG = '1';}
if($ENV{FUNCLOG} eq 1) {$FUNCLOG = '1';}

&_log($OUTPUT_VERBOSE, 	"iChatServer_restore was called.\n");

ParseOptions();
if ($DEBUG) 
	{ dumpAssociativeArray(@ARGV); }

validateOptionsAndDispatch(@ARGV);
exit();

################################################################################
sub validateOptionsAndDispatch()
{
	%BigList=@_;
	my $nothing = 0;
	SWITCH: {
		if (uc($BigList{"-cmd"}) eq uc("actions")) { if($DEBUG) {print("actions\n");} Actions(); last SWITCH; }
		if ((uc($BigList{"-cmd"}) eq uc("browse")) && (-e ($BigList{"-path"}))) { if ($DEBUG) {print("browse\n");} Browse(); last SWITCH; }
		if (uc($BigList{"-cmd"}) eq uc("help")) { if($DEBUG) {print("help\n");} Usage(); last SWITCH; }
		if ((uc($BigList{"-cmd"}) eq uc("restore")) && (-e ($BigList{"-path"}))) { print("restore\n"); Restore(); last SWITCH; }
		if (uc($BigList{"-cmd"}) eq uc("version")) { if($DEBUG) {print("version\n");} Version(); last SWITCH; }
		$nothing = 1;
		}
	if($nothing eq 1)
		{print("Legal options were not supplied!\n");Usage();}
}

################################################################################
sub Actions() 
{
	&_log($OUTPUT_FUNCLOG, "Start Actions-------------------------------------------------------+\n");

	&_log($OUTPUT_VERBOSE, 	(qq(${PBUDDY} -c \"Print :RestoreActions\" $SBS_CONF/$ServiceConf) . "\n"));

	$Version = qx(${PBUDDY} -c \"Print :RestoreActions\" $SBS_CONF/$ServiceConf);
	&_log($OUTPUT_PRINT, $Version);

	&_log($OUTPUT_FUNCLOG, "End   Actions-------------------------------------------------------+\n");
}

################################################################################
sub Browse()
{
	my $SBS_PATH = $BigList{"-path"};

	&_log($OUTPUT_FUNCLOG, "Start Browse-------------------------------------------------------+\n");
	
	if ($SBS_PATH eq "") {
        &_log($OUTPUT_PRINT, "Legal options were not supplied!\n");
		Usage();
	}

	my $browseFilePath=$SBS_PATH . "/$ServiceName.browse.plist";

	if(-e $browseFilePath) {
		open(FILEFD, $browseFilePath);
		@LINES = <FILEFD>;
		while($Line =shift(@LINES)) {
			chomp($Line);
			printf("%s\n", $Line);
		}
	}
	else {
		opendir(SBSDIR, $SBS_PATH);
		my @storedFiles = readdir(SBSDIR);
		closedir(SBSDIR);

		foreach my $storedFile (@storedFiles) {
			if ($storedFile =~ /iChatServer.conf.tar.gz/ || $storedFile =~ /iChatServer.data.tar.gz/) {
				print "$SBS_PATH/$storedFile\n";
			}
		}
	}

	&_log($OUTPUT_FUNCLOG, "End Browse-------------------------------------------------------+\n");
}

################################################################################
sub Restore() 
{
	my $jabberdDataPath = "/var/jabberd";
	my $jabberdDBPath = "";
	$jabberdDBPath = "$jabberdDataPath/sqlite";
	my $sqliteDefaultDbPath = "$jabberdDBPath/jabberd2.db";
	my $jabberdSqliteDataFile = "";
	my $jabberDataTmpDir = "";
	my $C2SPID = "/var/run/jabberd/c2s.pid";
	my $SBS_PATH = $BigList{"-path"};
	my $OPT = $BigList{"-opt"};
	my $initialJabberdState = 0;
	my $cmd;
	my $origCWD;

    # find sqlite database path
    my $pbuddyDbPath = qx(${PBUDDY} -c \"Print :jabberdDatabasePath:\" $servermgrJabberConfigPath);
    chomp($pbuddyDbPath);
    if (-e $pbuddyDbPath) {
        $jabberdSqliteDataFile = $pbuddyDbPath;
    } else {
        $jabberdSqliteDataFile = $sqliteDefaultDbPath;
    }

	umask(077);
        
	&_log($OUTPUT_FUNCLOG, "Start Restore-------------------------------------------------------+\n");

    if ($OPT ne "configuration" && $OPT ne "data" && $OPT ne "all") {
		&_log($OUTPUT_PRINT, "No valid -opt selected for restore operation.\n");
        return 1;
    }
    if (! -e $SBS_PATH) {
		&_log($OUTPUT_PRINT, "Supplied -path does not exist.\n");
		return 1;
	}
	
	# Shutdown jabber if its enabled
	my $res = `ps -xu \"$JABBER_USER\" |grep c2s|grep -v grep`;
	if ($res ne "") {
		&_log($OUTPUT_VERBOSE, "Stopping jabber service\n");

		qx($SERVER_ADMIN stop jabber);
		$initialJabberdState = 1;
		for (my $x = 0; $x < 5; $x++) {
			$res = `ps -xu \"$JABBER_USER\" |grep c2s|grep -v grep`;
			if ($res eq "") {
				last;
			}
			sleep 3;
		}
	}
	if ($res ne "") {
		&_log($OUTPUT_PRINT, "Error: Cannot stop jabber service\n");
		return 1;
	}

	if ($OPT eq "all" || $OPT eq "configuration") {
		# Backup old config
        my @configurationFiles;
        my @configurationFilesPbuddy = qx(${PBUDDY} -c \"Print :ConfigurationFilesToTar:\" $SBS_CONF/$ServiceConf);
        if ($#configurationFilesPbuddy > 1) {
            for (my $i = 1; $i < $#configurationFilesPbuddy; $i++) {
                my $configTmp = $configurationFilesPbuddy[$i];
                chomp($configTmp);
                $configTmp =~ s/^\s*//;
                push(@configurationFiles, $configTmp);
            }
        }
		if ($VERBOSE) {
			$cmd = "$TAR --ignore-failed-read -cvzf /etc/jabberd/iChatServer_settings_before_restore.tar.gz";
		} else {
			$cmd = "$TAR --ignore-failed-read -czf /etc/jabberd/iChatServer_settings_before_restore.tar.gz";
		}

		foreach my $configFile (@configurationFiles) {
			# Allow for wildcards in the plist strings.  Expand them and push them onto the array for later processing.
			if ($configFile =~ /\*/) {
				&_log($OUTPUT_DEBUG, "Found a wildcard in the plist ($configFile), expanding for later processing...\n");
				my @expFiles = `ls -1 $configFile`;
				chomp(@expFiles);
				foreach my $expFile (@expFiles) {
					&_log($OUTPUT_DEBUG, "Adding wildcard expansion to configurationFiles array: $expFile\n");
					push(@configurationFiles, $expFile);
				}
				next;
			}

			$cmd .= " \"$configFile\"";
		}
		&_log($OUTPUT_DEBUG, "Executing command: $cmd\n");
		system($cmd);

		# Restore config
		opendir(SBSDIR, "$SBS_PATH") || die ("Couldn't open the directory $SBS_PATH for reading.\n");;
		my @confTarFiles = readdir(SBSDIR);
		closedir(SBSDIR);

		# should only be one conf file in path. stop at first file.
		foreach my $confTarFile (@confTarFiles) {
			if ($confTarFile =~ /iChatServer.conf.tar.gz$/) {
				$origCWD = $ENV{PWD};
				chdir("/");
				if ($VERBOSE) {
					$cmd = "$TAR -xzvf \"$SBS_PATH/$confTarFile\"";
				} else {
					$cmd = "$TAR -xzf \"$SBS_PATH/$confTarFile\"";
				}
				system($cmd);
				chdir($origCWD);
				last;	
			}
		}
	}

	if ($OPT eq "all" || $OPT eq "data") {
		# Backup old data so it can be manually restored
		if ($VERBOSE) {
			$cmd = "$TAR -cvzf \"$jabberdDataPath/jabber_data_before_restore.tar.gz\" \"$jabberdDBPath\"";
		} else {
			$cmd = "$TAR -czf \"$jabberdDataPath/jabber_data_before_restore.tar.gz\" \"$jabberdDBPath\"";
		}
		&_log($OUTPUT_VERBOSE, "Executing command: $cmd\n");
		system($cmd);

		# Restore data
		opendir(SBSDIR, "$SBS_PATH") || die ("Couldn't open the directory $SBS_PATH for reading.\n");;
		my @sbsFiles = readdir(SBSDIR);
		closedir(SBSDIR);

		# should only be one data file in the path. stop at first file.
		my @sqlFiles;
		SBSFILESCAN: foreach my $sbsFile (@sbsFiles) {
			if ($sbsFile =~ /iChatServer.data.tar.gz$/) {
				for (my $i = 0; $i < 5; $i++) {
					$jabberDataTmpDir = `$MKTEMP_PATH -d $tmpPath/jabber_restore.XXXXXXXXXXXXXXXXXXXXXXXX`;
					chomp($jabberDataTmpDir);
					if (-e $jabberDataTmpDir) {
						last;
					}
					if ($i == 4) {
						die "Error: Cannot create temporary file:\n$jabberDataTmpDir";
					}
				}
				$origCWD = $ENV{PWD};
				chdir("$jabberDataTmpDir") || die ("Could not chdir to $jabberDataTmpDir");
				if ($VERBOSE) {
					$cmd = "$TAR -xvzf \"$SBS_PATH/$sbsFile\"";
					print "Executing command: $cmd\n";
				} else {
					$cmd = "$TAR -xzf \"$SBS_PATH/$sbsFile\"";
				}
				system($cmd);
				chdir($origCWD);
				opendir(SQL_TMP_DIR, "$jabberDataTmpDir");
				@sqlFiles = readdir(SQL_TMP_DIR);
				closedir(SQL_TMP_DIR);
				# should only be one SQL file in the data file. stop at first file.
				foreach my $sqlFile (@sqlFiles) {
					if ($sqlFile =~ /\.sql$/) {
						$cmd = "rm -f \"$jabberdSqliteDataFile\"; $SQLITE \"$jabberdSqliteDataFile\" < \"$jabberDataTmpDir/$sqlFile\"";
						&_log($OUTPUT_VERBOSE, "Executing command: $cmd\n");
						system($cmd);
						system("chmod 640 \"$jabberdSqliteDataFile\"");
						system("chown jabber:jabber \"$jabberdSqliteDataFile\"");
						unlink("$jabberDataTmpDir/$sqlFile");
						qx(rm -Rf "$jabberDataTmpDir");
						last SBSFILESCAN;  # should only be one SQL file
					}
				}
			}
		}
	}
	
	# Restart jabberd if necessary
	if ($initialJabberdState) {
		&_log($OUTPUT_VERBOSE, "Restarting iChatServer service\n");
		qx($SERVER_ADMIN start jabber);
	}

	&_log($OUTPUT_FUNCLOG, "End   Restore-------------------------------------------------------+\n");
}

################################################################################
sub Version() 
{
	&_log($OUTPUT_FUNCLOG, "Start Version-------------------------------------------------------+\n");

	$Version = qx(${PBUDDY} -c \"Print :Version\" $SBS_CONF/$ServiceConf);
	&_log($OUTPUT_PRINT, $Version);

	&_log($OUTPUT_FUNCLOG, "End Version-------------------------------------------------------+\n");
}

################################################################################
# ParseOptions takes a list of possible options and a boolean indicating
# whether the option has a value following, and sets up an associative array
# %opt of the values of the options given on the command line. It removes all
# the arguments it uses from @ARGV and returns them in @optArgs.
#
sub ParseOptions {
	local (@optval) = @_;
	local ($opt, @opts, %valFollows, @newargs);

	while (@optval) {
		$opt = shift(@optval);
		push(@opts,$opt);
		$valFollows{$opt} = shift(@optval);
	}

	@optArgs = ();
	%opt = ();

	arg: while (defined($arg = shift(@ARGV))) {
		foreach $opt (@opts) {
		    if ($arg eq $opt) {
				push(@optArgs, $arg);
				if ($valFollows{$opt}) {
					if (@ARGV == 0) {
						Usage();
					}
					$opt{$opt} = shift(@ARGV);
					push(@optArgs, $opt{$opt});
				} else {
					$opt{$opt} = 1;
				}
				next arg;
			}
		}
		push(@newargs,$arg);
	}
	@ARGV = @newargs;
}

################################################################################
sub dumpAssociativeArray()
{
	%BigList=@_;
	while(($theKey, $theVal) = each (%BigList))
		{ print "$theKey is the key for value $theVal\n"; }
	if($BigList{"-cmd"} eq "backup")
		{ print "cmd := ", $BigList{"-cmd"}, "\n"; }
}

################################################################################
sub Usage()
{
	print "Usage:\n";
	print "iChatServer_restore supports the following options:\n";
	print " -cmd actions :                Prints out the dictionary of BackupActions from the conf file := $SBS_CONF$ServiceConf\n";
	print " -cmd browse -path path : \n";
	print "    where path is the path to the mounted image for browsing.\n";
	print " -cmd restore -path path -opt option\n";
	print "    where path is the path to the mounted image where the data was backed-up.\n";
	print "    where option is one of: configuration, data, all\n";
	print " -cmd help :                   Displays this usage.\n";
	print " -cmd version :                Prints out the version value from the property list := $SBS_CONF$ServiceConf\n";
	exit(0);
}

################################################################################
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";
	#my $ret = $year."-".$mon."-".$mday;
	return $ret;
}

################################################################################
# Handle the various output modes, log to our file
sub _log {
	my $type = shift;
	my $msg = shift;
	
	# log the message to our service-specific log file
	if ($LOG_PATH ne "") {
		open(OUT, ">>$LOG_PATH") || die "ERROR: Cannot open log file at path: $LOG_PATH : $!";
		print OUT &timestamp."\t".$msg;
		close(OUT);
	}

	# print to STDOUT if appropriate
	if (($type == $OUTPUT_PRINT)  ||
			($FUNCLOG && ($type == $OUTPUT_FUNCLOG)) ||
			($DEBUG && ($type == $OUTPUT_DEBUG)) ||
			($VERBOSE && ($type == $OUTPUT_VERBOSE))) {
		print $msg;
	}
}

# Pull the -log arg from argv
sub _get_log_path {
	my ($theKey, $theVal);
	my %theArgList = @ARGV;
	while(($theKey, $theVal) = each (%theArgList)) {
		if ($theKey eq "-log") {
			return $theVal;
		}
	}
	return "";
}
