Thomas Bruederli
2013-06-20 07c6c69eca8751c0e96a846afb30c24ab2638b1f
commit | author | age
48e9c1 1 <?php
T 2
3 /**
4  * Simple LDAP Password Driver
5  *
6  * Driver for passwords stored in LDAP
7  * This driver is based on Edouard's LDAP Password Driver, but does not
8  * require PEAR's Net_LDAP2 to be installed
9  *
10  * @version 2.0
11  * @author Wout Decre <wout@canodus.be>
12  */
13
14 class rcube_ldap_simple_password
15 {
16     function save($curpass, $passwd)
17     {
d6938b 18         $rcmail = rcmail::get_instance();
48e9c1 19
d6938b 20         // Connect
AM 21         if (!$ds = ldap_connect($rcmail->config->get('password_ldap_host'), $rcmail->config->get('password_ldap_port'))) {
22             ldap_unbind($ds);
23             return PASSWORD_CONNECT_ERROR;
24         }
48e9c1 25
d6938b 26         // Set protocol version
AM 27         if (!ldap_set_option($ds, LDAP_OPT_PROTOCOL_VERSION, $rcmail->config->get('password_ldap_version'))) {
28             ldap_unbind($ds);
29             return PASSWORD_CONNECT_ERROR;
30         }
48e9c1 31
d6938b 32         // Start TLS
AM 33         if ($rcmail->config->get('password_ldap_starttls')) {
34             if (!ldap_start_tls($ds)) {
35                 ldap_unbind($ds);
36                 return PASSWORD_CONNECT_ERROR;
37             }
38         }
48e9c1 39
d6938b 40         // Build user DN
AM 41         if ($user_dn = $rcmail->config->get('password_ldap_userDN_mask')) {
42             $user_dn = $this->substitute_vars($user_dn);
43         }
44         else {
45             $user_dn = $this->search_userdn($rcmail, $ds);
46         }
48e9c1 47
d6938b 48         if (empty($user_dn)) {
AM 49             ldap_unbind($ds);
50             return PASSWORD_CONNECT_ERROR;
51         }
48e9c1 52
d6938b 53         // Connection method
AM 54         switch ($rcmail->config->get('password_ldap_method')) {
55         case 'admin':
56             $binddn = $rcmail->config->get('password_ldap_adminDN');
57             $bindpw = $rcmail->config->get('password_ldap_adminPW');
58             break;
59         case 'user':
60         default:
61             $binddn = $user_dn;
62             $bindpw = $curpass;
63             break;
64         }
48e9c1 65
d6938b 66         $crypted_pass = $this->hash_password($passwd, $rcmail->config->get('password_ldap_encodage'));
AM 67         $lchattr      = $rcmail->config->get('password_ldap_lchattr');
68         $pwattr       = $rcmail->config->get('password_ldap_pwattr');
48e9c1 69         $smbpwattr    = $rcmail->config->get('password_ldap_samba_pwattr');
T 70         $smblchattr   = $rcmail->config->get('password_ldap_samba_lchattr');
71         $samba        = $rcmail->config->get('password_ldap_samba');
72
73         // Support password_ldap_samba option for backward compat.
74         if ($samba && !$smbpwattr) {
75             $smbpwattr  = 'sambaNTPassword';
76             $smblchattr = 'sambaPwdLastSet';
77         }
78
d6938b 79         // Crypt new password
AM 80         if (!$crypted_pass) {
81             return PASSWORD_CRYPT_ERROR;
82         }
48e9c1 83
T 84         // Crypt new Samba password
85         if ($smbpwattr && !($samba_pass = $this->hash_password($passwd, 'samba'))) {
d6938b 86             return PASSWORD_CRYPT_ERROR;
48e9c1 87         }
T 88
d6938b 89         // Bind
AM 90         if (!ldap_bind($ds, $binddn, $bindpw)) {
91             ldap_unbind($ds);
92             return PASSWORD_CONNECT_ERROR;
93         }
48e9c1 94
d6938b 95         $entree[$pwattr] = $crypted_pass;
48e9c1 96
d6938b 97         // Update PasswordLastChange Attribute if desired
AM 98         if ($lchattr) {
99             $entree[$lchattr] = (int)(time() / 86400);
100         }
48e9c1 101
T 102         // Update Samba password
103         if ($smbpwattr) {
104             $entree[$smbpwattr] = $samba_pass;
105         }
106
107         // Update Samba password last change
108         if ($smblchattr) {
109             $entree[$smblchattr] = time();
110         }
111
d6938b 112         if (!ldap_modify($ds, $user_dn, $entree)) {
AM 113             ldap_unbind($ds);
114             return PASSWORD_CONNECT_ERROR;
115         }
48e9c1 116
d6938b 117         // All done, no error
AM 118         ldap_unbind($ds);
119         return PASSWORD_SUCCESS;
48e9c1 120     }
T 121
122     /**
123      * Bind with searchDN and searchPW and search for the user's DN
124      * Use search_base and search_filter defined in config file
125      * Return the found DN
126      */
127     function search_userdn($rcmail, $ds)
128     {
d6938b 129         /* Bind */
AM 130         if (!ldap_bind($ds, $rcmail->config->get('password_ldap_searchDN'), $rcmail->config->get('password_ldap_searchPW'))) {
131             return false;
132         }
48e9c1 133
d6938b 134         /* Search for the DN */
AM 135         if (!$sr = ldap_search($ds, $rcmail->config->get('password_ldap_search_base'), $this->substitute_vars($rcmail->config->get('password_ldap_search_filter')))) {
136             return false;
137         }
48e9c1 138
d6938b 139         /* If no or more entries were found, return false */
AM 140         if (ldap_count_entries($ds, $sr) != 1) {
141             return false;
142         }
48e9c1 143
d6938b 144         return ldap_get_dn($ds, ldap_first_entry($ds, $sr));
48e9c1 145     }
T 146
147     /**
148      * Substitute %login, %name, %domain, %dc in $str
149      * See plugin config for details
150      */
151     function substitute_vars($str)
152     {
d6938b 153         $str = str_replace('%login', $_SESSION['username'], $str);
AM 154         $str = str_replace('%l', $_SESSION['username'], $str);
48e9c1 155
d6938b 156         $parts = explode('@', $_SESSION['username']);
48e9c1 157
d6938b 158         if (count($parts) == 2) {
48e9c1 159             $dc = 'dc='.strtr($parts[1], array('.' => ',dc=')); // hierarchal domain string
T 160
d6938b 161             $str = str_replace('%name', $parts[0], $str);
48e9c1 162             $str = str_replace('%n', $parts[0], $str);
T 163             $str = str_replace('%dc', $dc, $str);
d6938b 164             $str = str_replace('%domain', $parts[1], $str);
AM 165             $str = str_replace('%d', $parts[1], $str);
166         }
48e9c1 167
d6938b 168         return $str;
48e9c1 169     }
T 170
171     /**
172      * Code originaly from the phpLDAPadmin development team
173      * http://phpldapadmin.sourceforge.net/
174      *
175      * Hashes a password and returns the hash based on the specified enc_type
176      */
177     function hash_password($password_clear, $encodage_type)
178     {
d6938b 179         $encodage_type = strtolower($encodage_type);
AM 180         switch ($encodage_type) {
181         case 'crypt':
182             $crypted_password = '{CRYPT}' . crypt($password_clear, $this->random_salt(2));
183             break;
184         case 'ext_des':
185             /* Extended DES crypt. see OpenBSD crypt man page */
186             if (!defined('CRYPT_EXT_DES') || CRYPT_EXT_DES == 0) {
187                 /* Your system crypt library does not support extended DES encryption */
188                 return false;
189             }
190             $crypted_password = '{CRYPT}' . crypt($password_clear, '_' . $this->random_salt(8));
191             break;
192         case 'md5crypt':
193             if (!defined('CRYPT_MD5') || CRYPT_MD5 == 0) {
194                 /* Your system crypt library does not support md5crypt encryption */
195                 return false;
196             }
197             $crypted_password = '{CRYPT}' . crypt($password_clear, '$1$' . $this->random_salt(9));
198             break;
199         case 'blowfish':
200             if (!defined('CRYPT_BLOWFISH') || CRYPT_BLOWFISH == 0) {
201                 /* Your system crypt library does not support blowfish encryption */
202                 return false;
203             }
204             /* Hardcoded to second blowfish version and set number of rounds */
205             $crypted_password = '{CRYPT}' . crypt($password_clear, '$2a$12$' . $this->random_salt(13));
206             break;
207         case 'md5':
208             $crypted_password = '{MD5}' . base64_encode(pack('H*', md5($password_clear)));
209             break;
210         case 'sha':
211             if (function_exists('sha1')) {
212                 /* Use PHP 4.3.0+ sha1 function, if it is available */
213                 $crypted_password = '{SHA}' . base64_encode(pack('H*', sha1($password_clear)));
214             } else if (function_exists('mhash')) {
215                 $crypted_password = '{SHA}' . base64_encode(mhash(MHASH_SHA1, $password_clear));
216             } else {
217                 /* Your PHP install does not have the mhash() function */
218                 return false;
219             }
220             break;
221         case 'ssha':
222             if (function_exists('mhash') && function_exists('mhash_keygen_s2k')) {
223                 mt_srand((double) microtime() * 1000000 );
224                 $salt = mhash_keygen_s2k(MHASH_SHA1, $password_clear, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
225                 $crypted_password = '{SSHA}' . base64_encode(mhash(MHASH_SHA1, $password_clear . $salt) . $salt);
226             } else {
227                 /* Your PHP install does not have the mhash() function */
228                 return false;
229             }
230             break;
231         case 'smd5':
232             if (function_exists('mhash') && function_exists('mhash_keygen_s2k')) {
233                 mt_srand((double) microtime() * 1000000 );
234                 $salt = mhash_keygen_s2k(MHASH_MD5, $password_clear, substr(pack('h*', md5(mt_rand())), 0, 8), 4);
235                 $crypted_password = '{SMD5}' . base64_encode(mhash(MHASH_MD5, $password_clear . $salt) . $salt);
236             } else {
237                 /* Your PHP install does not have the mhash() function */
238                 return false;
239             }
240             break;
48e9c1 241         case 'samba':
T 242             if (function_exists('hash')) {
61be82 243                 $crypted_password = hash('md4', rcube_charset::convert($password_clear, RCUBE_CHARSET, 'UTF-16LE'));
48e9c1 244                 $crypted_password = strtoupper($crypted_password);
T 245             } else {
d6938b 246                 /* Your PHP install does not have the hash() function */
AM 247                 return false;
48e9c1 248             }
T 249             break;
d6938b 250         case 'clear':
AM 251         default:
252             $crypted_password = $password_clear;
253         }
48e9c1 254
d6938b 255         return $crypted_password;
48e9c1 256     }
T 257
258     /**
259      * Code originaly from the phpLDAPadmin development team
260      * http://phpldapadmin.sourceforge.net/
261      *
262      * Used to generate a random salt for crypt-style passwords
263      */
264     function random_salt($length)
265     {
d6938b 266         $possible = '0123456789' . 'abcdefghijklmnopqrstuvwxyz' . 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . './';
AM 267         $str = '';
268         // mt_srand((double)microtime() * 1000000);
48e9c1 269
d6938b 270         while (strlen($str) < $length) {
AM 271             $str .= substr($possible, (rand() % strlen($possible)), 1);
272         }
48e9c1 273
d6938b 274         return $str;
48e9c1 275     }
T 276 }