<?php

/**
 * @package Horde_Kolab
 *
 * $Horde: framework/Kolab/Kolab/LDAP.php,v 1.3.2.5 2008/06/07 16:07:24 chuck Exp $
 */

/** We need LDAP for the Kolab_LDAP class **/
require_once 'Horde/LDAP.php';

/**
 * The Horde_Kolab_LDAP class is a wrapper for a Kolab LDAP server and
 * provides utilities to rewrite a Kolab user alias into the primary mail
 * identifier.
 *
 * $Horde: framework/Kolab/Kolab/LDAP.php,v 1.3.2.5 2008/06/07 16:07:24 chuck Exp $
 *
 * Copyright 2007-2008 The Horde Project (http://www.horde.org/)
 *
 * @author  Steffen Hansen <steffen@klaralvdalens-datakonsult.se>
 * @package Horde_Kolab
 */
class Horde_Kolab_LDAP {

    /**
     * LDAP connection object.
     *
     * @var resource
     */
    var $connection;

    /**
     * Flag that indicates bound state for the LDAP connection.
     *
     * @var boolean
     */
    var $is_bound;

    /**
     * Current bind dn.
     *
     * @var string
     */
    var $bind_dn;

    /**
     * Stores a LDAP search result.
     *
     * @var array
     */
    var $search_result;

    /**
     * Constructs a new Horde_Kolab_LDAP object.
     */
    function Horde_Kolab_LDAP()
    {
        $this->is_bound = false;
        $this->bind_dn = false;
        $this->search_result = false;

        $server = $GLOBALS['conf']['kolab']['ldap']['server'];

        $this->connection = ldap_connect($server);
        // We really neeed v3!
        if (!ldap_set_option($this->connection, LDAP_OPT_PROTOCOL_VERSION, 3)) {
            return PEAR::raiseError(sprintf(_("Error setting LDAP protocol on server %s to v3!"),
                                            $server));
        }
    }

    /**
     * Binds the LDAP connection with a specific user and pass.
     *
     * @param string $dn  DN to bind with
     * @param string $pw  Password associated to this DN.
     *
     * @return boolean  Whether or not the binding succeeded.
     */
    function bind($dn = false, $pw = '')
    {
        if (!$dn) {
            // Default ldap auth
            $dn = $GLOBALS['conf']['kolab']['ldap']['phpdn'];
            $pw = $GLOBALS['conf']['kolab']['ldap']['phppw'];
        }

        $this->is_bound = ldap_bind($this->connection, $dn, $pw);

        if ($this->is_bound) {
            $this->bind_dn = $dn;
        } else {
            $this->bind_dn = false;
            return PEAR::raiseError(sprintf(_("Unable to bind to the LDAP server as %s!"),
                                            $GLOBALS['conf']['kolab']['ldap']['phpdn']));
        }

        return $this->is_bound;
    }

    /**
     * Closes the LDAP connection.
     */
    function close()
    {
        if ($this->search_result) {
            ldap_free_result($this->search_result);
        }
        ldap_close($this->connection);
        $this->connection = $this->is_bound = false;
    }

    /**
     * Clears the search result.
     */
    function freeSearchResult()
    {
        if ($this->search_result) {
            ldap_free_result($this->search_result);
            $this->search_result = false;
        }
    }

    /**
     * Returns the first search result.
     *
     * @return array  The first entry of the current search result.
     */
    function firstEntry()
    {
        return ldap_first_entry($this->connection, $this->search_result);
    }

    /**
     * Performs an LDAP search operation.
     *
     * @param string $base    Base DN to search
     * @param string $filter  Search filter to apply.
     * @param array $attrs    Attributes to return.
     *
     * @return array  The search result.
     */
    function search($base, $filter, $attrs = false)
    {
        $this->freeSearchResult();
        if ($attrs) {
            $this->search_result = ldap_search($this->connection, $base, $filter, $attrs);
        } else {
            $this->search_result = ldap_search($this->connection, $base, $filter);
        }
        return $this->search_result;
    }

    /**
     * Tries to find a DN for a given kolab mail address.
     *
     * @param string $mail  The mail address to search for.
     *
     * @return string  The corresponding dn or false.
     */
    function dnForMail($mail)
    {
        if ($this->search($GLOBALS['conf']['kolab']['ldap']['basedn'],
                          '(&(objectclass=kolabInetOrgPerson)(mail=' . Horde_LDAP::quote($mail) . '))')) {
            $entry = $this->firstEntry();
            if ($entry) {
                return ldap_get_dn($this->connection, $entry);
            }
        } else {
            return PEAR::raiseError(sprintf(_("Error searching for user with the email address \"%s\"!"),
                                            $mail));
        }
        return false;
    }

    /**
     * Tries to find a DN for a given kolab UID.
     *
     * @param string $mail  The UID to search for.
     *
     * @return string  The corresponding dn or false.
     */
    function dnForUid($uid)
    {
        if ($this->search($GLOBALS['conf']['kolab']['ldap']['basedn'],
                          '(&(objectclass=kolabInetOrgPerson)(uid=' . Horde_LDAP::quote($uid) . '))')) {
            $entry = $this->firstEntry();
            if ($entry) {
                return ldap_get_dn($this->connection, $entry);
            }
        } else {
            return PEAR::raiseError(sprintf(_("Error searching for user with the ID \"%s\"!"),
                                            $uid));
        }
        return false;
    }

