LDAP API
От: James Беларусь  
Дата: 25.03.02 15:18
Оценка:
Привет всем,

нужно написать клиент Win LDAP для просмотра и аутентификации юзеров.
Проблемы начинаются когда пытаешься получить список всех юзеров с помощью
ldap_search_ext, идет отлуп на сервере на превышение количества результатов.
Ок, пробую использовать контрол для получения постранично, но отваливается
опять на получении результата говоря Protocol Error.
Собсно вопрос: как? Примеров в МСДН именно по этому вопросу я не нашел, там только
мутное объяснение последовательности вызовов функций.
Хорошо было бы конкретные примеры на С\С++.
James Nicolas Borodco
Re: LDAP API
От: TepMuHyc  
Дата: 25.03.02 18:28
Оценка: 4 (1)
Здравствуйте James, Вы писали:

J>Привет всем,


J>нужно написать клиент Win LDAP для просмотра и аутентификации юзеров.


Смотри код ниже — эта штука вываливает все записи с сервера у которых
CommonName, surname или givenName начинаются со строки szLNameStart.

Код построен на синхронных функциях — т.к. асинхронные функции мастдай и извращение.
Захочется асинхронщины — запусти этот код в новой нитке или переделай под асинхронные фуннкции.

ЗЫ. На всякий случай, если ты еще не наступал на эти грабли....
Библиотека wldap32.lib из Visual Studio — ПЛОХАЯ. Хорошая есть в Platform SDK.

class CLdapDatabase {

//....
public:
    CString m_sLdapHost;//<--имя LDAP сервера
    CString m_sLdapUserDN;//<--DN юзера под которым ты коннектишься - для начала оставь пустым
    CString m_sLdapPassword;//<--обьяснять надо ?

protected:
    struct XEntry {
        CString sFname;
        CString sLname;
        CString sTitle;
        CString sDivision;
        CString sLocation;
        CString sOfficePhoneExt;
        CString sMobilePhone;
        CString sPagerPhone;
        CString sHomePhone;
        CString sWorkstationName;
        CString sCity;
    //--
        CString sEmailAddress;
    };
    CArray<XEntry, XEntry&>    m_aRequest;//<-- здесь будет лежать результат запроса.

};
//--------------------------------------------------------------------
#include <winldap.h>

bool CLdapDatabase::SuggestNames(LPCTSTR szLNameStart)
{
    m_aRequest.RemoveAll();
    if( szLNameBeginning == NULL )
        return false;

    LONG res;
    LDAP *ld = NULL;

    if( NULL == (ld = ldap_init((LPTSTR)(LPCTSTR)m_sLdapHost, LDAP_PORT)) )
        return false;

    int opt;
    res = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &(opt=LDAP_VERSION3)); 
    res = ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &(opt=LDAP_NO_LIMIT)); 

    if( LDAP_SUCCESS != (res = ldap_connect(ld, NULL)) )
        return false;

    if( LDAP_SUCCESS != (res = ldap_bind_s(ld,
            m_sLdapUserDN.IsEmpty()?NULL:(LPTSTR)(LPCTSTR)m_sLdapUserDN,
            m_sLdapPassword.IsEmpty()?NULL:(LPTSTR)(LPCTSTR)m_sLdapPassword,
            LDAP_AUTH_NTLM) ) )
    {//    NT logon didn't pay off - try simple one
        if( LDAP_SUCCESS != (res = ldap_simple_bind_s(ld,
                (LPTSTR)(LPCTSTR)m_sLdapUserDN, (LPTSTR)(LPCTSTR)m_sLdapPassword) ) )
            return false;
    }

    LDAPMessage *srchRes;
    CString sFilter;
    sFilter.Format(_T("(&(!objectClass=groupOfNames)(|(sn=%s*)(cn=%s*)(givenName=%s*)))"),
        szLNameBeginning,szLNameBeginning,szLNameBeginning);
    const LPCTSTR pcszAttrList[] = {
        _T("dn"),_T("givenName"),_T("sn"),_T("title"),_T("department"),    _T("physicalDeliveryOfficeName"),
        _T("telephoneNumber"),_T("mobile"),_T("pager"),_T("homephone"),_T("postalCode"),_T("mail"),_T("rfc822Mailbox"),
        _T("Company"),
        NULL
    };
    if( LDAP_SUCCESS != (res = ldap_search_s(ld, NULL, LDAP_SCOPE_SUBTREE,
            (LPTSTR)(LPCTSTR)sFilter, (TCHAR**)pcszAttrList, 0, &srchRes)) )
    {
        ldap_unbind(ld);
        return false;
    }

