From 1b32236b1ebbf046e84a435c97c7ed6bc9edd5f9 Mon Sep 17 00:00:00 2001 From: Meik Sievertsen Date: Sat, 26 May 2007 16:38:33 +0000 Subject: hopefully not too late in the game. Checked in new jabber class (the class done by the flyspray project). It would be nice if this could be tested with more servers - jabber.org seems to work fine... - other fixes git-svn-id: file:///svn/phpbb/trunk@7687 89ea8834-ac86-4346-8a33-228a782c2dd0 --- phpBB/includes/functions_jabber.php | 1669 ++++++++++------------------------- 1 file changed, 485 insertions(+), 1184 deletions(-) (limited to 'phpBB/includes/functions_jabber.php') diff --git a/phpBB/includes/functions_jabber.php b/phpBB/includes/functions_jabber.php index 3872346bbc..3ad96df928 100644 --- a/phpBB/includes/functions_jabber.php +++ b/phpBB/includes/functions_jabber.php @@ -10,1321 +10,711 @@ /** * -* Class.Jabber.PHP v0.4.2 -* (c) 2004 Nathan "Fritzy" Fritz -* http://cjphp.netflint.net *** fritzy@netflint.net +* Jabber class from Flyspray project +* @version class.jabber2.php 1209 2007-05-12 13:39:10Z floele +* @copyright 2006 Flyspray.org +* @author: Florian Schmitz (floele) * -* This is a bugfix version, specifically for those who can't get -* 0.4 to work on Jabberd2 servers. -* -* last modified: 24.03.2004 13:01:53 -* -* Modified by phpBB Development Team -* version: v0.4.3 +* Modified by Acyd Burn * * @package phpBB3 */ class jabber { + var $connection = null; + var $session = array(); + var $timeout = 10; + var $server; var $port; var $username; var $password; - var $resource; - var $jid; - - var $connection; - var $delay_disconnect; - - var $stream_id; + var $use_ssl; var $enable_logging; var $log_array; - var $iq_sleep_timer; - var $last_ping_time; - - var $packet_queue; - - var $iq_version_name; - var $iq_version_os; - var $iq_version_version; - - var $error_codes; + var $features = array(); - var $connected; - var $keep_alive_id; - var $returned_keep_alive; - var $txnid; - - var $connector; - - var $version; - var $show_version; - - /** - * Constructor - */ - function jabber($server, $port, $username, $password, $resource) + function jabber($server, $port, $username, $password, $use_ssl = false) { $this->server = ($server) ? $server : 'localhost'; - $this->port = ($port) ? $port : '5222'; + $this->port = ($port) ? $port : 5222; $this->username = $username; $this->password = $password; - $this->resource = ($resource) ? $resource : NULL; + $this->use_ssl = ($use_ssl && $this->can_use_ssl) ? true : false; + + // Change port if we use SSL + if ($this->port == 5222 && $this->use_ssl) + { + $this->port = 5223; + } $this->enable_logging = true; $this->log_array = array(); - - $this->version = '1.0'; - $this->show_version = false; - - $this->packet_queue = array(); - $this->iq_sleep_timer = $this->delay_disconnect = 1; - - $this->returned_keep_alive = true; - $this->txnid = 0; - - $this->iq_version_name = "Class.Jabber.PHP -- http://cjphp.netflint.net -- by Nathan 'Fritzy' Fritz, fritz@netflint.net"; - $this->iq_version_version = '0.4'; - $this->iq_version_os = $_SERVER['SERVER_SOFTWARE']; - - $this->error_codes = array( - 400 => 'Bad Request', - 401 => 'Unauthorised', - 402 => 'Payment Required', - 403 => 'Forbidden', - 404 => 'Not Found', - 405 => 'Not Allowed', - 406 => 'Not Acceptable', - 407 => 'Registration Required', - 408 => 'Request Timeout', - 409 => 'Conflict', - 500 => 'Internal Server Error', - 501 => 'Not Implemented', - 502 => 'Remove Server Error', - 503 => 'Service Unavailable', - 504 => 'Remove Server Timeout', - 510 => 'Disconnected' - ); } /** - * Connect + * Able to use the SSL functionality? */ - function connect() + function can_use_ssl() { - $this->connector = new cjp_standard_connector; - - if ($this->connector->open_socket($this->server, $this->port)) - { - $this->send_packet("\n"); - $this->send_packet("show_version) ? " version='{$this->version}'" : '') . ">\n"); - - sleep(2); - - if ($this->_check_connected()) - { - $this->connected = true; // Nathan Fritz - return true; - } - else - { - $this->add_to_log('ERROR: connect() #1'); - return false; - } - } - else - { - $this->add_to_log('ERROR: connect() #2'); - return false; - } + // Will not work with PHP >= 5.2.1 until timeout problem with ssl hasn't been fixed (http://bugs.php.net/41236) + return (version_compare(PHP_VERSION, '5.2.1', '<') && @extension_loaded('openssl')) ? true : false; } /** - * Disconnect + * Able to use TLS? */ - function disconnect() + function can_use_tls() { - if (is_int($this->delay_disconnect)) + if (!@extension_loaded('openssl') || !function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('socket_set_blocking') || !function_exists('stream_get_wrappers')) + { + return false; + } + + // Make sure the encryption stream is supported + $streams = stream_get_wrappers(); + + if (!in_array('streams.crypto', $streams)) { - sleep($this->delay_disconnect); + return false; } - $this->send_packet(''); - $this->connector->close_socket(); + return true; } /** - * Send authentication request + * Connect */ - function send_auth() + function connect() { - $this->auth_id = 'auth_' . md5(time() . $_SERVER['REMOTE_ADDR']); - $this->resource = ($this->resource != NULL) ? $this->resource : ('Class.Jabber.PHP ' . md5($this->auth_id)); - $this->jid = "{$this->username}@{$this->server}/{$this->resource}"; +/* if (!$this->check_jid($this->username . '@' . $this->server)) + { + $this->add_to_log('Error: Jabber ID is not valid: ' . $this->username . '@' . $this->server); + return false; + }*/ - // request available authentication methods - $payload = "{$this->username}"; - $packet = $this->send_iq(NULL, 'get', $this->auth_id, 'jabber:iq:auth', $payload); + $this->session['ssl'] = $this->use_ssl; - // was a result returned? - if ($this->get_info_from_iq_type($packet) == 'result' && $this->get_info_from_iq_id($packet) == $this->auth_id) + if ($this->open_socket($this->server, $this->port, $this->use_ssl)) { - // yes, now check for auth method availability in descending order (best to worst) - if (isset($packet['iq']['#']['query'][0]['#']['sequence'][0]['#']) && isset($packet['iq']['#']['query'][0]['#']['token'][0]['#'])) - { - // auth_0k - return $this->_sendauth_ok($packet['iq']['#']['query'][0]['#']['token'][0]['#'], $packet['iq']['#']['query'][0]['#']['sequence'][0]['#']); - } - else if (isset($packet['iq']['#']['query'][0]['#']['digest'])) - { - // digest - return $this->_sendauth_digest(); - } - else if ($packet['iq']['#']['query'][0]['#']['password']) - { - // plain text - return $this->_sendauth_plaintext(); - } - else - { - $this->add_to_log('ERROR: send_auth() #2 - No auth method available!'); - return false; - } + $this->send("\n"); + $this->send("\n"); } else { - // no result returned - $this->add_to_log('ERROR: send_auth() #1'); + $this->add_to_log('Error: connect() #2'); return false; } + + // Now we listen what the server has to say...and give appropriate responses + $this->response($this->listen()); + return true; } /** - * Register account + * Disconnect */ - function account_registration($reg_email = NULL, $reg_name = NULL) + function disconnect() { - $packet = $this->send_iq($this->server, 'get', 'reg_01', 'jabber:iq:register'); - - if ($packet) + if ($this->connected()) { - // just in case a key was passed back from the server - $key = $this->get_info_from_iq_key($packet); - unset($packet); - - $payload = "{$this->username} - {$this->password} - $reg_email - $reg_name\n"; - - $payload .= ($key) ? "$key\n" : ''; - - $packet = $this->send_iq($this->server, 'set', 'reg_01', 'jabber:iq:register', $payload); - - if ($this->get_info_from_iq_type($packet) == 'result') + // disconnect gracefully + if (isset($this->session['sent_presence'])) { - $return_code = (isset($packet['iq']['#']['query'][0]['#']['registered'][0]['#'])) ? 1 : 2; - $this->jid = ($this->resource) ? "{$this->username}@{$this->server}/{$this->resource}" : "{$this->username}@{$this->server}"; - } - else if ($this->get_info_from_iq_type($packet) == 'error' && isset($packet['iq']['#']['error'][0]['#'])) - { - // "conflict" error, i.e. already registered - if ($packet['iq']['#']['error'][0]['@']['code'] == '409') - { - $return_code = 1; - } - else - { - $return_code = 'Error ' . $packet['iq']['#']['error'][0]['@']['code'] . ': ' . $packet['iq']['#']['error'][0]['#']; - } + $this->presence('offline', '', true); } - return $return_code; - } - else - { - return 3; + $this->send(''); + $this->session = array(); + return fclose($this->connection); } + + return false; } /** - * Change password + * Connected? */ - function change_password($new_password) + function connected() { - $packet = $this->send_iq($this->server, 'get', 'A0', 'jabber:iq:register'); - - if ($packet) - { - // just in case a key was passed back from the server - $key = $this->get_info_from_iq_key($packet); - unset($packet); - - $payload = "{$this->username} - {$new_password}\n"; - $payload .= ($key) ? "$key\n" : ''; - - $packet = $this->send_iq($this->server, 'set', 'A0', 'jabber:iq:register', $payload); + return (is_resource($this->connection) && !feof($this->connection)) ? true : false; + } - if ($this->get_info_from_iq_type($packet) == 'result') - { - $return_code = (isset($packet['iq']['#']['query'][0]['#']['registered'][0]['#'])) ? 1 : 2; - } - else if ($this->get_info_from_iq_type($packet) == 'error' && isset($packet['iq']['#']['error'][0]['#'])) - { - // "conflict" error, i.e. already registered - if ($packet['iq']['#']['error'][0]['@']['code'] == '409') - { - $return_code = 1; - } - else - { - $return_code = 'Error ' . $packet['iq']['#']['error'][0]['@']['code'] . ': ' . $packet['iq']['#']['error'][0]['#']; - } - } - return $return_code; - } - else + /** + * Initiates login (using data from contructor, after calling connect()) + * @access public + * @return bool + */ + function login() + { + if (!sizeof($this->features)) { - return 3; + $this->add_to_log('Error: No feature information from server available.'); + return false; } + + return $this->response($this->features); } /** - * Send packet + * Send data to the Jabber server + * @param string $xml + * @access public + * @return bool */ - function send_packet($xml) + function send($xml) { - $xml = trim($xml); - - if ($this->connector->write_to_socket($xml)) + if ($this->connected()) { - $this->add_to_log('SEND: ' . $xml); - return true; + $xml = trim($xml); + $this->add_to_log('SEND: '. $xml); + return fwrite($this->connection, $xml); } else { - $this->add_to_log('ERROR: send_packet() #1'); + $this->add_to_log('Error: Could not send, connection lost (flood?).'); return false; } } /** - * Listen to socket + * OpenSocket + * @param string $server host to connect to + * @param int $port port number + * @param bool $use_ssl use ssl or not + * @access public + * @return bool */ - function listen() + function open_socket($server, $port, $use_ssl = false) { - $incoming = ''; - - while ($line = $this->connector->read_from_socket(4096)) + if (@function_exists('dns_get_record')) + { + $record = dns_get_record("_xmpp-client._tcp.$server", DNS_SRV); + if (!empty($record)) + { + $server = $record[0]['target']; + } + } + else { - $incoming .= $line; + $this->add_to_log('Warning: dns_get_record function not found. GTalk will not work.'); } - $incoming = trim($incoming); + $server = $use_ssl ? 'ssl://' . $server : $server; - if ($incoming != '') + if ($this->connection = @fsockopen($server, $port, $errorno, $errorstr, $this->timeout)) { - $this->add_to_log('RECV: ' . $incoming); - $temp = $this->_split_incoming($incoming); + socket_set_blocking($this->connection, 0); + socket_set_timeout($this->connection, 60); - for ($i = 0, $size = sizeof($temp); $i < $size; $i++) - { - $this->packet_queue[] = $this->xmlize($temp[$i]); - } + return true; } - return true; + // Apparently an error occured... + $this->add_to_log('Error: open_socket() - ' . $errorstr); + return false; } /** - * Strip jid + * Return log */ - function strip_jid($jid = NULL) + function get_log() { - preg_match('#(.*)\/(.*)#Ui', $jid, $temp); - return ($temp[1] != '') ? $temp[1] : $jid; + if ($this->enable_logging && sizeof($this->log_array)) + { + return '

