<?php
/***********************************************
* File	  :   imap.php
* Descr	 :   This backend is based on
*			   'BackendDiff' and implements an
*			   IMAP interface
* Original by Zarafa Deutschland GmbH released under GPLv2. 
* Modifications by Atmail published under GPLv2 
* Consult LICENSE file for details
************************************************/


include_once('diffbackend.php');

// The is an improved version of mimeDecode from PEAR that correctly
// handles charsets and charset conversion
include_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . '../include/mimeDecode.php');
require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . '../include/atmail_RFC822.php');
//require_once( dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'bootloader.php');

class BackendIMAP extends BackendDiff 
{

    static public $lastBodyType = SYNC_AIRSYNCBASE_BODYPREFERENCE_TYPE_PLAIN;
	/* Called to logon a user. These are the three authentication strings that you must
	 * specify in ActiveSync on the PDA. Normally you would do some kind of password
	 * check here. Alternatively, you could ignore the password here and have Apache
	 * do authentication via mod_auth_*
	 */
	function Logon($username, $domain, $password) 
	{

		$this->_wasteID = false;
		$this->_sentID = false;
		
		// Check if we are a local domain, or external
		$domains = new domains();
		
		if( empty($domain) )
		{
			
			$arr = explode('@', $username);
			$domain = $arr[1];
			
		}

		//debugLog( "Checking if $domain is local for $username ");
		
		// If we are a local account, connect to the localhost IMAP
		if($domains->exists($domain)) {
			$imapServer = IMAP_SERVER;
			//debugLog( "Domain is local using " . $imapServer );
		} else {
			// Get the remote users IMAP server, as defined on the login page
			$imapServer = users::getMailserver($username);
			//debugLog( "Domain is remote using " . $imapServer );
		}

		// Set the connection string
		$this->_server = "{" . $imapServer . ":" . IMAP_PORT . "/imap" . IMAP_OPTIONS . "}";			

		if (!function_exists("imap_open"))
        {
			//debugLog("ERROR BackendIMAP : PHP-IMAP module not installed!!!!!");
        }

		if( strpos($username, '@') === false && strlen($domain) > 0 )
			$username = $username . '@' . $domain;
		
		//first check if user group has push enabled
		try 
		{
			
			$userRecord = users::get($username);
			$groupRecord = groups::get($userRecord['Ugroup']);
				
		}
		catch( Exception $e )
		{
			
			//debugLog("Access denied: " . imap_last_error());
			return false;
			
		}

		if( $groupRecord['PushSupport'] != '1')
		{
			
			//debugLog("Access denied: Push not enabled" . imap_last_error());
			return false;
			
		}
	
		// open the IMAP-mailbox
		$this->_mbox = @imap_open($this->_server , $username, $password, OP_HALFOPEN);
		$this->_mboxFolder = "";
        
		if ($this->_mbox) 
		{
			
			//debugLog("IMAP connection opened sucessfully ");
			$this->_username = $username;
			$this->_domain = $domain;
			// set serverdelimiter
			 $this->_serverdelimiter = $this->getServerDelimiter();
			return true;
			
		}
		else
		{
		
			//debugLog("IMAP can't connect: " . imap_last_error());
			return false;
		
		}
		
	}

	/* Called before shutting down the request to close the IMAP connection
	 */
	function Logoff() 
	{

		if ($this->_mbox) 
		{

			// list all errors
			$errors = imap_errors();
			if (is_array($errors)) 
			{
				foreach ($errors as $e)
                {
                    //debugLog("IMAP-session-errors: $e");
                }
			}
			@imap_close($this->_mbox);
			//debugLog( __FILE__ . "#" . __LINE__ . ": IMAP connection closed." );

		}

	}

	/* Called directly after the logon. This specifies the client's protocol version
	 * and device id. The device ID can be used for various things, including saving
	 * per-device state information.
	 * The $user parameter here is normally equal to the $username parameter from the
	 * Logon() call. In theory though, you could log on a 'foo', and then sync the emails
	 * of user 'bar'. The $user here is the username specified in the request URL, while the
	 * $username in the Logon() call is the username which was sent as a part of the HTTP
	 * authentication.
	 */
	function Setup($user, $devid, $protocolversion) 
	{

		//debugLog( __METHOD__ . ' #' . __LINE__ );
		$this->_user = $user;
		$this->_devid = $devid;
		$this->_protocolversion = $protocolversion;
		return true;

	}

	function GetWasteBasket() 
	{
		return $this->_wasteID;
	}

	function GetFolderList() 
	{
	
		$folders = array();
		$list = @imap_getmailboxes($this->_mbox, $this->_server, "*");
		
		if (is_array($list)) 
		{
			// reverse list to obtain folders in right order
			$list = array_reverse($list);
			foreach ($list as $val) 
			{
				
				$box = array();
                // cut off serverstring
				$folderGlobalUTF7 = substr($val->name, strlen($this->_server));
				$box["id"] = str_replace($val->delimiter, ".", $folderGlobalUTF7);
				$fhir = explode(".", $box["id"]);
				if( count($fhir) > 1 ) 
				{
				
					$box["mod"] = array_pop($fhir); // mod is last part of path
					$box["parent"] = implode(".", $fhir); // parent is all previous parts of path
				
				}
				else 
				{
				
					$box["mod"] = $box["id"];
					$box["parent"] = "0";
				
				}
				$folders[]=$box;
			}
		}
		else
        {
			//debugLog("GetFolderList: imap_list failed: " . imap_last_error());
        }
		return $folders;

	}

