Give this a try.  Let me know.  My comments are prefaced by (js).  There
are hardcoded paths in place.  It works for MD5 passwords which is what I
use in the ldap database.

If it doesn't work turn debug on, $debug = 1 in the file


-- 
Joe Sotham
------------
If the only prayer you say in your entire life is "Thank You",
that will suffice.
        - Meister Eckhart
<?php
// Change_ldappass 1.3 (29th October 2002)
//  
// Copyright Simon Annetts 2000 - 2002. 
// 

// (js) Turn debug on in an attempt to resolve problems, set $debug = 0; when working
//
   $debug = 0;
    
// (js) because I am running apache v2 + php 4.2.3 I need to specify absolute pathnames
//      that's my understanding of the problem anyways
   define("SM_PATH", "/usr/local/share/squirrelmail.rc2a/");

// (js) Don't know how this plugin worked without this include file, 
//      maybe previous versions did something magical?
   include_once (SM_PATH . "include/validate.php");

   chdir ("..");
   session_start();

      include_once (SM_PATH . "config/config.php");
      include_once (SM_PATH . "functions/strings.php");
      include_once (SM_PATH . "functions/page_header.php");
      include_once (SM_PATH . "functions/imap.php");

   if ($_POST['plugin_change_ldappass'] != null) $Messages = change_ldappass_check();
 
    include_once (SM_PATH . "include/load_prefs.php");
   displayPageHeader($color, "None");

?>

<br>
<table width=95% align=center cellpadding=2 cellspacing=2 border=0>
<tr><td bgcolor="<?php echo $color[0] ?>">
   <center><b>Change Password</b></center>
</td><?php

if (isset($Messages) && count($Messages)) {
    echo "<tr><td>\n";
    foreach ($Messages as $line) {
        echo $line . "<br>\n";
    }
    echo "</td></tr>\n";
}

?><tr><td>
    <form method=post action="<?php print $_SERVER['PHP_SELF'] ?>">
    <table>
      <tr>
        <th align=right>Old Password:</th>
        <td><input type=password name=cp_oldpass value=""  size=20></td>
      </tr>
      <tr>
        <th align=right>New Password:</th>
        <td><input type=password name=cp_newpass value="" size=20></td>
      </tr>
      <tr>
        <th align=right>Verify New Password:</th>
        <td><input type=password name=cp_verify value="" size=20></td>
      </tr>
      <tr>
        <td align=center colspan=2><input type=submit value="Submit" name="plugin_change_ldappass"></td>
      </tr>
    </table>
</td></tr>
</tr></table>
</body></html>
<?php

function change_ldappass_check($debug = 0) {
	$cp_oldpass = $_POST['cp_oldpass'];
	$cp_newpass = $_POST['cp_newpass'];
	$cp_verify = $_POST['cp_verify'];
	$plugin_change_ldappass = $_POST['plugin_change_ldappass'];
	$username = $_SESSION['username'];

	$Messages = array();

	if ($cp_oldpass == "")	array_push($Messages, 'You must type in your old password.');
	if ($cp_newpass == "") array_push($Messages, 'You must type in a new password.');
	if ($cp_verify == "") array_push($Messages,'You must also type in your new password in the verify box.');
	if ($cp_newpass != "" && $cp_verify != $cp_newpass) array_push($Messages,'Your new password doesn\'t match the verify password.');
	if (!preg_match("/^[A-Za-z0-9\�\%\^\*\(\)\-\_\+\=\[\]\{\}\:[EMAIL PROTECTED],\.\? ]+$/",$cp_newpass)) array_push($Messages,'Passwords can only contain the following characters:<br>A-Z, a-z, 0-9, �%^*()-_+=[]{}:@#~,.?');
	if (count($Messages)) return $Messages;
	return change_ldappass_go($debug);
}


