ejabberd-module

How to implement modules in ejabberd?


I am new on the XMPP server ejabberd. I installed ejabberd on ubuntu from this link: https://docs.ejabberd.im/admin/installation/#install-on-linux. I am using the default ejabberd.yml file which is present in ejabberd-20.07/conf folder. Here is my ejabberd.yml file:

hosts:
  - "faiqkhan-VirtualBox"

loglevel: 4
log_rotate_size: 10485760
log_rotate_count: 1

certfiles:
  - "/home/faiqkhan/ejabberd-20.07/conf/server.pem"
##  - "/etc/letsencrypt/live/localhost/fullchain.pem"
##  - "/etc/letsencrypt/live/localhost/privkey.pem"

ca_file: "/home/faiqkhan/ejabberd-20.07/conf/cacert.pem"

listen:
  -
    port: 5222
    ip: "::"
    module: ejabberd_c2s
    max_stanza_size: 262144
    shaper: c2s_shaper
    access: c2s
    starttls_required: false
  -
    port: 5269
    ip: "::"
    module: ejabberd_s2s_in
    max_stanza_size: 524288
  -
    port: 5443
    ip: "::"
    module: ejabberd_http
    tls: true
    request_handlers:
      "/admin": ejabberd_web_admin
      "/api": mod_http_api
      "/bosh": mod_bosh
      "/captcha": ejabberd_captcha
      "/upload": mod_http_upload
      "/ws": ejabberd_http_ws
      "/oauth": ejabberd_oauth
  -
    port: 5280
    ip: "::"
    module: ejabberd_http
    request_handlers:
      "/admin": ejabberd_web_admin
  -
    port: 1883
    ip: "::"
    module: mod_mqtt
    backlog: 1000

s2s_use_starttls: optional

acl:
  local:
    user_regexp: ""
  loopback:
    ip:
      - 127.0.0.0/8
      - ::1/128
      - ::FFFF:127.0.0.1/128
  admin:
    user:
      - "admin@faiqkhan-VirtualBox"

access_rules:
  local:
    allow: local
  c2s:
    deny: blocked
    allow: all
  announce:
    allow: admin
  configure:
    allow: admin
  muc_create:
    allow: local
  pubsub_createnode:
    allow: local
  trusted_network:
    allow: local

api_permissions:
  "console commands":
    from:
      - ejabberd_ctl
    who: all
    what: "*"
  "admin access":
    who:
      access:
        allow:
          acl: loopback
          acl: admin
      oauth:
        scope: "ejabberd:admin"
        access:
          allow:
            acl: loopback
            acl: admin
    what:
      - "*"
      - "!stop"
      - "!start"
  "public commands":
    who:
      ip: 127.0.0.1/8
    what:
      - status
      - connected_users_number

shaper:
  normal: 1000
  fast: 50000

shaper_rules:
  max_user_sessions: 10
  max_user_offline_messages:
    5000: admin
    100: all
  c2s_shaper:
    none: admin
    normal: all
  s2s_shaper: fast

max_fsm_queue: 10000

acme:
   contact: "mailto:admin@faiqkhan-VirtualBox"
   ca_url: "https://acme-v02.api.letsencrypt.org/directory"

modules:
  mod_adhoc: {}
  mod_admin_extra: {}
  mod_announce:
    access: announce
  mod_avatar: {}
  mod_blocking: {}
  mod_bosh: {}
  mod_caps: {}
  mod_carboncopy: {}
  mod_client_state: {}
  mod_configure: {}
  mod_disco: {}
  mod_fail2ban: {}
  mod_http_api: {}
  mod_http_upload:
    put_url: https://@HOST@:5443/upload
  mod_last: {}
  mod_mam:
    ## Mnesia is limited to 2GB, better to use an SQL backend
    ## For small servers SQLite is a good fit and is very easy
    ## to configure. Uncomment this when you have SQL configured:
    ## db_type: sql
    assume_mam_usage: true
    default: never
  mod_mqtt: {}
  mod_muc:
    access:
      - allow
    access_admin:
      - allow: admin
    access_create: muc_create
    access_persistent: muc_create
    access_mam:
      - allow
    default_room_options:
      allow_subscription: true  # enable MucSub
      mam: false
  mod_muc_admin: {}
  mod_offline:
    access_max_user_messages: max_user_offline_messages
  mod_ping: {}
  mod_privacy: {}
  mod_private: {}
  mod_proxy65:
    access: local
    max_connections: 5
  mod_pubsub:
    access_createnode: pubsub_createnode
    plugins:
      - flat
      - pep
    force_node_config:
      ## Avoid buggy clients to make their bookmarks public
      storage:bookmarks:
        access_model: whitelist
  mod_push: {}
  mod_push_keepalive: {}
  mod_register:
    ## Only accept registration requests from the "trusted"
    ## network (see access_rules section above).
    ## Think twice before enabling registration from any
    ## address. See the Jabber SPAM Manifesto for details:
    ## https://github.com/ge0rg/jabber-spam-fighting-manifesto
    ip_access: all
  mod_roster:
    versioning: true
  mod_s2s_dialback: {}
  mod_shared_roster: {}
  mod_stream_mgmt:
    resend_on_timeout: if_offline
  mod_vcard: {}
  mod_vcard_xupdate: {}
  mod_version:
    show_os: false
  mod_stanza_ack: {}

I try the given code in the link question Ejabberd return message to sender hook / message receipts and added my module in ejabberd.yml file which is in the last line of the above code. I create mod_stanza_ack.erl file and compile the file using command

./erlc mod_stanza_ack.erl

