Packages
ejabberd
19.5.0
26.4.0
26.3.0
26.2.0
26.1.0
25.10.0
25.8.0
25.7.0
25.4.0
25.3.0
24.12.0
24.10.0
24.7.0
24.6.0
24.2.6
23.10.0
23.4.0
23.1.0
22.10.0
22.5.0
21.12.0
21.7.0
21.4.0
21.1.0
20.12.0
20.7.0
20.4.0
20.3.0
20.2.0
20.1.0
19.9.1
19.9.0
19.8.0
19.5.0
19.2.0
18.12.1
18.12.0
18.6.0
18.4.0
18.3.0
18.1.0
17.11.0
17.9.0
17.6.0
17.3.0
17.1.0
16.12.0-beta1
16.9.0
16.8.0
16.6.2
16.6.1
16.6.0
16.4.1
16.4.0
16.3.0
16.2.0
16.1.0-beta1
Robust, Ubiquitous and Massively Scalable Messaging Platform (XMPP, MQTT, SIP Server)
Current section
Files
Jump to
Current section
Files
src/eldap_utils.erl
%%%----------------------------------------------------------------------
%%% File : eldap_utils.erl
%%% Author : Mickael Remond <mremond@process-one.net>
%%% Purpose : ejabberd LDAP helper functions
%%% Created : 12 Oct 2006 by Mickael Remond <mremond@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2019 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
%%% published by the Free Software Foundation; either version 2 of the
%%% License, or (at your option) any later version.
%%%
%%% This program is distributed in the hope that it will be useful,
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
%%% General Public License for more details.
%%%
%%% You should have received a copy of the GNU General Public License along
%%% with this program; if not, write to the Free Software Foundation, Inc.,
%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
%%%
%%%----------------------------------------------------------------------
-module(eldap_utils).
-behaviour(ejabberd_config).
-author('mremond@process-one.net').
-export([generate_subfilter/1, find_ldap_attrs/2, check_filter/1,
get_ldap_attr/2, get_user_part/2, make_filter/2,
get_state/2, case_insensitive_match/2, get_config/2,
decode_octet_string/3, uids_domain_subst/2, opt_type/1,
options/1]).
-include("logger.hrl").
-include("eldap.hrl").
%% 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) ->
iolist_to_binary(["(|", [subfilter(UID) || UID <- UIDs], ")"]).
%% Subfilter for a single attribute
subfilter({UIDAttr, UIDAttrFormat}) ->
%% The default UiDAttrFormat is %u
<<$(, UIDAttr/binary, $=, UIDAttrFormat/binary, $)>>;
%% The default UiDAttrFormat is <<"%u">>
subfilter({UIDAttr}) ->
<<$(, UIDAttr/binary, $=, "%u)">>.
%% Not tail-recursive, but it is not very terribly.
%% It stops finding on the first not empty value.
-spec find_ldap_attrs([{binary()} | {binary(), binary()}],
[{binary(), [binary()]}]) -> <<>> | {binary(), binary()}.
find_ldap_attrs([{Attr} | Rest], Attributes) ->
find_ldap_attrs([{Attr, <<"%u">>} | Rest], Attributes);
find_ldap_attrs([{Attr, Format} | Rest], Attributes) ->
case get_ldap_attr(Attr, Attributes) of
Value when is_binary(Value), Value /= <<>> ->
{Value, Format};
_ ->
find_ldap_attrs(Rest, Attributes)
end;
find_ldap_attrs([], _) ->
<<>>.
-spec get_ldap_attr(binary(), [{binary(), [binary()]}]) -> binary().
get_ldap_attr(LDAPAttr, Attributes) ->
Res = lists:filter(
fun({Name, _}) ->
case_insensitive_match(Name, LDAPAttr)
end, Attributes),
case Res of
[{_, [Value|_]}] -> Value;
_ -> <<>>
end.
-spec get_user_part(binary(), binary()) -> {ok, binary()} | {error, badmatch}.
get_user_part(String, Pattern) ->
F = fun(S, P) ->
First = str:str(P, <<"%u">>),
TailLength = byte_size(P) - (First+1),
str:sub_string(S, First, byte_size(S) - TailLength)
end,
case catch F(String, Pattern) of
{'EXIT', _} ->
{error, badmatch};
Result ->
case catch ejabberd_regexp:replace(Pattern, <<"%u">>, Result) of
{'EXIT', _} ->
{error, badmatch};
StringRes ->
case case_insensitive_match(StringRes, String) of
true ->
{ok, Result};
false ->
{error, badmatch}
end
end
end.
-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}]) -> any().
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(
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.
check_filter(F) ->
NewF = iolist_to_binary(F),
{ok, _} = eldap_filter:parse(NewF),
NewF.
-spec case_insensitive_match(binary(), binary()) -> boolean().
case_insensitive_match(X, Y) ->
X1 = str:to_lower(X),
Y1 = str:to_lower(Y),
if
X1 == Y1 -> true;
true -> false
end.
get_state(Server, Module) ->
Proc = gen_mod:get_module_proc(Server, Module),
gen_server:call(Proc, get_state).
%% From the list of uids attribute:
%% we look from alias domain (%d) and make the substitution
%% with the actual host domain
%% This help when you need to configure many virtual domains.
-spec uids_domain_subst(binary(), [{binary(), binary()}]) ->
[{binary(), binary()}].
uids_domain_subst(Host, UIDs) ->
lists:map(fun({U,V}) ->
{U, eldap_filter:do_sub(V,[{<<"%d">>, Host}])};
(A) -> A
end,
UIDs).
-spec get_config(binary(), list()) -> eldap_config().
get_config(Host, Opts) ->
Servers = get_opt(ldap_servers, Host, Opts, [<<"localhost">>]),
Backups = get_opt(ldap_backups, Host, Opts, []),
Encrypt = get_opt(ldap_encrypt, Host, Opts, none),
TLSVerify = get_opt(ldap_tls_verify, Host, Opts, false),
TLSCertFile = get_opt(ldap_tls_certfile, Host, Opts),
TLSCAFile = get_opt(ldap_tls_cacertfile, Host, Opts),
TLSDepth = get_opt(ldap_tls_depth, Host, Opts),
Port = case get_opt(ldap_port, Host, Opts) of
undefined ->
case Encrypt of
tls -> ?LDAPS_PORT;
starttls -> ?LDAP_PORT;
_ -> ?LDAP_PORT
end;
P ->
P
end,
RootDN = get_opt(ldap_rootdn, Host, Opts, <<"">>),
Password = get_opt(ldap_password, Host, Opts, <<"">>),
Base = get_opt(ldap_base, Host, Opts, <<"">>),
OldDerefAliases = get_opt(deref_aliases, Host, Opts, unspecified),
DerefAliases =
if OldDerefAliases == unspecified ->
get_opt(ldap_deref_aliases, Host, Opts, never);
true ->
?WARNING_MSG("Option 'deref_aliases' is deprecated. "
"The option is still supported "
"but it is better to fix your config: "
"use 'ldap_deref_aliases' instead.", []),
OldDerefAliases
end,
#eldap_config{servers = Servers,
backups = Backups,
tls_options = [{encrypt, Encrypt},
{tls_verify, TLSVerify},
{tls_certfile, TLSCertFile},
{tls_cacertfile, TLSCAFile},
{tls_depth, TLSDepth}],
port = Port,
dn = RootDN,
password = Password,
base = Base,
deref_aliases = DerefAliases}.
get_opt(Opt, Host, Opts) ->
get_opt(Opt, Host, Opts, undefined).
get_opt(Opt, Host, Opts, Default) ->
case proplists:get_value(Opt, Opts) of
undefined -> ejabberd_config:get_option({Opt, Host}, Default);
Value -> Value
end.
%%----------------------------------------
%% Borrowed from asn1rt_ber_bin_v2.erl
%%----------------------------------------
%%% The tag-number for universal types
-define(N_BOOLEAN, 1).
-define(N_INTEGER, 2).
-define(N_BIT_STRING, 3).
-define(N_OCTET_STRING, 4).
-define(N_NULL, 5).
-define(N_OBJECT_IDENTIFIER, 6).
-define(N_OBJECT_DESCRIPTOR, 7).
-define(N_EXTERNAL, 8).
-define(N_REAL, 9).
-define(N_ENUMERATED, 10).
-define(N_EMBEDDED_PDV, 11).
-define(N_SEQUENCE, 16).
-define(N_SET, 17).
-define(N_NumericString, 18).
-define(N_PrintableString, 19).
-define(N_TeletexString, 20).
-define(N_VideotexString, 21).
-define(N_IA5String, 22).
-define(N_UTCTime, 23).
-define(N_GeneralizedTime, 24).
-define(N_GraphicString, 25).
-define(N_VisibleString, 26).
-define(N_GeneralString, 27).
-define(N_UniversalString, 28).
-define(N_BMPString, 30).
decode_octet_string(Buffer, Range, Tags) ->
% NewTags = new_tags(HasTag,#tag{class=?UNIVERSAL,number=?N_OCTET_STRING}),
decode_restricted_string(Buffer, Range, Tags).
decode_restricted_string(Tlv, Range, TagsIn) ->
Val = match_tags(Tlv, TagsIn),
Val2 =
case Val of
PartList = [_H|_T] -> % constructed val
collect_parts(PartList);
Bin ->
Bin
end,
check_and_convert_restricted_string(Val2, Range).
check_and_convert_restricted_string(Val, Range) ->
{StrLen,NewVal} = if is_binary(Val) ->
{size(Val), Val};
true ->
{length(Val), list_to_binary(Val)}
end,
case Range of
[] -> % No length constraint
NewVal;
{Lb,Ub} when StrLen >= Lb, Ub >= StrLen -> % variable length constraint
NewVal;
{{Lb,_Ub},[]} when StrLen >= Lb ->
NewVal;
{{Lb,_Ub},_Ext=[Min|_]} when StrLen >= Lb; StrLen >= Min ->
NewVal;
{{Lb1,Ub1},{Lb2,Ub2}} when StrLen >= Lb1, StrLen =< Ub1;
StrLen =< Ub2, StrLen >= Lb2 ->
NewVal;
StrLen -> % fixed length constraint
NewVal;
{_,_} ->
exit({error,{asn1,{length,Range,Val}}});
_Len when is_integer(_Len) ->
exit({error,{asn1,{length,Range,Val}}});
_ -> % some strange constraint that we don't support yet
NewVal
end.
%%----------------------------------------
%% Decode the in buffer to bits
%%----------------------------------------
match_tags({T,V},[T]) ->
V;
match_tags({T,V}, [T|Tt]) ->
match_tags(V,Tt);
match_tags([{T,V}],[T|Tt]) ->
match_tags(V, Tt);
match_tags(Vlist = [{T,_V}|_], [T]) ->
Vlist;
match_tags(Tlv, []) ->
Tlv;
match_tags({Tag,_V},[T|_Tt]) ->
{error,{asn1,{wrong_tag,{Tag,T}}}}.
collect_parts(TlvList) ->
collect_parts(TlvList,[]).
collect_parts([{_,L}|Rest],Acc) when is_list(L) ->
collect_parts(Rest,[collect_parts(L)|Acc]);
collect_parts([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],_Acc) ->
collect_parts_bit(Rest,[Bits],Unused);
collect_parts([{_T,V}|Rest],Acc) ->
collect_parts(Rest,[V|Acc]);
collect_parts([],Acc) ->
list_to_binary(lists:reverse(Acc)).
collect_parts_bit([{?N_BIT_STRING,<<Unused,Bits/binary>>}|Rest],Acc,Uacc) ->
collect_parts_bit(Rest,[Bits|Acc],Unused+Uacc);
collect_parts_bit([],Acc,Uacc) ->
list_to_binary([Uacc|lists:reverse(Acc)]).
-spec opt_type(atom()) -> fun((any()) -> any()) | [atom()].
opt_type(deref_aliases) ->
fun(unspecified) -> unspecified;
(never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_backups) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_base) -> fun iolist_to_binary/1;
opt_type(ldap_deref_aliases) ->
fun (never) -> never;
(searching) -> searching;
(finding) -> finding;
(always) -> always
end;
opt_type(ldap_encrypt) ->
fun (tls) -> tls;
(starttls) -> starttls;
(none) -> none
end;
opt_type(ldap_password) -> fun iolist_to_binary/1;
opt_type(ldap_port) ->
fun(undefined) -> undefined;
(I) when is_integer(I), I > 0 -> I
end;
opt_type(ldap_rootdn) -> fun iolist_to_binary/1;
opt_type(ldap_servers) ->
fun (L) -> [iolist_to_binary(H) || H <- L] end;
opt_type(ldap_tls_certfile) ->
fun(undefined) -> undefined;
(S) -> binary_to_list(ejabberd_pkix:try_certfile(S))
end;
opt_type(ldap_tls_cacertfile) ->
fun(undefined) -> undefined;
(S) -> binary_to_list(misc:try_read_file(S))
end;
opt_type(ldap_tls_depth) ->
fun(undefined) -> undefined;
(I) when is_integer(I), I >= 0 -> I
end;
opt_type(ldap_tls_verify) ->
fun (hard) -> hard;
(soft) -> soft;
(false) -> false
end;
opt_type(ldap_filter) ->
fun(<<"">>) -> <<"">>;
(F) -> check_filter(F)
end;
opt_type(ldap_uids) ->
fun (Us) ->
lists:map(fun ({U, P}) ->
{iolist_to_binary(U), iolist_to_binary(P)};
({U}) -> {iolist_to_binary(U)};
(U) -> {iolist_to_binary(U)}
end,
lists:flatten(Us))
end;
opt_type(_) ->
[deref_aliases, ldap_backups, ldap_base, ldap_uids,
ldap_deref_aliases, ldap_encrypt, ldap_password,
ldap_port, ldap_rootdn, ldap_servers, ldap_filter,
ldap_tls_certfile, ldap_tls_cacertfile, ldap_tls_depth,
ldap_tls_verify].
options(_) ->
[{deref_aliases, unspecified},
{ldap_backups, []},
{ldap_base, <<"">>},
{ldap_uids, [{<<"uid">>, <<"%u">>}]},
{ldap_deref_aliases, never},
{ldap_encrypt, none},
{ldap_password, <<"">>},
{ldap_port, undefined},
{ldap_rootdn, <<"">>},
{ldap_servers, [<<"localhost">>]},
{ldap_filter, <<"">>},
{ldap_tls_certfile, undefined},
{ldap_tls_cacertfile, undefined},
{ldap_tls_depth, undefined},
{ldap_tls_verify, false}].