function change_ldappass_go($debug) {
		$username = $_SESSION['username'];
		$base_uri = $_SESSION['base_uri'];
		$cp_oldpass = $_POST['cp_oldpass'];
		$cp_newpass = $_POST['cp_newpass'];

		include_once(SM_PATH . "plugins/change_ldappass/config.php");
    $Messages = array();
    if ($debug) array_push($Messages, "Connecting to LDAP Server");

 		$ds=ldap_connect($ldap_server);
 		if (! $ds) {
			array_push($Messages,'Can\'t connect to Directory Server, please try later!');
	 		return $Messages;
 		}
		// first bind anonymously and try to determine the correct dn for the user with uid=$username
		$r=ldap_bind($ds,"","");
		$sr=ldap_search($ds,$ldap_base_dn,"($ldap_user_field=$username)",array("dn")); //search for uid
		if (ldap_count_entries($ds,$sr)>1) {
			array_push($Messages,'Duplicate login entries detected, cannot change password!');
			return $Messages;
		}
		if (ldap_count_entries($ds,$sr)==0) {
			array_push($Messages,'Your login account was not found in the LDAP database, cannot change password!');
			return $Messages;
		}
		$info=ldap_get_entries($ds,$sr);
		if ($debug) array_push($Messages,ldap_debug_print_array($info));
		$dn=$info[0]["dn"]; //finally get the full users dn
		
		// now rebind the the database as user or as root if required
		if (! $ldap_bind_as_manager) {
			if (! @ldap_bind($ds,$dn,$cp_oldpass)) { //if we can't bind as the user then the old passwd must be wrong
				array_push($Messages,'Your old password is not correct.');
				return $Messages;
			} else {
				if ($debug) array_push($Messages,"Bind to database as $dn successful.");	
			}
		} else {
			if (! @ldap_bind($ds,$ldap_manager_dn,$ldap_manager_pw)) {
				array_push($Messages,"Could not bind to the LDAP server as $ldap_manager_dn. Is the password correct?");
				return $Messages;
			} else {
				if ($debug) array_push($Messages,"Bind to database as $ldap_manager_dn successful.");	
			}				
		}
		$sr=ldap_search($ds,$ldap_base_dn,"($ldap_user_field=$username)"); //check the db again, this time so we get the password field returned
		$info = ldap_get_entries($ds, $sr);
		if ($debug) array_push($Messages,ldap_debug_print_array($info));
		$storedpass=$info[0][$ldap_password_field][0];
		
		//this next code tries to identify the correct password type
		//*Note* I've not tested all types here as I cannot reproduce some setups
		//Please let me know if the code does not work for you, or if you have code that will work
		//for a particular type.
		
		//password types:
		//{crypt} salted passwords of type DES, MD5 and Blowfish
		//{MD5} unsalted password in MD5 format
		//{SHA} unsalted password in SHA format
		
		//lets try to determine the encrytion method of the stored password
		//
		$p=split("}",$storedpass); 					//split the password
		$ctype=strtolower($p[0]);                                       //into the {crypttype}
		$lpass=$p[1];							//and the password
		//if the stored password is {crypt} then its salted, but which sub-type?
		//$ctype="{md5";
		switch ($ctype) {
			case "{crypt":
				$pl=strlen($lpass);	// We'll look at the length and salt
							// of what's already stored to determine the crypt sub-type
				$stype="DES";		// sensible default if not detected
				if ($pl>=34 and substr($lpass,0,3)=="$1$") $stype="MD5"; 
				if ($pl>=34 and substr($lpass,0,3)=="$2$") $stype="BLOWFISH";	
				if ($debug) array_push($Messages, "Password type is {crypt}, sub-type $stype");
				$cpass=ldap_crypt_passwd($cp_oldpass,$lpass,$stype); //crypt up our old password so we can check it again
				break;
			case "{md5":
				if ($debug) array_push($Messages, "Password type is {MD5}");
				$cpass=ldap_md5_passwd($cp_oldpass);
				break;
			case "{sha":
				if ($debug) array_push($Messages, "Password type is {SHA}");
				$cpass=ldap_sha_passwd($cp_oldpass);
				break;
      default:                        // Use plain text password
        $cpass=$cp_oldpass;
        $lpass=$storedpass;     // Override $lpass as it is truncated from the original
        break;
		}	
		//now check again the stored password against the encrypted version of the supplied old password
		if ($lpass != $cpass) {
			array_push($Messages,"Your old password is not correct.");
			if ($debug) array_push($Messages,"Stored Password: $lpass  Old Password: $cpass");
			return $Messages;
		}
		//Make sure the new passwd generation uses the encryption method of the previous password
		switch ($ctype) {
			case "{crypt":		
				$newpass="{crypt}".ldap_crypt_passwd($cp_newpass,ldap_generate_salt($stype),$stype);
				break;
			case "{md5":
				$newpass="{MD5}".ldap_md5_passwd($cp_newpass);
				break;
			case "{sha":
				$newpass="{SHA}".ldap_sha_passwd($cp_newpass);
				break;			
			// more password types should go here ;-) and the functions to drive them below.
	    default:                        // Use plain text password
      	$newpass=$cp_newpass;
        break;
		}
		if ($debug) array_push($Messages,"New Password: $newpass");
		$newinfo=array();
		$newinfo[$ldap_password_field][0]=$newpass;
		if (@ldap_modify($ds,$dn,$newinfo)) {
			$smb=0;
			if ($change_smb) {
				$exe="echo \"$cp_oldpass\" |$smb_passwd -U $username -s \"$cp_newpass\" 2>&1";
				if ($debug) array_push($Messages,$exe);
				$r=exec($exe,$s);
				if ($r=="Password changed for user $username") $smb=1;
			} else {
				$smb=1;
			}
			if ($debug) array_push($Messages, $r);			
			if ($smb) {
				// Write new cookies for the password
				$onetimepad = OneTimePadCreate(strlen($cp_newpass));
				$_SESSION['onetimepad']=$onetimepad; //do I need to do this now?
				$key = OneTimePadEncrypt($cp_newpass, $onetimepad);
				$_COOKIES['key']=$key;
				setcookie("key", $key, 0, $base_uri);
				// Automatically forward back to the options screen if correct
				if ($debug == 0) {
					header("Location: $base_uri/src/options.php?plugin_change_ldappass=true");
					exit(0);
				}
				return $Messages;
			} else { //smbpasswd change failed so we must re sync the ldap password back to its original
				$newinfo[$ldap_password_field][0]=$storedpass;
				$r=ldap_modify($ds,$dn,$newinfo);  //change it back so  they're not out of sync.
				array_push($Messages,'SMB Password change was not successful, so LDAP not changed!');
				return $Messages;
			}
		} else {
			array_push($Messages,'LDAP Password change was not successful!');
			return $Messages;
		}
		@ldap_close($ds);
}