    /**
     * Returns a mail address for a given DN.
     *
     * @param string $dn  The DN of the user.
     *
     * @return string  The corresponding mail address or false.
     */
    function mailForDn($dn)
    {
        $result = ldap_read($this->connection, $dn, '(objectclass=*)', array('mail'));
        if ($result) {
            $entries = ldap_get_entries($this->connection, $result);
            ldap_free_result($result);

            if ($entries[0]['count'] == 1) {
                return $entries[0]['mail'][0];
            } else {
                return PEAR::raiseError(sprintf(_("No such object %s!"), $dn));
            }
        } else {
            return PEAR::raiseError(sprintf(_("LDAP Error searching for DN %s: %s!"),
                                            $dn,
                                            ldap_error($this->connection)));
        }
    }

    /**
     * Return a hash of info about a user
     *
     * @param string $uid  The UID of the user.
     *
     * @return string  The hash containing the user information.
     */
    function userInfo($uid)
    {
        $result = ldap_search($this->connection,
                              $GLOBALS['conf']['kolab']['ldap']['basedn'],
                              '(&(objectClass=kolabInetOrgPerson)(|(uid='.
                              Horde_LDAP::quote($uid) . ')(mail=' .
                              Horde_LDAP::quote($uid) . ')(alias=' .
                              Horde_LDAP::quote($uid) . ')))',
                              array('dn', 'cn', 'mail', 'uid', 'kolabHomeServer',
                                    'kolabFreeBusyFuture'));
        if ($result) {
            $entries = ldap_get_entries($this->connection, $result);
            if($entries['count'] > 0 && !empty($entries[0]['mail'][0])) {
                $hash = array();
                $hash['DN']         = $this->readLdapAttr($entries[0], 'dn');
                $hash['CN']         = $this->readLdapAttr($entries[0], 'cn');
                $hash['UID']        = $this->readLdapAttr($entries[0], 'uid');
                $hash['MAIL']       = $this->readLdapAttr($entries[0], 'mail',
                                                          $uid);
                $hash['HOMESERVER'] = $this->readLdapAttr($entries[0],
                                                          'kolabhomeserver');
                $hash['FBFUTURE']   = (int)($this->readLdapAttr($entries[0],
                                                                'kolabfreebusyfuture',
                                                                60));
                $hash['GROUPS']     = $this->getGroups($hash['DN']);
                ldap_free_result( $result );
                return $hash;
            }
            ldap_free_result($result);
        } else {
            return PEAR::raiseError(sprintf(_("Error searching for user with the uid \"%s\"!"),
                                            $uid));
        }
        return false;
    }

    /**
     * Read an LDAP attribute
     *
     * @param string $entry    The LDAP search result
     * @param string $attrname The attribute to read.
     * @param mixed  $default  A possible default value.
     *
     * @return mixed The attribute value or the default if the
     *               attribute was not found or the attribute
     *               was empty.
     */
    function readLdapAttr($entry, $attrname, $default = false ) {
        $val = $default;
        if (!array_key_exists($attrname, $entry)) {
            return $default;
        } else if (is_array($entry[$attrname])) {
            $val = $entry[$attrname][0];
        } else {
            $val = $entry[$attrname];
        }
        if($val == '') {
            return $default;
        }
        return $val;
    }

    function freeBusyPast() {
        $result = ldap_search($this->connection,
                              $GLOBALS['conf']['kolab']['ldap']['basedn'],
                              '(&(objectClass=kolab)(k=kolab))',
                              array('kolabFreeBusyPast'));
        if ($result) {
            $entries = ldap_get_entries($this->connection, $result);
            if ($entries['count'] > 0 &&
                !empty($entries[0]['kolabfreebusypast'][0])) {
                ldap_free_result($result);
                return $entries[0]['kolabfreebusypast'][0];
            }
        }
        // Default
        return 0;
    }

    function getGroups($dn) {
        $result = ldap_search($this->connection,
                              $GLOBALS['conf']['kolab']['ldap']['basedn'],
                              '(&(objectClass=kolabGroupOfNames)(member=' .
                              Horde_LDAP::quote($dn) . '))', array('mail'));
        if ($result) {
            $entries = ldap_get_entries($this->connection, $result);
            $lst = array();
            for ($i = 0; $i < $entries['count']; $i++) {
                $lst[] = $entries[$i]['mail'][0];
            }
            return $lst;
        }
        return array();
    }

    /**
     * Attempts to return a reference to a concrete Horde_Kolab_LDAP
     * instance. It will only create a new instance if no
     * Horde_Kolab_LDAP instance currently exists.
     *
     * This method must be invoked as:
     *   <code>$var = &Horde_Kolab_LDAP::singleton();</code>
     *
     * @static
     *
     * @return Horde_Kolab_LDAP The concrete Horde_Kolab_LDAP
     *                          reference, or false on an error.
     */
    function &singleton()
    {
        static $ldap = null;

        if (empty($ldap)) {
            $ldap = new Horde_Kolab_LDAP();
        }

        return $ldap;
    }

}
