<?php

$block_name = _("Account Information");

/**
 * $Horde: horde/lib/Block/account.php,v 1.7.2.7 2007/02/02 11:03:22 jan Exp $
 *
 * Copyright 2001-2007 Eric Rostetter <eric.rostetter@physics.utexas.edu>
 * Copyright 2005-2007 Jan Schneider <jan@horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @since   Horde 3.1
 * @package Horde_Block
 */
class Horde_Block_Horde_account extends Horde_Block {

    var $_app = 'horde';

    /**
     * The title to go in this block.
     *
     * @return string   The title text.
     */
    function _title()
    {
        return _("My Account Information");
    }

    /**
     * The content to go in this block.
     *
     * @return string   The content
     */
    function _content()
    {
        global $registry, $conf;

        switch ($conf['accounts']['driver']) {
        case 'null':
            $mydriver = &new Accounts_Driver();
            break;
        case 'localhost':
        case 'ldap':
        case 'kolab':
            $class = 'Accounts_Driver_' . $conf['accounts']['driver'];
            $mydriver = &new $class($conf['accounts']['params']);
            break;
        default:
            return '';
        }

        // Check for password status.
        if (is_a($status = $mydriver->checkPasswordStatus(), 'PEAR_Error')) {
            $status = $status->getMessage();
        }

        if (is_a($username = $mydriver->getUsername(), 'PEAR_Error')) {
            return $username->getMessage();
        }
        $table = array(_("Login") => $username);
        if (is_a($fullname = $mydriver->getFullname(), 'PEAR_Error')) {
            return $fullname->getMessage();
        } elseif ($fullname) {
            $table[_("Full Name")] = $fullname;
        }
        if (is_a($home = $mydriver->getHome(), 'PEAR_Error')) {
            return $home->getMessage();
        } elseif ($home) {
            $table[_("Home Directory")] = $home;
        }
        if (is_a($shell = $mydriver->getShell(), 'PEAR_Error')) {
            return $shell->getMessage();
        } elseif ($shell) {
            $table[_("Default Shell")] = $shell;
        }
        if (is_a($quota = $mydriver->getQuota(), 'PEAR_Error')) {
            return $quota->getMessage();
        } elseif ($quota) {
            $table[_("Quota")] = sprintf(_("%.2fMB used of %.2fMB allowed (%.2f%%)"),
                                         $quota['used'] / ( 1024 * 1024.0),
                                         $quota['limit'] / ( 1024 * 1024.0),
                                         ($quota['used'] * 100.0) / $quota['limit']);
        }
        if (is_a($lastchange = $mydriver->getPasswordChange(), 'PEAR_Error')) {
            return $lastchange->getMessage();
        } elseif ($lastchange) {
            $table[_("Last Password Change")] = $lastchange;
        }

        $output = '<table class="item" width="100%" cellspacing="1">';

        if (!empty($status)) {
            $output .= '<tr><td colspan="2"><p class="notice">' .
                Horde::img('alerts/warning.png', _("Warning")) .
                '&nbsp;&nbsp;' . $status . '</p></td></tr>';
        }

        foreach ($table as $key => $value) {
            $output .= "<tr class=\"text\"><td>$key</td><td>$value</td></tr>\n";
        }
        $output .= "</table>\n";

        if ($registry->get('status', 'forwards') != 'inactive' &&
            $registry->hasMethod('summary', 'forwards')) {
            $summary = $registry->callByPackage('forwards', 'summary');
            if (!is_a($summary, 'PEAR_Error')) {
                $output .= '<br />' . $summary . "\n";
            }
        }
        if ($registry->get('status', 'vacation') != 'inactive' &&
            $registry->hasMethod('summary', 'vacation')) {
            $summary = $registry->callByPackage('vacation', 'summary');
            if (!is_a($summary, 'PEAR_Error')) {
                $output .= '<br />' . $summary . "\n";
            }
        }

        return $output;
    }

}

/**
 * Accounts_Driver:: defines an API for getting/displaying account information
 * for a user for the accounts module.
 *
 * Copyright 2001-2007 Eric Rostetter <eric.rostetter@physics.utexas.edu>
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Eric Rostetter <eric.rostetter@physics.utexas.edu>
 * @since   Horde 3.1
 * @package Horde_Block
 */
