#!/usr/bin/php
<?php
/** 
 * Zarafa Z-Merge
 * 
 * Copyright (C) 2005 - 2008  Zarafa B.V.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3, 
 * and under the terms of the GNU General Public License, version 3,
 * as published by the Free Software Foundation.
 * 
 * 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 (Affero) General Public License for more details.
 * 
 * You should have received a copy of the GNU Affero General Public License 
 * and the GNU General Public License along with this program.  
 * If not, see <http://www.gnu.org/licenses/>.
 *  
 * Created on 07.11.2007 by Sebastian Kummer & Manfred Kutas
 *
 * @package Z-Merge
 */


echo "\n\n";
echo "Step 1 - Check configuration\n----------------------------------------------------------------------------\n";
define('BASEP', dirname($_SERVER['SCRIPT_FILENAME']) . "/");

echo "parsing of configuration: ";
$incfile = BASEP. '../z-merge.inc.php';
if (!file_exists($incfile)) die ("failed\n\nThe file $incfile could not be found"); 
	
exec('php '.$incfile, &$ret);
if (!empty($ret)) {
	echo "Configuration error! System claimed:\n";
	echo $ret[0] . $ret[1] . "\n\n";
	exit;	
}
require_once($incfile);
echo "ok\n";


//initialize logging 
$log =& LoggerManager::getLogger('main');
$log->info("initializing check tool");


echo "checking ZMERGE_DIR: ";
if (!file_exists(ZMERGE_DIR . "/z-merge.php")) die("Main directory: " . ZMERGE_DIR . " doesn't exist or agent not found\n\n");
echo "ok\n";


echo "checking ZMERGE_LOGFILE: ";
if (!file_exists(ZMERGE_LOGFILE)) {
	system("touch ". ZMERGE_LOGFILE);	
	if (!file_exists(ZMERGE_LOGFILE ))
		die("Logfile: ". ZMERGE_LOGFILE ." doesn't exist and directory isn't writable\n\n");	
}
echo "ok\n";

echo "checking ZMERGE_ERRORLOGFILE: ";
if (!file_exists(ZMERGE_ERRORLOGFILE )) {
	system("touch ". ZMERGE_ERRORLOGFILE);	
	if (!file_exists(ZMERGE_ERRORLOGFILE))
		die("Logfile: " . ZMERGE_ERRORLOGFILE ." doesn't exist and directory isn't writable\n\n");	
}
echo "ok\n"; 

echo "checking ZMERGE_SENDERRORS emailaddress: ";
if (ZMERGE_SENDERRORS !== false && !checkEmail(ZMERGE_SENDERRORS))
	die(ZMERGE_SENDERRORS ." invalid or domain not reachable\n\n");
echo "ok\n";	

echo "\n\n";		
echo "Step 2 - Check agent database connection\n----------------------------------------------------------------------------\n";
echo "connecting to database: ";
$logFake = new LogFake(); // prints logentries to the STDOUT
$db = new Database($logFake, false);
if ($logFake->hasError()) {
	if ($logFake->dbMissing()) {
		echo "fail\n\n";
		if (question("The database '". $db_dsn['database'] . "' not found. Would you like to create it? (y/n):", "ny") === false) {
			$logFake->printErrors();
			die("\n");
		}
		else {
			system("echo \"CREATE DATABASE {$db_dsn['database']};\" | mysql -h{$db_dsn['hostspec']} -u{$db_dsn['username']} -p{$db_dsn['password']}");			
			system("mysql -h{$db_dsn['hostspec']} -u{$db_dsn['username']} -p{$db_dsn['password']} -D{$db_dsn['database']} < " . ZMERGE_DIR . "/tools/db.sql"); 
		}
		
		// retry connect
		echo "connecting to database (2): ";
		$logFake = new LogFake();
		$db = new Database($logFake, false);
		if ($logFake->dbMissing()) {
			echo "fail\n\n";
			echo "unsuccessfully creating the database\n";
			$logFake->printErrors();
			die("\n");
		}
	}
}
// shutdown the logFake
$logFake->disable();

echo "database structure: \n";
checkTable("lastmodes");
checkTable("objectmapping");
checkTable("usermapping");
checkTable("zmerge");

echo "\n\n";		
echo "Step 3 - Connector status and test\n----------------------------------------------------------------------------\n";
echo "loading connectors..";

try {
	$serv = MergeAgent::loadServers($servers, $log, $db);
}
catch (DynamicClassInstantiationZException $dci) {
	echo "\n\n". $dci->getTraceAsString();
	die("DynamicClassInstantiationZException: " . $dci->getMessage(). "\n");    		
} 
catch (AuthenticationZException $ae) {
	die("\n\nAuthentication error: ".$ae->getMessage(). "\n");
}
echo "ok\n";


