1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : eldap_utils.erl |
3 |
|
%%% Author : Mickael Remond <mremond@process-one.net> |
4 |
|
%%% Purpose : ejabberd LDAP helper functions |
5 |
|
%%% Created : 12 Oct 2006 by Mickael Remond <mremond@process-one.net> |
6 |
|
%%% |
7 |
|
%%% |
8 |
|
%%% ejabberd, Copyright (C) 2002-2013 ProcessOne |
9 |
|
%%% |
10 |
|
%%% This program is free software; you can redistribute it and/or |
11 |
|
%%% modify it under the terms of the GNU General Public License as |
12 |
|
%%% published by the Free Software Foundation; either version 2 of the |
13 |
|
%%% License, or (at your option) any later version. |
14 |
|
%%% |
15 |
|
%%% This program is distributed in the hope that it will be useful, |
16 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
17 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
18 |
|
%%% General Public License for more details. |
19 |
|
%%% |
20 |
|
%%% You should have received a copy of the GNU General Public License |
21 |
|
%%% along with this program; if not, write to the Free Software |
22 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
23 |
|
%%% |
24 |
|
%%%---------------------------------------------------------------------- |
25 |
|
|
26 |
|
-module(eldap_utils). |
27 |
|
-author('mremond@process-one.net'). |
28 |
|
|
29 |
|
-export([generate_subfilter/1, |
30 |
|
find_ldap_attrs/2, |
31 |
|
get_ldap_attr/2, |
32 |
|
get_user_part/2, |
33 |
|
make_filter/2, |
34 |
|
make_filter/3, |
35 |
|
get_state/2, |
36 |
|
case_insensitive_match/2, |
37 |
|
get_mod_opt/4, |
38 |
|
get_base/1, |
39 |
|
get_deref_aliases/1, |
40 |
|
deref_aliases/1, |
41 |
|
get_uids/2, |
42 |
|
get_user_filter/2, |
43 |
|
process_user_filter/2, |
44 |
|
get_search_filter/1, |
45 |
|
decode_octet_string/3, |
46 |
|
uids_domain_subst/2, |
47 |
|
singleton_value/1, |
48 |
|
maybe_list2b/1, |
49 |
|
maybe_b2list/1]). |
50 |
|
|
51 |
|
-ignore_xref([decode_octet_string/3, generate_subfilter/1, make_filter/2, uids_domain_subst/2]). |
52 |
|
|
53 |
|
-include("mongoose.hrl"). |
54 |
|
-include("eldap.hrl"). |
55 |
|
|
56 |
|
-type dn() :: binary(). |
57 |
|
-type deref() :: neverDerefAliases | derefInSearching |
58 |
|
| derefFindingBaseObj | derefAlways. |
59 |
|
%% Used to access mongoose_wpool |
60 |
|
-type eldap_id() :: {HostType :: mongooseim:host_type(), Tag :: mongoose_wpool:tag()}. |
61 |
|
|
62 |
|
-export_type([dn/0, |
63 |
|
deref/0, |
64 |
|
eldap_id/0]). |
65 |
|
|
66 |
|
%% @doc Generate an 'or' LDAP query on one or several attributes |
67 |
|
%% If there is only one attribute |
68 |
|
-spec generate_subfilter([{binary()} | {binary(), binary()}]) -> binary(). |
69 |
|
generate_subfilter([UID]) -> |
70 |
:-( |
subfilter(UID); |
71 |
|
%% If there is several attributes |
72 |
|
generate_subfilter(UIDs) -> |
73 |
:-( |
iolist_to_binary(["(|", [subfilter(UID) || UID <- UIDs], ")"]). |
74 |
|
|
75 |
|
|
76 |
|
%% @doc Subfilter for a single attribute |
77 |
|
-spec subfilter({binary()} | {binary(), binary()}) -> binary(). |
78 |
|
subfilter({UIDAttr, UIDAttrFormat}) -> |
79 |
|
%% The default UiDAttrFormat is %u |
80 |
:-( |
<<$(, UIDAttr/binary, $=, UIDAttrFormat/binary, $)>>; |
81 |
|
subfilter({UIDAttr}) -> |
82 |
|
%% The default UiDAttrFormat is <<"%u">> |
83 |
:-( |
<<$(, UIDAttr/binary, $=, "%u)">>. |
84 |
|
|
85 |
|
|
86 |
|
%% @doc Not tail-recursive, but it is not very terribly. |
87 |
|
%% It stops finding on the first not empty value. |
88 |
|
-spec find_ldap_attrs([{binary()} | {binary(), binary()}], |
89 |
|
[{binary(), [binary()]}]) -> <<>> | {binary(), binary()}. |
90 |
|
find_ldap_attrs([{Attr} | Rest], Attributes) -> |
91 |
:-( |
find_ldap_attrs([{Attr, <<"%u">>} | Rest], Attributes); |
92 |
|
find_ldap_attrs([{Attr, Format} | Rest], Attributes) -> |
93 |
:-( |
case get_ldap_attr(Attr, Attributes) of |
94 |
|
Value when Value /= <<>>, Value /= [] -> |
95 |
:-( |
{Value, Format}; |
96 |
|
_ -> |
97 |
:-( |
find_ldap_attrs(Rest, Attributes) |
98 |
|
end; |
99 |
|
find_ldap_attrs([], _) -> |
100 |
:-( |
<<>>. |
101 |
|
|
102 |
|
|
103 |
|
-spec get_ldap_attr(binary(), [{binary(), [binary()]}]) -> binary(). |
104 |
|
get_ldap_attr(LDAPAttr, Attributes) -> |
105 |
:-( |
Res = lists:filter( |
106 |
|
fun({Name, _}) -> |
107 |
:-( |
case_insensitive_match(Name, LDAPAttr) |
108 |
|
end, Attributes), |
109 |
:-( |
case singleton_value(Res) of |
110 |
:-( |
{_, Value} -> eldap_utils:maybe_list2b(Value); |
111 |
:-( |
_ -> <<>> |
112 |
|
end. |
113 |
|
|
114 |
|
|
115 |
|
-spec get_user_part(binary(), binary()) -> {ok, binary()} | {error, badmatch}. |
116 |
|
get_user_part(String, Pattern) -> |
117 |
:-( |
F = fun(S, P) -> |
118 |
:-( |
{First, _} = binary:match(P, <<"%u">>), |
119 |
:-( |
TailLength = byte_size(P) - (First+1), |
120 |
:-( |
binary:part(S, First, byte_size(S)-TailLength-First+1) |
121 |
|
end, |
122 |
:-( |
case catch F(String, Pattern) of |
123 |
|
{'EXIT', _} -> |
124 |
:-( |
{error, badmatch}; |
125 |
|
Result -> |
126 |
:-( |
case catch re:replace(Pattern, <<"%u">>, Result, [global, {return, binary}]) of |
127 |
|
{'EXIT', _} -> |
128 |
:-( |
{error, badmatch}; |
129 |
|
StringRes -> |
130 |
:-( |
case case_insensitive_match(StringRes, String) of |
131 |
|
true -> |
132 |
:-( |
{ok, Result}; |
133 |
|
false -> |
134 |
:-( |
{error, badmatch} |
135 |
|
end |
136 |
|
end |
137 |
|
end. |
138 |
|
|
139 |
|
|
140 |
|
-spec generate_substring_list(binary()) |
141 |
|
-> [{'any', binary()} | {'final', binary()} | {'initial', binary()}]. |
142 |
|
generate_substring_list(Value)-> |
143 |
:-( |
Splits = binary:split(Value, <<"*">>, [global]), |
144 |
:-( |
{Acc, S}=case Splits of |
145 |
:-( |
[<<"">>|T]->{[], maybe_b2list(T)}; |
146 |
:-( |
[H|T]-> {[{initial, maybe_b2list(H)}], T} |
147 |
|
end, |
148 |
:-( |
lists:reverse(generate_substring_list(S, Acc)). |
149 |
|
generate_substring_list([<<"">>], Acc)-> |
150 |
:-( |
Acc; |
151 |
|
generate_substring_list([Last], Acc)-> |
152 |
:-( |
[{final, Last}|Acc]; |
153 |
|
generate_substring_list([H|T], Acc)-> |
154 |
:-( |
generate_substring_list(T, [{any, H}|Acc]). |
155 |
|
|
156 |
|
|
157 |
|
-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}]) -> any(). |
158 |
|
make_filter(Data, UIDs) -> |
159 |
:-( |
make_filter(Data, UIDs, 'and'). |
160 |
|
|
161 |
|
-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}], |
162 |
|
'or' | 'and') -> any(). |
163 |
|
make_filter(Data, UIDs, Op) -> |
164 |
:-( |
NewUIDs = [{U, eldap_filter:do_sub( |
165 |
:-( |
UF, [{<<"%u">>, <<"*%u*">>, 1}])} || {U, UF} <- UIDs], |
166 |
:-( |
Filter = lists:flatmap( |
167 |
|
traverse_filter_fun(NewUIDs), Data), |
168 |
:-( |
case Filter of |
169 |
|
[F] -> |
170 |
:-( |
F; |
171 |
|
_ -> |
172 |
:-( |
eldap:Op(Filter) |
173 |
|
end. |
174 |
|
|
175 |
|
traverse_filter_fun(NewUIDs) -> |
176 |
:-( |
fun(Entry) -> |
177 |
:-( |
match_filter_name(Entry, NewUIDs) |
178 |
|
end. |
179 |
|
|
180 |
|
match_filter_name({<<"%u">>, [Value | _]}, NewUIDs) when Value /= <<"">> -> |
181 |
:-( |
case eldap_filter:parse( |
182 |
|
generate_subfilter(NewUIDs), |
183 |
|
[{<<"%u">>, Value}]) of |
184 |
:-( |
{ok, F} -> [F]; |
185 |
:-( |
_ -> [] |
186 |
|
end; |
187 |
|
match_filter_name({Name, [Value | _]}, _NewUIDs) when Value /= <<"">> -> |
188 |
:-( |
case binary:match(Value, <<"*">>) of |
189 |
:-( |
nomatch -> [eldap:equalityMatch(Name, Value)]; |
190 |
:-( |
_ -> [eldap:substrings(maybe_b2list(Name), |
191 |
|
generate_substring_list(Value))] |
192 |
|
end; |
193 |
|
match_filter_name(_, _) -> |
194 |
:-( |
[]. |
195 |
|
|
196 |
|
-spec case_insensitive_match(binary(), binary()) -> boolean(). |
197 |
|
case_insensitive_match(X, Y) -> |
198 |
:-( |
X1 = string:to_lower(maybe_b2list(X)), |
199 |
:-( |
Y1 = string:to_lower(maybe_b2list(Y)), |
200 |
:-( |
case X1 == Y1 of |
201 |
:-( |
true -> true; |
202 |
:-( |
_-> false |
203 |
|
end. |
204 |
|
|
205 |
|
|
206 |
|
-spec get_state(mongooseim:host_type(), atom()) -> any(). |
207 |
|
get_state(HostType, Module) -> |
208 |
:-( |
Proc = gen_mod:get_module_proc(HostType, Module), |
209 |
:-( |
gen_server:call(Proc, get_state). |
210 |
|
|
211 |
|
|
212 |
|
%% @doc From the list of uids attribute: we look from alias domain (%d) and make |
213 |
|
%% the substitution with the actual host domain. This helps when you need to |
214 |
|
%% configure many virtual domains. |
215 |
|
-spec uids_domain_subst(binary(), [{binary(), binary()}]) -> |
216 |
|
[{binary(), binary()}]. |
217 |
|
uids_domain_subst(Host, UIDs) -> |
218 |
:-( |
lists:map(fun({U, V}) -> |
219 |
:-( |
{U, eldap_filter:do_sub(V, [{<<"%d">>, Host}])}; |
220 |
:-( |
(A) -> A |
221 |
|
end, |
222 |
|
UIDs). |
223 |
|
|
224 |
|
|
225 |
|
-spec get_mod_opt(atom(), list(), fun(), any()) -> any(). |
226 |
|
get_mod_opt(Key, Opts, F, Default) -> |
227 |
:-( |
case gen_mod:get_opt(Key, Opts, Default) of |
228 |
|
Default -> |
229 |
:-( |
Default; |
230 |
|
Val -> |
231 |
:-( |
prepare_opt_val(Key, Val, F, Default) |
232 |
|
end. |
233 |
|
|
234 |
|
-type check_fun() :: fun((any()) -> any()) | {module(), atom()}. |
235 |
|
-spec prepare_opt_val(any(), any(), check_fun(), any()) -> any(). |
236 |
|
prepare_opt_val(Opt, Val, F, Default) -> |
237 |
:-( |
Res = case F of |
238 |
|
{Mod, Fun} -> |
239 |
:-( |
catch Mod:Fun(Val); |
240 |
|
_ -> |
241 |
:-( |
catch F(Val) |
242 |
|
end, |
243 |
:-( |
case Res of |
244 |
|
{'EXIT', _} -> |
245 |
:-( |
?LOG_ERROR(#{what => configuration_error, option => Opt, |
246 |
:-( |
value => Val, default => Default}), |
247 |
:-( |
Default; |
248 |
|
_ -> |
249 |
:-( |
Res |
250 |
|
end. |
251 |
|
|
252 |
|
get_base(Opts) -> |
253 |
:-( |
get_mod_opt(ldap_base, Opts, fun iolist_to_binary/1, <<"">>). |
254 |
|
|
255 |
|
get_deref_aliases(Opts) -> |
256 |
:-( |
get_mod_opt(ldap_deref, Opts, fun deref_aliases/1, neverDerefAliases). |
257 |
|
|
258 |
:-( |
deref_aliases(never) -> neverDerefAliases; |
259 |
:-( |
deref_aliases(searching) -> derefInSearching; |
260 |
:-( |
deref_aliases(finding) -> derefFindingBaseObj; |
261 |
:-( |
deref_aliases(always) -> derefAlways. |
262 |
|
|
263 |
|
get_uids(Host, Opts) -> |
264 |
:-( |
UIDsTemp = get_mod_opt(ldap_uids, Opts, fun(V) -> V end, [{<<"uid">>, <<"%u">>}]), |
265 |
:-( |
uids_domain_subst(Host, UIDsTemp). |
266 |
|
|
267 |
|
get_user_filter(UIDs, Opts) -> |
268 |
:-( |
RawUserFilter = get_mod_opt(ldap_filter, Opts, fun(V) -> V end, <<>>), |
269 |
:-( |
process_user_filter(UIDs, RawUserFilter). |
270 |
|
|
271 |
|
process_user_filter(UIDs, RawUserFilter) -> |
272 |
:-( |
SubFilter = generate_subfilter(UIDs), |
273 |
:-( |
case RawUserFilter of |
274 |
|
<<>> -> |
275 |
:-( |
SubFilter; |
276 |
|
F -> |
277 |
:-( |
<<"(&", SubFilter/binary, F/binary, ")">> |
278 |
|
end. |
279 |
|
|
280 |
|
get_search_filter(UserFilter) -> |
281 |
:-( |
eldap_filter:do_sub(UserFilter, [{<<"%u">>, <<"*">>}]). |
282 |
|
|
283 |
|
-spec singleton_value(list()) -> {binary(), binary()} | false. |
284 |
|
singleton_value([{K, [V]}]) -> |
285 |
:-( |
{K, V}; |
286 |
|
singleton_value([{_K, _V} = I]) -> |
287 |
:-( |
I; |
288 |
|
singleton_value(_) -> |
289 |
:-( |
false. |
290 |
|
%%---------------------------------------- |
291 |
|
%% Borrowed from asn1rt_ber_bin_v2.erl |
292 |
|
%%---------------------------------------- |
293 |
|
|
294 |
|
%%% The tag-number for universal types |
295 |
|
-define(N_BOOLEAN, 1). |
296 |
|
-define(N_INTEGER, 2). |
297 |
|
-define(N_BIT_STRING, 3). |
298 |
|
-define(N_OCTET_STRING, 4). |
299 |
|
-define(N_NULL, 5). |
300 |
|
-define(N_OBJECT_IDENTIFIER, 6). |
301 |
|
-define(N_OBJECT_DESCRIPTOR, 7). |
302 |
|
-define(N_EXTERNAL, 8). |
303 |
|
-define(N_REAL, 9). |
304 |
|
-define(N_ENUMERATED, 10). |
305 |
|
-define(N_EMBEDDED_PDV, 11). |
306 |
|
-define(N_SEQUENCE, 16). |
307 |
|
-define(N_SET, 17). |
308 |
|
-define(N_NumericString, 18). |
309 |
|
-define(N_PrintableString, 19). |
310 |
|
-define(N_TeletexString, 20). |
311 |
|
-define(N_VideotexString, 21). |
312 |
|
-define(N_IA5String, 22). |
313 |
|
-define(N_UTCTime, 23). |
314 |
|
-define(N_GeneralizedTime, 24). |
315 |
|
-define(N_GraphicString, 25). |
316 |
|
-define(N_VisibleString, 26). |
317 |
|
-define(N_GeneralString, 27). |
318 |
|
-define(N_UniversalString, 28). |
319 |
|
-define(N_BMPString, 30). |
320 |
|
|
321 |
|
|
322 |
|
-spec decode_octet_string(_, _, list()) -> binary(). |
323 |
|
decode_octet_string(Buffer, Range, Tags) -> |
324 |
|
% NewTags = new_tags(HasTag, #tag{class=?UNIVERSAL, number=?N_OCTET_STRING}), |
325 |
:-( |
decode_restricted_string(Buffer, Range, Tags). |
326 |
|
|
327 |
|
|
328 |
|
-spec decode_restricted_string(_, _, list()) -> binary(). |
329 |
|
decode_restricted_string(Tlv, Range, TagsIn) -> |
330 |
:-( |
Val = match_tags(Tlv, TagsIn), |
331 |
:-( |
Val2 = |
332 |
|
case Val of |
333 |
|
PartList = [_H|_T] -> % constructed val |
334 |
:-( |
collect_parts(PartList); |
335 |
|
Bin -> |
336 |
:-( |
Bin |
337 |
|
end, |
338 |
:-( |
check_and_convert_restricted_string(Val2, Range). |
339 |
|
|
340 |
|
|
341 |
|
-spec check_and_convert_restricted_string(iolist(), _) -> binary(). |
342 |
|
check_and_convert_restricted_string(Val, Range) -> |
343 |
:-( |
{StrLen, NewVal} = if is_binary(Val) -> |
344 |
:-( |
{size(Val), Val}; |
345 |
|
true -> |
346 |
:-( |
{length(Val), list_to_binary(Val)} |
347 |
|
end, |
348 |
:-( |
case Range of |
349 |
|
[] -> % No length constraint |
350 |
:-( |
NewVal; |
351 |
|
{Lb, Ub} when StrLen >= Lb, Ub >= StrLen -> % variable length constraint |
352 |
:-( |
NewVal; |
353 |
|
{{Lb, _Ub}, []} when StrLen >= Lb -> |
354 |
:-( |
NewVal; |
355 |
|
{{Lb, _Ub}, _Ext=[Min|_]} when StrLen >= Lb; StrLen >= Min -> |
356 |
:-( |
NewVal; |
357 |
|
{{Lb1, Ub1}, {Lb2, Ub2}} when StrLen >= Lb1, StrLen =< Ub1; |
358 |
|
StrLen =< Ub2, StrLen >= Lb2 -> |
359 |
:-( |
NewVal; |
360 |
|
StrLen -> % fixed length constraint |
361 |
:-( |
NewVal; |
362 |
|
{_, _} -> |
363 |
:-( |
exit({error, {asn1, {length, Range, Val}}}); |
364 |
|
_Len when is_integer(_Len) -> |
365 |
:-( |
exit({error, {asn1, {length, Range, Val}}}); |
366 |
|
_ -> % some strange constraint that we don't support yet |
367 |
:-( |
NewVal |
368 |
|
end. |
369 |
|
|
370 |
|
%%---------------------------------------- |
371 |
|
%% Decode the in buffer to bits |
372 |
|
%%---------------------------------------- |
373 |
|
match_tags({T, V}, [T]) -> |
374 |
:-( |
V; |
375 |
|
match_tags({T, V}, [T|Tt]) -> |
376 |
:-( |
match_tags(V, Tt); |
377 |
|
match_tags([{T, V}], [T|Tt]) -> |
378 |
:-( |
match_tags(V, Tt); |
379 |
|
match_tags(Vlist = [{T, _V}|_], [T]) -> |
380 |
:-( |
Vlist; |
381 |
|
match_tags(Tlv, []) -> |
382 |
:-( |
Tlv; |
383 |
|
match_tags({Tag, _V}, [T|_Tt]) -> |
384 |
:-( |
{error, {asn1, {wrong_tag, {Tag, T}}}}. |
385 |
|
|
386 |
|
|
387 |
|
-spec collect_parts([{_, _}]) -> binary(). |
388 |
|
collect_parts(TlvList) -> |
389 |
:-( |
collect_parts(TlvList, []). |
390 |
|
|
391 |
|
|
392 |
|
-spec collect_parts([{_, _}], [any()]) -> binary(). |
393 |
|
collect_parts([{_, L}|Rest], Acc) when is_list(L) -> |
394 |
:-( |
collect_parts(Rest, [collect_parts(L)|Acc]); |
395 |
|
collect_parts([{?N_BIT_STRING, <<Unused, Bits/binary>>}|Rest], _Acc) -> |
396 |
:-( |
collect_parts_bit(Rest, [Bits], Unused); |
397 |
|
collect_parts([{_T, V}|Rest], Acc) -> |
398 |
:-( |
collect_parts(Rest, [V|Acc]); |
399 |
|
collect_parts([], Acc) -> |
400 |
:-( |
list_to_binary(lists:reverse(Acc)). |
401 |
|
|
402 |
|
|
403 |
|
-spec collect_parts_bit([{3, binary()}], [binary(), ...], non_neg_integer()) -> binary(). |
404 |
|
collect_parts_bit([{?N_BIT_STRING, <<Unused, Bits/binary>>}|Rest], Acc, Uacc) -> |
405 |
:-( |
collect_parts_bit(Rest, [Bits|Acc], Unused+Uacc); |
406 |
|
collect_parts_bit([], Acc, Uacc) -> |
407 |
:-( |
maybe_list2b([Uacc|lists:reverse(Acc)]). |
408 |
|
|
409 |
|
maybe_b2list(B) when is_binary(B) -> |
410 |
:-( |
binary_to_list(B); |
411 |
|
maybe_b2list(L) when is_list(L) -> |
412 |
:-( |
L; |
413 |
|
maybe_b2list(O) -> |
414 |
:-( |
{error, {unknown_type, O}}. |
415 |
|
|
416 |
|
maybe_list2b(L) when is_list(L) -> |
417 |
:-( |
list_to_binary(L); |
418 |
|
maybe_list2b(B) when is_binary(B) -> |
419 |
:-( |
B; |
420 |
|
maybe_list2b(O) -> |
421 |
:-( |
{error, {unknown_type, O}}. |