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));
if(($returned_rows >=
$this->maxrows) &&
} // isset($row['mail']['count'])
* Add an entry to LDAP server.
* Warning: You must make sure that the arguments are correctly formated and
* sanitize use of special ldap keywords.
* @param string $dn the dn of the entry to be added
* @param array $data the values of the entry to be added
* @return boolean result (false on error)
/* Make sure connection is there */
$this->set_error(_("Write to address book failed"));
* Remove an entry from LDAP server.
* Warning: You must make sure that the argument is correctly formated and
* sanitize use of special ldap keywords.
* @param string $dn the dn of the entry to remove
* @return boolean result (false on error)
/* Make sure connection is there */
if(!@ldap_delete($this->linkid, $dn)) {
$this->set_error(_("Removing entry from address book failed"));
* Rename an entry on LDAP server.
* Warning: You must make sure that the arguments are correctly formated and
* sanitize use of special ldap keywords.
* @param string $sourcedn the dn of the entry to be renamed
* @param string $targetdn the dn which $sourcedn should be renamed to
* @param string $parent the dn of the parent entry
* @return boolean result (false on error)
/* Make sure connection is there */
/* Make sure that the protocol version supports rename */
$this->set_error(_("LDAP rename is not supported by used protocol version"));
* Function is available only in OpenLDAP 2.x.x or Netscape Directory
* SDK x.x, and was added in PHP 4.0.5
* @todo maybe we can use copy + delete instead of ldap_rename()
$this->set_error(_("LDAP rename is not supported by used LDAP library. You can't change nickname"));
* Modify the values of an entry on LDAP server.
* Warning: You must make sure that the arguments are correctly formated and
* sanitize use of special ldap keywords.
* @param string $dn the dn of the entry to be modified
* @param array $data the new values of the entry
* @param array $deleted_attribs attributes that should be deleted.
* @return bool result (false on error)
/* Make sure connection is there */
$this->set_error(_("Write to address book failed"));
if (!@ldap_mod_del($this->linkid, $dn, $deleted_attribs)) {
$this->set_error(_("Unable to remove some field values"));
* Get error from LDAP resource if possible
* Should get error from server using the ldap_errno() and ldap_err2str() functions
* @param string $sError error message used when ldap error functions
* and connection resource are unavailable
* @return string error message
// it is possible that function_exists() tests are not needed
return ldap_err2str(ldap_errno($this->linkid));
// return ldap_error($this->linkid);
/* ========================== Public ======================== */
* @param string $expr search expression
* @return array search results
/* To be replaced by advanded search expression parsing */
// don't allow wide search when listing is disabled.
if ($expr==
'*' &&
! $this->listing) {
// allow use of wildcard when listing is enabled.
/* Convert search from user's charset to the one used in ldap and sanitize */
/* Search for same string in cn, main and sn */
$expression =
'(|(cn=*'.
$expr.
'*)(mail=*'.
$expr.
'*)(sn=*'.
$expr.
'*))';
/* Undo sanitizing of * symbol */
/* Add search filtering */
$expression =
'(&' .
$this->filter .
$expression .
')';
/* Use internal search function and return search results */
* @param string $alias alias
* @return array search results
/* Generate the dn and try to retrieve that single entry */
$dn =
'cn=' .
$cn .
',' .
$this->basedn;
* List all entries present in LDAP server
* maxrows setting might limit list of returned entries.
* Careful with this -- it could get quite large for big sites.
* @return array all entries in ldap server
/* set wide search expression */
$expression =
'(&' .
$this->filter .
$expression .
')';
/* use internal search function and return search results */
* @param array $userdata new data
function add($userdata) {
return $this->set_error(_("Address book is read-only"));
/* Convert search from user's charset to the one used in ldap and sanitize */
/* See if user exists already */
} elseif (count($user) >
0) {
return $this->set_error(sprintf(_("User \"%s\" already exists"), $userdata['nickname']));
$data['mail'] =
$this->quotevalue($userdata['email']);
$data["objectclass"][0] =
"top";
$data["objectclass"][1] =
"person";
$data["objectclass"][2] =
"organizationalPerson";
$data["objectclass"][3] =
"inetOrgPerson";
/* sn is required in person object */
if(!empty($userdata['lastname'])) {
$data['sn'] =
$this->quotevalue($userdata['lastname']);
if(!empty($userdata['firstname']))
$data['givenName'] =
$this->quotevalue($userdata['firstname']);
if(!empty($userdata['label'])) {
$data['description'] =
$this->quotevalue($userdata['label']);
* @param array $aliases array of entries that have to be removed.
return $this->set_error(_("Address book is read-only"));
foreach ($aliases as $alias) {
/* Convert nickname from user's charset and derive cn/dn */
$dn =
'cn=' .
$cn .
',' .
$this->basedn;
* @param string $alias modified alias
* @param array $userdata new data
function modify($alias, $userdata) {
return $this->set_error(_("Address book is read-only"));
/* Convert search from user's charset to the one used in ldap and sanitize */
$sourcedn =
'cn=' .
$sourcecn .
',' .
trim($this->basedn);
$targetcn =
$this->quotevalue($userdata['nickname']);
$targetdn =
'cn=' .
$targetcn .
',' .
trim($this->basedn);
/* Check that the dn to modify exists */
$sourceuser =
$this->lookup($alias);
/* Check if dn is going to change */
if ($alias !=
$userdata['nickname']) {
/* Check that the target dn doesn't exist */
$targetuser =
$this->lookup($userdata['nickname']);
return $this->set_error(sprintf(_("User \"%s\" already exists"), $userdata['nickname']));
/* Rename from the source dn to target dn */
return $this->set_error(sprintf(_("Unable to rename user \"%s\" to \"%s\""), $alias, $userdata['nickname']));
$deleted_attribs =
array();
$data['mail'] =
$this->quotevalue($userdata['email']);
$data["objectclass"][0] =
"top";
$data["objectclass"][1] =
"person";
$data["objectclass"][2] =
"organizationalPerson";
$data["objectclass"][3] =
"inetOrgPerson";
if(!empty($userdata['firstname'])) {
$data['givenName'] =
$this->quotevalue($userdata['firstname']);
} elseif (!empty($sourceuser['firstname'])) {
$deleted_attribs['givenName'] =
$this->quotevalue($sourceuser['firstname']);
if(!empty($userdata['lastname'])) {
$data['sn'] =
$this->quotevalue($userdata['lastname']);
// sn is required attribute in LDAP person object.
// SquirrelMail requires givenName or Surname
if(!empty($userdata['label'])) {
$data['description'] =
$this->quotevalue($userdata['label']);
} elseif (!empty($sourceuser['label'])) {
$deleted_attribs['description'] =
$this->quotevalue($sourceuser['label']);
return $this->ldap_modify($targetdn, $data, $deleted_attribs);
Documentation generated on Sat, 07 Oct 2006 16:08:31 +0300 by phpDocumentor 1.3.0RC6