6 * Driver for passwords stored in LDAP
7 * This driver use the PEAR Net_LDAP2 class (http://pear.php.net/package/Net_LDAP2).
9 * @version 1.1 (2010-04-07)
10 * @author Edouard MOREAU <edouard.moreau@ensma.fr>
12 * function hashPassword based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
13 * function randomSalt based on code from the phpLDAPadmin development team (http://phpldapadmin.sourceforge.net/).
17 function password_save($curpass, $passwd)
19 $rcmail = rcmail::get_instance();
20 require_once ('Net/LDAP2.php');
23 if ($userDN = $rcmail->config->get('password_ldap_userDN_mask')) {
24 $userDN = substitute_vars($userDN);
26 $userDN = search_userdn($rcmail);
30 return PASSWORD_CONNECT_ERROR;
34 switch($rcmail->config->get('password_ldap_method')) {
36 $binddn = $rcmail->config->get('password_ldap_adminDN');
37 $bindpw = $rcmail->config->get('password_ldap_adminPW');
46 // Configuration array
50 'basedn' => $rcmail->config->get('password_ldap_basedn'),
51 'host' => $rcmail->config->get('password_ldap_host'),
52 'port' => $rcmail->config->get('password_ldap_port'),
53 'starttls' => $rcmail->config->get('password_ldap_starttls'),
54 'version' => $rcmail->config->get('password_ldap_version'),
57 // Connecting using the configuration array
58 $ldap = Net_LDAP2::connect($ldapConfig);
60 // Checking for connection error
61 if (PEAR::isError($ldap)) {
62 return PASSWORD_CONNECT_ERROR;
65 // Crypting new password
66 $newCryptedPassword = hashPassword($passwd, $rcmail->config->get('password_ldap_encodage'));
67 if (!$newCryptedPassword) {
68 return PASSWORD_CRYPT_ERROR;
71 // Writing new crypted password to LDAP
72 $userEntry = $ldap->getEntry($userDN);
73 if (Net_LDAP2::isError($userEntry)) {
74 return PASSWORD_CONNECT_ERROR;
77 $pwattr = $rcmail->config->get('password_ldap_pwattr');
78 $force = $rcmail->config->get('password_ldap_force_replace');
80 if (!$userEntry->replace(array($pwattr => $newCryptedPassword), $force)) {
81 return PASSWORD_CONNECT_ERROR;
84 // Updating PasswordLastChange Attribute if desired
85 if ($lchattr = $rcmail->config->get('password_ldap_lchattr')) {
86 $current_day = (int)(time() / 86400);
87 if (!$userEntry->replace(array($lchattr => $current_day), $force)) {
88 return PASSWORD_CONNECT_ERROR;
92 if (Net_LDAP2::isError($userEntry->update())) {
93 return PASSWORD_CONNECT_ERROR;
96 // Update Samba password fields, ignore errors if attributes are not found
97 if ($rcmail->config->get('password_ldap_samba')) {
98 $sambaNTPassword = hash('md4', rcube_charset_convert($passwd, RCMAIL_CHARSET, 'UTF-16LE'));
99 $userEntry->replace(array('sambaNTPassword' => $sambaNTPassword), $force);
100 $userEntry->replace(array('sambaPwdLastSet' => time()), $force);
101 $userEntry->update();
104 // All done, no error
105 return PASSWORD_SUCCESS;
109 * Bind with searchDN and searchPW and search for the user's DN.
110 * Use search_base and search_filter defined in config file.
111 * Return the found DN.
113 function search_userdn($rcmail)
115 $ldapConfig = array (
116 'binddn' => $rcmail->config->get('password_ldap_searchDN'),
117 'bindpw' => $rcmail->config->get('password_ldap_searchPW'),
118 'basedn' => $rcmail->config->get('password_ldap_basedn'),
119 'host' => $rcmail->config->get('password_ldap_host'),
120 'port' => $rcmail->config->get('password_ldap_port'),
121 'starttls' => $rcmail->config->get('password_ldap_starttls'),
122 'version' => $rcmail->config->get('password_ldap_version'),
125 $ldap = Net_LDAP2::connect($ldapConfig);
127 if (PEAR::isError($ldap)) {
131 $base = $rcmail->config->get('password_ldap_search_base');
132 $filter = substitute_vars($rcmail->config->get('password_ldap_search_filter'));
135 'attributes' => array(),
138 $result = $ldap->search($base, $filter, $options);
140 if (PEAR::isError($result) || ($result->count() != 1)) {
144 return $result->current()->dn();
148 * Substitute %login, %name, %domain, %dc in $str.
149 * See plugin config for details.
151 function substitute_vars($str)
153 $rcmail = rcmail::get_instance();
154 $domain = $rcmail->user->get_username('domain');
155 $dc = 'dc='.strtr($domain, array('.' => ',dc=')); // hierarchal domain string
157 $str = str_replace(array(
163 $_SESSION['username'],
164 $rcmail->user->get_username('local'),
175 * Code originaly from the phpLDAPadmin development team
176 * http://phpldapadmin.sourceforge.net/
178 * Hashes a password and returns the hash based on the specified enc_type.
180 * @param string $passwordClear The password to hash in clear text.
181 * @param string $encodageType Standard LDAP encryption type which must be one of
182 * crypt, ext_des, md5crypt, blowfish, md5, sha, smd5, ssha, or clear.
183 * @return string The hashed password.
187 function hashPassword( $passwordClear, $encodageType )
189 $encodageType = strtolower( $encodageType );
190 switch( $encodageType ) {
192 $cryptedPassword = '{CRYPT}' . crypt($passwordClear,randomSalt(2));
196 // extended des crypt. see OpenBSD crypt man page.
197 if ( ! defined( 'CRYPT_EXT_DES' ) || CRYPT_EXT_DES == 0 ) {
198 // Your system crypt library does not support extended DES encryption.
201 $cryptedPassword = '{CRYPT}' . crypt( $passwordClear, '_' . randomSalt(8) );
205 if( ! defined( 'CRYPT_MD5' ) || CRYPT_MD5 == 0 ) {
206 // Your system crypt library does not support md5crypt encryption.
209 $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$1$' . randomSalt(9) );
213 if( ! defined( 'CRYPT_BLOWFISH' ) || CRYPT_BLOWFISH == 0 ) {
214 // Your system crypt library does not support blowfish encryption.
217 // hardcoded to second blowfish version and set number of rounds
218 $cryptedPassword = '{CRYPT}' . crypt( $passwordClear , '$2a$12$' . randomSalt(13) );
222 $cryptedPassword = '{MD5}' . base64_encode( pack( 'H*' , md5( $passwordClear) ) );
226 if( function_exists('sha1') ) {
227 // use php 4.3.0+ sha1 function, if it is available.
228 $cryptedPassword = '{SHA}' . base64_encode( pack( 'H*' , sha1( $passwordClear) ) );
229 } elseif( function_exists( 'mhash' ) ) {
230 $cryptedPassword = '{SHA}' . base64_encode( mhash( MHASH_SHA1, $passwordClear) );
232 return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
237 if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
238 mt_srand( (double) microtime() * 1000000 );
239 $salt = mhash_keygen_s2k( MHASH_SHA1, $passwordClear, substr( pack( 'h*', md5( mt_rand() ) ), 0, 8 ), 4 );
240 $cryptedPassword = '{SSHA}'.base64_encode( mhash( MHASH_SHA1, $passwordClear.$salt ).$salt );
242 return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
247 if( function_exists( 'mhash' ) && function_exists( 'mhash_keygen_s2k' ) ) {
248 mt_srand( (double) microtime() * 1000000 );
249 $salt = mhash_keygen_s2k( MHASH_MD5, $passwordClear, substr( pack( 'h*', md5( mt_rand() ) ), 0, 8 ), 4 );
250 $cryptedPassword = '{SMD5}'.base64_encode( mhash( MHASH_MD5, $passwordClear.$salt ).$salt );
252 return FALSE; //Your PHP install does not have the mhash() function. Cannot do SHA hashes.
258 $cryptedPassword = $passwordClear;
261 return $cryptedPassword;
265 * Code originaly from the phpLDAPadmin development team
266 * http://phpldapadmin.sourceforge.net/
268 * Used to generate a random salt for crypt-style passwords. Salt strings are used
269 * to make pre-built hash cracking dictionaries difficult to use as the hash algorithm uses
270 * not only the user's password but also a randomly generated string. The string is
271 * stored as the first N characters of the hash for reference of hashing algorithms later.
273 * --- added 20021125 by bayu irawan <bayuir@divnet.telkom.co.id> ---
274 * --- ammended 20030625 by S C Rigler <srigler@houston.rr.com> ---
276 * @param int $length The length of the salt string to generate.
277 * @return string The generated salt string.
279 function randomSalt( $length )
281 $possible = '0123456789'.
282 'abcdefghijklmnopqrstuvwxyz'.
283 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.
286 // mt_srand((double)microtime() * 1000000);
288 while (strlen($str) < $length)
289 $str .= substr($possible, (rand() % strlen($possible)), 1);