/* -- Debug - dump LDAP query to file
раскоментарь это чтобы все данные вывалить в файл - здорово помогает
разобраться в этом мусоре

    FILE *out = fopen("ldap.out", "w");
    LDAPMessage *entry = ldap_first_entry(ld, srchRes);
    while( out && entry )
    {
        fprintf(out, "\n#--------------------\n");
        BerElement *attr;
        TCHAR *pszAttr = ldap_first_attribute(ld, entry, &attr); 
        while( pszAttr ) {
            TCHAR **ppszVals = ldap_get_values(ld, entry, pszAttr);
            while( *ppszVals ) {
                fprintf(out, "%s: %s\n", pszAttr, *ppszVals);
                ppszVals++;
            }
            pszAttr = ldap_next_attribute(ld, entry, attr); 
        }
        entry = ldap_next_entry(ld, entry);
    }
    fclose(out);
*/
    #define GET_LDAP_VALUE(name, str)         do{             ppszVals = ldap_get_values(ld, entry, name);             if( ppszVals ) { str = *ppszVals; ldap_value_free(ppszVals); }         } while(0)

    LDAPMessage *entry = ldap_first_entry(ld, srchRes);
    while( entry )
    {
        XEntry xe;
        TCHAR **ppszVals;
        GET_LDAP_VALUE(_T("givenName"),                  xe.sFname);
        GET_LDAP_VALUE(_T("sn"),                         xe.sLname);
        GET_LDAP_VALUE(_T("title"),                      xe.sTitle);
        GET_LDAP_VALUE(_T("department"),                 xe.sDivision);
        GET_LDAP_VALUE(_T("physicalDeliveryOfficeName"), xe.sLocation);
        GET_LDAP_VALUE(_T("telephoneNumber"),            xe.sOfficePhoneExt);
        GET_LDAP_VALUE(_T("mobile"),                     xe.sMobilePhone);
        GET_LDAP_VALUE(_T("pager"),                      xe.sPagerPhone);
        GET_LDAP_VALUE(_T("homephone"),                  xe.sHomePhone);
        GET_LDAP_VALUE(_T("postalCode"),                 xe.sWorkstationName);
        GET_LDAP_VALUE(_T("mail"),                       xe.sEmailAddress);
        GET_LDAP_VALUE(_T("rfc822Mailbox"),              xe.sEmailAddress);
        GET_LDAP_VALUE(_T("Company"),                    xe.sCity);

        if( (!xe.sFname.IsEmpty()) || (!xe.sLname.IsEmpty()) )
        {
            //at least firstname or lastname should be filled -
            //this is not true for distribution lists
            m_aRequest.Add(xe);
        }
        entry = ldap_next_entry(ld, entry);
    }
    ldap_msgfree(srchRes);
    ldap_unbind(ld);

    return true;
}
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re[2]: LDAP API
От: James Беларусь  
Дата: 26.03.02 09:03
Оценка:
Здравствуйте TepMuHyc, Вы писали:

TMH>Смотри код ниже — эта штука вываливает все записи с сервера у которых

TMH>CommonName, surname или givenName начинаются со строки szLNameStart.

Спасибо большое за код. Кое-что из него я почерпнул. Но...
Результаты для конекретного юзера или, суть, по фильтру я знаю как получать.
Проблема в другом, и в сообщении я об этом писал, на сервере стоит ограничение на
100 результатов поиска и поэтому вопрос скорее заключается в том, можно ли это
как-то обойти.
Я подумал, что постраничное получение результатов (есть только в LDAP 3) может помочь
решить проблему, но нигде не нашел примера, а у самого что-то не получается.

Далее, я подумал, что и постраничное получение результатов тоже не поможет, потому что
ИМХО ограничение касается и его.

Поэтому вопрос пожалуй таков: правильно ли я думаю? ;)
Можно ли обойти это оганичение, если можно то как, и, если есть возможность, то пример
на С\С++ постраничного получения результатов.

best regards
James Nicolas Borodco
Re[3]: LDAP API
От: TepMuHyc  
Дата: 26.03.02 12:04
Оценка:
Здравствуйте James, Вы писали:

J>Здравствуйте TepMuHyc, Вы писали:


TMH>>Смотри код ниже — эта штука вываливает все записи с сервера у которых

TMH>>CommonName, surname или givenName начинаются со строки szLNameStart.

J>Спасибо большое за код. Кое-что из него я почерпнул. Но...

На здоровье.

J>Далее, я подумал, что и постраничное получение результатов тоже не поможет, потому что

J>ИМХО ограничение касается и его.
ПРавильно думаешь. С точки зрения сервера — все это одни и те же яйца — что потоком,
что по страницам — сервер будет ограничивать.


J>Поэтому вопрос пожалуй таков: правильно ли я думаю?

J>Можно ли обойти это оганичение, если можно то как, и, если есть возможность, то пример
Я бы посоветовал тебе такое: давать последовательно запросы типа "(cn=a*)"
"(cn=b*)" "(cn=c*)" и так далее — в итоге ты выгребешь все маленькой ложкой