' . implode("

", $this->log_array); + } + + return ''; } /** - * Send a message + * Add information to log */ - function send_message($to, $type = 'normal', $id = NULL, $content = NULL, $payload = NULL) + function add_to_log($string) { - if ($to && is_array($content)) - { - if (!$id) - { - $id = $type . '_' . time(); - } - - $this->_array_xmlspecialchars($content); - - $xml = "\n"; - - if (!empty($content['subject'])) - { - $xml .= '' . $content['subject'] . "\n"; - } - - if (!empty($content['thread'])) - { - $xml .= '' . $content['thread'] . "\n"; - } - - $xml .= '' . $content['body'] . "\n"; - $xml .= $payload; - $xml .= "\n"; - - if ($this->send_packet($xml)) - { - return true; - } - else - { - $this->add_to_log('ERROR: send_message() #1'); - } - } - else + if ($this->enable_logging) { - $this->add_to_log('ERROR: send_message() #2'); - return false; + $this->log_array[] = utf8_htmlspecialchars($string); } } /** - * Send presence + * Listens to the connection until it gets data or the timeout is reached. + * Thus, it should only be called if data is expected to be received. + * @access public + * @return mixed either false for timeout or an array with the received data */ - function send_presence($type = NULL, $to = NULL, $status = NULL, $show = NULL, $priority = NULL) + function listen($timeout = 10, $wait = false) { - $xml = '\n" : " />\n"; + if (!$this->connected()) + { + return false; + } - $xml .= ($status) ? " $status\n" : ''; - $xml .= ($show) ? " $show\n" : ''; - $xml .= ($priority) ? " $priority\n" : ''; + // Wait for a response until timeout is reached + $start = time(); + $data = ''; - $xml .= ($status || $show || $priority) ? "\n" : ''; + do + { + $read = trim(fread($this->connection, 4096)); + $data .= $read; + } + while (time() <= $start + $timeout && ($wait || $data == '' || $read != '' || (substr(rtrim($data), -1) != '>'))); - if ($this->send_packet($xml)) + if ($data != '') { - return true; + $this->add_to_log('RECV: '. $data); + return $this->xmlize($data); } else { - $this->add_to_log('ERROR: send_presence() #1'); + $this->add_to_log('Timeout, no response from server.'); return false; } } /** - * Send error + * Initiates account registration (based on data used for contructor) + * @access public + * @return bool */ - function send_error($to, $id = NULL, $error_number, $error_message = NULL) + function register() { - $xml = "error_codes[$error_number]; - $xml .= "\n"; - $xml .= ''; - - $this->send_packet($xml); - } + if (!isset($this->session['id']) || isset($this->session['jid'])) + { + $this->add_to_log('Error: Cannot initiate registration.'); + return false; + } - /** - * Get first from queue - */ - function get_first_from_queue() - { - return array_shift($this->packet_queue); + $this->send(""); + return $this->response($this->listen()); } /** - * Get from queue by id + * Sets account presence. No additional info required (default is "online" status) + * @param $message online, offline... + * @param $type dnd, away, chat, xa or nothing + * @access public + * @return bool */ - function get_from_queue_by_id($packet_type, $id) + function send_presence($message = '', $type = '', $unavailable = false) { - $found_message = false; - - foreach ($this->packet_queue as $key => $value) + if (!isset($this->session['jid'])) { - if ($value[$packet_type]['@']['id'] == $id) - { - $found_message = $value; - unset($this->packet_queue[$key]); - - break; - } + $this->add_to_log('ERROR: send_presence() - Cannot set presence at this point, no jid given.'); + return false; } - return (is_array($found_message)) ? $found_message : false; + $type = strtolower($type); + $type = (in_array($type, array('dnd', 'away', 'chat', 'xa'))) ? ''. $type .'' : ''; + + $unavailable = ($unavailable) ? " type='unavailable'" : ''; + $message = ($message) ? '' . utf8_htmlspecialchars($message) .'' : ''; + + $this->session['sent_presence'] = !$unavailable; + + return $this->send("" . $type . $message . ''); } /** - * Call handler + * This handles all the different XML elements + * @param array $xml + * @access public + * @return bool */ - function call_handler($packet = NULL) + function response($xml) { - $packet_type = $this->_get_packet_type($packet); - - if ($packet_type == 'message') + if (!is_array($xml) || !sizeof($xml)) { - $type = $packet['message']['@']['type']; - $type = ($type != '') ? $type : 'normal'; - $funcmeth = "handler_message_$type"; - } - else if ($packet_type == 'iq') - { - $namespace = $packet['iq']['#']['query'][0]['@']['xmlns']; - $namespace = str_replace(':', '_', $namespace); - $funcmeth = "handler_iq_$namespace"; - } - else if ($packet_type == 'presence') - { - $type = $packet['presence']['@']['type']; - $type = ($type != '') ? $type : 'available'; - $funcmeth = "handler_presence_$type"; + return false; } - if ($funcmeth != '') + // did we get multiple elements? do one after another + // array('message' => ..., 'presence' => ...) + if (sizeof($xml) > 1) { - if (function_exists($funcmeth)) - { - call_user_func($funcmeth, $packet); - } - else if (method_exists($this, $funcmeth)) + foreach ($xml as $key => $value) { - call_user_func(array(&$this, $funcmeth), $packet); + $this->response(array($key => $value)); } - else + return; + } + else + { + // or even multiple elements of the same type? + // array('message' => array(0 => ..., 1 => ...)) + if (sizeof(reset($xml)) > 1) { - $this->handler_not_implemented($packet); - $this->add_to_log("ERROR: call_handler() #1 - neither method nor function $funcmeth() available"); + foreach (reset($xml) as $value) + { + $this->response(array(key($xml) => array(0 => $value))); + } + return; } } - } - - /** - * Cruise Control - */ - function cruise_control($seconds = -1) - { - $count = 0; - while ($count != $seconds) + switch (key($xml)) { - $this->listen(); - - do - { - $packet = $this->get_first_from_queue(); + case 'stream:stream': + // Connection initialised (or after authentication). Not much to do here... + $this->session['id'] = $xml['stream:stream'][0]['@']['id']; - if ($packet) + if (isset($xml['stream:stream'][0]['#']['stream:features'])) { - $this->call_handler($packet); + // we already got all info we need + $this->features = $xml['stream:stream'][0]['#']; + } + else + { + $this->features = $this->listen(); } - } - while (sizeof($this->packet_queue) > 1); - $count += 0.25; - usleep(250000); + // go on with authentication? + if (isset($this->features['stream:features'][0]['#']['bind'])) + { + return $this->response($this->features); + } + break; - if (($this->last_ping_time + 180) < time()) - { - // Modified by Nathan Fritz - if ($this->returned_keep_alive == false) + case 'stream:features': + // Resource binding after successful authentication + if (isset($this->session['authenticated'])) { - $this->connected = false; - $this->add_to_log('EVENT: Disconnected'); + // session required? + $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']); + + $this->send(" + + functions_jabber.phpbb.php + + "); + return $this->response($this->listen()); } - if ($this->returned_keep_alive == true) + // Let's use TLS if SSL is not enabled and we can actually use it + if (!$this->session['ssl'] && $this->can_use_tls() && isset($xml['stream:features'][0]['#']['starttls'])) { - $this->connected = true; + $this->add_to_log('Switching to TLS.'); + $this->send("\n"); + return $this->response($this->listen()); } - $this->returned_keep_alive = false; + // Does the server support SASL authentication? + // I hope so, because we do (and no other method). + if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl') + { + // Now decide on method + $methods = array(); - $this->keep_alive_id = 'keep_alive_' . time(); - // $this->send_packet("", 'cruise_control'); - $this->send_packet(""); - $this->last_ping_time = time(); - } - } + foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value) + { + $methods[] = $value['#']; + } - return true; - } + // we prefer this one + if (in_array('DIGEST-MD5', $methods)) + { + $this->send(""); + } + else if (in_array('PLAIN', $methods) && ($this->session['ssl'] || $this->session['tls'])) + { + // we don't want to use this (neither does the server usually) if no encryption is in place + $this->send("" + . base64_encode(chr(0) . $this->username . '@' . $this->server . chr(0) . $this->password) . + ''); + } + else + { + // not good... + $this->add_to_log('Error: No authentication method supported.'); + $this->disconnect(); + return false; + } - /** - * Send iq - */ - function send_iq($to = NULL, $type = 'get', $id = NULL, $xmlns = NULL, $payload = NULL, $from = NULL) - { - if (!preg_match('#^(get|set|result|error)$#', $type)) - { - unset($type); + return $this->response($this->listen()); + } + else + { + // ok, this is it. bye. + $this->add_to_log('Error: Server does not offer SASL authentication.'); + $this->disconnect(); + return false; + } + break; - $this->add_to_log("ERROR: send_iq() #2 - type must be 'get', 'set', 'result' or 'error'"); - return false; - } - else if ($id && $xmlns) - { - $xml = "send_packet($xml); - sleep($this->iq_sleep_timer); - $this->listen(); - - return (preg_match('#^(get|set)$#', $type)) ? $this->get_from_queue_by_id('iq', $id) : true; - } - else - { - $this->add_to_log('ERROR: send_iq() #1 - to, id and xmlns are mandatory'); - return false; - } - } + case 'challenge': + // continue with authentication...a challenge literally -_- + $decoded = base64_decode($xml['challenge'][0]['#']); + $decoded = $this->parse_data($decoded); - /** - * get the transport registration fields - * method written by Steve Blinch, http://www.blitzaffe.com - */ - function transport_registration_details($transport) - { - $this->txnid++; - $packet = $this->send_iq($transport, 'get', "reg_{$this->txnid}", 'jabber:iq:register', NULL, $this->jid); + if (!isset($decoded['digest-uri'])) + { + $decoded['digest-uri'] = 'xmpp/'. $this->server; + } - if ($packet) - { - $res = array(); + // better generate a cnonce, maybe it's needed + $str = ''; + mt_srand((double)microtime()*10000000); - foreach ($packet['iq']['#']['query'][0]['#'] as $element => $data) - { - if ($element != 'instructions' && $element != 'key') + for ($i = 0; $i < 32; $i++) { - $res[] = $element; + $str .= chr(mt_rand(0, 255)); } - } + $decoded['cnonce'] = base64_encode($str); - return $res; - } - else - { - return 3; - } - } + // second challenge? + if (isset($decoded['rspauth'])) + { + $this->send(""); + } + else + { + $response = array( + 'username' => $this->username, + 'response' => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))), + 'charset' => 'utf-8', + 'nc' => '00000001', + ); + + foreach (array('nonce', 'qop', 'digest-uri', 'realm', 'cnonce') as $key) + { + if (isset($decoded[$key])) + { + $response[$key] = $decoded[$key]; + } + } - /** - * register with the transport - * method written by Steve Blinch, http://www.blitzaffe.com - */ - function transport_registration($transport, $details) - { - $this->txnid++; - $packet = $this->send_iq($transport, 'get', "reg_{$this->txnid}", 'jabber:iq:register', NULL, $this->jid); + $this->send("" . base64_encode($this->implode_data($response)) . ''); + } - if ($packet) - { - // just in case a key was passed back from the server - $key = $this->get_info_from_iq_key($packet); - unset($packet); + return $this->response($this->listen()); + break; - $payload = ($key) ? "$key\n" : ''; - foreach ($details as $element => $value) - { - $payload .= "<$element>$value\n"; - } + case 'failure': + $this->add_to_log('Error: Server sent "failure".'); + $this->disconnect(); + return false; + break; - $packet = $this->send_iq($transport, 'set', "reg_{$this->txnid}", 'jabber:iq:register', $payload); + case 'proceed': + // continue switching to TLS + $meta = stream_get_meta_data($this->connection); + socket_set_blocking($this->connection, 1); - if ($this->get_info_from_iq_type($packet) == 'result') - { - $return_code = (isset($packet['iq']['#']['query'][0]['#']['registered'][0]['#'])) ? 1 : 2; - } - else if ($this->get_info_from_iq_type($packet) == 'error') - { - if (isset($packet['iq']['#']['error'][0]['#'])) + if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { - $return_code = 'Error ' . $packet['iq']['#']['error'][0]['@']['code'] . ': ' . $packet['iq']['#']['error'][0]['#']; - $this->add_to_log('ERROR: transport_registration()'); + $this->add_to_log('Error: TLS mode change failed.'); + return false; } - } - return $return_code; - } - else - { - return 3; - } - } - - /** - * Return log - */ - function get_log() - { - if ($this->enable_logging && sizeof($this->log_array)) - { - return implode("

