peardb.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433
  1. <?php
  2. /**
  3. * Change password PearDB 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$
  8. * @package plugins
  9. * @subpackage change_password
  10. */
  11. /** load Pear DB.
  12. * Global is needed because library must be loaded before configuration
  13. * in order to use DB constants.
  14. */
  15. global $cpw_peardb_detect;
  16. $cpw_peardb_detect=@include_once('DB.php');
  17. /** declare configuration globals */
  18. global $cpw_peardb_dsn, $cpw_peardb_connect_opts, $cpw_peardb_table,
  19. $cpw_peardb_uid_field, $cpw_peardb_domain_field, $cpw_peardb_passwd_field,
  20. $cpw_peardb_crypted_passwd, $cpw_peardb_debug;
  21. /**
  22. * Connection DSN.
  23. * Any format supported by peardb
  24. * @global mixed $cpw_peardb_dsn
  25. */
  26. $cpw_peardb_dsn='';
  27. /**
  28. * Pear DB connection options
  29. * @global array $cpw_peardb_connect_opts
  30. */
  31. $cpw_peardb_connect_opts=array();
  32. /**
  33. * Table that stores user information
  34. * @global string $cpw_peardb_table
  35. */
  36. $cpw_peardb_table='';
  37. /**
  38. * Field that stores user name
  39. * @global string $cpw_peardb_uid_field
  40. */
  41. $cpw_peardb_uid_field='userid';
  42. /**
  43. * Field that stores domain part of username
  44. * @global string $cpw_peardb_domain_field
  45. */
  46. $cpw_peardb_domain_field='';
  47. /**
  48. * Field that stores password
  49. * @global string $cpw_peardb_passwd_field
  50. */
  51. $cpw_peardb_passwd_field='password';
  52. /**
  53. * Passwords are plaintext or encrypted
  54. * @global boolean $cpw_peardb_crypted_passwd
  55. */
  56. $cpw_peardb_crypted_passwd=false;
  57. /**
  58. * Controls output debugging errors
  59. * Error messages might contain login and password information.
  60. * Don't enable on production systems.
  61. * @global boolean $cpw_peardb_debug
  62. */
  63. $cpw_peardb_debug=false;
  64. /** configuration overrides */
  65. if ( isset($cpw_peardb) && is_array($cpw_peardb) && !empty($cpw_peardb) ) {
  66. if (isset($cpw_peardb['dsn']))
  67. $cpw_peardb_dsn=$cpw_peardb['dsn'];
  68. if (isset($cpw_peardb['connect_opts']))
  69. $cpw_peardb_connect_opts=$cpw_peardb['connect_opts'];
  70. if (isset($cpw_peardb['table']))
  71. $cpw_peardb_table=$cpw_peardb['table'];
  72. if (isset($cpw_peardb['uid_field']))
  73. $cpw_peardb_uid_field=$cpw_peardb['uid_field'];
  74. if (isset($cpw_peardb['domain_field']))
  75. $cpw_peardb_domain_field=$cpw_peardb['domain_field'];
  76. if (isset($cpw_peardb['password_field']))
  77. $cpw_peardb_passwd_field=$cpw_peardb['password_field'];
  78. if (isset($cpw_peardb['crypted_passwd']))
  79. $cpw_peardb_crypted_passwd=true;
  80. if (isset($cpw_peardb['debug']))
  81. $cpw_peardb_debug=$cpw_peardb['debug'];
  82. }
  83. /**
  84. * Define here the name of your password changing function.
  85. */
  86. global $squirrelmail_plugin_hooks;
  87. $squirrelmail_plugin_hooks['change_password_dochange']['peardb'] =
  88. 'cpw_peardb_dochange';
  89. $squirrelmail_plugin_hooks['change_password_init']['peardb'] =
  90. 'cpw_peardb_init';
  91. /**
  92. * Checks if configuration is correct
  93. */
  94. function cpw_peardb_init() {
  95. global $oTemplate, $cpw_peardb_detect, $cpw_peardb_dsn, $cpw_peardb_table;
  96. if (! $cpw_peardb_detect) {
  97. error_box(_("Plugin is unable to use PHP Pear DB libraries. PHP Pear includes must be available in your PHP include_path setting."));
  98. $oTemplate->display('footer.tpl');
  99. exit();
  100. }
  101. // Test required settings
  102. if ((is_string($cpw_peardb_dsn) && trim($cpw_peardb_dsn)=='')
  103. || trim($cpw_peardb_table)=='' ) {
  104. error_box(_("Required change password backend configuration options are missing."));
  105. $oTemplate->display('footer.tpl');
  106. exit();
  107. }
  108. }
  109. /**
  110. * Changes password
  111. * @param array data The username/curpw/newpw data.
  112. * @return array Array of error messages.
  113. */
  114. function cpw_peardb_dochange($data) {
  115. global $cpw_peardb_dsn, $cpw_peardb_table, $cpw_peardb_connect_opts, $cpw_peardb_debug,
  116. $cpw_peardb_uid_field, $cpw_peardb_passwd_field, $cpw_peardb_domain_field,
  117. $cpw_peardb_crypted_passwd, $domain;
  118. $username = $data['username'];
  119. $curpw = $data['curpw'];
  120. $newpw = $data['newpw'];
  121. $msgs = array();
  122. // split user and domain parts from username, if domain field is set and username looks like email.
  123. if ($cpw_peardb_domain_field!='' && preg_match("/(.*)@(.*)/",$username,$match)) {
  124. $user=$match[1];
  125. $user_domain=$match[2];
  126. } else {
  127. $user=$username;
  128. $user_domain=$domain;
  129. }
  130. // connect to database and make sure that table exists
  131. $cpw_db = DB::connect($cpw_peardb_dsn, $cpw_peardb_connect_opts);
  132. if (PEAR::isError($cpw_db)) {
  133. array_push($msgs,sprintf(_("Connection error: %s"),htmlspecialchars($cpw_db->getMessage())));
  134. if ($cpw_peardb_debug)
  135. array_push($msgs,htmlspecialchars($cpw_db->getuserinfo()));
  136. return $msgs;
  137. }
  138. // get table information
  139. $table_info = $cpw_db->tableinfo($cpw_peardb_table);
  140. if (PEAR::isError($table_info)) {
  141. array_push($msgs,sprintf(_("Invalid table name: %s"),htmlspecialchars($cpw_peardb_table)));
  142. $cpw_db->disconnect();
  143. return $msgs;
  144. }
  145. if (empty($table_info)) {
  146. array_push($msgs,_("User table is empty."));
  147. $cpw_db->disconnect();
  148. return $msgs;
  149. }
  150. $cpw_peardb_uid_check=false;
  151. $cpw_peardb_passwd_check=false;
  152. $cpw_peardb_domain_check=(($cpw_peardb_domain_field=='')? true : false);
  153. foreach($table_info as $key => $field_data) {
  154. if ($field_data['name']==$cpw_peardb_uid_field)
  155. $cpw_peardb_uid_check=true;
  156. if ($field_data['name']==$cpw_peardb_passwd_field)
  157. $cpw_peardb_passwd_check=true;
  158. if ($cpw_peardb_domain_field!='' && $field_data['name']==$cpw_peardb_domain_field)
  159. $cpw_peardb_domain_check=true;
  160. }
  161. if (! $cpw_peardb_uid_check) {
  162. array_push($msgs,_("Invalid uid field."));
  163. }
  164. if (! $cpw_peardb_passwd_check) {
  165. array_push($msgs,_("Invalid password field"));
  166. }
  167. if (! $cpw_peardb_domain_check) {
  168. array_push($msgs,_("Invalid domain field"));
  169. }
  170. if (! empty($msgs)) {
  171. $cpw_db->disconnect();
  172. return $msgs;
  173. }
  174. // find user's entry
  175. $query='SELECT'
  176. .' '.$cpw_db->quoteIdentifier($cpw_peardb_uid_field)
  177. .', '.$cpw_db->quoteIdentifier($cpw_peardb_passwd_field)
  178. .(($cpw_peardb_domain_field!='') ? ', '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field):'')
  179. .' FROM '.$cpw_db->quoteIdentifier($cpw_peardb_table)
  180. .' WHERE '
  181. .$cpw_db->quoteIdentifier($cpw_peardb_uid_field).'='.$cpw_db->quoteSmart($user)
  182. .(($cpw_peardb_domain_field!='') ?
  183. ' AND '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field).'='.$cpw_db->quoteSmart($user_domain):
  184. '');
  185. $cpw_res=$cpw_db->query($query);
  186. if (PEAR::isError($cpw_res)) {
  187. array_push($msgs,sprintf(_("Query failed: %s"),htmlspecialchars($cpw_res->getMessage())));
  188. $cpw_db->disconnect();
  189. return $msgs;
  190. }
  191. // make sure that there is only one user.
  192. if ($cpw_res->numRows()==0) {
  193. array_push($msgs,_("Unable to find user in user table."));
  194. $cpw_db->disconnect();
  195. return $msgs;
  196. }
  197. if ($cpw_res->numRows()>1) {
  198. array_push($msgs,_("Too many matches found in user table."));
  199. $cpw_db->disconnect();
  200. return $msgs;
  201. }
  202. // FIXME: process possible errors
  203. $cpw_res->fetchInto($userdb,DB_FETCHMODE_ASSOC);
  204. // validate password
  205. $valid_passwd=false;
  206. if ($cpw_peardb_crypted_passwd) {
  207. // detect password type
  208. $pw_type=cpw_peardb_detect_crypto($userdb[$cpw_peardb_passwd_field]);
  209. if (! $pw_type) {
  210. array_push($msgs,_("Unable to detect password crypto algorithm."));
  211. } else {
  212. $hashed_pw=cpw_peardb_passwd_hash($curpw,$pw_type,$msgs,$userdb[$cpw_peardb_passwd_field]);
  213. if ($hashed_pw==$userdb[$cpw_peardb_passwd_field]) {
  214. $valid_passwd=true;
  215. }
  216. }
  217. } elseif ($userdb[$cpw_peardb_passwd_field]==$curpw) {
  218. $valid_passwd=true;
  219. }
  220. if (! $valid_passwd) {
  221. array_push($msgs,CPW_CURRENT_NOMATCH);
  222. $cpw_db->disconnect();
  223. return $msgs;
  224. }
  225. // create new password
  226. if ($cpw_peardb_crypted_passwd) {
  227. $hashed_passwd=cpw_peardb_passwd_hash($newpw,$pw_type,$msgs);
  228. } else {
  229. $hashed_passwd=$newpw;
  230. }
  231. // make sure that password was created
  232. if (! empty($msgs)) {
  233. array_push($msgs,_("Unable to encrypt new password."));
  234. $cpw_db->disconnect();
  235. return $msgs;
  236. }
  237. // create update query
  238. $update_query='UPDATE '
  239. . $cpw_db->quoteIdentifier($cpw_peardb_table)
  240. .' SET '.$cpw_db->quoteIdentifier($cpw_peardb_passwd_field)
  241. .'='.$cpw_db->quoteSmart($hashed_passwd)
  242. .' WHERE '.$cpw_db->quoteIdentifier($cpw_peardb_uid_field)
  243. .'='.$cpw_db->quoteSmart($user)
  244. .(($cpw_peardb_domain_field!='') ?
  245. ' AND '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field).'='.$cpw_db->quoteSmart($user_domain) :
  246. '');
  247. // update database
  248. $cpw_res=$cpw_db->query($update_query);
  249. // check for update error
  250. if (PEAR::isError($cpw_res)) {
  251. array_push($msgs,sprintf(_("Unable to set new password: %s"),htmlspecialchars($cpw_res->getMessage())));
  252. }
  253. // close database connection
  254. $cpw_db->disconnect();
  255. return $msgs;
  256. }
  257. /**
  258. * Detects password crypto
  259. * reports 'crypt' if fails to detect any other crypt
  260. * @param string $password
  261. * @return string
  262. */
  263. function cpw_peardb_detect_crypto($password) {
  264. $ret = false;
  265. if (preg_match("/^\{(.+)\}+/",$password,$crypto)) {
  266. $ret=strtolower($crypto[1]);
  267. }
  268. if ($ret=='crypt') {
  269. // {CRYPT} can be standard des crypt, extended des crypt, md5 crypt or blowfish
  270. // depends on first salt symbols (ext_des = '_', md5 = '$1$', blowfish = '$2')
  271. // and length of salt (des = 2 chars, ext_des = 9, md5 = 12, blowfish = 16).
  272. if (preg_match("/^\{crypt\}\\\$1\\\$+/i",$password)) {
  273. $ret='tagged_md5crypt';
  274. } elseif (preg_match("/^\{crypt\}\\\$2+/i",$password)) {
  275. $ret='tagged_blowfish';
  276. } elseif (preg_match("/^\{crypt\}_+/i",$password)) {
  277. $ret='tagged_extcrypt';
  278. } else {
  279. $ret='tagged_crypt';
  280. }
  281. }
  282. if (! $ret) {
  283. if (preg_match("/^\\\$1\\\$+/i",$password)) {
  284. $ret='md5crypt';
  285. } elseif (preg_match("/^\\\$2+/i",$password)) {
  286. $ret='blowfish';
  287. } elseif (preg_match("/^_+/i",$password)) {
  288. $ret='extcrypt';
  289. } else {
  290. $ret='crypt';
  291. }
  292. }
  293. return $ret;
  294. }
  295. /**
  296. * Encode password
  297. * @param string $password plain text password
  298. * @param string $crypto used crypto
  299. * @param array $msgs error messages
  300. * @param string $forced_salt old password used to create password hash for verification
  301. * @return string hashed password. false, if hashing fails
  302. */
  303. function cpw_peardb_passwd_hash($password,$crypto,&$msgs,$forced_salt='') {
  304. global $username;
  305. $crypto = strtolower($crypto);
  306. $ret=false;
  307. $salt='';
  308. // extra symbols used for random string in crypt salt
  309. // squirrelmail GenerateRandomString() adds alphanumerics with third argument = 7.
  310. $extra_salt_chars='./';
  311. switch($crypto) {
  312. case 'plain-md5':
  313. $ret='{PLAIN-MD5}' . md5($password);
  314. break;
  315. case 'digest-md5':
  316. // split username into user and domain parts
  317. if (preg_match("/(.*)@(.*)/",$username,$match)) {
  318. $ret='{DIGEST-MD5}' . md5($match[1].':'.$match[2].':'.$password);
  319. } else {
  320. array_push($msgs,_("Unable to use digest-md5 crypto."));
  321. }
  322. break;
  323. case 'tagged_crypt':
  324. case 'crypt':
  325. if (! defined('CRYPT_STD_DES') || CRYPT_STD_DES==0) {
  326. array_push($msgs,sprintf(_("Unsupported crypto: %s"),'crypt'));
  327. break;
  328. }
  329. if ($forced_salt=='') {
  330. $salt=GenerateRandomString(2,$extra_salt_chars,7);
  331. } else {
  332. $salt=$forced_salt;
  333. }
  334. $ret = ($crypto=='tagged_crypt' ? '{crypt}' : '');
  335. $ret.= crypt($password,$salt);
  336. break;
  337. case 'tagged_md5crypt':
  338. case 'md5crypt':
  339. if (! defined('CRYPT_MD5') || CRYPT_MD5==0) {
  340. array_push($msgs,sprintf(_("Unsupported crypto: %s"),'md5crypt'));
  341. break;
  342. }
  343. if ($forced_salt=='') {
  344. $salt='$1$' .GenerateRandomString(9,$extra_salt_chars,7);
  345. } else {
  346. $salt=$forced_salt;
  347. }
  348. $ret = ($crypto=='tagged_md5crypt' ? '{crypt}' : '');
  349. $ret.= crypt($password,$salt);
  350. break;
  351. case 'tagged_extcrypt':
  352. case 'extcrypt':
  353. if (! defined('CRYPT_EXT_DES') || CRYPT_EXT_DES==0) {
  354. array_push($msgs,sprintf(_("Unsupported crypto: %s"),'extcrypt'));
  355. break;
  356. }
  357. if ($forced_salt=='') {
  358. $salt='_' . GenerateRandomString(8,$extra_salt_chars,7);
  359. } else {
  360. $salt=$forced_salt;
  361. }
  362. $ret = ($crypto=='tagged_extcrypt' ? '{crypt}' : '');
  363. $ret.= crypt($password,$salt);
  364. break;
  365. case 'tagged_blowfish':
  366. case 'blowfish':
  367. if (! defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH==0) {
  368. array_push($msgs,sprintf(_("Unsupported crypto: %s"),'blowfish'));
  369. break;
  370. }
  371. if ($forced_salt=='') {
  372. $salt='$2a$12$' . GenerateRandomString(13,$extra_salt_chars,7);
  373. } else {
  374. $salt=$forced_salt;
  375. }
  376. $ret = ($crypto=='tagged_blowfish' ? '{crypt}' : '');
  377. $ret.= crypt($password,$salt);
  378. break;
  379. case 'plain':
  380. case 'plaintext':
  381. $ret = $password;
  382. break;
  383. default:
  384. array_push($msgs,sprintf(_("Unsupported crypto: %s"),htmlspecialchars($crypto)));
  385. }
  386. return $ret;
  387. }