You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ldap.php 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. <?php
  2. /**
  3. * LDAP Password Driver
  4. *
  5. * Driver for passwords stored in LDAP
  6. * This driver use the PEAR Net_LDAP2 class (http://pear.php.net/package/Net_LDAP2).
  7. *
  8. * @version 2.0
  9. * @author Edouard MOREAU <edouard.moreau@ensma.fr>
  10. *
  11. * method hashPassword based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
  12. * method randomSalt based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
  13. *
  14. * Copyright (C) 2005-2014, The Roundcube Dev Team
  15. *
  16. * This program is free software: you can redistribute it and/or modify
  17. * it under the terms of the GNU General Public License as published by
  18. * the Free Software Foundation, either version 3 of the License, or
  19. * (at your option) any later version.
  20. *
  21. * This program is distributed in the hope that it will be useful,
  22. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  24. * GNU General Public License for more details.
  25. *
  26. * You should have received a copy of the GNU General Public License
  27. * along with this program. If not, see http://www.gnu.org/licenses/.
  28. */
  29. class rcube_ldap_password
  30. {
  31. public function save($curpass, $passwd)
  32. {
  33. $rcmail = rcmail::get_instance();
  34. require_once 'Net/LDAP2.php';
  35. // Building user DN
  36. if ($userDN = $rcmail->config->get('password_ldap_userDN_mask')) {
  37. $userDN = self::substitute_vars($userDN);
  38. }
  39. else {
  40. $userDN = $this->search_userdn($rcmail);
  41. }
  42. if (empty($userDN)) {
  43. return PASSWORD_CONNECT_ERROR;
  44. }
  45. // Connection Method
  46. switch($rcmail->config->get('password_ldap_method')) {
  47. case 'admin':
  48. $binddn = $rcmail->config->get('password_ldap_adminDN');
  49. $bindpw = $rcmail->config->get('password_ldap_adminPW');
  50. break;
  51. case 'user':
  52. default:
  53. $binddn = $userDN;
  54. $bindpw = $curpass;
  55. break;
  56. }
  57. // Configuration array
  58. $ldapConfig = array (
  59. 'binddn' => $binddn,
  60. 'bindpw' => $bindpw,
  61. 'basedn' => $rcmail->config->get('password_ldap_basedn'),
  62. 'host' => $rcmail->config->get('password_ldap_host'),
  63. 'port' => $rcmail->config->get('password_ldap_port'),
  64. 'starttls' => $rcmail->config->get('password_ldap_starttls'),
  65. 'version' => $rcmail->config->get('password_ldap_version'),
  66. );
  67. // Connecting using the configuration array
  68. $ldap = Net_LDAP2::connect($ldapConfig);
  69. // Checking for connection error
  70. if (is_a($ldap, 'PEAR_Error')) {
  71. return PASSWORD_CONNECT_ERROR;
  72. }
  73. $force = $rcmail->config->get('password_ldap_force_replace');
  74. $pwattr = $rcmail->config->get('password_ldap_pwattr');
  75. $lchattr = $rcmail->config->get('password_ldap_lchattr');
  76. $smbpwattr = $rcmail->config->get('password_ldap_samba_pwattr');
  77. $smblchattr = $rcmail->config->get('password_ldap_samba_lchattr');
  78. $samba = $rcmail->config->get('password_ldap_samba');
  79. $encodage = $rcmail->config->get('password_ldap_encodage');
  80. // Support multiple userPassword values where desired.
  81. // multiple encodings can be specified separated by '+' (e.g. "cram-md5+ssha")
  82. $encodages = explode('+', $encodage);
  83. $crypted_pass = array();
  84. foreach ($encodages as $enc) {
  85. if ($cpw = password::hash_password($passwd, $enc)) {
  86. $crypted_pass[] = $cpw;
  87. }
  88. }
  89. // Support password_ldap_samba option for backward compat.
  90. if ($samba && !$smbpwattr) {
  91. $smbpwattr = 'sambaNTPassword';
  92. $smblchattr = 'sambaPwdLastSet';
  93. }
  94. // Crypt new password
  95. if (empty($crypted_pass)) {
  96. return PASSWORD_CRYPT_ERROR;
  97. }
  98. // Crypt new samba password
  99. if ($smbpwattr && !($samba_pass = password::hash_password($passwd, 'samba'))) {
  100. return PASSWORD_CRYPT_ERROR;
  101. }
  102. // Writing new crypted password to LDAP
  103. $userEntry = $ldap->getEntry($userDN);
  104. if (Net_LDAP2::isError($userEntry)) {
  105. return PASSWORD_CONNECT_ERROR;
  106. }
  107. if (!$userEntry->replace(array($pwattr => $crypted_pass), $force)) {
  108. return PASSWORD_CONNECT_ERROR;
  109. }
  110. // Updating PasswordLastChange Attribute if desired
  111. if ($lchattr) {
  112. $current_day = (int)(time() / 86400);
  113. if (!$userEntry->replace(array($lchattr => $current_day), $force)) {
  114. return PASSWORD_CONNECT_ERROR;
  115. }
  116. }
  117. // Update Samba password and last change fields
  118. if ($smbpwattr) {
  119. $userEntry->replace(array($smbpwattr => $samba_pass), $force);
  120. }
  121. // Update Samba password last change field
  122. if ($smblchattr) {
  123. $userEntry->replace(array($smblchattr => time()), $force);
  124. }
  125. if (Net_LDAP2::isError($userEntry->update())) {
  126. return PASSWORD_CONNECT_ERROR;
  127. }
  128. // All done, no error
  129. return PASSWORD_SUCCESS;
  130. }
  131. /**
  132. * Bind with searchDN and searchPW and search for the user's DN.
  133. * Use search_base and search_filter defined in config file.
  134. * Return the found DN.
  135. */
  136. function search_userdn($rcmail)
  137. {
  138. $binddn = $rcmail->config->get('password_ldap_searchDN');
  139. $bindpw = $rcmail->config->get('password_ldap_searchPW');
  140. $ldapConfig = array (
  141. 'basedn' => $rcmail->config->get('password_ldap_basedn'),
  142. 'host' => $rcmail->config->get('password_ldap_host'),
  143. 'port' => $rcmail->config->get('password_ldap_port'),
  144. 'starttls' => $rcmail->config->get('password_ldap_starttls'),
  145. 'version' => $rcmail->config->get('password_ldap_version'),
  146. );
  147. // allow anonymous searches
  148. if (!empty($binddn)) {
  149. $ldapConfig['binddn'] = $binddn;
  150. $ldapConfig['bindpw'] = $bindpw;
  151. }
  152. $ldap = Net_LDAP2::connect($ldapConfig);
  153. if (is_a($ldap, 'PEAR_Error')) {
  154. return '';
  155. }
  156. $base = self::substitute_vars($rcmail->config->get('password_ldap_search_base'));
  157. $filter = self::substitute_vars($rcmail->config->get('password_ldap_search_filter'));
  158. $options = array (
  159. 'scope' => 'sub',
  160. 'attributes' => array(),
  161. );
  162. $result = $ldap->search($base, $filter, $options);
  163. if (is_a($result, 'PEAR_Error') || ($result->count() != 1)) {
  164. $ldap->done();
  165. return '';
  166. }
  167. $userDN = $result->current()->dn();
  168. $ldap->done();
  169. return $userDN;
  170. }
  171. /**
  172. * Substitute %login, %name, %domain, %dc in $str
  173. * See plugin config for details
  174. */
  175. static function substitute_vars($str)
  176. {
  177. $str = str_replace('%login', $_SESSION['username'], $str);
  178. $str = str_replace('%l', $_SESSION['username'], $str);
  179. $parts = explode('@', $_SESSION['username']);
  180. if (count($parts) == 2) {
  181. $dc = 'dc='.strtr($parts[1], array('.' => ',dc=')); // hierarchal domain string
  182. $str = str_replace('%name', $parts[0], $str);
  183. $str = str_replace('%n', $parts[0], $str);
  184. $str = str_replace('%dc', $dc, $str);
  185. $str = str_replace('%domain', $parts[1], $str);
  186. $str = str_replace('%d', $parts[1], $str);
  187. }
  188. return $str;
  189. }
  190. }