From f06aa8058b7e32ba32d4551074b6e0b8a300f751 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Mon, 21 Oct 2013 15:02:40 -0400 Subject: [PATCH] Bump version after security fix --- program/include/rcube_ldap.php | 212 +++++++++++++++++++++++++++++++++++++---------------- 1 files changed, 148 insertions(+), 64 deletions(-) diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index 9f8086b..17ef20a 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -103,32 +103,60 @@ } // use fieldmap to advertise supported coltypes to the application - foreach ($this->fieldmap as $col => $lf) { - list($col, $type) = explode(':', $col); + foreach ($this->fieldmap as $colv => $lfv) { + list($col, $type) = explode(':', $colv); + list($lf, $limit, $delim) = explode(':', $lfv); + + if ($limit == '*') $limit = null; + else $limit = max(1, intval($limit)); + if (!is_array($this->coltypes[$col])) { $subtypes = $type ? array($type) : null; - $this->coltypes[$col] = array('limit' => 1, 'subtypes' => $subtypes); + $this->coltypes[$col] = array('limit' => $limit, 'subtypes' => $subtypes); } elseif ($type) { $this->coltypes[$col]['subtypes'][] = $type; - $this->coltypes[$col]['limit']++; + $this->coltypes[$col]['limit'] += $limit; } + + if ($delim) + $this->coltypes[$col]['serialized'][$type] = $delim; + if ($type && !$this->fieldmap[$col]) - $this->fieldmap[$col] = $lf; + $this->fieldmap[$col] = $lf; + + $this->fieldmap[$colv] = $lf; } // support for composite address if ($this->fieldmap['street'] && $this->fieldmap['locality']) { - $this->coltypes['address'] = array('limit' => max(1, $this->coltypes['locality']['limit']), 'subtypes' => $this->coltypes['locality']['subtypes'], 'childs' => array()); + $this->coltypes['address'] = array( + 'limit' => max(1, $this->coltypes['locality']['limit'] + $this->coltypes['address']['limit']), + 'subtypes' => array_merge((array)$this->coltypes['address']['subtypes'], (array)$this->coltypes['locality']['subtypes']), + 'childs' => array(), + ) + (array)$this->coltypes['address']; + foreach (array('street','locality','zipcode','region','country') as $childcol) { if ($this->fieldmap[$childcol]) { $this->coltypes['address']['childs'][$childcol] = array('type' => 'text'); unset($this->coltypes[$childcol]); // remove address child col from global coltypes list } } + + // at least one address type must be specified + if (empty($this->coltypes['address']['subtypes'])) { + $this->coltypes['address']['subtypes'] = array('home'); + } } else if ($this->coltypes['address']) { - $this->coltypes['address'] = array('type' => 'textarea', 'childs' => null, 'limit' => 1, 'size' => 40); + $this->coltypes['address'] += array('type' => 'textarea', 'childs' => null, 'size' => 40); + + // 'serialized' means the UI has to present a composite address field + if ($this->coltypes['address']['serialized']) { + $childprop = array('type' => 'text'); + $this->coltypes['address']['type'] = 'composite'; + $this->coltypes['address']['childs'] = array('street' => $childprop, 'locality' => $childprop, 'zipcode' => $childprop, 'country' => $childprop); + } } // make sure 'required_fields' is an array @@ -775,24 +803,26 @@ for ($i = 0; $i < $entries['count']; $i++) { $rec = $this->_ldap2result($entries[$i]); - foreach (array('email', 'name') as $f) { - $val = mb_strtolower($rec[$f]); - switch ($mode) { - case 1: - $got = ($val == $search); - break; - case 2: - $got = ($search == substr($val, 0, strlen($search))); - break; - default: - $got = (strpos($val, $search) !== false); - break; - } + foreach ($fields as $f) { + foreach ((array)$rec[$f] as $val) { + $val = mb_strtolower($val); + switch ($mode) { + case 1: + $got = ($val == $search); + break; + case 2: + $got = ($search == substr($val, 0, strlen($search))); + break; + default: + $got = (strpos($val, $search) !== false); + break; + } - if ($got) { - $this->result->add($rec); - $this->result->count++; - break; + if ($got) { + $this->result->add($rec); + $this->result->count++; + break 2; + } } } } @@ -993,10 +1023,13 @@ if ($missing) { // try to complete record automatically if ($autofix) { - $reverse_map = array_flip($this->fieldmap); - $name_parts = preg_split('/[\s,.]+/', $save_data['name']); $sn_field = $this->fieldmap['surname']; $fn_field = $this->fieldmap['firstname']; + $mail_field = $this->fieldmap['email']; + + // try to extract surname and firstname from displayname + $reverse_map = array_flip($this->fieldmap); + $name_parts = preg_split('/[\s,.]+/', $save_data['name']); if ($sn_field && $missing[$sn_field]) { $save_data['surname'] = array_pop($name_parts); @@ -1006,6 +1039,16 @@ if ($fn_field && $missing[$fn_field]) { $save_data['firstname'] = array_shift($name_parts); unset($missing[$fn_field]); + } + + // try to fix missing e-mail, very often on import + // from vCard we have email:other only defined + if ($mail_field && $missing[$mail_field]) { + $emails = $this->get_col_values('email', $save_data, true); + if (!empty($emails) && ($email = array_shift($emails))) { + $save_data['email'] = $email; + unset($missing[$mail_field]); + } } } @@ -1109,6 +1152,14 @@ $ldap_data = $this->_map_data($save_cols); $old_data = $record['_raw_attrib']; + // special handling of photo col + if ($photo_fld = $this->fieldmap['photo']) { + // undefined means keep old photo + if (!array_key_exists('photo', $save_cols)) { + $ldap_data[$photo_fld] = $record['photo']; + } + } + foreach ($this->fieldmap as $col => $fld) { if ($fld) { $val = $ldap_data[$fld]; @@ -1120,6 +1171,9 @@ // make sure comparing array with one element with a string works as expected if (is_array($old) && count($old) == 1 && !is_array($val)) { $old = array_pop($old); + } + if (is_array($val) && count($val) == 1 && !is_array($old)) { + $val = array_pop($val); } // Subentries must be handled separately if (!empty($this->prop['sub_fields']) && isset($this->prop['sub_fields'][$fld])) { @@ -1136,6 +1190,7 @@ } continue; } + // The field does exist compare it to the ldap record. if ($old != $val) { // Changed, but find out how. @@ -1146,8 +1201,11 @@ else if ($val == '') { // Field supplied is empty, verify that it is not required. if (!in_array($fld, $this->prop['required_fields'])) { - // It is not, safe to clear. - $deletedata[$fld] = $old_data[$fld]; + // ...It is not, safe to clear. + // #1488420: Workaround "ldap_mod_del(): Modify: Inappropriate matching in..." + // jpegPhoto attribute require an array() here. It looks to me that it works for other attribs too + $deletedata[$fld] = array(); + //$deletedata[$fld] = $old_data[$fld]; } } else { @@ -1157,18 +1215,9 @@ } // end if } // end if } // end foreach -/* - console($old_data); - console($ldap_data); - console('----'); - console($newdata); - console($replacedata); - console($deletedata); - console('----'); - console($subdata); - console($subnewdata); - console($subdeldata); -*/ + + // console($old_data, $ldap_data, '----', $newdata, $replacedata, $deletedata, '----', $subdata, $subnewdata, $subdeldata); + $dn = self::dn_decode($id); // Update the entry as required. @@ -1456,6 +1505,8 @@ $out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain); else if (in_array($col, array('street','zipcode','locality','country','region'))) $out['address'.($subtype?':':'').$subtype][$i][$col] = $value; + else if ($col == 'address' && strpos($value, '$') !== false) // address data is represented as string separated with $ + list($out[$rf][$i]['street'], $out[$rf][$i]['locality'], $out[$rf][$i]['zipcode'], $out[$rf][$i]['country']) = explode('$', $value); else if ($rec[$lf]['count'] > 1) $out[$rf][] = $value; else @@ -1498,6 +1549,15 @@ } } } + + // if addresses are to be saved as serialized string, do so + if (is_array($colprop['serialized'])) { + foreach ($colprop['serialized'] as $subtype => $delim) { + $key = $col.':'.$subtype; + foreach ((array)$save_cols[$key] as $i => $val) + $save_cols[$key][$i] = join($delim, array($val['street'], $val['locality'], $val['zipcode'], $val['country'])); + } + } } $ldap_data = array(); @@ -1518,17 +1578,21 @@ /** * Returns unified attribute name (resolving aliases) */ - private static function _attr_name($name) + private static function _attr_name($namev) { // list of known attribute aliases - $aliases = array( + static $aliases = array( 'gn' => 'givenname', 'rfc822mailbox' => 'email', 'userid' => 'uid', 'emailaddress' => 'email', 'pkcs9email' => 'email', ); - return isset($aliases[$name]) ? $aliases[$name] : $name; + + list($name, $limit) = explode(':', $namev, 2); + $suffix = $limit ? ':'.$limit : ''; + + return (isset($aliases[$name]) ? $aliases[$name] : $name) . $suffix; } @@ -1673,23 +1737,7 @@ $groups[$group_id]['ID'] = $group_id; $groups[$group_id]['dn'] = $ldap_data[$i]['dn']; $groups[$group_id]['name'] = $group_name; - $groups[$group_id]['member_attr'] = $this->prop['member_attr']; - - // check objectClass attributes of group and act accordingly - for ($j=0; $j < $ldap_data[$i]['objectclass']['count']; $j++) { - switch (strtolower($ldap_data[$i]['objectclass'][$j])) { - case 'group': - case 'groupofnames': - case 'kolabgroupofnames': - $groups[$group_id]['member_attr'] = 'member'; - break; - - case 'groupofuniquenames': - case 'kolabgroupofuniquenames': - $groups[$group_id]['member_attr'] = 'uniqueMember'; - break; - } - } + $groups[$group_id]['member_attr'] = $this->get_group_member_attr($ldap_data[$i]['objectclass']); // list email attributes of a group for ($j=0; $ldap_data[$i][$email_attr] && $j < $ldap_data[$i][$email_attr]['count']; $j++) { @@ -1750,8 +1798,8 @@ $base_dn = $this->groups_base_dn; $new_dn = "cn=$group_name,$base_dn"; $new_gid = self::dn_encode($group_name); - $member_attr = $this->prop['groups']['member_attr']; - $name_attr = $this->prop['groups']['name_attr']; + $member_attr = $this->get_group_member_attr(); + $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; $new_entry = array( 'objectClass' => $this->prop['groups']['object_classes'], @@ -1903,8 +1951,8 @@ $base_dn = $this->groups_base_dn; $contact_dn = self::dn_decode($contact_id); - $name_attr = $this->prop['groups']['name_attr']; - $member_attr = $this->prop['member_attr']; + $name_attr = $this->prop['groups']['name_attr'] ? $this->prop['groups']['name_attr'] : 'cn'; + $member_attr = $this->get_group_member_attr(); $add_filter = ''; if ($member_attr != 'member' && $member_attr != 'uniqueMember') $add_filter = "($member_attr=$contact_dn)"; @@ -1931,6 +1979,42 @@ return $groups; } + /** + * Detects group member attribute name + */ + private function get_group_member_attr($object_classes = array()) + { + if (empty($object_classes)) { + $object_classes = $this->prop['groups']['object_classes']; + } + if (!empty($object_classes)) { + foreach ((array)$object_classes as $oc) { + switch (strtolower($oc)) { + case 'group': + case 'groupofnames': + case 'kolabgroupofnames': + $member_attr = 'member'; + break; + + case 'groupofuniquenames': + case 'kolabgroupofuniquenames': + $member_attr = 'uniqueMember'; + break; + } + } + } + + if (!empty($member_attr)) { + return $member_attr; + } + + if (!empty($this->prop['groups']['member_attr'])) { + return $this->prop['groups']['member_attr']; + } + + return 'member'; + } + /** * Generate BER encoded string for Virtual List View option -- Gitblit v1.9.1