CHANGELOG | ●●●●● patch | view | raw | blame | history | |
config/main.inc.php.dist | ●●●●● patch | view | raw | blame | history | |
program/include/main.inc | ●●●●● patch | view | raw | blame | history | |
program/include/rcube_ldap.php | ●●●●● patch | view | raw | blame | history | |
program/js/app.js | ●●●●● patch | view | raw | blame | history | |
program/steps/addressbook/delete.inc | ●●●●● patch | view | raw | blame | history | |
program/steps/addressbook/edit.inc | ●●●●● patch | view | raw | blame | history | |
program/steps/addressbook/func.inc | ●●●●● patch | view | raw | blame | history | |
program/steps/mail/addcontact.inc | ●●●●● patch | view | raw | blame | history |
CHANGELOG
@@ -1,6 +1,14 @@ CHANGELOG RoundCube Webmail --------------------------- 2008/05/07 (davidke/richs) ---------- - Completed LDAP address book support so it can now write to an LDAP server. - Expanded LDAP configuration options to support LDAP server writes. - Modified config/main.inc.php.dist: New Option: $rcmail_config['use_SQL_address_book'] Changed Option: $rcmail_config['ldap_public']['Verisign'] 2008/05/05 (alec) ---------- - Installer: encode special characters in DB username/password (#1485042) config/main.inc.php.dist
@@ -213,9 +213,28 @@ // session domain: .example.org $rcmail_config['session_domain'] = ''; // in order to enable public ldap search, create a config array // like the Verisign example below. if you would like to test, // simply uncomment the Verisign example. // This indicates whether or not to use the SQL address book. // If set to false then it will look at using the first writable LDAP // address book as the primary address book and it will not display the // SQL address book in the 'Address Book' view. $rcmail_config['use_SQL_address_book'] = true; // In order to enable public ldap search, configure an array like the Verisign // example further below. if you would like to test, simply uncomment the example. // // If you are going to use LDAP for individual address books, you will need to // set 'user_specific' to true and use the variables to generate the appropriate DNs to access it. // // The recommended directory structure for LDAP is to store all the address book entries // under the users main entry, e.g.: // // o=root // ou=people // uid=user@domain // mail=contact@contactdomain // // So the base_dn would be uid=%fu,ou=people,o=root // The bind_dn would be the same as based_dn or some super user login. /** * example config for Verisign directory * @@ -223,15 +242,27 @@ * 'name' => 'Verisign.com', * 'hosts' => array('directory.verisign.com'), * 'port' => 389, * 'user_specific' => false, // If true the base_dn, bind_dn and bind_pass default to the user's IMAP login. * // %fu - The full username provided, assumes the username is an email * // address, uses the username_domain value if not an email address. * // %u - The username prior to the '@'. * // %d - The domain name after the '@'. * 'base_dn' => '', * 'bind_dn' => '', * 'bind_pass' => '', * 'writable' => false, // Indicates if we can write to the LDAP directory or not. * // If writable is true then these fields need to be populated: * // LDAP_Object_Classes, required_fields, LDAP_rdn * 'LDAP_Object_Classes' => array("top", "inetOrgPerson"), // To create a new contact these are the object classes to specify (or any other classes you wish to use). * 'required_fields' => array("cn", "sn", "mail"), // The required fields needed to build a new contact as required by the object classes (can include additional fields not required by the object classes). * 'LDAP_rdn' => 'mail', // The RDN field that is used for new entries, this field needs to be one of the search_fields, the base of base_dn is appended to the RDN to insert into the LDAP directory. * 'ldap_version' => 3, // using LDAPv3 * 'search_fields' => array('mail', 'cn'), // fields to search in * 'name_field' => 'cn', // this field represents the contact's name * 'email_field' => 'mail', // this field represents the contact's e-mail * 'surname_field' => 'sn', // this field represents the contact's last name * 'firstname_field' => 'gn', // this field represents the contact's first name * 'sort' => 'cn', // The field to sort the listing by. * 'scope' => 'sub', // search mode: sub|base|list * 'filter' => '', // used for basic listing (if not empty) and will be &'d with search queries. example: status=act * 'fuzzy_search' => true); // server allows wildcard search program/include/main.inc
@@ -602,7 +602,7 @@ { $zebra_class = $c%2 ? 'even' : 'odd'; $table .= sprintf('<tr id="rcmrow%d" class="contact '.$zebra_class.'">'."\n", $row_data[$id_col]); $table .= sprintf('<tr id="rcmrow%s" class="contact '.$zebra_class.'">'."\n", $row_data[$id_col]); // format each col foreach ($a_show_cols as $col) program/include/rcube_ldap.php
@@ -57,6 +57,8 @@ if (preg_match('/^(.+)_field$/', $prop, $matches)) $this->fieldmap[$matches[1]] = $value; $this->sort_col = $p["sort"]; $this->connect(); } @@ -102,11 +104,54 @@ if (is_resource($this->conn)) { $this->ready = true; if (!empty($this->prop['bind_dn']) && !empty($this->prop['bind_pass'])) if ($this->prop["user_specific"]) { // User specific access, generate the proper values to use. global $CONFIG, $RCMAIL; if (empty($this->prop['bind_pass'])) { // No password set, use the users. $this->prop['bind_pass'] = $RCMAIL->decrypt_passwd($_SESSION["password"]); } // end if // Get the pieces needed for variable replacement. // See if the logged in username has an "@" in it. if (is_bool(strstr($_SESSION["username"], "@"))) { // It does not, use the global default. $fu = $_SESSION["username"]."@".$CONFIG["username_domain"]; $u = $_SESSION["username"]; $d = $CONFIG["username_domain"]; } // end if else { // It does. $fu = $_SESSION["username"]; // Get the pieces needed for username and domain. list($u, $d) = explode("@", $_SESSION["username"]); } # end else // Replace the bind_dn variables. $bind_dn = str_replace(array("%fu", "%u", "%d"), array($fu, $u, $d), $this->prop['bind_dn']); $this->prop['bind_dn'] = $bind_dn; // Replace the base_dn variables. $base_dn = str_replace(array("%fu", "%u", "%d"), array($fu, $u, $d), $this->prop['base_dn']); $this->prop['base_dn'] = $base_dn; $this->ready = $this->bind($this->prop['bind_dn'], $this->prop['bind_pass']); } // end if elseif (!empty($this->prop['bind_dn']) && !empty($this->prop['bind_pass'])) $this->ready = $this->bind($this->prop['bind_dn'], $this->prop['bind_pass']); } else raise_error(array('type' => 'ldap', 'message' => "Could not connect to any LDAP server, tried $host:{$this->prop[port]} last"), true); // See if the directory is writeable. if ($this->prop['writable']) { $this->readonly = false; } // end if } @@ -211,7 +256,7 @@ * List the current set of contact records * * @param array List of cols to show * @param int Only return this number of records (not implemented) * @param int Only return this number of records * @return array Indexed list of contact records, each a hash array */ function list_records($cols=null, $subset=0) @@ -236,8 +281,12 @@ if ($this->sort_col && $this->prop['scope'] !== "base") @ldap_sort($this->conn, $this->ldap_result, $this->sort_col); $start_row = $subset < 0 ? $this->result->first + $this->page_size + $subset : $this->result->first; $last_row = $this->result->first + $this->page_size; $last_row = $subset != 0 ? $start_row + abs($subset) : $last_row; $entries = ldap_get_entries($this->conn, $this->ldap_result); for ($i = $this->result->first; $i < min($entries['count'], $this->result->first + $this->page_size); $i++) for ($i = $start_row; $i < min($entries['count'], $last_row); $i++) $this->result->add($this->_ldap2result($entries[$i])); } @@ -313,8 +362,20 @@ function count() { $count = 0; if ($this->conn && $this->ldap_result) if ($this->conn && $this->ldap_result) { $count = ldap_count_entries($this->conn, $this->ldap_result); } // end if elseif ($this->conn) { // We have a connection but no result set, attempt to get one. if (empty($this->filter)) { // The filter is not set, set it. $this->filter = $this->prop['filter']; } // end if $this->_exec_search(); if ($this->ldap_result) { $count = ldap_count_entries($this->conn, $this->ldap_result); } // end if } // end else return new rcube_result_set($count, ($this->list_page-1) * $this->page_size); } @@ -348,6 +409,8 @@ if ($entry && ($rec = ldap_get_attributes($this->conn, $entry))) { // Add in the dn for the entry. $rec["dn"] = base64_decode($dn); $res = $this->_ldap2result($rec); $this->result = new rcube_result_set(1); $this->result->add($res); @@ -362,12 +425,39 @@ * Create a new contact record * * @param array Hash array with save data * @return boolean The create record ID on success, False on error * @return encoded record ID on success, False on error */ function insert($save_cols) { // TODO // Map out the column names to their LDAP ones to build the new entry. $newentry = array(); $newentry["objectClass"] = $this->prop["LDAP_Object_Classes"]; foreach ($save_cols as $col => $val) { $fld = ""; $fld = $this->_map_field($col); if ($fld != "") { // The field does exist, add it to the entry. $newentry[$fld] = $val; } // end if } // end foreach // Verify that the required fields are set. // We know that the email address is required as a default of rcube, so // we will default its value into any unfilled required fields. foreach ($this->prop["required_fields"] as $fld) { if (!isset($newentry[$fld])) { $newentry[$fld] = $newentry[$this->_map_field("email")]; } // end if } // end foreach // Build the new entries DN. $dn = $this->prop["LDAP_rdn"]."=".$newentry[$this->prop["LDAP_rdn"]].",".$this->prop['base_dn']; $res = @ldap_add($this->conn, $dn, $newentry); if ($res === FALSE) { return false; } // end if return base64_encode($dn); } @@ -380,8 +470,66 @@ */ function update($id, $save_cols) { // TODO $record = $this->get_record($id, true); $result = $this->get_result(); $record = $result->first(); $newdata = array(); $replacedata = array(); $deletedata = array(); foreach ($save_cols as $col => $val) { $fld = ""; $fld = $this->_map_field($col); if ($fld != "") { // The field does exist compare it to the ldap record. if ($record[$col] != $val) { // Changed, but find out how. if (!isset($record[$col])) { // Field was not set prior, need to add it. $newdata[$fld] = $val; } // end if elseif ($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] = $record[$col]; } // end if } // end elseif else { // The data was modified, save it out. $replacedata[$fld] = $val; } // end else } // end if } // end if } // end foreach // Update the entry as required. $dn = base64_decode($id); if (!empty($deletedata)) { // Delete the fields. $res = @ldap_mod_del($this->conn, $dn, $deletedata); if ($res === FALSE) { return false; } // end if } // end if if (!empty($replacedata)) { // Replace the fields. $res = @ldap_mod_replace($this->conn, $dn, $replacedata); if ($res === FALSE) { return false; } // end if } // end if if (!empty($newdata)) { // Add the fields. $res = @ldap_mod_add($this->conn, $dn, $newdata); if ($res === FALSE) { return false; } // end if } // end if return true; } @@ -393,8 +541,21 @@ */ function delete($ids) { // TODO if (!is_array($ids)) { // Not an array, break apart the encoded DNs. $dns = explode(",", $ids); } // end if foreach ($dns as $id) { $dn = base64_decode($id); // Delete the record. $res = @ldap_delete($this->conn, $dn); if ($res === FALSE) { return false; } // end if } // end foreach return true; } program/js/app.js
@@ -2476,7 +2476,7 @@ qs += '&_search='+this.env.search_request; // send request to server this.http_post('delete', '_cid='+urlencode(a_cids.join(','))+'&_from='+(this.env.action ? this.env.action : '')+qs); this.http_post('delete', '_cid='+urlencode(a_cids.join(','))+'&_source='+urlencode(this.env.source)+'&_from='+(this.env.action ? this.env.action : '')+qs); return true; }; program/steps/addressbook/delete.inc
@@ -19,7 +19,10 @@ */ if (($cid = get_input_value('_cid', RCUBE_INPUT_POST)) && preg_match('/^[0-9]+(,[0-9]+)*$/', $cid)) if (($cid = get_input_value('_cid', RCUBE_INPUT_POST)) && (preg_match('/^[0-9]+(,[0-9]+)*$/', $cid) || preg_match('/^[a-zA-Z0-9=]+(,[a-zA-Z0-9=]+)*$/', $cid)) ) { $deleted = $CONTACTS->delete($cid); if (!$deleted) program/steps/addressbook/edit.inc
@@ -91,6 +91,7 @@ { $hiddenfields = new html_hiddenfield(array('name' => '_task', 'value' => $RCMAIL->task)); $hiddenfields->add(array('name' => '_action', 'value' => 'save', 'source' => get_input_value('_source', RCUBE_INPUT_GPC))); $hiddenfields->add(array('name' => '_source', 'value' => get_input_value('_source', RCUBE_INPUT_GPC))); if (($result = $CONTACTS->get_result()) && ($record = $result->first())) $hiddenfields->add(array('name' => '_cid', 'value' => $record['ID'])); program/steps/addressbook/func.inc
@@ -22,8 +22,17 @@ // instantiate a contacts object according to the given source if (($source = get_input_value('_source', RCUBE_INPUT_GPC)) && isset($CONFIG['ldap_public'][$source])) $CONTACTS = new rcube_ldap($CONFIG['ldap_public'][$source]); else else { if (!$CONFIG["use_SQL_address_book"]) { // Get the first LDAP address book. $source = key((array)$CONFIG['ldap_public']); $prop = current((array)$CONFIG['ldap_public']); $CONTACTS = new rcube_ldap($prop); } // end if else { $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']); } // end else } // end else $CONTACTS->set_pagesize($CONFIG['pagesize']); @@ -42,9 +51,13 @@ $OUTPUT->set_env('readonly', $CONTACTS->readonly, false); // add list of address sources to client env $js_list = array(); if ($CONFIG["use_SQL_address_book"]) { // We are using the DB address book, add it. $js_list = array("0" => array('id' => 0, 'readonly' => false)); } // end if foreach ((array)$CONFIG['ldap_public'] as $id => $prop) $js_list[$id] = array('id' => $id, 'readonly' => !$prop['writeable']); $js_list[$id] = array('id' => $id, 'readonly' => !$prop['writable']); $OUTPUT->set_env('address_sources', $js_list); @@ -66,6 +79,7 @@ // allow the following attributes to be added to the <ul> tag $out = '<ul' . create_attrib_string($attrib, array('style', 'class', 'id')) . ">\n"; if ($CONFIG["use_SQL_address_book"]) { $out .= sprintf($line_templ, 'rcmli'.$local_id, !$current ? 'selected' : '', @@ -79,6 +93,14 @@ JS_OBJECT_NAME, $local_id, rcube_label('personaladrbook')); } // end if else { // DB address book not used, see if a source is set, if not use the // first LDAP directory. if (!$current) { $current = key((array)$CONFIG['ldap_public']); } // end if } // end else foreach ((array)$CONFIG['ldap_public'] as $id => $prop) { program/steps/mail/addcontact.inc
@@ -23,7 +23,19 @@ if (!empty($_POST['_address'])) { $CONTACTS = array(); if (!$CONFIG["use_SQL_address_book"]) { // Use the first writable LDAP address book. foreach ($CONFIG["ldap_public"] as $id => $prop) { if ($prop["writable"]) { $CONTACTS = new rcube_ldap($prop); break; } // end if } // end foreach } // end if else { $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']); } // end else $contact_arr = $IMAP->decode_address_list(get_input_value('_address', RCUBE_INPUT_POST, true), 1, false); if (!empty($contact_arr[1]['mailto']))