沃梦达 / 编程问答 / php问题 / 正文

使用 PHP 枚举 LDAP 中的所有用户

Enumerate all users in LDAP with PHP(使用 PHP 枚举 LDAP 中的所有用户)

本文介绍了使用 PHP 枚举 LDAP 中的所有用户的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想创建一个作为每日 cron 运行的 php 脚本.我想要做的是枚举 Active Directory 中的所有用户,从每个条目中提取某些字段,并使用这些信息更新 MySQL 数据库中的字段.

I'd like to create a php script that runs as a daily cron. What I'd like to do is enumerate through all users within an Active Directory, extract certain fields from each entry, and use this information to update fields within a MySQL database.

基本上我想做的是在 Active Directory 和 MySQL 表之间同步某些用户信息.

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

我遇到的问题是 Active Directory 服务器上的大小限制通常设置为每个搜索结果 1000 个条目.我曾希望 php 函数ldap_next_entry"能够通过一次只获取一个条目来解决这个问题,但是在调用ldap_next_entry"之前,您首先必须调用ldap_search",这会触发超出 SizeLimit 的错误.

The problem I have is that the sizelimit on the Active Directory server is often set at 1000 entries per search result. I had hoped that the php function "ldap_next_entry" would get around this by only fetching one entry at a time, but before you can call "ldap_next_entry", you first have to call "ldap_search", which can trigger the SizeLimit exceeded error.

除了从服务器中删除 sizelimit 之外还有什么方法吗?我能以某种方式获得结果的页面"吗?

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

顺便说一句 - 我目前没有使用任何 3rd 方库或代码.只是 PHP 的 ldap 方法.不过,如果有帮助的话,我当然愿意使用图书馆.

BTW - I am currently not using any 3rd party libraries or code. Just PHPs ldap methods. Although, I am certainly open to using a library if that will help.

推荐答案

我在为 Zend 框架开发 Zend_Ldap 时遇到了同样的问题.我将尝试解释真正的问题是什么,但简而言之:直到 PHP 5.4,不可能使用来自 Active Directory 的分页结果和未修补的 PHP (ext/ldap) 版本,因为此扩展程序有限制.

I've been struck by the same problem while developing Zend_Ldap for the Zend Framework. I'll try to explain what the real problem is, but to make it short: until PHP 5.4, it wasn't possible to use paged results from an Active Directory with an unpatched PHP (ext/ldap) version due to limitations in exactly this extension.

让我们尝试解开整个事情... Microsoft Active Directory 使用所谓的服务器控件来完成服务器端结果分页.此控件在 RFC 2696用于简单分页结果操作的 LDAP 控制扩展" 中有所描述.

Let's try to unravel the whole thing... Microsoft Active Directory uses a so called server control to accomplish server-side result paging. This control ist described in RFC 2696 "LDAP Control Extension for Simple Paged Results Manipulation" .

ext/php 通过其 ldap_set_option()LDAP_OPT_SERVER_CONTROLSLDAP_OPT_CLIENT_CONTROLS 选项.要设置分页控件,您确实需要 control-oid,即 1.2.840.113556.1.4.319,并且我们需要知道如何对 control-value 进行编码(这在 RFC).该值是一个八位字节字符串,其中包含以下序列的 BER 编码版本(从 RFC 中复制):

ext/php offers an access to LDAP control extensions via its ldap_set_option() and the LDAP_OPT_SERVER_CONTROLS and LDAP_OPT_CLIENT_CONTROLS option respectively. To set the paged control you do need the control-oid, which 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 the BER-encoded version of the following SEQUENCE (copied from the RFC):

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

因此我们可以在执行 LDAP 查询之前设置适当的服务器控制:

So we can set the appropriate server control prior to executing the LDAP query:

$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
);

这允许我们向 LDAP/AD 服务器发送分页查询.但是我们如何知道是否还有更多的页面要关注,以及我们如何指定我们必须发送下一个查询的控制值?

This allows us to send a paged query to the LDAP/AD server. But how do we know if there are more pages to follow and how do we specify with which control-value we have to send our next query?

这就是我们陷入困境的地方...服务器以包含所需分页信息的结果集作为响应,但 PHP 缺乏从结果集中准确检索此信息的方法.PHP 为 LDAP API 函数提供了一个包装器 ldap_parse_result() 但是所需的最后一个参数 serverctrlsp 没有暴露给 PHP 函数,因此无法检索所需的信息.已针对此问题提交了错误报告,但此后一直没有回应2005. 如果 ldap_parse_result() 函数提供了所需的参数,则使用分页结果会像

This is where we're getting stuck... The server responds with a result set that includes the required paging information but PHP lacks a method to retrieve exactly this information from the result set. PHP provides a wrapper for the LDAP API function ldap_parse_result() but the required last parameter serverctrlsp is not exposed to the PHP function, so there is no way to retrieve the required information. A bug report has been filed for this issue but there has been no response since 2005. If the ldap_parse_result() function provided the required parameter, using paged results would work like

$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);
    }
}

如您所见,只有一行代码 (*) 使整个事情变得毫无用处.在我的路上,虽然关于这个主题的信息很少,但我发现了 Iñaki Arenaza 针对 PHP 4.3.10 ext/ldap 的补丁,但我既没有尝试过,也不知道该补丁是否可以应用于一个 PHP5 ext/ldap.该补丁扩展了 ldap_parse_result() 以将第 7 个参数暴露给 PHP:

As you see there is a single line of code (*) that renders the whole thing useless. On my way though the sparse information on this subject I found a patch against the PHP 4.3.10 ext/ldap by Iñaki Arenaza but neither did I try it nor do I know if the patch can be applied on a PHP5 ext/ldap. The patch extends ldap_parse_result() to expose the 7th parameter to 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) {

实际上,剩下的唯一选择是更改 Active Directory 配置并提高最大结果限制.相关选项称为 MaxPageSize,可以使用 ntdsutil.exe 更改 - 请参阅 "如何使用 Ntdsutil.exe 在 Active Directory 中查看和设置 LDAP 策略".

Actually the only option left would be to change the Active Directory configuration and raise the maximum result limit. The relevant option is called MaxPageSize and can be altered by using ntdsutil.exe - please see "How to view and set LDAP policy in Active Directory by using Ntdsutil.exe".

编辑(参考 COM):

或者你可以反过来,按照 链接 由 eykanal.

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

这篇关于使用 PHP 枚举 LDAP 中的所有用户的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!

本文标题为:使用 PHP 枚举 LDAP 中的所有用户

基础教程推荐