Source for file abook_ldap_server.php
Documentation is available at abook_ldap_server.php
* Address book backend for LDAP server
* LDAP filtering code by Tim Bell
* <bhat at users.sourceforge.net> (#539534)
* ADS limit_scope code by Michael Brown
* <mcb30 at users.sourceforge.net> (#1035454)
* StartTLS code by John Lane
* <starfry at users.sourceforge.net> (#1197703)
* Code for remove, add, modify, lookup by David Härdeman
* <david at 2gen.com> (#1495763)
* This backend uses LDAP person (RFC2256), organizationalPerson (RFC2256)
* and inetOrgPerson (RFC2798) objects and dn, description, sn, givenname,
* cn, mail attributes. Other attributes are ignored.
* @copyright © 1999-2006 The SquirrelMail Project Team
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
* @version $Id: abook_ldap_server.php,v 1.42 2006/06/04 12:42:24 tokul Exp $
* @subpackage addressbook
* Address book backend for LDAP server
* An array with the following elements must be passed to
* the class constructor (elements marked ? are optional)
* host => LDAP server hostname, IP-address or any other URI compatible
* with used LDAP library.
* base => LDAP server root (base dn). Empty string allowed.
* ? port => LDAP server TCP port number (default: 389)
* ? charset => LDAP server charset (default: utf-8)
* ? name => Name for LDAP server (default "LDAP: hostname")
* Used to tag the result data
* ? maxrows => Maximum # of rows in search result
* ? timeout => Timeout for LDAP operations (in seconds, default: 30)
* Might not work for all LDAP libraries or servers.
* ? binddn => LDAP Bind DN.
* ? bindpw => LDAP Bind Password.
* ? protocol => LDAP Bind protocol.
* ? filter => Filter expression to limit ldap searches
* ? limit_scope => Limits scope to base DN (Specific to Win2k3 ADS).
* ? listing => Controls listing of LDAP directory.
* ? writeable => Controls write access to address book
* ? search_tree => Controls subtree or one level search.
* ? starttls => Controls use of StartTLS on LDAP connections
* NOTE. This class should not be used directly. Use addressbook_init()
* @subpackage addressbook
* @var string backend type
* @var string backend name
/* Parameters changed by class */
* @var string displayed name
var $sname =
'LDAP'; /* Service name */
* @var string LDAP server name or address or url
* @var integer LDAP server port
* @var string LDAP base DN
* @var string charset used for entries in LDAP server
* @var object PHP LDAP link ID
* @var bool True if LDAP server is bound
* @var integer max rows in result
* @var string ldap filter
* @var integer timeout of LDAP operations (in seconds)
* @var string DN to bind to (non-anonymous bind)
* @var string password to bind with (non-anonymous bind)
* @var integer protocol used to connect to ldap server
* @var boolean limits scope to base dn
* @var boolean controls listing of directory
* @var boolean true if removing/adding/modifying entries is allowed
* @var boolean controls ldap search type.
* only first level entries are displayed if set to false
* @var boolean controls use of StartTLS on ldap
* connections. Requires php 4.2+ and protocol >= 3
* Constructor. Connects to database
* @param array connection options
$this->set_error(_("PHP install does not have LDAP support."));
$this->server =
$param['host'];
// remove whitespace from basedn
if(!empty($param['port']))
$this->port =
$param['port'];
if(!empty($param['charset']))
if(isset
($param['maxrows']))
$this->maxrows =
$param['maxrows'];
if(isset
($param['timeout']))
$this->timeout =
$param['timeout'];
if(isset
($param['binddn']))
$this->binddn =
$param['binddn'];
if(isset
($param['bindpw']))
$this->bindpw =
$param['bindpw'];
if(isset
($param['protocol']))
$this->protocol = (int)
$param['protocol'];
if(isset
($param['filter']))
if(isset
($param['limit_scope']))
if(isset
($param['listing']))
$this->listing = (bool)
$param['listing'];
if(isset
($param['writeable'])) {
$this->writeable = (bool)
$param['writeable'];
// switch backend type to local, if it is writable
if(isset
($param['search_tree']))
if(isset
($param['starttls']))
$this->starttls = (bool)
$param['starttls'];
if(empty($param['name'])) {
$this->sname =
'LDAP: ' .
$param['host'];
$this->sname =
$param['name'];
* don't open LDAP server on addressbook_init(),
* open ldap connection only on search. Speeds up
* addressbook_init() call.
$this->set_error('Invalid argument to constructor');
* @param bool $new is it a new connection
function open($new =
false) {
/* Connection is already open */
if($this->linkid !=
false &&
!$new) {
* check if connection was successful
* It does not work with OpenLDAP 2.x libraries. Connect error will be
* displayed only on ldap command that tries to make connection
* (ldap_start_tls or ldap_bind).
// make sure that ldap_set_option() is available before using it
!@ldap_set_option($this->linkid, LDAP_OPT_PROTOCOL_VERSION, $this->protocol)) {
return $this->set_error('unable to set ldap protocol number');
* http://www.php.net/ldap-start-tls
* Check if v3 or newer protocol is used,
* check if ldap_start_tls function is available.
* Silently ignore setting, if these requirements are not satisfied.
* Break with error message if somebody tries to start TLS on
* ldaps or socket connection.
// make sure that $this->server is not ldaps:// or ldapi:// URL.
return $this->set_error("you can't enable starttls on ldaps and ldapi connections.");
if (! @ldap_start_tls($this->linkid)) {
// set error if call fails
return $this->set_error('limit_scope requires protocol >= 3');
// See http://msdn.microsoft.com/library/en-us/ldap/ldap/ldap_server_domain_scope_oid.asp
$ctrl =
array ( "oid" =>
"1.2.840.113556.1.4.1339", "iscritical" =>
TRUE );
* Option is set only during connection.
* It does not cause immediate errors with OpenLDAP 2.x libraries.
!@ldap_set_option($this->linkid, LDAP_OPT_SERVER_CONTROLS, array($ctrl))) {
if(!@ldap_bind($this->linkid)) {
* Encode string to the charset used by this LDAP server
* @param string string that has to be encoded
* @return string encoded string
if($this->charset !=
$default_charset) {
* Decode from charset used by this LDAP server to charset used by translation
* Uses SquirrelMail charset_decode functions
* @param string string that has to be decoded
* @return string decoded string
if ($this->charset !=
$default_charset) {
* Sanitizes ldap search strings.
* @link http://www.faqs.org/rfcs/rfc2254.html
* @return string sanitized string
$sanitized=
array('\\' =>
'\5c',
* Prepares user input for use in a ldap query.
* Function converts input string to character set used in LDAP server
* (charset_encode() method) and sanitizes it (ldapspecialchars()).
* @param string $string string to encode
* @return string ldap encoded string
* Warning: You must make sure that ldap query is correctly formated and
* sanitize use of special ldap keywords.
* @param string $expression ldap query
* @param boolean $singleentry (since 1.5.2) whether we are looking for a
* single entry. Boolean true forces LDAP_SCOPE_BASE search.
* @return array search results (false on error)
function ldap_search($expression, $singleentry =
false) {
/* Make sure connection is there */
$attributes =
array('dn', 'description', 'sn', 'givenname', 'cn', 'mail');
// ldap_read - search for one single entry
$sret =
@ldap_read($this->linkid, $expression, "objectClass=*",
// ldap_search - search subtree
// ldap_list - search one level
$sret =
@ldap_list($this->linkid, $this->basedn, $expression,
/* Return error if search failed */
// Check for LDAP_NO_SUCH_OBJECT (0x20 or 32) error
if (ldap_errno($this->linkid)==
32) {
if(@ldap_count_entries($this->linkid, $sret) <=
0) {
$res =
@ldap_get_entries($this->linkid, $sret);
for($i =
0 ; $i <
$res['count'] ; $i++
) {
/* Extract data common for all e-mail addresses
* of an object. Use only the first name */
* remove whitespaces between RDNs
* which gives nicknames which are shorter while still unique
if($offset >
0 &&
substr($nickname, $offset) ==
$this->basedn) {
$nickname =
substr($nickname, 0, $offset);
if(substr($nickname, -
1) ==
",")
$nickname =
substr($nickname, 0, -
1);
$nickname=
substr($nickname, 3);
if(empty($row['description'][0])) {
if(empty($row['givenname'][0])) {
if(empty($row['sn'][0])) {
// remove whitespace in order to handle sn set to empty string
$fullname =
$this->fullname($firstname,$surname);
/* Add one row to result for each e-mail address */
if(isset
($row['mail']['count'])) {
for($j =
0 ; $j <
$row['mail']['count'] ; $j++
) {
'firstname' =>
$firstname,
'email' =>
$row['mail'][$j],
'backend' =>
$this->bnum,
'source' =>
&$this->sname));