PageRenderTime 46ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/trunk/squirrelmail/plugins/change_password/backend/ldap.php

#
PHP | 784 lines | 560 code | 53 blank | 171 comment | 78 complexity | 3d1fe1f60dde901b5f4ada1d8583bb93 MD5 | raw file
Possible License(s): AGPL-1.0, GPL-2.0
  1. <?php
  2. /**
  3. * Change password LDAP backend
  4. *
  5. * @copyright 2005-2012 The SquirrelMail Project Team
  6. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  7. * @version $Id: ldap.php 14249 2012-01-02 02:09:17Z pdontthink $
  8. * @package plugins
  9. * @subpackage change_password
  10. */
  11. /**
  12. * do not allow to call this file directly
  13. */
  14. if (isset($_SERVER['SCRIPT_FILENAME']) && $_SERVER['SCRIPT_FILENAME'] == __FILE__) {
  15. header("Location: ../../../src/login.php");
  16. die();
  17. }
  18. /** load required functions */
  19. /** sqimap_get_user_server() function */
  20. include_once(SM_PATH . 'functions/imap_general.php');
  21. /** get imap server and username globals */
  22. global $imapServerAddress, $username;
  23. /** Default plugin configuration.*/
  24. /**
  25. * Address of LDAP server.
  26. * You can use any URL format that is supported by your LDAP extension.
  27. * Examples:
  28. * <ul>
  29. * <li>'ldap.example.com' - connect to server on ldap.example.com address
  30. * <li>'ldaps://ldap.example.com' - connect to server on ldap.example.com address
  31. * and use SSL encrypted connection to default LDAPs port.
  32. * </ul>
  33. * defaults to imap server address.
  34. * @link http://www.php.net/ldap-connect
  35. * @global string $cpw_ldap_server
  36. */
  37. global $cpw_ldap_server;
  38. $cpw_ldap_server=$imapServerAddress;
  39. /**
  40. * Port of LDAP server.
  41. * Used only when $cpw_ldap_server specifies IP address or DNS name.
  42. * @global integer $cpw_ldap_port
  43. */
  44. global $cpw_ldap_port;
  45. $cpw_ldap_port=389;
  46. /**
  47. * LDAP basedn that is used for binding to LDAP server.
  48. * this option must be set to correct value.
  49. * @global string $cpw_ldap_basedn;
  50. */
  51. global $cpw_ldap_basedn;
  52. $cpw_ldap_basedn='';
  53. /**
  54. * LDAP connection options
  55. * @link http://www.php.net/ldap-set-option
  56. * @global array $cpw_ldap_connect_opts
  57. */
  58. global $cpw_ldap_connect_opts;
  59. $cpw_ldap_connect_opts=array();
  60. /**
  61. * Controls use of starttls on LDAP connection.
  62. * Requires PHP 4.2+, PHP LDAP extension with SSL support and
  63. * PROTOCOL_VERSION => 3 setting in $cpw_ldap_connect_opts
  64. * @global boolean $cpw_ldap_use_tls
  65. */
  66. global $cpw_ldap_use_tls;
  67. $cpw_ldap_use_tls=false;
  68. /**
  69. * BindDN that should be able to search LDAP directory and find DN used by user.
  70. * Uses anonymous bind if set to empty string. You should not use DN with write
  71. * access to LDAP directory here. Write access is not required.
  72. * @global string $cpw_ldap_binddn
  73. */
  74. global $cpw_ldap_binddn;
  75. $cpw_ldap_binddn='';
  76. /**
  77. * password used for $cpw_ldap_binddn
  78. * @global string $cpw_ldap_bindpw
  79. */
  80. global $cpw_ldap_bindpw;
  81. $cpw_ldap_bindpw='';
  82. /**
  83. * BindDN that should be able to change password.
  84. * WARNING: sometimes user has enough privileges to change own password.
  85. * If you leave default value, plugin will try to connect with DN that
  86. * is detected in $cpw_ldap_username_attr=$username search and current
  87. * user password will be used for authentication.
  88. * @global string $cpw_ldap_admindn
  89. */
  90. global $cpw_ldap_admindn;
  91. $cpw_ldap_admindn='';
  92. /**
  93. * password used for $cpw_ldap_admindn
  94. * @global string $cpw_ldap_adminpw
  95. */
  96. global $cpw_ldap_adminpw;
  97. $cpw_ldap_adminpw='';
  98. /**
  99. * LDAP attribute that stores username.
  100. * username entry should be unique for $cpw_ldap_basedn
  101. * @global string $cpw_ldap_userid_attr
  102. */
  103. global $cpw_ldap_userid_attr;
  104. $cpw_ldap_userid_attr='uid';
  105. /**
  106. * crypto that is used to encode new password
  107. * If set to empty string, system tries to keep same encoding/hashing algorithm
  108. * @global string $cpw_ldap_default_crypto
  109. */
  110. global $cpw_ldap_default_crypto;
  111. $cpw_ldap_default_crypto='';
  112. /** end of default config */
  113. /** configuration overrides from config file */
  114. if (isset($cpw_ldap['server'])) $cpw_ldap_server=$cpw_ldap['server'];
  115. if (isset($cpw_ldap['port'])) $cpw_ldap_port=$cpw_ldap['port'];
  116. if (isset($cpw_ldap['basedn'])) $cpw_ldap_basedn=$cpw_ldap['basedn'];
  117. if (isset($cpw_ldap['connect_opts'])) $cpw_ldap_connect_opts=$cpw_ldap['connect_opts'];
  118. if (isset($cpw_ldap['use_tls'])) $cpw_ldap_use_tls=$cpw_ldap['use_tls'];
  119. if (isset($cpw_ldap['binddn'])) $cpw_ldap_binddn=$cpw_ldap['binddn'];
  120. if (isset($cpw_ldap['bindpw'])) $cpw_ldap_bindpw=$cpw_ldap['bindpw'];
  121. if (isset($cpw_ldap['admindn'])) $cpw_ldap_admindn=$cpw_ldap['admindn'];
  122. if (isset($cpw_ldap['adminpw'])) $cpw_ldap_adminpw=$cpw_ldap['adminpw'];
  123. if (isset($cpw_ldap['userid_attr'])) $cpw_ldap_userid_attr=$cpw_ldap['userid_attr'];
  124. if (isset($cpw_ldap['default_crypto'])) $cpw_ldap_default_crypto=$cpw_ldap['default_crypto'];
  125. /** make sure that setting does not contain mapping */
  126. $cpw_ldap_server=sqimap_get_user_server($cpw_ldap_server,$username);
  127. /**
  128. * Adding plugin hooks
  129. */
  130. global $squirrelmail_plugin_hooks;
  131. $squirrelmail_plugin_hooks['change_password_dochange']['ldap'] =
  132. 'cpw_ldap_dochange';
  133. $squirrelmail_plugin_hooks['change_password_init']['ldap'] =
  134. 'cpw_ldap_init';
  135. /**
  136. * Makes sure that required functions and configuration options are set.
  137. */
  138. function cpw_ldap_init() {
  139. global $oTemplate, $cpw_ldap_basedn;
  140. // set initial value for error tracker
  141. $cpw_ldap_initerr=false;
  142. // check for ldap support in php
  143. if (! function_exists('ldap_connect')) {
  144. error_box(_("Current configuration requires LDAP support in PHP."));
  145. $cpw_ldap_initerr=true;
  146. }
  147. // chech required configuration settings.
  148. if ($cpw_ldap_basedn=='') {
  149. error_box(_("Plugin is not configured correctly."));
  150. $cpw_ldap_initerr=true;
  151. }
  152. // if error var is positive, close html and stop execution
  153. if ($cpw_ldap_initerr) {
  154. $oTemplate->display('footer.tpl');
  155. exit;
  156. }
  157. }
  158. /**
  159. * Changes password. Main function attached to hook
  160. * @param array $data The username/curpw/newpw data.
  161. * @return array Array of error messages.
  162. */
  163. function cpw_ldap_dochange($data) {
  164. global $cpw_ldap_server, $cpw_ldap_port, $cpw_ldap_basedn,
  165. $cpw_ldap_connect_opts,$cpw_ldap_use_tls,
  166. $cpw_ldap_binddn, $cpw_ldap_bindpw,
  167. $cpw_ldap_admindn, $cpw_ldap_adminpw;
  168. // unfortunately, we can only pass one parameter to a hook function,
  169. // so we have to pass it as an array.
  170. $username = $data['username'];
  171. $curpw = $data['curpw'];
  172. $newpw = $data['newpw'];
  173. // globalize current password.
  174. $msgs = array();
  175. /**
  176. * connect to LDAP server
  177. * hide ldap_connect() function call errors, because they are processed in script.
  178. * any script execution error is treated as critical, error messages are dumped
  179. * to $msgs and LDAP connection is closed with ldap_unbind(). all ldap_unbind()
  180. * errors are suppressed. Any other error suppression should be explained.
  181. */
  182. $cpw_ldap_con=@ldap_connect($cpw_ldap_server);
  183. if ($cpw_ldap_con) {
  184. $cpw_ldap_con_err=false;
  185. // set connection options
  186. if (is_array($cpw_ldap_connect_opts) && $cpw_ldap_connect_opts!=array()) {
  187. // ldap_set_option() is available only with openldap 2.x and netscape directory sdk.
  188. if (function_exists('ldap_set_option')) {
  189. foreach ($cpw_ldap_connect_opts as $opt => $value) {
  190. // Make sure that constant is defined defore using it.
  191. if (defined('LDAP_OPT_' . $opt)) {
  192. // ldap_set_option() should not produce E_NOTICE or E_ALL errors and does not modify ldap_error().
  193. // leave it without @ in order to see any weird errors
  194. if (! ldap_set_option($cpw_ldap_con,constant('LDAP_OPT_' . $opt),$value)) {
  195. // set error message
  196. array_push($msgs,sprintf(_("Setting of LDAP connection option %s to value %s failed."),$opt,$value));
  197. $cpw_ldap_con_err=true;
  198. }
  199. } else {
  200. array_push($msgs,sprintf(_("Incorrect LDAP connection option: %s"),$opt));
  201. $cpw_ldap_con_err=true;
  202. }
  203. }
  204. } else {
  205. array_push($msgs,_("Current PHP LDAP extension does not allow use of ldap_set_option() function."));
  206. $cpw_ldap_con_err=true;
  207. }
  208. }
  209. // check for connection errors and stop execution if something is wrong
  210. if ($cpw_ldap_con_err) {
  211. @ldap_unbind($cpw_ldap_con);
  212. return $msgs;
  213. }
  214. // enable ldap starttls
  215. if ($cpw_ldap_use_tls &&
  216. check_php_version(4,2,0) &&
  217. isset($cpw_ldap_connect_opts['PROTOCOL_VERSION']) &&
  218. $cpw_ldap_connect_opts['PROTOCOL_VERSION']>=3 &&
  219. function_exists('ldap_start_tls')) {
  220. // suppress ldap_start_tls errors and process error messages
  221. if (! @ldap_start_tls($cpw_ldap_con)) {
  222. array_push($msgs,
  223. _("Unable to use TLS."),
  224. sprintf(_("Error: %s"),ldap_error($cpw_ldap_con)));
  225. $cpw_ldap_con_err=true;
  226. }
  227. } elseif ($cpw_ldap_use_tls) {
  228. array_push($msgs,_("Unable to use LDAP TLS in current setup."));
  229. $cpw_ldap_con_err=true;
  230. }
  231. // check for connection errors and stop execution if something is wrong
  232. if ($cpw_ldap_con_err) {
  233. @ldap_unbind($cpw_ldap_con);
  234. return $msgs;
  235. }
  236. /**
  237. * Bind to LDAP (use anonymous bind or unprivileged DN) in order to get user's DN
  238. * hide ldap_bind() function call errors, because errors are processed in script
  239. */
  240. if ($cpw_ldap_binddn!='') {
  241. // authenticated bind
  242. $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_binddn,$cpw_ldap_bindpw);
  243. } else {
  244. // anonymous bind
  245. $cpw_ldap_binding=@ldap_bind($cpw_ldap_con);
  246. }
  247. // check ldap_bind errors
  248. if (! $cpw_ldap_binding) {
  249. array_push($msgs,
  250. _("Unable to bind to LDAP server."),
  251. sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
  252. @ldap_unbind($cpw_ldap_con);
  253. return $msgs;
  254. }
  255. // find userdn
  256. $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res,$cpw_ldap_userdn);
  257. // check for search errors and stop execution if something is wrong
  258. if (! $cpw_ldap_search_err) {
  259. @ldap_unbind($cpw_ldap_con);
  260. return $msgs;
  261. }
  262. /**
  263. * unset $cpw_ldap_res2 variable, if such var exists.
  264. * $cpw_ldap_res2 object can be set in two places and second place checks,
  265. * if object was created in first place. if variable name matches (somebody
  266. * uses $cpw_ldap_res2 in code or globals), incorrect validation might
  267. * cause script errors.
  268. */
  269. if (isset($cpw_ldap_res2)) unset($cpw_ldap_res2);
  270. // rebind as userdn or admindn
  271. if ($cpw_ldap_admindn!='') {
  272. // admindn bind
  273. $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_admindn,$cpw_ldap_adminpw);
  274. if ($cpw_ldap_binding) {
  275. // repeat search in order to get password info. Password info should be unavailable in unprivileged bind.
  276. $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res2,$cpw_ldap_userdn);
  277. // check for connection errors and stop execution if something is wrong
  278. if (! $cpw_ldap_search_err) {
  279. @ldap_unbind($cpw_ldap_con);
  280. // errors are added to msgs by cpw_ldap_uid_search()
  281. return $msgs;
  282. }
  283. // we should check user password here.
  284. // suppress errors and check value returned by function call
  285. $cpw_ldap_cur_pass_array=@ldap_get_values($cpw_ldap_con,
  286. ldap_first_entry($cpw_ldap_con,$cpw_ldap_res2),'userpassword');
  287. // check if ldap_get_values() have found userpassword field
  288. if (! $cpw_ldap_cur_pass_array) {
  289. array_push($msgs,_("Unable to find user's password attribute."));
  290. return $msgs;
  291. }
  292. // compare passwords
  293. if (! cpw_ldap_compare_pass($cpw_ldap_cur_pass_array[0],$curpw,$msgs)) {
  294. @ldap_unbind($cpw_ldap_con);
  295. // errors are added to $msgs by cpw_ldap_compare_pass()
  296. return $msgs;
  297. }
  298. }
  299. } else {
  300. $cpw_ldap_binding=@ldap_bind($cpw_ldap_con,$cpw_ldap_userdn,$curpw);
  301. }
  302. if (! $cpw_ldap_binding) {
  303. array_push($msgs,
  304. _("Unable to rebind to LDAP server."),
  305. sprintf(_("Server replied: %s"),ldap_error($cpw_ldap_con)));
  306. @ldap_unbind($cpw_ldap_con);
  307. return $msgs;
  308. }
  309. // repeat search in order to get password info
  310. if (! isset($cpw_ldap_res2))
  311. $cpw_ldap_search_err=cpw_ldap_uid_search($cpw_ldap_con,$cpw_ldap_basedn,$msgs,$cpw_ldap_res2,$cpw_ldap_userdn);
  312. // check for connection errors and stop execution if something is wrong
  313. if (! $cpw_ldap_search_err) {
  314. @ldap_unbind($cpw_ldap_con);
  315. return $msgs;
  316. }
  317. // getpassword. suppress errors and check value returned by function call
  318. $cpw_ldap_cur_pass_array=@ldap_get_values($cpw_ldap_con,ldap_first_entry($cpw_ldap_con,$cpw_ldap_res2),'userpassword');
  319. // check if ldap_get_values() have found userpassword field.
  320. // Error differs from previous one, because user managed to authenticate.
  321. if (! $cpw_ldap_cur_pass_array) {
  322. array_push($msgs,_("LDAP server uses different attribute to store user's password."));
  323. return $msgs;
  324. }
  325. // encrypt new password (old password is needed for plaintext encryption detection)
  326. $cpw_ldap_new_pass=cpw_ldap_encrypt_pass($newpw,$cpw_ldap_cur_pass_array[0],$msgs,$curpw);
  327. if (! $cpw_ldap_new_pass) {
  328. @ldap_unbind($cpw_ldap_con);
  329. return $msgs;
  330. }
  331. // set new password. suppress ldap_modify errors. script checks and displays ldap_modify errors.
  332. $ldap_pass_change=@ldap_modify($cpw_ldap_con,$cpw_ldap_userdn,array('userpassword'=>$cpw_ldap_new_pass));
  333. // check if ldap_modify was successful
  334. if(! $ldap_pass_change) {
  335. array_push($msgs,ldap_error($cpw_ldap_con));
  336. }
  337. // close connection
  338. @ldap_unbind($cpw_ldap_con);
  339. } else {
  340. array_push($msgs,_("Unable to connect to LDAP server."));
  341. }
  342. return $msgs;
  343. }
  344. /** backend support functions **/
  345. /**
  346. * Sanitizes LDAP query strings.
  347. * original code - ldapquery plugin.
  348. * See rfc2254
  349. * @link http://www.faqs.org/rfcs/rfc2254.html
  350. * @param string $string
  351. * @return string sanitized string
  352. */
  353. function cpw_ldap_specialchars($string) {
  354. $sanitized=array('\\' => '\5c',
  355. '*' => '\2a',
  356. '(' => '\28',
  357. ')' => '\29',
  358. "\x00" => '\00');
  359. return str_replace(array_keys($sanitized),array_values($sanitized),$string);
  360. }
  361. /**
  362. * returns crypto algorithm used in password.
  363. * @param string $pass encrypted/hashed password
  364. * @return string lowercased crypto algorithm name
  365. */
  366. function cpw_ldap_get_crypto($pass,$curpass='') {
  367. $ret = false;
  368. if (preg_match("/^\{(.+)\}+/",$pass,$crypto)) {
  369. $ret=strtolower($crypto[1]);
  370. }
  371. if ($ret=='crypt') {
  372. // {CRYPT} can be standard des crypt, extended des crypt, md5 crypt or blowfish
  373. // depends on first salt symbols (ext_des = '_', md5 = '$1$', blowfish = '$2')
  374. // and length of salt (des = 2 chars, ext_des = 9, md5 = 12, blowfish = 16).
  375. if (preg_match("/^\{crypt\}\\\$1\\\$+/i",$pass)) {
  376. $ret='md5crypt';
  377. } elseif (preg_match("/^\{crypt\}\\\$2+/i",$pass)) {
  378. $ret='blowfish';
  379. } elseif (preg_match("/^\{crypt\}_+/i",$pass)) {
  380. $ret='extcrypt';
  381. }
  382. }
  383. // maybe password is plaintext
  384. if (! $ret && $curpass!='' && $pass==$curpass) $ret='plaintext';
  385. return $ret;
  386. }
  387. /**
  388. * Search LDAP for user id.
  389. * @param object $ldap_con ldap connection
  390. * @param string $ldap_basedn ldap basedn
  391. * @param array $msgs error messages
  392. * @param object $results ldap search results
  393. * @param string $userdn DN of found entry
  394. * @param boolean $onlyone require unique search results
  395. * @return boolean false if connection failed.
  396. */
  397. function cpw_ldap_uid_search($ldap_con,$ldap_basedn,&$msgs,&$results,&$userdn,$onlyone=true) {
  398. global $cpw_ldap_userid_attr,$username;
  399. $ret=true;
  400. $results=ldap_search($ldap_con,$ldap_basedn,cpw_ldap_specialchars($cpw_ldap_userid_attr . '=' . $username));
  401. if (! $results) {
  402. array_push($msgs,
  403. _("Unable to find user's DN."),
  404. _("Search error."),
  405. sprintf(_("Error: %s"),ldap_error($ldap_con)));
  406. $ret=false;
  407. } elseif ($onlyone && ldap_count_entries($ldap_con,$results)>1) {
  408. array_push($msgs,_("Multiple userid matches found."));
  409. $ret=false;
  410. } elseif (! $userdn = ldap_get_dn($ldap_con,ldap_first_entry($ldap_con,$results))) {
  411. // ldap_get_dn() returned error
  412. array_push($msgs,
  413. _("Unable to find user's DN."),
  414. _("ldap_get_dn error."));
  415. $ret=false;
  416. }
  417. return $ret;
  418. }
  419. /**
  420. * Encrypts LDAP password
  421. *
  422. * if $cpw_ldap_default_crypto is set to empty string or $same_crypto is set,
  423. * uses same crypto as in old password.
  424. * See phpldapadmin password_hash() function
  425. * @link http://phpldapadmin.sf.net
  426. * @param string $pass string that has to be encrypted/hashed
  427. * @param string $cur_pass_hash old password hash
  428. * @param array $msgs error message
  429. * @param string $curpass current password. Used for plaintext password detection.
  430. * @return string encrypted/hashed password or false
  431. */
  432. function cpw_ldap_encrypt_pass($pass,$cur_pass_hash,&$msgs,$curpass='') {
  433. global $cpw_ldap_default_crypto;
  434. // which crypto should be used to encode/hash password
  435. if ($cpw_ldap_default_crypto=='') {
  436. $ldap_crypto=cpw_ldap_get_crypto($cur_pass_hash,$curpass);
  437. } else {
  438. $ldap_crypto=$cpw_ldap_default_crypto;
  439. }
  440. return cpw_ldap_password_hash($pass,$ldap_crypto,$msgs);
  441. }
  442. /**
  443. * create hashed password
  444. * @param string $pass plain text password
  445. * @param string $crypto used crypto algorithm
  446. * @param array $msgs array used for error messages
  447. * @param string $forced_salt salt that should be used during hashing.
  448. * Is used only when is not set to empty string. Salt should be formated
  449. * according to $crypto requirements.
  450. * @return hashed password or false.
  451. */
  452. function cpw_ldap_password_hash($pass,$crypto,&$msgs,$forced_salt='') {
  453. // set default return code
  454. $ret=false;
  455. // lowercase crypto just in case
  456. $crypto=strtolower($crypto);
  457. // extra symbols used for random string in crypt salt
  458. // squirrelmail GenerateRandomString() adds alphanumerics with third argument = 7.
  459. $extra_salt_chars='./';
  460. // encrypt/hash password
  461. switch ($crypto) {
  462. case 'md4':
  463. // minimal requirement = php with mhash extension
  464. if ( function_exists( 'mhash' ) && defined('MHASH_MD4')) {
  465. $ret = '{MD4}' . base64_encode( mhash( MHASH_MD4, $pass) );
  466. } else {
  467. array_push($msgs,
  468. sprintf(_("Unsupported crypto: %s"),'md4'),
  469. _("PHP mhash extension is missing or does not support selected crypto."));
  470. }
  471. break;
  472. case 'md5':
  473. $ret='{MD5}' . base64_encode(pack('H*',md5($pass)));
  474. break;
  475. case 'smd5':
  476. // minimal requirement = mhash extension with md5 support and php 4.0.4.
  477. if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_MD5')) {
  478. if ($forced_salt!='') {
  479. $salt=$forced_salt;
  480. } else {
  481. $salt = mhash_keygen_s2k( MHASH_MD5, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
  482. }
  483. $ret = "{SMD5}".base64_encode( mhash( MHASH_MD5, $pass.$salt ).$salt );
  484. } else {
  485. // use two array_push calls in order to display messages in different lines.
  486. array_push($msgs,
  487. sprintf(_("Unsupported crypto: %s"),'smd5'),
  488. _("PHP mhash extension is missing or does not support selected crypto."));
  489. }
  490. break;
  491. case 'rmd160':
  492. // minimal requirement = php with mhash extension
  493. if ( function_exists( 'mhash' ) && defined('MHASH_RIPEMD160')) {
  494. $ret = '{RMD160}' . base64_encode( mhash( MHASH_RIPEMD160, $pass) );
  495. } else {
  496. array_push($msgs,
  497. sprintf(_("Unsupported crypto: %s"),'ripe-md160'),
  498. _("PHP mhash extension is missing or does not support selected crypto."));
  499. }
  500. break;
  501. case 'sha':
  502. // minimal requirement = php 4.3.0+ or php with mhash extension
  503. if ( function_exists('sha1') && defined('MHASH_SHA1')) {
  504. // use php 4.3.0+ sha1 function, if it is available.
  505. $ret = '{SHA}' . base64_encode(pack('H*',sha1($pass)));
  506. } elseif( function_exists( 'mhash' ) ) {
  507. $ret = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $pass) );
  508. } else {
  509. array_push($msgs,
  510. sprintf(_("Unsupported crypto: %s"),'sha'),
  511. _("PHP mhash extension is missing or does not support selected crypto."));
  512. }
  513. break;
  514. case 'ssha':
  515. // minimal requirement = mhash extension and php 4.0.4
  516. if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_SHA1')) {
  517. if ($forced_salt!='') {
  518. $salt=$forced_salt;
  519. } else {
  520. $salt = mhash_keygen_s2k( MHASH_SHA1, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
  521. }
  522. $ret = "{SSHA}".base64_encode( mhash( MHASH_SHA1, $pass.$salt ).$salt );
  523. } else {
  524. array_push($msgs,
  525. sprintf(_("Unsupported crypto: %s"),'ssha'),
  526. _("PHP mhash extension is missing or does not support selected crypto."));
  527. }
  528. break;
  529. case 'crypt':
  530. if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
  531. $ret = '{CRYPT}' . crypt($pass,GenerateRandomString(2,$extra_salt_chars,7));
  532. } else {
  533. array_push($msgs,
  534. sprintf(_("Unsupported crypto: %s"),'crypt'),
  535. _("System crypt library doesn't support standard DES crypt."));
  536. }
  537. break;
  538. case 'md5crypt':
  539. // check if crypt() supports md5
  540. if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
  541. $ret = '{CRYPT}' . crypt($pass,'$1$' . GenerateRandomString(9,$extra_salt_chars,7));
  542. } else {
  543. array_push($msgs,
  544. sprintf(_("Unsupported crypto: %s"),'md5crypt'),
  545. _("System crypt library doesn't have MD5 support."));
  546. }
  547. break;
  548. case 'extcrypt':
  549. // check if crypt() supports extended des
  550. if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
  551. $ret = '{CRYPT}' . crypt($pass,'_' . GenerateRandomString(8,$extra_salt_chars,7));
  552. } else {
  553. array_push($msgs,
  554. sprintf(_("Unsupported crypto: %s"),'ext_des'),
  555. _("System crypt library doesn't support extended DES crypt."));
  556. }
  557. break;
  558. case 'blowfish':
  559. // check if crypt() supports blowfish
  560. if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
  561. $ret = '{CRYPT}' . crypt($pass,'$2a$12$' . GenerateRandomString(13,$extra_salt_chars,7));
  562. } else {
  563. array_push($msgs,
  564. sprintf(_("Unsupported crypto: %s"),'Blowfish'),
  565. _("System crypt library doesn't have Blowfish support."));
  566. }
  567. break;
  568. case 'plaintext':
  569. // clear plain text password
  570. $ret=$pass;
  571. break;
  572. default:
  573. array_push($msgs,sprintf(_("Unsupported crypto: %s"),
  574. (is_string($ldap_crypto) ? htmlspecialchars($ldap_crypto) : _("unknown"))));
  575. }
  576. return $ret;
  577. }
  578. /**
  579. * compares two passwords
  580. * Code reuse. See phpldapadmin password_compare() function.
  581. * Some parts of code was rewritten to backend specifics.
  582. * @link http://phpldapadmin.sf.net
  583. * @param string $pass_hash hashed password string with password type indicators
  584. * @param string $pass_clear plain text password
  585. * @param array $msgs error messages
  586. * @return boolean true, if passwords match
  587. */
  588. function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
  589. $ret=false;
  590. if( preg_match( "/{([^}]+)}(.*)/", $pass_hash, $cypher ) ) {
  591. $pass_hash = $cypher[2];
  592. $_cypher = strtolower($cypher[1]);
  593. } else {
  594. $_cypher = NULL;
  595. }
  596. switch( $_cypher ) {
  597. case 'ssha':
  598. // Salted SHA
  599. // check for mhash support
  600. if ( function_exists('mhash') && defined('MHASH_SHA1')) {
  601. $hash = base64_decode($pass_hash);
  602. $salt = substr($hash, -4);
  603. $new_hash = base64_encode( mhash( MHASH_SHA1, $pass_clear.$salt).$salt );
  604. if( strcmp( $pass_hash, $new_hash ) == 0 )
  605. $ret=true;
  606. } else {
  607. array_push($msgs,
  608. _("Unable to validate user's password."),
  609. _("PHP mhash extension is missing or does not support selected crypto."));
  610. }
  611. break;
  612. case 'smd5':
  613. // Salted MD5
  614. // check for mhash support
  615. if ( function_exists('mhash') && defined('MHASH_MD5')) {
  616. $hash = base64_decode($pass_hash);
  617. $salt = substr($hash, -4);
  618. $new_hash = base64_encode( mhash( MHASH_MD5, $pass_clear.$salt).$salt );
  619. if( strcmp( $pass_hash, $new_hash ) == 0)
  620. $ret=true;
  621. } else {
  622. array_push($msgs,
  623. _("Unable to validate user's password."),
  624. _("PHP mhash extension is missing or does not support selected crypto."));
  625. }
  626. break;
  627. case 'sha':
  628. // SHA crypted passwords
  629. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'sha',$msgs), "{SHA}".$pass_hash ) == 0)
  630. $ret=true;
  631. break;
  632. case 'rmd160':
  633. // RIPE-MD160 crypted passwords
  634. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'rmd160',$msgs), "{RMD160}".$pass_hash ) == 0 )
  635. $ret=true;
  636. break;
  637. case 'md5':
  638. // MD5 crypted passwords
  639. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md5',$msgs), "{MD5}".$pass_hash ) == 0 )
  640. $ret=true;
  641. break;
  642. case 'md4':
  643. // MD4 crypted passwords
  644. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md4',$msgs), "{MD4}".$pass_hash ) == 0 )
  645. $ret=true;
  646. break;
  647. case 'crypt':
  648. // Crypt passwords
  649. if( preg_match( "/^\\\$2+/",$pass_hash ) ) { // Check if it's blowfish crypt
  650. // check CRYPT_BLOWFISH here.
  651. // ldap server might support it, but php can be on other OS
  652. if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
  653. if( crypt( $pass_clear, $pass_hash ) == $pass_hash )
  654. $ret=true;
  655. } else {
  656. array_push($msgs,
  657. _("Unable to validate user's password."),
  658. _("Blowfish is not supported by webserver's system crypt library."));
  659. }
  660. } elseif( strstr( $pass_hash, '$1$' ) ) { // Check if it's md5 crypt
  661. // check CRYPT_MD5 here.
  662. // ldap server might support it, but php might be on other OS
  663. if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
  664. list(,$type,$salt,$hash) = explode('$',$pass_hash);
  665. if( crypt( $pass_clear, '$1$' .$salt ) == $pass_hash )
  666. $ret=true;
  667. } else {
  668. array_push($msgs,
  669. _("Unable to validate user's password."),
  670. _("MD5 is not supported by webserver's system crypt library."));
  671. }
  672. } elseif( strstr( $pass_hash, '_' ) ) { // Check if it's extended des crypt
  673. // check CRYPT_EXT_DES here.
  674. // ldap server might support it, but php might be on other OS
  675. if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
  676. if( crypt( $pass_clear, $pass_hash ) == $pass_hash )
  677. $ret=true;
  678. } else {
  679. array_push($msgs,
  680. _("Unable to validate user's password."),
  681. _("Extended DES crypt is not supported by webserver's system crypt library."));
  682. }
  683. } else {
  684. // it is possible that this test is useless and any crypt library supports it, but ...
  685. if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
  686. // plain crypt password
  687. if( crypt($pass_clear, $pass_hash ) == $pass_hash )
  688. $ret=true;
  689. } else {
  690. array_push($msgs,
  691. _("Unable to validate user's password."),
  692. _("Standard DES crypt is not supported by webserver's system crypt library."));
  693. }
  694. }
  695. break;
  696. // No crypt is given, assume plaintext passwords are used
  697. default:
  698. if( $pass_clear == $pass_hash )
  699. $ret=true;
  700. break;
  701. }
  702. if (! $ret && empty($msgs)) {
  703. array_push($msgs,CPW_CURRENT_NOMATCH);
  704. }
  705. return $ret;
  706. }