d032df69 |
1 | <?php |
4b4abf93 |
2 | |
d032df69 |
3 | /** |
4 | * Change password PearDB backend |
5 | * |
f197ec88 |
6 | * @copyright 2005-2016 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 | |
202bcbcc |
13 | /** load Pear DB. |
14 | * Global is needed because library must be loaded before configuration |
d032df69 |
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 */ |
202bcbcc |
21 | global $cpw_peardb_dsn, $cpw_peardb_connect_opts, $cpw_peardb_table, |
d032df69 |
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'])) |
b437888c |
81 | $cpw_peardb_connect_opts=$cpw_peardb['connect_opts']; |
d032df69 |
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() { |
1b858d86 |
109 | global $oTemplate, $cpw_peardb_detect, $cpw_peardb_dsn, $cpw_peardb_table; |
d032df69 |
110 | |
d032df69 |
111 | if (! $cpw_peardb_detect) { |
1b858d86 |
112 | error_box(_("Plugin is unable to use PHP Pear DB libraries. PHP Pear includes must be available in your PHP include_path setting.")); |
113 | $oTemplate->display('footer.tpl'); |
d032df69 |
114 | exit(); |
115 | } |
116 | |
117 | // Test required settings |
202bcbcc |
118 | if ((is_string($cpw_peardb_dsn) && trim($cpw_peardb_dsn)=='') |
d032df69 |
119 | || trim($cpw_peardb_table)=='' ) { |
1b858d86 |
120 | error_box(_("Required change password backend configuration options are missing.")); |
121 | $oTemplate->display('footer.tpl'); |
d032df69 |
122 | exit(); |
123 | } |
124 | } |
125 | |
126 | |
127 | /** |
128 | * Changes password |
129 | * @param array data The username/curpw/newpw data. |
130 | * @return array Array of error messages. |
131 | */ |
132 | function cpw_peardb_dochange($data) { |
133 | global $cpw_peardb_dsn, $cpw_peardb_table, $cpw_peardb_connect_opts, $cpw_peardb_debug, |
202bcbcc |
134 | $cpw_peardb_uid_field, $cpw_peardb_passwd_field, $cpw_peardb_domain_field, |
d032df69 |
135 | $cpw_peardb_crypted_passwd, $domain; |
136 | |
137 | $username = $data['username']; |
138 | $curpw = $data['curpw']; |
139 | $newpw = $data['newpw']; |
140 | |
141 | $msgs = array(); |
142 | |
143 | // split user and domain parts from username, if domain field is set and username looks like email. |
144 | if ($cpw_peardb_domain_field!='' && preg_match("/(.*)@(.*)/",$username,$match)) { |
145 | $user=$match[1]; |
146 | $user_domain=$match[2]; |
147 | } else { |
148 | $user=$username; |
149 | $user_domain=$domain; |
150 | } |
202bcbcc |
151 | |
d032df69 |
152 | // connect to database and make sure that table exists |
153 | $cpw_db = DB::connect($cpw_peardb_dsn, $cpw_peardb_connect_opts); |
154 | if (PEAR::isError($cpw_db)) { |
3047e291 |
155 | array_push($msgs,sprintf(_("Connection error: %s"),sm_encode_html_special_chars($cpw_db->getMessage()))); |
d032df69 |
156 | if ($cpw_peardb_debug) |
3047e291 |
157 | array_push($msgs,sm_encode_html_special_chars($cpw_db->getuserinfo())); |
d032df69 |
158 | return $msgs; |
159 | } |
160 | |
161 | // get table information |
162 | $table_info = $cpw_db->tableinfo($cpw_peardb_table); |
163 | if (PEAR::isError($table_info)) { |
3047e291 |
164 | array_push($msgs,sprintf(_("Invalid table name: %s"),sm_encode_html_special_chars($cpw_peardb_table))); |
d032df69 |
165 | $cpw_db->disconnect(); |
166 | return $msgs; |
167 | } |
168 | |
169 | if (empty($table_info)) { |
170 | array_push($msgs,_("User table is empty.")); |
171 | $cpw_db->disconnect(); |
172 | return $msgs; |
173 | } |
174 | |
175 | $cpw_peardb_uid_check=false; |
176 | $cpw_peardb_passwd_check=false; |
177 | $cpw_peardb_domain_check=(($cpw_peardb_domain_field=='')? true : false); |
178 | foreach($table_info as $key => $field_data) { |
202bcbcc |
179 | if ($field_data['name']==$cpw_peardb_uid_field) |
d032df69 |
180 | $cpw_peardb_uid_check=true; |
202bcbcc |
181 | if ($field_data['name']==$cpw_peardb_passwd_field) |
d032df69 |
182 | $cpw_peardb_passwd_check=true; |
183 | if ($cpw_peardb_domain_field!='' && $field_data['name']==$cpw_peardb_domain_field) |
184 | $cpw_peardb_domain_check=true; |
185 | } |
186 | if (! $cpw_peardb_uid_check) { |
187 | array_push($msgs,_("Invalid uid field.")); |
188 | } |
189 | if (! $cpw_peardb_passwd_check) { |
190 | array_push($msgs,_("Invalid password field")); |
191 | } |
192 | if (! $cpw_peardb_domain_check) { |
193 | array_push($msgs,_("Invalid domain field")); |
194 | } |
195 | if (! empty($msgs)) { |
196 | $cpw_db->disconnect(); |
197 | return $msgs; |
198 | } |
199 | |
200 | // find user's entry |
201 | $query='SELECT' |
202 | .' '.$cpw_db->quoteIdentifier($cpw_peardb_uid_field) |
203 | .', '.$cpw_db->quoteIdentifier($cpw_peardb_passwd_field) |
204 | .(($cpw_peardb_domain_field!='') ? ', '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field):'') |
205 | .' FROM '.$cpw_db->quoteIdentifier($cpw_peardb_table) |
206 | .' WHERE ' |
207 | .$cpw_db->quoteIdentifier($cpw_peardb_uid_field).'='.$cpw_db->quoteSmart($user) |
202bcbcc |
208 | .(($cpw_peardb_domain_field!='') ? |
d032df69 |
209 | ' AND '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field).'='.$cpw_db->quoteSmart($user_domain): |
210 | ''); |
211 | $cpw_res=$cpw_db->query($query); |
212 | if (PEAR::isError($cpw_res)) { |
3047e291 |
213 | array_push($msgs,sprintf(_("Query failed: %s"),sm_encode_html_special_chars($cpw_res->getMessage()))); |
d032df69 |
214 | $cpw_db->disconnect(); |
215 | return $msgs; |
216 | } |
217 | |
218 | // make sure that there is only one user. |
219 | if ($cpw_res->numRows()==0) { |
220 | array_push($msgs,_("Unable to find user in user table.")); |
221 | $cpw_db->disconnect(); |
222 | return $msgs; |
223 | } |
224 | |
225 | if ($cpw_res->numRows()>1) { |
226 | array_push($msgs,_("Too many matches found in user table.")); |
227 | $cpw_db->disconnect(); |
228 | return $msgs; |
229 | } |
230 | |
231 | // FIXME: process possible errors |
232 | $cpw_res->fetchInto($userdb,DB_FETCHMODE_ASSOC); |
233 | |
234 | // validate password |
235 | $valid_passwd=false; |
236 | if ($cpw_peardb_crypted_passwd) { |
237 | // detect password type |
238 | $pw_type=cpw_peardb_detect_crypto($userdb[$cpw_peardb_passwd_field]); |
239 | if (! $pw_type) { |
240 | array_push($msgs,_("Unable to detect password crypto algorithm.")); |
241 | } else { |
242 | $hashed_pw=cpw_peardb_passwd_hash($curpw,$pw_type,$msgs,$userdb[$cpw_peardb_passwd_field]); |
243 | if ($hashed_pw==$userdb[$cpw_peardb_passwd_field]) { |
244 | $valid_passwd=true; |
245 | } |
246 | } |
247 | } elseif ($userdb[$cpw_peardb_passwd_field]==$curpw) { |
248 | $valid_passwd=true; |
249 | } |
250 | |
251 | if (! $valid_passwd) { |
252 | array_push($msgs,CPW_CURRENT_NOMATCH); |
253 | $cpw_db->disconnect(); |
254 | return $msgs; |
255 | } |
256 | |
257 | // create new password |
258 | if ($cpw_peardb_crypted_passwd) { |
259 | $hashed_passwd=cpw_peardb_passwd_hash($newpw,$pw_type,$msgs); |
260 | } else { |
261 | $hashed_passwd=$newpw; |
262 | } |
263 | |
264 | // make sure that password was created |
265 | if (! empty($msgs)) { |
266 | array_push($msgs,_("Unable to encrypt new password.")); |
267 | $cpw_db->disconnect(); |
268 | return $msgs; |
269 | } |
270 | |
271 | // create update query |
272 | $update_query='UPDATE ' |
273 | . $cpw_db->quoteIdentifier($cpw_peardb_table) |
274 | .' SET '.$cpw_db->quoteIdentifier($cpw_peardb_passwd_field) |
275 | .'='.$cpw_db->quoteSmart($hashed_passwd) |
276 | .' WHERE '.$cpw_db->quoteIdentifier($cpw_peardb_uid_field) |
277 | .'='.$cpw_db->quoteSmart($user) |
202bcbcc |
278 | .(($cpw_peardb_domain_field!='') ? |
d032df69 |
279 | ' AND '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field).'='.$cpw_db->quoteSmart($user_domain) : |
280 | ''); |
281 | |
282 | // update database |
283 | $cpw_res=$cpw_db->query($update_query); |
284 | |
285 | // check for update error |
286 | if (PEAR::isError($cpw_res)) { |
3047e291 |
287 | array_push($msgs,sprintf(_("Unable to set new password: %s"),sm_encode_html_special_chars($cpw_res->getMessage()))); |
d032df69 |
288 | } |
289 | |
290 | // close database connection |
291 | $cpw_db->disconnect(); |
292 | |
293 | return $msgs; |
294 | } |
295 | |
296 | /** |
297 | * Detects password crypto |
298 | * reports 'crypt' if fails to detect any other crypt |
299 | * @param string $password |
300 | * @return string |
301 | */ |
302 | function cpw_peardb_detect_crypto($password) { |
303 | $ret = false; |
304 | |
305 | if (preg_match("/^\{(.+)\}+/",$password,$crypto)) { |
306 | $ret=strtolower($crypto[1]); |
307 | } |
308 | |
309 | if ($ret=='crypt') { |
310 | // {CRYPT} can be standard des crypt, extended des crypt, md5 crypt or blowfish |
311 | // depends on first salt symbols (ext_des = '_', md5 = '$1$', blowfish = '$2') |
312 | // and length of salt (des = 2 chars, ext_des = 9, md5 = 12, blowfish = 16). |
313 | if (preg_match("/^\{crypt\}\\\$1\\\$+/i",$password)) { |
314 | $ret='tagged_md5crypt'; |
315 | } elseif (preg_match("/^\{crypt\}\\\$2+/i",$password)) { |
316 | $ret='tagged_blowfish'; |
317 | } elseif (preg_match("/^\{crypt\}_+/i",$password)) { |
318 | $ret='tagged_extcrypt'; |
319 | } else { |
320 | $ret='tagged_crypt'; |
321 | } |
322 | } |
323 | |
324 | if (! $ret) { |
325 | if (preg_match("/^\\\$1\\\$+/i",$password)) { |
326 | $ret='md5crypt'; |
327 | } elseif (preg_match("/^\\\$2+/i",$password)) { |
328 | $ret='blowfish'; |
329 | } elseif (preg_match("/^_+/i",$password)) { |
330 | $ret='extcrypt'; |
331 | } else { |
332 | $ret='crypt'; |
333 | } |
334 | } |
335 | return $ret; |
336 | } |
337 | |
338 | /** |
339 | * Encode password |
340 | * @param string $password plain text password |
341 | * @param string $crypto used crypto |
342 | * @param array $msgs error messages |
343 | * @param string $forced_salt old password used to create password hash for verification |
344 | * @return string hashed password. false, if hashing fails |
345 | */ |
346 | function cpw_peardb_passwd_hash($password,$crypto,&$msgs,$forced_salt='') { |
347 | global $username; |
348 | |
349 | $crypto = strtolower($crypto); |
350 | |
351 | $ret=false; |
352 | $salt=''; |
353 | // extra symbols used for random string in crypt salt |
354 | // squirrelmail GenerateRandomString() adds alphanumerics with third argument = 7. |
355 | $extra_salt_chars='./'; |
356 | |
357 | switch($crypto) { |
358 | case 'plain-md5': |
359 | $ret='{PLAIN-MD5}' . md5($password); |
360 | break; |
361 | case 'digest-md5': |
362 | // split username into user and domain parts |
363 | if (preg_match("/(.*)@(.*)/",$username,$match)) { |
364 | $ret='{DIGEST-MD5}' . md5($match[1].':'.$match[2].':'.$password); |
365 | } else { |
366 | array_push($msgs,_("Unable to use digest-md5 crypto.")); |
367 | } |
368 | break; |
369 | case 'tagged_crypt': |
370 | case 'crypt': |
371 | if (! defined('CRYPT_STD_DES') || CRYPT_STD_DES==0) { |
372 | array_push($msgs,sprintf(_("Unsupported crypto: %s"),'crypt')); |
373 | break; |
374 | } |
375 | if ($forced_salt=='') { |
376 | $salt=GenerateRandomString(2,$extra_salt_chars,7); |
377 | } else { |
378 | $salt=$forced_salt; |
379 | } |
380 | $ret = ($crypto=='tagged_crypt' ? '{crypt}' : ''); |
381 | $ret.= crypt($password,$salt); |
382 | break; |
383 | case 'tagged_md5crypt': |
384 | case 'md5crypt': |
385 | if (! defined('CRYPT_MD5') || CRYPT_MD5==0) { |
386 | array_push($msgs,sprintf(_("Unsupported crypto: %s"),'md5crypt')); |
387 | break; |
388 | } |
389 | if ($forced_salt=='') { |
390 | $salt='$1$' .GenerateRandomString(9,$extra_salt_chars,7); |
391 | } else { |
392 | $salt=$forced_salt; |
393 | } |
394 | $ret = ($crypto=='tagged_md5crypt' ? '{crypt}' : ''); |
395 | $ret.= crypt($password,$salt); |
396 | break; |
397 | case 'tagged_extcrypt': |
398 | case 'extcrypt': |
399 | if (! defined('CRYPT_EXT_DES') || CRYPT_EXT_DES==0) { |
400 | array_push($msgs,sprintf(_("Unsupported crypto: %s"),'extcrypt')); |
401 | break; |
402 | } |
403 | if ($forced_salt=='') { |
404 | $salt='_' . GenerateRandomString(8,$extra_salt_chars,7); |
405 | } else { |
406 | $salt=$forced_salt; |
407 | } |
408 | $ret = ($crypto=='tagged_extcrypt' ? '{crypt}' : ''); |
409 | $ret.= crypt($password,$salt); |
410 | break; |
411 | case 'tagged_blowfish': |
412 | case 'blowfish': |
413 | if (! defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH==0) { |
414 | array_push($msgs,sprintf(_("Unsupported crypto: %s"),'blowfish')); |
415 | break; |
416 | } |
417 | if ($forced_salt=='') { |
418 | $salt='$2a$12$' . GenerateRandomString(13,$extra_salt_chars,7); |
419 | } else { |
420 | $salt=$forced_salt; |
421 | } |
422 | $ret = ($crypto=='tagged_blowfish' ? '{crypt}' : ''); |
423 | $ret.= crypt($password,$salt); |
424 | break; |
425 | case 'plain': |
426 | case 'plaintext': |
427 | $ret = $password; |
428 | break; |
429 | default: |
3047e291 |
430 | array_push($msgs,sprintf(_("Unsupported crypto: %s"),sm_encode_html_special_chars($crypto))); |
d032df69 |
431 | } |
432 | return $ret; |
433 | } |