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]).