web/lib/Zend/Ldap.php
changeset 64 162c1de6545a
parent 19 1c2f13fd785c
child 68 ecaf28ffe26e
equal deleted inserted replaced
63:5b37998e522e 64:162c1de6545a
       
     1 <?php
       
     2 
       
     3 /**
       
     4  * Zend Framework
       
     5  *
       
     6  * LICENSE
       
     7  *
       
     8  * This source file is subject to the new BSD license that is bundled
       
     9  * with this package in the file LICENSE.txt.
       
    10  * It is also available through the world-wide-web at this URL:
       
    11  * http://framework.zend.com/license/new-bsd
       
    12  * If you did not receive a copy of the license and are unable to
       
    13  * obtain it through the world-wide-web, please send an email
       
    14  * to license@zend.com so we can send you a copy immediately.
       
    15  *
       
    16  * @category   Zend
       
    17  * @package    Zend_Ldap
       
    18  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    19  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    20  * @version    $Id: Ldap.php 22996 2010-09-22 17:01:46Z sgehrig $
       
    21  */
       
    22 
       
    23 /**
       
    24  * @category   Zend
       
    25  * @package    Zend_Ldap
       
    26  * @copyright  Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
       
    27  * @license    http://framework.zend.com/license/new-bsd     New BSD License
       
    28  */
       
    29 class Zend_Ldap
       
    30 {
       
    31     const SEARCH_SCOPE_SUB  = 1;
       
    32     const SEARCH_SCOPE_ONE  = 2;
       
    33     const SEARCH_SCOPE_BASE = 3;
       
    34 
       
    35     const ACCTNAME_FORM_DN        = 1;
       
    36     const ACCTNAME_FORM_USERNAME  = 2;
       
    37     const ACCTNAME_FORM_BACKSLASH = 3;
       
    38     const ACCTNAME_FORM_PRINCIPAL = 4;
       
    39 
       
    40     /**
       
    41      * String used with ldap_connect for error handling purposes.
       
    42      *
       
    43      * @var string
       
    44      */
       
    45     private $_connectString;
       
    46 
       
    47     /**
       
    48      * The options used in connecting, binding, etc.
       
    49      *
       
    50      * @var array
       
    51      */
       
    52     protected $_options = null;
       
    53 
       
    54     /**
       
    55      * The raw LDAP extension resource.
       
    56      *
       
    57      * @var resource
       
    58      */
       
    59     protected $_resource = null;
       
    60 
       
    61     /**
       
    62      * FALSE if no user is bound to the LDAP resource
       
    63      * NULL if there has been an anonymous bind
       
    64      * username of the currently bound user
       
    65      *
       
    66      * @var boolean|null|string
       
    67      */
       
    68     protected $_boundUser = false;
       
    69 
       
    70     /**
       
    71      * Caches the RootDSE
       
    72      *
       
    73      * @var Zend_Ldap_Node
       
    74      */
       
    75     protected $_rootDse = null;
       
    76 
       
    77     /**
       
    78      * Caches the schema
       
    79      *
       
    80      * @var Zend_Ldap_Node
       
    81      */
       
    82     protected $_schema = null;
       
    83 
       
    84     /**
       
    85      * @deprecated will be removed, use {@see Zend_Ldap_Filter_Abstract::escapeValue()}
       
    86      * @param  string $str The string to escape.
       
    87      * @return string The escaped string
       
    88      */
       
    89     public static function filterEscape($str)
       
    90     {
       
    91         /**
       
    92          * @see Zend_Ldap_Filter_Abstract
       
    93          */
       
    94         require_once 'Zend/Ldap/Filter/Abstract.php';
       
    95         return Zend_Ldap_Filter_Abstract::escapeValue($str);
       
    96     }
       
    97 
       
    98     /**
       
    99      * @deprecated will be removed, use {@see Zend_Ldap_Dn::checkDn()}
       
   100      * @param  string $dn   The DN to parse
       
   101      * @param  array  $keys An optional array to receive DN keys (e.g. CN, OU, DC, ...)
       
   102      * @param  array  $vals An optional array to receive DN values
       
   103      * @return boolean True if the DN was successfully parsed or false if the string is
       
   104      * not a valid DN.
       
   105      */
       
   106     public static function explodeDn($dn, array &$keys = null, array &$vals = null)
       
   107     {
       
   108         /**
       
   109          * @see Zend_Ldap_Dn
       
   110          */
       
   111         require_once 'Zend/Ldap/Dn.php';
       
   112         return Zend_Ldap_Dn::checkDn($dn, $keys, $vals);
       
   113     }
       
   114 
       
   115     /**
       
   116      * Constructor.
       
   117      *
       
   118      * @param  array|Zend_Config $options Options used in connecting, binding, etc.
       
   119      * @return void
       
   120      * @throws Zend_Ldap_Exception if ext/ldap is not installed
       
   121      */
       
   122     public function __construct($options = array())
       
   123     {
       
   124         if (!extension_loaded('ldap')) {
       
   125             /**
       
   126              * @see Zend_Ldap_Exception
       
   127              */
       
   128             require_once 'Zend/Ldap/Exception.php';
       
   129             throw new Zend_Ldap_Exception(null, 'LDAP extension not loaded',
       
   130                 Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED);
       
   131         }
       
   132         $this->setOptions($options);
       
   133     }
       
   134 
       
   135     /**
       
   136      * Destructor.
       
   137      *
       
   138      * @return void
       
   139      */
       
   140     public function __destruct()
       
   141     {
       
   142         $this->disconnect();
       
   143     }
       
   144 
       
   145     /**
       
   146      * @return resource The raw LDAP extension resource.
       
   147      */
       
   148     public function getResource()
       
   149     {
       
   150         if (!is_resource($this->_resource) || $this->_boundUser === false) {
       
   151             $this->bind();
       
   152         }
       
   153         return $this->_resource;
       
   154     }
       
   155 
       
   156     /**
       
   157      * Return the LDAP error number of the last LDAP command
       
   158      *
       
   159      * @return int
       
   160      */
       
   161     public function getLastErrorCode()
       
   162     {
       
   163         $ret = @ldap_get_option($this->_resource, LDAP_OPT_ERROR_NUMBER, $err);
       
   164         if ($ret === true) {
       
   165             if ($err <= -1 && $err >= -17) {
       
   166                 /**
       
   167                  * @see Zend_Ldap_Exception
       
   168                  */
       
   169                 require_once 'Zend/Ldap/Exception.php';
       
   170                 /* For some reason draft-ietf-ldapext-ldap-c-api-xx.txt error
       
   171                  * codes in OpenLDAP are negative values from -1 to -17.
       
   172                  */
       
   173                 $err = Zend_Ldap_Exception::LDAP_SERVER_DOWN + (-$err - 1);
       
   174             }
       
   175             return $err;
       
   176         }
       
   177         return 0;
       
   178     }
       
   179 
       
   180     /**
       
   181      * Return the LDAP error message of the last LDAP command
       
   182      *
       
   183      * @param  int   $errorCode
       
   184      * @param  array $errorMessages
       
   185      * @return string
       
   186      */
       
   187     public function getLastError(&$errorCode = null, array &$errorMessages = null)
       
   188     {
       
   189         $errorCode = $this->getLastErrorCode();
       
   190         $errorMessages = array();
       
   191 
       
   192         /* The various error retrieval functions can return
       
   193          * different things so we just try to collect what we
       
   194          * can and eliminate dupes.
       
   195          */
       
   196         $estr1 = @ldap_error($this->_resource);
       
   197         if ($errorCode !== 0 && $estr1 === 'Success') {
       
   198             $estr1 = @ldap_err2str($errorCode);
       
   199         }
       
   200         if (!empty($estr1)) {
       
   201             $errorMessages[] = $estr1;
       
   202         }
       
   203 
       
   204         @ldap_get_option($this->_resource, LDAP_OPT_ERROR_STRING, $estr2);
       
   205         if (!empty($estr2) && !in_array($estr2, $errorMessages)) {
       
   206             $errorMessages[] = $estr2;
       
   207         }
       
   208 
       
   209         $message = '';
       
   210         if ($errorCode > 0) {
       
   211             $message = '0x' . dechex($errorCode) . ' ';
       
   212         } else {
       
   213             $message = '';
       
   214         }
       
   215         if (count($errorMessages) > 0) {
       
   216             $message .= '(' . implode('; ', $errorMessages) . ')';
       
   217         } else {
       
   218             $message .= '(no error message from LDAP)';
       
   219         }
       
   220         return $message;
       
   221     }
       
   222 
       
   223     /**
       
   224      * Get the currently bound user
       
   225      *
       
   226      * FALSE if no user is bound to the LDAP resource
       
   227      * NULL if there has been an anonymous bind
       
   228      * username of the currently bound user
       
   229      *
       
   230      * @return false|null|string
       
   231      */
       
   232     public function getBoundUser()
       
   233     {
       
   234         return $this->_boundUser;
       
   235     }
       
   236 
       
   237     /**
       
   238      * Sets the options used in connecting, binding, etc.
       
   239      *
       
   240      * Valid option keys:
       
   241      *  host
       
   242      *  port
       
   243      *  useSsl
       
   244      *  username
       
   245      *  password
       
   246      *  bindRequiresDn
       
   247      *  baseDn
       
   248      *  accountCanonicalForm
       
   249      *  accountDomainName
       
   250      *  accountDomainNameShort
       
   251      *  accountFilterFormat
       
   252      *  allowEmptyPassword
       
   253      *  useStartTls
       
   254      *  optRefferals
       
   255      *  tryUsernameSplit
       
   256      *
       
   257      * @param  array|Zend_Config $options Options used in connecting, binding, etc.
       
   258      * @return Zend_Ldap Provides a fluent interface
       
   259      * @throws Zend_Ldap_Exception
       
   260      */
       
   261     public function setOptions($options)
       
   262     {
       
   263         if ($options instanceof Zend_Config) {
       
   264             $options = $options->toArray();
       
   265         }
       
   266 
       
   267         $permittedOptions = array(
       
   268             'host'                   => null,
       
   269             'port'                   => 0,
       
   270             'useSsl'                 => false,
       
   271             'username'               => null,
       
   272             'password'               => null,
       
   273             'bindRequiresDn'         => false,
       
   274             'baseDn'                 => null,
       
   275             'accountCanonicalForm'   => null,
       
   276             'accountDomainName'      => null,
       
   277             'accountDomainNameShort' => null,
       
   278             'accountFilterFormat'    => null,
       
   279             'allowEmptyPassword'     => false,
       
   280             'useStartTls'            => false,
       
   281             'optReferrals'           => false,
       
   282             'tryUsernameSplit'       => true,
       
   283         );
       
   284 
       
   285         foreach ($permittedOptions as $key => $val) {
       
   286             if (array_key_exists($key, $options)) {
       
   287                 $val = $options[$key];
       
   288                 unset($options[$key]);
       
   289                 /* Enforce typing. This eliminates issues like Zend_Config_Ini
       
   290                  * returning '1' as a string (ZF-3163).
       
   291                  */
       
   292                 switch ($key) {
       
   293                     case 'port':
       
   294                     case 'accountCanonicalForm':
       
   295                         $permittedOptions[$key] = (int)$val;
       
   296                         break;
       
   297                     case 'useSsl':
       
   298                     case 'bindRequiresDn':
       
   299                     case 'allowEmptyPassword':
       
   300                     case 'useStartTls':
       
   301                     case 'optReferrals':
       
   302                     case 'tryUsernameSplit':
       
   303                         $permittedOptions[$key] = ($val === true ||
       
   304                                 $val === '1' || strcasecmp($val, 'true') == 0);
       
   305                         break;
       
   306                     default:
       
   307                         $permittedOptions[$key] = trim($val);
       
   308                         break;
       
   309                 }
       
   310             }
       
   311         }
       
   312         if (count($options) > 0) {
       
   313             $key = key($options);
       
   314             /**
       
   315              * @see Zend_Ldap_Exception
       
   316              */
       
   317             require_once 'Zend/Ldap/Exception.php';
       
   318             throw new Zend_Ldap_Exception(null, "Unknown Zend_Ldap option: $key");
       
   319         }
       
   320         $this->_options = $permittedOptions;
       
   321         return $this;
       
   322     }
       
   323 
       
   324     /**
       
   325      * @return array The current options.
       
   326      */
       
   327     public function getOptions()
       
   328     {
       
   329         return $this->_options;
       
   330     }
       
   331 
       
   332     /**
       
   333      * @return string The hostname of the LDAP server being used to authenticate accounts
       
   334      */
       
   335     protected function _getHost()
       
   336     {
       
   337         return $this->_options['host'];
       
   338     }
       
   339 
       
   340     /**
       
   341      * @return int The port of the LDAP server or 0 to indicate that no port value is set
       
   342      */
       
   343     protected function _getPort()
       
   344     {
       
   345         return $this->_options['port'];
       
   346     }
       
   347 
       
   348     /**
       
   349      * @return boolean The default SSL / TLS encrypted transport control
       
   350      */
       
   351     protected function _getUseSsl()
       
   352     {
       
   353         return $this->_options['useSsl'];
       
   354     }
       
   355 
       
   356     /**
       
   357      * @return string The default acctname for binding
       
   358      */
       
   359     protected function _getUsername()
       
   360     {
       
   361         return $this->_options['username'];
       
   362     }
       
   363 
       
   364     /**
       
   365      * @return string The default password for binding
       
   366      */
       
   367     protected function _getPassword()
       
   368     {
       
   369         return $this->_options['password'];
       
   370     }
       
   371 
       
   372     /**
       
   373      * @return boolean Bind requires DN
       
   374      */
       
   375     protected function _getBindRequiresDn()
       
   376     {
       
   377         return $this->_options['bindRequiresDn'];
       
   378     }
       
   379 
       
   380     /**
       
   381      * Gets the base DN under which objects of interest are located
       
   382      *
       
   383      * @return string
       
   384      */
       
   385     public function getBaseDn()
       
   386     {
       
   387         return $this->_options['baseDn'];
       
   388     }
       
   389 
       
   390     /**
       
   391      * @return integer Either ACCTNAME_FORM_BACKSLASH, ACCTNAME_FORM_PRINCIPAL or
       
   392      * ACCTNAME_FORM_USERNAME indicating the form usernames should be canonicalized to.
       
   393      */
       
   394     protected function _getAccountCanonicalForm()
       
   395     {
       
   396         /* Account names should always be qualified with a domain. In some scenarios
       
   397          * using non-qualified account names can lead to security vulnerabilities. If
       
   398          * no account canonical form is specified, we guess based in what domain
       
   399          * names have been supplied.
       
   400          */
       
   401 
       
   402         $accountCanonicalForm = $this->_options['accountCanonicalForm'];
       
   403         if (!$accountCanonicalForm) {
       
   404             $accountDomainName = $this->_getAccountDomainName();
       
   405             $accountDomainNameShort = $this->_getAccountDomainNameShort();
       
   406             if ($accountDomainNameShort) {
       
   407                 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_BACKSLASH;
       
   408             } else if ($accountDomainName) {
       
   409                 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_PRINCIPAL;
       
   410             } else {
       
   411                 $accountCanonicalForm = Zend_Ldap::ACCTNAME_FORM_USERNAME;
       
   412             }
       
   413         }
       
   414 
       
   415         return $accountCanonicalForm;
       
   416     }
       
   417 
       
   418     /**
       
   419      * @return string The account domain name
       
   420      */
       
   421     protected function _getAccountDomainName()
       
   422     {
       
   423         return $this->_options['accountDomainName'];
       
   424     }
       
   425 
       
   426     /**
       
   427      * @return string The short account domain name
       
   428      */
       
   429     protected function _getAccountDomainNameShort()
       
   430     {
       
   431         return $this->_options['accountDomainNameShort'];
       
   432     }
       
   433 
       
   434     /**
       
   435      * @return string A format string for building an LDAP search filter to match
       
   436      * an account
       
   437      */
       
   438     protected function _getAccountFilterFormat()
       
   439     {
       
   440         return $this->_options['accountFilterFormat'];
       
   441     }
       
   442 
       
   443     /**
       
   444      * @return boolean Allow empty passwords
       
   445      */
       
   446     protected function _getAllowEmptyPassword()
       
   447     {
       
   448         return $this->_options['allowEmptyPassword'];
       
   449     }
       
   450 
       
   451     /**
       
   452      * @return boolean The default SSL / TLS encrypted transport control
       
   453      */
       
   454     protected function _getUseStartTls()
       
   455     {
       
   456         return $this->_options['useStartTls'];
       
   457     }
       
   458 
       
   459     /**
       
   460      * @return boolean Opt. Referrals
       
   461      */
       
   462     protected function _getOptReferrals()
       
   463     {
       
   464         return $this->_options['optReferrals'];
       
   465     }
       
   466 
       
   467     /**
       
   468      * @return boolean Try splitting the username into username and domain
       
   469      */
       
   470     protected function _getTryUsernameSplit()
       
   471     {
       
   472         return $this->_options['tryUsernameSplit'];
       
   473     }
       
   474 
       
   475     /**
       
   476      * @return string The LDAP search filter for matching directory accounts
       
   477      */
       
   478     protected function _getAccountFilter($acctname)
       
   479     {
       
   480         /**
       
   481          * @see Zend_Ldap_Filter_Abstract
       
   482          */
       
   483         require_once 'Zend/Ldap/Filter/Abstract.php';
       
   484         $this->_splitName($acctname, $dname, $aname);
       
   485         $accountFilterFormat = $this->_getAccountFilterFormat();
       
   486         $aname = Zend_Ldap_Filter_Abstract::escapeValue($aname);
       
   487         if ($accountFilterFormat) {
       
   488             return sprintf($accountFilterFormat, $aname);
       
   489         }
       
   490         if (!$this->_getBindRequiresDn()) {
       
   491             // is there a better way to detect this?
       
   492             return sprintf("(&(objectClass=user)(sAMAccountName=%s))", $aname);
       
   493         }
       
   494         return sprintf("(&(objectClass=posixAccount)(uid=%s))", $aname);
       
   495     }
       
   496 
       
   497     /**
       
   498      * @param string $name  The name to split
       
   499      * @param string $dname The resulting domain name (this is an out parameter)
       
   500      * @param string $aname The resulting account name (this is an out parameter)
       
   501      * @return void
       
   502      */
       
   503     protected function _splitName($name, &$dname, &$aname)
       
   504     {
       
   505         $dname = null;
       
   506         $aname = $name;
       
   507 
       
   508         if (!$this->_getTryUsernameSplit()) {
       
   509             return;
       
   510         }
       
   511 
       
   512         $pos = strpos($name, '@');
       
   513         if ($pos) {
       
   514             $dname = substr($name, $pos + 1);
       
   515             $aname = substr($name, 0, $pos);
       
   516         } else {
       
   517             $pos = strpos($name, '\\');
       
   518             if ($pos) {
       
   519                 $dname = substr($name, 0, $pos);
       
   520                 $aname = substr($name, $pos + 1);
       
   521             }
       
   522         }
       
   523     }
       
   524 
       
   525     /**
       
   526      * @param  string $acctname The name of the account
       
   527      * @return string The DN of the specified account
       
   528      * @throws Zend_Ldap_Exception
       
   529      */
       
   530     protected function _getAccountDn($acctname)
       
   531     {
       
   532         /**
       
   533          * @see Zend_Ldap_Dn
       
   534          */
       
   535         require_once 'Zend/Ldap/Dn.php';
       
   536         if (Zend_Ldap_Dn::checkDn($acctname)) return $acctname;
       
   537         $acctname = $this->getCanonicalAccountName($acctname, Zend_Ldap::ACCTNAME_FORM_USERNAME);
       
   538         $acct = $this->_getAccount($acctname, array('dn'));
       
   539         return $acct['dn'];
       
   540     }
       
   541 
       
   542     /**
       
   543      * @param  string $dname The domain name to check
       
   544      * @return boolean
       
   545      */
       
   546     protected function _isPossibleAuthority($dname)
       
   547     {
       
   548         if ($dname === null) {
       
   549             return true;
       
   550         }
       
   551         $accountDomainName = $this->_getAccountDomainName();
       
   552         $accountDomainNameShort = $this->_getAccountDomainNameShort();
       
   553         if ($accountDomainName === null && $accountDomainNameShort === null) {
       
   554             return true;
       
   555         }
       
   556         if (strcasecmp($dname, $accountDomainName) == 0) {
       
   557             return true;
       
   558         }
       
   559         if (strcasecmp($dname, $accountDomainNameShort) == 0) {
       
   560             return true;
       
   561         }
       
   562         return false;
       
   563     }
       
   564 
       
   565     /**
       
   566      * @param  string $acctname The name to canonicalize
       
   567      * @param  int    $type     The desired form of canonicalization
       
   568      * @return string The canonicalized name in the desired form
       
   569      * @throws Zend_Ldap_Exception
       
   570      */
       
   571     public function getCanonicalAccountName($acctname, $form = 0)
       
   572     {
       
   573         $this->_splitName($acctname, $dname, $uname);
       
   574 
       
   575         if (!$this->_isPossibleAuthority($dname)) {
       
   576             /**
       
   577              * @see Zend_Ldap_Exception
       
   578              */
       
   579             require_once 'Zend/Ldap/Exception.php';
       
   580             throw new Zend_Ldap_Exception(null,
       
   581                 "Binding domain is not an authority for user: $acctname",
       
   582                 Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH);
       
   583         }
       
   584 
       
   585         if (!$uname) {
       
   586             /**
       
   587              * @see Zend_Ldap_Exception
       
   588              */
       
   589             require_once 'Zend/Ldap/Exception.php';
       
   590             throw new Zend_Ldap_Exception(null, "Invalid account name syntax: $acctname");
       
   591         }
       
   592 
       
   593         if (function_exists('mb_strtolower')) {
       
   594             $uname = mb_strtolower($uname, 'UTF-8');
       
   595         } else {
       
   596             $uname = strtolower($uname);
       
   597         }
       
   598 
       
   599         if ($form === 0) {
       
   600             $form = $this->_getAccountCanonicalForm();
       
   601         }
       
   602 
       
   603         switch ($form) {
       
   604             case Zend_Ldap::ACCTNAME_FORM_DN:
       
   605                 return $this->_getAccountDn($acctname);
       
   606             case Zend_Ldap::ACCTNAME_FORM_USERNAME:
       
   607                 return $uname;
       
   608             case Zend_Ldap::ACCTNAME_FORM_BACKSLASH:
       
   609                 $accountDomainNameShort = $this->_getAccountDomainNameShort();
       
   610                 if (!$accountDomainNameShort) {
       
   611                     /**
       
   612                      * @see Zend_Ldap_Exception
       
   613                      */
       
   614                     require_once 'Zend/Ldap/Exception.php';
       
   615                     throw new Zend_Ldap_Exception(null, 'Option required: accountDomainNameShort');
       
   616                 }
       
   617                 return "$accountDomainNameShort\\$uname";
       
   618             case Zend_Ldap::ACCTNAME_FORM_PRINCIPAL:
       
   619                 $accountDomainName = $this->_getAccountDomainName();
       
   620                 if (!$accountDomainName) {
       
   621                     /**
       
   622                      * @see Zend_Ldap_Exception
       
   623                      */
       
   624                     require_once 'Zend/Ldap/Exception.php';
       
   625                     throw new Zend_Ldap_Exception(null, 'Option required: accountDomainName');
       
   626                 }
       
   627                 return "$uname@$accountDomainName";
       
   628             default:
       
   629                 /**
       
   630                  * @see Zend_Ldap_Exception
       
   631                  */
       
   632                 require_once 'Zend/Ldap/Exception.php';
       
   633                 throw new Zend_Ldap_Exception(null, "Unknown canonical name form: $form");
       
   634         }
       
   635     }
       
   636 
       
   637     /**
       
   638      * @param  array $attrs An array of names of desired attributes
       
   639      * @return array An array of the attributes representing the account
       
   640      * @throws Zend_Ldap_Exception
       
   641      */
       
   642     protected function _getAccount($acctname, array $attrs = null)
       
   643     {
       
   644         $baseDn = $this->getBaseDn();
       
   645         if (!$baseDn) {
       
   646             /**
       
   647              * @see Zend_Ldap_Exception
       
   648              */
       
   649             require_once 'Zend/Ldap/Exception.php';
       
   650             throw new Zend_Ldap_Exception(null, 'Base DN not set');
       
   651         }
       
   652 
       
   653         $accountFilter = $this->_getAccountFilter($acctname);
       
   654         if (!$accountFilter) {
       
   655             /**
       
   656              * @see Zend_Ldap_Exception
       
   657              */
       
   658             require_once 'Zend/Ldap/Exception.php';
       
   659             throw new Zend_Ldap_Exception(null, 'Invalid account filter');
       
   660         }
       
   661 
       
   662         if (!is_resource($this->getResource())) {
       
   663             $this->bind();
       
   664         }
       
   665 
       
   666         $accounts = $this->search($accountFilter, $baseDn, self::SEARCH_SCOPE_SUB, $attrs);
       
   667         $count = $accounts->count();
       
   668         if ($count === 1) {
       
   669             $acct = $accounts->getFirst();
       
   670             $accounts->close();
       
   671             return $acct;
       
   672         } else if ($count === 0) {
       
   673             /**
       
   674              * @see Zend_Ldap_Exception
       
   675              */
       
   676             require_once 'Zend/Ldap/Exception.php';
       
   677             $code = Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT;
       
   678             $str = "No object found for: $accountFilter";
       
   679         } else {
       
   680             /**
       
   681              * @see Zend_Ldap_Exception
       
   682              */
       
   683             require_once 'Zend/Ldap/Exception.php';
       
   684             $code = Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR;
       
   685             $str = "Unexpected result count ($count) for: $accountFilter";
       
   686         }
       
   687         $accounts->close();
       
   688         /**
       
   689          * @see Zend_Ldap_Exception
       
   690          */
       
   691         require_once 'Zend/Ldap/Exception.php';
       
   692         throw new Zend_Ldap_Exception($this, $str, $code);
       
   693     }
       
   694 
       
   695     /**
       
   696      * @return Zend_Ldap Provides a fluent interface
       
   697      */
       
   698     public function disconnect()
       
   699     {
       
   700         if (is_resource($this->_resource)) {
       
   701             @ldap_unbind($this->_resource);
       
   702         }
       
   703         $this->_resource = null;
       
   704         $this->_boundUser = false;
       
   705         return $this;
       
   706     }
       
   707 
       
   708     /**
       
   709      * To connect using SSL it seems the client tries to verify the server
       
   710      * certificate by default. One way to disable this behavior is to set
       
   711      * 'TLS_REQCERT never' in OpenLDAP's ldap.conf and restarting Apache. Or,
       
   712      * if you really care about the server's cert you can put a cert on the
       
   713      * web server.
       
   714      *
       
   715      * @param  string  $host        The hostname of the LDAP server to connect to
       
   716      * @param  int     $port        The port number of the LDAP server to connect to
       
   717      * @param  boolean $useSsl      Use SSL
       
   718      * @param  boolean $useStartTls Use STARTTLS
       
   719      * @return Zend_Ldap Provides a fluent interface
       
   720      * @throws Zend_Ldap_Exception
       
   721      */
       
   722     public function connect($host = null, $port = null, $useSsl = null, $useStartTls = null)
       
   723     {
       
   724         if ($host === null) {
       
   725             $host = $this->_getHost();
       
   726         }
       
   727         if ($port === null) {
       
   728             $port = $this->_getPort();
       
   729         } else {
       
   730             $port = (int)$port;
       
   731         }
       
   732         if ($useSsl === null) {
       
   733             $useSsl = $this->_getUseSsl();
       
   734         } else {
       
   735             $useSsl = (bool)$useSsl;
       
   736         }
       
   737         if ($useStartTls === null) {
       
   738             $useStartTls = $this->_getUseStartTls();
       
   739         } else {
       
   740             $useStartTls = (bool)$useStartTls;
       
   741         }
       
   742 
       
   743         if (!$host) {
       
   744             /**
       
   745              * @see Zend_Ldap_Exception
       
   746              */
       
   747             require_once 'Zend/Ldap/Exception.php';
       
   748             throw new Zend_Ldap_Exception(null, 'A host parameter is required');
       
   749         }
       
   750 
       
   751         $useUri = false;
       
   752         /* Because ldap_connect doesn't really try to connect, any connect error
       
   753          * will actually occur during the ldap_bind call. Therefore, we save the
       
   754          * connect string here for reporting it in error handling in bind().
       
   755          */
       
   756         $hosts = array();
       
   757         if (preg_match_all('~ldap(?:i|s)?://~', $host, $hosts, PREG_SET_ORDER) > 0) {
       
   758             $this->_connectString = $host;
       
   759             $useUri = true;
       
   760             $useSsl = false;
       
   761         } else {
       
   762             if ($useSsl) {
       
   763                 $this->_connectString = 'ldaps://' . $host;
       
   764                 $useUri = true;
       
   765             } else {
       
   766                 $this->_connectString = 'ldap://' . $host;
       
   767             }
       
   768             if ($port) {
       
   769                 $this->_connectString .= ':' . $port;
       
   770             }
       
   771         }
       
   772 
       
   773         $this->disconnect();
       
   774 
       
   775         /* Only OpenLDAP 2.2 + supports URLs so if SSL is not requested, just
       
   776          * use the old form.
       
   777          */
       
   778         $resource = ($useUri) ? @ldap_connect($this->_connectString) : @ldap_connect($host, $port);
       
   779 
       
   780         if (is_resource($resource) === true) {
       
   781             $this->_resource = $resource;
       
   782             $this->_boundUser = false;
       
   783 
       
   784             $optReferrals = ($this->_getOptReferrals()) ? 1 : 0;
       
   785             if (@ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3) &&
       
   786                         @ldap_set_option($resource, LDAP_OPT_REFERRALS, $optReferrals)) {
       
   787                 if ($useSsl || !$useStartTls || @ldap_start_tls($resource)) {
       
   788                     return $this;
       
   789                 }
       
   790             }
       
   791 
       
   792             /**
       
   793              * @see Zend_Ldap_Exception
       
   794              */
       
   795             require_once 'Zend/Ldap/Exception.php';
       
   796             $zle = new Zend_Ldap_Exception($this, "$host:$port");
       
   797             $this->disconnect();
       
   798             throw $zle;
       
   799         }
       
   800         /**
       
   801          * @see Zend_Ldap_Exception
       
   802          */
       
   803         require_once 'Zend/Ldap/Exception.php';
       
   804         throw new Zend_Ldap_Exception(null, "Failed to connect to LDAP server: $host:$port");
       
   805     }
       
   806 
       
   807     /**
       
   808      * @param  string $username The username for authenticating the bind
       
   809      * @param  string $password The password for authenticating the bind
       
   810      * @return Zend_Ldap Provides a fluent interface
       
   811      * @throws Zend_Ldap_Exception
       
   812      */
       
   813     public function bind($username = null, $password = null)
       
   814     {
       
   815         $moreCreds = true;
       
   816 
       
   817         if ($username === null) {
       
   818             $username = $this->_getUsername();
       
   819             $password = $this->_getPassword();
       
   820             $moreCreds = false;
       
   821         }
       
   822 
       
   823         if (empty($username)) {
       
   824             /* Perform anonymous bind
       
   825              */
       
   826             $username = null;
       
   827             $password = null;
       
   828         } else {
       
   829             /* Check to make sure the username is in DN form.
       
   830              */
       
   831             /**
       
   832              * @see Zend_Ldap_Dn
       
   833              */
       
   834             require_once 'Zend/Ldap/Dn.php';
       
   835             if (!Zend_Ldap_Dn::checkDn($username)) {
       
   836                 if ($this->_getBindRequiresDn()) {
       
   837                     /* moreCreds stops an infinite loop if _getUsername does not
       
   838                      * return a DN and the bind requires it
       
   839                      */
       
   840                     if ($moreCreds) {
       
   841                         try {
       
   842                             $username = $this->_getAccountDn($username);
       
   843                         } catch (Zend_Ldap_Exception $zle) {
       
   844                             switch ($zle->getCode()) {
       
   845                                 case Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT:
       
   846                                 case Zend_Ldap_Exception::LDAP_X_DOMAIN_MISMATCH:
       
   847                                 case Zend_Ldap_Exception::LDAP_X_EXTENSION_NOT_LOADED:
       
   848                                     throw $zle;
       
   849                             }
       
   850                             throw new Zend_Ldap_Exception(null,
       
   851                                 'Failed to retrieve DN for account: ' . $username .
       
   852                                 ' [' . $zle->getMessage() . ']',
       
   853                                 Zend_Ldap_Exception::LDAP_OPERATIONS_ERROR);
       
   854                         }
       
   855                     } else {
       
   856                         /**
       
   857                          * @see Zend_Ldap_Exception
       
   858                          */
       
   859                         require_once 'Zend/Ldap/Exception.php';
       
   860                         throw new Zend_Ldap_Exception(null, 'Binding requires username in DN form');
       
   861                     }
       
   862                 } else {
       
   863                     $username = $this->getCanonicalAccountName($username,
       
   864                         $this->_getAccountCanonicalForm());
       
   865                 }
       
   866             }
       
   867         }
       
   868 
       
   869         if (!is_resource($this->_resource)) {
       
   870             $this->connect();
       
   871         }
       
   872 
       
   873         if ($username !== null && $password === '' && $this->_getAllowEmptyPassword() !== true) {
       
   874             /**
       
   875              * @see Zend_Ldap_Exception
       
   876              */
       
   877             require_once 'Zend/Ldap/Exception.php';
       
   878             $zle = new Zend_Ldap_Exception(null,
       
   879                 'Empty password not allowed - see allowEmptyPassword option.');
       
   880         } else {
       
   881             if (@ldap_bind($this->_resource, $username, $password)) {
       
   882                 $this->_boundUser = $username;
       
   883                 return $this;
       
   884             }
       
   885 
       
   886             $message = ($username === null) ? $this->_connectString : $username;
       
   887             /**
       
   888              * @see Zend_Ldap_Exception
       
   889              */
       
   890             require_once 'Zend/Ldap/Exception.php';
       
   891             switch ($this->getLastErrorCode()) {
       
   892                 case Zend_Ldap_Exception::LDAP_SERVER_DOWN:
       
   893                     /* If the error is related to establishing a connection rather than binding,
       
   894                      * the connect string is more informative than the username.
       
   895                      */
       
   896                     $message = $this->_connectString;
       
   897             }
       
   898 
       
   899             $zle = new Zend_Ldap_Exception($this, $message);
       
   900         }
       
   901         $this->disconnect();
       
   902         throw $zle;
       
   903     }
       
   904 
       
   905     /**
       
   906      * A global LDAP search routine for finding information.
       
   907      *
       
   908      * Options can be either passed as single parameters according to the
       
   909      * method signature or as an array with one or more of the following keys
       
   910      * - filter
       
   911      * - baseDn
       
   912      * - scope
       
   913      * - attributes
       
   914      * - sort
       
   915      * - collectionClass
       
   916      * - sizelimit
       
   917      * - timelimit
       
   918      *
       
   919      * @param  string|Zend_Ldap_Filter_Abstract|array $filter
       
   920      * @param  string|Zend_Ldap_Dn|null               $basedn
       
   921      * @param  integer                                $scope
       
   922      * @param  array                                  $attributes
       
   923      * @param  string|null                            $sort
       
   924      * @param  string|null                            $collectionClass
       
   925      * @param  integer                            	  $sizelimit
       
   926      * @param  integer                            	  $timelimit
       
   927      * @return Zend_Ldap_Collection
       
   928      * @throws Zend_Ldap_Exception
       
   929      */
       
   930     public function search($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB, array $attributes = array(),
       
   931         $sort = null, $collectionClass = null, $sizelimit = 0, $timelimit = 0)
       
   932     {
       
   933         if (is_array($filter)) {
       
   934             $options = array_change_key_case($filter, CASE_LOWER);
       
   935             foreach ($options as $key => $value) {
       
   936                 switch ($key) {
       
   937                     case 'filter':
       
   938                     case 'basedn':
       
   939                     case 'scope':
       
   940                     case 'sort':
       
   941                         $$key = $value;
       
   942                         break;
       
   943                     case 'attributes':
       
   944                         if (is_array($value)) {
       
   945                             $attributes = $value;
       
   946                         }
       
   947                         break;
       
   948                     case 'collectionclass':
       
   949                         $collectionClass = $value;
       
   950                         break;
       
   951                     case 'sizelimit':
       
   952                     case 'timelimit':
       
   953                         $$key = (int)$value;
       
   954                 }
       
   955             }
       
   956         }
       
   957 
       
   958         if ($basedn === null) {
       
   959             $basedn = $this->getBaseDn();
       
   960         }
       
   961         else if ($basedn instanceof Zend_Ldap_Dn) {
       
   962             $basedn = $basedn->toString();
       
   963         }
       
   964 
       
   965         if ($filter instanceof Zend_Ldap_Filter_Abstract) {
       
   966             $filter = $filter->toString();
       
   967         }
       
   968 
       
   969         switch ($scope) {
       
   970             case self::SEARCH_SCOPE_ONE:
       
   971                 $search = @ldap_list($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit);
       
   972                 break;
       
   973             case self::SEARCH_SCOPE_BASE:
       
   974                 $search = @ldap_read($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit);
       
   975                 break;
       
   976             case self::SEARCH_SCOPE_SUB:
       
   977             default:
       
   978                 $search = @ldap_search($this->getResource(), $basedn, $filter, $attributes, 0, $sizelimit, $timelimit);
       
   979                 break;
       
   980         }
       
   981 
       
   982         if($search === false) {
       
   983             /**
       
   984              * @see Zend_Ldap_Exception
       
   985              */
       
   986             require_once 'Zend/Ldap/Exception.php';
       
   987             throw new Zend_Ldap_Exception($this, 'searching: ' . $filter);
       
   988         }
       
   989         if ($sort !== null && is_string($sort)) {
       
   990             $isSorted = @ldap_sort($this->getResource(), $search, $sort);
       
   991             if($isSorted === false) {
       
   992                 /**
       
   993                  * @see Zend_Ldap_Exception
       
   994                  */
       
   995                 require_once 'Zend/Ldap/Exception.php';
       
   996                 throw new Zend_Ldap_Exception($this, 'sorting: ' . $sort);
       
   997             }
       
   998         }
       
   999 
       
  1000         /**
       
  1001          * Zend_Ldap_Collection_Iterator_Default
       
  1002          */
       
  1003         require_once 'Zend/Ldap/Collection/Iterator/Default.php';
       
  1004         $iterator = new Zend_Ldap_Collection_Iterator_Default($this, $search);
       
  1005         return $this->_createCollection($iterator, $collectionClass);
       
  1006     }
       
  1007 
       
  1008     /**
       
  1009      * Extension point for collection creation
       
  1010      *
       
  1011      * @param  Zend_Ldap_Collection_Iterator_Default	$iterator
       
  1012      * @param  string|null								$collectionClass
       
  1013      * @return Zend_Ldap_Collection
       
  1014      * @throws Zend_Ldap_Exception
       
  1015      */
       
  1016     protected function _createCollection(Zend_Ldap_Collection_Iterator_Default $iterator, $collectionClass)
       
  1017     {
       
  1018         if ($collectionClass === null) {
       
  1019             /**
       
  1020              * Zend_Ldap_Collection
       
  1021              */
       
  1022             require_once 'Zend/Ldap/Collection.php';
       
  1023             return new Zend_Ldap_Collection($iterator);
       
  1024         } else {
       
  1025             $collectionClass = (string)$collectionClass;
       
  1026             if (!class_exists($collectionClass)) {
       
  1027                 /**
       
  1028                  * @see Zend_Ldap_Exception
       
  1029                  */
       
  1030                 require_once 'Zend/Ldap/Exception.php';
       
  1031                 throw new Zend_Ldap_Exception(null,
       
  1032                     "Class '$collectionClass' can not be found");
       
  1033             }
       
  1034             if (!is_subclass_of($collectionClass, 'Zend_Ldap_Collection')) {
       
  1035                 /**
       
  1036                  * @see Zend_Ldap_Exception
       
  1037                  */
       
  1038                 require_once 'Zend/Ldap/Exception.php';
       
  1039                 throw new Zend_Ldap_Exception(null,
       
  1040                     "Class '$collectionClass' must subclass 'Zend_Ldap_Collection'");
       
  1041             }
       
  1042             return new $collectionClass($iterator);
       
  1043         }
       
  1044     }
       
  1045 
       
  1046     /**
       
  1047      * Count items found by given filter.
       
  1048      *
       
  1049      * @param  string|Zend_Ldap_Filter_Abstract $filter
       
  1050      * @param  string|Zend_Ldap_Dn|null         $basedn
       
  1051      * @param  integer                          $scope
       
  1052      * @return integer
       
  1053      * @throws Zend_Ldap_Exception
       
  1054      */
       
  1055     public function count($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB)
       
  1056     {
       
  1057         try {
       
  1058             $result = $this->search($filter, $basedn, $scope, array('dn'), null);
       
  1059         } catch (Zend_Ldap_Exception $e) {
       
  1060             if ($e->getCode() === Zend_Ldap_Exception::LDAP_NO_SUCH_OBJECT) return 0;
       
  1061             else throw $e;
       
  1062         }
       
  1063         return $result->count();
       
  1064     }
       
  1065 
       
  1066     /**
       
  1067      * Count children for a given DN.
       
  1068      *
       
  1069      * @param  string|Zend_Ldap_Dn $dn
       
  1070      * @return integer
       
  1071      * @throws Zend_Ldap_Exception
       
  1072      */
       
  1073     public function countChildren($dn)
       
  1074     {
       
  1075         return $this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_ONE);
       
  1076     }
       
  1077 
       
  1078     /**
       
  1079      * Check if a given DN exists.
       
  1080      *
       
  1081      * @param  string|Zend_Ldap_Dn $dn
       
  1082      * @return boolean
       
  1083      * @throws Zend_Ldap_Exception
       
  1084      */
       
  1085     public function exists($dn)
       
  1086     {
       
  1087         return ($this->count('(objectClass=*)', $dn, self::SEARCH_SCOPE_BASE) == 1);
       
  1088     }
       
  1089 
       
  1090     /**
       
  1091      * Search LDAP registry for entries matching filter and optional attributes
       
  1092      *
       
  1093      * Options can be either passed as single parameters according to the
       
  1094      * method signature or as an array with one or more of the following keys
       
  1095      * - filter
       
  1096      * - baseDn
       
  1097      * - scope
       
  1098      * - attributes
       
  1099      * - sort
       
  1100      * - reverseSort
       
  1101      * - sizelimit
       
  1102      * - timelimit
       
  1103      *
       
  1104      * @param  string|Zend_Ldap_Filter_Abstract|array $filter
       
  1105      * @param  string|Zend_Ldap_Dn|null               $basedn
       
  1106      * @param  integer                                $scope
       
  1107      * @param  array                                  $attributes
       
  1108      * @param  string|null                            $sort
       
  1109      * @param  boolean                                $reverseSort
       
  1110      * @param  integer                            	  $sizelimit
       
  1111      * @param  integer                            	  $timelimit
       
  1112      * @return array
       
  1113      * @throws Zend_Ldap_Exception
       
  1114      */
       
  1115     public function searchEntries($filter, $basedn = null, $scope = self::SEARCH_SCOPE_SUB,
       
  1116         array $attributes = array(), $sort = null, $reverseSort = false, $sizelimit = 0, $timelimit = 0)
       
  1117     {
       
  1118         if (is_array($filter)) {
       
  1119             $filter = array_change_key_case($filter, CASE_LOWER);
       
  1120             if (isset($filter['collectionclass'])) {
       
  1121                 unset($filter['collectionclass']);
       
  1122             }
       
  1123             if (isset($filter['reversesort'])) {
       
  1124                 $reverseSort = $filter['reversesort'];
       
  1125                 unset($filter['reversesort']);
       
  1126             }
       
  1127         }
       
  1128         $result = $this->search($filter, $basedn, $scope, $attributes, $sort, null, $sizelimit, $timelimit);
       
  1129         $items = $result->toArray();
       
  1130         if ((bool)$reverseSort === true) {
       
  1131             $items = array_reverse($items, false);
       
  1132         }
       
  1133         return $items;
       
  1134     }
       
  1135 
       
  1136     /**
       
  1137      * Get LDAP entry by DN
       
  1138      *
       
  1139      * @param  string|Zend_Ldap_Dn $dn
       
  1140      * @param  array               $attributes
       
  1141      * @param  boolean             $throwOnNotFound
       
  1142      * @return array
       
  1143      * @throws Zend_Ldap_Exception
       
  1144      */
       
  1145     public function getEntry($dn, array $attributes = array(), $throwOnNotFound = false)
       
  1146     {
       
  1147         try {
       
  1148             $result = $this->search("(objectClass=*)", $dn, self::SEARCH_SCOPE_BASE,
       
  1149                 $attributes, null);
       
  1150             return $result->getFirst();
       
  1151         } catch (Zend_Ldap_Exception $e){
       
  1152             if ($throwOnNotFound !== false) throw $e;
       
  1153         }
       
  1154         return null;
       
  1155     }
       
  1156 
       
  1157     /**
       
  1158      * Prepares an ldap data entry array for insert/update operation
       
  1159      *
       
  1160      * @param  array $entry
       
  1161      * @return void
       
  1162      * @throws InvalidArgumentException
       
  1163      */
       
  1164     public static function prepareLdapEntryArray(array &$entry)
       
  1165     {
       
  1166         if (array_key_exists('dn', $entry)) unset($entry['dn']);
       
  1167         foreach ($entry as $key => $value) {
       
  1168             if (is_array($value)) {
       
  1169                 foreach ($value as $i => $v) {
       
  1170                     if ($v === null) unset($value[$i]);
       
  1171                     else if (!is_scalar($v)) {
       
  1172                         throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
       
  1173                     } else {
       
  1174                         $v = (string)$v;
       
  1175                         if (strlen($v) == 0) {
       
  1176                             unset($value[$i]);
       
  1177                         } else {
       
  1178                             $value[$i] = $v;
       
  1179                         }
       
  1180                     }
       
  1181                 }
       
  1182                 $entry[$key] = array_values($value);
       
  1183             } else {
       
  1184                 if ($value === null) $entry[$key] = array();
       
  1185                 else if (!is_scalar($value)) {
       
  1186                     throw new InvalidArgumentException('Only scalar values allowed in LDAP data');
       
  1187                 } else {
       
  1188                     $value = (string)$value;
       
  1189                     if (strlen($value) == 0) {
       
  1190                         $entry[$key] = array();
       
  1191                     } else {
       
  1192                         $entry[$key] = array($value);
       
  1193                     }
       
  1194                 }
       
  1195             }
       
  1196         }
       
  1197         $entry = array_change_key_case($entry, CASE_LOWER);
       
  1198     }
       
  1199 
       
  1200     /**
       
  1201      * Add new information to the LDAP repository
       
  1202      *
       
  1203      * @param  string|Zend_Ldap_Dn $dn
       
  1204      * @param  array               $entry
       
  1205      * @return Zend_Ldap                  Provides a fluid interface
       
  1206      * @throws Zend_Ldap_Exception
       
  1207      */
       
  1208     public function add($dn, array $entry)
       
  1209     {
       
  1210         if (!($dn instanceof Zend_Ldap_Dn)) {
       
  1211             $dn = Zend_Ldap_Dn::factory($dn, null);
       
  1212         }
       
  1213         self::prepareLdapEntryArray($entry);
       
  1214         foreach ($entry as $key => $value) {
       
  1215             if (is_array($value) && count($value) === 0) {
       
  1216                 unset($entry[$key]);
       
  1217             }
       
  1218         }
       
  1219 
       
  1220         $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
       
  1221         foreach ($rdnParts as $key => $value) {
       
  1222             $value = Zend_Ldap_Dn::unescapeValue($value);
       
  1223             if (!array_key_exists($key, $entry)) {
       
  1224                 $entry[$key] = array($value);
       
  1225             } else if (!in_array($value, $entry[$key])) {
       
  1226                 $entry[$key] = array_merge(array($value), $entry[$key]);
       
  1227             }
       
  1228         }
       
  1229         $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
       
  1230             'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
       
  1231         foreach ($adAttributes as $attr) {
       
  1232             if (array_key_exists($attr, $entry)) {
       
  1233                 unset($entry[$attr]);
       
  1234             }
       
  1235         }
       
  1236 
       
  1237         $isAdded = @ldap_add($this->getResource(), $dn->toString(), $entry);
       
  1238         if($isAdded === false) {
       
  1239             /**
       
  1240              * @see Zend_Ldap_Exception
       
  1241              */
       
  1242             require_once 'Zend/Ldap/Exception.php';
       
  1243             throw new Zend_Ldap_Exception($this, 'adding: ' . $dn->toString());
       
  1244         }
       
  1245         return $this;
       
  1246     }
       
  1247 
       
  1248     /**
       
  1249      * Update LDAP registry
       
  1250      *
       
  1251      * @param  string|Zend_Ldap_Dn $dn
       
  1252      * @param  array               $entry
       
  1253      * @return Zend_Ldap                  Provides a fluid interface
       
  1254      * @throws Zend_Ldap_Exception
       
  1255      */
       
  1256     public function update($dn, array $entry)
       
  1257     {
       
  1258         if (!($dn instanceof Zend_Ldap_Dn)) {
       
  1259             $dn = Zend_Ldap_Dn::factory($dn, null);
       
  1260         }
       
  1261         self::prepareLdapEntryArray($entry);
       
  1262 
       
  1263         $rdnParts = $dn->getRdn(Zend_Ldap_Dn::ATTR_CASEFOLD_LOWER);
       
  1264         foreach ($rdnParts as $key => $value) {
       
  1265             $value = Zend_Ldap_Dn::unescapeValue($value);
       
  1266             if (array_key_exists($key, $entry) && !in_array($value, $entry[$key])) {
       
  1267                 $entry[$key] = array_merge(array($value), $entry[$key]);
       
  1268             }
       
  1269         }
       
  1270 
       
  1271         $adAttributes = array('distinguishedname', 'instancetype', 'name', 'objectcategory',
       
  1272             'objectguid', 'usnchanged', 'usncreated', 'whenchanged', 'whencreated');
       
  1273         foreach ($adAttributes as $attr) {
       
  1274             if (array_key_exists($attr, $entry)) {
       
  1275                 unset($entry[$attr]);
       
  1276             }
       
  1277         }
       
  1278 
       
  1279         if (count($entry) > 0) {
       
  1280             $isModified = @ldap_modify($this->getResource(), $dn->toString(), $entry);
       
  1281             if($isModified === false) {
       
  1282                 /**
       
  1283                  * @see Zend_Ldap_Exception
       
  1284                  */
       
  1285                 require_once 'Zend/Ldap/Exception.php';
       
  1286                 throw new Zend_Ldap_Exception($this, 'updating: ' . $dn->toString());
       
  1287             }
       
  1288         }
       
  1289         return $this;
       
  1290     }
       
  1291 
       
  1292     /**
       
  1293      * Save entry to LDAP registry.
       
  1294      *
       
  1295      * Internally decides if entry will be updated to added by calling
       
  1296      * {@link exists()}.
       
  1297      *
       
  1298      * @param  string|Zend_Ldap_Dn $dn
       
  1299      * @param  array               $entry
       
  1300      * @return Zend_Ldap Provides a fluid interface
       
  1301      * @throws Zend_Ldap_Exception
       
  1302      */
       
  1303     public function save($dn, array $entry)
       
  1304     {
       
  1305         if ($dn instanceof Zend_Ldap_Dn) {
       
  1306             $dn = $dn->toString();
       
  1307         }
       
  1308         if ($this->exists($dn)) $this->update($dn, $entry);
       
  1309         else $this->add($dn, $entry);
       
  1310         return $this;
       
  1311     }
       
  1312 
       
  1313     /**
       
  1314      * Delete an LDAP entry
       
  1315      *
       
  1316      * @param  string|Zend_Ldap_Dn $dn
       
  1317      * @param  boolean             $recursively
       
  1318      * @return Zend_Ldap Provides a fluid interface
       
  1319      * @throws Zend_Ldap_Exception
       
  1320      */
       
  1321     public function delete($dn, $recursively = false)
       
  1322     {
       
  1323         if ($dn instanceof Zend_Ldap_Dn) {
       
  1324             $dn = $dn->toString();
       
  1325         }
       
  1326         if ($recursively === true) {
       
  1327             if ($this->countChildren($dn)>0) {
       
  1328                 $children = $this->_getChildrenDns($dn);
       
  1329                 foreach ($children as $c) {
       
  1330                     $this->delete($c, true);
       
  1331                 }
       
  1332             }
       
  1333         }
       
  1334         $isDeleted = @ldap_delete($this->getResource(), $dn);
       
  1335         if($isDeleted === false) {
       
  1336             /**
       
  1337              * @see Zend_Ldap_Exception
       
  1338              */
       
  1339             require_once 'Zend/Ldap/Exception.php';
       
  1340             throw new Zend_Ldap_Exception($this, 'deleting: ' . $dn);
       
  1341         }
       
  1342         return $this;
       
  1343     }
       
  1344 
       
  1345     /**
       
  1346      * Retrieve the immediate children DNs of the given $parentDn
       
  1347      *
       
  1348      * This method is used in recursive methods like {@see delete()}
       
  1349      * or {@see copy()}
       
  1350      *
       
  1351      * @param  string|Zend_Ldap_Dn $parentDn
       
  1352      * @return array of DNs
       
  1353      */
       
  1354     protected function _getChildrenDns($parentDn)
       
  1355     {
       
  1356         if ($parentDn instanceof Zend_Ldap_Dn) {
       
  1357             $parentDn = $parentDn->toString();
       
  1358         }
       
  1359         $children = array();
       
  1360         $search = @ldap_list($this->getResource(), $parentDn, '(objectClass=*)', array('dn'));
       
  1361         for ($entry = @ldap_first_entry($this->getResource(), $search);
       
  1362                 $entry !== false;
       
  1363                 $entry = @ldap_next_entry($this->getResource(), $entry)) {
       
  1364             $childDn = @ldap_get_dn($this->getResource(), $entry);
       
  1365             if ($childDn === false) {
       
  1366                 /**
       
  1367                  * @see Zend_Ldap_Exception
       
  1368                  */
       
  1369                 require_once 'Zend/Ldap/Exception.php';
       
  1370                 throw new Zend_Ldap_Exception($this, 'getting dn');
       
  1371             }
       
  1372             $children[] = $childDn;
       
  1373         }
       
  1374         @ldap_free_result($search);
       
  1375         return $children;
       
  1376     }
       
  1377 
       
  1378     /**
       
  1379      * Moves a LDAP entry from one DN to another subtree.
       
  1380      *
       
  1381      * @param  string|Zend_Ldap_Dn $from
       
  1382      * @param  string|Zend_Ldap_Dn $to
       
  1383      * @param  boolean             $recursively
       
  1384      * @param  boolean             $alwaysEmulate
       
  1385      * @return Zend_Ldap Provides a fluid interface
       
  1386      * @throws Zend_Ldap_Exception
       
  1387      */
       
  1388     public function moveToSubtree($from, $to, $recursively = false, $alwaysEmulate = false)
       
  1389     {
       
  1390         if ($from instanceof Zend_Ldap_Dn) {
       
  1391             $orgDnParts = $from->toArray();
       
  1392         } else {
       
  1393             $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
       
  1394         }
       
  1395 
       
  1396         if ($to instanceof Zend_Ldap_Dn) {
       
  1397             $newParentDnParts = $to->toArray();
       
  1398         } else {
       
  1399             $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
       
  1400         }
       
  1401 
       
  1402         $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
       
  1403         $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
       
  1404         return $this->rename($from, $newDn, $recursively, $alwaysEmulate);
       
  1405     }
       
  1406 
       
  1407     /**
       
  1408      * Moves a LDAP entry from one DN to another DN.
       
  1409      *
       
  1410      * This is an alias for {@link rename()}
       
  1411      *
       
  1412      * @param  string|Zend_Ldap_Dn $from
       
  1413      * @param  string|Zend_Ldap_Dn $to
       
  1414      * @param  boolean             $recursively
       
  1415      * @param  boolean             $alwaysEmulate
       
  1416      * @return Zend_Ldap Provides a fluid interface
       
  1417      * @throws Zend_Ldap_Exception
       
  1418      */
       
  1419     public function move($from, $to, $recursively = false, $alwaysEmulate = false)
       
  1420     {
       
  1421         return $this->rename($from, $to, $recursively, $alwaysEmulate);
       
  1422     }
       
  1423 
       
  1424     /**
       
  1425      * Renames a LDAP entry from one DN to another DN.
       
  1426      *
       
  1427      * This method implicitely moves the entry to another location within the tree.
       
  1428      *
       
  1429      * @param  string|Zend_Ldap_Dn $from
       
  1430      * @param  string|Zend_Ldap_Dn $to
       
  1431      * @param  boolean             $recursively
       
  1432      * @param  boolean             $alwaysEmulate
       
  1433      * @return Zend_Ldap Provides a fluid interface
       
  1434      * @throws Zend_Ldap_Exception
       
  1435      */
       
  1436     public function rename($from, $to, $recursively = false, $alwaysEmulate = false)
       
  1437     {
       
  1438         $emulate = (bool)$alwaysEmulate;
       
  1439         if (!function_exists('ldap_rename')) $emulate = true;
       
  1440         else if ($recursively) $emulate = true;
       
  1441 
       
  1442         if ($emulate === false) {
       
  1443             if ($from instanceof Zend_Ldap_Dn) {
       
  1444                 $from = $from->toString();
       
  1445             }
       
  1446 
       
  1447             if ($to instanceof Zend_Ldap_Dn) {
       
  1448                 $newDnParts = $to->toArray();
       
  1449             } else {
       
  1450                 $newDnParts = Zend_Ldap_Dn::explodeDn($to);
       
  1451             }
       
  1452 
       
  1453             $newRdn = Zend_Ldap_Dn::implodeRdn(array_shift($newDnParts));
       
  1454             $newParent = Zend_Ldap_Dn::implodeDn($newDnParts);
       
  1455             $isOK = @ldap_rename($this->getResource(), $from, $newRdn, $newParent, true);
       
  1456             if($isOK === false) {
       
  1457                 /**
       
  1458                  * @see Zend_Ldap_Exception
       
  1459                  */
       
  1460                 require_once 'Zend/Ldap/Exception.php';
       
  1461                 throw new Zend_Ldap_Exception($this, 'renaming ' . $from . ' to ' . $to);
       
  1462             }
       
  1463             else if (!$this->exists($to)) $emulate = true;
       
  1464         }
       
  1465         if ($emulate) {
       
  1466             $this->copy($from, $to, $recursively);
       
  1467             $this->delete($from, $recursively);
       
  1468         }
       
  1469         return $this;
       
  1470     }
       
  1471 
       
  1472     /**
       
  1473      * Copies a LDAP entry from one DN to another subtree.
       
  1474      *
       
  1475      * @param  string|Zend_Ldap_Dn $from
       
  1476      * @param  string|Zend_Ldap_Dn $to
       
  1477      * @param  boolean             $recursively
       
  1478      * @return Zend_Ldap Provides a fluid interface
       
  1479      * @throws Zend_Ldap_Exception
       
  1480      */
       
  1481     public function copyToSubtree($from, $to, $recursively = false)
       
  1482     {
       
  1483         if ($from instanceof Zend_Ldap_Dn) {
       
  1484             $orgDnParts = $from->toArray();
       
  1485         } else {
       
  1486             $orgDnParts = Zend_Ldap_Dn::explodeDn($from);
       
  1487         }
       
  1488 
       
  1489         if ($to instanceof Zend_Ldap_Dn) {
       
  1490             $newParentDnParts = $to->toArray();
       
  1491         } else {
       
  1492             $newParentDnParts = Zend_Ldap_Dn::explodeDn($to);
       
  1493         }
       
  1494 
       
  1495         $newDnParts = array_merge(array(array_shift($orgDnParts)), $newParentDnParts);
       
  1496         $newDn = Zend_Ldap_Dn::fromArray($newDnParts);
       
  1497         return $this->copy($from, $newDn, $recursively);
       
  1498     }
       
  1499 
       
  1500     /**
       
  1501      * Copies a LDAP entry from one DN to another DN.
       
  1502      *
       
  1503      * @param  string|Zend_Ldap_Dn $from
       
  1504      * @param  string|Zend_Ldap_Dn $to
       
  1505      * @param  boolean             $recursively
       
  1506      * @return Zend_Ldap Provides a fluid interface
       
  1507      * @throws Zend_Ldap_Exception
       
  1508      */
       
  1509     public function copy($from, $to, $recursively = false)
       
  1510     {
       
  1511         $entry = $this->getEntry($from, array(), true);
       
  1512 
       
  1513         if ($to instanceof Zend_Ldap_Dn) {
       
  1514             $toDnParts = $to->toArray();
       
  1515         } else {
       
  1516             $toDnParts = Zend_Ldap_Dn::explodeDn($to);
       
  1517         }
       
  1518         $this->add($to, $entry);
       
  1519 
       
  1520         if ($recursively === true && $this->countChildren($from)>0) {
       
  1521             $children = $this->_getChildrenDns($from);
       
  1522             foreach ($children as $c) {
       
  1523                 $cDnParts = Zend_Ldap_Dn::explodeDn($c);
       
  1524                 $newChildParts = array_merge(array(array_shift($cDnParts)), $toDnParts);
       
  1525                 $newChild = Zend_Ldap_Dn::implodeDn($newChildParts);
       
  1526                 $this->copy($c, $newChild, true);
       
  1527             }
       
  1528         }
       
  1529         return $this;
       
  1530     }
       
  1531 
       
  1532     /**
       
  1533      * Returns the specified DN as a Zend_Ldap_Node
       
  1534      *
       
  1535      * @param  string|Zend_Ldap_Dn $dn
       
  1536      * @return Zend_Ldap_Node|null
       
  1537      * @throws Zend_Ldap_Exception
       
  1538      */
       
  1539     public function getNode($dn)
       
  1540     {
       
  1541         /**
       
  1542          * Zend_Ldap_Node
       
  1543          */
       
  1544         require_once 'Zend/Ldap/Node.php';
       
  1545         return Zend_Ldap_Node::fromLdap($dn, $this);
       
  1546     }
       
  1547 
       
  1548     /**
       
  1549      * Returns the base node as a Zend_Ldap_Node
       
  1550      *
       
  1551      * @return Zend_Ldap_Node
       
  1552      * @throws Zend_Ldap_Exception
       
  1553      */
       
  1554     public function getBaseNode()
       
  1555     {
       
  1556         return $this->getNode($this->getBaseDn(), $this);
       
  1557     }
       
  1558 
       
  1559     /**
       
  1560      * Returns the RootDSE
       
  1561      *
       
  1562      * @return Zend_Ldap_Node_RootDse
       
  1563      * @throws Zend_Ldap_Exception
       
  1564      */
       
  1565     public function getRootDse()
       
  1566     {
       
  1567         if ($this->_rootDse === null) {
       
  1568             /**
       
  1569              * @see Zend_Ldap_Node_Schema
       
  1570              */
       
  1571             require_once 'Zend/Ldap/Node/RootDse.php';
       
  1572             $this->_rootDse = Zend_Ldap_Node_RootDse::create($this);
       
  1573         }
       
  1574         return $this->_rootDse;
       
  1575     }
       
  1576 
       
  1577     /**
       
  1578      * Returns the schema
       
  1579      *
       
  1580      * @return Zend_Ldap_Node_Schema
       
  1581      * @throws Zend_Ldap_Exception
       
  1582      */
       
  1583     public function getSchema()
       
  1584     {
       
  1585         if ($this->_schema === null) {
       
  1586             /**
       
  1587              * @see Zend_Ldap_Node_Schema
       
  1588              */
       
  1589             require_once 'Zend/Ldap/Node/Schema.php';
       
  1590             $this->_schema = Zend_Ldap_Node_Schema::create($this);
       
  1591         }
       
  1592         return $this->_schema;
       
  1593     }
       
  1594 }