Thomas Bruederli
2013-06-20 07c6c69eca8751c0e96a846afb30c24ab2638b1f
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  *
11  */
12
13 class rcube_sql_password
14 {
15     function save($curpass, $passwd)
16     {
17         $rcmail = rcmail::get_instance();
18
19         if (!($sql = $rcmail->config->get('password_query')))
20             $sql = 'SELECT update_passwd(%c, %u)';
21
22         if ($dsn = $rcmail->config->get('password_db_dsn')) {
d6938b 23             // #1486067: enable new_link option
AM 24             if (is_array($dsn) && empty($dsn['new_link']))
25                 $dsn['new_link'] = true;
26             else if (!is_array($dsn) && !preg_match('/\?new_link=true/', $dsn))
27                 $dsn .= '?new_link=true';
48e9c1 28
91f227 29             $db = rcube_db::factory($dsn, '', false);
48e9c1 30             $db->set_debug((bool)$rcmail->config->get('sql_debug'));
T 31             $db->db_connect('w');
32         }
33         else {
34             $db = $rcmail->get_dbh();
35         }
36
2193f6 37         if ($db->is_error()) {
48e9c1 38             return PASSWORD_ERROR;
2193f6 39         }
48e9c1 40
T 41         // crypted password
42         if (strpos($sql, '%c') !== FALSE) {
43             $salt = '';
5e9b40 44
5c603c 45             if (!($crypt_hash = $rcmail->config->get('password_crypt_hash')))
5e9b40 46             {
5c603c 47                 if (CRYPT_MD5)
D 48                     $crypt_hash = 'md5';
49                 else if (CRYPT_STD_DES)
50                     $crypt_hash = 'des';
51             }
d6938b 52
5c603c 53             switch ($crypt_hash)
D 54             {
55             case 'md5':
5e9b40 56                 $len = 8;
5c603c 57                 $salt_hashindicator = '$1$';
5e9b40 58                 break;
5c603c 59             case 'des':
5e9b40 60                 $len = 2;
D 61                 break;
5c603c 62             case 'blowfish':
5e9b40 63                 $len = 22;
5c603c 64                 $salt_hashindicator = '$2a$';
5e9b40 65                 break;
5c603c 66             case 'sha256':
5e9b40 67                 $len = 16;
5c603c 68                 $salt_hashindicator = '$5$';
5e9b40 69                 break;
5c603c 70             case 'sha512':
5e9b40 71                 $len = 16;
5c603c 72                 $salt_hashindicator = '$6$';
5e9b40 73                 break;
D 74             default:
75                 return PASSWORD_CRYPT_ERROR;
48e9c1 76             }
T 77
78             //Restrict the character set used as salt (#1488136)
79             $seedchars = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
80             for ($i = 0; $i < $len ; $i++) {
d6938b 81                 $salt .= $seedchars[rand(0, 63)];
48e9c1 82             }
T 83
5c603c 84             $sql = str_replace('%c',  $db->quote(crypt($passwd, $salt_hashindicator ? $salt_hashindicator .$salt.'$' : $salt)), $sql);
48e9c1 85         }
T 86
87         // dovecotpw
88         if (strpos($sql, '%D') !== FALSE) {
89             if (!($dovecotpw = $rcmail->config->get('password_dovecotpw')))
90                 $dovecotpw = 'dovecotpw';
91             if (!($method = $rcmail->config->get('password_dovecotpw_method')))
92                 $method = 'CRAM-MD5';
93
94             // use common temp dir
95             $tmp_dir = $rcmail->config->get('temp_dir');
96             $tmpfile = tempnam($tmp_dir, 'roundcube-');
97
98             $pipe = popen("$dovecotpw -s '$method' > '$tmpfile'", "w");
99             if (!$pipe) {
100                 unlink($tmpfile);
101                 return PASSWORD_CRYPT_ERROR;
102             }
103             else {
104                 fwrite($pipe, $passwd . "\n", 1+strlen($passwd)); usleep(1000);
105                 fwrite($pipe, $passwd . "\n", 1+strlen($passwd));
106                 pclose($pipe);
107                 $newpass = trim(file_get_contents($tmpfile), "\n");
108                 if (!preg_match('/^\{' . $method . '\}/', $newpass)) {
109                     return PASSWORD_CRYPT_ERROR;
110                 }
111                 if (!$rcmail->config->get('password_dovecotpw_with_method'))
112                     $newpass = trim(str_replace('{' . $method . '}', '', $newpass));
113                 unlink($tmpfile);
114             }
115             $sql = str_replace('%D', $db->quote($newpass), $sql);
116         }
117
118         // hashed passwords
119         if (preg_match('/%[n|q]/', $sql)) {
d6938b 120             if (!extension_loaded('hash')) {
61be82 121                 rcube::raise_error(array(
d6938b 122                     'code' => 600,
AM 123                     'type' => 'php',
124                     'file' => __FILE__, 'line' => __LINE__,
125                     'message' => "Password plugin: 'hash' extension not loaded!"
126                 ), true, false);
48e9c1 127
d6938b 128                 return PASSWORD_ERROR;
AM 129             }
48e9c1 130
d6938b 131             if (!($hash_algo = strtolower($rcmail->config->get('password_hash_algorithm'))))
48e9c1 132                 $hash_algo = 'sha1';
T 133
d6938b 134             $hash_passwd = hash($hash_algo, $passwd);
48e9c1 135             $hash_curpass = hash($hash_algo, $curpass);
T 136
d6938b 137             if ($rcmail->config->get('password_hash_base64')) {
48e9c1 138                 $hash_passwd = base64_encode(pack('H*', $hash_passwd));
T 139                 $hash_curpass = base64_encode(pack('H*', $hash_curpass));
140             }
141
d6938b 142             $sql = str_replace('%n', $db->quote($hash_passwd, 'text'), $sql);
AM 143             $sql = str_replace('%q', $db->quote($hash_curpass, 'text'), $sql);
48e9c1 144         }
T 145
146         // Handle clear text passwords securely (#1487034)
147         $sql_vars = array();
148         if (preg_match_all('/%[p|o]/', $sql, $m)) {
149             foreach ($m[0] as $var) {
150                 if ($var == '%p') {
151                     $sql = preg_replace('/%p/', '?', $sql, 1);
152                     $sql_vars[] = (string) $passwd;
153                 }
154                 else { // %o
155                     $sql = preg_replace('/%o/', '?', $sql, 1);
156                     $sql_vars[] = (string) $curpass;
157                 }
158             }
159         }
160
161         $local_part  = $rcmail->user->get_username('local');
162         $domain_part = $rcmail->user->get_username('domain');
163         $username    = $_SESSION['username'];
164         $host        = $_SESSION['imap_host'];
165
166         // convert domains to/from punnycode
167         if ($rcmail->config->get('password_idn_ascii')) {
61be82 168             $domain_part = rcube_utils::idn_to_ascii($domain_part);
AM 169             $username    = rcube_utils::idn_to_ascii($username);
170             $host        = rcube_utils::idn_to_ascii($host);
48e9c1 171         }
T 172         else {
61be82 173             $domain_part = rcube_utils::idn_to_utf8($domain_part);
AM 174             $username    = rcube_utils::idn_to_utf8($username);
175             $host        = rcube_utils::idn_to_utf8($host);
48e9c1 176         }
T 177
178         // at least we should always have the local part
179         $sql = str_replace('%l', $db->quote($local_part, 'text'), $sql);
180         $sql = str_replace('%d', $db->quote($domain_part, 'text'), $sql);
181         $sql = str_replace('%u', $db->quote($username, 'text'), $sql);
182         $sql = str_replace('%h', $db->quote($host, 'text'), $sql);
183
184         $res = $db->query($sql, $sql_vars);
185
186         if (!$db->is_error()) {
c027ba 187             if (strtolower(substr(trim($sql),0,6)) == 'select') {
2193f6 188                 if ($db->fetch_array($res))
d6938b 189                     return PASSWORD_SUCCESS;
AM 190             } else {
48e9c1 191                 // This is the good case: 1 row updated
d6938b 192                 if ($db->affected_rows($res) == 1)
AM 193                     return PASSWORD_SUCCESS;
48e9c1 194                 // @TODO: Some queries don't affect any rows
T 195                 // Should we assume a success if there was no error?
d6938b 196             }
48e9c1 197         }
T 198
199         return PASSWORD_ERROR;
200     }
201 }