// general check of the servers
$servVal = array();
foreach ($serv as $s) {
        echo "\nchecking server ------- \"". $s->getName() . "\"\n";
        $sp = "           ";
        $stat = getServerInfo($s);
		$servVal[$s->getName()] = $stat;
        if (array_search("failed", $stat['info'])) {
            echo $sp . "The connector presents ERRORS!! \n";
            echo $sp . "Please check the values below and consult the installation manual troubleshooting area!\n\n";
        }
        else {
                echo $sp . "The connector test was completed sucessfully.\n\n";
        }
        foreach ($stat['info'] as $k=>$v) {
                if (substr($k, -1) == "2") continue;

                echo $sp . "- ". strip_tags($k);
                echo str_repeat(" ", 30 - strlen($k) + 6);
                echo strip_tags($v) . "\n";
                // print detailed information if available
                if (isset($stat['info'][$k . "2"])) {
                        echo $sp . "  " .str_repeat("^", strlen($k)). "--->";
                        echo str_repeat(" ", 30 - strlen($k) + 2);
                        echo strip_tags($stat['info'][$k . "2"]) . "\n";
                }
        }

       	echo "\n" . $sp . "Users on the system: ". count($stat['users']) ."\n";

        
        if (isset($stat['appointments'])) {
        	echo "\n" . $sp . "Statistics\n";
       		echo $sp . "  - Appointments  : ". $stat['appointments'] . "\n";
        }
        if (isset($stat['contacts'])) 
       		echo $sp . "  - Contacts      : ". $stat['contacts'] . "\n";
        if (isset($stat['tasks']))  
       		echo $sp . "  - Tasks         : ". $stat['tasks'] . "\n";
        if (isset($stat['notes'])) 
       		echo $sp . "  - Notes         : ". $stat['notes'] . "\n";

}


echo "\n\n";		
echo "Step 4: Automap users for connectors\n----------------------------------------------------------------------------\n";

// check for potential groups or double mapping
$stmt = "SELECT count(*) as c FROM usermapping u1, usermapping u2 WHERE u1.servername = u2.servername AND u1.serveruser = u2.serveruser AND u1.userid <> u2.userid";
if ($db->executeStatement($stmt, false) > 0) {
	echo "Usermapping contains double mapping or virtual groups. Automapping not possible.\n"; 
}

// no potential problems found.. try automap
else {
	$users_ids = array();
	
	// build list of already mapped users
	foreach ($serv as $s) {
		// get the userlist for this server
		$users = $servVal[$s->getName()]['users'];
		// loop through users and check if they are already mapped
		foreach ($users as $u) {
			$userid = getUserMapping($s->getName(), $u);
	
			// save userid for other servers
			if ($userid !== false) $users_ids[$u] = array(true, $userid);
		}
	}
	
	// now look for unmapped users
	foreach ($serv as $s) {
		// get the userlist for this server
		$users = $servVal[$s->getName()]['users'];
		echo "\nprocessing ". count($users) . " users from \"" . $s->getName() . "\"\n";
		
		// loop through users and check if they are already mapped
		foreach ($users as $u) {
			$userid = getUserMapping($s->getName(), $u);
	
			// look for unmapped users
			if ($userid == false) {
				$users_found = &$users_ids[$u];
				
				if (is_array($users_found) && $users_found[0] == true) {
					// user already mapped,but not for this server.. just add mapping
					echo "User '$u' from \"" . $s->getName() . "\" is already mapped on another server. \n";
	
		           	$a = question("Do you want to automap this user? (y/n) ", "yn");
		        	if ($a === false) {echo "\n\n"; continue;}
		        	
		        	// MAP user!!!
		        	if ($a == "y") {
						addUserMapping($s->getName(), $u, $users_found[1]);
						echo " --> user '$u' mapped for '{$s->getName()}'\n";
		        	}					
				}
				// user found on other server, but not already mapped
				else if (is_array($users_found) && $users_found[0] == false) {
					$users_found[1]++;
				}
				// completely new
				else {
					$users_ids[$u] = array(false, 1);
				}
			} 	
			
		}
	}
	
	
	echo "\n";
	$unmapped = array();
	foreach ($users_ids as $user=>$found) {
		if ($found[0] == true) echo " -> username '$user' mapped for at least one system\n";
		else {
			if ($found[1] == 1)	
				$unmapped[] = "'" . $user . "'";
			else {
				echo "Found possible new user mapping. The username '$user' is available on {$found[1]} systems!\n";
	
	           	$a = question("Do you want to automap this user? (y/n) ", "yn");
	        	if ($a === false) {echo "\n\n"; continue;}
	        	
	        	// MAP user!!!
	        	if ($a == "y") {
					$newId = false;
					foreach ($serv as $s) {
						$users = $servVal[$s->getName()]['users'];
						// user available for that server?
						if (in_array($user, $users)) {
							if (! $newId) $newId = addUserMapping($s->getName(), $user, false);
							else		  $newId = addUserMapping($s->getName(), $user, $newId);
							echo " --> user '$user' mapped for '{$s->getName()}'\n";
						}
					}        		
	        	}
	        	echo "\n";
			}		
		}	
	}
	
	
	echo "\nThe username(s) ".implode(', ', $unmapped)." were found each only on one system. No mapping possible.\n";
} // end if no groups