	/* Return folder stats. This means you must return an associative array with the
	 * following properties:
	 * "id" => The server ID that will be used to identify the folder. It must be unique, and not too long
	 *		 How long exactly is not known, but try keeping it under 20 chars or so. It must be a string.
	 * "parent" => The server ID of the parent of the folder. Same restrictions as 'id' apply.
	 * "mod" => This is the modification signature. It is any arbitrary string which is constant as long as
	 *		  the folder has not changed. In practice this means that 'mod' can be equal to the folder name
	 *		  as this is the only thing that ever changes in folders. (the type is normally constant)
	 */
	function StatFolder($id) 
	{
	
		//$folder = $this->GetFolder($id);
		$stat = array();
		$stat["id"] = $id;
		$fhir = explode(".", $id);
		if( count($fhir) > 1 ) 
		{
		
			$stat["mod"] = array_pop($fhir); // mod is last part of path
			$stat["parent"] = implode(".", $fhir); // parent is all previous parts of path
		
		}
		else 
		{
		
			$stat["mod"] = $id;
			$stat["parent"] = "0";
		
		}
		return $stat;

	}

	/* GetFolder should return an actual SyncFolder object with all the properties set. Folders
	 * are pretty simple really, having only a type, a name, a parent and a server ID.
	 */

	function GetFolder($id) 
	{
	
		$folder = new SyncFolder();
		$folder->serverid = $id;

		// explode hierarchy
		$fhir = explode(".", $id);

		// compare on lowercase strings
		$lid = strtolower($id);

		if($lid == "inbox") 
		{
			$folder->parentid = "0"; // Root
			$folder->displayname = "Inbox";
			$folder->type = SYNC_FOLDER_TYPE_INBOX;
		}
		// Zarafa IMAP-Gateway outputs
		else if($lid == "drafts") 
		{
			$folder->parentid = "0";
			$folder->displayname = "Drafts";
			$folder->type = SYNC_FOLDER_TYPE_DRAFTS;
		}
		else if($lid == "trash") 
		{
			$folder->parentid = "0";
			$folder->displayname = "Trash";
			$folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
			$this->_wasteID = $id;
		}
		else if($lid == "sent" || $lid == "sent items" || $lid == IMAP_SENTFOLDER) 
		{
			$folder->parentid = "0";
			$folder->displayname = "Sent";
			$folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
			$this->_sentID = $id;
		}
		// courier-imap outputs
		else if($lid == "inbox.drafts") 
		{
			$folder->parentid = $fhir[0];
			$folder->displayname = "Drafts";
			$folder->type = SYNC_FOLDER_TYPE_DRAFTS;
		}
		else if($lid == "inbox.trash") 
		{
			$folder->parentid = $fhir[0];
			$folder->displayname = "Trash";
			$folder->type = SYNC_FOLDER_TYPE_WASTEBASKET;
			$this->_wasteID = $id;
		}
		else if($lid == "inbox.sent") 
		{
			$folder->parentid = $fhir[0];
			$folder->displayname = "Sent";
			$folder->type = SYNC_FOLDER_TYPE_SENTMAIL;
			$this->_sentID = $id;
		}

		// define the rest as other-folders
		else 
		{
			if (count($fhir) > 1) 
			{															  
				$folder->displayname = mb_convert_encoding(array_pop($fhir), 'UTF-8', 'UTF7-IMAP');
				$folder->parentid = implode(".", $fhir);
			}
			else 
			{
				$folder->displayname = mb_convert_encoding($id, 'UTF-8', 'UTF7-IMAP');
				$folder->parentid = "0";
			}
			$folder->type = SYNC_FOLDER_TYPE_OTHER;
		}

		return $folder;

	}

	/* Creates or modifies a folder
	 * "folderid" => id of the parent folder
	 * "oldid" => if empty -> new folder created, else folder is to be renamed
	 * "displayname" => new folder name (to be created, or to be renamed to)
	 * "type" => folder type, ignored in IMAP
	 *
	 */
	function ChangeFolder($parentidnew, $oldid, $displayname, $type)
	{
		
		//debugLog("ChangeFolder: (parent: '$parentidnew'  oldid: '$oldid'  displayname: '$displayname'  type: '$type')");

		// go to parent mailbox
		$this->imap_reopenFolder($parentidnew);

		// build name for new mailbox
		$folderIdOld = str_replace(".", $this->_serverdelimiter, $oldid);
		
		$folderIdNewUTF8 = str_replace(".", $this->_serverdelimiter, $parentidnew) . $this->_serverdelimiter . $displayname;
		$folderIdNewUTF7 = mb_convert_encoding($folderIdNewUTF8, 'UTF7-IMAP', 'UTF-8'); 
		
		$csts = false;
		// if $id is set => rename mailbox, otherwise create
		if( $oldid )
		{

			// rename doesn't work properly with IMAP
			// the activesync client doesn't support a 'changing ID'
			//the only way to work around this is to move to static unique IDs for folders and manaintain app wide
			//$csts = imap_renamemailbox( $this->_mbox, $this->_server . $folderIdOld, $this->_server . $folderIdNewUTF7 );
			
		}
		else
		{
			
			//debugLog( __METHOD__ . ' #' . __LINE__ . ': creating folder: ' . $folderIdNewUTF7 );
			$csts = @imap_createmailbox( $this->_mbox, $this->_server . $folderIdNewUTF7 );                          
		
		}
		if ($csts) 
			return $this->StatFolder($folderIdNewUTF7);
		else
			return false;
	
	}

