#!/usr/bin/perl

#################################   Constants  #################################
$DIFF = "/usr/bin/diff";
$RM = "/bin/rm";
$PBUDDY = "/usr/libexec/PlistBuddy";
$TAR = "/usr/bin/tar";
$SQLITE = "/usr/bin/sqlite3";
$ServiceConf = "75-iChatServer.plist";
$SBS_CONF = "/private/etc/server_backup";
$tmpPath = "/private/var/jabberd/tmp";
$servermgrJabberConfigPath = "/Library/Preferences/com.apple.ichatserver.plist";

%dbinfo = {};
%BigList = "";
$CONF_EXT = ".conf";
$STATUS_EXT = ".status";
$ret = 0;

$VERBOSE = 0;
$DEBUG = 0;
$FUNCLOG = 0;

# 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_verify was called.\n");

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

$ret = validateOptionsAndDispatch(@ARGV);
exit($ret);

################################################################################
sub validateOptionsAndDispatch()
{
	$ret = 0;
	%BigList = @_;
	SWITCH: {
		if (uc($BigList{"-cmd"}) eq uc("actions"))  { if($DEBUG) {print("actions\n");} Actions(); last SWITCH; }
		if (uc($BigList{"-cmd"}) eq uc("help"))	{ if ($DEBUG) {print("help\n");} Usage(); last SWITCH; }
		if ((uc($BigList{"-cmd"}) eq uc("verify")) && (-e ($BigList{"-path"})) ) { if ($DEBUG) {print("verify\n");} $ret = Verify(); 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();}

	return($ret);
}

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

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

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

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

}

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

	my $successCount = 0;
	my $failureCount = 0;
	my $omittedCount = 0;
	my $imagePath = $BigList{"-path"};
