<?php
/**
 * $Horde: passwd/lib/Driver/smbpasswd.php,v 1.15 2003/07/24 21:07:32 max Exp $
 *
 * The smbpassd class attempts to change a user's password on a
 * samba server.
 *
 * @author   Rene Lund Jensen <Rene@lundjensen.net>
 * @package  passwd
 */

class Passwd_Driver_smbpasswd extends Passwd_Driver {

    /** Pointer to the socket connection. */
    var $_fp;

    /** Hash containing connection parameters. */
    var $_params;

    /**
     * Constructs a new smbpasswd Passwd_Driver object.
     *
     * @param array  $params    A hash containing connection parameters.
     */
    function Passwd_Driver_smbpasswd($params = array())
    {
        $this->_params['host'] = array_key_exists('host', $params)
             ? $params['host']
             : 'localhost';
        $this->_params['program'] = array_key_exists('program', $params)
             ? $params['program']
             : '/usr/bin/smbpasswd';
    }

    /**
     * Connect a pipe to the sambaserver using the smbpasswd program
     *
     * @params string $user     The user to change the password for
     * @params string $tmpfile  The name of a tempfile in which to write output
     * @return                  True on success, fatal error on failure
     */
    function _connect($user, $tmpfile)
    {
        if (!is_executable($this->_params['program'])) {
            return PEAR::raiseError(_("Password module is not properly configured"));
        }

        $cmd = $this->_params['program'] . ' -r ' . $this->_params['host'] .
               " -s -U \"$user\" &> $tmpfile";
        $this->_fp = popen($cmd, 'w');
        if ( !$this->_fp) {
            return PEAR::raiseError(_("Could not open pipe to smbpasswd."));
        }
        return true;
    }

    /**
     * Disconnect the pipe
     */
    function _disconnect()
    {
        if (isset($this->_fp)) {
            pclose($this->_fp);
        }
    }

    /**
     * Send a string to the waiting sambaserver
     *
     * @param  $cmd    The string to send to the server.
     */
    function _sendCommand($cmd)
    {
        if (fputs($this->_fp, $cmd . "\n") == -1) {
            return PEAR::raiseError(_("Error sending data to smbpasswd."));
        }
        sleep(1);
        return true;
    }

    /**
     * Change the user's password.
     *
     * @param   $username      The user for which to change the password.
     * @param   $old_password  The old (current) user password.
     * @param   $new_password  The new user password to set.
     *
     * @return  boolean        True or false based on success of the change.
     */
    function changePassword($username,  $old_password, $new_password)
    {
        $conf = &$GLOBALS['conf'];

        $res = true;

        // clean up user name in case evil characters are in it
        $user = escapeshellcmd($username);

        $tmpfile = tempnam(@$conf['tmpdir'], 'smbpasswd.');
        if ($tmpfile == false) {
            return PEAR::raiseError(_("tempnam() failed!"));
        }

        $res = $this->_connect($user, $tmpfile);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        } else {
            $res = file($tmpfile);
            if (count($res) != 0) {
                $res = trim($res[count($res)-1]);
            } else {
                $res = true;
            }
        }

        $res = $this->_sendCommand($old_password);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $res = $this->_sendCommand($new_password);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $res = $this->_sendCommand($new_password);
        if (is_a($res, 'PEAR_Error')) {
            return $res;
        }

        $res = $this->_disconnect();
        $res = file($tmpfile);

        if (strstr($res[count($res)-1], "Password changed for user") === false) {
            $res = PEAR::raiseError(strrchr(trim($res[count($res)-2]), ":"));
        } else {
            $this->resetCredentials($username, $old_password, $new_password);
            $res = true;
        }

        // Remove the temporary file after use.
        // Hopefully this will be unnessesary with the new versions of PHP
        // When procopen comes into play ;-)
        unlink($tmpfile);

        return $res;
    }
}
