在为 Zend 框架开发 Zend_Ldap 时,我遇到了同样的问题。我将尝试解释真正的问题是什么,但简而言之:在 PHP 5.4 之前,无法使用带有未修补 PHP (ext/ldap) 版本的 Active Directory 的分页结果,因为正是这个扩展的限制。
让我们尝试解开整个事情... Microsoft Active Directory 使用所谓的服务器控件来完成服务器端结果分页。此控件在RFC 2696 "LDAP Control Extension for Simple Paged Results Manipulation" 中进行了描述。
ext/php 分别通过其ldap_set_option() 和LDAP_OPT_SERVER_CONTROLS 和LDAP_OPT_CLIENT_CONTROLS 选项提供对LDAP 控制扩展的访问。要设置分页控件,您确实需要 control-oid,即1.2.840.113556.1.4.319,并且我们需要知道如何对控件值进行编码(这在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(),将第7个参数暴露给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)
静态无符号字符 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 };
静态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 @@
从结果中提取信息 */
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;
字符 *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 ? &l推荐人:NULL,
- NULL /* &serverctrls */,
+ myargcount > 6 ? &lserverctrls : NULL,
0);
如果(rc!= LDAP_SUCCESS){
php_error(E_WARNING, "%s(): 无法解析结果: %s", get_active_function_name(TSRMLS_C), ldap_err2string(rc));
@@ -1805,6 +1806,29 @@
/* 反转 -> 跌倒 */
开关(myargcount){
+ 案例 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,“值”,ctrl->ldctl_value.bv_val,
+ ctrl->ldctl_value.bv_len,1);
+ add_next_index_zval (*serverctrls, ctrl_array);
+ ctrlp++;
+ }
+ ldap_controls_free (lserverctrls);
+ }
案例6:
zval_dtor(*推荐人);
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):
或者您可以反过来,按照eykanal 提供的link 中的建议,通过ADODB 使用COM 方法。