", $this->log_array); - } - - return ''; - } - - /** - * Add information to log - */ - function add_to_log($string) - { - if ($this->enable_logging) - { - $this->log_array[] = htmlspecialchars($string); - } - } + socket_set_blocking($this->connection, $meta['blocked']); + $this->session['tls'] = true; + // new stream + $this->send("\n"); + $this->send("\n"); - // ====================================================================== - // private methods - // ====================================================================== + return $this->response($this->listen()); + break; - /** - * Send auth - * @access private - */ - function _sendauth_ok($zerok_token, $zerok_sequence) - { - // initial hash of password - $zerok_hash = sha1($this->password); + case 'success': + // Yay, authentication successful. + $this->send("\n"); + $this->session['authenticated'] = true; - // sequence 0: hash of hashed-password and token - $zerok_hash = sha1($zerok_hash . $zerok_token); - - // repeat as often as needed - for ($i = 0; $i < $zerok_sequence; $i++) - { - $zerok_hash = sha1($zerok_hash); - } + // we have to wait for another response + return $this->response($this->listen()); + break; - $payload = "{$this->username} - $zerok_hash - {$this->resource}"; + case 'iq': + // we are not interested in IQs we did not expect + if (!isset($xml['iq'][0]['@']['id'])) + { + return false; + } - $packet = $this->send_iq(NULL, 'set', $this->auth_id, 'jabber:iq:auth', $payload); + // multiple possibilities here + switch ($xml['iq'][0]['@']['id']) + { + case 'bind_1': + $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#']; + + // and (maybe) yet another request to be able to send messages *finally* + if ($this->session['sess_required']) + { + $this->send(" + + "); + return $this->response($this->listen()); + } + + return true; + break; + + case 'sess_1': + return true; + break; + + case 'reg_1': + // more than instructions, username and password? + if (sizeof($xml['iq'][0]['#']['query'][0]['#']) > 3) + { + $this->add_to_log('Server requires too much data for registration.'); + return false; + } + + $this->send(" + + " . utf8_htmlspecialchars($this->username) . " + " . utf8_htmlspecialchars($this->password) . " + + "); + return $this->response($this->listen()); + break; + + case 'reg_2': + // registration end + if (isset($xml['iq'][0]['#']['error'])) + { + $this->add_to_log('Warning: Registration failed.'); + return false; + } + return true; + break; + + case 'unreg_1': + return true; + break; + + default: + $this->add_to_log('Notice: Received unexpected IQ.'); + return false; + break; + } + break; - // was a result returned? - if ($this->get_info_from_iq_type($packet) == 'result' && $this->get_info_from_iq_id($packet) == $this->auth_id) - { - return true; - } - else - { - $this->add_to_log('ERROR: _sendauth_ok() #1'); - return false; - } - } + case 'message': + // we are only interested in content... + if (!isset($xml['message'][0]['#']['body'])) + { + return false; + } - /** - * Send auth digest - * @access private - */ - function _sendauth_digest() - { - $payload = "{$this->username} - {$this->resource} - " . sha1($this->stream_id . $this->password) . ""; + $message['body'] = $xml['message'][0]['#']['body'][0]['#']; + $message['from'] = $xml['message'][0]['@']['from']; - $packet = $this->send_iq(NULL, 'set', $this->auth_id, 'jabber:iq:auth', $payload); + if (isset($xml['message'][0]['#']['subject'])) + { + $message['subject'] = $xml['message'][0]['#']['subject'][0]['#']; + } + $this->session['messages'][] = $message; + break; - // was a result returned? - if ($this->get_info_from_iq_type($packet) == 'result' && $this->get_info_from_iq_id($packet) == $this->auth_id) - { - return true; - } - else - { - $this->add_to_log('ERROR: _sendauth_digest() #1'); - return false; + default: + // hm...don't know this response + $this->add_to_log('Notice: Unknown server response (' . key($xml) . ')'); + return false; + break; } } - /** - * Send auth plain - * @access private - */ - function _sendauth_plaintext() + function send_message($to, $text, $subject = '', $type = 'normal') { - $payload = "{$this->username} - {$this->password} - {$this->resource}"; - - $packet = $this->send_iq(NULL, 'set', $this->auth_id, 'jabber:iq:auth', $payload); - - // was a result returned? - if ($this->get_info_from_iq_type($packet) == 'result' && $this->get_info_from_iq_id($packet) == $this->auth_id) + if (!isset($this->session['jid'])) { - return true; - } - else - { - $this->add_to_log('ERROR: _sendauth_plaintext() #1'); return false; } - } - - /** - * Listen on socket - * @access private - */ - function _listen_incoming() - { - $incoming = ''; - - while ($line = $this->connector->read_from_socket(4096)) - { - $incoming .= $line; - } - - $incoming = trim($incoming); - if ($incoming != '') + if (!in_array($type, array('chat', 'normal', 'error', 'groupchat', 'headline'))) { - $this->add_to_log('RECV: ' . $incoming); + $type = 'normal'; } - return $this->xmlize($incoming); + return $this->send(" + " . utf8_htmlspecialchars($subject) . " + " . utf8_htmlspecialchars($text) . " + " + ); } /** - * Check if connected - * @access private + * Encrypts a password as in RFC 2831 + * @param array $data Needs data from the client-server connection + * @access public + * @return string */ - function _check_connected($in_tls = false) + function encrypt_password($data) { - $incoming_array = $this->_listen_incoming(); - - if (is_array($incoming_array)) + // let's me think about again... + foreach (array('realm', 'cnonce', 'digest-uri') as $key) { - if ($incoming_array['stream:stream']['@']['from'] == $this->server && $incoming_array['stream:stream']['@']['xmlns'] == 'jabber:client' && $incoming_array['stream:stream']['@']['xmlns:stream'] == 'http://etherx.jabber.org/streams') - { - $this->stream_id = $incoming_array['stream:stream']['@']['id']; - - // We only start TLS authentication if not called within TLS authentication itself, which may produce a never ending loop... - if (!$in_tls) - { - if (!empty($incoming_array['stream:stream']['#']['stream:features'][0]['#']['starttls'][0]['@']['xmlns']) && $incoming_array['stream:stream']['#']['stream:features'][0]['#']['starttls'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-tls') - { - return $this->_starttls(); - } - } - - return true; - } - else + if (!isset($data[$key])) { - $this->add_to_log('ERROR: _check_connected() #1'); - return false; + $data[$key] = ''; } } - else - { - $this->add_to_log('ERROR: _check_connected() #2'); - return false; - } - } - - /** - * Start TLS/SSL session if supported (PHP5.1) - * @access private - */ - function _starttls() - { - if (!function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('socket_set_blocking') || !function_exists('stream_get_wrappers')) - { - $this->add_to_log('WARNING: TLS is not available'); - return true; - } - // Make sure the encryption stream is supported - $streams = stream_get_wrappers(); + $pack = md5($this->username . ':' . $data['realm'] . ':' . $this->password); - if (!in_array('streams.crypto', $streams)) + if (isset($data['authzid'])) { - $this->add_to_log('WARNING: SSL/crypto stream not supported'); - return true; - } - - $this->send_packet("\n"); - sleep(2); - $incoming_array = $this->_listen_incoming(); - - if (!is_array($incoming_array)) - { - $this->add_to_log('ERROR: _starttls() #1'); - return false; + $a1 = pack('H32', $pack) . sprintf(':%s:%s:%s', $data['nonce'], $data['cnonce'], $data['authzid']); } - - if ($incoming_array['proceed']['@']['xmlns'] != 'urn:ietf:params:xml:ns:xmpp-tls') - { - $this->add_to_log('ERROR: _starttls() #2'); - return false; - } - - $meta = stream_get_meta_data($this->connector->active_socket); - socket_set_blocking($this->connector->active_socket, 1); - - $result = @stream_socket_enable_crypto($this->connector->active_socket, true, STREAM_CRYPTO_METHOD_TLS_CLIENT); - if (!$result) + else { - socket_set_blocking($this->connector->active_socket, $meta['blocked']); - $this->add_to_log('ERROR: _starttls() #3'); - return false; + $a1 = pack('H32', $pack) . sprintf(':%s:%s', $data['nonce'], $data['cnonce']); } - socket_set_blocking($this->connector->active_socket, $meta['blocked']); - - $this->send_packet("\n"); - $this->send_packet("show_version) ? " version='{$this->version}'" : '') . ">\n"); - sleep(2); - - if (!$this->_check_connected(true)) - { - $this->add_to_log('ERROR: _starttls() #4'); - return false; - } + // should be: qop = auth + $a2 = 'AUTHENTICATE:'. $data['digest-uri']; - return true; + return md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2))); } /** - * Get packet type - * @access private + * parse_data like a="b",c="d",... + * @param string $data + * @access public + * @return array a => b ... */ - function _get_packet_type($packet = NULL) + function parse_data($data) { - if (is_array($packet)) - { - reset($packet); - $packet_type = key($packet); - } - - return ($packet_type) ? $packet_type : false; - } + // super basic, but should suffice + $data = explode(',', $data); + $pairs = array(); - /** - * Split incoming packet - * @access private - */ - function _split_incoming($incoming) - { - $temp = preg_split('#<(message|iq|presence|stream)#', $incoming, -1, PREG_SPLIT_DELIM_CAPTURE); - $array = array(); - - for ($i = 1, $size = sizeof($temp); $i < $size; $i += 2) - { - $array[] = '<' . $temp[$i] . $temp[($i + 1)]; - } - - return $array; - } - - /** - * Recursively prepares the strings in an array to be used in XML data. - * @access private - */ - function _array_xmlspecialchars(&$array) - { - if (is_array($array)) + foreach ($data as $pair) { - foreach ($array as $k => $v) + $dd = strpos($pair, '='); + if ($dd) { - if (is_array($v)) - { - $this->_array_xmlspecialchars($array[$k]); - } - else - { - $this->_xmlspecialchars($array[$k]); - } + $pairs[substr($pair, 0, $dd)] = trim(substr($pair, $dd + 1), '"'); } } + return $pairs; } /** - * Prepares a string for usage in XML data. - * @access private - */ - function _xmlspecialchars(&$string) - { - // we only have a few entities in xml - $string = str_replace(array('&', '>', '<', '"', '\''), array('&', '>', '<', '"', '''), $string); - } - - // ====================================================================== - // parsers - // ====================================================================== - - /** - * Get info from message (from) - */ - function get_info_from_message_from($packet = NULL) - { - return (is_array($packet)) ? $packet['message']['@']['from'] : false; - } - - /** - * Get info from message (type) - */ - function get_info_from_message_type($packet = NULL) - { - return (is_array($packet)) ? $packet['message']['@']['type'] : false; - } - - /** - * Get info from message (id) - */ - function get_info_from_message_id($packet = NULL) - { - return (is_array($packet)) ? $packet['message']['@']['id'] : false; - } - - /** - * Get info from message (thread) - */ - function get_info_from_message_thread($packet = NULL) - { - return (is_array($packet)) ? $packet['message']['#']['thread'][0]['#'] : false; - } - - /** - * Get info from message (subject) - */ - function get_info_from_message_subject($packet = NULL) - { - return (is_array($packet)) ? $packet['message']['#']['subject'][0]['#'] : false; - } - - /** - * Get info from message (body) + * opposite of jabber::parse_data() + * @param array $data + * @access public + * @return string */ - function get_info_from_message_body($packet = NULL) + function implode_data($data) { - return (is_array($packet)) ? $packet['message']['#']['body'][0]['#'] : false; - } - - /** - * Get info from message (xmlns) - */ - function get_info_from_message_xmlns($packet = NULL) - { - return (is_array($packet)) ? $packet['message']['#']['x'] : false; - } - - /** - * Get info from message (error) - */ - function get_info_from_message_error($packet = NULL) - { - $error = preg_replace('#^\/$#', '', ($packet['message']['#']['error'][0]['@']['code'] . '/' . $packet['message']['#']['error'][0]['#'])); - return (is_array($packet)) ? $error : false; - } - - // ====================================================================== - // parsers - // ====================================================================== - - /** - * Get info from iq (from) - */ - function get_info_from_iq_from($packet = NULL) - { - return (is_array($packet)) ? $packet['iq']['@']['from'] : false; - } - - /** - * Get info from iq (type) - */ - function get_info_from_iq_type($packet = NULL) - { - return (is_array($packet)) ? $packet['iq']['@']['type'] : false; - } - - /** - * Get info from iq (id) - */ - function get_info_from_iq_id($packet = NULL) - { - return (is_array($packet)) ? $packet['iq']['@']['id'] : false; - } - - /** - * Get info from iq (key) - */ - function get_info_from_iq_key($packet = NULL) - { - return (is_array($packet) && isset($packet['iq']['#']['query'][0]['#']['key'][0]['#'])) ? $packet['iq']['#']['query'][0]['#']['key'][0]['#'] : false; - } - - /** - * Get info from iq (error) - */ - function get_info_from_iq_error($packet = NULL) - { - $error = preg_replace('#^\/$#', '', ($packet['iq']['#']['error'][0]['@']['code'] . '/' . $packet['iq']['#']['error'][0]['#'])); - return (is_array($packet)) ? $error : false; - } - - // ====================================================================== - // handlers - // ====================================================================== - - /** - * Message type normal - */ - function handler_message_normal($packet) - { - $from = $packet['message']['@']['from']; - $this->add_to_log("EVENT: Message (type normal) from $from"); - } - - /** - * Message type chat - */ - function handler_message_chat($packet) - { - $from = $packet['message']['@']['from']; - $this->add_to_log("EVENT: Message (type chat) from $from"); - } - - /** - * Message type groupchat - */ - function handler_message_groupchat($packet) - { - $from = $packet['message']['@']['from']; - $this->add_to_log("EVENT: Message (type groupchat) from $from"); - } - - /** - * Message type headline - */ - function handler_message_headline($packet) - { - $from = $packet['message']['@']['from']; - $this->add_to_log("EVENT: Message (type headline) from $from"); - } - - /** - * Message type error - */ - function handler_message_error($packet) - { - $from = $packet['message']['@']['from']; - $this->add_to_log("EVENT: Message (type error) from $from"); - } - - // ====================================================================== - // handlers - // ====================================================================== - - /** - * application version updates - */ - function handler_iq_jabber_iq_autoupdate($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:autoupdate from $from"); - } - - /** - * interactive server component properties - */ - function handler_iq_jabber_iq_agent($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:agent from $from"); - } - - /** - * method to query interactive server components - */ - function handler_iq_jabber_iq_agents($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:agents from $from"); - } - - /** - * simple client authentication - */ - function handler_iq_jabber_iq_auth($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:auth from $from"); - } - - /** - * out of band data - */ - function handler_iq_jabber_iq_oob($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:oob from $from"); - } - - /** - * method to store private data on the server - */ - function handler_iq_jabber_iq_private($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:private from $from"); - } - - /** - * method for interactive registration - */ - function handler_iq_jabber_iq_register($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:register from $from"); - } - - /** - * client roster management - */ - function handler_iq_jabber_iq_roster($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:roster from $from"); - } - - /** - * method for searching a user database - */ - function handler_iq_jabber_iq_search($packet) - { - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: jabber:iq:search from $from"); - } - - /** - * method for requesting the current time - */ - function handler_iq_jabber_iq_time($packet) - { - if ($this->keep_alive_id == $this->get_info_from_iq_id($packet)) + $return = array(); + foreach ($data as $key => $value) { - $this->returned_keep_alive = true; - $this->connected = true; - - $this->add_to_log('EVENT: Keep-Alive returned, connection alive.'); + $return[] = $key . '="' . $value . '"'; } - - $type = $this->get_info_from_iq_type($packet); - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - $id = ($id != '') ? $id : 'time_' . time(); - - if ($type == 'get') - { - $payload = '' . gmdate("Ydm\TH:i:s") . '' . date('T') . '' . date("Y/d/m h:i:s A") . ''; - $this->send_iq($from, 'result', $id, 'jabber:iq:time', $payload); - } - - $this->add_to_log("EVENT: jabber:iq:time (type $type) from $from"); + return implode(',', $return); } - /** - */ - function handler_iq_error($packet) - { - // We'll do something with these later. This is a placeholder so that errors don't bounce back and forth. - } - - /** - * method for requesting version - */ - function handler_iq_jabber_iq_version($packet) - { - $type = $this->get_info_from_iq_type($packet); - $from = $this->get_info_from_iq_from($packet); - $id = $this->get_info_from_iq_id($packet); - $id = ($id != '') ? $id : 'version_' . time(); - - if ($type == 'get') - { - $payload = "{$this->iq_version_name} - {$this->iq_version_os} - {$this->iq_version_version}"; - - //$this->SendIq($from, 'result', $id, "jabber:iq:version", $payload); - } - - $this->add_to_log("EVENT: jabber:iq:version (type $type) from $from -- DISABLED"); - } - - // ====================================================================== - // Generic handlers - // ====================================================================== - - /** - * Generic handler for unsupported requests - */ - function handler_not_implemented($packet) - { - $packet_type = $this->_get_packet_type($packet); - $from = call_user_func(array(&$this, 'get_info_from_' . strtolower($packet_type) . '_from'), $packet); - $id = call_user_func(array(&$this, 'get_info_from_' . strtolower($packet_type) . '_id'), $packet); - - $this->send_error($from, $id, 501); - $this->add_to_log("EVENT: Unrecognized <$packet_type/> from $from"); - } - - // ====================================================================== - // Third party code - // m@d pr0ps to the coders ;) - // ====================================================================== - /** * xmlize() * @author Hans Anderson @@ -1334,6 +724,12 @@ class jabber { $data = trim($data); + if (substr($data, 0, 5) != ''. $data . ''; + } + $vals = $index = $array = array(); $parser = xml_parser_create($encoding); xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); @@ -1344,8 +740,13 @@ class jabber $i = 0; $tagname = $vals[$i]['tag']; - $array[$tagname]['@'] = (isset($vals[$i]['attributes'])) ? $vals[$i]['attributes'] : array(); - $array[$tagname]['#'] = $this->_xml_depth($vals, $i); + $array[$tagname][0]['@'] = (isset($vals[$i]['attributes'])) ? $vals[$i]['attributes'] : array(); + $array[$tagname][0]['#'] = $this->_xml_depth($vals, $i); + + if (substr($data, 0, 5) != ''; - } - - foreach ($array as $key => $val) - { - if (is_array($val)) - { - $this->traverse_xmlize($val, $arr_name . '[' . $key . ']', $level + 1); - } - else - { - $GLOBALS['traverse_array'][] = '$' . $arr_name . '[' . $key . '] = "' . $val . "\"\n"; - } - } - - if ($level == 0) - { - echo ''; - } - - return 1; - } -} - -/** -* Jabber Connector -* @package phpBB3 -*/ -class cjp_standard_connector -{ - var $active_socket; - - /** - * Open socket - */ - function open_socket($server, $port) - { - if (function_exists('dns_get_record')) - { - $record = dns_get_record("_xmpp-client._tcp.$server", DNS_SRV); - - if (!empty($record)) - { - $server = $record[0]['target']; - $port = $record[0]['port']; - } - } - - $errno = 0; - $errstr = ''; - - if ($this->active_socket = @fsockopen($server, $port, $errno, $errstr, 5)) - { - @socket_set_blocking($this->active_socket, 0); - @socket_set_timeout($this->active_socket, 31536000); - - return true; - } - else - { - return false; - } - } - - /** - * Close socket - */ - function close_socket() - { - return @fclose($this->active_socket); - } - - /** - * Write to socket - */ - function write_to_socket($data) - { - return @fwrite($this->active_socket, $data); - } - - /** - * Read from socket - */ - function read_from_socket($chunksize) - { - $buffer = @fread($this->active_socket, $chunksize); - $buffer = (STRIP) ? stripslashes($buffer) : $buffer; - - return $buffer; - } } ?> \ No newline at end of file -- cgit v1.2.1