and get mod_stanza_ack.beam file. I coped mod_stanza_ack.beam file to ejabberd-20.07/lib/ejabberd-20.07/ebin folder where all the module files are present. Then I start ejabberd server using

./ejabberdctl live

command to view logs. Module works for me but on the server-side, it always crashes with an error

**Hook user_send_packet crashed when running mod_stanza_ack:on_user_send_packet/1:
    ** exception error: undefined function mod_stanza_ack:on_user_send_packet/1
       in function  ejabberd_hooks:safe_apply/4 (src/ejabberd_hooks.erl, line 236)
       in call from ejabberd_hooks:run_fold1/4 (src/ejabberd_hooks.erl, line 217)
       in call from ejabberd_c2s:handle_authenticated_packet/2 (src/ejabberd_c2s.erl, line 484)
       in call from xmpp_stream_in:process_authenticated_packet/2 (src/xmpp_stream_in.erl, line 714)
       in call from xmpp_stream_in:handle_info/2 (src/xmpp_stream_in.erl, line 404)
       in call from p1_server:handle_msg/8 (src/p1_server.erl, line 696)
       in call from proc_lib:init_p_do_apply/3 (proc_lib.erl, line 249)**.

Did I miss something? Or using deprecated functions?


Solution

  • Well, that example source code is six years old, and ejabberd development API has changed since then. I've updated the example, and this compiles and starts correctly with ejabberd 20.07:

    -module(mod_stanza_ack).
    -behaviour(gen_mod).
    
    -include("xmpp.hrl").
    -include("logger.hrl").
    -include("translate.hrl").
    
    -export([start/2, stop/1, mod_options/1, mod_doc/0, depends/2]).
    -export([on_user_send_packet/1]).
    
    start(Host, _Opts) ->
        ?INFO_MSG("mod_stanza_ack starting", []),
        ejabberd_hooks:add(user_send_packet, Host, ?MODULE, on_user_send_packet, 0),
        ok.
    
    stop(Host) ->
        ?INFO_MSG("mod_stanza_ack stopping", []),
        ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, on_user_send_packet, 0),
        ok.
    
    on_user_send_packet({#presence{to = To, from = From} = Packet, C2SState}) ->
        ?INFO_MSG("mod_stanza_ack a presence has been sent coming from: ~p", [From]),
        ?INFO_MSG("mod_stanza_ack a presence has been sent to: ~p", [To]),
        ?INFO_MSG("mod_stanza_ack a presence has been sent with the following packet:~n ~p", [Packet]),
        {Packet, C2SState};
    
    on_user_send_packet({#iq{to = To, from = From} = Packet, C2SState}) ->
        ?INFO_MSG("mod_stanza_ack a iq has been sent coming from: ~p", [From]),
        ?INFO_MSG("mod_stanza_ack a iq has been sent to: ~p", [To]),
        ?INFO_MSG("mod_stanza_ack a iq has been sent with the following packet:~n ~p", [Packet]),
        {Packet, C2SState};
    
    on_user_send_packet({#message{to = To, from = From} = Packet, C2SState}) ->
        ?INFO_MSG("mod_stanza_ack a message has been sent coming from: ~p", [From]),
        ?INFO_MSG("mod_stanza_ack a message has been sent to: ~p", [To]),
        ?INFO_MSG("mod_stanza_ack a message has been sent with the following packet:~n ~p", [Packet]),
        {Packet, C2SState}.
    
    depends(_Host, _Opts) ->
        [].
    
    mod_options(_Host) ->
        [].
    
    mod_doc() ->
        #{desc =>
              ?T("This an example module.")}.
    

    Following your detailed step by step installation guide, I get two problems, that I describe here and how to solve them:

    1. Compilation lacks header files.

    I copy mod_stanza_ack.erl to ejabberd-20.07/bin, and then run this command:

    $ ./erlc mod_stanza_ack.erl
    mod_stanza_ack.erl:4: can't find include file "xmpp.hrl"
    mod_stanza_ack.erl:5: can't find include file "logger.hrl"
    mod_stanza_ack.erl:6: can't find include file "translate.hrl"
    mod_stanza_ack.erl:12: undefined macro 'INFO_MSG/2'
    mod_stanza_ack.erl:17: undefined macro 'INFO_MSG/2'
    mod_stanza_ack.erl:22: undefined macro 'INFO_MSG/2'
    mod_stanza_ack.erl:47: undefined macro 'T/1'
    mod_stanza_ack.erl:8: function mod_doc/0 undefined
    mod_stanza_ack.erl:8: function start/2 undefined
    mod_stanza_ack.erl:8: function stop/1 undefined
    mod_stanza_ack.erl:9: function on_user_send_packet/1 undefined
    

    The solution is simple: provide the paths to the header files:

    $ ./erlc -I ../lib/ejabberd-20.07/include/ -I ../lib/xmpp-1.4.9/include/ -I ../lib/fast_xml-1.1.43/include/ mod_stanza_ack.erl
    

    This way the file is compiled correctly.

    1. INFO_MSG in the source code do not produce log messages in ejabberd log file or "ejabberdctl live" console.

    This is because we didn't tell the compiler to use the LAGER library. The solution is quite simple: include -DLAGER in the module compilation. So, this is the perfect compilation call:

    $ ./erlc -I ../lib/ejabberd-20.07/include/ -I ../lib/xmpp-1.4.9/include/ -I ../lib/fast_xml-1.1.43/include/ -DLAGER mod_stanza_ack.erl
    

    Now, you copy the resulting mod_stanza_ack.beam with all the other ejabberd beam files, enable the module in ejabberd.yml, and restart ejabberd, and all will work as expected