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