]> granicus.if.org Git - ejabberd/commitdiff
* src/ejabberd_auth_ldap.erl: LDAP authentication now allows to
authorMickaël Rémond <mickael.remond@process-one.net>
Tue, 17 Oct 2006 12:35:47 +0000 (12:35 +0000)
committerMickaël Rémond <mickael.remond@process-one.net>
Tue, 17 Oct 2006 12:35:47 +0000 (12:35 +0000)
match on several alternative attributes.
* src/mod_vcard_ldap.erl: Likewise.
* doc/guide.tex: Updated.
* eldap_utils.erl: Refactoring.
* src/eldap/Makefile.in: Likewise.

SVN Revision: 661

ChangeLog
doc/guide.html
doc/guide.tex
src/ejabberd_auth_ldap.erl
src/eldap/Makefile.in
src/eldap/eldap_utils.erl [new file with mode: 0644]
src/mod_vcard_ldap.erl

index ff1a57aa45b9862b913b8dec9bfa88f2c7092a08..60f12f98fe6173c785fd0747eefc7f508fa90a39 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2006-10-17 Mickael Remond  <mickael.remond@process-one.net>
+
+       * src/ejabberd_auth_ldap.erl: LDAP authentication now allows to
+       match on several alternative attributes.
+       * src/mod_vcard_ldap.erl: Likewise.
+       * doc/guide.tex: Updated. 
+       * eldap_utils.erl: Refactoring.
+       * src/eldap/Makefile.in: Likewise.
+
 2006-10-09  Alexey Shchepin  <alexey@sevcom.net>
 
        * src/mod_privacy_odbc.erl: Privacy rules support using odbc
index 3c03f28cee19821e74393f6699d70d00534ffca7..df3896689d35a04e44f114fcab4f99e40f7a155d 100644 (file)
@@ -521,7 +521,7 @@ Domain <TT>example.net</TT> is using the internal authentication method while
 
 {host_config, "example.com", [{auth_method, ldap},
                               {ldap_servers, ["localhost"]},
-                              {ldap_uidattr, "uid"},
+                              {ldap_uids, [{"uid"}]},
                               {ldap_rootdn, "dc=localdomain"},
                               {ldap_rootdn, "dc=example,dc=com"},
                               {ldap_password, ""}]}.
@@ -534,7 +534,7 @@ Domain <TT>example.net</TT> is using the internal authentication method while
 
 {host_config, "example.com", [{auth_method, ldap},
                               {ldap_servers, ["localhost", "otherhost"]},
-                              {ldap_uidattr, "uid"},
+                              {ldap_uids, [{"uid"}]},
                               {ldap_rootdn, "dc=localdomain"},
                               {ldap_rootdn, "dc=example,dc=com"},
                               {ldap_password, ""}]}.
@@ -1372,15 +1372,26 @@ and SASL authentication.<BR>
 <A NAME="sec:ldapauth"></A>
 You can authenticate users against an LDAP directory. Available options are:
 <DL CLASS="description" COMPACT=compact><DT CLASS="dt-description">
-<B><TT>ldap_base</TT></B><DD CLASS="dd-description">LDAP base directory which stores users
- accounts. This option is required.
-<DT CLASS="dt-description"><B><TT>ldap_uidattr</TT></B><DD CLASS="dd-description">LDAP attribute which holds
+<B><TT>ldap_base</TT></B><DD CLASS="dd-description">LDAP base directory which stores
+ users accounts. This option is required.
+ <DT CLASS="dt-description"><B><TT>ldap_uids</TT></B><DD CLASS="dd-description">LDAP attribute which holds a list
+ of attributes to use as alternatives for getting the JID. The value is of
+ the form: <TT>[{ldap_uidattr}]</TT> or <TT>[{ldap_uidattr,
+ ldap_uidattr_format}]</TT>. You can use as many comma separated tuples
+ <TT>{ldap_uidattr, ldap_uidattr_format}</TT> that is needed. The default
+ value is <TT>[{"uid", "%u"}]</TT>. The defaut <TT>ldap_uidattr_format</TT>
+ is <TT>"%u"</TT>. The values for <TT>ldap_uidattr</TT> and
+ <TT>ldap_uidattr_format</TT> are described as follow:
+ <DL CLASS="description" COMPACT=compact><DT CLASS="dt-description">
+ <B><TT>ldap_uidattr</TT></B><DD CLASS="dd-description">LDAP attribute which holds
  the user's part of a JID. The default value is <TT>"uid"</TT>.
