/** * 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 * * @access public */ public function __construct() { $this->statemachine = ZPush::GetStateMachine(); $this->deviceHash = false; $this->devid = Request::GetDeviceID(); $this->saveDevice = true; $this->windowSize = array(); $this->latestFolder = false; $this->hierarchySyncRequired = false; // only continue if deviceid is set if ($this->devid) { $this->device = new ASDevice($this->devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); $this->loadDeviceData(); ZPush::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent()); } else { throw new FatalNotImplementedException("Can not proceed without a device id."); } $this->loopdetection = new LoopDetection(); $this->loopdetection->ProcessLoopDetectionInit(); $this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed(); $this->stateManager = new StateManager(); $this->stateManager->SetDevice($this->device); $this->additionalFoldersHash = $this->getAdditionalFoldersHash(); if ($this->IsKoe() && $this->device->GetKoeVersion() !== false) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KOE: %s / %s / %s", $this->device->GetKoeVersion(), $this->device->GetKoeBuild(), strftime("%Y-%m-%d %H:%M", $this->device->GetKoeBuildDate()))); } }
/** * Initializes the logging * * @access public * @return boolean */ public static function Initialize() { global $specialLogUsers; // define some constants for the logging if (!defined('LOGUSERLEVEL')) { define('LOGUSERLEVEL', LOGLEVEL_OFF); } if (!defined('LOGLEVEL')) { define('LOGLEVEL', LOGLEVEL_OFF); } list($user, ) = Utils::SplitDomainUser(Request::GetGETUser()); if (!defined('WBXML_DEBUG') && $user) { // define the WBXML_DEBUG mode on user basis depending on the configurations if (LOGLEVEL >= LOGLEVEL_WBXML || LOGUSERLEVEL >= LOGLEVEL_WBXML && in_array($user, $specialLogUsers)) { define('WBXML_DEBUG', true); } else { define('WBXML_DEBUG', false); } } if ($user) { self::$user = '******' . $user . '] '; } else { self::$user = ''; } // log the device id if the global loglevel is set to log devid or the user is in and has the right log level if (Request::GetDeviceID() != "" && (LOGLEVEL >= LOGLEVEL_DEVICEID || LOGUSERLEVEL >= LOGLEVEL_DEVICEID && in_array($user, $specialLogUsers))) { self::$devid = '[' . Request::GetDeviceID() . '] '; } else { self::$devid = ''; } return true; }
/** * Initializes internal parameters * * @access public * @return boolean */ public function InitializeParams() { if (!isset(self::$devid)) { self::$devid = Request::GetDeviceID(); self::$pid = @getmypid(); self::$user = Request::GetAuthUser(); self::$start = time(); } return true; }
/** * Constructor * * Performs some basic checks and initilizes the state directory * * @access public * @throws FatalMisconfigurationException */ public function FileStateMachine() { if (!defined('STATE_DIR')) { throw new FatalMisconfigurationException("No configuration for the state directory available."); } if (substr(STATE_DIR, -1, 1) != "/") { throw new FatalMisconfigurationException("The configured state directory should terminate with a '/'"); } if (!file_exists(STATE_DIR)) { throw new FatalMisconfigurationException("The configured state directory does not exist or can not be accessed: " . STATE_DIR); } // checks if the directory exists and tries to create the necessary subfolders if they do not exist $this->getDirectoryForDevice(Request::GetDeviceID()); $this->userfilename = STATE_DIR . 'users'; if (!touch($this->userfilename)) { throw new FatalMisconfigurationException("Not possible to write to the configured state directory."); } }
/** * 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"); } $this->server->handle(); ZLog::Write(LOGLEVEL_DEBUG, sprintf("Webservice::HandleWebservice('%s'): sucessfully sent %d bytes", $commandCode, ob_get_length())); return true; }
/** * Constructor * * @access public */ public function DeviceManager() { $this->statemachine = ZPush::GetStateMachine(); $this->deviceHash = false; $this->devid = Request::GetDeviceID(); $this->windowSize = array(); $this->latestFolder = false; $this->hierarchySyncRequired = false; // only continue if deviceid is set if ($this->devid) { $this->device = new ASDevice($this->devid, Request::GetDeviceType(), Request::GetGETUser(), Request::GetUserAgent()); $this->loadDeviceData(); ZPush::GetTopCollector()->SetUserAgent($this->device->GetDeviceUserAgent()); } else { throw new FatalNotImplementedException("Can not proceed without a device id."); } $this->loopdetection = new LoopDetection(); $this->loopdetection->ProcessLoopDetectionInit(); $this->loopdetection->ProcessLoopDetectionPreviousConnectionFailed(); $this->stateManager = new StateManager(); $this->stateManager->SetDevice($this->device); }
try { // check config & initialize the basics ZPush::CheckConfig(); Request::Initialize(); ZLog::Initialize(); ZLog::Write(LOGLEVEL_DEBUG, "-------- Start"); ZLog::Write(LOGLEVEL_INFO, sprintf("Version='%s' method='%s' from='%s' cmd='%s' getUser='******' devId='%s' devType='%s'", @constant('ZPUSH_VERSION'), Request::GetMethod(), Request::GetRemoteAddr(), Request::GetCommand(), Request::GetGETUser(), Request::GetDeviceID(), Request::GetDeviceType())); // Stop here if this is an OPTIONS request if (Request::IsMethodOPTIONS()) { throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST); } ZPush::CheckAdvancedConfig(); // Process request headers and look for AS headers Request::ProcessHeaders(); // Check required GET parameters if (Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetGETUser() || !Request::GetDeviceID() || !Request::GetDeviceType())) { throw new FatalException("Requested the Z-Push URL without the required GET parameters"); } // Load the backend $backend = ZPush::GetBackend(); // always request the authorization header if (!Request::AuthenticationInfo()) { throw new AuthenticationRequiredException("Access denied. Please send authorisation information"); } // check the provisioning information if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(Request::GetCommandCode()) && (Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0 || ZPush::GetDeviceManager()->ProvisioningRequired(Request::GetPolicyKey())) && (LOOSE_PROVISIONING === false || LOOSE_PROVISIONING === true && Request::WasPolicyKeySent())) { //TODO for AS 14 send a wbxml response throw new ProvisioningRequiredException(); } // most commands require an authenticated user if (ZPush::CommandNeedsAuthentication(Request::GetCommandCode())) {
/** * 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; }
/** * Imports a move of a message. This occurs when a user moves an item to another folder * * @param string $id * @param string $newfolder * * @access public * @return boolean */ public function ImportMessageMove($id, $newfolder) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportMessageMove('%s', '%s')", $id, $newfolder)); if (!$this->icc) { ZLog::Write(LOGLEVEL_ERROR, "ImportChangesCombined->ImportMessageMove icc not configured"); return false; } if ($this->backend->GetBackendId($this->folderid) != $this->backend->GetBackendId($newfolder)) { ZLog::Write(LOGLEVEL_WARN, "ImportChangesCombined->ImportMessageMove() cannot move message between two backends"); return false; } $res = $this->icc->ImportMessageMove($id, $this->backend->GetBackendFolder($newfolder)); if ($res) { //TODO: we should add newid to new folder, instead of a full folder resync ZLog::Write(LOGLEVEL_DEBUG, sprintf("ImportChangesCombined->ImportMessageMove(): Force resync of dest folder (%s)", $newfolder)); ZPushAdmin::ResyncFolder(Request::GetAuthUser(), Request::GetDeviceID(), $newfolder); } return $res; }
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 including the 'WBXML debug data' logged. Be aware that the debug data could contain confidential information."); } 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_INFO, sprintf("cmd='%s' memory='%s/%s' time='%ss' devType='%s' devId='%s' getUser='******' from='%s' version='%s' method='%s' httpcode='%s'", Request::GetCommand(), Utils::FormatBytes(memory_get_peak_usage(false)), Utils::FormatBytes(memory_get_peak_usage(true)), number_format(microtime(true) - $_SERVER["REQUEST_TIME_FLOAT"], 2), Request::GetDeviceType(), Request::GetDeviceID(), Request::GetGETUser(), Request::GetRemoteAddr(), @constant('ZPUSH_VERSION'), Request::GetMethod(), http_response_code())); ZLog::Write(LOGLEVEL_DEBUG, "-------- End");
} else { throw new NoPostRequestException("Options request", NoPostRequestException::OPTIONS_REQUEST); } } ZPush::CheckAdvancedConfig(); // Process request headers and look for AS headers Request::ProcessHeaders(); // Check required GET parameters if (Request::IsMethodPOST() && (Request::GetCommandCode() === false || !Request::GetDeviceID() || !Request::GetDeviceType())) { throw new FatalException("Requested the Z-Push URL without the required GET parameters"); } // This won't be useful with Zarafa, but it will be with standalone Z-Push if (defined('PRE_AUTHORIZE_USERS') && PRE_AUTHORIZE_USERS === true) { if (!Request::IsMethodGET()) { // Check if User/Device are authorized if (ZPush::GetDeviceManager()->GetUserDevicePermission($GETUser, Request::GetDeviceID()) != SYNC_COMMONSTATUS_SUCCESS) { throw new AuthenticationRequiredException("Access denied. Username and Device not authorized"); } } } // Load the backend $backend = ZPush::GetBackend(); // always request the authorization header if (!$autenticationInfo || !$GETUser) { throw new AuthenticationRequiredException("Access denied. Please send authorisation information"); } // check the provisioning information if (PROVISIONING === true && Request::IsMethodPOST() && ZPush::CommandNeedsProvisioning(Request::GetCommandCode()) && (Request::WasPolicyKeySent() && Request::GetPolicyKey() == 0 || ZPush::GetDeviceManager()->ProvisioningRequired(Request::GetPolicyKey())) && (LOOSE_PROVISIONING === false || LOOSE_PROVISIONING === true && Request::WasPolicyKeySent())) { //TODO for AS 14 send a wbxml response throw new ProvisioningRequiredException(); }
/** * Sends an email notification to the user containing the data the user tried to save. * * @param SyncObject $message * @param SyncObject $oldmessage * @return void */ private function sendNotificationEmail($message, $oldmessage) { // get email address and full name of the user $userinfo = ZPush::GetBackend()->GetUserDetails(Request::GetAuthUser()); // get the name of the folder $foldername = "unknown"; $folderid = bin2hex($this->folderid); $folders = ZPush::GetAdditionalSyncFolders(); if (isset($folders[$folderid]) && isset($folders[$folderid]->displayname)) { $foldername = $folders[$folderid]->displayname; } // get the differences between the two objects $data = substr(get_class($oldmessage), 4) . "\r\n"; // get the suppported fields as we need them to determine the ghosted properties $supportedFields = ZPush::GetDeviceManager()->GetSupportedFields(ZPush::GetDeviceManager()->GetFolderIdForBackendId($folderid)); $dataarray = $oldmessage->EvaluateAndCompare($message, @constant('READ_ONLY_NOTIFY_YOURDATA'), $supportedFields); foreach ($dataarray as $key => $value) { $value = str_replace("\r", "", $value); $value = str_replace("\n", str_pad("\r\n", 25), $value); $data .= str_pad(ucfirst($key) . ":", 25) . $value . "\r\n"; } // build a simple mime message $toEmail = $userinfo['emailaddress']; $mail = "From: Z-Push <no-reply>\r\n"; $mail .= "To: {$toEmail}\r\n"; $mail .= "Content-Type: text/plain; charset=utf-8\r\n"; $mail .= "Subject: " . @constant('READ_ONLY_NOTIFY_SUBJECT') . "\r\n\r\n"; $mail .= @constant('READ_ONLY_NOTIFY_BODY') . "\r\n"; // replace values of template $mail = str_replace("**USERFULLNAME**", $userinfo['fullname'], $mail); $mail = str_replace("**DATE**", strftime(@constant('READ_ONLY_NOTIFY_DATE_FORMAT')), $mail); $mail = str_replace("**TIME**", strftime(@constant('READ_ONLY_NOTIFY_TIME_FORMAT')), $mail); $mail = str_replace("**FOLDERNAME**", $foldername, $mail); $mail = str_replace("**MOBILETYPE**", Request::GetDeviceType(), $mail); $mail = str_replace("**MOBILEDEVICEID**", Request::GetDeviceID(), $mail); $mail = str_replace("**DIFFERENCES**", $data, $mail); // user send email to himself $m = new SyncSendMail(); $m->saveinsent = false; $m->replacemime = true; $m->mime = $mail; ZPush::GetBackend()->SendMail($m); }
/** * Handles the FolderSync command * * @param int $commandCode * * @access public * @return boolean */ public function Handle($commandCode) { // 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; } // every FolderSync with SyncKey 0 should return the supported AS version & command headers if ($synckey == "0") { self::$specialHeaders = array(); self::$specialHeaders[] = ZPush::GetSupportedProtocolVersions(); self::$specialHeaders[] = ZPush::GetSupportedCommands(); } $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; } } 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 $maxExporttime = Request::GetExpectedConnectionTimeout(); $totalChanges = $exporter->GetChangeCount(); $started = time(); $exported = 0; $partial = false; while (is_array($exporter->Synchronize())) { $exported++; if (time() % 4) { self::$topCollector->AnnounceInformation(sprintf("Exported %d from %d folders", $exported, $totalChanges)); } // if partial sync is allowed, stop if this takes too long if (USE_PARTIAL_FOLDERSYNC && time() - $started > $maxExporttime) { ZLog::Write(LOGLEVEL_WARN, sprintf("Request->HandleFolderSync(): Exporting folders is too slow. In %d seconds only %d from %d changes were processed.", time() - $started, $exported, $totalChanges)); self::$topCollector->AnnounceInformation(sprintf("Partial export of %d out of %d folders", $exported, $totalChanges), true); self::$deviceManager->SetFolderSyncComplete(false); $partial = true; break; } } // update the foldersync complete flag if (USE_PARTIAL_FOLDERSYNC && $partial == false && self::$deviceManager->GetFolderSyncComplete() === false) { // say that we are done with partial synching self::$deviceManager->SetFolderSyncComplete(true); // reset the loop data to prevent any loop detection to kick in now self::$deviceManager->ClearLoopDetectionData(Request::GetAuthUser(), Request::GetDeviceID()); ZLog::Write(LOGLEVEL_INFO, "Request->HandleFolderSync(): Chunked exporting of folders completed successfully"); } // 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; }
/** * Returns the logger object. If no logger has been initialized, FileLog will be initialized and returned. * * @access private * @return Log * @throws Exception thrown if the logger class cannot be instantiated. */ private static function getLogger() { if (!self::$logger) { global $specialLogUsers; // This variable comes from the configuration file (config.php) $logger = LOGBACKEND_CLASS; if (!class_exists($logger)) { $errmsg = 'The configured logging class `' . $logger . '` does not exist. Check your configuration.'; error_log($errmsg); throw new \Exception($errmsg); } list($user) = Utils::SplitDomainUser(strtolower(Request::GetGETUser())); $user = '******' . $user . ']'; self::$logger = new $logger(); self::$logger->SetUser($user); self::$logger->SetAuthUser(Request::GetAuthUser()); self::$logger->SetSpecialLogUsers($specialLogUsers); self::$logger->SetDevid('[' . Request::GetDeviceID() . ']'); self::$logger->SetPidstr('[' . str_pad(@getmypid(), 5, " ", STR_PAD_LEFT) . ']'); self::$logger->AfterInitialize(); } return self::$logger; }
/** * Authenticates the user with the configured Kopano server * * @param string $username * @param string $domain * @param string $password * * @access public * @return boolean * @throws AuthenticationRequiredException */ public function Logon($user, $domain, $pass) { ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->Logon(): Trying to authenticate user '%s'..", $user)); $this->mainUser = strtolower($user); try { // check if notifications are available in php-mapi if (function_exists('mapi_feature') && mapi_feature('LOGONFLAGS')) { // send Z-Push version and user agent to ZCP - ZP-589 if (Utils::CheckMapiExtVersion('7.2.0')) { $zpush_version = 'Z-Push_' . @constant('ZPUSH_VERSION'); $user_agent = Request::GetDeviceID() ? ZPush::GetDeviceManager()->GetUserAgent() : "unknown"; $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER, null, null, 0, $zpush_version, $user_agent); } else { $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER, null, null, 0); } $this->notifications = true; } else { $this->session = @mapi_logon_zarafa($user, $pass, MAPI_SERVER); $this->notifications = false; } if (mapi_last_hresult()) { ZLog::Write(LOGLEVEL_ERROR, sprintf("KopanoBackend->Logon(): login failed with error code: 0x%X", mapi_last_hresult())); if (mapi_last_hresult() == MAPI_E_NETWORK_ERROR) { throw new HTTPReturnCodeException("Error connecting to KC (login)", 503, null, LOGLEVEL_INFO); } } } catch (MAPIException $ex) { throw new AuthenticationRequiredException($ex->getDisplayMessage()); } if (!$this->session) { ZLog::Write(LOGLEVEL_WARN, sprintf("KopanoBackend->Logon(): logon failed for user '%s'", $user)); $this->defaultstore = false; return false; } // Get/open default store $this->defaultstore = $this->openMessageStore($this->mainUser); if (mapi_last_hresult() == MAPI_E_FAILONEPROVIDER) { throw new HTTPReturnCodeException("Error connecting to KC (open store)", 503, null, LOGLEVEL_INFO); } if ($this->defaultstore === false) { throw new AuthenticationRequiredException(sprintf("KopanoBackend->Logon(): User '%s' has no default store", $user)); } $this->store = $this->defaultstore; $this->storeName = $this->mainUser; ZLog::Write(LOGLEVEL_DEBUG, sprintf("KopanoBackend->Logon(): User '%s' is authenticated", $user)); $this->isZPushEnabled(); // check if this is a Zarafa 7 store with unicode support MAPIUtils::IsUnicodeStore($this->store); return true; }