Question

I am writing an ejabberd hook to create an outgoing SSL connection with Apple Push Notification Server. I have tested the send method outside of ejabberd (in the erlang interpreter) and can verify that it works. I am unsure as to why this module:

-module(mod_http_offline).

-author("Joseph Martin").

%% Every ejabberd module implements the gen_mod behavior
%% The gen_mod behavior requires two functions: start/2 and stop/1
-behaviour(gen_mod).

%% public methods for this module
-export([start/2, stop/1, create_message/3]).

%% included for writing to ejabberd log file
-include("ejabberd.hrl").

%% ejabberd functions for JID manipulation called jlib.
-include("jlib.hrl").

start(_Host, _Opt) -> 
        ?INFO_MSG("mod_http_offline loading", []),
        %send("Test Push"),
        ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 50).   


stop (_Host) -> 
        ?INFO_MSG("stopping mod_http_offline", []),
        ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 50).


create_message(_From, _To, Packet) ->
        Type = xml:get_tag_attr_s("type", Packet),
        FromS = xml:get_tag_attr_s("from", Packet),
        ToS = xml:get_tag_attr_s("to", Packet),
        Body = xml:get_path_s(Packet, [{elem, "body"}, cdata]),
        if (Type == "chat") ->
            send("You did it!","10","Chime")
        end.

% All argument fields expect a string
send(Msg) ->
  send_pn([{alert, Msg}]).
% send a string and and a bagde number
send(Msg, Badge) ->
  send_pn([{alert, Msg}, {badge, Badge}]).
% send a string, a badge number and play a sound
send(Msg, Badge, Sound) ->
  send_pn([{alert, Msg}, {badge, Badge}, {sound, Sound}]).

send_pn(Msg) ->

  % start ssl
    ssl:start(),
  % application:start(ssl),

  % socket configuration, may need to increase the timeout if concurrency becomes an issue
  Address = "gateway.sandbox.push.apple.com",
  % Address = "gateway.push.apple.com",
  Port = 2195,
  Cert = "/Users/joemartin/Desktop/PushNotificationCertificates/PushChatCert.pem",
  Key  = "/Users/joemartin/Desktop/PushNotificationCertificates/PushChatKey.pem",
  Options = [{certfile, Cert}, {keyfile, Key}, {password, "mypassword"}, {mode, binary}, {verify, verify_none}],
  Timeout = 5000,

  case ssl:connect(Address, Port, Options, Timeout) of
    {ok, Socket} ->

      % Convert the device token from hex to int to binary
      Token = "3eca19d7...mydevicetokenstring",
      TokenNum = erlang:list_to_integer(Token, 16),
      TokenBin = <<TokenNum:32/integer-unit:8>>,

      % Construct the protocol packet
      PayloadString = create_json(Msg),
      Payload = list_to_binary(PayloadString),
      PayloadLength = byte_size(Payload),
      Packet = <<0:8, 32:16, TokenBin/binary, PayloadLength:16, Payload/binary>>,

      % Send the packet then close the socket
      ssl:send(Socket, Packet),
      ssl:close(Socket),

      % Return the PayloadString (for debugging purposes)
      PayloadString;
    {error, Reason} ->
        ?INFO_MSG("THE SSL CONNECTION FAILED", []),
      Reason
  end.

% helper for creating json
create_json(List) ->
  lists:append(["{\"aps\":{", create_keyvalue(List), "}}"]).

create_keyvalue([Head]) ->
  create_pair(Head);

create_keyvalue([Head|Tail]) ->
  lists:append([create_pair(Head), ",", create_keyvalue(Tail)]).

create_pair({Key, Value}) ->
  lists:append([add_quotes(atom_to_list(Key)), ":", add_quotes(Value)]).

add_quotes(String) ->
  lists:append(["\"", String, "\""]).

Creates this stacktrace in my error log:

=ERROR REPORT==== 2013-12-31 16:23:57 ===
E(<0.1558.0>:ejabberd_hooks:294) : {undef,
                                    [{tls,connect,
                                      ["gateway.sandbox.push.apple.com",2195,
                                       [{certfile,
                                         "/Users/joemartin/Desktop/PushNotificationCertificates/PushChatCert.pem"},
                                        {keyfile,
                                         "/Users/joemartin/Desktop/PushNotificationCertificates/PushChatKey.pem"},
                                        {password,"mypassword"},
                                        {mode,binary},
                                        {verify,verify_none}],
                                       5000],
                                      []},
                                     {mod_http_offline,send_pn,1,
                                      [{file,"mod_http_offline.erl"},
                                       {line,69}]},
                                     {ejabberd_hooks,run1,3,
                                      [{file,"ejabberd_hooks.erl"},
                                       {line,290}]},
                                     {ejabberd_sm,route,3,
                                      [{file,"ejabberd_sm.erl"},{line,87}]},
                                     {ejabberd_local,route,3,
                                      [{file,"ejabberd_local.erl"},
                                       {line,120}]},
                                     {ejabberd_router,route,3,
                                      [{file,"ejabberd_router.erl"},
                                       {line,68}]},
                                     {ejabberd_c2s,session_established2,2,
                                      [{file,"ejabberd_c2s.erl"},{line,1122}]},
                                     {p1_fsm,handle_msg,10,
                                      [{file,"p1_fsm.erl"},{line,544}]}]}

I have read that ejabberd 2 has old-style SSL disabled by default, but thought that was only for incoming client connections. I also haven't found a good example of a hook that makes an outgoing SSL connection. I also realize the name mod_http_offline is a misnomer. Any ideas?

Thank you, Joe

Was it helpful?

Solution 2

Thanks for directing me to that buglist. The problem was that the tls module included with this version of ejabberd shadows erlang's tls module. Rather than fooling with symbolic links and mending that, I decided to just use the binary distribution of ejabberd 2.1.13 to compile in this module.

My steps were as follows:

1) Install ejabberd from the .app binary (I'm using MAC OSX)

2) Navigate to /Applications/ejabberd-directory/ebin

3) Use the included erlc (not the one running out of my path) to compile my module:

./erlc -I /Applications/ejabberd-2.1.13/lib/ejabberd-2.1.13/include/ mod_http_offline.erl

4) Add the module to the ebin directory with all the other beam files

5) Modify the ejabberd config file to include {mod_http_offline, []}

6) Restart my installation

OTHER TIPS

I saw it reported on ejabberd buglist on github.

Just to play with it downgrade one or another. To solve it for production use tailor the dependencies to your needs.

Re module name, make sure it is unique and consistent with start/stop callbacks. Probably it would be good to stick to convention and have mod prefix. Also the new name goes to ejabberd.cfg (in modules section).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top