adLDAP.php 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949
  1. <?php
  2. /**
  3. * PHP LDAP CLASS FOR MANIPULATING ACTIVE DIRECTORY
  4. * Version 4.0.4
  5. *
  6. * PHP Version 5 with SSL and LDAP support
  7. *
  8. * Written by Scott Barnett, Richard Hyland
  9. * email: scott@wiggumworld.com, adldap@richardhyland.com
  10. * http://adldap.sourceforge.net/
  11. *
  12. * Copyright (c) 2006-2012 Scott Barnett, Richard Hyland
  13. *
  14. * We'd appreciate any improvements or additions to be submitted back
  15. * to benefit the entire community :)
  16. *
  17. * This library is free software; you can redistribute it and/or
  18. * modify it under the terms of the GNU Lesser General Public
  19. * License as published by the Free Software Foundation; either
  20. * version 2.1 of the License.
  21. *
  22. * This library is distributed in the hope that it will be useful,
  23. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  24. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  25. * Lesser General Public License for more details.
  26. *
  27. * @category ToolsAndUtilities
  28. * @package adLDAP
  29. * @author Scott Barnett, Richard Hyland
  30. * @copyright (c) 2006-2012 Scott Barnett, Richard Hyland
  31. * @license http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html LGPLv2.1
  32. * @revision $Revision: 169 $
  33. * @version 4.0.4
  34. * @link http://adldap.sourceforge.net/
  35. */
  36. /**
  37. * Main adLDAP class
  38. *
  39. * Can be initialised using $adldap = new adLDAP();
  40. *
  41. * Something to keep in mind is that Active Directory is a permissions
  42. * based directory. If you bind as a domain user, you can't fetch as
  43. * much information on other users as you could as a domain admin.
  44. *
  45. * Before asking questions, please read the Documentation at
  46. * http://adldap.sourceforge.net/wiki/doku.php?id=api
  47. */
  48. require_once(dirname(__FILE__) . '/collections/adLDAPCollection.php');
  49. require_once(dirname(__FILE__) . '/classes/adLDAPGroups.php');
  50. require_once(dirname(__FILE__) . '/classes/adLDAPUsers.php');
  51. require_once(dirname(__FILE__) . '/classes/adLDAPFolders.php');
  52. require_once(dirname(__FILE__) . '/classes/adLDAPUtils.php');
  53. require_once(dirname(__FILE__) . '/classes/adLDAPContacts.php');
  54. require_once(dirname(__FILE__) . '/classes/adLDAPExchange.php');
  55. require_once(dirname(__FILE__) . '/classes/adLDAPComputers.php');
  56. class adLDAP {
  57. /**
  58. * Define the different types of account in AD
  59. */
  60. const ADLDAP_NORMAL_ACCOUNT = 805306368;
  61. const ADLDAP_WORKSTATION_TRUST = 805306369;
  62. const ADLDAP_INTERDOMAIN_TRUST = 805306370;
  63. const ADLDAP_SECURITY_GLOBAL_GROUP = 268435456;
  64. const ADLDAP_DISTRIBUTION_GROUP = 268435457;
  65. const ADLDAP_SECURITY_LOCAL_GROUP = 536870912;
  66. const ADLDAP_DISTRIBUTION_LOCAL_GROUP = 536870913;
  67. const ADLDAP_FOLDER = 'OU';
  68. const ADLDAP_CONTAINER = 'CN';
  69. /**
  70. * The default port for LDAP non-SSL connections
  71. */
  72. const ADLDAP_LDAP_PORT = '389';
  73. /**
  74. * The default port for LDAPS SSL connections
  75. */
  76. const ADLDAP_LDAPS_PORT = '636';
  77. /**
  78. * The account suffix for your domain, can be set when the class is invoked
  79. *
  80. * @var string
  81. */
  82. protected $accountSuffix = "@mydomain.local";
  83. /**
  84. * The base dn for your domain
  85. *
  86. * If this is set to null then adLDAP will attempt to obtain this automatically from the rootDSE
  87. *
  88. * @var string
  89. */
  90. protected $baseDn = "DC=mydomain,DC=local";
  91. /**
  92. * Port used to talk to the domain controllers.
  93. *
  94. * @var int
  95. */
  96. protected $adPort = self::ADLDAP_LDAP_PORT;
  97. /**
  98. * Array of domain controllers. Specifiy multiple controllers if you
  99. * would like the class to balance the LDAP queries amongst multiple servers
  100. *
  101. * @var array
  102. */
  103. protected $domainControllers = array("dc01.mydomain.local");
  104. /**
  105. * Optional account with higher privileges for searching
  106. * This should be set to a domain admin account
  107. *
  108. * @var string
  109. * @var string
  110. */
  111. protected $adminUsername = NULL;
  112. protected $adminPassword = NULL;
  113. /**
  114. * AD does not return the primary group. http://support.microsoft.com/?kbid=321360
  115. * This tweak will resolve the real primary group.
  116. * Setting to false will fudge "Domain Users" and is much faster. Keep in mind though that if
  117. * someone's primary group is NOT domain users, this is obviously going to mess up the results
  118. *
  119. * @var bool
  120. */
  121. protected $realPrimaryGroup = true;
  122. /**
  123. * Use SSL (LDAPS), your server needs to be setup, please see
  124. * http://adldap.sourceforge.net/wiki/doku.php?id=ldap_over_ssl
  125. *
  126. * @var bool
  127. */
  128. protected $useSSL = false;
  129. /**
  130. * Use TLS
  131. * If you wish to use TLS you should ensure that $useSSL is set to false and vice-versa
  132. *
  133. * @var bool
  134. */
  135. protected $useTLS = false;
  136. /**
  137. * Use SSO
  138. * To indicate to adLDAP to reuse password set by the brower through NTLM or Kerberos
  139. *
  140. * @var bool
  141. */
  142. protected $useSSO = false;
  143. /**
  144. * When querying group memberships, do it recursively
  145. * eg. User Fred is a member of Group A, which is a member of Group B, which is a member of Group C
  146. * user_ingroup("Fred","C") will returns true with this option turned on, false if turned off
  147. *
  148. * @var bool
  149. */
  150. protected $recursiveGroups = true;
  151. // You should not need to edit anything below this line
  152. //******************************************************************************************
  153. /**
  154. * Connection and bind default variables
  155. *
  156. * @var mixed
  157. * @var mixed
  158. */
  159. protected $ldapConnection;
  160. protected $ldapBind;
  161. /**
  162. * Get the active LDAP Connection
  163. *
  164. * @return resource
  165. */
  166. public function getLdapConnection() {
  167. if ($this->ldapConnection) {
  168. return $this->ldapConnection;
  169. }
  170. return false;
  171. }
  172. /**
  173. * Get the bind status
  174. *
  175. * @return bool
  176. */
  177. public function getLdapBind() {
  178. return $this->ldapBind;
  179. }
  180. /**
  181. * Get the current base DN
  182. *
  183. * @return string
  184. */
  185. public function getBaseDn() {
  186. return $this->baseDn;
  187. }
  188. /**
  189. * The group class
  190. *
  191. * @var adLDAPGroups
  192. */
  193. protected $groupClass;
  194. /**
  195. * Get the group class interface
  196. *
  197. * @return adLDAPGroups
  198. */
  199. public function group() {
  200. if (!$this->groupClass) {
  201. $this->groupClass = new adLDAPGroups($this);
  202. }
  203. return $this->groupClass;
  204. }
  205. /**
  206. * The user class
  207. *
  208. * @var adLDAPUsers
  209. */
  210. protected $userClass;
  211. /**
  212. * Get the userclass interface
  213. *
  214. * @return adLDAPUsers
  215. */
  216. public function user() {
  217. if (!$this->userClass) {
  218. $this->userClass = new adLDAPUsers($this);
  219. }
  220. return $this->userClass;
  221. }
  222. /**
  223. * The folders class
  224. *
  225. * @var adLDAPFolders
  226. */
  227. protected $folderClass;
  228. /**
  229. * Get the folder class interface
  230. *
  231. * @return adLDAPFolders
  232. */
  233. public function folder() {
  234. if (!$this->folderClass) {
  235. $this->folderClass = new adLDAPFolders($this);
  236. }
  237. return $this->folderClass;
  238. }
  239. /**
  240. * The utils class
  241. *
  242. * @var adLDAPUtils
  243. */
  244. protected $utilClass;
  245. /**
  246. * Get the utils class interface
  247. *
  248. * @return adLDAPUtils
  249. */
  250. public function utilities() {
  251. if (!$this->utilClass) {
  252. $this->utilClass = new adLDAPUtils($this);
  253. }
  254. return $this->utilClass;
  255. }
  256. /**
  257. * The contacts class
  258. *
  259. * @var adLDAPContacts
  260. */
  261. protected $contactClass;
  262. /**
  263. * Get the contacts class interface
  264. *
  265. * @return adLDAPContacts
  266. */
  267. public function contact() {
  268. if (!$this->contactClass) {
  269. $this->contactClass = new adLDAPContacts($this);
  270. }
  271. return $this->contactClass;
  272. }
  273. /**
  274. * The exchange class
  275. *
  276. * @var adLDAPExchange
  277. */
  278. protected $exchangeClass;
  279. /**
  280. * Get the exchange class interface
  281. *
  282. * @return adLDAPExchange
  283. */
  284. public function exchange() {
  285. if (!$this->exchangeClass) {
  286. $this->exchangeClass = new adLDAPExchange($this);
  287. }
  288. return $this->exchangeClass;
  289. }
  290. /**
  291. * The computers class
  292. *
  293. * @var adLDAPComputers
  294. */
  295. protected $computersClass;
  296. /**
  297. * Get the computers class interface
  298. *
  299. * @return adLDAPComputers
  300. */
  301. public function computer() {
  302. if (!$this->computerClass) {
  303. $this->computerClass = new adLDAPComputers($this);
  304. }
  305. return $this->computerClass;
  306. }
  307. /**
  308. * Getters and Setters
  309. */
  310. /**
  311. * Set the account suffix
  312. *
  313. * @param string $accountSuffix
  314. * @return void
  315. */
  316. public function setAccountSuffix($accountSuffix)
  317. {
  318. $this->accountSuffix = $accountSuffix;
  319. }
  320. /**
  321. * Get the account suffix
  322. *
  323. * @return string
  324. */
  325. public function getAccountSuffix()
  326. {
  327. return $this->accountSuffix;
  328. }
  329. /**
  330. * Set the domain controllers array
  331. *
  332. * @param array $domainControllers
  333. * @return void
  334. */
  335. public function setDomainControllers(array $domainControllers)
  336. {
  337. $this->domainControllers = $domainControllers;
  338. }
  339. /**
  340. * Get the list of domain controllers
  341. *
  342. * @return void
  343. */
  344. public function getDomainControllers()
  345. {
  346. return $this->domainControllers;
  347. }
  348. /**
  349. * Sets the port number your domain controller communicates over
  350. *
  351. * @param int $adPort
  352. */
  353. public function setPort($adPort)
  354. {
  355. $this->adPort = $adPort;
  356. }
  357. /**
  358. * Gets the port number your domain controller communicates over
  359. *
  360. * @return int
  361. */
  362. public function getPort()
  363. {
  364. return $this->adPort;
  365. }
  366. /**
  367. * Set the username of an account with higher priviledges
  368. *
  369. * @param string $adminUsername
  370. * @return void
  371. */
  372. public function setAdminUsername($adminUsername)
  373. {
  374. $this->adminUsername = $adminUsername;
  375. }
  376. /**
  377. * Get the username of the account with higher priviledges
  378. *
  379. * This will throw an exception for security reasons
  380. */
  381. public function getAdminUsername()
  382. {
  383. throw new adLDAPException('For security reasons you cannot access the domain administrator account details');
  384. }
  385. /**
  386. * Set the password of an account with higher priviledges
  387. *
  388. * @param string $adminPassword
  389. * @return void
  390. */
  391. public function setAdminPassword($adminPassword)
  392. {
  393. $this->adminPassword = $adminPassword;
  394. }
  395. /**
  396. * Get the password of the account with higher priviledges
  397. *
  398. * This will throw an exception for security reasons
  399. */
  400. public function getAdminPassword()
  401. {
  402. throw new adLDAPException('For security reasons you cannot access the domain administrator account details');
  403. }
  404. /**
  405. * Set whether to detect the true primary group
  406. *
  407. * @param bool $realPrimaryGroup
  408. * @return void
  409. */
  410. public function setRealPrimaryGroup($realPrimaryGroup)
  411. {
  412. $this->realPrimaryGroup = $realPrimaryGroup;
  413. }
  414. /**
  415. * Get the real primary group setting
  416. *
  417. * @return bool
  418. */
  419. public function getRealPrimaryGroup()
  420. {
  421. return $this->realPrimaryGroup;
  422. }
  423. /**
  424. * Set whether to use SSL
  425. *
  426. * @param bool $useSSL
  427. * @return void
  428. */
  429. public function setUseSSL($useSSL)
  430. {
  431. $this->useSSL = $useSSL;
  432. // Set the default port correctly
  433. if($this->useSSL) {
  434. $this->setPort(self::ADLDAP_LDAPS_PORT);
  435. }
  436. else {
  437. $this->setPort(self::ADLDAP_LDAP_PORT);
  438. }
  439. }
  440. /**
  441. * Get the SSL setting
  442. *
  443. * @return bool
  444. */
  445. public function getUseSSL()
  446. {
  447. return $this->useSSL;
  448. }
  449. /**
  450. * Set whether to use TLS
  451. *
  452. * @param bool $useTLS
  453. * @return void
  454. */
  455. public function setUseTLS($useTLS)
  456. {
  457. $this->useTLS = $useTLS;
  458. }
  459. /**
  460. * Get the TLS setting
  461. *
  462. * @return bool
  463. */
  464. public function getUseTLS()
  465. {
  466. return $this->useTLS;
  467. }
  468. /**
  469. * Set whether to use SSO
  470. * Requires ldap_sasl_bind support. Be sure --with-ldap-sasl is used when configuring PHP otherwise this function will be undefined.
  471. *
  472. * @param bool $useSSO
  473. * @return void
  474. */
  475. public function setUseSSO($useSSO)
  476. {
  477. if ($useSSO === true && !$this->ldapSaslSupported()) {
  478. throw new adLDAPException('No LDAP SASL support for PHP. See: http://php.net/ldap_sasl_bind');
  479. }
  480. $this->useSSO = $useSSO;
  481. }
  482. /**
  483. * Get the SSO setting
  484. *
  485. * @return bool
  486. */
  487. public function getUseSSO()
  488. {
  489. return $this->useSSO;
  490. }
  491. /**
  492. * Set whether to lookup recursive groups
  493. *
  494. * @param bool $recursiveGroups
  495. * @return void
  496. */
  497. public function setRecursiveGroups($recursiveGroups)
  498. {
  499. $this->recursiveGroups = $recursiveGroups;
  500. }
  501. /**
  502. * Get the recursive groups setting
  503. *
  504. * @return bool
  505. */
  506. public function getRecursiveGroups()
  507. {
  508. return $this->recursiveGroups;
  509. }
  510. /**
  511. * Default Constructor
  512. *
  513. * Tries to bind to the AD domain over LDAP or LDAPs
  514. *
  515. * @param array $options Array of options to pass to the constructor
  516. * @throws Exception - if unable to bind to Domain Controller
  517. * @return bool
  518. */
  519. function __construct($options = array()) {
  520. // You can specifically overide any of the default configuration options setup above
  521. if (count($options) > 0) {
  522. if (array_key_exists("account_suffix",$options)){ $this->accountSuffix = $options["account_suffix"]; }
  523. if (array_key_exists("base_dn",$options)){ $this->baseDn = $options["base_dn"]; }
  524. if (array_key_exists("domain_controllers",$options)){
  525. if (!is_array($options["domain_controllers"])) {
  526. throw new adLDAPException('[domain_controllers] option must be an array');
  527. }
  528. $this->domainControllers = $options["domain_controllers"];
  529. }
  530. if (array_key_exists("admin_username",$options)){ $this->adminUsername = $options["admin_username"]; }
  531. if (array_key_exists("admin_password",$options)){ $this->adminPassword = $options["admin_password"]; }
  532. if (array_key_exists("real_primarygroup",$options)){ $this->realPrimaryGroup = $options["real_primarygroup"]; }
  533. if (array_key_exists("use_ssl",$options)){ $this->setUseSSL($options["use_ssl"]); }
  534. if (array_key_exists("use_tls",$options)){ $this->useTLS = $options["use_tls"]; }
  535. if (array_key_exists("recursive_groups",$options)){ $this->recursiveGroups = $options["recursive_groups"]; }
  536. if (array_key_exists("ad_port",$options)){ $this->setPort($options["ad_port"]); }
  537. if (array_key_exists("sso",$options)) {
  538. $this->setUseSSO($options["sso"]);
  539. if (!$this->ldapSaslSupported()) {
  540. $this->setUseSSO(false);
  541. }
  542. }
  543. }
  544. if ($this->ldapSupported() === false) {
  545. throw new adLDAPException('No LDAP support for PHP. See: http://php.net/ldap');
  546. }
  547. return $this->connect();
  548. }
  549. /**
  550. * Default Destructor
  551. *
  552. * Closes the LDAP connection
  553. *
  554. * @return void
  555. */
  556. function __destruct() {
  557. $this->close();
  558. }
  559. /**
  560. * Connects and Binds to the Domain Controller
  561. *
  562. * @return bool
  563. */
  564. public function connect()
  565. {
  566. // Connect to the AD/LDAP server as the username/password
  567. $domainController = $this->randomController();
  568. if ($this->useSSL) {
  569. $this->ldapConnection = ldap_connect("ldaps://" . $domainController, $this->adPort);
  570. } else {
  571. $this->ldapConnection = ldap_connect($domainController, $this->adPort);
  572. }
  573. // Set some ldap options for talking to AD
  574. ldap_set_option($this->ldapConnection, LDAP_OPT_PROTOCOL_VERSION, 3);
  575. ldap_set_option($this->ldapConnection, LDAP_OPT_REFERRALS, 0);
  576. if ($this->useTLS) {
  577. ldap_start_tls($this->ldapConnection);
  578. }
  579. // Bind as a domain admin if they've set it up
  580. if ($this->adminUsername !== NULL && $this->adminPassword !== NULL) {
  581. $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix, $this->adminPassword);
  582. if (!$this->ldapBind) {
  583. if ($this->useSSL && !$this->useTLS) {
  584. // If you have problems troubleshooting, remove the @ character from the ldapldapBind command above to get the actual error message
  585. throw new adLDAPException('Bind to Active Directory failed. Either the LDAPs connection failed or the login credentials are incorrect. AD said: ' . $this->getLastError());
  586. }
  587. else {
  588. throw new adLDAPException('Bind to Active Directory failed. Check the login credentials and/or server details. AD said: ' . $this->getLastError());
  589. }
  590. }
  591. }
  592. if ($this->useSSO && $_SERVER['REMOTE_USER'] && $this->adminUsername === null && $_SERVER['KRB5CCNAME']) {
  593. putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);
  594. $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI");
  595. if (!$this->ldapBind){
  596. throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
  597. }
  598. else {
  599. return true;
  600. }
  601. }
  602. if ($this->baseDn == NULL) {
  603. $this->baseDn = $this->findBaseDn();
  604. }
  605. return true;
  606. }
  607. /**
  608. * Closes the LDAP connection
  609. *
  610. * @return void
  611. */
  612. public function close() {
  613. if ($this->ldapConnection) {
  614. @ldap_close($this->ldapConnection);
  615. }
  616. }
  617. /**
  618. * Validate a user's login credentials
  619. *
  620. * @param string $username A user's AD username
  621. * @param string $password A user's AD password
  622. * @param bool optional $preventRebind
  623. * @return bool
  624. */
  625. public function authenticate($username, $password, $preventRebind = false) {
  626. // Prevent null binding
  627. if ($username === NULL || $password === NULL) { return false; }
  628. if (empty($username) || empty($password)) { return false; }
  629. // Allow binding over SSO for Kerberos
  630. if ($this->useSSO && $_SERVER['REMOTE_USER'] && $_SERVER['REMOTE_USER'] == $username && $this->adminUsername === NULL && $_SERVER['KRB5CCNAME']) {
  631. putenv("KRB5CCNAME=" . $_SERVER['KRB5CCNAME']);
  632. $this->ldapBind = @ldap_sasl_bind($this->ldapConnection, NULL, NULL, "GSSAPI");
  633. if (!$this->ldapBind) {
  634. throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
  635. }
  636. else {
  637. return true;
  638. }
  639. }
  640. // Bind as the user
  641. $ret = true;
  642. $this->ldapBind = @ldap_bind($this->ldapConnection, $username . $this->accountSuffix, $password);
  643. if (!$this->ldapBind){
  644. $ret = false;
  645. }
  646. // Cnce we've checked their details, kick back into admin mode if we have it
  647. if ($this->adminUsername !== NULL && !$preventRebind) {
  648. $this->ldapBind = @ldap_bind($this->ldapConnection, $this->adminUsername . $this->accountSuffix , $this->adminPassword);
  649. if (!$this->ldapBind){
  650. // This should never happen in theory
  651. throw new adLDAPException('Rebind to Active Directory failed. AD said: ' . $this->getLastError());
  652. }
  653. }
  654. return $ret;
  655. }
  656. /**
  657. * Find the Base DN of your domain controller
  658. *
  659. * @return string
  660. */
  661. public function findBaseDn()
  662. {
  663. $namingContext = $this->getRootDse(array('defaultnamingcontext'));
  664. return $namingContext[0]['defaultnamingcontext'][0];
  665. }
  666. /**
  667. * Get the RootDSE properties from a domain controller
  668. *
  669. * @param array $attributes The attributes you wish to query e.g. defaultnamingcontext
  670. * @return array
  671. */
  672. public function getRootDse($attributes = array("*", "+")) {
  673. if (!$this->ldapBind){ return (false); }
  674. $sr = @ldap_read($this->ldapConnection, NULL, 'objectClass=*', $attributes);
  675. $entries = @ldap_get_entries($this->ldapConnection, $sr);
  676. return $entries;
  677. }
  678. /**
  679. * Get last error from Active Directory
  680. *
  681. * This function gets the last message from Active Directory
  682. * This may indeed be a 'Success' message but if you get an unknown error
  683. * it might be worth calling this function to see what errors were raised
  684. *
  685. * return string
  686. */
  687. public function getLastError() {
  688. return @ldap_error($this->ldapConnection);
  689. }
  690. /**
  691. * Detect LDAP support in php
  692. *
  693. * @return bool
  694. */
  695. protected function ldapSupported()
  696. {
  697. if (!function_exists('ldap_connect')) {
  698. return false;
  699. }
  700. return true;
  701. }
  702. /**
  703. * Detect ldap_sasl_bind support in PHP
  704. *
  705. * @return bool
  706. */
  707. protected function ldapSaslSupported()
  708. {
  709. if (!function_exists('ldap_sasl_bind')) {
  710. return false;
  711. }
  712. return true;
  713. }
  714. /**
  715. * Schema
  716. *
  717. * @param array $attributes Attributes to be queried
  718. * @return array
  719. */
  720. public function adldap_schema($attributes){
  721. // LDAP doesn't like NULL attributes, only set them if they have values
  722. // If you wish to remove an attribute you should set it to a space
  723. // TO DO: Adapt user_modify to use ldap_mod_delete to remove a NULL attribute
  724. $mod=array();
  725. // Check every attribute to see if it contains 8bit characters and then UTF8 encode them
  726. array_walk($attributes, array($this, 'encode8bit'));
  727. if ($attributes["address_city"]){ $mod["l"][0]=$attributes["address_city"]; }
  728. if ($attributes["address_code"]){ $mod["postalCode"][0]=$attributes["address_code"]; }
  729. //if ($attributes["address_country"]){ $mod["countryCode"][0]=$attributes["address_country"]; } // use country codes?
  730. if ($attributes["address_country"]){ $mod["c"][0]=$attributes["address_country"]; }
  731. if ($attributes["address_pobox"]){ $mod["postOfficeBox"][0]=$attributes["address_pobox"]; }
  732. if ($attributes["address_state"]){ $mod["st"][0]=$attributes["address_state"]; }
  733. if ($attributes["address_street"]){ $mod["streetAddress"][0]=$attributes["address_street"]; }
  734. if ($attributes["company"]){ $mod["company"][0]=$attributes["company"]; }
  735. if ($attributes["change_password"]){ $mod["pwdLastSet"][0]=0; }
  736. if ($attributes["department"]){ $mod["department"][0]=$attributes["department"]; }
  737. if ($attributes["description"]){ $mod["description"][0]=$attributes["description"]; }
  738. if ($attributes["display_name"]){ $mod["displayName"][0]=$attributes["display_name"]; }
  739. if ($attributes["email"]){ $mod["mail"][0]=$attributes["email"]; }
  740. if ($attributes["expires"]){ $mod["accountExpires"][0]=$attributes["expires"]; } //unix epoch format?
  741. if ($attributes["firstname"]){ $mod["givenName"][0]=$attributes["firstname"]; }
  742. if ($attributes["home_directory"]){ $mod["homeDirectory"][0]=$attributes["home_directory"]; }
  743. if ($attributes["home_drive"]){ $mod["homeDrive"][0]=$attributes["home_drive"]; }
  744. if ($attributes["initials"]){ $mod["initials"][0]=$attributes["initials"]; }
  745. if ($attributes["logon_name"]){ $mod["userPrincipalName"][0]=$attributes["logon_name"]; }
  746. if ($attributes["manager"]){ $mod["manager"][0]=$attributes["manager"]; } //UNTESTED ***Use DistinguishedName***
  747. if ($attributes["office"]){ $mod["physicalDeliveryOfficeName"][0]=$attributes["office"]; }
  748. if ($attributes["password"]){ $mod["unicodePwd"][0]=$this->user()->encodePassword($attributes["password"]); }
  749. if ($attributes["profile_path"]){ $mod["profilepath"][0]=$attributes["profile_path"]; }
  750. if ($attributes["script_path"]){ $mod["scriptPath"][0]=$attributes["script_path"]; }
  751. if ($attributes["surname"]){ $mod["sn"][0]=$attributes["surname"]; }
  752. if ($attributes["title"]){ $mod["title"][0]=$attributes["title"]; }
  753. if ($attributes["telephone"]){ $mod["telephoneNumber"][0]=$attributes["telephone"]; }
  754. if ($attributes["mobile"]){ $mod["mobile"][0]=$attributes["mobile"]; }
  755. if ($attributes["pager"]){ $mod["pager"][0]=$attributes["pager"]; }
  756. if ($attributes["ipphone"]){ $mod["ipphone"][0]=$attributes["ipphone"]; }
  757. if ($attributes["web_page"]){ $mod["wWWHomePage"][0]=$attributes["web_page"]; }
  758. if ($attributes["fax"]){ $mod["facsimileTelephoneNumber"][0]=$attributes["fax"]; }
  759. if ($attributes["enabled"]){ $mod["userAccountControl"][0]=$attributes["enabled"]; }
  760. if ($attributes["homephone"]){ $mod["homephone"][0]=$attributes["homephone"]; }
  761. // Distribution List specific schema
  762. if ($attributes["group_sendpermission"]){ $mod["dlMemSubmitPerms"][0]=$attributes["group_sendpermission"]; }
  763. if ($attributes["group_rejectpermission"]){ $mod["dlMemRejectPerms"][0]=$attributes["group_rejectpermission"]; }
  764. // Exchange Schema
  765. if ($attributes["exchange_homemdb"]){ $mod["homeMDB"][0]=$attributes["exchange_homemdb"]; }
  766. if ($attributes["exchange_mailnickname"]){ $mod["mailNickname"][0]=$attributes["exchange_mailnickname"]; }
  767. if ($attributes["exchange_proxyaddress"]){ $mod["proxyAddresses"][0]=$attributes["exchange_proxyaddress"]; }
  768. if ($attributes["exchange_usedefaults"]){ $mod["mDBUseDefaults"][0]=$attributes["exchange_usedefaults"]; }
  769. if ($attributes["exchange_policyexclude"]){ $mod["msExchPoliciesExcluded"][0]=$attributes["exchange_policyexclude"]; }
  770. if ($attributes["exchange_policyinclude"]){ $mod["msExchPoliciesIncluded"][0]=$attributes["exchange_policyinclude"]; }
  771. if ($attributes["exchange_addressbook"]){ $mod["showInAddressBook"][0]=$attributes["exchange_addressbook"]; }
  772. if ($attributes["exchange_altrecipient"]){ $mod["altRecipient"][0]=$attributes["exchange_altrecipient"]; }
  773. if ($attributes["exchange_deliverandredirect"]){ $mod["deliverAndRedirect"][0]=$attributes["exchange_deliverandredirect"]; }
  774. // This schema is designed for contacts
  775. if ($attributes["exchange_hidefromlists"]){ $mod["msExchHideFromAddressLists"][0]=$attributes["exchange_hidefromlists"]; }
  776. if ($attributes["contact_email"]){ $mod["targetAddress"][0]=$attributes["contact_email"]; }
  777. //echo ("<pre>"); print_r($mod);
  778. /*
  779. // modifying a name is a bit fiddly
  780. if ($attributes["firstname"] && $attributes["surname"]){
  781. $mod["cn"][0]=$attributes["firstname"]." ".$attributes["surname"];
  782. $mod["displayname"][0]=$attributes["firstname"]." ".$attributes["surname"];
  783. $mod["name"][0]=$attributes["firstname"]." ".$attributes["surname"];
  784. }
  785. */
  786. if (count($mod)==0){ return (false); }
  787. return ($mod);
  788. }
  789. /**
  790. * Convert 8bit characters e.g. accented characters to UTF8 encoded characters
  791. */
  792. protected function encode8Bit(&$item, $key) {
  793. $encode = false;
  794. if (is_string($item)) {
  795. for ($i=0; $i<strlen($item); $i++) {
  796. if (ord($item[$i]) >> 7) {
  797. $encode = true;
  798. }
  799. }
  800. }
  801. if ($encode === true && $key != 'password') {
  802. $item = utf8_encode($item);
  803. }
  804. }
  805. /**
  806. * Select a random domain controller from your domain controller array
  807. *
  808. * @return string
  809. */
  810. protected function randomController()
  811. {
  812. mt_srand(doubleval(microtime()) * 100000000); // For older PHP versions
  813. /*if (sizeof($this->domainControllers) > 1) {
  814. $adController = $this->domainControllers[array_rand($this->domainControllers)];
  815. // Test if the controller is responding to pings
  816. $ping = $this->pingController($adController);
  817. if ($ping === false) {
  818. // Find the current key in the domain controllers array
  819. $key = array_search($adController, $this->domainControllers);
  820. // Remove it so that we don't end up in a recursive loop
  821. unset($this->domainControllers[$key]);
  822. // Select a new controller
  823. return $this->randomController();
  824. }
  825. else {
  826. return ($adController);
  827. }
  828. } */
  829. return $this->domainControllers[array_rand($this->domainControllers)];
  830. }
  831. /**
  832. * Test basic connectivity to controller
  833. *
  834. * @return bool
  835. */
  836. protected function pingController($host) {
  837. $port = $this->adPort;
  838. fsockopen($host, $port, $errno, $errstr, 10);
  839. if ($errno > 0) {
  840. return false;
  841. }
  842. return true;
  843. }
  844. }
  845. /**
  846. * adLDAP Exception Handler
  847. *
  848. * Exceptions of this type are thrown on bind failure or when SSL is required but not configured
  849. * Example:
  850. * try {
  851. * $adldap = new adLDAP();
  852. * }
  853. * catch (adLDAPException $e) {
  854. * echo $e;
  855. * exit();
  856. * }
  857. */
  858. class adLDAPException extends Exception {}