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

/**
 * Migrate users from the IMAP server to user Maildirs on this server
 * Usage
 * php migrate-imap.php < list-of-users.csv
 *
 * Where list-of-users contains :
 * username,password,domain,mailserver:port,download-all-folders
 * ben,mypass,atmail.nl,mail.atmail.nl,0
 * Any number of users , each separated by a newline (:port is optional, defaults to 143)
 *
 * The download-all-folders by default is zero; and will only download the users subscribed
 * messages via IMAP ( recommended ) Set download-all-folders to 1 to download all
 * folders on the remote IMAP server including unsubscribed folders
 *
 * -i will switch on interactive mode so that you can see more progress information on the migration
 */

//
// ** SET YOUR CUSTOM DOMAIN MAPPINGS HERE **
//
$domainMappings = array(
	//'olddomain.com' => 'newdomain,com',
	//'olddomain2.com' => 'newdomain2.com'
);

function printError( $string )
{
	
	fwrite(STDOUT, "\033[1;31m" . $string . "\033[0;39m\n");
	
}   

function printSuccess( $string )
{
	
	fwrite(STDOUT, "\033[1;32m" . $string . "\033[0;39m\n");
	
}   

function printWarning( $string )
{
	
	fwrite(STDOUT, "\033[1;33m" . $string . "\033[0;39m\n");
	
}   


ini_set('memory_limit', "64M");
error_reporting(E_ALL & ~E_NOTICE);

// Do not allow access via web
if (isset($_SERVER['GATEWAY_INTERFACE']))
{
	header("Content-Type: text/html");

	print <<<EOF
<h1>Installation Script Error</h1>
<p>The migrate-imap.php script can only be run via the command-line. Please read
<a href='http://support.atmail.com/'>http://support.atmail.com/</a> for more information.
</p>
EOF;
	exit;
}


//Set up application env
require_once('bootloader.php');
require_once('Atmail/Utility.php');

$dbTables = new dbTables();

//get admin cridentials for all these actions
$dbAdapter = Zend_Registry::get('dbAdapter');

	$adminInfo = $dbAdapter->select()
							 ->from($dbTables->AdminUsers)
							 ->where("UMasterAdmin = 1")
							 ->query()
							 ->fetchAll();

$Username = $adminInfo[0]['Username'];

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

//process args
$args = array();

foreach($_SERVER['argv'] as $arg) {
	$args[] = $arg;
}

if( count($args) < 5 )
{
	
	exit("\nThe purpose and usage of this script has changed, use batch-migrate-script.php. (expecting 4 or 6 command line arguments in the order: \"user pass domain host allFolders\")\n\n");
	
}                                                                                         
$user = $args[1];
$pass = $args[2];
$domain = $args[3];
$imapserver = $args[4];
if( isset($args[5]) )
{
	
	$allfolders = $args[5];
	
}
else
{
	
	$allfolders = false;
	
}

// Make sure we are running as root
$ps = shell_exec("ps aux | grep 'php .*migrate-imap.php'");

if (preg_match("/^(.+?)\s+.+?php .*?migrate-imap\.php/m", $ps, $m))
{
    if (trim($m[1]) != 'root')
        exit("\nThe migrate-imap.php must be run as root. Please su to root, and consult the migration details for further information.\n\n");
}
else
{

    fwrite(STDOUT, "\nCould not detect what user migrate-imap.php is running as. If you are not running the migration as root please do so.\n");

}

migrateUser( $user, $pass, $domain, $imapserver, $allfolders );


