Hook not being called in ejabberd - erlang

The problem I have is that the hook I defined is not being called on the event "user_send_packet". I did assume that any stanza (including messages) that is being sent would trigger this event.
I have taken into account the priority of how the hook is being called by setting it to '0'. In the log I have verified that the module is started ("mod_stanza_ack starting"). The erl file did compile, it only got the warning "gen_mod" is undefined, but I have read on a ejabberd mailing list that this is harmless and also that the variable "Host" is unused in line 12. Maybe this has something to do with it, but I cannot find out if this is the case.
-module(mod_stanza_ack).
-behaviour(gen_mod).
-include("ejabberd.hrl").
-export([start/2,
stop/1]).
-export([on_user_send_packet/3]).
start(Host, _Opts) ->
?INFO_MSG("mod_stanza_ack starting", []),
ejabberd_hooks:add(user_send_packet, global, ?MODULE, on_user_send_packet, 0),
ok.
stop(Host) ->
?INFO_MSG("mod_stanza_ack stopping", []),
ejabberd_hooks:delete(user_send_packet, global, ?MODULE, on_user_send_packet, 0),
ok.
on_user_send_packet(From, To, Packet) ->
?INFO_MSG("mod_stanza_ack a package has been sent coming from: ~p", [From]),
?INFO_MSG("mod_stanza_ack a package has been sent to: ~p", [To]),
?INFO_MSG("mod_stanza_ack a package has been sent with the following packet: ~p", [Packet]),
Packet.

The syntax looks OK and you have correct assumptions regarding the hook and compilation warnings. Which version of ejabberd do you use?
It seems there is bug/confusion in 'global' handling.
ejabberd_c2s which runs user_send_packet, runs it with Host as a scope.
The implementation of scopes is pretty naive and 'global' callback is not hooked up under Host hook.
In my 2.1.6 there is still the 'buggy' behaviour and I could reproduce your case.
To fix it add hook with Host, not 'global' keyword.

Related

Ejabberd filter_packet clause not firing

I'm currently trying to write an ejabberd module but am falling at the first hurdle so assume I am missing something simple.
The aim for the finished module is to inspect a message body and look for a certain term. If the term is present then the module will drop the message and send a reply to the originator informing them that their message has been dropped due to the violation. I do not want to just obfuscate the offending term.
I have built a skeleton test module using the 'hello world' example from the main ejabberd webpages and it works so I seem to be putting things in the right place and compiling them properly.
Taking inspiration from sources such as mod_pottymouth as well as related stack overflow questions such as Filtering message packet's body in ejabberd and How to filter messages in Ejabberd (not to mention various blogs and other articles) code along the lines of the following should work as far as I can tell:
-module(mod_helloworld).
-behaviour(gen_mod).
-include("logger.hrl").
-include("ejabberd.hrl").
-export([start/2, stop/1, on_filter_packet/1]).
start(_Host, _Opts) ->
?INFO_MSG("Hello there, ejabberd world!", []),
ejabberd_hooks:add(filter_packet, global, ?MODULE, on_filter_packet, 0),
ok.
stop(_Host) ->
?INFO_MSG("Bye bye, ejabberd world!", []),
ejabberd_hooks:delete(filter_packet, global, ?MODULE, on_filter_packet, 0),
ok.
on_filter_packet(drop) ->
?INFO_MSG("world - Message was dropped",[]),
drop;
on_filter_packet({_From, _To, Xml} = Packet) ->
% this clause never fires
?INFO_MSG("world - message hit clause", [Xml]),
Packet;
on_filter_packet(Msg) ->
% catch-all clause for non-message types
?INFO_MSG("world - message hit default", []),
Msg.
When the module runs every packet triggers the catch-all clause so I know the hooks seem to be doing their job, and if I check the main ejabberd logs then messages are going through just fine. If I remove the catch-all then the filter just crashes.
I've tried a number of variations of the filter clause taken from various examples (plus supporting code) e.g. :
{_From, _To, #xmlel{name = StanzaType}} = Input
or
{_From, _To, {xmlel, <<"message">>, _Attrs, Els} = _Packet} = _Msg
but none of them seem to be working.
I'm using two different Centos 7 set-ups (one with ejabberd installed as a package running on a physical box, the other using Vagrant with ejabberd installed from source) and a number of different XMPP clients, but the result is the same. Ejabberd version is 17.07.31.
What obvious thing am I missing?

ejabberd: get users (version & os) when connect to the server

I'm trying to read users os "Windows XP, Windows 7, Linux .... etc" when they connected I'm working on ejabberd 2.1.x but nothing shown to me.
What I'm doing is :
-module(mod_test).
-behaviour(gen_mod).
%% gen_mod callbacks
-export([start/2, stop/1]).
%% hook handlers
-export([user_send_packet/3, filter_packet/1]).
-include("ejabberd.hrl").
-include("jlib.hrl").
-define(PROCNAME, ejabberd_mod_filter).
start(Host, _Opts) ->
ejabberd_hooks:add(user_send_packet, Host, ?MODULE, user_send_packet, 75),
ejabberd_hooks:add(filter_packet, global, ?MODULE, filter_packet, 75),
ok.
stop(Host) ->
ejabberd_hooks:delete(filter_packet, global, ?MODULE, filter_packet, 75),
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, user_send_packet, 75),
ok.
user_send_packet(_From, _To, _Packet) ->
ok.
filter_packet({From, To, Packet}) ->
io:format("~p", [Packet]),
filter_packet(Arg) ->
Arg.
It's return with all users data like (status, resource, caps ... etc) else (version, os, client name) are there any way to do this ?
I belive there is away to do this cuz in (muc chat) there is many bot do this like if I type :
version some_user
return to me with this msg :
name : Psi+
version : 1.1
os : Windows XP
but I don't know how they do this.
You can get this information by sending a "Software Version" request to the client as described in XEP-0092. This is something ejabberd usually doesn't do, so you may have to write code for tracking responses yourself. (The basic idea is that the server sends the request with a certain id, and then needs to check for responses with that same id, taking timeouts and errors into account.)

