Files
DomFramework/xmppclient.php

245 lines
7.0 KiB
PHP

<?php
/** DomFramework
* @package domframework
* @author Dominique Fournier <dominique@fournier38.fr>
* @license BSD
*/
//namespace Domframework;
require_once ("domframework/tcpclient.php");
/** This class allow to send XMPP messages to a server.
* If the server supports it, crypt the connection by StartTLS before auth
*/
class xmppclient
{
// CLASS CONSTANT //
// {{{
/** One send command each $RATE µs maximum
*/
const RATE = 30000;
// }}}
// PROPERTIES //
// {{{
/** The connection socket when connected
*/
private $sock;
/** The XML stream exchange string
*/
private $xmlStream;
/** The debug state
*/
private $debug = false;
/** The object identifier increment
*/
private $id = 0;
/** The JID provided by the user
*/
private $jid = "";
/** Ratelimiter of send commands to server. Store the last timestamp, in
* microseconds.
*/
private $sendLastTime = 0;
// }}}
// PUBLIC METHODS //
/** Connect to XMPP server $server, on port $port (5222 by default)
* @param string $server The XMPP server
* @param integer $port The port to use (5222 by default)
* @param string $login The login to use
* @param string $password The password to use
* @param boolean|null $debug Debug the socket on stderr
* @return $this
*/
public function connect ($server, $port, $login, $password,
$debug = false)
// {{{
{
// To have a really one microsecond precision in microtime function
ini_set ("precision", 16);
$this->debug = $debug;
$this->sock = new tcpclient ($server, $port);
$this->sock->readMode ("binary");
$client = gethostname ();
@list ($user, $domain) = explode ("@", $login, 2);
if ($domain === null)
$domain = $server;
$this->xmlStream = "<?xml version='1.0' encoding='UTF-8'?".">".
"<stream:stream version='1.0' ".
"xmlns:stream='http://etherx.jabber.org/streams' ".
"xmlns='jabber:client' ".
"to='$domain' from='$client'>";
$this->sock->connect ();
$this->debug ("Info: Connected to '$server:$port'");
$this->send ($this->xmlStream);
$xml = $this->read ();
// Check if we can/must go in StartTLS
preg_match ("#<starttls.*?".">(.*)<\/starttls>#", $xml, $tls);
if (count ($tls))
{
$this->send ("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
$xml = $this->read ();
// Can not use the tcpclient part : the certificate is provided by domain,
// not by the XMPP server globally
$options = array ("peer_name" => $domain);
$this->sock->cryptoEnable (true, STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT,
$options);
$this->debug ("Info: Crypto enabled");
}
$this->send ($this->xmlStream);
$xml = $this->read ();
preg_match ("#<mechanism.*>(.*)<\/mechanism>#", $xml, $auths);
if (! key_exists (1, $auths))
return null;
$method = strtoupper ($auths[1]);
if ($method === "PLAIN")
{
$val = base64_encode ($login.chr(0).$user.chr(0).$password);
}
else
throw new \Exception (sprintf (dgettext ("domframework",
"XMPP Client : unknown authentication method requested : %s"),
$method));
$this->send ("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' ".
"mechanism='$method'>$val</auth>");
$xml = $this->read ();
preg_match ("#<success #", $xml, $match);
preg_match ("#<text>(.+)</text>#", $xml, $error);
if (count ($match) === 0)
throw new \Exception (sprintf (dgettext ("domframework",
"XMPP Client : Authentication rejected for user '%s' : %s"),
$login, $error[1]), 401);
$this->debug ("Info: User '$login' authenticated");
$this->send ($this->xmlStream);
$xml = $this->read ();
// $xml : Here we can have the session.
$this->send ("<iq id='netjabber-$this->id' type='set'>".
"<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>".
"<resource>".__CLASS__."</resource></bind></iq>");
$xml = $this->read ();
preg_match ("#<jid>(.+)</jid>#", $xml, $jids);
if (! key_exists (1, $jids))
throw new \Exception (dgettext ("domframework",
"XMPP Client : can not get the JID from the server"), 500);
$this->debug ("Info: Get JID => ".$jids[1]);
$this->jid = $jids[1];
$this->sock->timeout (5);
return $this;
}
// }}}
/** Disconnect from the XMPP server
*/
public function disconnect ()
// {{{
{
if ($this->sock && $this->sock->getSock ())
{
$this->send ("</stream:stream>");
$this->sock->disconnect ();
}
}
// }}}
/** Call the Discovery Service to find the available services on the server
* @return array The services available
*/
public function discoveryService ()
// {{{
{
$this->send ("<iq id='disco-$this->id' type='get'>".
"<query xmlns='http://jabber.org/protocol/disco#info'/></iq>");
$xml = $this->read ();
preg_match_all ("#<feature .*var='(\S+)'/>#U", $xml, $features);
if (! key_exists (1, $features))
throw new \Exception (dgettext ("domframework",
"XMPP Client : Can not get the discovery service result from server"),
500);
$this->id++;
return $features[1];
}
// }}}
/** Send a direct message to a recipient. The message subject can be omitted,
* as it is not displayed on the clients
* @param string $recipient The recipient of the message
* @param string $message The message to send
* @param string|null $subject The message subject
* @return $this
*/
public function sendMessagePrivate ($recipient, $message, $subject = null)
// {{{
{
$this->send ("<message from='$this->jid' to='$recipient' ".
"id='message-$this->id' type='chat'>".
"<body>$message</body><subject>$subject</subject>".
"<nick xmlns='http://jabber.org/protocol/nick'>Ishmael</nick>".
"</message>");
$this->id++;
return $this;
}
// }}}
/** In case of destruction, try to disconnect the server properly
*/
public function __destruct ()
// {{{
{
$this->disconnect ();
}
// }}}
/** Each debug message is sent to stderr if $debug is set
* @param string $msg The message to display
*/
public function debug ($msg)
// {{{
{
if ($this->debug === false)
return;
file_put_contents ("php://stderr", $msg."\n");
}
// }}}
// PRIVATE METHODS //
/** Read fron socket
* @return the read value
*/
private function read ()
// {{{
{
$xml = $this->sock->read (4096);
$this->debug ("Read: $xml");
return $xml;
}
// }}}
/** Send the data on socket, add the carriage return
* @param string $msg The message to send
*/
private function send ($msg)
// {{{
{
$start = microtime (true);
$wait = intval (($start - $this->sendLastTime) * 100000);
if ($wait > self::RATE)
$this->debug ("Send: $msg");
else
{
$sleep = intval (self::RATE - $wait);
$this->debug ("Send: $msg");
usleep ($sleep);
}
$this->sendLastTime = microtime (true);
return $this->sock->send ($msg."\n");
}
// }}}
}