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

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