MongooseIM simple module event not getting handled

I am new in erlang and ejabbered/mongooseIM. I am trying to write a very simple mongoose module that will add an extra child element to the packets before sending. Below is my code:
-module(mod_test).
-behavior(gen_mod).
-export([start/2, stop/1]).
-export([add_child/1]).
-include("ejabberd.hrl").
start(Host, Opts) ->
ejabberd_hooks:add(filter_packet, Host, ?MODULE, add_child, 0),
?DEBUG(" MOD_TEST Started",[]),
ok.
stop(Host) ->
ejabberd_hooks:delete(filter_packet, Host, ?MODULE, add_child, 0),
ok.
add_child({From, To, XML} = Packet) ->
Tag = {"a","b"},
NewPacket = xml:append_subtags(Packet, [Tag]),
?INFO_MSG(" To party: ~p~n",[To]),
NewPacket.
I can compile the code with few warnings
mod_test.erl:3: Warning: behaviour gen_mod undefined
mod_test.erl:11: Warning: variable 'Opts' is unused
mod_test.erl:20: Warning: variable 'From' is unused
mod_test.erl:20: Warning: variable 'XML' is unused
Then when I add the module and run it it gets started but it doesnt make any changes to the packets and doesnt generate any logs either.
Another issue is, if I add a log within my start function, it gets compiled but I see errors while starting the module
2015-03-03 16:36:34.772 [critical] <0.200.0>#gen_mod:start_module:94 Problem starting the module mod_test for host <<"localhost">>
options: []
error: undef
[{lager,info,[" mod_test starting ...",[[]]],[]},
{mod_test,start,2,[{file,"mod_test.erl"},{line,13}]},
{gen_mod,start_module,3,[{file,"src/gen_mod.erl"},{line,83}]},
{lists,foreach,2,[{file,"lists.erl"},{line,1336}]},
{ejabberd_app,start,2,[{file,"src/ejabberd_app.erl"},{line,69}]},
{application_master,start_it_old,4,
[{file,"application_master.erl"},{line,272}]}]
2015-03-03 16:36:34.773 [critical] <0.200.0>#gen_mod:start_module:99 ejabberd initialization was aborted because a module start failed.
The trace is [{lager,info,[" mod_test starting ...",[[]]],[]},{mod_test,start,2,[{file,"mod_test.erl"},{line,13}]},{gen_mod,start_module,3,[{file,"src/gen_mod.erl"},{line,83}]},{lists,foreach,2,[{file,"lists.erl"},{line,1336}]},{ejabberd_app,start,2,[{file,"src/ejabberd_app.erl"},{line,69}]},{application_master,start_it_old,4,[{file,"application_master.erl"},{line,272}]}].
Crash dump was written to: erl_crash.dump
Problem starting the module mod_test for host <<"localhost">>
options: []
error: undef
[{lager,info,[" mod_xyz starting ...",[[]]],[]},
{mod_test,start,2,[{file,"mod_timetagg
What wrong am I doing?
So your example is actually a bit tricky, because of how the filter_packet hook works. You picked the worst hook to register for on your first attempt ;)
If you look into ejabberd_router:do_route, you'll see that filter_packet is run without a Host parameter -- it is a global hook, so when you register your add_child function for a particular Host, it will essentially be ignored.
Try the following:
-module(mod_test).
-behavior(gen_mod).
-export([start/2, stop/1]).
-export([add_child/1]).
-include_lib("ejabberd/include/ejabberd.hrl").
-include_lib("exml/include/exml.hrl").
start(Host, Opts) ->
ejabberd_loglevel:set_custom(?MODULE, 5),
ejabberd_hooks:add(filter_local_packet, Host, ?MODULE, add_child, 1),
?DEBUG(" MOD_TEST Started",[]),
ok.
stop(Host) ->
ejabberd_hooks:delete(filter_local_packet, Host, ?MODULE, add_child, 1),
ok.
add_child({From, To, Element} = HookData) ->
?DEBUG("Filtering ~p~n", [HookData]),
case Element#xmlel.name of
<<"message">> ->
Tag = #xmlel{name = <<"added-tag">>, attrs = [], children = []},
NewElement = xml:append_subtags(Element, [Tag]),
?DEBUG("will return new el: ~p", [NewElement]),
{From, To, NewElement};
_ ->
?DEBUG("will pass old el: ~p", [Element]),
HookData
end.
Registering for filter_local_packet on your given Host will now work, and all incoming stanzas will be passed to your function. It is important to remember that adding spurious tags to all stanzas may break things, so the above code will only add a <added-tag> element to <message> stanzas.
Use the example above and work from there.
Good luck!
The error is undef, which means that a function was called that isn't defined/exported. The stacktrace shows that the function in question is lager:info/2.
Lager (the logging library that handles your ?INFO_MSG) has a special quirk in that your code would call non-existent functions, but the code gets transformed by a parse transform before being compiled. It looks like this didn't happen for you.
The rebar.config file in apps/ejabberd in the MongooseIM tree contains {parse_transform, lager_transform} in erl_opts, which asks the compiler to apply the parse transform. I'd suggest putting mod_test.erl into apps/ejabberd/src and build the entire MongooseIM tree; that would ensure that your file gets built with the correct options.

erlang otp child workers

I'm trying to get an OTP supervisor to start child workers which will (eventually) connect to remote servers. I used Rebar to create a template test application and I'm trying to get the supervisor to fire off function 'hi' in module 'foo'. it compiles OK and runs:
Eshell V5.8.5 (abort with ^G)
1> test_app:start(1,1).
{ok,<0.34.0>}
but when I try to start the worker it goes pear shaped with this error:
2> test_sup:start_foo().
{error,{badarg,{foo,{foo,start_link,[]},
permanent,5000,worker,
[foo]}}}
The problem seems similar, but not the same, to this question: Erlang - Starting a child from the supervisor module
Any ideas?
test_app.erl
-module(test_app).
-behaviour(application).net
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
test_sup:start_link().
stop(_State) ->
ok.
Test_sup.erl:
-module(test_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1, start_foo/0]).
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, { {one_for_one, 5, 10}, []} }.
start_foo()->
supervisor:check_childspecs(?CHILD(foo, worker)),
supervisor:start_child(?MODULE, ?CHILD(foo, permanent)).
foo.erl:
-module(foo).
-export([hi/0]).
hi()->
io:format("worker ~n").
You check the childspec using the macro call ?CHILD(foo, worker) while you try to start the child with the macro using the macro call ?CHILD(foo, permanent). The second argument of the CHILD macro is the process type which should be either worker or supervisor. So the first macro call is correct. The value permanent is a value for the restart type, which you have already set to permanent, so the second call is wrong and you get a badarg error.
Note: It is quite common that library functions generate badarg errors as well, not just from built-in functions. It is not always obvious why it is a badarg.
I think that Robert answer is incomplete, after replacing permanent by worker you still have an error returned by supervisor:check_childspecs(?CHILD(foo, worker)),, I don't know why.
[edit]
The problem of bard arg comes from ... badarg :o)
check_childspecs extepect a list of child_specs, the correct syntax is supervisor:check_childspecs([?CHILD(foo, worker)]), and then it works fine. the following code is updated.
[end of edit]
But you will get also an error because the supervisor will try to launch the function foo:start_link that does not exist in the foo module.
the following code print an error, but seems to work properly.
-module(foo).
-export([hi/0,start_link/0,loop/0]).
start_link() ->
{ok,spawn_link(?MODULE,loop,[])}.
hi()->
io:format("worker ~n").
loop() ->
receive
_ -> ok
end.
-module(test_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1, start_foo/0]).
-define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
{ok, { {one_for_one, 5, 10}, []} }.
start_foo()->
io:format("~p~n",[supervisor:check_childspecs([?CHILD(foo, worker)])]),
supervisor:start_child(?MODULE, ?CHILD(foo, worker)).
[edit]
answering to David comment
in my code the loop/0 does not loop at all, on the receive block, the process waits for any message, and as soon as it receives one, the process dies returning the value ok. So as long as the worker process does not receive any message, it keeps living, which is nice when you make some test with supervisors :o).
On the opposite, the hi/0 function simply prints 'worker' on the console and finishes. As the restart strategy of the supervisor is one_for_one, the max restart is 5 and the child process is permanent, the supervisor will try to start the hi process 5 times, printing five time 'worker' on the console, and then it will give up and terminate itself with an error message ** exception error: shutdown
Generally you should choose permanent for never ending processes (main server of an application for example). For process that normally die as soon as they have done their job, you should use temporary. I never used transient but I read that it should be used for process that must complete a task before dying.

