Projects
Kolab:16
kolab-syncroton
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 17
View file
kolab-syncroton.spec
Changed
@@ -36,8 +36,8 @@ %global _ap_sysconfdir %{_sysconfdir}/%{httpd_name} Name: kolab-syncroton -Version: 2.3.6 -Release: 2%{?dist} +Version: 2.3.7 +Release: 1%{?dist} Summary: ActiveSync for Kolab Groupware Group: Applications/Internet @@ -47,12 +47,6 @@ Source0: https://mirror.kolabenterprise.com/pub/releases/%{name}-%{version}.tar.gz Source1: kolab-syncroton.logrotate -Patch0001: 0001-Add-ready-hook-for-Kolab-plugins-Bifrost-T36327.patch -Patch0002: 0002-Fix-LDAP-connection-errors-in-Ping-when-using-active.patch -Patch0003: 0003-Bump-SEQUENCE-number-on-update-Outlook-only.patch -Patch0004: 0004-Return-Invalid-item-6-status-on-SMS-entries-instead-.patch -Patch0005: 0005-implement-setAttendeeStatus.patch - BuildArch: noarch # Use this build requirement to make sure we are using @@ -127,43 +121,10 @@ pushd lib/ext ln -s ../../../roundcubemail/program/lib/Roundcube popd -ln -s ../roundcubemail/vendor vendor - -for plugin in kolab_auth kolab_folders libcalendaring libkolab; do - if [ ! -d "/usr/share/roundcubemail/plugins/$plugin" ]; then - continue - fi - mkdir -p lib/plugins/$plugin - pushd lib/plugins/$plugin - if [ -d "/usr/share/roundcubemail/plugins/" ]; then - find /usr/share/roundcubemail/plugins/$plugin/ -mindepth 1 -maxdepth 1 ! -name "config.inc.php" | while read link_target; do - ln -sv "$(echo ${link_target} | sed -e 's|/usr/share/roundcubemail/|../../../../roundcubemail/|g')" - done - else - find ../../../../roundcubemail/plugins/$plugin/ -mindepth 1 -maxdepth 1 ! -name "config.inc.php" -exec ln -sv {} \; - fi - popd -done -popd - -# Kolab Authentication plugin -%if 0%{?plesk} < 1 -pushd %{buildroot}/%{_datadir}/%{name}/lib/plugins/kolab_auth -rm -rf config.inc.php.dist -ln -s ../../../../../..%{_sysconfdir}/roundcubemail/kolab_auth.inc.php config.inc.php -popd -%endif - -# Kolab Folders plugin -pushd %{buildroot}/%{_datadir}/%{name}/lib/plugins/kolab_folders -rm -rf config.inc.php.dist -ln -s ../../../../../..%{_sysconfdir}/roundcubemail/kolab_folders.inc.php config.inc.php +pushd lib +ln -s ../../roundcubemail/plugins plugins popd - -# Libkolab plugin -pushd %{buildroot}/%{_datadir}/%{name}/lib/plugins/libkolab -rm -rf config.inc.php.dist -ln -s ../../../../../..%{_sysconfdir}/roundcubemail/libkolab.inc.php config.inc.php +ln -s ../roundcubemail/vendor vendor popd %if 0%{?plesk} < 1 @@ -192,17 +153,19 @@ if [ -d "/usr/share/kolab-syncroton/lib/ext/Roundcube" -a ! -L "/usr/share/kolab-syncroton/lib/ext/Roundcube" ]; then rm -rf "/usr/share/kolab-syncroton/lib/ext/Roundcube" fi -if [ -d "/usr/share/kolab-syncroton/lib/plugins/" ]; then - find /usr/share/kolab-syncroton/lib/plugins/ -mindepth 2 -maxdepth 2 | while read file; do - if [ ! -L "${file}" ]; then - rm -rf "${file}" - fi - done + +%pretrans +if [ -d "/usr/share/kolab-syncroton/lib/plugins" -a ! -L "/usr/share/kolab-syncroton/lib/plugins" ]; then + find /usr/share/kolab-syncroton/lib/plugins/ \ + -type l -exec rm -f {} \; + rm -rf /usr/share/kolab-syncroton/lib/plugins/ + pushd /usr/share/kolab-syncroton/lib/ + ln -s ../../roundcubemail/plugins fi %post if [ -f "%{php_inidir}/apc.ini" -o -f "%{php_inidir}/apcu.ini" ]; then - if [ ! -z "`grep ^apc.enabled=1 %{php_inidir}/apc{,u}.ini`" ]; then + if [ ! -z "`grep ^apc.enabled=1 %{php_inidir}/apc{,u}.ini 2>/dev/null`" ]; then %if 0%{?fedora} > 15 /bin/systemctl condrestart %{httpd_name}.service %else @@ -232,6 +195,9 @@ %attr(0770,%{httpd_user},%{httpd_group}) %{_var}/log/%{name} %changelog +* Sun Aug 27 2017 Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> - 2.3.7-1 +- Release 2.3.7 + * Fri Aug 18 2017 Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> - 2.3.6-2 - Patch setAttendeeStatus for increased Outlook compatibility
View file
0001-Add-ready-hook-for-Kolab-plugins-Bifrost-T36327.patch
Deleted
@@ -1,25 +0,0 @@ -From 945eeaf741bd6b94840adc81acc0281912919a16 Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak <machniak@kolabsys.com> -Date: Tue, 1 Aug 2017 13:13:15 +0200 -Subject: [PATCH 01/15] Add 'ready' hook for Kolab plugins (Bifrost#T36327) - ---- - lib/kolab_sync.php | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php -index 6d3838a..5e9d262 100644 ---- a/lib/kolab_sync.php -+++ b/lib/kolab_sync.php -@@ -139,6 +139,8 @@ class kolab_sync extends rcube - exit; - } - -+ $this->plugins->exec_hook('ready', array('task' => 'syncroton')); -+ - // Set log directory per-user - $this->set_log_dir($this->username ?: $_SERVER['PHP_AUTH_USER']); - --- -2.13.2 -
View file
0002-Fix-LDAP-connection-errors-in-Ping-when-using-active.patch
Deleted
@@ -1,59 +0,0 @@ -From ca089e32a5de5eea7766ec6ba69e037242f80f89 Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak <machniak@kolabsys.com> -Date: Wed, 2 Aug 2017 14:07:00 +0200 -Subject: [PATCH 02/15] Fix LDAP connection errors in Ping when using - activesync_gal_sync - -When the LDAP addressbook is used for syncronization we have to -deal with closing and re-creating LDAP connections on sleep(). ---- - lib/kolab_sync.php | 21 +++++++++++++++++++++ - lib/kolab_sync_data_gal.php | 2 +- - 2 files changed, 22 insertions(+), 1 deletion(-) - -diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php -index 5e9d262..c6cb192 100644 ---- a/lib/kolab_sync.php -+++ b/lib/kolab_sync.php -@@ -492,4 +492,25 @@ class kolab_sync extends rcube - self::console($log); - } - } -+ -+ /** -+ * When you're going to sleep the script execution for a longer time -+ * it is good to close all external connections (sql, memcache, SMTP, IMAP). -+ * -+ * No action is required on wake up, all connections will be -+ * re-established automatically. -+ */ -+ public function sleep() -+ { -+ parent::sleep(); -+ -+ // We'll have LDAP addressbooks here if using activesync_gal_sync -+ if ($this->config->get('activesync_gal_sync')) { -+ foreach (kolab_sync_data_gal::$address_books as $book) { -+ $book->close(); -+ } -+ -+ kolab_sync_data_gal::$address_books = array(); -+ } -+ } - } -diff --git a/lib/kolab_sync_data_gal.php b/lib/kolab_sync_data_gal.php -index 95894e7..09238f4 100644 ---- a/lib/kolab_sync_data_gal.php -+++ b/lib/kolab_sync_data_gal.php -@@ -42,7 +42,7 @@ class kolab_sync_data_gal extends kolab_sync_data implements Syncroton_Data_IDat - * - * @var array - */ -- protected static $address_books = array(); -+ public static $address_books = array(); - - /** - * Mapping from ActiveSync Contacts namespace fields --- -2.13.2 -
View file
0003-Bump-SEQUENCE-number-on-update-Outlook-only.patch
Deleted
@@ -1,43 +0,0 @@ -From a68e0428fdb0b34cdc7de673e2d0737fddf0d355 Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak <machniak@kolabsys.com> -Date: Fri, 4 Aug 2017 12:10:30 +0200 -Subject: [PATCH 03/15] Bump SEQUENCE number on update (Outlook only). - -It's been confirmed that any change of the event that has attendees specified -bumps SEQUENCE number of the event (we can see this in sent iTips). ---- - lib/kolab_sync_data_calendar.php | 11 +++++++++++ - 1 file changed, 11 insertions(+) - -diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php -index 946538a..8fab24d 100644 ---- a/lib/kolab_sync_data_calendar.php -+++ b/lib/kolab_sync_data_calendar.php -@@ -332,6 +332,7 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - $foldername = isset($event['_mailbox']) ? $event['_mailbox'] : $this->getFolderName($folderid); - $config = $this->getFolderConfig($foldername); - $is_exception = $data instanceof Syncroton_Model_EventException; -+ $last_update = $event['changed']; - - $event['allday'] = 0; - -@@ -483,6 +484,16 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - $event['recurrence'] = $this->recurrence_to_kolab($data, $folderid, $timezone); - } - -+ // Bump SEQUENCE number on update (Outlook only). -+ // It's been confirmed that any change of the event that has attendees specified -+ // bumps SEQUENCE number of the event (we can see this in sent iTips). -+ if (!empty($entry) && !$is_exception && !empty($data->attendees) -+ && $data->dtStamp && $last_update && $data->dtStamp > $last_update -+ && stripos($this->device->devicetype, 'outlook') !== false -+ ) { -+ $event['sequence'] += 1; -+ } -+ - return $event; - } - --- -2.13.2 -
View file
0004-Return-Invalid-item-6-status-on-SMS-entries-instead-.patch
Deleted
@@ -1,28 +0,0 @@ -From d32b2289c65d749e55ea97de87f0563d4f989881 Mon Sep 17 00:00:00 2001 -From: Aleksander Machniak <machniak@kolabsys.com> -Date: Fri, 4 Aug 2017 16:58:13 +0200 -Subject: [PATCH 04/15] Return "Invalid item" (6) status on SMS entries instead - of "Server error" (5) - -"Server error" status may cause client to try again and again, -while "Invalid item" should thell the device to stop. ---- - lib/kolab_sync_data_email.php | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/lib/kolab_sync_data_email.php b/lib/kolab_sync_data_email.php -index e528f9e..8f4d7a4 100644 ---- a/lib/kolab_sync_data_email.php -+++ b/lib/kolab_sync_data_email.php -@@ -570,7 +570,7 @@ class kolab_sync_data_email extends kolab_sync_data implements Syncroton_Data_ID - { - // Throw exception here for better handling of unsupported - // entry creation, it can be object of class Email or SMS here -- throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); -+ throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::INVALID_ITEM); - } - - /** --- -2.13.2 -
View file
0005-implement-setAttendeeStatus.patch
Deleted
@@ -1,389 +0,0 @@ -diff --git a/lib/kolab_sync.php b/lib/kolab_sync.php -index c6cb192..823d927 100644 ---- a/lib/kolab_sync.php -+++ b/lib/kolab_sync.php -@@ -82,7 +82,7 @@ class kolab_sync extends rcube - // 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')); -- $plugins = array_unique(array_merge($plugins, array('libkolab'))); -+ $plugins = array_unique(array_merge($plugins, array('libcalendaring', 'libkolab'))); - - // Initialize/load plugins - $this->plugins = kolab_sync_plugin_api::get_instance(); -diff --git a/lib/kolab_sync_data.php b/lib/kolab_sync_data.php -index 2b91a7d..f1d723e 100644 ---- a/lib/kolab_sync_data.php -+++ b/lib/kolab_sync_data.php -@@ -92,6 +92,11 @@ abstract class kolab_sync_data implements Syncroton_Data_IData - protected $imap_folders = array(); - - /** -+ * Shortcut to logging. -+ */ -+ protected $log; -+ -+ /** - * Timezone - * - * @var string -@@ -186,6 +191,8 @@ abstract class kolab_sync_data implements Syncroton_Data_IData - - $this->defaultRootFolder = $this->defaultFolder . '::Syncroton'; - -+ $this->log = Syncroton_Registry::get('loggerBackend'); -+ - // set internal timezone of kolab_format to user timezone - try { - $this->timezone = rcube::get_instance()->config->get('timezone', 'GMT'); -@@ -954,18 +961,16 @@ abstract class kolab_sync_data implements Syncroton_Data_IData - $crc = $matches[1]; - $uid = $matches[2]; - -- if (strlen($entryid) >= 64) { -- foreach ($folder->select(array(array('uid', '~*', $uid))) as $object) { -- if (($object['uid'] == $uid || strpos($object['uid'], $uid) === 0) -- && $crc == $this->objectCRC($object['uid'], $folder) -- ) { -- $object['_folderid'] = $folderid; -- return $object; -- } -+ foreach ($folder->select(array(array('uid', '~*', $uid))) as $object) { -+ if (($object['uid'] == $uid || strpos($object['uid'], $uid) === 0) -+ && $crc == $this->objectCRC($object['uid'], $folder) -+ ) { -+ $object['_folderid'] = $folderid; -+ return $object; - } -- -- continue; - } -+ -+ continue; - } - - // Or (faster) strict UID matching... -diff --git a/lib/kolab_sync_data_calendar.php b/lib/kolab_sync_data_calendar.php -index 8fab24d..621cb3c 100644 ---- a/lib/kolab_sync_data_calendar.php -+++ b/lib/kolab_sync_data_calendar.php -@@ -180,6 +180,14 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - public function getEntry(Syncroton_Model_SyncCollection $collection, $serverId, $as_array = false) - { - $event = is_array($serverId) ? $serverId : $this->getObject($collection->collectionId, $serverId); -+ -+ // If there is no such event, return NULL (otherwise this function -+ // returns a new Syncroton_Model_Event, that is further treated as if -+ // it were a valid, existing entry. -+ if (!$event) { -+ return NULL; -+ } -+ - $config = $this->getFolderConfig($event['_mailbox']); - $result = array(); - -@@ -273,11 +281,11 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - if ($name = $attendee['name']) { - $result['organizerName'] = $name; - } -+ - if ($email = $attendee['email']) { - $result['organizerEmail'] = $email; - } - -- unset($event['attendees'][$idx]); - break; - } - } -@@ -286,11 +294,15 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - // Attendees - if (!empty($event['attendees'])) { - foreach ($event['attendees'] as $idx => $attendee) { -- $att = array(); -+ if ($attendee['role'] == 'ORGANIZER') -+ continue; -+ -+ $att = Array(); - - if ($name = $attendee['name']) { - $att['name'] = $name; - } -+ - if ($email = $attendee['email']) { - $att['email'] = $email; - } -@@ -450,32 +462,65 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - $name = $data->organizerName; - $email = $data->organizerEmail; - if ($name || $email) { -- $event['attendees'][] = array( -+ $_attendee = array( - 'role' => 'ORGANIZER', - 'name' => $name, - 'email' => $email, - ); -+ -+ if ($this->asversion >= 12) { -+ $_attendee['status'] = $data->meetingStatus == 1 ? 'ACCEPTED' : 'DECLINED'; -+ $_attendee['rsvp'] = FALSE; -+ } -+ -+ $event['attendees'][] = $_attendee; - } - } - - // Attendees - if (isset($data->attendees)) { - foreach ($data->attendees as $attendee) { -+ if ($attendee->email == $data->organizerEmail) -+ continue; -+ - $role = false; -+ - if (isset($attendee->attendeeType)) { -- $role = array_search($attendee->attendeeType, $this->attendeeTypeMap); -+ $role = array_search( -+ $attendee->attendeeType, -+ $this->attendeeTypeMap -+ ); -+ - } -+ - if ($role === false) { -- $role = array_search(self::ATTENDEE_TYPE_REQUIRED, $this->attendeeTypeMap); -+ $role = array_search( -+ self::ATTENDEE_TYPE_REQUIRED, -+ $this->attendeeTypeMap -+ ); - } - -- // AttendeeStatus send only on repsonse (?) -- -- $event['attendees'][] = array( -- 'role' => $role, -- 'name' => $attendee->name, -- 'email' => $attendee->email, -+ // AttendeeStatus send only on response (?) -+ $_attendee = array( -+ 'role' => $role, -+ 'name' => $attendee->name, -+ 'email' => $attendee->email, - ); -+ -+ if ($this->asversion >= 12) { -+ if ($status = $attendee->attendeeStatus) { -+ $_attendee['status'] = array_search( -+ $status, -+ $this->attendeeStatusMap -+ ); -+ -+ if ($status == self::ATTENDEE_STATUS_UNKNOWN) { -+ $_attendee['rsvp'] = TRUE; -+ } -+ } -+ } -+ -+ $event['attendees'][] = $_attendee; - } - } - -@@ -506,8 +551,141 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - */ - public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request) - { -+ /* -+ * The request actually involves an iTip request. -+ * -+ * - Get to the iTip, -+ * - Extract the event UID, -+ * - If not already an event in the user's calendar(s), create the event, -+ * - If already an event, update that event. -+ */ -+ -+ $object = $this->parseMessageId($request->requestId); -+ $message = new rcube_message($object['uid'], $object['foldername']); -+ -+ // Parse the message -+ $libcal = libcalendaring::get_instance(); -+ $libcal->mail_message_load(array('object' => $message)); -+ $ical_objects = $libcal->get_mail_ical_objects(); -+ -+ $itip = $ical_objects->objects[0]; -+ $uid = $itip['uid']; -+ -+ // Search the user's (event) folders -+ $folders = $this->listFolders(); -+ -+ $serverIds = Array(); -+ foreach ($folders as $folder => $attrs) { -+ $serverIds[] = $this->serverId($uid, $attrs['imap_name']); -+ } -+ -+ // Go search for a match -+ $existing_event = NULL; -+ -+ foreach ($serverIds as $serverId) { -+ foreach ($folders as $_folderid => $attrs) { -+ $collection = new Syncroton_Model_SyncCollection( -+ Array( -+ 'collectionId' => $_folderid -+ ) -+ ); -+ -+ $existing_event = $this->getEntry( -+ $collection, -+ $serverId -+ ); -+ -+ if ($existing_event) { -+ $folderId = $_folderid; -+ break; -+ } -+ } -+ -+ if ($existing_event) { -+ break; -+ } -+ } -+ -+ /* -+ Consider; -+ -+ - $existing_event->sensitivity (private, confidential, public) -+ - $existing_event->attendees[$x]->{role,rsvp,status} -+ - $request->userresponse -+ */ -+ -+ if ($existing_event) { -+ // This is an existing event that needs updating -+ $user_emails = kolab_sync::get_instance()->user->list_emails(); -+ $user_emails = array_map( -+ function($v) { return $v['email']; }, -+ $user_emails -+ ); -+ -+ foreach ($existing_event->attendees as $idx => $attendee) { -+ if (in_array_nocase($attendee->email, $user_emails)) { -+ $this->log->debug("found my record, updating status"); -+ switch ($request->userResponse) { -+ case Syncroton_Model_MeetingResponse::RESPONSE_ACCEPTED: -+ $existing_event->attendees[$idx]->attendeeStatus = self::ATTENDEE_STATUS_ACCEPTED; -+ break; -+ case Syncroton_Model_MeetingResponse::RESPONSE_DECLINED: -+ $existing_event->attendees[$idx]->attendeeStatus = self::ATTENDEE_STATUS_DECLINED; -+ break; -+ case Syncroton_Model_MeetingResponse::RESPONSE_TENTATIVE: -+ $existing_event->attendees[$idx]->attendeeStatus = self::ATTENDEE_STATUS_TENTATIVE; -+ break; -+ } -+ } -+ } -+ -+ $this->updateEntry($folderId, $serverId, $existing_event); -+ -+ } else { -+ // This is a new event -+ $this->log->debug("This is a new event"); -+ $folderId = $this->getDefaultFolder()['realid']; -+ -+ -+ $user_emails = kolab_sync::get_instance()->user->list_emails(); -+ $user_emails = array_map( -+ function($v) { return $v['email']; }, -+ $user_emails -+ ); -+ -+ $attendees = Array(); -+ -+ foreach($itip['attendees'] as $idx => $attendee) { -+ $att = $attendee; -+ -+ if (in_array_nocase($att['email'], $user_emails)) { -+ switch ($request->userResponse) { -+ case Syncroton_Model_MeetingResponse::RESPONSE_ACCEPTED: -+ $att['status'] = 'ACCEPTED'; -+ $att['rsvp'] = FALSE; -+ break; -+ -+ case Syncroton_Model_MeetingResponse::RESPONSE_DECLINED: -+ $att['status'] = 'DECLINED'; -+ $att['rsvp'] = FALSE; -+ break; -+ -+ case Syncroton_Model_MeetingResponse::RESPONSE_TENTATIVE: -+ $att['status'] = 'TENTATIVE'; -+ break; -+ } -+ } -+ -+ $attendees[] = $att; -+ } -+ -+ $itip['attendees'] = $attendees; -+ -+ $data = $this->createObject($folderId, $itip); -+ } -+ - // @TODO: not implemented -- throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); -+ throw new Syncroton_Exception_Status_MeetingResponse(1); - } - - /** -@@ -562,20 +740,25 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - // TODO: Delegation/aliases support - $user_emails = kolab_sync::get_instance()->user->list_emails(); - $user_emails = array_map(function($v) { return $v['email']; }, $user_emails); -- $is_organizer = true; -+ $is_organizer = false; - - foreach ($event['attendees'] as $attendee) { -+ if ($attendee['role'] != 'ORGANIZER') { -+ // Irrelevant, skip -+ continue; -+ } -+ - if (in_array_nocase($attendee['email'], $user_emails)) { -- $is_organizer = false; -+ $is_organizer = true; - break; - } - } - - if ($event['status'] == 'CANCELLED') { -- $status = !empty($is_organizer) ? 5 : 7; -+ $status = $is_organizer ? 5 : 7; - } - else { -- $status = !empty($is_organizer) ? 1 : 3; -+ $status = $is_organizer ? 1 : 3; - } - } - -@@ -698,4 +881,26 @@ class kolab_sync_data_calendar extends kolab_sync_data implements Syncroton_Data - $entry->endTime = $rounded; - } - } -+ -+ private function parseMessageId($entryid) -+ { -+ // replyEmail/forwardEmail -+ if (is_array($entryid)) { -+ $entryid = $entryid['itemId']; -+ } -+ -+ list($folderid, $uid) = explode('::', $entryid); -+ $foldername = $this->backend->folder_id2name($folderid, $this->device->deviceid); -+ -+ if ($foldername === null || $foldername === false) { -+ // @TODO exception? -+ return null; -+ } -+ -+ return array( -+ 'uid' => $uid, -+ 'folderid' => $folderid, -+ 'foldername' => $foldername, -+ ); -+ } - }
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +kolab-syncroton (2.3.7-0~kolab1) unstable; urgency=low + + * Release 2.3.7 + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Sun, 27 Aug 2017 15:13:40 +0200 + kolab-syncroton (2.3.6-0~kolab2) unstable; urgency=low * Patch setAttendeeStatus for increased Outlook compatibility
View file
debian.series
Changed
@@ -1,5 +0,0 @@ -0001-Add-ready-hook-for-Kolab-plugins-Bifrost-T36327.patch -p1 -0002-Fix-LDAP-connection-errors-in-Ping-when-using-active.patch -p1 -0003-Bump-SEQUENCE-number-on-update-Outlook-only.patch -p1 -0004-Return-Invalid-item-6-status-on-SMS-entries-instead-.patch -p1 -0005-implement-setAttendeeStatus.patch -p1
View file
kolab-syncroton-2.3.7.tar.gz/docs/MAINTENANCE
Added
@@ -0,0 +1,46 @@ +Here we collect some hints for Kolab Syncroton administrators. + +1. Force re-synchronization of a device +--------------------------------------- + +From a user perspective there are two possibilities: delete and create again an account +in the ActiveSync client accounts configuration or delete the device entry +in Roundcube Settings > ActiveSync. + +Administrators can do this more nicely using SQL: + +- Force re-synchronization of all users/devices: + + DELETE FROM syncroton_synckey; + + Note: This will re-synchronize also folders structure. + +- Force re-synchronization of all users/devices, but only of specified folder type: + + DELETE FROM syncroton_synckey + WHERE type IN (SELECT id FROM syncroton_folder WHERE class = 'Calendar'); + + Note: possible classes: Calendar, Tasks, Email, Notes, Contacts. + +- Force re-synchronization of all devices of a specified user: + + DELETE FROM syncroton_synckey + WHERE type IN (SELECT id FROM syncroton_folder + WHERE device_id IN (SELECT id FROM syncroton_device + WHERE owner_id IN (SELECT id FROM users WHERE user_id = 'test.test@example.org') + ) + ); + +- Force re-synchronization of all contact folders of a specified user: + + DELETE FROM syncroton_synckey + WHERE type IN (SELECT id FROM syncroton_folder + WHERE class = 'Contacts' AND device_id IN (SELECT id FROM syncroton_device + WHERE owner_id IN (SELECT user_id FROM users WHERE username = 'test.test@example.org') + ) + ); + + Note: possible classes: Calendar, Tasks, Email, Notes, Contacts. + +Note: Above method will produce one warning entry for each folder in syncroton error log, but +other than that there should be no side effects and devices should re-synchronize data.
View file
kolab-syncroton-2.3.6.tar.gz/lib/kolab_sync.php -> kolab-syncroton-2.3.7.tar.gz/lib/kolab_sync.php
Changed
@@ -46,7 +46,7 @@ public $password; const CHARSET = 'UTF-8'; - const VERSION = "2.3.6"; + const VERSION = "2.3.7"; /** @@ -82,7 +82,7 @@ // 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')); - $plugins = array_unique(array_merge($plugins, array('libkolab'))); + $plugins = array_unique(array_merge($plugins, array('libkolab', 'libcalendaring'))); // Initialize/load plugins $this->plugins = kolab_sync_plugin_api::get_instance(); @@ -139,6 +139,8 @@ exit; } + $this->plugins->exec_hook('ready', array('task' => 'syncroton')); + // Set log directory per-user $this->set_log_dir($this->username ?: $_SERVER['PHP_AUTH_USER']); @@ -490,4 +492,25 @@ self::console($log); } } + + /** + * When you're going to sleep the script execution for a longer time + * it is good to close all external connections (sql, memcache, SMTP, IMAP). + * + * No action is required on wake up, all connections will be + * re-established automatically. + */ + public function sleep() + { + parent::sleep(); + + // We'll have LDAP addressbooks here if using activesync_gal_sync + if ($this->config->get('activesync_gal_sync')) { + foreach (kolab_sync_data_gal::$address_books as $book) { + $book->close(); + } + + kolab_sync_data_gal::$address_books = array(); + } + } }
View file
kolab-syncroton-2.3.6.tar.gz/lib/kolab_sync_data.php -> kolab-syncroton-2.3.7.tar.gz/lib/kolab_sync_data.php
Changed
@@ -91,6 +91,13 @@ */ protected $imap_folders = array(); + /** + * Logger instance. + * + * @var kolab_sync_logger + */ + protected $logger; + /** * Timezone * @@ -117,7 +124,6 @@ const RESULT_UID = 1; const RESULT_COUNT = 2; - /** * Recurrence types */ @@ -183,6 +189,7 @@ $this->device = $device; $this->asversion = floatval($device->acsversion); $this->syncTimeStamp = $syncTimeStamp; + $this->logger = Syncroton_Registry::get(Syncroton_Registry::LOGGERBACKEND); $this->defaultRootFolder = $this->defaultFolder . '::Syncroton'; @@ -1223,24 +1230,21 @@ return null; } -/* - // hash array e.g. organizer - else if ($count == 2) { - $name = $name_items[0]; - $type = $name_items[1]; - $key_name = $name_items[2]; - if (!empty($data[$name]) && is_array($data[$name])) { - foreach ($data[$name] as $element) { - if ($element['type'] == $type) { - return $element[$key_name]; - } + // custom properties + if ($count == 2 && $name_items[0] == 'x-custom') { + $value = null; + + foreach ((array) $data['x-custom'] as $val) { + if (is_array($val) && $val[0] == $name_items[1]) { + $value = $val[1]; + break; } } - return null; + return $value; } -*/ + $name_items = explode(':', $name); $name = $name_items[0]; @@ -1266,9 +1270,10 @@ } $name_items = explode('.', $name); + $count = count($name_items); // multi-level array (e.g. address, phone) - if (count($name_items) == 3) { + if ($count == 3) { $name = $name_items[0]; $type = $name_items[1]; $key_name = $name_items[2]; @@ -1295,6 +1300,20 @@ return; } + // custom properties + if ($count == 2 && $name_items[0] == 'x-custom') { + foreach ((array) $data['x-custom'] as $idx => $val) { + if (is_array($val) && $val[0] == $name_items[1]) { + $data['x-custom'][$idx][1] = $value; + return; + } + } + + $data['x-custom'][] = array($name_items[1], $value); + return; + } + + $name_items = explode(':', $name); $name = $name_items[0]; @@ -1313,9 +1332,10 @@ protected function unsetKolabDataItem(&$data, $name) { $name_items = explode('.', $name); + $count = count($name_items); // multi-level array (e.g. address, phone) - if (count($name_items) == 3) { + if ($count == 3) { $name = $name_items[0]; $type = $name_items[1]; $key_name = $name_items[2]; @@ -1351,6 +1371,15 @@ return; } + // custom properties + if ($count == 2 && $name_items[0] == 'x-custom') { + foreach ((array) $data['x-custom'] as $idx => $val) { + if (is_array($val) && $val[0] == $name_items[1]) { + unset($data['x-custom'][$idx]); + } + } + } + $name_items = explode(':', $name); $name = $name_items[0]; @@ -1881,6 +1910,17 @@ } /** + * Returns all email addresses of the current user + */ + protected function user_emails() + { + $user_emails = kolab_sync::get_instance()->user->list_emails(); + $user_emails = array_map(function($v) { return $v['email']; }, $user_emails); + + return $user_emails; + } + + /** * Generate CRC-based ServerId from object UID */ protected function serverId($uid, $folder)
View file
kolab-syncroton-2.3.6.tar.gz/lib/kolab_sync_data_calendar.php -> kolab-syncroton-2.3.7.tar.gz/lib/kolab_sync_data_calendar.php
Changed
@@ -127,8 +127,7 @@ 'ACCEPTED' => self::ATTENDEE_STATUS_ACCEPTED, 'DECLINED' => self::ATTENDEE_STATUS_DECLINED, 'DELEGATED' => self::ATTENDEE_STATUS_UNKNOWN, - 'NEEDS-ACTION' => self::ATTENDEE_STATUS_UNKNOWN, - //self::ATTENDEE_STATUS_NOTRESPONDED, + 'NEEDS-ACTION' => self::ATTENDEE_STATUS_NOTRESPONDED, ); /** @@ -246,7 +245,8 @@ break; } - if (empty($value) || is_array($value)) { + // Ignore empty values (but not integer 0) + if ((empty($value) || is_array($value)) && $value !== 0) { continue; } @@ -288,12 +288,15 @@ foreach ($event['attendees'] as $idx => $attendee) { $att = array(); - if ($name = $attendee['name']) { - $att['name'] = $name; - } if ($email = $attendee['email']) { $att['email'] = $email; } + else { + // In Activesync email is required + continue; + } + + $att['name'] = $attendee['name'] ?: $email; if ($this->asversion >= 12) { $type = isset($attendee['role']) ? $this->attendeeTypeMap[$attendee['role']] : null; @@ -332,33 +335,36 @@ $foldername = isset($event['_mailbox']) ? $event['_mailbox'] : $this->getFolderName($folderid); $config = $this->getFolderConfig($foldername); $is_exception = $data instanceof Syncroton_Model_EventException; - - $event['allday'] = 0; + $dummy_tz = str_repeat('A', 230) . '=='; + $is_outlook = stripos($this->device->devicetype, 'outlook') !== false; // check data validity - $this->eventCheck($data); + $this->check_event($data); + + if (!empty($event['start']) && ($event['start'] instanceof DateTime)) { + $old_timezone = $event['start']->getTimezone(); + } // Timezone - if (!$timezone && isset($data->timezone)) { + if (!$timezone && isset($data->timezone) && $data->timezone != $dummy_tz) { $tzc = kolab_sync_timezone_converter::getInstance(); - $expected = kolab_format::$timezone->getName(); - - if (!empty($event['start']) && ($event['start'] instanceof DateTime)) { - $expected = $event['start']->getTimezone()->getName(); - } + $expected = $old_timezone ?: kolab_format::$timezone; - $timezone = $tzc->getTimezone($data->timezone, $expected); try { + $timezone = $tzc->getTimezone($data->timezone, $expected->getName()); $timezone = new DateTimeZone($timezone); } catch (Exception $e) { $timezone = null; } } + if (empty($timezone)) { - $timezone = new DateTimeZone('UTC'); + $timezone = $old_timezone ?: new DateTimeZone('UTC'); } + $event['allday'] = 0; + // Calendar namespace fields foreach ($this->mapping as $key => $name) { // skip UID field, unsupported in event exceptions @@ -450,15 +456,27 @@ $email = $data->organizerEmail; if ($name || $email) { $event['attendees'][] = array( - 'role' => 'ORGANIZER', - 'name' => $name, - 'email' => $email, + 'role' => 'ORGANIZER', + 'name' => $name, + 'email' => $email, ); } } // Attendees - if (isset($data->attendees)) { + // Outlook 2013 sends a dummy update just after MeetingResponse has been processed, + // this update resets attendee status set in the MeetingResponse request. + // We ignore attendees data in such updates, they should not happen according to + // https://msdn.microsoft.com/en-us/library/office/hh428685(v=exchg.140).aspx + // but they will contain some data as alarms and free/busy status so we don't + // ignore them completely + if ($is_outlook && !empty($entry) && $data->timezone == $dummy_tz + && $data->responseRequested && !empty($data->attendees) + ) { + $event['attendees'] = $entry['attendees']; + } + else if (isset($data->attendees)) { + $statusMap = array_flip($this->attendeeStatusMap); foreach ($data->attendees as $attendee) { $role = false; if (isset($attendee->attendeeType)) { @@ -468,13 +486,31 @@ $role = array_search(self::ATTENDEE_TYPE_REQUIRED, $this->attendeeTypeMap); } - // AttendeeStatus send only on repsonse (?) - - $event['attendees'][] = array( + $_attendee = array( 'role' => $role, - 'name' => $attendee->name, + 'name' => $attendee->name != $attendee->email ? $attendee->name : '', 'email' => $attendee->email, ); + + if (isset($attendee->attendeeStatus)) { + $_attendee['status'] = $attendee->attendeeStatus ? array_search($attendee->attendeeStatus, $this->attendeeStatusMap) : null; + if (!$_attendee['status']) { + $_attendee['status'] = 'NEEDS-ACTION'; + $_attendee['rsvp'] = true; + } + } + else if (!empty($event['attendees'])) { + // copy the old attendee status + foreach ($event['attendees'] as $old_attendee) { + if ($old_attendee['email'] == $_attendee['email'] && isset($old_attendee['status'])) { + $_attendee['status'] = $old_attendee['status']; + $_attendee['rsvp'] = $old_attendee['rsvp']; + break; + } + } + } + + $event['attendees'][] = $_attendee; } } @@ -483,6 +519,34 @@ $event['recurrence'] = $this->recurrence_to_kolab($data, $folderid, $timezone); } + // Bump SEQUENCE number on update (Outlook only). + // It's been confirmed that any change of the event that has attendees specified + // bumps SEQUENCE number of the event (we can see this in sent iTips). + // Unfortunately Outlook also sends an update when no SEQUENCE bump + // is needed, e.g. when updating attendee status. + // We try our best to bump the SEQUENCE only when expected + if ($is_outlook && !empty($entry) && !$is_exception && !empty($data->attendees) && $data->timezone != $dummy_tz) { + if ($last_update = $this->getKolabDataItem($event, 'x-custom.X-ACTIVESYNC-DTSTAMP')) { + $last_update = new DateTime($last_update); + } + + if ($data->dtStamp && $data->dtStamp != $last_update) { + $event['sequence'] += 1; + } + } + + // Because we use last event modification time above, we make sure + // the event modification time is not (re)set by the server, + // we use the original Outlook's timestamp. + if ($is_outlook && $data->dtStamp) { + $this->setKolabDataItem($event, 'x-custom.X-ACTIVESYNC-DTSTAMP', $data->dtStamp->format(DateTime::ATOM)); + } + + // This prevents kolab_format code to bump the sequence when not needed + if (!isset($event['sequence'])) { + $event['sequence'] = 0; + } + return $event; } @@ -495,11 +559,194 @@ */ public function setAttendeeStatus(Syncroton_Model_MeetingResponse $request) { - // @TODO: not implemented + $status_map = array( + 1 => 'ACCEPTED', + 2 => 'TENTATIVE', + 3 => 'DECLINED', + ); + + if ($status = $status_map[$request->userResponse]) { + // extract event data from the invitation + $event = $this->get_event_from_invitation($request); + + // find the event in calendar + $existing = $this->find_event_by_uid($event['uid']); +/* + switch ($status) { + case 'ACCEPTED': $event['free_busy'] = 'busy'; break; + case 'TENTATIVE': $event['free_busy'] = 'tentative'; break; + case 'DECLINED': $event['free_busy'] = 'free'; break; + } +*/ + // Update/Save the event + if (empty($existing)) { + $folder = $this->save_event($event, $status); + } + else { + $folder = $this->update_event($event, $existing, $status, $request->instanceId); + } + + if (!$folder) { + throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); + } + + // TODO: ActiveSync version >= 16, send the iTip response. + if (isset($request->sendResponse)) { + // SendResponse can contain Body to use as email body (can be empty) + // TODO: Activesync >= 16.1 proposedStartTime and proposedEndTime. + } + } + + // FIXME: We should not return an UID when status=DECLINED + // as it's expected by the specification. Server + // should delete an event in such a case, but we + // keep the event copy with appropriate attendee status instead. + return empty($status) ? null : $this->serverId($event['uid'], $folder); + } + + /** + * Get an event from the invitation email + */ + protected function get_event_from_invitation(Syncroton_Model_MeetingResponse $request) + { + // Limitations: + // 1. The meeting request may be in an iTip or the calendar event + // For now we support iTips only here + // 2. LongId might be used instead of RequestId, this is not supported + if ($request->requestId) { + $mail_class = new kolab_sync_data_email($this->device, $this->syncTimeStamp); + + if ($event = $mail_class->get_invitation_event($request->requestId)) { + return $event; + } + + throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::INVALID_REQUEST); + } + throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); } /** + * Find the Kolab event in any (of subscribed personal calendars) folder + */ + protected function find_event_by_uid($uid) + { + if (empty($uid)) { + return; + } + + // TODO: should we check every existing event folder even if not subscribed for sync? + + foreach ($this->listFolders() as $folder) { + $storage_folder = $this->getFolderObject($folder['imap_name']); + if ($storage_folder->get_namespace() == 'personal' + && ($result = $storage_folder->get_object($uid)) + ) { + return $result; + } + } + } + + /** + * Wrapper to update an event object + */ + protected function update_event($event, $old, $status, $instanceId = null) + { + // TODO: instanceId - DateTime - of the exception to be processed, if not set process all occurrences + if ($instanceId) { + throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::INVALID_REQUEST); + } + + $this->update_attendee_status($old, $status); + + if ($event['free_busy']) { + $old['free_busy'] = $event['free_busy']; + } + + // Updating an existing event is most-likely a response + // to an iTip request with bumped SEQUENCE + $old['sequence'] += 1; + + // TODO: Free/busy trigger? + + // Update the event + return $this->save_event($old); + } + + /** + * Save the Kolab event (create if not exist) + * If an event does not exist it will be created in the default folder + */ + protected function save_event(&$event, $status = null) + { + // Find default folder to which we'll save the event + if (empty($event['_mailbox'])) { + $folders = $this->listFolders(); + $storage = rcube::get_instance()->get_storage(); + + // find the default + foreach ($folders as $folder) { + if ($folder['type'] == 8 && $storage->folder_namespace($folder['imap_name']) == 'personal') { + $event['_mailbox'] = $folder['imap_name']; + break; + } + } + + // if there's no folder marked as default, use any + if (!isset($event['_mailbox']) && !empty($folders)) { + foreach ($folders as $folder) { + if ($storage->folder_namespace($folder['imap_name']) == 'personal') { + $event['_mailbox'] = $folder['imap_name']; + break; + } + } + } + + // TODO: what if the user has no subscribed event folders for this device + // should we use any existing event folder even if not subscribed for sync? + } + + if ($status) { + $this->update_attendee_status($event, $status); + } + + if (isset($event['_mailbox'])) { + $folder = $this->getFolderObject($event['_mailbox']); + + if ($folder && $folder->valid && $folder->save($event)) { + return $folder; + } + } + + return false; + } + + /** + * Update the attendee status of the user + */ + protected function update_attendee_status(&$event, $status) + { + $organizer = null; + $emails = $this->user_emails(); + + foreach ((array) $event['attendees'] as $i => $attendee) { + if ($attendee['role'] == 'ORGANIZER') { + $organizer = $attendee; + } + else if ($attendee['email'] && in_array_nocase($attendee['email'], $emails)) { + $event['attendees'][$i]['status'] = $status; + $event['attendees'][$i]['rsvp'] = false; + $event_attendee = $attendee; + } + } + + if (!$event_attendee) { + $this->logger->warn('MeetingResponse on an event where the user is not an attendee. UID: ' . $event['uid']); + throw new Syncroton_Exception_Status_MeetingResponse(Syncroton_Exception_Status_MeetingResponse::MEETING_ERROR); + } + } + + /** * Returns filter query array according to specified ActiveSync FilterType * * @param int $filter_type Filter type @@ -549,22 +796,18 @@ if (!empty($event['attendees'])) { // Find out if the user is an organizer // TODO: Delegation/aliases support - $user_emails = kolab_sync::get_instance()->user->list_emails(); - $user_emails = array_map(function($v) { return $v['email']; }, $user_emails); - $is_organizer = true; + $user_emails = $this->user_emails(); + $is_organizer = false; - foreach ($event['attendees'] as $attendee) { - if (in_array_nocase($attendee['email'], $user_emails)) { - $is_organizer = false; - break; - } + if ($event['organizer'] && $event['organizer']['email']) { + $is_organizer = in_array_nocase($event['organizer']['email'], $user_emails); } if ($event['status'] == 'CANCELLED') { - $status = !empty($is_organizer) ? 5 : 7; + $status = $is_organizer ? 5 : 7; } else { - $status = !empty($is_organizer) ? 1 : 3; + $status = $is_organizer ? 1 : 3; } } @@ -653,7 +896,7 @@ * * @throws Syncroton_Exception_Status_Sync */ - protected function eventCheck(Syncroton_Model_IEntry &$entry) + protected function check_event(Syncroton_Model_IEntry &$entry) { // https://msdn.microsoft.com/en-us/library/jj194434(v=exchg.80).aspx
View file
kolab-syncroton-2.3.6.tar.gz/lib/kolab_sync_data_email.php -> kolab-syncroton-2.3.7.tar.gz/lib/kolab_sync_data_email.php
Changed
@@ -570,7 +570,7 @@ { // Throw exception here for better handling of unsupported // entry creation, it can be object of class Email or SMS here - throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::SYNC_SERVER_ERROR); + throw new Syncroton_Exception_Status_Sync(Syncroton_Exception_Status_Sync::INVALID_ITEM); } /** @@ -1620,4 +1620,25 @@ return $out; } + + /** + * Returns calendar event data from the iTip invitation attached to a mail message + */ + public function get_invitation_event($messageId) + { + // Get the mail message object + if ($message = $this->getObject($messageId)) { + // Parse the message and find iTip attachments + $libcal = libcalendaring::get_instance(); + $libcal->mail_message_load(array('object' => $message)); + $ical_objects = $libcal->get_mail_ical_objects(); + + // We support only one event in the iTip + foreach ($ical_objects as $mime_id => $event) { + if ($event['_type'] == 'event') { + return $event; + } + } + } + } }
View file
kolab-syncroton-2.3.6.tar.gz/lib/kolab_sync_data_gal.php -> kolab-syncroton-2.3.7.tar.gz/lib/kolab_sync_data_gal.php
Changed
@@ -42,7 +42,7 @@ * * @var array */ - protected static $address_books = array(); + public static $address_books = array(); /** * Mapping from ActiveSync Contacts namespace fields
View file
kolab-syncroton.dsc
Changed
@@ -2,7 +2,7 @@ Source: kolab-syncroton Binary: kolab-syncroton Architecture: all -Version: 2.3.6-0~kolab2 +Version: 2.3.7-0~kolab1 Maintainer: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Uploaders: Paul Klos <kolab@klos2day.nl> Homepage: http://www.kolab.org/ @@ -12,5 +12,5 @@ Package-List: kolab-syncroton deb utils extra Files: - 00000000000000000000000000000000 0 kolab-syncroton-2.3.6.tar.gz + 00000000000000000000000000000000 0 kolab-syncroton-2.3.7.tar.gz 00000000000000000000000000000000 0 debian.tar.gz
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.