2009-09-24 79 views
12

我想創建一個作爲每日cron運行的php腳本。我想要通過Active Directory中的所有用戶來枚舉,從每個條目中提取某些字段,並使用此信息更新MySQL數據庫中的字段。使用PHP枚舉LDAP中的所有用戶

基本上我想要做的是同步Active Directory和MySQL表之間的某些用戶信息。

我遇到的問題是,Active Directory服務器上的sizelimit通常設置爲每個搜索結果1000條目。我希望php函數「ldap_next_entry」能夠一次只讀取一個條目,但在你調用「ldap_next_entry」之前,你必須首先調用「ldap_search」,這會觸發SizeLimit超出的錯誤。

除了從服務器上刪除sizelimit還有什麼辦法嗎?我能以某種方式獲得結果的「頁面」嗎?

順便說一句 - 我目前沒有使用任何第三方庫或代碼。只需PHP的ldap方法。儘管如此,我肯定會開放使用圖書館。

回答

15

我在開發Zend_Ldap時遇到了同樣的問題爲Zend框架。我將嘗試解釋真正的問題,但要簡短一些:直到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控制擴展的訪問。要設置分頁控件,您需要使用control-oid,這是1.2.840.113556.1.4.319,我們需要知道如何對控制值進行編碼(這在RFC中進行了描述)。該值是一個字節串包裹以下序列的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 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) 

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):

或者你可以去周圍的其他方式,並使用通過ADODB的COM的方法爲在eykanal提供的link建議。

+0

太棒了!非常感謝,這對我非常有幫助! – Christian 2011-11-06 16:28:58

+2

PHP 5.4支持分頁LDAP結果,現在狀態如何? – Squazic 2012-10-12 22:29:39

+1

@Squazic:請參閱下面的回答http://stackoverflow.com/a/10140166/11354。似乎是可行的,從PHP 5.4開始。 – 2015-10-27 08:40:59

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.希望別人可以幫助你:)

0

這是一個替代方案(在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-*']); 
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; 
    }