<?php
/*
* Copyright (c) 2009-2010 ATMAIL. All rights reserved
* See http://atmail.com/license.php for license agreement
*/

/**
 * Test atmail deployment
 *
 * Arguments:
 * php testatmail.php [domain]
 *
 * @author : Ben Duncan
 */

$cwd = dirname(__FILE__) . "/";
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . $cwd . '../../library');
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . $cwd . '../../application/models');
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . $cwd . '../../');
ini_set('include_path', ini_get('include_path') . PATH_SEPARATOR . $cwd . '../../library/Pear/');

//Error reporting
error_reporting(E_ERROR);
ini_set('display_startup_errors', 1);
ini_set('display_errors', 1);

// Lets give the script some ram!
ini_set('memory_limit', -1);

// remove php time limit
set_time_limit(0);

mb_internal_encoding('UTF-8');
iconv_set_encoding("internal_encoding", "UTF-8");//setting gave grief at some point, i suspect it was surrounding Lucene, but needed for ZF mime header decoding. this doesnt work, possible because conflicts with php.ini setting
iconv_set_encoding("input_encoding", "BASE-64");
iconv_set_encoding("output_encoding", "UTF-8");

//Site application root and paths
//all references to directories have / appended to eliminate posibility of direcory reference composition resulting in '/....' as in root 
//i.e. when composing a directory string, all components dont/cant/wont start with a / eliminating the possibility if a resulting string referring to OS root
define('APP_ROOT', dirname(dirname(dirname(__FILE__))) . '' . DIRECTORY_SEPARATOR);

require_once 'Zend/Loader/Autoloader.php';
$loader = Zend_Loader_Autoloader::getInstance();
//$loader->registerNamespace('App_'); //for model namespacing
$loader->setFallbackAutoloader(true);
$loader->suppressNotFoundWarnings(false);

//Load general functions
Zend_Loader::loadFile('Atmail/General.php', null, true);
Zend_Loader::loadFile('Atmail/Exception.php', null, true);

$siteBaseUrl = substr($_SERVER["SCRIPT_NAME"],0,strpos($_SERVER["SCRIPT_NAME"],'index.php'));
Zend_Registry::set( 'siteBaseUrl', $siteBaseUrl );

// Get database configuration    
$dbConfig = new Zend_Config_Ini('config/dbconfig.ini', 'production');
Zend_Registry::set('dbConfig', $dbConfig);
$dbAdapter = Zend_Db::Factory($dbConfig->database);
$dbAdapter->query("SET NAMES 'utf8'");
Zend_Db_Table::setDefaultAdapter($dbAdapter);
Zend_Registry::set('dbAdapter', $dbAdapter); 

//Get main configuration from database
$config = new Atmail_Config_Mysql($dbConfig, true);
Zend_Registry::set('config', $config);

$locale = new Zend_Locale();
Zend_Registry::set('Zend_Locale', $locale);



/* Setup logs */
$log = new Atmail_Log();
$log->addPriority('atmail', 10); 
$log->addPriority('sysadmin', 20); 
$log->addPriority('imap', 30); 
$log->addPriority('firebug', 40); 

$logBasePath = APP_ROOT . 'log' . DIRECTORY_SEPARATOR;
//log errors by default even if not set in config 
$logPath = $logBasePath . 'error.log';
if( !file_exists($logPath))
	touch($logPath);
$writer = new Zend_Log_Writer_Stream($logPath);
$filter = new Zend_Log_Filter_Priority(Zend_Log::INFO, '<'); //will only log entries to this log that are < INFO 
   $writer->addFilter($filter);
$log->addWriter($writer);

// main debug log (no filters set so will log everything) - other logs also for more specific logging
//Only enabled if config debug == true
//INTENTION: only log priority == DEBUG
//INTENTION: only log priority == FIREBUG - i.e. use this to send errors to browser if they are ajax or XML (as in the RPC calls) so that it doesnt mess up the normal responce
//To switch debug logging (log/debug.log) on: 
// INSERT INTO `Config` (`section` ,`keyName` ,`keyValue` ,`keyType`) VALUES ( 'global', 'debug', '1', 'Boolean'); 

