List all users in LDAP using PHP

I would like to create a php script that works like a daily cron. What I would like to do is list all the users in Active Directory, extract certain fields from each record and use this information to update the fields in the MySQL database.

Basically, what I want to do is synchronize certain user information between Active Directory and a MySQL table.

The problem is that sizelimit on the Active Directory server is often set to 1000 records per search result. I was hoping the php function "ldap_next_entry" would get around this, just picking one entry at a time, but before you can call "ldap_next_entry", you first need to call "ldap_search", which can cause the error in SizeLimit to be exceeded.

Is there a way besides removing sizelimit from the server? Can I somehow get the "pages" of the results?

BTW - I currently do not use third-party libraries or code. Just PHP ldap methods. Although, of course, I am open to using the library, if that helps.

+12
source share
5 answers

I was struck by the same problem when developing Zend_Ldap for the Zend Framework. I will try to explain what the real problem is, but to make it short: before PHP 5.4, it was not possible to use paged results from Active Directory with an unsupported version of PHP ( ext/ldap ) due to the restriction in this extension .

Try to figure it all out ... Microsoft Active Directory uses the so-called server-side control to make a server-side paging. This control is described in RFC 2696, “Extending LDAP Controls for Simple Paging Manipulation” .

ext/php offers access to LDAP control extensions through ldap_set_option() and LDAP_OPT_SERVER_CONTROLS and LDAP_OPT_CLIENT_CONTROLS respectively. To set up a paged control, you need an oid control that is 1.2.840.113556.1.4.319 , and we need to know how to encode the control value (this is described in the RFC ). The value is an octet string wrapping a BER-encoded version of the following SEQUENCE (copied from RFC):

 realSearchControlValue ::= SEQUENCE { size INTEGER (0..maxInt), -- requested page size from client -- result set size estimate from server cookie OCTET STRING } 