-<DT CLASS="dt-description"><B><TT>ldap_uidattr_format</TT></B><DD CLASS="dd-description">Format of the
- <TT>ldap_uidattr</TT> variable. The format <EM>must</EM> contain one and only one
- pattern variable <TT>"%u"</TT> which will be replaced by the user's part of a
- JID. For example, <TT>"%u@example.org"</TT>. The default value is <TT>"%u"</TT>.
-<DT CLASS="dt-description"><B><TT>ldap_filter</TT></B><DD CLASS="dd-description">
+ <DT CLASS="dt-description"><B><TT>ldap_uidattr_format</TT></B><DD CLASS="dd-description">Format of
+ the <TT>ldap_uidattr</TT> variable. The format <EM>must</EM> contain one and
+ only one pattern variable <TT>"%u"</TT> which will be replaced by the
+ user's part of a JID. For example, <TT>"%u@example.org"</TT>. The default
+ value is <TT>"%u"</TT>.
+ </DL>
+ <DT CLASS="dt-description"><B><TT>ldap_filter</TT></B><DD CLASS="dd-description">
  <A HREF="http://www.faqs.org/rfcs/rfc2254.html">RFC 2254</A> LDAP filter. The
  default is <TT>none</TT>. Example:
  <TT>"(&amp;(objectClass=shadowAccount)(memberOf=Jabber Users))"</TT>. Please, do
@@ -1431,10 +1442,9 @@ Also we want users to search each other. Let's see how we can set it up:
       {ldap_password, ""},
       %% define the addressbook's base
       {ldap_base, "ou=AddressBook,dc=example,dc=org"},
-      %% user's part of JID is located in the "mail" attribute
-      {ldap_uidattr, "mail"},
-      %% common format for our emails
-      {ldap_uidattr_format, "%u@mail.example.org"},
+      %% uidattr: user's part of JID is located in the "mail" attribute
+      %% uidattr_format: common format for our emails
+      {ldap_uids, [{"mail", "%u@mail.example.org"}]},
       %% We have to define empty filter here, because entries in addressbook does not
       %% belong to shadowAccount object class
       {ldap_filter, ""},
@@ -1480,7 +1490,7 @@ configuration is showed below:
   {ldap_base, "DC=office,DC=org"}. % Search base of LDAP directory
   {ldap_rootdn, "CN=Administrator,CN=Users,DC=office,DC=org"}. % LDAP manager
   {ldap_password, "*******"}. % Password to LDAP manager
