Projects
Kolab:3.4
roundcubemail-plugins-kolab
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 12
View file
roundcubemail-plugins-kolab.spec
Changed
@@ -23,7 +23,7 @@ %global roundcube_lib %{_var}/lib/roundcubemail Name: roundcubemail-plugins-kolab -Version: 3.1.2 +Version: 3.1.3 Release: 1%{?dist} Summary: Kolab Groupware plugins for Roundcube Webmail @@ -210,6 +210,17 @@ %attr(0770,root,%{httpd_group}) %{roundcube_lib}/plugins/odfviewer %changelog +* Thu Sep 19 2013 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 3.1.3-1 +- New upstream release, resolves: + + 2191 Restrict email addresses for identity creation/editing to + what is registered in LDAP + 2197 Invitation Mail aren't send + 2209 not all location is displayed + 2214 PHP Fatal error in libvcalendar + 2241 Remove LDAP connection in + load_user_role_plugins_and_settings() for better performance + * Wed Sep 11 2013 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 3.1.2-1 - Release 3.1.2 with bugfixes
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +roundcubemail-plugins-kolab (1:3.1.3-1) unstable; urgency=low + + * New upstream version 3.1.3 + + -- Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com> Thu, 19 Sep 2013 18:41:13 +0200 + roundcubemail-plugins-kolab (1:3.1.2-1) unstable; urgency=low * New upstream version 3.1.2
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/kolab_auth/kolab_auth.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/kolab_auth/kolab_auth.php
Changed
@@ -63,25 +63,28 @@ $rcmail->config->set('ldap_debug', true); $rcmail->config->set('smtp_debug', true); } - } public function startup($args) { - // Arguments are task / action, not interested - if (!empty($_SESSION['user_roledns'])) { - $this->load_user_role_plugins_and_settings($_SESSION['user_roledns']); - } + $this->load_user_role_plugins_and_settings(); return $args; } - public function load_user_role_plugins_and_settings($role_dns) + /** + * Modifies list of plugins and settings according to + * specified LDAP roles + */ + public function load_user_role_plugins_and_settings() { + if (empty($_SESSION['user_roledns'])) { + return; + } + $rcmail = rcube::get_instance(); $this->load_config(); - // Check role dependent plugins to enable and settings to modify // Example 'kolab_auth_role_plugins' = // @@ -109,25 +112,19 @@ $role_settings = $rcmail->config->get('kolab_auth_role_settings'); - $ldap = self::ldap(); - if (!$ldap || !$ldap->ready) { - $args['abort'] = true; - return $args; - } - if (!empty($role_plugins)) { foreach ($role_plugins as $role_dn => $plugins) { - $role_plugins[$ldap->parse_vars($role_dn)] = $plugins; + $role_plugins[self::parse_ldap_vars($role_dn)] = $plugins; } } if (!empty($role_settings)) { foreach ($role_settings as $role_dn => $settings) { - $role_settings[$ldap->parse_vars($role_dn)] = $settings; + $role_settings[self::parse_ldap_vars($role_dn)] = $settings; } } - foreach ($role_dns as $role_dn) { + 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); @@ -326,11 +323,21 @@ $email_attr = $rcmail->config->get('kolab_auth_email'); $org_attr = $rcmail->config->get('kolab_auth_organization'); $role_attr = $rcmail->config->get('kolab_auth_role'); + $imap_attr = $rcmail->config->get('kolab_auth_mailhost'); if (!empty($role_attr) && !empty($record[$role_attr])) { $_SESSION['user_roledns'] = (array)($record[$role_attr]); } + if (!empty($imap_attr) && !empty($record[$role_attr])) { + $default_host = $rcmail->config->get('default_host'); + if (!empty($default_host)) { + rcube::write_log("errors", "Both default host and kolab_auth_mailhost set. Incompatible."); + } else { + $args['host'] = "tls://" . $record[$role_attr]; + } + } + // Login As... if (!empty($loginas) && $admin_login) { // Authenticate to LDAP @@ -394,6 +401,12 @@ $_SESSION['kolab_uid'] = is_array($record['uid']) ? $record['uid'][0] : $record['uid']; $_SESSION['kolab_dn'] = $record['dn']; + // Store LDAP replacement variables used for current user + // This improves performance of load_user_role_plugins_and_settings() + // which is executed on every request (via startup hook) and where + // we don't like to use LDAP (connection + bind + search) + $_SESSION['kolab_auth_vars'] = $ldap->get_parse_vars(); + // Set user login if ($login_attr) { $this->data['user_login'] = is_array($record[$login_attr]) ? $record[$login_attr][0] : $record[$login_attr]; @@ -475,13 +488,20 @@ */ public function identity_form($args) { + $rcmail = rcube::get_instance(); + $ident_level = intval($rcmail->config->get('identities_level', 0)); + + // do nothing if email address modification is disabled + if ($ident_level == 1 || $ident_level == 3) { + return $args; + } + $ldap = self::ldap(); if (!$ldap || !$ldap->ready || empty($_SESSION['kolab_dn'])) { return $args; } - $emails = array(); - $rcmail = rcube::get_instance(); + $emails = array(); $user_record = $ldap->get_record($_SESSION['kolab_dn']); foreach ((array)$rcmail->config->get('kolab_auth_email', array()) as $col) { @@ -490,6 +510,10 @@ $emails = array_merge($emails, array_filter($values)); } + // kolab_delegation might want to modify this addresses list + $plugin = $rcmail->plugins->exec_hook('kolab_auth_emails', array('emails' => $emails)); + $emails = $plugin['emails']; + if (!empty($emails)) { $args['form']['addressing']['content']['email'] = array( 'type' => 'select', @@ -538,4 +562,21 @@ return self::$ldap; } + + /** + * Parses LDAP DN string with replacing supported variables. + * See kolab_auth_ldap::parse_vars() + * + * @param string $str LDAP DN string + * + * @return string Parsed DN string + */ + public static function parse_ldap_vars($str) + { + if (!empty($_SESSION['kolab_auth_vars'])) { + $str = strtr($str, $_SESSION['kolab_auth_vars']); + } + + return $str; + } }
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/kolab_auth/kolab_auth_ldap.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/kolab_auth/kolab_auth_ldap.php
Changed
@@ -119,6 +119,7 @@ $entries = $result->entries(true); $dn = key($entries); $entry = array_pop($entries); + $entry = rcube_ldap_generic::normalize_entry($entry); $entry = $this->field_mapping($dn, $entry); return $entry; @@ -287,7 +288,8 @@ if ($limit && $limit <= $i) { break; } - $dn = $result->get_dn(); + $dn = $result->get_dn(); + $entry = rcube_ldap_generic::normalize_entry($entry); $list[$dn] = $this->field_mapping($dn, $entry); $i++; } @@ -369,7 +371,8 @@ if (!$user) { $user = $_SESSION['username']; } - else if (isset($this->icache[$user])) { + + if (isset($this->icache[$user])) { list($user, $dc) = $this->icache[$user]; } else { @@ -412,6 +415,8 @@ $replaces = array('%dc' => $dc, '%d' => $d, '%fu' => $user, '%u' => $u); + $this->parse_replaces = $replaces; + return strtr($str, $replaces); } @@ -459,6 +464,16 @@ } /** + * Returns variables used for replacement in (last) parse_vars() call + * + * @return array Variable-value hash array + */ + public function get_parse_vars() + { + return $this->parse_replaces; + } + + /** * HTML-safe DN string encoding * * @param string $str DN string
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/kolab_delegation/kolab_delegation.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/kolab_delegation/kolab_delegation.php
Changed
@@ -55,6 +55,9 @@ $this->add_hook('calendar_list_filter', array($this, 'calendar_list_filter')); $this->add_hook('calendar_load_itip', array($this, 'calendar_load_itip')); + // delegation support in kolab_auth plugin + $this->add_hook('kolab_auth_emails', array($this, 'kolab_auth_emails')); + if ($this->rc->task == 'settings') { // delegation management interface $this->register_action('plugin.delegation', array($this, 'controller_ui')); @@ -241,6 +244,27 @@ } /** + * Delegation support in kolab_auth plugin + */ + public function kolab_auth_emails($args) + { + // Add delegators addresses to address selector in user identity form + + if (!empty($_SESSION['delegators'])) { + // @TODO: Consider not adding all delegator addresses to the list. + // Instead add only address of currently edited identity + foreach ($_SESSION['delegators'] as $emails) { + $args['emails'] = array_merge($args['emails'], $emails); + } + + $args['emails'] = array_unique($args['emails']); + sort($args['emails']); + } + + return $args; + } + + /** * Delegation UI handler */ public function controller_ui()
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/libcalendaring/libcalendaring.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/libcalendaring/libcalendaring.php
Changed
@@ -308,7 +308,7 @@ { if ($val[0] == '@') return array(substr($val, 1)); - else if (preg_match('/([+-])(\d+)([HMD])/', $val, $m)) + else if (preg_match('/([+-])P?T?(\d+)([HMSDW])/', $val, $m)) return array($m[2], $m[1].$m[3]); return false;
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/libcalendaring/libvcalendar.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/libcalendaring/libvcalendar.php
Changed
@@ -23,6 +23,11 @@ use \Sabre\VObject; +// load Sabre\VObject classes +if (!class_exists('\Sabre\VObject\Reader')) { + require_once __DIR__ . '/lib/Sabre/VObject/includes.php'; +} + /** * Class to parse and build vCalendar (iCalendar) files * @@ -41,6 +46,7 @@ private $attendee_keymap = array('name' => 'CN', 'status' => 'PARTSTAT', 'role' => 'ROLE', 'cutype' => 'CUTYPE', 'rsvp' => 'RSVP'); public $method; + public $agent = ''; public $objects = array(); public $freebusy = array(); @@ -49,11 +55,6 @@ */ function __construct($tz = null) { - // load Sabre\VObject classes - if (!class_exists('\Sabre\VObject\Reader')) { - require_once(__DIR__ . '/lib/Sabre/VObject/includes.php'); - } - $this->timezone = $tz; $this->prodid = '-//Roundcube//Roundcube libcalendaring ' . RCUBE_VERSION . '//Sabre//Sabre VObject ' . VObject\Version::VERSION . '//EN'; } @@ -83,6 +84,14 @@ } /** + * Setter for a user-agent string to tweak input/output accordingly + */ + public function set_agent($agent) + { + $this->agent = $agent; + } + + /** * Free resources by clearing member vars */ public function reset() @@ -96,9 +105,10 @@ * * @param string vCalendar input * @param string Input charset (from envelope) + * @param boolean True if parsing exceptions should be forwarded to the caller * @return array List of events extracted from the input */ - public function import($vcal, $charset = 'UTF-8') + public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false) { // TODO: convert charset to UTF-8 if other @@ -113,6 +123,10 @@ 'file' => __FILE__, 'line' => __LINE__, 'message' => "iCal data parse error: " . $e->getMessage()), true, false); + + if ($forward_exceptions) { + throw $e; + } } return array(); @@ -121,10 +135,12 @@ /** * Read iCalendar events from a file * - * @param string File path to read from + * @param string File path to read from + * @param string Input charset (from envelope) + * @param boolean True if parsing exceptions should be forwarded to the caller * @return array List of events extracted from the file */ - public function import_from_file($filepath) + public function import_from_file($filepath, $charset = 'UTF-8', $forward_exceptions = false) { $this->objects = array(); $fp = fopen($filepath, 'r'); @@ -136,7 +152,7 @@ } fclose($fp); - return $this->import(file_get_contents($filepath)); + return $this->import(file_get_contents($filepath), $charset, $forward_exceptions); } /** @@ -151,6 +167,7 @@ if ($vobject->name == 'VCALENDAR') { $this->method = strval($vobject->METHOD); + $this->agent = strval($vobject->PRODID); foreach ($vobject->getBaseComponents() as $ve) { if ($ve->name == 'VEVENT' || $ve->name == 'VTODO') { @@ -195,6 +212,16 @@ } /** + * Helper method to determine whether the connected client is an Apple device + */ + private function is_apple() + { + return stripos($this->agent, 'Apple') !== false + || stripos($this->agent, 'Mac OS X') !== false + || stripos($this->agent, 'iOS/') !== false; + } + + /** * Convert the given VEvent object to a libkolab compatible array representation * * @param object Vevent object to convert @@ -209,6 +236,7 @@ // set defaults 'priority' => 0, 'attendees' => array(), + 'x-custom' => array(), ); // Catch possible exceptions when date is invalid (Bug #2144) @@ -290,8 +318,14 @@ $event['complete'] = intval($prop->value); break; - case 'DESCRIPTION': case 'LOCATION': + case 'DESCRIPTION': + if ($this->is_apple()) { + $event[strtolower($prop->name)] = str_replace('\,', ',', $prop->value); + break; + } + // else: fall through + case 'URL': $event[strtolower($prop->name)] = $prop->value; break; @@ -381,10 +415,7 @@ } // sanity-check and fix end date - if (empty($event['end'])) { - $event['end'] = clone $event['start']; - } - else if ($event['end'] < $event['start']) { + if (!empty($event['end']) && $event['end'] < $event['start']) { $event['end'] = clone $event['start']; } } @@ -409,7 +440,7 @@ } } if (!$trigger) { - $trigger = preg_replace('/PT/', '', $prop->value); + $trigger = preg_replace('/PT?/', '', $prop->value); } break; @@ -441,7 +472,7 @@ } // minimal validation - if (empty($event['uid']) || empty($event['start']) != empty($event['end'])) { + if (empty($event['uid']) || ($event['_type'] == 'event' && empty($event['start']) != empty($event['end']))) { throw new VObject\ParseException('Object validation failed: missing mandatory object properties'); } @@ -543,10 +574,10 @@ * @param string Property name * @param object DateTime */ - public static function datetime_prop($name, $dt, $utc = false) + public static function datetime_prop($name, $dt, $utc = false, $dateonly = null) { $vdt = new VObject\Property\DateTime($name); - $vdt->setDateTime($dt, $dt->_dateonly ? VObject\Property\DateTime::DATE : + $vdt->setDateTime($dt, $dt->_dateonly || $dateonly ? VObject\Property\DateTime::DATE : ($utc ? VObject\Property\DateTime::UTC : VObject\Property\DateTime::LOCALTZ)); return $vdt; } @@ -645,9 +676,9 @@ if (!empty($event['changed'])) $ve->add(self::datetime_prop('DTSTAMP', $event['changed'], true)); if (!empty($event['start'])) - $ve->add(self::datetime_prop('DTSTART', $event['start'], false)); + $ve->add(self::datetime_prop('DTSTART', $event['start'], false, $event['allday'])); if (!empty($event['end'])) - $ve->add(self::datetime_prop('DTEND', $event['end'], false)); + $ve->add(self::datetime_prop('DTEND', $event['end'], false, $event['allday'])); if (!empty($event['due'])) $ve->add(self::datetime_prop('DUE', $event['due'], false)); @@ -657,7 +688,7 @@ $ve->add('SUMMARY', $event['title']); if ($event['location']) - $ve->add('LOCATION', $event['location']); + $ve->add($this->is_apple() ? new vobject_location_property('LOCATION', $event['location']) : new VObject\Property('LOCATION', $event['location'])); if ($event['description']) $ve->add('DESCRIPTION', strtr($event['description'], array("\r\n" => "\n", "\r" => "\n"))); // normalize line endings @@ -717,7 +748,8 @@ $va = VObject\Component::create('VALARM'); list($trigger, $va->action) = explode(':', $event['alarms']); $val = libcalendaring::parse_alaram_value($trigger); - if ($val[1]) $va->add('TRIGGER', preg_replace('/^([-+])(.+)/', '\\1PT\\2', $trigger)); + $period = $val[1] && preg_match('/[HMS]$/', $val[1]) ? 'PT' : 'P'; + if ($val[1]) $va->add('TRIGGER', preg_replace('/^([-+])P?T?(.+)/', "\\1$period\\2", $trigger)); else $va->add('TRIGGER', gmdate('Ymd\THis\Z', $val[0]), array('VALUE' => 'DATE-TIME')); $ve->add($va); } @@ -807,3 +839,51 @@ } } + + +/** + * Override Sabre\VObject\Property that quotes commas in the location property + * because Apple clients treat that property as list. + */ +class vobject_location_property extends VObject\Property +{ + /** + * Turns the object back into a serialized blob. + * + * @return string + */ + public function serialize() + { + $str = $this->name; + + foreach ($this->parameters as $param) { + $str.=';' . $param->serialize(); + } + + $src = array( + '\\', + "\n", + ',', + ); + $out = array( + '\\\\', + '\n', + '\,', + ); + $str.=':' . str_replace($src, $out, $this->value); + + $out = ''; + while (strlen($str) > 0) { + if (strlen($str) > 75) { + $out.= mb_strcut($str, 0, 75, 'utf-8') . "\r\n"; + $str = ' ' . mb_strcut($str, 75, strlen($str), 'utf-8'); + } else { + $out.= $str . "\r\n"; + $str = ''; + break; + } + } + + return $out; + } +}
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/libcalendaring/tests/libvcalendar.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/libcalendaring/tests/libvcalendar.php
Changed
@@ -85,6 +85,14 @@ $this->assertFalse(array_key_exists('changed', $event), "No changed date field"); } + function test_invalid_vevent() + { + $this->setExpectedException('\Sabre\VObject\ParseException'); + + $ical = new libvcalendar(); + $events = $ical->import_from_file(__DIR__ . '/resources/invalid-event.ics', 'UTF-8', true); + } + /** * Test some extended ical properties such as attendees, recurrence rules, alarms and attachments * @@ -170,6 +178,18 @@ $this->assertContains('dummy', $freebusy['comment'], "Parse comment"); } + function test_vtodo() + { + $ical = new libvcalendar(); + $tasks = $ical->import_from_file(__DIR__ . '/resources/vtodo.ics', 'UTF-8', true); + $task = $tasks[0]; + + $this->assertInstanceOf('DateTime', $task['start'], "'start' property is DateTime object"); + $this->assertInstanceOf('DateTime', $task['due'], "'due' property is DateTime object"); + $this->assertEquals('-1D:DISPLAY', $task['alarms'], "Taks alarm value"); + $this->assertEquals(1, count($task['x-custom']), "Custom properties"); + } + /** * Test for iCal export from internal hash array representation *
View file
roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/libcalendaring/tests/resources/invalid-event.ics
Added
@@ -0,0 +1,14 @@ +BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//Apple Inc.//iCal 5.0.3//EN +CALSCALE:GREGORIAN +BEGIN:VEVENT +CREATED:20130917T000000Z +LAST-MODIFIED:20130755 +UID:C968B885-08FB-40E5-B89E-6FA05F26AACC +TRANSP:TRANSPARENT +SUMMARY:Event with no end date nor duration +DTSTART;VALUE=DATE:20130801 +SEQUENCE:2 +END:VEVENT +END:VCALENDAR
View file
roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/libcalendaring/tests/resources/vtodo.ics
Added
@@ -0,0 +1,38 @@ +BEGIN:VCALENDAR +PRODID:-//Mozilla.org/NONSGML Mozilla Calendar V1.1//EN +VERSION:2.0 +BEGIN:VTIMEZONE +TZID:Europe/Zurich +X-LIC-LOCATION:Europe/Zurich +BEGIN:DAYLIGHT +TZOFFSETFROM:+0100 +TZOFFSETTO:+0200 +TZNAME:CEST +DTSTART:19700329T020000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 +END:DAYLIGHT +BEGIN:STANDARD +TZOFFSETFROM:+0200 +TZOFFSETTO:+0100 +TZNAME:CET +DTSTART:19701025T030000 +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 +END:STANDARD +END:VTIMEZONE +BEGIN:VTODO +LAST-MODIFIED:20130919T075227Z +DTSTAMP:20130919T075227Z +UID:163A577B800E62BFFEF1CEC9DDDE4E11-FCBB6C4091F28CA0 +SUMMARY:My first task today +STATUS:IN-PROCESS +DTSTART;TZID=Europe/Zurich:20130921T000000 +DUE;VALUE=DATE:20130921 +SEQUENCE:2 +X-MOZ-GENERATION:1 +BEGIN:VALARM +ACTION:DISPLAY +TRIGGER;VALUE=DURATION:-P1D +DESCRIPTION:Default Mozilla Description +END:VALARM +END:VTODO +END:VCALENDAR
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/libkolab/lib/kolab_format.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/libkolab/lib/kolab_format.php
Changed
@@ -403,14 +403,22 @@ $this->obj->setLastModified(self::get_datetime($object['changed'], new DateTimeZone('UTC'))); // Save custom properties of the given object - if (!empty($object['x-custom'])) { + if (isset($object['x-custom'])) { $vcustom = new vectorcs; - foreach ($object['x-custom'] as $cp) { + foreach ((array)$object['x-custom'] as $cp) { if (is_array($cp)) $vcustom->push(new CustomProperty($cp[0], $cp[1])); } $this->obj->setCustomProperties($vcustom); } + else { // load custom properties from XML for caching (#2238) + $object['x-custom'] = array(); + $vcustom = $this->obj->customProperties(); + for ($i=0; $i < $vcustom->size(); $i++) { + $cp = $vcustom->get($i); + $object['x-custom'][] = array($cp->identifier, $cp->value); + } + } } /**
View file
roundcubemail-plugins-kolab-3.1.2.tar.gz/plugins/libkolab/lib/kolab_format_xcal.php -> roundcubemail-plugins-kolab-3.1.3.tar.gz/plugins/libkolab/lib/kolab_format_xcal.php
Changed
@@ -385,7 +385,7 @@ if (preg_match('/^@(\d+)/', $offset, $d)) { $alarm->setStart(self::get_datetime($d[1], new DateTimeZone('UTC'))); } - else if (preg_match('/^([-+]?)(\d+)([SMHDW])/', $offset, $d)) { + else if (preg_match('/^([-+]?)P?T?(\d+)([SMHDW])/', $offset, $d)) { $days = $hours = $minutes = $seconds = 0; switch ($d[3]) { case 'W': $days = 7*intval($d[2]); break;
View file
roundcubemail-plugins-kolab.dsc
Changed
@@ -2,7 +2,7 @@ Source: roundcubemail-plugins-kolab Binary: roundcubemail-plugins-kolab Architecture: all -Version: 1:3.1.2-1 +Version: 1:3.1.3-1 Maintainer: Christoph Wickert <wickert@kolabsys.com> Uploaders: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>, Paul Klos <kolab@klos2day.nl> Standards-Version: 3.9.3 @@ -12,5 +12,5 @@ Package-List: roundcubemail-plugins-kolab deb web extra Files: - 00000000000000000000000000000000 0 roundcubemail-plugins-kolab-3.1.2.tar.gz + 00000000000000000000000000000000 0 roundcubemail-plugins-kolab-3.1.3.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
.