<?php
/**
 * Copyright (C) 2008 Krzysztof Kozlowski
 * http://www.kozik.net.pl
 * License: GNU General Public License version 2
 *          http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 *
 * Zmieniaczka haseł w LDAP-ie.
 *
 */

// Download createntlm.inc from LDAP Account Manager:
// http://lam.cvs.sourceforge.net/lam/lam/lib/createntlm.inc?view=log
// It is GPL-v2 licensed.
require_once('createntlm.inc') ;

/**
 * Example:

require_once('labo_password_changer.php') ;
$lp = new LaboPasswordChanger(LDAP_URL, LDAP_HAVE_TLS, LDAP_REQUIRE_TLS, LDAP_DOMAIN, MYSQL_HOST) ;
try
{
    // Zabezpieczenie przed ewentualnym LDAP Injection,
    // choć login używany jest tylko w ldap_bind() i ldap_modify().
    // Klasa sama (jeszcze) nie przeprowadza walidacji danych względem zapytań LDAP-u.
    if (!preg_match('/^[a-zA-Z0-9]+$/', $login))
        throw new Exception('Niedozwolone znaki!') ;
    if ($lp->ldap_change_password($login, $password, $new_password_1))
        echo 'Hasło w LDAP zmieniono. Jeśli masz pod ręką piwo, to jest do dobry momemt na napicie się z adminem.' ;
    else
        echo 'Nastąpił błąd przy zmianie hasła w LDAP.' ;
}
catch (Exception $e)
{
    echo 'LaboPasswordChanger zwrócił błąd: ' . $e->getMessage() ;
}
try
{
    if ($lp->mysql_change_password($login, $password, $new_password_1))
        echo 'Hasło w MySQL zmieniono. Jeśli masz pod ręką piwo, to jest do dobry momemt na napicie się z adminem.' ;
    else
        echo 'Nastąpił błąd przy zmianie hasła w MySQL. Jeśli nie posiadałeś konta w MySQL, to tak naprawdę to wcale nie jest błąd i się tym nie przejmuj tylko idź na piwo.' ;
}
catch (Exception $e)
{
    echo 'LaboPasswordChanger zwrócił błąd:' . $e->getMessage() ;
}

 *
 * Throws excepion Exception().
 */
