Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2533243
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
46 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/config/main.inc.php.dist b/config/main.inc.php.dist
index c792377..5f0a7c2 100644
--- a/config/main.inc.php.dist
+++ b/config/main.inc.php.dist
@@ -1,85 +1,88 @@
<?php
// This file lists all ActiveSync-related configuration options
// Enables ActiveSync protocol debuging
$config['activesync_debug'] = true;
// Enables logging to a separate directory for every user/device
$config['activesync_user_log'] = false;
// Enable per-user debugging only if /var/log/syncroton/<username>/ folder exists
$config['activesync_user_debug'] = false;
// If specified all ActiveSync-related logs will be saved to this file
// Note: This doesn't change Roundcube Framework log locations
$config['activesync_log_file'] = null;
// Type of ActiveSync cache. Supported values: 'db', 'apc' and 'memcache'.
// Note: This is only for some additional data like timezones mapping.
$config['activesync_cache'] = 'db';
// lifetime of ActiveSync cache
// possible units: s, m, h, d, w
$config['activesync_cache_ttl'] = '1d';
// Type of ActiveSync Auth cache. Supported values: 'db', 'apc' and 'memcache'.
// Note: This is only for username canonification map.
$config['activesync_auth_cache'] = 'db';
// lifetime of ActiveSync Auth cache
// possible units: s, m, h, d, w
$config['activesync_auth_cache_ttl'] = '1d';
// List of global addressbooks (GAL)
// Note: If empty 'autocomplete_addressbooks' setting will be used
$config['activesync_addressbooks'] = array();
// ActiveSync => Roundcube contact fields map for GAL search
/* Default: array(
'alias' => 'nickname',
'company' => 'organization',
'displayName' => 'name',
'emailAddress' => 'email',
'firstName' => 'firstname',
'lastName' => 'surname',
'mobilePhone' => 'phone.mobile',
'office' => 'office',
'picture' => 'photo',
'phone' => 'phone',
'title' => 'jobtitle',
);
*/
$config['activesync_gal_fieldmap'] = null;
// List of Roundcube plugins
// WARNING: Not all plugins used in Roundcube can be listed here
$config['activesync_plugins'] = array();
// Defines for how many seconds we'll sleep between every
// action for detecting changes in folders. Default: 60
$config['activesync_ping_timeout'] = 60;
+// Defines maximum Ping interval in seconds. Default: 900 (15 minutes)
+$config['activesync_ping_interval'] = 900;
+
// We start detecting changes n seconds since the last sync of a folder
// Default: 180
$config['activesync_quiet_time'] = 180;
// When a device is reqistered, by default a set of folders are
// subscribed for syncronization, i.e. INBOX and personal folders with
// defined folder type:
// mail.drafts, mail.wastebasket, mail.sentitems, mail.outbox,
// event, event.default,
// contact, contact.default,
// task, task.default
// This default set can be extended by adding following values:
// 1 - all subscribed folders in personal namespace
// 2 - all folders in personal namespace
// 4 - all subscribed folders in other users namespace
// 8 - all folders in other users namespace
// 16 - all subscribed folders in shared namespace
// 32 - all folders in shared namespace
$config['activesync_init_subscriptions'] = 0;
// Enables adding sender name in the From: header of send email
// when a device uses email address only (e.g. iOS devices)
$config['activesync_fix_from'] = false;
diff --git a/lib/ext/Syncroton/Command/Ping.php b/lib/ext/Syncroton/Command/Ping.php
index d2e5c83..a2bd926 100644
--- a/lib/ext/Syncroton/Command/Ping.php
+++ b/lib/ext/Syncroton/Command/Ping.php
@@ -1,226 +1,233 @@
<?php
/**
* Syncroton
*
* @package Syncroton
* @subpackage Command
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class to handle ActiveSync Ping command
*
* @package Syncroton
* @subpackage Command
*/
class Syncroton_Command_Ping extends Syncroton_Command_Wbxml
{
const STATUS_NO_CHANGES_FOUND = 1;
const STATUS_CHANGES_FOUND = 2;
const STATUS_MISSING_PARAMETERS = 3;
const STATUS_REQUEST_FORMAT_ERROR = 4;
const STATUS_INTERVAL_TO_GREAT_OR_SMALL = 5;
const STATUS_TO_MUCH_FOLDERS = 6;
const STATUS_FOLDER_NOT_FOUND = 7;
const STATUS_GENERAL_ERROR = 8;
protected $_skipValidatePolicyKey = true;
protected $_changesDetected = false;
/**
* @var Syncroton_Backend_StandAlone_Abstract
*/
protected $_dataBackend;
protected $_defaultNameSpace = 'uri:Ping';
protected $_documentElement = 'Ping';
protected $_foldersWithChanges = array();
/**
* process the XML file and add, change, delete or fetches data
*
* @todo can we get rid of LIBXML_NOWARNING
* @todo we need to stored the initial data for folders and lifetime as the phone is sending them only when they change
* @return resource
*/
public function handle()
{
$intervalStart = time();
$status = self::STATUS_NO_CHANGES_FOUND;
// the client does not send a wbxml document, if the Ping parameters did not change compared with the last request
if ($this->_requestBody instanceof DOMDocument) {
$xml = simplexml_import_dom($this->_requestBody);
$xml->registerXPathNamespace('Ping', 'Ping');
- if(isset($xml->HeartBeatInterval)) {
- $this->_device->pinglifetime = (int)$xml->HeartBeatInterval;
+ if(isset($xml->HeartbeatInterval)) {
+ $this->_device->pinglifetime = (int)$xml->HeartbeatInterval;
}
if (isset($xml->Folders->Folder)) {
$folders = array();
foreach ($xml->Folders->Folder as $folderXml) {
try {
// does the folder exist?
$folder = $this->_folderBackend->getFolder($this->_device, (string)$folderXml->Id);
$folders[$folder->id] = $folder;
} catch (Syncroton_Exception_NotFound $senf) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $senf->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
break;
}
}
$this->_device->pingfolder = serialize(array_keys($folders));
}
}
$this->_device->lastping = new DateTime('now', new DateTimeZone('utc'));
if ($status == self::STATUS_NO_CHANGES_FOUND) {
$this->_device = $this->_deviceBackend->update($this->_device);
}
- $lifeTime = $this->_device->pinglifetime;
- #Tinebase_Core::setExecutionLifeTime($lifeTime);
+ $lifeTime = $this->_device->pinglifetime;
+ $maxLifeTime = Syncroton_Registry::getPingInterval();
+
+ if ($maxLifeTime > 0 && $lifeTime > $maxLifeTime) {
+ $ping = $this->_outputDom->documentElement;
+ $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', self::STATUS_INTERVAL_TO_GREAT_OR_SMALL));
+ $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'HeartbeatInterval', $maxLifeTime));
+ return;
+ }
$intervalEnd = $intervalStart + $lifeTime;
$secondsLeft = $intervalEnd;
$folders = unserialize($this->_device->pingfolder);
if ($status === self::STATUS_NO_CHANGES_FOUND && (!is_array($folders) || count($folders) == 0)) {
$status = self::STATUS_MISSING_PARAMETERS;
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " Folders to monitor($lifeTime / $intervalStart / $intervalEnd / $status): " . print_r($folders, true));
if ($status === self::STATUS_NO_CHANGES_FOUND) {
do {
// take a break to save battery lifetime
sleep(Syncroton_Registry::getPingTimeout());
try {
$device = $this->_deviceBackend->get($this->_device->id);
} catch (Syncroton_Exception_NotFound $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
break;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
// do nothing, maybe temporal issue, should we stop?
continue;
}
// if another Ping command updated lastping property, we can stop processing this Ping command request
if ((isset($device->lastping) && $device->lastping instanceof DateTime) &&
$device->pingfolder === $this->_device->pingfolder &&
$device->lastping->getTimestamp() > $this->_device->lastping->getTimestamp() ) {
break;
}
$now = new DateTime('now', new DateTimeZone('utc'));
foreach ($folders as $folderId) {
try {
$folder = $this->_folderBackend->get($folderId);
$dataController = Syncroton_Data_Factory::factory($folder->class, $this->_device, $this->_syncTimeStamp);
} catch (Syncroton_Exception_NotFound $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
$status = self::STATUS_FOLDER_NOT_FOUND;
break;
} catch (Exception $e) {
if ($this->_logger instanceof Zend_Log)
$this->_logger->err(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
// do nothing, maybe temporal issue, should we stop?
continue;
}
try {
$syncState = $this->_syncStateBackend->getSyncState($this->_device, $folder);
// another process synchronized data of this folder already. let's skip it
if ($syncState->lastsync > $this->_syncTimeStamp) {
continue;
}
// safe battery time by skipping folders which got synchronied less than Syncroton_Registry::getQuietTime() seconds ago
if (($now->getTimestamp() - $syncState->lastsync->getTimestamp()) < Syncroton_Registry::getQuietTime()) {
continue;
}
$foundChanges = $dataController->hasChanges($this->_contentStateBackend, $folder, $syncState);
} catch (Syncroton_Exception_NotFound $e) {
// folder got never synchronized to client
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " " . $e->getMessage());
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . ' syncstate not found. enforce sync for folder: ' . $folder->serverId);
$foundChanges = true;
}
if ($foundChanges == true) {
$this->_foldersWithChanges[] = $folder;
$status = self::STATUS_CHANGES_FOUND;
}
}
if ($status != self::STATUS_NO_CHANGES_FOUND) {
break;
}
$secondsLeft = $intervalEnd - time();
if ($this->_logger instanceof Zend_Log)
$this->_logger->debug(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " seconds left: " . $secondsLeft);
// See: http://www.tine20.org/forum/viewtopic.php?f=12&t=12146
//
// break if there are less than PingTimeout + 10 seconds left for the next loop
// otherwise the response will be returned after the client has finished his Ping
// request already maybe
} while ($secondsLeft > (Syncroton_Registry::getPingTimeout() + 10));
}
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " Lifetime: $lifeTime SecondsLeft: $secondsLeft Status: $status)");
$ping = $this->_outputDom->documentElement;
$ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Status', $status));
if($status === self::STATUS_CHANGES_FOUND) {
$folders = $ping->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folders'));
foreach($this->_foldersWithChanges as $changedFolder) {
$folder = $folders->appendChild($this->_outputDom->createElementNS('uri:Ping', 'Folder', $changedFolder->serverId));
if ($this->_logger instanceof Zend_Log)
$this->_logger->info(__METHOD__ . '::' . __LINE__ . " DeviceId: " . $this->_device->deviceid . " changes in folder: " . $changedFolder->serverId);
}
}
}
/**
* generate ping command response
*
*/
public function getResponse()
{
return $this->_outputDom;
}
}
diff --git a/lib/ext/Syncroton/Registry.php b/lib/ext/Syncroton/Registry.php
index 5460f96..f67e2d3 100644
--- a/lib/ext/Syncroton/Registry.php
+++ b/lib/ext/Syncroton/Registry.php
@@ -1,430 +1,445 @@
<?php
/**
* Zend Framework
*
* LICENSE
*
* This source file is subject to the new BSD license that is bundled
* with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://framework.zend.com/license/new-bsd
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@zend.com so we can send you a copy immediately.
*
* @package Syncroton
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
* @version $Id: Registry.php 10020 2009-08-18 14:34:09Z j.fischer@metaways.de $
*/
/**
* Generic storage class helps to manage global data.
*
* @package Syncroton
* @copyright Copyright (c) 2005-2009 Zend Technologies USA Inc. (http://www.zend.com)
* @license http://framework.zend.com/license/new-bsd New BSD License
*/
class Syncroton_Registry extends ArrayObject
{
const CALENDAR_DATA_CLASS = 'calendar_data_class';
const CONTACTS_DATA_CLASS = 'contacts_data_class';
const EMAIL_DATA_CLASS = 'email_data_class';
const NOTES_DATA_CLASS = 'notes_data_class';
const TASKS_DATA_CLASS = 'tasks_data_class';
const GAL_DATA_CLASS = 'gal_data_class';
const DEFAULT_POLICY = 'default_policy';
const PING_TIMEOUT = 'ping_timeout';
+ const PING_INTERVAL = 'ping_interval';
const QUIET_TIME = 'quiet_time';
const DATABASE = 'database';
const TRANSACTIONMANAGER = 'transactionmanager';
const CONTENTSTATEBACKEND = 'contentstatebackend';
const DEVICEBACKEND = 'devicebackend';
const FOLDERBACKEND = 'folderbackend';
const POLICYBACKEND = 'policybackend';
const SYNCSTATEBACKEND = 'syncstatebackend';
/**
* Class name of the singleton registry object.
* @var string
*/
private static $_registryClassName = 'Syncroton_Registry';
/**
* Registry object provides storage for shared objects.
* @var Syncroton_Registry
*/
private static $_registry = null;
/**
* Retrieves the default registry instance.
*
* @return Syncroton_Registry
*/
public static function getInstance()
{
if (self::$_registry === null) {
self::init();
}
return self::$_registry;
}
/**
* @return Zend_Db_Adapter_Abstract
*/
public static function getDatabase()
{
return self::get(self::DATABASE);
}
/**
* return transaction manager class
*
* @return Syncroton_TransactionManagerInterface
*/
public static function getTransactionManager()
{
if (!self::isRegistered(self::TRANSACTIONMANAGER)) {
self::set(self::TRANSACTIONMANAGER, Syncroton_TransactionManager::getInstance());
}
return self::get(self::TRANSACTIONMANAGER);
}
/**
* Set the default registry instance to a specified instance.
*
* @param Syncroton_Registry $registry An object instance of type Syncroton_Registry,
* or a subclass.
* @return void
* @throws Zend_Exception if registry is already initialized.
*/
public static function setInstance(Syncroton_Registry $registry)
{
if (self::$_registry !== null) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Registry is already initialized');
}
self::setClassName(get_class($registry));
self::$_registry = $registry;
}
/**
* Initialize the default registry instance.
*
* @return void
*/
protected static function init()
{
self::setInstance(new self::$_registryClassName());
}
/**
* Set the class name to use for the default registry instance.
* Does not affect the currently initialized instance, it only applies
* for the next time you instantiate.
*
* @param string $registryClassName
* @return void
* @throws Zend_Exception if the registry is initialized or if the
* class name is not valid.
*/
public static function setClassName($registryClassName = 'Syncroton_Registry')
{
if (self::$_registry !== null) {
require_once 'Zend/Exception.php';
throw new Zend_Exception('Registry is already initialized');
}
if (!is_string($registryClassName)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("Argument is not a class name");
}
/**
* @see Zend_Loader
*/
if (!class_exists($registryClassName)) {
require_once 'Zend/Loader.php';
Zend_Loader::loadClass($registryClassName);
}
self::$_registryClassName = $registryClassName;
}
/**
* Unset the default registry instance.
* Primarily used in tearDown() in unit tests.
* @returns void
*/
public static function _unsetInstance()
{
self::$_registry = null;
}
/**
* getter method, basically same as offsetGet().
*
* This method can be called from an object of type Syncroton_Registry, or it
* can be called statically. In the latter case, it uses the default
* static instance stored in the class.
*
* @param string $index - get the value associated with $index
* @return mixed
* @throws Zend_Exception if no entry is registerd for $index.
*/
public static function get($index)
{
$instance = self::getInstance();
if (!$instance->offsetExists($index)) {
require_once 'Zend/Exception.php';
throw new Zend_Exception("No entry is registered for key '$index'");
}
return $instance->offsetGet($index);
}
/**
* returns content state backend
*
* creates Syncroton_Backend_Content on the fly if not before via
* Syncroton_Registry::set(self::CONTENTSTATEBACKEND, $backend);
*
* @return Syncroton_Backend_IContent
*/
public static function getContentStateBackend()
{
if (!self::isRegistered(self::CONTENTSTATEBACKEND)) {
self::set(self::CONTENTSTATEBACKEND, new Syncroton_Backend_Content(self::getDatabase()));
}
return self::get(self::CONTENTSTATEBACKEND);
}
/**
* returns device backend
*
* creates Syncroton_Backend_Device on the fly if not before via
* Syncroton_Registry::set(self::DEVICEBACKEND, $backend);
*
* @return Syncroton_Backend_IDevice
*/
public static function getDeviceBackend()
{
if (!self::isRegistered(self::DEVICEBACKEND)) {
self::set(self::DEVICEBACKEND, new Syncroton_Backend_Device(self::getDatabase()));
}
return self::get(self::DEVICEBACKEND);
}
/**
* returns folder backend
*
* creates Syncroton_Backend_Folder on the fly if not before via
* Syncroton_Registry::set(self::FOLDERBACKEND, $backend);
*
* @return Syncroton_Backend_IFolder
*/
public static function getFolderBackend()
{
if (!self::isRegistered(self::FOLDERBACKEND)) {
self::set(self::FOLDERBACKEND, new Syncroton_Backend_Folder(self::getDatabase()));
}
return self::get(self::FOLDERBACKEND);
}
+ /**
+ * Return maximum ping interval (HeartbeatInterval) value (in seconds)
+ *
+ * @return int
+ */
+ public static function getPingInterval()
+ {
+ if (!self::isRegistered(self::PING_INTERVAL)) {
+ return 3540; // 59 minutes limit defined in Activesync protocol spec.
+ }
+
+ return self::get(self::PING_INTERVAL);
+ }
+
/**
* return ping timeout
*
* sleep "ping timeout" seconds between folder checks in Ping and Sync command
*
* @return int
*/
public static function getPingTimeout()
{
if (!self::isRegistered(self::PING_TIMEOUT)) {
return 60;
}
return self::get(self::PING_TIMEOUT);
}
/**
* returns policy backend
*
* creates Syncroton_Backend_Policy on the fly if not set before via
* Syncroton_Registry::set(self::POLICYBACKEND, $backend);
*
* @return Syncroton_Backend_ISyncState
*/
public static function getPolicyBackend()
{
if (!self::isRegistered(self::POLICYBACKEND)) {
self::set(self::POLICYBACKEND, new Syncroton_Backend_Policy(self::getDatabase()));
}
return self::get(self::POLICYBACKEND);
}
/**
* return quiet time
*
* don't check folders if last sync was "quiet time" seconds ago
*
* @return int
*/
public static function getQuietTime()
{
if (!self::isRegistered(self::QUIET_TIME)) {
return 180;
}
return self::get(self::QUIET_TIME);
}
/**
* returns syncstate backend
*
* creates Syncroton_Backend_SyncState on the fly if not before via
* Syncroton_Registry::set(self::SYNCSTATEBACKEND, $backend);
*
* @return Syncroton_Backend_ISyncState
*/
public static function getSyncStateBackend()
{
if (!self::isRegistered(self::SYNCSTATEBACKEND)) {
self::set(self::SYNCSTATEBACKEND, new Syncroton_Backend_SyncState(self::getDatabase()));
}
return self::get(self::SYNCSTATEBACKEND);
}
/**
* setter method, basically same as offsetSet().
*
* This method can be called from an object of type Syncroton_Registry, or it
* can be called statically. In the latter case, it uses the default
* static instance stored in the class.
*
* @param string $index The location in the ArrayObject in which to store
* the value.
* @param mixed $value The object to store in the ArrayObject.
* @return void
*/
public static function set($index, $value)
{
$instance = self::getInstance();
$instance->offsetSet($index, $value);
}
public static function setDatabase(Zend_Db_Adapter_Abstract $db)
{
self::set(self::DATABASE, $db);
}
public static function setCalendarDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::CALENDAR_DATA_CLASS, $className);
}
public static function setContactsDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::CONTACTS_DATA_CLASS, $className);
}
public static function setEmailDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::EMAIL_DATA_CLASS, $className);
}
public static function setNotesDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::NOTES_DATA_CLASS, $className);
}
public static function setTasksDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::TASKS_DATA_CLASS, $className);
}
public static function setGALDataClass($className)
{
if (!class_exists($className)) {
throw new InvalidArgumentException('invalid $_className provided');
}
self::set(self::GAL_DATA_CLASS, $className);
}
public static function setTransactionManager($manager)
{
self::set(self::TRANSACTIONMANAGER, $manager);
}
/**
* Returns TRUE if the $index is a named value in the registry,
* or FALSE if $index was not found in the registry.
*
* @param string $index
* @return boolean
*/
public static function isRegistered($index)
{
if (self::$_registry === null) {
return false;
}
return self::$_registry->offsetExists($index);
}
/**
* Constructs a parent ArrayObject with default
* ARRAY_AS_PROPS to allow acces as an object
*
* @param array $array data array
* @param integer $flags ArrayObject flags
*/
public function __construct($array = array(), $flags = parent::ARRAY_AS_PROPS)
{
parent::__construct($array, $flags);
}
/**
* @param string $index
* @returns mixed
*
* Workaround for http://bugs.php.net/bug.php?id=40442 (ZF-960).
*/
public function offsetExists($index)
{
return array_key_exists($index, $this);
}
}
diff --git a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage13.php b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage13.php
index f670f75..6b909f3 100644
--- a/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage13.php
+++ b/lib/ext/Syncroton/Wbxml/Dtd/ActiveSync/CodePage13.php
@@ -1,36 +1,35 @@
<?php
/**
* Syncroton
*
* @package Wbxml
* @subpackage ActiveSync
* @license http://www.tine20.org/licenses/lgpl.html LGPL Version 3
* @copyright Copyright (c) 2008-2012 Metaways Infosystems GmbH (http://www.metaways.de)
* @author Lars Kneschke <l.kneschke@metaways.de>
*/
/**
* class documentation
*
* @package Wbxml
* @subpackage ActiveSync
*/
-
class Syncroton_Wbxml_Dtd_ActiveSync_CodePage13 extends Syncroton_Wbxml_Dtd_ActiveSync_Abstract
{
protected $_codePageNumber = 13;
-
+
protected $_codePageName = 'Ping';
-
- protected $_tags = array(
+
+ protected $_tags = array(
'Ping' => 0x05,
'AutdState' => 0x06, //unused
'Status' => 0x07,
- 'HeartBeatInterval' => 0x08,
+ 'HeartbeatInterval' => 0x08,
'Folders' => 0x09,
'Folder' => 0x0a,
'Id' => 0x0b,
'Class' => 0x0c,
'MaxFolders' => 0x0d
);
-}
\ No newline at end of file
+}
diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php
index 3ae473c..d2867b8 100644
--- a/lib/kolab_sync.php
+++ b/lib/kolab_sync.php
@@ -1,467 +1,468 @@
<?php
/**
+--------------------------------------------------------------------------+
| Kolab Sync (ActiveSync for Kolab) |
| |
| Copyright (C) 2011-2012, Kolab Systems AG <contact@kolabsys.com> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU Affero General Public License as published |
| by the Free Software Foundation, either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| You should have received a copy of the GNU Affero General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/> |
+--------------------------------------------------------------------------+
| Author: Aleksander Machniak <machniak@kolabsys.com> |
+--------------------------------------------------------------------------+
*/
/**
* Main application class (based on Roundcube Framework)
*/
class kolab_sync extends rcube
{
/**
* Application name
*
* @var string
*/
public $app_name = 'ActiveSync for Kolab'; // no double quotes inside
/**
* Current user
*
* @var rcube_user
*/
public $user;
const CHARSET = 'UTF-8';
const VERSION = "2.3";
/**
* This implements the 'singleton' design pattern
*
* @return kolab_sync The one and only instance
*/
static function get_instance()
{
if (!self::$instance || !is_a(self::$instance, 'kolab_sync')) {
self::$instance = new kolab_sync();
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
/**
* Initialization of class instance
*/
public function startup()
{
// Initialize Syncroton Logger
$debug_mode = $this->config->get('activesync_debug') ? kolab_sync_logger::DEBUG : kolab_sync_logger::WARN;
$this->logger = new kolab_sync_logger($debug_mode);
// Get list of plugins
// WARNING: We can use only plugins that are prepared for this
// e.g. are not using output or rcmail objects or
// doesn't throw errors when using them
$plugins = (array)$this->config->get('activesync_plugins', array('kolab_auth'));
$required = array('libkolab');
// Initialize/load plugins
$this->plugins = kolab_sync_plugin_api::get_instance();
$this->plugins->init($this, $this->task);
$this->plugins->load_plugins($plugins, $required);
}
/**
* Application execution (authentication and ActiveSync)
*/
public function run()
{
$this->plugins->exec_hook('startup', array('task' => 'login'));
// when used with (f)cgi no PHP_AUTH* variables are available without defining a special rewrite rule
if (!isset($_SERVER['PHP_AUTH_USER'])) {
// "Basic didhfiefdhfu4fjfjdsa34drsdfterrde..."
if (isset($_SERVER["REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REMOTE_USER"], 6));
} elseif (isset($_SERVER["REDIRECT_REMOTE_USER"])) {
$basicAuthData = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6));
} elseif (isset($_SERVER["Authorization"])) {
$basicAuthData = base64_decode(substr($_SERVER["Authorization"], 6));
} elseif (isset($_SERVER["HTTP_AUTHORIZATION"])) {
$basicAuthData = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"], 6));
}
if (isset($basicAuthData) && !empty($basicAuthData)) {
list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(":", $basicAuthData);
}
}
if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
// Convert domain.tld\username into username@domain (?)
$username = explode("\\", $_SERVER['PHP_AUTH_USER']);
if (count($username) == 2) {
$_SERVER['PHP_AUTH_USER'] = $username[1];
if (!strpos($_SERVER['PHP_AUTH_USER'], '@') && !empty($username[0])) {
$_SERVER['PHP_AUTH_USER'] .= '@' . $username[0];
}
}
// Authenticate the user
$userid = $this->authenticate($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']);
}
if (empty($userid)) {
header('WWW-Authenticate: Basic realm="' . $this->app_name .'"');
header('HTTP/1.1 401 Unauthorized');
exit;
}
// Set log directory per-user
$this->set_log_dir($this->username ?: $_SERVER['PHP_AUTH_USER']);
// Save user password for Roundcube Framework
$this->password = $_SERVER['PHP_AUTH_PW'];
// Register Syncroton backends
Syncroton_Registry::set('loggerBackend', $this->logger);
Syncroton_Registry::set(Syncroton_Registry::DATABASE, new kolab_sync_db);
Syncroton_Registry::set(Syncroton_Registry::TRANSACTIONMANAGER, kolab_sync_transaction_manager::getInstance());
Syncroton_Registry::set(Syncroton_Registry::DEVICEBACKEND, new kolab_sync_backend_device);
Syncroton_Registry::set(Syncroton_Registry::FOLDERBACKEND, new kolab_sync_backend_folder);
Syncroton_Registry::set(Syncroton_Registry::SYNCSTATEBACKEND, new kolab_sync_backend_state);
Syncroton_Registry::set(Syncroton_Registry::CONTENTSTATEBACKEND, new kolab_sync_backend_content);
Syncroton_Registry::set(Syncroton_Registry::POLICYBACKEND, new kolab_sync_backend_policy);
Syncroton_Registry::setContactsDataClass('kolab_sync_data_contacts');
Syncroton_Registry::setCalendarDataClass('kolab_sync_data_calendar');
Syncroton_Registry::setEmailDataClass('kolab_sync_data_email');
Syncroton_Registry::setNotesDataClass('kolab_sync_data_notes');
Syncroton_Registry::setTasksDataClass('kolab_sync_data_tasks');
Syncroton_Registry::setGALDataClass('kolab_sync_data_gal');
// Configuration
Syncroton_Registry::set(Syncroton_Registry::PING_TIMEOUT, $this->config->get('activesync_ping_timeout', 60));
- Syncroton_Registry::set(Syncroton_Registry::QUIET_TIME, $this->config->get('activesync_quiet_time', 180));
+ Syncroton_Registry::set(Syncroton_Registry::PING_INTERVAL, $this->config->get('activesync_ping_interval', 15 * 60));
+ Syncroton_Registry::set(Syncroton_Registry::QUIET_TIME, $this->config->get('activesync_quiet_time', 3 * 60));
// Run Syncroton
$syncroton = new Syncroton_Server($userid);
$syncroton->handle();
}
/**
* Authenticates a user
*
* @param string $username User name
* @param string $password User password
*
* @param int User ID
*/
public function authenticate($username, $password)
{
// use shared cache for kolab_auth plugin result (username canonification)
$cache = $this->get_cache_shared('activesync_auth');
$host = $this->select_host($username);
$cache_key = sha1($username . '::' . $host);
if (!$cache || !($auth = $cache->get($cache_key))) {
$auth = $this->plugins->exec_hook('authenticate', array(
'host' => $host,
'user' => $username,
'pass' => $password,
));
if (!$auth['abort'] && $cache) {
$cache->set($cache_key, array(
'user' => $auth['user'],
'host' => $auth['host'],
));
}
// LDAP server failure... send 503 error
if ($auth['kolab_ldap_error']) {
self::server_error();
}
}
else {
$auth['pass'] = $password;
}
// Authenticate - get Roundcube user ID
if (!$auth['abort'] && ($userid = $this->login($auth['user'], $auth['pass'], $auth['host'], $err))) {
// set real username
$this->username = $auth['user'];
return $userid;
}
$this->plugins->exec_hook('login_failed', array(
'host' => $auth['host'],
'user' => $auth['user'],
));
// IMAP server failure... send 503 error
if ($err == rcube_imap_generic::ERROR_BAD) {
self::server_error();
}
}
/**
* Storage host selection
*/
private function select_host($username)
{
// Get IMAP host
$host = $this->config->get('default_host');
if (is_array($host)) {
list($user, $domain) = explode('@', $username);
// try to select host by mail domain
if (!empty($domain)) {
foreach ($host as $storage_host => $mail_domains) {
if (is_array($mail_domains) && in_array_nocase($domain, $mail_domains)) {
$host = $storage_host;
break;
}
else if (stripos($storage_host, $domain) !== false || stripos(strval($mail_domains), $domain) !== false) {
$host = is_numeric($storage_host) ? $mail_domains : $storage_host;
break;
}
}
}
// take the first entry if $host is not found
if (is_array($host)) {
list($key, $val) = each($default_host);
$host = is_numeric($key) ? $val : $key;
}
}
return rcube_utils::parse_host($host);
}
/**
* Authenticates a user in IMAP and returns Roundcube user ID.
*/
private function login($username, $password, $host, &$error = null)
{
if (empty($username)) {
return null;
}
$login_lc = $this->config->get('login_lc');
$default_port = $this->config->get('default_port', 143);
// parse $host
$a_host = parse_url($host);
if ($a_host['host']) {
$host = $a_host['host'];
$ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
if (!empty($a_host['port'])) {
$port = $a_host['port'];
}
else if ($ssl && $ssl != 'tls' && (!$default_port || $default_port == 143)) {
$port = 993;
}
}
if (!$port) {
$port = $default_port;
}
// Convert username to lowercase. If storage backend
// is case-insensitive we need to store always the same username
if ($login_lc) {
if ($login_lc == 2 || $login_lc === true) {
$username = mb_strtolower($username);
}
else if (strpos($username, '@')) {
// lowercase domain name
list($local, $domain) = explode('@', $username);
$username = $local . '@' . mb_strtolower($domain);
}
}
// Here we need IDNA ASCII
// Only rcube_contacts class is using domain names in Unicode
$host = rcube_utils::idn_to_ascii($host);
$username = rcube_utils::idn_to_ascii($username);
// user already registered?
if ($user = rcube_user::query($username, $host)) {
$username = $user->data['username'];
}
// authenticate user in IMAP
$storage = $this->get_storage();
if (!$storage->connect($host, $username, $password, $port, $ssl)) {
$error = $storage->get_error_code();
return null;
}
// No user in database, but IMAP auth works
if (!is_object($user)) {
if ($this->config->get('auto_create_user')) {
// create a new user record
$user = rcube_user::create($username, $host);
if (!$user) {
self::raise_error(array(
'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__,
'message' => "Failed to create a user record",
), true, false);
return null;
}
}
else {
self::raise_error(array(
'code' => 620, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__,
'message' => "Access denied for new user $username. 'auto_create_user' is disabled",
), true, false);
return null;
}
}
// overwrite config with user preferences
$this->user = $user;
$this->config->set_user_prefs((array)$this->user->get_prefs());
$this->set_storage_prop();
setlocale(LC_ALL, 'en_US.utf8', 'en_US.UTF-8');
// force reloading of mailboxes list/data
//$storage->clear_cache('mailboxes', true);
return $user->ID;
}
/**
* Set logging directory per-user
*/
protected function set_log_dir($username)
{
if (empty($username)) {
return;
}
$this->logger->set_username($username);
$user_debug = $this->config->get('activesync_user_debug');
$user_log = $user_debug || $this->config->get('activesync_user_log');
if (!$user_log) {
return;
}
$log_dir = $this->config->get('log_dir');
$log_dir .= DIRECTORY_SEPARATOR . $username;
// in user_debug mode enable logging only if user directory exists
if ($user_debug) {
if (!is_dir($log_dir)) {
return;
}
}
else if (!is_dir($log_dir)) {
if (!mkdir($log_dir, 0770)) {
return;
}
}
if (!empty($_GET['DeviceId'])) {
$log_dir .= DIRECTORY_SEPARATOR . $_GET['DeviceId'];
}
if (!is_dir($log_dir)) {
if (!mkdir($log_dir, 0770)) {
return;
}
}
// make sure we're using debug mode where possible,
if ($user_debug) {
$this->config->set('debug_level', 1);
$this->config->set('memcache_debug', true);
$this->config->set('imap_debug', true);
$this->config->set('ldap_debug', true);
$this->config->set('smtp_debug', true);
$this->config->set('sql_debug', true);
// SQL/IMAP debug need to be set directly on the object instance
// it's already initialized/configured
if ($db = $this->get_dbh()) {
$db->set_debug(true);
}
if ($storage = $this->get_storage()) {
$storage->set_debug(true);
}
$this->logger->mode = kolab_sync_logger::DEBUG;
}
$this->config->set('log_dir', $log_dir);
// re-set PHP error logging
if (($this->config->get('debug_level') & 1) && $this->config->get('log_driver') != 'syslog') {
ini_set('error_log', $log_dir . '/errors');
}
}
/**
* Send HTTP 503 response.
* We send it on LDAP/IMAP server error instead of 401 (Unauth),
* so devices will not ask for new password.
*/
public static function server_error()
{
header("HTTP/1.1 503 Service Temporarily Unavailable");
header("Retry-After: 120");
exit;
}
/**
* Function to be executed in script shutdown
*/
public function shutdown()
{
parent::shutdown();
// cache garbage collector
$this->gc_run();
// write performance stats to logs/console
if ($this->config->get('devel_mode')) {
if (function_exists('memory_get_usage'))
$mem = sprintf('%.1f', memory_get_usage() / 1048576);
if (function_exists('memory_get_peak_usage'))
$mem .= '/' . sprintf('%.1f', memory_get_peak_usage() / 1048576);
$query = $_SERVER['QUERY_STRING'];
$log = $query . ($mem ? ($query ? ' ' : '') . "[$mem]" : '');
if (defined('KOLAB_SYNC_START'))
self::print_timer(KOLAB_SYNC_START, $log);
else
self::console($log);
}
}
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Feb 5, 10:09 AM (6 h, 59 s)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
427898
Default Alt Text
(46 KB)
Attached To
Mode
R4 syncroton
Attached
Detach File
Event Timeline
Log In to Comment