我想创建一个运行作为日常cron的PHP脚本。 我想要做的是Active Directory中通过所有用户枚举,从每个条目中提取的某些字段,并使用这些信息来更新MySQL数据库中的字段。
基本上我想要做的是Active Directory和一个MySQL表之间的同步了一定的用户信息。
我的问题是,Active Directory服务器上了SizeLimit通常设置在每个搜索结果1000个条目。 我曾希望PHP函数“ldap_next_entry”将解决这个问题只取一次一个条目,但在此之前,你可以称之为“ldap_next_entry”,你首先要称之为“ldap_search”,这可以触发了SizeLimit超出误差。
有没有除了从服务器上删除了SizeLimit什么办法? 我能以某种方式得到结果的“页面”?
顺便说一句 - 我目前没有使用任何第三方库或代码。 只是PHP的LDAP的方法。 虽然,我当然要使用库,如果这将有助于打开。
同时开发了Zend框架Zend_Ldap我被击中受了同样的问题。 我会尽力解释,真正的问题是什么,而是使之短: 直到PHP 5.4,这是不可能的从Active Directory使用分页结果与未打补丁的PHP( ext/ldap
)版本由于正好限制这个扩展 。
让我们试着解开整个事情...微软Active Directory使用所谓的服务器控件来实现服务器端的结果分页。 这种控制在IST描述RFC 2696“LDAP控制扩展为简单分页结果操纵” 。
ext/php
经由其提供到LDAP控制扩展接入ldap_set_option()
和LDAP_OPT_SERVER_CONTROLS
和LDAP_OPT_CLIENT_CONTROLS
分别选项。 要设置分页控制你需要控制-旧,这是1.2.840.113556.1.4.319
,我们需要知道如何编码的控制值(这是在描述RFC )。 该值是一个字节串包裹以下顺序(从RFC复制)的BER编码版本:
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函数,所以没有方法来检索所需信息。 一个错误报告已经提交了这个问题,但一直存在自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);
}
}
正如你看到有一个单一的代码行(*)
呈现了整个事情无用。 在我的方式,虽然在这个问题上稀疏的信息,我发现对PHP 4.3.10补丁ext/ldap
通过的IñakiArenaza但也没有我试试吧我也不知道是否可以在PHP5应用补丁ext/ldap
。 该补丁扩展ldap_parse_result()
揭露第七参数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策略” 。
EDIT(参照COM):
或者你可以去周围的其他方式,并使用通过ADODB的COM-方法,因为在建议的链接提供eykanal 。
这不是一个完整的答案,但这个家伙是能够做到这一点。 我不明白他做了什么,虽然。
顺便说一句,部分答案是,你可以得到结果的“页面”。 从文档 :
resource ldap_search ( resource $link_identifier , string $base_dn , string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [, int $timelimit [, int $deref ]]]]] ) ...
的sizeLimit使您能够限制取条目的数量。 将其设置为0表示没有限制。
注意:此参数不能覆盖服务器端预设的sizeLimit。 你可以设置,虽然它低。 某些目录服务器主机将配置返回比项目的预设数量不多。 如果发生这种情况,服务器将表明,它只是返回的部分结果集。 如果你使用这个参数来限制获取的条目数也会出现这种情况。
我不知道如何指定你想从某一个位置搜索开始,虽然。 也就是说,你得到你之后第一个1000,我不知道如何指定,现在你需要在未来1000希望别人能帮助你那里:)
下面是一个替代(其中工程预PHP 5.4)。 如果你有10000条记录,你需要得到,但你的AD服务器只返回5000每页:
$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-*']);
我能得到的大小限制使用围绕ldap_control_paged_result
ldap_control_paged_result用于通过发送分页控制启用LDAP分页。 下面的功能在我的情况下完美地工作。
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;
}