我想创建一个作为日常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)版本一起使用扩展名

让我们尝试弄清整个事情……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控制扩展的访问。要设置分页控件,您确实需要控件oid,即1.2.840.113556.1.4.319,并且我们需要知道如何对控件值进行编码(这在RFC中进行了描述)。该值是一个八位字节字符串,包装了以下SEQUENCE(从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函数,因此无法检索所需的信息。已针对此问题提交了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 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)

静态无符号字符third_argument_force_ref [] = {3,BYREF_NONE,BYREF_NONE,BYREF_FORCE};
-static无符号字符arg3to6of6_force_ref [] = {6,BYREF_NONE,BYREF_NONE,BYREF_FORCE,BYREF_FORCE,BYREF_FORCE,BYREF_FORCE};
+静态无符号字符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,**引荐;
+ pval ** link,** result,** errcode,** matcheddn,** errmsg,** referrals,** serverctrls;
ldap_linkdata * ld;
LDAPMessage * ldap_result;
+ LDAPControl ** lserverctrls,** ctrlp,* ctrl;
char ** referrals,** refp;
char * lmatcheddn,* lerrmsg;
int rc,lerrcode,myargcount = ZEND_NUM_ARGS();

-如果(myargcount 6 || zend_get_parameters_ex(myargcount,&link,&result,&errcode,&matcheddn,&errmsg,&referrals)==失败){
+ if(myargcount 7 || zend_get_parameters_ex(myargcount,&link,&result,&errcode,&matcheddn,&errmsg,&referrals,&serverctrls)==失败){
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);
如果(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,“value”,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(*引荐);
如果(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方法。

10-07 16:46