-  {ldap_uidattr, "sAMAccountName"}.
+  {ldap_uids, [{"sAMAccountName"}]}.
   {ldap_filter, "(memberOf=*)"}.
   
   {mod_vcard_ldap,
@@ -2881,10 +2891,9 @@ Also we want users to search each other. Let's see how we can set it up:
       {ldap_password, ""},
       %% define the addressbook's base
       {ldap_base, "ou=AddressBook,dc=example,dc=org"},
-      %% user's part of JID is located in the "mail" attribute
-      {ldap_uidattr, "mail"},
-      %% common format for our emails
-      {ldap_uidattr_format, "%u@mail.example.org"},
+      %% uidattr: user's part of JID is located in the "mail" attribute
+      %% uidattr_format: common format for our emails
+      {ldap_uids, [{"mail","%u@mail.example.org"}]},
       %% We have to define empty filter here, because entries in addressbook does not
       %% belong to shadowAccount object class
       {ldap_filter, ""},
index 174dc95a6e02bad7fd71803d8f55d76ece6d48cd..3207b8bee33ab4e53cf16828e2da23493ef17be9 100644 (file)
@@ -378,7 +378,7 @@ Examples:
 
 {host_config, "example.com", [{auth_method, ldap},
                               {ldap_servers, ["localhost"]},
-                              {ldap_uidattr, "uid"},
+                              {ldap_uids, [{"uid"}]},
                               {ldap_rootdn, "dc=localdomain"},
                               {ldap_rootdn, "dc=example,dc=com"},
                               {ldap_password, ""}]}.
@@ -392,7 +392,7 @@ Examples:
 
 {host_config, "example.com", [{auth_method, ldap},
                               {ldap_servers, ["localhost", "otherhost"]},
-                              {ldap_uidattr, "uid"},
+                              {ldap_uids, [{"uid"}]},
                               {ldap_rootdn, "dc=localdomain"},
                               {ldap_rootdn, "dc=example,dc=com"},
                               {ldap_password, ""}]}.
@@ -1230,15 +1230,27 @@ and SASL authentication.
 You can authenticate users against an LDAP directory. Available options are:
 
 \begin{description}
-\titem{ldap\_base}\ind{options!ldap\_base}LDAP base directory which stores users
-  accounts. This option is required.
-\titem{ldap\_uidattr}\ind{options!ldap\_uidattr}LDAP attribute which holds
-  the user's part of a JID. The default value is \term{"uid"}.
-\titem{ldap\_uidattr\_format}\ind{options!ldap\_uidattr\_format}Format of the
-  \term{ldap\_uidattr} variable. The format \emph{must} contain one and only one
-  pattern variable \term{"\%u"} which will be replaced by the user's part of a
-  JID. For example, \term{"\%u@example.org"}. The default value is \term{"\%u"}.
-\titem{ldap\_filter}\ind{options!ldap\_filter}\ind{protocols!RFC 2254: The String Representation of LDAP Search Filters}
+\titem{ldap\_base}\ind{options!ldap\_base}LDAP base directory which stores
+  users accounts. This option is required.
+  \titem{ldap\_uids}\ind{options!ldap\_uids}LDAP attribute which holds a list
+  of attributes to use as alternatives for getting the JID. The value is of
+  the form: \term{[\{ldap\_uidattr\}]} or \term{[\{ldap\_uidattr,
+  ldap\_uidattr\_format\}]}. You can use as many comma separated tuples
+  \term{\{ldap\_uidattr, ldap\_uidattr\_format\}} that is needed. The default
+  value is \term{[\{"uid", "\%u"\}]}. The defaut \term{ldap\_uidattr\_format}
+  is \term{"\%u"}. The values for \term{ldap\_uidattr} and
+  \term{ldap\_uidattr\_format} are described as follow:
+  \begin{description}
+    \titem{ldap\_uidattr}\ind{options!ldap\_uidattr}LDAP attribute which holds
+    the user's part of a JID. The default value is \term{"uid"}.
+    \titem{ldap\_uidattr\_format}\ind{options!ldap\_uidattr\_format}Format of
+    the \term{ldap\_uidattr} variable. The format \emph{must} contain one and
+    only one pattern variable \term{"\%u"} which will be replaced by the
+    user's part of a JID. For example, \term{"\%u@example.org"}. The default
+    value is \term{"\%u"}.
+  \end{description}
+  \titem{ldap\_filter}\ind{options!ldap\_filter}\ind{protocols!RFC 2254: The
+  String Representation of LDAP Search Filters}
   \footahref{http://www.faqs.org/rfcs/rfc2254.html}{RFC 2254} LDAP filter. The
   default is \term{none}. Example:
   \term{"(\&(objectClass=shadowAccount)(memberOf=Jabber Users))"}. Please, do
@@ -1289,10 +1301,9 @@ Also we want users to search each other.  Let's see how we can set it up:
       {ldap_password, ""},
       %% define the addressbook's base
       {ldap_base, "ou=AddressBook,dc=example,dc=org"},
-      %% user's part of JID is located in the "mail" attribute
-      {ldap_uidattr, "mail"},
-      %% common format for our emails
-      {ldap_uidattr_format, "%u@mail.example.org"},
+      %% uidattr: user's part of JID is located in the "mail" attribute
+      %% uidattr_format: common format for our emails
+      {ldap_uids, [{"mail", "%u@mail.example.org"}]},
       %% We have to define empty filter here, because entries in addressbook does not
       %% belong to shadowAccount object class
       {ldap_filter, ""},
@@ -1339,7 +1350,7 @@ configuration is showed below:
   {ldap_base, "DC=office,DC=org"}. % Search base of LDAP directory
   {ldap_rootdn, "CN=Administrator,CN=Users,DC=office,DC=org"}. % LDAP manager
   {ldap_password, "*******"}. % Password to LDAP manager
-  {ldap_uidattr, "sAMAccountName"}.
+  {ldap_uids, [{"sAMAccountName"}]}.
   {ldap_filter, "(memberOf=*)"}.
   
   {mod_vcard_ldap,
@@ -2599,10 +2610,9 @@ Also we want users to search each other. Let's see how we can set it up:
       {ldap_password, ""},
       %% define the addressbook's base
       {ldap_base, "ou=AddressBook,dc=example,dc=org"},
-      %% user's part of JID is located in the "mail" attribute
-      {ldap_uidattr, "mail"},
-      %% common format for our emails
-      {ldap_uidattr_format, "%u@mail.example.org"},
+      %% uidattr: user's part of JID is located in the "mail" attribute
+      %% uidattr_format: common format for our emails
+      {ldap_uids, [{"mail","%u@mail.example.org"}]},
       %% We have to define empty filter here, because entries in addressbook does not
       %% belong to shadowAccount object class
       {ldap_filter, ""},
index d152d0895e14bf686509e40f2ed36f25b828e4d1..19b0c9e9d7b03c7123a1015fd804f083371b3b9a 100644 (file)
@@ -50,8 +50,7 @@
                dn,
                password,
                base,
-               uidattr,
-               uidattr_format,
+               uids,
                ufilter,
                sfilter,
                dn_filter,
@@ -188,11 +187,10 @@ handle_call({check_pass, User, Password}, _From, State) ->
     {reply, Reply, State};
 
 handle_call(get_vh_registered_users, _From, State) ->
-    UA = State#state.uidattr,
-    UAF = State#state.uidattr_format,
+    UIDs = State#state.uids,
     Eldap_ID = State#state.eldap_id,
     Server = State#state.host,
-    SortedDNAttrs = usort_attrs(State#state.dn_filter_attrs),
+    SortedDNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
     Reply = case eldap_filter:parse(State#state.sfilter) of
                {ok, EldapFilter} ->
                    case eldap:search(Eldap_ID, [{base, State#state.base},
@@ -205,10 +203,10 @@ handle_call(get_vh_registered_users, _From, State) ->
                                      case is_valid_dn(DN, Attrs, State) of
                                          false -> [];
                                          _ ->
-                                             case get_ldap_attr(UA, Attrs) of
+                                             case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
                                                  "" -> [];
-                                                 User ->
-                                                     case get_user_part(User, UAF) of
+                                                 {User, UIDFormat} ->
+                                                     case eldap_utils:get_user_part(User, UIDFormat) of
                                                          {ok, U} ->
                                                              case jlib:nodeprep(U) of
                                                                  error -> [];
@@ -241,7 +239,7 @@ handle_call(_Request, _From, State) ->
     {reply, bad_request, State}.
 
 find_user_dn(User, State) ->
-    DNAttrs = usort_attrs(State#state.dn_filter_attrs),
+    DNAttrs = eldap_utils:usort_attrs(State#state.dn_filter_attrs),
     case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
        {ok, Filter} ->
            case eldap:search(State#state.eldap_id, [{base, State#state.base},
@@ -262,13 +260,12 @@ is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
 
 is_valid_dn(DN, Attrs, State) ->
     DNAttrs = State#state.dn_filter_attrs,
-    UA = State#state.uidattr,
-    UAF = State#state.uidattr_format,
-    Values = [{"%s", get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
-    SubstValues = case get_ldap_attr(UA, Attrs) of
+    UIDs = State#state.uids,
+    Values = [{"%s", eldap_utils:get_ldap_attr(Attr, Attrs), 1} || Attr <- DNAttrs],
+    SubstValues = case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
                      "" -> Values;
-                     S ->
-                         case get_user_part(S, UAF) of
+                     {S, UAF} ->
+                         case eldap_utils:get_user_part(S, UAF) of
                              {ok, U} -> [{"%u", U} | Values];
                              _ -> Values
                          end
@@ -291,46 +288,6 @@ is_valid_dn(DN, Attrs, State) ->
 %%%----------------------------------------------------------------------
 %%% Auxiliary functions
 %%%----------------------------------------------------------------------
-get_user_part(String, Pattern) ->
-    F = fun(S, P) ->
-               First = string:str(P, "%u"),
-               TailLength = length(P) - (First+1),
-               string:sub_string(S, First, length(S) - TailLength)
-       end,
-    case catch F(String, Pattern) of
-       {'EXIT', _} ->
-           {error, badmatch};
-       Result ->
-           case regexp:sub(Pattern, "%u", Result) of
-               {ok, String, _} -> {ok, Result};
-               _ -> {error, badmatch}
-           end
-    end.
-
-case_insensitive_match(X, Y) ->
-    X1 = stringprep:tolower(X),
-    Y1 = stringprep:tolower(Y),
-    if
-       X1 == Y1 -> true;
-       true -> false
-    end.
-
-get_ldap_attr(LDAPAttr, Attributes) ->
-    Res = lists:filter(
-           fun({Name, _}) ->
-                   case_insensitive_match(Name, LDAPAttr)
-           end, Attributes),
-    case Res of
-       [{_, [Value|_]}] -> Value;
-       _ -> ""
-    end.
-
-usort_attrs(Attrs) when is_list(Attrs) ->
-    lists:usort(Attrs);
-
-usort_attrs(_) ->
-    [].
-
 parse_options(Host) ->
     Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, ?MODULE)),
     Bind_Eldap_ID = atom_to_list(gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
@@ -347,15 +304,11 @@ parse_options(Host) ->
                   undefined -> "";
                   Pass -> Pass
               end,
-    UIDAttr = case ejabberd_config:get_local_option({ldap_uidattr, Host}) of
-                 undefined -> "uid";
-                 UA -> UA
-             end,
-    UIDAttrFormat = case ejabberd_config:get_local_option({ldap_uidattr_format, Host}) of
-                       undefined -> "%u";
-                       UAF -> UAF
-                   end,
-    SubFilter = "(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")",
+    UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
+              undefined -> [{"uid", "%u"}];
+              UI -> UI
+          end,
+    SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
     UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
                     undefined -> SubFilter;
                     "" -> SubFilter;
@@ -376,8 +329,7 @@ parse_options(Host) ->
           dn = RootDN,
           password = Password,
           base = LDAPBase,
-          uidattr = UIDAttr,
-          uidattr_format = UIDAttrFormat,
+          uids = UIDs,
           ufilter = UserFilter,
           sfilter = SearchFilter,
           dn_filter = DNFilter,
index 72e7b463213f9034fecef72375d0dbe8b6e2ebc3..de79338311bfb2f31e2b18dff50131b617441e92 100644 (file)
@@ -13,7 +13,8 @@ EFLAGS = -I .. -pz ..
 OBJS   = \
        $(OUTDIR)/eldap.beam \
        $(OUTDIR)/ELDAPv3.beam \
-       $(OUTDIR)/eldap_filter.beam
+       $(OUTDIR)/eldap_filter.beam \
+       $(OUTDIR)/eldap_utils.beam
 
 all:    $(OBJS)
 
diff --git a/src/eldap/eldap_utils.erl b/src/eldap/eldap_utils.erl
new file mode 100644 (file)
index 0000000..b4eb67f
--- /dev/null
@@ -0,0 +1,110 @@
+%%%----------------------------------------------------------------------
+%%% File    : eldap_utils.erl
+%%% Author  : Mickael Remond <mickael.remond@process-one.net>
+%%% Purpose : ejabberd LDAP helper functions
+%%% Created : 12 Oct 2006 by Mickael Remond <mickael.remond@process-one.net>
+%%% Id      : $Id: ejabberd_auth_ldap.erl 623 2006-09-23 09:52:53Z mremond $
+%%%----------------------------------------------------------------------
+
+-module(eldap_utils).
+-author('mickael.remond@process-one.net').
+-svn('$Revision: $ ').
+
+-export([generate_subfilter/1,
+        find_ldap_attrs/2,
+        get_ldap_attr/2,
+        usort_attrs/1,
+        get_user_part/2,
+        make_filter/2]).
+
+%% Generate an 'or' LDAP query on one or several attributes
+%% If there is only one attribute
+generate_subfilter([UID]) ->
+    subfilter(UID);
+%% If there is several attributes
+generate_subfilter(UIDs) ->
+    "(|" ++ [subfilter(UID) || UID <- UIDs] ++ ")".
+%% Subfilter for a single attribute
+subfilter({UIDAttr, UIDAttrFormat}) ->
+    "(" ++ UIDAttr ++ "=" ++ UIDAttrFormat ++ ")";
+%% The default UiDAttrFormat is %u
+subfilter({UIDAttr}) ->
+    "(" ++ UIDAttr ++ "=" ++ "%u)".
+
+%% Not tail-recursive, but it is not very terribly.
+%% It stops finding on the first not empty value.
+find_ldap_attrs([{Attr, Format} | Rest], Attributes) ->
+       case get_ldap_attr(Attr, Attributes) of
+       Value when is_list(Value), Value /= "" ->
+               {Value, Format};
+       _ ->
+               find_ldap_attrs(Rest, Attributes)
+       end;
+find_ldap_attrs([], _) ->
+       "".
+
+get_ldap_attr(LDAPAttr, Attributes) ->
+    Res = lists:filter(
+           fun({Name, _}) ->
+                   case_insensitive_match(Name, LDAPAttr)
+           end, Attributes),
+    case Res of
+       [{_, [Value|_]}] -> Value;
+       _ -> ""
+    end.
+
+
+usort_attrs(Attrs) when is_list(Attrs) ->
+    lists:usort(Attrs);
+usort_attrs(_) ->
+    [].
+
+get_user_part(String, Pattern) ->
+    F = fun(S, P) ->
+               First = string:str(P, "%u"),
+               TailLength = length(P) - (First+1),
+               string:sub_string(S, First, length(S) - TailLength)
+       end,
+    case catch F(String, Pattern) of
+       {'EXIT', _} ->
+           {error, badmatch};
+       Result ->
+           case regexp:sub(Pattern, "%u", Result) of
+               {ok, String, _} -> {ok, Result};
+               _ -> {error, badmatch}
+           end
+    end.
+
+make_filter(Data, UIDs) ->
+    NewUIDs = [{U, eldap_filter:do_sub(UF, [{"%u", "*%u*", 1}])} || {U, UF} <- UIDs],
+    Filter = lists:flatmap(
+              fun({Name, [Value | _]}) ->
+                      case Name of
+                          "%u" when Value /= "" ->
+                              case eldap_filter:parse(
+                                     lists:flatten(generate_subfilter(NewUIDs)),
+                                              [{"%u", Value}]) of
+                                  {ok, F} -> [F];
+                                  _ -> []
+                              end;
+                          _ when Value /= "" ->
+                              [eldap:substrings(Name, [{any, Value}])];
+                          _ ->
+                              []
+                      end
+              end, Data),
+    case Filter of
+       [F] ->
+           F;
+       _ ->
+           eldap:'and'(Filter)
+    end.
+
+case_insensitive_match(X, Y) ->
+    X1 = stringprep:tolower(X),
+    Y1 = stringprep:tolower(Y),
+    if
+       X1 == Y1 -> true;
+       true -> false
+    end.
+
index 0921ce92fb247ab7a45c9f790721266b1d80550d..f0ec35462e5ae93ac934dac6ec7a5812c18f0867 100644 (file)
@@ -46,8 +46,7 @@
                dn,
                base,
                password,
-               uid,
-               uid_format,
+               uids,
                vcard_map,
                vcard_map_attrs,
                user_filter,
@@ -552,10 +551,9 @@ search(State, Data) ->
     Base = State#state.base,
     SearchFilter = State#state.search_filter,
     Eldap_ID = State#state.eldap_id,
-    UA = State#state.uid,
-    UAF = State#state.uid_format,
+    UIDs = State#state.uids,
     ReportedAttrs = State#state.search_reported_attrs,
-    Filter = eldap:'and'([SearchFilter, make_filter(Data, UA, UAF)]),
+    Filter = eldap:'and'([SearchFilter, eldap_utils:make_filter(Data, UIDs)]),
     case eldap:search(Eldap_ID, [{base, Base},
                                 {filter, Filter},
                                 {attributes, ReportedAttrs}]) of
@@ -569,8 +567,7 @@ search_items(Entries, State) ->
     LServer = State#state.serverhost,
     SearchReported = State#state.search_reported,
     VCardMap = State#state.vcard_map,
-    UIDAttr = State#state.uid,
-    UIDAttrFormat = State#state.uid_format,
+       UIDs = State#state.uids,
     Attributes = lists:map(
                   fun(E) ->
                           #eldap_entry{attributes = Attrs} = E,
@@ -578,12 +575,13 @@ search_items(Entries, State) ->
                   end, Entries),
     lists:flatmap(
       fun(Attrs) ->
-             U = get_ldap_attr(UIDAttr, Attrs),
-             case get_user_part(U, UIDAttrFormat) of
-                 {ok, Username} ->
-                     case ejabberd_auth:is_user_exists(Username, LServer) of
-                         true ->
-                             RFields = lists:map(
+             case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
+             {U, UIDAttrFormat} ->
+                 case eldap_utils:get_user_part(U, UIDAttrFormat) of
+                     {ok, Username} ->
+                         case ejabberd_auth:is_user_exists(Username, LServer) of
+                             true ->
+                               RFields = lists:map(
                                          fun({_, VCardName}) ->
                                                  {VCardName,
                                                   map_vcard_attr(
@@ -592,41 +590,20 @@ search_items(Entries, State) ->
                                                     VCardMap,
                                                     {Username, ?MYNAME})}
                                          end, SearchReported),
-                             Result = [?FIELD("jid", Username ++ "@" ++ LServer)] ++
-                                 [?FIELD(Name, Value) || {Name, Value} <- RFields],
-                             [{xmlelement, "item", [], Result}];
-                         _ ->
-                             []
-                     end;
-                 _ ->
+                               Result = [?FIELD("jid", Username ++ "@" ++ LServer)] ++
+                                   [?FIELD(Name, Value) || {Name, Value} <- RFields],
+                               [{xmlelement, "item", [], Result}];
+                             _ ->
+                                 []
+                         end;
+                     _ ->
+                        []
+                 end;
+          "" ->
                      []
-             end
+                 end
       end, Attributes).
 
-make_filter(Data, UAttr, UAttrFormat) ->
-    Filter = lists:flatmap(
-              fun({Name, [Value | _]}) ->
-                      case Name of
-                          "%u" when Value /= "" ->
-                              {ok, UAF, _} = regexp:sub(UAttrFormat, "%u", "*%u*"),
-                              case eldap_filter:parse(
-                                     "("++UAttr++"="++UAF++")", [{"%u", Value}]) of
-                                  {ok, F} -> [F];
-                                  _ -> []
-                              end;
-                          _ when Value /= "" ->
-                              [eldap:substrings(Name, [{any, Value}])];
-                          _ ->
-                              []
-                      end
-              end, Data),
-    case Filter of
-       [F] ->
-           F;
-       _ ->
-           eldap:'and'(Filter)
-    end.
-
 remove_user(_User) ->
     true.
 
@@ -634,39 +611,15 @@ remove_user(_User) ->
 %%% Auxiliary functions.
 %%%-----------------------
 
-get_user_part(String, Pattern) ->
-    F = fun(S, P) ->
-               First = string:str(P, "%u"),
-               TailLength = length(P) - (First+1),
-               string:sub_string(S, First, length(S) - TailLength)
-       end,
-    case catch F(String, Pattern) of
-       {'EXIT', _} ->
-           {error, badmatch};
-       Result ->
-           case regexp:sub(Pattern, "%u", Result) of
-               {ok, String, _} -> {ok, Result};
-               _ -> {error, badmatch}
-           end
-    end.
-
-case_insensitive_match(X, Y) ->
-    X1 = stringprep:tolower(X),
-    Y1 = stringprep:tolower(Y),
-    if
-       X1 == Y1 -> true;
-       true -> false
-    end.
-
 map_vcard_attr(VCardName, Attributes, Pattern, UD) ->
     Res = lists:filter(
            fun({Name, _, _}) ->
-                   case_insensitive_match(Name, VCardName)
+                   eldap_utils:case_insensitive_match(Name, VCardName)
            end, Pattern),
     case Res of
        [{_, Str, Attrs}] ->
            process_pattern(Str, UD,
-                           [get_ldap_attr(X, Attributes) || X<-Attrs]);
+                           [eldap_utils:get_ldap_attr(X, Attributes) || X<-Attrs]);
        _ -> ""
     end.
 
@@ -674,16 +627,6 @@ process_pattern(Str, {User, Domain}, AttrValues) ->
        eldap_filter:do_sub(Str,
                [{"%s", V, 1} || V <- AttrValues] ++ [{"%u", User},{"%d", Domain}]).
 
-get_ldap_attr(LDAPAttr, Attributes) ->
-    Res = lists:filter(
-           fun({Name, _}) ->
-                   case_insensitive_match(Name, LDAPAttr)
-           end, Attributes),
-    case Res of
-       [{_, [Value|_]}] -> Value;
-       _ -> ""
-    end.
-
 find_xdata_el({xmlelement, _Name, _Attrs, SubEls}) ->
     find_xdata_el1(SubEls).
 
@@ -721,22 +664,14 @@ parse_options(Host, Opts) ->
                       ejabberd_config:get_local_option({ldap_base, Host});
                   B -> B
               end,
-    UIDAttr = case gen_mod:get_opt(ldap_uidattr, Opts, undefined) of
-                 undefined ->
-                     case ejabberd_config:get_local_option({ldap_uidattr, Host}) of
-                         undefined -> "uid";
-                         UA -> UA
-                     end;
-                 UA -> UA
-             end,
-    UIDAttrFormat = case gen_mod:get_opt(ldap_uidattr_format, Opts, undefined) of
-                       undefined ->
-                           case ejabberd_config:get_local_option({ldap_uidattr_format, Host}) of
-                               undefined -> "%u";
-                               UAF -> UAF
-                           end;
-                       UAF -> UAF
-                   end,
+       UIDs = case gen_mod:get_opt(ldap_uids, Opts, undefined) of
+           undefined ->
+                   case ejabberd_config:get_local_option({ldap_uids, Host}) of
+                       undefined -> [{"uid", "%u"}];
+                       UI -> UI
+                       end;
+               UI -> UI
+               end,
     RootDN = case gen_mod:get_opt(ldap_rootdn, Opts, undefined) of
                 undefined ->
                     case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
@@ -753,7 +688,7 @@ parse_options(Host, Opts) ->
                       end;
                   Pass -> Pass
               end,
-    SubFilter = "("++UIDAttr++"="++UIDAttrFormat++")",
+    SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
     UserFilter = case gen_mod:get_opt(ldap_filter, Opts, undefined) of
                     undefined ->
                         case ejabberd_config:get_local_option({ldap_filter, Host}) of
@@ -772,8 +707,9 @@ parse_options(Host, Opts) ->
     %% In search requests we need to fetch only attributes defined
     %% in vcard-map and search-reported. In some cases,
     %% this will essentially reduce network traffic from an LDAP server.
+       UIDAttrs = [UAttr || {UAttr, _} <- UIDs],
     VCardMapAttrs = lists:usort(
-                     lists:append([A || {_, _, A} <- VCardMap]) ++ [UIDAttr]),
+                     lists:append([A || {_, _, A} <- VCardMap]) ++ UIDAttrs),
     SearchReportedAttrs =
        lists:usort(lists:flatmap(
                      fun({_, N}) ->
@@ -781,7 +717,7 @@ parse_options(Host, Opts) ->
                                  {value, {_, _, L}} -> L;
                                  _ -> []
                              end
-                     end, SearchReported) ++ [UIDAttr]),
+                     end, SearchReported) ++ UIDAttrs),
     #state{serverhost = Host,
           myhost = MyHost,
           eldap_id = Eldap_ID,
@@ -791,8 +727,7 @@ parse_options(Host, Opts) ->
           dn = RootDN,
           base = LDAPBase,
           password = Password,
-          uid = UIDAttr,
-          uid_format = UIDAttrFormat,
+          uids = UIDs,
           vcard_map = VCardMap,
           vcard_map_attrs = VCardMapAttrs,
           user_filter = UserFilter,