	function GetMessageList($folderid, $cutoffdate) 
	{
	
		//debugLog("IMAP-GetMessageList: (fid: '$folderid'  cutdate: '$cutoffdate' )");
		$messages = array();
		$this->imap_reopenFolder($folderid, true);

		$sequence = "1:*";
		if ($cutoffdate > 0) 
		{
			$search = @imap_search($this->_mbox, "SINCE ". date("d-M-Y", $cutoffdate));
			if ($search !== false)
				$sequence = implode(",", $search);
		}
		$overviews = @imap_fetch_overview($this->_mbox, $sequence);

		if (!$overviews)
        {
			//debugLog("IMAP-GetMessageList: Failed to retrieve overview");
        }
		else
		{
			foreach($overviews as $overview) 
			{
				$date = "";
				$vars = get_object_vars($overview);
				if (array_key_exists( "date", $vars)) 
				{
					// message is out of range for cutoffdate, ignore it
					if(strtotime($overview->date) < $cutoffdate) continue;
					$date = $overview->date;
				}
				else
					$date = '0';

				// cut of deleted messages
				if (array_key_exists( "deleted", $vars) && $overview->deleted)
					continue;

				if (array_key_exists( "uid", $vars)) 
				{
					$message = array();
					$message["mod"] = $date;
					$message["id"] = $overview->uid;
					// 'seen' aka 'read' is the only flag we want to know about
					$message["flags"] = 0;

					if(array_key_exists( "seen", $vars) && $overview->seen)
						$message["flags"] = 1;

					array_push($messages, $message);
				}
			}
		}
		return $messages;
	}
	
	/* StatMessage should return message stats, analogous to the folder stats (StatFolder). Entries are:
	 * 'id'	 => Server unique identifier for the message. Again, try to keep this short (under 20 chars)
	 * 'flags'	 => simply '0' for unread, '1' for read
	 * 'mod'	=> modification signature. As soon as this signature changes, the item is assumed to be completely
	 *			 changed, and will be sent to the PDA as a whole. Normally you can use something like the modification
	 *			 time for this field, which will change as soon as the contents have changed.
	 */

	function StatMessage($folderid, $id) 
	{

		//debugLog(__FILE__ . "#" . __LINE__ . ": IMAP-StatMessage: (fid: '$folderid'  id: '$id' )");

		$this->imap_reopenFolder($folderid);
		$overview = @imap_fetch_overview( $this->_mbox , $id , FT_UID);

		if (!$overview) 
		{
			//debugLog("IMAP-StatMessage: Failed to retrieve overview: ". imap_last_error());
			return false;
		}

		else 
		{
		
			// check if variables for this overview object are available
			$vars = get_object_vars($overview[0]);

			// without uid it's not a valid message
			if (! array_key_exists( "uid", $vars)) 
				return false;

			$entry = array();
			$entry["mod"] = (array_key_exists( "date", $vars)) ? $overview[0]->date : "0";
			$entry["id"] = $overview[0]->uid;
			// 'seen' aka 'read' is the only flag we want to know about
			$entry["flags"] = 0;

			if(array_key_exists( "seen", $vars) && $overview[0]->seen)
				$entry["flags"] = 1;

			return $entry;

		}

	}

	/* GetMessage should return the actual SyncXXX object type. You may or may not use the '$folderid' parent folder
	 * identifier here.
	 * Note that mixing item types is illegal and will be blocked by the engine; ie returning an Email object in a
	 * Tasks folder will not do anything. The SyncXXX objects should be filled with as much information as possible,
	 * but at least the subject, body, to, from, etc.
	 */
	function GetMessage($folderid, $id, $truncsize, $mimesupport = 0) 
	{
        global $protocolversion;

		//debugLog("IMAP-GetMessage: (fid: '$folderid'  id: '$id'  truncsize: $truncsize)");

		// Get flags, etc
		$stat = $this->StatMessage($folderid, $id);

		$mail = false;
		
		if ($stat) 
		{
			$this->imap_reopenFolder($folderid);
			$mail = @imap_fetchheader($this->_mbox, $id, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $id, FT_PEEK | FT_UID);

			$mobj = new Mail_mimeDecode($mail);
			$message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $mail, 'crlf' => "\n", 'charset' => 'utf-8'));

			$output = new SyncMail();


			if($protocolversion >= 12)
			{
				$body = $this->getBody($message, BackendIMAP::$lastBodyType);
			}
			else
			{
				$body = $this->getBody($message);
			}

			
			// truncate body, if requested
			if(strlen($body) > $truncsize) 
			{
			
				$body = utf8_truncate($body, $truncsize);
				$output->bodytruncated = 1;
			
			} 
			else
			{
			
				$output->bodytruncated = 0;
			
			}
            if($protocolversion >= 12)
            {
				if(BackendIMAP::$lastBodyType == SYNC_AIRSYNCBASE_BODYPREFERENCE_TYPE_PLAIN)
				{
	                $body = str_replace("\n","\r\n", str_replace("\r","",$body));
				}

                $syncHtmlBody = new SyncHtmlBody();
	            if(BackendIMAP::$lastBodyType != SYNC_AIRSYNCBASE_BODYPREFERENCE_TYPE_MIME)
	            {
	                $syncHtmlBody->bodydata = $body;
	            }
	            else
	            {
	                $syncHtmlBody->bodydata = $mail;
	            }
	            $syncHtmlBody->bodyestimateddatasize = strlen($body);
	            $syncHtmlBody->bodytype = BackendIMAP::$lastBodyType;
	            $syncHtmlBody->bodytruncated = $output->bodytruncated;
	            $output->body = $syncHtmlBody;
            }
            else
            {

                $body = str_replace("\n","\r\n", str_replace("\r","",$body));
                $output->bodysize = strlen($body);
                $output->body = $body;
            }
            
