**Use case**
In some organisations are using AD with hierarchical structure (nested organisation unit) and DN follow this structure. As result we can not  use permanent build_dn for users with different OU.

**Describe the solution you'd like**
Solution can be look like:
1) bind as service account
2) set dn for user_name by some filter string to user_dn
3) bind as user_dn with password
3) use user_dn with filter for group/roles fetching

This approach is implemented in Metabase.

{
    std::scoped_lock lock(ldap_global_mutex);
    SearchResults result;
    int scope = 0;
    switch (search_params.scope)
    {
        case SearchParams::Scope::BASE:      scope = LDAP_SCOPE_BASE;     break;
        case SearchParams::Scope::ONE_LEVEL: scope = LDAP_SCOPE_ONELEVEL; break;
        case SearchParams::Scope::SUBTREE:   scope = LDAP_SCOPE_SUBTREE;  break;
        case SearchParams::Scope::CHILDREN:  scope = LDAP_SCOPE_CHILDREN; break;
    }
    const auto escaped_user_name = escapeForLDAP(params.user);
    const auto bind_dn = replacePlaceholders(params.bind_dn, { {"{user_name}", escaped_user_name} });
    const auto base_dn = replacePlaceholders(search_params.base_dn, { {"{user_name}", escaped_user_name}, {"{bind_dn}", bind_dn} });
    const auto search_filter = replacePlaceholders(search_params.search_filter, { {"{user_name}", escaped_user_name}, {"{bind_dn}", bind_dn}, {"{base_dn}", base_dn} });
    char * attrs[] = { const_cast<char *>(search_params.attribute.c_str()), nullptr };
    ::timeval timeout = { params.search_timeout.count(), 0 };
    LDAPMessage* msgs = nullptr;
    SCOPE_EXIT({
        if (msgs)
        {
            ldap_msgfree(msgs);
            msgs = nullptr;
        }
    });
    diag(ldap_search_ext_s(handle, base_dn.c_str(), scope, search_filter.c_str(), attrs, 0, nullptr, nullptr, &timeout, params.search_limit, &msgs));
    for (
         auto * msg = ldap_first_message(handle, msgs);
         msg != nullptr;
         msg = ldap_next_message(handle, msg)