10

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?

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Andrew Ensley
  • 11,611
  • 16
  • 61
  • 73

7 Answers7

10

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.

Community
  • 1
  • 1
Andrew Ensley
  • 11,611
  • 16
  • 61
  • 73
  • 1
    Change your sudo line to `www-data ALL= (ejabberd) NOPASSWD: /usr/sbin/ejabberdctl` so that `ejabberdctl` does not run as root but as the user `ejabberd`. Execute `ejabberdctl` in your code like this: `sudo -u ejabberd ejabberdctl`. – nemo Nov 15 '13 at 14:32
  • Same issue i am facing in linux, (Error : sh: /bin/su: Permission denied), Solution (for Ubuntu) - add this line at the end of /etc/sudoers: How to add in linux ? – Purushottam Jan 21 '14 at 12:37
  • @Purushottam: Ubuntu is a linux distribution based on debian. Each linux distribution has its own way of doing some things. I'd suggest googling for the location of the sudoers file in your specific linux distribution. – Andrew Ensley Jan 21 '14 at 15:37
  • I have tried Googling, but still unable to do. I am using CentOS linux distribution,Can you help me ? – Purushottam Jan 22 '14 at 05:56
  • Getting error like, You are attempting to run "ejabberdctl" which requires administrative privileges, but more information is needed in order to do so. Authenticating as "root" Password: Password: Password: – Purushottam Jan 22 '14 at 09:35
  • You should be able to find help for your specific situation from the CentOS community ( http://wiki.centos.org/Documentation ). They'll know a lot more than I do. – Andrew Ensley Jan 22 '14 at 16:59
7

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
);
Amr H. Abd Elmajeed
  • 1,521
  • 15
  • 41
  • I tried to do the same, but am getting error as below. Uncaught exception 'Fabiang\Xmpp\Exception\TimeoutException' with message 'Connection lost after 20 seconds' in ****/***/vendor/fabiang/xmpp/src/Connection/AbstractConnection.php:308 – Mani Kandan May 17 '16 at 07:20
  • I'm getting this error Failed to enable crypto Fatal error: Uncaught exception 'Fabiang\Xmpp\Exception\ErrorException' – Lakshmaji May 29 '16 at 13:42
  • @lakshmaji I think this would be solved with the very last step in the answer, the part where you update SocketClient.php – Amr H. Abd Elmajeed May 29 '16 at 15:26
  • @AmrH.AbdelMajeed , I have replaced the existing code with the last step in your answer, Same error , – Lakshmaji May 29 '16 at 16:52
  • This is not create any issue for me but its not going to create any user in ejabberd so have you any debug points ? – Ankit Doshi Sep 16 '16 at 08:59
  • @AnkitDoshi Yes, you can use what ever logger you normally use in the registerChatUser function, or in the toString function in the Register class. Also double check your ejabberd server config, and test it with an Ejabberd client such as pidgin before testing it with the code. – Amr H. Abd Elmajeed Sep 18 '16 at 12:07
4

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 (!)

jldupont
  • 93,734
  • 56
  • 203
  • 318
1

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 $
Abhinav Singh
  • 2,643
  • 1
  • 19
  • 29
0

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 );
richp10
  • 820
  • 8
  • 20
  • I tried the above one but it is throwing following error **Argument 1 passed to fXmlRpc\Transport\HttpAdapterTransport::__construct() must be an instance of Http\Message\MessageFactory, instance of Ivory\HttpAdapter\CurlHttpAdapter given, called in /var/www/html/php-jabber-rpc/lib/GameNet/Jabber/RpcClient.php on line 140** – Lakshmaji Jun 06 '16 at 13:39
0

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).

Tunaki
  • 132,869
  • 46
  • 340
  • 423
serkas
  • 23
  • 4
  • Why set two password on above example ?? when i run above command : curl: (52) Empty reply from server – mayur panchal Nov 11 '19 at 09:07
  • "Why set two password on above example ??" - because it uses the POST method of user registration also used by web page. The two password fields correspond to "password" and "validate password" of a registration form – serkas Dec 24 '19 at 08:21
0
curl -XPOST 127.0.0.1:5281/api/register -d '{"user":"lucky","host":"data.com","password":"test"}'

To apply to php you can use standard curl , and the code above is the most recent curl code contained in the unpublished ejabberd doc,

I just apply it and analyze it from the doc ejabber then I adjust it to the ejabberd url path created on the ejabberd config xml

  • 2
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Donald Duck Feb 06 '17 at 17:00
  • Full json format should be submitted curl '{"user", "lucky", "master": "data.com", "password", "test"}' all must be adjusted to the format of the existing language and data on existing ejabberd doc – Fikalefaza Feb 08 '17 at 16:24
  • when i run above command : curl: (52) Empty reply from server – mayur panchal Nov 11 '19 at 09:10
  • There may be an error in your ejabber data, or it could be something else, please check again – Fikalefaza Feb 23 '20 at 11:38