#	my $OPT = $BigList{"-opt"};
	my $OPT = "all";	# not accepted or passed in by ServerBackup
	my $diffFilePath = "$tmpPath/iChatServer_verify_diff_tmp";
	my $storedFileDir = "$tmpPath/iChatServer_restored_for_verify";
	my $restoredSqlFile = "iChatServer_data_backup.sql";
	my $newSqlFile = "iChatServer_data_current.sql";
	my $sqliteDefaultDbPath = "/Library/Server/iChat/Data/sqlite/jabberd2.db";
	my $sqliteDbPath = "";
	my @diffLines;
	my $cmd;

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

	&_log($OUTPUT_DEBUG, "IMAGE_PATH := $imagePath\n");
	
	if ($OPT ne "configuration" && $OPT ne "data" && $OPT ne "all") {
		&_log($OUTPUT_PRINT, "Invalid or missing -opt given.\n");
		return 1;
	}
	
	if (! -e $imagePath) {
		&_log($OUTPUT_PRINT, "The provided image path does not exist. Aborting.\npath was: $imagePath\n");
		return 1;
	}
	
	# Find our config file backups
	if (! opendir(SBSDIR, "$imagePath")) {
		&_log($OUTPUT_PRINT, "Couldn't open the directory for reading: $imagePath\n");
		return 1;
	}

	my @imageTarFiles = readdir(SBSDIR);
	closedir(SBSDIR); 

	mkdir($storedFileDir);
	# should only be one conf file in imagePath. stop at first file.
	FINDCONF: foreach my $imageTarFile (@imageTarFiles) {
		if ($imageTarFile =~ /iChatServer.conf.tar.gz$/) {
			my $origCWD = $ENV{PWD};
			chdir($storedFileDir);
			qx($TAR -xzf \"$imagePath/$imageTarFile\");
			chdir($origCWD);
			last FINDCONF;   
		}
	}
	FINDDATA: foreach my $imageTarFile (@imageTarFiles) {
		if ($imageTarFile =~ /iChatServer.data.tar.gz$/) {
			my $origCWD = $ENV{PWD};
			chdir($storedFileDir);
			qx($TAR -xzf \"$imagePath/$imageTarFile\");
			chdir($origCWD);
			last FINDDATA;
		}
	}
	FINDROOMSDATA: foreach my $imageTarFile (@imageTarFiles) {
		if ($imageTarFile =~ /iChatServer_Rooms.data.tar.gz$/) {
			my $origCWD = $ENV{PWD};
			chdir($storedFileDir);
			qx($TAR -xzf \"$imagePath/$imageTarFile\");
			chdir($origCWD);
			last FINDROOMSDATA;
		}
	}

	# Configs
	if ($OPT eq "configuration" || $OPT eq "all") {
		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);
			}	 
		}

		my $origCWD = $ENV{PWD};
		chdir($storedFileDir);

		foreach $configFile (@configurationFiles) {
			# Allow for wildcards in the plist strings.  Expand them and push them onto the array for later processing.
			if ($configFile =~ /\*/) {
				&_log($OUTPUT_VERBOSE, "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_VERBOSE, "Adding wildcard expansion to configurationFiles array: $expFile\n");
					push(@configurationFiles, $expFile);
				}
				next;
			}
					
			&_log($OUTPUT_VERBOSE, "Comparing $configFile...\n");

			my $relativeFilePath = $configFile;
			$relativeFilePath =~ s/^\///;
			$cmd = "$DIFF -q \"$configFile\" \"$relativeFilePath\" &> \"$diffFilePath\"";
			&_log($OUTPUT_VERBOSE, "Executing: $cmd\n");
			qx($cmd);
			open(DIFF, "<$diffFilePath");
			@diffLines = <DIFF>;
			close(DIFF);
			chomp(@diffLines);
			if ($diffLines[0] eq "") {
				$successCount++;	
				&_log($OUTPUT_VERBOSE, "File match ($configFile)\n");
			} else {
				if (! -e $configFile && ! -e $relativeFilePath) {
					# if a file was missing from backup and current system, ignore
					$successCount++;	
					&_log($OUTPUT_VERBOSE, "Ignoring file ($configFile), counting as a match\n");
				} else {
					&_log($OUTPUT_VERBOSE, "VERIFICATION FAILURE: config file $configFile does not match original\n");
					$failureCount++;	
				}
			}
		}
		unlink($diffFilePath);
	}

	if ($OPT eq "data" || $OPT eq "all") {
		# Data: compare restored .sql file with current sql dump
		open(DIR, ".");
		my @files = <DIR>;
		closedir(DIR);

		my $roomsDirPath = qx(${PBUDDY} -c \"Print :RoomsDataDirectory:\" $SBS_CONF/$ServiceConf);
		chomp($roomsDirPath);
		my $roomsLogDirPath = qx(${PBUDDY} -c \"Print :RoomsLogDataDirectory:\" $SBS_CONF/$ServiceConf);
		chomp($roomsLogDirPath);

		if (! -e "$storedFileDir/$restoredSqlFile") {
			&_log($OUTPUT_VERBOSE, "VERIFICATION FAILURE: Could not find original .sql file for database comparison");
			$failureCount++;
		} elsif (! -e "$storedFileDir/$roomsDirPath") {
			&_log($OUTPUT_VERBOSE, "VERIFICATION FAILURE: Could not find original Rooms data directory for comparison");
			$failureCount++;
		} else {
			qx(echo ".dump" | $SQLITE $sqliteDbPath > $tmpPath/$newSqlFile);
			qx($DIFF -q \"$storedFileDir/$restoredSqlFile\" \"$tmpPath/$newSqlFile\" &> \"$diffFilePath\");
			open(DIFF, "<$diffFilePath");
			@diffLines = <DIFF>;
			close(DIFF); 
			chomp(@diffLines);
			if ($diffLines[0] eq "") {
				$successCount++;     
			} else {
				&_log($OUTPUT_VERBOSE, "VERIFICATION FAILURE: Backed up database does not match current database\n");
				$failureCount++;    
			}

			qx($DIFF -rq "$storedFileDir/$roomsDirPath" "$tmpPath/$roomsDirPath" &> "$diffFilePath");
			open(DIFF, "<$diffFilePath");
			@diffLines = <DIFF>;
			close(DIFF); 
			chomp(@diffLines);
			if ($diffLines[0] eq "") {
				$successCount++;     
			} else {
				&_log($OUTPUT_VERBOSE, "VERIFICATION FAILURE: Backed up Rooms data directory does not match current directory\n");
				$failureCount++;    
			}

			qx($DIFF -rq "$storedFileDir/$roomsLogDirPath" "$tmpPath/$roomsLogDirPath" &> "$diffFilePath");
			open(DIFF, "<$diffFilePath");
			@diffLines = <DIFF>;
			close(DIFF); 
			chomp(@diffLines);
			if ($diffLines[0] eq "") {
				$successCount++;     
			} else {
				&_log($OUTPUT_VERBOSE, "VERIFICATION FAILURE: Backed up Rooms data directory does not match current directory\n");
				$failureCount++;    
			}
		}
		unlink($diffFilePath);
		unlink("$tmpPath/$newSqlFile");
		my @pathComponents = split(/\//, $roomsDirPath);
		if ($pathComponents[0] ne "") {
			qx($RM -rf "$tmpPath/$pathComponents[0]");
		}
		@pathComponents = split(/\//, $roomsLogDirPath);
		if ($pathComponents[0] ne "") {
			qx($RM -rf "$tmpPath/$pathComponents[0]");
		}
		qx($RM -rf \"$storedFileDir\");
		chdir($origCWD);
	}

	#print("\nVerify service := iChatServer resulted in:\nSuccessful matches := $successCount\nNumber of failures to match := $failureCount\nNumber of ommisions := $omittedCount\n"); $ret = 1;
	&_log($OUTPUT_PRINT, "\nVerify service := iChatServer resulted in:\nSuccessful matches := $successCount\nNumber of failures to match := $failureCount\n"); 
	$ret = 1;

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

	return($ret);
}

################################################################################
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 Usage()
{
	print "Usage:\n";
	print "iChatServer_verify supports the following options:\n";
	print " -cmd actions :                                  Prints out the dictionary of VerifyActions from the conf file := $SBS_CONF$ServiceConf\n";
	print " -cmd help :                                     Displays this usage.\n";
	print " -cmd verify -path path -opt option           Verify the service data, it is verifing what's in the image with what is on the boot drive.\n";
	print "    where path is the path to the image.\n";
	print "    where option is one of: configuration, data, all\n";
	print " -cmd version :                                  Prints out the version value from the property list := $SBS_CONF$ServiceConf\n";
	exit(0);
}

################################################################################
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 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 "";
}