class Accounts_Driver {

    /**
     * Error string returned to user if an error occurs.
     *
     * @var string
     */
    var $err_str;

    /**
     * Returns the username.
     *
     * @return string  The lowercased username.
     *
     */
    function getUsername()
    {
        return String::lower(Auth::getBareAuth());
    }

    /**
     * Returns the user realm.
     *
     * @return string  The user realm.
     */
    function getRealm()
    {
        return (string)Auth::getAuthDomain();
    }

    /**
     * Returns the user's quota if available.
     *
     * @return mixed  A quota array, elements are used bytes and limit bytes on
     *                success, false on failure.
     */
    function getQuota()
    {
        return false;
    }

    /**
     * Returns the user's full name.
     *
     * @return mixed  The user's full name (string), or false (error).
     */
    function getFullname()
    {
        return false;
    }

    /**
     * Returns the user's home (login) directory.
     *
     * @return mixed  The user's directory (string), or false (error).
     */
    function getHome()
    {
        return false;
    }

    /**
     * Returns the user's default shell.
     *
     * @return mixed  The user's shell (string), or false (error).
     */
    function getShell()
    {
        return false;
    }

    /**
     * Returns the date of the user's last password change.
     *
     * @return mixed  Date string (string) or false (error).
     */
    function getPasswordChange()
    {
        return false;
    }

    /**
     * Returns the status of the current password.
     *
     * @return mixed  A string with a warning message if the password is about
     *                to expire, PEAR_Error on error and false otherwise.
     */
    function checkPasswordStatus()
    {
        return false;
    }

}

/**
 * Implements the Accounts API for servers with unix accounts on the localhost
 * machine (same machine as the web server).  Should work for local unix
 * accounts, nis/nis+ accounts, or any PAM oriented accounts that appear as
 * local accounts on the local machine.  The exception is the quota support.
 * See that routine for additional comments.
 *
 * Copyright 2002-2007 Eric Jon Rostetter <eric.rostetter@physics.utexas.edu>
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Eric Rostetter <eric.rosttter@physics.utexas.edu>
 * @since   Horde 3.1
 * @package Horde_Block
 */
class Accounts_Driver_localhost extends Accounts_Driver {

    /**
     * Hash containing connection parameters.
     *
     * @var array
     */
    var $params = array();

    /**
     * Constructor.
     *
     * @param array $params  Hash containing connection parameters.
     */
    function Accounts_Driver_localhost($params = array())
    {
        $this->params = $params;
        if (!isset($this->params['quota_path'])) {
            $this->params['quota_path'] = 'quota';
        }
        if (!isset($this->params['grep_path'])) {
            $this->params['grep_path'] = 'grep';
        }
    }

    /**
     * Returns the user account from the posix information.
     *
     * @return array  A hash with complete account details.
     */
    function _getAccount()
    {
        static $information;

        if (!isset($information)) {
            // this won't work if we don't have posix extensions
            if (!Util::extensionExists('posix')) {
                return PEAR::raiseError(_("POSIX extension is missing"));
            }

            $user = String::lower($this->getUsername());
            $information = posix_getpwnam($user);
        }

        return $information;
    }

