PageRenderTime 49ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/branches/GSoC-config/squirrelmail/plugins/change_password/backend/ldap.php

#
PHP | 786 lines | 562 code | 53 blank | 171 comment | 78 complexity | 29e354ac063ca8672dfdcec87ac1c0b8 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-2007 The SquirrelMail Project Team
  6. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  7. * @version $Id: ldap.php 12271 2007-02-18 12:46:47Z kink $
  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. sq_mt_seed( (double) microtime() * 1000000 );
  479. if ($forced_salt!='') {
  480. $salt=$forced_salt;
  481. } else {
  482. $salt = mhash_keygen_s2k( MHASH_MD5, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
  483. }
  484. $ret = "{SMD5}".base64_encode( mhash( MHASH_MD5, $pass.$salt ).$salt );
  485. } else {
  486. // use two array_push calls in order to display messages in different lines.
  487. array_push($msgs,
  488. sprintf(_("Unsupported crypto: %s"),'smd5'),
  489. _("PHP mhash extension is missing or does not support selected crypto."));
  490. }
  491. break;
  492. case 'rmd160':
  493. // minimal requirement = php with mhash extension
  494. if ( function_exists( 'mhash' ) && defined('MHASH_RIPEMD160')) {
  495. $ret = '{RMD160}' . base64_encode( mhash( MHASH_RIPEMD160, $pass) );
  496. } else {
  497. array_push($msgs,
  498. sprintf(_("Unsupported crypto: %s"),'ripe-md160'),
  499. _("PHP mhash extension is missing or does not support selected crypto."));
  500. }
  501. break;
  502. case 'sha':
  503. // minimal requirement = php 4.3.0+ or php with mhash extension
  504. if ( function_exists('sha1') && defined('MHASH_SHA1')) {
  505. // use php 4.3.0+ sha1 function, if it is available.
  506. $ret = '{SHA}' . base64_encode(pack('H*',sha1($pass)));
  507. } elseif( function_exists( 'mhash' ) ) {
  508. $ret = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $pass) );
  509. } else {
  510. array_push($msgs,
  511. sprintf(_("Unsupported crypto: %s"),'sha'),
  512. _("PHP mhash extension is missing or does not support selected crypto."));
  513. }
  514. break;
  515. case 'ssha':
  516. // minimal requirement = mhash extension and php 4.0.4
  517. if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) && defined('MHASH_SHA1')) {
  518. sq_mt_seed( (double) microtime() * 1000000 );
  519. if ($forced_salt!='') {
  520. $salt=$forced_salt;
  521. } else {
  522. $salt = mhash_keygen_s2k( MHASH_SHA1, $pass, substr( pack( "h*", md5( mt_rand() ) ), 0, 8 ), 4 );
  523. }
  524. $ret = "{SSHA}".base64_encode( mhash( MHASH_SHA1, $pass.$salt ).$salt );
  525. } else {
  526. array_push($msgs,
  527. sprintf(_("Unsupported crypto: %s"),'ssha'),
  528. _("PHP mhash extension is missing or does not support selected crypto."));
  529. }
  530. break;
  531. case 'crypt':
  532. if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
  533. $ret = '{CRYPT}' . crypt($pass,GenerateRandomString(2,$extra_salt_chars,7));
  534. } else {
  535. array_push($msgs,
  536. sprintf(_("Unsupported crypto: %s"),'crypt'),
  537. _("System crypt library doesn't support standard DES crypt."));
  538. }
  539. break;
  540. case 'md5crypt':
  541. // check if crypt() supports md5
  542. if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
  543. $ret = '{CRYPT}' . crypt($pass,'$1$' . GenerateRandomString(9,$extra_salt_chars,7));
  544. } else {
  545. array_push($msgs,
  546. sprintf(_("Unsupported crypto: %s"),'md5crypt'),
  547. _("System crypt library doesn't have MD5 support."));
  548. }
  549. break;
  550. case 'extcrypt':
  551. // check if crypt() supports extended des
  552. if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
  553. $ret = '{CRYPT}' . crypt($pass,'_' . GenerateRandomString(8,$extra_salt_chars,7));
  554. } else {
  555. array_push($msgs,
  556. sprintf(_("Unsupported crypto: %s"),'ext_des'),
  557. _("System crypt library doesn't support extended DES crypt."));
  558. }
  559. break;
  560. case 'blowfish':
  561. // check if crypt() supports blowfish
  562. if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
  563. $ret = '{CRYPT}' . crypt($pass,'$2a$12$' . GenerateRandomString(13,$extra_salt_chars,7));
  564. } else {
  565. array_push($msgs,
  566. sprintf(_("Unsupported crypto: %s"),'Blowfish'),
  567. _("System crypt library doesn't have Blowfish support."));
  568. }
  569. break;
  570. case 'plaintext':
  571. // clear plain text password
  572. $ret=$pass;
  573. break;
  574. default:
  575. array_push($msgs,sprintf(_("Unsupported crypto: %s"),
  576. (is_string($ldap_crypto) ? htmlspecialchars($ldap_crypto) : _("unknown"))));
  577. }
  578. return $ret;
  579. }
  580. /**
  581. * compares two passwords
  582. * Code reuse. See phpldapadmin password_compare() function.
  583. * Some parts of code was rewritten to backend specifics.
  584. * @link http://phpldapadmin.sf.net
  585. * @param string $pass_hash hashed password string with password type indicators
  586. * @param string $pass_clear plain text password
  587. * @param array $msgs error messages
  588. * @return boolean true, if passwords match
  589. */
  590. function cpw_ldap_compare_pass($pass_hash,$pass_clear,&$msgs) {
  591. $ret=false;
  592. if( preg_match( "/{([^}]+)}(.*)/", $pass_hash, $cypher ) ) {
  593. $pass_hash = $cypher[2];
  594. $_cypher = strtolower($cypher[1]);
  595. } else {
  596. $_cypher = NULL;
  597. }
  598. switch( $_cypher ) {
  599. case 'ssha':
  600. // Salted SHA
  601. // check for mhash support
  602. if ( function_exists('mhash') && defined('MHASH_SHA1')) {
  603. $hash = base64_decode($pass_hash);
  604. $salt = substr($hash, -4);
  605. $new_hash = base64_encode( mhash( MHASH_SHA1, $pass_clear.$salt).$salt );
  606. if( strcmp( $pass_hash, $new_hash ) == 0 )
  607. $ret=true;
  608. } else {
  609. array_push($msgs,
  610. _("Unable to validate user's password."),
  611. _("PHP mhash extension is missing or does not support selected crypto."));
  612. }
  613. break;
  614. case 'smd5':
  615. // Salted MD5
  616. // check for mhash support
  617. if ( function_exists('mhash') && defined('MHASH_MD5')) {
  618. $hash = base64_decode($pass_hash);
  619. $salt = substr($hash, -4);
  620. $new_hash = base64_encode( mhash( MHASH_MD5, $pass_clear.$salt).$salt );
  621. if( strcmp( $pass_hash, $new_hash ) == 0)
  622. $ret=true;
  623. } else {
  624. array_push($msgs,
  625. _("Unable to validate user's password."),
  626. _("PHP mhash extension is missing or does not support selected crypto."));
  627. }
  628. break;
  629. case 'sha':
  630. // SHA crypted passwords
  631. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'sha',$msgs), "{SHA}".$pass_hash ) == 0)
  632. $ret=true;
  633. break;
  634. case 'rmd160':
  635. // RIPE-MD160 crypted passwords
  636. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'rmd160',$msgs), "{RMD160}".$pass_hash ) == 0 )
  637. $ret=true;
  638. break;
  639. case 'md5':
  640. // MD5 crypted passwords
  641. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md5',$msgs), "{MD5}".$pass_hash ) == 0 )
  642. $ret=true;
  643. break;
  644. case 'md4':
  645. // MD4 crypted passwords
  646. if( strcasecmp( cpw_ldap_password_hash($pass_clear,'md4',$msgs), "{MD4}".$pass_hash ) == 0 )
  647. $ret=true;
  648. break;
  649. case 'crypt':
  650. // Crypt passwords
  651. if( preg_match( "/^\\\$2+/",$pass_hash ) ) { // Check if it's blowfish crypt
  652. // check CRYPT_BLOWFISH here.
  653. // ldap server might support it, but php can be on other OS
  654. if (defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH==1) {
  655. if( crypt( $pass_clear, $pass_hash ) == $pass_hash )
  656. $ret=true;
  657. } else {
  658. array_push($msgs,
  659. _("Unable to validate user's password."),
  660. _("Blowfish is not supported by webserver's system crypt library."));
  661. }
  662. } elseif( strstr( $pass_hash, '$1$' ) ) { // Check if it's md5 crypt
  663. // check CRYPT_MD5 here.
  664. // ldap server might support it, but php might be on other OS
  665. if (defined('CRYPT_MD5') && CRYPT_MD5==1) {
  666. list(,$type,$salt,$hash) = explode('$',$pass_hash);
  667. if( crypt( $pass_clear, '$1$' .$salt ) == $pass_hash )
  668. $ret=true;
  669. } else {
  670. array_push($msgs,
  671. _("Unable to validate user's password."),
  672. _("MD5 is not supported by webserver's system crypt library."));
  673. }
  674. } elseif( strstr( $pass_hash, '_' ) ) { // Check if it's extended des crypt
  675. // check CRYPT_EXT_DES here.
  676. // ldap server might support it, but php might be on other OS
  677. if (defined('CRYPT_EXT_DES') && CRYPT_EXT_DES==1) {
  678. if( crypt( $pass_clear, $pass_hash ) == $pass_hash )
  679. $ret=true;
  680. } else {
  681. array_push($msgs,
  682. _("Unable to validate user's password."),
  683. _("Extended DES crypt is not supported by webserver's system crypt library."));
  684. }
  685. } else {
  686. // it is possible that this test is useless and any crypt library supports it, but ...
  687. if (defined('CRYPT_STD_DES') && CRYPT_STD_DES==1) {
  688. // plain crypt password
  689. if( crypt($pass_clear, $pass_hash ) == $pass_hash )
  690. $ret=true;
  691. } else {
  692. array_push($msgs,
  693. _("Unable to validate user's password."),
  694. _("Standard DES crypt is not supported by webserver's system crypt library."));
  695. }
  696. }
  697. break;
  698. // No crypt is given, assume plaintext passwords are used
  699. default:
  700. if( $pass_clear == $pass_hash )
  701. $ret=true;
  702. break;
  703. }
  704. if (! $ret && empty($msgs)) {
  705. array_push($msgs,CPW_CURRENT_NOMATCH);
  706. }
  707. return $ret;
  708. }