function migrateUser( $user, $pass, $domain, $imapserver, $allfolders = true )
{
	global $domainMappings;
	global $api;

	printSuccess("Processing $user@$domain ( $imapserver ) ...");
	
	$port = 143;
	$ssl = false;
	
	if (strpos($imapserver, ':') !== false) {
	    list($imapserver, $port) = explode(":", $imapserver);
	    if ($port == 993) {
	        $ssl = 'SSL';
	    }
	}
	
	if( !isset($imapserver) || $imapserver == '' || $imapserver == null || strlen($imapserver) < 1 || strpos($imapserver, '(') !== false)
	{
		
		printError("Bad remote host name found while processing current input: " . $imapserver . '. SKIPPING.' );
		return;
		
	}
	                                
	                                
	if( !isset($domain) || $domain == '' || $domain == null || strlen($domain) < 1 || strpos($domain, '(') !== false || strpos($domain, ':') !== false )
	{
		
		printError("Bad email domain name found while processing current input: " . $domain . '. SKIPPING.' );
		return;
		
	}

	//try connect to remote server and user account
	//$remoteMailStoreConfig = array( 'user' =>  $user . '@' . $domain, 'host' => $imapserver, 'password' => $pass, 'protocol' => 'IMAP');
	$_remoteProtocol = null;
	unset( $_remoteProtocol );
    $_remoteProtocol = new Zend_Mail_Protocol_Imap();
    try
	{
        
		$_remoteProtocol->connect($imapserver, $port, $ssl);
	    printSuccess("Connected to remote host: " . $imapserver );
	
	}
	catch( Exception $e ) 
	{
    
    	printError("Unable to connect to remote host while processing current input:" . $imapserver . ': ' . $e->getMessage() . ' SKIPPING.' );
		return;
    
	}
	
	try
	{
	
		$result = $_remoteProtocol->login($user . '@' . $domain, $pass);
		if( !$result )
		{
			
			throw new Exception("Unable to log in to remote mailbox with username $user@$domain on " . $imapserver );
			
		}
	    printSuccess("Logged in to remote mailbox with username $user@$domain ( $imapserver )");
	
	}
	catch( Exception $e ) 
	{
    
    	try
		{
			
			$result = $_remoteProtocol->login($user, $pass);
			if( !$result )
			{

				throw new Exception("Unable to log in to remote mailbox with username $user or $user@$domain on " . $imapserver );

			}
			printSuccess("Logged in to remote mailbox with username $user on $imapserver");
			
		}
		catch( Exception $e )
		{
			
			printError('Unable to login to remote mailbox while processing current account. SKIPPING.' );
			return;
			
		}

	}
	$remoteMailStore = new Zend_Mail_Storage_Imap($_remoteProtocol);
    $remoteNamespaces = $_remoteProtocol->requestAndResponse('NAMESPACE');
	if( isset($remoteNamespaces[0][0]) && $remoteNamespaces[0][0] == 'NAMESPACE')
	{
		
		$remoteNamespaceDefault = $remoteNamespaces[0][1][0][0];
		$remoteNamespaceDelimiter = $remoteNamespaces[0][1][0][1];
		if( substr($remoteNamespaceDefault,-1) == $remoteNamespaceDelimiter )
		{
			
			$remoteNamespaceDefault = substr($remoteNamespaceDefault,0,-1);
			
		}
		
	}
	else
	{
		
		$remoteNamespaceDefault = '';
		$remoteNamespaceDelimiter = '';
		
	}
	//Zend_Registry::get('log')->debug( "\n" . print_r($remoteNamespaceDefault, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$remoteNamespaceDefault \n");
	//Zend_Registry::get('log')->debug( "\n" . print_r($remoteNamespaceDelimiter, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$remoteNamespaceDelimiter \n");

	// *** MAP OLD DOMAIN TO NEW DOMAIN DURING MIGRATION ** //

	if(array_key_exists(strtolower($domain), $domainMappings))
	{
		$domain = strtolower($domainMappings[$domain]);
	}

	//try connect to local server and user account
	$domainCheckResponse = $api->domainCheck( $domain );
	printSuccess( 'Domain status: ' . $domainCheckResponse['response']['message'] . "\n" );

	if( strpos( $domainCheckResponse['response']['message'], 'exists' ) === false )
	{

		$api->domainCreate( $domain );
		printSuccess( "Domain created\n" );

	}
                                               
	$userRecord = array('name' => $user . '@' . $domain, 'Password' => $pass, 'MailServer' => $imapserver);                                       
	$userCreateResponse = $api->userCreate( $userRecord );
	printSuccess( 'User status: ' . $userCreateResponse['response']['message'] . "\n" );
	$userRecord = $api->userView( $user . '@' . $domain );
	$userPasswordLocal = $userRecord['Password'];
	$localBaseDir = $userRecord['MailDir'];
	
	
	$localConfig = array('namespaceName' => 'remoteServer', 'protocol' => 'IMAP', 'host' => 'localhost', 'user' =>  $user. '@' . $domain, 'password' => $userPasswordLocal);
    $localMailStore = new Atmail_Mail_Storage_Imap( $localConfig );
	printSuccess("Trying to log into local box $user");
	try 
	{
	
		$localMailStore->connect();
		
	}
	catch( Exception $e )
	{
		
		printError('Unable to connect to local mailbox: ' . $user . '. Trying to continue.');
		return;
		
	}
	printSuccess("Logged in to local mailbox with username $user");
	
	$remoteFoldersObject = $remoteMailStore->getFolders();
	
	$remoteFoldersSubscribedRaw = $_remoteProtocol->requestAndResponse('LSUB', array('""', "*") );
	//$remoteFoldersSubscribedRaw = $remoteMailStore->listSubscribedFolders();
	$remoteFoldersSubscribed = array();
	if(gettype($remoteFoldersSubscribedRaw) == 'array')
	{
		
		foreach( $remoteFoldersSubscribedRaw as $remoteFolderSubscribedRaw )
		{
		
			$remoteFoldersSubscribed[] = $remoteFolderSubscribedRaw[3];	
		
		}
		
	}                                   
	
	// go through each folder
	$remoteFolders = new RecursiveIteratorIterator( $remoteFoldersObject, RecursiveIteratorIterator::SELF_FIRST );
	$firstFolder = true;
	foreach ($remoteFolders as $remoteLocalName => $remoteFolderObject) 
	{
		
		if($no_calendar)
		{
			//dont process calendar folder, exchange only
			if($remoteFolderObject->getGlobalName() == 'Calendar')
				continue;
		}
		//dont process shared folders, only subscribed folders except for the first one (INBOX) always process the first one
		if( !$allfolders && !in_array($remoteFolderObject->getGlobalName(), $remoteFoldersSubscribed) && !$firstFolder  )
		{
			
			continue;
			
		}                                                                                 
		$firstFolder = false;
		
		$localFolderPath = '';
		printSuccess('Processing ' . $remoteFolderObject->getGlobalName() );
		//return;
		if (!$remoteFolderObject->isSelectable()) 
		{
			
			echo ' skipping un-selectable folder';
			
		}
		else
		{
			
			try 
			{
				//remove global name will be in remote namespace so need to strip remote namespace and add local namespace
				$remoteGlobalName = $remoteFolderObject->getGlobalName();
				//Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ": \$remoteGlobalName: " . $remoteGlobalName);
				
				//strip remote namespace off if found
				if( substr($remoteGlobalName,0,strlen($remoteNamespaceDefault.$remoteNamespaceDelimiter)) == $remoteNamespaceDefault.$remoteNamespaceDelimiter )
				{
					
					$remoteGlobalNameWithoutNamespace = substr( $remoteGlobalName, strlen($remoteNamespaceDefault.$remoteNamespaceDelimiter) );
					
				}
				else
				{
					
					$remoteGlobalNameWithoutNamespace = $remoteGlobalName;
					
				}
				//special case Inbox, assume already created, and dont recreate
				if( strtoupper($remoteGlobalNameWithoutNamespace) == 'INBOX' )
				{
					
					printSuccess('Inbox folder already exists' );
					$remoteGlobalNameWithoutNamespace = '';
					
				}                                             
				else
				{
					$localDelimiter = $localMailStore->getFolderDelimiterDefault();
					$remoteGlobalNameWithoutNamespace = str_replace($remoteNamespaceDelimiter, $localDelimiter, $remoteGlobalNameWithoutNamespace);
					if( strpos($remoteGlobalNameWithoutNamespace, '.INBOX') === 0 )
						$remoteGlobalNameWithoutNamespace = substr( $remoteGlobalNameWithoutNamespace, strlen('.INBOX') );
					printSuccess('Trying to create local folder ' . $remoteGlobalNameWithoutNamespace );
					$localMailStore->createFolder( $remoteGlobalNameWithoutNamespace );                     
					
				}
				
			}
			catch( Exception $e)
			{
				
				echo $e->getMessage() . "\n";
				if( strpos($e->getMessage(), 'folder already exists') === false && strpos($e->getMessage(), 'cannot create folder') === false )
				{
					
					throw $e;
					
				}
				
			}
			
			//special folder case for Dovecot: if inbox then ./
			$localFolderName = $remoteGlobalNameWithoutNamespace;
			if( substr($localFolderName, 0, 5) == 'INBOX' ) 
			{
	        
				$localFolderName = substr($localFolderName, 6);
	        
			}
            
			$localFolderPath = $localBaseDir . (strlen($localFolderName)>0?'.' . $localFolderName . '/':'') . 'new/';
			//echo "would write file to: " . $localFolderPath . "\n";
			//continue;
			
			//now select this remote folder, and read all its messages, get received time, and append to disk with correct flags on filename
			$remoteMailStore->selectFolder( $remoteFolderObject->getGlobalName() );
			$arrivals = $_remoteProtocol->fetch( 'INTERNALDATE', 1, INF);
			/*
			# Flag "R" (replied): the user has replied to this message.
			# Flag "S" (seen): the user has viewed this message, though perhaps he didn't read all the way through it.
			# Flag "F" (flagged): user-defined flag; toggled at user discretion.
			*/
			$countMessages = $remoteMailStore->countMessages();
			echo $countMessages . " messages found\n";
			if( $countMessages == 0 )
			{
				continue;
			}
			foreach ($remoteMailStore as $id => $message) 
			{
				
				$rawMessage = $remoteMailStore->getRawHeader($id) . Zend_Mime::LINEEND . $remoteMailStore->getRawContent($id);
				
				//get flags
				$flags = $message->getFlags(); 
				$flagAppend = ':2,'; 
				$wasSeen = false;
				foreach ($flags as $flag) 
				{
					switch ($flag) 
					{ 
						case Zend_Mail_Storage::FLAG_ANSWERED:
							$flagAppend .= 'R';
							break; 
						case Zend_Mail_Storage::FLAG_SEEN:
							$flagAppend .= 'S';
							$wasSeen = true;
							break; 
						case Zend_Mail_Storage::FLAG_FLAGGED:
							$flagAppend .= 'F';
							break;
						
					}
					
				}
				
				try
				{
				
					$from = $message->getHeader('from', 'string');
				
				}
				catch( Exception $e )
				{
					
					$from = '';
					
				}
				
				try
				{
				
					$subject = $message->getHeader('subject', 'string');
				
				}
				catch( Exception $e )
				{
					
					$subject = '';
					
				}
				                                                                                                         
				//remove new seen flag if was not seen
				if( !$wasSeen )
				{
					
					//Zend_Registry::get('log')->debug( __METHOD__ . ' #' . __LINE__ . ": flags for:\n'$from': '$subject'");
					//Zend_Registry::get('log')->debug( "\n" . print_r($flags, true) . "\n" . __METHOD__ . ' #' . __LINE__ . ": \$flags \n");
					
					try {
						$response = $remoteMailStore->setFlags($id, array() );						
					} catch ( Exception $e ) {
						echo " Skipping flag for $id ($flags) \n";
					}
					
				}
				
				//echo $id . ' ';
				echo "# $id, Arrived: " . $arrivals[$id] . ", size: " . strlen($rawMessage) . ", from '$from': '$subject' \n";
				
				$sizeAppend = ',S=' . strlen($rawMessage);
				$messageId = generateMaildirId($localFolderPath);
				$absoluteFilename = escapeshellcmd($localFolderPath . $messageId . $sizeAppend . $flagAppend);
				file_put_contents( $absoluteFilename, $rawMessage );
				
				//touch the file to the correct ctime
				$ctime = strtotime($arrivals[$id]);
				touch( $absoluteFilename, $ctime, $ctime );
                
				//set permissions
				chown( $absoluteFilename, 'atmail');
				
			}
			
		}
		echo "\n";
	}
	
	//set correct ownership
	if(strpos(PHP_OS, 'WINNT') === false) 
	{
	
	    fwrite(STDOUT, "Changing ownership of $localBaseDir directory to atmail\n");
	    system('chown -R atmail ' . $localBaseDir);

	}
	
	$localMailStore->close();
	$remoteMailStore->close();
    printSuccess("Done processing $user@$domain on $imapserver \n");

}

function generateMaildirId($folder)
{

	$id;
	$h = "H";
	$pid = "P" . getmypid();

    // Generate key
	for(;;)
	{

		$secs = time();
	    for ($i=0; $i<6; $i++)
		{
	    
			$h .= intval(rand(0,9));
		
		}
		$id = "$secs.$h$pid.localhost";
	    // Generate another key if it already exists
	    if (!file_exists("$folder/$id"))
		{
		 
		   return $id;
		
		}

	}

}
