1 %%%----------------------------------------------------------------------
2 %%% File    : mod_echo.erl
3 %%% Author  : Alexey Shchepin <[email protected]>
4 %%% Purpose : Simple ejabberd module.
5 %%% Created : 15 Jan 2003 by Alexey Shchepin <[email protected]>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2012   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., 59 Temple Place, Suite 330, Boston, MA
23 %%% 02111-1307 USA
24 %%%
25 %%%----------------------------------------------------------------------
26 
27 
28 -module(mod_echo).
29 
30 -author('[email protected]').
31 
32 -behaviour(gen_server).
33 
34 -behaviour(gen_mod).
35 
36 %% API
37 -export([start_link/2, start/2, stop/1]).
38 
39 %% gen_server callbacks
40 -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
41 	 code_change/3]).
42 
43 -include_lib("exmpp/include/exmpp.hrl").
44 
45 -include("ejabberd.hrl").
46 
47 -record(state, {host, client_version}).
48 
49 -define(PROCNAME, ejabberd_mod_echo).
50 
51 %%====================================================================
52 %% API
53 %%====================================================================
54 %%--------------------------------------------------------------------
55 %% Function: start_link() -> {ok,Pid} | ignore | {error,Error}
56 %% Description: Starts the server
57 %%--------------------------------------------------------------------
58 start_link(Host, Opts) ->
59     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
60     gen_server:start_link({local, Proc}, ?MODULE, [Host, Opts], []).
61 
62 start(Host, Opts) ->
63     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
64     ChildSpec = {Proc, {?MODULE, start_link, [Host, Opts]}, temporary, 1000, worker,
65 		 [?MODULE]},
66     supervisor:start_child(ejabberd_sup, ChildSpec).
67 
68 stop(Host) ->
69     Proc = gen_mod:get_module_proc(Host, ?PROCNAME),
70     gen_server:call(Proc, stop),
71     supervisor:terminate_child(ejabberd_sup, Proc),
72     supervisor:delete_child(ejabberd_sup, Proc).
73 
74 %%====================================================================
75 %% gen_server callbacks
76 %%====================================================================
77 
78 
79 %%--------------------------------------------------------------------
80 %% Function: init(Args) -> {ok, State} |
81 %%                         {ok, State, Timeout} |
82 %%                         ignore               |
83 %%                         {stop, Reason}
84 %% Description: Initiates the server
85 %%--------------------------------------------------------------------
86 init([Host, Opts]) ->
87     MyHost = gen_mod:get_opt_host(Host, Opts, "echo.@HOST@"),
88     ClientVersion = gen_mod:get_opt(client_version, Opts, false),
89     ejabberd_router:register_route(MyHost),
90     {ok, #state{host = MyHost, client_version = ClientVersion}}.
91 
92 %%--------------------------------------------------------------------
93 %% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
94 %%                                      {reply, Reply, State, Timeout} |
95 %%                                      {noreply, State} |
96 %%                                      {noreply, State, Timeout} |
97 %%                                      {stop, Reason, Reply, State} |
98 %%                                      {stop, Reason, State}
99 %% Description: Handling call messages
100 %%--------------------------------------------------------------------
101 handle_call(stop, _From, State) -> {stop, normal, ok, State}.
102 
103 %%--------------------------------------------------------------------
104 %% Function: handle_cast(Msg, State) -> {noreply, State} |
105 %%                                      {noreply, State, Timeout} |
106 %%                                      {stop, Reason, State}
107 %% Description: Handling cast messages
108 %%--------------------------------------------------------------------
109 handle_cast(_Msg, State) -> {noreply, State}.
110 
111 %%--------------------------------------------------------------------
112 %% Function: handle_info(Info, State) -> {noreply, State} |
113 %%                                       {noreply, State, Timeout} |
114 %%                                       {stop, Reason, State}
115 %% Description: Handling all non call/cast messages
116 %%--------------------------------------------------------------------
117 handle_info({route, From, To, Packet}, State) ->
118     Packet2 = case exmpp_jid:node(From) of
119 		undefined -> exmpp_stanza:reply_with_error(Packet, 'bad-request');
120 		_ -> Packet
121 	      end,
122     do_client_version(State#state.client_version, To, From),
123     ejabberd_router:route(To, From, Packet2),
124     {noreply, State};
125 handle_info(_Info, State) -> {noreply, State}.
126 
127 %%--------------------------------------------------------------------
128 %% Function: terminate(Reason, State) -> void()
129 %% Description: This function is called by a gen_server when it is about to
130 %% terminate. It should be the opposite of Module:init/1 and do any necessary
131 %% cleaning up. When it returns, the gen_server terminates with Reason.
132 %% The return value is ignored.
133 %%--------------------------------------------------------------------
134 terminate(_Reason, State) -> ejabberd_router:unregister_route(State#state.host), ok.
135 
136 %%--------------------------------------------------------------------
137 %% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
138 %% Description: Convert process state when code is changed
139 %%--------------------------------------------------------------------
140 code_change(_OldVsn, State, _Extra) -> {ok, State}.
141 
142 %%--------------------------------------------------------------------
143 %% Example of routing XMPP packets using Erlang's message passing
144 %%--------------------------------------------------------------------
145 
146 
147 %% To enable this educational example, configure the module option
148 %% client_version like this:
149 %% {modules, [
150 %%   {mod_echo, [{client_version, true}]},
151 %%   ...
152 %% ]}.
153 
154 
155 %% ejabberd provides a method to receive XMPP packets using Erlang's
156 %% message passing mechanism.
157 %%
158 %% The packets received by ejabberd are sent
159 %% to the local destination process by sending an Erlang message.
160 %% This means that you can receive XMPP stanzas in an Erlang process
161 %% using Erlang's Receive, as long as this process is registered in
162 %% ejabberd as the process which handles the destination JID.
163 %%
164 %% This example function is called when a client queries the echo service.
165 %% This function then sends a query to the client, and waits 5 seconds to
166 %% receive an answer. The answer will only be accepted if it was sent
167 %% using exactly the same JID. We add a (mostly) random resource to
168 %% try to guarantee that the received response matches the request sent.
169 %% Finally, the received response is printed in the ejabberd log file.
170 do_client_version(false, _From, _To) -> ok;
171 do_client_version(true, From, To) ->
172         %% It is important to identify this process and packet
173     Random_resource = integer_to_list(random:uniform(100000)),
174     From2 = exmpp_jid:full(From, Random_resource),
175         %% Build an iq:query request
176     Request = #xmlel{ns = ?NS_SOFT_VERSION, name = 'query'},
177     Packet = exmpp_stanza:set_recipient(exmpp_iq:get(?NS_JABBER_CLIENT, Request), To),
178         %% Send the request
179     ejabberd_router:route(From2, To, Packet),
180         %% Wait to receive the response
181 	%% It is very important to only accept a packet which is the
182 	%% response to the request that he sent
183     Els = receive
184 	    {route, To, From2, IQ} ->
185 		#xmlel{ns = ?NS_SOFT_VERSION, name = 'query', children = List} =
186 		    exmpp_iq:get_payload(IQ),
187 		List
188 	    after 5000 -> % Timeout in miliseconds: 5 seconds
189 		      []
190 	  end,
191     Values = [{Name, exmpp_xml:get_cdata_as_list(El)} || #xmlel{name = Name} = El <- Els],
192         %% Print in log
193     Values_string1 = [io_lib:format("~n~s: ~p", [N, V]) || {N, V} <- Values],
194     Values_string2 = lists:concat(Values_string1),
195     ?INFO_MSG("Information of the client: ~s~s",
196 	      [exmpp_jid:to_binary(To), Values_string2]).