Aleksander Machniak
2014-09-08 4baf96a4ca1621a267c10c67e84e80e6bf72dbfd
commit | author | age
48e9c1 1 <?php
T 2
3 /**
4  * SQL Password Driver
5  *
6  * Driver for passwords stored in SQL database
7  *
8  * @version 2.0
9  * @author Aleksander 'A.L.E.C' Machniak <alec@alec.pl>
10  *
4baf96 11  * Copyright (C) 2005-2013, The Roundcube Dev Team
AM 12  *
13  * This program is free software: you can redistribute it and/or modify
14  * it under the terms of the GNU General Public License as published by
15  * the Free Software Foundation, either version 3 of the License, or
16  * (at your option) any later version.
17  *
18  * This program is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21  * GNU General Public License for more details.
22  *
23  * You should have received a copy of the GNU General Public License
24  * along with this program. If not, see http://www.gnu.org/licenses/.
48e9c1 25  */
T 26
27 class rcube_sql_password
28 {
29     function save($curpass, $passwd)
30     {
31         $rcmail = rcmail::get_instance();
32
4baf96 33         if (!($sql = $rcmail->config->get('password_query'))) {
48e9c1 34             $sql = 'SELECT update_passwd(%c, %u)';
4baf96 35         }
48e9c1 36
T 37         if ($dsn = $rcmail->config->get('password_db_dsn')) {
d6938b 38             // #1486067: enable new_link option
AM 39             if (is_array($dsn) && empty($dsn['new_link']))
40                 $dsn['new_link'] = true;
41             else if (!is_array($dsn) && !preg_match('/\?new_link=true/', $dsn))
42                 $dsn .= '?new_link=true';
48e9c1 43
91f227 44             $db = rcube_db::factory($dsn, '', false);
48e9c1 45             $db->set_debug((bool)$rcmail->config->get('sql_debug'));
T 46             $db->db_connect('w');
47         }
48         else {
49             $db = $rcmail->get_dbh();
50         }
51
2193f6 52         if ($db->is_error()) {
48e9c1 53             return PASSWORD_ERROR;
2193f6 54         }
48e9c1 55
T 56         // crypted password
57         if (strpos($sql, '%c') !== FALSE) {
58             $salt = '';
5e9b40 59
4baf96 60             if (!($crypt_hash = $rcmail->config->get('password_crypt_hash'))) {
5c603c 61                 if (CRYPT_MD5)
D 62                     $crypt_hash = 'md5';
63                 else if (CRYPT_STD_DES)
64                     $crypt_hash = 'des';
65             }
d6938b 66
4baf96 67             switch ($crypt_hash) {
5c603c 68             case 'md5':
5e9b40 69                 $len = 8;
5c603c 70                 $salt_hashindicator = '$1$';
5e9b40 71                 break;
5c603c 72             case 'des':
5e9b40 73                 $len = 2;
D 74                 break;
5c603c 75             case 'blowfish':
5e9b40 76                 $len = 22;
5c603c 77                 $salt_hashindicator = '$2a$';
5e9b40 78                 break;
5c603c 79             case 'sha256':
5e9b40 80                 $len = 16;
5c603c 81                 $salt_hashindicator = '$5$';
5e9b40 82                 break;
5c603c 83             case 'sha512':
5e9b40 84                 $len = 16;
5c603c 85                 $salt_hashindicator = '$6$';
5e9b40 86                 break;
D 87             default:
88                 return PASSWORD_CRYPT_ERROR;
48e9c1 89             }
T 90
91             //Restrict the character set used as salt (#1488136)
92             $seedchars = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
93             for ($i = 0; $i < $len ; $i++) {
d6938b 94                 $salt .= $seedchars[rand(0, 63)];
48e9c1 95             }
T 96
5c603c 97             $sql = str_replace('%c',  $db->quote(crypt($passwd, $salt_hashindicator ? $salt_hashindicator .$salt.'$' : $salt)), $sql);
48e9c1 98         }
T 99
100         // dovecotpw
101         if (strpos($sql, '%D') !== FALSE) {
102             if (!($dovecotpw = $rcmail->config->get('password_dovecotpw')))
103                 $dovecotpw = 'dovecotpw';
104             if (!($method = $rcmail->config->get('password_dovecotpw_method')))
105                 $method = 'CRAM-MD5';
106
107             // use common temp dir
108             $tmp_dir = $rcmail->config->get('temp_dir');
109             $tmpfile = tempnam($tmp_dir, 'roundcube-');
110
111             $pipe = popen("$dovecotpw -s '$method' > '$tmpfile'", "w");
112             if (!$pipe) {
113                 unlink($tmpfile);
114                 return PASSWORD_CRYPT_ERROR;
115             }
116             else {
117                 fwrite($pipe, $passwd . "\n", 1+strlen($passwd)); usleep(1000);
118                 fwrite($pipe, $passwd . "\n", 1+strlen($passwd));
119                 pclose($pipe);
120                 $newpass = trim(file_get_contents($tmpfile), "\n");
121                 if (!preg_match('/^\{' . $method . '\}/', $newpass)) {
122                     return PASSWORD_CRYPT_ERROR;
123                 }
124                 if (!$rcmail->config->get('password_dovecotpw_with_method'))
125                     $newpass = trim(str_replace('{' . $method . '}', '', $newpass));
126                 unlink($tmpfile);
127             }
128             $sql = str_replace('%D', $db->quote($newpass), $sql);
129         }
130
131         // hashed passwords
132         if (preg_match('/%[n|q]/', $sql)) {
d6938b 133             if (!extension_loaded('hash')) {
61be82 134                 rcube::raise_error(array(
d6938b 135                     'code' => 600,
AM 136                     'type' => 'php',
137                     'file' => __FILE__, 'line' => __LINE__,
138                     'message' => "Password plugin: 'hash' extension not loaded!"
139                 ), true, false);
48e9c1 140
d6938b 141                 return PASSWORD_ERROR;
AM 142             }
48e9c1 143
4baf96 144             if (!($hash_algo = strtolower($rcmail->config->get('password_hash_algorithm')))) {
48e9c1 145                 $hash_algo = 'sha1';
4baf96 146             }
48e9c1 147
d6938b 148             $hash_passwd = hash($hash_algo, $passwd);
48e9c1 149             $hash_curpass = hash($hash_algo, $curpass);
T 150
d6938b 151             if ($rcmail->config->get('password_hash_base64')) {
48e9c1 152                 $hash_passwd = base64_encode(pack('H*', $hash_passwd));
T 153                 $hash_curpass = base64_encode(pack('H*', $hash_curpass));
154             }
155
d6938b 156             $sql = str_replace('%n', $db->quote($hash_passwd, 'text'), $sql);
AM 157             $sql = str_replace('%q', $db->quote($hash_curpass, 'text'), $sql);
48e9c1 158         }
T 159
160         // Handle clear text passwords securely (#1487034)
161         $sql_vars = array();
162         if (preg_match_all('/%[p|o]/', $sql, $m)) {
163             foreach ($m[0] as $var) {
164                 if ($var == '%p') {
165                     $sql = preg_replace('/%p/', '?', $sql, 1);
166                     $sql_vars[] = (string) $passwd;
167                 }
168                 else { // %o
169                     $sql = preg_replace('/%o/', '?', $sql, 1);
170                     $sql_vars[] = (string) $curpass;
171                 }
172             }
173         }
174
175         $local_part  = $rcmail->user->get_username('local');
176         $domain_part = $rcmail->user->get_username('domain');
177         $username    = $_SESSION['username'];
178         $host        = $_SESSION['imap_host'];
179
180         // convert domains to/from punnycode
181         if ($rcmail->config->get('password_idn_ascii')) {
61be82 182             $domain_part = rcube_utils::idn_to_ascii($domain_part);
AM 183             $username    = rcube_utils::idn_to_ascii($username);
184             $host        = rcube_utils::idn_to_ascii($host);
48e9c1 185         }
T 186         else {
61be82 187             $domain_part = rcube_utils::idn_to_utf8($domain_part);
AM 188             $username    = rcube_utils::idn_to_utf8($username);
189             $host        = rcube_utils::idn_to_utf8($host);
48e9c1 190         }
T 191
192         // at least we should always have the local part
193         $sql = str_replace('%l', $db->quote($local_part, 'text'), $sql);
194         $sql = str_replace('%d', $db->quote($domain_part, 'text'), $sql);
195         $sql = str_replace('%u', $db->quote($username, 'text'), $sql);
196         $sql = str_replace('%h', $db->quote($host, 'text'), $sql);
197
198         $res = $db->query($sql, $sql_vars);
199
200         if (!$db->is_error()) {
c027ba 201             if (strtolower(substr(trim($sql),0,6)) == 'select') {
4baf96 202                 if ($db->fetch_array($res)) {
d6938b 203                     return PASSWORD_SUCCESS;
4baf96 204                 }
AM 205             }
206             else {
48e9c1 207                 // This is the good case: 1 row updated
d6938b 208                 if ($db->affected_rows($res) == 1)
AM 209                     return PASSWORD_SUCCESS;
48e9c1 210                 // @TODO: Some queries don't affect any rows
T 211                 // Should we assume a success if there was no error?
d6938b 212             }
48e9c1 213         }
T 214
215         return PASSWORD_ERROR;
216     }
217 }