			$output->datereceived = isset($message->headers["date"]) ? strtotime($message->headers["date"]) : null;
			foreach( array('to', 'from', 'cc', 'reply-to') as $k )
			{
				if( array_key_exists( $k, $message->headers) )
				{
					if( is_array($message->headers[$k]) )
						$message->headers[$k] = implode(', ', $message->headers[$k]);
				}
				else
					$message->headers[$k] = null;
			}
			foreach( $message->headers as $k => &$v )
			{
				
				if( !in_array($k, array('subject', 'to', 'x-priority', 'from', 'cc', 'reply-to')) )
					continue;
				$charset = mb_detect_encoding($v, '7bit, ASCII, WINDOWS-1252, UTF7, UTF8, ISO-8859-1', true);
				$v = iconv($charset,'UTF-8//IGNORE//TRANSLIT', $v);
				
			}
			$output->displayto = $message->headers["to"];
			$output->importance = isset($message->headers["x-priority"]) ? preg_replace("/\D+/", "", $message->headers["x-priority"]) : null;
			$output->messageclass = "IPM.Note";
			$output->subject = isset($message->headers["subject"]) ? $message->headers["subject"] : "";
			$output->read = $stat["flags"];
			$output->to = $message->headers["to"];
			$output->cc = $message->headers["cc"];
			$output->from = $message->headers["from"];
			$output->reply_to = $message->headers["reply-to"];

