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