    /**
     * Implement the Quota API for IMAP servers with a unix quota command.
     * This may require a modified "quota" command that allows the httpd
     * server account to get quotas for other users...  It requires that your
     * web server and user server be the same server or at least have shared
     * authentication and file servers (e.g. via NIS/NFS).  And last, it (as
     * written) requires the posix php extensions.
     *
     * If your quota command wraps the output onto two lines, then this module
     * will only work if you have a grep which supports the -A switch, and you
     * append an -A1 switch to your grep_path (e.g. '/bin/grep -A1').
     *
     * @return mixed  The quota hash (bytes used, limit) or false for error.
     */
    function getQuota()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }

        $homedir = $information['dir'];

        // If we want mount point translations, then translate the login dir
        // name to a mount point.  If not, then simply parse out the device
        // name from the login directory, and use that instead.
        if ($this->params['translateMountPoint'] &&
            file_exists($this->params['translationTable']))
        {
            require_once 'File/Fstab.php';
            $sysTab = File_Fstab::singleton($this->params['translationTable']);
            do {
               $entry = &$sysTab->getEntryForPath($homedir);
               $homedir = dirname($homedir);
               if ($homedir == '.' || empty($homedir)) {
                   $homedir = '/';
               }
            } while (is_a($entry, 'PEAR_Error'));
            $mountPoint = $entry->device;
        } else {
           $homedir = explode('/', $homedir);
           $mountPoint = '/' . $homedir[1];
        }

        $cmdline = sprintf('%s -u %s 2>&1 | %s %s',
                           $this->params['quota_path'],
                           $this->getUserName(),
                           $this->params['grep_path'],
                           $mountPoint);
        $junk = exec($cmdline, $quota_data, $return_code);
        if ($return_code == 0 && !empty($quota_data[0])) {
           // In case of quota output wrapping on two lines, we concat the
           // second line of results, if any, here.
           if (!empty($quota_data[1])) {
               $quota_data[0] .= $quota_data[1];
           }
           // Now parse out the quota info and return it.
           $quota = preg_split('/\s+/', trim($quota_data[0]));
           return array('used' => $quota[1] * 1024, 'limit' => $quota[2] * 1024);
        }
        return false;
    }

    /**
     * Returns the user's full name.
     *
     * @return mixed  The user's full name (string), or false (error).
     */
    function getFullname()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }
        $gecos_array = explode(',', $information['gecos']);
        return $gecos_array[0];
    }

    /**
     * Returns the user's home (login) directory.
     *
     * @return mixed  The user's directory (string), or false (error).
     */
    function getHome()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }
        return $information['dir'];
    }

    /**
     * Returns the user's default shell.
     *
     * @return mixed  The user's shell (string), or false (error).
     */
    function getShell()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }
        return $information['shell'];
    }

}

/**
 * The ldap class attempts to return user information stored in an ldap
 * directory service.
 *
 * Copyright 2001-2007 Eric Rostetter <eric.rostetter@physics.utexas.edu>
 *
 * See the enclosed file COPYING for license information (LGPL). If you did
 * not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Eric Jon Rostetter <eric.rostetter@physics.utexas.edu>
 * @since   Horde 3.1
 * @package Horde_Block
 */
class Accounts_Driver_ldap extends Accounts_Driver {

    /**
     * Pointer to the ldap connection.
     *
     * @var resource
     */
    var $_ds;

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

    /**
     * Constructs a new Accounts_Driver_ldap object.
     *
     * @param array $params  A hash containing connection parameters.
     */
    function Accounts_Driver_ldap($params = array())
    {
        $this->_params = array_merge(
            array('host' => 'localhost',
                  'port' => 389,
                  'basedn' => '',
                  'attr' => 'uid',
                  'version' => '3'),
            $params);
    }


    /**
     * A wrapper method for anonymous binding.
     *
     * @return boolean  False if binding failed.
     */
    function _ldapBind()
    {
        return @ldap_bind($this->_ds);
    }

    /**
     * Returns the user account from the ldap source.
     *
     * @return array  A hash with complete account details.
     */
    function _getAccount()
    {
        static $information;

        if (!isset($information)) {
            $username  = $this->getUsername();

            // connect to the ldap server
            $this->_ds = ldap_connect($this->_params['host'],
                                      $this->_params['port']);
            if (!$this->_ds) {
                return PEAR::raiseError(_("Could not connect to LDAP server."));
            }
            if (isset($this->_params['version'])) {
                if (!ldap_set_option($this->_ds, LDAP_OPT_PROTOCOL_VERSION,
                                     $this->_params['version'])) {
                    Horde::logMessage(sprintf('Set LDAP protocol version to %d failed: [%d] %s',
                                              $this->_params['version'],
                                              ldap_errno($conn),
                                              ldap_error($conn)),
                                              __FILE__, __LINE__);
                }
            }

            // bind as anonymous
            $result = $this->_ldapBind();
            if (!$result) {
                return PEAR::raiseError(_("Could not bind to LDAP server."));
            }

            // Get the fullname
            $searchResult = ldap_search($this->_ds, $this->_params['basedn'],
                                        $this->_params['attr'] . '=' . $username);
            $information = ldap_get_entries($this->_ds, $searchResult);

            // disconnect from the ldap server
            @ldap_close($this->_ds);

            if ($information['count'] == 0) {
                return PEAR::raiseError(_("User account not found"));
            }
        }

        return $information;
    }

