我想创建一个作为每日cron运行的php脚本。我想要做的是枚举Active Directory中的所有用户,从每个条目中提取某些字段,并使用此信息更新MySQL数据库中的字段。
基本上我想要做的是在Active Directory和MySQL表之间同步某些用户信息。
我遇到的问题是,Active Directory服务器上的sizelimit通常设置为每个搜索结果1000个条目。我曾经希望php函数“ldap_next_entry”可以通过一次只获取一个条目来解决这个问题,但是在你可以调用“ldap_next_entry”之前,首先必须调用“ldap_search”,这可能会触发SizeLimit超出错误。 / p>
除了从服务器上删除sizelimit之外还有什么方法吗?我可以以某种方式获得结果的“页面”吗?
BTW - 我目前没有使用任何第三方库或代码。只是PHPs的ldap方法。虽然如果有帮助的话,我当然愿意使用图书馆。
答案 0 :(得分:15)
在为Zend Framework开发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ñakiArenaza的PHP 4.3.10 ext/ldap
的补丁但我也没有尝试过,也不知道该补丁是否可以应用于PHP5 {{1 }}。补丁扩展ext/ldap
以向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配置并提高最大结果限制。相关选项名为ldap_parse_result()
,可以使用MaxPageSize
进行更改 - 请参阅"How to view and set LDAP policy in Active Directory by using Ntdsutil.exe"。
编辑(对COM的引用):
答案 1 :(得分:6)
在PHP 5.4中添加了对分页结果的支持。
有关详细信息,请参阅ldap_control_paged_result。
答案 2 :(得分:3)
这不是一个完整的答案,但this guy能够做到这一点。但我不明白他做了什么。
顺便说一下,部分答案是你可以获得结果的“页面”。来自documentation:
resource ldap_search ( resource $link_identifier , string $base_dn , string $filter [, array $attributes [, int $attrsonly [, int $sizelimit [, int $timelimit [, int $deref ]]]]] ) ...
sizelimit 允许您限制提取的条目数。将此值设置为0表示没有限制。
注意:此参数不能覆盖服务器端预设sizelimit。 你可以把它设置得更低。一些目录服务器主机将是 配置为返回不超过预设数量的条目。如果这 发生时,服务器将指示它只返回了部分 结果集。如果使用此参数来限制,也会发生这种情况 获取条目的数量。
我不知道如何指定你想从某个位置搜索STARTING。在你获得第一个1000之后,我不知道如何指定现在你需要下一个1000.希望其他人可以帮助你:)
答案 3 :(得分:0)
这是另一种选择(在PHP 5.4之前可以使用)。如果您需要获得10,000条记录,但您的AD服务器每页只返回5,000条:
$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-*']);
答案 4 :(得分:0)
我能够使用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;
}