Thus, we can establish the appropriate server control before executing the LDAP request:

 $pageSize = 100; $pageControl = array( 'oid' => '1.2.840.113556.1.4.319', // the control-oid 'iscritical' => true, // the operation should fail if the server is not able to support this control 'value' => sprintf ("%c%c%c%c%c%c%c", 48, 5, 2, 1, $pageSize, 4, 0) // the required BER-encoded control-value ); 

This allows us to send a page request to the LDAP / AD server. But how do we know if there are more pages, and how do we indicate with what control value we should send our next request?

Here we are stuck ... The server responds with a result set that includes the necessary paging information, but PHP does not have a method for retrieving this information from the result set. PHP provides a wrapper for the LDAP API function ldap_parse_result() , but the last serverctrlsp parameter serverctrlsp not exposed to the PHP function, so there is no way to get the required information. A bug report has been filed for this problem, but since 2005 there has been no response. If the ldap_parse_result() function provided the required parameter, using the paged results will work as

 $l = ldap_connect('somehost.mydomain.com'); $pageSize = 100; $pageControl = array( 'oid' => '1.2.840.113556.1.4.319', 'iscritical' => true, 'value' => sprintf ("%c%c%c%c%c%c%c", 48, 5, 2, 1, $pageSize, 4, 0) ); $controls = array($pageControl); ldap_set_option($l, LDAP_OPT_PROTOCOL_VERSION, 3); ldap_bind($l, 'CN=bind-user,OU=my-users,DC=mydomain,DC=com', 'bind-user-password'); $continue = true; while ($continue) { ldap_set_option($l, LDAP_OPT_SERVER_CONTROLS, $controls); $sr = ldap_search($l, 'OU=some-ou,DC=mydomain,DC=com', 'cn=*', array('sAMAccountName'), null, null, null, null); ldap_parse_result ($l, $sr, $errcode, $matcheddn, $errmsg, $referrals, $serverctrls); // (*) if (isset($serverctrls)) { foreach ($serverctrls as $i) { if ($i["oid"] == '1.2.840.113556.1.4.319') { $i["value"]{8} = chr($pageSize); $i["iscritical"] = true; $controls = array($i); break; } } } $info = ldap_get_entries($l, $sr); if ($info["count"] < $pageSize) { $continue = false; } for ($entry = ldap_first_entry($l, $sr); $entry != false; $entry = ldap_next_entry($l, $entry)) { $dn = ldap_get_dn($l, $entry); } } 

As you can see, there is one line of code (*) that makes all of this useless. On my way, although rare information on this subject, I found a patch against PHP 4.3.10 ext/ldap from Iñaki Arenaza, but I have not tried it either, and I don’t know if the patch can be applied to PHP5 ext/ldap . The patch extends ldap_parse_result() to show the 7th parameter for PHP:

  --- ldap.c 2004-06-01 23: 05: 33.000000000 +0200
 +++ /usr/src/php4/php4-4.3.10/ext/ldap/ldap.c 2005-09-03 17: 02: 03.000000000 +0200
 @@ -74.7 +74.7 @@
  ZEND_DECLARE_MODULE_GLOBALS (ldap)

  static unsigned char third_argument_force_ref [] = {3, BYREF_NONE, BYREF_NONE, BYREF_FORCE};
 -static unsigned char arg3to6of6_force_ref [] = {6, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE};
 + static unsigned char arg3to7of7_force_ref [] = {7, BYREF_NONE, BYREF_NONE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE, BYREF_FORCE};

  static int le_link, le_result, le_result_entry, le_ber_entry;

 @@ -124.7 +124.7 @@
  #if (LDAP_API_VERSION> 2000) ||  HAVE_NSLDAP
   PHP_FE (ldap_get_option, third_argument_force_ref)
   PHP_FE (ldap_set_option, NULL)
 - PHP_FE (ldap_parse_result, arg3to6of6_force_ref)
 + PHP_FE (ldap_parse_result, arg3to7of7_force_ref)
   PHP_FE (ldap_first_reference, NULL)
   PHP_FE (ldap_next_reference, NULL)
  #ifdef HAVE_LDAP_PARSE_REFERENCE
 @@ -1775.14 +1775.15 @@
     Extract information from result * /
  PHP_FUNCTION (ldap_parse_result) 
  {
 - pval ** link, ** result, ** errcode, ** matcheddn, ** errmsg, ** referrals;
 + pval ** link, ** result, ** errcode, ** matcheddn, ** errmsg, ** referrals, ** serverctrls;
   ldap_linkdata * ld;
   LDAPMessage * ldap_result;
 + LDAPControl ** lserverctrls, ** ctrlp, * ctrl;
   char ** lreferrals, ** refp;
   char * lmatcheddn, * lerrmsg;
   int rc, lerrcode, myargcount = ZEND_NUM_ARGS ();

 - if (myargcount 6 || zend_get_parameters_ex (myargcount, & link, & result, & errcode, & matcheddn, & errmsg, & referrals) == FAILURE) {
 + if (myargcount 7 || zend_get_parameters_ex (myargcount, & link, & result, & errcode, & matcheddn, & errmsg, & referrals, & serverctrls) == FAILURE) {
    WRONG_PARAM_COUNT;
   }

 @@ -1793.7 +1794.7 @@
      myargcount> 3?  & lmatcheddn: NULL,
      myargcount> 4?  & lerrmsg: NULL,
      myargcount> 5?  & lreferrals: NULL,
 - NULL / * & serverctrls * /,
 + myargcount> 6?  & lserverctrls: NULL,
      0);
   if (rc! = LDAP_SUCCESS) {
    php_error (E_WARNING, "% s (): Unable to parse result:% s", get_active_function_name (TSRMLS_C), ldap_err2string (rc));
 @@ -1805.6 +1806.29 @@

   / * Reverse -> fall through * /
   switch (myargcount) {
 + case 7:
 + zval_dtor (* serverctrls);
 +
 + if (lserverctrls! = NULL) {
 + array_init (* serverctrls);
 + ctrlp = lserverctrls;
 +
 + while (* ctrlp! = NULL) {
 + zval * ctrl_array;
 +
 + ctrl = * ctrlp;
 + MAKE_STD_ZVAL (ctrl_array);
 + array_init (ctrl_array);
 +
 + add_assoc_string (ctrl_array, "oid", ctrl-> ldctl_oid, 1);
 + add_assoc_bool (ctrl_array, "iscritical", ctrl-> ldctl_iscritical);
 + add_assoc_stringl (ctrl_array, "value", ctrl-> ldctl_value.bv_val,
 + ctrl-> ldctl_value.bv_len, 1);
 + add_next_index_zval (* serverctrls, ctrl_array);
 + ctrlp ++;
 +}
 + ldap_controls_free (lserverctrls);
 +}
    case 6:
     zval_dtor (* referrals);
     if (array_init (* referrals) == FAILURE) { 

In fact, the only remaining option would be to change the Active Directory configuration and increase the maximum result limit. The corresponding option is called MaxPageSize and can be changed using ntdsutil.exe - see "How to View and Install an LDAP Policy in Active Directory Using Ntdsutil.exe" .

EDIT (link to COM):

Or you can go the other way around and use the COM approach through ADODB as suggested in the link provided by eykanal .

+15
source

Support for paged results was added in PHP 5.4.

See ldap_control_paged_result for more details.

+6
source

This is not a complete answer, but this guy was able to do it. However, I do not understand what he did.

By the way, a partial answer is that you can get "pages" of results. From the doc:

 resource ldap_search ( resource $link_identifier , string $base_dn , string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [, int $timelimit [, int $deref ]]]]] ) ... 

sizelimit Allows you to limit the number of selected entries. Setting this value to 0 means no restrictions.

Note. This parameter can NOT override the specified sizelimit on the server side. However, you can install it below. Some directory server hosts will be configured to return no more than a specified number of entries. If this is the case, the server will indicate that it has only returned a partial result set. This also happens if you use this option to limit the number of records selected.

I do not know how to indicate that you want to search for STARTING from a specific position. Ie, after you get your first 1000, I don’t know how to indicate that now you need the next 1000. I hope someone else can help you :)

+3
source

Here is an alternative (which works pre PHP 5.4). If you have 10,000 records you need to get, but your AD server only returns 5,000 per page:

 $ldapSearch = ldap_search($ldapResource, $basedn, $filter, array('member;range=0-4999')); $ldapResults = ldap_get_entries($dn, $ldapSearch); $members = $ldapResults[0]['member;range=0-4999']; $ldapSearch = ldap_search($ldapResource, $basedn, $filter, array('member;range=5000-10000')); $ldapResults = ldap_get_entries($dn, $ldapSearch); $members = array_merge($members, $ldapResults[0]['member;range=5000-*']); 
0
source

I managed to get around the size limit using ldap_control_paged_result

ldap_control_paged_result is used to enable LDAP pagination by sending the pagination control. The function below worked perfectly in my case.

 function retrieves_users($conn) { $dn = 'ou=,dc=,dc='; $filter = "(&(objectClass=user)(objectCategory=person)(sn=*))"; $justthese = array(); // enable pagination with a page size of 100. $pageSize = 100; $cookie = ''; do { ldap_control_paged_result($conn, $pageSize, true, $cookie); $result = ldap_search($conn, $dn, $filter, $justthese); $entries = ldap_get_entries($conn, $result); if(!empty($entries)){ for ($i = 0; $i < $entries["count"]; $i++) { $data['usersLdap'][] = array( 'name' => $entries[$i]["cn"][0], 'username' => $entries[$i]["userprincipalname"][0] ); } } ldap_control_paged_result_response($conn, $result, $cookie); } while($cookie !== null && $cookie != ''); return $data; } 
0
source

Source: https://habr.com/ru/post/1388020/


All Articles