From: Michael J. Rubinsky Date: Tue, 30 Mar 2010 03:22:57 +0000 (-0400) Subject: Implement working policy enforcement and provisioning. X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=e084a37ef334f07770a5f1b19cdb1b51bde7caa1;p=horde.git Implement working policy enforcement and provisioning. In the process, rework some state storage methods. For the file storage backend, store the state files in a user directory to allow a (as yet non-existant) UI for allowing the user and/or admin to request a remote device wipe. Also seperate out the device-specific information such as the policykey, user-agent, device string etc... into it's own state file. Again, this will be more efficient in a history/sql based state storage driver. --- diff --git a/framework/ActiveSync/lib/Horde/ActiveSync.php b/framework/ActiveSync/lib/Horde/ActiveSync.php index 8f24c7642..2da5150ad 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync.php @@ -447,17 +447,6 @@ define("SYNC_TRUNCATION_5K", 4); define("SYNC_TRUNCATION_SEVEN", 7); define("SYNC_TRUNCATION_ALL", 9); -define("SYNC_PROVISION_STATUS_SUCCESS", 1); -define("SYNC_PROVISION_STATUS_PROTERROR", 2); -define("SYNC_PROVISION_STATUS_SERVERERROR", 3); -define("SYNC_PROVISION_STATUS_DEVEXTMANAGED", 4); -define("SYNC_PROVISION_STATUS_POLKEYMISM", 5); - -define("SYNC_PROVISION_RWSTATUS_NA", 0); -define("SYNC_PROVISION_RWSTATUS_OK", 1); -define("SYNC_PROVISION_RWSTATUS_PENDING", 2); -define("SYNC_PROVISION_RWSTATUS_WIPED", 3); - /** * Main ActiveSync class. Entry point for performing all ActiveSync operations * @@ -868,12 +857,10 @@ class Horde_ActiveSync ); /** - * Used to track what error code to send back to PIM on failure + * Provisioning support * - * @var integer + * @var string (TODO _constant this) */ - protected $_statusCode = 0; - protected $_provisioning; /** @@ -1549,10 +1536,12 @@ class Horde_ActiveSync * @param $protocolversion * @return unknown_type */ - public function handleRequest($cmd, $devId, $version) + public function handleRequest($cmd, $devId) { $class = 'Horde_ActiveSync_Request_' . basename($cmd); + $version = $this->getProtocolVersion(); if (class_exists($class)) { + //@TODO: Remove version - get it in handle() since we pass self to it anyway $request = new $class($this->_driver, $this->_decoder, $this->_encoder, @@ -1564,9 +1553,6 @@ class Horde_ActiveSync return $request->handle($this); } - // GetHierarchy is used in v1.0 of the AS protocol, in v2, it is replaced - // by the FolderSync command - // @TODO: Leave the following in place until all are refactored...then throw // an error if the class does not exist. switch($cmd) { @@ -1653,6 +1639,7 @@ class Horde_ActiveSync /** * Send the MS_Server-ActiveSync header + * (This is the version Exchange 2003 implements) * * @return void */ @@ -1698,15 +1685,12 @@ class Horde_ActiveSync */ public function getPolicyKey() { - if (isset($this->_policykey)) { - return $this->_policykey; - } - /* Policy key headers may be sent in either of these forms: */ $this->_policykey = $this->_request->getHeader('X-Ms-Policykey'); if (empty($this->_policykey)) { $this->_policykey = $this->_request->getHeader('X-MS-PolicyKey'); } + if (empty($this->_policykey)) { $this->_policykey = 0; } diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php b/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php index aab1b641d..e9d553883 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Driver/Base.php @@ -241,6 +241,16 @@ abstract class Horde_ActiveSync_Driver_Base } /** + * Get the username for this request. + * + * @return string The current username + */ + public function getUser() + { + return $this->_authUser; + } + + /** * Any code to run on log off * * @return boolean @@ -326,7 +336,8 @@ abstract class Horde_ActiveSync_Driver_Base /** * Will (eventually) return an appropriate state object based on the class * being sync'd. - * @param $collection + * + * @param array $collection */ public function &getStateObject($collection = array()) { @@ -477,42 +488,7 @@ abstract class Horde_ActiveSync_Driver_Base } /** - * Checks if the sent policykey matches the latest policykey on the server - * TODO: Revisit this once we have refactored state storage - * @param string $policykey - * @param string $devid - * - * @return status flag - */ - public function CheckPolicy($policyKey, $devId) - { - $status = SYNC_PROVISION_STATUS_SUCCESS; -// $user_policykey = $this->getPolicyKey($this->_authUser, $this->_authPass, $devId); -// if ($user_policykey != $policyKey) { -// $status = SYNC_PROVISION_STATUS_POLKEYMISM; -// } -// - return $status; - } - - /** - * Return a policy key for given user with a given device id. - * If there is no combination user-deviceid available, a new key - * should be generated. - * - * @param string $user - * @param string $pass - * @param string $devid - * - * @return unknown - */ - public function getPolicyKey($user, $pass, $devid) - { - return false; - } - - /** - * Generate a random policy key. Right now it's a 10-digit number. + * Generate a random 10 digit policy key * * @return unknown */ @@ -522,18 +498,6 @@ abstract class Horde_ActiveSync_Driver_Base } /** - * Set a new policy key for the given device id. - * - * @param string $policykey - * @param string $devid - * @return unknown - */ - public function setPolicyKey($policykey, $devid) - { - return false; - } - - /** * Return a device wipe status * * @param string $user diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Base.php b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Base.php index 4812255fa..65713ed47 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Base.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Base.php @@ -50,7 +50,7 @@ abstract class Horde_ActiveSync_Request_Base * * @var mixed */ - protected $_provisioning = false; + protected $_provisioning; /** * The ActiveSync Version @@ -66,6 +66,9 @@ abstract class Horde_ActiveSync_Request_Base */ protected $_devId; + protected $_userAgent; + protected $_deviceType; + /** * Used to track what error code to send back to PIM on failure * @@ -113,10 +116,40 @@ abstract class Horde_ActiveSync_Request_Base /* Provisioning support */ $this->_provisioning = $provisioning; + /* Useragent and device identification */ + $this->_userAgent = $request->getHeader('User-Agent'); + $get = $request->getGetParams(); + $this->_deviceType = $get['DeviceType']; + /* Logger */ $this->_logger; } + /** + * Ensure the PIM's policy key is current. + * + * @param $devId + * @return + */ + public function checkPolicyKey($sentKey) + { + /* Don't attempt if we don't care */ + if ($this->_provisioning !== false) { + $state = $this->_driver->getStateObject(); + $storedKey = $state->getPolicyKey($this->_devId); + /* Loose provsioning should allow a blank key */ + if ($storedKey != $sentKey && + ($this->_provisioning !== 'loose' || + ($this->_provisioning === 'loose' && !empty($this->_policyKey)))) { + + Horde_ActiveSync::provisioningRequired(); + return false; + } + } + + return true; + } + public function setLogger(Horde_Log_Logger $logger) { $this->_logger = $logger; } diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Request/FolderSync.php b/framework/ActiveSync/lib/Horde/ActiveSync/Request/FolderSync.php index 7a59b46e9..f333a878b 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Request/FolderSync.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Request/FolderSync.php @@ -23,10 +23,15 @@ class Horde_ActiveSync_Request_FolderSync extends Horde_ActiveSync_Request_Base public function handle(Horde_ActiveSync $activeSync) { - /* Be optomistic */ + /* Be optimistic */ $this->_statusCode = self::STATUS_SUCCESS; $this->_logger->info('[Horde_ActiveSync::handleFolderSync] Beginning FOLDERSYNC'); + /* Check policy */ + if (!$this->checkPolicyKey($activeSync->getPolicyKey())) { + return false; + } + /* Maps serverid -> clientid for items that are received from the PIM */ $map = array(); diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Request/GetItemEstimate.php b/framework/ActiveSync/lib/Horde/ActiveSync/Request/GetItemEstimate.php index ed9fec468..264374d80 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Request/GetItemEstimate.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Request/GetItemEstimate.php @@ -14,7 +14,6 @@ */ class Horde_ActiveSync_Request_GetItemEstimate extends Horde_ActiveSync_Request_Base { - /** Status Codes **/ const STATUS_SUCCESS = 1; const STATUS_INVALIDCOL = 2; @@ -29,6 +28,13 @@ class Horde_ActiveSync_Request_GetItemEstimate extends Horde_ActiveSync_Request_ */ public function handle(Horde_ActiveSync $activeSync) { + $this->_logger->info('[Horde_ActiveSync::handleFolderSync] Beginning GETITEMESTIMATE'); + + /* Check policy */ + if (!$this->checkPolicyKey($activeSync->getPolicyKey())) { + return false; + } + $status = array(); $collections = array(); @@ -111,40 +117,29 @@ class Horde_ActiveSync_Request_GetItemEstimate extends Horde_ActiveSync_Request_ $this->_encoder->startTag(SYNC_GETITEMESTIMATE_GETITEMESTIMATE); foreach ($collections as $collection) { $this->_encoder->startTag(SYNC_GETITEMESTIMATE_RESPONSE); - $this->_encoder->startTag(SYNC_GETITEMESTIMATE_STATUS); $this->_encoder->content($status[$collection['id']]); $this->_encoder->endTag(); - $this->_encoder->startTag(SYNC_GETITEMESTIMATE_FOLDER); - $this->_encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERTYPE); $this->_encoder->content($collection['class']); $this->_encoder->endTag(); - $this->_encoder->startTag(SYNC_GETITEMESTIMATE_FOLDERID); $this->_encoder->content($collection['id']); $this->_encoder->endTag(); - $this->_encoder->startTag(SYNC_GETITEMESTIMATE_ESTIMATE); $importer = new Horde_ActiveSync_ContentsCache(); - $state = $this->_driver->getStateObject($collection); $state->loadState($collection['synckey']); - $exporter = $this->_driver->GetExporter(); $exporter->init($state, $importer, $collection); $this->_encoder->content($exporter->GetChangeCount()); - $this->_encoder->endTag(); - $this->_encoder->endTag(); - $this->_encoder->endTag(); } - $this->_encoder->endTag(); return true; diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Provision.php b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Provision.php index 86cb7b7f7..240f9dfc5 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Provision.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Provision.php @@ -17,140 +17,173 @@ class Horde_ActiveSync_Request_Provision extends Horde_ActiveSync_Request_Base { + /* Status Constants */ + const STATUS_SUCCESS = 1; + const STATUS_PROTERROR = 2; // Global status + const STATUS_NOTDEFINED = 2; // Policy status + + const STATUS_SERVERERROR = 3; // Global + const STATUS_POLICYUNKNOWN = 3; // Policy + + const STATUS_DEVEXTMANAGED = 4; // Global + const STATUS_POLICYCORRUPT = 4; // Policy + + const STATUS_POLKEYMISM = 5; + + /* Client -> Server Status */ + const STATUS_CLIENT_SUCCESS = 1; + const STATUS_CLIENT_PARTIAL = 2; // Only pin was enabled. + const STATUS_CLIENT_FAILED = 3; // No policies applied at all. + const STATUS_CLIENT_THIRDPARTY = 4; // Client provisioned by 3rd party? + + const RWSTATUS_NA = 0; + const RWSTATUS_OK = 1; + const RWSTATUS_PENDING = 2; + const RWSTATUS_WIPED = 3; + + /** + * Handle the Provision request. This is a 3-phase process. Phase 1 is + * actually the enforcement, when the server rejects a request and forces + * the client to perform this PROVISION request...so we are handling phase + * 2 (download policies) and 3 (acknowledge policies) here. + * + * @param Horde_ActiveSync $activeSync The activesync object. + * + * @return boolean + * @throws Horde_ActiveSync_Exception + */ public function handle(Horde_ActiveSync $activeSync) { + /* Get the policy key if it was sent */ $policykey = $activeSync->getPolicyKey(); - $status = SYNC_PROVISION_STATUS_SUCCESS; + $this->_logger->debug('PIM PolicyKey: ' . $policykey); + /* Be optimistic */ + $status = self::STATUS_SUCCESS; + $policyStatus = self::STATUS_SUCCESS; + + /* Start by assuming we are in stage 2 */ $phase2 = true; if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_PROVISION)) { - return false; + return $this->_globalError(self::STATUS_PROTERROR); } - //handle android remote wipe. + /* Handle android remote wipe */ if ($this->_decoder->getElementStartTag(SYNC_PROVISION_REMOTEWIPE)) { if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_STATUS)) { - return false; + return $this->_globalError(self::STATUS_PROTERROR); } - + // TODO: Look at $status here... $status = $this->_decoder->getElementContent(); - - if (!$this->_decoder->getElementEndTag()) { - return false; - } - - if (!$this->_decoder->getElementEndTag()) { - return false; + if (!$this->_decoder->getElementEndTag() || + !$this->_decoder->getElementEndTag()) { + return $this->_globalError(self::STATUS_PROTERROR); } } else { - if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_POLICIES)) { - return false; - } - - if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_POLICY)) { - return false; - } + if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_POLICIES) || + !$this->_decoder->getElementStartTag(SYNC_PROVISION_POLICY) || + !$this->_decoder->getElementStartTag(SYNC_PROVISION_POLICYTYPE)) { - if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_POLICYTYPE)) { - return false; + return $this->_globalError(self::STATUS_PROTERROR); } $policytype = $this->_decoder->getElementContent(); if ($policytype != 'MS-WAP-Provisioning-XML') { - $status = SYNC_PROVISION_STATUS_SERVERERROR; + $policyStatus = self::STATUS_POLICYUNKNOWN; } if (!$this->_decoder->getElementEndTag()) {//policytype - return false; + return $this->_globalError(self::STATUS_PROTERROR); } + /* POLICYKEY is only sent by client in phase 3 */ if ($this->_decoder->getElementStartTag(SYNC_PROVISION_POLICYKEY)) { - // This should be Phase 3 of the Provision conversation... - // We get the intermediate policy key sent back from the client. - // TODO: Still need to verify the key once we have some kind of - // storage for it. $policykey = $this->_decoder->getElementContent(); - if (!$this->_decoder->getElementEndTag()) { - return false; - } + if (!$this->_decoder->getElementEndTag() || + !$this->_decoder->getElementStartTag(SYNC_PROVISION_STATUS)) { - if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_STATUS)) { - return false; + return $this->_globalError(self::STATUS_PROTERROR); + } + if ($this->_decoder->getElementContent() != self::STATUS_SUCCESS) { + $policyStatus = self::STATUS_POLICYCORRUPT; } - - $status = $this->_decoder->getElementContent(); - //do status handling - $status = SYNC_PROVISION_STATUS_SUCCESS; if (!$this->_decoder->getElementEndTag()) { - return false; + return $this->_globalError(self::STATUS_PROTERROR); } $phase2 = false; } - if (!$this->_decoder->getElementEndTag()) {//policy - return false; - } + if (!$this->_decoder->getElementEndTag() || + !$this->_decoder->getElementEndTag()) { - if (!$this->_decoder->getElementEndTag()) {//policies - return false; + return $this->_globalError(self::STATUS_PROTERROR); } + /* Handle remote wipe for other devices */ if ($this->_decoder->getElementStartTag(SYNC_PROVISION_REMOTEWIPE)) { if (!$this->_decoder->getElementStartTag(SYNC_PROVISION_STATUS)) { - return false; + return $this->_globalError(self::STATUS_PROTERROR); } - + // @TODO: look at status here?? $status = $this->_decoder->getElementContent(); - if (!$this->_decoder->getElementEndTag()) { - return false; - } + if (!$this->_decoder->getElementEndTag() || + !$this->_decoder->getElementEndTag()) { - if (!$this->_decoder->getElementEndTag()) { - return false; + return $this->_globalError(self::STATUS_PROTERROR); } } } if (!$this->_decoder->getElementEndTag()) {//provision - return false; + return $this->_globalError(self::STATUS_PROTERROR); } + + /* Start handling request and sending output */ $this->_encoder->StartWBXML(); // End of Phase 3 - We create the "final" policy key, store it, then // send it to the client. + $state = $this->_driver->getStateObject(); if (!$phase2) { + /* Verify intermediate key */ + if ($state->getPolicyKey($this->_devId) != $policykey) { + $policyStatus = self::STATUS_POLKEYMISM; + } else { + $policykey = $this->_driver->generatePolicyKey(); + $info = array('policykey' => $policykey, + 'useragent' => $this->_userAgent, + 'devicetype' => $this->_deviceType); + $state->setDeviceInfo($this->_devId, $info); + } + } elseif (empty($policykey)) { + // This is phase2 - we need to set the intermediate key $policykey = $this->_driver->generatePolicyKey(); - $this->_driver->setPolicyKey($policykey, $this->_devId); + $state->setPolicyKey($this->_devId, $policykey); } $this->_encoder->startTag(SYNC_PROVISION_PROVISION); - $this->_encoder->startTag(SYNC_PROVISION_STATUS); $this->_encoder->content($status); $this->_encoder->endTag(); $this->_encoder->startTag(SYNC_PROVISION_POLICIES); $this->_encoder->startTag(SYNC_PROVISION_POLICY); - $this->_encoder->startTag(SYNC_PROVISION_POLICYTYPE); $this->_encoder->content($policytype); $this->_encoder->endTag(); $this->_encoder->startTag(SYNC_PROVISION_STATUS); - $this->_encoder->content($status); + $this->_encoder->content($policyStatus); $this->_encoder->endTag(); $this->_encoder->startTag(SYNC_PROVISION_POLICYKEY); $this->_encoder->content($policykey); $this->_encoder->endTag(); - if ($phase2) { - // If we are in Phase 2, send the security policies. - // TODO: Configure this! + + /* Send security policies - configure this/move to it's own method...*/ + if ($phase2 && $status == self::STATUS_SUCCESS && $policyStatus == self::STATUS_SUCCESS) { $this->_encoder->startTag(SYNC_PROVISION_DATA); if ($policytype == 'MS-WAP-Provisioning-XML') { // Set 4131 to 0 to require a PIN, 4133 $this->_encoder->content(''); - } else { - $this->_logger->err('Wrong policy type'); - return false; } $this->_encoder->endTag();//data } @@ -159,9 +192,9 @@ class Horde_ActiveSync_Request_Provision extends Horde_ActiveSync_Request_Base $rwstatus = $this->_driver->getDeviceRWStatus($this->_devId); //wipe data if status is pending or wiped - if ($rwstatus == SYNC_PROVISION_RWSTATUS_PENDING || $rwstatus == SYNC_PROVISION_RWSTATUS_WIPED) { + if ($rwstatus == self::RWSTATUS_PENDING || $rwstatus == self::RWSTATUS_WIPED) { $this->_encoder->startTag(SYNC_PROVISION_REMOTEWIPE, false, true); - $this->_driver->setDeviceRWStatus($this->_devId, SYNC_PROVISION_RWSTATUS_WIPED); + $this->_driver->setDeviceRWStatus($this->_devId, self::RWSTATUS_WIPED); //$rwstatus = SYNC_PROVISION_RWSTATUS_WIPED; } @@ -170,4 +203,16 @@ class Horde_ActiveSync_Request_Provision extends Horde_ActiveSync_Request_Base return true; } + private function _globalError($status) + { + $this->_encoder->StartWBXML(); + $this->_encoder->startTag(SYNC_PROVISION_PROVISION); + $this->_encoder->startTag(SYNC_PROVISION_STATUS); + $this->_encoder->content($status); + $this->_encoder->endTag(); + $this->_encoder->endTag(); + + return false; + } + } \ No newline at end of file diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Sync.php b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Sync.php index 257e71b87..2e7596c14 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/Request/Sync.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/Request/Sync.php @@ -30,6 +30,11 @@ class Horde_ActiveSync_Request_Sync extends Horde_ActiveSync_Request_Base { $this->_logger->info('[Horde_ActiveSync::handleSync] Handling SYNC command.'); + /* Check policy */ + if (!$this->checkPolicyKey($activeSync->getPolicyKey())) { + return false; + } + /* Be optimistic */ $this->_statusCode = self::STATUS_SUCCESS; diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/State/Base.php b/framework/ActiveSync/lib/Horde/ActiveSync/State/Base.php index 77b3248bc..8a3be982a 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/State/Base.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/State/Base.php @@ -113,13 +113,20 @@ abstract class Horde_ActiveSync_State_Base * Loads the initial state from storage for the specified syncKey and * intializes the stateMachine for use. * - * @param string $key The key for the syncState or pingState to load. + * @param string $key The key for the syncState or pingState to load. * * @return array The state array */ abstract public function loadState($syncKey); /** + * Load/initialize the ping state for the specified device. + * + * @param string $devId + */ + abstract public function initPingState($devId); + + /** * Load the ping state for the given device id * * @param string $devid The device id. @@ -169,6 +176,25 @@ abstract class Horde_ActiveSync_State_Base abstract public function isConflict($stat, $type); /** + * Get the specified device's policy key. + * + * @param string $devId The device id to get key for. + * + * @return integer The policy key + */ + abstract public function getPolicyKey($devId); + + /** + * Set the policy key for the specified device id + * + * @param string $devId The device id + * @param integer $key The policy key + * + * @return void + */ + abstract public function setPolicyKey($devId, $key); + + /** * Set the backend driver * (should really only be called by a backend object when passing this * object to client code) diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php b/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php index a2e58d0ea..211f85ef8 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/State/File.php @@ -92,27 +92,37 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base /** * Load the sync state * + * @param string $syncKey The synckey + * * @return void * @throws Horde_ActiveSync_Exception */ public function loadState($syncKey) { + /* Make sure this user's state directory exists */ + $dir = $this->_stateDir . '/' . $this->_backend->getUser(); + if (!file_exists($dir)) { + if (!mkdir($dir)) { + throw new Horde_ActiveSync_Exception('Failed to create user state storage'); + } + } + /* Prime the state cache for the first sync */ if (empty($syncKey)) { $this->_stateCache = array(); return; } - // Check if synckey is allowed + /* Check if synckey is allowed */ if (!preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $syncKey, $matches)) { throw new Horde_ActiveSync_Exception('Invalid sync key'); } - // Cleanup all older syncstates + /* Cleanup all older syncstates */ $this->_gc($syncKey); - // Read current sync state - $filename = $this->_stateDir . '/' . $syncKey; + /* Read current sync state */ + $filename = $dir . '/' . $syncKey; if (!file_exists($filename)) { throw new Horde_ActiveSync_Exception('Sync state not found'); } @@ -164,7 +174,9 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base */ public function save() { - return file_put_contents($this->_stateDir . '/' . $this->_syncKey, !empty($this->_stateCache) ? serialize($this->_stateCache) : ''); + return file_put_contents( + $this->_stateDir . '/' . $this->_backend->getUser() . '/' . $this->_syncKey, + !empty($this->_stateCache) ? serialize($this->_stateCache) : ''); } /** @@ -218,10 +230,11 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base } /** - * Save folder data for a specific device + * Save folder data for a specific device. Used only for compatibility with + * older (version 1) ActiveSync requests. * - * @param string $devId The device Id - * @param array $folders The folder data + * @param string $devId The device Id + * @param array $folders The folder data * * @return boolean * @throws Horde_ActiveSync_Exception @@ -254,14 +267,15 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base $unique_folders[SYNC_FOLDER_TYPE_CONTACT] = SYNC_FOLDER_TYPE_DUMMY; } - if (!file_put_contents($this->_stateDir . '/compat-' . $devId, serialize($unique_folders))) { + if (!file_put_contents($this->_stateDir . '/' . $this->_backend->getUser() . '/compat-' . $devId, serialize($unique_folders))) { $this->logError('_saveFolderData: Data could not be saved!'); throw new Horde_ActiveSync_Exception('Folder data could not be saved'); } } /** - * Get the folder data for a specific device + * Get the folder data for a specific collection for a specific device. Used + * only with older (version 1) ActiveSync requests. * * @param string $devId The device id * @param string $class The folder class to fetch (Calendar, Contacts etc.) @@ -270,7 +284,7 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base */ public function getFolderData($devId, $class) { - $filename = $this->_stateDir . '/compat-' . $devId; + $filename = $this->_stateDir . '/' . $this->_backend->getUser() . '/compat-' . $devId; if (file_exists($filename)) { $arr = unserialize(file_get_contents($filename)); if ($class == "Calendar") { @@ -285,7 +299,9 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base } /** - * Return an array of known folders. + * Return an array of known folders. This is essentially the state for a + * FOLDERSYNC request. AS uses a seperate synckey for FOLDERSYNC requests + * also, so need to treat it as any other collection. * * @return array */ @@ -305,14 +321,14 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base * Perform any initialization needed to deal with pingStates * For this driver, it loads the device's state file. * - * @param string $devId The device id of the PIM to load PING state for + * @param string $devId The device id of the PIM to load PING state for * - * @return The $collection array + * @return array The $collection array */ public function initPingState($devId) { $this->_devId = $devId; - $file = $this->_stateDir . '/' . $devId; + $file = $this->_stateDir . '/' . $this->_backend->getUser() . '/' . $devId; if (file_exists($file)) { $this->_pingState = unserialize(file_get_contents($file)); } else { @@ -325,7 +341,38 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base } /** - * Load a specific collection's ping state + * Obtain the device info array. + * + * @param $devId + */ + public function getDeviceInfo($devId) + { + $this->_devId = $devId; + $file = $this->_stateDir . '/' . $this->_backend->getUser() . '/info-' . $devId; + if (file_exists($file)) { + return unserialize(file_get_contents($file)); + } + + return array('policykey' => 0, + 'rwstatus' => 0, + 'devicetype' => '', + 'useragent' => ''); + } + + /** + * Set new device info + * + */ + public function setDeviceInfo($devId, $data) + { + $this->_devId = $devId; + $file = $this->_stateDir . '/' . $this->_backend->getUser() . '/info-' . $devId; + return file_put_contents($file, serialize($data)); + } + + /** + * Load a specific collection's ping state. Ping state must already have + * been loaded. * * @param array $pingCollection The collection array from the PIM request * @@ -354,9 +401,6 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base if (!$haveState) { $this->_logger->debug('Empty state for '. $pingCollection['class']); - /* Start with empty state cache */ - //$this->_stateCache[$pingCollection['id']] = array(); - /* Init members for the getChanges call */ $this->_syncKey = $this->_devId; $this->_collection = $pingCollection; @@ -406,9 +450,10 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base if (empty($this->_pingState)) { throw new Horde_ActiveSync_Exception('PING state not initialized'); } - $state = serialize(array('lifetime' => $this->_pingState['lifetime'], 'collections' => $this->_pingState['collections'])); + $state = serialize(array('lifetime' => $this->_pingState['lifetime'], + 'collections' => $this->_pingState['collections'])); - return file_put_contents($this->_stateDir . '/' . $this->_devId, $state); + return file_put_contents($this->_stateDir . '/' . $this->_backend->getUser() . '/' . $this->_devId, $state); } /** @@ -434,10 +479,38 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base } /** + * Obtain the current policy key, if it exists. * - * @param $collectionClass The collection we are syncing + * @param string $devId The device id to obtain policy key for. * - * @return + * @return integer The current policy key for this device, or 0 if none + * exists. + */ + public function getPolicyKey($devId) + { + $info = $this->getDeviceInfo($devId); + return $info['policykey']; + } + + /** + * Save a new device policy key to storage. + * + * @param string $devId The device id + * @param integer $key The new policy key + */ + public function setPolicyKey($devId, $key) + { + $info = $this->getDeviceInfo($devId); + $info['policykey'] = $key; + $this->setDeviceInfo($devId, $info); + } + + /** + * Get list of server changes + * + * @param integer $flags + * + * @return array */ public function getChanges($flags = 0) { @@ -485,12 +558,17 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base return $this->_changes; } + /** + * Get the number of server changes. + * + * @return integer + */ public function getChangeCount() { if (!isset($this->_changes)) { $this->getChanges(); - //throw new Horde_ActiveSync_Exception('Changes not yet retrieved. Must call getChanges() first'); } + return count($this->_changes); } @@ -500,8 +578,8 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base * * @params string $syncKey The sync key * + * @return boolean * @throws Horde_ActiveSync_Exception - * @return boolean? */ protected function _gc($syncKey) { @@ -511,14 +589,14 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base $guid = $matches[1]; $n = $matches[2]; - $dir = opendir($this->_stateDir); + $dir = @opendir($this->_stateDir . '/' . $this->_backend->getUser()); if (!$dir) { return false; } while ($entry = readdir($dir)) { if (preg_match('/^s{0,1}\{([0-9A-Za-z-]+)\}([0-9]+)$/', $entry, $matches)) { if ($matches[1] == $guid && $matches[2] < $n) { - unlink($this->_stateDir . '/' . $entry); + unlink($this->_stateDir . '/' . $this->_backend->getUser() . '/' . $entry); } } } @@ -527,9 +605,12 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base } /** + * Helper function that performs the actual diff between PIM state and + * server state arrays. + * + * @param array $old The PIM state + * @param array $new The current server state * - * @param $old - * @param $new * @return unknown_type */ private function _getDiff($old, $new) @@ -609,6 +690,7 @@ class Horde_ActiveSync_State_File extends Horde_ActiveSync_State_Base } /** + * Helper function for the _diff method * * @param $a * @param $b diff --git a/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php b/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php index a50e8ab2b..c1b2c1c11 100644 --- a/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php +++ b/framework/ActiveSync/lib/Horde/ActiveSync/State/History.php @@ -74,7 +74,7 @@ class Horde_ActiveSync_State_History extends Horde_ActiveSync_State_Base * @return void * @throws Horde_ActiveSync_Exception */ - public function loadState($syncKey) + public function loadState($syncKey, $username) { if (empty($syncKey)) { return; diff --git a/framework/Rpc/lib/Horde/Rpc/ActiveSync.php b/framework/Rpc/lib/Horde/Rpc/ActiveSync.php index 7e8d5ea5a..b9af31a4e 100644 --- a/framework/Rpc/lib/Horde/Rpc/ActiveSync.php +++ b/framework/Rpc/lib/Horde/Rpc/ActiveSync.php @@ -42,15 +42,6 @@ class Horde_Rpc_ActiveSync extends Horde_Rpc private $_policykey; /** - * ActiveSync protocol version - * (Sent as either 'Ms-Asprotocolversion' or 'MS-ASProtocolVersion') - * Should default to '1.0' if not sent. - * - * @var string - */ - private $_protocolVersion; - - /** * Require provisioning? Valid values: * true - provisioning required * false - not checked @@ -61,6 +52,7 @@ class Horde_Rpc_ActiveSync extends Horde_Rpc */ private $_provisioning; + /** * Constructor. * Parameters in addition to Horde_Rpc's: @@ -96,9 +88,6 @@ class Horde_Rpc_ActiveSync extends Horde_Rpc $this->_policykey = $this->_server->getPolicyKey(); } $this->_server->setProvisioning = $this->_provisioning; - - /* Protocol Version */ - $this->_protocolVersion = $this->_server->getProtocolVersion(); } /** @@ -145,19 +134,8 @@ class Horde_Rpc_ActiveSync extends Horde_Rpc // Do the actual request $this->_logger->debug('Horde_Rpc_ActiveSync::getResponse() starting for ' . $this->_get['Cmd']); - if (!$this->_server->handleRequest($this->_get['Cmd'], $this->_get['DeviceId'], $this->_protocolVersion)) { - /* @TODO If request failed, try to output a reasonable error to the - * device if we can...and this should be done from the ActiveSync - * objects anyway... - */ - if(!headers_sent()) { - header('Content-type: text/html'); - echo("\n"); - echo("

Error

\n"); - echo("There was a problem processing the {$this->_get['Cmd']} command from your PDA.\n"); - echo("\n"); - } - } + $this->_server->handleRequest($this->_get['Cmd'], $this->_get['DeviceId']); + break; case 'GET': @@ -244,18 +222,6 @@ class Horde_Rpc_ActiveSync extends Horde_Rpc echo 'Access denied or user ' . $this->_get['User'] . ' unknown.'; } - /* Policies / Provisioning */ - if ($this->_provisioning !== false && $this->_request->getServer('REQUEST_METHOD') != 'OPTIONS' && - $this->_get['Cmd'] != 'Ping' && $this->_get['Cmd'] != 'Provision' && - $this->_backend->CheckPolicy($this->_policykey, $this->_get['DeviceId']) != SYNC_PROVISION_STATUS_SUCCESS && - ($this->_provisioning !== 'loose' || - ($this->_provisioning === 'loose' && !empty($this->_policyKey)))) { - - Horde_ActiveSync::provisioningRequired(); - - return false; - } - $this->_logger->debug('Horde_Rpc_ActiveSync::authorize() exiting'); return true;