Question

I need to create an ejabberd user from a PHP script. I also need to be able to add the new user to a predefined shared roster.

Should I just call ejabberdctl using exec() or is there a better way?

Was it helpful?

Solution

ejabberdctl is by far the easiest in this specific case. The other options are:

  • Implement a full client XMPP in PHP (!)

  • Implement a module in Erlang that proxies the requests: the PHP<-->Erlang communication would need to be through a socket and lots of marshaling would be involved (!)

OTHER TIPS

Here's my final solution:

Thanks to jldupont's advice that ejabberdctl would be the easiest solution, I pressed on through the obstacles I ran into and have a working solution.

By default, apache's user doesn't have the right privileges to successfully run ejabberdctl (and for good reason). So in order for it to work, you have to call it with sudo. But... sudo requires a password, which presents 2 problems:

  1. The apache user doesn't have a password.
  2. Even if it did, there's no way to enter it from PHP.

Solution (for Ubuntu) - add this line at the end of /etc/sudoers:

www-data ALL= (ejabberd) NOPASSWD: /usr/sbin/ejabberdctl

The path to the sudoers file and ejabberdctl may vary for other Linux distros. This allows apache's user (www-data) to run only ejabberdctl with elevated privileges and without requiring a password.

All that's left is the PHP code:

<?php
    $username = 'tester';
    $password = 'testerspassword';
    $node = 'myserver.com';
    exec('sudo -u ejabberd /usr/sbin/ejabberdctl register '.$username.' '.$node.' '.$password.' 2>&1',$output,$status);
    if($output == 0)
    {
        // Success!
    }
    else
    {
        // Failure, $output has the details
        echo '<pre>';
        foreach($output as $o)
        {
            echo $o."\n";
        }
        echo '</pre>';
    }
?>

Security

It's important to note that this does present a significant security risk even though you're only allowing one command to be run by www-data. If you use this approach, you need to make sure you protect the PHP code behind some sort of authentication so that not just anybody can make it execute. Beyond the obvious security risks, it could open your server up to a Denial of Service attack.

I came upon this question in 2016, there are much easier ways to implement this than the accepted answer and the highest voted one.

  1. Use an XMPP PHP library, the most common one being:

https://github.com/fabiang/xmpp

  1. While this library doesn't support adding a user out of the box, you can very easily extend it

here is the class I wrote for adding a user:

use Fabiang\Xmpp\Util\XML;

/**
 * Register new user
 * @param string $username
 * @param string $password
 * @param string $email
 * @package XMPP\Protocol
 * @category XMPP
 */
class Register implements ProtocolImplementationInterface
{  
    protected $username;
    protected $password;
    protected $email;

    /**
     * Constructor.
     *
     * @param string $username
     * @param string $password
     * @param string $email
     */
    public function __construct($username, $password, $email)
    {
        $this->username = $username;
        $this->password = $password;
        $this->email = $email;
    }

    /**
     * Build XML message
     * @return type
     */
    public function toString()
    {
        $query = "<iq type='set' id='%s'><query xmlns='jabber:iq:register'><username>%s</username><password>%s</password><email>%s</email></query></iq>";        
        return XML::quoteMessage($query, XML::generateId(), (string) $this->username, (string) $this->password, (string) $this->email);
    }
}
  1. You must enable in-band registration in the ejabberd.cfg file, as it's denied by default:

{access, register, [{allow, all}]}.

Finally here is a sample code for using this class:

private function registerChatUser($name, $password, $email)
    {       
        $address = 'tcp://yourserverip:5222';
        $adminUsername = 'youradmin';
        $adminPassword = 'youradminpassword';

        $options = new Options($address);
        $options->setUsername($adminUsername)->setPassword($adminPassword);

        $client = new Client($options);         
        $client->connect();             

        $register = new Register($name, $password, $email);                 
        $client->send($register);   

        $client->disconnect();
    }

The library call will fail if the server does not have a valid SSL certificate. Either place a valid certificate, or replace this part in SocketClient.php with the below snippet

// call stream_socket_client with custom error handler enabled
$handler = new ErrorHandler(
    function ($address, $timeout, $flags) {
        $options = [
            'ssl' => [
                'allow_self_signed' => true,
                'verify_peer_name' => false,
            ],
        ];
        $context = stream_context_create($options);
        return stream_socket_client($address, $errno, $errstr, $timeout, $flags, $context);
    },
    $this->address,
    $timeout,
    $flags
);

If you want a clean and secure way of doing this using PHP within XMPP protocol, I will recommend working with this example script register_user.php. This is an example that can be found inside Jaxl PHP Library.

Download Jaxl library and use as follows:

JAXL $ php examples/register_user.php localhost
Choose a username and password to register with this server
username:test_user
password:test_pass
registration successful
shutting down...
JAXL $

The easiest way of doing this is using mod_xmlrpc - which allows you to run the ejabberdctl commands using xmlrpc. This is easy to use with a library such as:

https://github.com/gamenet/php-jabber-rpc

/* Add user to Jabber */
use \GameNet\Jabber\RpcClient;
use \GameNet\Jabber\Mixins\UserTrait;
$rpc = new RpcClient([
        'server' => 'jabber.org:4560',
        'host' => 'myhost.org',
        'debug' => false,
    ]);

$result=$rpc->createUser( $username, $password );

I've solved the problem with mod_register_web [1, 2]. It doesn't require tonnes of code and, I think, is secure enough. mod_register_web provides html page with simple POST form to register new user.

Enable module under separate http listener (in my case, port 5281). Make this port available only for local requests with "ip" parameter.

listen:
  port: 5280
  module: ejabberd_http
  web_admin: true
  http_bind: true
  ## register: true

ip: "127.0.0.1"   # Only local requests allowed for user registration
  port: 5281
  module: ejabberd_http
  register: true

modules:
  mod_register_web: {}

Request example:

curl -XPOST 127.0.0.1:5281/register/new -d 'username=lucky&host=vHost&password=test&password2=test'

Request can be executed from php code with appropriate library (which was already in my framework).

curl -XPOST 127.0.0.1:5281/api/register -d '{"user":"lucky","host":"data.com","password":"test"}'

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