/** * Handles a webservice command * * @param int $commandCode * * @access public * @return boolean * @throws SoapFault */ public function Handle($commandCode) { if (Request::GetDeviceType() !== "webservice" || Request::GetDeviceID() !== "webservice") { throw new FatalException("Invalid device id and type for webservice execution"); } if (Request::GetGETUser() != Request::GetAuthUser()) { ZLog::Write(LOGLEVEL_INFO, sprintf("Webservice::HandleWebservice('%s'): user '%s' executing action for user '%s'", $commandCode, Request::GetAuthUser(), Request::GetGETUser())); } // initialize non-wsdl soap server $this->server = new SoapServer(null, array('uri' => "http://z-push.sf.net/webservice")); // the webservice command is handled by its class if ($commandCode == ZPush::COMMAND_WEBSERVICE_DEVICE) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceDevice service", $commandCode)); include_once 'webservicedevice.php'; $this->server->setClass("WebserviceDevice"); } // the webservice command is handled by its class if ($commandCode == ZPush::COMMAND_WEBSERVICE_USERS) { if (!defined("ALLOW_WEBSERVICE_USERS_ACCESS") || ALLOW_WEBSERVICE_USERS_ACCESS !== true) { throw new HTTPReturnCodeException(sprintf("Access to the WebserviceUsers service is disabled in configuration. Enable setting ALLOW_WEBSERVICE_USERS_ACCESS.", Request::GetAuthUser()), 403); } ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): executing WebserviceUsers service", $commandCode)); if (ZPush::GetBackend()->Setup("SYSTEM", true) == false) { throw new AuthenticationRequiredException(sprintf("User '%s' has no admin privileges", Request::GetAuthUser())); } include_once 'webserviceusers.php'; $this->server->setClass("WebserviceUsers"); } $this->server->handle(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): sucessfully sent %d bytes", $commandCode, ob_get_length())); return true; }
/** * Constructor of the combined backend * * @access public */ public function BackendCombined() { parent::Backend(); $this->config = BackendCombinedConfig::GetBackendCombinedConfig(); foreach ($this->config['backends'] as $i => $b) { // load and instatiate backend ZPush::IncludeBackend($b['name']); $this->backends[$i] = new $b['name']($b['config']); } ZLog::Write(LOGLEVEL_INFO, sprintf("Combined %d backends loaded.", count($this->backends))); }
/** * Constructor of the combined backend * * @access public */ public function BackendCombined() { parent::Backend(); $this->config = BackendCombinedConfig::GetBackendCombinedConfig(); $backend_values = array_unique(array_values($this->config['folderbackend'])); foreach ($backend_values as $i) { ZPush::IncludeBackend($this->config['backends'][$i]['name']); $this->backends[$i] = new $this->config['backends'][$i]['name'](); } ZLog::Write(LOGLEVEL_DEBUG, sprintf("Combined %d backends loaded.", count($this->backends))); }
/** * Returns a list of all known devices with users and when they synchronized for the first time * * @access public * @return array */ public function ListDevicesDetails() { $devices = ZPushAdmin::ListDevices(false); $output = array(); ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceUsers::ListLastSync(): found %d devices", count($devices))); ZPush::GetTopCollector()->AnnounceInformation(sprintf("Retrieved details of %d devices and getting users", count($devices)), true); foreach ($devices as $deviceId) { $output[$deviceId] = array(); $users = ZPushAdmin::ListUsers($deviceId); foreach ($users as $user) { $output[$deviceId][$user] = ZPushAdmin::GetDeviceDetails($deviceId, $user); } } return $output; }
/** * Returns a list of folders of the Request::GetGETUser(). * If the user has not enough permissions an empty result is returned. * * @access public * @return array */ public function ListUserFolders() { $user = Request::GetGETUser(); $output = array(); $hasRights = ZPush::GetBackend()->Setup($user); ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceInfo::ListUserFolders(): permissions to open store '%s': %s", $user, Utils::PrintAsString($hasRights))); if ($hasRights) { $folders = ZPush::GetBackend()->GetHierarchy(); ZPush::GetTopCollector()->AnnounceInformation(sprintf("Retrieved details of %d folders", count($folders)), true); foreach ($folders as $folder) { $folder->StripData(); unset($folder->Store, $folder->flags, $folder->content, $folder->NoBackendFolder); $output[] = $folder; } } return $output; }
/** * Only used to load additional folder sync information for hierarchy changes * * @param array $state current state of additional hierarchy folders * * @access public * @return boolean */ public function Config($state, $flags = 0) { // we should never forward this changes to a backend if (!isset($this->destinationImporter)) { foreach ($state as $addKey => $addFolder) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->Config(AdditionalFolders) : process folder '%s'", $addFolder->displayname)); if (isset($addFolder->NoBackendFolder) && $addFolder->NoBackendFolder == true) { $hasRights = ZPush::GetBackend()->Setup($addFolder->Store, true, $addFolder->serverid); // delete the folder on the device if (!$hasRights) { // delete the folder only if it was an additional folder before, else ignore it $synchedfolder = $this->GetFolder($addFolder->serverid); if (isset($synchedfolder->NoBackendFolder) && $synchedfolder->NoBackendFolder == true) { $this->ImportFolderDeletion($addFolder->serverid, $addFolder->parentid); } continue; } } // add folder to the device - if folder is already on the device, nothing will happen $this->ImportFolderChange($addFolder); } // look for folders which are currently on the device if there are now not to be synched anymore $alreadyDeleted = $this->GetDeletedFolders(); foreach ($this->ExportFolders(true) as $sid => $folder) { // we are only looking at additional folders if (isset($folder->NoBackendFolder)) { // look if this folder is still in the list of additional folders and was not already deleted (e.g. missing permissions) if (!array_key_exists($sid, $state) && !array_key_exists($sid, $alreadyDeleted)) { ZLog::Write(LOGLEVEL_INFO, sprintf("ChangesMemoryWrapper->Config(AdditionalFolders) : previously synchronized folder '%s' is not to be synched anymore. Sending delete to mobile.", $folder->displayname)); $this->ImportFolderDeletion($folder->serverid, $folder->parentid); } } } } return true; }
* along with this program. If not, see <http://www.gnu.org/licenses/>. * * Consult LICENSE file for details ************************************************/ require_once 'vendor/autoload.php'; require_once 'config.php'; /** * //TODO resync of single folders of a users device */ /************************************************ * MAIN */ define('BASE_PATH_CLI', dirname(__FILE__) . "/"); set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI); try { ZPush::CheckConfig(); ZLog::Initialize(); ZPushAdminCLI::CheckEnv(); ZPushAdminCLI::CheckOptions(); if (!ZPushAdminCLI::SureWhatToDo()) { // show error message if available if (ZPushAdminCLI::GetErrorMessage()) { echo "ERROR: " . ZPushAdminCLI::GetErrorMessage() . "\n"; } echo ZPushAdminCLI::UsageInstructions(); exit(1); } ZPushAdminCLI::RunCommand(); } catch (ZPushException $zpe) { die(get_class($zpe) . ": " . $zpe->getMessage() . "\n"); }
/** * Indicates if the next messages should be ignored (not be sent to the mobile!) * * @param string $messageid (opt) id of the message which is to be exported next * @param string $folderid (opt) parent id of the message * @param boolean $markAsIgnored (opt) to peek without setting the next message to be * ignored, set this value to false * @access public * @return boolean */ public function IgnoreNextMessage($markAsIgnored = true, $messageid = false, $folderid = false) { // as the next message id is not available at all point this method is called, we use different indicators. // potentialbroken indicates that we know that the broken message should be exported next, // alltho we do not know for sure as it's export message orders can change // if the $messageid is available and matches then we are sure and only then really ignore it $potentialBroken = false; $realBroken = false; if (Request::GetCommandCode() == ZPush::COMMAND_SYNC && $this->ignore_messageid !== false) { $potentialBroken = true; } if ($messageid !== false && $this->ignore_messageid == $messageid) { $realBroken = true; } // this call is just to know what should be happening // no further actions necessary if ($markAsIgnored === false) { return $potentialBroken; } // we should really do something here // first we check if we are in the loop mode, if so, // we update the potential broken id message so we loop count the same message $changedData = false; // exclusive block if ($this->blockMutex()) { $loopdata = $this->hasData() ? $this->getData() : array(); // check and initialize the array structure $this->checkArrayStructure($loopdata, $folderid); $current = $loopdata[self::$devid][self::$user][$folderid]; // we found our broken message! if ($realBroken) { $this->ignore_messageid = false; $current['ignored'] = $messageid; $changedData = true; // check if this message was broken before - here we know that it still is and remove it from the tracking $brokenkey = self::BROKENMSGS . "-" . $folderid; if (isset($loopdata[self::$devid][self::$user][$brokenkey]) && isset($loopdata[self::$devid][self::$user][$brokenkey][$messageid])) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): previously broken message '%s' is still broken and will not be tracked anymore", $messageid)); unset($loopdata[self::$devid][self::$user][$brokenkey][$messageid]); } } else { // update potential id if looping on an item if (isset($current['loopcount'])) { $current['potential'] = $messageid; // this message should be the broken one, but is not!! // we should reset the loop count because this is certainly not the broken one if ($potentialBroken) { $current['loopcount'] = 1; ZLog::Write(LOGLEVEL_DEBUG, "LoopDetection->IgnoreNextMessage(): this should be the broken one, but is not! Resetting loop count."); } ZLog::Write(LOGLEVEL_DEBUG, sprintf("LoopDetection->IgnoreNextMessage(): Loop mode, potential broken message id '%s'", $current['potential'])); $changedData = true; } } // update loop data if ($changedData == true) { $loopdata[self::$devid][self::$user][$folderid] = $current; $ok = $this->setData($loopdata); } $this->releaseMutex(); } // end exclusive block if ($realBroken) { ZPush::GetTopCollector()->AnnounceInformation("Broken message ignored", true); } return $realBroken; }
if ($ex instanceof AuthenticationRequiredException) { ZPush::PrintZPushLegal($exclass, sprintf('<pre>%s</pre>', $ex->getMessage())); // log the failed login attemt e.g. for fail2ban if (defined('LOGAUTHFAIL') && LOGAUTHFAIL != false) { ZLog::Write(LOGLEVEL_WARN, sprintf("IP: %s failed to authenticate user '%s'", Request::GetRemoteAddr(), Request::GetAuthUser() ? Request::GetAuthUser() : Request::GetGETUser())); } } else { if ($ex instanceof WBXMLException) { ZLog::Write(LOGLEVEL_FATAL, "Request could not be processed correctly due to a WBXMLException. Please report this."); } else { if (!$ex instanceof ZPushException || $ex->showLegalNotice()) { $cmdinfo = Request::GetCommand() ? sprintf(" processing command <i>%s</i>", Request::GetCommand()) : ""; $extrace = $ex->getTrace(); $trace = !empty($extrace) ? "\n\nTrace:\n" . print_r($extrace, 1) : ""; ZPush::PrintZPushLegal($exclass . $cmdinfo, sprintf('<pre>%s</pre>', $ex->getMessage() . $trace)); } } } // Announce exception to process loop detection if (ZPush::GetDeviceManager(false)) { ZPush::GetDeviceManager()->AnnounceProcessException($ex); } // Announce exception if the TopCollector if available ZPush::GetTopCollector()->AnnounceInformation(get_class($ex), true); } // save device data if the DeviceManager is available if (ZPush::GetDeviceManager(false)) { ZPush::GetDeviceManager()->Save(); } // end gracefully ZLog::Write(LOGLEVEL_DEBUG, '-------- End');
/** * Does the complete autodiscover. * @access public * @throws AuthenticationRequiredException if login to the backend failed. * @throws ZPushException if the incoming XML is invalid.. * * @return void */ public function DoAutodiscover() { $response = ""; try { $incomingXml = $this->getIncomingXml(); $backend = ZPush::GetBackend(); $username = $this->login($backend, $incomingXml); $userDetails = $backend->GetUserDetails($username); $email = $this->getAttribFromUserDetails($userDetails, 'emailaddress') ? $this->getAttribFromUserDetails($userDetails, 'emailaddress') : $incomingXml->Request->EMailAddress; $userFullname = $this->getAttribFromUserDetails($userDetails, 'fullname') ? $this->getAttribFromUserDetails($userDetails, 'fullname') : $email; ZLog::Write(LOGLEVEL_WBXML, sprintf("Resolved user's '%s' fullname to '%s'", $username, $userFullname)); // At the moment Z-Push only supports mobile response schema for autodiscover. Send empty response if the client request outlook response schema. if ($incomingXml->Request->AcceptableResponseSchema == ZPushAutodiscover::ACCEPTABLERESPONSESCHEMAMOBILESYNC) { $response = $this->createResponse($email, $userFullname); setcookie("membername", $username); } } catch (AuthenticationRequiredException $ex) { if (isset($incomingXml)) { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because login failed for user with email '%s'", $incomingXml->Request->EMailAddress)); } else { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover incorrect request: '%s'", $ex->getMessage())); } http_response_code(401); header('WWW-Authenticate: Basic realm="ZPush"'); } catch (ZPushException $ex) { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because of ZPushException. Error: %s", $ex->getMessage())); if (!headers_sent()) { header('HTTP/1.1 ' . $ex->getHTTPCodeString()); foreach ($ex->getHTTPHeaders() as $h) { header($h); } } } $this->sendResponse($response); }
/** * Handles creates, updates or deletes of a folder * issued by the commands FolderCreate, FolderUpdate and FolderDelete * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { $el = self::$decoder->getElement(); if ($el[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } $create = $update = $delete = false; if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERCREATE) { $create = true; } else { if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERUPDATE) { $update = true; } else { if ($el[EN_TAG] == SYNC_FOLDERHIERARCHY_FOLDERDELETE) { $delete = true; } } } if (!$create && !$update && !$delete) { return false; } // SyncKey if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } // ServerID $serverid = false; if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID)) { $serverid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // Parent $parentid = false; // when creating or updating more information is necessary if (!$delete) { if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_PARENTID)) { $parentid = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // Displayname if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_DISPLAYNAME)) { return false; } $displayname = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } // Type $type = false; if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_TYPE)) { $type = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } } // endtag foldercreate, folderupdate, folderdelete if (!self::$decoder->getElementEndTag()) { return false; } $status = SYNC_FSSTATUS_SUCCESS; // Get state of hierarchy try { $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey); $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey); // Over the ChangesWrapper the HierarchyCache is notified about all changes $changesMem = self::$deviceManager->GetHierarchyChangesWrapper(); // the hierarchyCache should now fully be initialized - check for changes in the additional folders $changesMem->Config(ZPush::GetAdditionalSyncFolders()); // there are unprocessed changes in the hierarchy, trigger resync if ($changesMem->GetChangeCount() > 0) { throw new StatusException("HandleFolderChange() can not proceed as there are unprocessed hierarchy changes", SYNC_FSSTATUS_SERVERERROR); } // any additional folders can not be modified! if ($serverid !== false && ZPush::GetAdditionalSyncFolderStore($serverid)) { throw new StatusException("HandleFolderChange() can not change additional folders which are configured", SYNC_FSSTATUS_SYSTEMFOLDER); } // switch user store if this this happens inside an additional folder // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($parentid != false ? $parentid : $serverid))) { throw new StatusException(sprintf("HandleFolderChange() could not Setup() the backend for folder id '%s'", $parentid != false ? $parentid : $serverid), SYNC_FSSTATUS_SERVERERROR); } } catch (StateNotFoundException $snfex) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } catch (StatusException $stex) { $status = $stex->getCode(); } // set $newsynckey in case of an error if (!isset($newsynckey)) { $newsynckey = $synckey; } if ($status == SYNC_FSSTATUS_SUCCESS) { try { // Configure importer with last state $importer = self::$backend->GetImporter(); $importer->Config($syncstate); // the messages from the PIM will be forwarded to the real importer $changesMem->SetDestinationImporter($importer); // process incoming change if (!$delete) { // Send change $folder = new SyncFolder(); $folder->serverid = $serverid; $folder->parentid = $parentid; $folder->displayname = $displayname; $folder->type = $type; $serverid = $changesMem->ImportFolderChange($folder); } else { // delete folder $changesMem->ImportFolderDeletion($serverid, 0); } } catch (StatusException $stex) { $status = $stex->getCode(); } } self::$encoder->startWBXML(); if ($create) { self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERCREATE); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); self::$encoder->content($newsynckey); self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SERVERENTRYID); self::$encoder->content($serverid); self::$encoder->endTag(); self::$encoder->endTag(); } elseif ($update) { self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERUPDATE); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); self::$encoder->content($newsynckey); self::$encoder->endTag(); self::$encoder->endTag(); } elseif ($delete) { self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERDELETE); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); self::$encoder->content($newsynckey); self::$encoder->endTag(); self::$encoder->endTag(); } self::$topCollector->AnnounceInformation(sprintf("Operation status %d", $status), true); // Save the sync state for the next time if (isset($importer)) { self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $importer->GetState()); } return true; }
/** * Imports a single message * * @param array $props * @param long $flags * @param object $retmapimessage * * @access public * @return long */ public function ImportMessageChange($props, $flags, &$retmapimessage) { $sourcekey = $props[PR_SOURCE_KEY]; $parentsourcekey = $props[PR_PARENT_SOURCE_KEY]; $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $parentsourcekey, $sourcekey); if (!$entryid) { return SYNC_E_IGNORE; } $mapimessage = mapi_msgstore_openentry($this->store, $entryid); try { $message = $this->mapiprovider->GetMessage($mapimessage, $this->contentparameters); } catch (SyncObjectBrokenException $mbe) { $brokenSO = $mbe->GetSyncObject(); if (!$brokenSO) { ZLog::Write(LOGLEVEL_ERROR, sprintf("PHPWrapper->ImportMessageChange(): Catched SyncObjectBrokenException but broken SyncObject available")); } else { if (!isset($brokenSO->id)) { $brokenSO->id = "Unknown ID"; ZLog::Write(LOGLEVEL_ERROR, sprintf("PHPWrapper->ImportMessageChange(): Catched SyncObjectBrokenException but no ID of object set")); } ZPush::GetDeviceManager()->AnnounceIgnoredMessage(false, $brokenSO->id, $brokenSO); } // tell MAPI to ignore the message return SYNC_E_IGNORE; } // substitute the MAPI SYNC_NEW_MESSAGE flag by a z-push proprietary flag if ($flags == SYNC_NEW_MESSAGE) { $message->flags = SYNC_NEWMESSAGE; } else { $message->flags = $flags; } $this->importer->ImportMessageChange(bin2hex($sourcekey), $message); // Tell MAPI it doesn't need to do anything itself, as we've done all the work already. return SYNC_E_IGNORE; }
/** * Handles the Search command * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { $searchrange = '0'; $cpo = new ContentParameters(); if (!self::$decoder->getElementStartTag(SYNC_SEARCH_SEARCH)) { return false; } // TODO check: possible to search in other stores? if (!self::$decoder->getElementStartTag(SYNC_SEARCH_STORE)) { return false; } if (!self::$decoder->getElementStartTag(SYNC_SEARCH_NAME)) { return false; } $searchname = strtoupper(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } if (!self::$decoder->getElementStartTag(SYNC_SEARCH_QUERY)) { return false; } // check if it is a content of an element (= GAL search) // or a starttag (= mailbox or documentlibrary search) $searchquery = self::$decoder->getElementContent(); if ($searchquery && !self::$decoder->getElementEndTag()) { return false; } if ($searchquery === false) { $cpo->SetSearchName($searchname); if (self::$decoder->getElementStartTag(SYNC_SEARCH_AND)) { if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { $searchfolderid = self::$decoder->getElementContent(); $cpo->SetSearchFolderid($searchfolderid); if (!self::$decoder->getElementEndTag()) { // SYNC_FOLDERTYPE return false; } } if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $searchclass = self::$decoder->getElementContent(); $cpo->SetSearchClass($searchclass); if (!self::$decoder->getElementEndTag()) { // SYNC_FOLDERTYPE return false; } } if (self::$decoder->getElementStartTag(SYNC_FOLDERID)) { $searchfolderid = self::$decoder->getElementContent(); $cpo->SetSearchFolderid($searchfolderid); if (!self::$decoder->getElementEndTag()) { // SYNC_FOLDERTYPE return false; } } if (self::$decoder->getElementStartTag(SYNC_SEARCH_FREETEXT)) { $searchfreetext = self::$decoder->getElementContent(); $cpo->SetSearchFreeText($searchfreetext); if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_FREETEXT return false; } } //TODO - review if (self::$decoder->getElementStartTag(SYNC_SEARCH_GREATERTHAN)) { if (self::$decoder->getElementStartTag(SYNC_POOMMAIL_DATERECEIVED)) { $datereceivedgreater = true; if (($dam = self::$decoder->getElementContent()) !== false) { $datereceivedgreater = true; if (!self::$decoder->getElementEndTag()) { return false; } } $cpo->SetSearchDateReceivedGreater($datereceivedgreater); } if (self::$decoder->getElementStartTag(SYNC_SEARCH_VALUE)) { $searchvalue = self::$decoder->getElementContent(); $cpo->SetSearchValueGreater($searchvalue); if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_VALUE return false; } } if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_GREATERTHAN return false; } } if (self::$decoder->getElementStartTag(SYNC_SEARCH_LESSTHAN)) { if (self::$decoder->getElementStartTag(SYNC_POOMMAIL_DATERECEIVED)) { $datereceivedless = true; if (($dam = self::$decoder->getElementContent()) !== false) { $datereceivedless = true; if (!self::$decoder->getElementEndTag()) { return false; } } $cpo->SetSearchDateReceivedLess($datereceivedless); } if (self::$decoder->getElementStartTag(SYNC_SEARCH_VALUE)) { $searchvalue = self::$decoder->getElementContent(); $cpo->SetSearchValueLess($searchvalue); if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_VALUE return false; } } if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_LESSTHAN return false; } } if (self::$decoder->getElementStartTag(SYNC_SEARCH_FREETEXT)) { $searchfreetext = self::$decoder->getElementContent(); $cpo->SetSearchFreeText($searchfreetext); if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_FREETEXT return false; } } if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_AND return false; } } elseif (self::$decoder->getElementStartTag(SYNC_SEARCH_EQUALTO)) { // linkid can be an empty tag as well as have value if (self::$decoder->getElementStartTag(SYNC_DOCUMENTLIBRARY_LINKID)) { if (($linkId = self::$decoder->getElementContent()) !== false) { $cpo->SetLinkId($linkId); if (!self::$decoder->getElementEndTag()) { // SYNC_DOCUMENTLIBRARY_LINKID return false; } } } if (self::$decoder->getElementStartTag(SYNC_SEARCH_VALUE)) { $searchvalue = self::$decoder->getElementContent(); $cpo->SetSearchValueLess($searchvalue); if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_VALUE return false; } } if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_EQUALTO return false; } } if (!self::$decoder->getElementEndTag()) { // SYNC_SEARCH_QUERY return false; } } if (self::$decoder->getElementStartTag(SYNC_SEARCH_OPTIONS)) { while (1) { if (self::$decoder->getElementStartTag(SYNC_SEARCH_RANGE)) { $searchrange = self::$decoder->getElementContent(); $cpo->SetSearchRange($searchrange); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_SEARCH_REBUILDRESULTS)) { $rebuildresults = true; if (($dam = self::$decoder->getElementContent()) !== false) { $rebuildresults = true; if (!self::$decoder->getElementEndTag()) { return false; } } $cpo->SetSearchRebuildResults($rebuildresults); } if (self::$decoder->getElementStartTag(SYNC_SEARCH_DEEPTRAVERSAL)) { $deeptraversal = true; if (($dam = self::$decoder->getElementContent()) !== false) { $deeptraversal = true; if (!self::$decoder->getElementEndTag()) { return false; } } $cpo->SetSearchDeepTraversal($deeptraversal); } if (self::$decoder->getElementStartTag(SYNC_MIMESUPPORT)) { $cpo->SetMimeSupport(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } //TODO body preferences while (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_BODYPREFERENCE)) { if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TYPE)) { $bptype = self::$decoder->getElementContent(); $cpo->BodyPreference($bptype); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_TRUNCATIONSIZE)) { $cpo->BodyPreference($bptype)->SetTruncationSize(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_ALLORNONE)) { $cpo->BodyPreference($bptype)->SetAllOrNone(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_AIRSYNCBASE_PREVIEW)) { $cpo->BodyPreference($bptype)->SetPreview(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (!self::$decoder->getElementEndTag()) { return false; } } $e = self::$decoder->peek(); if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { self::$decoder->getElementEndTag(); break; } } } if (!self::$decoder->getElementEndTag()) { //store return false; } if (!self::$decoder->getElementEndTag()) { //search return false; } // get SearchProvider $searchprovider = ZPush::GetSearchProvider(); $status = SYNC_SEARCHSTATUS_SUCCESS; $rows = array(); // TODO support other searches if ($searchprovider->SupportsType($searchname)) { $storestatus = SYNC_SEARCHSTATUS_STORE_SUCCESS; try { if ($searchname == ISearchProvider::SEARCH_GAL) { //get search results from the searchprovider $rows = $searchprovider->GetGALSearchResults($searchquery, $searchrange); } elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) { $rows = $searchprovider->GetMailboxSearchResults($cpo); } } catch (StatusException $stex) { $storestatus = $stex->getCode(); } } else { $rows = array('searchtotal' => 0); $status = SYNC_SEARCHSTATUS_SERVERERROR; ZLog::Write(LOGLEVEL_WARN, sprintf("Searchtype '%s' is not supported.", $searchname)); self::$topCollector->AnnounceInformation(sprintf("Unsupported type '%s''", $searchname), true); } $searchprovider->Disconnect(); self::$topCollector->AnnounceInformation(sprintf("'%s' search found %d results", $searchname, $rows['searchtotal']), true); self::$encoder->startWBXML(); self::$encoder->startTag(SYNC_SEARCH_SEARCH); self::$encoder->startTag(SYNC_SEARCH_STATUS); self::$encoder->content($status); self::$encoder->endTag(); if ($status == SYNC_SEARCHSTATUS_SUCCESS) { self::$encoder->startTag(SYNC_SEARCH_RESPONSE); self::$encoder->startTag(SYNC_SEARCH_STORE); self::$encoder->startTag(SYNC_SEARCH_STATUS); self::$encoder->content($storestatus); self::$encoder->endTag(); if (isset($rows['range'])) { $searchrange = $rows['range']; unset($rows['range']); } if (isset($rows['searchtotal'])) { $searchtotal = $rows['searchtotal']; unset($rows['searchtotal']); } if ($searchname == ISearchProvider::SEARCH_GAL) { if (is_array($rows) && !empty($rows)) { foreach ($rows as $u) { self::$encoder->startTag(SYNC_SEARCH_RESULT); self::$encoder->startTag(SYNC_SEARCH_PROPERTIES); self::$encoder->startTag(SYNC_GAL_DISPLAYNAME); self::$encoder->content(isset($u[SYNC_GAL_DISPLAYNAME]) ? $u[SYNC_GAL_DISPLAYNAME] : "No name"); self::$encoder->endTag(); if (isset($u[SYNC_GAL_PHONE])) { self::$encoder->startTag(SYNC_GAL_PHONE); self::$encoder->content($u[SYNC_GAL_PHONE]); self::$encoder->endTag(); } if (isset($u[SYNC_GAL_OFFICE])) { self::$encoder->startTag(SYNC_GAL_OFFICE); self::$encoder->content($u[SYNC_GAL_OFFICE]); self::$encoder->endTag(); } if (isset($u[SYNC_GAL_TITLE])) { self::$encoder->startTag(SYNC_GAL_TITLE); self::$encoder->content($u[SYNC_GAL_TITLE]); self::$encoder->endTag(); } if (isset($u[SYNC_GAL_COMPANY])) { self::$encoder->startTag(SYNC_GAL_COMPANY); self::$encoder->content($u[SYNC_GAL_COMPANY]); self::$encoder->endTag(); } if (isset($u[SYNC_GAL_ALIAS])) { self::$encoder->startTag(SYNC_GAL_ALIAS); self::$encoder->content($u[SYNC_GAL_ALIAS]); self::$encoder->endTag(); } // Always send the firstname, even empty. Nokia needs this to display the entry self::$encoder->startTag(SYNC_GAL_FIRSTNAME); self::$encoder->content(isset($u[SYNC_GAL_FIRSTNAME]) ? $u[SYNC_GAL_FIRSTNAME] : ""); self::$encoder->endTag(); self::$encoder->startTag(SYNC_GAL_LASTNAME); self::$encoder->content(isset($u[SYNC_GAL_LASTNAME]) ? $u[SYNC_GAL_LASTNAME] : "No name"); self::$encoder->endTag(); if (isset($u[SYNC_GAL_HOMEPHONE])) { self::$encoder->startTag(SYNC_GAL_HOMEPHONE); self::$encoder->content($u[SYNC_GAL_HOMEPHONE]); self::$encoder->endTag(); } if (isset($u[SYNC_GAL_MOBILEPHONE])) { self::$encoder->startTag(SYNC_GAL_MOBILEPHONE); self::$encoder->content($u[SYNC_GAL_MOBILEPHONE]); self::$encoder->endTag(); } self::$encoder->startTag(SYNC_GAL_EMAILADDRESS); self::$encoder->content(isset($u[SYNC_GAL_EMAILADDRESS]) ? $u[SYNC_GAL_EMAILADDRESS] : ""); self::$encoder->endTag(); self::$encoder->endTag(); //result self::$encoder->endTag(); //properties } } } elseif ($searchname == ISearchProvider::SEARCH_MAILBOX) { foreach ($rows as $u) { self::$encoder->startTag(SYNC_SEARCH_RESULT); self::$encoder->startTag(SYNC_FOLDERTYPE); self::$encoder->content($u['class']); self::$encoder->endTag(); self::$encoder->startTag(SYNC_SEARCH_LONGID); self::$encoder->content($u['longid']); self::$encoder->endTag(); self::$encoder->startTag(SYNC_FOLDERID); self::$encoder->content($u['folderid']); self::$encoder->endTag(); self::$encoder->startTag(SYNC_SEARCH_PROPERTIES); $tmp = explode(":", $u['longid']); $message = self::$backend->Fetch($u['folderid'], $tmp[1], $cpo); $message->Encode(self::$encoder); self::$encoder->endTag(); //result self::$encoder->endTag(); //properties } } // it seems that android 4 requires range and searchtotal // or it won't display the search results if (isset($searchrange)) { self::$encoder->startTag(SYNC_SEARCH_RANGE); self::$encoder->content($searchrange); self::$encoder->endTag(); } if (isset($searchtotal) && $searchtotal > 0) { self::$encoder->startTag(SYNC_SEARCH_TOTAL); self::$encoder->content($searchtotal); self::$encoder->endTag(); } self::$encoder->endTag(); //store self::$encoder->endTag(); //response } self::$encoder->endTag(); //search return true; }
/** * Does the complete autodiscover. * @access public * @throws AuthenticationRequiredException if login to the backend failed. * @throws ZPushException if the incoming XML is invalid.. * * @return void */ public function DoAutodiscover() { if (!defined('REAL_BASE_PATH')) { define('REAL_BASE_PATH', str_replace('autodiscover/', '', BASE_PATH)); } set_include_path(get_include_path() . PATH_SEPARATOR . REAL_BASE_PATH); $response = ""; try { $incomingXml = $this->getIncomingXml(); $backend = ZPush::GetBackend(); $username = $this->login($backend, $incomingXml); $userDetails = $backend->GetUserDetails($username); $email = $this->getAttribFromUserDetails($userDetails, 'emailaddress') ? $this->getAttribFromUserDetails($userDetails, 'emailaddress') : $incomingXml->Request->EMailAddress; $userFullname = $this->getAttribFromUserDetails($userDetails, 'fullname') ? $this->getAttribFromUserDetails($userDetails, 'fullname') : $email; ZLog::Write(LOGLEVEL_WBXML, sprintf("Resolved user's '%s' fullname to '%s'", $username, $userFullname)); $response = $this->createResponse($email, $userFullname); setcookie("membername", $username); } catch (AuthenticationRequiredException $ex) { if (isset($incomingXml)) { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because login failed for user with email '%s'", $incomingXml->Request->EMailAddress)); } else { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover incorrect request: '%s'", $ex->getMessage())); } header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Basic realm="ZPush"'); http_response_code(401); } catch (ZPushException $ex) { ZLog::Write(LOGLEVEL_ERROR, sprintf("Unable to complete autodiscover because of ZPushException. Error: %s", $ex->getMessage())); if (!headers_sent()) { header('HTTP/1.1 ' . $ex->getHTTPCodeString()); foreach ($ex->getHTTPHeaders() as $h) { header($h); } } } $this->sendResponse($response); }
/** * Handles the FolderSync command * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { // Maps serverid -> clientid for items that are received from the PIM $map = array(); // Parse input if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC)) { return false; } if (!self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_SYNCKEY)) { return false; } $synckey = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } $status = SYNC_FSSTATUS_SUCCESS; $newsynckey = $synckey; try { $syncstate = self::$deviceManager->GetStateManager()->GetSyncState($synckey); // We will be saving the sync state under 'newsynckey' $newsynckey = self::$deviceManager->GetStateManager()->GetNewSyncKey($synckey); } catch (StateNotFoundException $snfex) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } catch (StateInvalidException $sive) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } // The ChangesWrapper caches all imports in-memory, so we can send a change count // before sending the actual data. // the HierarchyCache is notified and the changes from the PIM are transmitted to the actual backend $changesMem = self::$deviceManager->GetHierarchyChangesWrapper(); // the hierarchyCache should now fully be initialized - check for changes in the additional folders $changesMem->Config(ZPush::GetAdditionalSyncFolders()); // process incoming changes if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_CHANGES)) { // Ignore <Count> if present if (self::$decoder->getElementStartTag(SYNC_FOLDERHIERARCHY_COUNT)) { self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { return false; } } // Process the changes (either <Add>, <Modify>, or <Remove>) $element = self::$decoder->getElement(); if ($element[EN_TYPE] != EN_TYPE_STARTTAG) { return false; } $importer = false; while (1) { $folder = new SyncFolder(); if (!$folder->Decode(self::$decoder)) { break; } try { if ($status == SYNC_FSSTATUS_SUCCESS && !$importer) { // Configure the backends importer with last state $importer = self::$backend->GetImporter(); $importer->Config($syncstate); // the messages from the PIM will be forwarded to the backend $changesMem->forwardImporter($importer); } if ($status == SYNC_FSSTATUS_SUCCESS) { switch ($element[EN_TAG]) { case SYNC_ADD: case SYNC_MODIFY: $serverid = $changesMem->ImportFolderChange($folder); break; case SYNC_REMOVE: $serverid = $changesMem->ImportFolderDeletion($folder); break; } // TODO what does $map?? if ($serverid) { $map[$serverid] = $folder->clientid; } } else { ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): ignoring incoming folderchange for folder '%s' as status indicates problem.", $folder->displayname)); self::$topCollector->AnnounceInformation("Incoming change ignored", true); } } catch (StatusException $stex) { $status = $stex->getCode(); } } if (!self::$decoder->getElementEndTag()) { return false; } } else { // check for a potential process loop like described in Issue ZP-5 if ($synckey != "0" && self::$deviceManager->IsHierarchyFullResyncRequired()) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } self::$deviceManager->AnnounceProcessStatus(false, $status); } if (!self::$decoder->getElementEndTag()) { return false; } // We have processed incoming foldersync requests, now send the PIM // our changes // Output our WBXML reply now self::$encoder->StartWBXML(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_FOLDERSYNC); if ($status == SYNC_FSSTATUS_SUCCESS) { try { // do nothing if this is an invalid device id (like the 'validate' Androids internal client sends) if (!Request::IsValidDeviceID()) { throw new StatusException(sprintf("Request::IsValidDeviceID() indicated that '%s' is not a valid device id", Request::GetDeviceID()), SYNC_FSSTATUS_SERVERERROR); } // Changes from backend are sent to the MemImporter and processed for the HierarchyCache. // The state which is saved is from the backend, as the MemImporter is only a proxy. $exporter = self::$backend->GetExporter(); $exporter->Config($syncstate); $exporter->InitializeExporter($changesMem); // Stream all changes to the ImportExportChangesMem while (is_array($exporter->Synchronize())) { } // get the new state from the backend $newsyncstate = isset($exporter) ? $exporter->GetState() : ""; } catch (StatusException $stex) { if ($stex->getCode() == SYNC_FSSTATUS_CODEUNKNOWN) { $status = SYNC_FSSTATUS_SYNCKEYERROR; } else { $status = $stex->getCode(); } } } self::$encoder->startTag(SYNC_FOLDERHIERARCHY_STATUS); self::$encoder->content($status); self::$encoder->endTag(); if ($status == SYNC_FSSTATUS_SUCCESS) { self::$encoder->startTag(SYNC_FOLDERHIERARCHY_SYNCKEY); $synckey = $changesMem->IsStateChanged() ? $newsynckey : $synckey; self::$encoder->content($synckey); self::$encoder->endTag(); // Stream folders directly to the PDA $streamimporter = new ImportChangesStream(self::$encoder, false); $changesMem->InitializeExporter($streamimporter); $changeCount = $changesMem->GetChangeCount(); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_CHANGES); self::$encoder->startTag(SYNC_FOLDERHIERARCHY_COUNT); self::$encoder->content($changeCount); self::$encoder->endTag(); while ($changesMem->Synchronize()) { } self::$encoder->endTag(); self::$topCollector->AnnounceInformation(sprintf("Outgoing %d folders", $changeCount), true); // everything fine, save the sync state for the next time if ($synckey == $newsynckey) { self::$deviceManager->GetStateManager()->SetSyncState($newsynckey, $newsyncstate); } } self::$encoder->endTag(); return true; }
/** * Loads the command handler and processes a command sent from the mobile * * @access public * @return boolean */ public static function HandleRequest() { $handler = ZPush::GetRequestHandlerForCommand(Request::GetCommandCode()); // TODO handle WBXML exceptions here and print stack return $handler->Handle(Request::GetCommandCode()); }
/** * Reads and processes the request headers * * @access public * @return */ public static function ProcessHeaders() { self::$headers = array_change_key_case(apache_request_headers(), CASE_LOWER); self::$useragent = isset(self::$headers["user-agent"]) ? self::$headers["user-agent"] : self::UNKNOWN; if (!isset(self::$asProtocolVersion)) { self::$asProtocolVersion = isset(self::$headers["ms-asprotocolversion"]) ? self::filterEvilInput(self::$headers["ms-asprotocolversion"], self::NUMBERSDOT_ONLY) : ZPush::GetLatestSupportedASVersion(); } //if policykey is not yet set, try to set it from the header //the policy key might be set in Request::Initialize from the base64 encoded query if (!isset(self::$policykey)) { if (isset(self::$headers["x-ms-policykey"])) { self::$policykey = (int) self::filterEvilInput(self::$headers["x-ms-policykey"], self::NUMBERS_ONLY); } else { self::$policykey = 0; } } if (!empty($_SERVER['QUERY_STRING']) && Utils::IsBase64String($_SERVER['QUERY_STRING'])) { ZLog::Write(LOGLEVEL_DEBUG, "Using data from base64 encoded query string"); if (isset(self::$policykey)) { self::$headers["x-ms-policykey"] = self::$policykey; } if (isset(self::$asProtocolVersion)) { self::$headers["ms-asprotocolversion"] = self::$asProtocolVersion; } } if (!isset(self::$acceptMultipart) && isset(self::$headers["ms-asacceptmultipart"]) && strtoupper(self::$headers["ms-asacceptmultipart"]) == "T") { self::$acceptMultipart = true; } ZLog::Write(LOGLEVEL_DEBUG, sprintf("Request::ProcessHeaders() ASVersion: %s", self::$asProtocolVersion)); if (defined('USE_X_FORWARDED_FOR_HEADER') && USE_X_FORWARDED_FOR_HEADER == true && isset(self::$headers["x-forwarded-for"])) { $forwardedIP = self::filterEvilInput(self::$headers["x-forwarded-for"], self::NUMBERSDOT_ONLY); if ($forwardedIP) { self::$remoteAddr = $forwardedIP; ZLog::Write(LOGLEVEL_INFO, sprintf("'X-Forwarded-for' indicates remote IP: %s", self::$remoteAddr)); } } }
/** * Imports a folder change * * @param SyncFolder $folder folder to be changed * * @access public * @return boolean/SyncObject status/object with the ath least the serverid of the folder set */ public function ImportFolderChange($folder) { // if the destinationImporter is set, then this folder should be processed by another importer // instead of being loaded in memory. if (isset($this->destinationImporter)) { // normally the $folder->type is not set, but we need this value to check if the change operation is permitted // e.g. system folders can normally not be changed - set the type from cache and let the destinationImporter decide if (!isset($folder->type) || !$folder->type) { $cacheFolder = $this->GetFolder($folder->serverid); $folder->type = $cacheFolder->type; ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Set foldertype for folder '%s' from cache as it was not sent: '%s'", $folder->displayname, $folder->type)); } // KOE ZO-42: When Notes folders are updated in Outlook, it tries to update the name (that fails by default, as it's a system folder) // catch this case here and ignore the change if (($folder->type == SYNC_FOLDER_TYPE_NOTE || $folder->type == SYNC_FOLDER_TYPE_USER_NOTE) && ZPush::GetDeviceManager()->IsKoe()) { $retFolder = false; } else { $retFolder = $this->destinationImporter->ImportFolderChange($folder); } // if the operation was sucessfull, update the HierarchyCache if ($retFolder) { // if we get a folder back, we need to update some data in the cache if (isset($retFolder->serverid) && $retFolder->serverid) { // for folder creation, the serverid & backendid are not set and have to be updated if (!isset($folder->serverid) || $folder->serverid == "") { $folder->serverid = $retFolder->serverid; if (isset($retFolder->BackendId) && $retFolder->BackendId) { $folder->BackendId = $retFolder->BackendId; } } // if the parentid changed (folder was moved) this needs to be updated as well if ($retFolder->parentid != $folder->parentid) { $folder->parentid = $retFolder->parentid; } } $this->AddFolder($folder); } return $retFolder; } else { if (isset($folder->serverid)) { // The Zarafa/Kopano HierarchyExporter exports all kinds of changes for folders (e.g. update no. of unread messages in a folder). // These changes are not relevant for the mobiles, as something changes but the relevant displayname and parentid // stay the same. These changes will be dropped and are not sent! $cacheFolder = $this->GetFolder($folder->serverid); if ($folder->equals($this->GetFolder($folder->serverid))) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Change for folder '%s' will not be sent as modification is not relevant.", $folder->displayname)); return false; } // check if the parent ID is known on the device if (!isset($folder->parentid) || $folder->parentid != "0" && !$this->GetFolder($folder->parentid)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ChangesMemoryWrapper->ImportFolderChange(): Change for folder '%s' will not be sent as parent folder is not set or not known on mobile.", $folder->displayname)); return false; } // load this change into memory $this->changes[] = array(self::CHANGE, $folder); // HierarchyCache: already add/update the folder so changes are not sent twice (if exported twice) $this->AddFolder($folder); return true; } return false; } }
/** * Constructor * * @access public */ public function ZPushTop() { $this->starttime = time(); $this->currenttime = time(); $this->action = ""; $this->filter = false; $this->status = false; $this->statusexpire = 0; $this->helpexpire = 0; $this->doingTail = false; $this->wide = false; $this->terminate = false; $this->showPush = true; $this->showOption = self::SHOW_DEFAULT; $this->showTermSec = self::SHOW_TERM_DEFAULT_TIME; $this->scrSize = array('width' => 80, 'height' => 24); $this->pingInterval = defined('PING_INTERVAL') && PING_INTERVAL > 0 ? PING_INTERVAL : 12; // get a TopCollector $this->topCollector = ZPush::GetTopCollector(); }
/** * Marks a a device of the Request::GetGETUser() for resynchronization * * @param string $deviceId the device id * * @access public * @return boolean * @throws SoapFault */ public function ResyncDevice($deviceId) { $deviceId = preg_replace("/[^A-Za-z0-9]/", "", $deviceId); ZLog::Write(LOGLEVEL_INFO, sprintf("WebserviceDevice::ResyncDevice('%s'): mark device of user '%s' for resynchronization", $deviceId, Request::GetGETUser())); if (!ZPushAdmin::ResyncDevice(Request::GetGETUser(), $deviceId)) { ZPush::GetTopCollector()->AnnounceInformation(ZLog::GetLastMessage(LOGLEVEL_ERROR), true); throw new SoapFault("ERROR", ZLog::GetLastMessage(LOGLEVEL_ERROR)); } ZPush::GetTopCollector()->AnnounceInformation(sprintf("Resync requested - device id '%s'", $deviceId), true); return true; }
/** * Reads an email object from MAPI * * @param mixed $mapimessage * @param ContentParameters $contentparameters * * @access private * @return SyncEmail */ private function getEmail($mapimessage, $contentparameters) { $message = new SyncMail(); $this->getPropsFromMAPI($message, $mapimessage, MAPIMapping::GetEmailMapping()); $emailproperties = MAPIMapping::GetEmailProperties(); $messageprops = $this->getProps($mapimessage, $emailproperties); if (isset($messageprops[PR_SOURCE_KEY])) { $sourcekey = $messageprops[PR_SOURCE_KEY]; } else { return false; } //set the body according to contentparameters and supported AS version $this->setMessageBody($mapimessage, $contentparameters, $message); $fromname = $fromaddr = ""; if (isset($messageprops[$emailproperties["representingname"]])) { // remove encapsulating double quotes from the representingname $fromname = preg_replace('/^\\"(.*)\\"$/', "\${1}", $messageprops[$emailproperties["representingname"]]); } if (isset($messageprops[$emailproperties["representingentryid"]])) { $fromaddr = $this->getSMTPAddressFromEntryID($messageprops[$emailproperties["representingentryid"]]); } if ($fromname == $fromaddr) { $fromname = ""; } if ($fromname) { $from = "\"" . w2u($fromname) . "\" <" . w2u($fromaddr) . ">"; } else { //START CHANGED dw2412 HTC shows "error" if sender name is unknown $from = "\"" . w2u($fromaddr) . "\" <" . w2u($fromaddr) . ">"; } //END CHANGED dw2412 HTC shows "error" if sender name is unknown $message->from = $from; // process Meeting Requests if (isset($message->messageclass) && strpos($message->messageclass, "IPM.Schedule.Meeting") === 0) { $message->meetingrequest = new SyncMeetingRequest(); $this->getPropsFromMAPI($message->meetingrequest, $mapimessage, MAPIMapping::GetMeetingRequestMapping()); $meetingrequestproperties = MAPIMapping::GetMeetingRequestProperties(); $props = $this->getProps($mapimessage, $meetingrequestproperties); // Get the GOID if (isset($props[$meetingrequestproperties["goidtag"]])) { $message->meetingrequest->globalobjid = base64_encode($props[$meetingrequestproperties["goidtag"]]); } // Set Timezone if (isset($props[$meetingrequestproperties["timezonetag"]])) { $tz = $this->getTZFromMAPIBlob($props[$meetingrequestproperties["timezonetag"]]); } else { $tz = $this->getGMTTZ(); } $message->meetingrequest->timezone = base64_encode(TimezoneUtil::GetSyncBlobFromTZ($tz)); // send basedate if exception if (isset($props[$meetingrequestproperties["recReplTime"]]) || isset($props[$meetingrequestproperties["lidIsException"]]) && $props[$meetingrequestproperties["lidIsException"]] == true) { if (isset($props[$meetingrequestproperties["recReplTime"]])) { $basedate = $props[$meetingrequestproperties["recReplTime"]]; $message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, $this->getGMTTZ()); } else { if (!isset($props[$meetingrequestproperties["goidtag"]]) || !isset($props[$meetingrequestproperties["recurStartTime"]]) || !isset($props[$meetingrequestproperties["timezonetag"]])) { ZLog::Write(LOGLEVEL_WARN, "Missing property to set correct basedate for exception"); } else { $basedate = Utils::ExtractBaseDate($props[$meetingrequestproperties["goidtag"]], $props[$meetingrequestproperties["recurStartTime"]]); $message->meetingrequest->recurrenceid = $this->getGMTTimeByTZ($basedate, $tz); } } } // Organizer is the sender if (strpos($message->messageclass, "IPM.Schedule.Meeting.Resp") === 0) { $message->meetingrequest->organizer = $message->to; } else { $message->meetingrequest->organizer = $message->from; } // Process recurrence if (isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]]) { $myrec = new SyncMeetingRequestRecurrence(); // get recurrence -> put $message->meetingrequest as message so the 'alldayevent' is set correctly $this->getRecurrence($mapimessage, $props, $message->meetingrequest, $myrec, $tz); $message->meetingrequest->recurrences = array($myrec); } // Force the 'alldayevent' in the object at all times. (non-existent == 0) if (!isset($message->meetingrequest->alldayevent) || $message->meetingrequest->alldayevent == "") { $message->meetingrequest->alldayevent = 0; } // Instancetype // 0 = single appointment // 1 = master recurring appointment // 2 = single instance of recurring appointment // 3 = exception of recurring appointment $message->meetingrequest->instancetype = 0; if (isset($props[$meetingrequestproperties["isrecurringtag"]]) && $props[$meetingrequestproperties["isrecurringtag"]] == 1) { $message->meetingrequest->instancetype = 1; } else { if ((!isset($props[$meetingrequestproperties["isrecurringtag"]]) || $props[$meetingrequestproperties["isrecurringtag"]] == 0) && isset($message->meetingrequest->recurrenceid)) { if (isset($props[$meetingrequestproperties["appSeqNr"]]) && $props[$meetingrequestproperties["appSeqNr"]] == 0) { $message->meetingrequest->instancetype = 2; } else { $message->meetingrequest->instancetype = 3; } } } // Disable reminder if it is off if (!isset($props[$meetingrequestproperties["reminderset"]]) || $props[$meetingrequestproperties["reminderset"]] == false) { $message->meetingrequest->reminder = ""; } else { ///set the default reminder time to seconds if ($props[$meetingrequestproperties["remindertime"]] == 0x5ae980e1) { $message->meetingrequest->reminder = 900; } else { $message->meetingrequest->reminder = $props[$meetingrequestproperties["remindertime"]] * 60; } } // Set sensitivity to 0 if missing if (!isset($message->meetingrequest->sensitivity)) { $message->meetingrequest->sensitivity = 0; } // If the user is working from a location other than the office the busystatus should be interpreted as free. if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == fbWorkingElsewhere) { $message->meetingrequest->busystatus = fbFree; } // If the busystatus has the value of -1, we should be interpreted as tentative (1) / ZP-581 if (isset($message->meetingrequest->busystatus) && $message->meetingrequest->busystatus == -1) { $message->meetingrequest->busystatus = fbTentative; } // if a meeting request response hasn't been processed yet, // do it so that the attendee status is updated on the mobile if (!isset($messageprops[$emailproperties["processed"]])) { // check if we are not sending the MR so we can process it - ZP-581 $cuser = ZPush::GetBackend()->GetUserDetails(ZPush::GetBackend()->GetCurrentUsername()); if (isset($cuser["emailaddress"]) && $cuser["emailaddress"] != $fromaddr) { $req = new Meetingrequest($this->store, $mapimessage, $this->session); if ($req->isMeetingRequestResponse()) { $req->processMeetingRequestResponse(); } if ($req->isMeetingCancellation()) { $req->processMeetingCancellation(); } } } $message->contentclass = DEFAULT_CALENDAR_CONTENTCLASS; } // Add attachments $attachtable = mapi_message_getattachmenttable($mapimessage); $rows = mapi_table_queryallrows($attachtable, array(PR_ATTACH_NUM)); $entryid = bin2hex($messageprops[$emailproperties["entryid"]]); foreach ($rows as $row) { if (isset($row[PR_ATTACH_NUM])) { if (Request::GetProtocolVersion() >= 12.0) { $attach = new SyncBaseAttachment(); } else { $attach = new SyncAttachment(); } $mapiattach = mapi_message_openattach($mapimessage, $row[PR_ATTACH_NUM]); $attachprops = mapi_getprops($mapiattach, array(PR_ATTACH_LONG_FILENAME, PR_ATTACH_FILENAME, PR_ATTACHMENT_HIDDEN, PR_ATTACH_CONTENT_ID, PR_ATTACH_CONTENT_ID_W, PR_ATTACH_MIME_TAG, PR_ATTACH_MIME_TAG_W, PR_ATTACH_METHOD, PR_DISPLAY_NAME, PR_DISPLAY_NAME_W, PR_ATTACH_SIZE)); if (isset($attachprops[PR_ATTACH_MIME_TAG]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG]), 'signed') !== false || isset($attachprops[PR_ATTACH_MIME_TAG_W]) && strpos(strtolower($attachprops[PR_ATTACH_MIME_TAG_W]), 'signed') !== false) { continue; } // the displayname is handled equaly for all AS versions $attach->displayname = w2u(isset($attachprops[PR_ATTACH_LONG_FILENAME]) ? $attachprops[PR_ATTACH_LONG_FILENAME] : (isset($attachprops[PR_ATTACH_FILENAME]) ? $attachprops[PR_ATTACH_FILENAME] : (isset($attachprops[PR_DISPLAY_NAME]) ? $attachprops[PR_DISPLAY_NAME] : "attachment.bin"))); // fix attachment name in case of inline images if ($attach->displayname == "inline.txt" && (isset($attachprops[PR_ATTACH_MIME_TAG]) || $attachprops[PR_ATTACH_MIME_TAG_W])) { $mimetype = isset($attachprops[PR_ATTACH_MIME_TAG]) ? $attachprops[PR_ATTACH_MIME_TAG] : $attachprops[PR_ATTACH_MIME_TAG_W]; $mime = explode("/", $mimetype); if (count($mime) == 2 && $mime[0] == "image") { $attach->displayname = "inline." . $mime[1]; } } // set AS version specific parameters if (Request::GetProtocolVersion() >= 12.0) { $attach->filereference = $entryid . ":" . $row[PR_ATTACH_NUM]; $attach->method = isset($attachprops[PR_ATTACH_METHOD]) ? $attachprops[PR_ATTACH_METHOD] : ATTACH_BY_VALUE; // if displayname does not have the eml extension for embedde messages, android and WP devices won't open it if ($attach->method == ATTACH_EMBEDDED_MSG) { if (strtolower(substr($attach->displayname, -4)) != '.eml') { $attach->displayname .= '.eml'; } } $attach->estimatedDataSize = $attachprops[PR_ATTACH_SIZE]; if (isset($attachprops[PR_ATTACH_CONTENT_ID]) && $attachprops[PR_ATTACH_CONTENT_ID]) { $attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID]; } if (!isset($attach->contentid) && isset($attachprops[PR_ATTACH_CONTENT_ID_W]) && $attachprops[PR_ATTACH_CONTENT_ID_W]) { $attach->contentid = $attachprops[PR_ATTACH_CONTENT_ID_W]; } if (isset($attachprops[PR_ATTACHMENT_HIDDEN]) && $attachprops[PR_ATTACHMENT_HIDDEN]) { $attach->isinline = 1; } if (!isset($message->asattachments)) { $message->asattachments = array(); } array_push($message->asattachments, $attach); } else { $attach->attsize = $attachprops[PR_ATTACH_SIZE]; $attach->attname = $entryid . ":" . $row[PR_ATTACH_NUM]; if (!isset($message->attachments)) { $message->attachments = array(); } array_push($message->attachments, $attach); } } } // Get To/Cc as SMTP addresses (this is different from displayto and displaycc because we are putting // in the SMTP addresses as well, while displayto and displaycc could just contain the display names $message->to = array(); $message->cc = array(); $reciptable = mapi_message_getrecipienttable($mapimessage); $rows = mapi_table_queryallrows($reciptable, array(PR_RECIPIENT_TYPE, PR_DISPLAY_NAME, PR_ADDRTYPE, PR_EMAIL_ADDRESS, PR_SMTP_ADDRESS, PR_ENTRYID)); foreach ($rows as $row) { $address = ""; $fulladdr = ""; $addrtype = isset($row[PR_ADDRTYPE]) ? $row[PR_ADDRTYPE] : ""; if (isset($row[PR_SMTP_ADDRESS])) { $address = $row[PR_SMTP_ADDRESS]; } elseif ($addrtype == "SMTP" && isset($row[PR_EMAIL_ADDRESS])) { $address = $row[PR_EMAIL_ADDRESS]; } elseif ($addrtype == "ZARAFA" && isset($row[PR_ENTRYID])) { $address = $this->getSMTPAddressFromEntryID($row[PR_ENTRYID]); } $name = isset($row[PR_DISPLAY_NAME]) ? $row[PR_DISPLAY_NAME] : ""; if ($name == "" || $name == $address) { $fulladdr = w2u($address); } else { if (substr($name, 0, 1) != '"' && substr($name, -1) != '"') { $fulladdr = "\"" . w2u($name) . "\" <" . w2u($address) . ">"; } else { $fulladdr = w2u($name) . "<" . w2u($address) . ">"; } } if ($row[PR_RECIPIENT_TYPE] == MAPI_TO) { array_push($message->to, $fulladdr); } else { if ($row[PR_RECIPIENT_TYPE] == MAPI_CC) { array_push($message->cc, $fulladdr); } } } if (is_array($message->to) && !empty($message->to)) { $message->to = implode(", ", $message->to); } if (is_array($message->cc) && !empty($message->cc)) { $message->cc = implode(", ", $message->cc); } // without importance some mobiles assume "0" (low) - Mantis #439 if (!isset($message->importance)) { $message->importance = IMPORTANCE_NORMAL; } //TODO contentclass and nativebodytype and internetcpid if (!isset($message->internetcpid)) { $message->internetcpid = defined('STORE_INTERNET_CPID') ? constant('STORE_INTERNET_CPID') : INTERNET_CPID_WINDOWS1252; } $this->setFlag($mapimessage, $message); if (!isset($message->contentclass)) { $message->contentclass = DEFAULT_EMAIL_CONTENTCLASS; } if (!isset($message->nativebodytype)) { $message->nativebodytype = $this->getNativeBodyType($messageprops); } // reply, reply to all, forward flags if (isset($message->lastverbexecuted) && $message->lastverbexecuted) { $message->lastverbexecuted = Utils::GetLastVerbExecuted($message->lastverbexecuted); } return $message; }
/** * UnLinks all states from a folder id * Old states are removed assisting the StateMachine to get rid of old data. * The UUID is then removed from the device * * @param ASDevice $device * @param string $folderid * @param boolean $removeFromDevice indicates if the device should be * notified that the state was removed * @param boolean $retrieveUUIDFromDevice indicates if the UUID should be retrieved from * device. If not true this parameter will be used as UUID. * * @access public * @return boolean */ public static function UnLinkState(&$device, $folderid, $removeFromDevice = true, $retrieveUUIDFromDevice = true) { if ($retrieveUUIDFromDevice === true) { $savedUuid = $device->GetFolderUUID($folderid); } else { $savedUuid = $retrieveUUIDFromDevice; } if ($savedUuid) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("StateManager::UnLinkState('%s'): saved state '%s' will be deleted.", $folderid, $savedUuid)); ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::DEFTYPE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FOLDERDATA, $savedUuid); // CPO ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::FAILSAVE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::BACKENDSTORAGE, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); // remove all messages which could not be synched before $device->RemoveIgnoredMessage($folderid, false); if ($folderid === false && $savedUuid !== false) { ZPush::GetStateMachine()->CleanStates($device->GetDeviceId(), IStateMachine::HIERARCHY, $savedUuid, self::FIXEDHIERARCHYCOUNTER * 2); } } // delete this id from the uuid cache if ($removeFromDevice) { return $device->SetFolderUUID(false, $folderid); } else { return true; } }
/** * Gets the StateManager from the DeviceManager * if it's not available * * @access private * @return */ private function loadStateManager() { if (!isset($this->stateManager)) { $this->stateManager = ZPush::GetDeviceManager()->GetStateManager(); } }
/** * Handles the GetItemEstimate command * Returns an estimation of how many items will be synchronized at the next sync * This is mostly used to show something in the progress bar * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { $sc = new SyncCollections(); if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE)) { return false; } if (!self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERS)) { return false; } while (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDER)) { $spa = new SyncParameters(); $spastatus = false; if (Request::GetProtocolVersion() >= 14.0) { if (self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { try { $spa->SetSyncKey(self::$decoder->getElementContent()); } catch (StateInvalidException $siex) { $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED; } if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) { $spa->SetFolderId(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } // conversation mode requested if (self::$decoder->getElementStartTag(SYNC_CONVERSATIONMODE)) { $spa->SetConversationMode(true); if (($conversationmode = self::$decoder->getElementContent()) !== false) { $spa->SetConversationMode((bool) $conversationmode); if (!self::$decoder->getElementEndTag()) { return false; } } } if (self::$decoder->getElementStartTag(SYNC_OPTIONS)) { while (1) { if (self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { $spa->SetFilterType(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_FOLDERTYPE)) { $spa->SetContentClass(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_MAXITEMS)) { $spa->SetWindowSize($maxitems = self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } $e = self::$decoder->peek(); if ($e[EN_TYPE] == EN_TYPE_ENDTAG) { self::$decoder->getElementEndTag(); break; } } } } else { //get items estimate does not necessarily send the folder type if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERTYPE)) { $spa->SetContentClass(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (self::$decoder->getElementStartTag(SYNC_GETITEMESTIMATE_FOLDERID)) { $spa->SetFolderId(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } } if (!self::$decoder->getElementStartTag(SYNC_FILTERTYPE)) { return false; } $spa->SetFilterType(self::$decoder->getElementContent()); if (!self::$decoder->getElementEndTag()) { return false; } if (!self::$decoder->getElementStartTag(SYNC_SYNCKEY)) { return false; } try { $spa->SetSyncKey(self::$decoder->getElementContent()); } catch (StateInvalidException $siex) { $spastatus = SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED; } if (!self::$decoder->getElementEndTag()) { return false; } } if (!self::$decoder->getElementEndTag()) { return false; } //SYNC_GETITEMESTIMATE_FOLDER // Process folder data //In AS 14 request only collectionid is sent, without class if (!$spa->HasContentClass() && $spa->HasFolderId()) { try { $spa->SetContentClass(self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId())); } catch (NoHierarchyCacheAvailableException $nhca) { $spastatus = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID; } } // compatibility mode AS 1.0 - get folderid which was sent during GetHierarchy() if (!$spa->HasFolderId() && $spa->HasContentClass()) { $spa->SetFolderId(self::$deviceManager->GetFolderIdFromCacheByClass($spa->GetContentClass())); } // Add collection to SC and load state $sc->AddCollection($spa); if ($spastatus) { // the CPO has a folder id now, so we can set the status $sc->AddParameter($spa, "status", $spastatus); } else { try { $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey())); // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) { throw new StatusException(sprintf("HandleGetItemEstimate() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_GETITEMESTSTATUS_COLLECTIONINVALID); } } catch (StateNotFoundException $snfex) { // ok, the key is invalid. Question is, if the hierarchycache is still ok //if not, we have to issue SYNC_GETITEMESTSTATUS_COLLECTIONINVALID which triggers a FolderSync try { self::$deviceManager->GetFolderClassFromCacheByID($spa->GetFolderId()); // we got here, so the HierarchyCache is ok $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCKKEYINVALID); } catch (NoHierarchyCacheAvailableException $nhca) { $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID); } self::$topCollector->AnnounceInformation("StateNotFoundException " . $sc->GetParameter($spa, "status"), true); } catch (StatusException $stex) { if ($stex->getCode() == SYNC_GETITEMESTSTATUS_COLLECTIONINVALID) { $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_COLLECTIONINVALID); } else { $sc->AddParameter($spa, "status", SYNC_GETITEMESTSTATUS_SYNCSTATENOTPRIMED); } self::$topCollector->AnnounceInformation("StatusException " . $sc->GetParameter($spa, "status"), true); } } } if (!self::$decoder->getElementEndTag()) { return false; } //SYNC_GETITEMESTIMATE_FOLDERS if (!self::$decoder->getElementEndTag()) { return false; } //SYNC_GETITEMESTIMATE_GETITEMESTIMATE self::$encoder->startWBXML(); self::$encoder->startTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE); $status = SYNC_GETITEMESTSTATUS_SUCCESS; // look for changes in all collections try { $sc->CountChanges(); } catch (StatusException $ste) { $status = SYNC_GETITEMESTSTATUS_COLLECTIONINVALID; } $changes = $sc->GetChangedFolderIds(); foreach ($sc as $folderid => $spa) { self::$encoder->startTag(SYNC_GETITEMESTIMATE_RESPONSE); if ($sc->GetParameter($spa, "status")) { $status = $sc->GetParameter($spa, "status"); } self::$encoder->startTag(SYNC_GETITEMESTIMATE_STATUS); self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDER); self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERTYPE); self::$encoder->content($spa->GetContentClass()); self::$encoder->endTag(); self::$encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERID); self::$encoder->content($spa->GetFolderId()); self::$encoder->endTag(); if (isset($changes[$folderid]) && $changes[$folderid] !== false) { self::$encoder->startTag(SYNC_GETITEMESTIMATE_ESTIMATE); self::$encoder->content($changes[$folderid]); self::$encoder->endTag(); if ($changes[$folderid] > 0) { self::$topCollector->AnnounceInformation(sprintf("%s %d changes", $spa->GetContentClass(), $changes[$folderid]), true); } } self::$encoder->endTag(); self::$encoder->endTag(); } if (array_sum($changes) == 0) { self::$topCollector->AnnounceInformation("No changes found", true); } self::$encoder->endTag(); return true; }
/** * Execute the migration * * @access public * @return true */ public function DoMigration() { // go through all files $files = glob(STATE_DIR . "/*/*/*", GLOB_NOSORT); $filetotal = count($files); $filecount = 0; $rencount = 0; $igncount = 0; foreach ($files as $file) { $filecount++; $newfile = strtolower($file); echo "[1G"; if ($file !== $newfile) { $rencount++; rename($file, $newfile); } else { $igncount++; } printf("Migrating file %d/%d\t%s", $filecount, $filetotal, $file); } echo "[1G" . sprintf("Migrated total of %d files, %d renamed and %d ignored (as already correct)%s\n\n", $filetotal, $rencount, $igncount, str_repeat(" ", 50)); // get all states of synchronized devices $alldevices = $this->sm->GetAllDevices(false); foreach ($alldevices as $devid) { $lowerDevid = strtolower($devid); echo "Processing device: " . $devid . "\t"; // update device data $devState = ZPush::GetStateMachine()->GetState($lowerDevid, IStateMachine::DEVICEDATA); $newdata = array(); foreach ($devState->devices as $user => $dev) { if (!isset($dev->deviceidOrg)) { $dev->deviceidOrg = $dev->deviceid; } $dev->deviceid = strtolower($dev->deviceid); $dev->useragenthistory = array_unique($dev->useragenthistory); $newdata[$user] = $dev; } $devState->devices = $newdata; $this->sm->SetState($devState, $lowerDevid, IStateMachine::DEVICEDATA); // go through the users again: device was updated sucessfully, now we change the global user <-> device link foreach ($devState->devices as $user => $dev) { printf("\n\tUn-linking %s with old device id %s", $user, $dev->deviceidOrg); $this->sm->UnLinkUserDevice($user, $dev->deviceidOrg); printf("\n\tRe-linking %s with new device id %s", $user, $dev->deviceid); $this->sm->LinkUserDevice($user, $dev->deviceid); } echo "\n\tcompleted\n"; } echo "\nSetting new StateVersion\n"; $this->sm->SetStateVersion(self::TOVERSION); echo "Migration completed!\n\n"; return true; }
/** * Imports a change on a folder * * @param object $folder SyncFolder * * @access public * @return string id of the folder */ public function ImportFolderChange($folder) { // checks if the next message may cause a loop or is broken if (ZPush::GetDeviceManager(false) && ZPush::GetDeviceManager()->DoNotStreamMessage($folder->serverid, $folder)) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesStream->ImportFolderChange('%s'): folder ignored as requested by DeviceManager.", $folder->serverid)); return true; } // send a modify flag if the folder is already known on the device if (isset($folder->flags) && $folder->flags === SYNC_NEWMESSAGE) { $this->encoder->startTag(SYNC_FOLDERHIERARCHY_ADD); } else { $this->encoder->startTag(SYNC_FOLDERHIERARCHY_UPDATE); } $folder->Encode($this->encoder); $this->encoder->endTag(); return true; }
/** * Handles the MoveItems command * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { if (!self::$decoder->getElementStartTag(SYNC_MOVE_MOVES)) { return false; } $moves = array(); while (self::$decoder->getElementStartTag(SYNC_MOVE_MOVE)) { $move = array(); if (self::$decoder->getElementStartTag(SYNC_MOVE_SRCMSGID)) { $move["srcmsgid"] = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { break; } } if (self::$decoder->getElementStartTag(SYNC_MOVE_SRCFLDID)) { $move["srcfldid"] = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { break; } } if (self::$decoder->getElementStartTag(SYNC_MOVE_DSTFLDID)) { $move["dstfldid"] = self::$decoder->getElementContent(); if (!self::$decoder->getElementEndTag()) { break; } } array_push($moves, $move); if (!self::$decoder->getElementEndTag()) { return false; } } if (!self::$decoder->getElementEndTag()) { return false; } self::$encoder->StartWBXML(); self::$encoder->startTag(SYNC_MOVE_MOVES); foreach ($moves as $move) { self::$encoder->startTag(SYNC_MOVE_RESPONSE); self::$encoder->startTag(SYNC_MOVE_SRCMSGID); self::$encoder->content($move["srcmsgid"]); self::$encoder->endTag(); $status = SYNC_MOVEITEMSSTATUS_SUCCESS; $result = false; try { // if the source folder is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($move["srcfldid"]))) { throw new StatusException(sprintf("HandleMoveItems() could not Setup() the backend for folder id '%s'", $move["srcfldid"]), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } $importer = self::$backend->GetImporter($move["srcfldid"]); if ($importer === false) { throw new StatusException(sprintf("HandleMoveItems() could not get an importer for folder id '%s'", $move["srcfldid"]), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } $result = $importer->ImportMessageMove($move["srcmsgid"], $move["dstfldid"]); // We discard the importer state for now. } catch (StatusException $stex) { if ($stex->getCode() == SYNC_STATUS_FOLDERHIERARCHYCHANGED) { // same as SYNC_FSSTATUS_CODEUNKNOWN $status = SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID; } else { $status = $stex->getCode(); } } self::$topCollector->AnnounceInformation(sprintf("Operation status: %s", $status), true); self::$encoder->startTag(SYNC_MOVE_STATUS); self::$encoder->content($status); self::$encoder->endTag(); self::$encoder->startTag(SYNC_MOVE_DSTMSGID); self::$encoder->content($result !== false ? $result : $move["srcmsgid"]); self::$encoder->endTag(); self::$encoder->endTag(); } self::$encoder->endTag(); return true; }
/** * Loads the states and writes them into the SyncCollection Object and the actiondata failstate * * @param SyncCollection $sc SyncCollection object * @param SyncParameters $spa SyncParameters object * @param array $actiondata Actiondata array * @param boolean $loadFailsave (opt) default false - indicates if the failsave states should be loaded * * @access private * @return status indicating if there were errors. If no errors, status is SYNC_STATUS_SUCCESS */ private function loadStates($sc, $spa, &$actiondata, $loadFailsave = false) { $status = SYNC_STATUS_SUCCESS; if ($sc->GetParameter($spa, "state") == null) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("Sync->loadStates(): loading states for folder '%s'", $spa->GetFolderId())); try { $sc->AddParameter($spa, "state", self::$deviceManager->GetStateManager()->GetSyncState($spa->GetSyncKey())); if ($loadFailsave) { // if this request was made before, there will be a failstate available $actiondata["failstate"] = self::$deviceManager->GetStateManager()->GetSyncFailState(); } // if this is an additional folder the backend has to be setup correctly if (!self::$backend->Setup(ZPush::GetAdditionalSyncFolderStore($spa->GetFolderId()))) { throw new StatusException(sprintf("HandleSync() could not Setup() the backend for folder id '%s'", $spa->GetFolderId()), SYNC_STATUS_FOLDERHIERARCHYCHANGED); } } catch (StateNotFoundException $snfex) { $status = SYNC_STATUS_INVALIDSYNCKEY; self::$topCollector->AnnounceInformation("StateNotFoundException", true); } catch (StatusException $stex) { $status = $stex->getCode(); self::$topCollector->AnnounceInformation(sprintf("StatusException code: %d", $status), true); } } return $status; }
/** * Saves the permanent and state related storage data of the user and device * if they were loaded previousily * If the backend storage is used this should be called * * @access protected * @return */ protected function SaveStorages() { if (isset($this->permanentStorage)) { try { ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->permanentStorage, StateManager::BACKENDSTORAGE_PERMANENT); } catch (StateNotYetAvailableException $snyae) { } catch (StateNotFoundException $snfe) { } } if (isset($this->stateStorage)) { try { $this->storage_state = ZPush::GetDeviceManager()->GetStateManager()->SetBackendStorage($this->stateStorage, StateManager::BACKENDSTORAGE_STATE); } catch (StateNotYetAvailableException $snyae) { } catch (StateNotFoundException $snfe) { } } }
/** * Imports a move of a message. This occurs when a user moves an item to another folder * * Normally, we would implement this via the 'offical' importmessagemove() function on the ICS importer, * but the Zarafa importer does not support this. Therefore we currently implement it via a standard mapi * call. This causes a mirror 'add/delete' to be sent to the PDA at the next sync. * Manfred, 2010-10-21. For some mobiles import was causing duplicate messages in the destination folder * (Mantis #202). Therefore we will create a new message in the destination folder, copy properties * of the source message to the new one and then delete the source message. * * @param string $id * @param string $newfolder destination folder * * @access public * @return boolean * @throws StatusException */ public function ImportMessageMove($id, $newfolder) { if (strtolower($newfolder) == strtolower(bin2hex($this->folderid))) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, source and destination are equal", $id, $newfolder), SYNC_MOVEITEMSSTATUS_SAMESOURCEANDDEST); } // Get the entryid of the message we're moving $entryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid, hex2bin($id)); if (!$entryid) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to resolve source message id", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } //open the source message $srcmessage = mapi_msgstore_openentry($this->store, $entryid); if (!$srcmessage) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open source message: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } // get correct mapi store for the destination folder $dststore = ZPush::GetBackend()->GetMAPIStoreForFolderId(ZPush::GetAdditionalSyncFolderStore($newfolder), $newfolder); if ($dststore === false) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open store of destination folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); } $dstentryid = mapi_msgstore_entryidfromsourcekey($dststore, hex2bin($newfolder)); if (!$dstentryid) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to resolve destination folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); } $dstfolder = mapi_msgstore_openentry($dststore, $dstentryid); if (!$dstfolder) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open destination folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); } $newmessage = mapi_folder_createmessage($dstfolder); if (!$newmessage) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to create message in destination folder: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_INVALIDDESTID); } // Copy message mapi_copyto($srcmessage, array(), array(), $newmessage); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, copy to destination message failed: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } $srcfolderentryid = mapi_msgstore_entryidfromsourcekey($this->store, $this->folderid); if (!$srcfolderentryid) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to resolve source folder", $id, $newfolder), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } $srcfolder = mapi_msgstore_openentry($this->store, $srcfolderentryid); if (!$srcfolder) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, unable to open source folder: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_INVALIDSOURCEID); } // Save changes mapi_savechanges($newmessage); if (mapi_last_hresult()) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, mapi_savechanges() failed: 0x%X", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_CANNOTMOVE); } // Delete the old message if (!mapi_folder_deletemessages($srcfolder, array($entryid))) { throw new StatusException(sprintf("ImportChangesICS->ImportMessageMove('%s','%s'): Error, delete of source message failed: 0x%X. Possible duplicates.", $id, $newfolder, mapi_last_hresult()), SYNC_MOVEITEMSSTATUS_SOURCEORDESTLOCKED); } $sourcekeyprops = mapi_getprops($newmessage, array(PR_SOURCE_KEY)); if (isset($sourcekeyprops[PR_SOURCE_KEY]) && $sourcekeyprops[PR_SOURCE_KEY]) { return bin2hex($sourcekeyprops[PR_SOURCE_KEY]); } return false; }