class LaboPasswordChanger
{
    const 
VERSION '0.2.3' ;
    const 
DATE '18.06.2008' ;
    private 
$ldap NULL ;
    private 
$ldap_url '' ;
    private 
$ldap_have_tls TRUE ;
    private 
$ldap_require_tls FALSE ;
    private 
$ldap_domain '' ;
    private 
$mysql_host '' ;

    
/**
     * @param    $ldap_url        string; LDAP url (e.g. ldap://localhost:389, ldaps://localhost:636)
     * @param    $ldap_have_tls        boolean; Server accepts STARTTLS?
     * @param    $ldap_require_tls    boolean; If server accepts STARTTLS, require it (will fail with plaintext connection)
     * @param    $ldap_domain        string; LDAP domain suffix with user accounts (e.g. ou=People,dc=ldap,dc=com)
     * @param    $mysql_host        string; MySQL host (e.g. localhost)
     */
    
function __construct($ldap_url$ldap_have_tls$ldap_require_tls$ldap_domain$mysql_host)
    {
        
$this->ldap_url $ldap_url ;
        
$this->ldap_have_tls $ldap_have_tls ;
        
$this->ldap_require_tls $ldap_require_tls ;
        
$this->ldap_domain $ldap_domain ;
        
$this->mysql_host $mysql_host ;
    }

    
/**
     * @param    $username        string; username
     * @param    $old_password    string; old password
     * @param    $new_password    string; new password
     * @return    TRUE
     */
    
public function mysql_change_password($username$old_password$new_password)
    {
        
$mysql = @mysql_connect($this->mysql_host$username$old_password) or $this->throw_error("Unexpected MySQL error: Could not connect to " $this->mysql_host " (err=" mysql_error() . ").") ;
        
$sql "SET PASSWORD = PASSWORD( '" addslashes($new_password) . "' )" ;
        @
mysql_query($sql$mysql) or $this->throw_error("Unexpected MySQL error: Could not change password (err=" mysql_error() . ").") ;
        @
mysql_close($mysql) ;
        return 
TRUE ;
    }

    
/**
     * Sets up connection to LDAP.
     */
    
private function ldap_connect()
    {
        if (
$this->ldap)
            
$this->ldap_close() ;
        
$ldap = @ldap_connect($this->ldap_url) or $this->throw_error("Unexpected LDAP error: Could not connect to " $this->ldap_url ".") ;
        @
ldap_set_option($ldapLDAP_OPT_PROTOCOL_VERSION3) ;
        @
ldap_set_option($ldapLDAP_OPT_REFERRALS0) ;
        if (
$this->ldap_have_tls)
        {
            
// We have TLS
            
if (!ldap_start_tls($ldap))
            {
                
// Could not start TLS session, so we reset connection to PLAIN TEXT
                // or quit if user checked "ldap_require_tls"
                
!$this->ldap_require_tls or $this->throw_error("Unexpected LDAP error: Could not start TLS session (err=" ldap_error($ldap) . ")") ;
                
$ldap = @ldap_connect($this->ldap_url) or $this->throw_error("Unexpected LDAP error: Could not connect to " $this->ldap_url ".") ;
                @
ldap_set_option($ldapLDAP_OPT_PROTOCOL_VERSION3) ;
                @
ldap_set_option($ldapLDAP_OPT_REFERRALS0) ;
                
// We have PLAIN TEXT connection
            
}
        }
        
$this->ldap $ldap ;
    }

    
/**
     * Change LDAP password.
     *
     * @param    $username        string; username
     * @param    $old_password    string; old password
     * @param    $new_password    string; new password
     * @return    boolean
     */
    
public function ldap_change_password($username$old_password$new_password)
    {
        
$this->ldap_connect() ;
        if (
$this->ldap)
        {
            
// TODO: LDAP Injection? Validate $username
            
$user_dn "uid=" $username "," $this->ldap_domain ;
            @
ldap_bind($this->ldap$user_dn$old_password) or $this->throw_error("Unexpected LDAP error: Could not bind to $user_dn (err=" ldap_error($this->ldap) . ").") ;
            
// Bind was successful so we can change password:
            
$new_data = array(
                
'sambaNTPassword' => $this->nt_password($new_password),
                
'sambaLMPassword' => $this->lm_password($new_password),
                
'sambaPwdLastSet' => time(),
                
'userPassword'    => $this->ssha_password($new_password),
                ) ;
            
// TODO: Change also users attribute "user must change password"
            
@ldap_modify($this->ldap$user_dn$new_data) or $this->throw_error("Unexpected LDAP  error: Could not change password (err=" ldap_error($this->ldap) . ").") ;
            
$this->ldap_close() ;
            return 
TRUE ;
        }
        
$this->ldap_close() ;
        return 
FALSE ;
    }

    
/**
     * SSHA hash of password
     * @param    $password    string
     * @return    string
     */
    
public function ssha_password($password)
    {
        
// Code from phpldapadmin (licensed under GPLv2):
        // http://phpldapadmin.sourceforge.net/
        // $Header: /cvsroot/phpldapadmin/phpldapadmin/lib/functions.php,v 1.303.2.26 2008/01/30 11:17:00 wurley Exp $
        
if( function_exists'mhash' ) && function_exists'mhash_keygen_s2k' ) )
        {
            
mt_srand( (double) microtime() * 1000000 ) ;
            
$salt mhash_keygen_s2kMHASH_SHA1$passwordsubstrpack"h*"md5mt_rand() ) ), 0), ) ;
            return 
"{SSHA}".base64_encodemhashMHASH_SHA1$password.$salt ).$salt ) ;
        }
        
// Code^
        
else
        {
            
$this->throw_error("Unexpected error: Your PHP install does not have the mhash() function. Cannot do SHA hashes.") ;
        }
        return 
'' ;
    }

    
/**
     * Close connection
     */
    
private function ldap_close()
    {
        if (
$this->ldap)
            @
ldap_close($this->ldap) ;
        
$this->ldap NULL ;
    }

    
/**
     * LM hash
     *
     * @param    $password    string
     * @return    string
     */
    
public function lm_password($password)
    {
        
$hash = new smbHash();
        return @
$hash->lmhash($password);
    }

    
/**
     * NT hash
     *
     * @param    $password    string
     * @return    string
     */
    
public function nt_password($password)
    {
        
$hash = new smbHash();
        return @
$hash->nthash($password);
    }

    
/**
     * Throw new exception with error message
     */
    
private function throw_error($s)
    {
        
$this->ldap_close() ;
        throw new 
Exception ($s) ;
    }
}
?>