Erlang newbie: why do I have to restart to load new code

I am trying to write a first program in Erlang that effects message communication between a client and server. In theory the server exits when it receives no message from the client, but every time I edit the client code and run the server again, it executes the old code. I have to ^G>q>erl>[re-enter command] to get it to see the new code.
-module(srvEsOne).
%%
%% export functions
%%
-export([start/0]).
%%function definition
start()->
io:format("Server: Starting at pid: ~p \n",[self()]),
case lists:member(serverEsOne, registered()) of
true ->
unregister(serverEsOne); %if the token is present, remove it
false ->
ok
end,
register(serverEsOne,self()),
Pid = spawn(esOne, start,[self()]),
loop(false, false,Pid).
%
loop(Prec, Nrec,Pd)->
io:format("Server: I am waiting to hear from: ~p \n",[Pd]),
case Prec of
true ->
case Nrec of
true ->
io:format("Server: I reply to ~p \n",[Pd]),
Pd ! {reply, self()},
io:format("Server: I quit \n",[]),
ok;
false ->
receiveLoop(Prec,Nrec,Pd)
end;
false ->
receiveLoop(Prec,Nrec,Pd)
end.
receiveLoop(Prec,Nrec,Pid) ->
receive
{onPid, Pid}->
io:format("Server: I received a message to my pid from ~p \n",[Pid]),
loop(true, Nrec,Pid);
{onName,Pid}->
io:format("Server: I received a message to name from ~p \n",[Pid]),
loop(Prec,true,Pid)
after
5000->
io:format("Server: I received no messages, i quit\n",[]),
ok
end.
And the client code reads
-module(esOne).
-export([start/1, func/1]).
start(Par) ->
io:format("Client: I am ~p, i was spawned by the server: ~p \n",[self(),Par]),
spawn(esOne, func, [self()]),
io:format("Client: Now I will try to send a message to: ~p \n",[Par]),
Par ! {self(), hotbelgo},
serverEsOne ! {self(), hotbelgo},
ok.
func(Parent)->
io:format("Child: I am ~p, i was spawned from ~p \n",[self(),Parent]).
The server is failing to receive a message from the client, but I can't sensibly begin to debug that until I can try changes to the code in a more straightforward manner.
When you make modification to a module you need to compile it.
If you do it in an erlang shell using the command c(module) or c(module,[options]), the new compiled version of the module is automatically loaded in that shell. It will be used by all the new process you launch.
For the one that are alive and already use it is is more complex to explain and I think it is not what you are asking for.
If you have several erlang shells running, only the one where you compile the module loaded it. That means that in the other shell, if the module were previously loaded, basically if you already use the module in those shell, and even if the corresponding processes are terminated, the new version is ignored.
Same thing if you use the command erlc to compile.
In all these cases, you need to explicitly load the module with the command l(module) in the shell.
Your server loop contain only local function calls. Running code is changed only if there is remote (or external) function call. So you have to export your loop function first:
-export([loop/3]).
and then you have to change all loop/3 calls in function receiveLoop/3 to
?MODULE:loop(...)
Alternatively you can do same thing with receiveLoop/3 instead. Best practice for serious applications is doing hot code swapping by demand so you change loop/3 to remote/external only after receiving some special message.

Resources