ini_set('html_errors', 0);	// ensure html errors are turned off
$debugLogging = ( isset(Zend_Registry::get('config')->global['debug'])? Zend_Registry::get('config')->global['debug']:false);
if( $debugLogging == true || $debugLogging == 'true' || $debugLogging == 1 ) 
{
	//Error reporting - To turn on debugging, set Config.debug=1 in the SQL DB
	error_reporting(E_ALL & ~E_NOTICE);
	ini_set('display_startup_errors', 1);
	ini_set('display_errors', 1);
	
	//no filters set so will log everything
	$logPath = $logBasePath . 'debug.log';
	if( !file_exists($logPath))
		touch($logPath);
	$writer = new Zend_Log_Writer_Stream($logPath);
	$filter = new Zend_Log_Filter_Priority(Zend_Log::INFO, '>='); //will only log entries to this log where priority >= INFO 
	$filter = new Zend_Log_Filter_Priority(Zend_Log::DEBUG, '<='); //will only log entries to this log where priority <= DEBUG 
	$writer->addFilter($filter); 
	
	$log->addWriter($writer);
	$log->debug('');
	$log->debug('------------------------------');
	$log->debug('Hit starting up: ' . time());
	
	//we also set up a logger to log things to firebug console for debugging ajax, RPC, XML responces so that debug content doesnt mess with the body
	//set up a logger to log things to firebug console for debugging ajax, RPC, XML responces so that debug content doesnt mess with the body 
	$writer = new Zend_Log_Writer_Firebug();
	$filter = new Zend_Log_Filter_Priority(40, '=='); //will only log entries to this log where priority == DEBUG
	$writer->addFilter($filter);
	$log->addWriter($writer);
}
else
{
	//switch off conventional error displaying if debug dissabled
	//error_reporting(0);
	ini_set('display_startup_errors', 0);
	ini_set('display_errors', 0);
}
//log IMAP communication only if enabled in config
$logPath = $logBasePath . 'imap.log';
define('IMAP_LOG_PATH', $logPath); //needed by imap debug shutdown logging, because registry gets shut down before imap connection being logged
if( !file_exists($logPath))
	touch($logPath);
try
{
	$writer = new Zend_Log_Writer_Stream($logPath);
	$filter = new Zend_Log_Filter_Priority(30, '=='); //will only log entries to this log that are == IMAP 
    $writer->addFilter($filter);
	$log->addWriter($writer);
}
catch (Exception $e)
{
}

//MUST have at least one writer set
//log sysAdmin communication on by default unless diasbled in Config
//INTENTION: log only where priority == SYSADMIN e.g. quota warnings, login failures (with reasons) 
$logPath = $logBasePath . 'sysadmin.log';
if( !file_exists($logPath))
	touch($logPath);
try
{
	$writer = new Zend_Log_Writer_Stream($logPath);
	$filter = new Zend_Log_Filter_Priority(20, '=='); //will only log entries to this log that are == SYSADMIN 
	$writer->addFilter($filter);
	$log->addWriter($writer);
}
catch (Exception $e)
{

}

Zend_Registry::set('log', $log);
/* log setup complete */

// Add the API model
require_once($cwd . '../../application/models/api.php');
require_once($cwd . '../../application/models/calendar.php');
require_once($cwd . '../../application/models/users.php');
require_once($cwd . '../../application/models/groups.php');

$_SERVER['PHP_AUTH_USER'] = "admin";

$api = new api( array('directApi' => 1) );

// Command line access
if (!isset($_SERVER['GATEWAY_INTERFACE']))
{

	$domain  = ( isset($_SERVER['argv'][1]) ) ? $_SERVER['argv'][1] : '';
	$numtimes = ( isset($_SERVER['argv'][2]) ) ? $_SERVER['argv'][2] : 1;
	$verbose  = ( isset($_SERVER['argv'][3]) ) ? $_SERVER['argv'][3] : 0;
	$su       = ( isset($_SERVER['argv'][4]) ) ? $_SERVER['argv'][4] : false;

} else {

// TODO: Add web

}

// Create the account
$username = get_generate_userid($domain);
$account = "$username@$domain";

if ( empty($username) || empty($domain))
{
	$msg = <<<_EOF
Usage: php testatmail.php [domain] [numtimes] [verbose]

Test the Atmail installation on your server. There are a variety of different functions Atmail performs:

* Email-Server Mode
	- Create an account
	- Send mail from account
	- Receive mail from account
	- Check account via IMAP , read and delete messages

_EOF;

    fwrite(STDOUT, $msg);

	exit;
}