echo "\n\n";		
echo "Step 5: Check the agents last-modification-ids\n----------------------------------------------------------------------------\n";


// get lastmodes of every server
foreach ($serv as $s) {
        foreach ($serv as $s2) {
                // don't query yourself
                if ($s->getName() == $s2->getName()) continue;
                $lastmod = $s->getLastModID($s2);
				$fromConnId = $servVal[$s2->getName()]['modid'];                	

				if (!is_numeric($fromConnId)) 
            		die("The connector from '{$s2->getName()}' hasn't reported an modification id. \n\nThe installation is NOT complete. Exiting.\n\n");

                if ($lastmod !== false && ($fromConnId - $lastmod) >= 0) {
                	echo "Overview for syncs to '{$s->getName()}' from '{$s2->getName()}'\n";
                	echo "     id saved by agent: $lastmod\n";
                	echo "       id on connector: $fromConnId\n";
                	echo "   unprocessed changes: " . ($fromConnId - $lastmod) . "\n\n";
                }
                // modid resetted on the connector
                else if (($fromConnId - $lastmod) < 0) {
                 	echo "The modificationID for '{$s->getName()}' from '{$s2->getName()}' is greater than the id reported by the connector!!\n";
               		echo "That is not a normal behaviour. The connectors installation was changed. You must reinitialize the ids.\n";
                	 
                	
                	echo "\nDo you want to set: \n";
                	echo "   (0) modificationID to zero (will produce $fromConnId changes when starting the agent!!)\n";
                	echo "   (c) to the last value reported from the connector (no old changes found when starting the agent)\n";
                	echo "   (n) do nothing (exit - installation incomplete)\n";
                	$a = question("Please choose (0/c/n) ", "0nc");
                	if ($a === false) {echo "\n\n"; continue;}

                	// insert into lastmods table
                	if ($a == "0" || $a == "c") {
						if ($a == "c") $a = $fromConnId;
                		echo "inserted into lastmodes: ". 
                			($db->executeStatement("UPDATE lastmodes SET lastmod = $a WHERE servername = '{$s->getName()}' AND targetserver = '{$s2->getName()}'"))
                				?"ok\n\n":"fail\n\n";
                	}               	
                }
                else {
                	echo "The modificationID for '{$s->getName()}' from '{$s2->getName()}' could not be found!!\n";
               		echo "The value reported from '{$s2->getName()}' is: ". $fromConnId . "\n";
                	 
                	
                	echo "\nDo you want to set: \n";
                	echo "   (0) modificationID to zero (will produce $fromConnId changes when starting the agent!!)\n";
                	echo "   (c) to the last value reported from the connector (no old changes found when starting the agent)\n";
                	echo "   (n) do nothing (exit - installation incomplete)\n";
                	$a = question("Please choose (0/c/n) ", "0nc");
                	if ($a === false) {echo "\n\n"; continue;}

                	// insert into lastmods table
                	if ($a == "0" || $a == "c") {
						if ($a == "c") $a = $fromConnId;
                		echo "inserted into lastmodes: ". 
                			($db->executeStatement("INSERT INTO lastmodes (servername,targetserver,lastmod) VALUES('{$s->getName()}','{$s2->getName()}',$a)"))
                				?"ok\n\n":"fail\n\n";
                	}

                }
        }
}




function checkTable($table) {
	global $db;

	echo "   $table";
	echo str_repeat(" ", 13 - strlen($table));
	echo "      : "; 
	if ($db->tableExists($table))
		echo "ok\n"; 
	else 
		die("fail\n\nDatabase structure compromised");
}



function getUserMapping($serverName, $serveruser) {
	global $db;
	return $db->executeStatement("SELECT userid FROM usermapping WHERE servername='$serverName' AND serveruser='$serveruser' LIMIT 1", false);
}