// Generate an unsalted SHA1 pw. This should work with Netscape messaging / directory server 4+ 
function ldap_sha_password($clear_pw) {
	return base64_encode(mHash(MHASH_SHA1, $clear_pw));
}

// Generate an unsalted MD5 pw. This works fine with OpenLDAP.
function ldap_md5_passwd($clear_pw) {
	return base64_encode(pack("H*", md5($clear_pw)));	
}

// Generate salted crypt passwords
function ldap_crypt_passwd($password,$salt,$stype) {
	if ($stype=="MD5") 	return crypt($password,substr($salt,0,12)); 						//MD5 uses 12 chr salt
	if ($stype=="BLOWFISH") 	return crypt($password,substr($salt,0,16)); //BLOWFISH uses 16 chr salt
	if ($stype=="DES")  return crypt($password,substr($salt,0,2)); 							//crypt uses 2 chr salt
}

function ldap_generate_salt($stype) {
  $salt="";	//generate a salt using characters [A-Z][a-z][0-9]./
  $chars="./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
	for ($i=0;$i<16;$i++) {
		$salt.=$chars[mt_rand(0,strlen($chars))];
	}
	//return salts longer than we need but these will be trimmed in the ldap_crypt_passwd function
	if ($stype=="MD5") return "$1$".$salt;
	if ($stype=="BLOWFISH") return "$2$".$salt;
	if ($stype=="DES")	return $salt;
}

function ldap_debug_print_array($array) {
	$out="<br>--------------------------------------------------------<br>\n";
  $out.=ldap_debug_print_array1($array,"");
	$out.= "<br>--------------------------------------------------------<br>\n";
	return $out;
}

function ldap_debug_print_array1($array,$out) {
	if(gettype($array)=="array") {
		$out.="<ul>";
		while (list($index, $subarray) = each($array) ) {
			$out.="<li>$index <code>=&gt;</code>";
			$out=ldap_debug_print_array1($subarray,$out);
			$out.="</li>";
		}
		$out.="</ul>";
	} else $out.="$array";
	return $out;
}
?>

Reply via email to