			// Attachments are only searched in the top-level part
            if($protocolversion < 12 || BackendIMAP::$lastBodyType != SYNC_AIRSYNCBASE_BODYPREFERENCE_TYPE_MIME)
            {
			$n = 0;
			if(isset($message->parts)) 
			{
				foreach($message->parts as $part) 
				{
					
					if( isset($part->disposition) && ($part->disposition == "attachment" || $part->disposition == "inline"))
					{
						
						$attachment = new SyncAttachment();

						if (isset($part->body))
							$attachment->attsize = strlen($part->body);

						if(isset($part->d_parameters['filename']))
							$attname = $part->d_parameters['filename'];
						else if(isset($part->ctype_parameters['name']))
							$attname = $part->ctype_parameters['name'];
						else if(isset($part->headers['content-description']))
							$attname = $part->headers['content-description'];
						else $attname = "unknown attachment";

						$attachment->displayname = $attname;
						$attachment->attname = $folderid . ":" . $id . ":" . $n;
						$attachment->attmethod = 1;
						//$attachment->attoid = isset($part->headers['content-id']) ? $part->headers['content-id'] : "";
						array_push($output->attachments, $attachment);
					}
					$n++;
				}
			}
            }
            else
            {
                // mime

            }
			// unset mimedecoder & mail
			unset($mobj);
			unset($mail);
			return $output;
		}
        else
        {
          
        }
		return false;
	}

	/* Should return attachment data for the specified attachment. The passed attachment identifier is
	 * the exact string that is returned in the 'AttName' property of an SyncAttachment. So, you should
	 * encode any information you need to find the attachment in that 'attname' property.
	 */
	function GetAttachmentData($attname) 
	{
		
		//debugLog("getAttachmentDate: (attname: '$attname') ");
        list($folderid, $id, $part) = explode(":", $attname);

		$this->imap_reopenFolder($folderid);
		$mail = @imap_fetchheader($this->_mbox, $id, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $id, FT_PEEK | FT_UID);

		$mobj = new Mail_mimeDecode($mail);
		$message = $mobj->decode(array('decode_headers' => true, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $mail, 'crlf' => "\n", 'charset' => 'utf-8'));

		if( isset($message->parts[$part]->body) )
			print $message->parts[$part]->body;

		// unset mimedecoder & mail
		unset($mobj);
		unset($mail);
		return true;
		
	}

	/* This should change the 'read' flag of a message on disk. The $flags
	 * parameter can only be '1' (read) or '0' (unread). After a call to
	 * SetReadFlag(), GetMessageList() should return the message with the
	 * new 'flags' but should not modify the 'mod' parameter. If you do
	 * change 'mod', simply setting the message to 'read' on the PDA will trigger
	 * a full resync of the item from the server
	 */
	function SetReadFlag($folderid, $id, $flags) 
	{
		
		//debugLog("IMAP-SetReadFlag: (fid: '$folderid'  id: '$id'  flags: '$flags' )");

		$this->imap_reopenFolder($folderid);

		if ($flags == 0) 
		{
			// set as "Unseen" (unread)
			$status = @imap_clearflag_full ( $this->_mbox, $id, "\\Seen", ST_UID);
		}
		else
		{
			// set as "Seen" (read)
			$status = @imap_setflag_full($this->_mbox, $id, "\\Seen",ST_UID);
		}

		//debugLog("IMAP-SetReadFlag -> set as " . (($flags) ? "read" : "unread") . "-->". $status);

		return $status;
	
	}

	/* This function is called when the user moves an item on the PDA. You should do whatever is needed
	 * to move the message on disk. After this call, StatMessage() and GetMessageList() should show the items
	 * to have a new parent. This means that it will disappear from GetMessageList() will not return the item
	 * at all on the source folder, and the destination folder will show the new message
	 *
	 */
	function MoveMessage($folderid, $id, $newfolderid) 
	{
		
		//debugLog("IMAP-MoveMessage: (sfid: '$folderid'  id: '$id'  dfid: '$newfolderid' )");

		$this->imap_reopenFolder($folderid);

		// read message flags
		$overview = @imap_fetch_overview ( $this->_mbox , $id, FT_UID);
        
		if (!$overview) 
		{
			//debugLog("IMAP-MoveMessage: Failed to retrieve overview");
			return false;
		}
		else 
		{
			// move message
			$s1 = imap_mail_move($this->_mbox, $id, str_replace(".", $this->_serverdelimiter, $newfolderid), FT_UID);
            
			// delete message in from-folder
			$s2 = imap_expunge($this->_mbox);
			
			// open new folder
			$this->imap_reopenFolder($newfolderid);

			// remove all flags
			$s3 = @imap_clearflag_full ($this->_mbox, $id, "\\Seen \\Answered \\Flagged \\Deleted \\Draft", FT_UID);
			
			$newflags = "";
			if($overview[0]->seen) $newflags .= "\\Seen";
			if($overview[0]->flagged) $newflags .= " \\Flagged";
			if($overview[0]->answered) $newflags .= " \\Answered";
			$s4 = @imap_setflag_full ($this->_mbox, $id, trim($newflags), FT_UID);
			
			//debugLog("MoveMessage: (" . $folderid . "->" . $newfolderid . ") s-move: $s1   s-expunge: $s2	unset-Flags: $s3	set-Flags: $s4");
            
			return ($s1 && $s2 && $s3 && $s4);
			
		}
		
	}

	function DeleteMessage($folderid, $id) 
	{
		//debugLog("IMAP-DeleteMessage: (fid: '$folderid'  id: '$id' )");

		$this->imap_reopenFolder($folderid);
		$s1 = @imap_delete ($this->_mbox, $id, FT_UID);
		$s11 = @imap_setflag_full($this->_mbox, $id, "\\Deleted", FT_UID);
		$s2 = @imap_expunge($this->_mbox);

		 //debugLog("IMAP-DeleteMessage: s-delete: $s1   s-expunge: $s2	setflag: $s11");

		return ($s1 && $s2 && $s11);
	
	}


	function SendMail($rfc822, $forward = false, $reply = false, $parent = false) 
	{
        global $protocolversion;
		
		//debugLog("IMAP-SendMail: \n" . $rfc822 . "forward: $forward   reply: $reply   parent: $parent" );
        if( substr($parent,0,2) == "i/")
			$parent = substr($parent,2);
		
		$mobj = new Mail_mimeDecode($rfc822);
		$message = $mobj->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $rfc822, 'crlf' => "\n", 'charset' => 'utf-8'));
        $Mail_RFC822 = new Mail_RFC822();
		$toaddr = $ccaddr = $bccaddr = array();

		if ( isset($message->headers["to"]) )
			$toaddr = $this->parseAddrObjectsToArray($Mail_RFC822->parseAddressList($message->headers["to"]));
		if ( isset($message->headers["cc"]) )
			$ccaddr = $this->parseAddrObjectsToArray($Mail_RFC822->parseAddressList($message->headers["cc"]));
		if ( isset($message->headers["bcc"]) )
			$bccaddr = $this->parseAddrObjectsToArray($Mail_RFC822->parseAddressList($message->headers["bcc"]));

		// save some headers when forwarding mails (content type & transfer-encoding)
		$headers = "";
		$forward_h_ct = "";
		$forward_h_cte = "";

		$use_orgbody = false;

		// clean up the transmitted headers
		// remove default headers because we are using imap_mail
		$fromSet = false;
		$from = '';
		$returnPathSet = false;
		$body_base64 = false;
		$org_charset = "";
		$headers = '';

		//TODO: catch/find relatedMessageMessageId in and addHeader('in-reply-to', $requestParams['relatedMessageMessageId'] );
		foreach($message->headers as $k => $v) 
		{
			//if ($k == "subject" || $k == "to" || $k == "cc" || $k == "bcc")
			//	continue;
            
			//sanitize fields
			if( $k == 'to' )
			{
				
				//strip accidental quoted commas ","
				$v = str_replace('","', ',', $v);
				
			}

			if ($k == "content-type") 
			{
				// save the original content-type header for the body part when forwarding
				if ($forward) 
				{
					$forward_h_ct = $v;
					continue;
				}

				// set charset always to utf-8
				$org_charset = $v;
				$v = preg_replace("/charset=([A-Za-z0-9-\"']+)/", "charset=\"utf-8\"", $v);
			}

			if ($k == "content-transfer-encoding") 
			{
				// if the content was base64 encoded, encode the body again when sending
				if (trim($v) == "base64") 
					$body_base64 = true;

				// save the original encoding header for the body part when forwarding
				if ($forward) 
				{
					$forward_h_cte = $v;
					continue;
				}
			}

			// if the message is a multipart message, then we should use the sent body
			if (!$forward && $k == "content-type" && preg_match("/multipart/i", $v)) 
				$use_orgbody = true;
			
			// check if "from"-header is set
			if( $k == "from" && !trim($v) && IMAP_DEFAULTFROM )
			{
				
				if( IMAP_DEFAULTFROM == 'username' )
					$v = $this->_username;
				elseif( IMAP_DEFAULTFROM == 'domain' )
					$v = $this->_domain;
				else 
					$v = $this->_username . IMAP_DEFAULTFROM;
				$from = $v;
				$fromSet = true;
				
			}
			elseif( $k == 'from' )
				$fromSet = true;

			// check if "Return-Path"-header is set
			if ($k == "return-path") 
			{
				
				$returnPathSet = true;
				if (! trim($v) && IMAP_DEFAULTFROM) 
				{
					if( IMAP_DEFAULTFROM == 'username' ) 
						$v = $this->_username;
					elseif( IMAP_DEFAULTFROM == 'domain' )
						$v = $this->_domain;
					else 
						$v = $this->_username . IMAP_DEFAULTFROM;
				}
				if( !$fromSet )
					$from = $v;
					
			}
			elseif( $k == 'return-path' )
				$returnPathSet = true; //dont add it double

			$headers .= ucfirst($k) . ": ". $v . "\n";
			
		}
		// set "From" header if not set on the device
		if(IMAP_DEFAULTFROM && !$fromSet)
		{
			
			if( IMAP_DEFAULTFROM == 'username' ) 
				$v = $this->_username;
			elseif( IMAP_DEFAULTFROM == 'domain' )
				$v = $this->_domain;
			else 
				$v = $this->_username . IMAP_DEFAULTFROM;
			$headers .= 'From: ' . $v . "\n";
			
		}

		// set "Return-Path" header if not set on the device
		if( IMAP_DEFAULTFROM && !$returnPathSet )
		{
			
			if( IMAP_DEFAULTFROM == 'username')
				$v = $this->_username;
			elseif( IMAP_DEFAULTFROM == 'domain' )
				$v = $this->_domain;
			else
				$v = $this->_username . IMAP_DEFAULTFROM;
			$headers .= 'Return-Path: ' . $v . "\n";
			
		}
		// if this is a multipart message with a boundary, we must use the original body
		if ($use_orgbody) 
			list(,$body) = $mobj->_splitBodyHeader($rfc822);
		else if ($protocolversion >= 12)
			$body = $this->getBody($message,  BackendIMAP::$lastBodyType);
		else
			$body = $this->getBody($message);
        
		// reply
		if (isset($reply) && isset($parent) &&  $reply && $parent) 
		{
			$this->imap_reopenFolder($parent);
			// receive entire mail (header + body) to decode body correctly
			$origmail = @imap_fetchheader($this->_mbox, $reply, FT_UID) . @imap_body($this->_mbox, $reply, FT_PEEK | FT_UID);
			$mobj2 = new Mail_mimeDecode($origmail);
			// receive only body
            if(($pos = strpos($mobj->_header, "boundary=")) !== false)
            {
                $pos += strlen("boundary=") + 1;
                $end = strpos($mobj->_header, "\"", $pos);
                $boundaryDelim = substr($mobj->_header, $pos, $end-$pos);
                if($boundaryDelim != '')
                {
                    // lets insert

                    $contents = explode("--" . $boundaryDelim, $body);
                    $newbody = '';
                    foreach($contents as &$content)
                    {
                        $pos = stripos($content, "Content-Type:");
                        if($pos !== false)
                        {
                            $pos += strlen("Content-Type:");
                            $end = strpos($content, ";", $pos);
                            $contenttype = trim(strtolower(substr($content, $pos, $end-$pos)));

                            $pos = stripos($content, "Content-Transfer-Encoding:");
                            $pos += strlen("Content-Transfer-Encoding:");
                            $end = strpos($content, "\n", $pos);
                            $encoding = trim(strtolower(substr($content, $pos, $end-$pos)));

                            if($contenttype == 'text/plain' && $encoding == "base64")
                            {
                                $currentBody = $this->getBody($message, 1);
                                $body2 = $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')),  1);
                                $content = "\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: base64\n\n" . base64_encode($currentBody . $body2) . "\n\n";
                            }
                            else if ($contenttype == 'text/html' && $encoding == "base64")
                            {
                                $currentBody = $this->getBody($message, 2);
                                $body2 = $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')),  2);
                                $content = "\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: base64\n\n" .base64_encode($currentBody . $body2) . "\n\n";
                            }

                        }
                    }
                    $body = implode("--" . $boundaryDelim, $contents);

                }
            }
            else
            {
            	if ($protocolversion >= 12)
        			$body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')),  self::$lastBodyType);
    	    	else
    		    	$body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')));
            }
			// unset mimedecoder & origmail - free memory
			unset($mobj2);
			unset($origmail);
		}

		// encode the body to base64 if it was sent originally in base64 by the pda
		// the encoded body is included in the forward
		if ($body_base64) $body = base64_encode($body);


		// forward
		if (isset($forward) && isset($parent) && $forward && $parent) 
		{
			/*
			$this->imap_reopenFolder($parent);
			// receive entire mail (header + body)
			$origmail = @imap_fetchheader($this->_mbox, $forward, FT_PREFETCHTEXT | FT_UID) . @imap_body($this->_mbox, $forward, FT_PEEK | FT_UID);

			// build a new mime message, forward entire old mail as file
			list($aheader, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte);

			// unset origmail - free memory
			unset($origmail);

			// add boundary headers
			$headers .= "\n" . $aheader;
			*/
			/////////
			$this->imap_reopenFolder($parent);
			// receive entire mail (header + body) to decode body correctly
			$origmail = @imap_fetchheader($this->_mbox, $forward, FT_UID) . @imap_body($this->_mbox, $forward, FT_PEEK | FT_UID);
			
			$mobj2 = new Mail_mimeDecode($origmail);
			
			// receive only body
            if (($pos = strpos($mobj->_header, "boundary=")) !== false)
            {
                $pos += strlen("boundary=") + 1;
                $end = strpos($mobj->_header, "\"", $pos);
                $boundaryDelim = substr($mobj->_header, $pos, $end-$pos);
                if($boundaryDelim != '')
                {					
                    // lets insert

                    $contents = explode("--" . $boundaryDelim, $mobj->_body);
                    $newbody = '';

                    foreach($contents as &$content)
                    {
                        $pos = stripos($content, "Content-Type:");
                        if($pos !== false)
                        {
                            $pos += strlen("Content-Type:");
                            $end = strpos($content, ";", $pos);
                            $contenttype = trim(strtolower(substr($content, $pos, $end-$pos)));

                            $pos = stripos($content, "Content-Transfer-Encoding:");
                            $pos += strlen("Content-Transfer-Encoding:");
                            $end = strpos($content, "\n", $pos);
                            $encoding = trim(strtolower(substr($content, $pos, $end-$pos)));

                            if($contenttype == 'text/plain' && $encoding == "base64")
                            {
                                $currentBody = $this->getBody($message, 1);
                                $body2 = $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')),  1);
                                $content = "\nContent-Type: text/plain; charset=utf-8\nContent-Transfer-Encoding: base64\n\n" . base64_encode($currentBody . $body2) . "\n\n";
                            }
                            else if ($contenttype == 'text/html' && $encoding == "base64")
                            {
                                $currentBody = $this->getBody($message, 2);
                                $body2 = $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')),  2);
                                $content = "\nContent-Type: text/html; charset=utf-8\nContent-Transfer-Encoding: base64\n\n" .base64_encode($currentBody . $body2) . "\n\n";
                            }
                        }
                    }
                    $body = implode("--" . $boundaryDelim, $contents);

                }
            }
            else
            {
            	if ($protocolversion >= 12)
        			$body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')),  self::$lastBodyType);
    	    	else
    		    	$body .= $this->getBody($mobj2->decode(array('decode_headers' => false, 'decode_bodies' => true, 'include_bodies' => true, 'input' => $origmail, 'crlf' => "\n", 'charset' => 'utf-8')));
            }
            
            list($headers, $body) = $this->mail_attach("forwarded_message.eml",strlen($origmail),$origmail, $body, $forward_h_ct, $forward_h_cte, $headers);
			// unset mimedecoder & origmail - free memory
			unset($mobj2);
			unset($origmail);

		}

		// Decide if to enable SMTP auth or regular connection
		$this->_globalConfig = Zend_Registry::get('config')->global;
		if( !empty($this->_globalConfig['smtpauth_username']) && !empty($this->_globalConfig['smtpauth_password']) && !empty($this->_globalConfig['smtp_auth']) )
		{

			$config = array(
							'auth' => 'login',
							'username' => $this->_globalConfig['smtpauth_username'],
							'password' => $this->_globalConfig['smtpauth_password']
	   		);

		}
		else
			$config = array( );

		if(PHP_OS == "Darwin")
			$config['name'] = php_uname('n');
		
        $transport = new Atmail_Mail_Transport_Smtp($this->_globalConfig['smtphost'], $config);
		$result = $transport->sendRawMessage($this->_username, $toaddr, $body, $headers, $ccaddr, $bccaddr);

		// email sent?
		if (!$result)
        {
			//debugLog("The email could not be sent. Last-IMAP-error: ". imap_last_error());
        }

		$asf = false;
		if ($this->_sentID) 
			$asf = $this->addSentMessage($this->_sentID, $headers, $body);
		else if (IMAP_SENTFOLDER) 
		{
			$asf = $this->addSentMessage(IMAP_SENTFOLDER, $headers, $body);
			//debugLog("IMAP-SendMail: Outgoing mail saved in configured 'Sent' folder '".IMAP_SENTFOLDER."': ". (($asf)?"success":"failed"));
		}
		// No Sent folder set, try defaults
		else 
		{
			//debugLog("IMAP-SendMail: No Sent mailbox set");
			if($this->addSentMessage("INBOX.Sent", $headers, $body)) 
			{
				//debugLog("IMAP-SendMail: Outgoing mail saved in 'INBOX.Sent'");
				$asf = true;
			}
			else if ($this->addSentMessage("Sent", $headers, $body)) 
			{
				//debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent'");
				$asf = true;
			}
			else if ($this->addSentMessage("Sent Items", $headers, $body)) 
			{
				//debugLog("IMAP-SendMail: Outgoing mail saved in 'Sent Items'");
				$asf = true;
			}
		}

		// unset mimedecoder - free memory
		unset($mobj);
		return ($result && $asf);
	}
	

	// new ping mechanism for the IMAP-Backend. Return true if supported and AlterPingChanges is implimented
	function AlterPing() 
	{
		return true;
	}

	// returns a changes array using imap_status
	// if changes occurr default diff engine computes the actual changes
	function AlterPingChanges($folderid, &$syncstate) 
	{
		//debugLog("AlterPingChanges on $folderid stat: ". $syncstate);
		$this->imap_reopenFolder($folderid);

		// courier-imap only cleares the status cache after checking
		@imap_check($this->_mbox);

		$status = imap_status($this->_mbox, $this->_server . str_replace(".", $this->_serverdelimiter, $folderid), SA_ALL);
		if (!$status) 
		{
			//debugLog("AlterPingChanges: could not stat folder $folderid : ". imap_last_error());
			return false;
		}
		else 
		{

			$newstate = "M:". $status->messages ."-R:". $status->recent ."-U:". $status->unseen;

			// message number is different - change occured
			if ($syncstate != $newstate) 
			{

				$syncstate = $newstate;
				//debugLog(__FILE__ . "#" . __LINE__ . ": AlterPingChanges: Change FOUND!");
				// build a dummy change
				return array(array("type" => "fakeChange"));
			}
		}

		return array();
	}

	function ChangeMessage($folderid, $id, $message) 
	{
		return false;
	}



	// ----------------------------------------
	// imap-specific internals

	/* Parse the message and return only the plaintext body
	 */
	function getBody($message, $typein = SYNC_AIRSYNCBASE_BODYPREFERENCE_TYPE_PLAIN)
	{
		$body = "";
		$htmlbody = "";
        $type = 'plain';

        if($typein && $typein == 1) $type = 'plain';
        else if($typein == 2 || $typein == 4) $type = 'html';
        $this->getBodyRecursive($message, $type, $body);

        if(!isset($body) || $body == "")
		{
            if($type == "plain") $this->getBodyRecursive($message, "html", $body);
            else if($type == "html") $this->getBodyRecursive($message, "plain", $body);

            if(isset($body) && $body != "")
            {
                if($type == "plain")
                {
                    // html body, convert to plaintext
                    // remove css-style tags
                    $body = preg_replace("/<style.*?<\/style>/is", "", $body);
                    // remove all other html
                    $body = strip_tags($body);
                }
                else if ($type == "html")
                {
                    // text body, convert to html
                    $body = "<p>" . str_replace(array("\n\r", "\n"), array("</p><p>", "</p><p>"), htmlspecialchars($body)) . "</p>";

                }
            }

        }

		return $body;
	}

	// Get all parts in the message with specified type and concatenate them together, unless the
	// Content-Disposition is 'attachment', in which case the text is apparently an attachment
	function getBodyRecursive($message, $subtype, &$body) 
	{
		if(!isset($message->ctype_primary)) 
			return;
		if(strcasecmp($message->ctype_primary,"text")==0 && strcasecmp($message->ctype_secondary,$subtype)==0 && isset($message->body))
			$body .= $message->body;

		if(strcasecmp($message->ctype_primary,"multipart")==0 && isset($message->parts) && is_array($message->parts)) 
		{
			foreach($message->parts as $part) 
			{
				if(!isset($part->disposition) || strcasecmp($part->disposition,"attachment") == 0)  {
					$this->getBodyRecursive($part, $subtype, $body);
				}
			}
		}
	}

	// save the serverdelimiter for later folder (un)parsing
	function getServerDelimiter() 
	{
		$list = @imap_getmailboxes($this->_mbox, $this->_server, "*");
		if (is_array($list)) 
		{
			$val = $list[0];

			return $val->delimiter;
		}
		return "."; // default "."
	}

	// speed things up
	// remember what folder is currently open and only change if necessary
	function imap_reopenFolder($folderid, $force = false) 
	{
		// to see changes, the folder has to be reopened!
		   if ($this->_mboxFolder != $folderid || $force) 
			{
				$s = @imap_reopen($this->_mbox, $this->_server . str_replace(".", $this->_serverdelimiter, $folderid));
				if (!$s)
                {
                    //debugLog("failed to change folder: ". implode(", ", imap_errors()));
                }
    			$this->_mboxFolder = $folderid;
			}
	}


	// build a multipart email, embedding body and one file (for attachments)
	function mail_attach($filenm,$filesize,$file_cont,$body, $body_ct, $body_cte, $headers) 
	{
		if (preg_match("/boundary=(\"|')?([^\"' ;]+/)", $headers, $m))
		{
			$boundary = $m[1];
			preg_replace(
				"/Content-Type:\s*([a-z0-9\-]+\/[a-z0-9\-]+)/i", 
				"Content-Type: multipart/mixed",
				$headers
			);
		}
		else
		{
			$boundary = strtoupper(md5(uniqid(time())));
			if (strpos($headers, "Content-Type:"))
			{
				preg_replace(
					"/Content-Type:\s*([a-z0-9\-]+\/[a-z0-9\-]+)/i", 
					"Content-Type: multipart/mixed; boundary=$boundary",
					$headers
				);
			}
			else
			{
				$headers .= "Content-Type: multipart/mixed; boundary=$boundary\n";
			}
        }

		// build main body with the sumitted type & encoding from the pda
		$mail_body  = "This is a multi-part message in MIME format\n\n";
		$mail_body .= "--$boundary\n";
		$mail_body .= "Content-Type:$body_ct\n";
		$mail_body .= "Content-Transfer-Encoding:$body_cte\n\n";
		$mail_body .= "$body\n\n";

		$mail_body .= "--$boundary\n";
		$mail_body .= "Content-Type: message/rfc822; name=\"$filenm\"\n";
		$mail_body .= "Content-Transfer-Encoding: base64\n";
		$mail_body .= "Content-Disposition: attachment; filename=\"$filenm\"\n";
		$mail_body .= "Content-Description: $filenm\n\n";
		$mail_body .= base64_encode($file_cont) . "\n\n";

		$mail_body .= "--$boundary--\n\n";

		return array($headers, $mail_body);
	}

	// adds a message as seen to a specified folder (used for saving sent mails)
	function addSentMessage($folderid, $header, $body) 
	{
		$header_body = str_replace("\n", "\r\n", str_replace("\r", "", $header . "\n\n" . $body));

		return @imap_append($this->_mbox, $this->_server . $folderid, $header_body, "\\Seen");
	}


	// parses address objects back to a simple "," separated string
	function parseAddr($ad) 
	{
		$addr_string = "";
		if (isset($ad) && is_array($ad)) 
		{
			foreach($ad as $addr) 
			{
				if ($addr_string) $addr_string .= ",";
					$addr_string .= $addr->mailbox . "@" . $addr->host;
			}
		}
		return $addr_string;
	}
	
	// parses address objects to a simple array
	function parseAddrObjectsToArray( $addrObjectsArray ) 
	{

		$addrArray = array();
		if( isset($addrObjectsArray) && is_array($addrObjectsArray) ) 
			foreach($addrObjectsArray as $addrObject) 
				$addrArray[] = $addrObject->mailbox . "@" . $addrObject->host;
		return $addrArray;

	}

}
