/** * Sends an e-mail * This messages needs to be saved into the 'sent items' folder * * @param SyncSendMail $sm SyncSendMail object * * @access public * @return boolean * @throws StatusException */ public function SendMail($sm) { // Check if imtomapi function is available and use it to send the mime message. // It is available since ZCP 7.0.6 // @see http://jira.zarafa.com/browse/ZCP-9508 if (!(function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI'))) { throw new StatusException("ZarafaBackend->SendMail(): ZCP version is too old, INETMAPI_IMTOMAPI is not available. Install at least ZCP version 7.0.6 or later.", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED, null, LOGLEVEL_FATAL); return false; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->SendMail(): RFC822: %d bytes forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'", strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag), Utils::PrintAsString(isset($sm->source->folderid) ? $sm->source->folderid : false), Utils::PrintAsString($sm->saveinsent), Utils::PrintAsString(isset($sm->replacemime)))); // by splitting the message in several lines we can easily grep later foreach (preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line) { ZLog::Write(LOGLEVEL_WBXML, "RFC822: " . $rfc822line); } $sendMailProps = MAPIMapping::GetSendMailProperties(); $sendMailProps = getPropIdsFromStrings($this->store, $sendMailProps); // Open the outbox and create the message there $storeprops = mapi_getprops($this->store, array($sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"])); if (isset($storeprops[$sendMailProps["outboxentryid"]])) { $outbox = mapi_msgstore_openentry($this->store, $storeprops[$sendMailProps["outboxentryid"]]); } if (!$outbox) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR); } $mapimessage = mapi_folder_createmessage($outbox); //message properties to be set $mapiprops = array(); // only save the outgoing in sent items folder if the mobile requests it $mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]]; ZLog::Write(LOGLEVEL_DEBUG, "Use the mapi_inetmapi_imtomapi function"); $ab = mapi_openaddressbook($this->session); mapi_inetmapi_imtomapi($this->session, $this->store, $ab, $mapimessage, $sm->mime, array()); // Set the appSeqNr so that tracking tab can be updated for meeting request updates // @see http://jira.zarafa.com/browse/ZP-68 $meetingRequestProps = MAPIMapping::GetMeetingRequestProperties(); $meetingRequestProps = getPropIdsFromStrings($this->store, $meetingRequestProps); $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"], $sendMailProps["body"], $sendMailProps["html"], $sendMailProps["rtf"], $sendMailProps["rtfinsync"])); // Convert sent message's body to UTF-8 if it was a HTML message. // @see http://jira.zarafa.com/browse/ZP-505 and http://jira.zarafa.com/browse/ZP-555 if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8 && MAPIUtils::GetNativeBodyType($props) == SYNC_BODYPREFERENCE_HTML) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sent email cpid is not unicode (%d). Set it to unicode and convert email html body.", $props[$sendMailProps["internetcpid"]])); $mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8; $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); $bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml); $mapiprops[$sendMailProps["html"]] = $bodyHtml; mapi_setprops($mapimessage, $mapiprops); } if (stripos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) { // search for calendar items using goid $mr = new Meetingrequest($this->store, $mapimessage); $appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]); if (is_array($appointments) && !empty($appointments)) { $app = mapi_msgstore_openentry($this->store, $appointments[0]); $appprops = mapi_getprops($app, array($meetingRequestProps["appSeqNr"])); if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) { $mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]]; ZLog::Write(LOGLEVEL_DEBUG, sprintf("Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]])); } } } // Delete the PR_SENT_REPRESENTING_* properties because some android devices // do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and // PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID // which results in spooler not being able to send the message. // @see http://jira.zarafa.com/browse/ZP-85 mapi_deleteprops($mapimessage, array($sendMailProps["sentrepresentingname"], $sendMailProps["sentrepresentingemail"], $sendMailProps["representingentryid"], $sendMailProps["sentrepresentingaddt"], $sendMailProps["sentrepresentinsrchk"])); if (isset($sm->source->itemid) && $sm->source->itemid) { // answering an email in a public/shared folder if (!$this->Setup(ZPush::GetAdditionalSyncFolderStore($sm->source->folderid))) { throw new StatusException(sprintf("ZarafaBackend->SendMail() could not Setup() the backend for folder id '%s'", $sm->source->folderid), SYNC_COMMONSTATUS_SERVERERROR); } $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid)); if ($entryid) { $fwmessage = mapi_msgstore_openentry($this->store, $entryid); } if (!isset($fwmessage) || !$fwmessage) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND); } //update icon when forwarding or replying message if ($sm->forwardflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 262)); } elseif ($sm->replyflag) { mapi_setprops($fwmessage, array(PR_ICON_INDEX => 261)); } mapi_savechanges($fwmessage); // only attach the original message if the mobile does not send it itself if (!isset($sm->replacemime)) { // get message's body in order to append forward or reply text if (!isset($body)) { $body = MAPIUtils::readPropStream($mapimessage, PR_BODY); } if (!isset($bodyHtml)) { $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); } $cpid = mapi_getprops($fwmessage, array($sendMailProps["internetcpid"])); if ($sm->forwardflag) { // attach the original attachments to the outgoing message $this->copyAttachments($mapimessage, $fwmessage); } // regarding the conversion @see ZP-470 if (strlen($body) > 0) { $fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY); // if only the old message's cpid is set, convert from old charset to utf-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->SendMail(): convert plain forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbody = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody); } else { ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): no charset conversion done for plain forwarded message"); $fwbody = w2u($fwbody); } $mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody; } if (strlen($bodyHtml) > 0) { $fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML); // if only new message's cpid is set, convert to UTF-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ZarafaBackend->SendMail(): convert html forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbodyHtml = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml); } else { ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): no charset conversion done for html forwarded message"); $fwbodyHtml = w2u($fwbodyHtml); } $mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml; } } } mapi_setprops($mapimessage, $mapiprops); mapi_message_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); $hr = mapi_last_hresult(); if ($hr) { throw new StatusException(sprintf("ZarafaBackend->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); } ZLog::Write(LOGLEVEL_DEBUG, "ZarafaBackend->SendMail(): email submitted"); return true; }
/** * Sends an e-mail * This messages needs to be saved into the 'sent items' folder * * @param SyncSendMail $sm SyncSendMail object * * @access public * @return boolean * @throws StatusException */ public function SendMail($sm) { // Check if imtomapi function is available and use it to send the mime message. // It is available since ZCP 7.0.6 // @see http://jira.zarafa.com/browse/ZCP-9508 if (!(function_exists('mapi_feature') && mapi_feature('INETMAPI_IMTOMAPI'))) { throw new StatusException("KopanoBackend->SendMail(): ZCP/KC version is too old, INETMAPI_IMTOMAPI is not available. Install at least ZCP version 7.0.6 or later.", SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED, null, LOGLEVEL_FATAL); return false; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): RFC822: %d bytes forward-id: '%s' reply-id: '%s' parent-id: '%s' SaveInSent: '%s' ReplaceMIME: '%s'", strlen($sm->mime), Utils::PrintAsString($sm->forwardflag), Utils::PrintAsString($sm->replyflag), Utils::PrintAsString(isset($sm->source->folderid) ? $sm->source->folderid : false), Utils::PrintAsString($sm->saveinsent), Utils::PrintAsString(isset($sm->replacemime)))); // Send-As functionality - https://jira.z-hub.io/browse/ZP-908 $sendingAsSomeone = false; if (defined('KOE_CAPABILITY_SENDAS') && KOE_CAPABILITY_SENDAS) { $senderEmail = array(); // KOE: grep for the Sender header indicating we should send-as // the 'X-Push-Sender-Name' header is not used if (preg_match("/^X-Push-Sender:\\s(.*?)\$/im", $sm->mime, $senderEmail)) { $sendAsEmail = trim($senderEmail[1]); ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Send-As '%s' requested by KOE", $sendAsEmail)); $sm->mime = preg_replace("/^From: .*?\$/im", "From: " . $sendAsEmail, $sm->mime, 1); $sendingAsSomeone = true; } elseif (isset($sm->source->folderid)) { // get the owner of this folder - System is not allowed $sharedUser = ZPush::GetAdditionalSyncFolderStore($sm->source->folderid); if ($sharedUser != false && $sharedUser != 'SYSTEM') { $folders = ZPush::GetAdditionalSyncFolders(); if (isset($folders[$sm->source->folderid]) && $folders[$sm->source->folderid]->Flags & DeviceManager::FLD_FLAGS_REPLYASUSER) { $sendAs = $this->resolveRecipientGAL($sharedUser, 1); if (isset($sendAs[0])) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): Server side Send-As activated for shared folder. Sending as '%s'.", $sendAs[0]->emailaddress)); $sm->mime = preg_replace("/^From: .*?\$/im", "From: " . $sendAs[0]->emailaddress, $sm->mime, 1); $sendingAsSomeone = true; } } } } } // by splitting the message in several lines we can easily grep later foreach (preg_split("/((\r)?\n)/", $sm->mime) as $rfc822line) { ZLog::Write(LOGLEVEL_WBXML, "RFC822: " . $rfc822line); } $sendMailProps = MAPIMapping::GetSendMailProperties(); $sendMailProps = getPropIdsFromStrings($this->defaultstore, $sendMailProps); // Open the outbox and create the message there $storeprops = mapi_getprops($this->defaultstore, array($sendMailProps["outboxentryid"], $sendMailProps["ipmsentmailentryid"])); if (isset($storeprops[$sendMailProps["outboxentryid"]])) { $outbox = mapi_msgstore_openentry($this->defaultstore, $storeprops[$sendMailProps["outboxentryid"]]); } if (!$outbox) { throw new StatusException(sprintf("KopanoBackend->SendMail(): No Outbox found or unable to create message: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_SERVERERROR); } $mapimessage = mapi_folder_createmessage($outbox); //message properties to be set $mapiprops = array(); // only save the outgoing in sent items folder if the mobile requests it $mapiprops[$sendMailProps["sentmailentryid"]] = $storeprops[$sendMailProps["ipmsentmailentryid"]]; ZLog::Write(LOGLEVEL_DEBUG, "Use the mapi_inetmapi_imtomapi function"); $ab = mapi_openaddressbook($this->session); mapi_inetmapi_imtomapi($this->session, $this->defaultstore, $ab, $mapimessage, $sm->mime, array()); // Set the appSeqNr so that tracking tab can be updated for meeting request updates // @see http://jira.zarafa.com/browse/ZP-68 $meetingRequestProps = MAPIMapping::GetMeetingRequestProperties(); $meetingRequestProps = getPropIdsFromStrings($this->defaultstore, $meetingRequestProps); $props = mapi_getprops($mapimessage, array(PR_MESSAGE_CLASS, $meetingRequestProps["goidtag"], $sendMailProps["internetcpid"], $sendMailProps["body"], $sendMailProps["html"], $sendMailProps["rtf"], $sendMailProps["rtfinsync"])); // Convert sent message's body to UTF-8 if it was a HTML message. // @see http://jira.zarafa.com/browse/ZP-505 and http://jira.zarafa.com/browse/ZP-555 if (isset($props[$sendMailProps["internetcpid"]]) && $props[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8 && MAPIUtils::GetNativeBodyType($props) == SYNC_BODYPREFERENCE_HTML) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sent email cpid is not unicode (%d). Set it to unicode and convert email html body.", $props[$sendMailProps["internetcpid"]])); $mapiprops[$sendMailProps["internetcpid"]] = INTERNET_CPID_UTF8; $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); $bodyHtml = Utils::ConvertCodepageStringToUtf8($props[$sendMailProps["internetcpid"]], $bodyHtml); $mapiprops[$sendMailProps["html"]] = $bodyHtml; mapi_setprops($mapimessage, $mapiprops); } if (stripos($props[PR_MESSAGE_CLASS], "IPM.Schedule.Meeting.Resp.") === 0) { // search for calendar items using goid $mr = new Meetingrequest($this->defaultstore, $mapimessage); $appointments = $mr->findCalendarItems($props[$meetingRequestProps["goidtag"]]); if (is_array($appointments) && !empty($appointments)) { $app = mapi_msgstore_openentry($this->defaultstore, $appointments[0]); $appprops = mapi_getprops($app, array($meetingRequestProps["appSeqNr"])); if (isset($appprops[$meetingRequestProps["appSeqNr"]]) && $appprops[$meetingRequestProps["appSeqNr"]]) { $mapiprops[$meetingRequestProps["appSeqNr"]] = $appprops[$meetingRequestProps["appSeqNr"]]; ZLog::Write(LOGLEVEL_DEBUG, sprintf("Set sequence number to:%d", $appprops[$meetingRequestProps["appSeqNr"]])); } } } // Delete the PR_SENT_REPRESENTING_* properties because some android devices // do not send neither From nor Sender header causing empty PR_SENT_REPRESENTING_NAME and // PR_SENT_REPRESENTING_EMAIL_ADDRESS properties and "broken" PR_SENT_REPRESENTING_ENTRYID // which results in spooler not being able to send the message. // @see http://jira.zarafa.com/browse/ZP-85 // If using KOE send-as feature, we keep this properties because they actually are the send-as if (!$sendingAsSomeone) { mapi_deleteprops($mapimessage, array($sendMailProps["sentrepresentingname"], $sendMailProps["sentrepresentingemail"], $sendMailProps["representingentryid"], $sendMailProps["sentrepresentingaddt"], $sendMailProps["sentrepresentinsrchk"])); } if (isset($sm->source->itemid) && $sm->source->itemid) { // answering an email in a public/shared folder // TODO as the store is setup, we should actually user $this->store instead of $this->defaultstore - nevertheless we need to make sure this store is able to send mail (has an outbox) if (!$this->Setup(ZPush::GetAdditionalSyncFolderStore($sm->source->folderid))) { throw new StatusException(sprintf("KopanoBackend->SendMail() could not Setup() the backend for folder id '%s'", $sm->source->folderid), SYNC_COMMONSTATUS_SERVERERROR); } $entryid = mapi_msgstore_entryidfromsourcekey($this->store, hex2bin($sm->source->folderid), hex2bin($sm->source->itemid)); if ($entryid) { $fwmessage = mapi_msgstore_openentry($this->store, $entryid); } if (isset($fwmessage) && $fwmessage) { // update icon and last_verb when forwarding or replying message // reply-all (verb 103) is not supported, as we cannot really detect this case if ($sm->forwardflag) { $updateProps = array(PR_ICON_INDEX => 262, PR_LAST_VERB_EXECUTED => 104); } elseif ($sm->replyflag) { $updateProps = array(PR_ICON_INDEX => 261, PR_LAST_VERB_EXECUTED => 102); } if (isset($updateProps)) { $updateProps[PR_LAST_VERB_EXECUTION_TIME] = time(); mapi_setprops($fwmessage, $updateProps); mapi_savechanges($fwmessage); } // only attach the original message if the mobile does not send it itself if (!isset($sm->replacemime)) { // get message's body in order to append forward or reply text if (!isset($body)) { $body = MAPIUtils::readPropStream($mapimessage, PR_BODY); } if (!isset($bodyHtml)) { $bodyHtml = MAPIUtils::readPropStream($mapimessage, PR_HTML); } $cpid = mapi_getprops($fwmessage, array($sendMailProps["internetcpid"])); if ($sm->forwardflag) { // attach the original attachments to the outgoing message $this->copyAttachments($mapimessage, $fwmessage); } // regarding the conversion @see ZP-470 if (strlen($body) > 0) { $fwbody = MAPIUtils::readPropStream($fwmessage, PR_BODY); // if only the old message's cpid is set, convert from old charset to utf-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): convert plain forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbody = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbody); } else { ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): no charset conversion done for plain forwarded message"); $fwbody = w2u($fwbody); } $mapiprops[$sendMailProps["body"]] = $body . "\r\n\r\n" . $fwbody; } if (strlen($bodyHtml) > 0) { $fwbodyHtml = MAPIUtils::readPropStream($fwmessage, PR_HTML); // if only new message's cpid is set, convert to UTF-8 if (isset($cpid[$sendMailProps["internetcpid"]]) && $cpid[$sendMailProps["internetcpid"]] != INTERNET_CPID_UTF8) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->SendMail(): convert html forwarded message charset (only fw set) from '%s' to '65001'", $cpid[$sendMailProps["internetcpid"]])); $fwbodyHtml = Utils::ConvertCodepageStringToUtf8($cpid[$sendMailProps["internetcpid"]], $fwbodyHtml); } else { ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): no charset conversion done for html forwarded message"); $fwbodyHtml = w2u($fwbodyHtml); } $mapiprops[$sendMailProps["html"]] = $bodyHtml . "<br><br>" . $fwbodyHtml; } } } else { // no fwmessage could be opened and we need it because we do not replace mime if (!isset($sm->replacemime) || $sm->replacemime == false) { throw new StatusException(sprintf("KopanoBackend->SendMail(): Could not open message id '%s' in folder id '%s' to be replied/forwarded: 0x%X", $sm->source->itemid, $sm->source->folderid, mapi_last_hresult()), SYNC_COMMONSTATUS_ITEMNOTFOUND); } } } mapi_setprops($mapimessage, $mapiprops); mapi_message_savechanges($mapimessage); mapi_message_submitmessage($mapimessage); $hr = mapi_last_hresult(); if ($hr) { throw new StatusException(sprintf("KopanoBackend->SendMail(): Error saving/submitting the message to the Outbox: 0x%X", mapi_last_hresult()), SYNC_COMMONSTATUS_MAILSUBMISSIONFAILED); } ZLog::Write(LOGLEVEL_DEBUG, "KopanoBackend->SendMail(): email submitted"); return true; }