diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/channel.php | 57 | ||||
-rw-r--r-- | lib/command.php | 4 | ||||
-rw-r--r-- | lib/imchannel.php | 104 | ||||
-rw-r--r-- | lib/immanager.php | 56 | ||||
-rw-r--r-- | lib/implugin.php | 613 | ||||
-rw-r--r-- | lib/imqueuehandler.php (renamed from lib/jabberqueuehandler.php) | 33 | ||||
-rw-r--r-- | lib/imreceiverqueuehandler.php (renamed from lib/publicqueuehandler.php) | 29 | ||||
-rw-r--r-- | lib/imsenderqueuehandler.php | 43 | ||||
-rw-r--r-- | lib/jabber.php | 473 | ||||
-rw-r--r-- | lib/queued_xmpp.php | 117 | ||||
-rw-r--r-- | lib/queuehandler.php | 14 | ||||
-rw-r--r-- | lib/queuemanager.php | 41 | ||||
-rw-r--r-- | lib/queuemonitor.php | 2 | ||||
-rw-r--r-- | lib/statusnet.php | 16 | ||||
-rw-r--r-- | lib/util.php | 9 | ||||
-rw-r--r-- | lib/xmppmanager.php | 485 | ||||
-rw-r--r-- | lib/xmppoutqueuehandler.php | 55 |
17 files changed, 867 insertions, 1284 deletions
diff --git a/lib/channel.php b/lib/channel.php index 3cd168786..05437b4e9 100644 --- a/lib/channel.php +++ b/lib/channel.php @@ -47,63 +47,6 @@ class Channel } } -class XMPPChannel extends Channel -{ - - var $conn = null; - - function source() - { - return 'xmpp'; - } - - function __construct($conn) - { - $this->conn = $conn; - } - - function on($user) - { - return $this->set_notify($user, 1); - } - - function off($user) - { - return $this->set_notify($user, 0); - } - - function output($user, $text) - { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function error($user, $text) - { - $text = '['.common_config('site', 'name') . '] ' . $text; - jabber_send_message($user->jabber, $text); - } - - function set_notify(&$user, $notify) - { - $orig = clone($user); - $user->jabbernotify = $notify; - $result = $user->update($orig); - if (!$result) { - $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); - common_log(LOG_ERR, - 'Could not set notify flag to ' . $notify . - ' for user ' . common_log_objstring($user) . - ': ' . $last_error->message); - return false; - } else { - common_log(LOG_INFO, - 'User ' . $user->nickname . ' set notify flag to ' . $notify); - return true; - } - } -} - class WebChannel extends Channel { var $out = null; diff --git a/lib/command.php b/lib/command.php index 2a51fd687..44b7b2274 100644 --- a/lib/command.php +++ b/lib/command.php @@ -596,7 +596,7 @@ class OffCommand extends Command } function execute($channel) { - if ($other) { + if ($this->other) { $channel->error($this->user, _("Command not yet implemented.")); } else { if ($channel->off($this->user)) { @@ -619,7 +619,7 @@ class OnCommand extends Command function execute($channel) { - if ($other) { + if ($this->other) { $channel->error($this->user, _("Command not yet implemented.")); } else { if ($channel->on($this->user)) { diff --git a/lib/imchannel.php b/lib/imchannel.php new file mode 100644 index 000000000..12354ce4b --- /dev/null +++ b/lib/imchannel.php @@ -0,0 +1,104 @@ +<?php +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class IMChannel extends Channel +{ + + var $imPlugin; + + function source() + { + return $imPlugin->transport; + } + + function __construct($imPlugin) + { + $this->imPlugin = $imPlugin; + } + + function on($user) + { + return $this->set_notify($user, 1); + } + + function off($user) + { + return $this->set_notify($user, 0); + } + + function output($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + $this->imPlugin->send_message($this->imPlugin->get_screenname($user), $text); + } + + function error($user, $text) + { + $text = '['.common_config('site', 'name') . '] ' . $text; + + $screenname = $this->imPlugin->get_screenname($user); + if($screenname){ + $this->imPlugin->send_message($screenname, $text); + return true; + }else{ + common_log(LOG_ERR, + 'Could not send error message to user ' . common_log_objstring($user) . + ' on transport ' . $this->imPlugin->transport .' : user preference does not exist'); + return false; + } + } + + function set_notify($user, $notify) + { + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $this->imPlugin->transport; + $user_im_prefs->user_id = $user->id; + if($user_im_prefs->find() && $user_im_prefs->fetch()){ + if($user_im_prefs->notify == $notify){ + //notify is already set the way they want + return true; + }else{ + $original = clone($user_im_prefs); + $user_im_prefs->notify = $notify; + $result = $user_im_prefs->update($original); + + if (!$result) { + $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError'); + common_log(LOG_ERR, + 'Could not set notify flag to ' . $notify . + ' for user ' . common_log_objstring($user) . + ' on transport ' . $this->imPlugin->transport .' : ' . $last_error->message); + return false; + } else { + common_log(LOG_INFO, + 'User ' . $user->nickname . ' set notify flag to ' . $notify); + return true; + } + } + }else{ + common_log(LOG_ERR, + 'Could not set notify flag to ' . $notify . + ' for user ' . common_log_objstring($user) . + ' on transport ' . $this->imPlugin->transport .' : user preference does not exist'); + return false; + } + } +} diff --git a/lib/immanager.php b/lib/immanager.php new file mode 100644 index 000000000..da80b74b7 --- /dev/null +++ b/lib/immanager.php @@ -0,0 +1,56 @@ +<?php +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * IKM background connection manager for IM-using queue handlers, + * allowing them to send outgoing messages on the right connection. + * + * In a multi-site queuedaemon.php run, one connection will be instantiated + * for each site being handled by the current process that has IM enabled. + * + * Implementations that extend this class will likely want to: + * 1) override start() with their connection process. + * 2) override handleInput() with what to do when data is waiting on + * one of the sockets + * 3) override idle($timeout) to do keepalives (if necessary) + * 4) implement send_raw_message() to send raw data that ImPlugin::enqueue_outgoing_raw + * enqueued + */ + +abstract class ImManager extends IoManager +{ + abstract function send_raw_message($data); + + function __construct($imPlugin) + { + $this->plugin = $imPlugin; + $this->plugin->imManager = $this; + } + + /** + * Fetch the singleton manager for the current site. + * @return mixed ImManager, or false if unneeded + */ + public static function get() + { + throw new Exception('ImManager should be created using it\'s constructor, not the static get method'); + } +} diff --git a/lib/implugin.php b/lib/implugin.php new file mode 100644 index 000000000..018b0ecee --- /dev/null +++ b/lib/implugin.php @@ -0,0 +1,613 @@ +<?php +/** + * StatusNet, the distributed open-source microblogging tool + * + * Superclass for plugins that do instant messaging + * + * PHP version 5 + * + * LICENCE: This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +/** + * Superclass for plugins that do authentication + * + * Implementations will likely want to override onStartIoManagerClasses() so that their + * IO manager is used + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews <candrews@integralblue.com> + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +abstract class ImPlugin extends Plugin +{ + //name of this IM transport + public $transport = null; + //list of screennames that should get all public notices + public $public = array(); + + /** + * normalize a screenname for comparison + * + * @param string $screenname screenname to normalize + * + * @return string an equivalent screenname in normalized form + */ + abstract function normalize($screenname); + + + /** + * validate (ensure the validity of) a screenname + * + * @param string $screenname screenname to validate + * + * @return boolean + */ + abstract function validate($screenname); + + /** + * get the internationalized/translated display name of this IM service + * + * @return string + */ + abstract function getDisplayName(); + + /** + * send a single notice to a given screenname + * The implementation should put raw data, ready to send, into the outgoing + * queue using enqueue_outgoing_raw() + * + * @param string $screenname screenname to send to + * @param Notice $notice notice to send + * + * @return boolean success value + */ + function send_notice($screenname, $notice) + { + return $this->send_message($screenname, $this->format_notice($notice)); + } + + /** + * send a message (text) to a given screenname + * The implementation should put raw data, ready to send, into the outgoing + * queue using enqueue_outgoing_raw() + * + * @param string $screenname screenname to send to + * @param Notice $body text to send + * + * @return boolean success value + */ + abstract function send_message($screenname, $body); + + /** + * receive a raw message + * Raw IM data is taken from the incoming queue, and passed to this function. + * It should parse the raw message and call handle_incoming() + * + * @param object $data raw IM data + * + * @return boolean success value + */ + abstract function receive_raw_message($data); + + /** + * get the screenname of the daemon that sends and receives message for this service + * + * @return string screenname of this plugin + */ + abstract function daemon_screenname(); + + /** + * get the microid uri of a given screenname + * + * @param string $screenname screenname + * + * @return string microid uri + */ + function microiduri($screenname) + { + return $this->transport . ':' . $screenname; + } + //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - MISC ========================\ + + /** + * Put raw message data (ready to send) into the outgoing queue + * + * @param object $data + */ + function enqueue_outgoing_raw($data) + { + $qm = QueueManager::get(); + $qm->enqueue($data, $this->transport . '-out'); + } + + /** + * Put raw message data (received, ready to be processed) into the incoming queue + * + * @param object $data + */ + function enqueue_incoming_raw($data) + { + $qm = QueueManager::get(); + $qm->enqueue($data, $this->transport . '-in'); + } + + /** + * given a screenname, get the corresponding user + * + * @param string $screenname + * + * @return User user + */ + function get_user($screenname) + { + $user_im_prefs = $this->get_user_im_prefs_from_screenname($screenname); + if($user_im_prefs){ + $user = User::staticGet('id', $user_im_prefs->user_id); + $user_im_prefs->free(); + return $user; + }else{ + return false; + } + } + + + /** + * given a screenname, get the User_im_prefs object for this transport + * + * @param string $screenname + * + * @return User_im_prefs user_im_prefs + */ + function get_user_im_prefs_from_screenname($screenname) + { + if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'screenname' => $screenname) )){ + return $user_im_prefs; + }else{ + return false; + } + } + + + /** + * given a User, get their screenname + * + * @param User $user + * + * @return string screenname of that user + */ + function get_screenname($user) + { + $user_im_prefs = $this->get_user_im_prefs_from_user($user); + if($user_im_prefs){ + return $user_im_prefs->screenname; + }else{ + return false; + } + } + + + /** + * given a User, get their User_im_prefs + * + * @param User $user + * + * @return User_im_prefs user_im_prefs of that user + */ + function get_user_im_prefs_from_user($user) + { + if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'user_id' => $user->id) )){ + return $user_im_prefs; + }else{ + return false; + } + } + //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - SENDING ========================\ + /** + * Send a message to a given screenname from the site + * + * @param string $screenname screenname to send the message to + * @param string $msg message contents to send + * + * @param boolean success + */ + protected function send_from_site($screenname, $msg) + { + $text = '['.common_config('site', 'name') . '] ' . $msg; + $this->send_message($screenname, $text); + } + + /** + * send a confirmation code to a user + * + * @param string $screenname screenname sending to + * @param string $code the confirmation code + * @param User $user user sending to + * + * @return boolean success value + */ + function send_confirmation_code($screenname, $code, $user) + { + $body = sprintf(_('User "%s" on %s has said that your %s screenname belongs to them. ' . + 'If that\'s true, you can confirm by clicking on this URL: ' . + '%s' . + ' . (If you cannot click it, copy-and-paste it into the ' . + 'address bar of your browser). If that user isn\'t you, ' . + 'or if you didn\'t request this confirmation, just ignore this message.'), + $user->nickname, common_config('site', 'name'), $this->getDisplayName(), common_local_url('confirmaddress', array('code' => $code))); + + return $this->send_message($screenname, $body); + } + + /** + * send a notice to all public listeners + * + * For notices that are generated on the local system (by users), we can optionally + * forward them to remote listeners by XMPP. + * + * @param Notice $notice notice to broadcast + * + * @return boolean success flag + */ + + function public_notice($notice) + { + // Now, users who want everything + + // FIXME PRIV don't send out private messages here + // XXX: should we send out non-local messages if public,localonly + // = false? I think not + + foreach ($this->public as $screenname) { + common_log(LOG_INFO, + 'Sending notice ' . $notice->id . + ' to public listener ' . $screenname, + __FILE__); + $this->send_notice($screenname, $notice); + } + + return true; + } + + /** + * broadcast a notice to all subscribers and reply recipients + * + * This function will send a notice to all subscribers on the local server + * who have IM addresses, and have IM notification enabled, and + * have this subscription enabled for IM. It also sends the notice to + * all recipients of @-replies who have IM addresses and IM notification + * enabled. This is really the heart of IM distribution in StatusNet. + * + * @param Notice $notice The notice to broadcast + * + * @return boolean success flag + */ + + function broadcast_notice($notice) + { + + $ni = $notice->whoGets(); + + foreach ($ni as $user_id => $reason) { + $user = User::staticGet($user_id); + if (empty($user)) { + // either not a local user, or just not found + continue; + } + $user_im_prefs = $this->get_user_im_prefs_from_user($user); + if(!$user_im_prefs || !$user_im_prefs->notify){ + continue; + } + + switch ($reason) { + case NOTICE_INBOX_SOURCE_REPLY: + if (!$user_im_prefs->replies) { + continue 2; + } + break; + case NOTICE_INBOX_SOURCE_SUB: + $sub = Subscription::pkeyGet(array('subscriber' => $user->id, + 'subscribed' => $notice->profile_id)); + if (empty($sub) || !$sub->jabber) { + continue 2; + } + break; + case NOTICE_INBOX_SOURCE_GROUP: + break; + default: + throw new Exception(sprintf(_("Unknown inbox source %d."), $reason)); + } + + common_log(LOG_INFO, + 'Sending notice ' . $notice->id . ' to ' . $user_im_prefs->screenname, + __FILE__); + $this->send_notice($user_im_prefs->screenname, $notice); + $user_im_prefs->free(); + } + + return true; + } + + /** + * makes a plain-text formatted version of a notice, suitable for IM distribution + * + * @param Notice $notice notice being sent + * + * @return string plain-text version of the notice, with user nickname prefixed + */ + + function format_notice($notice) + { + $profile = $notice->getProfile(); + return $profile->nickname . ': ' . $notice->content . ' [' . $notice->id . ']'; + } + //========================UTILITY FUNCTIONS USEFUL TO IMPLEMENTATIONS - RECEIVING ========================\ + + /** + * Attempt to handle a message as a command + * @param User $user user the message is from + * @param string $body message text + * @return boolean true if the message was a command and was executed, false if it was not a command + */ + protected function handle_command($user, $body) + { + $inter = new CommandInterpreter(); + $cmd = $inter->handle_command($user, $body); + if ($cmd) { + $chan = new IMChannel($this); + $cmd->execute($chan); + return true; + } else { + return false; + } + } + + /** + * Is some text an autoreply message? + * @param string $txt message text + * @return boolean true if autoreply + */ + protected function is_autoreply($txt) + { + if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) { + return true; + } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) { + return true; + } else { + return false; + } + } + + /** + * Is some text an OTR message? + * @param string $txt message text + * @return boolean true if OTR + */ + protected function is_otr($txt) + { + if (preg_match('/^\?OTR/', $txt)) { + return true; + } else { + return false; + } + } + + /** + * Helper for handling incoming messages + * Your incoming message handler will probably want to call this function + * + * @param string $from screenname the message was sent from + * @param string $message message contents + * + * @param boolean success + */ + protected function handle_incoming($from, $notice_text) + { + $user = $this->get_user($from); + // For common_current_user to work + global $_cur; + $_cur = $user; + + if (!$user) { + $this->send_from_site($from, 'Unknown user; go to ' . + common_local_url('imsettings') . + ' to add your address to your account'); + common_log(LOG_WARNING, 'Message from unknown user ' . $from); + return; + } + if ($this->handle_command($user, $notice_text)) { + common_log(LOG_INFO, "Command message by $from handled."); + return; + } else if ($this->is_autoreply($notice_text)) { + common_log(LOG_INFO, 'Ignoring auto reply from ' . $from); + return; + } else if ($this->is_otr($notice_text)) { + common_log(LOG_INFO, 'Ignoring OTR from ' . $from); + return; + } else { + + common_log(LOG_INFO, 'Posting a notice from ' . $user->nickname); + + $this->add_notice($from, $user, $notice_text); + } + + $user->free(); + unset($user); + unset($_cur); + unset($message); + } + + /** + * Helper for handling incoming messages + * Your incoming message handler will probably want to call this function + * + * @param string $from screenname the message was sent from + * @param string $message message contents + * + * @param boolean success + */ + protected function add_notice($screenname, $user, $body) + { + $body = trim(strip_tags($body)); + $content_shortened = common_shorten_links($body); + if (Notice::contentTooLong($content_shortened)) { + $this->send_from_site($screenname, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'), + Notice::maxContent(), + mb_strlen($content_shortened))); + return; + } + + try { + $notice = Notice::saveNew($user->id, $content_shortened, $this->transport); + } catch (Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + $this->send_from_site($from, $e->getMessage()); + return; + } + + common_broadcast_notice($notice); + common_log(LOG_INFO, + 'Added notice ' . $notice->id . ' from user ' . $user->nickname); + $notice->free(); + unset($notice); + } + + //========================EVENT HANDLERS========================\ + + /** + * Register notice queue handler + * + * @param QueueManager $manager + * + * @return boolean hook return + */ + function onEndInitializeQueueManager($manager) + { + $manager->connect($this->transport . '-in', new ImReceiverQueueHandler($this), 'im'); + $manager->connect($this->transport, new ImQueueHandler($this)); + $manager->connect($this->transport . '-out', new ImSenderQueueHandler($this), 'im'); + return true; + } + + function onStartImDaemonIoManagers(&$classes) + { + //$classes[] = new ImManager($this); // handles sending/receiving/pings/reconnects + return true; + } + + function onStartEnqueueNotice($notice, &$transports) + { + $profile = Profile::staticGet($notice->profile_id); + + if (!$profile) { + common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . + 'unknown profile ' . common_log_objstring($notice), + __FILE__); + }else{ + $transports[] = $this->transport; + } + + return true; + } + + function onEndShowHeadElements($action) + { + $aname = $action->trimmed('action'); + + if ($aname == 'shownotice') { + + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $action->profile->id; + $user_im_prefs->transport = $this->transport; + + if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->notice->uri) { + $id = new Microid($this->microiduri($user_im_prefs->screenname), + $action->notice->uri); + $action->element('meta', array('name' => 'microid', + 'content' => $id->toString())); + } + + } else if ($aname == 'showstream') { + + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $action->user->id; + $user_im_prefs->transport = $this->transport; + + if ($user_im_prefs->find() && $user_im_prefs->fetch() && $user_im_prefs->microid && $action->profile->profileurl) { + $id = new Microid($this->microiduri($user_im_prefs->screenname), + $action->selfUrl()); + $action->element('meta', array('name' => 'microid', + 'content' => $id->toString())); + } + } + } + + function onNormalizeImScreenname($transport, &$screenname) + { + if($transport == $this->transport) + { + $screenname = $this->normalize($screenname); + return false; + } + } + + function onValidateImScreenname($transport, $screenname, &$valid) + { + if($transport == $this->transport) + { + $valid = $this->validate($screenname); + return false; + } + } + + function onGetImTransports(&$transports) + { + $transports[$this->transport] = array('display' => $this->getDisplayName()); + } + + function onSendImConfirmationCode($transport, $screenname, $code, $user) + { + if($transport == $this->transport) + { + $this->send_confirmation_code($screenname, $code, $user); + return false; + } + } + + function onUserDeleteRelated($user, &$tables) + { + $tables[] = 'User_im_prefs'; + return true; + } + + function initialize() + { + if(is_null($this->transport)){ + throw new Exception('transport cannot be null'); + } + } +} diff --git a/lib/jabberqueuehandler.php b/lib/imqueuehandler.php index d6b4b7416..b42d8e7c0 100644 --- a/lib/jabberqueuehandler.php +++ b/lib/imqueuehandler.php @@ -17,31 +17,32 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } /** - * Queue handler for pushing new notices to Jabber users. - * @fixme this exception handling doesn't look very good. + * Common superclass for all IM sending queue handlers. */ -class JabberQueueHandler extends QueueHandler -{ - var $conn = null; - function transport() +class ImQueueHandler extends QueueHandler +{ + function __construct($plugin) { - return 'jabber'; + $this->plugin = $plugin; } + /** + * Handle a notice + * @param Notice $notice + * @return boolean success + */ function handle($notice) { - require_once(INSTALLDIR.'/lib/jabber.php'); - try { - return jabber_broadcast_notice($notice); - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; + $this->plugin->broadcast_notice($notice); + if ($notice->is_local == Notice::LOCAL_PUBLIC || + $notice->is_local == Notice::LOCAL_NONPUBLIC) { + $this->plugin->public_notice($notice); } + return true; } + } diff --git a/lib/publicqueuehandler.php b/lib/imreceiverqueuehandler.php index a497d1385..269c7db91 100644 --- a/lib/publicqueuehandler.php +++ b/lib/imreceiverqueuehandler.php @@ -17,29 +17,26 @@ * along with this program. If not, see <http://www.gnu.org/licenses/>. */ -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } /** - * Queue handler for pushing new notices to public XMPP subscribers. + * Common superclass for all IM receiving queue handlers. */ -class PublicQueueHandler extends QueueHandler -{ - function transport() +class ImReceiverQueueHandler extends QueueHandler +{ + function __construct($plugin) { - return 'public'; + $this->plugin = $plugin; } - function handle($notice) + /** + * Handle incoming IM data sent by a user to the IM bot + * @param object $data + * @return boolean success + */ + function handle($data) { - require_once(INSTALLDIR.'/lib/jabber.php'); - try { - return jabber_public_notice($notice); - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; - } + return $this->plugin->receive_raw_message($data); } } diff --git a/lib/imsenderqueuehandler.php b/lib/imsenderqueuehandler.php new file mode 100644 index 000000000..790dd7b10 --- /dev/null +++ b/lib/imsenderqueuehandler.php @@ -0,0 +1,43 @@ +<?php +/* + * StatusNet - the distributed open-source microblogging tool + * Copyright (C) 2008, 2009, StatusNet, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM sending queue handlers. + */ + +class ImSenderQueueHandler extends QueueHandler +{ + function __construct($plugin) + { + $this->plugin = $plugin; + } + + /** + * Handle outgoing IM data to be sent from the bot to a user + * @param object $data + * @return boolean success + */ + function handle($data) + { + return $this->plugin->imManager->send_raw_message($data); + } +} + diff --git a/lib/jabber.php b/lib/jabber.php deleted file mode 100644 index e1bf06ba6..000000000 --- a/lib/jabber.php +++ /dev/null @@ -1,473 +0,0 @@ -<?php -/** - * StatusNet, the distributed open-source microblogging tool - * - * utility functions for Jabber/GTalk/XMPP messages - * - * PHP version 5 - * - * LICENCE: This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * @category Network - * @package StatusNet - * @author Evan Prodromou <evan@status.net> - * @copyright 2008 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once 'XMPPHP/XMPP.php'; - -/** - * checks whether a string is a syntactically valid Jabber ID (JID) - * - * @param string $jid string to check - * - * @return boolean whether the string is a valid JID - */ - -function jabber_valid_base_jid($jid) -{ - // Cheap but effective - return Validate::email($jid); -} - -/** - * normalizes a Jabber ID for comparison - * - * @param string $jid JID to check - * - * @return string an equivalent JID in normalized (lowercase) form - */ - -function jabber_normalize_jid($jid) -{ - if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $jid, $matches)) { - $node = $matches[1]; - $server = $matches[2]; - return strtolower($node.'@'.$server); - } else { - return null; - } -} - -/** - * the JID of the Jabber daemon for this StatusNet instance - * - * @return string JID of the Jabber daemon - */ - -function jabber_daemon_address() -{ - return common_config('xmpp', 'user') . '@' . common_config('xmpp', 'server'); -} - -class Sharing_XMPP extends XMPPHP_XMPP -{ - function getSocket() - { - return $this->socket; - } -} - -/** - * Build an XMPP proxy connection that'll save outgoing messages - * to the 'xmppout' queue to be picked up by xmppdaemon later. - */ -function jabber_proxy() -{ - $proxy = new Queued_XMPP(common_config('xmpp', 'host') ? - common_config('xmpp', 'host') : - common_config('xmpp', 'server'), - common_config('xmpp', 'port'), - common_config('xmpp', 'user'), - common_config('xmpp', 'password'), - common_config('xmpp', 'resource') . 'daemon', - common_config('xmpp', 'server'), - common_config('xmpp', 'debug') ? - true : false, - common_config('xmpp', 'debug') ? - XMPPHP_Log::LEVEL_VERBOSE : null); - return $proxy; -} - -/** - * Lazy-connect the configured Jabber account to the configured server; - * if already opened, the same connection will be returned. - * - * In a multi-site background process, each site configuration - * will get its own connection. - * - * @param string $resource Resource to connect (defaults to configured resource) - * - * @return XMPPHP connection to the configured server - */ - -function jabber_connect($resource=null) -{ - static $connections = array(); - $site = common_config('site', 'server'); - if (empty($connections[$site])) { - if (empty($resource)) { - $resource = common_config('xmpp', 'resource'); - } - $conn = new Sharing_XMPP(common_config('xmpp', 'host') ? - common_config('xmpp', 'host') : - common_config('xmpp', 'server'), - common_config('xmpp', 'port'), - common_config('xmpp', 'user'), - common_config('xmpp', 'password'), - $resource, - common_config('xmpp', 'server'), - common_config('xmpp', 'debug') ? - true : false, - common_config('xmpp', 'debug') ? - XMPPHP_Log::LEVEL_VERBOSE : null - ); - - if (!$conn) { - return false; - } - $connections[$site] = $conn; - - $conn->autoSubscribe(); - $conn->useEncryption(common_config('xmpp', 'encryption')); - - try { - common_log(LOG_INFO, __METHOD__ . ": connecting " . - common_config('xmpp', 'user') . '/' . $resource); - //$conn->connect(true); // true = persistent connection - $conn->connect(); // persistent connections break multisite - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, $e->getMessage()); - return false; - } - - $conn->processUntil('session_start'); - } - return $connections[$site]; -} - -/** - * Queue send for a single notice to a given Jabber address - * - * @param string $to JID to send the notice to - * @param Notice $notice notice to send - * - * @return boolean success value - */ - -function jabber_send_notice($to, $notice) -{ - $conn = jabber_proxy(); - $profile = Profile::staticGet($notice->profile_id); - if (!$profile) { - common_log(LOG_WARNING, 'Refusing to send notice with ' . - 'unknown profile ' . common_log_objstring($notice), - __FILE__); - return false; - } - $msg = jabber_format_notice($profile, $notice); - $entry = jabber_format_entry($profile, $notice); - $conn->message($to, $msg, 'chat', null, $entry); - $profile->free(); - return true; -} - -/** - * extra information for XMPP messages, as defined by Twitter - * - * @param Profile $profile Profile of the sending user - * @param Notice $notice Notice being sent - * - * @return string Extra information (Atom, HTML, addresses) in string format - */ - -function jabber_format_entry($profile, $notice) -{ - $entry = $notice->asAtomEntry(true, true); - - $xs = new XMLStringer(); - $xs->elementStart('html', array('xmlns' => 'http://jabber.org/protocol/xhtml-im')); - $xs->elementStart('body', array('xmlns' => 'http://www.w3.org/1999/xhtml')); - $xs->element('a', array('href' => $profile->profileurl), - $profile->nickname); - $xs->text(": "); - if (!empty($notice->rendered)) { - $xs->raw($notice->rendered); - } else { - $xs->raw(common_render_content($notice->content, $notice)); - } - $xs->text(" "); - $xs->element('a', array( - 'href'=>common_local_url('conversation', - array('id' => $notice->conversation)).'#notice-'.$notice->id - ),sprintf(_('[%s]'),$notice->id)); - $xs->elementEnd('body'); - $xs->elementEnd('html'); - - $html = $xs->getString(); - - return $html . ' ' . $entry; -} - -/** - * sends a single text message to a given JID - * - * @param string $to JID to send the message to - * @param string $body body of the message - * @param string $type type of the message - * @param string $subject subject of the message - * - * @return boolean success flag - */ - -function jabber_send_message($to, $body, $type='chat', $subject=null) -{ - $conn = jabber_proxy(); - $conn->message($to, $body, $type, $subject); - return true; -} - -/** - * sends a presence stanza on the Jabber network - * - * @param string $status current status, free-form string - * @param string $show structured status value - * @param string $to recipient of presence, null for general - * @param string $type type of status message, related to $show - * @param int $priority priority of the presence - * - * @return boolean success value - */ - -function jabber_send_presence($status, $show='available', $to=null, - $type = 'available', $priority=null) -{ - $conn = jabber_connect(); - if (!$conn) { - return false; - } - $conn->presence($status, $show, $to, $type, $priority); - return true; -} - -/** - * sends a confirmation request to a JID - * - * @param string $code confirmation code for confirmation URL - * @param string $nickname nickname of confirming user - * @param string $address JID to send confirmation to - * - * @return boolean success flag - */ - -function jabber_confirm_address($code, $nickname, $address) -{ - $body = 'User "' . $nickname . '" on ' . common_config('site', 'name') . ' ' . - 'has said that your Jabber ID belongs to them. ' . - 'If that\'s true, you can confirm by clicking on this URL: ' . - common_local_url('confirmaddress', array('code' => $code)) . - ' . (If you cannot click it, copy-and-paste it into the ' . - 'address bar of your browser). If that user isn\'t you, ' . - 'or if you didn\'t request this confirmation, just ignore this message.'; - - return jabber_send_message($address, $body); -} - -/** - * sends a "special" presence stanza on the Jabber network - * - * @param string $type Type of presence - * @param string $to JID to send presence to - * @param string $show show value for presence - * @param string $status status value for presence - * - * @return boolean success flag - * - * @see jabber_send_presence() - */ - -function jabber_special_presence($type, $to=null, $show=null, $status=null) -{ - // FIXME: why use this instead of jabber_send_presence()? - $conn = jabber_connect(); - - $to = htmlspecialchars($to); - $status = htmlspecialchars($status); - - $out = "<presence"; - if ($to) { - $out .= " to='$to'"; - } - if ($type) { - $out .= " type='$type'"; - } - if ($show == 'available' and !$status) { - $out .= "/>"; - } else { - $out .= ">"; - if ($show && ($show != 'available')) { - $out .= "<show>$show</show>"; - } - if ($status) { - $out .= "<status>$status</status>"; - } - $out .= "</presence>"; - } - $conn->send($out); -} - -/** - * Queue broadcast of a notice to all subscribers and reply recipients - * - * This function will send a notice to all subscribers on the local server - * who have Jabber addresses, and have Jabber notification enabled, and - * have this subscription enabled for Jabber. It also sends the notice to - * all recipients of @-replies who have Jabber addresses and Jabber notification - * enabled. This is really the heart of Jabber distribution in StatusNet. - * - * @param Notice $notice The notice to broadcast - * - * @return boolean success flag - */ - -function jabber_broadcast_notice($notice) -{ - if (!common_config('xmpp', 'enabled')) { - return true; - } - $profile = Profile::staticGet($notice->profile_id); - - if (!$profile) { - common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . - 'unknown profile ' . common_log_objstring($notice), - __FILE__); - return true; // not recoverable; discard. - } - - $msg = jabber_format_notice($profile, $notice); - $entry = jabber_format_entry($profile, $notice); - - $profile->free(); - unset($profile); - - $sent_to = array(); - - $conn = jabber_proxy(); - - $ni = $notice->whoGets(); - - foreach ($ni as $user_id => $reason) { - $user = User::staticGet($user_id); - if (empty($user) || - empty($user->jabber) || - !$user->jabbernotify) { - // either not a local user, or just not found - continue; - } - switch ($reason) { - case NOTICE_INBOX_SOURCE_REPLY: - if (!$user->jabberreplies) { - continue 2; - } - break; - case NOTICE_INBOX_SOURCE_SUB: - $sub = Subscription::pkeyGet(array('subscriber' => $user->id, - 'subscribed' => $notice->profile_id)); - if (empty($sub) || !$sub->jabber) { - continue 2; - } - break; - case NOTICE_INBOX_SOURCE_GROUP: - break; - default: - throw new Exception(sprintf(_("Unknown inbox source %d."), $reason)); - } - - common_log(LOG_INFO, - 'Sending notice ' . $notice->id . ' to ' . $user->jabber, - __FILE__); - $conn->message($user->jabber, $msg, 'chat', null, $entry); - } - - return true; -} - -/** - * Queue send of a notice to all public listeners - * - * For notices that are generated on the local system (by users), we can optionally - * forward them to remote listeners by XMPP. - * - * @param Notice $notice notice to broadcast - * - * @return boolean success flag - */ - -function jabber_public_notice($notice) -{ - // Now, users who want everything - - $public = common_config('xmpp', 'public'); - - // FIXME PRIV don't send out private messages here - // XXX: should we send out non-local messages if public,localonly - // = false? I think not - - if ($public && $notice->is_local == Notice::LOCAL_PUBLIC) { - $profile = Profile::staticGet($notice->profile_id); - - if (!$profile) { - common_log(LOG_WARNING, 'Refusing to broadcast notice with ' . - 'unknown profile ' . common_log_objstring($notice), - __FILE__); - return true; // not recoverable; discard. - } - - $msg = jabber_format_notice($profile, $notice); - $entry = jabber_format_entry($profile, $notice); - - $conn = jabber_proxy(); - - foreach ($public as $address) { - common_log(LOG_INFO, - 'Sending notice ' . $notice->id . - ' to public listener ' . $address, - __FILE__); - $conn->message($address, $msg, 'chat', null, $entry); - } - $profile->free(); - } - - return true; -} - -/** - * makes a plain-text formatted version of a notice, suitable for Jabber distribution - * - * @param Profile &$profile profile of the sending user - * @param Notice &$notice notice being sent - * - * @return string plain-text version of the notice, with user nickname prefixed - */ - -function jabber_format_notice(&$profile, &$notice) -{ - return $profile->nickname . ': ' . $notice->content . ' [' . $notice->id . ']'; -} diff --git a/lib/queued_xmpp.php b/lib/queued_xmpp.php deleted file mode 100644 index fdd074db2..000000000 --- a/lib/queued_xmpp.php +++ /dev/null @@ -1,117 +0,0 @@ -<?php -/** - * StatusNet, the distributed open-source microblogging tool - * - * Queue-mediated proxy class for outgoing XMPP messages. - * - * PHP version 5 - * - * LICENCE: This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - * - * @category Network - * @package StatusNet - * @author Brion Vibber <brion@status.net> - * @copyright 2010 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { - exit(1); -} - -require_once INSTALLDIR . '/lib/jabber.php'; - -class Queued_XMPP extends XMPPHP_XMPP -{ - /** - * Constructor - * - * @param string $host - * @param integer $port - * @param string $user - * @param string $password - * @param string $resource - * @param string $server - * @param boolean $printlog - * @param string $loglevel - */ - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) - { - parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); - // Normally the fulljid isn't filled out until resource binding time; - // we need to save it here since we're not talking to a real server. - $this->fulljid = "{$this->basejid}/{$this->resource}"; - } - - /** - * Send a formatted message to the outgoing queue for later forwarding - * to a real XMPP connection. - * - * @param string $msg - */ - public function send($msg, $timeout=NULL) - { - $qm = QueueManager::get('xmppout'); - $qm->enqueue(strval($msg), 'xmppout'); - } - - /** - * Since we'll be getting input through a queue system's run loop, - * we'll process one standalone message at a time rather than our - * own XMPP message pump. - * - * @param string $message - */ - public function processMessage($message) { - $frame = array_shift($this->frames); - xml_parse($this->parser, $frame->body, false); - } - - //@{ - /** - * Stream i/o functions disabled; push input through processMessage() - */ - public function connect($timeout = 30, $persistent = false, $sendinit = true) - { - throw new Exception("Can't connect to server from XMPP queue proxy."); - } - - public function disconnect() - { - throw new Exception("Can't connect to server from XMPP queue proxy."); - } - - public function process() - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - - public function processUntil($event, $timeout=-1) - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - - public function read() - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - - public function readyToProcess() - { - throw new Exception("Can't read stream from XMPP queue proxy."); - } - //@} -} - diff --git a/lib/queuehandler.php b/lib/queuehandler.php index 2909cd83b..2194dd161 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -37,20 +37,6 @@ class QueueHandler { /** - * Return transport keyword which identifies items this queue handler - * services; must be defined for all subclasses. - * - * Must be 8 characters or less to fit in the queue_item database. - * ex "email", "jabber", "sms", "irc", ... - * - * @return string - */ - function transport() - { - return null; - } - - /** * Here's the meat of your queue handler -- you're handed a Notice * or other object, which you may do as you will with. * diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 710829e49..576c7c0af 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -156,21 +156,14 @@ abstract class QueueManager extends IoManager } /** - * Encode an object or variable for queued storage. - * Notice objects are currently stored as an id reference; - * other items are serialized. + * Encode an object for queued storage. * * @param mixed $item * @return string */ protected function encode($item) { - if ($item instanceof Notice) { - // Backwards compat - return $item->id; - } else { - return serialize($item); - } + return serialize($item); } /** @@ -182,25 +175,7 @@ abstract class QueueManager extends IoManager */ protected function decode($frame) { - if (is_numeric($frame)) { - // Back-compat for notices... - return Notice::staticGet(intval($frame)); - } elseif (substr($frame, 0, 1) == '<') { - // Back-compat for XML source - return $frame; - } else { - // Deserialize! - #$old = error_reporting(); - #error_reporting($old & ~E_NOTICE); - $out = unserialize($frame); - #error_reporting($old); - - if ($out === false && $frame !== 'b:0;') { - common_log(LOG_ERR, "Couldn't unserialize queued frame: $frame"); - return false; - } - return $out; - } + return unserialize($frame); } /** @@ -264,16 +239,6 @@ abstract class QueueManager extends IoManager $this->connect('sms', 'SmsQueueHandler'); } - // XMPP output handlers... - if (common_config('xmpp', 'enabled')) { - // Delivery prep, read by queuedaemon.php: - $this->connect('jabber', 'JabberQueueHandler'); - $this->connect('public', 'PublicQueueHandler'); - - // Raw output, read by xmppdaemon.php: - $this->connect('xmppout', 'XmppOutQueueHandler', 'xmpp'); - } - // For compat with old plugins not registering their own handlers. $this->connect('plugin', 'PluginQueueHandler'); } diff --git a/lib/queuemonitor.php b/lib/queuemonitor.php index 1c306a629..3dc0ea65a 100644 --- a/lib/queuemonitor.php +++ b/lib/queuemonitor.php @@ -36,7 +36,7 @@ class QueueMonitor * Only explicitly listed thread/site/queue owners will be incremented. * * @param string $key counter name - * @param array $owners list of owner keys like 'queue:jabber' or 'site:stat01' + * @param array $owners list of owner keys like 'queue:xmpp' or 'site:stat01' */ public function stats($key, $owners=array()) { diff --git a/lib/statusnet.php b/lib/statusnet.php index 7c4df84b4..3f9a48e6a 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -339,7 +339,6 @@ class StatusNet } // Backwards compatibility - if (array_key_exists('memcached', $config)) { if ($config['memcached']['enabled']) { addPlugin('Memcache', array('servers' => $config['memcached']['server'])); @@ -349,6 +348,21 @@ class StatusNet $config['cache']['base'] = $config['memcached']['base']; } } + if (array_key_exists('xmpp', $config)) { + if ($config['xmpp']['enabled']) { + addPlugin('xmpp', array( + 'server' => $config['xmpp']['server'], + 'port' => $config['xmpp']['port'], + 'user' => $config['xmpp']['user'], + 'resource' => $config['xmpp']['resource'], + 'encryption' => $config['xmpp']['encryption'], + 'password' => $config['xmpp']['password'], + 'host' => $config['xmpp']['host'], + 'debug' => $config['xmpp']['debug'], + 'public' => $config['xmpp']['public'] + )); + } + } } } diff --git a/lib/util.php b/lib/util.php index ef7852953..5bfeebdc3 100644 --- a/lib/util.php +++ b/lib/util.php @@ -1001,19 +1001,10 @@ function common_enqueue_notice($notice) $transports[] = 'plugin'; } - $xmpp = common_config('xmpp', 'enabled'); - - if ($xmpp) { - $transports[] = 'jabber'; - } - // @fixme move these checks into QueueManager and/or individual handlers if ($notice->is_local == Notice::LOCAL_PUBLIC || $notice->is_local == Notice::LOCAL_NONPUBLIC) { $transports = array_merge($transports, $localTransports); - if ($xmpp) { - $transports[] = 'public'; - } } if (Event::handle('StartEnqueueNotice', array($notice, &$transports))) { diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php deleted file mode 100644 index f37635855..000000000 --- a/lib/xmppmanager.php +++ /dev/null @@ -1,485 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2008, 2009, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -/** - * XMPP background connection manager for XMPP-using queue handlers, - * allowing them to send outgoing messages on the right connection. - * - * Input is handled during socket select loop, keepalive pings during idle. - * Any incoming messages will be forwarded to the main XmppDaemon process, - * which handles direct user interaction. - * - * In a multi-site queuedaemon.php run, one connection will be instantiated - * for each site being handled by the current process that has XMPP enabled. - */ - -class XmppManager extends IoManager -{ - protected $site = null; - protected $pingid = 0; - protected $lastping = null; - - static protected $singletons = array(); - - const PING_INTERVAL = 120; - - /** - * Fetch the singleton XmppManager for the current site. - * @return mixed XmppManager, or false if unneeded - */ - public static function get() - { - if (common_config('xmpp', 'enabled')) { - $site = StatusNet::currentSite(); - if (empty(self::$singletons[$site])) { - self::$singletons[$site] = new XmppManager(); - } - return self::$singletons[$site]; - } else { - return false; - } - } - - /** - * Tell the i/o master we need one instance for each supporting site - * being handled in this process. - */ - public static function multiSite() - { - return IoManager::INSTANCE_PER_SITE; - } - - function __construct() - { - $this->site = StatusNet::currentSite(); - $this->resource = common_config('xmpp', 'resource') . 'daemon'; - } - - /** - * Initialize connection to server. - * @return boolean true on success - */ - public function start($master) - { - parent::start($master); - $this->switchSite(); - - require_once INSTALLDIR . "/lib/jabber.php"; - - # Low priority; we don't want to receive messages - - common_log(LOG_INFO, "INITIALIZE"); - $this->conn = jabber_connect($this->resource); - - if (empty($this->conn)) { - common_log(LOG_ERR, "Couldn't connect to server."); - return false; - } - - $this->log(LOG_DEBUG, "Initializing stanza handlers."); - - $this->conn->addEventHandler('message', 'handle_message', $this); - $this->conn->addEventHandler('presence', 'handle_presence', $this); - $this->conn->addEventHandler('reconnect', 'handle_reconnect', $this); - - $this->conn->setReconnectTimeout(600); - jabber_send_presence("Send me a message to post a notice", 'available', null, 'available', 100); - - return !is_null($this->conn); - } - - /** - * Message pump is triggered on socket input, so we only need an idle() - * call often enough to trigger our outgoing pings. - */ - function timeout() - { - return self::PING_INTERVAL; - } - - /** - * Lists the XMPP connection socket to allow i/o master to wake - * when input comes in here as well as from the queue source. - * - * @return array of resources - */ - public function getSockets() - { - if ($this->conn) { - return array($this->conn->getSocket()); - } else { - return array(); - } - } - - /** - * Process XMPP events that have come in over the wire. - * Side effects: may switch site configuration - * @fixme may kill process on XMPP error - * @param resource $socket - */ - public function handleInput($socket) - { - $this->switchSite(); - - # Process the queue for as long as needed - try { - if ($this->conn) { - assert($socket === $this->conn->getSocket()); - - common_log(LOG_DEBUG, "Servicing the XMPP queue."); - $this->stats('xmpp_process'); - $this->conn->processTime(0); - } - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); - } - } - - /** - * Idle processing for io manager's execution loop. - * Send keepalive pings to server. - * - * Side effect: kills process on exception from XMPP library. - * - * @fixme non-dying error handling - */ - public function idle($timeout=0) - { - if ($this->conn) { - $now = time(); - if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { - $this->switchSite(); - try { - $this->sendPing(); - $this->lastping = $now; - } catch (XMPPHP_Exception $e) { - common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); - } - } - } - } - - /** - * For queue handlers to pass us a message to push out, - * if we're active. - * - * @fixme should this be blocking etc? - * - * @param string $msg XML stanza to send - * @return boolean success - */ - public function send($msg) - { - if ($this->conn && !$this->conn->isDisconnected()) { - $bytes = $this->conn->send($msg); - if ($bytes > 0) { - $this->conn->processTime(0); - return true; - } else { - return false; - } - } else { - // Can't send right now... - return false; - } - } - - /** - * Send a keepalive ping to the XMPP server. - */ - protected function sendPing() - { - $jid = jabber_daemon_address().'/'.$this->resource; - $server = common_config('xmpp', 'server'); - - if (!isset($this->pingid)) { - $this->pingid = 0; - } else { - $this->pingid++; - } - - common_log(LOG_DEBUG, "Sending ping #{$this->pingid}"); - - $this->conn->send("<iq from='{$jid}' to='{$server}' id='ping_{$this->pingid}' type='get'><ping xmlns='urn:xmpp:ping'/></iq>"); - } - - /** - * Callback for Jabber reconnect event - * @param $pl - */ - function handle_reconnect(&$pl) - { - common_log(LOG_NOTICE, 'XMPP reconnected'); - - $this->conn->processUntil('session_start'); - $this->conn->presence(null, 'available', null, 'available', 100); - } - - - function get_user($from) - { - $user = User::staticGet('jabber', jabber_normalize_jid($from)); - return $user; - } - - /** - * XMPP callback for handling message input... - * @param array $pl XMPP payload - */ - function handle_message(&$pl) - { - $from = jabber_normalize_jid($pl['from']); - - if ($pl['type'] != 'chat') { - $this->log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from."); - return; - } - - if (mb_strlen($pl['body']) == 0) { - $this->log(LOG_WARNING, "Ignoring message with empty body from $from."); - return; - } - - // Forwarded from another daemon for us to handle; this shouldn't - // happen any more but we might get some legacy items. - if ($this->is_self($from)) { - $this->log(LOG_INFO, "Got forwarded notice from self ($from)."); - $from = $this->get_ofrom($pl); - $this->log(LOG_INFO, "Originally sent by $from."); - if (is_null($from) || $this->is_self($from)) { - $this->log(LOG_INFO, "Ignoring notice originally sent by $from."); - return; - } - } - - $user = $this->get_user($from); - - // For common_current_user to work - global $_cur; - $_cur = $user; - - if (!$user) { - $this->from_site($from, 'Unknown user; go to ' . - common_local_url('imsettings') . - ' to add your address to your account'); - $this->log(LOG_WARNING, 'Message from unknown user ' . $from); - return; - } - if ($this->handle_command($user, $pl['body'])) { - $this->log(LOG_INFO, "Command message by $from handled."); - return; - } else if ($this->is_autoreply($pl['body'])) { - $this->log(LOG_INFO, 'Ignoring auto reply from ' . $from); - return; - } else if ($this->is_otr($pl['body'])) { - $this->log(LOG_INFO, 'Ignoring OTR from ' . $from); - return; - } else { - - $this->log(LOG_INFO, 'Posting a notice from ' . $user->nickname); - - $this->add_notice($user, $pl); - } - - $user->free(); - unset($user); - unset($_cur); - - unset($pl['xml']); - $pl['xml'] = null; - - $pl = null; - unset($pl); - } - - - function is_self($from) - { - return preg_match('/^'.strtolower(jabber_daemon_address()).'/', strtolower($from)); - } - - function get_ofrom($pl) - { - $xml = $pl['xml']; - $addresses = $xml->sub('addresses'); - if (!$addresses) { - $this->log(LOG_WARNING, 'Forwarded message without addresses'); - return null; - } - $address = $addresses->sub('address'); - if (!$address) { - $this->log(LOG_WARNING, 'Forwarded message without address'); - return null; - } - if (!array_key_exists('type', $address->attrs)) { - $this->log(LOG_WARNING, 'No type for forwarded message'); - return null; - } - $type = $address->attrs['type']; - if ($type != 'ofrom') { - $this->log(LOG_WARNING, 'Type of forwarded message is not ofrom'); - return null; - } - if (!array_key_exists('jid', $address->attrs)) { - $this->log(LOG_WARNING, 'No jid for forwarded message'); - return null; - } - $jid = $address->attrs['jid']; - if (!$jid) { - $this->log(LOG_WARNING, 'Could not get jid from address'); - return null; - } - $this->log(LOG_DEBUG, 'Got message forwarded from jid ' . $jid); - return $jid; - } - - function is_autoreply($txt) - { - if (preg_match('/[\[\(]?[Aa]uto[-\s]?[Rr]e(ply|sponse)[\]\)]/', $txt)) { - return true; - } else if (preg_match('/^System: Message wasn\'t delivered. Offline storage size was exceeded.$/', $txt)) { - return true; - } else { - return false; - } - } - - function is_otr($txt) - { - if (preg_match('/^\?OTR/', $txt)) { - return true; - } else { - return false; - } - } - - function from_site($address, $msg) - { - $text = '['.common_config('site', 'name') . '] ' . $msg; - jabber_send_message($address, $text); - } - - function handle_command($user, $body) - { - $inter = new CommandInterpreter(); - $cmd = $inter->handle_command($user, $body); - if ($cmd) { - $chan = new XMPPChannel($this->conn); - $cmd->execute($chan); - return true; - } else { - return false; - } - } - - function add_notice(&$user, &$pl) - { - $body = trim($pl['body']); - $content_shortened = common_shorten_links($body); - if (Notice::contentTooLong($content_shortened)) { - $from = jabber_normalize_jid($pl['from']); - $this->from_site($from, sprintf(_('Message too long - maximum is %1$d characters, you sent %2$d.'), - Notice::maxContent(), - mb_strlen($content_shortened))); - return; - } - - try { - $notice = Notice::saveNew($user->id, $content_shortened, 'xmpp'); - } catch (Exception $e) { - $this->log(LOG_ERR, $e->getMessage()); - $this->from_site($user->jabber, $e->getMessage()); - return; - } - - common_broadcast_notice($notice); - $this->log(LOG_INFO, - 'Added notice ' . $notice->id . ' from user ' . $user->nickname); - $notice->free(); - unset($notice); - } - - function handle_presence(&$pl) - { - $from = jabber_normalize_jid($pl['from']); - switch ($pl['type']) { - case 'subscribe': - # We let anyone subscribe - $this->subscribed($from); - $this->log(LOG_INFO, - 'Accepted subscription from ' . $from); - break; - case 'subscribed': - case 'unsubscribed': - case 'unsubscribe': - $this->log(LOG_INFO, - 'Ignoring "' . $pl['type'] . '" from ' . $from); - break; - default: - if (!$pl['type']) { - $user = User::staticGet('jabber', $from); - if (!$user) { - $this->log(LOG_WARNING, 'Presence from unknown user ' . $from); - return; - } - if ($user->updatefrompresence) { - $this->log(LOG_INFO, 'Updating ' . $user->nickname . - ' status from presence.'); - $this->add_notice($user, $pl); - } - $user->free(); - unset($user); - } - break; - } - unset($pl['xml']); - $pl['xml'] = null; - - $pl = null; - unset($pl); - } - - function log($level, $msg) - { - $text = 'XMPPDaemon('.$this->resource.'): '.$msg; - common_log($level, $text); - } - - function subscribed($to) - { - jabber_special_presence('subscribed', $to); - } - - /** - * Make sure we're on the right site configuration - */ - protected function switchSite() - { - if ($this->site != StatusNet::currentSite()) { - common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site"); - $this->stats('switch'); - StatusNet::switchSite($this->site); - } - } -} diff --git a/lib/xmppoutqueuehandler.php b/lib/xmppoutqueuehandler.php deleted file mode 100644 index 2afa260f1..000000000 --- a/lib/xmppoutqueuehandler.php +++ /dev/null @@ -1,55 +0,0 @@ -<?php -/* - * StatusNet - the distributed open-source microblogging tool - * Copyright (C) 2010, StatusNet, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * Queue handler for pre-processed outgoing XMPP messages. - * Formatted XML stanzas will have been pushed into the queue - * via the Queued_XMPP connection proxy, probably from some - * other queue processor. - * - * Here, the XML stanzas are simply pulled out of the queue and - * pushed out over the wire; an XmppManager is needed to set up - * and maintain the actual server connection. - * - * This queue will be run via XmppDaemon rather than QueueDaemon. - * - * @author Brion Vibber <brion@status.net> - */ -class XmppOutQueueHandler extends QueueHandler -{ - function transport() { - return 'xmppout'; - } - - /** - * Take a previously-queued XMPP stanza and send it out ot the server. - * @param string $msg - * @return boolean true on success - */ - function handle($msg) - { - assert(is_string($msg)); - - $xmpp = XmppManager::get(); - $ok = $xmpp->send($msg); - - return $ok; - } -} - |