Thomas Bruederli
2013-06-20 07c6c69eca8751c0e96a846afb30c24ab2638b1f
commit | author | age
48e9c1 1 <?php
T 2
3 /**
4  * LDAP Password Driver
5  *
6  * Driver for passwords stored in LDAP
7  * This driver use the PEAR Net_LDAP2 class (http://pear.php.net/package/Net_LDAP2).
8  *
9  * @version 2.0
10  * @author Edouard MOREAU <edouard.moreau@ensma.fr>
11  *
12  * method hashPassword based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
13  * method randomSalt based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
14  *
15  */
16
17 class rcube_ldap_password
18 {
19     public function save($curpass, $passwd)
20     {
21         $rcmail = rcmail::get_instance();
22         require_once 'Net/LDAP2.php';
23
24         // Building user DN
25         if ($userDN = $rcmail->config->get('password_ldap_userDN_mask')) {
26             $userDN = $this->substitute_vars($userDN);
27         } else {
28             $userDN = $this->search_userdn($rcmail);
29         }
30
31         if (empty($userDN)) {
32             return PASSWORD_CONNECT_ERROR;
33         }
34
35         // Connection Method
36         switch($rcmail->config->get('password_ldap_method')) {
37             case 'admin':
38                 $binddn = $rcmail->config->get('password_ldap_adminDN');
39                 $bindpw = $rcmail->config->get('password_ldap_adminPW');
40                 break;
41             case 'user':
42             default:
43                 $binddn = $userDN;
44                 $bindpw = $curpass;
45                 break;
46         }
47
48         // Configuration array
49         $ldapConfig = array (
50             'binddn'    => $binddn,
51             'bindpw'    => $bindpw,
52             'basedn'    => $rcmail->config->get('password_ldap_basedn'),
53             'host'      => $rcmail->config->get('password_ldap_host'),
54             'port'      => $rcmail->config->get('password_ldap_port'),
55             'starttls'  => $rcmail->config->get('password_ldap_starttls'),
56             'version'   => $rcmail->config->get('password_ldap_version'),
57         );
58
59         // Connecting using the configuration array
60         $ldap = Net_LDAP2::connect($ldapConfig);
61
62         // Checking for connection error
63         if (PEAR::isError($ldap)) {
64             return PASSWORD_CONNECT_ERROR;
65         }
66
67         $crypted_pass = $this->hashPassword($passwd, $rcmail->config->get('password_ldap_encodage'));
68         $force        = $rcmail->config->get('password_ldap_force_replace');
69         $pwattr       = $rcmail->config->get('password_ldap_pwattr');
70         $lchattr      = $rcmail->config->get('password_ldap_lchattr');
71         $smbpwattr    = $rcmail->config->get('password_ldap_samba_pwattr');
72         $smblchattr   = $rcmail->config->get('password_ldap_samba_lchattr');
73         $samba        = $rcmail->config->get('password_ldap_samba');
74
75         // Support password_ldap_samba option for backward compat.
76         if ($samba && !$smbpwattr) {
77             $smbpwattr  = 'sambaNTPassword';
78             $smblchattr = 'sambaPwdLastSet';
79         }
80
81         // Crypt new password
82         if (!$crypted_pass) {
83             return PASSWORD_CRYPT_ERROR;
84         }
85
86         // Crypt new samba password
87         if ($smbpwattr && !($samba_pass = $this->hashPassword($passwd, 'samba'))) {
d6938b 88             return PASSWORD_CRYPT_ERROR;
48e9c1 89         }
T 90
91         // Writing new crypted password to LDAP
92         $userEntry = $ldap->getEntry($userDN);
93         if (Net_LDAP2::isError($userEntry)) {
94             return PASSWORD_CONNECT_ERROR;
95         }
96
97         if (!$userEntry->replace(array($pwattr => $crypted_pass), $force)) {
98             return PASSWORD_CONNECT_ERROR;
99         }
100
101         // Updating PasswordLastChange Attribute if desired
102         if ($lchattr) {
103             $current_day = (int)(time() / 86400);
104             if (!$userEntry->replace(array($lchattr => $current_day), $force)) {
105                 return PASSWORD_CONNECT_ERROR;
106             }
107         }
108
109         // Update Samba password and last change fields
110         if ($smbpwattr) {
111             $userEntry->replace(array($smbpwattr => $samba_pass), $force);
112         }
113         // Update Samba password last change field
114         if ($smblchattr) {
115             $userEntry->replace(array($smblchattr => time()), $force);
116         }
117
118         if (Net_LDAP2::isError($userEntry->update())) {
119             return PASSWORD_CONNECT_ERROR;
120         }
121
122         // All done, no error
123         return PASSWORD_SUCCESS;
124     }
125
126     /**
127      * Bind with searchDN and searchPW and search for the user's DN.
128      * Use search_base and search_filter defined in config file.
129      * Return the found DN.
130      */
131     function search_userdn($rcmail)
132     {
133         $ldapConfig = array (
134             'binddn'    => $rcmail->config->get('password_ldap_searchDN'),
135             'bindpw'    => $rcmail->config->get('password_ldap_searchPW'),
136             'basedn'    => $rcmail->config->get('password_ldap_basedn'),
137             'host'      => $rcmail->config->get('password_ldap_host'),
138             'port'      => $rcmail->config->get('password_ldap_port'),
139             'starttls'  => $rcmail->config->get('password_ldap_starttls'),
140             'version'   => $rcmail->config->get('password_ldap_version'),
141         );
142
143         $ldap = Net_LDAP2::connect($ldapConfig);
144
145         if (PEAR::isError($ldap)) {
146             return '';
147         }
148
149         $base = $rcmail->config->get('password_ldap_search_base');
150         $filter = $this->substitute_vars($rcmail->config->get('password_ldap_search_filter'));
151         $options = array (
152             'scope' => 'sub',
153             'attributes' => array(),
154         );
155
156         $result = $ldap->search($base, $filter, $options);
157         $ldap->done();
158         if (PEAR::isError($result) || ($result->count() != 1)) {
159             return '';
160         }
161
162         return $result->current()->dn();
163     }
164
165     /**
166      * Substitute %login, %name, %domain, %dc in $str.
167      * See plugin config for details.
168      */
169     function substitute_vars($str)
170     {
171         $rcmail = rcmail::get_instance();
172         $domain = $rcmail->user->get_username('domain');
173         $dc     = 'dc='.strtr($domain, array('.' => ',dc=')); // hierarchal domain string
174
175         $str = str_replace(array(
176                 '%login',
177                 '%name',
178                 '%domain',
179                 '%dc',
180             ), array(
181                 $_SESSION['username'],
182                 $rcmail->user->get_username('local'),
183                 $domain,
184                 $dc,
185             ), $str
186         );
187
188         return $str;
189     }
190
191     /**
192      * Code originaly from the phpLDAPadmin development team
193      * http://phpldapadmin.sourceforge.net/
194      *
195      * Hashes a password and returns the hash based on the specified enc_type.
196      *
197      * @param string $passwordClear The password to hash in clear text.
198      * @param string $encodageType Standard LDAP encryption type which must be one of
199      *        crypt, ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, or clear.
200      * @return string The hashed password.
201      *
202      */
203     function hashPassword( $passwordClear, $encodageType )
204     {
205         $encodageType = strtolower( $encodageType );
206         switch( $encodageType ) {
207         case 'crypt':
208             $cryptedPassword = '{CRYPT}' . crypt($passwordClear, $this->randomSalt(2));
209             break;
210
211         case 'ext_des':
212             // extended des crypt. see OpenBSD crypt man page.
213             if ( ! defined( 'CRYPT_EXT_DES' ) || CRYPT_EXT_DES == 0 ) {
214                 // Your system crypt library does not support extended DES encryption.
215                 return FALSE;
216             }
217             $cryptedPassword = '{CRYPT}' . crypt( $passwordClear, '_' . $this->randomSalt(8) );
218             break;
219
220         case 'md5crypt':
221             if( ! defined( 'CRYPT_MD5' ) || CRYPT_MD5 == 0 ) {
222                 // Your system crypt library does not support md5crypt encryption.
223                 return FALSE;
224             }
225             $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$1$' . $this->randomSalt(9) );
226             break;
227
228         case 'blowfish':
229             if( ! defined( 'CRYPT_BLOWFISH' ) || CRYPT_BLOWFISH == 0 ) {
230                 // Your system crypt library does not support blowfish encryption.
231                 return FALSE;
232             }
233             // hardcoded to second blowfish version and set number of rounds
234             $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$2a$12$' . $this->randomSalt(13) );
235             break;
236
237         case 'md5':
238             $cryptedPassword = '{MD5}' . base64_encode( pack( 'H*' , md5( $passwordClear) ) );
239             break;
240
241         case 'sha':
242             if( function_exists('sha1') ) {
243                 // use php 4.3.0+ sha1 function, if it is available.
244                 $cryptedPassword = '{SHA}' . base64_encode( pack( 'H*' , sha1( $passwordClear) ) );
245             } elseif( function_exists( 'mhash' ) ) {
246                 $cryptedPassword = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $passwordClear) );
247             } else {
248                 return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
249             }
250             break;
251
252         case 'ssha':
253             if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
254                 mt_srand( (double) microtime() * 1000000 );
255                 $salt = mhash_keygen_s2k( MHASH_SHA1, $passwordClear, substr( pack( 'h*', md5( mt_rand() ) ), 0, 8 ), 4 );
256                 $cryptedPassword = '{SSHA}'.base64_encode( mhash( MHASH_SHA1, $passwordClear.$salt ).$salt );
257             } else {
258                 return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
259             }
260             break;
261
262         case 'smd5':
263             if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
264                 mt_srand( (double) microtime() * 1000000 );
265                 $salt = mhash_keygen_s2k( MHASH_MD5, $passwordClear, substr( pack( 'h*', md5( mt_rand() ) ), 0, 8 ), 4 );
266                 $cryptedPassword = '{SMD5}'.base64_encode( mhash( MHASH_MD5, $passwordClear.$salt ).$salt );
267             } else {
268                 return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
269             }
270             break;
271
272         case 'samba':
273             if (function_exists('hash')) {
61be82 274                 $cryptedPassword = hash('md4', rcube_charset::convert($passwordClear, RCUBE_CHARSET, 'UTF-16LE'));
48e9c1 275                 $cryptedPassword = strtoupper($cryptedPassword);
T 276             } else {
d6938b 277                 /* Your PHP install does not have the hash() function */
AM 278                 return false;
48e9c1 279             }
T 280             break;
281
282         case 'clear':
283         default:
284             $cryptedPassword = $passwordClear;
285         }
286
287         return $cryptedPassword;
288     }
289
290     /**
291      * Code originaly from the phpLDAPadmin development team
292      * http://phpldapadmin.sourceforge.net/
293      *
294      * Used to generate a random salt for crypt-style passwords. Salt strings are used
295      * to make pre-built hash cracking dictionaries difficult to use as the hash algorithm uses
296      * not only the user's password but also a randomly generated string. The string is
297      * stored as the first N characters of the hash for reference of hashing algorithms later.
298      *
299      * --- added 20021125 by bayu irawan <bayuir@divnet.telkom.co.id> ---
300      * --- ammended 20030625 by S C Rigler <srigler@houston.rr.com> ---
301      *
302      * @param int $length The length of the salt string to generate.
303      * @return string The generated salt string.
304      */
305     function randomSalt( $length )
306     {
307         $possible = '0123456789'.
308             'abcdefghijklmnopqrstuvwxyz'.
309             'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
310             './';
311         $str = '';
312         // mt_srand((double)microtime() * 1000000);
313
314         while (strlen($str) < $length)
315             $str .= substr($possible, (rand() % strlen($possible)), 1);
316
317         return $str;
318     }
319 }