1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : mod_adhoc.erl |
3 |
|
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se> |
4 |
|
%%% Purpose : Handle incoming ad-doc requests (XEP-0050) |
5 |
|
%%% Created : 15 Nov 2005 by Magnus Henoch <henoch@dtek.chalmers.se> |
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 |
|
-module(mod_adhoc). |
26 |
|
-author('henoch@dtek.chalmers.se'). |
27 |
|
|
28 |
|
-behaviour(gen_mod). |
29 |
|
-behaviour(mongoose_module_metrics). |
30 |
|
|
31 |
|
-type command_hook_acc() :: {error, exml:element()} | exml:element() | ignore | empty. |
32 |
|
-export_type([command_hook_acc/0]). |
33 |
|
|
34 |
|
%% Gen_mod callbacks |
35 |
|
-export([start/2, |
36 |
|
stop/1, |
37 |
|
config_spec/0, |
38 |
|
supported_features/0]). |
39 |
|
|
40 |
|
%% IQ and hook handlers |
41 |
|
-export([process_local_iq/5, |
42 |
|
process_sm_iq/5, |
43 |
|
disco_local_items/1, |
44 |
|
disco_local_identity/1, |
45 |
|
disco_local_features/1, |
46 |
|
disco_sm_items/1, |
47 |
|
disco_sm_identity/1, |
48 |
|
disco_sm_features/1, |
49 |
|
ping_command/4]). |
50 |
|
|
51 |
|
-ignore_xref([disco_local_features/1, disco_local_identity/1, disco_local_items/1, |
52 |
|
disco_sm_features/1, disco_sm_identity/1, disco_sm_items/1, |
53 |
|
ping_command/4, process_local_iq/5, process_sm_iq/5]). |
54 |
|
|
55 |
|
-include("mongoose.hrl"). |
56 |
|
-include("jlib.hrl"). |
57 |
|
-include("adhoc.hrl"). |
58 |
|
-include("mongoose_config_spec.hrl"). |
59 |
|
|
60 |
|
start(HostType, Opts) -> |
61 |
310 |
IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue), |
62 |
|
|
63 |
310 |
[gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_COMMANDS, Component, Fn, #{}, IQDisc) || |
64 |
310 |
{Component, Fn} <- iq_handlers()], |
65 |
310 |
ejabberd_hooks:add(hooks(HostType)). |
66 |
|
|
67 |
|
stop(HostType) -> |
68 |
310 |
ejabberd_hooks:delete(hooks(HostType)), |
69 |
310 |
[gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_COMMANDS, Component) || |
70 |
310 |
{Component, _Fn} <- iq_handlers()]. |
71 |
|
|
72 |
|
iq_handlers() -> |
73 |
620 |
[{ejabberd_local, fun ?MODULE:process_local_iq/5}, |
74 |
|
{ejabberd_sm, fun ?MODULE:process_sm_iq/5}]. |
75 |
|
|
76 |
|
hooks(HostType) -> |
77 |
620 |
[{disco_local_identity, HostType, ?MODULE, disco_local_identity, 99}, |
78 |
|
{disco_local_features, HostType, ?MODULE, disco_local_features, 99}, |
79 |
|
{disco_local_items, HostType, ?MODULE, disco_local_items, 99}, |
80 |
|
{disco_sm_identity, HostType, ?MODULE, disco_sm_identity, 99}, |
81 |
|
{disco_sm_features, HostType, ?MODULE, disco_sm_features, 99}, |
82 |
|
{disco_sm_items, HostType, ?MODULE, disco_sm_items, 99}, |
83 |
|
{adhoc_local_commands, HostType, ?MODULE, ping_command, 100}]. |
84 |
|
|
85 |
|
%%% |
86 |
|
%%% config_spec |
87 |
|
%%% |
88 |
|
|
89 |
|
-spec config_spec() -> mongoose_config_spec:config_section(). |
90 |
|
config_spec() -> |
91 |
160 |
#section{ |
92 |
|
items = #{<<"report_commands_node">> => #option{type = boolean}, |
93 |
|
<<"iqdisc">> => mongoose_config_spec:iqdisc()} |
94 |
|
}. |
95 |
|
|
96 |
|
-spec supported_features() -> [atom()]. |
97 |
142 |
supported_features() -> [dynamic_domains]. |
98 |
|
|
99 |
|
%%% |
100 |
|
%%% IQ handlers |
101 |
|
%%% |
102 |
|
|
103 |
|
-spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) |
104 |
|
-> {mongoose_acc:t(), ignore | jlib:iq()}. |
105 |
|
process_local_iq(Acc, From, To, IQ, _Extra) -> |
106 |
1 |
{Acc, process_adhoc_request(Acc, From, To, IQ, adhoc_local_commands)}. |
107 |
|
|
108 |
|
-spec process_sm_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) -> |
109 |
|
{mongoose_acc:t(), ignore | jlib:iq()}. |
110 |
|
process_sm_iq(Acc, From, To, IQ, _Extra) -> |
111 |
:-( |
{Acc, process_adhoc_request(Acc, From, To, IQ, adhoc_sm_commands)}. |
112 |
|
|
113 |
|
-spec process_adhoc_request(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), |
114 |
|
Hook :: atom()) -> ignore | jlib:iq(). |
115 |
|
process_adhoc_request(Acc, From, To, #iq{sub_el = SubEl} = IQ, Hook) -> |
116 |
1 |
?LOG_DEBUG(#{what => adhoc_parse_request, iq => IQ, hook => Hook}), |
117 |
1 |
case adhoc:parse_request(IQ) of |
118 |
|
{error, Error} -> |
119 |
:-( |
IQ#iq{type = error, sub_el = [SubEl, Error]}; |
120 |
|
#adhoc_request{} = AdhocRequest -> |
121 |
1 |
HostType = mongoose_acc:host_type(Acc), |
122 |
1 |
case run_request_hook(Hook, HostType, From, To, AdhocRequest) of |
123 |
|
ignore -> |
124 |
:-( |
ignore; |
125 |
|
empty -> |
126 |
:-( |
IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:item_not_found()]}; |
127 |
|
{error, Error} -> |
128 |
:-( |
IQ#iq{type = error, sub_el = [SubEl, Error]}; |
129 |
|
Command -> |
130 |
1 |
IQ#iq{type = result, sub_el = [Command]} |
131 |
|
end |
132 |
|
end. |
133 |
|
|
134 |
|
run_request_hook(adhoc_local_commands, HostType, From, To, AdhocRequest) -> |
135 |
1 |
mongoose_hooks:adhoc_local_commands(HostType, From, To, AdhocRequest); |
136 |
|
run_request_hook(adhoc_sm_commands, HostType, From, To, AdhocRequest) -> |
137 |
:-( |
mongoose_hooks:adhoc_sm_commands(HostType, From, To, AdhocRequest). |
138 |
|
|
139 |
|
%%% |
140 |
|
%%% Hooks handlers |
141 |
|
%%% |
142 |
|
|
143 |
|
-spec disco_local_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). |
144 |
|
disco_local_items(Acc = #{host_type := HostType, to_jid := #jid{lserver = LServer}, node := <<>>, lang := Lang}) -> |
145 |
29 |
Items = case are_commands_visible(HostType) of |
146 |
|
false -> |
147 |
28 |
[]; |
148 |
|
_ -> |
149 |
1 |
[item(LServer, ?NS_COMMANDS, <<"Commands">>, Lang)] |
150 |
|
end, |
151 |
29 |
mongoose_disco:add_items(Items, Acc); |
152 |
|
disco_local_items(Acc = #{to_jid := #jid{lserver = LServer}, node := ?NS_COMMANDS, lang := Lang}) -> |
153 |
2 |
Items = [item(LServer, <<"ping">>, <<"Ping">>, Lang)], |
154 |
2 |
mongoose_disco:add_items(Items, Acc); |
155 |
|
disco_local_items(Acc = #{node := <<"ping">>}) -> |
156 |
:-( |
Acc#{result := []}; % override the result |
157 |
|
disco_local_items(Acc) -> |
158 |
:-( |
Acc. |
159 |
|
|
160 |
|
%%------------------------------------------------------------------------- |
161 |
|
|
162 |
|
-spec disco_sm_items(mongoose_disco:item_acc()) -> mongoose_disco:item_acc(). |
163 |
|
disco_sm_items(Acc = #{host_type := HostType, to_jid := To, node := <<>>, lang := Lang}) -> |
164 |
6 |
Items = case are_commands_visible(HostType) of |
165 |
|
false -> |
166 |
5 |
[]; |
167 |
|
_ -> |
168 |
1 |
[item(jid:to_binary(To), ?NS_COMMANDS, <<"Commands">>, Lang)] |
169 |
|
end, |
170 |
6 |
mongoose_disco:add_items(Items, Acc); |
171 |
|
disco_sm_items(Acc) -> |
172 |
4 |
Acc. |
173 |
|
|
174 |
|
are_commands_visible(HostType) -> |
175 |
35 |
gen_mod:get_module_opt(HostType, ?MODULE, report_commands_node, false). |
176 |
|
|
177 |
|
item(LServer, Node, Name, Lang) -> |
178 |
4 |
#{jid => LServer, node => Node, name => translate:translate(Lang, Name)}. |
179 |
|
|
180 |
|
%%------------------------------------------------------------------------- |
181 |
|
|
182 |
|
%% @doc On disco info request to the ad-hoc node, return automation/command-list. |
183 |
|
-spec disco_local_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). |
184 |
|
disco_local_identity(Acc = #{node := ?NS_COMMANDS, lang := Lang}) -> |
185 |
2 |
mongoose_disco:add_identities([command_list_identity(Lang)], Acc); |
186 |
|
disco_local_identity(Acc = #{node := <<"ping">>, lang := Lang}) -> |
187 |
2 |
mongoose_disco:add_identities([ping_identity(Lang)], Acc); |
188 |
|
disco_local_identity(Acc) -> |
189 |
157 |
Acc. |
190 |
|
|
191 |
|
%%------------------------------------------------------------------------- |
192 |
|
|
193 |
|
%% @doc On disco info request to the ad-hoc node, return automation/command-list. |
194 |
|
-spec disco_sm_identity(mongoose_disco:identity_acc()) -> mongoose_disco:identity_acc(). |
195 |
|
disco_sm_identity(Acc = #{node := ?NS_COMMANDS, lang := Lang}) -> |
196 |
2 |
mongoose_disco:add_identities([command_list_identity(Lang)], Acc); |
197 |
|
disco_sm_identity(Acc) -> |
198 |
9 |
Acc. |
199 |
|
|
200 |
|
ping_identity(Lang) -> |
201 |
2 |
#{category => <<"automation">>, |
202 |
|
type => <<"command-node">>, |
203 |
|
name => translate:translate(Lang, <<"Ping">>)}. |
204 |
|
|
205 |
|
command_list_identity(Lang) -> |
206 |
4 |
#{category => <<"automation">>, |
207 |
|
type => <<"command-list">>, |
208 |
|
name => translate:translate(Lang, <<"Commands">>)}. |
209 |
|
|
210 |
|
%%------------------------------------------------------------------------- |
211 |
|
|
212 |
|
-spec disco_local_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). |
213 |
|
disco_local_features(Acc = #{node := <<>>}) -> |
214 |
156 |
mongoose_disco:add_features([?NS_COMMANDS], Acc); |
215 |
|
disco_local_features(Acc = #{node := ?NS_COMMANDS}) -> |
216 |
|
%% override all lesser features... |
217 |
2 |
Acc#{result := []}; |
218 |
|
disco_local_features(Acc = #{node := <<"ping">>}) -> |
219 |
|
%% override all lesser features... |
220 |
2 |
Acc#{result := [?NS_COMMANDS]}; |
221 |
|
disco_local_features(Acc) -> |
222 |
1 |
Acc. |
223 |
|
|
224 |
|
%%------------------------------------------------------------------------- |
225 |
|
|
226 |
|
-spec disco_sm_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc(). |
227 |
|
disco_sm_features(Acc = #{node := <<>>}) -> |
228 |
9 |
mongoose_disco:add_features([?NS_COMMANDS], Acc); |
229 |
|
disco_sm_features(Acc = #{node := ?NS_COMMANDS}) -> |
230 |
|
%% override all lesser features... |
231 |
2 |
Acc#{result := []}; |
232 |
|
disco_sm_features(Acc) -> |
233 |
:-( |
Acc. |
234 |
|
|
235 |
|
%%------------------------------------------------------------------------- |
236 |
|
|
237 |
|
-spec ping_command(Acc :: command_hook_acc(), |
238 |
|
From :: jid:jid(), |
239 |
|
To :: jid:jid(), |
240 |
|
adhoc:request()) -> command_hook_acc(). |
241 |
|
ping_command(empty, _From, _To, |
242 |
|
#adhoc_request{lang = Lang, |
243 |
|
node = <<"ping">> = Node, |
244 |
|
session_id = SessionID, |
245 |
|
action = Action}) -> |
246 |
1 |
case Action == <<"">> orelse Action == <<"execute">> of |
247 |
|
true -> |
248 |
1 |
adhoc:produce_response( |
249 |
|
#adhoc_response{lang = Lang, |
250 |
|
node = Node, |
251 |
|
session_id = SessionID, |
252 |
|
status = completed, |
253 |
|
notes = [{<<"info">>, translate:translate(Lang, <<"Pong">>)}]}); |
254 |
|
false -> |
255 |
:-( |
{error, mongoose_xmpp_errors:bad_request()} |
256 |
|
end; |
257 |
|
ping_command(Acc, _From, _To, _Request) -> |
258 |
:-( |
Acc. |
259 |
|
|