./ct_report/coverage/mod_amp.COVER.html

1 %% @doc MongooseIM/Ejabberd module for (a subset of) XEP-0079 support.
2 %% This work was sponsored by Grindr LLC
3 %% @reference <a href="http://xmpp.org/extensions/xep-0079.html">XEP-0079</a>
4 %% @author <mongooseim@erlang-solutions.com>
5 %% @copyright 2014 Erlang Solutions, Ltd.
6 -module(mod_amp).
7
8 -behavior(gen_mod).
9 -behaviour(mongoose_module_metrics).
10 -xep([{xep, 79}, {version, "1.2"}, {comment, "partially implemented."}]).
11 -export([start/2, stop/1, supported_features/0]).
12 -export([run_initial_check/2,
13 check_packet/2,
14 disco_local_features/1,
15 c2s_stream_features/3
16 ]).
17
18 -ignore_xref([c2s_stream_features/3, disco_local_features/1, run_initial_check/2]).
19
20 -include("amp.hrl").
21 -include("mongoose.hrl").
22 -include("jlib.hrl").
23 -include("ejabberd_c2s.hrl").
24
25 -define(AMP_FEATURE,
26 #xmlel{name = <<"amp">>, attrs = [{<<"xmlns">>, ?NS_AMP_FEATURE}]}).
27 -define(AMP_RESOLVER, amp_resolver).
28 -define(AMP_STRATEGY, amp_strategy).
29
30 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
31 start(HostType, _Opts) ->
32 132 ejabberd_hooks:add(hooks(HostType)).
33
34 -spec stop(mongooseim:host_type()) -> ok.
35 stop(HostType) ->
36 128 ejabberd_hooks:delete(hooks(HostType)).
37
38 -spec supported_features() -> [atom()].
39 66 supported_features() -> [dynamic_domains].
40
41 hooks(HostType) ->
42 260 [{c2s_stream_features, HostType, ?MODULE, c2s_stream_features, 50},
43 {disco_local_features, HostType, ?MODULE, disco_local_features, 99},
44 {c2s_preprocessing_hook, HostType, ?MODULE, run_initial_check, 10},
45 {amp_verify_support, HostType, ?AMP_RESOLVER, verify_support, 10},
46 {amp_check_condition, HostType, ?AMP_RESOLVER, check_condition, 10},
47 {amp_determine_strategy, HostType, ?AMP_STRATEGY, determine_strategy, 10}].
48
49 %% API
50
51 -spec run_initial_check(mongoose_acc:t(), ejabberd_c2s:state()) -> mongoose_acc:t().
52 run_initial_check(#{result := drop} = Acc, _C2SState) ->
53
:-(
Acc;
54 run_initial_check(Acc, _C2SState) ->
55 12003 case mongoose_acc:stanza_name(Acc) of
56 3353 <<"message">> -> run_initial_check(Acc);
57 8650 _ -> Acc
58 end.
59
60 -spec check_packet(mongoose_acc:t(), amp_event()) -> mongoose_acc:t().
61 check_packet(Acc, Event) ->
62 35613 case mongoose_acc:get(amp, rules, none, Acc) of
63 35316 none -> Acc;
64 297 Rules -> process_event(Acc, Rules, Event)
65 end.
66
67 -spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
68 disco_local_features(Acc = #{node := Node}) ->
69 77 case amp_features(Node) of
70 4 [] -> Acc;
71 73 Features -> mongoose_disco:add_features(Features, Acc)
72 end.
73
74 -spec c2s_stream_features([exml:element()], mongooseim:host_type(), jid:lserver()) ->
75 [exml:element()].
76 c2s_stream_features(Acc, _HostType, _Lserver) ->
77 9293 lists:keystore(<<"amp">>, #xmlel.name, Acc, ?AMP_FEATURE).
78
79 %% Internal
80
81 -spec run_initial_check(mongoose_acc:t()) -> mongoose_acc:t().
82 run_initial_check(Acc) ->
83 3353 Packet = mongoose_acc:element(Acc),
84 3353 From = mongoose_acc:from_jid(Acc),
85 3353 To = mongoose_acc:to_jid(Acc),
86 3353 Result = case amp:extract_requested_rules(Packet) of
87 3081 none -> nothing_to_do;
88 271 {rules, Rules} -> validate_and_process_rules(Packet, From, Rules, Acc);
89 1 {errors, Errors} -> send_errors_and_drop(Packet, From, Errors, Acc)
90 end,
91 3353 case Result of
92 nothing_to_do ->
93 3081 Acc;
94 drop ->
95 63 mongoose_acc:set(hook, result, drop, Acc);
96 NewRules ->
97 209 Acc1 = mongoose_acc:set_permanent(amp, rules, NewRules, Acc),
98 209 mongoose_acc:update_stanza(#{element => amp:strip_amp_el(Packet),
99 from_jid => From,
100 to_jid => To}, Acc1)
101 end.
102
103 -spec validate_and_process_rules(exml:element(), jid:jid(), amp_rules(), mongoose_acc:t()) ->
104 amp_rules() | drop.
105 validate_and_process_rules(Packet, From, Rules, Acc) ->
106 271 VerifiedRules = verify_support(mongoose_acc:host_type(Acc), Rules),
107 271 {Good, Bad} = lists:partition(fun is_supported_rule/1, VerifiedRules),
108 271 ValidRules = [ Rule || {supported, Rule} <- Good ],
109 271 case Bad of
110 [{error, ValidationError, InvalidRule} | _] ->
111 2 send_error_and_drop(Packet, From, ValidationError, InvalidRule, Acc);
112 [] ->
113 269 process_rules(Packet, From, initial_check, ValidRules, Acc)
114 end.
115
116 -spec process_event(mongoose_acc:t(), amp_rules(), amp_event()) -> mongoose_acc:t().
117 process_event(Acc, Rules, Event) when Event =/= initial_check ->
118 297 Packet = mongoose_acc:element(Acc),
119 297 From = mongoose_acc:from_jid(Acc),
120 297 NewRules = process_rules(Packet, From, Event, Rules, Acc),
121 297 mongoose_acc:set_permanent(amp, rules, NewRules, Acc).
122
123 -spec amp_features(binary()) -> [mongoose_disco:feature()].
124 amp_features(?NS_AMP) ->
125 1 [<<?NS_AMP/binary, Suffix/binary>> || Suffix <- amp_feature_suffixes()];
126 amp_features(<<>>) ->
127 72 [?NS_AMP];
128 amp_features(_) ->
129 4 [].
130
131 %% @doc This may eventually be configurable, but for now we return a constant list.
132 amp_feature_suffixes() ->
133 1 [<<>>,
134 <<"?action=drop">>,
135 <<"?action=notify">>,
136 <<"?action=error">>,
137 <<"?condition=deliver">>,
138 <<"?condition=match-resource">>].
139
140 -spec process_rules(exml:element(), jid:jid(), amp_event(), amp_rules(), mongoose_acc:t()) ->
141 amp_rules() | drop.
142 process_rules(Packet, From, Event, Rules, Acc) ->
143 566 HostType = mongoose_acc:host_type(Acc),
144 566 To = mongoose_acc:to_jid(Acc),
145 566 Strategy = determine_strategy(HostType, Packet, From, To, Event),
146 566 RulesWithResults = apply_rules(fun(Rule) ->
147 544 resolve_condition(HostType, Strategy, Event, Rule)
148 end, Rules),
149 566 PacketResult = take_action(Packet, From, RulesWithResults, Acc),
150 566 return_result(PacketResult, Event, RulesWithResults).
151
152 %% @doc hooks helpers
153 -spec verify_support(mongooseim:host_type(), amp_rules()) -> [amp_rule_support()].
154 verify_support(HostType, Rules) ->
155 271 mongoose_hooks:amp_verify_support(HostType, Rules).
156
157 -spec determine_strategy(mongooseim:host_type(), exml:element(), jid:jid(), jid:jid(),
158 amp_event()) ->
159 amp_strategy().
160 determine_strategy(HostType, Packet, From, To, Event) ->
161 566 mongoose_hooks:amp_determine_strategy(HostType, From, To, Packet, Event).
162
163 apply_rules(F, Rules) ->
164 566 [Rule#amp_rule{result = F(Rule)} || Rule <- Rules].
165
166 -spec resolve_condition(mongooseim:host_type(), amp_strategy(), amp_event(), amp_rule()) ->
167 amp_match_result().
168 resolve_condition(HostType, Strategy, Event, Rule) ->
169 544 Result = mongoose_hooks:amp_check_condition(HostType, Strategy, Rule),
170 544 match_undecided_for_final_event(Rule, Event, Result).
171
172 match_undecided_for_final_event(#amp_rule{condition = deliver}, Event, undecided)
173 when Event =:= delivered;
174 16 Event =:= delivery_failed -> match;
175 528 match_undecided_for_final_event(_, _, Result) -> Result.
176
177 -spec take_action(exml:element(), jid:jid(), amp_rules(), mongoose_acc:t()) -> pass | drop.
178 take_action(Packet, From, Rules, Acc) ->
179 566 case find(fun(#amp_rule{result = Result}) -> Result =:= match end, Rules) of
180 429 not_found -> pass;
181 137 {found, Rule} -> take_action_for_matched_rule(Packet, From, Rule, Acc)
182 end.
183
184 60 return_result(drop, initial_check, _Rules) -> drop;
185 return_result(pass, _Event, Rules) ->
186 506 lists:filter(fun(#amp_rule{result = Result}) ->
187 424 Result =:= undecided
188 end, Rules).
189
190 -spec take_action_for_matched_rule(exml:element(), jid:jid(), amp_rule(), mongoose_acc:t()) ->
191 pass | drop.
192 take_action_for_matched_rule(Packet, From, #amp_rule{action = notify} = Rule, _Acc) ->
193 77 reply_to_sender(Rule, server_jid(From), From, Packet),
194 77 pass;
195 take_action_for_matched_rule(Packet, From, #amp_rule{action = error} = Rule, Acc) ->
196 30 send_error_and_drop(Packet, From, 'undefined-condition', Rule, Acc);
197 take_action_for_matched_rule(Packet, From, #amp_rule{action = drop}, Acc) ->
198 30 update_metric_and_drop(Packet, From, Acc).
199
200 -spec reply_to_sender(amp_rule(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
201 reply_to_sender(MatchedRule, ServerJid, OriginalSender, OriginalPacket) ->
202 77 Response = amp:make_response(MatchedRule, OriginalSender, OriginalPacket),
203 77 ejabberd_router:route(ServerJid, OriginalSender, Response).
204
205 -spec send_error_and_drop(exml:element(), jid:jid(), amp_error(), amp_rule(), mongoose_acc:t()) -> drop.
206 send_error_and_drop(Packet, From, AmpError, MatchedRule, Acc) ->
207 32 send_errors_and_drop(Packet, From, [{AmpError, MatchedRule}], Acc).
208
209 -spec send_errors_and_drop(exml:element(), jid:jid(), [{amp_error(), amp_rule()}], mongoose_acc:t()) -> drop.
210 send_errors_and_drop(Packet, From, [], Acc) ->
211
:-(
?LOG_ERROR(#{what => empty_list_of_errors_generated,
212 text => <<"This shouldn't happen!">>,
213
:-(
exml_packet => Packet, from => From}),
214
:-(
update_metric_and_drop(Packet, From, Acc);
215 send_errors_and_drop(Packet, From, ErrorRules, Acc) ->
216 33 {Errors, Rules} = lists:unzip(ErrorRules),
217 33 ErrorResponse = amp:make_error_response(Errors, Rules, From, Packet),
218 33 ejabberd_router:route(server_jid(From), From, ErrorResponse),
219 33 update_metric_and_drop(Packet, From, Acc).
220
221 -spec update_metric_and_drop(exml:element(), jid:jid(), mongoose_acc:t()) -> drop.
222 update_metric_and_drop(Packet, From, Acc) ->
223 63 mongoose_hooks:xmpp_stanza_dropped(Acc, From, mongoose_acc:to_jid(Acc), Packet),
224 63 drop.
225
226 -spec is_supported_rule(amp_rule_support()) -> boolean().
227 403 is_supported_rule({supported, _}) -> true;
228 2 is_supported_rule(_) -> false.
229
230 server_jid(#jid{lserver = Host}) ->
231 110 jid:from_binary(Host).
232
233 429 find(_Pred, []) -> not_found;
234 find(Pred, [H|T]) ->
235 504 case Pred(H) of
236 137 true -> {found, H};
237 367 false -> find(Pred, T)
238 end.
Line Hits Source