./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 127 ejabberd_hooks:add(hooks(HostType)).
33
34 -spec stop(mongooseim:host_type()) -> ok.
35 stop(HostType) ->
36 123 ejabberd_hooks:delete(hooks(HostType)).
37
38 -spec supported_features() -> [atom()].
39 64 supported_features() -> [dynamic_domains].
40
41 hooks(HostType) ->
42 250 [{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 7339 case mongoose_acc:stanza_name(Acc) of
56 1868 <<"message">> -> run_initial_check(Acc);
57 5471 _ -> Acc
58 end.
59
60 -spec check_packet(mongoose_acc:t(), amp_event()) -> mongoose_acc:t().
61 check_packet(Acc, Event) ->
62 16137 case mongoose_acc:get(amp, rules, none, Acc) of
63 15984 none -> Acc;
64 153 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 144 case amp_features(Node) of
70 4 [] -> Acc;
71 140 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 6252 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 1868 Packet = mongoose_acc:element(Acc),
84 1868 From = mongoose_acc:from_jid(Acc),
85 1868 To = mongoose_acc:to_jid(Acc),
86 1868 Result = case amp:extract_requested_rules(Packet) of
87 1692 none -> nothing_to_do;
88 175 {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 1868 case Result of
92 nothing_to_do ->
93 1692 Acc;
94 drop ->
95 39 mongoose_acc:set(hook, result, drop, Acc);
96 NewRules ->
97 137 Acc1 = mongoose_acc:set_permanent(amp, rules, NewRules, Acc),
98 137 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 175 VerifiedRules = verify_support(mongoose_acc:host_type(Acc), Rules),
107 175 {Good, Bad} = lists:partition(fun is_supported_rule/1, VerifiedRules),
108 175 ValidRules = [ Rule || {supported, Rule} <- Good ],
109 175 case Bad of
110 [{error, ValidationError, InvalidRule} | _] ->
111 2 send_error_and_drop(Packet, From, ValidationError, InvalidRule, Acc);
112 [] ->
113 173 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 153 Packet = mongoose_acc:element(Acc),
119 153 From = mongoose_acc:from_jid(Acc),
120 153 NewRules = process_rules(Packet, From, Event, Rules, Acc),
121 153 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 139 [?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 326 HostType = mongoose_acc:host_type(Acc),
144 326 To = mongoose_acc:to_jid(Acc),
145 326 Strategy = determine_strategy(HostType, Packet, From, To, Event),
146 326 RulesWithResults = apply_rules(fun(Rule) ->
147 332 resolve_condition(HostType, Strategy, Event, Rule)
148 end, Rules),
149 326 PacketResult = take_action(Packet, From, RulesWithResults, Acc),
150 326 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 175 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 326 mongoose_hooks:amp_determine_strategy(HostType, From, To, Packet, Event).
162
163 apply_rules(F, Rules) ->
164 326 [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 332 Result = mongoose_hooks:amp_check_condition(HostType, Strategy, Rule),
170 332 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 12 Event =:= delivery_failed -> match;
175 320 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 326 case find(fun(#amp_rule{result = Result}) -> Result =:= match end, Rules) of
180 237 not_found -> pass;
181 89 {found, Rule} -> take_action_for_matched_rule(Packet, From, Rule, Acc)
182 end.
183
184 36 return_result(drop, initial_check, _Rules) -> drop;
185 return_result(pass, _Event, Rules) ->
186 290 lists:filter(fun(#amp_rule{result = Result}) ->
187 260 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 53 reply_to_sender(Rule, server_jid(From), From, Packet),
194 53 pass;
195 take_action_for_matched_rule(Packet, From, #amp_rule{action = error} = Rule, Acc) ->
196 18 send_error_and_drop(Packet, From, 'undefined-condition', Rule, Acc);
197 take_action_for_matched_rule(Packet, From, #amp_rule{action = drop}, Acc) ->
198 18 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 53 Response = amp:make_response(MatchedRule, OriginalSender, OriginalPacket),
203 53 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 20 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 21 {Errors, Rules} = lists:unzip(ErrorRules),
217 21 ErrorResponse = amp:make_error_response(Errors, Rules, From, Packet),
218 21 ejabberd_router:route(server_jid(From), From, ErrorResponse),
219 21 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 39 mongoose_hooks:xmpp_stanza_dropped(Acc, From, mongoose_acc:to_jid(Acc), Packet),
224 39 drop.
225
226 -spec is_supported_rule(amp_rule_support()) -> boolean().
227 259 is_supported_rule({supported, _}) -> true;
228 2 is_supported_rule(_) -> false.
229
230 server_jid(#jid{lserver = Host}) ->
231 74 jid:from_binary(Host).
232
233 237 find(_Pred, []) -> not_found;
234 find(Pred, [H|T]) ->
235 309 case Pred(H) of
236 89 true -> {found, H};
237 220 false -> find(Pred, T)
238 end.
Line Hits Source