if (!$su && !isset($_SERVER['GATEWAY_INTERFACE']))
{
	if (getProcessUser('testatmail.php') == 'root')
	{
		fwrite(STDOUT, "Detecting script run as root, switching permissions to atmail user\n");

		if (file_exists('/usr/bin/su') || file_exists('/bin/su'))
		{
			fwrite(STDOUT, "Running su atmail -c '$php " . __FILE__ . " $domain $numtimes $verbose' ...\n");
			system("su atmail -c 'php " . __FILE__ . " $domain $numtimes $verbose 1'");
			exit;
		}
		else
		{
			fwrite(STDOUT, "Cannot switch to the atmail username. Please run the testatmail.php script manually as the 'atmail' user\n");
		}
	}
}

if (!($api->dbAdapter->fetchOne("select Hostname from Domains where Hostname=?", array($domain))))
{
	exit("\nThe domain $domain is not hosted by Atmail\n\n");
}

if(!$web)
	fwrite(STDOUT, "Running Email-Server tests for $domain.\n");

$testok = 0;
$numtests = 5;


$password = "testing";
createAccount($account, $password);

$user = users::get($account);

try
{
	$group = groups::get( $user['Ugroup'] );
	if($group == false)
	{
		$group = groups::get( 'default' );
	}
}
catch( exception $e )
{
	$group = groups::get( 'default' );			
}


// Add one more test if we are the AV scanner
if($config->global['virus_scanner'])
$numtests++;

// Add one more test if spamassassin enabled
if( $config->exim['filter_sa_enable'] == true)
$numtests++;

if($group['Calendar'])
	$numtests++;

sendTestMsg($account);

viewIMAP($account, $domain, $password, $mode, $mailtype);

viewPOP3($account, $domain, $password, $mode, $mailtype);
   if( $config->global['virus_scanner'] == true )
{
	
	sendTestVirus($account);
	
}

//if($config->global['filter_sa_enable'] && !$config->global['filter_skip_trusted'])
if( $config->exim['filter_sa_enable'] == true )
{
   
	sendTestSpam($account);
	
}

if($group['Calendar'])
{
	
	viewCalendar($account);
	
}

deleteaccount($account);



$count = 1;

if ($testok > 0)
{
	$num = $testok / $numtests * 100;
	$num = intval($num);

if($web)	{
print<<<_EOF
  <tr>
    <td>
	<br>
	  <font class="text">Test Summary</font>
	  <table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
		  <td class="option">
		    <font class="option">$testok tests passed out of $numtests</font>
		  </td>
		  <td class="option">
		    <font class="option"> ( $num % )</font>
		  </td>
		</tr>
      </table>
	</td>
  </tr>
</table>

<script language="Javascript">
loadingdiv.style.display='none';
</script>
_EOF;
} else	{
fwrite(STDOUT, "\n--- $testok tests passed out of $numtests ( $num % )\n\n");
}

} else
	fwrite(STDOUT, "ALL $numtests TESTS FAILED!\n");

exit;

###########
# Functions
###########

function createAccount($account, $pass)
{
	global $testok, $db, $config, $web, $api;

	list($username, $domain) = explode('@', $account);

	// Add user
	$userData = array();
	$userData['name']			= $account;
	$userData['username']		= $username;
	$userData['Password']		= $pass;
	$userData['Domain']			= $domain;
	$userData['Ugroup']			= 'default';
	$userData['skipWelcome']	= 1;
	$userData['lifetime']		= 120;
	
	fwrite(STDOUT, "Adding user {$userData['name']}...");

	try {
	 $res = $api->userCreate($userData);		
	} catch( Exception $e ) {
	    $report = "ERROR: " . $e->getMessage();
	}

	if (!empty($res['response']['id'])) {
	    $report = "OK";
		$testok++;
	}

if($web)	{
print<<<_EOF
<tr>
<td>
<br>
<font class="text">Account Signup Test</font>
<table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
<td class="option">
<font class="option">Testing account $account</font>
</td>
<td class="option">
<font class="option">$report</font>
</td>
</tr>
</table>
</td>
</tr>
_EOF;
} else	{
print "\n--- Creating account: $report\n";
}


}


