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