Setting Up ejabberd for XEP-0389 Implementation

Assurance MBULAAssurance MBULA
6 min read

Quickly reminder

As part of my Google Summer of Code (GSoC) project, I embarked on the journey of implementing XEP-0389 in ejabberd. This blog post details the process of setting up ejabberd, the challenges I encountered, and how I overcame them. I aim to provide a comprehensive guide for developers, especially those new to ejabberd and XMPP.

Setting Up ejabberd

1. Installation:

  • First, I installed ejabberd from the official website. For developers it is strongly recommended to download the source code and follow the instructions :

  • Ejaberd Install Source

  • For this project, the configuration file is located at /usr/local/etc/ejabberd/ejabberd.yml.

2. Configuration: (Optional)

  • I configured ejabberd to use SQL for user management instead of the default Mnesia database:

  •   yaml
           auth_method: sql
           sql_type: pgsql
           sql_server: "localhost"
           sql_database: "ejabberd"
           sql_username: "ejabberd"
           sql_password: "password"
    

3. Port Configuration:

  • I made sure ejabberd listens on the correct ports by updating ejabberd.yml:

  •   yaml
           listen:
             - port: 5222
               ip: "::"
               module: ejabberd_c2s
               starttls: false
               starttls_required: false
    

Overcoming Challenges

1. Module Loading Issues:

  • Initially, I encountered issues with modules not loading. The command ejabberdctl modules_installed returned empty. After verifying that the module files were in place and correctly referenced in the configuration, I realized that I was wrong. I reached out to ejabberd developpers (badlop and prefiks helped oud on this) and got some usefull feedbacks from them.

  •   %%if you want to check if an ejabberd module is loaded (this includes internal, external/contrib...)
    
      $ ejabberdctl debug
    
      > gen_mod:loaded_modules(<<"localhost">>).
      [mod_adhoc,mod_ping,mod_mam,mod_announce,mod_offline,
      ...
    
      > gen_mod:is_loaded(<<"localhost">>, mod_ping).
      true`
      `
      you can also check any erlang beam file, not only ejabberd modules:
    
      > code:is_loaded(mod_ping).
      {file,"/home/badlop/git/ejabberd/_build/dev/lib/ejabberd/ebin/mod_ping.beam"}
    
      > code:is_loaded(ejabberd_sm).
      {file,"/home/badlop/git/ejabberd/_build/dev/lib/ejabberd/ebin/ejabberd_sm.beam"}
    
      > code:is_loaded(mnesia).
      {file,"/home/badlop/.asdf/installs/erlang/27.0/lib/mnesia-4.23.2/ebin/mnesia.beam"}`
    

Also I ensured the ejabberd service user had the necessary permissions:

sh
     sudo chown -R assu_2000:ejabberd /usr/local/var/lib/ejabberd
     sudo chmod -R 750 /usr/local/var/lib/ejabberd

2. Database Configuration:

  • I needed to switch from the default Mnesia database to SQL for user management.

  • Solution:

    • Install PostgreSQL and configure the database.

    • Update ejabberd.yml with the appropriate database settings as shown above.

3. TLS and Connection Issues:

  • During testing with xmppconsole, I faced TLS connection issues and errors indicating that the server was refusing to authenticate over an unencrypted connection. I didn't want to encrypt the connection as I was working and testing locally.

  • Solution: I set starttls_required: false in the ejabberd.yml configuration:

  •   yaml
           listen:
             - port: 5222
               ip: "::"
               module: ejabberd_c2s
               starttls: false
               starttls_required: false
    
  • Also I ensured that the main.go file (more on this later) was set up to allow un-encrypted connection.

  •   package main
    
      import (
          "fmt"
          "log"
          "os"
    
          "github.com/xmppo/go-xmpp"
      )
    
      func main() {
          jid := os.Getenv("JID")
          password := os.Getenv("PASSWORD")
          host := os.Getenv("HOST")
    
          if jid == "" || password == "" || host == "" {
              log.Fatal("Usage: JID, PASSWORD and HOST must be set as environment variables")
          }
    
          options := xmpp.Options{
              Host:          host,
              User:          jid,
              Password:      password,
              NoTLS:         true,
              StartTLS:      false,
              Debug:         true,
              InsecureAllowUnencryptedAuth: true,
              Session:       true,
              Status:        "available",
              StatusMessage: "Welcome on our new codebot overlords.",
              Resource:      "xmppconsole",
          }
    
          talk, err := options.NewClient()
          if err != nil {
              log.Fatalf("Failed to create XMPP client: %v", err)
          }
    
          fmt.Println("Connected to XMPP server.")
          for {
              chat, err := talk.Recv()
              if err != nil {
                  log.Fatal(err)
              }
              switch v := chat.(type) {
              case xmpp.Chat:
                  fmt.Printf("Received message from %s: %s\n", v.Remote, v.Text)
              case xmpp.Presence:
                  fmt.Printf("Received presence from %s\n", v.From)
              }
          }
      }
    

`

4. Hostname and Hosts File Configuration:

  • I faced issues with hostname resolution and had to ensure that my /etc/hosts file was correctly configured:

  •   plaintext
           127.0.0.1   localhost mbula
    

Using ejabberdctl

Instead of using systemctl, I decided to use ejabberdctl to manage the ejabberd server. Here are some useful commands:

  • Start ejabberd: sh ejabberdctl start

  • Stop ejabberd: sh ejabberdctl stop

  • Check registered users: sh ejabberdctl registered_users localhost

  • Register new user : sh ejabberdctl register newuser localhost newpassword

Setting Up xmppconsole

xmppconsole has the unique purpose of testing. To execute it , you need it installed alongside Python3 or Go. I chose to go with Go :) By the way, xmppconsole has only 1 required dependency: libstrophe version 0.10.0 or higher

