diff options
author | Aleksander Machniak <machniak@kolabsys.com> | 2015-02-04 11:23:13 (GMT) |
---|---|---|
committer | Aleksander Machniak <machniak@kolabsys.com> | 2015-02-04 11:23:13 (GMT) |
commit | 11e93ffa1f329a2cbcb9544e4962c6b58051cdba (patch) | |
tree | 866497817155b211cc3c3931a40321fb4c752986 | |
parent | 5990b21898f7a5b5a19edbfd79c4cf83eb510d11 (diff) | |
download | kolab-chwala-11e93ffa1f329a2cbcb9544e4962c6b58051cdba.tar.gz |
Update kolab plugins
36 files changed, 936 insertions, 383 deletions
diff --git a/lib/drivers/kolab/plugins/kolab_auth/composer.json b/lib/drivers/kolab/plugins/kolab_auth/composer.json new file mode 100644 index 0000000..3e7012f --- /dev/null +++ b/lib/drivers/kolab/plugins/kolab_auth/composer.json @@ -0,0 +1,30 @@ +{ + "name": "kolab/kolab_auth", + "type": "roundcube-plugin", + "description": "Kolab authentication", + "homepage": "http://git.kolab.org/roundcubemail-plugins-kolab/", + "license": "AGPLv3", + "version": "3.2.2", + "authors": [ + { + "name": "Thomas Bruederli", + "email": "bruederli@kolabsys.com", + "role": "Lead" + }, + { + "name": "Aleksander Machniak", + "email": "machniak@kolabsys.com", + "role": "Lead" + } + ], + "repositories": [ + { + "type": "composer", + "url": "http://plugins.roundcube.net" + } + ], + "require": { + "php": ">=5.3.0", + "roundcube/plugin-installer": ">=0.1.3" + } +} diff --git a/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist b/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist index e7b9d15..8c01d56 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist +++ b/lib/drivers/kolab/plugins/kolab_auth/config.inc.php.dist @@ -13,44 +13,55 @@ // With this %dc variable in base_dn and groups/base_dn will be // replaced with DN string of resolved domain //--------------------------------------------------------------------- -$rcmail_config['kolab_auth_addressbook'] = ''; +$config['kolab_auth_addressbook'] = ''; // This will overwrite defined filter -$rcmail_config['kolab_auth_filter'] = '(&(objectClass=kolabInetOrgPerson)(|(uid=%u)(mail=%fu)(alias=%fu)))'; +$config['kolab_auth_filter'] = '(&(objectClass=kolabInetOrgPerson)(|(uid=%u)(mail=%fu)(alias=%fu)))'; -// Use this fields (from fieldmap configuration) to get authentication ID -$rcmail_config['kolab_auth_login'] = 'email'; +// Use this field (from fieldmap configuration) to get authentication ID. Don't use an array here! +$config['kolab_auth_login'] = 'email'; -// Use this fields (from fieldmap configuration) for default identity. +// Use these fields (from fieldmap configuration) for default identity. // If the value array contains more than one field, first non-empty will be used // Note: These aren't LDAP attributes, but field names in config // Note: If there's more than one email address, as many identities will be created -$rcmail_config['kolab_auth_name'] = array('name', 'cn'); -$rcmail_config['kolab_auth_email'] = array('email'); -$rcmail_config['kolab_auth_organization'] = array('organization'); +$config['kolab_auth_name'] = array('name', 'cn'); +$config['kolab_auth_email'] = array('email'); +$config['kolab_auth_organization'] = array('organization'); + +// Role field (from fieldmap configuration) +$config['kolab_auth_role'] = 'role'; + +// Template for user names displayed in the UI. +// You can use all attributes from the 'fieldmap' property of the 'kolab_auth_addressbook' configuration +$config['kolab_auth_user_displayname'] = '{name} ({ou})'; // Login and password of the admin user. Enables "Login As" feature. -$rcmail_config['kolab_auth_admin_login'] = ''; -$rcmail_config['kolab_auth_admin_password'] = ''; +$config['kolab_auth_admin_login'] = ''; +$config['kolab_auth_admin_password'] = ''; // Enable audit logging for abuse of administrative privileges. -$rcmail_config['kolab_auth_auditlog'] = true; - -// Role field (from fieldmap configuration) -$rcmail_config['kolab_auth_role'] = 'role'; -// The required value for the role attribute to contain should the user be allowed -// to login as another user. -$rcmail_config['kolab_auth_role_value'] = ''; +$config['kolab_auth_auditlog'] = false; -// Administrative group name to which user must be assigned to -// which adds privilege to login as another user. -$rcmail_config['kolab_auth_group'] = ''; +// As set of rules to define the required rights on the target entry +// which allow an admin user to login as another user (the target). +// The effective rights value refers to either entry level attribute level rights: +// * entry:[read|add|delete] +// * attrib:<attribute-name>:[read|write|delete] +$config['kolab_auth_admin_rights'] = array( + // Roundcube task => required effective right + 'settings' => 'entry:read', + 'mail' => 'entry:delete', + 'addressbook' => 'entry:delete', + // or use a wildcard entry like this: + '*' => 'entry:read', +); // Enable plugins on a role-by-role basis. In this example, the 'acl' plugin // is enabled for people with a 'cn=professional-user,dc=mykolab,dc=ch' role. // // Note that this does NOT mean the 'acl' plugin is disabled for other people. -$rcmail_config['kolab_auth_role_plugins'] = Array( +$config['kolab_auth_role_plugins'] = Array( 'cn=professional-user,dc=mykolab,dc=ch' => Array( 'acl', ), @@ -62,7 +73,7 @@ $rcmail_config['kolab_auth_role_plugins'] = Array( // do not allow the setting to be controlled through the preferences, enable the // html editor for professional users and allow them to override the setting in // the preferences. -$rcmail_config['kolab_auth_role_settings'] = Array( +$config['kolab_auth_role_settings'] = Array( 'cn=professional-user,dc=mykolab,dc=ch' => Array( 'htmleditor' => Array( 'mode' => 'override', @@ -75,6 +86,6 @@ $rcmail_config['kolab_auth_role_settings'] = Array( // List of LDAP addressbooks (keys of ldap_public configuration array) // for which base_dn variables (%dc, etc.) will be replaced according to authenticated user DN // Note: special name '*' for all LDAP addressbooks -$rcmail_config['kolab_auth_ldap_addressbooks'] = array('*'); +$config['kolab_auth_ldap_addressbooks'] = array('*'); ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php index 7ff5761..033d5b1 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php +++ b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth.php @@ -31,6 +31,7 @@ class kolab_auth extends rcube_plugin { static $ldap; + private $username; private $data = array(); public function init() @@ -56,11 +57,12 @@ class kolab_auth extends rcube_plugin // Hook to modify some configuration, e.g. ldap $this->add_hook('config_get', array($this, 'config_get')); - // Enable debug logs per-user, this enables logging only after - // user has logged in - if (!empty($_SESSION['username']) && $rcmail->config->get('kolab_auth_auditlog')) { - $this->add_hook('write_log', array($this, 'write_log')); + // Hook to modify logging directory + $this->add_hook('write_log', array($this, 'write_log')); + $this->username = $_SESSION['username']; + // Enable debug logs (per-user), when logged as another user + if (!empty($_SESSION['kolab_auth_admin']) && $rcmail->config->get('kolab_auth_auditlog')) { $rcmail->config->set('debug_level', 1); $rcmail->config->set('devel_mode', true); $rcmail->config->set('smtp_log', true); @@ -81,8 +83,30 @@ class kolab_auth extends rcube_plugin } } + /** + * Startup hook handler + */ public function startup($args) { + // Check access rights when logged in as another user + if (!empty($_SESSION['kolab_auth_admin']) && $args['task'] != 'login' && $args['task'] != 'logout') { + // access to specified task is forbidden, + // redirect to the first task on the list + if (!empty($_SESSION['kolab_auth_allowed_tasks'])) { + $tasks = (array)$_SESSION['kolab_auth_allowed_tasks']; + if (!in_array($args['task'], $tasks) && !in_array('*', $tasks)) { + header('Location: ?_task=' . array_shift($tasks)); + die; + } + + // add script that will remove disabled taskbar buttons + if (!in_array('*', $tasks)) { + $this->add_hook('render_page', array($this, 'render_page')); + } + } + } + + // load per-user settings $this->load_user_role_plugins_and_settings(); return $args; @@ -101,21 +125,36 @@ class kolab_auth extends rcube_plugin foreach ($args['result'] as $name => $config) { if (in_array($name, $kolab_books) || in_array('*', $kolab_books)) { - $args['result'][$name]['base_dn'] = self::parse_ldap_vars($config['base_dn']); - $args['result'][$name]['search_base_dn'] = self::parse_ldap_vars($config['search_base_dn']); - $args['result'][$name]['bind_dn'] = str_replace('%dn', $_SESSION['kolab_dn'], $config['bind_dn']); - - if (!empty($config['groups'])) { - $args['result'][$name]['groups']['base_dn'] = self::parse_ldap_vars($config['groups']['base_dn']); - } + $args['result'][$name] = $this->patch_ldap_config($config); } } } + else if ($args['name'] == 'kolab_users_directory' && !empty($args['result'])) { + $args['result'] = $this->patch_ldap_config($args['result']); + } return $args; } /** + * Helper method to patch the given LDAP directory config with user-specific values + */ + protected function patch_ldap_config($config) + { + if (is_array($config)) { + $config['base_dn'] = self::parse_ldap_vars($config['base_dn']); + $config['search_base_dn'] = self::parse_ldap_vars($config['search_base_dn']); + $config['bind_dn'] = str_replace('%dn', $_SESSION['kolab_dn'], $config['bind_dn']); + + if (!empty($config['groups'])) { + $config['groups']['base_dn'] = self::parse_ldap_vars($config['groups']['base_dn']); + } + } + + return $config; + } + + /** * Modifies list of plugins and settings according to * specified LDAP roles */ @@ -155,24 +194,28 @@ class kolab_auth extends rcube_plugin if (!empty($role_plugins)) { foreach ($role_plugins as $role_dn => $plugins) { - $role_plugins[self::parse_ldap_vars($role_dn)] = $plugins; + $role_dn = self::parse_ldap_vars($role_dn); + if (!empty($role_plugins[$role_dn])) { + $role_plugins[$role_dn] = array_unique(array_merge((array)$role_plugins[$role_dn], $plugins)); + } else { + $role_plugins[$role_dn] = $plugins; + } } } if (!empty($role_settings)) { foreach ($role_settings as $role_dn => $settings) { - $role_settings[self::parse_ldap_vars($role_dn)] = $settings; + $role_dn = self::parse_ldap_vars($role_dn); + if (!empty($role_settings[$role_dn])) { + $role_settings[$role_dn] = array_merge((array)$role_settings[$role_dn], $settings); + } else { + $role_settings[$role_dn] = $settings; + } } } foreach ($_SESSION['user_roledns'] as $role_dn) { - if (isset($role_plugins[$role_dn]) && is_array($role_plugins[$role_dn])) { - foreach ($role_plugins[$role_dn] as $plugin) { - $this->require_plugin($plugin); - } - } - - if (isset($role_settings[$role_dn]) && is_array($role_settings[$role_dn])) { + if (!empty($role_settings[$role_dn]) && is_array($role_settings[$role_dn])) { foreach ($role_settings[$role_dn] as $setting_name => $setting) { if (!isset($setting['mode'])) { $setting['mode'] = 'override'; @@ -194,7 +237,7 @@ class kolab_auth extends rcube_plugin $dont_override = (array) $rcmail->config->get('dont_override'); - if (!isset($setting['allow_override']) || !$setting['allow_override']) { + if (empty($setting['allow_override'])) { $rcmail->config->set('dont_override', array_merge($dont_override, array($setting_name))); } else { @@ -208,6 +251,19 @@ class kolab_auth extends rcube_plugin $rcmail->config->set('dont_override', $_dont_override); } } + + if ($setting_name == 'skin') { + if ($rcmail->output->type == 'html') { + $rcmail->output->set_skin($setting['value']); + $rcmail->output->set_env('skin', $setting['value']); + } + } + } + } + + if (!empty($role_plugins[$role_dn])) { + foreach ((array)$role_plugins[$role_dn] as $plugin) { + $this->api->load_plugin($plugin); } } } @@ -225,37 +281,29 @@ class kolab_auth extends rcube_plugin return $args; } - $line = sprintf("[%s]: %s\n", $args['date'], $args['line']); - // log_driver == 'file' is assumed here $log_dir = $rcmail->config->get('log_dir', RCUBE_INSTALL_PATH . 'logs'); - $log_path = $log_dir.'/'.strtolower($_SESSION['kolab_auth_admin']).'/'.strtolower($_SESSION['username']); - // Append original username + target username - if (!is_dir($log_path)) { + // Append original username + target username for audit-logging + if ($rcmail->config->get('kolab_auth_auditlog') && !empty($_SESSION['kolab_auth_admin'])) { + $args['dir'] = $log_dir . '/' . strtolower($_SESSION['kolab_auth_admin']) . '/' . strtolower($this->username); + // Attempt to create the directory - if (@mkdir($log_path, 0750, true)) { - $log_dir = $log_path; + if (!is_dir($args['dir'])) { + @mkdir($args['dir'], 0750, true); } } - else { - $log_dir = $log_path; - } - - // try to open specific log file for writing - $logfile = $log_dir.'/'.$args['name']; - - if ($fp = fopen($logfile, 'a')) { - fwrite($fp, $line); - fflush($fp); - fclose($fp); - } - else { - trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING); + // Define the user log directory if a username is provided + else if ($rcmail->config->get('per_user_logging') && !empty($this->username)) { + $user_log_dir = $log_dir . '/' . strtolower($this->username); + if (is_writable($user_log_dir)) { + $args['dir'] = $user_log_dir; + } + else if ($args['name'] != 'errors') { + $args['abort'] = true; // don't log if unauthenticed + } } - $args['abort'] = true; - return $args; } @@ -337,9 +385,13 @@ class kolab_auth extends rcube_plugin return $args; } + // temporarily set the current username to the one submitted + $this->username = $user; + $ldap = self::ldap(); if (!$ldap || !$ldap->ready) { $args['abort'] = true; + $args['kolab_ldap_error'] = true; $message = sprintf( 'Login failure for user %s from %s in session %s (error %s)', $user, @@ -414,24 +466,69 @@ class kolab_auth extends rcube_plugin return $args; } - // check if the original user has/belongs to administrative role/group $isadmin = false; - $group = $rcmail->config->get('kolab_auth_group'); - $role_dn = $rcmail->config->get('kolab_auth_role_value'); - - // check role attribute - if (!empty($role_attr) && !empty($role_dn) && !empty($record[$role_attr])) { - $role_dn = $ldap->parse_vars($role_dn, $user, $host); - if (in_array($role_dn, (array)$record[$role_attr])) { - $isadmin = true; + $admin_rights = $rcmail->config->get('kolab_auth_admin_rights', array()); + + // @deprecated: fall-back to the old check if the original user has/belongs to administrative role/group + if (empty($admin_rights)) { + $group = $rcmail->config->get('kolab_auth_group'); + $role_dn = $rcmail->config->get('kolab_auth_role_value'); + + // check role attribute + if (!empty($role_attr) && !empty($role_dn) && !empty($record[$role_attr])) { + $role_dn = $ldap->parse_vars($role_dn, $user, $host); + if (in_array($role_dn, (array)$record[$role_attr])) { + $isadmin = true; + } + } + + // check group + if (!$isadmin && !empty($group)) { + $groups = $ldap->get_user_groups($record['dn'], $user, $host); + if (in_array($group, $groups)) { + $isadmin = true; + } + } + + if ($isadmin) { + // user has admin privileges privilage, get "login as" user credentials + $target_entry = $ldap->get_user_record($loginas, $host); + $allowed_tasks = $rcmail->config->get('kolab_auth_allowed_tasks'); } } + else { + // get "login as" user credentials + $target_entry = $ldap->get_user_record($loginas, $host); + + if (!empty($target_entry)) { + // get effective rights to determine login-as permissions + $effective_rights = (array)$ldap->effective_rights($target_entry['dn']); + + if (!empty($effective_rights)) { + $effective_rights['attrib'] = $effective_rights['attributeLevelRights']; + $effective_rights['entry'] = $effective_rights['entryLevelRights']; + + // compare the rights with the permissions mapping + $allowed_tasks = array(); + foreach ($admin_rights as $task => $perms) { + $perms_ = explode(':', $perms); + $type = array_shift($perms_); + $req = array_pop($perms_); + $attrib = array_pop($perms_); + + if (array_key_exists($type, $effective_rights)) { + if ($type == 'entry' && in_array($req, $effective_rights[$type])) { + $allowed_tasks[] = $task; + } + else if ($type == 'attrib' && array_key_exists($attrib, $effective_rights[$type]) && + in_array($req, $effective_rights[$type][$attrib])) { + $allowed_tasks[] = $task; + } + } + } - // check group - if (!$isadmin && !empty($group)) { - $groups = $ldap->get_user_groups($record['dn'], $user, $host); - if (in_array($group, $groups)) { - $isadmin = true; + $isadmin = !empty($allowed_tasks); + } } } @@ -443,22 +540,22 @@ class kolab_auth extends rcube_plugin $origname = $user; } - $record = null; - - // user has the privilage, get "login as" user credentials - if ($isadmin) { - $record = $ldap->get_user_record($loginas, $host); - } + if (!$isadmin || empty($target_entry)) { + $this->add_texts('localization/'); - if (empty($record)) { $args['abort'] = true; + $args['error'] = $this->gettext(array( + 'name' => 'loginasnotallowed', + 'vars' => array('user' => Q($loginas)), + )); + $message = sprintf( 'Login failure for user %s (as user %s) from %s in session %s (error %s)', $user, $loginas, rcube_utils::remote_ip(), session_id(), - "No user record found for '" . $loginas . "'" + "No privileges to login as '" . $loginas . "'" ); rcube::write_log('userlogins', $message); @@ -466,12 +563,16 @@ class kolab_auth extends rcube_plugin return $args; } - $args['user'] = $loginas; + // replace $record with target entry + $record = $target_entry; + + $args['user'] = $this->username = $loginas; // Mark session to use SASL proxy for IMAP authentication $_SESSION['kolab_auth_admin'] = strtolower($origname); $_SESSION['kolab_auth_login'] = $rcmail->encrypt($admin_login); $_SESSION['kolab_auth_password'] = $rcmail->encrypt($admin_pass); + $_SESSION['kolab_auth_allowed_tasks'] = $allowed_tasks; } // Store UID and DN of logged user in session for use by other plugins @@ -489,7 +590,7 @@ class kolab_auth extends rcube_plugin $this->data['user_login'] = is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr]; } if ($this->data['user_login']) { - $args['user'] = $this->data['user_login']; + $args['user'] = $this->username = $this->data['user_login']; } // User name for identity (first log in) @@ -522,6 +623,9 @@ class kolab_auth extends rcube_plugin $args['user'], $origname, rcube_utils::remote_ip())); } + // load per-user settings/plugins + $this->load_user_role_plugins_and_settings(); + return $args; } @@ -566,8 +670,8 @@ class kolab_auth extends rcube_plugin $admin_login = $rcmail->decrypt($_SESSION['kolab_auth_login']); $admin_pass = $rcmail->decrypt($_SESSION['kolab_auth_password']); - $args['options']['smtp_auth_cid'] = $admin_login; - $args['options']['smtp_auth_pw'] = $admin_pass; + $args['smtp_auth_cid'] = $admin_login; + $args['smtp_auth_pw'] = $admin_pass; } return $args; @@ -616,6 +720,28 @@ class kolab_auth extends rcube_plugin } /** + * Action executed before the page is rendered to add an onload script + * that will remove all taskbar buttons for disabled tasks + */ + public function render_page($args) + { + $rcmail = rcube::get_instance(); + $tasks = (array)$_SESSION['kolab_auth_allowed_tasks']; + $tasks[] = 'logout'; + + // disable buttons in taskbar + $script = " + \$('a').filter(function() { + var ev = \$(this).attr('onclick'); + return ev && ev.match(/'switch-task','([a-z]+)'/) + && \$.inArray(RegExp.\$1, " . json_encode($tasks) . ") < 0; + }).remove(); + "; + + $rcmail->output->add_script($script, 'docready'); + } + + /** * Initializes LDAP object and connects to LDAP server */ public static function ldap() diff --git a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php index b9b3e4a..431133b 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php +++ b/lib/drivers/kolab/plugins/kolab_auth/kolab_auth_ldap.php @@ -28,17 +28,22 @@ class kolab_auth_ldap extends rcube_ldap_generic { private $icache = array(); + private $conf = array(); + private $fieldmap = array(); function __construct($p) { $rcmail = rcube::get_instance(); - $this->debug = (bool) $rcmail->config->get('ldap_debug'); + $this->conf = $p; + $this->conf['kolab_auth_user_displayname'] = $rcmail->config->get('kolab_auth_user_displayname', '{name}'); + $this->fieldmap = $p['fieldmap']; $this->fieldmap['uid'] = 'uid'; $p['attributes'] = array_values($this->fieldmap); + $p['debug'] = (bool) $rcmail->config->get('ldap_debug'); // Connect to the server (with bind) parent::__construct($p); @@ -52,8 +57,6 @@ class kolab_auth_ldap extends rcube_ldap_generic */ private function _connect() { - $rcube = rcube::get_instance(); - // try to connect + bind for every host configured // with OpenLDAP 2.x ldap_connect() always succeeds but ldap_bind will fail if host isn't reachable // see http://www.php.net/manual/en/function.ldap-connect.php @@ -152,11 +155,10 @@ class kolab_auth_ldap extends rcube_ldap_generic $groups = array(); foreach ($result as $entry) { + $dn = $entry['dn']; $entry = rcube_ldap_generic::normalize_entry($entry); - if (!$entry['dn']) { - $entry['dn'] = $result->get_dn(); - } - $groups[$entry['dn']] = $entry[$name_attr]; + + $groups[$dn] = $entry[$name_attr]; } return $groups; @@ -197,7 +199,9 @@ class kolab_auth_ldap extends rcube_ldap_generic foreach ($this->fieldmap as $field => $attr) { if (array_key_exists($field, $entry)) { $entry[$attr] = $entry[$field]; - unset($entry[$field]); + if ($attr != $field) { + unset($entry[$field]); + } } } @@ -207,20 +211,24 @@ class kolab_auth_ldap extends rcube_ldap_generic /** * Search records (simplified version of rcube_ldap::search) * - * @param mixed $fields The field name of array of field names to search in + * @param mixed $fields The field name or array of field names to search in * @param mixed $value Search value (or array of values when $fields is array) * @param int $mode Matching mode: * 0 - partial (*abc*), * 1 - strict (=), * 2 - prefix (abc*) - * @param boolean $select True if results are requested, False if count only * @param array $required List of fields that cannot be empty * @param int $limit Number of records + * @param int $count Returns the number of records found * * @return array List or false on error */ - function search($fields, $value, $mode=1, $required = array(), $limit = 0) + function dosearch($fields, $value, $mode=1, $required = array(), $limit = 0, &$count = 0) { + if (empty($fields)) { + return array(); + } + $mode = intval($mode); // use AND operator for advanced searches @@ -236,8 +244,13 @@ class kolab_auth_ldap extends rcube_ldap_generic } foreach ((array)$fields as $idx => $field) { - $val = is_array($value) ? $value[$idx] : $value; - if ($attrs = (array) $this->fieldmap[$field]) { + $val = is_array($value) ? $value[$idx] : $value; + $attrs = (array) $this->fieldmap[$field]; + + if (empty($attrs)) { + $filter .= "($field=$wp" . rcube_ldap_generic::quote_string($val) . "$ws)"; + } + else { if (count($attrs) > 1) $filter .= '(|'; foreach ($attrs as $f) @@ -254,7 +267,13 @@ class kolab_auth_ldap extends rcube_ldap_generic foreach ((array)$required as $field) { if (in_array($field, (array)$fields)) // required field is already in search filter continue; - if ($attrs = (array) $this->fieldmap[$field]) { + + $attrs = (array) $this->fieldmap[$field]; + + if (empty($attrs)) { + $req_filter .= "($field=*)"; + } + else { if (count($attrs) > 1) $req_filter .= '(|'; foreach ($attrs as $f) @@ -281,13 +300,15 @@ class kolab_auth_ldap extends rcube_ldap_generic $attrs = array_values($this->fieldmap); $list = array(); - if ($result = parent::search($base_dn, $filter, $scope, $attrs)) { + if ($result = $this->search($base_dn, $filter, $scope, $attrs)) { + $count = $result->count(); $i = 0; foreach ($result as $entry) { if ($limit && $limit <= $i) { break; } - $dn = $result->get_dn(); + + $dn = $entry['dn']; $entry = rcube_ldap_generic::normalize_entry($entry); $list[$dn] = $this->field_mapping($dn, $entry); $i++; @@ -314,11 +335,26 @@ class kolab_auth_ldap extends rcube_ldap_generic // fields mapping foreach ($this->fieldmap as $field => $attr) { - if (isset($entry[$attr])) { + // $entry might be indexed by lower-case attribute names + $attr_lc = strtolower($attr); + if (isset($entry[$attr_lc])) { + $entry[$field] = $entry[$attr_lc]; + } + else if (isset($entry[$attr])) { $entry[$field] = $entry[$attr]; } } + // compose display name according to config + if (empty($this->fieldmap['displayname'])) { + $entry['displayname'] = rcube_addressbook::compose_search_name( + $entry, + $entry['email'], + $entry['name'], + $this->conf['kolab_auth_user_displayname'] + ); + } + return $entry; } @@ -473,6 +509,19 @@ class kolab_auth_ldap extends rcube_ldap_generic } /** + * Register additional fields + */ + public function extend_fieldmap($map) + { + foreach ((array)$map as $name => $attr) { + if (!in_array($attr, $this->attributes)) { + $this->attributes[] = $attr; + $this->fieldmap[$name] = $attr; + } + } + } + + /** * HTML-safe DN string encoding * * @param string $str DN string diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc index 1f7a573..01e1ac2 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/bg_BG.inc @@ -1,3 +1,10 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Влизане като'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc index 5e85a01..0332070 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/de_CH.inc @@ -1,3 +1,10 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Anmelden als'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc index 5e85a01..3918e6e 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/de_DE.inc @@ -1,3 +1,11 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Anmelden als'; +$labels['loginasnotallowed'] = 'Keine Privilegien zum Anmelden als $user'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc index e1adb3f..4882bdc 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/en_US.inc @@ -1,5 +1,14 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ + $labels['loginas'] = 'Login As'; +$labels['loginasnotallowed'] = 'No privileges to login as $user'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc index acb6c35..ed203e6 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/es_ES.inc @@ -1,2 +1,9 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc index acb6c35..ed203e6 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/et_EE.inc @@ -1,2 +1,9 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc index 6f72695..6538f5b 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/fr_FR.inc @@ -1,3 +1,11 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Se connecter en tant que'; +$labels['loginasnotallowed'] = 'Pas de privilège de se connecter comme $utilisateur'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc index ed0358a..e360737 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/ja_JP.inc @@ -1,3 +1,10 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'ログイン'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc index a98283f..ea3a1c0 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/nl_NL.inc @@ -1,3 +1,10 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Log in als'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc index 124c373..ca67859 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/pl_PL.inc @@ -1,3 +1,10 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Zaloguj jako'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc index 26e9541..e594c2e 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/pt_BR.inc @@ -1,3 +1,10 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Logar como'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc b/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc index 9e28c12..ac9e5a7 100644 --- a/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc +++ b/lib/drivers/kolab/plugins/kolab_auth/localization/ru_RU.inc @@ -1,3 +1,11 @@ <?php +/** + * Localizations for the Kolab Auth plugin + * + * Copyright (C) 2014, Kolab Systems AG + * + * For translation see https://www.transifex.com/projects/p/kolab/resource/kolab_auth/ + */ $labels['loginas'] = 'Войти как'; +$labels['loginasnotallowed'] = 'Нет привилегий войти как $user'; ?> diff --git a/lib/drivers/kolab/plugins/kolab_auth/package.xml b/lib/drivers/kolab/plugins/kolab_auth/package.xml deleted file mode 100644 index 5a2093b..0000000 --- a/lib/drivers/kolab/plugins/kolab_auth/package.xml +++ /dev/null @@ -1,63 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 - http://pear.php.net/dtd/tasks-1.0.xsd - http://pear.php.net/dtd/package-2.0 - http://pear.php.net/dtd/package-2.0.xsd"> - <name>kolab_auth</name> - <uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri> - <summary>Kolab Authentication</summary> - <description> - Authenticates on LDAP server, finds canonized authentication ID for IMAP - and for new users creates identity based on LDAP information. - Supports impersonate feature (login as another user). To use this feature - imap_auth_type/smtp_auth_type must be set to DIGEST-MD5 or PLAIN. - </description> - <lead> - <name>Aleksander Machniak</name> - <user>machniak</user> - <email>machniak@kolabsys.com</email> - <active>yes</active> - </lead> - <date>2013-10-04</date> - <version> - <release>1.0</release> - <api>1.0</api> - </version> - <stability> - <release>stable</release> - <api>stable</api> - </stability> - <license uri="http://www.gnu.org/licenses/agpl.html">GNU AGPLv3</license> - <notes>-</notes> - <contents> - <dir baseinstalldir="/" name="/"> - <file name="kolab_auth.php" role="php"> - <tasks:replace from="@name@" to="name" type="package-info"/> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="kolab_auth_ldap.php" role="php"> - <tasks:replace from="@name@" to="name" type="package-info"/> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="config.inc.php.dist" role="data"></file> - <file name="LICENSE" role="data"></file> - - <file name="localization/de_CH.inc" role="data"></file> - <file name="localization/de_DE.inc" role="data"></file> - <file name="localization/en_US.inc" role="data"></file> - <file name="localization/pl_PL.inc" role="data"></file> - </dir> - <!-- / --> - </contents> - <dependencies> - <required> - <php> - <min>5.2.1</min> - </php> - <pearinstaller> - <min>1.7.0</min> - </pearinstaller> - </required> - </dependencies> - <phprelease/> -</package> diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql index 2aa046d..98e7e78 100644 --- a/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql +++ b/lib/drivers/kolab/plugins/libkolab/SQL/mysql.initial.sql @@ -31,7 +31,7 @@ CREATE TABLE `kolab_cache_contact` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, `name` VARCHAR(255) NOT NULL, @@ -55,7 +55,7 @@ CREATE TABLE `kolab_cache_event` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, `dtstart` DATETIME, `dtend` DATETIME, @@ -75,7 +75,7 @@ CREATE TABLE `kolab_cache_task` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, `dtstart` DATETIME, `dtend` DATETIME, @@ -95,7 +95,7 @@ CREATE TABLE `kolab_cache_journal` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, `dtstart` DATETIME, `dtend` DATETIME, @@ -115,7 +115,7 @@ CREATE TABLE `kolab_cache_note` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, CONSTRAINT `fk_kolab_cache_note_folder` FOREIGN KEY (`folder_id`) REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, @@ -133,7 +133,7 @@ CREATE TABLE `kolab_cache_file` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, `filename` varchar(255) DEFAULT NULL, CONSTRAINT `fk_kolab_cache_file_folder` FOREIGN KEY (`folder_id`) @@ -153,7 +153,7 @@ CREATE TABLE `kolab_cache_configuration` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, CONSTRAINT `fk_kolab_cache_configuration_folder` FOREIGN KEY (`folder_id`) @@ -173,7 +173,7 @@ CREATE TABLE `kolab_cache_freebusy` ( `changed` DATETIME DEFAULT NULL, `data` LONGTEXT NOT NULL, `xml` LONGBLOB NOT NULL, - `tags` VARCHAR(255) NOT NULL, + `tags` TEXT NOT NULL, `words` TEXT NOT NULL, `dtstart` DATETIME, `dtend` DATETIME, @@ -184,4 +184,4 @@ CREATE TABLE `kolab_cache_freebusy` ( ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; -INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2014021000'); +INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2015011600'); diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014112700.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014112700.sql new file mode 100644 index 0000000..90c77b8 --- /dev/null +++ b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2014112700.sql @@ -0,0 +1,2 @@ +-- delete cache entries for old folder identifiers +DELETE FROM `kolab_folders` WHERE `resource` LIKE 'imap://anonymous@%'; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015011600.sql b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015011600.sql new file mode 100644 index 0000000..be523ae --- /dev/null +++ b/lib/drivers/kolab/plugins/libkolab/SQL/mysql/2015011600.sql @@ -0,0 +1,8 @@ +ALTER TABLE `kolab_cache_contact` MODIFY `tags` text NOT NULL; +ALTER TABLE `kolab_cache_event` MODIFY `tags` text NOT NULL; +ALTER TABLE `kolab_cache_task` MODIFY `tags` text NOT NULL; +ALTER TABLE `kolab_cache_journal` MODIFY `tags` text NOT NULL; +ALTER TABLE `kolab_cache_note` MODIFY `tags` text NOT NULL; +ALTER TABLE `kolab_cache_file` MODIFY `tags` text NOT NULL; +ALTER TABLE `kolab_cache_configuration` MODIFY `tags` text NOT NULL; +ALTER TABLE `kolab_cache_freebusy` MODIFY `tags` text NOT NULL; diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/oracle.initial.sql b/lib/drivers/kolab/plugins/libkolab/SQL/oracle.initial.sql index 2c078cb..8f1ed64 100644 --- a/lib/drivers/kolab/plugins/libkolab/SQL/oracle.initial.sql +++ b/lib/drivers/kolab/plugins/libkolab/SQL/oracle.initial.sql @@ -25,7 +25,7 @@ BEFORE INSERT ON "kolab_folders" FOR EACH ROW BEGIN :NEW."folder_id" := "kolab_folders_seq".nextval; END; - +/ CREATE TABLE "kolab_cache_contact" ( "folder_id" number NOT NULL @@ -36,7 +36,7 @@ CREATE TABLE "kolab_cache_contact" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, "type" varchar(32) NOT NULL, "name" varchar(255) DEFAULT NULL, @@ -59,7 +59,7 @@ CREATE TABLE "kolab_cache_event" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, "dtstart" timestamp DEFAULT NULL, "dtend" timestamp DEFAULT NULL, @@ -78,7 +78,7 @@ CREATE TABLE "kolab_cache_task" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, "dtstart" timestamp DEFAULT NULL, "dtend" timestamp DEFAULT NULL, @@ -97,7 +97,7 @@ CREATE TABLE "kolab_cache_journal" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, "dtstart" timestamp DEFAULT NULL, "dtend" timestamp DEFAULT NULL, @@ -116,7 +116,7 @@ CREATE TABLE "kolab_cache_note" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, PRIMARY KEY ("folder_id", "msguid") ); @@ -133,7 +133,7 @@ CREATE TABLE "kolab_cache_file" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, "filename" varchar(255) DEFAULT NULL, PRIMARY KEY ("folder_id", "msguid") @@ -152,7 +152,7 @@ CREATE TABLE "kolab_cache_configuration" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, "type" varchar(32) NOT NULL, PRIMARY KEY ("folder_id", "msguid") @@ -171,7 +171,7 @@ CREATE TABLE "kolab_cache_freebusy" ( "changed" timestamp DEFAULT NULL, "data" clob NOT NULL, "xml" clob NOT NULL, - "tags" varchar(255) DEFAULT NULL, + "tags" clob DEFAULT NULL, "words" clob DEFAULT NULL, "dtstart" timestamp DEFAULT NULL, "dtend" timestamp DEFAULT NULL, @@ -181,4 +181,4 @@ CREATE TABLE "kolab_cache_freebusy" ( CREATE INDEX "kolab_cache_fb_uid2msguid" ON "kolab_cache_freebusy" ("folder_id", "uid", "msguid"); -INSERT INTO "system" ("name", "value") VALUES ('libkolab-version', '2014021000'); +INSERT INTO "system" ("name", "value") VALUES ('libkolab-version', '2015011600'); diff --git a/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015011600.sql b/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015011600.sql new file mode 100644 index 0000000..69f7953 --- /dev/null +++ b/lib/drivers/kolab/plugins/libkolab/SQL/oracle/2015011600.sql @@ -0,0 +1,40 @@ +-- direct change from varchar to clob does not work, need temp column (#4257) +ALTER TABLE "kolab_cache_contact" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_contact" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_contact" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_contact" RENAME COLUMN "tags1" TO "tags"; + +ALTER TABLE "kolab_cache_event" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_event" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_event" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_event" RENAME COLUMN "tags1" TO "tags"; + +ALTER TABLE "kolab_cache_task" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_task" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_task" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_task" RENAME COLUMN "tags1" TO "tags"; + +ALTER TABLE "kolab_cache_journal" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_journal" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_journal" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_journal" RENAME COLUMN "tags1" TO "tags"; + +ALTER TABLE "kolab_cache_note" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_note" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_note" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_note" RENAME COLUMN "tags1" TO "tags"; + +ALTER TABLE "kolab_cache_file" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_file" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_file" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_file" RENAME COLUMN "tags1" TO "tags"; + +ALTER TABLE "kolab_cache_configuration" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_configuration" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_configuration" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_configuration" RENAME COLUMN "tags1" TO "tags"; + +ALTER TABLE "kolab_cache_freebusy" ADD "tags1" clob DEFAULT NULL; +UPDATE "kolab_cache_freebusy" SET "tags1" = "tags"; +ALTER TABLE "kolab_cache_freebusy" DROP COLUMN "tags"; +ALTER TABLE "kolab_cache_freebusy" RENAME COLUMN "tags1" TO "tags"; diff --git a/lib/drivers/kolab/plugins/libkolab/bin/readcache.sh b/lib/drivers/kolab/plugins/libkolab/bin/readcache.sh new file mode 100755 index 0000000..7e6a3a3 --- /dev/null +++ b/lib/drivers/kolab/plugins/libkolab/bin/readcache.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env php +<?php + +/** + * Kolab storage cache testing script + * + * @author Thomas Bruederli <bruederli@kolabsys.com> + * + * Copyright (C) 2014, 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/>. + */ + +define('INSTALL_PATH', realpath('.') . '/' ); +ini_set('display_errors', 1); +libxml_use_internal_errors(true); + +if (!file_exists(INSTALL_PATH . 'program/include/clisetup.php')) + die("Execute this from the Roundcube installation dir!\n\n"); + +require_once INSTALL_PATH . 'program/include/clisetup.php'; + +function print_usage() +{ + print "Usage: readcache.sh [OPTIONS] FOLDER\n"; + print "-h, --host IMAP host name\n"; + print "-l, --limit Limit the number of records to be listed\n"; +} + +// read arguments +$opts = get_opt(array( + 'h' => 'host', + 'l' => 'limit', + 'v' => 'verbose', +)); + +$folder = $opts[0]; +$imap_host = $opts['host']; + +$rcmail = rcube::get_instance(rcube::INIT_WITH_DB | rcube::INIT_WITH_PLUGINS); + +if (empty($imap_host)) { + $default_host = $rcmail->config->get('default_host'); + if (is_array($default_host)) { + list($k,$v) = each($default_host); + $imap_host = is_numeric($k) ? $v : $k; + } + else { + $imap_host = $default_host; + } + + // strip protocol prefix + $imap_host = preg_replace('!^[a-z]+://!', '', $imap_host); +} + +if (empty($folder) || empty($imap_host)) { + print_usage(); + exit; +} + +// connect to database +$db = $rcmail->get_dbh(); +$db->db_connect('r'); +if (!$db->is_connected() || $db->is_error()) + die("No DB connection\n"); + + +// resolve folder_id +if (!is_numeric($folder)) { + if (strpos($folder, '@')) { + list($mailbox, $domain) = explode('@', $folder); + list($username, $subpath) = explode('/', preg_replace('!^user/!', '', $mailbox), 2); + $folder_uri = 'imap://' . urlencode($username.'@'.$domain) . '@' . $imap_host . '/' . $subpath; + } + else { + die("Invalid mailbox identifier! Example: user/john.doe/Calendar@example.org\n"); + } + + print "Resolving folder $folder_uri..."; + $sql_result = $db->query('SELECT * FROM `kolab_folders` WHERE `resource`=?', $folder_uri); + if ($sql_result && ($folder_data = $db->fetch_assoc($sql_result))) { + $folder_id = $folder_data['folder_id']; + print $folder_id; + } + print "\n"; +} +else { + $folder_id = intval($folder); + $sql_result = $db->query('SELECT * FROM `kolab_folders` WHERE `folder_id`=?', $folder_id); + if ($sql_result) { + $folder_data = $db->fetch_assoc($sql_result); + } +} + +if (empty($folder_data)) { + die("Can't find cache mailbox for '$folder'\n"); +} + +print "Querying cache for folder $folder_id ($folder_data[type])...\n"; + +$extra_cols = array( + 'event' => array('dtstart','dtend'), + 'contact' => array('type'), +); + +$cache_table = $db->table_name('kolab_cache_' . $folder_data['type']); +$extra_cols_ = $extra_cols[$folder_data['type']] ?: array(); +$sql_arr = $db->fetch_assoc($db->query("SELECT COUNT(*) as cnt FROM `$cache_table` WHERE `folder_id`=?", intval($folder_id))); + +print "CTag = " . $folder_data['ctag'] . "\n"; +print "Lock = " . $folder_data['synclock'] . "\n"; +print "Count = " . $sql_arr['cnt'] . "\n"; +print "----------------------------------------------------------------------------------\n"; +print "<MSG>\t<UUID>\t<CHANGED>\t<DATA>\t<XML>\t"; +print join("\t", array_map(function($c) { return '<' . strtoupper($c) . '>'; }, $extra_cols_)); +print "\n----------------------------------------------------------------------------------\n"; + +$result = $db->limitquery("SELECT * FROM `$cache_table` WHERE `folder_id`=?", 0, $opts['limit'], intval($folder_id)); +while ($result && ($sql_arr = $db->fetch_assoc($result))) { + print $sql_arr['msguid'] . "\t" . $sql_arr['uid'] . "\t" . $sql_arr['changed']; + + // try to unserialize data block + $object = @unserialize(@base64_decode($sql_arr['data'])); + print "\t" . ($object === false ? 'FAIL!' : ($object['uid'] == $sql_arr['uid'] ? 'OK' : '!!!')); + + // check XML validity + $xml = simplexml_load_string($sql_arr['xml']); + print "\t" . ($xml === false ? 'FAIL!' : 'OK'); + + // print extra cols + array_walk($extra_cols_, function($c) use ($sql_arr) { + print "\t" . $sql_arr[$c]; + }); + + print "\n"; +} + +print "----------------------------------------------------------------------------------\n"; +echo "Done.\n"; diff --git a/lib/drivers/kolab/plugins/libkolab/composer.json b/lib/drivers/kolab/plugins/libkolab/composer.json index 8926037..b458df6 100644 --- a/lib/drivers/kolab/plugins/libkolab/composer.json +++ b/lib/drivers/kolab/plugins/libkolab/composer.json @@ -4,7 +4,7 @@ "description": "Plugin to setup a basic environment for the interaction with a Kolab server.", "homepage": "http://git.kolab.org/roundcubemail-plugins-kolab/", "license": "AGPLv3", - "version": "1.1.0", + "version": "3.2.3", "authors": [ { "name": "Thomas Bruederli", diff --git a/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist b/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist index 79d2aa8..7efa8d1 100644 --- a/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist +++ b/lib/drivers/kolab/plugins/libkolab/config.inc.php.dist @@ -38,6 +38,7 @@ $config['kolab_messages_cache_bypass'] = 0; // LDAP directory to find avilable users for folder sharing. // Either contains an array with LDAP addressbook configuration or refers to entry in $config['ldap_public']. // If not specified, the configuraton from 'kolab_auth_addressbook' will be used. +// Should be provided for multi-domain setups with placeholders like %dc, %d, %u, %fu or %dn. $config['kolab_users_directory'] = null; // Filter to be used for resolving user folders in LDAP. diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api.php index 23dafd8..e8ac131 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_bonnie_api.php @@ -36,7 +36,7 @@ class kolab_bonnie_api */ public function __construct($config) { - $this->config = $confg; + $this->config = $config; $this->client = new kolab_bonnie_api_client($config['uri'], $config['timeout'] ?: 5, (bool)$config['debug']); diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php index 8c6b1d4..625483b 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format.php @@ -223,7 +223,7 @@ abstract class kolab_format if ($tz) $datetime->setTimezone($tz); } else if (is_string($datetime) && strlen($datetime)) { - $datetime = new DateTime($datetime, $tz ?: null); + $datetime = $tz ? new DateTime($datetime, $tz) : new DateTime($datetime); } } catch (Exception $e) {} diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php index 4b06302..ceb7ebb 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_configuration.php @@ -24,7 +24,7 @@ class kolab_format_configuration extends kolab_format { - public $CTYPE = 'application/x-vnd.kolab.configuration'; + public $CTYPE = 'application/vnd.kolab+xml'; public $CTYPEv2 = 'application/x-vnd.kolab.configuration'; protected $objclass = 'Configuration'; @@ -48,9 +48,6 @@ class kolab_format_configuration extends kolab_format */ public function set(&$object) { - // set common object properties - parent::set($object); - // read type-specific properties switch ($object['type']) { case 'dictionary': @@ -126,7 +123,12 @@ class kolab_format_configuration extends kolab_format } // adjust content-type string - $this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; + $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; + + // reset old object data, otherwise set() will overwrite current data (#4095) + $this->xmldata = null; + // set common object properties + parent::set($object); // cache this data $this->data = $object; @@ -222,7 +224,7 @@ class kolab_format_configuration extends kolab_format // adjust content-type string if ($object['type']) { - $this->CTYPE = $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; + $this->CTYPEv2 = 'application/x-vnd.kolab.configuration.' . $object['type']; } $this->data = $object; diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php index c233f44..8cad89a 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_event.php @@ -96,11 +96,13 @@ class kolab_format_event extends kolab_format_xcal // save recurrence exceptions if (is_array($object['recurrence']) && $object['recurrence']['EXCEPTIONS']) { $vexceptions = new vectorevent; - foreach((array)$object['recurrence']['EXCEPTIONS'] as $exception) { + foreach((array)$object['recurrence']['EXCEPTIONS'] as $i => $exception) { $exevent = new kolab_format_event; - $exevent->set($this->compact_exception($exception, $object)); // only save differing values + $exevent->set(($compacted = $this->compact_exception($exception, $object))); // only save differing values $exevent->obj->setRecurrenceID(self::get_datetime($exception['start'], null, true), (bool)$exception['thisandfuture']); $vexceptions->push($exevent->obj); + // write cleaned-up exception data back to memory/cache + $object['recurrence']['EXCEPTIONS'][$i] = $this->expand_exception($compacted, $object); } $this->obj->setExceptions($vexceptions); } @@ -217,6 +219,12 @@ class kolab_format_event extends kolab_format_xcal } } + foreach ($master as $prop => $value) { + if (isset($exception[$prop]) && gettype($exception[$prop]) == gettype($value) && $exception[$prop] == $value) { + unset($exception[$prop]); + } + } + return $exception; } diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php index 08f27d0..ad54505 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_format_xcal.php @@ -364,14 +364,17 @@ abstract class kolab_format_xcal extends kolab_format $cr = new ContactReference(ContactReference::EmailReference, $attendee['email']); $cr->setName($attendee['name']); + // set attendee RSVP if missing + if (!isset($attendee['rsvp'])) { + $object['attendees'][$i]['rsvp'] = $attendee['rsvp'] = true; + } + $att = new Attendee; $att->setContact($cr); $att->setPartStat($this->part_status_map[$attendee['status']]); $att->setRole($this->role_map[$attendee['role']] ? $this->role_map[$attendee['role']] : kolabformat::Required); $att->setCutype($this->cutype_map[$attendee['cutype']] ? $this->cutype_map[$attendee['cutype']] : kolabformat::CutypeIndividual); - $att->setRSVP((bool)$attendee['rsvp'] || $reschedule); - - $object['attendees'][$i]['rsvp'] = $attendee['rsvp'] || $reschedule; + $att->setRSVP((bool)$attendee['rsvp']); if (!empty($attendee['delegated-from'])) { $vdelegators = new vectorcontactref; diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php index dfd1887..47c1e4b 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage.php @@ -35,6 +35,11 @@ class kolab_storage const UID_KEY_PRIVATE = '/private/vendor/kolab/uniqueid'; const UID_KEY_CYRUS = '/shared/vendor/cmu/cyrus-imapd/uniqueid'; + const ERROR_IMAP_CONN = 1; + const ERROR_CACHE_DB = 2; + const ERROR_NO_PERMISSION = 3; + const ERROR_INVALID_FOLDER = 4; + public static $version = '3.0'; public static $last_error; public static $encode_ids = false; @@ -139,7 +144,6 @@ class kolab_storage return self::$ldap; } - /** * Get a list of storage folders for the given data type * @@ -154,7 +158,7 @@ class kolab_storage if (self::setup()) { foreach ((array)self::list_folders('', '*', $type, $subscribed, $folderdata) as $foldername) { - $folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]); + $folders[$foldername] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); } } @@ -171,26 +175,26 @@ class kolab_storage { if (self::setup()) { foreach ((array)self::list_folders('', '*', $type . '.default', false, $folderdata) as $foldername) { - return new kolab_storage_folder($foldername, $folderdata[$foldername]); + return new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); } } return null; } - /** * Getter for a specific storage folder * - * @param string IMAP folder to access (UTF7-IMAP) + * @param string IMAP folder to access (UTF7-IMAP) + * @param string Expected folder type + * * @return object kolab_storage_folder The folder object */ - public static function get_folder($folder) + public static function get_folder($folder, $type = null) { - return self::setup() ? new kolab_storage_folder($folder) : null; + return self::setup() ? new kolab_storage_folder($folder, $type) : null; } - /** * Getter for a single Kolab object, identified by its UID. * This will search all folders storing objects of the given type. @@ -203,11 +207,11 @@ class kolab_storage { self::setup(); $folder = null; - foreach ((array)self::list_folders('', '*', $type) as $foldername) { + foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) { if (!$folder) - $folder = new kolab_storage_folder($foldername); + $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); else - $folder->set_folder($foldername); + $folder->set_folder($foldername, $type, $folderdata[$foldername]); if ($object = $folder->get_object($uid, '*')) return $object; @@ -230,11 +234,11 @@ class kolab_storage $folder = null; $result = array(); - foreach ((array)self::list_folders('', '*', $type) as $foldername) { + foreach ((array)self::list_folders('', '*', $type, null, $folderdata) as $foldername) { if (!$folder) - $folder = new kolab_storage_folder($foldername); + $folder = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); else - $folder->set_folder($foldername); + $folder->set_folder($foldername, $type, $folderdata[$foldername]); foreach ($folder->select($query, '*') as $object) { $result[] = $object; @@ -403,13 +407,13 @@ class kolab_storage 'oldname' => $oldname, 'newname' => $newname)); $oldfolder = self::get_folder($oldname); - $active = self::folder_is_active($oldname); - $success = self::$imap->rename_folder($oldname, $newname); + $active = self::folder_is_active($oldname); + $success = self::$imap->rename_folder($oldname, $newname); self::$last_error = self::$imap->get_error_str(); // pass active state to new folder name if ($success && $active) { - self::set_state($oldnam, false); + self::set_state($oldname, false); self::set_state($newname, true); } @@ -901,7 +905,7 @@ class kolab_storage !self::folder_is_subscribed($foldername, true) && !in_array(self::$imap->folder_namespace($foldername), (array)$exclude_ns) ) { - $folders[] = new kolab_storage_folder($foldername, $folderdata[$foldername]); + $folders[] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); } } @@ -974,7 +978,7 @@ class kolab_storage $parent_parent = join($delim, $path); if (!$refs[$parent]) { if ($folder->type && self::folder_type($parent) == $folder->type) { - $refs[$parent] = new kolab_storage_folder($parent, $folder->type); + $refs[$parent] = new kolab_storage_folder($parent, $folder->type, $folder->type); $refs[$parent]->parent = $parent_parent; } else if ($parent_parent == $other_ns) { @@ -1086,7 +1090,7 @@ class kolab_storage return $types[self::CTYPE_KEY_PRIVATE]; } else if (!empty($types[self::CTYPE_KEY])) { - list($ctype, $suffix) = explode('.', $types[self::CTYPE_KEY]); + list($ctype, ) = explode('.', $types[self::CTYPE_KEY]); return $ctype; } return null; @@ -1193,7 +1197,7 @@ class kolab_storage } } else if (self::$imap->subscribe($folder)) { - self::$subscriptions === null; + self::$subscriptions = null; return true; } @@ -1221,7 +1225,7 @@ class kolab_storage return true; } else if (self::$imap->unsubscribe($folder)) { - self::$subscriptions === null; + self::$subscriptions = null; return true; } @@ -1464,7 +1468,7 @@ class kolab_storage $user_attrib = self::$config->get('kolab_users_id_attrib', self::$config->get('kolab_auth_login', 'mail')); array_walk($results, function(&$user, $dn) use ($root, $user_attrib) { - list($localpart, $domain) = explode('@', $user[$user_attrib]); + list($localpart, ) = explode('@', $user[$user_attrib]); $user['kolabtargetfolder'] = $root . $localpart; }); @@ -1542,7 +1546,7 @@ class kolab_storage foreach ($folders as $userfolder) { foreach ((array)self::list_folders($userfolder->name . $delimiter, '*', $type, false, $folderdata) as $foldername) { if (!$folders[$foldername]) { - $folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]); + $folders[$foldername] = new kolab_storage_folder($foldername, $type, $folderdata[$foldername]); $userfolder->children[] = $folders[$foldername]; } } diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php index bced3b3..227fa4e 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_cache.php @@ -26,6 +26,8 @@ class kolab_storage_cache { const DB_DATE_FORMAT = 'Y-m-d H:i:s'; + public $sync_complete = false; + protected $db; protected $imap; protected $folder; @@ -46,6 +48,7 @@ class kolab_storage_cache protected $extra_cols = array(); protected $order_by = null; protected $limit = null; + protected $error = 0; /** @@ -78,6 +81,7 @@ class kolab_storage_cache $this->db = $rcmail->get_dbh(); $this->imap = $rcmail->get_storage(); $this->enabled = $rcmail->config->get('kolab_cache', false); + $this->folders_table = $this->db->table_name('kolab_folders'); if ($this->enabled) { // always read folder cache and lock state from DB master @@ -96,8 +100,7 @@ class kolab_storage_cache */ public function select_by_id($folder_id) { - $folders_table = $this->db->table_name('kolab_folders', true); - $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT * FROM $folders_table WHERE `folder_id` = ?", $folder_id)); + $sql_arr = $this->db->fetch_assoc($this->db->query("SELECT * FROM `{$this->folders_table}` WHERE `folder_id` = ?", $folder_id)); if ($sql_arr) { $this->metadata = $sql_arr; $this->folder_id = $sql_arr['folder_id']; @@ -118,14 +121,13 @@ class kolab_storage_cache { $this->folder = $storage_folder; - if (empty($this->folder->name)) { + if (empty($this->folder->name) || !$this->folder->valid) { $this->ready = false; return; } // compose fully qualified ressource uri for this instance $this->resource_uri = $this->folder->get_resource_uri(); - $this->folders_table = $this->db->table_name('kolab_folders'); $this->cache_table = $this->db->table_name('kolab_cache_' . $this->folder->type); $this->ready = $this->enabled && !empty($this->folder->type); $this->folder_id = null; @@ -149,6 +151,16 @@ class kolab_storage_cache } /** + * Returns code of last error + * + * @return int Error code + */ + public function get_error() + { + return $this->error; + } + + /** * Synchronize local cache data with remote */ public function synchronize() @@ -158,7 +170,14 @@ class kolab_storage_cache return; // increase time limit - @set_time_limit($this->max_sync_lock_time); + @set_time_limit($this->max_sync_lock_time - 60); + + // get effective time limit we have for synchronization (~70% of the execution time) + $time_limit = ini_get('max_execution_time') * 0.7; + $sync_start = time(); + + // assume sync will be completed + $this->sync_complete = true; if (!$this->ready) { // kolab cache is disabled, synchronize IMAP mailbox cache only @@ -195,13 +214,19 @@ class kolab_storage_cache $old_index = array(); while ($sql_arr = $this->db->fetch_assoc($sql_result)) { $old_index[] = $sql_arr['msguid']; - $this->uid2msg[$sql_arr['uid']] = $sql_arr['msguid']; } // fetch new objects from imap + $i = 0; foreach (array_diff($imap_index, $old_index) as $msguid) { if ($object = $this->folder->read_object($msguid, '*')) { $this->_extended_insert($msguid, $object); + + // check time limit and abort sync if running too long + if (++$i % 50 == 0 && time() - $sync_start > $time_limit) { + $this->sync_complete = false; + break; + } } } $this->_extended_insert(0, null); @@ -217,7 +242,9 @@ class kolab_storage_cache } // update ctag value (will be written to database in _sync_unlock()) - $this->metadata['ctag'] = $this->folder->get_ctag(); + if ($this->sync_complete) { + $this->metadata['ctag'] = $this->folder->get_ctag(); + } } $this->bypass(false); @@ -227,6 +254,7 @@ class kolab_storage_cache } } + $this->check_error(); $this->synched = time(); } @@ -243,7 +271,12 @@ class kolab_storage_cache { // delegate to another cache instance if ($foldername && $foldername != $this->folder->name) { - return kolab_storage::get_folder($foldername)->cache->get($msguid, $type); + $success = false; + if ($targetfolder = kolab_storage::get_folder($foldername)) { + $success = $targetfolder->cache->get($msguid, $type); + $this->error = $targetfolder->cache->get_error(); + } + return $success; } // load object if not in memory @@ -272,6 +305,7 @@ class kolab_storage_cache } } + $this->check_error(); return $this->objects[$msguid]; } @@ -291,8 +325,11 @@ class kolab_storage_cache // delegate to another cache instance if ($foldername && $foldername != $this->folder->name) { - kolab_storage::get_folder($foldername)->cache->set($msguid, $object); - return; + if ($targetfolder = kolab_storage::get_folder($foldername)) { + $targetfolder->cache->set($msguid, $object); + $this->error = $targetfolder->cache->get_error(); + } + return; } // remove old entry @@ -310,6 +347,8 @@ class kolab_storage_cache // ...or set in-memory cache to false $this->objects[$msguid] = $object; } + + $this->check_error(); } @@ -368,6 +407,8 @@ class kolab_storage_cache // keep a copy in memory for fast access $this->objects = array($msguid => $object); $this->uid2msg = array($object['uid'] => $msguid); + + $this->check_error(); } @@ -407,13 +448,14 @@ class kolab_storage_cache } unset($this->uid2msg[$uid]); + $this->check_error(); } /** * Remove all objects from local cache */ - public function purge($type = null) + public function purge() { if (!$this->ready) { return true; @@ -440,15 +482,20 @@ class kolab_storage_cache return; } - $target = kolab_storage::get_folder($new_folder); + if ($target = kolab_storage::get_folder($new_folder)) { + // resolve new message UID in target folder + $this->db->query( + "UPDATE `{$this->folders_table}` SET `resource` = ? ". + "WHERE `resource` = ?", + $target->get_resource_uri(), + $this->resource_uri + ); - // resolve new message UID in target folder - $this->db->query( - "UPDATE `{$this->folders_table}` SET `resource` = ? ". - "WHERE `resource` = ?", - $target->get_resource_uri(), - $this->resource_uri - ); + $this->check_error(); + } + else { + $this->error = kolab_storage::ERROR_IMAP_CONN; + } } /** @@ -513,6 +560,7 @@ class kolab_storage_cache } if ($index->is_error()) { + $this->check_error(); if ($uids) { return null; } @@ -535,6 +583,8 @@ class kolab_storage_cache } } + $this->check_error(); + return $result; } @@ -577,6 +627,7 @@ class kolab_storage_cache } if ($index->is_error()) { + $this->check_error(); return null; } @@ -585,6 +636,7 @@ class kolab_storage_cache $count = $index->count(); } + $this->check_error(); return $count; } @@ -918,24 +970,29 @@ class kolab_storage_cache return; $this->_read_folder_data(); - $sql_query = "SELECT `synclock`, `ctag` FROM `{$this->folders_table}` WHERE `folder_id` = ?"; // abort if database is not set-up if ($this->db->is_error()) { + $this->check_error(); $this->ready = false; return; } - $this->synclock = true; + $read_query = "SELECT `synclock`, `ctag` FROM `{$this->folders_table}` WHERE `folder_id` = ?"; + $write_query = "UPDATE `{$this->folders_table}` SET `synclock` = ? WHERE `folder_id` = ? AND `synclock` = ?"; - // wait if locked (expire locks after 10 minutes) - while ($this->metadata && intval($this->metadata['synclock']) > 0 && $this->metadata['synclock'] + $this->max_sync_lock_time > time()) { + // wait if locked (expire locks after 10 minutes) ... + // ... or if setting lock fails (another process meanwhile set it) + while ( + (intval($this->metadata['synclock']) + $this->max_sync_lock_time > time()) || + (($res = $this->db->query($write_query, time(), $this->folder_id, intval($this->metadata['synclock']))) && + !($affected = $this->db->affected_rows($res))) + ) { usleep(500000); - $this->metadata = $this->db->fetch_assoc($this->db->query($sql_query, $this->folder_id)); + $this->metadata = $this->db->fetch_assoc($this->db->query($read_query, $this->folder_id)); } - // set lock - $this->db->query("UPDATE `{$this->folders_table}` SET `synclock` = ? WHERE `folder_id` = ?", time(), $this->folder_id); + $this->synclock = $affected > 0; } /** @@ -956,6 +1013,22 @@ class kolab_storage_cache } /** + * Check IMAP connection error state + */ + protected function check_error() + { + if (($err_code = $this->imap->get_error_code()) < 0) { + $this->error = kolab_storage::ERROR_IMAP_CONN; + if (($res_code = $this->imap->get_response_code()) !== 0 && in_array($res_code, array(rcube_storage::NOPERM, rcube_storage::READONLY))) { + $this->error = kolab_storage::ERROR_NO_PERMISSION; + } + } + else if ($this->db->is_error()) { + $this->error = kolab_storage::ERROR_CACHE_DB; + } + } + + /** * Resolve an object UID into an IMAP message UID * * @param string Kolab object UID diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_config.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_config.php index d58e3c0..036b827 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_config.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_config.php @@ -837,4 +837,30 @@ class kolab_storage_config return self::build_member_url($params); } + + /** + * Resolve the email message reference from the given URI + */ + public function get_message_reference($uri, $rel = null) + { + if ($linkref = self::parse_member_url($uri)) { + $linkref['subject'] = $linkref['params']['subject']; + $linkref['uri'] = $uri; + + $rcmail = rcube::get_instance(); + if (method_exists($rcmail, 'url')) { + $linkref['mailurl'] = $rcmail->url(array( + 'task' => 'mail', + 'action' => 'show', + 'mbox' => $linkref['folder'], + 'uid' => $linkref['uid'], + 'rel' => $rel, + )); + } + + unset($linkref['params']); + } + + return $linkref; + } } diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php index 2435fa3..ab3c63f 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder.php @@ -30,18 +30,28 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public $cache; - private $type_annotation; - private $resource_uri; + /** + * Indicate validity status + * @var boolean + */ + public $valid = false; + + protected $error = 0; + + protected $resource_uri; /** * Default constructor + * + * @param string The folder name/path + * @param string Expected folder type */ - function __construct($name, $type = null) + function __construct($name, $type = null, $type_annotation = null) { parent::__construct($name); $this->imap->set_options(array('skip_deleted' => true)); - $this->set_folder($name, $type); + $this->set_folder($name, $type, $type_annotation); } @@ -49,30 +59,63 @@ class kolab_storage_folder extends kolab_storage_folder_api * Set the IMAP folder this instance connects to * * @param string The folder name/path + * @param string Expected folder type * @param string Optional folder type if known */ - public function set_folder($name, $type = null) + public function set_folder($name, $type = null, $type_annotation = null) { - $this->type_annotation = $type ? $type : kolab_storage::folder_type($name); + if (empty($type_annotation)) { + $type_annotation = kolab_storage::folder_type($name); + } $oldtype = $this->type; - list($this->type, $suffix) = explode('.', $this->type_annotation); + list($this->type, $suffix) = explode('.', $type_annotation); $this->default = $suffix == 'default'; $this->subtype = $this->default ? '' : $suffix; $this->name = $name; $this->id = kolab_storage::folder_id($name); + $this->valid = !empty($this->type) && $this->type != 'mail' && (!$type || $this->type == $type); + + if (!$this->valid) { + $this->error = $this->imap->get_error_code() < 0 ? kolab_storage::ERROR_IMAP_CONN : kolab_storage::ERROR_INVALID_FOLDER; + } // reset cached object properties $this->owner = $this->namespace = $this->resource_uri = $this->info = $this->idata = null; - // get a new cache instance of folder type changed - if (!$this->cache || $type != $oldtype) + // get a new cache instance if folder type changed + if (!$this->cache || $this->type != $oldtype) $this->cache = kolab_storage_cache::factory($this); + else + $this->cache->set_folder($this); $this->imap->set_folder($this->name); - $this->cache->set_folder($this); } + /** + * Returns code of last error + * + * @return int Error code + */ + public function get_error() + { + return $this->error ?: $this->cache->get_error(); + } + + /** + * Check IMAP connection error state + */ + public function check_error() + { + if (($err_code = $this->imap->get_error_code()) < 0) { + $this->error = kolab_storage::ERROR_IMAP_CONN; + if (($res_code = $this->imap->get_response_code()) !== 0 && in_array($res_code, array(rcube_storage::NOPERM, rcube_storage::READONLY))) { + $this->error = kolab_storage::ERROR_NO_PERMISSION; + } + } + + return $this->error; + } /** * Compose a unique resource URI for this IMAP folder @@ -97,7 +140,7 @@ class kolab_storage_folder extends kolab_storage_folder_api } // compose fully qualified ressource uri for this instance - $this->resource_uri = 'imap://' . urlencode($this->get_owner()) . '@' . $this->imap->options['host'] . '/' . $subpath; + $this->resource_uri = 'imap://' . urlencode($this->get_owner(true)) . '@' . $this->imap->options['host'] . '/' . $subpath; return $this->resource_uri; } @@ -138,6 +181,8 @@ class kolab_storage_folder extends kolab_storage_folder_api if (!($success = $this->set_metadata(array(kolab_storage::UID_KEY_SHARED => $uid)))) { $success = $this->set_metadata(array(kolab_storage::UID_KEY_PRIVATE => $uid)); } + + $this->check_error(); return $success; } @@ -147,6 +192,7 @@ class kolab_storage_folder extends kolab_storage_folder_api public function get_ctag() { $fdata = $this->get_imap_data(); + $this->check_error(); return sprintf('%d-%d-%d', $fdata['UIDVALIDITY'], $fdata['HIGHESTMODSEQ'], $fdata['UIDNEXT']); } @@ -204,6 +250,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function count($query = null) { + if (!$this->valid) { + return 0; + } + // synchronize cache first $this->cache->synchronize(); @@ -221,6 +271,10 @@ class kolab_storage_folder extends kolab_storage_folder_api { if (!$type) $type = $this->type; + if (!$this->valid) { + return array(); + } + // synchronize caches $this->cache->synchronize(); @@ -238,9 +292,14 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function select($query = array()) { + if (!$this->valid) { + return array(); + } + // check query argument - if (empty($query)) + if (empty($query)) { return $this->get_objects(); + } // synchronize caches $this->cache->synchronize(); @@ -258,6 +317,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function get_uids($query = array()) { + if (!$this->valid) { + return array(); + } + // synchronize caches $this->cache->synchronize(); @@ -319,6 +382,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function get_object($uid, $type = null) { + if (!$this->valid) { + return false; + } + // synchronize caches $this->cache->synchronize(); @@ -348,7 +415,7 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function get_attachment($uid, $part, $mailbox = null, $print = false, $fp = null, $skip_charset_conv = false) { - if ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid))) { + if ($this->valid && ($msguid = ($mailbox ? $uid : $this->cache->uid2msguid($uid)))) { $this->imap->set_folder($mailbox ? $mailbox : $this->name); if (substr($part, 0, 2) == 'i:') { @@ -360,7 +427,7 @@ class kolab_storage_folder extends kolab_storage_folder_api $object['_formatobj']->get_attachments($object); } - foreach ($object['_attachments'] as $k => $attach) { + foreach ($object['_attachments'] as $attach) { if ($attach['id'] == $part) { if ($print) echo $attach['content']; else if ($fp) fwrite($fp, $attach['content']); @@ -392,6 +459,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function read_object($msguid, $type = null, $folder = null) { + if (!$this->valid) { + return false; + } + if (!$type) $type = $this->type; if (!$folder) $folder = $this->name; @@ -444,7 +515,7 @@ class kolab_storage_folder extends kolab_storage_folder_api // get XML part foreach ((array)$message->attachments as $part) { if (!$xml && ($part->mimetype == $content_type || preg_match('!application/([a-z.]+\+)?xml!', $part->mimetype))) { - $xml = $part->body ? $part->body : $message->get_part_content($part->mime_id); + $xml = $message->get_part_body($part->mime_id, true); } else if ($part->filename || $part->content_id) { $key = $part->content_id ? trim($part->content_id, '<>') : $part->filename; @@ -537,6 +608,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function save(&$object, $type = null, $uid = null) { + if (!$this->valid) { + return false; + } + if (!$type) $type = $this->type; @@ -732,6 +807,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function delete($object, $expunge = true) { + if (!$this->valid) { + return false; + } + $msguid = is_array($object) ? $object['_msguid'] : $this->cache->uid2msguid($object); $success = false; @@ -759,6 +838,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function delete_all() { + if (!$this->valid) { + return false; + } + $this->cache->purge(); $this->cache->bypass(true); $result = $this->imap->clear_folder($this->name); @@ -776,6 +859,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function undelete($uid) { + if (!$this->valid) { + return false; + } + if ($msguid = $this->cache->uid2msguid($uid, true)) { $this->cache->bypass(true); $result = $this->imap->set_flag($msguid, 'UNDELETED', $this->name); @@ -799,6 +886,10 @@ class kolab_storage_folder extends kolab_storage_folder_api */ public function move($uid, $target_folder) { + if (!$this->valid) { + return false; + } + if (is_string($target_folder)) $target_folder = kolab_storage::get_folder($target_folder); @@ -882,7 +973,7 @@ class kolab_storage_folder extends kolab_storage_folder_api if (!empty($object['_attachments']) && ($mem_limit = parse_bytes(ini_get('memory_limit'))) > 0) { $memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB - foreach ($object['_attachments'] as $id => $attachment) { + foreach ($object['_attachments'] as $attachment) { $memory += $attachment['size']; } @@ -1054,8 +1145,6 @@ class kolab_storage_folder extends kolab_storage_folder_api */ private function trigger_url($url, $auth_user = null, $auth_passwd = null) { - require_once('HTTP/Request2.php'); - try { $request = libkolab::http_request($url); diff --git a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_api.php b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_api.php index ea603b1..7280389 100644 --- a/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_api.php +++ b/lib/drivers/kolab/plugins/libkolab/lib/kolab_storage_folder_api.php @@ -85,9 +85,10 @@ abstract class kolab_storage_folder_api /** * Returns the owner of the folder. * + * @param boolean Return a fully qualified owner name (i.e. including domain for shared folders) * @return string The owner of this folder. */ - public function get_owner() + public function get_owner($fully_qualified = false) { // return cached value if (isset($this->owner)) @@ -106,16 +107,21 @@ abstract class kolab_storage_folder_api break; default: - list($prefix, $user) = explode($this->imap->get_hierarchy_delimiter(), $info['name']); - if (strpos($user, '@') === false) { - $domain = strstr($rcmail->get_user_name(), '@'); - if (!empty($domain)) - $user .= $domain; - } - $this->owner = $user; + list($prefix, $this->owner) = explode($this->imap->get_hierarchy_delimiter(), $info['name']); + $fully_qualified = true; // enforce email addresses (backwards compatibility) break; } + if ($fully_qualified && strpos($this->owner, '@') === false) { + // extract domain from current user name + $domain = strstr($rcmail->get_user_name(), '@'); + // fall back to mail_domain config option + if (empty($domain) && ($mdomain = $rcmail->config->mail_domain($this->imap->options['host']))) { + $domain = '@' . $mdomain; + } + $this->owner .= $domain; + } + return $this->owner; } diff --git a/lib/drivers/kolab/plugins/libkolab/package.xml b/lib/drivers/kolab/plugins/libkolab/package.xml deleted file mode 100644 index cd3e3a0..0000000 --- a/lib/drivers/kolab/plugins/libkolab/package.xml +++ /dev/null @@ -1,101 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 - http://pear.php.net/dtd/tasks-1.0.xsd - http://pear.php.net/dtd/package-2.0 - http://pear.php.net/dtd/package-2.0.xsd"> - <name>libkolab</name> - <uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri> - <summary>Kolab core library</summary> - <description>Plugin to setup a basic environment for the interaction with a Kolab server.</description> - <lead> - <name>Thomas Bruederli</name> - <user>bruederli</user> - <email>bruederli@kolabsys.com</email> - <active>yes</active> - </lead> - <developer> - <name>Alensader Machniak</name> - <user>machniak</user> - <email>machniak@kolabsys.com</email> - <active>yes</active> - </developer> - <date>2013-04-19</date> - <version> - <release>0.9</release> - <api>0.9</api> - </version> - <stability> - <release>stable</release> - <api>stable</api> - </stability> - <license uri="http://www.gnu.org/licenses/agpl.html">GNU AGPLv3</license> - <notes>-</notes> - <contents> - <dir baseinstalldir="/" name="/"> - <file name="libkolab.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_configuration.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_contact.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_distributionlist.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_event.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_file.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_journal.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_note.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_task.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_format_xcal.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_storage.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_storage_cache.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_storage_folder.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - <file name="lib/kolab_date_recurrence.php" role="php"> - <tasks:replace from="@package_version@" to="version" type="package-info"/> - </file> - - <file name="bin/modcache.php" role="php"></file> - - <file name="config.inc.php.dist" role="data"></file> - <file name="LICENSE" role="data"></file> - <file name="README" role="data"></file> - <file name="UPGRADING" role="data"></file> - </dir> - <!-- / --> - </contents> - <dependencies> - <required> - <php> - <min>5.3.1</min> - </php> - <pearinstaller> - <min>1.7.0</min> - </pearinstaller> - </required> - </dependencies> - <phprelease/> -</package> |