function deleteAccount($account)
{
	global $config, $testok, $web, $api;
	
	try {
	 $res = $api->userDelete($account);
		$report = "OK";
		$testok++;
	} catch( Exception $e ) {
	    $report = "ERROR: " . $e->getMessage();
	}
	
if($web)	{
print<<<_EOF
<tr>
<td>
<br>
<font class="text">Delete Account</font>
<table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
<td class="option">
<font class="option">Removing $account</font>
</td>
<td class="option">
<font class="option">$report</font>
</td>
</tr>
</table>
</td>
</tr>
_EOF;
} else	{
print "\n--- Delete account: $report\n";
}
	
}



#### Send an email message
function sendTestMsg($account)
{
	global $config, $testok, $web;
	
	$transport = new Zend_Mail_Transport_Smtp($config->global['smtphost']);
	$mail = new Zend_Mail();

	$message = "Hello testing this message to $account ...";
	
	$mail->setBodyText($message);
	$mail->setFrom($account);
	
	$mail->addTo($account);
	$mail->setSubject("Testing mail to $account");

	try {
		$mail->send($transport);

		$testok++;
		$report = "OK";

	} catch(Exception $e) {
		$report = "ERROR: " . $e->getMessage();
		
	}
	
	sleep(4);

if($web)	{
print<<<_EOF
<tr>
<td>
<br>
<font class="text">Message Sending</font>
<table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
<td class="option">
<font class="option">Status</font>
</td>
<td class="option">
<font class="option">$report</font>
</td>
</tr>
</table>
</td>
</tr>
_EOF;
} else	{
print "\n--- Testing Mail Send: $report\n";
}

	return;
}

function sendTestVirus($account)
{
	global $config, $testok, $web;


	$transport = new Zend_Mail_Transport_Smtp($config->global['smtphost']);
	$mail = new Zend_Mail();

	$message = "Hello testing this message to $account ...";
	
// Break up the EICAR so it doesn't break if really AV scanned!
$eicar = 'X5O!P%@AP[4\PZX54(P^)';
$eicar .= '7CC)7}$EICAR-STANDARD';
$eicar .= '-ANTIVIRUS-TEST' . '-FILE!$H+H*';

$mail->setBodyText($eicar);
$mail->setFrom($account);

$mail->addTo($account);
$mail->setSubject("EICAR test to $account");

try {
	$mail->send($transport);

	$report = "FAIL";
	
} catch(Exception $e) {
	$report = "OK";
	$testok++;
}


if($web)	{
print<<<_EOF
<tr>
<td>
<br>
<font class="text">Virus Scanner</font>
<table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
<td class="option">
<font class="option">Status</font>
</td>
<td class="option">
<font class="option">$report</font>
</td>
</tr>
</table>
</td>
</tr>
_EOF;
} else	{
print "\n--- Testing SMTP Virus scanner: $report\n";
}

	return;
}

function sendTestSpam($args, $skip=null)
{
	$account = $args;
	global $config, $testok, $web;

// Break up the GTUBE so it doesn't break if really SPAM scanned!
$gtube = 'XJS*C4JDBQADN1.NSBN3*';
$gtube .= '2IDNEN*GTUBE-STANDARD-';
$gtube .= 'ANTI-UBE-TEST-EMAIL*C.34X';

	$msg = "From: $account\r\nTo: $account\r\nSubject: GTUBE test to $account" . "\r\n\r\n" . $gtube;

    $fh2 = fopen("{$config->global['user_dir']}/tmp/spam.msg", 'w');
	fwrite($fh2, $msg);
	fclose($fh2);

	$output = shell_exec("/usr/local/atmail/spamassassin/bin/spamc < {$config->global['user_dir']}/tmp/spam.msg");

	unlink("{$config->global['user_dir']}/tmp/spam.msg");

	if (preg_match('/X-Spam-Status: Yes/i', $output))	{
		$report = "OK";
		$testok++;
	}

	if(!$report)	{
		$report = "FAIL";
	}

if($web)	{
print<<<_EOF
<tr>
<td>
<br>
<font class="text">Anti-Spam Scanner</font>
<table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
<td class="option">
<font class="option">Status</font>
</td>
<td class="option">
<font class="option">$report</font>
</td>
</tr>
</table>
</td>
</tr>
_EOF;
} else	{
print "\n--- Testing Anti-Spam scanner: $report\n";
}

	return;
}