This is where the main.go file plays a key role .

1. Installation and Configuration:

2. Configuring main.go:

  • Ensure the main.go file is set up to use environment variables for JID, password, and host:

  •   go
           package main
    
           import (
               "fmt"
               "os"
               "github.com/mattn/go-xmpp"
           )
    
           func main() {
               options := xmpp.Options{
                   Host:     os.Getenv("HOST"),
                   User:     os.Getenv("JID"),
                   Password: os.Getenv("PASSWORD"),
                   NoTLS:    true,
                   Debug:    true,
               }
    
               client, err := options.NewClient()
               if err != nil {
                   fmt.Printf("Failed to create XMPP client: %v\n", err)
                   return
               }
    
               fmt.Println("Connected to XMPP server.")
           }
    
  • Set the environment variables before running xmppconsole:

  •   sh
           export JID=assu@localhost
           export PASSWORD=yourpassword
           export HOST=localhost
    

3. Running xmppconsole:

  • I executed the following command to connect to ejabberd:

  •   sh
           ./xmppconsole -jid=assu@localhost -password=yourpassword -host=localhost -port=5222
    
  • Expected output :

  •   ./xmppconsole -jid=assu@localhost -password=yourpassword -host=localhost -port=5222 
      <?xml version='1.0'?><stream:stream to='localhost' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>
      <?xml version='1.0'?><stream:stream id='16999550736600889301' version='1.0' xml:lang='en' xmlns:stream='http://etherx.jabber.org/streams' from='localhost' xmlns='jabber:client'>
      <stream:features><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-sasl'><mechanism>DIGEST-MD5</mechanism><mechanism>PLAIN</mechanism><mechanism>SCRAM-SHA-1</mechanism><mechanism>X-OAUTH2</mechanism></mechanisms><register xmlns='http://jabber.org/features/iq-register'/></stream:features>
      <auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='SCRAM-SHA-1'>biwsbj1hc3N1LHI9MDVmMDA0OGFiMWI4NThhMA==</auth>
      <challenge xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>cj0wNWYwMDQ4YWIxYjg1OGEwQXpsUnd3b1p4Uk05ZzZTendCOEsyQT09LHM9bFBQdjJJc0hHemdaK1d5eTFsQlcvdz09LGk9NDA5Ng==</challenge>
      <response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>Yz1iaXdzLHI9MDVmMDA0OGFiMWI4NThhMEF6bFJ3d29aeFJNOWc2U3p3QjhLMkE9PSxwPVFleTE1SWE2T1hydThRaE5yWElJZ1pycHFxTT0=</response>
      <success xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>dj1TYzloRFU3a2w0MEp5WXA5UUNQRk15M1Q2Nzg9</success>
      <?xml version='1.0'?><stream:stream to='localhost' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>
      <?xml version='1.0'?><stream:stream id='9610448628226382715' version='1.0' xml:lang='en' xmlns:stream='http://etherx.jabber.org/streams' from='localhost' xmlns='jabber:client'>
      <stream:features><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'/><session xmlns='urn:ietf:params:xml:ns:xmpp-session'><optional/></session><c ver='i0t6tJKP3J1KZE1GE9MypiHzU7A=' node='http://www.process-one.net/en/ejabberd/' hash='sha-1' xmlns='http://jabber.org/protocol/caps'/><sm xmlns='urn:xmpp:sm:2'/><sm xmlns='urn:xmpp:sm:3'/><ver xmlns='urn:xmpp:features:rosterver'/><csi xmlns='urn:xmpp:csi:0'/></stream:features>
      <iq type='set' id='881fa764e569de75'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>xmppconsole</resource></bind></iq>
      <iq type='result' id='881fa764e569de75'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><jid>assu@localhost/xmppconsole</jid></bind></iq>
      <iq to='localhost' type='set' id='a53f5bec29b21f81'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>
      <presence xml:lang='en'><show>available</show><status>Welcome on our new codebot overlords.</status></presence>
      Connected to XMPP server.
      <iq xml:lang='en' to='assu@localhost/xmppconsole' from='localhost' type='result' id='a53f5bec29b21f81'/>
      <presence xml:lang='en' type='error'><show>available</show><status>I for one welcome our new codebot overlords.</status><error code='400' type='modify'><bad-request xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'/><text xml:lang='en' xmlns='urn:ietf:params:xml:ns:xmpp-stanzas'>Bad value of cdata in tag &lt;show/&gt; qualified by namespace &apos;jabber:client&apos;</text></error></presence>
      Received presence from
    

`

Using xmppconsole

Once connected, I could send messages and manage presence using xmppconsole. Here are a few examples:

*Send a Message: sh message recipient@localhost "Hello, this is a test message"

*Set Presence: sh presence

Conclusion

Setting up ejabberd for XEP-0389 implementation involved configuring the server, addressing module loading issues, managing database settings, and overcoming connection problems with xmppconsole. This detailed journey highlights the common challenges and solutions, providing a guide for developers embarking on similar projects.

For more detailed information and troubleshooting, refer to the ejabberd and XMPP documentation and the ejabberd xmpp community group as well. Happy coding!


P.S : This blog post provides a comprehensive overview of my experience setting up ejabberd and troubleshooting various issues. By following this guide, other developers should be able to set up their environments more smoothly and focus on implementing new features other to XEP-0389.

I'd love to hear from you! If you're struggling with XMPP implementation or have any questions, feel free to reach out. I'm always happy to help a fellow coder out.

Github

Thanks for reading !

0
Subscribe to my newsletter

Read articles from Assurance MBULA directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Assurance MBULA
Assurance MBULA

Google - Microsoft - Github