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

标签 php active-directory ldap

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

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

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

除了取消服务器的大小限制之外,还有什么办法吗?我能以某种方式获得“页面”结果吗?

顺便说一句 - 我目前没有使用任何第三方库或代码。只是 PHP 的 ldap 方法。尽管如此,如果有帮助的话,我当然愿意使用库。

最佳答案

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

让我们尝试解开整个问题... Microsoft Active Directory 使用所谓的服务器控件来完成服务器端结果分页。该控件在 RFC 2696 "LDAP Control Extension for Simple Paged Results Manipulation" 中描述。

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

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

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

$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 服务器发送分页查询。但是我们如何知道是否还有更多页面要跟随,以及如何指定必须发送下一个查询的控制值?

这就是我们陷入困境的地方...服务器响应一个包含所需分页信息的结果集,但 PHP 缺乏从结果集中准确检索此信息的方法。 PHP 为 LDAP API 函数 ldap_parse_result() 提供了包装器,但所需的最后一个参数 serverctrlsp 未向 PHP 函数公开,因此无法检索所需的信息。已针对此问题提交了 bug report,但自 2005 年以来一直没有响应。如果 ldap_parse_result() 函数提供了所需的参数,则使用分页结果的工作方式如下:

$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() 以向 PHP 公开第 7 个参数:

--- 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 进行更改 - 请参阅 "How to view and set LDAP policy in Active Directory by using Ntdsutil.exe"

编辑(引用 COM):

或者您可以反过来,按照 link 提供的 eykanal 中的建议,通过 ADODB 使用 COM 方法。

关于php - 使用 PHP 枚举 LDAP 中的所有用户,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1473075/

相关文章:

AD 的 SQL 链接服务器查询返回消息 7321

c# - LDAP 密码到期天数

java - 使用 "com.sun.jndi.ldap.LdapCtxFactory"的 LDAP 身份验证中 SECURITY_PRINCIPAL 的正确格式是什么

PHP 对象到字符串

c# - 使用 AccountManagement 扩展类时如何设置二进制属性?

node.js - ldap查询获取组node.js中的所有用户

php - LDAP - 具有多个组的搜索过滤器

php - 在 mysql 中回显更新字段

php - 用php锁定mysql表

php - 在 CodeIgniter Activerecord 中执行 WHERE !=