./ct_report/coverage/mod_register.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_register.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Inband registration support
5 %%% Created : 8 Dec 2002 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2011 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(mod_register).
27 -author('alexey@process-one.net').
28 -xep([{xep, 77}, {version, "2.4"}]).
29 -behaviour(gen_mod).
30 -behaviour(mongoose_module_metrics).
31
32 %% Gen_mod callbacks
33 -export([start/2, stop/1, hooks/1, config_spec/0, supported_features/0]).
34
35 %% IQ and hook handlers
36 -export([c2s_stream_features/3, process_iq/5]).
37 -export([user_send_xmlel/3]).
38
39 %% API
40 -export([try_register/6,
41 process_ip_access/1,
42 process_welcome_message/1]).
43
44 -ignore_xref([try_register/6]).
45
46 -include("mongoose.hrl").
47 -include("jlib.hrl").
48 -include("mongoose_config_spec.hrl").
49 -define(TABLE, mod_register_ip).
50
51 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
52 start(HostType, #{iqdisc := IQDisc}) ->
53 403 [gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_REGISTER, Component, Fn, #{}, IQDisc) ||
54 403 {Component, Fn} <- iq_handlers()],
55 403 Concurrency = case IQDisc of
56 403 one_queue -> [];
57
:-(
_ -> [{read_concurrency, true}]
58 end,
59 403 ejabberd_sup:create_ets_table(?TABLE, [named_table, public | Concurrency]).
60
61 -spec stop(mongooseim:host_type()) -> ok.
62 stop(HostType) ->
63 403 [gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_REGISTER, Component) ||
64 403 {Component, _Fn} <- iq_handlers()],
65 403 ok.
66
67 iq_handlers() ->
68 806 [{ejabberd_local, fun ?MODULE:process_iq/5},
69 {ejabberd_sm, fun ?MODULE:process_iq/5}].
70
71 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list().
72 hooks(HostType) ->
73 806 [{c2s_stream_features, HostType, fun ?MODULE:c2s_stream_features/3, #{}, 50}
74 | c2s_hooks(HostType) ].
75
76 -spec c2s_hooks(mongooseim:host_type()) -> gen_hook:hook_list(mongoose_c2s_hooks:fn()).
77 c2s_hooks(HostType) ->
78 806 [{user_send_xmlel, HostType, fun ?MODULE:user_send_xmlel/3, #{}, 30}].
79
80 %%%
81 %%% config_spec
82 %%%
83
84 -spec config_spec() -> mongoose_config_spec:config_section().
85 config_spec() ->
86 202 #section{
87 items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(),
88 <<"access">> => #option{type = atom,
89 validate = access_rule},
90 <<"welcome_message">> => welcome_message_spec(),
91 <<"registration_watchers">> => #list{items = #option{type = binary,
92 validate = jid}},
93 <<"password_strength">> => #option{type = integer,
94 validate = non_negative},
95 <<"ip_access">> => #list{items = ip_access_spec()}
96 },
97 defaults = #{<<"iqdisc">> => one_queue,
98 <<"access">> => all,
99 <<"registration_watchers">> => [],
100 <<"password_strength">> => 0,
101 <<"ip_access">> => []}
102 }.
103
104 welcome_message_spec() ->
105 202 #section{
106 items = #{<<"body">> => #option{type = string},
107 <<"subject">> => #option{type = string}},
108 defaults = #{<<"body">> => "",
109 <<"subject">> => ""},
110 process = fun ?MODULE:process_welcome_message/1
111 }.
112
113 ip_access_spec() ->
114 202 #section{
115 items = #{<<"address">> => #option{type = string,
116 validate = ip_mask},
117 <<"policy">> => #option{type = atom,
118 validate = {enum, [allow, deny]}}
119 },
120 required = all,
121 process = fun ?MODULE:process_ip_access/1
122 }.
123
124 198 supported_features() -> [dynamic_domains].
125
126 process_ip_access(#{policy := Policy, address := Address}) ->
127 202 {Policy, Address}.
128
129 process_welcome_message(#{subject := Subject, body := Body}) ->
130
:-(
{Subject, Body}.
131
132 %%%
133 %%% Hooks and IQ handlers
134 %%%
135
136 -spec c2s_stream_features(Acc, Params, Extra) -> {ok, Acc} when
137 Acc :: [exml:element()],
138 Params :: map(),
139 Extra :: gen_hook:extra().
140 c2s_stream_features(Acc, _, _) ->
141 6848 NewAcc = [#xmlel{name = <<"register">>,
142 attrs = [{<<"xmlns">>, ?NS_FEATURE_IQREGISTER}]} | Acc],
143 6848 {ok, NewAcc}.
144
145 -spec user_send_xmlel(mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra()) ->
146 mongoose_c2s_hooks:result().
147 user_send_xmlel(Acc, Params, Extra) ->
148 595 case mongoose_acc:stanza_name(Acc) of
149 <<"iq">> ->
150 390 {Iq, Acc1} = mongoose_iq:info(Acc),
151 390 handle_unauthenticated_iq(Acc1, Params, Extra, Iq);
152 205 _ -> {ok, Acc}
153 end.
154
155 -spec handle_unauthenticated_iq(
156 mongoose_acc:t(), mongoose_c2s_hooks:params(), gen_hook:extra(), atom() | jlib:iq()) ->
157 mongoose_c2s_hooks:result().
158 handle_unauthenticated_iq(Acc,
159 #{c2s_data := StateData},
160 #{host_type := HostType},
161 #iq{xmlns = ?NS_REGISTER} = Iq) ->
162 390 process_unauthenticated_iq(Acc, StateData, Iq, HostType);
163 handle_unauthenticated_iq(Acc, _, _, _) ->
164
:-(
{ok, Acc}.
165
166 process_unauthenticated_iq(Acc, StateData, Iq, HostType) ->
167 390 IP = mongoose_c2s:get_ip(StateData),
168 390 Address = case IP of
169 390 {A, _Port} -> A;
170
:-(
_ -> undefined
171 end,
172 390 LServer = mongoose_c2s:get_lserver(StateData),
173 390 FromServer = jid:make_noprep(<<>>, LServer, <<>>),
174 390 ResIQ = process_unauthenticated_iq(HostType,
175 no_JID,
176 %% For the above: the client is
177 %% not registered (no JID), at
178 %% least not yet, so they can
179 %% not be authenticated either.
180 FromServer,
181 Iq,
182 Address),
183 390 Response = set_sender(jlib:iq_to_xml(ResIQ), FromServer),
184 390 AccParams = #{from_jid => FromServer, to_jid => FromServer, element => Response},
185 390 ResponseAcc = mongoose_acc:update_stanza(AccParams, Acc),
186 390 {stop, mongoose_c2s_acc:to_acc(Acc, route, ResponseAcc)}.
187
188 %% Clients must register before being able to authenticate.
189 process_unauthenticated_iq(HostType, From, To, #iq{type = set} = IQ, IPAddr) ->
190 124 process_iq_set(HostType, From, To, IQ, IPAddr);
191 process_unauthenticated_iq(HostType, From, To, #iq{type = get} = IQ, IPAddr) ->
192 266 process_iq_get(HostType, From, To, IQ, IPAddr).
193
194 -spec process_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map())
195 -> {mongoose_acc:t(), jlib:iq()}.
196 process_iq(Acc, From, To, #iq{type = set} = IQ, _Extra) ->
197 113 HostType = mongoose_acc:host_type(Acc),
198 113 Res = process_iq_set(HostType, From, To, IQ, jid:to_lower(From)),
199 113 {Acc, Res};
200 process_iq(Acc, From, To, #iq{type = get} = IQ, _Extra) ->
201 1 HostType = mongoose_acc:host_type(Acc),
202 1 Res = process_iq_get(HostType, From, To, IQ, jid:to_lower(From)),
203 1 {Acc, Res}.
204
205 process_iq_set(HostType, From, To, #iq{sub_el = Child} = IQ, Source) ->
206 237 true = is_query_element(Child),
207 237 handle_set(HostType, IQ, From, To, Source).
208
209 handle_set(HostType, IQ, ClientJID, ServerJID, Source) ->
210 237 #iq{sub_el = Query} = IQ,
211 237 case which_child_elements(Query) of
212 bad_request ->
213 1 error_response(IQ, mongoose_xmpp_errors:bad_request());
214 only_remove_child ->
215 108 attempt_cancelation(HostType, ClientJID, ServerJID, IQ);
216 various_elements_present ->
217 128 case has_username_and_password_children(Query) of
218 true ->
219 128 Credentials = get_username_and_password_values(Query),
220 128 register_or_change_password(HostType, Credentials, ClientJID, ServerJID, IQ, Source);
221 false ->
222
:-(
error_response(IQ, mongoose_xmpp_errors:bad_request())
223 end
224 end.
225
226 which_child_elements(#xmlel{children = C} = Q) when length(C) =:= 1 ->
227 108 case Q#xmlel.children of
228 [#xmlel{name = <<"remove">>}] ->
229 108 only_remove_child;
230 [_] ->
231
:-(
bad_request
232 end;
233 which_child_elements(#xmlel{children = C} = Q) when length(C) > 1 ->
234 129 case exml_query:subelement(Q, <<"remove">>) of
235 #xmlel{name = <<"remove">>} ->
236 1 bad_request;
237 undefined ->
238 128 various_elements_present
239 end;
240 which_child_elements(#xmlel{children = []}) ->
241
:-(
bad_request.
242
243 has_username_and_password_children(Q) ->
244 (undefined =/= exml_query:path(Q, [{element, <<"username">>}]))
245 128 and
246 (undefined =/= exml_query:path(Q, [{element, <<"password">>}])).
247
248 get_username_and_password_values(Q) ->
249 128 {exml_query:path(Q, [{element, <<"username">>}, cdata]),
250 exml_query:path(Q, [{element, <<"password">>}, cdata])}.
251
252 register_or_change_password(HostType, Credentials, ClientJID, #jid{lserver = ServerDomain}, IQ, IPAddr) ->
253 128 {Username, Password} = Credentials,
254 128 case inband_registration_and_cancelation_allowed(HostType, ServerDomain, ClientJID) of
255 true ->
256 128 #iq{sub_el = Children, lang = Lang} = IQ,
257 128 try_register_or_set_password(HostType, Username, ServerDomain, Password,
258 ClientJID, IQ, Children, IPAddr, Lang);
259 false ->
260 %% This is not described in XEP 0077.
261
:-(
error_response(IQ, mongoose_xmpp_errors:forbidden())
262 end.
263
264 attempt_cancelation(HostType, #jid{} = ClientJID, #jid{lserver = ServerDomain}, #iq{} = IQ) ->
265 108 case inband_registration_and_cancelation_allowed(HostType, ServerDomain, ClientJID) of
266 true ->
267 %% The response must be sent *before* the
268 %% XML stream is closed (the call to
269 %% `ejabberd_auth:remove_user/1' does
270 %% this): as it is, when canceling a
271 %% registration, there is no way to deal
272 %% with failure.
273 107 ResIQ = IQ#iq{type = result, sub_el = []},
274 107 ejabberd_router:route(
275 jid:make_noprep(<<>>, <<>>, <<>>),
276 ClientJID,
277 jlib:iq_to_xml(ResIQ)),
278 107 ejabberd_auth:remove_user(ClientJID),
279 107 ignore;
280 false ->
281 1 error_response(IQ, mongoose_xmpp_errors:not_allowed())
282 end.
283
284 inband_registration_and_cancelation_allowed(_HostType, _ServerDomain, no_JID) ->
285 124 true;
286 inband_registration_and_cancelation_allowed(HostType, ServerDomain, JID) ->
287 112 Rule = gen_mod:get_module_opt(HostType, ?MODULE, access),
288 112 allow =:= acl:match_rule(HostType, ServerDomain, Rule, JID).
289
290 process_iq_get(_HostType, From, _To, #iq{lang = Lang, sub_el = Child} = IQ, _Source) ->
291 267 true = is_query_element(Child),
292 267 {_IsRegistered, UsernameSubels, QuerySubels} =
293 case From of
294 JID = #jid{luser = LUser} ->
295 1 case ejabberd_auth:does_user_exist(JID) of
296 true ->
297 1 {true, [#xmlcdata{content = LUser}],
298 [#xmlel{name = <<"registered">>}]};
299 false ->
300
:-(
{false, [#xmlcdata{content = LUser}], []}
301 end;
302 _ ->
303 266 {false, [], []}
304 end,
305 267 TranslatedMsg = translate:translate(
306 Lang, <<"Choose a username and password to register with this server">>),
307 267 IQ#iq{type = result,
308 sub_el = [#xmlel{name = <<"query">>,
309 attrs = [{<<"xmlns">>, <<"jabber:iq:register">>}],
310 children = [#xmlel{name = <<"instructions">>,
311 children = [#xmlcdata{content = TranslatedMsg}]},
312 #xmlel{name = <<"username">>,
313 children = UsernameSubels},
314 #xmlel{name = <<"password">>}
315 | QuerySubels]}]}.
316
317 try_register_or_set_password(HostType, LUser, Server, Password, #jid{luser = LUser, lserver = Server} = UserJID,
318 IQ, SubEl, _Source, Lang) ->
319 4 try_set_password(HostType, UserJID, Password, IQ, SubEl, Lang);
320 try_register_or_set_password(HostType, LUser, Server, Password, _From, IQ, SubEl, Source, Lang) ->
321 124 case check_timeout(Source) of
322 true ->
323 114 case try_register(HostType, LUser, Server, Password, Source, Lang) of
324 ok ->
325 106 IQ#iq{type = result, sub_el = [SubEl]};
326 {error, Error} ->
327 8 error_response(IQ, [SubEl, Error])
328 end;
329 false ->
330 10 ErrText = <<"Users are not allowed to register accounts so quickly">>,
331 10 error_response(IQ, mongoose_xmpp_errors:resource_constraint(Lang, ErrText))
332 end.
333
334 %% @doc Try to change password and return IQ response
335 try_set_password(HostType, #jid{} = UserJID, Password, IQ, SubEl, Lang) ->
336 4 case is_strong_password(HostType, Password) of
337 true ->
338 4 case ejabberd_auth:set_password(UserJID, Password) of
339 ok ->
340 2 IQ#iq{type = result, sub_el = [SubEl]};
341 {error, empty_password} ->
342 2 error_response(IQ, [SubEl, mongoose_xmpp_errors:bad_request()]);
343 {error, not_allowed} ->
344
:-(
error_response(IQ, [SubEl, mongoose_xmpp_errors:not_allowed()]);
345 {error, invalid_jid} ->
346
:-(
error_response(IQ, [SubEl, mongoose_xmpp_errors:item_not_found()])
347 end;
348 false ->
349
:-(
ErrText = <<"The password is too weak">>,
350
:-(
error_response(IQ, [SubEl, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)])
351 end.
352
353 try_register(HostType, User, Server, Password, SourceRaw, Lang) ->
354 114 case jid:is_nodename(User) of
355 false ->
356
:-(
{error, mongoose_xmpp_errors:bad_request()};
357 _ ->
358 114 JID = jid:make_bare(User, Server),
359 114 Access = gen_mod:get_module_opt(HostType, ?MODULE, access),
360 114 IPAccess = get_ip_access(HostType),
361 114 case {acl:match_rule(HostType, Server, Access, JID),
362 check_ip_access(SourceRaw, IPAccess)} of
363 {deny, _} ->
364 1 {error, mongoose_xmpp_errors:forbidden()};
365 {_, deny} ->
366
:-(
{error, mongoose_xmpp_errors:forbidden()};
367 {allow, allow} ->
368 113 verify_password_and_register(HostType, JID, Password, SourceRaw, Lang)
369 end
370 end.
371
372 verify_password_and_register(HostType, #jid{} = JID, Password, SourceRaw, Lang) ->
373 113 case is_strong_password(HostType, Password) of
374 true ->
375 113 case ejabberd_auth:try_register(JID, Password) of
376 {error, exists} ->
377 6 {error, mongoose_xmpp_errors:conflict()};
378 {error, invalid_jid} ->
379
:-(
{error, mongoose_xmpp_errors:jid_malformed()};
380 {error, not_allowed} ->
381
:-(
{error, mongoose_xmpp_errors:not_allowed()};
382 {error, null_password} ->
383 1 {error, mongoose_xmpp_errors:not_acceptable()};
384 _ ->
385 106 send_welcome_message(HostType, JID),
386 106 send_registration_notifications(HostType, JID, SourceRaw),
387 106 ok
388 end;
389 false ->
390
:-(
ErrText = <<"The password is too weak">>,
391
:-(
{error, mongoose_xmpp_errors:not_acceptable(Lang, ErrText)}
392 end.
393
394 send_welcome_message(HostType, #jid{lserver = Server} = JID) ->
395 106 case gen_mod:lookup_module_opt(HostType, ?MODULE, welcome_message) of
396 {error, not_found} ->
397 106 ok;
398 {ok, {Subj, Body}} ->
399
:-(
ejabberd_router:route(
400 jid:make_noprep(<<>>, Server, <<>>),
401 JID,
402 #xmlel{name = <<"message">>, attrs = [{<<"type">>, <<"normal">>}],
403 children = [#xmlel{name = <<"subject">>,
404 children = [#xmlcdata{content = Subj}]},
405 #xmlel{name = <<"body">>,
406 children = [#xmlcdata{content = Body}]}]})
407 end.
408
409 send_registration_notifications(HostType, #jid{lserver = Domain} = UJID, Source) ->
410 106 case gen_mod:get_module_opt(HostType, ?MODULE, registration_watchers) of
411 104 [] -> ok;
412 JIDs when is_list(JIDs) ->
413 2 Body = lists:flatten(
414 io_lib:format(
415 "[~s] The account ~s was registered from IP address ~s "
416 "on node ~w using ~p.",
417 [get_time_string(), jid:to_binary(UJID),
418 ip_to_string(Source), node(), ?MODULE])),
419 2 lists:foreach(fun(S) -> send_registration_notification(S, Domain, Body) end, JIDs);
420 _ ->
421
:-(
ok
422 end.
423
424 send_registration_notification(JIDBin, Domain, Body) ->
425 2 case jid:from_binary(JIDBin) of
426
:-(
error -> ok;
427 JID ->
428 2 Message = #xmlel{name = <<"message">>,
429 attrs = [{<<"type">>, <<"chat">>}],
430 children = [#xmlel{name = <<"body">>,
431 children = [#xmlcdata{content = Body}]}]},
432 2 ejabberd_router:route(jid:make_noprep(<<>>, Domain, <<>>), JID, Message)
433 end.
434
435 check_timeout(undefined) ->
436
:-(
true;
437 check_timeout(Source) ->
438 124 Timeout = mongoose_config:get_opt(registration_timeout),
439 124 case is_integer(Timeout) of
440 true ->
441 13 TS = erlang:system_time(second),
442 13 clean_ets(TS - Timeout),
443 13 check_and_store_ip_entry(Source, TS);
444 false ->
445 111 true
446 end.
447
448 check_and_store_ip_entry(Source, Timestamp) ->
449 13 case ets:member(?TABLE, Source) of
450 false ->
451 3 ets:insert(?TABLE, {Source, Timestamp}),
452 3 true;
453 true ->
454 10 false
455 end.
456
457 clean_ets(CleanTimestamp) ->
458 13 ets:select_delete(?TABLE, [{ {'_', '$1'}, [{'<', '$1', CleanTimestamp}], [true]}]).
459
460 2 ip_to_string(Source) when is_tuple(Source) -> inet_parse:ntoa(Source);
461
:-(
ip_to_string(undefined) -> "undefined";
462
:-(
ip_to_string(_) -> "unknown".
463
464 2 get_time_string() -> write_time(erlang:localtime()).
465 %% Function copied from ejabberd_logger_h.erl and customized
466 write_time({{Y, Mo, D}, {H, Mi, S}}) ->
467 2 io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w",
468 [Y, Mo, D, H, Mi, S]).
469
470 is_strong_password(HostType, Password) ->
471 117 case gen_mod:get_module_opt(HostType, ?MODULE, password_strength) of
472 Entropy when is_number(Entropy), Entropy == 0 ->
473 117 true;
474 Entropy when is_number(Entropy), Entropy > 0 ->
475
:-(
ejabberd_auth:entropy(Password) >= Entropy;
476 Wrong ->
477
:-(
?LOG_WARNING(#{what => reg_wrong_password_strength,
478
:-(
host => HostType, value => Wrong}),
479
:-(
true
480 end.
481
482 %%%
483 %%% ip_access management
484 %%%
485
486 get_ip_access(HostType) ->
487 114 IPAccess = gen_mod:get_module_opt(HostType, ?MODULE, ip_access),
488 114 lists:flatmap(
489 fun({Access, {IP, Mask}}) ->
490
:-(
[{Access, IP, Mask}];
491 ({Access, S}) ->
492 228 case mongoose_lib:parse_ip_netmask(S) of
493 {ok, {IP, Mask}} ->
494 228 [{Access, IP, Mask}];
495 error ->
496
:-(
?LOG_ERROR(#{what => reg_invalid_network_specification,
497
:-(
specification => S}),
498
:-(
[]
499 end
500 end, IPAccess).
501
502 check_ip_access(_Source, []) ->
503
:-(
allow;
504 check_ip_access({User, Server, Resource}, IPAccess) ->
505
:-(
case ejabberd_sm:get_session_ip(jid:make(User, Server, Resource)) of
506
:-(
{IPAddress, _PortNumber} -> check_ip_access(IPAddress, IPAccess);
507
:-(
_ -> true
508 end;
509 check_ip_access({_, _, _, _} = IP,
510 [{Access, {_, _, _, _} = Net, Mask} | IPAccess]) ->
511 114 IPInt = ip_to_integer(IP),
512 114 NetInt = ip_to_integer(Net),
513 114 M = bnot ((1 bsl (32 - Mask)) - 1),
514 114 case IPInt band M =:= NetInt band M of
515 114 true -> Access;
516
:-(
false -> check_ip_access(IP, IPAccess)
517 end;
518 check_ip_access({_, _, _, _, _, _, _, _} = IP,
519 [{Access, {_, _, _, _, _, _, _, _} = Net, Mask} | IPAccess]) ->
520
:-(
IPInt = ip_to_integer(IP),
521
:-(
NetInt = ip_to_integer(Net),
522
:-(
M = bnot ((1 bsl (128 - Mask)) - 1),
523
:-(
case IPInt band M =:= NetInt band M of
524
:-(
true -> Access;
525
:-(
false -> check_ip_access(IP, IPAccess)
526 end;
527 check_ip_access(IP, [_ | IPAccess]) ->
528
:-(
check_ip_access(IP, IPAccess).
529
530 ip_to_integer({IP1, IP2, IP3, IP4}) ->
531 228 <<X:32>> = <<IP1, IP2, IP3, IP4>>,
532 228 X;
533 ip_to_integer({IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8}) ->
534
:-(
<<X:64>> = <<IP1, IP2, IP3, IP4, IP5, IP6, IP7, IP8>>,
535
:-(
X.
536
537 set_sender(#xmlel{attrs = A} = Stanza, #jid{} = From) ->
538 390 Stanza#xmlel{attrs = [{<<"from">>, jid:to_binary(From)}|A]}.
539
540 is_query_element(#xmlel{name = <<"query">>}) ->
541 504 true;
542 is_query_element(_) ->
543
:-(
false.
544
545 error_response(Request, Reasons) when is_list(Reasons) ->
546 10 Request#iq{type = error, sub_el = Reasons};
547 error_response(Request, Reason) ->
548 12 Request#iq{type = error, sub_el = Reason}.
Line Hits Source