*/ 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 = "". ""; $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>#", $xml, $tls); if (count ($tls)) { $this->send (""); $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>#", $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 ("$val"); $xml = $this->read (); preg_match ("#(.+)#", $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 ("". "". "".__CLASS__.""); $xml = $this->read (); preg_match ("#(.+)#", $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 (""); $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 ("". ""); $xml = $this->read (); preg_match_all ("##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$subject". "Ishmael". ""); $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"); } // }}} }