copyright update
[squirrelmail.git] / plugins / change_password / backend / peardb.php
CommitLineData
d032df69 1<?php
4b4abf93 2
d032df69 3/**
4 * Change password PearDB backend
5 *
47ccfad4 6 * @copyright &copy; 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 */
17global $cpw_peardb_detect;
18$cpw_peardb_detect=@include_once('DB.php');
19
20/** declare configuration globals */
21global $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 */
77if ( 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 */
99global $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 */
108function 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 */
143function 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 */
313function 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 */
357function 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?>