И еще такой совет: сходи на http://www.openldap.org/ — там вместе с исходниками ldap-сервера
лежат и клиентские прибамбахи — в частности либа libldap (с которой мелкософтовцы и содрали
свою либу winldap) доки там тоже мало (только man-pages), но возможно анализ кода лдаповских
утилит тебе поможет
____________________
God obviously didn't debug, hasn't done any maintenance, and no documentation can be found. Truly amateur work.
Re[4]: LDAP API
От: James Беларусь  
Дата: 26.03.02 13:03
Оценка:
Здравствуйте TepMuHyc, Вы писали:

J>>Поэтому вопрос пожалуй таков: правильно ли я думаю? ;)

J>>Можно ли обойти это оганичение, если можно то как, и, если есть возможность, то пример
TMH>Я бы посоветовал тебе такое: давать последовательно запросы типа "(cn=a*)"
TMH>"(cn=b*)" "(cn=c*)" и так далее — в итоге ты выгребешь все маленькой ложкой :-)

Таки программеры иногда одинаково мыслят, признаюсь было такое искушение. Но прога пишется для больших
корпораций (вернее и для них тоже), поэтому способ ессно не пойдет в общем случае.

TMH>И еще такой совет: сходи на http://www.openldap.org/ — там вместе с исходниками ldap-сервера

TMH>лежат и клиентские прибамбахи — в частности либа libldap (с которой мелкософтовцы и содрали
TMH>свою либу winldap) доки там тоже мало (только man-pages), но возможно анализ кода лдаповских
TMH>утилит тебе поможет

Спасибо за ссылку. Был там, но на исходники не обратил внимания, обязательно посмотрю.

best regards
James Nicolas Borodco
Re[3]: LDAP API
От: Ivan Россия www.rsdn.ru
Дата: 26.03.02 14:13
Оценка: 3 (1)
Здравствуйте James, Вы писали:

J>Здравствуйте TepMuHyc, Вы писали:



J>Поэтому вопрос пожалуй таков: правильно ли я думаю?

J>Можно ли обойти это оганичение, если можно то как, и, если есть возможность, то пример
J>на С\С++ постраничного получения результатов.

J>best regards


По-моему, с помощью страничного поиска можно обойти ограничение сервера на количество
возвращаемых записей.
Вот код, который получает всех пользователей из LDAP каталога страницами по 10 штук.
Выделенные строки надо заменить конкретными значениями

#include <windows.h>
#include <winldap.h>
#include <tchar.h>
#include <iostream>
using namespace std;
#include <crtdbg.h>

#pragma comment(lib, "wldap32.lib")

void main()
{
    PCHAR sLdapHost = "localhost";
    PCHAR sUser = NULL;
    PCHAR sPassword = NULL;
    PCHAR sDN = "ou=...";
    PCHAR sFilter = "(objectClass=user)";
    PCHAR Attributes[] = {"distinguishedName", "name", NULL};

    ULONG ulPageSize = 10;


    LDAP *ld = NULL;
    ld = ldap_init(sLdapHost, LDAP_PORT);
    _ASSERT(ld);
   
    LONG res;
    int opt;
    res = ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &(opt=LDAP_VERSION3)); 
    res = ldap_set_option(ld, LDAP_OPT_TIMELIMIT, &(opt=LDAP_NO_LIMIT)); 

    res = ldap_connect(ld, NULL);
    _ASSERT( LDAP_SUCCESS == res);

    res = ldap_bind_s(ld, sUser, sPassword, LDAP_AUTH_NTLM);
    if( res != LDAP_SUCCESS)
    {
        res = ldap_simple_bind_s(ld, sUser, sPassword);
        _ASSERT( LDAP_SUCCESS == res);
    }

    LDAPSearch* pSearch = NULL;
    pSearch = ldap_search_init_page(ld, sDN, LDAP_SCOPE_SUBTREE, sFilter, Attributes, 0, NULL, 
        NULL, 0, 0, NULL);
    _ASSERT(pSearch);

    LDAPMessage* pResults;
    ULONG ulCount;
    
    while((res = ldap_get_next_page_s(ld, pSearch, NULL, ulPageSize, &ulCount, &pResults)) == ERROR_SUCCESS)
    {
        
       LDAPMessage *entry = ldap_first_entry(ld, pResults);
        while( entry )
        {
            PCHAR* ppValues = ldap_get_values(ld, entry, Attributes[0]);
            if( ppValues )
            { 
                if(ppValues[0]) cout << ppValues[0] << endl;
                ldap_value_free(ppValues);
            }
            entry = ldap_next_entry(ld, entry);
        }
        ldap_msgfree(pResults);
    }

    ldap_search_abandon_page(ld, pSearch);
    ldap_unbind(ld);
}
Re[4]: LDAP API
От: James Беларусь  
Дата: 26.03.02 14:32
Оценка:
Здравствуйте Ivan, Вы писали:

I>По-моему, с помощью страничного поиска можно обойти ограничение сервера на количество

I>возвращаемых записей.
I>Вот код, который получает всех пользователей из LDAP каталога страницами по 10 штук.
I>Выделенные строки надо заменить конкретными значениями

Большое спасибо, Иван!
Это абсолютно в точку.

best regards
James Nicolas Borodco
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.