function addUserMapping($serverName, $serveruser, $newId) {
	global $db;
	
	if ($newId === false) {
		// get the next user id! (not threadsafe, but should not be a problem)
		$newId = $db->executeStatement("SELECT MAX(userid)+1 FROM usermapping", false);
		if (! $newId) $newId = 1;
	}

	$db->executeStatement("INSERT INTO usermapping (userid, servername, serveruser) VALUES($newId, '$serverName', '$serveruser')", false);
	return $newId;
}

function checkEmail($email) {
	if(preg_match("/^([a-zA-Z0-9])+([a-zA-Z0-9\._-])*@([a-zA-Z0-9_-])+([a-zA-Z0-9\._-]+)+$/" , $email)){
		list($username,$domain)=split('@',$email);
		if(!checkdnsrr($domain,'MX')) return false;
		return true;
	}
	return false;
}



function question($question, $validanswers) {
	if (!in_array("--auto", $_SERVER['argv']))
	{
	        while(!isset($k) || strpos($k, $validanswers) !== false) {
    		        echo $question;
            		fscanf(STDIN, "%s", $k);
	                $k = trim($k);
	        }
	}
	else
	{
	    $k=$validanswers[0];
	    echo "$question (automaticly choosing default: $k)\n";
	}
        if ($k == "n") return false;
        return $k;
}

// contact servers
function getServerInfo($serverProfile) {
        // connect to the check script
        try {
			$serverProfile->getConnection()->initializeConnection("check");
		}
		catch (AuthenticationZException $ae) {
			die("\n\nAuthentication error: ".$ae->getMessage(). "\n");
		}		

        $stat = array();
        $info = array();
        $users = array();
        if (! ($serverProfile->getConnection()->getRawError())) {
      			$result = $serverProfile->getConnection()->soapCall('check');

                if (!$serverProfile->getConnection()->getRawFault()) {
                        if ($err = $serverProfile->getConnection()->getRawError()) {

                                echo "Could not connect to the server \"{$serverProfile->getName()}\" - Error: $err\n";
                                $response = strip_tags($serverProfile->getConnection()->getRawResponse());

                                if ((preg_match("/HTTP\/1.+(\d{3}.*)/", $response, &$errs) && $errs[1] != 200) ||
                                	 preg_match("/Fatal error:.((\w| |\.|\/|-|,|\(|\)|\[|\]|:|'|=)*)/", $response, &$errs) ||
                                	 preg_match("/Parse error:.((\w| |\.|\/|-|,|\(|\)|\[|\]|:|'|=)*)/", $response, &$errs) ||
                                	 preg_match("/Warning:.((\w| |\.|\/|-|,|\(|\)|\[|\]|:|'|=)*)/", $response, &$errs) ||
									 preg_match("/(.+)$/", $response, &$errs)                                 	  
                                	) {
                                        echo "Server \"{$serverProfile->getName()}\" said: " . $errs[1] . "\n";
                                }
                                
                                die();
                        }
                        else {
                                foreach ($result as $kv) {
                                        $n = $kv['name'];
                                        if ($n == 'modid' || $n == 'contacts' || $n == 'notes' || $n == 'tasks' || $n == 'appointments') {
                                                $stat[$n] = $kv['value'];
                                        }
                                        else if (stripos($n,'usr_') === 0) {
                                        	$users[] = $kv['value'];
                                        }
                                        else {
                                                $info[$n] = $kv['value'];
                                        }
                                }
                                $stat['info'] = $info;
                                $stat['users'] = $users;
                                return $stat;
                        }
                }
                else echo "SOAP-Fault: ". $serverProfile->getConnection()->getRawFault . "\n";
                die();
        }
}

class LogFake {
	private $_err;
	private $_errors;
	
	function LogFake() {
		$this->_err = 0;
		$this->_errors = array();
		$this->_print = true;
	}
	
	function info($string) {
		if ($this->_print) echo $string . "\n";	
	}
	
	function debug($string) {
		if ($this->_print) echo $string . "\n";	
	}
	
	function warn($string) {
		if ($this->_print) echo $string . "\n";	
	}
		
	function error($string) {
		if ($this->_print) {
			$this->_errors[] = $string;
			$this->_err++;	
		}
	}
	
	function hasError() {
		return ($this->_err > 0);
	}
	
	function dbMissing() {
		$dbMiss = false;
		foreach ($this->_errors as $e) {
			if (stripos($e, "not select") !== true ||  stripos($e, "Unknown database") !== true) 
				$dbMiss = true;
		}				
		return $dbMiss;
	}

	function printErrors() {
		foreach ($this->_errors as $e) echo $e ."\n"; 
	}
	
	function disable() {
		$this->_print = false;
	}
}

?>
