XMPP Client : basic message send to XMPP server
git-svn-id: https://svn.fournier38.fr/svn/ProgSVN/trunk@5912 bf3deb0d-5f1a-0410-827f-c0cc1f45334c
This commit is contained in:
241
xmppclient.php
Normal file
241
xmppclient.php
Normal file
@@ -0,0 +1,241 @@
|
||||
<?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");
|
||||
}
|
||||
// }}}
|
||||
}
|
||||
Reference in New Issue
Block a user