| | |
| | | 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) |
| | |
| | | // 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 |
| | | * |
| | |
| | | * '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 |
| | |
| | | { |
| | | $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) |
| | |
| | | foreach ($p as $prop => $value) |
| | | if (preg_match('/^(.+)_field$/', $prop, $matches)) |
| | | $this->fieldmap[$matches[1]] = $value; |
| | | |
| | | |
| | | $this->sort_col = $p["sort"]; |
| | | |
| | | $this->connect(); |
| | | } |
| | | |
| | |
| | | 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 |
| | | |
| | | } |
| | | |
| | | |
| | |
| | | * 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) |
| | |
| | | { |
| | | 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])); |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | |
| | | |
| | | 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); |
| | |
| | | * 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 |
| | | return false; |
| | | // 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); |
| | | } |
| | | |
| | | |
| | |
| | | */ |
| | | function update($id, $save_cols) |
| | | { |
| | | // TODO |
| | | return false; |
| | | $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; |
| | | } |
| | | |
| | | |
| | |
| | | */ |
| | | function delete($ids) |
| | | { |
| | | // TODO |
| | | return false; |
| | | 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; |
| | | } |
| | | |
| | | |
| | |
| | | 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; |
| | | }; |
| | | |
| | |
| | | |
| | | */ |
| | | |
| | | 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) |
| | |
| | | { |
| | | $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'])); |
| | |
| | | |
| | | // this will be executed if no template for addcontact exists |
| | | $OUTPUT->send('editcontact'); |
| | | ?> |
| | | ?> |
| | |
| | | // 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 |
| | | $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']); |
| | | 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']); |
| | | |
| | |
| | | $OUTPUT->set_env('readonly', $CONTACTS->readonly, false); |
| | | |
| | | // add list of address sources to client env |
| | | $js_list = array("0" => array('id' => 0, 'readonly' => false)); |
| | | $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); |
| | | |
| | | |
| | |
| | | |
| | | // allow the following attributes to be added to the <ul> tag |
| | | $out = '<ul' . create_attrib_string($attrib, array('style', 'class', 'id')) . ">\n"; |
| | | $out .= sprintf($line_templ, |
| | | 'rcmli'.$local_id, |
| | | !$current ? 'selected' : '', |
| | | Q(rcmail_url('list', array('_source' => 0))), |
| | | JS_OBJECT_NAME, |
| | | $local_id, |
| | | JS_OBJECT_NAME, |
| | | $local_id, |
| | | JS_OBJECT_NAME, |
| | | $local_id, |
| | | JS_OBJECT_NAME, |
| | | $local_id, |
| | | rcube_label('personaladrbook')); |
| | | if ($CONFIG["use_SQL_address_book"]) { |
| | | $out .= sprintf($line_templ, |
| | | 'rcmli'.$local_id, |
| | | !$current ? 'selected' : '', |
| | | Q(rcmail_url('list', array('_source' => 0))), |
| | | JS_OBJECT_NAME, |
| | | $local_id, |
| | | JS_OBJECT_NAME, |
| | | $local_id, |
| | | JS_OBJECT_NAME, |
| | | $local_id, |
| | | 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) |
| | | { |
| | |
| | | |
| | | if (!empty($_POST['_address'])) |
| | | { |
| | | $CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']); |
| | | $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'])) |
| | |
| | | $OUTPUT->show_message('errorsavingcontact', 'warning'); |
| | | |
| | | $OUTPUT->send(); |
| | | ?> |
| | | ?> |