git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5912 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
242 lines
6.9 KiB
PHP
242 lines
6.9 KiB
PHP
<?php
|
|
/** DomFramework
|
|
* @package domframework
|
|
* @author Dominique Fournier <dominique@fournier38.fr>
|
|
*/
|
|
|
|
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 $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");
|
|
}
|
|
// }}}
|
|
}
|