PageRenderTime 47ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/tags/rel-1_5_1/squirrelmail/plugins/change_password/backend/ldap.php

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