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.16 2004/04/08 13:50:28 jw 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.  * basedn:      the base dn of your server
  53.  * userdn:      gets prepended to basedn when searching for user
  54.  * userscope:   Scope for user searching: one, sub (default), or base
  55.  * userattr:    the user attribute to search for (default: uid)
  56.  * userfilter:  filter that will be added to the search filter
  57.  *              this way: (&(userattr=username)(userfilter))
  58.  *              default: (objectClass=posixAccount)
  59.  * attributes:  array of additional attributes to fetch from entry.
  60.  *              these will added to auth data and can be retrieved via
  61.  *              Auth::getAuthData(). An empty array will fetch all attributes,
  62.  *              array('') will fetch no attributes at all (default)
  63.  * groupdn:     gets prepended to basedn when searching for group
  64.  * groupattr:   the group attribute to search for (default: cn)
  65.  * groupfilter: filter that will be added to the search filter when
  66.  *              searching for a group:
  67.  *              (&(groupattr=group)(memberattr=username)(groupfilter))
  68.  *              default: (objectClass=groupOfUniqueNames)
  69.  * memberattr : the attribute of the group object where the user dn
  70.  *              may be found (default: uniqueMember)
  71.  * memberisdn:  whether the memberattr is the dn of the user (default)
  72.  *              or the value of userattr (usually uid)
  73.  * group:       the name of group to search for
  74.  * groupscope:  Scope for group searching: one, sub (default), or base
  75.  * debug:       Enable/Disable debugging output (default: false)
  76.  *
  77.  * To use this storage container, you have to use the following syntax:
  78.  *
  79.  * <?php
  80.  * ...
  81.  *
  82.  * $a = new Auth("LDAP", array(
  83.  *       'host' => 'localhost',
  84.  *       'port' => '389',
  85.  *       'basedn' => 'o=netsols,c=de',
  86.  *       'userattr' => 'uid'
  87.  *       'binddn' => 'cn=admin,o=netsols,c=de',
  88.  *       'bindpw' => 'password'));
  89.  *
  90.  * $a2 = new Auth('LDAP', array(
  91.  *       'url' => 'ldaps://ldap.netsols.de',
  92.  *       'basedn' => 'o=netsols,c=de',
  93.  *       'userscope' => 'one',
  94.  *       'userdn' => 'ou=People',
  95.  *       'groupdn' => 'ou=Groups',
  96.  *       'groupfilter' => '(objectClass=posixGroup)',
  97.  *       'memberattr' => 'memberUid',
  98.  *       'memberisdn' => false,
  99.  *       'group' => 'admin'
  100.  *       ));
  101.  *
  102.  * This is a full blown example with user/group checking to an Active Directory
  103.  *
  104.  * $a3 = new Auth('LDAP', array(
  105.  *       'host' => 'ldap.netsols.de',
  106.  *       'port' => 389,
  107.  *       'basedn' => 'dc=netsols,dc=de',
  108.  *       'binddn' => 'cn=Jan Wagner,ou=Users,dc=netsols,dc=de',
  109.  *       'bindpw' => 'password',
  110.  *       'userattr' => 'samAccountName',
  111.  *       'userfilter' => '(objectClass=user)',
  112.  *       'attributes' => array(''),
  113.  *       'group' => 'testing',
  114.  *       'groupattr' => 'samAccountName',
  115.  *       'groupfilter' => '(objectClass=group)',
  116.  *       'memberattr' => 'member',
  117.  *       'memberisdn' => true,
  118.  *       'groupdn' => 'ou=Users',
  119.  *       'groupscope' => 'one',
  120.  *       'debug' => true);
  121.  *
  122.  * The parameter values have to correspond
  123.  * to the ones for your LDAP server of course.
  124.  *
  125.  * When talking to a Microsoft ActiveDirectory server you have to
  126.  * use 'samaccountname' as the 'userattr' and follow special rules
  127.  * to translate the ActiveDirectory directory names into 'basedn'.
  128.  * The 'basedn' for the default 'Users' folder on an ActiveDirectory
  129.  * server for the ActiveDirectory Domain (which is not related to
  130.  * its DNS name) "win2000.example.org" would be:
  131.  * "CN=Users, DC=win2000, DC=example, DC=org'
  132.  * where every component of the domain name becomes a DC attribute
  133.  * of its own. If you want to use a custom users folder you have to
  134.  * replace "CN=Users" with a sequence of "OU" attributes that specify
  135.  * the path to your custom folder in reverse order.
  136.  * So the ActiveDirectory folder
  137.  *   "win2000.example.org\Custom\Accounts"
  138.  * would become
  139.  *   "OU=Accounts, OU=Custom, DC=win2000, DC=example, DC=org'
  140.  *
  141.  * It seems that binding anonymously to an Active Directory
  142.  * is not allowed, so you have to set binddn and bindpw for
  143.  * user searching,
  144.  *
  145.  * Example a3 shows a tested example for connenction to Windows 2000
  146.  * Active Directory
  147.  *
  148.  * @author   Jan Wagner <wagner@netsols.de>
  149.  * @package  Auth
  150.  * @version  $Revision: 1.16 $
  151.  */
  152. {
  153.     /**
  154.      * Options for the class
  155.      * @var array 
  156.      */
  157.     var $options = array();
  158.  
  159.     /**
  160.      * Connection ID of LDAP Link
  161.      * @var string 
  162.      */
  163.     var $conn_id = false;
  164.  
  165.     /**
  166.      * Constructor of the container class
  167.      *
  168.      * @param  $params, associative hash with host,port,basedn and userattr key
  169.      * @return object Returns an error object if something went wrong
  170.      */
  171.     function Auth_Container_LDAP($params)
  172.     {
  173.         $this->_setDefaults();
  174.  
  175.         if (is_array($params)) {
  176.             $this->_parseOptions($params);
  177.         }
  178.     }
  179.  
  180.     // }}}
  181.     // {{{ _connect()
  182.  
  183.     /**
  184.      * Connect to the LDAP server using the global options
  185.      *
  186.      * @access private
  187.      * @return object  Returns a PEAR error object if an error occurs.
  188.      */
  189.     function _connect()
  190.     {
  191.         // connect
  192.         if (isset($this->options['url']&& $this->options['url'!= ''{
  193.             $this->_debug('Connecting with URL'__LINE__);
  194.             $conn_params = array($this->options['url']);
  195.         else {
  196.             $this->_debug('Connecting with host:port'__LINE__);
  197.             $conn_params = array($this->options['host']$this->options['port']);
  198.         }
  199.  
  200.         if (($this->conn_id = @call_user_func_array('ldap_connect'$conn_params)) === false{
  201.             return PEAR::raiseError('Auth_Container_LDAP: Could not connect to server.'41PEAR_ERROR_DIE);
  202.         }
  203.         $this->_debug('Successfully connected to server'__LINE__);
  204.  
  205.         // try switchig to LDAPv3        
  206.         if (@ldap_get_option($this->conn_idLDAP_OPT_PROTOCOL_VERSION$ver&& $ver >= 2{
  207.             $this->_debug('Switching to LDAPv3'__LINE__);
  208.             @ldap_set_option($this->conn_idLDAP_OPT_PROTOCOL_VERSION3);
  209.         }
  210.         // bind with credentials or anonymously
  211.         if ($this->options['binddn'&& $this->options['bindpw']{
  212.             $this->_debug('Binding with credentials'__LINE__);
  213.             $bind_params = array($this->conn_id$this->options['binddn']$this->options['bindpw']);
  214.         else {
  215.             $this->_debug('Binding anonymously'__LINE__);
  216.             $bind_params = array($this->conn_id);
  217.         }        
  218.         // bind for searching
  219.         if ((@call_user_func_array('ldap_bind'$bind_params)) == false{
  220.             $this->_debug();
  221.             $this->_disconnect();
  222.             return PEAR::raiseError("Auth_Container_LDAP: Could not bind to LDAP server."41PEAR_ERROR_DIE);
  223.         }
  224.         $this->_debug('Binding was successful'__LINE__);
  225.     }
  226.  
  227.     /**
  228.      * Disconnects (unbinds) from ldap server
  229.      *
  230.      * @access private
  231.      */
  232.     function _disconnect(
  233.     {
  234.         if ($this->_isValidLink()) {
  235.             $this->_debug('disconnecting from server');
  236.             @ldap_unbind($this->conn_id);
  237.         }
  238.     }
  239.  
  240.     /**
  241.      * Tries to find Basedn via namingContext Attribute
  242.      *
  243.      * @access private
  244.      */
  245.     function _getBaseDN()
  246.     {
  247.         if ($this->options['basedn'== "" && $this->_isValidLink()) {           
  248.             $this->_debug("basedn not set, searching via namingContexts."__LINE__);
  249.  
  250.             $result_id @ldap_read($this->conn_id"""(objectclass=*)"array("namingContexts"));
  251.             
  252.             if (@ldap_count_entries($this->conn_id$result_id== 1{
  253.                 
  254.                 $this->_debug("got result for namingContexts"__LINE__);
  255.                 
  256.                 $entry_id @ldap_first_entry($this->conn_id$result_id);
  257.                 $attrs @ldap_get_attributes($this->conn_id$entry_id);
  258.                 $basedn $attrs['namingContexts'][0];
  259.  
  260.                 if ($basedn != ""{
  261.                     $this->_debug("result for namingContexts was $basedn"__LINE__);
  262.                     $this->options['basedn'$basedn;
  263.                 }
  264.             }
  265.             @ldap_free_result($result_id);
  266.         }
  267.  
  268.         // if base ist still not set, raise error
  269.         if ($this->options['basedn'== ""{
  270.             return PEAR::raiseError("Auth_Container_LDAP: LDAP search base not specified!"41PEAR_ERROR_DIE);
  271.         }        
  272.         return true;
  273.     }
  274.  
  275.     /**
  276.      * determines whether there is a valid ldap conenction or not
  277.      *
  278.      * @accessd private
  279.      * @return boolean 
  280.      */
  281.     function _isValidLink(
  282.     {
  283.         if (is_resource($this->conn_id)) {
  284.             if (get_resource_type($this->conn_id== 'ldap link'{
  285.                 return true;
  286.             }
  287.         }
  288.         return false;
  289.     }
  290.  
  291.     /**
  292.      * Set some default options
  293.      *
  294.      * @access private
  295.      */
  296.     function _setDefaults()
  297.     {
  298.         $this->options['url']         '';
  299.         $this->options['host']        'localhost';
  300.         $this->options['port']        '389';
  301.         $this->options['binddn']      '';
  302.         $this->options['bindpw']      '';        
  303.         $this->options['basedn']      '';
  304.         $this->options['userdn']      '';
  305.         $this->options['userscope']   'sub';
  306.         $this->options['userattr']    "uid";
  307.         $this->options['userfilter']  '(objectClass=posixAccount)';
  308.         $this->options['attributes']  = array('')// no attributes
  309.         $this->options['group']       '';
  310.         $this->options['groupdn']     '';
  311.         $this->options['groupscope']  'sub';
  312.         $this->options['groupattr']   'cn';
  313.         $this->options['groupfilter''(objectClass=groupOfUniqueNames)';
  314.         $this->options['memberattr']  'uniqueMember';
  315.         $this->options['memberisdn']  = true;
  316.         $this->options['debug']       = false;
  317.     }
  318.  
  319.     /**
  320.      * Parse options passed to the container class
  321.      *
  322.      * @access private
  323.      * @param  array 
  324.      */
  325.     function _parseOptions($array)
  326.     {
  327.         foreach ($array as $key => $value{
  328.             if (array_key_exists($key$this->options)) {
  329.                 $this->options[$key$value;
  330.             }
  331.         }
  332.     }
  333.     
  334.     /**
  335.      * Get search function for scope
  336.      *
  337.      * @param  string scope
  338.      * @return string ldap search function
  339.      */
  340.     function _scope2function($scope)
  341.     {
  342.         switch($scope{
  343.         case 'one':
  344.             $function 'ldap_list';
  345.             break;
  346.         case 'base':
  347.             $function 'ldap_read';
  348.             break;
  349.         default:
  350.             $function 'ldap_search';
  351.             break;
  352.         }
  353.         return $function;
  354.     }
  355.  
  356.     /**
  357.      * Fetch data from LDAP server
  358.      *
  359.      * Searches the LDAP server for the given username/password
  360.      * combination.
  361.      *
  362.      * @param  string Username
  363.      * @param  string Password
  364.      * @return boolean 
  365.      */
  366.     function fetchData($username$password)
  367.     {                
  368.         $this->_connect();
  369.         $this->_getBaseDN();
  370.  
  371.         // UTF8 Encode username for LDAPv3
  372.         if (@ldap_get_option($this->conn_idLDAP_OPT_PROTOCOL_VERSION$ver&& $ver == 3{
  373.             $this->_debug('UTF8 encoding username for LDAPv3'__LINE__);
  374.             $username utf8_encode($username);
  375.         }
  376.         // make search filter
  377.         $filter sprintf('(&(%s=%s)%s)',
  378.                           $this->options['userattr'],
  379.                           $username,
  380.                           $this->options['userfilter']);
  381.         // make search base dn
  382.         $search_basedn $this->options['userdn'];
  383.         if ($search_basedn != '' && substr($search_basedn-1!= ','{
  384.             $search_basedn .= ',';
  385.         }
  386.         $search_basedn .= $this->options['basedn'];
  387.  
  388.         // attributes
  389.         $attributes $this->options['attributes'];
  390.  
  391.         // make functions params array
  392.         $func_params = array($this->conn_id$search_basedn$filter$attributes);
  393.  
  394.         // search function to use
  395.         $func_name $this->_scope2function($this->options['userscope']);
  396.         
  397.         $this->_debug("Searching with $func_name and filter $filter in $search_basedn"__LINE__);
  398.  
  399.         // search
  400.         if (($result_id @call_user_func_array($func_name$func_params)) == false{
  401.             $this->_debug('User not found'__LINE__);
  402.         elseif (@ldap_count_entries($this->conn_id$result_id== 1// did we get just one entry?
  403.  
  404.             $this->_debug('User was found'__LINE__);
  405.             
  406.             // then get the user dn
  407.             $entry_id @ldap_first_entry($this->conn_id$result_id);
  408.             $user_dn  @ldap_get_dn($this->conn_id$entry_id);
  409.  
  410.             // fetch attributes
  411.             if ($attributes @ldap_get_attributes($this->conn_id$entry_id)) {
  412.                 if (is_array($attributes&& isset($attributes['count']&&
  413.                      $attributes['count'> 0)
  414.                 {
  415.                     $this->_debug('Saving attributes to Auth data'__LINE__);
  416.                     $this->_auth_obj->setAuthData('attributes'$attributes);
  417.                 }
  418.             }
  419.             @ldap_free_result($result_id);
  420.  
  421.             // need to catch an empty password as openldap seems to return TRUE
  422.             // if anonymous binding is allowed
  423.             if ($password != ""{
  424.                 $this->_debug("Bind as $user_dn"__LINE__);                
  425.  
  426.                 // try binding as this user with the supplied password
  427.                 if (@ldap_bind($this->conn_id$user_dn$password)) {
  428.                     $this->_debug('Bind successful'__LINE__);
  429.  
  430.                     // check group if appropiate
  431.                     if (strlen($this->options['group'])) {
  432.                         // decide whether memberattr value is a dn or the username
  433.                         $this->_debug('Checking group membership'__LINE__);
  434.                         return $this->checkGroup(($this->options['memberisdn']$user_dn $username);
  435.                     else {
  436.                         $this->_debug('Authenticated'__LINE__);
  437.                         $this->_disconnect();
  438.                         return true; // user authenticated
  439.                     // checkGroup
  440.                 // bind
  441.             // non-empty password
  442.         // one entry
  443.         // default
  444.         $this->_debug('NOT authenticated!'__LINE__);
  445.         $this->_disconnect();
  446.         return false;
  447.     }
  448.  
  449.     /**
  450.      * Validate group membership
  451.      *
  452.      * Searches the LDAP server for group membership of the
  453.      * authenticated user
  454.      *
  455.      * @param  string Distinguished Name of the authenticated User
  456.      * @return boolean 
  457.      */
  458.     function checkGroup($user
  459.     {
  460.         // make filter
  461.         $filter sprintf('(&(%s=%s)(%s=%s)%s)',
  462.                           $this->options['groupattr'],
  463.                           $this->options['group'],
  464.                           $this->options['memberattr'],
  465.                           $user,
  466.                           $this->options['groupfilter']);
  467.  
  468.         // make search base dn
  469.         $search_basedn $this->options['groupdn'];
  470.         if ($search_basedn != '' && substr($search_basedn-1!= ','{
  471.             $search_basedn .= ',';
  472.         }
  473.         $search_basedn .= $this->options['basedn'];
  474.         
  475.         $func_params = array($this->conn_id$search_basedn$filter,
  476.                              array($this->options['memberattr']));
  477.         $func_name $this->_scope2function($this->options['groupscope']);
  478.  
  479.         $this->_debug("Searching with $func_name and filter $filter in $search_basedn"__LINE__);
  480.         
  481.         // search
  482.         if (($result_id @call_user_func_array($func_name$func_params)) != false{
  483.             if (@ldap_count_entries($this->conn_id$result_id== 1{                
  484.                 @ldap_free_result($result_id);
  485.                 $this->_debug('User is member of group'__LINE__);
  486.                 $this->_disconnect();
  487.                 return true;
  488.             }
  489.         }
  490.         // default
  491.         $this->_debug('User is NOT member of group'__LINE__);
  492.         $this->_disconnect();
  493.         return false;
  494.     }
  495.  
  496.     /**
  497.      * Outputs debugging messages
  498.      *
  499.      * @access private
  500.      * @param string Debugging Message
  501.      * @param integer Line number
  502.      */
  503.     function _debug($msg ''$line = 0)
  504.     {
  505.         if ($this->options['debug'=== true{
  506.             if ($msg == '' && $this->_isValidLink()) {
  507.                 $msg 'LDAP_Error: ' @ldap_err2str(@ldap_errno($this->_conn_id));
  508.             }
  509.             print("$line$msg <br />");
  510.         }
  511.     }
  512. }
  513.  
  514. ?>

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