function viewPOP3($account, $domain, $password, $mode, $mailtype)
{
	global $testok, $verbose, $web;
		
	
	try {

		$mail = new Zend_Mail_Storage_Pop3(array('host'     => '127.0.0.1',
		                                         'user'     => $account,
		                                         'password' => 'testing'));

		$message = $mail->getMessage(1);

		if($message->subject == "Testing mail to $account")	{
		$report = "OK";
		$testok++;
		}
		
	} catch(Exception $e) {
		$report = "FAIL - Cannot connect to POP3 server";
	}	


if($web)	{
print<<<_EOF
  <tr>
    <td>
	<br>
	  <font class="text">POP3 Access</font>
	  <table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
		  <td class="option">
		    <font class="option">Status</font>
		  </td>
		  <td class="option">
		    <font class="option">$report</font>
		  </td>
		</tr>
      </table>
	</td>
  </tr>
_EOF;
} else	{
print "\n--- Accessing POP3: $report\n";
}

	return $results;
}

function viewIMAP($account, $domain, $password, $mode, $mailtype)
{
	global $testok, $verbose, $web;
		
	
	try {

		$mail = new Zend_Mail_Storage_Imap(array('host'     => '127.0.0.1',
		                                         'user'     => $account,
		                                         'password' => 'testing'));
		
		$message = $mail->getMessage(1);

		if($message->subject == "Testing mail to $account")	{
		$report = "OK";
		$testok++;
		}
		
	} catch(Exception $e) {
		$report = "FAIL - Cannot connect to IMAP server";
	}	


if($web)	{
print<<<_EOF
  <tr>
    <td>
	<br>
	  <font class="text">IMAP Access</font>
	  <table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
		  <td class="option">
		    <font class="option">Status</font>
		  </td>
		  <td class="option">
		    <font class="option">$report</font>
		  </td>
		</tr>
      </table>
	</td>
  </tr>
_EOF;
} else	{
print "\n--- Accessing IMAP: $report\n";
}

	return $results;
}

function viewCalendar($account) {
	global $testok, $config;
	
	try {

		// Turning on Zend's 'unit test mode', so we won't get errors in relation to 'output already started'.
		Zend_Session::$_unitTestEnabled = true;	
		$calendar = new calendar(array('TimeZone' => $config->defaultUserSettings['TimeZone'], 'URL' => $config->defaultUserSettings['CalDavUrl'], 'Account' => $account, 'Password' => "testing", 'Calendar' => "calendar" ));
		$number_of_tries = 5;
		$support = '';
		while($number_of_tries > 0 && $support != 'at')
		{
			$support = $calendar->ping();
			if($support != 'at')
			{
				// calendar server isn't responding.
				// this COULD be a slow machine and it hasn't 
				// started yet
				// so lets sleep for a while, then retry
				sleep(5);
			}
			$number_of_tries--;
		}
	
		if($support != 'at') {
			$report = "FAIL - Calendar server not responding";
		} else {
			$report = "OK";
			$testok++;		
		}
	
	}
	catch(Exception $e)
	{
		$report = "FAIL - Cannot connect to Calendar server\nDetails : " . $e->getMessage();
	}

	if($web)	{
print<<<_EOF
  <tr>
    <td>
	<br>
	  <font class="text">Calendar Access</font>
	  <table class="option" cellspacing="0" cellpadding="0" width='100%'>
<tr class="odd" height='22'>
		  <td class="option">
		    <font class="option">Status</font>
		  </td>
		  <td class="option">
		    <font class="option">$report</font>
		  </td>
		</tr>
      </table>
	</td>
  </tr>
_EOF;
} else	{
print "\n--- Accessing Calendar Server: $report\n";
}

}
/*
Private functions
*/

function get_generate_userid($domain)	{
	global $api;

	$user = rand(0,9000) . getmypid();

	if($api->dbAdapter->fetchOne("select Account from UserSession where Account=?", "$user\@$domain"))	{
		return generate_userid();
	} else	{
		return $user;
	}

}

function getProcessUser($proc)
{
        $process = preg_quote(basename($proc));
        $line = `ps aux | grep $process | grep -v sudo`;

        if (preg_match("/^(.+?)\s+.+?php .*?$process/", $line, $m))
                return $m[1];

        return '';
}
