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 |
|
deref_aliases/1, |
38 |
|
process_user_filter/2, |
39 |
|
get_search_filter/1, |
40 |
|
decode_octet_string/3, |
41 |
|
uids_domain_subst/2, |
42 |
|
singleton_value/1, |
43 |
|
maybe_list2b/1, |
44 |
|
maybe_b2list/1]). |
45 |
|
|
46 |
|
-ignore_xref([decode_octet_string/3, generate_subfilter/1, make_filter/2, uids_domain_subst/2]). |
47 |
|
|
48 |
|
%% Used to be eldap:filter() |
49 |
|
-type filter() :: term(). |
50 |
|
|
51 |
|
%% Used to be eldap:eldap_entry() |
52 |
|
-type eldap_entry() :: term(). |
53 |
|
|
54 |
|
%% Used to be eldap:handle() |
55 |
|
-type handle() :: term(). |
56 |
|
|
57 |
|
-export_type([handle/0, |
58 |
|
filter/0, |
59 |
|
eldap_entry/0]). |
60 |
|
|
61 |
|
-type dn() :: binary(). |
62 |
|
-type deref() :: neverDerefAliases | derefInSearching |
63 |
|
| derefFindingBaseObj | derefAlways. |
64 |
|
%% Used to access mongoose_wpool |
65 |
|
-type eldap_id() :: {HostType :: mongooseim:host_type(), Tag :: mongoose_wpool:tag()}. |
66 |
|
|
67 |
|
-export_type([dn/0, |
68 |
|
deref/0, |
69 |
|
eldap_id/0]). |
70 |
|
|
71 |
|
%% @doc Generate an 'or' LDAP query on one or several attributes |
72 |
|
%% If there is only one attribute |
73 |
|
-spec generate_subfilter([{binary()} | {binary(), binary()}]) -> binary(). |
74 |
|
generate_subfilter([UID]) -> |
75 |
259 |
subfilter(UID); |
76 |
|
%% If there is several attributes |
77 |
|
generate_subfilter(UIDs) -> |
78 |
:-( |
iolist_to_binary(["(|", [subfilter(UID) || UID <- UIDs], ")"]). |
79 |
|
|
80 |
|
|
81 |
|
%% @doc Subfilter for a single attribute |
82 |
|
-spec subfilter({binary()} | {binary(), binary()}) -> binary(). |
83 |
|
subfilter({UIDAttr, UIDAttrFormat}) -> |
84 |
|
%% The default UiDAttrFormat is %u |
85 |
259 |
<<$(, UIDAttr/binary, $=, UIDAttrFormat/binary, $)>>; |
86 |
|
subfilter({UIDAttr}) -> |
87 |
|
%% The default UiDAttrFormat is <<"%u">> |
88 |
:-( |
<<$(, UIDAttr/binary, $=, "%u)">>. |
89 |
|
|
90 |
|
|
91 |
|
%% @doc Not tail-recursive, but it is not very terribly. |
92 |
|
%% It stops finding on the first not empty value. |
93 |
|
-spec find_ldap_attrs([{binary()} | {binary(), binary()}], |
94 |
|
[{binary(), [binary()]}]) -> <<>> | {binary(), binary()}. |
95 |
|
find_ldap_attrs([{Attr} | Rest], Attributes) -> |
96 |
:-( |
find_ldap_attrs([{Attr, <<"%u">>} | Rest], Attributes); |
97 |
|
find_ldap_attrs([{Attr, Format} | Rest], Attributes) -> |
98 |
170930 |
case get_ldap_attr(Attr, Attributes) of |
99 |
|
Value when Value /= <<>>, Value /= [] -> |
100 |
170930 |
{Value, Format}; |
101 |
|
_ -> |
102 |
:-( |
find_ldap_attrs(Rest, Attributes) |
103 |
|
end; |
104 |
|
find_ldap_attrs([], _) -> |
105 |
:-( |
<<>>. |
106 |
|
|
107 |
|
|
108 |
|
-spec get_ldap_attr(binary(), [{binary(), [binary()]}]) -> binary(). |
109 |
|
get_ldap_attr(LDAPAttr, Attributes) -> |
110 |
172047 |
Res = lists:filter( |
111 |
|
fun({Name, _}) -> |
112 |
174561 |
case_insensitive_match(Name, LDAPAttr) |
113 |
|
end, Attributes), |
114 |
172047 |
case singleton_value(Res) of |
115 |
171108 |
{_, Value} -> eldap_utils:maybe_list2b(Value); |
116 |
939 |
_ -> <<>> |
117 |
|
end. |
118 |
|
|
119 |
|
|
120 |
|
-spec get_user_part(binary(), binary()) -> {ok, binary()} | {error, badmatch}. |
121 |
|
get_user_part(String, Pattern) -> |
122 |
170935 |
F = fun(S, P) -> |
123 |
170935 |
{First, _} = binary:match(P, <<"%u">>), |
124 |
170935 |
TailLength = byte_size(P) - (First+1), |
125 |
170935 |
binary:part(S, First, byte_size(S)-TailLength-First+1) |
126 |
|
end, |
127 |
170935 |
case catch F(String, Pattern) of |
128 |
|
{'EXIT', _} -> |
129 |
:-( |
{error, badmatch}; |
130 |
|
Result -> |
131 |
170935 |
case catch re:replace(Pattern, <<"%u">>, Result, [global, {return, binary}]) of |
132 |
|
{'EXIT', _} -> |
133 |
:-( |
{error, badmatch}; |
134 |
|
StringRes -> |
135 |
170935 |
case case_insensitive_match(StringRes, String) of |
136 |
|
true -> |
137 |
170935 |
{ok, Result}; |
138 |
|
false -> |
139 |
:-( |
{error, badmatch} |
140 |
|
end |
141 |
|
end |
142 |
|
end. |
143 |
|
|
144 |
|
|
145 |
|
-spec generate_substring_list(binary()) |
146 |
|
-> [{'any', binary()} | {'final', binary()} | {'initial', binary()}]. |
147 |
|
generate_substring_list(Value)-> |
148 |
11 |
Splits = binary:split(Value, <<"*">>, [global]), |
149 |
11 |
{Acc, S}=case Splits of |
150 |
1 |
[<<"">>|T]->{[], maybe_b2list(T)}; |
151 |
10 |
[H|T]-> {[{initial, maybe_b2list(H)}], T} |
152 |
|
end, |
153 |
11 |
lists:reverse(generate_substring_list(S, Acc)). |
154 |
|
generate_substring_list([<<"">>], Acc)-> |
155 |
10 |
Acc; |
156 |
|
generate_substring_list([Last], Acc)-> |
157 |
1 |
[{final, Last}|Acc]; |
158 |
|
generate_substring_list([H|T], Acc)-> |
159 |
1 |
generate_substring_list(T, [{any, H}|Acc]). |
160 |
|
|
161 |
|
|
162 |
|
-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}]) -> any(). |
163 |
|
make_filter(Data, UIDs) -> |
164 |
:-( |
make_filter(Data, UIDs, 'and'). |
165 |
|
|
166 |
|
-spec make_filter([{binary(), [binary()]}], [{binary(), binary()}], |
167 |
|
'or' | 'and') -> any(). |
168 |
|
make_filter(Data, UIDs, Op) -> |
169 |
19 |
NewUIDs = [{U, eldap_filter:do_sub( |
170 |
19 |
UF, [{<<"%u">>, <<"*%u*">>, 1}])} || {U, UF} <- UIDs], |
171 |
19 |
Filter = lists:flatmap( |
172 |
|
traverse_filter_fun(NewUIDs), Data), |
173 |
19 |
case Filter of |
174 |
|
[F] -> |
175 |
17 |
F; |
176 |
|
_ -> |
177 |
2 |
eldap:Op(Filter) |
178 |
|
end. |
179 |
|
|
180 |
|
traverse_filter_fun(NewUIDs) -> |
181 |
19 |
fun(Entry) -> |
182 |
21 |
match_filter_name(Entry, NewUIDs) |
183 |
|
end. |
184 |
|
|
185 |
|
match_filter_name({<<"%u">>, [Value | _]}, NewUIDs) when Value /= <<"">> -> |
186 |
:-( |
case eldap_filter:parse( |
187 |
|
generate_subfilter(NewUIDs), |
188 |
|
[{<<"%u">>, Value}]) of |
189 |
:-( |
{ok, F} -> [F]; |
190 |
:-( |
_ -> [] |
191 |
|
end; |
192 |
|
match_filter_name({Name, [Value | _]}, _NewUIDs) when Value /= <<"">> -> |
193 |
21 |
case binary:match(Value, <<"*">>) of |
194 |
10 |
nomatch -> [eldap:equalityMatch(Name, Value)]; |
195 |
11 |
_ -> [eldap:substrings(maybe_b2list(Name), |
196 |
|
generate_substring_list(Value))] |
197 |
|
end; |
198 |
|
match_filter_name(_, _) -> |
199 |
:-( |
[]. |
200 |
|
|
201 |
|
-spec case_insensitive_match(binary(), binary()) -> boolean(). |
202 |
|
case_insensitive_match(X, Y) -> |
203 |
370541 |
X1 = string:to_lower(maybe_b2list(X)), |
204 |
370541 |
Y1 = string:to_lower(maybe_b2list(Y)), |
205 |
370541 |
case X1 == Y1 of |
206 |
343188 |
true -> true; |
207 |
27353 |
_-> false |
208 |
|
end. |
209 |
|
|
210 |
|
|
211 |
|
-spec get_state(mongooseim:host_type(), atom()) -> any(). |
212 |
|
get_state(HostType, Module) -> |
213 |
19689 |
Proc = gen_mod:get_module_proc(HostType, Module), |
214 |
19689 |
gen_server:call(Proc, get_state). |
215 |
|
|
216 |
|
|
217 |
|
%% @doc From the list of uids attribute: we look from alias domain (%d) and make |
218 |
|
%% the substitution with the actual host domain. This helps when you need to |
219 |
|
%% configure many virtual domains. |
220 |
|
-spec uids_domain_subst(binary(), [{binary(), binary()}]) -> |
221 |
|
[{binary(), binary()}]. |
222 |
|
uids_domain_subst(Host, UIDs) -> |
223 |
259 |
lists:map(fun({U, V}) -> |
224 |
259 |
{U, eldap_filter:do_sub(V, [{<<"%d">>, Host}])}; |
225 |
:-( |
(A) -> A |
226 |
|
end, |
227 |
|
UIDs). |
228 |
|
|
229 |
260 |
deref_aliases(never) -> neverDerefAliases; |
230 |
:-( |
deref_aliases(searching) -> derefInSearching; |
231 |
:-( |
deref_aliases(finding) -> derefFindingBaseObj; |
232 |
:-( |
deref_aliases(always) -> derefAlways. |
233 |
|
|
234 |
|
process_user_filter(UIDs, RawUserFilter) -> |
235 |
259 |
SubFilter = generate_subfilter(UIDs), |
236 |
259 |
case RawUserFilter of |
237 |
|
<<>> -> |
238 |
:-( |
SubFilter; |
239 |
|
F -> |
240 |
259 |
<<"(&", SubFilter/binary, F/binary, ")">> |
241 |
|
end. |
242 |
|
|
243 |
|
get_search_filter(UserFilter) -> |
244 |
259 |
eldap_filter:do_sub(UserFilter, [{<<"%u">>, <<"*">>}]). |
245 |
|
|
246 |
|
-spec singleton_value(list()) -> {binary(), binary()} | false. |
247 |
|
singleton_value([{K, [V]}]) -> |
248 |
171122 |
{K, V}; |
249 |
|
singleton_value([{_K, _V} = I]) -> |
250 |
:-( |
I; |
251 |
|
singleton_value(_) -> |
252 |
939 |
false. |
253 |
|
%%---------------------------------------- |
254 |
|
%% Borrowed from asn1rt_ber_bin_v2.erl |
255 |
|
%%---------------------------------------- |
256 |
|
|
257 |
|
%%% The tag-number for universal types |
258 |
|
-define(N_BOOLEAN, 1). |
259 |
|
-define(N_INTEGER, 2). |
260 |
|
-define(N_BIT_STRING, 3). |
261 |
|
-define(N_OCTET_STRING, 4). |
262 |
|
-define(N_NULL, 5). |
263 |
|
-define(N_OBJECT_IDENTIFIER, 6). |
264 |
|
-define(N_OBJECT_DESCRIPTOR, 7). |
265 |
|
-define(N_EXTERNAL, 8). |
266 |
|
-define(N_REAL, 9). |
267 |
|
-define(N_ENUMERATED, 10). |
268 |
|
-define(N_EMBEDDED_PDV, 11). |
269 |
|
-define(N_SEQUENCE, 16). |
270 |
|
-define(N_SET, 17). |
271 |
|
-define(N_NumericString, 18). |
272 |
|
-define(N_PrintableString, 19). |
273 |
|
-define(N_TeletexString, 20). |
274 |
|
-define(N_VideotexString, 21). |
275 |
|
-define(N_IA5String, 22). |
276 |
|
-define(N_UTCTime, 23). |
277 |
|
-define(N_GeneralizedTime, 24). |
278 |
|
-define(N_GraphicString, 25). |
279 |
|
-define(N_VisibleString, 26). |
280 |
|
-define(N_GeneralString, 27). |
281 |
|
-define(N_UniversalString, 28). |
282 |
|
-define(N_BMPString, 30). |
283 |
|
|
284 |
|
|
285 |
|
-spec decode_octet_string(_, _, list()) -> binary(). |
286 |
|
decode_octet_string(Buffer, Range, Tags) -> |
287 |
|
% NewTags = new_tags(HasTag, #tag{class=?UNIVERSAL, number=?N_OCTET_STRING}), |
288 |
:-( |
decode_restricted_string(Buffer, Range, Tags). |
289 |
|
|
290 |
|
|
291 |
|
-spec decode_restricted_string(_, _, list()) -> binary(). |
292 |
|
decode_restricted_string(Tlv, Range, TagsIn) -> |
293 |
:-( |
Val = match_tags(Tlv, TagsIn), |
294 |
:-( |
Val2 = |
295 |
|
case Val of |
296 |
|
PartList = [_H|_T] -> % constructed val |
297 |
:-( |
collect_parts(PartList); |
298 |
|
Bin -> |
299 |
:-( |
Bin |
300 |
|
end, |
301 |
:-( |
check_and_convert_restricted_string(Val2, Range). |
302 |
|
|
303 |
|
|
304 |
|
-spec check_and_convert_restricted_string(iolist(), _) -> binary(). |
305 |
|
check_and_convert_restricted_string(Val, Range) -> |
306 |
:-( |
{StrLen, NewVal} = if is_binary(Val) -> |
307 |
:-( |
{size(Val), Val}; |
308 |
|
true -> |
309 |
:-( |
{length(Val), list_to_binary(Val)} |
310 |
|
end, |
311 |
:-( |
case Range of |
312 |
|
[] -> % No length constraint |
313 |
:-( |
NewVal; |
314 |
|
{Lb, Ub} when StrLen >= Lb, Ub >= StrLen -> % variable length constraint |
315 |
:-( |
NewVal; |
316 |
|
{{Lb, _Ub}, []} when StrLen >= Lb -> |
317 |
:-( |
NewVal; |
318 |
|
{{Lb, _Ub}, _Ext=[Min|_]} when StrLen >= Lb; StrLen >= Min -> |
319 |
:-( |
NewVal; |
320 |
|
{{Lb1, Ub1}, {Lb2, Ub2}} when StrLen >= Lb1, StrLen =< Ub1; |
321 |
|
StrLen =< Ub2, StrLen >= Lb2 -> |
322 |
:-( |
NewVal; |
323 |
|
StrLen -> % fixed length constraint |
324 |
:-( |
NewVal; |
325 |
|
{_, _} -> |
326 |
:-( |
exit({error, {asn1, {length, Range, Val}}}); |
327 |
|
_Len when is_integer(_Len) -> |
328 |
:-( |
exit({error, {asn1, {length, Range, Val}}}); |
329 |
|
_ -> % some strange constraint that we don't support yet |
330 |
:-( |
NewVal |
331 |
|
end. |
332 |
|
|
333 |
|
%%---------------------------------------- |
334 |
|
%% Decode the in buffer to bits |
335 |
|
%%---------------------------------------- |
336 |
|
match_tags({T, V}, [T]) -> |
337 |
:-( |
V; |
338 |
|
match_tags({T, V}, [T|Tt]) -> |
339 |
:-( |
match_tags(V, Tt); |
340 |
|
match_tags([{T, V}], [T|Tt]) -> |
341 |
:-( |
match_tags(V, Tt); |
342 |
|
match_tags(Vlist = [{T, _V}|_], [T]) -> |
343 |
:-( |
Vlist; |
344 |
|
match_tags(Tlv, []) -> |
345 |
:-( |
Tlv; |
346 |
|
match_tags({Tag, _V}, [T|_Tt]) -> |
347 |
:-( |
{error, {asn1, {wrong_tag, {Tag, T}}}}. |
348 |
|
|
349 |
|
|
350 |
|
-spec collect_parts([{_, _}]) -> binary(). |
351 |
|
collect_parts(TlvList) -> |
352 |
:-( |
collect_parts(TlvList, []). |
353 |
|
|
354 |
|
|
355 |
|
-spec collect_parts([{_, _}], [any()]) -> binary(). |
356 |
|
collect_parts([{_, L}|Rest], Acc) when is_list(L) -> |
357 |
:-( |
collect_parts(Rest, [collect_parts(L)|Acc]); |
358 |
|
collect_parts([{?N_BIT_STRING, <<Unused, Bits/binary>>}|Rest], _Acc) -> |
359 |
:-( |
collect_parts_bit(Rest, [Bits], Unused); |
360 |
|
collect_parts([{_T, V}|Rest], Acc) -> |
361 |
:-( |
collect_parts(Rest, [V|Acc]); |
362 |
|
collect_parts([], Acc) -> |
363 |
:-( |
list_to_binary(lists:reverse(Acc)). |
364 |
|
|
365 |
|
|
366 |
|
-spec collect_parts_bit([{3, binary()}], [binary(), ...], non_neg_integer()) -> binary(). |
367 |
|
collect_parts_bit([{?N_BIT_STRING, <<Unused, Bits/binary>>}|Rest], Acc, Uacc) -> |
368 |
:-( |
collect_parts_bit(Rest, [Bits|Acc], Unused+Uacc); |
369 |
|
collect_parts_bit([], Acc, Uacc) -> |
370 |
:-( |
maybe_list2b([Uacc|lists:reverse(Acc)]). |
371 |
|
|
372 |
|
maybe_b2list(B) when is_binary(B) -> |
373 |
877944 |
binary_to_list(B); |
374 |
|
maybe_b2list(L) when is_list(L) -> |
375 |
48052 |
L; |
376 |
|
maybe_b2list(O) -> |
377 |
:-( |
{error, {unknown_type, O}}. |
378 |
|
|
379 |
|
maybe_list2b(L) when is_list(L) -> |
380 |
:-( |
list_to_binary(L); |
381 |
|
maybe_list2b(B) when is_binary(B) -> |
382 |
171127 |
B; |
383 |
|
maybe_list2b(O) -> |
384 |
:-( |
{error, {unknown_type, O}}. |