aboutsummaryrefslogtreecommitdiff
path: root/src/ejabberd_auth_ldap.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/ejabberd_auth_ldap.erl')
-rw-r--r--src/ejabberd_auth_ldap.erl550
1 files changed, 258 insertions, 292 deletions
diff --git a/src/ejabberd_auth_ldap.erl b/src/ejabberd_auth_ldap.erl
index 5e5ca2422..998f21215 100644
--- a/src/ejabberd_auth_ldap.erl
+++ b/src/ejabberd_auth_ldap.erl
@@ -25,73 +25,59 @@
%%%----------------------------------------------------------------------
-module(ejabberd_auth_ldap).
+
-author('alexey@process-one.net').
-behaviour(gen_server).
+-behaviour(ejabberd_auth).
%% gen_server callbacks
--export([init/1,
- handle_info/2,
- handle_call/3,
- handle_cast/2,
- terminate/2,
- code_change/3
- ]).
+-export([init/1, handle_info/2, handle_call/3,
+ handle_cast/2, terminate/2, code_change/3]).
%% External exports
--export([start/1,
- stop/1,
- start_link/1,
- set_password/3,
- check_password/3,
- check_password/5,
- try_register/3,
- dirty_get_registered_users/0,
- get_vh_registered_users/1,
- get_vh_registered_users_number/1,
- get_password/2,
- get_password_s/2,
- is_user_exists/2,
- remove_user/2,
- remove_user/3,
- store_type/0,
- plain_password_required/0
- ]).
+-export([start/1, stop/1, start_link/1, set_password/3,
+ check_password/3, check_password/5, try_register/3,
+ dirty_get_registered_users/0, get_vh_registered_users/1,
+ get_vh_registered_users/2,
+ get_vh_registered_users_number/1,
+ get_vh_registered_users_number/2, get_password/2,
+ get_password_s/2, is_user_exists/2, remove_user/2,
+ remove_user/3, store_type/0,
+ plain_password_required/0]).
-include("ejabberd.hrl").
--include("eldap/eldap.hrl").
-
--record(state, {host,
- eldap_id,
- bind_eldap_id,
- servers,
- backups,
- port,
- tls_options,
- dn,
- password,
- base,
- uids,
- ufilter,
- sfilter,
- lfilter, %% Local filter (performed by ejabberd, not LDAP)
- deref_aliases,
- dn_filter,
- dn_filter_attrs
- }).
-
%% Unused callbacks.
-handle_cast(_Request, State) ->
- {noreply, State}.
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-handle_info(_Info, State) ->
- {noreply, State}.
%% -----
+-include("eldap/eldap.hrl").
--define(LDAP_SEARCH_TIMEOUT, 5). % Timeout for LDAP search queries in seconds
-
+-record(state,
+ {host = <<"">> :: binary(),
+ eldap_id = <<"">> :: binary(),
+ bind_eldap_id = <<"">> :: binary(),
+ servers = [] :: [binary()],
+ backups = [] :: [binary()],
+ port = ?LDAP_PORT :: inet:port_number(),
+ tls_options = [] :: list(),
+ dn = <<"">> :: binary(),
+ password = <<"">> :: binary(),
+ base = <<"">> :: binary(),
+ uids = [] :: [{binary()} | {binary(), binary()}],
+ ufilter = <<"">> :: binary(),
+ sfilter = <<"">> :: binary(),
+ lfilter :: {any(), any()},
+ deref_aliases = never :: never | searching | finding | always,
+ dn_filter :: binary(),
+ dn_filter_attrs = [] :: [binary()]}).
+
+handle_cast(_Request, State) -> {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+handle_info(_Info, State) -> {noreply, State}.
+
+-define(LDAP_SEARCH_TIMEOUT, 5).
%%%----------------------------------------------------------------------
%%% API
@@ -99,10 +85,8 @@ handle_info(_Info, State) ->
start(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
- ChildSpec = {
- Proc, {?MODULE, start_link, [Host]},
- transient, 1000, worker, [?MODULE]
- },
+ ChildSpec = {Proc, {?MODULE, start_link, [Host]},
+ transient, 1000, worker, [?MODULE]},
supervisor:start_child(ejabberd_sup, ChildSpec).
stop(Host) ->
@@ -115,56 +99,45 @@ start_link(Host) ->
Proc = gen_mod:get_module_proc(Host, ?MODULE),
gen_server:start_link({local, Proc}, ?MODULE, Host, []).
-terminate(_Reason, _State) ->
- ok.
+terminate(_Reason, _State) -> ok.
init(Host) ->
State = parse_options(Host),
eldap_pool:start_link(State#state.eldap_id,
- State#state.servers,
- State#state.backups,
- State#state.port,
- State#state.dn,
- State#state.password,
- State#state.tls_options),
+ State#state.servers, State#state.backups,
+ State#state.port, State#state.dn,
+ State#state.password, State#state.tls_options),
eldap_pool:start_link(State#state.bind_eldap_id,
- State#state.servers,
- State#state.backups,
- State#state.port,
- State#state.dn,
- State#state.password,
- State#state.tls_options),
+ State#state.servers, State#state.backups,
+ State#state.port, State#state.dn,
+ State#state.password, State#state.tls_options),
{ok, State}.
-plain_password_required() ->
- true.
+plain_password_required() -> true.
-store_type() ->
- external.
+store_type() -> external.
check_password(User, Server, Password) ->
- %% In LDAP spec: empty password means anonymous authentication.
- %% As ejabberd is providing other anonymous authentication mechanisms
- %% we simply prevent the use of LDAP anonymous authentication.
- if Password == "" ->
- false;
- true ->
- case catch check_password_ldap(User, Server, Password) of
- {'EXIT', _} -> false;
- Result -> Result
- end
+ if Password == <<"">> -> false;
+ true ->
+ case catch check_password_ldap(User, Server, Password)
+ of
+ {'EXIT', _} -> false;
+ Result -> Result
+ end
end.
-check_password(User, Server, Password, _Digest, _DigestGen) ->
+check_password(User, Server, Password, _Digest,
+ _DigestGen) ->
check_password(User, Server, Password).
set_password(User, Server, Password) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of
- false ->
- {error, user_not_found};
- DN ->
- eldap_pool:modify_passwd(State#state.eldap_id, DN, Password)
+ false -> {error, user_not_found};
+ DN ->
+ eldap_pool:modify_passwd(State#state.eldap_id, DN,
+ Password)
end.
%% @spec (User, Server, Password) -> {error, not_allowed}
@@ -173,55 +146,56 @@ try_register(_User, _Server, _Password) ->
dirty_get_registered_users() ->
Servers = ejabberd_config:get_vh_by_auth_method(ldap),
- lists:flatmap(
- fun(Server) ->
- get_vh_registered_users(Server)
- end, Servers).
+ lists:flatmap(fun (Server) ->
+ get_vh_registered_users(Server)
+ end,
+ Servers).
get_vh_registered_users(Server) ->
case catch get_vh_registered_users_ldap(Server) of
- {'EXIT', _} -> [];
- Result -> Result
- end.
+ {'EXIT', _} -> [];
+ Result -> Result
+ end.
+
+get_vh_registered_users(Server, _) ->
+ get_vh_registered_users(Server).
get_vh_registered_users_number(Server) ->
length(get_vh_registered_users(Server)).
-get_password(_User, _Server) ->
- false.
+get_vh_registered_users_number(Server, _) ->
+ get_vh_registered_users_number(Server).
+
+get_password(_User, _Server) -> false.
-get_password_s(_User, _Server) ->
- "".
+get_password_s(_User, _Server) -> <<"">>.
%% @spec (User, Server) -> true | false | {error, Error}
is_user_exists(User, Server) ->
case catch is_user_exists_ldap(User, Server) of
- {'EXIT', Error} ->
- {error, Error};
- Result ->
- Result
+ {'EXIT', Error} -> {error, Error};
+ Result -> Result
end.
-remove_user(_User, _Server) ->
- {error, not_allowed}.
+remove_user(_User, _Server) -> {error, not_allowed}.
-remove_user(_User, _Server, _Password) ->
- not_allowed.
+remove_user(_User, _Server, _Password) -> not_allowed.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
check_password_ldap(User, Server, Password) ->
- {ok, State} = eldap_utils:get_state(Server, ?MODULE),
- case find_user_dn(User, State) of
- false ->
- false;
- DN ->
- case eldap_pool:bind(State#state.bind_eldap_id, DN, Password) of
- ok -> true;
- _ -> false
- end
- end.
+ {ok, State} = eldap_utils:get_state(Server, ?MODULE),
+ case find_user_dn(User, State) of
+ false -> false;
+ DN ->
+ case eldap_pool:bind(State#state.bind_eldap_id, DN,
+ Password)
+ of
+ ok -> true;
+ _ -> false
+ end
+ end.
get_vh_registered_users_ldap(Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
@@ -230,114 +204,123 @@ get_vh_registered_users_ldap(Server) ->
Server = State#state.host,
ResAttrs = result_attrs(State),
case eldap_filter:parse(State#state.sfilter) of
- {ok, EldapFilter} ->
- case eldap_pool:search(Eldap_ID,
- [{base, State#state.base},
- {filter, EldapFilter},
- {timeout, ?LDAP_SEARCH_TIMEOUT},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ResAttrs}]) of
- #eldap_search_result{entries = Entries} ->
- lists:flatmap(
- fun(#eldap_entry{attributes = Attrs,
- object_name = DN}) ->
+ {ok, EldapFilter} ->
+ case eldap_pool:search(Eldap_ID,
+ [{base, State#state.base},
+ {filter, EldapFilter},
+ {timeout, ?LDAP_SEARCH_TIMEOUT},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, ResAttrs}])
+ of
+ #eldap_search_result{entries = Entries} ->
+ lists:flatmap(fun (#eldap_entry{attributes = Attrs,
+ object_name = DN}) ->
case is_valid_dn(DN, Attrs, State) of
- false -> [];
- _ ->
- case eldap_utils:find_ldap_attrs(UIDs, Attrs) of
- "" -> [];
- {User, UIDFormat} ->
- case eldap_utils:get_user_part(User, UIDFormat) of
- {ok, U} ->
- case jlib:nodeprep(U) of
- error -> [];
- LU -> [{LU, jlib:nameprep(Server)}]
- end;
- _ -> []
- end
- end
+ false -> [];
+ _ ->
+ case
+ eldap_utils:find_ldap_attrs(UIDs,
+ Attrs)
+ of
+ <<"">> -> [];
+ {User, UIDFormat} ->
+ case
+ eldap_utils:get_user_part(User,
+ UIDFormat)
+ of
+ {ok, U} ->
+ case jlib:nodeprep(U) of
+ error -> [];
+ LU ->
+ [{LU,
+ jlib:nameprep(Server)}]
+ end;
+ _ -> []
+ end
+ end
end
- end, Entries);
- _ ->
- []
- end;
- _ ->
- []
- end.
+ end,
+ Entries);
+ _ -> []
+ end;
+ _ -> []
+ end.
is_user_exists_ldap(User, Server) ->
{ok, State} = eldap_utils:get_state(Server, ?MODULE),
case find_user_dn(User, State) of
- false -> false;
- _DN -> true
- end.
+ false -> false;
+ _DN -> true
+ end.
handle_call(get_state, _From, State) ->
- {reply, {ok, State}, State};
-
+ {reply, {ok, State}, State};
handle_call(stop, _From, State) ->
{stop, normal, ok, State};
-
handle_call(_Request, _From, State) ->
{reply, bad_request, State}.
find_user_dn(User, State) ->
ResAttrs = result_attrs(State),
- case eldap_filter:parse(State#state.ufilter, [{"%u", User}]) of
- {ok, Filter} ->
- case eldap_pool:search(State#state.eldap_id,
- [{base, State#state.base},
- {filter, Filter},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ResAttrs}]) of
- #eldap_search_result{entries = [#eldap_entry{attributes = Attrs,
- object_name = DN} | _]} ->
- dn_filter(DN, Attrs, State);
- _ ->
- false
- end;
- _ ->
- false
+ case eldap_filter:parse(State#state.ufilter,
+ [{<<"%u">>, User}])
+ of
+ {ok, Filter} ->
+ case eldap_pool:search(State#state.eldap_id,
+ [{base, State#state.base}, {filter, Filter},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, ResAttrs}])
+ of
+ #eldap_search_result{entries =
+ [#eldap_entry{attributes = Attrs,
+ object_name = DN}
+ | _]} ->
+ dn_filter(DN, Attrs, State);
+ _ -> false
+ end;
+ _ -> false
end.
%% apply the dn filter and the local filter:
dn_filter(DN, Attrs, State) ->
- %% Check if user is denied access by attribute value (local check)
case check_local_filter(Attrs, State) of
- false -> false;
- true -> is_valid_dn(DN, Attrs, State)
+ false -> false;
+ true -> is_valid_dn(DN, Attrs, State)
end.
%% Check that the DN is valid, based on the dn filter
-is_valid_dn(DN, _, #state{dn_filter = undefined}) ->
- DN;
-
+is_valid_dn(DN, _, #state{dn_filter = undefined}) -> DN;
is_valid_dn(DN, Attrs, State) ->
DNAttrs = State#state.dn_filter_attrs,
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, UAF} ->
- case eldap_utils:get_user_part(S, UAF) of
- {ok, U} -> [{"%u", U} | Values];
- _ -> Values
- end
- end ++ [{"%d", State#state.host}, {"%D", DN}],
- case eldap_filter:parse(State#state.dn_filter, SubstValues) of
- {ok, EldapFilter} ->
- case eldap_pool:search(State#state.eldap_id,
- [{base, State#state.base},
- {filter, EldapFilter},
- {deref_aliases, State#state.deref_aliases},
- {attributes, ["dn"]}]) of
- #eldap_search_result{entries = [_|_]} ->
- DN;
- _ ->
- false
- end;
- _ ->
- false
+ Values = [{<<"%s">>,
+ eldap_utils:get_ldap_attr(Attr, Attrs), 1}
+ || Attr <- DNAttrs],
+ SubstValues = case eldap_utils:find_ldap_attrs(UIDs,
+ Attrs)
+ of
+ <<"">> -> Values;
+ {S, UAF} ->
+ case eldap_utils:get_user_part(S, UAF) of
+ {ok, U} -> [{<<"%u">>, U} | Values];
+ _ -> Values
+ end
+ end
+ ++ [{<<"%d">>, State#state.host}, {<<"%D">>, DN}],
+ case eldap_filter:parse(State#state.dn_filter,
+ SubstValues)
+ of
+ {ok, EldapFilter} ->
+ case eldap_pool:search(State#state.eldap_id,
+ [{base, State#state.base},
+ {filter, EldapFilter},
+ {deref_aliases, State#state.deref_aliases},
+ {attributes, [<<"dn">>]}])
+ of
+ #eldap_search_result{entries = [_ | _]} -> DN;
+ _ -> false
+ end;
+ _ -> false
end.
%% The local filter is used to check an attribute in ejabberd
@@ -346,109 +329,92 @@ is_valid_dn(DN, Attrs, State) ->
%% {equal, {"accountStatus",["active"]}}
%% {notequal, {"accountStatus",["disabled"]}}
%% {ldap_local_filter, {notequal, {"accountStatus",["disabled"]}}}
-check_local_filter(_Attrs, #state{lfilter = undefined}) ->
+check_local_filter(_Attrs,
+ #state{lfilter = undefined}) ->
true;
-check_local_filter(Attrs, #state{lfilter = LocalFilter}) ->
+check_local_filter(Attrs,
+ #state{lfilter = LocalFilter}) ->
{Operation, FilterMatch} = LocalFilter,
local_filter(Operation, Attrs, FilterMatch).
-
+
local_filter(equal, Attrs, FilterMatch) ->
{Attr, Value} = FilterMatch,
case lists:keysearch(Attr, 1, Attrs) of
- false -> false;
- {value,{Attr,Value}} -> true;
- _ -> false
+ false -> false;
+ {value, {Attr, Value}} -> true;
+ _ -> false
end;
local_filter(notequal, Attrs, FilterMatch) ->
not local_filter(equal, Attrs, FilterMatch).
-result_attrs(#state{uids = UIDs, dn_filter_attrs = DNFilterAttrs}) ->
- lists:foldl(
- fun({UID}, Acc) ->
- [UID | Acc];
- ({UID, _}, Acc) ->
- [UID | Acc]
- end, DNFilterAttrs, UIDs).
+result_attrs(#state{uids = UIDs,
+ dn_filter_attrs = DNFilterAttrs}) ->
+ lists:foldl(fun ({UID}, Acc) -> [UID | Acc];
+ ({UID, _}, Acc) -> [UID | Acc]
+ end,
+ DNFilterAttrs, UIDs).
%%%----------------------------------------------------------------------
%%% Auxiliary functions
%%%----------------------------------------------------------------------
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)),
- LDAPServers = ejabberd_config:get_local_option({ldap_servers, Host}),
- LDAPBackups = case ejabberd_config:get_local_option({ldap_backups, Host}) of
- undefined -> [];
- Backups -> Backups
- end,
- LDAPEncrypt = ejabberd_config:get_local_option({ldap_encrypt, Host}),
- LDAPTLSVerify = ejabberd_config:get_local_option({ldap_tls_verify, Host}),
- LDAPTLSCAFile = ejabberd_config:get_local_option({ldap_tls_cacertfile, Host}),
- LDAPTLSDepth = ejabberd_config:get_local_option({ldap_tls_depth, Host}),
- LDAPPort = case ejabberd_config:get_local_option({ldap_port, Host}) of
- undefined -> case LDAPEncrypt of
- tls -> ?LDAPS_PORT;
- starttls -> ?LDAP_PORT;
- _ -> ?LDAP_PORT
- end;
- P -> P
- end,
- RootDN = case ejabberd_config:get_local_option({ldap_rootdn, Host}) of
- undefined -> "";
- RDN -> RDN
- end,
- Password = case ejabberd_config:get_local_option({ldap_password, Host}) of
- undefined -> "";
- Pass -> Pass
- end,
- UIDs = case ejabberd_config:get_local_option({ldap_uids, Host}) of
- undefined -> [{"uid", "%u"}];
- UI -> eldap_utils:uids_domain_subst(Host, UI)
- end,
- SubFilter = lists:flatten(eldap_utils:generate_subfilter(UIDs)),
- UserFilter = case ejabberd_config:get_local_option({ldap_filter, Host}) of
- undefined -> SubFilter;
- "" -> SubFilter;
- F ->
- eldap_utils:check_filter(F),
- "(&" ++ SubFilter ++ F ++ ")"
- end,
- SearchFilter = eldap_filter:do_sub(UserFilter, [{"%u", "*"}]),
- LDAPBase = ejabberd_config:get_local_option({ldap_base, Host}),
+ Cfg = eldap_utils:get_config(Host, []),
+ Eldap_ID = jlib:atom_to_binary(gen_mod:get_module_proc(Host, ?MODULE)),
+ Bind_Eldap_ID = jlib:atom_to_binary(
+ gen_mod:get_module_proc(Host, bind_ejabberd_auth_ldap)),
+ UIDsTemp = eldap_utils:get_opt(
+ {ldap_uids, Host}, [],
+ fun(Us) ->
+ lists:map(
+ fun({U, P}) ->
+ {iolist_to_binary(U),
+ iolist_to_binary(P)};
+ ({U}) ->
+ {iolist_to_binary(U)}
+ end, Us)
+ end, [{<<"uid">>, <<"%u">>}]),
+ UIDs = eldap_utils:uids_domain_subst(Host, UIDsTemp),
+ SubFilter = eldap_utils:generate_subfilter(UIDs),
+ UserFilter = case eldap_utils:get_opt(
+ {ldap_filter, Host}, [],
+ fun check_filter/1, <<"">>) of
+ <<"">> ->
+ SubFilter;
+ F ->
+ <<"(&", SubFilter/binary, F/binary, ")">>
+ end,
+ SearchFilter = eldap_filter:do_sub(UserFilter,
+ [{<<"%u">>, <<"*">>}]),
{DNFilter, DNFilterAttrs} =
- case ejabberd_config:get_local_option({ldap_dn_filter, Host}) of
- undefined ->
- {undefined, []};
- {DNF, undefined} ->
- {DNF, []};
- {DNF, DNFA} ->
- {DNF, DNFA}
- end,
- eldap_utils:check_filter(DNFilter),
- LocalFilter = ejabberd_config:get_local_option({ldap_local_filter, Host}),
- DerefAliases = case ejabberd_config:get_local_option(
- {ldap_deref_aliases, Host}) of
- undefined -> never;
- Val -> Val
- end,
- #state{host = Host,
- eldap_id = Eldap_ID,
- bind_eldap_id = Bind_Eldap_ID,
- servers = LDAPServers,
- backups = LDAPBackups,
- port = LDAPPort,
- tls_options = [{encrypt, LDAPEncrypt},
- {tls_verify, LDAPTLSVerify},
- {tls_cacertfile, LDAPTLSCAFile},
- {tls_depth, LDAPTLSDepth}],
- dn = RootDN,
- password = Password,
- base = LDAPBase,
- uids = UIDs,
- ufilter = UserFilter,
- sfilter = SearchFilter,
- lfilter = LocalFilter,
- deref_aliases = DerefAliases,
- dn_filter = DNFilter,
- dn_filter_attrs = DNFilterAttrs
- }.
+ eldap_utils:get_opt({ldap_dn_filter, Host}, [],
+ fun({DNF, DNFA}) ->
+ NewDNFA = case DNFA of
+ undefined ->
+ [];
+ _ ->
+ [iolist_to_binary(A)
+ || A <- DNFA]
+ end,
+ NewDNF = check_filter(DNF),
+ {NewDNF, NewDNFA}
+ end, {undefined, []}),
+ LocalFilter = eldap_utils:get_opt(
+ {ldap_local_filter, Host}, [], fun(V) -> V end),
+ #state{host = Host, eldap_id = Eldap_ID,
+ bind_eldap_id = Bind_Eldap_ID,
+ servers = Cfg#eldap_config.servers,
+ backups = Cfg#eldap_config.backups,
+ port = Cfg#eldap_config.port,
+ tls_options = Cfg#eldap_config.tls_options,
+ dn = Cfg#eldap_config.dn,
+ password = Cfg#eldap_config.password,
+ base = Cfg#eldap_config.base,
+ deref_aliases = Cfg#eldap_config.deref_aliases,
+ uids = UIDs, ufilter = UserFilter,
+ sfilter = SearchFilter, lfilter = LocalFilter,
+ dn_filter = DNFilter, dn_filter_attrs = DNFilterAttrs}.
+
+check_filter(F) ->
+ NewF = iolist_to_binary(F),
+ {ok, _} = eldap_filter:parse(NewF),
+ NewF.