|
@@ -0,0 +1,442 @@
|
|
|
+<?php
|
|
|
+/**
|
|
|
+ * Change password PearDB backend
|
|
|
+ *
|
|
|
+ * @version $Id$
|
|
|
+ * @package plugins
|
|
|
+ * @subpackage change_password
|
|
|
+ */
|
|
|
+
|
|
|
+/** load Pear DB.
|
|
|
+ * Global is needed because library must be loaded before configuration
|
|
|
+ * in order to use DB constants.
|
|
|
+ */
|
|
|
+global $cpw_peardb_detect;
|
|
|
+$cpw_peardb_detect=@include_once('DB.php');
|
|
|
+
|
|
|
+/** declare configuration globals */
|
|
|
+global $cpw_peardb_dsn, $cpw_peardb_connect_opts, $cpw_peardb_table,
|
|
|
+ $cpw_peardb_uid_field, $cpw_peardb_domain_field, $cpw_peardb_passwd_field,
|
|
|
+ $cpw_peardb_crypted_passwd, $cpw_peardb_debug;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Connection DSN.
|
|
|
+ * Any format supported by peardb
|
|
|
+ * @global mixed $cpw_peardb_dsn
|
|
|
+ */
|
|
|
+$cpw_peardb_dsn='';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Pear DB connection options
|
|
|
+ * @global array $cpw_peardb_connect_opts
|
|
|
+ */
|
|
|
+$cpw_peardb_connect_opts=array();
|
|
|
+
|
|
|
+/**
|
|
|
+ * Table that stores user information
|
|
|
+ * @global string $cpw_peardb_table
|
|
|
+ */
|
|
|
+$cpw_peardb_table='';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Field that stores user name
|
|
|
+ * @global string $cpw_peardb_uid_field
|
|
|
+ */
|
|
|
+$cpw_peardb_uid_field='userid';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Field that stores domain part of username
|
|
|
+ * @global string $cpw_peardb_domain_field
|
|
|
+ */
|
|
|
+$cpw_peardb_domain_field='';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Field that stores password
|
|
|
+ * @global string $cpw_peardb_passwd_field
|
|
|
+ */
|
|
|
+$cpw_peardb_passwd_field='password';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Passwords are plaintext or encrypted
|
|
|
+ * @global boolean $cpw_peardb_crypted_passwd
|
|
|
+ */
|
|
|
+$cpw_peardb_crypted_passwd=false;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Controls output debugging errors
|
|
|
+ * Error messages might contain login and password information.
|
|
|
+ * Don't enable on production systems.
|
|
|
+ * @global boolean $cpw_peardb_debug
|
|
|
+ */
|
|
|
+$cpw_peardb_debug=false;
|
|
|
+
|
|
|
+/** configuration overrides */
|
|
|
+if ( isset($cpw_peardb) && is_array($cpw_peardb) && !empty($cpw_peardb) ) {
|
|
|
+ if (isset($cpw_peardb['dsn']))
|
|
|
+ $cpw_peardb_dsn=$cpw_peardb['dsn'];
|
|
|
+ if (isset($cpw_peardb['connect_opts']))
|
|
|
+ $cpw_peardb_connect_opts=$cpw_peaddb['connect_opts'];
|
|
|
+ if (isset($cpw_peardb['table']))
|
|
|
+ $cpw_peardb_table=$cpw_peardb['table'];
|
|
|
+ if (isset($cpw_peardb['uid_field']))
|
|
|
+ $cpw_peardb_uid_field=$cpw_peardb['uid_field'];
|
|
|
+ if (isset($cpw_peardb['domain_field']))
|
|
|
+ $cpw_peardb_domain_field=$cpw_peardb['domain_field'];
|
|
|
+ if (isset($cpw_peardb['password_field']))
|
|
|
+ $cpw_peardb_passwd_field=$cpw_peardb['password_field'];
|
|
|
+ if (isset($cpw_peardb['crypted_passwd']))
|
|
|
+ $cpw_peardb_crypted_passwd=true;
|
|
|
+ if (isset($cpw_peardb['debug']))
|
|
|
+ $cpw_peardb_debug=$cpw_peardb['debug'];
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Define here the name of your password changing function.
|
|
|
+ */
|
|
|
+global $squirrelmail_plugin_hooks;
|
|
|
+$squirrelmail_plugin_hooks['change_password_dochange']['peardb'] =
|
|
|
+ 'cpw_peardb_dochange';
|
|
|
+$squirrelmail_plugin_hooks['change_password_init']['peardb'] =
|
|
|
+ 'cpw_peardb_init';
|
|
|
+
|
|
|
+/**
|
|
|
+ * Checks if configuration is correct
|
|
|
+ */
|
|
|
+function cpw_peardb_init() {
|
|
|
+ global $color, $cpw_peardb_detect, $cpw_peardb_dsn, $cpw_peardb_table;
|
|
|
+
|
|
|
+ /**
|
|
|
+ * If SM_PATH isn't defined, define it. Required to include files.
|
|
|
+ * @ignore
|
|
|
+ */
|
|
|
+ if (!defined('SM_PATH')) {
|
|
|
+ define('SM_PATH','../../../');
|
|
|
+ }
|
|
|
+
|
|
|
+ // load error_box() function
|
|
|
+ include_once(SM_PATH . 'functions/display_messages.php');
|
|
|
+
|
|
|
+ if (! $cpw_peardb_detect) {
|
|
|
+ error_box(_("Plugin is unable to use PHP Pear DB libraries. PHP Pear includes must be available in your PHP include_path setting."),$color);
|
|
|
+ echo "</body></html>\n";
|
|
|
+ exit();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Test required settings
|
|
|
+ if ((is_string($cpw_peardb_dsn) && trim($cpw_peardb_dsn)=='')
|
|
|
+ || trim($cpw_peardb_table)=='' ) {
|
|
|
+ error_box(_("Required change password backend configuration options are missing."),$color);
|
|
|
+ echo "</body></html>\n";
|
|
|
+ exit();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * Changes password
|
|
|
+ * @param array data The username/curpw/newpw data.
|
|
|
+ * @return array Array of error messages.
|
|
|
+ */
|
|
|
+function cpw_peardb_dochange($data) {
|
|
|
+ global $cpw_peardb_dsn, $cpw_peardb_table, $cpw_peardb_connect_opts, $cpw_peardb_debug,
|
|
|
+ $cpw_peardb_uid_field, $cpw_peardb_passwd_field, $cpw_peardb_domain_field,
|
|
|
+ $cpw_peardb_crypted_passwd, $domain;
|
|
|
+
|
|
|
+ $username = $data['username'];
|
|
|
+ $curpw = $data['curpw'];
|
|
|
+ $newpw = $data['newpw'];
|
|
|
+
|
|
|
+ $msgs = array();
|
|
|
+
|
|
|
+ // split user and domain parts from username, if domain field is set and username looks like email.
|
|
|
+ if ($cpw_peardb_domain_field!='' && preg_match("/(.*)@(.*)/",$username,$match)) {
|
|
|
+ $user=$match[1];
|
|
|
+ $user_domain=$match[2];
|
|
|
+ } else {
|
|
|
+ $user=$username;
|
|
|
+ $user_domain=$domain;
|
|
|
+ }
|
|
|
+
|
|
|
+ // connect to database and make sure that table exists
|
|
|
+ $cpw_db = DB::connect($cpw_peardb_dsn, $cpw_peardb_connect_opts);
|
|
|
+ if (PEAR::isError($cpw_db)) {
|
|
|
+ array_push($msgs,sprintf(_("Connection error: %s"),htmlspecialchars($cpw_db->getMessage())));
|
|
|
+ if ($cpw_peardb_debug)
|
|
|
+ array_push($msgs,htmlspecialchars($cpw_db->getuserinfo()));
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // get table information
|
|
|
+ $table_info = $cpw_db->tableinfo($cpw_peardb_table);
|
|
|
+ if (PEAR::isError($table_info)) {
|
|
|
+ array_push($msgs,sprintf(_("Invalid table name: %s"),htmlspecialchars($cpw_peardb_table)));
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (empty($table_info)) {
|
|
|
+ array_push($msgs,_("User table is empty."));
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ $cpw_peardb_uid_check=false;
|
|
|
+ $cpw_peardb_passwd_check=false;
|
|
|
+ $cpw_peardb_domain_check=(($cpw_peardb_domain_field=='')? true : false);
|
|
|
+ foreach($table_info as $key => $field_data) {
|
|
|
+ if ($field_data['name']==$cpw_peardb_uid_field)
|
|
|
+ $cpw_peardb_uid_check=true;
|
|
|
+ if ($field_data['name']==$cpw_peardb_passwd_field)
|
|
|
+ $cpw_peardb_passwd_check=true;
|
|
|
+ if ($cpw_peardb_domain_field!='' && $field_data['name']==$cpw_peardb_domain_field)
|
|
|
+ $cpw_peardb_domain_check=true;
|
|
|
+ }
|
|
|
+ if (! $cpw_peardb_uid_check) {
|
|
|
+ array_push($msgs,_("Invalid uid field."));
|
|
|
+ }
|
|
|
+ if (! $cpw_peardb_passwd_check) {
|
|
|
+ array_push($msgs,_("Invalid password field"));
|
|
|
+ }
|
|
|
+ if (! $cpw_peardb_domain_check) {
|
|
|
+ array_push($msgs,_("Invalid domain field"));
|
|
|
+ }
|
|
|
+ if (! empty($msgs)) {
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // find user's entry
|
|
|
+ $query='SELECT'
|
|
|
+ .' '.$cpw_db->quoteIdentifier($cpw_peardb_uid_field)
|
|
|
+ .', '.$cpw_db->quoteIdentifier($cpw_peardb_passwd_field)
|
|
|
+ .(($cpw_peardb_domain_field!='') ? ', '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field):'')
|
|
|
+ .' FROM '.$cpw_db->quoteIdentifier($cpw_peardb_table)
|
|
|
+ .' WHERE '
|
|
|
+ .$cpw_db->quoteIdentifier($cpw_peardb_uid_field).'='.$cpw_db->quoteSmart($user)
|
|
|
+ .(($cpw_peardb_domain_field!='') ?
|
|
|
+ ' AND '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field).'='.$cpw_db->quoteSmart($user_domain):
|
|
|
+ '');
|
|
|
+ $cpw_res=$cpw_db->query($query);
|
|
|
+ if (PEAR::isError($cpw_res)) {
|
|
|
+ array_push($msgs,sprintf(_("Query failed: %s"),htmlspecialchars($cpw_res->getMessage())));
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // make sure that there is only one user.
|
|
|
+ if ($cpw_res->numRows()==0) {
|
|
|
+ array_push($msgs,_("Unable to find user in user table."));
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($cpw_res->numRows()>1) {
|
|
|
+ array_push($msgs,_("Too many matches found in user table."));
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // FIXME: process possible errors
|
|
|
+ $cpw_res->fetchInto($userdb,DB_FETCHMODE_ASSOC);
|
|
|
+
|
|
|
+ // validate password
|
|
|
+ $valid_passwd=false;
|
|
|
+ if ($cpw_peardb_crypted_passwd) {
|
|
|
+ // detect password type
|
|
|
+ $pw_type=cpw_peardb_detect_crypto($userdb[$cpw_peardb_passwd_field]);
|
|
|
+ if (! $pw_type) {
|
|
|
+ array_push($msgs,_("Unable to detect password crypto algorithm."));
|
|
|
+ } else {
|
|
|
+ $hashed_pw=cpw_peardb_passwd_hash($curpw,$pw_type,$msgs,$userdb[$cpw_peardb_passwd_field]);
|
|
|
+ if ($hashed_pw==$userdb[$cpw_peardb_passwd_field]) {
|
|
|
+ $valid_passwd=true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } elseif ($userdb[$cpw_peardb_passwd_field]==$curpw) {
|
|
|
+ $valid_passwd=true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (! $valid_passwd) {
|
|
|
+ array_push($msgs,CPW_CURRENT_NOMATCH);
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // create new password
|
|
|
+ if ($cpw_peardb_crypted_passwd) {
|
|
|
+ $hashed_passwd=cpw_peardb_passwd_hash($newpw,$pw_type,$msgs);
|
|
|
+ } else {
|
|
|
+ $hashed_passwd=$newpw;
|
|
|
+ }
|
|
|
+
|
|
|
+ // make sure that password was created
|
|
|
+ if (! empty($msgs)) {
|
|
|
+ array_push($msgs,_("Unable to encrypt new password."));
|
|
|
+ $cpw_db->disconnect();
|
|
|
+ return $msgs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // create update query
|
|
|
+ $update_query='UPDATE '
|
|
|
+ . $cpw_db->quoteIdentifier($cpw_peardb_table)
|
|
|
+ .' SET '.$cpw_db->quoteIdentifier($cpw_peardb_passwd_field)
|
|
|
+ .'='.$cpw_db->quoteSmart($hashed_passwd)
|
|
|
+ .' WHERE '.$cpw_db->quoteIdentifier($cpw_peardb_uid_field)
|
|
|
+ .'='.$cpw_db->quoteSmart($user)
|
|
|
+ .(($cpw_peardb_domain_field!='') ?
|
|
|
+ ' AND '.$cpw_db->quoteIdentifier($cpw_peardb_domain_field).'='.$cpw_db->quoteSmart($user_domain) :
|
|
|
+ '');
|
|
|
+
|
|
|
+ // update database
|
|
|
+ $cpw_res=$cpw_db->query($update_query);
|
|
|
+
|
|
|
+ // check for update error
|
|
|
+ if (PEAR::isError($cpw_res)) {
|
|
|
+ array_push($msgs,sprintf(_("Unable to set new password: %s"),htmlspecialchars($cpw_res->getMessage())));
|
|
|
+ }
|
|
|
+
|
|
|
+ // close database connection
|
|
|
+ $cpw_db->disconnect();
|
|
|
+
|
|
|
+ return $msgs;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Detects password crypto
|
|
|
+ * reports 'crypt' if fails to detect any other crypt
|
|
|
+ * @param string $password
|
|
|
+ * @return string
|
|
|
+ */
|
|
|
+function cpw_peardb_detect_crypto($password) {
|
|
|
+ $ret = false;
|
|
|
+
|
|
|
+ if (preg_match("/^\{(.+)\}+/",$password,$crypto)) {
|
|
|
+ $ret=strtolower($crypto[1]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if ($ret=='crypt') {
|
|
|
+ // {CRYPT} can be standard des crypt, extended des crypt, md5 crypt or blowfish
|
|
|
+ // depends on first salt symbols (ext_des = '_', md5 = '$1$', blowfish = '$2')
|
|
|
+ // and length of salt (des = 2 chars, ext_des = 9, md5 = 12, blowfish = 16).
|
|
|
+ if (preg_match("/^\{crypt\}\\\$1\\\$+/i",$password)) {
|
|
|
+ $ret='tagged_md5crypt';
|
|
|
+ } elseif (preg_match("/^\{crypt\}\\\$2+/i",$password)) {
|
|
|
+ $ret='tagged_blowfish';
|
|
|
+ } elseif (preg_match("/^\{crypt\}_+/i",$password)) {
|
|
|
+ $ret='tagged_extcrypt';
|
|
|
+ } else {
|
|
|
+ $ret='tagged_crypt';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (! $ret) {
|
|
|
+ if (preg_match("/^\\\$1\\\$+/i",$password)) {
|
|
|
+ $ret='md5crypt';
|
|
|
+ } elseif (preg_match("/^\\\$2+/i",$password)) {
|
|
|
+ $ret='blowfish';
|
|
|
+ } elseif (preg_match("/^_+/i",$password)) {
|
|
|
+ $ret='extcrypt';
|
|
|
+ } else {
|
|
|
+ $ret='crypt';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $ret;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Encode password
|
|
|
+ * @param string $password plain text password
|
|
|
+ * @param string $crypto used crypto
|
|
|
+ * @param array $msgs error messages
|
|
|
+ * @param string $forced_salt old password used to create password hash for verification
|
|
|
+ * @return string hashed password. false, if hashing fails
|
|
|
+ */
|
|
|
+function cpw_peardb_passwd_hash($password,$crypto,&$msgs,$forced_salt='') {
|
|
|
+ global $username;
|
|
|
+
|
|
|
+ $crypto = strtolower($crypto);
|
|
|
+
|
|
|
+ $ret=false;
|
|
|
+ $salt='';
|
|
|
+ // extra symbols used for random string in crypt salt
|
|
|
+ // squirrelmail GenerateRandomString() adds alphanumerics with third argument = 7.
|
|
|
+ $extra_salt_chars='./';
|
|
|
+
|
|
|
+ switch($crypto) {
|
|
|
+ case 'plain-md5':
|
|
|
+ $ret='{PLAIN-MD5}' . md5($password);
|
|
|
+ break;
|
|
|
+ case 'digest-md5':
|
|
|
+ // split username into user and domain parts
|
|
|
+ if (preg_match("/(.*)@(.*)/",$username,$match)) {
|
|
|
+ $ret='{DIGEST-MD5}' . md5($match[1].':'.$match[2].':'.$password);
|
|
|
+ } else {
|
|
|
+ array_push($msgs,_("Unable to use digest-md5 crypto."));
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ case 'tagged_crypt':
|
|
|
+ case 'crypt':
|
|
|
+ if (! defined('CRYPT_STD_DES') || CRYPT_STD_DES==0) {
|
|
|
+ array_push($msgs,sprintf(_("Unsupported crypto: %s"),'crypt'));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if ($forced_salt=='') {
|
|
|
+ $salt=GenerateRandomString(2,$extra_salt_chars,7);
|
|
|
+ } else {
|
|
|
+ $salt=$forced_salt;
|
|
|
+ }
|
|
|
+ $ret = ($crypto=='tagged_crypt' ? '{crypt}' : '');
|
|
|
+ $ret.= crypt($password,$salt);
|
|
|
+ break;
|
|
|
+ case 'tagged_md5crypt':
|
|
|
+ case 'md5crypt':
|
|
|
+ if (! defined('CRYPT_MD5') || CRYPT_MD5==0) {
|
|
|
+ array_push($msgs,sprintf(_("Unsupported crypto: %s"),'md5crypt'));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if ($forced_salt=='') {
|
|
|
+ $salt='$1$' .GenerateRandomString(9,$extra_salt_chars,7);
|
|
|
+ } else {
|
|
|
+ $salt=$forced_salt;
|
|
|
+ }
|
|
|
+ $ret = ($crypto=='tagged_md5crypt' ? '{crypt}' : '');
|
|
|
+ $ret.= crypt($password,$salt);
|
|
|
+ break;
|
|
|
+ case 'tagged_extcrypt':
|
|
|
+ case 'extcrypt':
|
|
|
+ if (! defined('CRYPT_EXT_DES') || CRYPT_EXT_DES==0) {
|
|
|
+ array_push($msgs,sprintf(_("Unsupported crypto: %s"),'extcrypt'));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if ($forced_salt=='') {
|
|
|
+ $salt='_' . GenerateRandomString(8,$extra_salt_chars,7);
|
|
|
+ } else {
|
|
|
+ $salt=$forced_salt;
|
|
|
+ }
|
|
|
+ $ret = ($crypto=='tagged_extcrypt' ? '{crypt}' : '');
|
|
|
+ $ret.= crypt($password,$salt);
|
|
|
+ break;
|
|
|
+ case 'tagged_blowfish':
|
|
|
+ case 'blowfish':
|
|
|
+ if (! defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH==0) {
|
|
|
+ array_push($msgs,sprintf(_("Unsupported crypto: %s"),'blowfish'));
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if ($forced_salt=='') {
|
|
|
+ $salt='$2a$12$' . GenerateRandomString(13,$extra_salt_chars,7);
|
|
|
+ } else {
|
|
|
+ $salt=$forced_salt;
|
|
|
+ }
|
|
|
+ $ret = ($crypto=='tagged_blowfish' ? '{crypt}' : '');
|
|
|
+ $ret.= crypt($password,$salt);
|
|
|
+ break;
|
|
|
+ case 'plain':
|
|
|
+ case 'plaintext':
|
|
|
+ $ret = $password;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ array_push($msgs,sprintf(_("Unsupported crypto: %s"),htmlspecialchars($crypto)));
|
|
|
+ }
|
|
|
+ return $ret;
|
|
|
+}
|
|
|
+?>
|