Auth
[ class tree: Auth ] [ index: Auth ] [ all elements ]

Source for file LDAP.php

Documentation is available at LDAP.php

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 2.02 of the PHP license,      |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available at through the world-wide-web at                           |
  11. // | http://www.php.net/license/2_02.txt.                                 |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Jan Wagner <wagner@netsols.de>                              |
  17. // +----------------------------------------------------------------------+
  18. //
  19. // $Id: LDAP.php,v 1.14 2003/06/02 16:55:10 mj Exp $
  20. //
  21.  
  22. require_once "Auth/Container.php";
  23. require_once "PEAR.php";
  24.  
  25. /**
  26.  * Storage driver for fetching login data from LDAP
  27.  *
  28.  * This class is heavily based on the DB and File containers. By default it
  29.  * connects to localhost:389 and searches for uid=$username with the scope
  30.  * "sub". If no search base is specified, it will try to determine it via
  31.  * the namingContexts attribute. It takes its parameters in a hash, connects
  32.  * to the ldap server, binds anonymously, searches for the user, and tries
  33.  * to bind as the user with the supplied password. When a group was set, it
  34.  * will look for group membership of the authenticated user. If all goes
  35.  * well the authentication was successful.
  36.  *
  37.  * Parameters:
  38.  *
  39.  * host:        localhost (default), ldap.netsols.de or 127.0.0.1
  40.  * port:        389 (default) or 636 or whereever your server runs
  41.  * url:         ldap://localhost:389/
  42.  *              useful for ldaps://, works only with openldap2 ?
  43.  *              it will be preferred over host and port
  44.  * binddn:      If set, searching for user will be done after binding
  45.  *              as this user, if not set the bind will be anonymous.
  46.  *              This is reported to make the container work with MS
  47.  *              Active Directory, but should work with any server that
  48.  *              is configured this way.
  49.  *              This has to be a complete dn for now (basedn and
  50.  *              userdn will not be appended).
  51.  * bindpw:      The password to use for binding with binddn
  52.  * scope:       one, sub (default), or base
  53.  * basedn:      the base dn of your server
  54.  * userdn:      gets prepended to basedn when searching for user
  55.  * userattr:    the user attribute to search for (default: uid)
  56.  * useroc:      objectclass of user (for the search filter)
  57.  *              (default: posixAccount)
  58.  * groupdn:     gets prepended to basedn when searching for group
  59.  * groupattr  : the group attribute to search for (default: cn)
  60.  * groupoc    : objectclass of group (for the search filter)
  61.  *              (default: groupOfUniqueNames)
  62.  * memberattr : the attribute of the group object where the user dn
  63.  *              may be found (default: uniqueMember)
  64.  * memberisdn:  whether the memberattr is the dn of the user (default)
  65.  *              or the value of userattr (usually uid)
  66.  * group:       the name of group to search for
  67.  * debug:       Enable/Disable debugging output (default: false)
  68.  *
  69.  * To use this storage container, you have to use the following syntax:
  70.  *
  71.  * <?php
  72.  * ...
  73.  *
  74.  * $a = new Auth("LDAP", array(
  75.  *       'host' => 'localhost',
  76.  *       'port' => '389',
  77.  *       'basedn' => 'o=netsols,c=de',
  78.  *       'userattr' => 'uid'
  79.  *       'binddn' => 'cn=admin,o=netsols,c=de',
  80.  *       'bindpw' => 'password'));
  81.  *
  82.  * $a2 = new Auth('LDAP', array(
  83.  *       'url' => 'ldaps://ldap.netsols.de',
  84.  *       'basedn' => 'o=netsols,c=de',
  85.  *       'scope' => 'one',
  86.  *       'userdn' => 'ou=People',
  87.  *       'groupdn' => 'ou=Groups',
  88.  *       'groupoc' => 'posixGroup',
  89.  *       'memberattr' => 'memberUid',
  90.  *       'memberisdn' => false,
  91.  *       'group' => 'admin'
  92.  *       ));
  93.  *
  94.  * $a3 = new Auth('LDAP', array(
  95.  *         'host' => 'ad.netsols.de',
  96.  *         'basedn' => 'dc=netsols,dc=de',
  97.  *         'userdn' => 'ou=Users',
  98.  *         'binddn' => 'cn=Jan Wagner,ou=Users,dc=netsols,dc=de',
  99.  *         'bindpw' => '*******',
  100.  *         'userattr' => 'samAccountName',
  101.  *         'useroc' => 'user',
  102.  *          'debug' => true
  103.  *         ));
  104.  *
  105.  * The parameter values have to correspond
  106.  * to the ones for your LDAP server of course.
  107.  *
  108.  * When talking to a Microsoft ActiveDirectory server you have to
  109.  * use 'samaccountname' as the 'userattr' and follow special rules
  110.  * to translate the ActiveDirectory directory names into 'basedn'.
  111.  * The 'basedn' for the default 'Users' folder on an ActiveDirectory
  112.  * server for the ActiveDirectory Domain (which is not related to
  113.  * its DNS name) "win2000.example.org" would be:
  114.  * "CN=Users, DC=win2000, DC=example, DC=org'
  115.  * where every component of the domain name becomes a DC attribute
  116.  * of its own. If you want to use a custom users folder you have to
  117.  * replace "CN=Users" with a sequence of "OU" attributes that specify
  118.  * the path to your custom folder in reverse order.
  119.  * So the ActiveDirectory folder
  120.  *   "win2000.example.org\Custom\Accounts"
  121.  * would become
  122.  *   "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
  123.  *
  124.  * It seems that binding anonymously to an Active Directory
  125.  * is not allowed, so you have to set binddn and bindpw for
  126.  * user searching,
  127.  *
  128.  * Example a3 shows a tested example for connenction to Windows 2000
  129.  * Active Directory
  130.  *
  131.  * @author   Jan Wagner <wagner@netsols.de>
  132.  * @package  Auth
  133.  * @version  $Revision: 1.14 $
  134.  */
  135. {
  136.     /**
  137.      * Options for the class
  138.      * @var array 
  139.      */
  140.     var $options = array();
  141.  
  142.     /**
  143.      * Connection ID of LDAP Link
  144.      * @var string 
  145.      */
  146.     var $conn_id = false;
  147.  
  148.     /**
  149.      * LDAP search function to use
  150.      * @var string 
  151.      */
  152.     var $ldap_search_func;
  153.  
  154.     /**
  155.      * Constructor of the container class
  156.      *
  157.      * @param  $params, associative hash with host,port,basedn and userattr key
  158.      * @return object Returns an error object if something went wrong
  159.      */
  160.     function Auth_Container_LDAP($params)
  161.     {
  162.         $this->_setDefaults();
  163.  
  164.         if (is_array($params)) {
  165.             $this->_parseOptions($params);
  166.         }
  167.     }
  168.  
  169.     // }}}
  170.     // {{{ _connect()
  171.  
  172.     /**
  173.      * Connect to the LDAP server using the global options
  174.      *
  175.      * @access private
  176.      * @return object  Returns a PEAR error object if an error occurs.
  177.      */
  178.     function _connect()
  179.     {
  180.         // connect
  181.         if (isset($this->options['url']&& $this->options['url'!= ''{
  182.             $this->_debug('Connecting with URL'__LINE__);
  183.             $conn_params = array($this->options['url']);
  184.         else {
  185.             $this->_debug('Connecting with host:port'__LINE__);
  186.             $conn_params = array($this->options['host']$this->options['port']);
  187.         }
  188.  
  189.         if(($this->conn_id = @call_user_func_array('ldap_connect'$conn_params)) === false{
  190.             return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.'41PEAR_ERROR_DIE);
  191.         }
  192.         $this->_debug('Successfully connected to server'__LINE__);
  193.  
  194.         // try switchig to LDAPv3
  195.         $ver = 0;
  196.         if(@ldap_get_option($this->conn_idLDAP_OPT_PROTOCOL_VERSION$ver&& $ver >= 2{
  197.             $this->_debug('Switching to LDAPv3'__LINE__);
  198.             @ldap_set_option($this->conn_idLDAP_OPT_PROTOCOL_VERSION3);
  199.         }
  200.  
  201.         // bind with credentials or anonymously
  202.         if($this->options['binddn'&& $this->options['bindpw']{
  203.             $this->_debug('Binding with credentials'__LINE__);
  204.             $bind_params = array($this->conn_id$this->options['binddn']$this->options['bindpw']);
  205.         else {
  206.             $this->_debug('Binding anonymously'__LINE__);
  207.             $bind_params = array($this->conn_id);
  208.         }
  209.         
  210.         // bind for searching
  211.         if ((@call_user_func_array('ldap_bind'$bind_params)) == false{
  212.             $this->_debug();
  213.             $this->_disconnect();
  214.             return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server."41PEAR_ERROR_DIE);
  215.         }
  216.         $this->_debug('Binding was successful'__LINE__);
  217.     }
  218.  
  219.     /**
  220.      * Disconnects (unbinds) from ldap server
  221.      *
  222.      * @access private
  223.      */
  224.     function _disconnect(
  225.     {
  226.         if($this->_isValidLink()) {
  227.             $this->_debug('disconnecting from server');
  228.             @ldap_unbind($this->conn_id);
  229.         }
  230.     }
  231.  
  232.     /**
  233.      * Tries to find Basedn via namingContext Attribute
  234.      *
  235.      * @access private
  236.      */
  237.     function _getBaseDN()
  238.     {
  239.         if ($this->options['basedn'== "" && $this->_isValidLink()) {           
  240.             $this->_debug("basedn not set, searching via namingContexts."__LINE__);
  241.  
  242.             $result_id @ldap_read($this->conn_id"""(objectclass=*)"array("namingContexts"));
  243.             
  244.             if (ldap_count_entries($this->conn_id$result_id== 1{
  245.                 
  246.                 $this->_debug("got result for namingContexts"__LINE__);
  247.                 
  248.                 $entry_id = ldap_first_entry($this->conn_id$result_id);
  249.                 $attrs = ldap_get_attributes($this->conn_id$entry_id);
  250.                 $basedn $attrs['namingContexts'][0];
  251.  
  252.                 if ($basedn != ""{
  253.                     $this->_debug("result for namingContexts was $basedn"__LINE__);
  254.                     $this->options['basedn'$basedn;
  255.                 }
  256.             }
  257.             ldap_free_result($result_id);
  258.         }
  259.  
  260.         // if base ist still not set, raise error
  261.         if ($this->options['basedn'== ""{
  262.             return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!"41PEAR_ERROR_DIE);
  263.         }        
  264.         return true;
  265.     }
  266.  
  267.     /**
  268.      * determines whether there is a valid ldap conenction or not
  269.      *
  270.      * @accessd private
  271.      * @return boolean 
  272.      */
  273.     function _isValidLink(
  274.     {
  275.         if(is_resource($this->conn_id)) {
  276.             if(get_resource_type($this->conn_id== 'ldap link'{
  277.                 return true;
  278.             }
  279.         }
  280.         return false;
  281.     }
  282.  
  283.     /**
  284.      * Set some default options
  285.      *
  286.      * @access private
  287.      */
  288.     function _setDefaults()
  289.     {
  290.         $this->options['host']        'localhost';
  291.         $this->options['port']        '389';
  292.         $this->options['binddn']      '';
  293.         $this->options['bindpw']      '';
  294.         $this->options['scope']       'sub';
  295.         $this->options['basedn']      '';
  296.         $this->options['userdn']      '';
  297.         $this->options['userattr']    "uid";
  298.         $this->options['useroc']      'posixAccount';
  299.         $this->options['groupdn']     '';
  300.         $this->options['groupattr']   'cn';
  301.         $this->options['groupoc']     'groupOfUniqueNames';
  302.         $this->options['memberattr']  'uniqueMember';
  303.         $this->options['memberisdn']  = true;
  304.         $this->options['debug']       = false;
  305.     }
  306.  
  307.     /**
  308.      * Parse options passed to the container class
  309.      *
  310.      * @access private
  311.      * @param  array 
  312.      */
  313.     function _parseOptions($array)
  314.     {
  315.         foreach ($array as $key => $value{
  316.             $this->options[$key$value;
  317.         }
  318.  
  319.         // get the according search function for selected scope
  320.         switch($this->options['scope']{
  321.         case 'one':
  322.             $this->ldap_search_func = 'ldap_list';
  323.             break;
  324.         case 'base':
  325.             $this->ldap_search_func = 'ldap_read';
  326.             break;
  327.         default:
  328.             $this->ldap_search_func = 'ldap_search';
  329.             break;
  330.         }
  331.         $this->_debug("LDAP search function will be: {$this->ldap_search_func}", __LINE__);
  332.     }
  333.  
  334.     /**
  335.      * Fetch data from LDAP server
  336.      *
  337.      * Searches the LDAP server for the given username/password
  338.      * combination.  Escapes all LDAP meta characters in username
  339.      * before performing the query.
  340.      *
  341.      * @param  string Username
  342.      * @param  string Password
  343.      * @return boolean
  344.      */
  345.     function fetchData($username, $password)
  346.     {        
  347.  
  348.         $this->_connect();
  349.         $this->_getBaseDN();
  350.         
  351.         // make search filter
  352.         $filter = sprintf('(&(objectClass=%s)(%s=%s))'$this->options['useroc']$this->options['userattr']
  353.                           $this->_quoteFilterString($username));
  354.  
  355.         // make search base dn
  356.         $search_basedn $this->options['userdn'];
  357.         if ($search_basedn != '' && substr($search_basedn-1!= ','{
  358.             $search_basedn .= ',';
  359.         }
  360.         $search_basedn .= $this->options['basedn'];
  361.         
  362.         // make functions params array
  363.         $func_params = array($this->conn_id$search_basedn$filterarray($this->options['userattr']));
  364.  
  365.         $this->_debug("Searching with $filter in $search_basedn", __LINE__);
  366.  
  367.         // search
  368.         if (($result_id = @call_user_func_array($this->ldap_search_func$func_params)) == false{
  369.             $this->_debug('User not found'__LINE__);
  370.         } elseif (ldap_count_entries($this->conn_id$result_id== 1{ // did we get just one entry?
  371.             $this->_debug('User was found'__LINE__);
  372.             
  373.             // then get the user dn
  374.             $entry_id = ldap_first_entry($this->conn_id$result_id);
  375.             $user_dn  = ldap_get_dn($this->conn_id$entry_id);
  376.  
  377.             ldap_free_result($result_id);
  378.  
  379.             // need to catch an empty password as openldap seems to return TRUE
  380.             // if anonymous binding is allowed
  381.             if ($password != ""{
  382.                 $this->_debug("Bind as $user_dn", __LINE__);                
  383.  
  384.                 // try binding as this user with the supplied password
  385.                 if (@ldap_bind($this->conn_id$user_dn$password)) {
  386.                     $this->_debug('Bind successful'__LINE__);
  387.  
  388.                     // check group if appropiate
  389.                     if(isset($this->options['group'])) {
  390.                         // decide whether memberattr value is a dn or the username
  391.                         $this->_debug('Checking group membership'__LINE__);
  392.                         return $this->checkGroup(($this->options['memberisdn']$user_dn $username);
  393.                     } else {
  394.                         $this->_debug('Authenticated'__LINE__);
  395.                         $this->_disconnect();
  396.                         return true; // user authenticated
  397.                     } // checkGroup
  398.                 } // bind
  399.             } // non-empty password
  400.         } // one entry
  401.         // default
  402.         $this->_debug('NOT authenticated!'__LINE__);
  403.         $this->_disconnect();
  404.         return false;
  405.     }
  406.  
  407.     /**
  408.      * Validate group membership
  409.      *
  410.      * Searches the LDAP server for group membership of the
  411.      * authenticated user.  Quotes all LDAP filter meta characters in
  412.      * the user name before querying the LDAP server.
  413.      *
  414.      * @param  string Distinguished Name of the authenticated User
  415.      * @return boolean
  416.      */
  417.     function checkGroup($user) 
  418.     {
  419.         // make filter
  420.         $filter = sprintf('(&(%s=%s)(objectClass=%s)(%s=%s))',
  421.                           $this->options['groupattr'],
  422.                           $this->options['group'],
  423.                           $this->options['groupoc'],
  424.                           $this->options['memberattr'],
  425.                           $this->_quoteFilterString($user)
  426.                           );
  427.  
  428.         // make search base dn
  429.         $search_basedn $this->options['groupdn'];
  430.         if($search_basedn != '' && substr($search_basedn-1!= ','{
  431.             $search_basedn .= ',';
  432.         }
  433.         $search_basedn .= $this->options['basedn'];
  434.         
  435.         $func_params = array($this->conn_id$search_basedn$filterarray($this->options['memberattr']));
  436.  
  437.         $this->_debug("Searching with $filter in $search_basedn", __LINE__);
  438.         
  439.         // search
  440.         if(($result_id = @call_user_func_array($this->ldap_search_func$func_params)) != false{
  441.             if(ldap_count_entries($this->conn_id$result_id== 1{                
  442.                 ldap_free_result($result_id);
  443.                 $this->_debug('User is member of group'__LINE__);
  444.                 $this->_disconnect();
  445.                 return true;
  446.             }
  447.         }
  448.  
  449.         // default
  450.         $this->_debug('User is NOT member of group'__LINE__);
  451.         $this->_disconnect();
  452.         return false;
  453.     }
  454.  
  455.     /**
  456.      * Outputs debugging messages
  457.      *
  458.      * @access private
  459.      * @param string Debugging Message
  460.      * @param integer Line number
  461.      */
  462.     function _debug($msg = '', $line = 0)
  463.     {
  464.         if($this->options['debug'=== true{
  465.             if($msg == '' && $this->_isValidLink()) {
  466.                 $msg = 'LDAP_Error: ' . @ldap_err2str(@ldap_errno($this->_conn_id));
  467.             }
  468.             print("$line$msg <br />");
  469.         }
  470.     }
  471.  
  472.     /**
  473.      * Escapes LDAP filter special characters as defined in RFC 2254.
  474.      *
  475.      * @access private
  476.      * @param string Filter String
  477.      */
  478.     function _quoteFilterString($filter_str)
  479.     {
  480.         $metas        = array(  '\\',  '*',  '(',  ')',   "\x00");
  481.         $quoted_metas = array('\\\\', '\*', '\(', '\)', "\\\x00");
  482.  
  483.         return str_replace($metas, $quoted, $filter_str);
  484.     }
  485. }
  486.  

Documentation generated on Mon, 11 Mar 2019 14:36:36 -0400 by phpDocumentor 1.4.4. PEAR Logo Copyright © PHP Group 2004.