    /**
     * Returns the username. Since this method is called for authenticating
     * in the parent class, we also have the option to NOT strip the domain
     * name if there is one. This is a configuration checkbox.
     *
     * @return string  The username.
     */
    function getUsername()
    {
        return $this->_params['strip'] ? Auth::getBareAuth() : Auth::getAuth();
    }

    /**
     * Returns the user's full name.
     *
     * @return mixed  The user's full name (string), or false (error).
     */
    function getFullname()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }

        if (isset($information[0]['cn;lang-es'][0]) &&
            $information[0]['cn;lang-es'][0] != '') {
            $name = $information[0]['cn;lang-es'][0];
        } else {
            $name = $information[0]['cn'][0];
        }

        return (empty($name) ? false : $name);
    }

    /**
     * Returns the user's home (login) directory.
     *
     * @return mixed  The user's directory (string), or false (error).
     */
    function getHome()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }
        $homedir = $information[0]['homedirectory'][0];
        return (empty($homedir) ? false : $homedir);
    }

    /**
     * Returns the user's default shell.
     *
     * @return mixed  The user's shell (string), or false (error).
     */
    function getShell()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }
        $shell = $information[0]['loginshell'][0];
        return (empty($shell) ? false : $shell);
    }

    /**
     * Returns the date of the user's last password change.
     *
     * @return mixed  Date string (string) or false (error).
     */
    function getPasswordChange()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }

        if (isset($information[0]['shadowlastchange'][0])) {
            $lastchange = strftime('%x', $information[0]['shadowlastchange'][0] * 86400);
        }

        return (empty($lastchange) ? false : $lastchange);
    }

    /**
     * Returns the status of the current password.
     *
     * @return mixed  A string with a warning message if the password is about
     *                to expire, PEAR_Error on error and false otherwise.
     */
    function checkPasswordStatus()
    {
        $information = $this->_getAccount();
        if (is_a($information, 'PEAR_Error')) {
            return $information;
        }

        if (isset($information[0]['shadowmax'][0]) &&
            isset($information[0]['shadowlastchange'][0]) &&
            isset($information[0]['shadowwarning'][0])) {
            $today = floor(time() / 86400);
            $warnday = $information[0]['shadowlastchange'][0] +
                       $information[0]['shadowmax'][0] - $information[0]['shadowwarning'][0];
            $toexpire = $information[0]['shadowlastchange'][0] + $information[0]['shadowmax'][0] - $today;

            if ($today >= $warnday) {
                return sprintf(_("%d days until your password expires."), $toexpire);
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

}

/**
 * The kolab driver class is merely an extension to the ldap class. It reuses
 * existing Kolab configuration instead of requiring another set of
 * parameters. It also binds using a set internal user (normally this is
 * cn=nobody,cn=internal), so that account information can be fetched even for
 * internal accounts.
 *
 * @author mzizka@hotmail.com
 */
class Accounts_Driver_kolab extends Accounts_Driver_ldap {

    /**
     * Constructs a new Accounts_Driver_kolab object. Uses Kolab configuration.
     *
     * @param array $params  A hash containing connection parameters.
     */
    function Accounts_Driver_kolab($params = array())
    {
        $ldap_params = array('host'   => $GLOBALS['conf']['kolab']['ldap']['server'],
                             'port'   => $GLOBALS['conf']['kolab']['ldap']['port'],
                             'basedn' => $GLOBALS['conf']['kolab']['ldap']['basedn'],
                             'phpdn'  => $GLOBALS['conf']['kolab']['ldap']['phpdn'],
                             'phppw'  => $GLOBALS['conf']['kolab']['ldap']['phppw']);
        $params = array_merge($ldap_params, $params);
        parent::Accounts_Driver_ldap($params);
    }

    /**
     * Overrides the binding method so it uses the Kolab user instead.
     * Typically this will be the internal nobody user.
     *
     * @return boolean  False if binding failed.
     */
    function _ldapBind()
    {
        if (!empty($this->_params['phpdn'])) {
           return @ldap_bind($this->_ds, $this->_params['phpdn'], $this->_params['phppw']);
        }
        return parent::_ldapBind();
    }

}
