From 77ea02cac3576f395e4548e7e6cbace90ba566ea Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Wed, 20 Jan 2010 16:31:48 -0500 Subject: [PATCH 001/655] Any object (not just Notice's) can be queued --- classes/Queue_item.php | 13 +-- classes/statusnet.ini | 6 +- db/statusnet.sql | 5 +- lib/dbqueuemanager.php | 95 ++++++------------- lib/jabberqueuehandler.php | 4 +- lib/ombqueuehandler.php | 2 +- lib/pingqueuehandler.php | 2 +- lib/pluginqueuehandler.php | 2 +- lib/publicqueuehandler.php | 6 +- lib/queuehandler.php | 95 ++----------------- lib/smsqueuehandler.php | 2 +- lib/stompqueuemanager.php | 38 +++----- lib/xmppmanager.php | 24 +++++ plugins/Enjit/enjitqueuehandler.php | 9 +- plugins/Facebook/facebookqueuehandler.php | 2 +- plugins/RSSCloud/RSSCloudPlugin.php | 41 ++------ plugins/RSSCloud/RSSCloudQueueHandler.php | 50 +--------- plugins/TwitterBridge/twitterqueuehandler.php | 2 +- scripts/handlequeued.php | 2 +- 19 files changed, 109 insertions(+), 291 deletions(-) mode change 100755 => 100644 plugins/RSSCloud/RSSCloudQueueHandler.php diff --git a/classes/Queue_item.php b/classes/Queue_item.php index cf805a6060..4d90e1d231 100644 --- a/classes/Queue_item.php +++ b/classes/Queue_item.php @@ -10,7 +10,8 @@ class Queue_item extends Memcached_DataObject /* the code below is auto generated do not remove the above tag */ public $__table = 'queue_item'; // table name - public $notice_id; // int(4) primary_key not_null + public $id; // int(4) primary_key not_null + public $frame; // blob not_null public $transport; // varchar(8) primary_key not_null public $created; // datetime() not_null public $claimed; // datetime() @@ -22,9 +23,6 @@ class Queue_item extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE - function sequenceKey() - { return array(false, false); } - static function top($transport=null) { $qi = new Queue_item(); @@ -42,7 +40,7 @@ class Queue_item extends Memcached_DataObject # XXX: potential race condition # can we force it to only update if claimed is still null # (or old)? - common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . + common_log(LOG_INFO, 'claiming queue item id=' . $qi->id . ' for transport ' . $qi->transport); $orig = clone($qi); $qi->claimed = common_sql_now(); @@ -57,9 +55,4 @@ class Queue_item extends Memcached_DataObject $qi = null; return null; } - - function pkeyGet($kv) - { - return Memcached_DataObject::pkeyGet('Queue_item', $kv); - } } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 44088cf6b0..6203650a69 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -428,14 +428,14 @@ tagged = K tag = K [queue_item] -notice_id = 129 +id = 129 +frame = 66 transport = 130 created = 142 claimed = 14 [queue_item__keys] -notice_id = K -transport = K +id = K [related_group] group_id = 129 diff --git a/db/statusnet.sql b/db/statusnet.sql index 2a9ab74c77..cb7dad3e24 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -274,13 +274,12 @@ create table remember_me ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table queue_item ( - - notice_id integer not null comment 'notice queued' references notice (id), + id integer auto_increment primary key comment 'unique identifier', + frame blob not null comment 'serialized object', transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', created datetime not null comment 'date this record was created', claimed datetime comment 'date this item was claimed', - constraint primary key (notice_id, transport), index queue_item_created_idx (created) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php index 889365b649..139f502340 100644 --- a/lib/dbqueuemanager.php +++ b/lib/dbqueuemanager.php @@ -31,19 +31,17 @@ class DBQueueManager extends QueueManager { /** - * Saves a notice object reference into the queue item table. + * Saves an object into the queue item table. * @return boolean true on success * @throws ServerException on failure */ public function enqueue($object, $queue) { - $notice = $object; - $qi = new Queue_item(); - $qi->notice_id = $notice->id; + $qi->frame = serialize($object); $qi->transport = $queue; - $qi->created = $notice->created; + $qi->created = common_sql_now(); $result = $qi->insert(); if (!$result) { @@ -73,34 +71,35 @@ class DBQueueManager extends QueueManager */ public function poll() { - $this->_log(LOG_DEBUG, 'Checking for notices...'); - $item = $this->_nextItem(); - if ($item === false) { - $this->_log(LOG_DEBUG, 'No notices waiting; idling.'); + $this->_log(LOG_DEBUG, 'Checking for queued objects...'); + $qi = $this->_nextItem(); + if ($qi === false) { + $this->_log(LOG_DEBUG, 'No queue items waiting; idling.'); return false; } - if ($item === true) { - // We dequeued an entry for a deleted or invalid notice. + if ($qi === true) { + // We dequeued an entry for a deleted or invalid object. // Consider it a hit for poll rate purposes. return true; } - list($queue, $notice) = $item; - $this->_log(LOG_INFO, 'Got notice '. $notice->id . ' for transport ' . $queue); + $queue = $qi->transport; + $object = unserialize($qi->frame); + $this->_log(LOG_INFO, 'Got item id=' . $qi->id . ' for transport ' . $queue); // Yay! Got one! $handler = $this->getHandler($queue); if ($handler) { - if ($handler->handle_notice($notice)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Successfully handled notice"); - $this->_done($notice, $queue); + if ($handler->handle($object)) { + $this->_log(LOG_INFO, "[$queue] Successfully handled object"); + $this->_done($qi); } else { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Failed to handle notice"); - $this->_fail($notice, $queue); + $this->_log(LOG_INFO, "[$queue] Failed to handle object"); + $this->_fail($qi); } } else { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] No handler for queue $queue; discarding."); - $this->_done($notice, $queue); + $this->_log(LOG_INFO, "[$queue] No handler for queue $queue; discarding."); + $this->_done($qi); } return true; } @@ -108,8 +107,7 @@ class DBQueueManager extends QueueManager /** * Pop the oldest unclaimed item off the queue set and claim it. * - * @return mixed false if no items; true if bogus hit; otherwise array(string, Notice) - * giving the queue transport name. + * @return mixed false if no items; true if bogus hit; otherwise Queue_item */ protected function _nextItem() { @@ -121,70 +119,42 @@ class DBQueueManager extends QueueManager return false; } - $queue = $qi->transport; - $notice = Notice::staticGet('id', $qi->notice_id); - if (empty($notice)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] dequeued non-existent notice"); - $qi->delete(); - return true; - } - - $result = $notice; - return array($queue, $notice); + return $qi; } /** * Delete our claimed item from the queue after successful processing. * - * @param Notice $object - * @param string $queue + * @param QueueItem $qi */ - protected function _done($object, $queue) + protected function _done($qi) { - // XXX: right now, we only handle notices - - $notice = $object; - - $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, - 'transport' => $queue)); - if (empty($qi)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); + $this->_log(LOG_INFO, "_done passed an empty queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Reluctantly releasing unclaimed queue item"); + $this->_log(LOG_WARNING, "Reluctantly releasing unclaimed queue item"); } $qi->delete(); $qi->free(); } - $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with item"); - $this->stats('handled', $queue); - - $notice->free(); + $this->_log(LOG_INFO, "done with item"); } /** * Free our claimed queue item for later reprocessing in case of * temporary failure. * - * @param Notice $object - * @param string $queue + * @param QueueItem $qi */ - protected function _fail($object, $queue) + protected function _fail($qi) { - // XXX: right now, we only handle notices - - $notice = $object; - - $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, - 'transport' => $queue)); - if (empty($qi)) { - $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); + $this->_log(LOG_INFO, "_fail passed an empty queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Ignoring failure for unclaimed queue item"); + $this->_log(LOG_WARNING, "Ignoring failure for unclaimed queue item"); } else { $orig = clone($qi); $qi->claimed = null; @@ -193,10 +163,7 @@ class DBQueueManager extends QueueManager } } - $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with queue item"); - $this->stats('error', $queue); - - $notice->free(); + $this->_log(LOG_INFO, "done with queue item"); } protected function _log($level, $msg) diff --git a/lib/jabberqueuehandler.php b/lib/jabberqueuehandler.php index b1518866d7..83471f2df7 100644 --- a/lib/jabberqueuehandler.php +++ b/lib/jabberqueuehandler.php @@ -34,14 +34,14 @@ class JabberQueueHandler extends QueueHandler return 'jabber'; } - function handle_notice($notice) + function handle($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_broadcast_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - exit(1); + return false; } } } diff --git a/lib/ombqueuehandler.php b/lib/ombqueuehandler.php index 3ffc1313bc..24896c784c 100644 --- a/lib/ombqueuehandler.php +++ b/lib/ombqueuehandler.php @@ -36,7 +36,7 @@ class OmbQueueHandler extends QueueHandler * @fixme doesn't currently report failure back to the queue manager * because omb_broadcast_notice() doesn't report it to us */ - function handle_notice($notice) + function handle($notice) { if ($this->is_remote($notice)) { $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id); diff --git a/lib/pingqueuehandler.php b/lib/pingqueuehandler.php index 8bb2180786..4e4d74cb1a 100644 --- a/lib/pingqueuehandler.php +++ b/lib/pingqueuehandler.php @@ -30,7 +30,7 @@ class PingQueueHandler extends QueueHandler { return 'ping'; } - function handle_notice($notice) { + function handle($notice) { require_once INSTALLDIR . '/lib/ping.php'; return ping_broadcast_notice($notice); } diff --git a/lib/pluginqueuehandler.php b/lib/pluginqueuehandler.php index 24d5046997..9653ccad42 100644 --- a/lib/pluginqueuehandler.php +++ b/lib/pluginqueuehandler.php @@ -42,7 +42,7 @@ class PluginQueueHandler extends QueueHandler return 'plugin'; } - function handle_notice($notice) + function handle($notice) { Event::handle('HandleQueuedNotice', array(&$notice)); return true; diff --git a/lib/publicqueuehandler.php b/lib/publicqueuehandler.php index 9ea9ee73a3..c9edb8d5d7 100644 --- a/lib/publicqueuehandler.php +++ b/lib/publicqueuehandler.php @@ -23,7 +23,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { /** * Queue handler for pushing new notices to public XMPP subscribers. - * @fixme correct this exception handling */ class PublicQueueHandler extends QueueHandler { @@ -33,15 +32,14 @@ class PublicQueueHandler extends QueueHandler return 'public'; } - function handle_notice($notice) + function handle($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_public_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - die($e->getMessage()); + return false; } - return true; } } diff --git a/lib/queuehandler.php b/lib/queuehandler.php index 613be6e330..2909cd83b1 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -22,51 +22,20 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } /** * Base class for queue handlers. * - * As extensions of the Daemon class, each queue handler has the ability - * to launch itself in the background, at which point it'll pass control - * to the configured QueueManager class to poll for updates. + * As of 0.9, queue handlers are short-lived for items as they are + * dequeued by a QueueManager running in an IoMaster in a daemon + * such as queuedaemon.php. + * + * Extensions requiring long-running maintenance or polling should + * register an IoManager. * * Subclasses must override at least the following methods: * - transport - * - handle_notice + * - handle */ -#class QueueHandler extends Daemon class QueueHandler { -# function __construct($id=null, $daemonize=true) -# { -# parent::__construct($daemonize); -# -# if ($id) { -# $this->set_id($id); -# } -# } - - /** - * How many seconds a polling-based queue manager should wait between - * checks for new items to handle. - * - * Defaults to 60 seconds; override to speed up or slow down. - * - * @fixme not really compatible with global queue manager - * @return int timeout in seconds - */ -# function timeout() -# { -# return 60; -# } - -# function class_name() -# { -# return ucfirst($this->transport()) . 'Handler'; -# } - -# function name() -# { -# return strtolower($this->class_name().'.'.$this->get_id()); -# } - /** * Return transport keyword which identifies items this queue handler * services; must be defined for all subclasses. @@ -83,61 +52,17 @@ class QueueHandler /** * Here's the meat of your queue handler -- you're handed a Notice - * object, which you may do as you will with. + * or other object, which you may do as you will with. * * If this function indicates failure, a warning will be logged * and the item is placed back in the queue to be re-run. * - * @param Notice $notice + * @param mixed $object * @return boolean true on success, false on failure */ - function handle_notice($notice) + function handle($object) { return true; } - - /** - * Setup and start of run loop for this queue handler as a daemon. - * Most of the heavy lifting is passed on to the QueueManager's service() - * method, which passes control back to our handle_notice() method for - * each notice that comes in on the queue. - * - * Most of the time this won't need to be overridden in a subclass. - * - * @return boolean true on success, false on failure - */ - function run() - { - if (!$this->start()) { - $this->log(LOG_WARNING, 'failed to start'); - return false; - } - - $this->log(LOG_INFO, 'checking for queued notices'); - - $queue = $this->transport(); - $timeout = $this->timeout(); - - $qm = QueueManager::get(); - - $qm->service($queue, $this); - - $this->log(LOG_INFO, 'finished servicing the queue'); - - if (!$this->finish()) { - $this->log(LOG_WARNING, 'failed to clean up'); - return false; - } - - $this->log(LOG_INFO, 'terminating normally'); - - return true; - } - - - function log($level, $msg) - { - common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg); - } } diff --git a/lib/smsqueuehandler.php b/lib/smsqueuehandler.php index 48a96409d0..6085d2b4ac 100644 --- a/lib/smsqueuehandler.php +++ b/lib/smsqueuehandler.php @@ -31,7 +31,7 @@ class SmsQueueHandler extends QueueHandler return 'sms'; } - function handle_notice($notice) + function handle($notice) { require_once(INSTALLDIR.'/lib/mail.php'); return mail_broadcast_notice_sms($notice); diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 00590fdb69..6496b5cf17 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -125,28 +125,25 @@ class StompQueueManager extends QueueManager } /** - * Saves a notice object reference into the queue item table. + * Saves an object into the queue item table. * @return boolean true on success */ public function enqueue($object, $queue) { - $notice = $object; + $msg = serialize($object); $this->_connect(); - // XXX: serialize and send entire notice - $result = $this->con->send($this->queueName($queue), - $notice->id, // BODY of the message - array ('created' => $notice->created)); + $msg, // BODY of the message + array ('created' => $timestamp)); if (!$result) { common_log(LOG_ERR, 'Error sending to '.$queue.' queue'); return false; } - common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' - . $notice->id . ' for ' . $queue); + common_log(LOG_DEBUG, "complete remote queueing $log for $queue"); $this->stats('enqueued', $queue); } @@ -174,7 +171,7 @@ class StompQueueManager extends QueueManager $ok = true; $frames = $this->con->readFrames(); foreach ($frames as $frame) { - $ok = $ok && $this->_handleNotice($frame); + $ok = $ok && $this->_handleItem($frame); } return $ok; } @@ -265,10 +262,10 @@ class StompQueueManager extends QueueManager } /** - * Handle and acknowledge a notice event that's come in through a queue. + * Handle and acknowledge an event that's come in through a queue. * * If the queue handler reports failure, the message is requeued for later. - * Missing notices or handler classes will drop the message. + * Missing objects or handler classes will drop the message. * * Side effects: in multi-site mode, may reset site configuration to * match the site that queued the event. @@ -276,24 +273,15 @@ class StompQueueManager extends QueueManager * @param StompFrame $frame * @return bool */ - protected function _handleNotice($frame) + protected function _handleItem($frame) { list($site, $queue) = $this->parseDestination($frame->headers['destination']); if ($site != common_config('site', 'server')) { $this->stats('switch'); StatusNet::init($site); } - - $id = intval($frame->body); - $info = "notice $id posted at {$frame->headers['created']} in queue $queue"; - - $notice = Notice::staticGet('id', $id); - if (empty($notice)) { - $this->_log(LOG_WARNING, "Skipping missing $info"); - $this->con->ack($frame); - $this->stats('badnotice', $queue); - return false; - } + $info = "object posted at {$frame->headers['created']} in queue $queue"; + $item = unserialize($frame->body); $handler = $this->getHandler($queue); if (!$handler) { @@ -303,7 +291,7 @@ class StompQueueManager extends QueueManager return false; } - $ok = $handler->handle_notice($notice); + $ok = $handler->handle($item); if (!$ok) { $this->_log(LOG_WARNING, "Failed handling $info"); @@ -311,7 +299,7 @@ class StompQueueManager extends QueueManager // this kind of queue management ourselves; // if we don't ack, it should resend... $this->con->ack($frame); - $this->enqueue($notice, $queue); + $this->enqueue($item, $queue); $this->stats('requeued', $queue); return false; } diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index dfff63a30c..c499868548 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -175,6 +175,30 @@ class XmppManager extends IoManager } } + /** + * 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) { + return true; + } else { + return false; + } + } else { + // Can't send right now... + return false; + } + } + /** * Send a keepalive ping to the XMPP server. */ diff --git a/plugins/Enjit/enjitqueuehandler.php b/plugins/Enjit/enjitqueuehandler.php index f0e706b929..14085cc5e3 100644 --- a/plugins/Enjit/enjitqueuehandler.php +++ b/plugins/Enjit/enjitqueuehandler.php @@ -32,14 +32,7 @@ class EnjitQueueHandler extends QueueHandler return 'enjit'; } - function start() - { - $this->log(LOG_INFO, "Starting EnjitQueueHandler"); - $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); - return true; - } - - function handle_notice($notice) + function handle($notice) { $profile = Profile::staticGet($notice->profile_id); diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php index 1778690e5b..524af7bc45 100644 --- a/plugins/Facebook/facebookqueuehandler.php +++ b/plugins/Facebook/facebookqueuehandler.php @@ -28,7 +28,7 @@ class FacebookQueueHandler extends QueueHandler return 'facebook'; } - function handle_notice($notice) + function handle($notice) { if ($this->_isLocal($notice)) { return facebookBroadcastNotice($notice); diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php index 2de162628f..9f444c8bba 100644 --- a/plugins/RSSCloud/RSSCloudPlugin.php +++ b/plugins/RSSCloud/RSSCloudPlugin.php @@ -138,6 +138,9 @@ class RSSCloudPlugin extends Plugin case 'RSSCloudNotifier': include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php'; return false; + case 'RSSCloudQueueHandler': + include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudQueueHandler.php'; + return false; case 'RSSCloudRequestNotifyAction': case 'LoggingAggregatorAction': include_once INSTALLDIR . '/plugins/RSSCloud/' . @@ -193,32 +196,6 @@ class RSSCloudPlugin extends Plugin return true; } - /** - * broadcast the message when not using queuehandler - * - * @param Notice &$notice the notice - * @param array $queue destination queue - * - * @return boolean hook return - */ - - function onUnqueueHandleNotice(&$notice, $queue) - { - if (($queue == 'rsscloud') && ($this->_isLocal($notice))) { - - common_debug('broadcasting rssCloud bound notice ' . $notice->id); - - $profile = $notice->getProfile(); - - $notifier = new RSSCloudNotifier(); - $notifier->notify($profile); - - return false; - } - - return true; - } - /** * Determine whether the notice was locally created * @@ -261,19 +238,15 @@ class RSSCloudPlugin extends Plugin } /** - * Add RSSCloudQueueHandler to the list of valid daemons to - * start + * Register RSSCloud notice queue handler * - * @param array $daemons the list of daemons to run + * @param QueueManager $manager * * @return boolean hook return - * */ - - function onGetValidDaemons($daemons) + function onEndInitializeQueueManager($manager) { - array_push($daemons, INSTALLDIR . - '/plugins/RSSCloud/RSSCloudQueueHandler.php'); + $manager->connect('rsscloud', 'RSSCloudQueueHandler'); return true; } diff --git a/plugins/RSSCloud/RSSCloudQueueHandler.php b/plugins/RSSCloud/RSSCloudQueueHandler.php old mode 100755 new mode 100644 index 693dd27c1f..295c261895 --- a/plugins/RSSCloud/RSSCloudQueueHandler.php +++ b/plugins/RSSCloud/RSSCloudQueueHandler.php @@ -1,4 +1,3 @@ -#!/usr/bin/env php . */ -define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); - -$shortoptions = 'i::'; -$longoptions = array('id::'); - -$helptext = <<log(LOG_INFO, "INITIALIZE"); - $this->notifier = new RSSCloudNotifier(); - return true; - } - - function handle_notice($notice) + function handle($notice) { $profile = $notice->getProfile(); - return $this->notifier->notify($profile); + $notifier = new RSSCloudNotifier(); + return $notifier->notify($profile); } - - function finish() - { - } - } -if (have_option('i')) { - $id = get_option_value('i'); -} else if (have_option('--id')) { - $id = get_option_value('--id'); -} else if (count($args) > 0) { - $id = $args[0]; -} else { - $id = null; -} - -$handler = new RSSCloudQueueHandler($id); - -$handler->runOnce(); diff --git a/plugins/TwitterBridge/twitterqueuehandler.php b/plugins/TwitterBridge/twitterqueuehandler.php index 5089ca7b74..b5a624e83d 100644 --- a/plugins/TwitterBridge/twitterqueuehandler.php +++ b/plugins/TwitterBridge/twitterqueuehandler.php @@ -28,7 +28,7 @@ class TwitterQueueHandler extends QueueHandler return 'twitter'; } - function handle_notice($notice) + function handle($notice) { return broadcast_twitter($notice); } diff --git a/scripts/handlequeued.php b/scripts/handlequeued.php index 9031437aac..8158849695 100755 --- a/scripts/handlequeued.php +++ b/scripts/handlequeued.php @@ -50,7 +50,7 @@ if (empty($notice)) { exit(1); } -if (!$handler->handle_notice($notice)) { +if (!$handler->handle($notice)) { print "Failed to handle notice id $noticeId on queue '$queue'.\n"; exit(1); } From bd72e8b96e1bc360ba46a1864c6121f3b5f11235 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 15 Jan 2010 22:28:43 -0500 Subject: [PATCH 002/655] Allow for instances as well as class names to be passed as queue handlers and iomanagers. Introduce IoManager::GLOBAL_SINGLE_ONLY which indicates that only one instance of this iomanager will be run, regardless of how many threads/processes and sites there are. --- lib/iomanager.php | 1 + lib/iomaster.php | 25 ++++++++++++++++++------- lib/queuemanager.php | 6 ++++-- scripts/queuedaemon.php | 13 +++++++++---- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/iomanager.php b/lib/iomanager.php index ee2ff958b9..f9d97d6a97 100644 --- a/lib/iomanager.php +++ b/lib/iomanager.php @@ -31,6 +31,7 @@ abstract class IoManager { + const GLOBAL_SINGLE_ONLY = -1; const SINGLE_ONLY = 0; const INSTANCE_PER_SITE = 1; const INSTANCE_PER_PROCESS = 2; diff --git a/lib/iomaster.php b/lib/iomaster.php index ce77b53b2e..979f73e753 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -32,6 +32,7 @@ class IoMaster public $id; protected $multiSite = false; + protected $includeGlobalSingletons = true; protected $managers = array(); protected $singletons = array(); @@ -47,8 +48,9 @@ class IoMaster $this->monitor = new QueueMonitor(); } - public function init($multiSite=null) + public function init($multiSite=null, $includeGlobalSingletons = true) { + $this->includeGlobalSingletons = $includeGlobalSingletons; if ($multiSite !== null) { $this->multiSite = $multiSite; } @@ -107,7 +109,7 @@ class IoMaster */ protected function instantiate($class) { - if (isset($this->singletons[$class])) { + if (is_string($class) && isset($this->singletons[$class])) { // Already instantiated a multi-site-capable handler. // Just let it know it should listen to this site too! $this->singletons[$class]->addSite(common_config('site', 'server')); @@ -116,25 +118,34 @@ class IoMaster $manager = $this->getManager($class); + $caps = $manager->multiSite(); if ($this->multiSite) { - $caps = $manager->multiSite(); if ($caps == IoManager::SINGLE_ONLY) { throw new Exception("$class can't run with --all; aborting."); } - if ($caps == IoManager::INSTANCE_PER_PROCESS) { + if ($caps == IoManager::INSTANCE_PER_PROCESS || + ( $this->includeGlobalSingletons && $caps == IoManager::GLOBAL_SINGLE_ONLY )) { // Save this guy for later! // We'll only need the one to cover multiple sites. - $this->singletons[$class] = $manager; + if (is_string($class)){ + $this->singletons[$class] = $manager; + } $manager->addSite(common_config('site', 'server')); } } - $this->managers[] = $manager; + if( $this->includeGlobalSingletons || $caps != IoManager::GLOBAL_SINGLE_ONLY ) { + $this->managers[] = $manager; + } } protected function getManager($class) { - return call_user_func(array($class, 'get')); + if(is_object($class)){ + return $class; + }else{ + return call_user_func(array($class, 'get')); + } } /** diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 291174d3c4..b20a934687 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -119,7 +119,9 @@ abstract class QueueManager extends IoManager { if (isset($this->handlers[$queue])) { $class = $this->handlers[$queue]; - if (class_exists($class)) { + if(is_object($class)) { + return $class; + } else if (class_exists($class)) { return new $class(); } else { common_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'"); @@ -182,7 +184,7 @@ abstract class QueueManager extends IoManager * Only registered transports will be reliably picked up! * * @param string $transport - * @param string $class + * @param string $class class name or object instance */ public function connect($transport, $class) { diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index 162f617e0d..6cce4eaa75 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -122,7 +122,7 @@ class QueueDaemon extends Daemon if ($this->threads > 1) { return $this->runThreads(); } else { - return $this->runLoop(); + return $this->runLoop(true); } } @@ -176,7 +176,8 @@ class QueueDaemon extends Daemon { $this->set_id($this->get_id() . "." . $thread); $this->resetDb(); - $this->runLoop(); + //only include global singletons on the first thread + $this->runLoop($thread == 1); } /** @@ -213,14 +214,18 @@ class QueueDaemon extends Daemon * * Most of the time this won't need to be overridden in a subclass. * + * @param boolean $includeGlobalSingletons Include IoManagers that are + * global singletons (should only be one instance - regardless of how + * many processes or sites there are) + * * @return boolean true on success, false on failure */ - function runLoop() + function runLoop($includeGlobalSingletons) { $this->log(LOG_INFO, 'checking for queued notices'); $master = new IoMaster($this->get_id()); - $master->init($this->all); + $master->init($this->all, $includeGlobalSingletons); $master->service(); $this->log(LOG_INFO, 'finished servicing the queue'); From ef7db60fed8575c381801c0b998d9ed58aab1745 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 22 Jan 2010 17:14:41 -0500 Subject: [PATCH 003/655] Revert "Allow for instances as well as class names to be passed as queue handlers and iomanagers." Going to use brion's SpawningDaemon instead This reverts commit bd72e8b96e1bc360ba46a1864c6121f3b5f11235. --- lib/iomanager.php | 1 - lib/iomaster.php | 25 +++++++------------------ lib/queuemanager.php | 6 ++---- scripts/queuedaemon.php | 13 ++++--------- 4 files changed, 13 insertions(+), 32 deletions(-) diff --git a/lib/iomanager.php b/lib/iomanager.php index f9d97d6a97..ee2ff958b9 100644 --- a/lib/iomanager.php +++ b/lib/iomanager.php @@ -31,7 +31,6 @@ abstract class IoManager { - const GLOBAL_SINGLE_ONLY = -1; const SINGLE_ONLY = 0; const INSTANCE_PER_SITE = 1; const INSTANCE_PER_PROCESS = 2; diff --git a/lib/iomaster.php b/lib/iomaster.php index 979f73e753..ce77b53b2e 100644 --- a/lib/iomaster.php +++ b/lib/iomaster.php @@ -32,7 +32,6 @@ class IoMaster public $id; protected $multiSite = false; - protected $includeGlobalSingletons = true; protected $managers = array(); protected $singletons = array(); @@ -48,9 +47,8 @@ class IoMaster $this->monitor = new QueueMonitor(); } - public function init($multiSite=null, $includeGlobalSingletons = true) + public function init($multiSite=null) { - $this->includeGlobalSingletons = $includeGlobalSingletons; if ($multiSite !== null) { $this->multiSite = $multiSite; } @@ -109,7 +107,7 @@ class IoMaster */ protected function instantiate($class) { - if (is_string($class) && isset($this->singletons[$class])) { + if (isset($this->singletons[$class])) { // Already instantiated a multi-site-capable handler. // Just let it know it should listen to this site too! $this->singletons[$class]->addSite(common_config('site', 'server')); @@ -118,34 +116,25 @@ class IoMaster $manager = $this->getManager($class); - $caps = $manager->multiSite(); if ($this->multiSite) { + $caps = $manager->multiSite(); if ($caps == IoManager::SINGLE_ONLY) { throw new Exception("$class can't run with --all; aborting."); } - if ($caps == IoManager::INSTANCE_PER_PROCESS || - ( $this->includeGlobalSingletons && $caps == IoManager::GLOBAL_SINGLE_ONLY )) { + if ($caps == IoManager::INSTANCE_PER_PROCESS) { // Save this guy for later! // We'll only need the one to cover multiple sites. - if (is_string($class)){ - $this->singletons[$class] = $manager; - } + $this->singletons[$class] = $manager; $manager->addSite(common_config('site', 'server')); } } - if( $this->includeGlobalSingletons || $caps != IoManager::GLOBAL_SINGLE_ONLY ) { - $this->managers[] = $manager; - } + $this->managers[] = $manager; } protected function getManager($class) { - if(is_object($class)){ - return $class; - }else{ - return call_user_func(array($class, 'get')); - } + return call_user_func(array($class, 'get')); } /** diff --git a/lib/queuemanager.php b/lib/queuemanager.php index b20a934687..291174d3c4 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -119,9 +119,7 @@ abstract class QueueManager extends IoManager { if (isset($this->handlers[$queue])) { $class = $this->handlers[$queue]; - if(is_object($class)) { - return $class; - } else if (class_exists($class)) { + if (class_exists($class)) { return new $class(); } else { common_log(LOG_ERR, "Nonexistent handler class '$class' for queue '$queue'"); @@ -184,7 +182,7 @@ abstract class QueueManager extends IoManager * Only registered transports will be reliably picked up! * * @param string $transport - * @param string $class class name or object instance + * @param string $class */ public function connect($transport, $class) { diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index 6cce4eaa75..162f617e0d 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -122,7 +122,7 @@ class QueueDaemon extends Daemon if ($this->threads > 1) { return $this->runThreads(); } else { - return $this->runLoop(true); + return $this->runLoop(); } } @@ -176,8 +176,7 @@ class QueueDaemon extends Daemon { $this->set_id($this->get_id() . "." . $thread); $this->resetDb(); - //only include global singletons on the first thread - $this->runLoop($thread == 1); + $this->runLoop(); } /** @@ -214,18 +213,14 @@ class QueueDaemon extends Daemon * * Most of the time this won't need to be overridden in a subclass. * - * @param boolean $includeGlobalSingletons Include IoManagers that are - * global singletons (should only be one instance - regardless of how - * many processes or sites there are) - * * @return boolean true on success, false on failure */ - function runLoop($includeGlobalSingletons) + function runLoop() { $this->log(LOG_INFO, 'checking for queued notices'); $master = new IoMaster($this->get_id()); - $master->init($this->all, $includeGlobalSingletons); + $master->init($this->all); $master->service(); $this->log(LOG_INFO, 'finished servicing the queue'); From 78eb9c78a781ba8d6929a260e5f9c07714d59ee3 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 22 Jan 2010 17:19:32 -0500 Subject: [PATCH 004/655] Will re-enable anything queueing after 0.9.x merge Revert "Any object (not just Notice's) can be queued" This reverts commit 77ea02cac3576f395e4548e7e6cbace90ba566ea. --- classes/Queue_item.php | 13 ++- classes/statusnet.ini | 6 +- db/statusnet.sql | 5 +- lib/dbqueuemanager.php | 95 +++++++++++++------ lib/jabberqueuehandler.php | 4 +- lib/ombqueuehandler.php | 2 +- lib/pingqueuehandler.php | 2 +- lib/pluginqueuehandler.php | 2 +- lib/publicqueuehandler.php | 6 +- lib/queuehandler.php | 95 +++++++++++++++++-- lib/smsqueuehandler.php | 2 +- lib/stompqueuemanager.php | 38 +++++--- lib/xmppmanager.php | 24 ----- plugins/Enjit/enjitqueuehandler.php | 9 +- plugins/Facebook/facebookqueuehandler.php | 2 +- plugins/RSSCloud/RSSCloudPlugin.php | 41 ++++++-- plugins/RSSCloud/RSSCloudQueueHandler.php | 50 +++++++++- plugins/TwitterBridge/twitterqueuehandler.php | 2 +- scripts/handlequeued.php | 2 +- 19 files changed, 291 insertions(+), 109 deletions(-) mode change 100644 => 100755 plugins/RSSCloud/RSSCloudQueueHandler.php diff --git a/classes/Queue_item.php b/classes/Queue_item.php index 4d90e1d231..cf805a6060 100644 --- a/classes/Queue_item.php +++ b/classes/Queue_item.php @@ -10,8 +10,7 @@ class Queue_item extends Memcached_DataObject /* the code below is auto generated do not remove the above tag */ public $__table = 'queue_item'; // table name - public $id; // int(4) primary_key not_null - public $frame; // blob not_null + public $notice_id; // int(4) primary_key not_null public $transport; // varchar(8) primary_key not_null public $created; // datetime() not_null public $claimed; // datetime() @@ -23,6 +22,9 @@ class Queue_item extends Memcached_DataObject /* the code above is auto generated do not remove the tag below */ ###END_AUTOCODE + function sequenceKey() + { return array(false, false); } + static function top($transport=null) { $qi = new Queue_item(); @@ -40,7 +42,7 @@ class Queue_item extends Memcached_DataObject # XXX: potential race condition # can we force it to only update if claimed is still null # (or old)? - common_log(LOG_INFO, 'claiming queue item id=' . $qi->id . + common_log(LOG_INFO, 'claiming queue item = ' . $qi->notice_id . ' for transport ' . $qi->transport); $orig = clone($qi); $qi->claimed = common_sql_now(); @@ -55,4 +57,9 @@ class Queue_item extends Memcached_DataObject $qi = null; return null; } + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('Queue_item', $kv); + } } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 6203650a69..44088cf6b0 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -428,14 +428,14 @@ tagged = K tag = K [queue_item] -id = 129 -frame = 66 +notice_id = 129 transport = 130 created = 142 claimed = 14 [queue_item__keys] -id = K +notice_id = K +transport = K [related_group] group_id = 129 diff --git a/db/statusnet.sql b/db/statusnet.sql index cb7dad3e24..2a9ab74c77 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -274,12 +274,13 @@ create table remember_me ( ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table queue_item ( - id integer auto_increment primary key comment 'unique identifier', - frame blob not null comment 'serialized object', + + notice_id integer not null comment 'notice queued' references notice (id), transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', created datetime not null comment 'date this record was created', claimed datetime comment 'date this item was claimed', + constraint primary key (notice_id, transport), index queue_item_created_idx (created) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/lib/dbqueuemanager.php b/lib/dbqueuemanager.php index 139f502340..889365b649 100644 --- a/lib/dbqueuemanager.php +++ b/lib/dbqueuemanager.php @@ -31,17 +31,19 @@ class DBQueueManager extends QueueManager { /** - * Saves an object into the queue item table. + * Saves a notice object reference into the queue item table. * @return boolean true on success * @throws ServerException on failure */ public function enqueue($object, $queue) { + $notice = $object; + $qi = new Queue_item(); - $qi->frame = serialize($object); + $qi->notice_id = $notice->id; $qi->transport = $queue; - $qi->created = common_sql_now(); + $qi->created = $notice->created; $result = $qi->insert(); if (!$result) { @@ -71,35 +73,34 @@ class DBQueueManager extends QueueManager */ public function poll() { - $this->_log(LOG_DEBUG, 'Checking for queued objects...'); - $qi = $this->_nextItem(); - if ($qi === false) { - $this->_log(LOG_DEBUG, 'No queue items waiting; idling.'); + $this->_log(LOG_DEBUG, 'Checking for notices...'); + $item = $this->_nextItem(); + if ($item === false) { + $this->_log(LOG_DEBUG, 'No notices waiting; idling.'); return false; } - if ($qi === true) { - // We dequeued an entry for a deleted or invalid object. + if ($item === true) { + // We dequeued an entry for a deleted or invalid notice. // Consider it a hit for poll rate purposes. return true; } - $queue = $qi->transport; - $object = unserialize($qi->frame); - $this->_log(LOG_INFO, 'Got item id=' . $qi->id . ' for transport ' . $queue); + list($queue, $notice) = $item; + $this->_log(LOG_INFO, 'Got notice '. $notice->id . ' for transport ' . $queue); // Yay! Got one! $handler = $this->getHandler($queue); if ($handler) { - if ($handler->handle($object)) { - $this->_log(LOG_INFO, "[$queue] Successfully handled object"); - $this->_done($qi); + if ($handler->handle_notice($notice)) { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Successfully handled notice"); + $this->_done($notice, $queue); } else { - $this->_log(LOG_INFO, "[$queue] Failed to handle object"); - $this->_fail($qi); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Failed to handle notice"); + $this->_fail($notice, $queue); } } else { - $this->_log(LOG_INFO, "[$queue] No handler for queue $queue; discarding."); - $this->_done($qi); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] No handler for queue $queue; discarding."); + $this->_done($notice, $queue); } return true; } @@ -107,7 +108,8 @@ class DBQueueManager extends QueueManager /** * Pop the oldest unclaimed item off the queue set and claim it. * - * @return mixed false if no items; true if bogus hit; otherwise Queue_item + * @return mixed false if no items; true if bogus hit; otherwise array(string, Notice) + * giving the queue transport name. */ protected function _nextItem() { @@ -119,42 +121,70 @@ class DBQueueManager extends QueueManager return false; } - return $qi; + $queue = $qi->transport; + $notice = Notice::staticGet('id', $qi->notice_id); + if (empty($notice)) { + $this->_log(LOG_INFO, "[$queue:notice $notice->id] dequeued non-existent notice"); + $qi->delete(); + return true; + } + + $result = $notice; + return array($queue, $notice); } /** * Delete our claimed item from the queue after successful processing. * - * @param QueueItem $qi + * @param Notice $object + * @param string $queue */ - protected function _done($qi) + protected function _done($object, $queue) { + // XXX: right now, we only handle notices + + $notice = $object; + + $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, + 'transport' => $queue)); + if (empty($qi)) { - $this->_log(LOG_INFO, "_done passed an empty queue item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "Reluctantly releasing unclaimed queue item"); + $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Reluctantly releasing unclaimed queue item"); } $qi->delete(); $qi->free(); } - $this->_log(LOG_INFO, "done with item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with item"); + $this->stats('handled', $queue); + + $notice->free(); } /** * Free our claimed queue item for later reprocessing in case of * temporary failure. * - * @param QueueItem $qi + * @param Notice $object + * @param string $queue */ - protected function _fail($qi) + protected function _fail($object, $queue) { + // XXX: right now, we only handle notices + + $notice = $object; + + $qi = Queue_item::pkeyGet(array('notice_id' => $notice->id, + 'transport' => $queue)); + if (empty($qi)) { - $this->_log(LOG_INFO, "_fail passed an empty queue item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] Cannot find queue item"); } else { if (empty($qi->claimed)) { - $this->_log(LOG_WARNING, "Ignoring failure for unclaimed queue item"); + $this->_log(LOG_WARNING, "[$queue:notice $notice->id] Ignoring failure for unclaimed queue item"); } else { $orig = clone($qi); $qi->claimed = null; @@ -163,7 +193,10 @@ class DBQueueManager extends QueueManager } } - $this->_log(LOG_INFO, "done with queue item"); + $this->_log(LOG_INFO, "[$queue:notice $notice->id] done with queue item"); + $this->stats('error', $queue); + + $notice->free(); } protected function _log($level, $msg) diff --git a/lib/jabberqueuehandler.php b/lib/jabberqueuehandler.php index 83471f2df7..b1518866d7 100644 --- a/lib/jabberqueuehandler.php +++ b/lib/jabberqueuehandler.php @@ -34,14 +34,14 @@ class JabberQueueHandler extends QueueHandler return 'jabber'; } - function handle($notice) + function handle_notice($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_broadcast_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; + exit(1); } } } diff --git a/lib/ombqueuehandler.php b/lib/ombqueuehandler.php index 24896c784c..3ffc1313bc 100644 --- a/lib/ombqueuehandler.php +++ b/lib/ombqueuehandler.php @@ -36,7 +36,7 @@ class OmbQueueHandler extends QueueHandler * @fixme doesn't currently report failure back to the queue manager * because omb_broadcast_notice() doesn't report it to us */ - function handle($notice) + function handle_notice($notice) { if ($this->is_remote($notice)) { $this->log(LOG_DEBUG, 'Ignoring remote notice ' . $notice->id); diff --git a/lib/pingqueuehandler.php b/lib/pingqueuehandler.php index 4e4d74cb1a..8bb2180786 100644 --- a/lib/pingqueuehandler.php +++ b/lib/pingqueuehandler.php @@ -30,7 +30,7 @@ class PingQueueHandler extends QueueHandler { return 'ping'; } - function handle($notice) { + function handle_notice($notice) { require_once INSTALLDIR . '/lib/ping.php'; return ping_broadcast_notice($notice); } diff --git a/lib/pluginqueuehandler.php b/lib/pluginqueuehandler.php index 9653ccad42..24d5046997 100644 --- a/lib/pluginqueuehandler.php +++ b/lib/pluginqueuehandler.php @@ -42,7 +42,7 @@ class PluginQueueHandler extends QueueHandler return 'plugin'; } - function handle($notice) + function handle_notice($notice) { Event::handle('HandleQueuedNotice', array(&$notice)); return true; diff --git a/lib/publicqueuehandler.php b/lib/publicqueuehandler.php index c9edb8d5d7..9ea9ee73a3 100644 --- a/lib/publicqueuehandler.php +++ b/lib/publicqueuehandler.php @@ -23,6 +23,7 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { /** * Queue handler for pushing new notices to public XMPP subscribers. + * @fixme correct this exception handling */ class PublicQueueHandler extends QueueHandler { @@ -32,14 +33,15 @@ class PublicQueueHandler extends QueueHandler return 'public'; } - function handle($notice) + function handle_notice($notice) { require_once(INSTALLDIR.'/lib/jabber.php'); try { return jabber_public_notice($notice); } catch (XMPPHP_Exception $e) { $this->log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); - return false; + die($e->getMessage()); } + return true; } } diff --git a/lib/queuehandler.php b/lib/queuehandler.php index 2909cd83b1..613be6e330 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -22,20 +22,51 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } /** * Base class for queue handlers. * - * As of 0.9, queue handlers are short-lived for items as they are - * dequeued by a QueueManager running in an IoMaster in a daemon - * such as queuedaemon.php. - * - * Extensions requiring long-running maintenance or polling should - * register an IoManager. + * As extensions of the Daemon class, each queue handler has the ability + * to launch itself in the background, at which point it'll pass control + * to the configured QueueManager class to poll for updates. * * Subclasses must override at least the following methods: * - transport - * - handle + * - handle_notice */ +#class QueueHandler extends Daemon class QueueHandler { +# function __construct($id=null, $daemonize=true) +# { +# parent::__construct($daemonize); +# +# if ($id) { +# $this->set_id($id); +# } +# } + + /** + * How many seconds a polling-based queue manager should wait between + * checks for new items to handle. + * + * Defaults to 60 seconds; override to speed up or slow down. + * + * @fixme not really compatible with global queue manager + * @return int timeout in seconds + */ +# function timeout() +# { +# return 60; +# } + +# function class_name() +# { +# return ucfirst($this->transport()) . 'Handler'; +# } + +# function name() +# { +# return strtolower($this->class_name().'.'.$this->get_id()); +# } + /** * Return transport keyword which identifies items this queue handler * services; must be defined for all subclasses. @@ -52,17 +83,61 @@ class QueueHandler /** * Here's the meat of your queue handler -- you're handed a Notice - * or other object, which you may do as you will with. + * object, which you may do as you will with. * * If this function indicates failure, a warning will be logged * and the item is placed back in the queue to be re-run. * - * @param mixed $object + * @param Notice $notice * @return boolean true on success, false on failure */ - function handle($object) + function handle_notice($notice) { return true; } + + /** + * Setup and start of run loop for this queue handler as a daemon. + * Most of the heavy lifting is passed on to the QueueManager's service() + * method, which passes control back to our handle_notice() method for + * each notice that comes in on the queue. + * + * Most of the time this won't need to be overridden in a subclass. + * + * @return boolean true on success, false on failure + */ + function run() + { + if (!$this->start()) { + $this->log(LOG_WARNING, 'failed to start'); + return false; + } + + $this->log(LOG_INFO, 'checking for queued notices'); + + $queue = $this->transport(); + $timeout = $this->timeout(); + + $qm = QueueManager::get(); + + $qm->service($queue, $this); + + $this->log(LOG_INFO, 'finished servicing the queue'); + + if (!$this->finish()) { + $this->log(LOG_WARNING, 'failed to clean up'); + return false; + } + + $this->log(LOG_INFO, 'terminating normally'); + + return true; + } + + + function log($level, $msg) + { + common_log($level, $this->class_name() . ' ('. $this->get_id() .'): '.$msg); + } } diff --git a/lib/smsqueuehandler.php b/lib/smsqueuehandler.php index 6085d2b4ac..48a96409d0 100644 --- a/lib/smsqueuehandler.php +++ b/lib/smsqueuehandler.php @@ -31,7 +31,7 @@ class SmsQueueHandler extends QueueHandler return 'sms'; } - function handle($notice) + function handle_notice($notice) { require_once(INSTALLDIR.'/lib/mail.php'); return mail_broadcast_notice_sms($notice); diff --git a/lib/stompqueuemanager.php b/lib/stompqueuemanager.php index 6496b5cf17..00590fdb69 100644 --- a/lib/stompqueuemanager.php +++ b/lib/stompqueuemanager.php @@ -125,25 +125,28 @@ class StompQueueManager extends QueueManager } /** - * Saves an object into the queue item table. + * Saves a notice object reference into the queue item table. * @return boolean true on success */ public function enqueue($object, $queue) { - $msg = serialize($object); + $notice = $object; $this->_connect(); + // XXX: serialize and send entire notice + $result = $this->con->send($this->queueName($queue), - $msg, // BODY of the message - array ('created' => $timestamp)); + $notice->id, // BODY of the message + array ('created' => $notice->created)); if (!$result) { common_log(LOG_ERR, 'Error sending to '.$queue.' queue'); return false; } - common_log(LOG_DEBUG, "complete remote queueing $log for $queue"); + common_log(LOG_DEBUG, 'complete remote queueing notice ID = ' + . $notice->id . ' for ' . $queue); $this->stats('enqueued', $queue); } @@ -171,7 +174,7 @@ class StompQueueManager extends QueueManager $ok = true; $frames = $this->con->readFrames(); foreach ($frames as $frame) { - $ok = $ok && $this->_handleItem($frame); + $ok = $ok && $this->_handleNotice($frame); } return $ok; } @@ -262,10 +265,10 @@ class StompQueueManager extends QueueManager } /** - * Handle and acknowledge an event that's come in through a queue. + * Handle and acknowledge a notice event that's come in through a queue. * * If the queue handler reports failure, the message is requeued for later. - * Missing objects or handler classes will drop the message. + * Missing notices or handler classes will drop the message. * * Side effects: in multi-site mode, may reset site configuration to * match the site that queued the event. @@ -273,15 +276,24 @@ class StompQueueManager extends QueueManager * @param StompFrame $frame * @return bool */ - protected function _handleItem($frame) + protected function _handleNotice($frame) { list($site, $queue) = $this->parseDestination($frame->headers['destination']); if ($site != common_config('site', 'server')) { $this->stats('switch'); StatusNet::init($site); } - $info = "object posted at {$frame->headers['created']} in queue $queue"; - $item = unserialize($frame->body); + + $id = intval($frame->body); + $info = "notice $id posted at {$frame->headers['created']} in queue $queue"; + + $notice = Notice::staticGet('id', $id); + if (empty($notice)) { + $this->_log(LOG_WARNING, "Skipping missing $info"); + $this->con->ack($frame); + $this->stats('badnotice', $queue); + return false; + } $handler = $this->getHandler($queue); if (!$handler) { @@ -291,7 +303,7 @@ class StompQueueManager extends QueueManager return false; } - $ok = $handler->handle($item); + $ok = $handler->handle_notice($notice); if (!$ok) { $this->_log(LOG_WARNING, "Failed handling $info"); @@ -299,7 +311,7 @@ class StompQueueManager extends QueueManager // this kind of queue management ourselves; // if we don't ack, it should resend... $this->con->ack($frame); - $this->enqueue($item, $queue); + $this->enqueue($notice, $queue); $this->stats('requeued', $queue); return false; } diff --git a/lib/xmppmanager.php b/lib/xmppmanager.php index c499868548..dfff63a30c 100644 --- a/lib/xmppmanager.php +++ b/lib/xmppmanager.php @@ -175,30 +175,6 @@ class XmppManager extends IoManager } } - /** - * 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) { - return true; - } else { - return false; - } - } else { - // Can't send right now... - return false; - } - } - /** * Send a keepalive ping to the XMPP server. */ diff --git a/plugins/Enjit/enjitqueuehandler.php b/plugins/Enjit/enjitqueuehandler.php index 14085cc5e3..f0e706b929 100644 --- a/plugins/Enjit/enjitqueuehandler.php +++ b/plugins/Enjit/enjitqueuehandler.php @@ -32,7 +32,14 @@ class EnjitQueueHandler extends QueueHandler return 'enjit'; } - function handle($notice) + function start() + { + $this->log(LOG_INFO, "Starting EnjitQueueHandler"); + $this->log(LOG_INFO, "Broadcasting to ".common_config('enjit', 'apiurl')); + return true; + } + + function handle_notice($notice) { $profile = Profile::staticGet($notice->profile_id); diff --git a/plugins/Facebook/facebookqueuehandler.php b/plugins/Facebook/facebookqueuehandler.php index 524af7bc45..1778690e5b 100644 --- a/plugins/Facebook/facebookqueuehandler.php +++ b/plugins/Facebook/facebookqueuehandler.php @@ -28,7 +28,7 @@ class FacebookQueueHandler extends QueueHandler return 'facebook'; } - function handle($notice) + function handle_notice($notice) { if ($this->_isLocal($notice)) { return facebookBroadcastNotice($notice); diff --git a/plugins/RSSCloud/RSSCloudPlugin.php b/plugins/RSSCloud/RSSCloudPlugin.php index 9f444c8bba..2de162628f 100644 --- a/plugins/RSSCloud/RSSCloudPlugin.php +++ b/plugins/RSSCloud/RSSCloudPlugin.php @@ -138,9 +138,6 @@ class RSSCloudPlugin extends Plugin case 'RSSCloudNotifier': include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudNotifier.php'; return false; - case 'RSSCloudQueueHandler': - include_once INSTALLDIR . '/plugins/RSSCloud/RSSCloudQueueHandler.php'; - return false; case 'RSSCloudRequestNotifyAction': case 'LoggingAggregatorAction': include_once INSTALLDIR . '/plugins/RSSCloud/' . @@ -196,6 +193,32 @@ class RSSCloudPlugin extends Plugin return true; } + /** + * broadcast the message when not using queuehandler + * + * @param Notice &$notice the notice + * @param array $queue destination queue + * + * @return boolean hook return + */ + + function onUnqueueHandleNotice(&$notice, $queue) + { + if (($queue == 'rsscloud') && ($this->_isLocal($notice))) { + + common_debug('broadcasting rssCloud bound notice ' . $notice->id); + + $profile = $notice->getProfile(); + + $notifier = new RSSCloudNotifier(); + $notifier->notify($profile); + + return false; + } + + return true; + } + /** * Determine whether the notice was locally created * @@ -238,15 +261,19 @@ class RSSCloudPlugin extends Plugin } /** - * Register RSSCloud notice queue handler + * Add RSSCloudQueueHandler to the list of valid daemons to + * start * - * @param QueueManager $manager + * @param array $daemons the list of daemons to run * * @return boolean hook return + * */ - function onEndInitializeQueueManager($manager) + + function onGetValidDaemons($daemons) { - $manager->connect('rsscloud', 'RSSCloudQueueHandler'); + array_push($daemons, INSTALLDIR . + '/plugins/RSSCloud/RSSCloudQueueHandler.php'); return true; } diff --git a/plugins/RSSCloud/RSSCloudQueueHandler.php b/plugins/RSSCloud/RSSCloudQueueHandler.php old mode 100644 new mode 100755 index 295c261895..693dd27c1f --- a/plugins/RSSCloud/RSSCloudQueueHandler.php +++ b/plugins/RSSCloud/RSSCloudQueueHandler.php @@ -1,3 +1,4 @@ +#!/usr/bin/env php . */ -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } +define('INSTALLDIR', realpath(dirname(__FILE__) . '/../..')); + +$shortoptions = 'i::'; +$longoptions = array('id::'); + +$helptext = <<log(LOG_INFO, "INITIALIZE"); + $this->notifier = new RSSCloudNotifier(); + return true; + } + + function handle_notice($notice) { $profile = $notice->getProfile(); - $notifier = new RSSCloudNotifier(); - return $notifier->notify($profile); + return $this->notifier->notify($profile); } + + function finish() + { + } + } +if (have_option('i')) { + $id = get_option_value('i'); +} else if (have_option('--id')) { + $id = get_option_value('--id'); +} else if (count($args) > 0) { + $id = $args[0]; +} else { + $id = null; +} + +$handler = new RSSCloudQueueHandler($id); + +$handler->runOnce(); diff --git a/plugins/TwitterBridge/twitterqueuehandler.php b/plugins/TwitterBridge/twitterqueuehandler.php index b5a624e83d..5089ca7b74 100644 --- a/plugins/TwitterBridge/twitterqueuehandler.php +++ b/plugins/TwitterBridge/twitterqueuehandler.php @@ -28,7 +28,7 @@ class TwitterQueueHandler extends QueueHandler return 'twitter'; } - function handle($notice) + function handle_notice($notice) { return broadcast_twitter($notice); } diff --git a/scripts/handlequeued.php b/scripts/handlequeued.php index 8158849695..9031437aac 100755 --- a/scripts/handlequeued.php +++ b/scripts/handlequeued.php @@ -50,7 +50,7 @@ if (empty($notice)) { exit(1); } -if (!$handler->handle($notice)) { +if (!$handler->handle_notice($notice)) { print "Failed to handle notice id $noticeId on queue '$queue'.\n"; exit(1); } From b34bbb0e8045008a0048829672af905385241735 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Fri, 22 Jan 2010 17:55:26 -0500 Subject: [PATCH 005/655] Store serialized representations of queue items in the queue --- lib/queuemanager.php | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/lib/queuemanager.php b/lib/queuemanager.php index 4eb39bfa8c..8921b02cc8 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -139,20 +139,13 @@ abstract class QueueManager extends IoManager /** * Encode an object for queued storage. - * Next gen may use serialization. * * @param mixed $object * @return string */ protected function encode($object) { - if ($object instanceof Notice) { - return $object->id; - } else if (is_string($object)) { - return $object; - } else { - throw new ServerException("Can't queue this type", 500); - } + return serialize($object); } /** @@ -164,11 +157,7 @@ abstract class QueueManager extends IoManager */ protected function decode($frame) { - if (is_numeric($frame)) { - return Notice::staticGet(intval($frame)); - } else { - return $frame; - } + return unserialize($frame); } /** From e9995b0f6ab0788162e22c996e5ee0af416dd519 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Sat, 23 Jan 2010 01:25:27 -0500 Subject: [PATCH 006/655] Create IM plugin, Pluginize XMPP, Create AIM plugin --- EVENTS.txt | 18 + actions/apiaccountupdatedeliverydevice.php | 10 +- actions/confirmaddress.php | 87 +- actions/imsettings.php | 319 +-- actions/shownotice.php | 6 - actions/showstream.php | 6 - classes/User.php | 7 +- classes/User_im_prefs.php | 71 + classes/statusnet.ini | 22 +- db/statusnet.sql | 28 +- lib/channel.php | 57 - lib/command.php | 4 +- lib/imchannel.php | 104 + lib/immanager.php | 70 + lib/implugin.php | 612 +++++ ...berqueuehandler.php => imqueuehandler.php} | 33 +- ...handler.php => imreceiverqueuehandler.php} | 29 +- lib/imsenderqueuehandler.php | 44 + lib/jabber.php | 473 ---- lib/queuehandler.php | 14 - lib/queuemanager.php | 14 +- lib/queuemonitor.php | 2 +- lib/util.php | 9 - lib/xmppmanager.php | 485 ---- lib/xmppoutqueuehandler.php | 55 - plugins/Aim/AimPlugin.php | 162 ++ plugins/Aim/Fake_Aim.php | 43 + plugins/Aim/README | 27 + plugins/Aim/aimmanager.php | 100 + plugins/Aim/extlib/phptoclib/README.txt | 169 ++ plugins/Aim/extlib/phptoclib/aimclassw.php | 2370 +++++++++++++++++ plugins/Aim/extlib/phptoclib/dconnection.php | 229 ++ plugins/Imap/imapmanager.php | 8 +- .../Xmpp/Fake_XMPP.php | 37 +- plugins/Xmpp/README | 35 + plugins/Xmpp/Sharing_XMPP.php | 43 + plugins/Xmpp/XmppPlugin.php | 247 ++ plugins/Xmpp/xmppmanager.php | 279 ++ scripts/getvaliddaemons.php | 4 +- scripts/{xmppdaemon.php => imdaemon.php} | 39 +- scripts/queuedaemon.php | 9 +- scripts/stopdaemons.sh | 4 +- 42 files changed, 4968 insertions(+), 1416 deletions(-) create mode 100644 classes/User_im_prefs.php create mode 100644 lib/imchannel.php create mode 100644 lib/immanager.php create mode 100644 lib/implugin.php rename lib/{jabberqueuehandler.php => imqueuehandler.php} (60%) rename lib/{publicqueuehandler.php => imreceiverqueuehandler.php} (60%) create mode 100644 lib/imsenderqueuehandler.php delete mode 100644 lib/jabber.php delete mode 100644 lib/xmppmanager.php delete mode 100644 lib/xmppoutqueuehandler.php create mode 100644 plugins/Aim/AimPlugin.php create mode 100644 plugins/Aim/Fake_Aim.php create mode 100644 plugins/Aim/README create mode 100644 plugins/Aim/aimmanager.php create mode 100755 plugins/Aim/extlib/phptoclib/README.txt create mode 100755 plugins/Aim/extlib/phptoclib/aimclassw.php create mode 100755 plugins/Aim/extlib/phptoclib/dconnection.php rename lib/queued_xmpp.php => plugins/Xmpp/Fake_XMPP.php (68%) create mode 100644 plugins/Xmpp/README create mode 100644 plugins/Xmpp/Sharing_XMPP.php create mode 100644 plugins/Xmpp/XmppPlugin.php create mode 100644 plugins/Xmpp/xmppmanager.php rename scripts/{xmppdaemon.php => imdaemon.php} (66%) diff --git a/EVENTS.txt b/EVENTS.txt index 1ed670697b..45f1e9d70f 100644 --- a/EVENTS.txt +++ b/EVENTS.txt @@ -699,3 +699,21 @@ StartShowContentLicense: Showing the default license for content EndShowContentLicense: Showing the default license for content - $action: the current action + +GetImTransports: Get IM transports that are available +- &$transports: append your transport to this array like so: $transports[transportName]=array('display'=>display) + +NormalizeImScreenname: Normalize an IM screenname +- $transport: transport the screenname is on +- &$screenname: screenname to be normalized + +ValidateImScreenname: Validate an IM screenname +- $transport: transport the screenname is on +- $screenname: screenname to be validated +- $valid: is the screenname valid? + +SendImConfirmationCode: Send a confirmation code to confirm a user owns an IM screenname +- $transport: transport the screenname exists on +- $screenname: screenname being confirmed +- $code: confirmation code for confirmation URL +- $user: user requesting the confirmation diff --git a/actions/apiaccountupdatedeliverydevice.php b/actions/apiaccountupdatedeliverydevice.php index 684906fe90..4bd6c326f8 100644 --- a/actions/apiaccountupdatedeliverydevice.php +++ b/actions/apiaccountupdatedeliverydevice.php @@ -119,10 +119,16 @@ class ApiAccountUpdateDeliveryDeviceAction extends ApiAuthAction if (strtolower($this->device) == 'sms') { $this->user->smsnotify = true; } elseif (strtolower($this->device) == 'im') { - $this->user->jabbernotify = true; + //TODO IM is pluginized now, so what should we do? + //Enable notifications for all IM plugins? + //For now, don't do anything + //$this->user->jabbernotify = true; } elseif (strtolower($this->device == 'none')) { $this->user->smsnotify = false; - $this->user->jabbernotify = false; + //TODO IM is pluginized now, so what should we do? + //Disable notifications for all IM plugins? + //For now, don't do anything + //$this->user->jabbernotify = false; } $result = $this->user->update($original); diff --git a/actions/confirmaddress.php b/actions/confirmaddress.php index cc8351d8dc..eaf1c91c1a 100644 --- a/actions/confirmaddress.php +++ b/actions/confirmaddress.php @@ -49,7 +49,7 @@ class ConfirmaddressAction extends Action { /** type of confirmation. */ - var $type = null; + var $address; /** * Accept a confirmation code @@ -86,37 +86,75 @@ class ConfirmaddressAction extends Action return; } $type = $confirm->address_type; - if (!in_array($type, array('email', 'jabber', 'sms'))) { + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if (!in_array($type, array('email', 'sms')) && !in_array($type, array_keys($transports))) { $this->serverError(sprintf(_('Unrecognized address type %s'), $type)); return; } - if ($cur->$type == $confirm->address) { - $this->clientError(_('That address has already been confirmed.')); - return; - } - + $this->address = $confirm->address; $cur->query('BEGIN'); + if (in_array($type, array('email', 'sms'))) + { + if ($cur->$type == $confirm->address) { + $this->clientError(_('That address has already been confirmed.')); + return; + } - $orig_user = clone($cur); + $orig_user = clone($cur); - $cur->$type = $confirm->address; + $cur->$type = $confirm->address; - if ($type == 'sms') { - $cur->carrier = ($confirm->address_extra)+0; - $carrier = Sms_carrier::staticGet($cur->carrier); - $cur->smsemail = $carrier->toEmailAddress($cur->sms); - } + if ($type == 'sms') { + $cur->carrier = ($confirm->address_extra)+0; + $carrier = Sms_carrier::staticGet($cur->carrier); + $cur->smsemail = $carrier->toEmailAddress($cur->sms); + } - $result = $cur->updateKeys($orig_user); + $result = $cur->updateKeys($orig_user); - if (!$result) { - common_log_db_error($cur, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t update user.')); - return; - } + if (!$result) { + common_log_db_error($cur, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update user.')); + return; + } + + if ($type == 'email') { + $cur->emailChanged(); + } + + } else { + + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $confirm->address_type; + $user_im_prefs->user_id = $cur->id; + if ($user_im_prefs->find() && $user_im_prefs->fetch()) { + if($user_im_prefs->screenname == $confirm->address){ + $this->clientError(_('That address has already been confirmed.')); + return; + } + $user_im_prefs->screenname = $confirm->address; + $result = $user_im_prefs->update(); + + if (!$result) { + common_log_db_error($user_im_prefs, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update user im preferences.')); + return; + } + }else{ + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->screenname = $confirm->address; + $user_im_prefs->transport = $confirm->address_type; + $user_im_prefs->user_id = $cur->id; + $result = $user_im_prefs->insert(); + + if (!$result) { + common_log_db_error($user_im_prefs, 'INSERT', __FILE__); + $this->serverError(_('Couldn\'t insert user im preferences.')); + return; + } + } - if ($type == 'email') { - $cur->emailChanged(); } $result = $confirm->delete(); @@ -128,8 +166,6 @@ class ConfirmaddressAction extends Action } $cur->query('COMMIT'); - - $this->type = $type; $this->showPage(); } @@ -153,11 +189,10 @@ class ConfirmaddressAction extends Action function showContent() { $cur = common_current_user(); - $type = $this->type; $this->element('p', null, sprintf(_('The address "%s" has been '. 'confirmed for your account.'), - $cur->$type)); + $this->address)); } } diff --git a/actions/imsettings.php b/actions/imsettings.php index af4915843d..fe1864f0d1 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -31,9 +31,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR.'/lib/connectsettingsaction.php'; -require_once INSTALLDIR.'/lib/jabber.php'; - /** * Settings for Jabber/XMPP integration * @@ -68,8 +65,8 @@ class ImsettingsAction extends ConnectSettingsAction function getInstructions() { return _('You can send and receive notices through '. - 'Jabber/GTalk [instant messages](%%doc.im%%). '. - 'Configure your address and settings below.'); + 'instant messaging [instant messages](%%doc.im%%). '. + 'Configure your addresses and settings below.'); } /** @@ -84,85 +81,108 @@ class ImsettingsAction extends ConnectSettingsAction function showContent() { - if (!common_config('xmpp', 'enabled')) { + $transports = array(); + Event::handle('GetImTransports', array(&$transports)); + if (! $transports) { $this->element('div', array('class' => 'error'), _('IM is not available.')); return; } $user = common_current_user(); - $this->elementStart('form', array('method' => 'post', - 'id' => 'form_settings_im', - 'class' => 'form_settings', - 'action' => - common_local_url('imsettings'))); - $this->elementStart('fieldset', array('id' => 'settings_im_address')); - $this->element('legend', null, _('Address')); - $this->hidden('token', common_session_token()); - if ($user->jabber) { - $this->element('p', 'form_confirmed', $user->jabber); - $this->element('p', 'form_note', - _('Current confirmed Jabber/GTalk address.')); - $this->hidden('jabber', $user->jabber); - $this->submit('remove', _('Remove')); - } else { - $confirm = $this->getConfirmation(); - if ($confirm) { - $this->element('p', 'form_unconfirmed', $confirm->address); - $this->element('p', 'form_note', - sprintf(_('Awaiting confirmation on this address. '. - 'Check your Jabber/GTalk account for a '. - 'message with further instructions. '. - '(Did you add %s to your buddy list?)'), - jabber_daemon_address())); - $this->hidden('jabber', $confirm->address); - $this->submit('cancel', _('Cancel')); - } else { - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->input('jabber', _('IM address'), - ($this->arg('jabber')) ? $this->arg('jabber') : null, - sprintf(_('Jabber or GTalk address, '. - 'like "UserName@example.org". '. - 'First, make sure to add %s to your '. - 'buddy list in your IM client or on GTalk.'), - jabber_daemon_address())); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->submit('add', _('Add')); - } - } - $this->elementEnd('fieldset'); + $user_im_prefs_by_transport = array(); - $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); - $this->element('legend', null, _('Preferences')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->checkbox('jabbernotify', - _('Send me notices through Jabber/GTalk.'), - $user->jabbernotify); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('updatefrompresence', - _('Post a notice when my Jabber/GTalk status changes.'), - $user->updatefrompresence); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('jabberreplies', - _('Send me replies through Jabber/GTalk '. - 'from people I\'m not subscribed to.'), - $user->jabberreplies); - $this->elementEnd('li'); - $this->elementStart('li'); - $this->checkbox('jabbermicroid', - _('Publish a MicroID for my Jabber/GTalk address.'), - $user->jabbermicroid); - $this->elementEnd('li'); - $this->elementEnd('ul'); - $this->submit('save', _('Save')); - $this->elementEnd('fieldset'); - $this->elementEnd('form'); + foreach($transports as $transport=>$transport_info) + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_im', + 'class' => 'form_settings', + 'action' => + common_local_url('imsettings'))); + $this->elementStart('fieldset', array('id' => 'settings_im_address')); + $this->element('legend', null, $transport_info['display']); + $this->hidden('token', common_session_token()); + $this->hidden('transport', $transport); + + if ($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $transport, 'user_id' => $user->id) )) { + $user_im_prefs_by_transport[$transport] = $user_im_prefs; + $this->element('p', 'form_confirmed', $user_im_prefs->screenname); + $this->element('p', 'form_note', + sprintf(_('Current confirmed %s address.'),$transport_info['display'])); + $this->hidden('screenname', $user_im_prefs->screenname); + $this->submit('remove', _('Remove')); + } else { + $confirm = $this->getConfirmation($transport); + if ($confirm) { + $this->element('p', 'form_unconfirmed', $confirm->address); + $this->element('p', 'form_note', + sprintf(_('Awaiting confirmation on this address. '. + 'Check your %s account for a '. + 'message with further instructions.'), + $transport_info['display'])); + $this->hidden('screenname', $confirm->address); + $this->submit('cancel', _('Cancel')); + } else { + $this->elementStart('ul', 'form_data'); + $this->elementStart('li'); + $this->input('screenname', _('IM address'), + ($this->arg('screenname')) ? $this->arg('screenname') : null, + sprintf(_('%s screenname.'), + $transport_info['display'])); + $this->elementEnd('li'); + $this->elementEnd('ul'); + $this->submit('add', _('Add')); + } + } + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } + + if($user_im_prefs_by_transport) + { + $this->elementStart('form', array('method' => 'post', + 'id' => 'form_settings_im', + 'class' => 'form_settings', + 'action' => + common_local_url('imsettings'))); + $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); + $this->element('legend', null, _('Preferences')); + $this->hidden('token', common_session_token()); + $this->elementStart('table'); + $this->elementStart('tr'); + $this->element('th', null, _('Preferences')); + foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) + { + $this->element('th', null, $transports[$transport]['display']); + } + $this->elementEnd('tr'); + $preferences = array( + array('name'=>'notify', 'description'=>_('Send me notices')), + array('name'=>'updatefrompresence', 'description'=>_('Post a notice when my status changes.')), + array('name'=>'replies', 'description'=>_('Send me replies '. + 'from people I\'m not subscribed to.')), + array('name'=>'microid', 'description'=>_('Publish a MicroID')) + ); + foreach($preferences as $preference) + { + $this->elementStart('tr'); + foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) + { + $preference_name = $preference['name']; + $this->elementStart('td'); + $this->checkbox($transport . '_' . $preference['name'], + $preference['description'], + $user_im_prefs->$preference_name); + $this->elementEnd('td'); + } + $this->elementEnd('tr'); + } + $this->elementEnd('table'); + $this->submit('save', _('Save')); + $this->elementEnd('fieldset'); + $this->elementEnd('form'); + } } /** @@ -171,14 +191,14 @@ class ImsettingsAction extends ConnectSettingsAction * @return Confirm_address address object for this user */ - function getConfirmation() + function getConfirmation($transport) { $user = common_current_user(); $confirm = new Confirm_address(); $confirm->user_id = $user->id; - $confirm->address_type = 'jabber'; + $confirm->address_type = $transport; if ($confirm->find(true)) { return $confirm; @@ -232,35 +252,31 @@ class ImsettingsAction extends ConnectSettingsAction function savePreferences() { - - $jabbernotify = $this->boolean('jabbernotify'); - $updatefrompresence = $this->boolean('updatefrompresence'); - $jabberreplies = $this->boolean('jabberreplies'); - $jabbermicroid = $this->boolean('jabbermicroid'); - $user = common_current_user(); - assert(!is_null($user)); // should already be checked + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $user->id; + if($user_im_prefs->find() && $user_im_prefs->fetch()) + { + $preferences = array('notify', 'updatefrompresence', 'replies', 'microid'); + $user_im_prefs->query('BEGIN'); + do + { + $original = clone($user_im_prefs); + foreach($preferences as $preference) + { + $user_im_prefs->$preference = $this->boolean($user_im_prefs->transport . '_' . $preference); + } + $result = $user_im_prefs->update($original); - $user->query('BEGIN'); - - $original = clone($user); - - $user->jabbernotify = $jabbernotify; - $user->updatefrompresence = $updatefrompresence; - $user->jabberreplies = $jabberreplies; - $user->jabbermicroid = $jabbermicroid; - - $result = $user->update($original); - - if ($result === false) { - common_log_db_error($user, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t update user.')); - return; + if ($result === false) { + common_log_db_error($user, 'UPDATE', __FILE__); + $this->serverError(_('Couldn\'t update IM preferences.')); + return; + } + }while($user_im_prefs->fetch()); + $user_im_prefs->query('COMMIT'); } - - $user->query('COMMIT'); - $this->showForm(_('Preferences saved.'), true); } @@ -268,7 +284,7 @@ class ImsettingsAction extends ConnectSettingsAction * Sends a confirmation to the address given * * Stores a confirmation record and sends out a - * Jabber message with the confirmation info. + * message with the confirmation info. * * @return void */ @@ -277,36 +293,41 @@ class ImsettingsAction extends ConnectSettingsAction { $user = common_current_user(); - $jabber = $this->trimmed('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); // Some validation - if (!$jabber) { - $this->showForm(_('No Jabber ID.')); + if (!$screenname) { + $this->showForm(_('No screenname.')); return; } - $jabber = jabber_normalize_jid($jabber); - - if (!$jabber) { - $this->showForm(_('Cannot normalize that Jabber ID')); + if (!$transport) { + $this->showForm(_('No transport.')); return; } - if (!jabber_valid_base_jid($jabber)) { - $this->showForm(_('Not a valid Jabber ID')); + + Event::handle('NormalizeImScreenname', array($transport, &$screenname)); + + if (!$screenname) { + $this->showForm(_('Cannot normalize that screenname')); return; - } else if ($user->jabber == $jabber) { - $this->showForm(_('That is already your Jabber ID.')); + } + $valid = false; + Event::handle('ValidateImScreenname', array($transport, $screenname, &$valid)); + if (!$valid) { + $this->showForm(_('Not a valid screenname')); return; - } else if ($this->jabberExists($jabber)) { - $this->showForm(_('Jabber ID already belongs to another user.')); + } else if ($this->screennameExists($transport, $screenname)) { + $this->showForm(_('Screenname already belongs to another user.')); return; } $confirm = new Confirm_address(); - $confirm->address = $jabber; - $confirm->address_type = 'jabber'; + $confirm->address = $screenname; + $confirm->address_type = $transport; $confirm->user_id = $user->id; $confirm->code = common_confirmation_code(64); $confirm->sent = common_sql_now(); @@ -320,15 +341,10 @@ class ImsettingsAction extends ConnectSettingsAction return; } - jabber_confirm_address($confirm->code, - $user->nickname, - $jabber); + Event::handle('SendImConfirmationCode', array($transport, $screenname, $confirm->code, $user)); - $msg = sprintf(_('A confirmation code was sent '. - 'to the IM address you added. '. - 'You must approve %s for '. - 'sending messages to you.'), - jabber_daemon_address()); + $msg = _('A confirmation code was sent '. + 'to the IM address you added.'); $this->showForm($msg, true); } @@ -343,15 +359,16 @@ class ImsettingsAction extends ConnectSettingsAction function cancelConfirmation() { - $jabber = $this->arg('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); - $confirm = $this->getConfirmation(); + $confirm = $this->getConfirmation($transport); if (!$confirm) { $this->showForm(_('No pending confirmation to cancel.')); return; } - if ($confirm->address != $jabber) { + if ($confirm->address != $screenname) { $this->showForm(_('That is the wrong IM address.')); return; } @@ -360,7 +377,7 @@ class ImsettingsAction extends ConnectSettingsAction if (!$result) { common_log_db_error($confirm, 'DELETE', __FILE__); - $this->serverError(_('Couldn\'t delete email confirmation.')); + $this->serverError(_('Couldn\'t delete confirmation.')); return; } @@ -379,29 +396,25 @@ class ImsettingsAction extends ConnectSettingsAction { $user = common_current_user(); - $jabber = $this->arg('jabber'); + $screenname = $this->trimmed('screenname'); + $transport = $this->trimmed('transport'); // Maybe an old tab open...? - if ($user->jabber != $jabber) { - $this->showForm(_('That is not your Jabber ID.')); + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->user_id = $user->id; + if(! ($user_im_prefs->find() && $user_im_prefs->fetch())) { + $this->showForm(_('That is not your screenname.')); return; } - $user->query('BEGIN'); - - $original = clone($user); - - $user->jabber = null; - - $result = $user->updateKeys($original); + $result = $user_im_prefs->delete(); if (!$result) { common_log_db_error($user, 'UPDATE', __FILE__); - $this->serverError(_('Couldn\'t update user.')); + $this->serverError(_('Couldn\'t update user im prefs.')); return; } - $user->query('COMMIT'); // XXX: unsubscribe to the old address @@ -409,25 +422,27 @@ class ImsettingsAction extends ConnectSettingsAction } /** - * Does this Jabber ID exist? + * Does this screenname exist? * * Checks if we already have another user with this address. * - * @param string $jabber Address to check + * @param string $transport Transport to check + * @param string $screenname Screenname to check * - * @return boolean whether the Jabber ID exists + * @return boolean whether the screenname exists */ - function jabberExists($jabber) + function screennameExists($transport, $screenname) { $user = common_current_user(); - $other = User::staticGet('jabber', $jabber); - - if (!$other) { + $user_im_prefs = new User_im_prefs(); + $user_im_prefs->transport = $transport; + $user_im_prefs->screenname = $screenname; + if($user_im_prefs->find() && $user_im_prefs->fetch()){ + return true; + }else{ return false; - } else { - return $other->id != $user->id; } } } diff --git a/actions/shownotice.php b/actions/shownotice.php index d09100f676..d0528a9f0f 100644 --- a/actions/shownotice.php +++ b/actions/shownotice.php @@ -275,12 +275,6 @@ class ShownoticeAction extends OwnerDesignAction 'content' => $id->toString())); } - if ($user->jabbermicroid && $user->jabber && $this->notice->uri) { - $id = new Microid('xmpp:', $user->jabber, - $this->notice->uri); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } $this->element('link',array('rel'=>'alternate', 'type'=>'application/json+oembed', 'href'=>common_local_url( diff --git a/actions/showstream.php b/actions/showstream.php index 75e10858d0..b9782a3f14 100644 --- a/actions/showstream.php +++ b/actions/showstream.php @@ -166,12 +166,6 @@ class ShowstreamAction extends ProfileAction $this->element('meta', array('name' => 'microid', 'content' => $id->toString())); } - if ($this->user->jabbermicroid && $this->user->jabber && $this->profile->profileurl) { - $id = new Microid('xmpp:'.$this->user->jabber, - $this->selfUrl()); - $this->element('meta', array('name' => 'microid', - 'content' => $id->toString())); - } // See https://wiki.mozilla.org/Microsummaries diff --git a/classes/User.php b/classes/User.php index 6ea975202d..57c56849d6 100644 --- a/classes/User.php +++ b/classes/User.php @@ -48,11 +48,6 @@ class User extends Memcached_DataObject public $language; // varchar(50) public $timezone; // varchar(50) public $emailpost; // tinyint(1) default_1 - public $jabber; // varchar(255) unique_key - public $jabbernotify; // tinyint(1) - public $jabberreplies; // tinyint(1) - public $jabbermicroid; // tinyint(1) default_1 - public $updatefrompresence; // tinyint(1) public $sms; // varchar(64) unique_key public $carrier; // int(4) public $smsnotify; // tinyint(1) @@ -92,7 +87,7 @@ class User extends Memcached_DataObject function updateKeys(&$orig) { $parts = array(); - foreach (array('nickname', 'email', 'jabber', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { + foreach (array('nickname', 'email', 'incomingemail', 'sms', 'carrier', 'smsemail', 'language', 'timezone') as $k) { if (strcmp($this->$k, $orig->$k) != 0) { $parts[] = $k . ' = ' . $this->_quote($this->$k); } diff --git a/classes/User_im_prefs.php b/classes/User_im_prefs.php new file mode 100644 index 0000000000..8ecdfe9fab --- /dev/null +++ b/classes/User_im_prefs.php @@ -0,0 +1,71 @@ +. + * + * @category Data + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 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/ + */ + +require_once INSTALLDIR.'/classes/Memcached_DataObject.php'; + +class User_im_prefs extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_im_prefs'; // table name + public $user_id; // int(4) primary_key not_null + public $screenname; // varchar(255) not_null + public $transport; // varchar(255) not_null + public $notify; // tinyint(1) + public $replies; // tinyint(1) + public $microid; // tinyint(1) + public $updatefrompresence; // tinyint(1) + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_im_prefs',$k,$v); } + + function pkeyGet($kv) + { + return Memcached_DataObject::pkeyGet('User_im_prefs', $kv); + } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + /* + DB_DataObject calculates the sequence key(s) by taking the first key returned by the keys() function. + In this case, the keys() function returns user_id as the first key. user_id is not a sequence, but + DB_DataObject's sequenceKey() will incorrectly think it is. Then, since the sequenceKey() is a numeric + type, but is not set to autoincrement in the database, DB_DataObject will create a _seq table and + manage the sequence itself. This is not the correct behavior for the user_id in this class. + So we override that incorrect behavior, and simply say there is no sequence key. + */ + function sequenceKey() + { + return array(false,false); + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 6203650a69..d8a817ebc7 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -540,11 +540,6 @@ emailmicroid = 17 language = 2 timezone = 2 emailpost = 17 -jabber = 2 -jabbernotify = 17 -jabberreplies = 17 -jabbermicroid = 17 -updatefrompresence = 17 sms = 2 carrier = 1 smsnotify = 17 @@ -564,7 +559,6 @@ id = K nickname = U email = U incomingemail = U -jabber = U sms = U uri = U @@ -616,3 +610,19 @@ modified = 384 [user_location_prefs__keys] user_id = K +[user_im_prefs] +user_id = 129 +screenname = 130 +transport = 130 +notify = 17 +replies = 17 +microid = 17 +updatefrompresence = 17 +created = 142 +modified = 384 + +[user_im_prefs__keys] +user_id = K +transport = K +transport = U +screenname = U diff --git a/db/statusnet.sql b/db/statusnet.sql index 17de4fd0d4..63b50a3f71 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -62,11 +62,6 @@ create table user ( language varchar(50) comment 'preferred language', timezone varchar(50) comment 'timezone', emailpost tinyint default 1 comment 'Post by email', - jabber varchar(255) unique key comment 'jabber ID for notices', - jabbernotify tinyint default 0 comment 'whether to send notices to jabber', - jabberreplies tinyint default 0 comment 'whether to send notices to jabber on replies', - jabbermicroid tinyint default 1 comment 'whether to publish xmpp microid', - updatefrompresence tinyint default 0 comment 'whether to record updates from Jabber presence notices', sms varchar(64) unique key comment 'sms phone number', carrier integer comment 'foreign key to sms_carrier' references sms_carrier (id), smsnotify tinyint default 0 comment 'whether to send notices to SMS', @@ -259,9 +254,9 @@ create table oid_nonces ( create table confirm_address ( code varchar(32) not null primary key comment 'good random code', user_id integer not null comment 'user who requested confirmation' references user (id), - address varchar(255) not null comment 'address (email, Jabber, SMS, etc.)', + address varchar(255) not null comment 'address (email, xmpp, SMS, etc.)', address_extra varchar(255) not null comment 'carrier ID, for SMS', - address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")', + address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")', claimed datetime comment 'date this was claimed for queueing', sent datetime comment 'date this was sent for queueing', modified timestamp comment 'date this record was modified' @@ -276,7 +271,7 @@ create table remember_me ( create table queue_item ( id integer auto_increment primary key comment 'unique identifier', frame blob not null comment 'data: object reference or opaque string', - transport varchar(8) not null comment 'queue for what? "email", "jabber", "sms", "irc", ...', + transport varchar(8) not null comment 'queue for what? "email", "xmpp", "sms", "irc", ...', created datetime not null comment 'date this record was created', claimed datetime comment 'date this item was claimed', @@ -348,7 +343,7 @@ create table invitation ( code varchar(32) not null primary key comment 'random code for an invitation', user_id int not null comment 'who sent the invitation' references user (id), address varchar(255) not null comment 'invitation sent to', - address_type varchar(8) not null comment 'address type ("email", "jabber", "sms")', + address_type varchar(8) not null comment 'address type ("email", "xmpp", "sms")', created datetime not null comment 'date this record was created', index invitation_address_idx (address, address_type), @@ -633,3 +628,18 @@ create table inbox ( constraint primary key (user_id) ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table user_im_prefs ( + user_id integer not null comment 'user' references user (id), + screenname varchar(255) not null comment 'screenname on this service', + transport varchar(255) not null comment 'transport (ex xmpp, aim)', + notify tinyint(1) not null default 0 comment 'Notify when a new notice is sent', + replies tinyint(1) not null default 0 comment 'Send replies from people not subscribed to', + microid tinyint(1) not null default 1 comment 'Publish a MicroID', + updatefrompresence tinyint(1) not null default 0 comment 'Send replies from people not subscribed to.', + created timestamp not null DEFAULT CURRENT_TIMESTAMP comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + constraint primary key (user_id, transport), + constraint unique key `transport_screenname_key` ( `transport` , `screenname` ) +); diff --git a/lib/channel.php b/lib/channel.php index 3cd168786c..05437b4e9e 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 2a51fd6872..44b7b22743 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 0000000000..12354ce4b0 --- /dev/null +++ b/lib/imchannel.php @@ -0,0 +1,104 @@ +. + */ + +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 0000000000..4c28ec0e6a --- /dev/null +++ b/lib/immanager.php @@ -0,0 +1,70 @@ +. + */ + +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; + //TODO We only really want to register this event if this is the thread that runs the ImManager + Event::addHandler('EndInitializeQueueManager', array($this, 'onEndInitializeQueueManager')); + } + + /** + * 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'); + } + + /** + * Register notice queue handler + * + * @param QueueManager $manager + * + * @return boolean hook return + */ + function onEndInitializeQueueManager($manager) + { + $manager->connect($this->plugin->transport . '-out', new ImSenderQueueHandler($this->plugin, $this), 'imdaemon'); + return true; + } +} diff --git a/lib/implugin.php b/lib/implugin.php new file mode 100644 index 0000000000..5d4d8949cd --- /dev/null +++ b/lib/implugin.php @@ -0,0 +1,612 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 + * @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)); + $manager->connect($this->transport, new ImQueueHandler($this)); + 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 similarity index 60% rename from lib/jabberqueuehandler.php rename to lib/imqueuehandler.php index 83471f2df7..b42d8e7c0b 100644 --- a/lib/jabberqueuehandler.php +++ b/lib/imqueuehandler.php @@ -17,31 +17,32 @@ * along with this program. If not, see . */ -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) { - $this->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 similarity index 60% rename from lib/publicqueuehandler.php rename to lib/imreceiverqueuehandler.php index c9edb8d5d7..269c7db918 100644 --- a/lib/publicqueuehandler.php +++ b/lib/imreceiverqueuehandler.php @@ -17,29 +17,26 @@ * along with this program. If not, see . */ -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) { - $this->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 0000000000..3dd96ff731 --- /dev/null +++ b/lib/imsenderqueuehandler.php @@ -0,0 +1,44 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * Common superclass for all IM sending queue handlers. + */ + +class ImSenderQueueHandler extends QueueHandler +{ + function __construct($plugin, $immanager) + { + $this->plugin = $plugin; + $this->immanager = $immanager; + } + + /** + * Handle outgoing IM data to be sent from the bot to a user + * @param object $data + * @return boolean success + */ + function handle($data) + { + return $this->immanager->send_raw_message($data); + } +} + diff --git a/lib/jabber.php b/lib/jabber.php deleted file mode 100644 index b6b23521bd..0000000000 --- a/lib/jabber.php +++ /dev/null @@ -1,473 +0,0 @@ -. - * - * @category Network - * @package StatusNet - * @author Evan Prodromou - * @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 = "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 false; - } - - $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 false; - } - - $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/queuehandler.php b/lib/queuehandler.php index 2909cd83b1..2194dd1618 100644 --- a/lib/queuehandler.php +++ b/lib/queuehandler.php @@ -36,20 +36,6 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } 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 61e28085ab..0b405943d3 100644 --- a/lib/queuemanager.php +++ b/lib/queuemanager.php @@ -213,18 +213,8 @@ abstract class QueueManager extends IoManager $this->connect('sms', 'SmsQueueHandler'); } - // XMPP output handlers... - $this->connect('jabber', 'JabberQueueHandler'); - $this->connect('public', 'PublicQueueHandler'); - - // @fixme this should get an actual queue - //$this->connect('confirm', 'XmppConfirmHandler'); - // For compat with old plugins not registering their own handlers. $this->connect('plugin', 'PluginQueueHandler'); - - $this->connect('xmppout', 'XmppOutQueueHandler', 'xmppdaemon'); - } Event::handle('EndInitializeQueueManager', array($this)); } @@ -251,8 +241,8 @@ abstract class QueueManager extends IoManager $group = 'queuedaemon'; if ($this->master) { // hack hack - if ($this->master instanceof XmppMaster) { - return 'xmppdaemon'; + if ($this->master instanceof ImMaster) { + return 'imdaemon'; } } return $group; diff --git a/lib/queuemonitor.php b/lib/queuemonitor.php index 1c306a6298..3dc0ea65aa 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/util.php b/lib/util.php index 6c9f6316ad..e60cb6765c 100644 --- a/lib/util.php +++ b/lib/util.php @@ -994,18 +994,9 @@ function common_enqueue_notice($notice) $transports = $allTransports; - $xmpp = common_config('xmpp', 'enabled'); - - if ($xmpp) { - $transports[] = 'jabber'; - } - 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 985e7c32e4..0000000000 --- a/lib/xmppmanager.php +++ /dev/null @@ -1,485 +0,0 @@ -. - */ - -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 = common_config('site', 'server'); - 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 = common_config('site', 'server'); - $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(""); - } - - /** - * 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 != common_config('site', 'server')) { - common_log(LOG_DEBUG, __METHOD__ . ": switching to site $this->site"); - $this->stats('switch'); - StatusNet::init($this->site); - } - } -} diff --git a/lib/xmppoutqueuehandler.php b/lib/xmppoutqueuehandler.php deleted file mode 100644 index 2afa260f18..0000000000 --- a/lib/xmppoutqueuehandler.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ - -/** - * 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 - */ -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; - } -} - diff --git a/plugins/Aim/AimPlugin.php b/plugins/Aim/AimPlugin.php new file mode 100644 index 0000000000..3855d1fb05 --- /dev/null +++ b/plugins/Aim/AimPlugin.php @@ -0,0 +1,162 @@ +. + * + * @category IM + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} +// We bundle the phptoclib library... +set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phptoclib'); + +/** + * Plugin for AIM + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class AimPlugin extends ImPlugin +{ + public $user = null; + public $password = null; + public $publicFeed = array(); + + public $transport = 'aim'; + + function getDisplayName() + { + return _m('AIM'); + } + + function normalize($screenname) + { + $screenname = str_replace(" ","", $screenname); + return strtolower($screenname); + } + + function daemon_screenname() + { + return $this->user; + } + + function validate($screenname) + { + if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { + return true; + }else{ + return false; + } + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'Aim': + require_once(INSTALLDIR.'/plugins/Aim/extlib/phptoclib/aimclassw.php'); + return false; + case 'AimManager': + include_once $dir . '/'.strtolower($cls).'.php'; + return false; + case 'Fake_Aim': + include_once $dir . '/'. $cls .'.php'; + return false; + default: + return true; + } + } + + function onStartImDaemonIoManagers(&$classes) + { + parent::onStartImDaemonIoManagers(&$classes); + $classes[] = new AimManager($this); // handles sending/receiving + return true; + } + + function microiduri($screenname) + { + return 'aim:' . $screenname; + } + + function send_message($screenname, $body) + { + $this->fake_aim->sendIm($screenname, $body); + $this->enqueue_outgoing_raw($this->fake_aim->would_be_sent); + return true; + } + + function receive_raw_message($message) + { + $info=Aim::getMessageInfo($message); + $from = $info['from']; + $user = $this->get_user($from); + $notice_text = $info['message']; + + return $this->handle_incoming($from, $notice_text); + } + + function initialize(){ + if(!isset($this->user)){ + throw new Exception("must specify a user"); + } + if(!isset($this->password)){ + throw new Exception("must specify a password"); + } + + $this->fake_aim = new Fake_Aim($this->user,$this->password,4); + return true; + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'AIM', + 'version' => STATUSNET_VERSION, + 'author' => 'Craig Andrews', + 'homepage' => 'http://status.net/wiki/Plugin:AIM', + 'rawdescription' => + _m('The AIM plugin allows users to send and receive notices over the AIM network.')); + return true; + } +} + diff --git a/plugins/Aim/Fake_Aim.php b/plugins/Aim/Fake_Aim.php new file mode 100644 index 0000000000..139b68f82b --- /dev/null +++ b/plugins/Aim/Fake_Aim.php @@ -0,0 +1,43 @@ +. + * + * @category Network + * @package StatusNet + * @author Craig Andrews + * @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); +} + +class Fake_Aim extends Aim +{ + public $would_be_sent = null; + + function sflapSend($sflap_type, $sflap_data, $no_null, $formatted) + { + $this->would_be_sent = array($sflap_type, $sflap_data, $no_null, $formatted); + } +} + diff --git a/plugins/Aim/README b/plugins/Aim/README new file mode 100644 index 0000000000..0465917383 --- /dev/null +++ b/plugins/Aim/README @@ -0,0 +1,27 @@ +The AIM plugin allows users to send and receive notices over the AIM network. + +Installation +============ +add "addPlugin('aim', + array('setting'=>'value', 'setting2'=>'value2', ...);" +to the bottom of your config.php + +The daemon included with this plugin must be running. It will be started by +the plugin along with their other daemons when you run scripts/startdaemons.sh. +See the StatusNet README for more about queuing and daemons. + +Settings +======== +user*: username (screenname) to use when logging into AIM +password*: password for that user + +* required +default values are in (parenthesis) + +Example +======= +addPlugin('aim', array( + 'user=>'...', + 'password'=>'...' +)); + diff --git a/plugins/Aim/aimmanager.php b/plugins/Aim/aimmanager.php new file mode 100644 index 0000000000..d9b7421fb2 --- /dev/null +++ b/plugins/Aim/aimmanager.php @@ -0,0 +1,100 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * AIM background connection manager for AIM-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 handled. + * + * 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 AimManager extends ImManager +{ + + public $conn = null; + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + if(parent::start($master)) + { + $this->connect(); + return true; + }else{ + return false; + } + } + + public function getSockets() + { + $this->connect(); + if($this->conn){ + return array($this->conn->myConnection); + }else{ + return array(); + } + } + + /** + * Process AIM events that have come in over the wire. + * @param resource $socket + */ + public function handleInput($socket) + { + common_log(LOG_DEBUG, "Servicing the AIM queue."); + $this->stats('aim_process'); + $this->conn->receive(); + } + + function connect() + { + if (!$this->conn) { + $this->conn=new Aim($this->plugin->user,$this->plugin->password,4); + $this->conn->registerHandler("IMIn",array($this,"handle_aim_message")); + $this->conn->myServer="toc.oscar.aol.com"; + $this->conn->signon(); + $this->conn->setProfile(_m('Send me a message to post a notice'),false); + } + return $this->conn; + } + + function handle_aim_message($data) + { + $this->plugin->enqueue_incoming_raw($data); + return true; + } + + function send_raw_message($data) + { + $this->connect(); + if (!$this->conn) { + return false; + } + $this->conn->sflapSend($data[0],$data[1],$data[2],$data[3]); + return true; + } +} diff --git a/plugins/Aim/extlib/phptoclib/README.txt b/plugins/Aim/extlib/phptoclib/README.txt new file mode 100755 index 0000000000..0eec13af8a --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/README.txt @@ -0,0 +1,169 @@ +phpTOCLib version 1.0 RC1 + +This is released under the LGPL. AIM,TOC,OSCAR, and all other related protocols/terms are +copyright AOL/Time Warner. This project is in no way affiliated with them, nor is this +project supported by them. + +Some of the code is loosely based off of a script by Jeffrey Grafton. Mainly the decoding of packets, and the +function for roasting passwords is entirly his. + +TOC documentation used is available at http://simpleaim.sourceforge.net/docs/TOC.txt + + +About: +phpTOCLib aims to be a PHP equivalent to the PERL module NET::AIM. Due to some limitations, +this is difficult. Many features have been excluded in the name of simplicity, and leaves +you alot of room to code with externally, providing function access to the variables that +need them. + +I have aimed to make this extensible, and easy to use, therefore taking away some built in +functionality that I had originally out in. This project comes after several months of +researching the TOC protocol. + +example.php is included with the class. It needs to be executed from the command line +(ie:php -q testscript.php) and you need to call php.exe with the -q +example is provided as a demonstaration only. Though it creats a very simple, functional bot, it lacks any sort of commands, it merely resends the message it recieves in reverse. + + +Revisions: + +----------------------------------- +by Rajiv Makhijani +(02/24/04) + - Fixed Bug in Setting Permit/Deny Mode + - Fixes so Uninitialized string offset notice doesn't appear + - Replaced New Lines Outputed for Each Flap Read with " . " so + that you can still tell it is active but it does not take so much space + - Removed "eh?" message + - Added MySQL Database Connection Message + - New Functions: + update_profile(profile data string, powered by boolean) + * The profile data string is the text that goes in the profile. + * The powered by boolean if set to true displays a link to the + sourceforge page of the script. +(02/28/04) + - Silent option added to set object not to output any information + - To follow silent rule use sEcho function instead of Echo +----------------------------------- +by Jeremy (pickleman78) +(05/26/04) beta 1 release + -Complete overhaul of class design and message handling + -Fixed bug involving sign off after long periods of idling + -Added new function $Aim->registerHandler + -Added the capability to handle all AIM messages + -Processing the messages is still the users responsibility + -Did a little bit of code cleanup + -Added a few internal functions to make the classes internal life easier + -Improved AIM server error message processing + -Updated this document (hopefully Rajiv will clean it up some, since I'm a terrible documenter) +------------------------------------------------------------------------------------------------------------- + + + +Functions: + +Several methods are provided in the class that allow for simple access to some of the +common features of AIM. Below are details. + +$Aim->Aim($sn,$password,$pdmode, $silent=false) +The constructor, it takes 4 arguments. +$sn is your screen name +$password is you password, in plain text +$pdmode is the permit deny mode. This can be as follows: +1 - Allow All +2 - Deny All +3 - Permit only those on your permit list +4 - Permit all those not on your deny list +$silent if set to true prints out nothing + +So, if your screen-name is JohnDoe746 and your password is fertu, and you want to allow +all users of the AIM server to contact you, you would code as follows +$myaim=new Aim("JohnDoe746","fertu",1); + + +$Aim->add_permit($buddy) +This adds the buddy passed to the function to your permit list. +ie: $myaim->add_permit("My friend22"); + +$Aim->block_buddy($buddy) +Blocks a user. This will switch your pd mode to 4. After using this, for the user to remain +out of contact with you, it is required to provide the constructor with a pd mode of 4 +ie:$myaim->block_buddy("Annoying guy 4"); + +$Aim->send_im($to,$message,$auto=false) +Sends $message to $user. If you set the 3rd argument to true, then the recipient will receive it in +the same format as an away message. (Auto Response from me:) +A message longer than 65535 will be truncated +ie:$myaim->send_im("myfriend","This is a happy message"); + +$Aim->set_my_info() +Sends an update buddy command to the server and allows some internal values about yourself +to be set. +ie:$myaim->set_my_info(); + +$Aim->signon() +Call this to connect to the server. This must be called before any other methods will work +properly +ie:$mybot->signon(); + +$Aim->getLastReceived() +Returns $this->myLastReceived['decoded']. This should be the only peice of the gotten data +you need to concern yourself with. This is a preferred method of accessing this variable to prevent +accidental modification of $this->myLastReceived. Accidently modifying this variable can +cause some internal failures. + +$Aim->read_from_aim() +This is a wrapper for $Aim->sflap_read(), and only returns the $this->myLastReceived['data'] +portion of the message. It is preferred that you do not call $Aim->sflap_read() and use this +function instead. This function has a return value. Calling this prevents the need to call +$Aim->getLastReceived() + +$Aim->setWarning($wl) +This allows you to update the bots warning level when warned. + +$Aim->getBuddies() +Returns the $this->myBuddyList array. Use this instead of modifying the internal variable + +$Aim->getPermit() +Returns the $this->myPermitList array. Use this instead of modifying the internal variable + +$Aim->getBlocked() +Returns the $this->myBlockedList array. Use this instead of modifying the internal variable + +$Aim->warn_user($user,$anon=false) +Warn $user. If anon is set to true, then it warns the user anonomously + +$Aim->update_profile($information, $poweredby=false) +Updates Profile to $information. If $poweredby is true a link to +sourceforge page for this script is appended to profile + +$Aim->registerHandler($function_name,$command) +This is by far the best thing about the new release. +For more information please reas supplement.txt. It is not included here because of the sheer size of the document. +supplement.txt contains full details on using registerHandler and what to expect for each input. + + +For convenience, I have provided some functions to simplify message processing. + +They can be read about in the file "supplement.txt". I chose not to include the text here because it +is a huge document + + + +There are a few things you should note about AIM +1)An incoming message has HTML tags in it. You are responsible for stripping those tags +2)Outgoing messages can have HTML tags, but will work fine if they don't. To include things + in the time feild next to the users name, send it as a comment + +Conclusion: +The class is released under the LGPL. If you have any bug reports, comments, questions +feature requests, or want to help/show me what you've created with this(I am very interested in this), +please drop me an email: pickleman78@users.sourceforge.net. This code was written by +Jeremy(a.k.a pickleman78) and Rajiv M (a.k.a compwiz562). + + +Special thanks: +I'd like to thank all of the people who have contributed ideas, testing, bug reports, and code additions to +this project. I'd like to especially thank Rajiv, who has done do much for the project, and has kept this documnet +looking nice. He also has done alot of testing of this script too. I'd also like to thank SpazLink for his help in +testing. And finally I'd like to thank Jeffery Grafton, whose script inspired me to start this project. diff --git a/plugins/Aim/extlib/phptoclib/aimclassw.php b/plugins/Aim/extlib/phptoclib/aimclassw.php new file mode 100755 index 0000000000..0657910d9e --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/aimclassw.php @@ -0,0 +1,2370 @@ + + * @author Rajiv Makhijani + * @package phptoclib + * @version 1.0RC1 + * @copyright 2005 + * @access public + * + */ +class Aim +{ + /** + * AIM ScreenName + * + * @var String + * @access private + */ + var $myScreenName; + + /** + * AIM Password (Plain Text) + * + * @var String + * @access private + */ + var $myPassword; + + + /** + * AIM TOC Server + * + * @var String + * @access public + */ + var $myServer="toc.oscar.aol.com"; + + /** + * AIM Formatted ScreenName + * + * @var String + * @access private + */ + var $myFormatSN; + + /** + * AIM TOC Server Port + * + * @var String + * @access public + */ + var $myPort="5190"; + + /** + * Profile Data + * Use setProfile() to update + * + * @var String + * @access private + */ + var $myProfile="Powered by phpTOCLib. Please visit http://sourceforge.net/projects/phptoclib for more information"; //The profile of the bot + + /** + * Socket Connection Resource ID + * + * @var Resource + * @access private + */ + var $myConnection; //Connection resource ID + + /** + * Roasted AIM Password + * + * @var String + * @access private + */ + var $myRoastedPass; + + /** + * Last Message Recieved From Server + * + * @var String + * @access private + */ + var $myLastReceived; + + /** + * Current Seq Number Used to Communicate with Server + * + * @var Integer + * @access private + */ + var $mySeqNum; + + /** + * Current Warning Level + * Getter: getWarning() + * Setter: setWarning() + * + * @var Integer + * @access private + */ + var $myWarnLevel; //Warning Level of the bot + + /** + * Auth Code + * + * @var Integer + * @access private + */ + var $myAuthCode; + + /** + * Buddies + * Getter: getBuddies() + * + * @var Array + * @access private + */ + var $myBuddyList; + + /** + * Blocked Buddies + * Getter: getBlocked() + * + * @var Array + * @access private + */ + var $myBlockedList; + + /** + * Permited Buddies + * Getter: getBlocked() + * + * @var Array + * @access private + */ + var $myPermitList; + + /** + * Permit/Deny Mode + * 1 - Allow All + * 2 - Deny All + * 3 - Permit only those on your permit list + * 4 - Permit all those not on your deny list + * + * @var Integer + * @access private + */ + var $myPdMode; + + //Below variables added 4-29 by Jeremy: Implementing chat + + /** + * Contains Chat Room Info + * $myChatRooms['roomid'] = people in room + * + * @var Array + * @access private + */ + var $myChatRooms; + + //End of chat implementation + + + /** + * Event Handler Functions + * + * @var Array + * @access private + */ + var $myEventHandlers = array(); + + /** + * Array of direct connection objects(including file transfers) + * + * @var Array + * @access private + */ + var $myDirectConnections = array(); + + /** + * Array of the actual connections + * + * @var Array + * @access private + */ + var $myConnections = array(); + + /** + * The current state of logging + * + * @var Boolean + * @access private + */ + + var $myLogging = false; + + /** + * Constructor + * + * Permit/Deny Mode Options + * 1 - Allow All + * 2 - Deny All + * 3 - Permit only those on your permit list + * 4 - Permit all those not on your deny list + * + * @param String $sn AIM Screenname + * @param String $password AIM Password + * @param Integer $pdmode Permit/Deny Mode + * @access public + */ + function Aim($sn, $password, $pdmode) + { + //Constructor assignment + $this->myScreenName = $this->normalize($sn); + $this->myPassword = $password; + $this->myRoastedPass = $this->roastPass($password); + $this->mySeqNum = 1; + $this->myConnection = 0; + $this->myWarnLevel = 0; + $this->myAuthCode = $this->makeCode(); + $this->myPdMode = $pdmode; + $this->myFormatSN = $this->myScreenName; + + $this->log("PHPTOCLIB v" . PHPTOCLIB_VERSION . " Object Created"); + + } + + /** + * Enables debug logging (Logging is disabled by default) + * + * + * @access public + * @return void + */ + + function setLogging($enable) + { + $this->myLogging=$enable; + } + + function log($data) + { + if($this->myLogging){ + error_log($data); + } + } + + /** + * Logs a packet + * + * + * @access private + * @param Array $packary Packet + * @param String $in Prepend + * @return void + */ + function logPacket($packary,$in) + { + if(!$this->myLogging || sizeof($packary)<=0 || (@strlen($packary['decoded'])<=0 && @isset($packary['decoded']))) + return; + $towrite=$in . ": "; + foreach($packary as $k=>$d) + { + $towrite.=$k . ":" . $d . "\r\n"; + } + $towrite.="\r\n\r\n"; + $this->log($towrite); + } + /** + * Roasts/Hashes Password + * + * @param String $password Password + * @access private + * @return String Roasted Password + */ + function roastPass($password) + { + $roaststring = 'Tic/Toc'; + $roasted_password = '0x'; + for ($i = 0; $i < strlen($password); $i++) + $roasted_password .= bin2hex($password[$i] ^ $roaststring[($i % 7)]); + return $roasted_password; + } + + /** + * Access Method for myScreenName + * + * @access public + * @param $formated Returns formatted Screenname if true as returned by server + * @return String Screenname + */ + function getMyScreenName($formated = false) + { + if ($formated) + { + return $this->myFormatSN; + } + else + { + return $this->normalize($this->myScreenName); + } + } + + /** + * Generated Authorization Code + * + * @access private + * @return Integer Auth Code + */ + function makeCode() + { + $sn = ord($this->myScreenName[0]) - 96; + $pw = ord($this->myPassword[0]) - 96; + $a = $sn * 7696 + 738816; + $b = $sn * 746512; + $c = $pw * $a; + + return $c - $a + $b + 71665152; + } + + + /** + * Reads from Socket + * + * @access private + * @return String Data + */ + function sflapRead() + { + if ($this->socketcheck($this->myConnection)) + { + $this->log("Disconnected.... Reconnecting in 60 seconds"); + sleep(60); + $this->signon(); + } + + $header = fread($this->myConnection,SFLAP_HEADER_LEN); + + if (strlen($header) == 0) + { + $this->myLastReceived = ""; + return ""; + } + $header_data = unpack("aast/Ctype/nseq/ndlen", $header); + $this->log(" . ", false); + $packet = fread($this->myConnection, $header_data['dlen']); + if (strlen($packet) <= 0 && $sockinfo['blocked']) + $this->derror("Could not read data"); + + if ($header_data['type'] == SFLAP_TYPE_SIGNON) + { + $packet_data=unpack("Ndecoded", $packet); + } + + if ($header_data['type'] == SFLAP_TYPE_KEEPALIVE) + { + $this->myLastReceived = ''; + return 0; + } + else if (strlen($packet)>0) + { + $packet_data = unpack("a*decoded", $packet); + } + $this->log("socketcheck check now"); + if ($this->socketcheck($this->myConnection)) + { + $this->derror("Connection ended unexpectedly"); + } + + $data = array_merge($header_data, $packet_data); + $this->myLastReceived = $data; + $this->logPacket($data,"in"); + return $data; + } + + /** + * Sends Data on Socket + * + * @param String $sflap_type Type + * @param String $sflap_data Data + * @param boolean $no_null No Null + * @param boolean $formatted Format + * @access private + * @return String Roasted Password + */ + function sflapSend($sflap_type, $sflap_data, $no_null, $formatted) + { + $packet = ""; + if (strlen($sflap_data) >= MAX_PACKLENGTH) + $sflap_data = substr($sflap_data,0,MAX_PACKLENGTH); + + if ($formatted) + { + $len = strlen($sflap_len); + $sflap_header = pack("aCnn",'*', $sflap_type, $this->mySeqNum, $len); + $packet = $sflap_header . $sflap_data; + } else { + if (!$no_null) + { + $sflap_data = str_replace("\0","", trim($sflap_data)); + $sflap_data .= "\0"; + } + $data = pack("a*", $sflap_data); + $len = strlen($sflap_data); + $header = pack("aCnn","*", $sflap_type, $this->mySeqNum, $len); + $packet = $header . $data; + } + + //Make sure we are still connected + if ($this->socketcheck($this->myConnection)) + { + $this->log("Disconnected.... reconnecting in 60 seconds"); + sleep(60); + $this->signon(); + } + $sent = fputs($this->myConnection, $packet) or $this->derror("Error sending packet to AIM"); + $this->mySeqNum++; + sleep(ceil($this->myWarnLevel/10)); + $this->logPacket(array($sflap_type,$sflap_data),"out"); + } + + /** + * Escape the thing that TOC doesn't like,that would be + * ",', $,{,},[,] + * + * @param String $data Data to Escape + * @see decodeData + * @access private + * @return String $data Escaped Data + */ + function encodeData($data) + { + $data = str_replace('"','\"', $data); + $data = str_replace('$','\$', $data); + $data = str_replace("'","\'", $data); + $data = str_replace('{','\{', $data); + $data = str_replace('}','\}', $data); + $data = str_replace('[','\[', $data); + $data = str_replace(']','\]', $data); + return $data; + } + + /** + * Unescape data TOC has escaped + * ",', $,{,},[,] + * + * @param String $data Data to Unescape + * @see encodeData + * @access private + * @return String $data Unescape Data + */ + function decodeData($data) + { + $data = str_replace('\"','"', $data); + $data = str_replace('\$','$', $data); + $data = str_replace("\'","'", $data); + $data = str_replace('\{','{', $data); + $data = str_replace('\}','}', $data); + $data = str_replace('\[','[', $data); + $data = str_replace('\]',']', $data); + $data = str_replace('"','"', $data); + $data = str_replace('&','&', $data); + return $data; + } + + /** + * Normalize ScreenName + * no spaces and all lowercase + * + * @param String $nick ScreenName + * @access public + * @return String $nick Normalized ScreenName + */ + function normalize($nick) + { + $nick = str_replace(" ","", $nick); + $nick = strtolower($nick); + return $nick; + } + + /** + * Sets internal info with update buddy + * Currently only sets warning level + * + * @access public + * @return void + */ + function setMyInfo() + { + //Sets internal values bvase on the update buddy command + $this->log("Setting my warning level ..."); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_get_status " . $this->normalize($this->myScreenName),0,0); + //The rest of this will now be handled by the other functions. It is assumed + //that we may have other data queued in the socket, do we should just add this + //message to the queue instead of trying to set it in here + } + + /** + * Connects to AIM and Signs On Using Info Provided in Constructor + * + * @access public + * @return void + */ + function signon() + { + $this->log("Ready to sign on to the server"); + $this->myConnection = fsockopen($this->myServer, $this->myPort, $errno, $errstr,10) or die("$errorno:$errstr"); + $this->log("Connected to server"); + $this->mySeqNum = (time() % 65536); //Select an arbitrary starting point for + //sequence numbers + if (!$this->myConnection) + $this->derror("Error connecting to toc.oscar.aol.com"); + $this->log("Connected to AOL"); + //Send the flapon packet + fputs($this->myConnection,"FLAPON\r\n\n\0"); //send the initial handshake + $this->log("Sent flapon"); + $this->sflapRead(); //Make sure the server responds with what we expect + if (!$this->myLastReceived) + $this->derror("Error sending the initialization string"); + + //send the FLAP SIGNON packet back with what it needs + //There are 2 parts to the signon packet. They are sent in succession, there + //is no indication if either packet was correctly sent + $signon_packet = pack("Nnna".strlen($this->myScreenName),1,1,strlen($this->myScreenName), $this->myScreenName); + $this->sflapSend(SFLAP_TYPE_SIGNON, $signon_packet,1,0); + $this->log("sent signon packet part one"); + + $signon_packet_part2 = 'toc2_signon login.oscar.aol.com 29999 ' . $this->myScreenName . ' ' . $this->myRoastedPass . ' english-US "TIC:TOC2:REVISION" 160 ' . $this->myAuthCode; + $this->log($signon_packet_part2 . ""); + $this->sflapSend(SFLAP_TYPE_DATA, $signon_packet_part2,0,0); + $this->log("Sent signon packet part 2... Awaiting response..."); + + $this->sflapRead(); + $this->log("Received Sign on packet, beginning initilization..."); + $message = $this->getLastReceived(); + $this->log($message . "\n"); + if (strstr($message,"ERROR:")) + { + $this->onError($message); + die("Fatal signon error"); + } + stream_set_timeout($this->myConnection,2); + //The information sent before the config2 command is utterly useless to us + //So we will just skim through them until we reach it + + //Add the first entry to the connection array + $this->myConnections[] = $this->myConnection; + + + //UPDATED 4/12/03: Now this will use the receive function and send the + //received messaged to the assigned handlers. This is where the signon + //method has no more use + + $this->log("Done with signon proccess"); + //socket_set_blocking($this->myConnection,false); + } + + /** + * Sends Instant Message + * + * @param String $to Message Recipient SN + * @param String $message Message to Send + * @param boolean $auto Sent as Auto Response / Away Message Style + * @access public + * @return void + */ + function sendIM($to, $message, $auto = false) + { + if ($auto) $auto = "auto"; + else $auto = ""; + $to = $this->normalize($to); + $message = $this->encodeData($message); + $command = 'toc2_send_im "' . $to . '" "' . $message . '" ' . $auto; + $this->sflapSend(SFLAP_TYPE_DATA, trim($command),0,0); + $cleanedmessage = str_replace("
", " ", $this->decodeData($message)); + $cleanedmessage = strip_tags($cleanedmessage); + $this->log("TO - " . $to . " : " . $cleanedmessage); + } + + /** + * Set Away Message + * + * @param String $message Away message (some HTML supported). + * Use null to remove the away message + * @access public + * @return void + */ + function setAway($message) + { + $message = $this->encodeData($message); + $command = 'toc_set_away "' . $message . '"'; + $this->sflapSend(SFLAP_TYPE_DATA, trim($command),0,0); + $this->log("SET AWAY MESSAGE - " . $this->decodeData($message)); + } + + /** + * Fills Buddy List + * Not implemented fully yet + * + * @access public + * @return void + */ + function setBuddyList() + { + //This better be the right message + $message = $this->myLastReceived['decoded']; + if (strpos($message,"CONFIG2:") === false) + { + $this->log("setBuddyList cannot be called at this time because I got $message"); + return false; + } + $people = explode("\n",trim($message,"\n")); + //The first 3 elements of the array are who knows what, element 3 should be + //a letter followed by a person + for($i = 1; $imyPermitList[] = $name; + break; + case 'd': + $this->myBlockedList[] = $name; + break; + case 'b': + $this->myBuddyList[] = $name; + break; + case 'done': + break; + default: + // + } + } + } + + /** + * Adds buddy to Permit list + * + * @param String $buddy Buddy's Screenname + * @access public + * @return void + */ + function addPermit($buddy) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_permit " . $this->normalize($buddy),0,0); + $this->myPermitList[] = $this->normalize($buddy); + return 1; + } + + /** + * Blocks buddy + * + * @param String $buddy Buddy's Screenname + * @access public + * @return void + */ + function blockBuddy($buddy) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_deny " . $this->normalize($buddy),0,0); + $this->myBlockedList[] = $this->normalize($buddy); + return 1; + } + + /** + * Returns last message received from server + * + * @access private + * @return String Last Message from Server + */ + function getLastReceived() + { + if (@$instuff = $this->myLastReceived['decoded']){ + return $this->myLastReceived['decoded']; + }else{ + return; + } + } + + /** + * Returns Buddy List + * + * @access public + * @return array Buddy List + */ + function getBuddies() + { + return $this->myBuddyList; + } + + /** + * Returns Permit List + * + * @access public + * @return array Permit List + */ + function getPermit() + { + return $this->myPermitList; + } + + /** + * Returns Blocked Buddies + * + * @access public + * @return array Blocked List + */ + function getBlocked() + { + return $this->myBlockedList; + } + + + + + /** + * Reads and returns data from server + * + * This is a wrapper for $Aim->sflap_read(), and only returns the $this->myLastReceived['data'] + * portion of the message. It is preferred that you do not call $Aim->sflap_read() and use this + * function instead. This function has a return value. Calling this prevents the need to call + * $Aim->getLastReceived() + * + * @access public + * @return String Data recieved from server + */ + function read_from_aim() + { + $this->sflapRead(); + $returnme = $this->getLastReceived(); + return $returnme; + } + + /** + * Sets current internal warning level + * + * This allows you to update the bots warning level when warned. + * + * @param int Warning Level % + * @access private + * @return void + */ + function setWarningLevel($warnlevel) + { + $this->myWarnLevel = $warnlevel; + } + + /** + * Warns / "Evils" a User + * + * To successfully warn another user they must have sent you a message. + * There is a limit on how much and how often you can warn another user. + * Normally when you warn another user they are aware who warned them, + * however there is the option to warn anonymously. When warning anon. + * note that the warning is less severe. + * + * @param String $to Screenname to warn + * @param boolean $anon Warn's anonymously if true. (default = false) + * @access public + * @return void + */ + function warnUser($to, $anon = false) + { + if (!$anon) + $anon = '"norm"'; + + else + $anon = '"anon"'; + $this->sflapSend(SFLAP_TYPE_DATA,"toc_evil " . $this->normalize($to) . " $anon",0,0); + } + + /** + * Returns warning level of bot + * + * @access public + * @return void + */ + function getWarningLevel() + { + return $this->myWarningLevel; + } + + /** + * Sets bot's profile/info + * + * Limited to 1024 bytes. + * + * @param String $profiledata Profile Data (Can contain limited html: br,hr,font,b,i,u etc) + * @param boolean $poweredby If true, appends link to phpTOCLib project to profile + * @access public + * @return void + */ + function setProfile($profiledata, $poweredby = false) + { + if ($poweredby == false){ + $this->myProfile = $profiledata; + }else{ + $this->myProfile = $profiledata . "

Powered by phpTOCLib
http://sourceforge.net/projects/phptoclib
"; + } + + $this->sflapSend(SFLAP_TYPE_DATA,"toc_set_info \"" . $this->encodeData($this->myProfile) . "\"",0,0); + $this->setMyInfo(); + $this->log("Profile has been updated..."); + } + + //6/29/04 by Jeremy: + //Added mthod to accept a rvous,decline it, and + //read from the rvous socket + + //Decline + + /** + * Declines a direct connection request (rvous) + * + * @param String $nick ScreenName request was from + * @param String $cookie Request cookie (from server) + * @param String $uuid UUID + * + * @access public + * @return void + */ + function declineRvous($nick, $cookie, $uuid) + { + $nick = $this->normalize($nick); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_rvous_cancel $nick $cookie $uuid",0,0); + } + + /** + * Accepts a direct connection request (rvous) + * + * @param String $nick ScreenName request was from + * @param String $cookie Request cookie (from server) + * @param String $uuid UUID + * @param String $vip IP of User DC with + * @param int $port Port number to connect to + * + * @access public + * @return void + */ + function acceptRvous($nick, $cookie, $uuid, $vip, $port) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_rvous_accept $nick $cookie $uuid",0,0); + + //Now open the connection to that user + if ($uuid == IMAGE_UID) + { + $dcon = new Dconnect($vip, $port); + } + else if ($uuid == FILE_SEND_UID) + { + $dcon = new FileSendConnect($vip, $port); + } + if (!$dcon->connected) + { + $this->log("The connection failed"); + return false; + } + + //Place this dcon object inside the array + $this->myDirectConnections[] = $dcon; + //Place the socket in an array to + $this->myConnections[] = $dcon->sock; + + + //Get rid of the first packet because its worthless + //and confusing + $dcon->readDIM(); + //Assign the cookie + $dcon->cookie = $dcon->lastReceived['cookie']; + $dcon->connectedTo = $this->normalize($nick); + return $dcon; + } + + /** + * Sends a Message over a Direct Connection + * + * Only works if a direct connection is already established with user + * + * @param String $to Message Recipient SN + * @param String $message Message to Send + * + * @access public + * @return void + */ + function sendDim($to, $message) + { + //Find the connection + for($i = 0;$imyDirectConnections);$i++) + { + if ($this->normalize($to) == $this->myDirectConnections[$i]->connectedTo && $this->myDirectConnections[$i]->type == CONN_TYPE_DC) + { + $dcon = $this->myDirectConnections[$i]; + break; + } + } + if (!$dcon) + { + $this->log("Could not find a direct connection to $to"); + return false; + } + $dcon->sendMessage($message, $this->normalize($this->myScreenName)); + return true; + } + + /** + * Closes an established Direct Connection + * + * @param DConnect $dcon Direct Connection Object to Close + * + * @access public + * @return void + */ + function closeDcon($dcon) + { + + $nary = array(); + for($i = 0;$imyConnections);$i++) + { + if ($dcon->sock == $this->myConnections[$i]) + unset($this->myConnections[$i]); + } + + $this->myConnections = array_values($this->myConnections); + unset($nary); + $nary2 = array(); + + for($i = 0;$imyDirectConnections);$i++) + { + if ($dcon == $this->myDirectConnections[$i]) + unset($this->myDirectConnections[$i]); + } + $this->myDirectConnections = array_values($this->myDirectConnections); + $dcon->close(); + unset($dcon); + } + + //Added 4/29/04 by Jeremy: + //Various chat related methods + + /** + * Accepts a Chat Room Invitation (Joins room) + * + * @param String $chatid ID of Chat Room + * + * @access public + * @return void + */ + function joinChat($chatid) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_accept " . $chatid,0,0); + } + + /** + * Leaves a chat room + * + * @param String $chatid ID of Chat Room + * + * @access public + * @return void + */ + function leaveChat($chatid) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_leave " . $chatid,0,0); + } + + /** + * Sends a message in a chat room + * + * @param String $chatid ID of Chat Room + * @param String $message Message to send + * + * @access public + * @return void + */ + function chatSay($chatid, $message) + { + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_send " . $chatid . " \"" . $this->encodeData($message) . "\"",0,0); + } + + /** + * Invites a user to a chat room + * + * @param String $chatid ID of Chat Room + * @param String $who Screenname of user + * @param String $message Note to include with invitiation + * + * @access public + * @return void + */ + function chatInvite($chatid, $who, $message) + { + $who = $this->normalize($who); + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_invite " . $chatid . " \"" . $this->encodeData($message) . "\" " . $who,0,0); + } + + /** + * Joins/Creates a new chat room + * + * @param String $name Name of the new chat room + * @param String $exchange Exchange of new chat room + * + * @access public + * @return void + */ + function joinNewChat($name, $exchange) + { + //Creates a new chat + $this->sflapSend(SFLAP_TYPE_DATA,"toc_chat_join " . $exchange . " \"" . $name . "\"",0,0); + } + + /** + * Disconnect error handler, attempts to reconnect in 60 seconds + * + * @param String $message Error message (desc of where error encountered etc) + * + * @access private + * @return void + */ + function derror($message) + { + $this->log($message); + $this->log("Error"); + fclose($this->myConnection); + if ((time() - $GLOBALS['errortime']) < 600){ + $this->log("Reconnecting in 60 Seconds"); + sleep(60); + } + $this->signon(); + $GLOBALS['errortime'] = time(); + } + + /** + * Returns connection type of socket (main or rvous etc) + * + * Helper method for recieve() + * + * @param Resource $sock Socket to determine type for + * + * @access private + * @return void + * @see receive + */ + function connectionType($sock) + { + //Is it the main connection? + if ($sock == $this->myConnection) + return CONN_TYPE_NORMAL; + else + { + for($i = 0;$imyDirectConnections);$i++) + { + if ($sock == $this->myDirectConnections[$i]->sock) + return $this->myDirectConnections[$i]->type; + } + } + return false; + } + + /** + * Checks for new data and calls appropriate methods + * + * This method is usually called in an infinite loop to keep checking for new data + * + * @access public + * @return void + * @see connectionType + */ + function receive() + { + //This function will be used to get the incoming data + //and it will be used to call the event handlers + + //First, get an array of sockets that have data that is ready to be read + $ready = array(); + $ready = $this->myConnections; + $numrdy = stream_select($ready, $w = NULL, $x = NULL,NULL); + + //Now that we've waited for something, go through the $ready + //array and read appropriately + + for($i = 0;$iconnectionType($ready[$i]); + if ($type == CONN_TYPE_NORMAL) + { + //Next step:Get the data sitting in the socket + $message = $this->read_from_aim(); + if (strlen($message) <= 0) + { + return; + } + + //Third step: Get the command from the server + @list($cmd, $rest) = explode(":", $message); + + //Fourth step, take the command, test the type, and pass it off + //to the correct internal handler. The internal handler will + //do what needs to be done on the class internals to allow + //it to work, then proceed to pass it off to the user created handle + //if there is one + $this->log($cmd); + switch($cmd) + { + case 'SIGN_ON': + $this->onSignOn($message); + break; + case 'CONFIG2': + $this->onConfig($message); + break; + case 'ERROR': + $this->onError($message); + break; + case 'NICK': + $this->onNick($message); + break; + case 'IM_IN2': + $this->onImIn($message); + break; + case 'UPDATE_BUDDY2': + $this->onUpdateBuddy($message); + break; + case 'EVILED': + $this->onWarn($message); + break; + case 'CHAT_JOIN': + $this->onChatJoin($message); + break; + case 'CHAT_IN': + $this->onChatIn($message); + break; + case 'CHAT_UPDATE_BUDDY': + $this->onChatUpdate($message); + break; + case 'CHAT_INVITE': + $this->onChatInvite($message); + break; + case 'CHAT_LEFT': + $this->onChatLeft($message); + break; + case 'GOTO_URL': + $this->onGotoURL($message); + break; + case 'DIR_STATUS': + $this->onDirStatus($message); + break; + case 'ADMIN_NICK_STATUS': + $this->onAdminNick($message); + break; + case 'ADMIN_PASSWD_STATUS': + $this->onAdminPasswd($message); + break; + case 'PAUSE': + $this->onPause($message); + break; + case 'RVOUS_PROPOSE': + $this->onRvous($message); + break; + default: + $this->log("Fell through: $message"); + $this->CatchAll($message); + break; + } + } + else + { + for($j = 0;$jmyDirectConnections);$j++) + { + if ($this->myDirectConnections[$j]->sock == $ready[$i]) + { + $dcon = $this->myDirectConnections[$j]; + break; + } + } + //Now read from the dcon + if ($dcon->type == CONN_TYPE_DC) + { + if ($dcon->readDIM() == false) + { + $this->closeDcon($dcon); + continue; + } + + $message['message'] = $dcon->lastMessage; + if ($message['message'] == "too big") + { + $this->sendDim("Connection dropped because you sent a message larger that " . MAX_DCON_SIZE . " bytes.", $dcon->connectedTo); + $this->closeDcon($dcon); + continue; + } + $message['from'] = $dcon->connectedTo; + $this->onDimIn($message); + } + } + } + $this->conn->myLastReceived=""; + //Now get out of this function because the handlers should take care + //of everything + } + + //The next block of code is all the event handlers needed by the class + //Some are left blank and only call the users handler because the class + //either does not support the command, or cannot do anything with it + // --------------------------------------------------------------------- + + /** + * Direct IM In Event Handler + * + * Called when Direct IM is received. + * Call's user handler (if available) for DimIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onDimIn($data) + { + $this->callHandler("DimIn", $data); + } + + /** + * Sign On Event Handler + * + * Called when Sign On event occurs. + * Call's user handler (if available) for SIGN_ON. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onSignOn($data) + { + $this->callHandler("SignOn", $data); + } + + /** + * Config Event Handler + * + * Called when Config data received. + * Call's user handler (if available) for Config. + * + * Loads buddy list and other info + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onConfig($data) + { + $this->log("onConfig Message: " . $data); + + if (strpos($data,"CONFIG2:") === false) + { + $this->log("get_buddy_list cannot be called at this time because I got $data"); + //return false; + } + $people = explode("\n",trim($data,"\n")); + //The first 3 elements of the array are who knows what, element 3 should be + //a letter followed by a person + + //AIM decided to add this wonderful new feature, the recent buddy thing, this kind of + //messes this funtion up, so we need to adapt it... unfortuneately, its not really + //clear how this works, so we are just going to add their name to the permit list. + + //Recent buddies I believe are in the format + //number:name:number.... I think the first number counts down from 25 how long its + //been... but I don't know the second number,,,, + + //TODO: Figure out the new recent buddies system + + //Note: adding that at the bottom is a quick hack and may have adverse consequences... + for($i = 1;$imyPermitList[] = $name; + break; + case 'd': + $this->myBlockedList[] = $name; + break; + case 'b': + $this->myBuddyList[] = $name; + break; + case 'done': + break; + default: + //This is assumed to be recent buddies... + $this->myPermitList[]=$name; + } + } + + //We only get the config message once, so now we should send our pd mode + + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_set_pdmode " . $this->myPdMode,0,0); + //Adds yourself to the permit list + //This is to fix an odd behavior if you have nobody on your list + //the server won't send the config command... so this takes care of it + $this->sflapSend(SFLAP_TYPE_DATA,"toc2_add_permit " . $this->normalize($this->myScreenName),0,0); + + //Now we allow the user to send a list, update anything they want, etc + $this->callHandler("Config", $data); + //Now that we have taken care of what the user wants, send the init_done message + $this->sflapSend(SFLAP_TYPE_DATA,"toc_init_done",0,0); + //'VOICE_UID' + //'FILE_GET_UID' + //'IMAGE_UID' + //'BUDDY_ICON_UID' + //'STOCKS_UID' + //'GAMES_UID' + $this->sflapSend(SFLAP_TYPE_DATA, "toc_set_caps " . IMAGE_UID . " " . FILE_SEND_UID ." " . FILE_GET_UID . " " . BUDDY_ICON_UID . "",0,0); + } + + + /** + * Error Event Handler + * + * Called when an Error occurs. + * Call's user handler (if available) for Error. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onError($data) + { + static $errarg = ''; + static $ERRORS = array( + 0=>'Success', + 901 =>'$errarg not currently available', + 902 =>'Warning of $errarg not currently available', + 903 =>'A message has been dropped, you are exceeding + the server speed limit', + 911 =>'Error validating input', + 912 =>'Invalid account', + 913 =>'Error encountered while processing request', + 914 =>'Service unavailable', + 950 =>'Chat in $errarg is unavailable.', + 960 =>'You are sending message too fast to $errarg', + 961 =>'You missed an im from $errarg because it was too big.', + 962 =>'You missed an im from $errarg because it was sent too fast.', + 970 =>'Failure', + 971 =>'Too many matches', + 972 =>'Need more qualifiers', + 973 =>'Dir service temporarily unavailable', + 974 =>'Email lookup restricted', + 975 =>'Keyword Ignored', + 976 =>'No Keywords', + 977 =>'Language not supported', + 978 =>'Country not supported', + 979 =>'Failure unknown $errarg', + 980 =>'Incorrect nickname or password.', + 981 =>'The service is temporarily unavailable.', + 982 =>'Your warning level is currently too high to sign on.', + 983 =>'You have been connecting and + disconnecting too frequently. Wait 10 minutes and try again. + If you continue to try, you will need to wait even longer.', + 989 =>'An unknown signon error has occurred $errarg' + ); + $data_array = explode(":", $data); + for($i=0; $ilog($errorstring . "\n"); + + $this->callHandler("Error", $data); + } + + /** + * Nick Event Handler + * + * Called when formatted own ScreenName is receieved + * Call's user handler (if available) for Nick. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onNick($data) + { + //This is our nick, so set a field called "myFormatSN" which will represent + //the actual name given by the server to us, NOT the normalized screen name + @list($cmd, $nick) = explode(":", $data); + $this->myFormatSN = $nick; + + $this->callHandler("Nick", $data); + } + + /** + * IM In Event Handler + * + * Called when an Instant Message is received. + * Call's user handler (if available) for IMIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onImIn($data) + { + //Perhaps we should add an internal log for debugging purposes?? + //But now, this should probably be handled by the user purely + + $this->callHandler("IMIn", $data); + } + + /** + * UpdateBuddy Event Handler + * + * Called when a Buddy Update is receieved. + * Call's user handler (if available) for UpdateBuddy. + * If info is about self, updates self info (Currently ownly warning). + * + * ToDo: Keep track of idle, warning etc on Buddy List + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onUpdateBuddy($data) + { + //Again, since our class currently does not deal with other people without + //outside help, then this is also probably best left to the user. Though + //we should probably allow this to replace the setMyInfo function above + //by handling the input if and only if it is us + //Check and see that this is the command expected + if (strpos($data,"UPDATE_BUDDY2:") == -1) + { + $this->log("A different message than expected was received"); + return false; + } + + //@list($cmd, $info['sn'], $info['online'], $info['warnlevel'], $info['signon'], $info['idle'], $info['uc']) = explode(":", $command['incoming']); + + //@list($cmd, $sn, $online, $warning, $starttime, $idletime, $uc) = explode(":", $data); + $info = $this->getMessageInfo($data); + if ($this->normalize($info['sn']) == $this->normalize($this->myScreenName)) + { + $warning = rtrim($info['warnlevel'],"%"); + $this->myWarnLevel = $warning; + $this->log("My warning level is $this->myWarnLevel %"); + } + + $this->callHandler("UpdateBuddy", $data); + } + + /** + * Warning Event Handler + * + * Called when bot is warned. + * Call's user handler (if available) for Warn. + * Updates internal warning level + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onWarn($data) + { + /* + For reference: + $command['incoming'] .= ":0"; + $it = explode(":", $command['incoming']); + $info['warnlevel'] = $it[1]; + $info['from'] = $it[2]; + */ + //SImply update our warning level + //@list($cmd, $newwarn, $user) = explode(":", $data); + + $info = $this->getMessageInfo($data); + + $this->setWarningLevel(trim($info['warnlevel'],"%")); + $this->log("My warning level is $this->myWarnLevel %"); + + $this->callHandler("Warned", $data); + } + + /** + * Chat Join Handler + * + * Called when bot joins a chat room. + * Call's user handler (if available) for ChatJoin. + * Adds chat room to internal chat room list. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatJoin($data) + { + @list($cmd, $rmid, $rmname) = explode(":", $data); + $this->myChatRooms[$rmid] = 0; + + $this->callHandler("ChatJoin", $data); + } + + /** + * Returns number of chat rooms bot is in + * + * @access public + * @param String $data Raw message from server + * @return int + */ + function getNumChats() + { + return count($this->myChatRooms); + } + + /** + * Chat Update Handler + * + * Called when bot received chat room data (user update). + * Call's user handler (if available) for ChatUpdate. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatUpdate($data) + { + $stuff = explode(":", $data); + $people = sizeof($stuff); + $people -= 2; + + $this->callHandler("ChatUpdate", $data); + } + + /** + * Chat Message In Handler + * + * Called when chat room message is received. + * Call's user handler (if available) for ChatIn. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatIn($data) + { + $this->callHandler("ChatIn", $data); + } + + + /** + * Chat Invite Handler + * + * Called when bot is invited to a chat room. + * Call's user handler (if available) for ChatInvite. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatInvite($data) + { + //@list($cmd, $name, $id, $from, $data) = explode(":", $data,6); + //$data = explode(":",$data,6); + //$nm = array(); + //@list($nm['cmd'],$nm['name'],$nm['id'],$nm['from'],$nm['message']) = $data; + + + $this->callHandler("ChatInvite", $data); + } + + /** + * Chat Left Handler + * + * Called when bot leaves a chat room + * Call's user handler (if available) for ChatLeft. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onChatLeft($data) + { + $info = $this->getMessageInfo($data); + unset($this->myChatRooms[$info['chatid']]); + $this->callHandler("ChatLeft", $data); + } + + /** + * Goto URL Handler + * + * Called on GotoURL. + * Call's user handler (if available) for GotoURL. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onGotoURL($data) + { + //This is of no use to the internal class + + $this->callHandler("GotoURL", $data); + } + + /** + * Dir Status Handler + * + * Called on DirStatus. + * Call's user handler (if available) for DirStatus. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onDirStatus($data) + { + //This is not currently suported + + $this->callHandler("DirStatus", $data); + } + + /** + * AdminNick Handler + * + * Called on AdminNick. + * Call's user handler (if available) for AdminNick. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onAdminNick($data) + { + //NOt particularly useful to us + $this->callHandler("AdminNick", $data); + } + + /** + * AdminPasswd Handler + * + * Called on AdminPasswd. + * Call's user handler (if available) for AdminPasswd. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onAdminPasswd($data) + { + //Also not particlualry useful to the internals + $this->callHandler("AdminPasswd", $data); + } + + /** + * Pause Handler + * + * Called on Pause. + * Call's user handler (if available) for Pause. + * No detailed info available for this / Unsupported. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onPause($data) + { + //This is pretty useless to us too... + + $this->callHandler("Pause", $data); + } + + /** + * Direct Connection Handler + * + * Called on Direct Connection Request(Rvous). + * Call's user handler (if available) for Rvous. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function onRvous($data) + { + $this->callHandler("Rvous", $data); + } + + /** + * CatchAll Handler + * + * Called for unrecognized commands. + * Logs unsupported messages to array. + * Call's user handler (if available) for CatchAll. + * + * @access private + * @param String $data Raw message from server + * @return void + */ + function CatchAll($data) + { + //Add to a log of unsupported messages. + + $this->unsupported[] = $data; + //$this->log($data); + //print_r($data); + + $this->callHandler("CatchAll", $data); + } + + /** + * Calls User Handler + * + * Calls registered handler for a specific event. + * + * @access private + * @param String $event Command (event) name (Rvous etc) + * @param String $data Raw message from server + * @see registerHandler + * @return void + */ + function callHandler($event, $data) + { + + if (isset($this->myEventHandlers[$event])) + { + //$function = $this->myEventHandlers[$event] . "(\$data);"; + //eval($function); + call_user_func($this->myEventHandlers[$event], $data); + } + else + { + $this->noHandler($data); + } + } + + /** + * Registers a user handler + * + * Handler List + * SignOn, Config, ERROR, NICK, IMIn, UpdateBuddy, Eviled, Warned, ChatJoin + * ChatIn, ChatUpdate, ChatInvite, ChatLeft, GotoURL, DirStatus, AdminNick + * AdminPasswd, Pause, Rvous, DimIn, CatchAll + * + * @access private + * @param String $event Event name + * @param String $handler User function to call + * @see callHandler + * @return boolean Returns true if successful + */ + function registerHandler($event, $handler) + { + if (is_callable($handler)) + { + $this->myEventHandlers[$event] = $handler; + return true; + } + else + { + return false; + } + } + + /** + * No user handler method fall back. + * + * Does nothing with message. + * + * @access public + * @param String $message Raw server message + * @return void + */ + function noHandler($message) + { + //This function intentionally left blank + //This is where the handlers will fall to for now. I plan on including a more + //efficent check to avoid the apparent stack jumps that this code will produce + //But for now, just fall into here, and be happy + return; + } + + //GLOBAL FUNCTIONS + + /** + * Finds type, and returns as part of array ['type'] + * Puts message in ['incoming'] + * + * Helper method for getMessageInfo. + * + * @access public + * @param String $message Raw server message + * @see msg_parse + * @see getMessageInfo + * @return array + */ + static function msg_type($message) + { + $command = array(); + @list($cmd, $rest) = explode(":", $message); + switch($cmd) + { + case 'IM_IN2': + $type = AIM_TYPE_MSG; + break; + + case 'UPDATE_BUDDY2': + $type = AIM_TYPE_UPDATEBUDDY; + break; + + case 'EVILED': + $type = AIM_TYPE_WARN; + break; + + case 'SIGN_ON': + $type = AIM_TYPE_SIGNON; + break; + + case 'NICK': + $type = AIM_TYPE_NICK; + break; + + case 'ERROR': + $type = AIM_TYPE_ERROR; + break; + + case 'CHAT_JOIN': + $type = AIM_TYPE_CHATJ; + break; + + case 'CHAT_IN': + $type = AIM_TYPE_CHATI; + break; + + case 'CHAT_UPDATE_BUDDY': + $type = AIM_TYPE_CHATUPDBUD; + break; + + case 'CHAT_INVITE': + $type = AIM_TYPE_CHATINV; + break; + + case 'CHAT_LEFT': + $type = AIM_TYPE_CHATLE; + break; + + case 'GOTO_URL': + $type = AIM_TYPE_URL; + break; + + case 'ADMIN_NICK_STATUS': + $type = AIM_TYPE_NICKSTAT; + break; + + case 'ADMIN_PASSWD_STATUS': + $type = AIM_TYPE_PASSSTAT; + break; + + case 'RVOUS_PROPOSE': + $type = AIM_TYPE_RVOUSP; + break; + + default: + $type = AIM_TYPE_NOT_IMPLEMENTED; + break; + } + $command['type'] = $type; + $command['incoming'] = $message; + return $command; + } + + /** + * Parses message and splits into info array + * + * Helper method for getMessageInfo. + * + * @access public + * @param String $command Message and type (after msg_type) + * @see msg_type + * @see getMessageInfo + * @return array + */ + static function msg_parse($command) + { + $info = array(); + switch($command['type']) + { + case AIM_TYPE_WARN: + $command['incoming'] .= ":0"; + $it = explode(":", $command['incoming']); + $info['warnlevel'] = $it[1]; + $info['from'] = $it[2]; + + break; + + case AIM_TYPE_MSG: + $it = explode(":", $command['incoming'],5); + $info['auto'] = $it[2]; + $info['from'] = $it[1]; + $info['message'] = $it[4]; + break; + + case AIM_TYPE_UPDATEBUDDY: + @list($cmd, $info['sn'], $info['online'], $info['warnlevel'], $info['signon'], $info['idle'], $info['uc']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_SIGNON: + @list($cmd, $info['version']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_NICK: + @list($cmd, $info['nickname']) = explode(":", $command['incoming']); + break; + case AIM_TYPE_ERROR: + @list($cmd, $info['errorcode'], $info['args']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_CHATJ: + @list($cmd, $info['chatid'], $info['chatname']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_CHATI: + @list($cmd, $info['chatid'], $info['user'], $info['whisper'], $info['message']) = explode(":", $command['incoming'],5); + break; + + case AIM_TYPE_CHATUPDBUD: + @list($cmd, $info['chatid'], $info['inside'], $info['userlist']) = explode(":", $command['incoming'],3); + break; + + case AIM_TYPE_CHATINV: + @list($cmd, $info['chatname'], $info['chatid'], $info['from'], $info['message']) = explode(":", $command['incoming'],5); + break; + + case AIM_TYPE_CHATLE: + @list($cmd, $info['chatid']) = explode(":", $command['incoming']); + break; + + case AIM_TYPE_URL: + @list($cmd, $info['windowname'], $info['url']) = explode(":", $command['incoming'],3); + break; + + case AIM_TYPE_RVOUSP: + @list($cmd,$info['user'],$info['uuid'],$info['cookie'],$info['seq'],$info['rip'],$info['pip'],$info['vip'],$info['port'],$info['tlvs']) = explode(":",$command['incoming'],10); + break; + + case AIM_TYPE_NICKSTAT: + case AIM_TYPE_PASSSTAT: + @list($cmd, $info['returncode'], $info['opt']) = explode(":", $command['incoming'],3); + break; + + default: + $info['command'] = $command['incoming']; + } + return $info; + } + + /** + * Returns a parsed message + * + * Calls msg_parse(msg_type( to first determine message type and then parse accordingly + * + * @access public + * @param String $command Raw server message + * @see msg_type + * @see msg_parse + * @return array + */ + static function getMessageInfo($message) + { + return self::msg_parse(self::msg_type($message)); + } + + /** + * Checks socket for end of file + * + * @access public + * @param Resource $socket Socket to check + * @return boolean true if end of file (socket) + */ + static function socketcheck($socket){ + $info = stream_get_meta_data($socket); + return $info['eof']; + //return(feof($socket)); + } +} + +?> diff --git a/plugins/Aim/extlib/phptoclib/dconnection.php b/plugins/Aim/extlib/phptoclib/dconnection.php new file mode 100755 index 0000000000..c6be25ffb9 --- /dev/null +++ b/plugins/Aim/extlib/phptoclib/dconnection.php @@ -0,0 +1,229 @@ +connect($ip,$port)) + { + sEcho("Connection failed constructor"); + $this->connected=false; + } + else + $this->connected=true; + + $this->lastMessage=""; + $this->lastReceived=""; + } + + function readDIM() + { + /* + if(!$this->stuffToRead()) + { + sEcho("Nothing to read"); + $this->lastMessage=$this->lastReceived=""; + return false; + } + */ + $head=fread($this->sock,6); + if(strlen($head)<=0) + { + sEcho("The direct connection has been closed"); + return false; + } + $minihead=unpack("a4ver/nsize",$head); + if($minihead['size'] <=0) + return; + $headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6))); + $allheader=array_merge($minihead,$headerinfo); + sEcho($allheader); + if($allheader['len']>0 && $allheader['len'] <= MAX_DIM_SIZE) + { + $left=$allheader['len']; + $stuff=""; + $nonin=0; + while(strlen($stuff) < $allheader['len'] && $nonin<3) + { + $stuffg=fread($this->sock,$left); + if(strlen($stuffg)<0) + { + $nonin++; + continue; + } + $left=$left - strlen($stuffg); + $stuff.=$stuffg; + } + $data=unpack("a*decoded",$stuff); + } + + else if($allheader['len'] > MAX_DIM_SIZE) + { + $data['decoded']="too big"; + } + + else + $data['decoded']=""; + $all=array_merge($allheader,$data); + + $this->lastReceived=$all; + $this->lastMessage=$all['decoded']; + + //$function=$this->DimInf . "(\$all);"; + //eval($function); + + return $all; + } + + function sendMessage($message,$sn) + { + //Make the "mini header" + $minihead=pack("a4n","ODC2",76); + $header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn); + $bighead=$minihead . $header; + while(strlen($bighead)<76) + $bighead.=pack("c",0); + + $tosend=$bighead . pack("a*",$message); + $w=array($this->sock); + stream_select($r=NULL,$w,$e=NULL,NULL); + //Now send it all + fputs($this->sock,$tosend,strlen($tosend)); + } + function stuffToRead() + { + //$info=stream_get_meta_data($this->sock); + //sEcho($info); + $s=array($this->sock); + $changed=stream_select($s,$fds=NULL,$m=NULL,0,20000); + return ($changed>0); + } + + function close() + { + $this->connected=false; + return fclose($this->sock); + } + + function connect($ip,$port) + { + $this->sock=fsockopen($ip,$port,$en,$es,3); + if(!$this->sock) + { sEcho("Connection failed"); + $this->sock=null; + return false; + } + return true; + } +} + + +class FileSendConnect +{ + var $sock; + var $lastReceived; + var $lastMessage; + var $connected; + var $cookie; + var $tpye=3; + + + function FileSendConnect($ip,$port) + { + if(!$this->connect($ip,$port)) + { + sEcho("Connection failed constructor"); + $this->connected=false; + } + else + $this->connected=true; + + $this->lastMessage=""; + $this->lastReceived=""; + } + + function readDIM() + { + + if(!$this->stuffToRead()) + { + sEcho("Nothing to read"); + $this->lastMessage=$this->lastReceived=""; + return; + } + + $minihead=unpack("a4ver/nsize",fread($this->sock,6)); + if($minihead['size'] <=0) + return; + $headerinfo=unpack("nchan/nsix/nzero/a6cookie/Npt1/Npt2/npt3/Nlen/Npt/npt0/ntype/Nzerom/a*sn",fread($this->sock,($minihead['size']-6))); + $allheader=array_merge($minihead,$headerinfo); + sEcho($allheader); + if($allheader['len']>0) + $data=unpack("a*decoded",fread($this->sock,$allheader['len'])); + else + $data['decoded']=""; + $all=array_merge($allheader,$data); + + $this->lastReceived=$all; + $this->lastMessage=$all['decoded']; + + //$function=$this->DimInf . "(\$all);"; + //eval($function); + + return $all; + } + + function sendMessage($message,$sn) + { + //Make the "mini header" + $minihead=pack("a4n","ODC2",76); + $header=pack("nnna6NNnNNnnNa*",1,6,0,$this->cookie,0,0,0,strlen($message),0,0,96,0,$sn); + $bighead=$minihead . $header; + while(strlen($bighead)<76) + $bighead.=pack("c",0); + + $tosend=$bighead . pack("a*",$message); + + //Now send it all + fwrite($this->sock,$tosend,strlen($tosend)); + } + function stuffToRead() + { + //$info=stream_get_meta_data($this->sock); + //sEcho($info); + $s=array($this->sock); + $changed=stream_select($s,$fds=NULL,$m=NULL,1); + return ($changed>0); + } + + function close() + { + $this->connected=false; + fclose($this->sock); + unset($this->sock); + return true; + } + + function connect($ip,$port) + { + $this->sock=fsockopen($ip,$port,$en,$es,3); + if(!$this->sock) + { sEcho("Connection failed to" . $ip . ":" . $port); + $this->sock=null; + return false; + } + return true; + } +} diff --git a/plugins/Imap/imapmanager.php b/plugins/Imap/imapmanager.php index e4fda58099..4c0edeaa1d 100644 --- a/plugins/Imap/imapmanager.php +++ b/plugins/Imap/imapmanager.php @@ -57,12 +57,14 @@ class ImapManager extends IoManager } /** - * Tell the i/o master we need one instance for each supporting site - * being handled in this process. + * Tell the i/o master we need one instance globally. + * Since this is a plugin manager, the plugin class itself will + * create one instance per site. This prevents the IoMaster from + * making more instances. */ public static function multiSite() { - return IoManager::INSTANCE_PER_SITE; + return IoManager::GLOBAL_SINGLE_ONLY; } /** diff --git a/lib/queued_xmpp.php b/plugins/Xmpp/Fake_XMPP.php similarity index 68% rename from lib/queued_xmpp.php rename to plugins/Xmpp/Fake_XMPP.php index 4b890c4ca4..a0f329ca08 100644 --- a/lib/queued_xmpp.php +++ b/plugins/Xmpp/Fake_XMPP.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Queue-mediated proxy class for outgoing XMPP messages. + * Instead of sending XMPP messages, retrieve the raw XML that would be sent * * PHP version 5 * @@ -31,10 +31,10 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -require_once INSTALLDIR . '/lib/jabber.php'; - -class Queued_XMPP extends XMPPHP_XMPP +class Fake_XMPP extends XMPPHP_XMPP { + public $would_be_sent = null; + /** * Constructor * @@ -63,54 +63,41 @@ class Queued_XMPP extends XMPPHP_XMPP */ public function send($msg, $timeout=NULL) { - $qm = QueueManager::get(); - $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); + $this->would_be_sent = $msg; } //@{ /** - * Stream i/o functions disabled; push input through processMessage() + * Stream i/o functions disabled; only do output */ public function connect($timeout = 30, $persistent = false, $sendinit = true) { - throw new Exception("Can't connect to server from XMPP queue proxy."); + throw new Exception("Can't connect to server from fake XMPP."); } public function disconnect() { - throw new Exception("Can't connect to server from XMPP queue proxy."); + throw new Exception("Can't connect to server from fake XMPP."); } public function process() { - throw new Exception("Can't read stream from XMPP queue proxy."); + throw new Exception("Can't read stream from fake XMPP."); } public function processUntil($event, $timeout=-1) { - throw new Exception("Can't read stream from XMPP queue proxy."); + throw new Exception("Can't read stream from fake XMPP."); } public function read() { - throw new Exception("Can't read stream from XMPP queue proxy."); + throw new Exception("Can't read stream from fake XMPP."); } public function readyToProcess() { - throw new Exception("Can't read stream from XMPP queue proxy."); + throw new Exception("Can't read stream from fake XMPP."); } //@} } diff --git a/plugins/Xmpp/README b/plugins/Xmpp/README new file mode 100644 index 0000000000..9bd71e9807 --- /dev/null +++ b/plugins/Xmpp/README @@ -0,0 +1,35 @@ +The XMPP plugin allows users to send and receive notices over the XMPP/Jabber/GTalk network. + +Installation +============ +add "addPlugin('xmpp', + array('setting'=>'value', 'setting2'=>'value2', ...);" +to the bottom of your config.php + +The daemon included with this plugin must be running. It will be started by +the plugin along with their other daemons when you run scripts/startdaemons.sh. +See the StatusNet README for more about queuing and daemons. + +Settings +======== +user*: user part of the jid +server*: server part of the jid +resource: resource part of the jid +port (5222): port on which to connect to the server +encryption (true): use encryption on the connection +host (same as server): host to connect to. Usually, you won't set this. +debug (false): log extra debug info +public: list of jid's that should get the public feed (firehose) + +* required +default values are in (parenthesis) + +Example +======= +addPlugin('xmpp', array( + 'user=>'update', + 'server=>'identi.ca', + 'password'=>'...', + 'public'=>array('bob@aol.com', 'sue@google.com') +)); + diff --git a/plugins/Xmpp/Sharing_XMPP.php b/plugins/Xmpp/Sharing_XMPP.php new file mode 100644 index 0000000000..4b69125da3 --- /dev/null +++ b/plugins/Xmpp/Sharing_XMPP.php @@ -0,0 +1,43 @@ +. + * + * @category Jabber + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +class Sharing_XMPP extends XMPPHP_XMPP +{ + function getSocket() + { + return $this->socket; + } +} diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php new file mode 100644 index 0000000000..b9551b8526 --- /dev/null +++ b/plugins/Xmpp/XmppPlugin.php @@ -0,0 +1,247 @@ +. + * + * @category IM + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} + +/** + * Plugin for XMPP + * + * @category Plugin + * @package StatusNet + * @author Evan Prodromou + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class XmppPlugin extends ImPlugin +{ + public $server = null; + public $port = 5222; + public $user = 'update'; + public $resource = null; + public $encryption = true; + public $password = null; + public $host = null; // only set if != server + public $debug = false; // print extra debug info + + public $transport = 'xmpp'; + + protected $fake_xmpp; + + function getDisplayName(){ + return _m('XMPP/Jabber/GTalk'); + } + + function normalize($screenname) + { + if (preg_match("/(?:([^\@]+)\@)?([^\/]+)(?:\/(.*))?$/", $screenname, $matches)) { + $node = $matches[1]; + $server = $matches[2]; + return strtolower($node.'@'.$server); + } else { + return null; + } + } + + function daemon_screenname() + { + $ret = $this->user . '@' . $this->server; + if($this->resource) + { + return $ret . '/' . $this->resource; + }else{ + return $ret; + } + } + + function validate($screenname) + { + // Cheap but effective + return Validate::email($screenname); + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'Sharing_XMPP': + case 'Fake_XMPP': + include_once $dir . '/'.$cls.'.php'; + return false; + case 'XmppManager': + include_once $dir . '/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + function onStartImDaemonIoManagers(&$classes) + { + parent::onStartImDaemonIoManagers(&$classes); + $classes[] = new XmppManager($this); // handles pings/reconnects + return true; + } + + function microiduri($screenname) + { + return 'xmpp:' . $screenname; + } + + function send_message($screenname, $body) + { + $this->fake_xmpp->message($screenname, $body, 'chat'); + $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); + return true; + } + + function send_notice($screenname, $notice) + { + $msg = $this->format_notice($notice); + $entry = $this->format_entry($notice); + + $this->fake_xmpp->message($screenname, $msg, 'chat', null, $entry); + $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); + 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 format_entry($notice) + { + $profile = $notice->getProfile(); + + $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; + } + + function receive_raw_message($pl) + { + $from = $this->normalize($pl['from']); + + if ($pl['type'] != 'chat') { + common_log(LOG_WARNING, "Ignoring message of type ".$pl['type']." from $from."); + return true; + } + + if (mb_strlen($pl['body']) == 0) { + common_log(LOG_WARNING, "Ignoring message with empty body from $from."); + return true; + } + + return $this->handle_incoming($from, $pl['body']); + } + + function initialize(){ + if(!isset($this->server)){ + throw new Exception("must specify a server"); + } + if(!isset($this->port)){ + throw new Exception("must specify a port"); + } + if(!isset($this->user)){ + throw new Exception("must specify a user"); + } + if(!isset($this->password)){ + throw new Exception("must specify a password"); + } + + $this->fake_xmpp = new Fake_XMPP($this->host ? + $this->host : + $this->server, + $this->port, + $this->user, + $this->password, + $this->resource, + $this->server, + $this->debug ? + true : false, + $this->debug ? + XMPPHP_Log::LEVEL_VERBOSE : null + ); + return true; + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'XMPP', + 'version' => STATUSNET_VERSION, + 'author' => 'Craig Andrews, Evan Prodromou', + 'homepage' => 'http://status.net/wiki/Plugin:XMPP', + 'rawdescription' => + _m('The XMPP plugin allows users to send and receive notices over the XMPP/Jabber network.')); + return true; + } +} + diff --git a/plugins/Xmpp/xmppmanager.php b/plugins/Xmpp/xmppmanager.php new file mode 100644 index 0000000000..87d8186684 --- /dev/null +++ b/plugins/Xmpp/xmppmanager.php @@ -0,0 +1,279 @@ +. + */ + +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 handled. + * + * 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 ImManager +{ + protected $lastping = null; + protected $pingid = null; + + public $conn = null; + + const PING_INTERVAL = 120; + + + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + if(parent::start($master)) + { + $this->connect(); + return true; + }else{ + return false; + } + } + + function send_raw_message($data) + { + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + $this->conn->send($data); + return true; + } + + /** + * 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; + } + + /** + * Process XMPP events that have come in over the wire. + * @fixme may kill process on XMPP error + * @param resource $socket + */ + public function handleInput($socket) + { + # Process the queue for as long as needed + try { + 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()); + } + } + + /** + * Lists the IM 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() + { + $this->connect(); + if($this->conn){ + return array($this->conn->getSocket()); + }else{ + return array(); + } + } + + /** + * 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) + { + $now = time(); + if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { + try { + $this->send_ping(); + } catch (XMPPHP_Exception $e) { + common_log(LOG_ERR, "Got an XMPPHP_Exception: " . $e->getMessage()); + die($e->getMessage()); + } + } + } + + function connect() + { + if (!$this->conn || $this->conn->isDisconnected()) { + $resource = 'queue' . posix_getpid(); + $this->conn = new Sharing_XMPP($this->plugin->host ? + $this->plugin->host : + $this->plugin->server, + $this->plugin->port, + $this->plugin->user, + $this->plugin->password, + $this->plugin->resource, + $this->plugin->server, + $this->plugin->debug ? + true : false, + $this->plugin->debug ? + XMPPHP_Log::LEVEL_VERBOSE : null + ); + + if (!$this->conn) { + return false; + } + $this->conn->addEventHandler('message', 'handle_xmpp_message', $this); + $this->conn->addEventHandler('reconnect', 'handle_xmpp_reconnect', $this); + $this->conn->setReconnectTimeout(600); + + $this->conn->autoSubscribe(); + $this->conn->useEncryption($this->plugin->encryption); + + try { + $this->conn->connect(true); // true = persistent connection + } catch (XMPPHP_Exception $e) { + common_log(LOG_ERR, $e->getMessage()); + return false; + } + + $this->conn->processUntil('session_start'); + $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); + } + return $this->conn; + } + + function send_ping() + { + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + $now = time(); + if (!isset($this->pingid)) { + $this->pingid = 0; + } else { + $this->pingid++; + } + + common_log(LOG_DEBUG, "Sending ping #{$this->pingid}"); + $this->conn->send(""); + $this->lastping = $now; + return true; + } + + function handle_xmpp_message(&$pl) + { + $this->plugin->enqueue_incoming_raw($pl); + return true; + } + + /** + * Callback for Jabber reconnect event + * @param $pl + */ + function handle_xmpp_reconnect(&$pl) + { + common_log(LOG_NOTICE, 'XMPP reconnected'); + + $this->conn->processUntil('session_start'); + $this->send_presence(_m('Send me a message to post a notice'), 'available', null, 'available', 100); + } + + /** + * sends a presence stanza on the XMPP 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 send_presence($status, $show='available', $to=null, + $type = 'available', $priority=null) + { + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + $this->conn->presence($status, $show, $to, $type, $priority); + return true; + } + + /** + * sends a "special" presence stanza on the XMPP 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 send_presence() + */ + + function special_presence($type, $to=null, $show=null, $status=null) + { + // FIXME: why use this instead of send_presence()? + $this->connect(); + if (!$this->conn || $this->conn->isDisconnected()) { + return false; + } + + $to = htmlspecialchars($to); + $status = htmlspecialchars($status); + + $out = "conn->send($out); + return true; + } +} diff --git a/scripts/getvaliddaemons.php b/scripts/getvaliddaemons.php index a332e06b58..80c21bce58 100755 --- a/scripts/getvaliddaemons.php +++ b/scripts/getvaliddaemons.php @@ -39,9 +39,7 @@ $daemons = array(); $daemons[] = INSTALLDIR.'/scripts/queuedaemon.php'; -if(common_config('xmpp','enabled')) { - $daemons[] = INSTALLDIR.'/scripts/xmppdaemon.php'; -} +$daemons[] = INSTALLDIR.'/scripts/imdaemon.php'; if (Event::handle('GetValidDaemons', array(&$daemons))) { foreach ($daemons as $daemon) { diff --git a/scripts/xmppdaemon.php b/scripts/imdaemon.php similarity index 66% rename from scripts/xmppdaemon.php rename to scripts/imdaemon.php index fd7cf055b4..c37a26b7c0 100755 --- a/scripts/xmppdaemon.php +++ b/scripts/imdaemon.php @@ -23,34 +23,32 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); $shortoptions = 'fi::'; $longoptions = array('id::', 'foreground'); -$helptext = <<get_id()); + $master = new ImMaster($this->get_id()); $master->init(); $master->service(); @@ -61,7 +59,7 @@ class XMPPDaemon extends SpawningDaemon } -class XmppMaster extends IoMaster +class ImMaster extends IoMaster { /** * Initialize IoManagers for the currently configured site @@ -69,20 +67,17 @@ class XmppMaster extends IoMaster */ function initManagers() { - // @fixme right now there's a hack in QueueManager to determine - // which queues to subscribe to based on the master class. - $this->instantiate('QueueManager'); - $this->instantiate('XmppManager'); + $classes = array(); + if (Event::handle('StartImDaemonIoManagers', array(&$classes))) { + $classes[] = 'QueueManager'; + } + Event::handle('EndImDaemonIoManagers', array(&$classes)); + foreach ($classes as $class) { + $this->instantiate($class); + } } } -// Abort immediately if xmpp is not enabled, otherwise the daemon chews up -// lots of CPU trying to connect to unconfigured servers -if (common_config('xmpp','enabled')==false) { - print "Aborting daemon - xmpp is disabled\n"; - exit(); -} - if (have_option('i', 'id')) { $id = get_option_value('i', 'id'); } else if (count($args) > 0) { @@ -93,6 +88,6 @@ if (have_option('i', 'id')) { $foreground = have_option('f', 'foreground'); -$daemon = new XMPPDaemon($id, !$foreground); +$daemon = new ImDaemon($id, !$foreground); $daemon->runOnce(); diff --git a/scripts/queuedaemon.php b/scripts/queuedaemon.php index a9cfda6d72..bedd14b1a3 100755 --- a/scripts/queuedaemon.php +++ b/scripts/queuedaemon.php @@ -21,7 +21,7 @@ define('INSTALLDIR', realpath(dirname(__FILE__) . '/..')); $shortoptions = 'fi:at:'; -$longoptions = array('id=', 'foreground', 'all', 'threads=', 'skip-xmpp', 'xmpp-only'); +$longoptions = array('id=', 'foreground', 'all', 'threads='); /** * Attempts to get a count of the processors available on the current system @@ -163,13 +163,6 @@ if (!$threads) { $daemonize = !(have_option('f') || have_option('--foreground')); $all = have_option('a') || have_option('--all'); -if (have_option('--skip-xmpp')) { - define('XMPP_EMERGENCY_FLAG', true); -} -if (have_option('--xmpp-only')) { - define('XMPP_ONLY_FLAG', true); -} - $daemon = new QueueDaemon($id, $daemonize, $threads, $all); $daemon->runOnce(); diff --git a/scripts/stopdaemons.sh b/scripts/stopdaemons.sh index c790f1f349..bc1230e645 100755 --- a/scripts/stopdaemons.sh +++ b/scripts/stopdaemons.sh @@ -23,8 +23,8 @@ SDIR=`dirname $0` DIR=`php $SDIR/getpiddir.php` -for f in jabberhandler ombhandler publichandler smshandler pinghandler \ - xmppconfirmhandler xmppdaemon twitterhandler facebookhandler \ +for f in ombhandler smshandler pinghandler \ + twitterhandler facebookhandler \ twitterstatusfetcher synctwitterfriends pluginhandler rsscloudhandler; do FILES="$DIR/$f.*.pid" From 065140400018dec28ed82fd31a754c94b7b87521 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 28 Jan 2010 16:08:42 -0500 Subject: [PATCH 007/655] Add backwards compatibility for the XMPP configuration before XMPP was made into a plugin --- lib/statusnet.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/statusnet.php b/lib/statusnet.php index beeb26cccd..2654ba5736 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -274,7 +274,6 @@ class StatusNet } // Backwards compatibility - if (array_key_exists('memcached', $config)) { if ($config['memcached']['enabled']) { addPlugin('Memcache', array('servers' => $config['memcached']['server'])); @@ -284,6 +283,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'] + )); + } + } } } From 886e28aaa9e4e9a524d5b1a933a2d2a13994aec9 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Tue, 16 Mar 2010 14:18:37 -0700 Subject: [PATCH 008/655] Stub plugins administration panel, allows for disabling/re-enabling plugins from the default plugins list. --- actions/plugindisable.php | 78 +++++++++++++ actions/pluginenable.php | 166 ++++++++++++++++++++++++++ actions/pluginsadminpanel.php | 110 ++++++++++++++++++ lib/adminpanelaction.php | 8 ++ lib/default.php | 3 +- lib/plugindisableform.php | 93 +++++++++++++++ lib/pluginenableform.php | 114 ++++++++++++++++++ lib/pluginlist.php | 213 ++++++++++++++++++++++++++++++++++ lib/router.php | 7 ++ lib/statusnet.php | 5 + 10 files changed, 796 insertions(+), 1 deletion(-) create mode 100644 actions/plugindisable.php create mode 100644 actions/pluginenable.php create mode 100644 actions/pluginsadminpanel.php create mode 100644 lib/plugindisableform.php create mode 100644 lib/pluginenableform.php create mode 100644 lib/pluginlist.php diff --git a/actions/plugindisable.php b/actions/plugindisable.php new file mode 100644 index 0000000000..7f107b3335 --- /dev/null +++ b/actions/plugindisable.php @@ -0,0 +1,78 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin enable action. + * + * (Re)-enables a plugin from the default plugins list. + * + * Takes parameters: + * + * - plugin: plugin name + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class PluginDisableAction extends PluginEnableAction +{ + /** + * Value to save into $config['plugins']['disable-'] + */ + protected function overrideValue() + { + return 1; + } + + protected function successShortTitle() + { + // TRANS: Page title for AJAX form return when a disabling a plugin. + return _m('plugin', 'Disabled'); + } + + protected function successNextForm() + { + return new EnablePluginForm($this, $this->plugin); + } +} + + diff --git a/actions/pluginenable.php b/actions/pluginenable.php new file mode 100644 index 0000000000..2dbb3e3956 --- /dev/null +++ b/actions/pluginenable.php @@ -0,0 +1,166 @@ +. + * + * PHP version 5 + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + exit(1); +} + +/** + * Plugin enable action. + * + * (Re)-enables a plugin from the default plugins list. + * + * Takes parameters: + * + * - plugin: plugin name + * - token: session token to prevent CSRF attacks + * - ajax: boolean; whether to return Ajax or full-browser results + * + * Only works if the current user is logged in. + * + * @category Action + * @package StatusNet + * @author Brion Vibber + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPLv3 + * @link http://status.net/ + */ + +class PluginEnableAction extends Action +{ + var $user; + var $plugin; + + /** + * Check pre-requisites and instantiate attributes + * + * @param Array $args array of arguments (URL, GET, POST) + * + * @return boolean success flag + */ + + function prepare($args) + { + parent::prepare($args); + + // @fixme these are pretty common, should a parent class factor these out? + + // Only allow POST requests + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError(_('This action only accepts POST requests.')); + return false; + } + + // CSRF protection + + $token = $this->trimmed('token'); + + if (!$token || $token != common_session_token()) { + $this->clientError(_('There was a problem with your session token.'. + ' Try again, please.')); + return false; + } + + // Only for logged-in users + + $this->user = common_current_user(); + + if (empty($this->user)) { + $this->clientError(_('Not logged in.')); + return false; + } + + if (!AdminPanelAction::canAdmin('plugins')) { + $this->clientError(_('You cannot administer plugins.')); + return false; + } + + $this->plugin = $this->arg('plugin'); + $defaultPlugins = common_config('plugins', 'default'); + if (!array_key_exists($this->plugin, $defaultPlugins)) { + $this->clientError(_('No such plugin.')); + return false; + } + + return true; + } + + /** + * Handle request + * + * Does the subscription and returns results. + * + * @param Array $args unused. + * + * @return void + */ + + function handle($args) + { + $key = 'disable-' . $this->plugin; + Config::save('plugins', $key, $this->overrideValue()); + + // @fixme this is a pretty common pattern and should be refactored down + if ($this->boolean('ajax')) { + $this->startHTML('text/xml;charset=utf-8'); + $this->elementStart('head'); + $this->element('title', null, $this->successShortTitle()); + $this->elementEnd('head'); + $this->elementStart('body'); + $form = $this->successNextForm(); + $form->show(); + $this->elementEnd('body'); + $this->elementEnd('html'); + } else { + $url = common_local_url('pluginsadminpanel'); + common_redirect($url, 303); + } + } + + /** + * Value to save into $config['plugins']['disable-'] + */ + protected function overrideValue() + { + return 0; + } + + protected function successShortTitle() + { + // TRANS: Page title for AJAX form return when enabling a plugin. + return _m('plugin', 'Enabled'); + } + + protected function successNextForm() + { + return new DisablePluginForm($this, $this->plugin); + } +} diff --git a/actions/pluginsadminpanel.php b/actions/pluginsadminpanel.php new file mode 100644 index 0000000000..bc400bd514 --- /dev/null +++ b/actions/pluginsadminpanel.php @@ -0,0 +1,110 @@ +. + * + * @category Settings + * @package StatusNet + * @author Brion Vibber + * @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')) { + exit(1); +} + +/** + * Plugins settings + * + * @category Admin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class PluginsadminpanelAction extends AdminPanelAction +{ + + /** + * Returns the page title + * + * @return string page title + */ + + function title() + { + // TRANS: Tab and title for plugins admin panel. + return _('Plugins'); + } + + /** + * Instructions for using this form. + * + * @return string instructions + */ + + function getInstructions() + { + // TRANS: Instructions at top of plugin admin page. + return _('Additional plugins can be enabled and configured manually. ' . + 'See the online plugin ' . + 'documentation for more details.'); + } + + /** + * Show the plugins admin panel form + * + * @return void + */ + + function showForm() + { + $this->elementStart('fieldset', array('id' => 'settings_plugins_default')); + + // TRANS: Admin form section header + $this->element('legend', null, _('Default plugins'), 'default'); + + $this->showDefaultPlugins(); + + $this->elementEnd('fieldset'); + } + + /** + * Until we have a general plugin metadata infrastructure, for now + * we'll just list up the ones we know from the global default + * plugins list. + */ + protected function showDefaultPlugins() + { + $plugins = array_keys(common_config('plugins', 'default')); + natsort($plugins); + + if ($plugins) { + $list = new PluginList($plugins, $this); + $list->show(); + } else { + $this->element('p', null, + _('All default plugins have been disabled from the ' . + 'site\'s configuration file.')); + } + } +} diff --git a/lib/adminpanelaction.php b/lib/adminpanelaction.php index a927e23336..d87981b6a7 100644 --- a/lib/adminpanelaction.php +++ b/lib/adminpanelaction.php @@ -407,6 +407,14 @@ class AdminPanelNav extends Widget $menu_title, $action_name == 'snapshotadminpanel', 'nav_snapshot_admin_panel'); } + if (AdminPanelAction::canAdmin('plugins')) { + // TRANS: Menu item title/tooltip + $menu_title = _('Plugins configuration'); + // TRANS: Menu item for site administration + $this->out->menuItem(common_local_url('pluginsadminpanel'), _('Plugins'), + $menu_title, $action_name == 'pluginsadminpanel', 'nav_design_admin_panel'); + } + Event::handle('EndAdminPanelNav', array($this)); } $this->action->elementEnd('ul'); diff --git a/lib/default.php b/lib/default.php index 10f3f1a97e..eb0cb0b64a 100644 --- a/lib/default.php +++ b/lib/default.php @@ -285,8 +285,9 @@ $default = 'RSSCloud' => null, 'OpenID' => null), ), + 'pluginlist' => array(), 'admin' => - array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions', 'sitenotice')), + array('panels' => array('design', 'site', 'user', 'paths', 'access', 'sessions', 'sitenotice', 'plugins')), 'singleuser' => array('enabled' => false, 'nickname' => null), diff --git a/lib/plugindisableform.php b/lib/plugindisableform.php new file mode 100644 index 0000000000..3cbabdb2ce --- /dev/null +++ b/lib/plugindisableform.php @@ -0,0 +1,93 @@ +. + * + * @category Form + * @package StatusNet + * @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); +} + +/** + * Form for joining a group + * + * @category Form + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see PluginEnableForm + */ + +class PluginDisableForm extends PluginEnableForm +{ + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + return 'plugin-disable-' . $this->plugin; + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_plugin_disable'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('plugindisable', + array('plugin' => $this->plugin)); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + // TRANS: Plugin admin panel controls + $this->out->submit('submit', _m('plugin', 'Disable')); + } + +} diff --git a/lib/pluginenableform.php b/lib/pluginenableform.php new file mode 100644 index 0000000000..8683ffd0be --- /dev/null +++ b/lib/pluginenableform.php @@ -0,0 +1,114 @@ +. + * + * @category Form + * @package StatusNet + * @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/form.php'; + +/** + * Form for joining a group + * + * @category Form + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + * @see PluginDisableForm + */ + +class PluginEnableForm extends Form +{ + /** + * Plugin to enable/disable + */ + + var $plugin = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param string $plugin plugin to enable/disable + */ + + function __construct($out=null, $plugin=null) + { + parent::__construct($out); + + $this->plugin = $plugin; + } + + /** + * ID of the form + * + * @return string ID of the form + */ + + function id() + { + return 'plugin-enable-' . $this->plugin; + } + + /** + * class of the form + * + * @return string of the form class + */ + + function formClass() + { + return 'form_plugin_enable'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return common_local_url('pluginenable', + array('plugin' => $this->plugin)); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + // TRANS: Plugin admin panel controls + $this->out->submit('submit', _m('plugin', 'Enable')); + } +} diff --git a/lib/pluginlist.php b/lib/pluginlist.php new file mode 100644 index 0000000000..07a17ba397 --- /dev/null +++ b/lib/pluginlist.php @@ -0,0 +1,213 @@ +. + * + * @category Settings + * @package StatusNet + * @author Brion Vibber + * @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')) { + exit(1); +} + +require INSTALLDIR . "/lib/pluginenableform.php"; +require INSTALLDIR . "/lib/plugindisableform.php"; + +/** + * Plugin list + * + * @category Admin + * @package StatusNet + * @author Brion Vibber + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class PluginList extends Widget +{ + var $plugins = array(); + + function __construct($plugins, $out) + { + parent::__construct($out); + $this->plugins = $plugins; + } + + function show() + { + $this->startList(); + $this->showPlugins(); + $this->endList(); + } + + function startList() + { + $this->out->elementStart('table', 'plugin_list'); + } + + function endList() + { + $this->out->elementEnd('table'); + } + + function showPlugins() + { + foreach ($this->plugins as $plugin) { + $pli = $this->newListItem($plugin); + $pli->show(); + } + } + + function newListItem($plugin) + { + return new PluginListItem($plugin, $this->out); + } +} + +class PluginListItem extends Widget +{ + /** Current plugin. */ + var $plugin = null; + + /** Local cache for plugin version info */ + protected static $versions = false; + + function __construct($plugin, $out) + { + parent::__construct($out); + $this->plugin = $plugin; + } + + function show() + { + $meta = $this->metaInfo(); + + $this->out->elementStart('tr', array('id' => 'plugin-' . $this->plugin)); + + // Name and controls + $this->out->elementStart('td'); + $this->out->elementStart('div'); + if (!empty($meta['homepage'])) { + $this->out->elementStart('a', array('href' => $meta['homepage'])); + } + $this->out->text($this->plugin); + if (!empty($meta['homepage'])) { + $this->out->elementEnd('a'); + } + $this->out->elementEnd('div'); + + $form = $this->getControlForm(); + $form->show(); + + $this->out->elementEnd('td'); + + // Version and authors + $this->out->elementStart('td'); + if (!empty($meta['version'])) { + $this->out->elementStart('div'); + $this->out->text($meta['version']); + $this->out->elementEnd('div'); + } + if (!empty($meta['author'])) { + $this->out->elementStart('div'); + $this->out->text($meta['author']); + $this->out->elementEnd('div'); + } + $this->out->elementEnd('td'); + + // Description + $this->out->elementStart('td'); + if (!empty($meta['rawdescription'])) { + $this->out->raw($meta['rawdescription']); + } + $this->out->elementEnd('td'); + + $this->out->elementEnd('tr'); + } + + /** + * Pull up the appropriate control form for this plugin, depending + * on its current state. + * + * @return Form + */ + protected function getControlForm() + { + $key = 'disable-' . $this->plugin; + if (common_config('plugins', $key)) { + return new PluginEnableForm($this->out, $this->plugin); + } else { + return new PluginDisableForm($this->out, $this->plugin); + } + } + + /** + * Grab metadata about this plugin... + * Warning: horribly inefficient and may explode! + * Doesn't work for disabled plugins either. + * + * @fixme pull structured data from plugin source + */ + function metaInfo() + { + $versions = self::getPluginVersions(); + $found = false; + + foreach ($versions as $info) { + // hack for URL shorteners... "LilUrl (ur1.ca)" etc + list($name, ) = explode(' ', $info['name']); + + if ($name == $this->plugin) { + if ($found) { + // hack for URL shorteners... + $found['rawdescription'] .= "
\n" . $info['rawdescription']; + } else { + $found = $info; + } + } + } + + if ($found) { + return $found; + } else { + return array('name' => $this->plugin, + 'rawdescription' => _m('plugin-description', + '(Plugin descriptions unavailable when disabled.)')); + } + } + + /** + * Lazy-load the set of active plugin version info + * @return array + */ + protected static function getPluginVersions() + { + if (!is_array(self::$versions)) { + $versions = array(); + Event::handle('PluginVersion', array(&$versions)); + self::$versions = $versions; + } + return self::$versions; + } +} diff --git a/lib/router.php b/lib/router.php index a48ee875e1..9fe2f60ae7 100644 --- a/lib/router.php +++ b/lib/router.php @@ -658,6 +658,13 @@ class Router $m->connect('admin/sessions', array('action' => 'sessionsadminpanel')); $m->connect('admin/sitenotice', array('action' => 'sitenoticeadminpanel')); $m->connect('admin/snapshot', array('action' => 'snapshotadminpanel')); + $m->connect('admin/plugins', array('action' => 'pluginsadminpanel')); + $m->connect('admin/plugins/enable/:plugin', + array('action' => 'pluginenable'), + array('plugin' => '[A-Za-z0-9_]+')); + $m->connect('admin/plugins/disable/:plugin', + array('action' => 'plugindisable'), + array('plugin' => '[A-Za-z0-9_]+')); $m->connect('getfile/:filename', array('action' => 'getfile'), diff --git a/lib/statusnet.php b/lib/statusnet.php index 776cfb579b..98f25c8a08 100644 --- a/lib/statusnet.php +++ b/lib/statusnet.php @@ -163,6 +163,11 @@ class StatusNet { // Load default plugins foreach (common_config('plugins', 'default') as $name => $params) { + $key = 'disable-' . $name; + if (common_config('plugins', $key)) { + continue; + } + if (is_null($params)) { addPlugin($name); } else if (is_array($params)) { From 64b5ea2e6238017fba5ad53274d26d53ed2b5fb3 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 24 Mar 2010 14:18:06 -0700 Subject: [PATCH 009/655] Use InnoDB and UTF-8 options when creating user_im_prefs table, to match others --- db/statusnet.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/statusnet.sql b/db/statusnet.sql index d1cd670750..16d09a11f7 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -647,7 +647,7 @@ create table user_im_prefs ( constraint primary key (user_id, transport), constraint unique key `transport_screenname_key` ( `transport` , `screenname` ) -); +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; create table conversation ( id integer auto_increment primary key comment 'unique identifier', From abf2ce873b23f238041c4b4190dc709b2d40774d Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 24 Mar 2010 14:18:25 -0700 Subject: [PATCH 010/655] Avoid notice when reporting DB errors for objects that don't have an 'id' field --- classes/Memcached_DataObject.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/classes/Memcached_DataObject.php b/classes/Memcached_DataObject.php index bc4c3a000c..af148ef693 100644 --- a/classes/Memcached_DataObject.php +++ b/classes/Memcached_DataObject.php @@ -502,7 +502,7 @@ class Memcached_DataObject extends Safe_DataObject function raiseError($message, $type = null, $behaviour = null) { $id = get_class($this); - if ($this->id) { + if (!empty($this->id)) { $id .= ':' . $this->id; } throw new ServerException("[$id] DB_DataObject error [$type]: $message"); From 9398c61ed321472ab7131c063ed319a51d2b2bef Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 1 Mar 2010 21:40:42 -0500 Subject: [PATCH 011/655] Use PHP exceptions for PEAR error handling. Allows for the common try/catch construct, which makes error handling cleaner and easier. --- index.php | 97 ++++++++++++++++++++++++++------------------------ lib/common.php | 12 +++++++ 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/index.php b/index.php index 6bfbc11da8..78c4de62a3 100644 --- a/index.php +++ b/index.php @@ -37,8 +37,6 @@ define('INSTALLDIR', dirname(__FILE__)); define('STATUSNET', true); define('LACONICA', true); // compatibility -require_once INSTALLDIR . '/lib/common.php'; - $user = null; $action = null; @@ -68,52 +66,63 @@ function getPath($req) */ function handleError($error) { - if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { - return; - } + try { - $logmsg = "PEAR error: " . $error->getMessage(); - if (common_config('site', 'logdebug')) { - $logmsg .= " : ". $error->getDebugInfo(); - } - // DB queries often end up with a lot of newlines; merge to a single line - // for easier grepability... - $logmsg = str_replace("\n", " ", $logmsg); - common_log(LOG_ERR, $logmsg); - - // @fixme backtrace output should be consistent with exception handling - if (common_config('site', 'logdebug')) { - $bt = $error->getBacktrace(); - foreach ($bt as $n => $line) { - common_log(LOG_ERR, formatBacktraceLine($n, $line)); + if ($error->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + return; } - } - if ($error instanceof DB_DataObject_Error - || $error instanceof DB_Error - ) { - $msg = sprintf( - _( - 'The database for %s isn\'t responding correctly, '. - 'so the site won\'t work properly. '. - 'The site admins probably know about the problem, '. - 'but you can contact them at %s to make sure. '. - 'Otherwise, wait a few minutes and try again.' - ), - common_config('site', 'name'), - common_config('site', 'email') - ); - } else { - $msg = _( - 'An important error occured, probably related to email setup. '. - 'Check logfiles for more info..' - ); - } - $dac = new DBErrorAction($msg, 500); - $dac->showPage(); + $logmsg = "PEAR error: " . $error->getMessage(); + if ($error instanceof PEAR_Exception && common_config('site', 'logdebug')) { + $logmsg .= " : ". $error->toText(); + } + // DB queries often end up with a lot of newlines; merge to a single line + // for easier grepability... + $logmsg = str_replace("\n", " ", $logmsg); + common_log(LOG_ERR, $logmsg); + + // @fixme backtrace output should be consistent with exception handling + if (common_config('site', 'logdebug')) { + $bt = $error->getTrace(); + foreach ($bt as $n => $line) { + common_log(LOG_ERR, formatBacktraceLine($n, $line)); + } + } + if ($error instanceof DB_DataObject_Error + || $error instanceof DB_Error + || ($error instanceof PEAR_Exception && $error->getCode() == -24) + ) { + $msg = sprintf( + _( + 'The database for %s isn\'t responding correctly, '. + 'so the site won\'t work properly. '. + 'The site admins probably know about the problem, '. + 'but you can contact them at %s to make sure. '. + 'Otherwise, wait a few minutes and try again.' + ), + common_config('site', 'name'), + common_config('site', 'email') + ); + } else { + $msg = _( + 'An important error occured, probably related to email setup. '. + 'Check logfiles for more info..' + ); + } + + $dac = new DBErrorAction($msg, 500); + $dac->showPage(); + + } catch (Exception $e) { + echo _('An error occurred.'); + } exit(-1); } +set_exception_handler('handleError'); + +require_once INSTALLDIR . '/lib/common.php'; + /** * Format a backtrace line for debug output roughly like debug_print_backtrace() does. * Exceptions already have this built in, but PEAR error objects just give us the array. @@ -238,10 +247,6 @@ function main() return; } - // For database errors - - PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'handleError'); - // Make sure RW database is setup setupRW(); diff --git a/lib/common.php b/lib/common.php index 8d2e6b420b..45946c216a 100644 --- a/lib/common.php +++ b/lib/common.php @@ -71,6 +71,7 @@ if (!function_exists('dl')) { # global configuration object require_once('PEAR.php'); +require_once('PEAR/Exception.php'); require_once('DB/DataObject.php'); require_once('DB/DataObject/Cast.php'); # for dates @@ -127,6 +128,17 @@ require_once INSTALLDIR.'/lib/subs.php'; require_once INSTALLDIR.'/lib/clientexception.php'; require_once INSTALLDIR.'/lib/serverexception.php'; + +//set PEAR error handling to use regular PHP exceptions +function PEAR_ErrorToPEAR_Exception($err) +{ + if ($err->getCode()) { + throw new PEAR_Exception($err->getMessage(), $err->getCode()); + } + throw new PEAR_Exception($err->getMessage()); +} +PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, 'PEAR_ErrorToPEAR_Exception'); + try { StatusNet::init(@$server, @$path, @$conffile); } catch (NoConfigException $e) { From d7d3a50d8751f071aa95541813af1d190e71430e Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 1 Mar 2010 21:53:10 -0500 Subject: [PATCH 012/655] Don't attempt to retrieve the current user from the DB while processing a DB error --- index.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/index.php b/index.php index 78c4de62a3..66a9838d65 100644 --- a/index.php +++ b/index.php @@ -92,6 +92,12 @@ function handleError($error) || $error instanceof DB_Error || ($error instanceof PEAR_Exception && $error->getCode() == -24) ) { + //If we run into a DB error, assume we can't connect to the DB at all + //so set the current user to null, so we don't try to access the DB + //while rendering the error page. + global $_cur; + $_cur = null; + $msg = sprintf( _( 'The database for %s isn\'t responding correctly, '. From d259c37ad231ca0010c60e5cfd397bb1732874a4 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 24 Apr 2010 13:40:10 -0400 Subject: [PATCH 013/655] Add User_urlshortener_prefs Add a table for URL shortener prefs, a corresponding class, and the correct mumbo-jumbo in statusnet.ini to make everything work. --- classes/User_urlshortener_prefs.php | 47 +++++++++++++++++++++++++++++ classes/statusnet.ini | 11 +++++++ db/statusnet.sql | 13 ++++++++ 3 files changed, 71 insertions(+) create mode 100755 classes/User_urlshortener_prefs.php diff --git a/classes/User_urlshortener_prefs.php b/classes/User_urlshortener_prefs.php new file mode 100755 index 0000000000..aef39e3719 --- /dev/null +++ b/classes/User_urlshortener_prefs.php @@ -0,0 +1,47 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class User_urlshortener_prefs extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_urlshortener_prefs'; // table name + public $user_id; // int(4) primary_key not_null + public $urlshorteningservice; // varchar(50) default_ur1.ca + public $maxurllength; // int(4) not_null + public $maxnoticelength; // int(4) not_null + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_urlshortener_prefs',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 473bd6ff5f..d13fdfa526 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -649,3 +649,14 @@ user_id = K transport = K transport = U screenname = U + +[user_urlshortener_prefs] +user_id = 129 +urlshorteningservice = 2 +maxurllength = 129 +maxnoticelength = 129 +created = 142 +modified = 384 + +[user_urlshortener_prefs__keys] +user_id = K diff --git a/db/statusnet.sql b/db/statusnet.sql index 16d09a11f7..a0c497fff5 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -665,3 +665,16 @@ create table local_group ( modified timestamp comment 'date this record was modified' ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table user_urlshortener_prefs ( + + user_id integer not null comment 'user' references user (id), + urlshorteningservice varchar(50) default 'ur1.ca' comment 'service to use for auto-shortening URLs', + maxurllength integer not null comment 'urls greater than this length will be shortened, 0 = always, null = never', + maxnoticelength integer not null comment 'notices with content greater than this value will have all urls shortened, 0 = always, null = never', + + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + constraint primary key (user_id) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; From 7a1dd5798f0fcf2c03d1257a18ddcb9008879de0 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 24 Apr 2010 14:05:46 -0400 Subject: [PATCH 014/655] add defaults for URL shortening --- README | 20 +++++++++++++++++--- lib/default.php | 4 ++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README b/README index 1e244c4482..dcf305ea6f 100644 --- a/README +++ b/README @@ -843,9 +843,7 @@ sslserver: use an alternate server name for SSL URLs, like parameters correctly so that both the SSL server and the "normal" server can access the session cookie and preferably other cookies as well. -shorturllength: Length of URL at which URLs in a message exceeding 140 - characters will be sent to the user's chosen - shortening service. +shorturllength: ignored. See 'url' section below. dupelimit: minimum time allowed for one person to say the same thing twice. Default 60s. Anything lower is considered a user or UI error. @@ -1468,6 +1466,22 @@ disallow: Array of (virtual) directories to disallow. Default is 'main', 'search', 'message', 'settings', 'admin'. Ignored when site is private, in which case the entire site ('/') is disallowed. +url +--- + +Everybody loves URL shorteners. These are some options for fine-tuning +how and when the server shortens URLs. + +shortener: URL shortening service to use by default. Users can override + individually. 'ur1.ca' by default. +maxlength: If an URL is strictly longer than this limit, it will be + shortened. Note that the URL shortener service may return an + URL longer than this limit. Defaults to 25. Users can + override. If set to 0, all URLs will be shortened. +maxnoticelength: If a notice is strictly longer than this limit, all + URLs in the notice will be shortened. Users can override. + -1 means the text limit for notices. + Plugins ======= diff --git a/lib/default.php b/lib/default.php index c98f179ae1..dec08fc066 100644 --- a/lib/default.php +++ b/lib/default.php @@ -304,4 +304,8 @@ $default = array('subscribers' => true, 'members' => true, 'peopletag' => true), + 'url' => + array('shortener' => 'ur1.ca', + 'maxlength' => 25, + 'maxnoticelength' => -1) ); From 9c0c9863d532942b99184f14e923fc3c050f8177 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 24 Apr 2010 14:20:28 -0400 Subject: [PATCH 015/655] documentation and whitespace on UrlShortenerPlugin --- plugins/UrlShortener/UrlShortenerPlugin.php | 108 +++++++++++++++----- 1 file changed, 84 insertions(+), 24 deletions(-) diff --git a/plugins/UrlShortener/UrlShortenerPlugin.php b/plugins/UrlShortener/UrlShortenerPlugin.php index 027624b7ae..8acfac26f4 100644 --- a/plugins/UrlShortener/UrlShortenerPlugin.php +++ b/plugins/UrlShortener/UrlShortenerPlugin.php @@ -19,11 +19,11 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . * - * @category Plugin - * @package StatusNet - * @author Craig Andrews - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 - * @link http://status.net/ + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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')) { @@ -43,53 +43,113 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { abstract class UrlShortenerPlugin extends Plugin { public $shortenerName; - public $freeService=false; - //------------Url Shortener plugin should implement some (or all) of these methods------------\\ + public $freeService = false; + + // Url Shortener plugins should implement some (or all) + // of these methods /** - * Short a URL - * @param url - * @return string shortened version of the url, or null if URL shortening failed - */ + * Make an URL shorter. + * + * @param string $url URL to shorten + * + * @return string shortened version of the url, or null on failure + */ + protected abstract function shorten($url); - //------------These methods may help you implement your plugin------------\\ + /** + * Utility to get the data at an URL + * + * @param string $url URL to fetch + * + * @return string response body + * + * @todo rename to code-standard camelCase httpGet() + */ + protected function http_get($url) { - $request = HTTPClient::start(); + $request = HTTPClient::start(); $response = $request->get($url); return $response->getBody(); } - protected function http_post($url,$data) + /** + * Utility to post a request and get a response URL + * + * @param string $url URL to fetch + * @param array $data post parameters + * + * @return string response body + * + * @todo rename to code-standard httpPost() + */ + + protected function http_post($url, $data) { - $request = HTTPClient::start(); + $request = HTTPClient::start(); $response = $request->post($url, null, $data); return $response->getBody(); } - //------------Below are the methods that connect StatusNet to the implementing Url Shortener plugin------------\\ + // Hook handlers - function onInitializePlugin(){ - if(!isset($this->shortenerName)){ + /** + * Called when all plugins have been initialized + * + * @return boolean hook value + */ + + function onInitializePlugin() + { + if (!isset($this->shortenerName)) { throw new Exception("must specify a shortenerName"); } + return true; } + /** + * Called when a showing the URL shortener drop-down box + * + * Properties of the shortening service currently only + * include whether it's a free service. + * + * @param array &$shorteners array mapping shortener name to properties + * + * @return boolean hook value + */ + function onGetUrlShorteners(&$shorteners) { - $shorteners[$this->shortenerName]=array('freeService'=>$this->freeService); + $shorteners[$this->shortenerName] = + array('freeService' => $this->freeService); + return true; } - function onStartShortenUrl($url,$shortenerName,&$shortenedUrl) + /** + * Called to shorten an URL + * + * @param string $url URL to shorten + * @param string $shortenerName Shortening service. Don't handle if it's + * not you! + * @param string &$shortenedUrl URL after shortening; out param. + * + * @return boolean hook value + */ + + function onStartShortenUrl($url, $shortenerName, &$shortenedUrl) { - if($shortenerName == $this->shortenerName && strlen($url) >= common_config('site', 'shorturllength')){ + if ($shortenerName == $this->shortenerName) { $result = $this->shorten($url); - if(isset($result) && $result != null && $result !== false){ - $shortenedUrl=$result; - common_log(LOG_INFO, __CLASS__ . ": $this->shortenerName shortened $url to $shortenedUrl"); + if (isset($result) && $result != null && $result !== false) { + $shortenedUrl = $result; + common_log(LOG_INFO, + __CLASS__ . ": $this->shortenerName ". + "shortened $url to $shortenedUrl"); return false; } } + return true; } } From 869a6be0f5779aff69018d02f9ac0273946040d9 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 24 Apr 2010 14:22:51 -0400 Subject: [PATCH 016/655] move url shortener superclass to lib from plugin --- .../UrlShortenerPlugin.php => lib/urlshortenerplugin.php | 0 plugins/BitlyUrl/BitlyUrlPlugin.php | 2 -- plugins/LilUrl/LilUrlPlugin.php | 2 -- plugins/PtitUrl/PtitUrlPlugin.php | 1 - plugins/SimpleUrl/SimpleUrlPlugin.php | 2 -- plugins/TightUrl/TightUrlPlugin.php | 2 -- 6 files changed, 9 deletions(-) rename plugins/UrlShortener/UrlShortenerPlugin.php => lib/urlshortenerplugin.php (100%) diff --git a/plugins/UrlShortener/UrlShortenerPlugin.php b/lib/urlshortenerplugin.php similarity index 100% rename from plugins/UrlShortener/UrlShortenerPlugin.php rename to lib/urlshortenerplugin.php diff --git a/plugins/BitlyUrl/BitlyUrlPlugin.php b/plugins/BitlyUrl/BitlyUrlPlugin.php index f7f28b4d6c..b649d3d0b2 100644 --- a/plugins/BitlyUrl/BitlyUrlPlugin.php +++ b/plugins/BitlyUrl/BitlyUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class BitlyUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php index c3e37c0c0f..cdff9f4e65 100644 --- a/plugins/LilUrl/LilUrlPlugin.php +++ b/plugins/LilUrl/LilUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class LilUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php index ddba942e6d..cdf46846ba 100644 --- a/plugins/PtitUrl/PtitUrlPlugin.php +++ b/plugins/PtitUrl/PtitUrlPlugin.php @@ -30,7 +30,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; class PtitUrlPlugin extends UrlShortenerPlugin { diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php index 6eac7dbb1e..5d3f97d33d 100644 --- a/plugins/SimpleUrl/SimpleUrlPlugin.php +++ b/plugins/SimpleUrl/SimpleUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class SimpleUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php index e2d494a7bd..f242db6c80 100644 --- a/plugins/TightUrl/TightUrlPlugin.php +++ b/plugins/TightUrl/TightUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class TightUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; From 1e21af42a685f600f4a53f49a194013e78b12f20 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:01:11 -0400 Subject: [PATCH 017/655] add more URL-shortening options to othersettings --- actions/othersettings.php | 58 ++++++++++++++++++++++++++++- classes/User_urlshortener_prefs.php | 41 ++++++++++++++++++++ 2 files changed, 97 insertions(+), 2 deletions(-) diff --git a/actions/othersettings.php b/actions/othersettings.php index 10e9873b39..ba3bdd8872 100644 --- a/actions/othersettings.php +++ b/actions/othersettings.php @@ -98,8 +98,10 @@ class OthersettingsAction extends AccountSettingsAction $this->hidden('token', common_session_token()); $this->elementStart('ul', 'form_data'); - $shorteners = array(); + $shorteners = array(_('[none]') => array('freeService' => false)); + Event::handle('GetUrlShorteners', array(&$shorteners)); + $services = array(); foreach($shorteners as $name=>$value) { @@ -119,8 +121,22 @@ class OthersettingsAction extends AccountSettingsAction $this->elementEnd('li'); } $this->elementStart('li'); + $this->input('maxurllength', + _('URL longer than'), + ($this->arg('maxurllength')) ? + $this->arg('maxurllength') : User_urlshortener_prefs::maxUrlLength($user), + _('URLs longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->input('maxnoticelength', + _('Text longer than'), + ($this->arg('maxnoticelength')) ? + $this->arg('maxnoticelength') : User_urlshortener_prefs::maxNoticeLength($user), + _('URLs in notices longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); $this->checkbox('viewdesigns', _('View profile designs'), - $user->viewdesigns, _('Show or hide profile designs.')); + - $user->viewdesigns, _('Show or hide profile designs.')); $this->elementEnd('li'); $this->elementEnd('ul'); $this->submit('save', _('Save')); @@ -156,6 +172,18 @@ class OthersettingsAction extends AccountSettingsAction $viewdesigns = $this->boolean('viewdesigns'); + $maxurllength = $this->trimmed('maxurllength'); + + if (!Validate::number($maxurllength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max url length.')); + } + + $maxnoticelength = $this->trimmed('maxnoticelength'); + + if (!Validate::number($maxnoticelength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max notice length.')); + } + $user = common_current_user(); assert(!is_null($user)); // should already be checked @@ -175,6 +203,32 @@ class OthersettingsAction extends AccountSettingsAction return; } + $prefs = User_urlshortener_prefs::getPrefs($user); + $orig = null; + + if (empty($prefs)) { + $prefs = new User_urlshortener_prefs(); + + $prefs->user_id = $user->id; + $prefs->created = common_sql_now(); + } else { + $orig = clone($prefs); + } + + $prefs->urlshorteningservice = $urlshorteningservice; + $prefs->maxurllength = $maxurllength; + $prefs->maxnoticelength = $maxnoticelength; + + if (!empty($orig)) { + $result = $prefs->update($orig); + } else { + $result = $prefs->insert(); + } + + if (!$result) { + throw new ServerException(_('Error saving user URL shortening preferences.')); + } + $user->query('COMMIT'); $this->showForm(_('Preferences saved.'), true); diff --git a/classes/User_urlshortener_prefs.php b/classes/User_urlshortener_prefs.php index aef39e3719..3eb008a672 100755 --- a/classes/User_urlshortener_prefs.php +++ b/classes/User_urlshortener_prefs.php @@ -44,4 +44,45 @@ class User_urlshortener_prefs extends Memcached_DataObject { return array(false, false, false); } + + static function maxUrlLength($user) + { + $def = common_config('url', 'maxlength'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxurllength; + } + } + + static function maxNoticeLength($user) + { + $def = common_config('url', 'maxnoticelength'); + + if ($def == -1) { + $def = Notice::maxContent(); + } + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxnoticelength; + } + } + + static function getPrefs($user) + { + if (empty($user)) { + return null; + } + + $prefs = User_urlshortener_prefs::staticGet('user_id', $user->id); + + return $prefs; + } } From 767ff2f7ecfd7e76e8418fc79d45e61898f09382 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:36:46 -0400 Subject: [PATCH 018/655] allow 0 or blank string in inputs --- lib/htmloutputter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 7786b5941e..9d06ba23c9 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -176,7 +176,7 @@ class HTMLOutputter extends XMLOutputter $attrs = array('name' => $id, 'type' => 'text', 'id' => $id); - if ($value) { + if (!is_null($value)) { // value can be 0 or '' $attrs['value'] = $value; } $this->element('input', $attrs); From a9c6a3bace0af44bcf38d1c790425a7be9c72147 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:37:11 -0400 Subject: [PATCH 019/655] allow 0 in numeric entries in othersettings --- actions/othersettings.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actions/othersettings.php b/actions/othersettings.php index ba3bdd8872..8d6e004047 100644 --- a/actions/othersettings.php +++ b/actions/othersettings.php @@ -123,14 +123,14 @@ class OthersettingsAction extends AccountSettingsAction $this->elementStart('li'); $this->input('maxurllength', _('URL longer than'), - ($this->arg('maxurllength')) ? + (!is_null($this->arg('maxurllength'))) ? $this->arg('maxurllength') : User_urlshortener_prefs::maxUrlLength($user), _('URLs longer than this will be shortened.')); $this->elementEnd('li'); $this->elementStart('li'); $this->input('maxnoticelength', _('Text longer than'), - ($this->arg('maxnoticelength')) ? + (!is_null($this->arg('maxnoticelength'))) ? $this->arg('maxnoticelength') : User_urlshortener_prefs::maxNoticeLength($user), _('URLs in notices longer than this will be shortened.')); $this->elementEnd('li'); From 4d29ca0b91201f6df42940297ed5b64b070efe49 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:37:41 -0400 Subject: [PATCH 020/655] static method for getting best URL shortening service --- classes/User_urlshortener_prefs.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/classes/User_urlshortener_prefs.php b/classes/User_urlshortener_prefs.php index 3eb008a672..e0f85af012 100755 --- a/classes/User_urlshortener_prefs.php +++ b/classes/User_urlshortener_prefs.php @@ -75,6 +75,23 @@ class User_urlshortener_prefs extends Memcached_DataObject } } + static function urlShorteningService($user) + { + $def = common_config('url', 'shortener'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + if (!empty($user)) { + return $user->urlshorteningservice; + } else { + return $def; + } + } else { + return $prefs->urlshorteningservice; + } + } + static function getPrefs($user) { if (empty($user)) { From 1e1c851ff3cb2da5e0dc3a0b06239a9d9c618488 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:38:40 -0400 Subject: [PATCH 021/655] add a method to force shortening URLs --- classes/File_redirection.php | 38 ++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/classes/File_redirection.php b/classes/File_redirection.php index f128b3e07c..00ec75309a 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -176,22 +176,52 @@ class File_redirection extends Memcached_DataObject * @param string $long_url * @return string */ - function makeShort($long_url) { + function makeShort($long_url) + { $canon = File_redirection::_canonUrl($long_url); $short_url = File_redirection::_userMakeShort($canon); // Did we get one? Is it shorter? - if (!empty($short_url) && mb_strlen($short_url) < mb_strlen($long_url)) { + + if (!empty($short_url)) { return $short_url; } else { return $long_url; } } - function _userMakeShort($long_url) { - $short_url = common_shorten_url($long_url); + /** + * Shorten a URL with the current user's configured shortening + * options, if applicable. + * + * If it cannot be shortened or the "short" URL is longer than the + * original, the original is returned. + * + * If the referenced item has not been seen before, embedding data + * may be saved. + * + * @param string $long_url + * @return string + */ + + function forceShort($long_url) + { + $canon = File_redirection::_canonUrl($long_url); + + $short_url = File_redirection::_userMakeShort($canon, true); + + // Did we get one? Is it shorter? + if (!empty($short_url)) { + return $short_url; + } else { + return $long_url; + } + } + + function _userMakeShort($long_url, $force = false) { + $short_url = common_shorten_url($long_url, $force); if (!empty($short_url) && $short_url != $long_url) { $short_url = (string)$short_url; // store it From d136b390115829c4391b3666bb1967f190a0de35 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:39:00 -0400 Subject: [PATCH 022/655] use site and user settings to determine when to shorten URLs --- lib/util.php | 54 +++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 15 deletions(-) diff --git a/lib/util.php b/lib/util.php index 96d21bc59c..c78ed33bd5 100644 --- a/lib/util.php +++ b/lib/util.php @@ -828,9 +828,21 @@ function common_linkify($url) { function common_shorten_links($text) { - $maxLength = Notice::maxContent(); - if ($maxLength == 0 || mb_strlen($text) <= $maxLength) return $text; - return common_replace_urls_callback($text, array('File_redirection', 'makeShort')); + common_debug("common_shorten_links() called"); + + $user = common_current_user(); + + $maxLength = User_urlshortener_prefs::maxNoticeLength($user); + + common_debug("maxLength = $maxLength"); + + if (mb_strlen($text) > $maxLength) { + common_debug("Forcing shortening"); + return common_replace_urls_callback($text, array('File_redirection', 'forceShort')); + } else { + common_debug("Not forcing shortening"); + return common_replace_urls_callback($text, array('File_redirection', 'makeShort')); + } } function common_xml_safe_str($str) @@ -1392,7 +1404,7 @@ function common_valid_tag($tag) * Determine if given domain or address literal is valid * eg for use in JIDs and URLs. Does not check if the domain * exists! - * + * * @param string $domain * @return boolean valid or not */ @@ -1734,30 +1746,42 @@ function common_database_tablename($tablename) /** * Shorten a URL with the current user's configured shortening service, * or ur1.ca if configured, or not at all if no shortening is set up. - * Length is not considered. * - * @param string $long_url + * @param string $long_url original URL + * @param boolean $force Force shortening (used when notice is too long) + * * @return string may return the original URL if shortening failed * * @fixme provide a way to specify a particular shortener * @fixme provide a way to specify to use a given user's shortening preferences */ -function common_shorten_url($long_url) + +function common_shorten_url($long_url, $force = false) { + common_debug("Shortening URL '$long_url' (force = $force)"); + $long_url = trim($long_url); + $user = common_current_user(); - if (empty($user)) { - // common current user does not find a user when called from the XMPP daemon - // therefore we'll set one here fix, so that XMPP given URLs may be shortened - $shortenerName = 'ur1.ca'; - } else { - $shortenerName = $user->urlshorteningservice; + + $maxUrlLength = User_urlshortener_prefs::maxUrlLength($user); + common_debug("maxUrlLength = $maxUrlLength"); + + // $force forces shortening even if it's not strictly needed + + if (mb_strlen($long_url) < $maxUrlLength && !$force) { + common_debug("Skipped shortening URL."); + return $long_url; } - if(Event::handle('StartShortenUrl', array($long_url,$shortenerName,&$shortenedUrl))){ + $shortenerName = User_urlshortener_prefs::urlShorteningService($user); + + common_debug("Shortener name = '$shortenerName'"); + + if (Event::handle('StartShortenUrl', array($long_url, $shortenerName, &$shortenedUrl))) { //URL wasn't shortened, so return the long url return $long_url; - }else{ + } else { //URL was shortened, so return the result return trim($shortenedUrl); } From 14adb7cc41e3d5d4e543c1f13f7a60d3cadb5c71 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 26 Apr 2010 02:40:36 -0400 Subject: [PATCH 023/655] Give users more control over URL shortening Users and administrators can set how long an URL can be before it's shortened, and how long a notice can be before all its URLs are shortened. They can also turn off shortening altogether. Squashed commit of the following: commit d136b390115829c4391b3666bb1967f190a0de35 Author: Evan Prodromou Date: Mon Apr 26 02:39:00 2010 -0400 use site and user settings to determine when to shorten URLs commit 1e1c851ff3cb2da5e0dc3a0b06239a9d9c618488 Author: Evan Prodromou Date: Mon Apr 26 02:38:40 2010 -0400 add a method to force shortening URLs commit 4d29ca0b91201f6df42940297ed5b64b070efe49 Author: Evan Prodromou Date: Mon Apr 26 02:37:41 2010 -0400 static method for getting best URL shortening service commit a9c6a3bace0af44bcf38d1c790425a7be9c72147 Author: Evan Prodromou Date: Mon Apr 26 02:37:11 2010 -0400 allow 0 in numeric entries in othersettings commit 767ff2f7ecfd7e76e8418fc79d45e61898f09382 Author: Evan Prodromou Date: Mon Apr 26 02:36:46 2010 -0400 allow 0 or blank string in inputs commit 1e21af42a685f600f4a53f49a194013e78b12f20 Author: Evan Prodromou Date: Mon Apr 26 02:01:11 2010 -0400 add more URL-shortening options to othersettings commit 869a6be0f5779aff69018d02f9ac0273946040d9 Author: Evan Prodromou Date: Sat Apr 24 14:22:51 2010 -0400 move url shortener superclass to lib from plugin commit 9c0c9863d532942b99184f14e923fc3c050f8177 Author: Evan Prodromou Date: Sat Apr 24 14:20:28 2010 -0400 documentation and whitespace on UrlShortenerPlugin commit 7a1dd5798f0fcf2c03d1257a18ddcb9008879de0 Author: Evan Prodromou Date: Sat Apr 24 14:05:46 2010 -0400 add defaults for URL shortening commit d259c37ad231ca0010c60e5cfd397bb1732874a4 Author: Evan Prodromou Date: Sat Apr 24 13:40:10 2010 -0400 Add User_urlshortener_prefs Add a table for URL shortener prefs, a corresponding class, and the correct mumbo-jumbo in statusnet.ini to make everything work. --- README | 20 ++- actions/othersettings.php | 58 +++++++- classes/File_redirection.php | 38 ++++- classes/User_urlshortener_prefs.php | 105 +++++++++++++ classes/statusnet.ini | 11 ++ db/statusnet.sql | 13 ++ lib/default.php | 4 + lib/htmloutputter.php | 2 +- lib/urlshortenerplugin.php | 155 ++++++++++++++++++++ lib/util.php | 54 +++++-- plugins/BitlyUrl/BitlyUrlPlugin.php | 2 - plugins/LilUrl/LilUrlPlugin.php | 2 - plugins/PtitUrl/PtitUrlPlugin.php | 1 - plugins/SimpleUrl/SimpleUrlPlugin.php | 2 - plugins/TightUrl/TightUrlPlugin.php | 2 - plugins/UrlShortener/UrlShortenerPlugin.php | 95 ------------ 16 files changed, 435 insertions(+), 129 deletions(-) create mode 100755 classes/User_urlshortener_prefs.php create mode 100644 lib/urlshortenerplugin.php delete mode 100644 plugins/UrlShortener/UrlShortenerPlugin.php diff --git a/README b/README index 1e244c4482..dcf305ea6f 100644 --- a/README +++ b/README @@ -843,9 +843,7 @@ sslserver: use an alternate server name for SSL URLs, like parameters correctly so that both the SSL server and the "normal" server can access the session cookie and preferably other cookies as well. -shorturllength: Length of URL at which URLs in a message exceeding 140 - characters will be sent to the user's chosen - shortening service. +shorturllength: ignored. See 'url' section below. dupelimit: minimum time allowed for one person to say the same thing twice. Default 60s. Anything lower is considered a user or UI error. @@ -1468,6 +1466,22 @@ disallow: Array of (virtual) directories to disallow. Default is 'main', 'search', 'message', 'settings', 'admin'. Ignored when site is private, in which case the entire site ('/') is disallowed. +url +--- + +Everybody loves URL shorteners. These are some options for fine-tuning +how and when the server shortens URLs. + +shortener: URL shortening service to use by default. Users can override + individually. 'ur1.ca' by default. +maxlength: If an URL is strictly longer than this limit, it will be + shortened. Note that the URL shortener service may return an + URL longer than this limit. Defaults to 25. Users can + override. If set to 0, all URLs will be shortened. +maxnoticelength: If a notice is strictly longer than this limit, all + URLs in the notice will be shortened. Users can override. + -1 means the text limit for notices. + Plugins ======= diff --git a/actions/othersettings.php b/actions/othersettings.php index 10e9873b39..8d6e004047 100644 --- a/actions/othersettings.php +++ b/actions/othersettings.php @@ -98,8 +98,10 @@ class OthersettingsAction extends AccountSettingsAction $this->hidden('token', common_session_token()); $this->elementStart('ul', 'form_data'); - $shorteners = array(); + $shorteners = array(_('[none]') => array('freeService' => false)); + Event::handle('GetUrlShorteners', array(&$shorteners)); + $services = array(); foreach($shorteners as $name=>$value) { @@ -119,8 +121,22 @@ class OthersettingsAction extends AccountSettingsAction $this->elementEnd('li'); } $this->elementStart('li'); + $this->input('maxurllength', + _('URL longer than'), + (!is_null($this->arg('maxurllength'))) ? + $this->arg('maxurllength') : User_urlshortener_prefs::maxUrlLength($user), + _('URLs longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); + $this->input('maxnoticelength', + _('Text longer than'), + (!is_null($this->arg('maxnoticelength'))) ? + $this->arg('maxnoticelength') : User_urlshortener_prefs::maxNoticeLength($user), + _('URLs in notices longer than this will be shortened.')); + $this->elementEnd('li'); + $this->elementStart('li'); $this->checkbox('viewdesigns', _('View profile designs'), - $user->viewdesigns, _('Show or hide profile designs.')); + - $user->viewdesigns, _('Show or hide profile designs.')); $this->elementEnd('li'); $this->elementEnd('ul'); $this->submit('save', _('Save')); @@ -156,6 +172,18 @@ class OthersettingsAction extends AccountSettingsAction $viewdesigns = $this->boolean('viewdesigns'); + $maxurllength = $this->trimmed('maxurllength'); + + if (!Validate::number($maxurllength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max url length.')); + } + + $maxnoticelength = $this->trimmed('maxnoticelength'); + + if (!Validate::number($maxnoticelength, array('min' => 0))) { + throw new ClientException(_('Invalid number for max notice length.')); + } + $user = common_current_user(); assert(!is_null($user)); // should already be checked @@ -175,6 +203,32 @@ class OthersettingsAction extends AccountSettingsAction return; } + $prefs = User_urlshortener_prefs::getPrefs($user); + $orig = null; + + if (empty($prefs)) { + $prefs = new User_urlshortener_prefs(); + + $prefs->user_id = $user->id; + $prefs->created = common_sql_now(); + } else { + $orig = clone($prefs); + } + + $prefs->urlshorteningservice = $urlshorteningservice; + $prefs->maxurllength = $maxurllength; + $prefs->maxnoticelength = $maxnoticelength; + + if (!empty($orig)) { + $result = $prefs->update($orig); + } else { + $result = $prefs->insert(); + } + + if (!$result) { + throw new ServerException(_('Error saving user URL shortening preferences.')); + } + $user->query('COMMIT'); $this->showForm(_('Preferences saved.'), true); diff --git a/classes/File_redirection.php b/classes/File_redirection.php index f128b3e07c..00ec75309a 100644 --- a/classes/File_redirection.php +++ b/classes/File_redirection.php @@ -176,22 +176,52 @@ class File_redirection extends Memcached_DataObject * @param string $long_url * @return string */ - function makeShort($long_url) { + function makeShort($long_url) + { $canon = File_redirection::_canonUrl($long_url); $short_url = File_redirection::_userMakeShort($canon); // Did we get one? Is it shorter? - if (!empty($short_url) && mb_strlen($short_url) < mb_strlen($long_url)) { + + if (!empty($short_url)) { return $short_url; } else { return $long_url; } } - function _userMakeShort($long_url) { - $short_url = common_shorten_url($long_url); + /** + * Shorten a URL with the current user's configured shortening + * options, if applicable. + * + * If it cannot be shortened or the "short" URL is longer than the + * original, the original is returned. + * + * If the referenced item has not been seen before, embedding data + * may be saved. + * + * @param string $long_url + * @return string + */ + + function forceShort($long_url) + { + $canon = File_redirection::_canonUrl($long_url); + + $short_url = File_redirection::_userMakeShort($canon, true); + + // Did we get one? Is it shorter? + if (!empty($short_url)) { + return $short_url; + } else { + return $long_url; + } + } + + function _userMakeShort($long_url, $force = false) { + $short_url = common_shorten_url($long_url, $force); if (!empty($short_url) && $short_url != $long_url) { $short_url = (string)$short_url; // store it diff --git a/classes/User_urlshortener_prefs.php b/classes/User_urlshortener_prefs.php new file mode 100755 index 0000000000..e0f85af012 --- /dev/null +++ b/classes/User_urlshortener_prefs.php @@ -0,0 +1,105 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { + exit(1); +} + +class User_urlshortener_prefs extends Memcached_DataObject +{ + ###START_AUTOCODE + /* the code below is auto generated do not remove the above tag */ + + public $__table = 'user_urlshortener_prefs'; // table name + public $user_id; // int(4) primary_key not_null + public $urlshorteningservice; // varchar(50) default_ur1.ca + public $maxurllength; // int(4) not_null + public $maxnoticelength; // int(4) not_null + public $created; // datetime not_null default_0000-00-00%2000%3A00%3A00 + public $modified; // timestamp not_null default_CURRENT_TIMESTAMP + + /* Static get */ + function staticGet($k,$v=NULL) { return Memcached_DataObject::staticGet('User_urlshortener_prefs',$k,$v); } + + /* the code above is auto generated do not remove the tag below */ + ###END_AUTOCODE + + function sequenceKey() + { + return array(false, false, false); + } + + static function maxUrlLength($user) + { + $def = common_config('url', 'maxlength'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxurllength; + } + } + + static function maxNoticeLength($user) + { + $def = common_config('url', 'maxnoticelength'); + + if ($def == -1) { + $def = Notice::maxContent(); + } + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + return $def; + } else { + return $prefs->maxnoticelength; + } + } + + static function urlShorteningService($user) + { + $def = common_config('url', 'shortener'); + + $prefs = self::getPrefs($user); + + if (empty($prefs)) { + if (!empty($user)) { + return $user->urlshorteningservice; + } else { + return $def; + } + } else { + return $prefs->urlshorteningservice; + } + } + + static function getPrefs($user) + { + if (empty($user)) { + return null; + } + + $prefs = User_urlshortener_prefs::staticGet('user_id', $user->id); + + return $prefs; + } +} diff --git a/classes/statusnet.ini b/classes/statusnet.ini index 473bd6ff5f..d13fdfa526 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -649,3 +649,14 @@ user_id = K transport = K transport = U screenname = U + +[user_urlshortener_prefs] +user_id = 129 +urlshorteningservice = 2 +maxurllength = 129 +maxnoticelength = 129 +created = 142 +modified = 384 + +[user_urlshortener_prefs__keys] +user_id = K diff --git a/db/statusnet.sql b/db/statusnet.sql index 16d09a11f7..a0c497fff5 100644 --- a/db/statusnet.sql +++ b/db/statusnet.sql @@ -665,3 +665,16 @@ create table local_group ( modified timestamp comment 'date this record was modified' ) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; + +create table user_urlshortener_prefs ( + + user_id integer not null comment 'user' references user (id), + urlshorteningservice varchar(50) default 'ur1.ca' comment 'service to use for auto-shortening URLs', + maxurllength integer not null comment 'urls greater than this length will be shortened, 0 = always, null = never', + maxnoticelength integer not null comment 'notices with content greater than this value will have all urls shortened, 0 = always, null = never', + + created datetime not null comment 'date this record was created', + modified timestamp comment 'date this record was modified', + + constraint primary key (user_id) +) ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_bin; diff --git a/lib/default.php b/lib/default.php index c98f179ae1..dec08fc066 100644 --- a/lib/default.php +++ b/lib/default.php @@ -304,4 +304,8 @@ $default = array('subscribers' => true, 'members' => true, 'peopletag' => true), + 'url' => + array('shortener' => 'ur1.ca', + 'maxlength' => 25, + 'maxnoticelength' => -1) ); diff --git a/lib/htmloutputter.php b/lib/htmloutputter.php index 7786b5941e..9d06ba23c9 100644 --- a/lib/htmloutputter.php +++ b/lib/htmloutputter.php @@ -176,7 +176,7 @@ class HTMLOutputter extends XMLOutputter $attrs = array('name' => $id, 'type' => 'text', 'id' => $id); - if ($value) { + if (!is_null($value)) { // value can be 0 or '' $attrs['value'] = $value; } $this->element('input', $attrs); diff --git a/lib/urlshortenerplugin.php b/lib/urlshortenerplugin.php new file mode 100644 index 0000000000..8acfac26f4 --- /dev/null +++ b/lib/urlshortenerplugin.php @@ -0,0 +1,155 @@ +. + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 URL shortening + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @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 UrlShortenerPlugin extends Plugin +{ + public $shortenerName; + public $freeService = false; + + // Url Shortener plugins should implement some (or all) + // of these methods + + /** + * Make an URL shorter. + * + * @param string $url URL to shorten + * + * @return string shortened version of the url, or null on failure + */ + + protected abstract function shorten($url); + + /** + * Utility to get the data at an URL + * + * @param string $url URL to fetch + * + * @return string response body + * + * @todo rename to code-standard camelCase httpGet() + */ + + protected function http_get($url) + { + $request = HTTPClient::start(); + $response = $request->get($url); + return $response->getBody(); + } + + /** + * Utility to post a request and get a response URL + * + * @param string $url URL to fetch + * @param array $data post parameters + * + * @return string response body + * + * @todo rename to code-standard httpPost() + */ + + protected function http_post($url, $data) + { + $request = HTTPClient::start(); + $response = $request->post($url, null, $data); + return $response->getBody(); + } + + // Hook handlers + + /** + * Called when all plugins have been initialized + * + * @return boolean hook value + */ + + function onInitializePlugin() + { + if (!isset($this->shortenerName)) { + throw new Exception("must specify a shortenerName"); + } + return true; + } + + /** + * Called when a showing the URL shortener drop-down box + * + * Properties of the shortening service currently only + * include whether it's a free service. + * + * @param array &$shorteners array mapping shortener name to properties + * + * @return boolean hook value + */ + + function onGetUrlShorteners(&$shorteners) + { + $shorteners[$this->shortenerName] = + array('freeService' => $this->freeService); + return true; + } + + /** + * Called to shorten an URL + * + * @param string $url URL to shorten + * @param string $shortenerName Shortening service. Don't handle if it's + * not you! + * @param string &$shortenedUrl URL after shortening; out param. + * + * @return boolean hook value + */ + + function onStartShortenUrl($url, $shortenerName, &$shortenedUrl) + { + if ($shortenerName == $this->shortenerName) { + $result = $this->shorten($url); + if (isset($result) && $result != null && $result !== false) { + $shortenedUrl = $result; + common_log(LOG_INFO, + __CLASS__ . ": $this->shortenerName ". + "shortened $url to $shortenedUrl"); + return false; + } + } + return true; + } +} diff --git a/lib/util.php b/lib/util.php index 96d21bc59c..c78ed33bd5 100644 --- a/lib/util.php +++ b/lib/util.php @@ -828,9 +828,21 @@ function common_linkify($url) { function common_shorten_links($text) { - $maxLength = Notice::maxContent(); - if ($maxLength == 0 || mb_strlen($text) <= $maxLength) return $text; - return common_replace_urls_callback($text, array('File_redirection', 'makeShort')); + common_debug("common_shorten_links() called"); + + $user = common_current_user(); + + $maxLength = User_urlshortener_prefs::maxNoticeLength($user); + + common_debug("maxLength = $maxLength"); + + if (mb_strlen($text) > $maxLength) { + common_debug("Forcing shortening"); + return common_replace_urls_callback($text, array('File_redirection', 'forceShort')); + } else { + common_debug("Not forcing shortening"); + return common_replace_urls_callback($text, array('File_redirection', 'makeShort')); + } } function common_xml_safe_str($str) @@ -1392,7 +1404,7 @@ function common_valid_tag($tag) * Determine if given domain or address literal is valid * eg for use in JIDs and URLs. Does not check if the domain * exists! - * + * * @param string $domain * @return boolean valid or not */ @@ -1734,30 +1746,42 @@ function common_database_tablename($tablename) /** * Shorten a URL with the current user's configured shortening service, * or ur1.ca if configured, or not at all if no shortening is set up. - * Length is not considered. * - * @param string $long_url + * @param string $long_url original URL + * @param boolean $force Force shortening (used when notice is too long) + * * @return string may return the original URL if shortening failed * * @fixme provide a way to specify a particular shortener * @fixme provide a way to specify to use a given user's shortening preferences */ -function common_shorten_url($long_url) + +function common_shorten_url($long_url, $force = false) { + common_debug("Shortening URL '$long_url' (force = $force)"); + $long_url = trim($long_url); + $user = common_current_user(); - if (empty($user)) { - // common current user does not find a user when called from the XMPP daemon - // therefore we'll set one here fix, so that XMPP given URLs may be shortened - $shortenerName = 'ur1.ca'; - } else { - $shortenerName = $user->urlshorteningservice; + + $maxUrlLength = User_urlshortener_prefs::maxUrlLength($user); + common_debug("maxUrlLength = $maxUrlLength"); + + // $force forces shortening even if it's not strictly needed + + if (mb_strlen($long_url) < $maxUrlLength && !$force) { + common_debug("Skipped shortening URL."); + return $long_url; } - if(Event::handle('StartShortenUrl', array($long_url,$shortenerName,&$shortenedUrl))){ + $shortenerName = User_urlshortener_prefs::urlShorteningService($user); + + common_debug("Shortener name = '$shortenerName'"); + + if (Event::handle('StartShortenUrl', array($long_url, $shortenerName, &$shortenedUrl))) { //URL wasn't shortened, so return the long url return $long_url; - }else{ + } else { //URL was shortened, so return the result return trim($shortenedUrl); } diff --git a/plugins/BitlyUrl/BitlyUrlPlugin.php b/plugins/BitlyUrl/BitlyUrlPlugin.php index f7f28b4d6c..b649d3d0b2 100644 --- a/plugins/BitlyUrl/BitlyUrlPlugin.php +++ b/plugins/BitlyUrl/BitlyUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class BitlyUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/LilUrl/LilUrlPlugin.php b/plugins/LilUrl/LilUrlPlugin.php index c3e37c0c0f..cdff9f4e65 100644 --- a/plugins/LilUrl/LilUrlPlugin.php +++ b/plugins/LilUrl/LilUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class LilUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/PtitUrl/PtitUrlPlugin.php b/plugins/PtitUrl/PtitUrlPlugin.php index ddba942e6d..cdf46846ba 100644 --- a/plugins/PtitUrl/PtitUrlPlugin.php +++ b/plugins/PtitUrl/PtitUrlPlugin.php @@ -30,7 +30,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; class PtitUrlPlugin extends UrlShortenerPlugin { diff --git a/plugins/SimpleUrl/SimpleUrlPlugin.php b/plugins/SimpleUrl/SimpleUrlPlugin.php index 6eac7dbb1e..5d3f97d33d 100644 --- a/plugins/SimpleUrl/SimpleUrlPlugin.php +++ b/plugins/SimpleUrl/SimpleUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class SimpleUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/TightUrl/TightUrlPlugin.php b/plugins/TightUrl/TightUrlPlugin.php index e2d494a7bd..f242db6c80 100644 --- a/plugins/TightUrl/TightUrlPlugin.php +++ b/plugins/TightUrl/TightUrlPlugin.php @@ -31,8 +31,6 @@ if (!defined('STATUSNET')) { exit(1); } -require_once INSTALLDIR.'/plugins/UrlShortener/UrlShortenerPlugin.php'; - class TightUrlPlugin extends UrlShortenerPlugin { public $serviceUrl; diff --git a/plugins/UrlShortener/UrlShortenerPlugin.php b/plugins/UrlShortener/UrlShortenerPlugin.php deleted file mode 100644 index 027624b7ae..0000000000 --- a/plugins/UrlShortener/UrlShortenerPlugin.php +++ /dev/null @@ -1,95 +0,0 @@ -. - * - * @category Plugin - * @package StatusNet - * @author Craig Andrews - * @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 URL shortening - * - * @category Plugin - * @package StatusNet - * @author Craig Andrews - * @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 UrlShortenerPlugin extends Plugin -{ - public $shortenerName; - public $freeService=false; - //------------Url Shortener plugin should implement some (or all) of these methods------------\\ - - /** - * Short a URL - * @param url - * @return string shortened version of the url, or null if URL shortening failed - */ - protected abstract function shorten($url); - - //------------These methods may help you implement your plugin------------\\ - protected function http_get($url) - { - $request = HTTPClient::start(); - $response = $request->get($url); - return $response->getBody(); - } - - protected function http_post($url,$data) - { - $request = HTTPClient::start(); - $response = $request->post($url, null, $data); - return $response->getBody(); - } - - //------------Below are the methods that connect StatusNet to the implementing Url Shortener plugin------------\\ - - function onInitializePlugin(){ - if(!isset($this->shortenerName)){ - throw new Exception("must specify a shortenerName"); - } - } - - function onGetUrlShorteners(&$shorteners) - { - $shorteners[$this->shortenerName]=array('freeService'=>$this->freeService); - } - - function onStartShortenUrl($url,$shortenerName,&$shortenedUrl) - { - if($shortenerName == $this->shortenerName && strlen($url) >= common_config('site', 'shorturllength')){ - $result = $this->shorten($url); - if(isset($result) && $result != null && $result !== false){ - $shortenedUrl=$result; - common_log(LOG_INFO, __CLASS__ . ": $this->shortenerName shortened $url to $shortenedUrl"); - return false; - } - } - } -} From 5414396a2ee9f1401d69b60969e04a1941e24e21 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 30 Apr 2010 14:41:54 -0700 Subject: [PATCH 024/655] IM cleanup on 1.0.x branch: * Fake_XMPP back to Queued_XMPP, refactor how we use it and don't create objects and load classes until we need them. * fix fatal error in IM settings while waiting for a Jabber confirmation. * Caching fix for user_im_prefs * fix for saving multiple transport settings * some fixes for AIM & using normalized addresses for lookups --- actions/imsettings.php | 17 ++++++------ classes/User_im_prefs.php | 23 ++++++++++++++++ classes/statusnet.ini | 6 +++-- lib/implugin.php | 25 ++++++++++++----- plugins/Aim/AimPlugin.php | 9 ++++++- plugins/Aim/README | 2 +- .../Xmpp/{Fake_XMPP.php => Queued_XMPP.php} | 17 ++++++++---- plugins/Xmpp/XmppPlugin.php | 27 ++++++++++--------- 8 files changed, 89 insertions(+), 37 deletions(-) rename plugins/Xmpp/{Fake_XMPP.php => Queued_XMPP.php} (87%) diff --git a/actions/imsettings.php b/actions/imsettings.php index 2c2606b76c..662b1063e7 100644 --- a/actions/imsettings.php +++ b/actions/imsettings.php @@ -133,8 +133,7 @@ class ImsettingsAction extends ConnectSettingsAction 'message with further instructions. '. '(Did you add %s to your buddy list?)'), $transport_info['display'], - $transport_info['daemon_screenname'], - jabber_daemon_address())); + $transport_info['daemon_screenname'])); $this->hidden('screenname', $confirm->address); // TRANS: Button label to cancel an IM address confirmation procedure. $this->submit('cancel', _m('BUTTON','Cancel')); @@ -163,12 +162,11 @@ class ImsettingsAction extends ConnectSettingsAction 'action' => common_local_url('imsettings'))); $this->elementStart('fieldset', array('id' => 'settings_im_preferences')); - $this->element('legend', null, _('Preferences')); + // TRANS: Header for IM preferences form. + $this->element('legend', null, _('IM Preferences')); $this->hidden('token', common_session_token()); $this->elementStart('table'); $this->elementStart('tr'); - // TRANS: Header for IM preferences form. - $this->element('th', null, _('IM Preferences')); foreach($user_im_prefs_by_transport as $transport=>$user_im_prefs) { $this->element('th', null, $transports[$transport]['display']); @@ -278,19 +276,20 @@ class ImsettingsAction extends ConnectSettingsAction $user = common_current_user(); $user_im_prefs = new User_im_prefs(); + $user_im_prefs->query('BEGIN'); $user_im_prefs->user_id = $user->id; if($user_im_prefs->find() && $user_im_prefs->fetch()) { $preferences = array('notify', 'updatefrompresence', 'replies', 'microid'); - $user_im_prefs->query('BEGIN'); do { $original = clone($user_im_prefs); + $new = clone($user_im_prefs); foreach($preferences as $preference) { - $user_im_prefs->$preference = $this->boolean($user_im_prefs->transport . '_' . $preference); + $new->$preference = $this->boolean($new->transport . '_' . $preference); } - $result = $user_im_prefs->update($original); + $result = $new->update($original); if ($result === false) { common_log_db_error($user, 'UPDATE', __FILE__); @@ -299,8 +298,8 @@ class ImsettingsAction extends ConnectSettingsAction return; } }while($user_im_prefs->fetch()); - $user_im_prefs->query('COMMIT'); } + $user_im_prefs->query('COMMIT'); // TRANS: Confirmation message for successful IM preferences save. $this->showForm(_('Preferences saved.'), true); } diff --git a/classes/User_im_prefs.php b/classes/User_im_prefs.php index 8ecdfe9fab..75be8969e0 100644 --- a/classes/User_im_prefs.php +++ b/classes/User_im_prefs.php @@ -68,4 +68,27 @@ class User_im_prefs extends Memcached_DataObject { return array(false,false); } + + /** + * We have two compound keys with unique constraints: + * (transport, user_id) which is our primary key, and + * (transport, screenname) which is an additional constraint. + * + * Currently there's not a way to represent that second key + * in the general keys list, so we're adding it here to the + * list of keys to use for caching, ensuring that it gets + * cleared as well when we change. + * + * @return array of cache keys + */ + function _allCacheKeys() + { + $ukeys = 'transport,screenname'; + $uvals = $this->transport . ',' . $this->screenname; + + $ckeys = parent::_allCacheKeys(); + $ckeys[] = $this->cacheKey($this->tableName(), $ukeys, $uvals); + return $ckeys; + } + } diff --git a/classes/statusnet.ini b/classes/statusnet.ini index d13fdfa526..b57d862263 100644 --- a/classes/statusnet.ini +++ b/classes/statusnet.ini @@ -647,8 +647,10 @@ modified = 384 [user_im_prefs__keys] user_id = K transport = K -transport = U -screenname = U +; There's another unique index on (transport, screenname) +; but we have no way to represent a compound index other than +; the primary key in here. To ensure proper cache purging, +; we need to tweak the class. [user_urlshortener_prefs] user_id = 129 diff --git a/lib/implugin.php b/lib/implugin.php index 7302859a47..7125aaee8d 100644 --- a/lib/implugin.php +++ b/lib/implugin.php @@ -107,10 +107,15 @@ abstract class ImPlugin extends Plugin * 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() + * + * Returning false may CAUSE REPROCESSING OF THE QUEUE ITEM, and should + * be used for temporary failures only. For permanent failures such as + * unrecognized addresses, return true to indicate your processing has + * completed. * * @param object $data raw IM data * - * @return boolean success value + * @return boolean true if processing completed, false for temporary failures */ abstract function receive_raw_message($data); @@ -185,9 +190,12 @@ abstract class ImPlugin extends Plugin */ function get_user_im_prefs_from_screenname($screenname) { - if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'screenname' => $screenname) )){ + $user_im_prefs = User_im_prefs::pkeyGet( + array('transport' => $this->transport, + 'screenname' => $this->normalize($screenname))); + if ($user_im_prefs) { return $user_im_prefs; - }else{ + } else { return false; } } @@ -203,9 +211,9 @@ abstract class ImPlugin extends Plugin function get_screenname($user) { $user_im_prefs = $this->get_user_im_prefs_from_user($user); - if($user_im_prefs){ + if ($user_im_prefs) { return $user_im_prefs->screenname; - }else{ + } else { return false; } } @@ -220,9 +228,12 @@ abstract class ImPlugin extends Plugin */ function get_user_im_prefs_from_user($user) { - if($user_im_prefs = User_im_prefs::pkeyGet( array('transport' => $this->transport, 'user_id' => $user->id) )){ + $user_im_prefs = User_im_prefs::pkeyGet( + array('transport' => $this->transport, + 'user_id' => $user->id)); + if ($user_im_prefs){ return $user_im_prefs; - }else{ + } else { return false; } } diff --git a/plugins/Aim/AimPlugin.php b/plugins/Aim/AimPlugin.php index 3855d1fb05..30da1dbc79 100644 --- a/plugins/Aim/AimPlugin.php +++ b/plugins/Aim/AimPlugin.php @@ -126,6 +126,11 @@ class AimPlugin extends ImPlugin return true; } + /** + * Accept a queued input message. + * + * @return true if processing completed, false if message should be reprocessed + */ function receive_raw_message($message) { $info=Aim::getMessageInfo($message); @@ -133,7 +138,9 @@ class AimPlugin extends ImPlugin $user = $this->get_user($from); $notice_text = $info['message']; - return $this->handle_incoming($from, $notice_text); + $this->handle_incoming($from, $notice_text); + + return true; } function initialize(){ diff --git a/plugins/Aim/README b/plugins/Aim/README index 0465917383..7d486a0366 100644 --- a/plugins/Aim/README +++ b/plugins/Aim/README @@ -6,7 +6,7 @@ add "addPlugin('aim', array('setting'=>'value', 'setting2'=>'value2', ...);" to the bottom of your config.php -The daemon included with this plugin must be running. It will be started by +scripts/imdaemon.php included with StatusNet must be running. It will be started by the plugin along with their other daemons when you run scripts/startdaemons.sh. See the StatusNet README for more about queuing and daemons. diff --git a/plugins/Xmpp/Fake_XMPP.php b/plugins/Xmpp/Queued_XMPP.php similarity index 87% rename from plugins/Xmpp/Fake_XMPP.php rename to plugins/Xmpp/Queued_XMPP.php index 0f7cfd3b4d..73eff22467 100644 --- a/plugins/Xmpp/Fake_XMPP.php +++ b/plugins/Xmpp/Queued_XMPP.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Instead of sending XMPP messages, retrieve the raw XML that would be sent + * Queue-mediated proxy class for outgoing XMPP messages. * * PHP version 5 * @@ -31,13 +31,17 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } -class Fake_XMPP extends XMPPHP_XMPP +class Queued_XMPP extends XMPPHP_XMPP { - public $would_be_sent = null; + /** + * Reference to the XmppPlugin object we're hooked up to. + */ + public $plugin; /** * Constructor * + * @param XmppPlugin $plugin * @param string $host * @param integer $port * @param string $user @@ -47,8 +51,10 @@ class Fake_XMPP extends XMPPHP_XMPP * @param boolean $printlog * @param string $loglevel */ - public function __construct($host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) + public function __construct($plugin, $host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) { + $this->plugin = $plugin; + parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); // We use $host to connect, but $server to build JIDs if specified. @@ -73,7 +79,7 @@ class Fake_XMPP extends XMPPHP_XMPP */ public function send($msg, $timeout=NULL) { - $this->would_be_sent = $msg; + $this->plugin->enqueue_outgoing_raw($msg); } //@{ @@ -110,5 +116,6 @@ class Fake_XMPP extends XMPPHP_XMPP throw new Exception("Can't read stream from fake XMPP."); } //@} + } diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index 03bf47feac..a2521536bc 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -60,8 +60,6 @@ class XmppPlugin extends ImPlugin public $transport = 'xmpp'; - protected $fake_xmpp; - function getDisplayName(){ return _m('XMPP/Jabber/GTalk'); } @@ -292,7 +290,7 @@ class XmppPlugin extends ImPlugin require_once 'XMPP.php'; return false; case 'Sharing_XMPP': - case 'Fake_XMPP': + case 'Queued_XMPP': require_once $dir . '/'.$cls.'.php'; return false; case 'XmppManager': @@ -317,9 +315,7 @@ class XmppPlugin extends ImPlugin function send_message($screenname, $body) { - $this->fake_xmpp->message($screenname, $body, 'chat'); - $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); - return true; + $this->queuedConnection()->message($screenname, $body, 'chat'); } function send_notice($screenname, $notice) @@ -327,8 +323,7 @@ class XmppPlugin extends ImPlugin $msg = $this->format_notice($notice); $entry = $this->format_entry($notice); - $this->fake_xmpp->message($screenname, $msg, 'chat', null, $entry); - $this->enqueue_outgoing_raw($this->fake_xmpp->would_be_sent); + $this->queuedConnection()->message($screenname, $msg, 'chat', null, $entry); return true; } @@ -385,10 +380,19 @@ class XmppPlugin extends ImPlugin return true; } - return $this->handle_incoming($from, $pl['body']); + $this->handle_incoming($from, $pl['body']); + + return true; } - function initialize(){ + /** + * Build a queue-proxied XMPP interface object. Any outgoing messages + * will be run back through us for enqueing rather than sent directly. + * + * @return Queued_XMPP + * @throws Exception if server settings are invalid. + */ + function queuedConnection(){ if(!isset($this->server)){ throw new Exception("must specify a server"); } @@ -402,7 +406,7 @@ class XmppPlugin extends ImPlugin throw new Exception("must specify a password"); } - $this->fake_xmpp = new Fake_XMPP($this->host ? + return new Queued_XMPP($this, $this->host ? $this->host : $this->server, $this->port, @@ -415,7 +419,6 @@ class XmppPlugin extends ImPlugin $this->debug ? XMPPHP_Log::LEVEL_VERBOSE : null ); - return true; } function onPluginVersion(&$versions) From 081ee9b29c7e4b207633aec0219b5a5b1ef36800 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Mon, 3 May 2010 16:49:59 -0700 Subject: [PATCH 025/655] extlibs updates: PEAR::Mail to 1.2.0, PEAR::Net_SMTP to 1.4.2 (need to go together as a pair) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PEAR::Mail updated to 1.2.0 from 1.1.4, fixes deprecation warnings on PHP 5.3, as well as: 1.2.0: • QA release - stable. • Updated minimum dependencies (Net_SMTP, PEAR, PHP) • Doc Bug #15620 Licence change to BSD • Bug #13659 Mail parse error in special condition • Bug #16200 - Security hole allow to read/write Arbitrary File _hasUnclosedQuotes() doesn't properly handle a double slash before an end quote (slusarz@curecanti.org, Bug #9137). • Make sure Net_SMTP is defined when calling getSMTPObject() directly (slusarz@curecanti.org, Bug #13772). • Add addServiceExtensionParameter() to the SMTP driver (slusarz@curecanti.org, Bug #13764). • Add a method to obtain the Net_SMTP object from the SMTP driver (slusarz@curecanti.org, Bug #13766). PEAR::Net_SMTP updated to 1.4.2 from 1.3.1, needed to support updated PEAR::Mail: 1.4.2: • Fixing header string quoting in data(). (Bug #17199) 1.4.1: • The auth() method now includes an optional $tls parameter that determines whether or not TLS should be attempted (if supported by the PHP runtime and the remote SMTP server). This parameter defaults to true. (Bug #16349) • Header data can be specified separately from message body data by passing it as the optional second parameter to ``data()``. This is especially useful when an open file resource is being used to supply message data because it allows header fields (like *Subject:*) to be built dynamically at runtime. (Request #17012) 1.4.0: • The data() method now accepts either a string or a file resource containing the message data. (Request #16962) 1.3.4: • All Net_Socket write failures are now recognized. (Bug #16831) 1.3.3: • Added getGreeting(), for retrieving the server's greeting string. (Request #16066) [needed for PEAR::Mail] • We no longer attempt a TLS connection if we're already using a secure socket. (Bug #16254) • You can now specify a debug output handler via setDebug(). (Request #16420) 1.3.2: • TLS connection only gets started if no AUTH methods are sent. (Bug #14944) --- extlib/Mail.php | 82 +++++++++++----- extlib/Mail/RFC822.php | 83 +++++++++------- extlib/Mail/mail.php | 63 +++++++++---- extlib/Mail/mock.php | 64 +++++++++---- extlib/Mail/null.php | 64 +++++++++---- extlib/Mail/sendmail.php | 7 +- extlib/Mail/smtp.php | 73 +++++++++++---- extlib/Mail/smtpmx.php | 44 +++++++-- extlib/Net/SMTP.php | 198 ++++++++++++++++++++++++++++++--------- 9 files changed, 481 insertions(+), 197 deletions(-) mode change 100644 => 100755 extlib/Mail.php mode change 100644 => 100755 extlib/Mail/RFC822.php mode change 100644 => 100755 extlib/Mail/mail.php mode change 100644 => 100755 extlib/Mail/mock.php mode change 100644 => 100755 extlib/Mail/null.php mode change 100644 => 100755 extlib/Mail/sendmail.php mode change 100644 => 100755 extlib/Mail/smtp.php mode change 100644 => 100755 extlib/Mail/smtpmx.php diff --git a/extlib/Mail.php b/extlib/Mail.php old mode 100644 new mode 100755 index 3a0c1a9cb8..75132ac2a6 --- a/extlib/Mail.php +++ b/extlib/Mail.php @@ -1,22 +1,47 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: Mail.php,v 1.17 2006/09/15 03:41:18 jon Exp $ +/** + * PEAR's Mail:: interface. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2002-2007, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail + * @author Chuck Hagenbuch + * @copyright 1997-2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: Mail.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ require_once 'PEAR.php'; @@ -26,7 +51,7 @@ require_once 'PEAR.php'; * useful in multiple mailer backends. * * @access public - * @version $Revision: 1.17 $ + * @version $Revision: 294747 $ * @package Mail */ class Mail @@ -82,12 +107,20 @@ class Mail * @return mixed Returns true on success, or a PEAR_Error * containing a descriptive error message on * failure. + * * @access public * @deprecated use Mail_mail::send instead */ function send($recipients, $headers, $body) { - $this->_sanitizeHeaders($headers); + if (!is_array($headers)) { + return PEAR::raiseError('$headers must be an array'); + } + + $result = $this->_sanitizeHeaders($headers); + if (is_a($result, 'PEAR_Error')) { + return $result; + } // if we're passed an array of recipients, implode it. if (is_array($recipients)) { @@ -103,10 +136,9 @@ class Mail } // flatten the headers out. - list(,$text_headers) = Mail::prepareHeaders($headers); + list(, $text_headers) = Mail::prepareHeaders($headers); return mail($recipients, $subject, $body, $text_headers); - } /** @@ -151,9 +183,9 @@ class Mail foreach ($headers as $key => $value) { if (strcasecmp($key, 'From') === 0) { include_once 'Mail/RFC822.php'; - $parser = &new Mail_RFC822(); + $parser = new Mail_RFC822(); $addresses = $parser->parseAddressList($value, 'localhost', false); - if (PEAR::isError($addresses)) { + if (is_a($addresses, 'PEAR_Error')) { return $addresses; } @@ -221,7 +253,7 @@ class Mail $addresses = Mail_RFC822::parseAddressList($recipients, 'localhost', false); // If parseAddressList() returned a PEAR_Error object, just return it. - if (PEAR::isError($addresses)) { + if (is_a($addresses, 'PEAR_Error')) { return $addresses; } diff --git a/extlib/Mail/RFC822.php b/extlib/Mail/RFC822.php old mode 100644 new mode 100755 index 8714df2e29..58d36465cb --- a/extlib/Mail/RFC822.php +++ b/extlib/Mail/RFC822.php @@ -1,37 +1,48 @@ | -// | Chuck Hagenbuch | -// +-----------------------------------------------------------------------+ +/** + * RFC 822 Email address list validation Utility + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2001-2010, Richard Heyes + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail + * @author Richard Heyes + * @author Chuck Hagenbuch * @author Chuck Hagenbuch - * @version $Revision: 1.24 $ + * @version $Revision: 294749 $ * @license BSD * @package Mail */ @@ -635,8 +646,8 @@ class Mail_RFC822 { $comment = $this->_splitCheck($parts, ')'); $comments[] = $comment; - // +1 is for the trailing ) - $_mailbox = substr($_mailbox, strpos($_mailbox, $comment)+strlen($comment)+1); + // +2 is for the brackets + $_mailbox = substr($_mailbox, strpos($_mailbox, '('.$comment)+strlen($comment)+2); } else { break; } diff --git a/extlib/Mail/mail.php b/extlib/Mail/mail.php old mode 100644 new mode 100755 index b13d695656..a8b4b5dbee --- a/extlib/Mail/mail.php +++ b/extlib/Mail/mail.php @@ -1,27 +1,52 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: mail.php,v 1.20 2007/10/06 17:00:00 chagenbu Exp $ +/** + * internal PHP-mail() implementation of the PEAR Mail:: interface. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010 Chuck Hagenbuch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail + * @author Chuck Hagenbuch + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: mail.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** * internal PHP-mail() implementation of the PEAR Mail:: interface. * @package Mail - * @version $Revision: 1.20 $ + * @version $Revision: 294747 $ */ class Mail_mail extends Mail { diff --git a/extlib/Mail/mock.php b/extlib/Mail/mock.php old mode 100644 new mode 100755 index 971dae6a0e..61570ba408 --- a/extlib/Mail/mock.php +++ b/extlib/Mail/mock.php @@ -1,29 +1,53 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: mock.php,v 1.1 2007/12/08 17:57:54 chagenbu Exp $ -// +/** + * Mock implementation + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010 Chuck Hagenbuch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail + * @author Chuck Hagenbuch + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: mock.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** * Mock implementation of the PEAR Mail:: interface for testing. * @access public * @package Mail - * @version $Revision: 1.1 $ + * @version $Revision: 294747 $ */ class Mail_mock extends Mail { diff --git a/extlib/Mail/null.php b/extlib/Mail/null.php old mode 100644 new mode 100755 index 982bfa45b6..f8d58272ee --- a/extlib/Mail/null.php +++ b/extlib/Mail/null.php @@ -1,29 +1,53 @@ | -// +----------------------------------------------------------------------+ -// -// $Id: null.php,v 1.2 2004/04/06 05:19:03 jon Exp $ -// +/** + * Null implementation of the PEAR Mail interface + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010 Phil Kernick + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category Mail + * @package Mail + * @author Phil Kernick + * @copyright 2010 Phil Kernick + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: null.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** * Null implementation of the PEAR Mail:: interface. * @access public * @package Mail - * @version $Revision: 1.2 $ + * @version $Revision: 294747 $ */ class Mail_null extends Mail { diff --git a/extlib/Mail/sendmail.php b/extlib/Mail/sendmail.php old mode 100644 new mode 100755 index cd248e61d2..b056575e99 --- a/extlib/Mail/sendmail.php +++ b/extlib/Mail/sendmail.php @@ -20,7 +20,7 @@ * Sendmail implementation of the PEAR Mail:: interface. * @access public * @package Mail - * @version $Revision: 1.19 $ + * @version $Revision: 294744 $ */ class Mail_sendmail extends Mail { @@ -117,7 +117,7 @@ class Mail_sendmail extends Mail { if (is_a($recipients, 'PEAR_Error')) { return $recipients; } - $recipients = escapeShellCmd(implode(' ', $recipients)); + $recipients = implode(' ', array_map('escapeshellarg', $recipients)); $headerElements = $this->prepareHeaders($headers); if (is_a($headerElements, 'PEAR_Error')) { @@ -141,7 +141,8 @@ class Mail_sendmail extends Mail { return PEAR::raiseError('From address specified with dangerous characters.'); } - $from = escapeShellCmd($from); + $from = escapeshellarg($from); // Security bug #16200 + $mail = @popen($this->sendmail_path . (!empty($this->sendmail_args) ? ' ' . $this->sendmail_args : '') . " -f$from -- $recipients", 'w'); if (!$mail) { return PEAR::raiseError('Failed to open sendmail [' . $this->sendmail_path . '] for execution.'); diff --git a/extlib/Mail/smtp.php b/extlib/Mail/smtp.php old mode 100644 new mode 100755 index baf3a962ba..52ea602086 --- a/extlib/Mail/smtp.php +++ b/extlib/Mail/smtp.php @@ -1,21 +1,48 @@ | -// | Jon Parise | -// +----------------------------------------------------------------------+ +/** + * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. + * + * PHP versions 4 and 5 + * + * LICENSE: + * + * Copyright (c) 2010, Chuck Hagenbuch + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * @category HTTP + * @package HTTP_Request + * @author Jon Parise + * @author Chuck Hagenbuch + * @copyright 2010 Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: smtp.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ + */ /** Error: Failed to create a Net_SMTP object */ define('PEAR_MAIL_SMTP_ERROR_CREATE', 10000); @@ -42,7 +69,7 @@ define('PEAR_MAIL_SMTP_ERROR_DATA', 10006); * SMTP implementation of the PEAR Mail interface. Requires the Net_SMTP class. * @access public * @package Mail - * @version $Revision: 1.33 $ + * @version $Revision: 294747 $ */ class Mail_smtp extends Mail { @@ -278,6 +305,16 @@ class Mail_smtp extends Mail { /* Send the message's headers and the body as SMTP data. */ $res = $this->_smtp->data($textHeaders . "\r\n\r\n" . $body); + list(,$args) = $this->_smtp->getResponse(); + + if (preg_match("/Ok: queued as (.*)/", $args, $queued)) { + $this->queued_as = $queued[1]; + } + + /* we need the greeting; from it we can extract the authorative name of the mail server we've really connected to. + * ideal if we're connecting to a round-robin of relay servers and need to track which exact one took the email */ + $this->greeting = $this->_smtp->getGreeting(); + if (is_a($res, 'PEAR_Error')) { $error = $this->_error('Failed to send data', $res); $this->_smtp->rset(); diff --git a/extlib/Mail/smtpmx.php b/extlib/Mail/smtpmx.php old mode 100644 new mode 100755 index 9d2dccfb13..f0b6940868 --- a/extlib/Mail/smtpmx.php +++ b/extlib/Mail/smtpmx.php @@ -8,19 +8,43 @@ * * PHP versions 4 and 5 * - * LICENSE: This source file is subject to version 3.0 of the PHP license - * that is available through the world-wide-web at the following URI: - * http://www.php.net/license/3_0.txt. If you did not receive a copy of - * the PHP License and are unable to obtain it through the web, please - * send a note to license@php.net so we can mail you a copy immediately. + * LICENSE: + * + * Copyright (c) 2010, gERD Schaufelberger + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * o Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * o Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * o The names of the authors may not be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @category Mail * @package Mail_smtpmx * @author gERD Schaufelberger - * @copyright 1997-2005 The PHP Group - * @license http://www.php.net/license/3_0.txt PHP License 3.0 - * @version CVS: $Id: smtpmx.php,v 1.2 2007/10/06 17:00:00 chagenbu Exp $ - * @see Mail + * @copyright 2010 gERD Schaufelberger + * @license http://opensource.org/licenses/bsd-license.php New BSD License + * @version CVS: $Id: smtpmx.php 294747 2010-02-08 08:18:33Z clockwerx $ + * @link http://pear.php.net/package/Mail/ */ require_once 'Net/SMTP.php'; @@ -32,7 +56,7 @@ require_once 'Net/SMTP.php'; * @access public * @author gERD Schaufelberger * @package Mail - * @version $Revision: 1.2 $ + * @version $Revision: 294747 $ */ class Mail_smtpmx extends Mail { diff --git a/extlib/Net/SMTP.php b/extlib/Net/SMTP.php index d632258d63..ea4b55e8d2 100644 --- a/extlib/Net/SMTP.php +++ b/extlib/Net/SMTP.php @@ -18,7 +18,7 @@ // | Damian Alejandro Fernandez Sosa | // +----------------------------------------------------------------------+ // -// $Id: SMTP.php,v 1.63 2008/06/10 05:39:12 jon Exp $ +// $Id: SMTP.php 293948 2010-01-24 21:46:00Z jon $ require_once 'PEAR.php'; require_once 'Net/Socket.php'; @@ -91,6 +91,13 @@ class Net_SMTP */ var $_debug = false; + /** + * Debug output handler. + * @var callback + * @access private + */ + var $_debug_handler = null; + /** * The socket resource being used to connect to the SMTP server. * @var resource @@ -112,6 +119,13 @@ class Net_SMTP */ var $_arguments = array(); + /** + * Stores the SMTP server's greeting string. + * @var string + * @access private + */ + var $_greeting = null; + /** * Stores detected features of the SMTP server. * @var array @@ -172,9 +186,30 @@ class Net_SMTP * @access public * @since 1.1.0 */ - function setDebug($debug) + function setDebug($debug, $handler = null) { $this->_debug = $debug; + $this->_debug_handler = $handler; + } + + /** + * Write the given debug text to the current debug output handler. + * + * @param string $message Debug mesage text. + * + * @access private + * @since 1.3.3 + */ + function _debug($message) + { + if ($this->_debug) { + if ($this->_debug_handler) { + call_user_func_array($this->_debug_handler, + array(&$this, $message)); + } else { + echo "DEBUG: $message\n"; + } + } } /** @@ -189,13 +224,12 @@ class Net_SMTP */ function _send($data) { - if ($this->_debug) { - echo "DEBUG: Send: $data\n"; - } + $this->_debug("Send: $data"); - if (PEAR::isError($error = $this->_socket->write($data))) { - return PEAR::raiseError('Failed to write to socket: ' . - $error->getMessage()); + $error = $this->_socket->write($data); + if ($error === false || PEAR::isError($error)) { + $msg = ($error) ? $error->getMessage() : "unknown error"; + return PEAR::raiseError("Failed to write to socket: $msg"); } return true; @@ -262,9 +296,7 @@ class Net_SMTP for ($i = 0; $i <= $this->_pipelined_commands; $i++) { while ($line = $this->_socket->readLine()) { - if ($this->_debug) { - echo "DEBUG: Recv: $line\n"; - } + $this->_debug("Recv: $line"); /* If we receive an empty line, the connection has been closed. */ if (empty($line)) { @@ -319,6 +351,20 @@ class Net_SMTP return array($this->_code, join("\n", $this->_arguments)); } + /** + * Return the SMTP server's greeting string. + * + * @return string A string containing the greeting string, or null if a + * greeting has not been received. + * + * @access public + * @since 1.3.3 + */ + function getGreeting() + { + return $this->_greeting; + } + /** * Attempt to connect to the SMTP server. * @@ -334,6 +380,7 @@ class Net_SMTP */ function connect($timeout = null, $persistent = false) { + $this->_greeting = null; $result = $this->_socket->connect($this->host, $this->port, $persistent, $timeout); if (PEAR::isError($result)) { @@ -344,6 +391,10 @@ class Net_SMTP if (PEAR::isError($error = $this->_parseResponse(220))) { return $error; } + + /* Extract and store a copy of the server's greeting string. */ + list(, $this->_greeting) = $this->getResponse(); + if (PEAR::isError($error = $this->_negotiate())) { return $error; } @@ -452,40 +503,43 @@ class Net_SMTP * @param string The password to authenticate with. * @param string The requested authentication method. If none is * specified, the best supported method will be used. + * @param bool Flag indicating whether or not TLS should be attempted. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @access public * @since 1.0 */ - function auth($uid, $pwd , $method = '') + function auth($uid, $pwd , $method = '', $tls = true) { - if (empty($this->_esmtp['AUTH'])) { - if (version_compare(PHP_VERSION, '5.1.0', '>=')) { - if (!isset($this->_esmtp['STARTTLS'])) { - return PEAR::raiseError('SMTP server does not support authentication'); - } - if (PEAR::isError($result = $this->_put('STARTTLS'))) { - return $result; - } - if (PEAR::isError($result = $this->_parseResponse(220))) { - return $result; - } - if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { - return $result; - } elseif ($result !== true) { - return PEAR::raiseError('STARTTLS failed'); - } - - /* Send EHLO again to recieve the AUTH string from the - * SMTP server. */ - $this->_negotiate(); - if (empty($this->_esmtp['AUTH'])) { - return PEAR::raiseError('SMTP server does not support authentication'); - } - } else { - return PEAR::raiseError('SMTP server does not support authentication'); + /* We can only attempt a TLS connection if one has been requested, + * we're running PHP 5.1.0 or later, have access to the OpenSSL + * extension, are connected to an SMTP server which supports the + * STARTTLS extension, and aren't already connected over a secure + * (SSL) socket connection. */ + if ($tls && version_compare(PHP_VERSION, '5.1.0', '>=') && + extension_loaded('openssl') && isset($this->_esmtp['STARTTLS']) && + strncasecmp($this->host, 'ssl://', 6) !== 0) { + /* Start the TLS connection attempt. */ + if (PEAR::isError($result = $this->_put('STARTTLS'))) { + return $result; } + if (PEAR::isError($result = $this->_parseResponse(220))) { + return $result; + } + if (PEAR::isError($result = $this->_socket->enableCrypto(true, STREAM_CRYPTO_METHOD_TLS_CLIENT))) { + return $result; + } elseif ($result !== true) { + return PEAR::raiseError('STARTTLS failed'); + } + + /* Send EHLO again to recieve the AUTH string from the + * SMTP server. */ + $this->_negotiate(); + } + + if (empty($this->_esmtp['AUTH'])) { + return PEAR::raiseError('SMTP server does not support authentication'); } /* If no method has been specified, get the name of the best @@ -844,30 +898,51 @@ class Net_SMTP /** * Send the DATA command. * - * @param string $data The message body to send. + * @param mixed $data The message data, either as a string or an open + * file resource. + * @param string $headers The message headers. If $headers is provided, + * $data is assumed to contain only body data. * * @return mixed Returns a PEAR_Error with an error message on any * kind of failure, or true on success. * @access public * @since 1.0 */ - function data($data) + function data($data, $headers = null) { + /* Verify that $data is a supported type. */ + if (!is_string($data) && !is_resource($data)) { + return PEAR::raiseError('Expected a string or file resource'); + } + /* RFC 1870, section 3, subsection 3 states "a value of zero * indicates that no fixed maximum message size is in force". * Furthermore, it says that if "the parameter is omitted no * information is conveyed about the server's fixed maximum * message size". */ if (isset($this->_esmtp['SIZE']) && ($this->_esmtp['SIZE'] > 0)) { - if (strlen($data) >= $this->_esmtp['SIZE']) { + /* Start by considering the size of the optional headers string. + * We also account for the addition 4 character "\r\n\r\n" + * separator sequence. */ + $size = (is_null($headers)) ? 0 : strlen($headers) + 4; + + if (is_resource($data)) { + $stat = fstat($data); + if ($stat === false) { + return PEAR::raiseError('Failed to get file size'); + } + $size += $stat['size']; + } else { + $size += strlen($data); + } + + if ($size >= $this->_esmtp['SIZE']) { $this->disconnect(); - return PEAR::raiseError('Message size excedes the server limit'); + return PEAR::raiseError('Message size exceeds server limit'); } } - /* Quote the data based on the SMTP standards. */ - $this->quotedata($data); - + /* Initiate the DATA command. */ if (PEAR::isError($error = $this->_put('DATA'))) { return $error; } @@ -875,9 +950,40 @@ class Net_SMTP return $error; } - if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) { - return $result; + /* If we have a separate headers string, send it first. */ + if (!is_null($headers)) { + $this->quotedata($headers); + if (PEAR::isError($result = $this->_send($headers . "\r\n\r\n"))) { + return $result; + } } + + /* Now we can send the message body data. */ + if (is_resource($data)) { + /* Stream the contents of the file resource out over our socket + * connection, line by line. Each line must be run through the + * quoting routine. */ + while ($line = fgets($data, 1024)) { + $this->quotedata($line); + if (PEAR::isError($result = $this->_send($line))) { + return $result; + } + } + + /* Finally, send the DATA terminator sequence. */ + if (PEAR::isError($result = $this->_send("\r\n.\r\n"))) { + return $result; + } + } else { + /* Just send the entire quoted string followed by the DATA + * terminator. */ + $this->quotedata($data); + if (PEAR::isError($result = $this->_send($data . "\r\n.\r\n"))) { + return $result; + } + } + + /* Verify that the data was successfully received by the server. */ if (PEAR::isError($error = $this->_parseResponse(250, $this->pipelining))) { return $error; } From ecf9dc6d1b5a068b2e5ba23debf2b7bec04d3d2c Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Mon, 3 May 2010 21:25:10 -0400 Subject: [PATCH 026/655] use the new maxNoticeLength and maxUrlLength functionality introduced in commit 14adb7cc41e3d5d4e543c1f13f7a60d3cadb5c71 --- .../ClientSideShorten/ClientSideShortenPlugin.php | 4 +++- plugins/ClientSideShorten/shorten.js | 15 +++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/plugins/ClientSideShorten/ClientSideShortenPlugin.php b/plugins/ClientSideShorten/ClientSideShortenPlugin.php index ba1f7d3a7c..454bedb084 100644 --- a/plugins/ClientSideShorten/ClientSideShortenPlugin.php +++ b/plugins/ClientSideShorten/ClientSideShortenPlugin.php @@ -51,8 +51,10 @@ class ClientSideShortenPlugin extends Plugin } function onEndShowScripts($action){ - $action->inlineScript('var Notice_maxContent = ' . Notice::maxContent()); if (common_logged_in()) { + $user = common_current_user(); + $action->inlineScript('var maxNoticeLength = ' . User_urlshortener_prefs::maxNoticeLength($user)); + $action->inlineScript('var maxUrlLength = ' . User_urlshortener_prefs::maxUrlLength($user)); $action->script('plugins/ClientSideShorten/shorten.js'); } } diff --git a/plugins/ClientSideShorten/shorten.js b/plugins/ClientSideShorten/shorten.js index 856c7f05fd..bdffb81e26 100644 --- a/plugins/ClientSideShorten/shorten.js +++ b/plugins/ClientSideShorten/shorten.js @@ -31,10 +31,21 @@ })(jQuery,'smartkeypress'); + function longestWordInString(string) + { + var words = string.split(/\s/); + var longestWord = 0; + for(var i=0;i longestWord) longestWord = words[i].length; + return longestWord; + } + function shorten() { - $noticeDataText = $('#'+SN.C.S.NoticeDataText); - if(Notice_maxContent > 0 && $noticeDataText.val().length > Notice_maxContent){ + var $noticeDataText = $('#'+SN.C.S.NoticeDataText); + var noticeText = $noticeDataText.val(); + + if(noticeText.length > maxNoticeLength || longestWordInString(noticeText) > maxUrlLength) { var original = $noticeDataText.val(); shortenAjax = $.ajax({ url: $('address .url')[0].href+'/plugins/ClientSideShorten/shorten', From f803c1fbfecbe31d30441c695e44b7f62b02cebf Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Tue, 4 May 2010 12:31:55 -0700 Subject: [PATCH 027/655] Add Emacs Identica-mode to notice sources --- db/notice_source.sql | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/db/notice_source.sql b/db/notice_source.sql index 50660e9480..f9c5256791 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -12,7 +12,8 @@ VALUES ('deskbar','Deskbar-Applet','http://www.gnome.org/projects/deskbar-applet/', now()), ('Do','Gnome Do','http://do.davebsd.com/wiki/index.php?title=Microblog_Plugin', now()), ('drupal','Drupal','http://drupal.org/', now()), - ('eventbox','EventBox','http://thecosmicmachine.com/eventbox/ ', now()), + ('eventbox','EventBox','http://thecosmicmachine.com/eventbox/', now()), + ('identica-mode','Emacs Identica-mode','http://nongnu.org/identica-mode/', now()), ('Facebook','Facebook','http://apps.facebook.com/identica/', now()), ('feed2omb','feed2omb','http://projects.ciarang.com/p/feed2omb/', now()), ('get2gnow', 'get2gnow', 'http://uberchicgeekchick.com/?projects=get2gnow', now()), From ddc7811a7b412c9c3c4b6bfb9350dd18a62fdf51 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 May 2010 16:52:31 -0700 Subject: [PATCH 028/655] Move XMPPHP from core extlibs to Xmpp plugin extlibs --- plugins/Xmpp/XmppPlugin.php | 4 +--- {extlib => plugins/Xmpp/extlib}/XMPPHP/BOSH.php | 0 {extlib => plugins/Xmpp/extlib}/XMPPHP/Exception.php | 0 {extlib => plugins/Xmpp/extlib}/XMPPHP/Log.php | 0 {extlib => plugins/Xmpp/extlib}/XMPPHP/Roster.php | 0 {extlib => plugins/Xmpp/extlib}/XMPPHP/XMLObj.php | 0 {extlib => plugins/Xmpp/extlib}/XMPPHP/XMLStream.php | 0 {extlib => plugins/Xmpp/extlib}/XMPPHP/XMPP.php | 0 {extlib => plugins/Xmpp/extlib}/XMPPHP/XMPP_Old.php | 0 9 files changed, 1 insertion(+), 3 deletions(-) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/BOSH.php (100%) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/Exception.php (100%) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/Log.php (100%) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/Roster.php (100%) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/XMLObj.php (100%) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/XMLStream.php (100%) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/XMPP.php (100%) rename {extlib => plugins/Xmpp/extlib}/XMPPHP/XMPP_Old.php (100%) diff --git a/plugins/Xmpp/XmppPlugin.php b/plugins/Xmpp/XmppPlugin.php index a2521536bc..9d75e2475d 100644 --- a/plugins/Xmpp/XmppPlugin.php +++ b/plugins/Xmpp/XmppPlugin.php @@ -34,8 +34,6 @@ if (!defined('STATUSNET')) { exit(1); } -set_include_path(get_include_path() . PATH_SEPARATOR . INSTALLDIR . '/extlib/XMPPHP'); - /** * Plugin for XMPP * @@ -287,7 +285,7 @@ class XmppPlugin extends ImPlugin switch ($cls) { case 'XMPPHP_XMPP': - require_once 'XMPP.php'; + require_once $dir . '/extlib/XMPPHP/XMPP.php'; return false; case 'Sharing_XMPP': case 'Queued_XMPP': diff --git a/extlib/XMPPHP/BOSH.php b/plugins/Xmpp/extlib/XMPPHP/BOSH.php similarity index 100% rename from extlib/XMPPHP/BOSH.php rename to plugins/Xmpp/extlib/XMPPHP/BOSH.php diff --git a/extlib/XMPPHP/Exception.php b/plugins/Xmpp/extlib/XMPPHP/Exception.php similarity index 100% rename from extlib/XMPPHP/Exception.php rename to plugins/Xmpp/extlib/XMPPHP/Exception.php diff --git a/extlib/XMPPHP/Log.php b/plugins/Xmpp/extlib/XMPPHP/Log.php similarity index 100% rename from extlib/XMPPHP/Log.php rename to plugins/Xmpp/extlib/XMPPHP/Log.php diff --git a/extlib/XMPPHP/Roster.php b/plugins/Xmpp/extlib/XMPPHP/Roster.php similarity index 100% rename from extlib/XMPPHP/Roster.php rename to plugins/Xmpp/extlib/XMPPHP/Roster.php diff --git a/extlib/XMPPHP/XMLObj.php b/plugins/Xmpp/extlib/XMPPHP/XMLObj.php similarity index 100% rename from extlib/XMPPHP/XMLObj.php rename to plugins/Xmpp/extlib/XMPPHP/XMLObj.php diff --git a/extlib/XMPPHP/XMLStream.php b/plugins/Xmpp/extlib/XMPPHP/XMLStream.php similarity index 100% rename from extlib/XMPPHP/XMLStream.php rename to plugins/Xmpp/extlib/XMPPHP/XMLStream.php diff --git a/extlib/XMPPHP/XMPP.php b/plugins/Xmpp/extlib/XMPPHP/XMPP.php similarity index 100% rename from extlib/XMPPHP/XMPP.php rename to plugins/Xmpp/extlib/XMPPHP/XMPP.php diff --git a/extlib/XMPPHP/XMPP_Old.php b/plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php similarity index 100% rename from extlib/XMPPHP/XMPP_Old.php rename to plugins/Xmpp/extlib/XMPPHP/XMPP_Old.php From 3e8af172d636cc7ca3a9e2301b928f6ca79b9eb2 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Wed, 5 May 2010 17:30:42 -0700 Subject: [PATCH 029/655] Add ?uselang=xx language override option (only valid, locally-enabled languages supported, just as with headers and user settings). Great aid for debugging & translation testing --- lib/util.php | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/lib/util.php b/lib/util.php index e7ea9df613..e15c69f950 100644 --- a/lib/util.php +++ b/lib/util.php @@ -138,23 +138,38 @@ function common_timezone() return common_config('site', 'timezone'); } +function common_valid_language($lang) +{ + if ($lang) { + // Validate -- we don't want to end up with a bogus code + // left over from some old junk. + foreach (common_config('site', 'languages') as $code => $info) { + if ($info['lang'] == $lang) { + return true; + } + } + } + return false; +} + function common_language() { + // Allow ?uselang=xx override, very useful for debugging + // and helping translators check usage and context. + if (isset($_GET['uselang'])) { + $uselang = strval($_GET['uselang']); + if (common_valid_language($uselang)) { + return $uselang; + } + } // If there is a user logged in and they've set a language preference // then return that one... if (_have_config() && common_logged_in()) { $user = common_current_user(); - $user_language = $user->language; - if ($user->language) { - // Validate -- we don't want to end up with a bogus code - // left over from some old junk. - foreach (common_config('site', 'languages') as $code => $info) { - if ($info['lang'] == $user_language) { - return $user_language; - } - } + if (common_valid_language($user->language)) { + return $user->language; } } From 30328fc1666b9e3a6651c5d8881933debaf5ecc6 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 6 May 2010 23:33:27 -0400 Subject: [PATCH 030/655] Enable ClientSideShorten plugin by default --- lib/default.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/default.php b/lib/default.php index 449d2b3bb4..52a4ec7836 100644 --- a/lib/default.php +++ b/lib/default.php @@ -287,6 +287,7 @@ $default = 'OStatus' => null, 'WikiHashtags' => null, 'RSSCloud' => null, + 'ClientSideShorten' => null, 'OpenID' => null), ), 'pluginlist' => array(), From 4b0458801af03b40fa636849da0a7e96bbd3e860 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Thu, 6 May 2010 23:40:07 -0400 Subject: [PATCH 031/655] Ignore PEAR errors with code DB_DATAOBJECT_ERROR_NODATA --- lib/common.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/common.php b/lib/common.php index 2bda88c974..72a1b70751 100644 --- a/lib/common.php +++ b/lib/common.php @@ -132,6 +132,12 @@ require_once INSTALLDIR.'/lib/serverexception.php'; //set PEAR error handling to use regular PHP exceptions function PEAR_ErrorToPEAR_Exception($err) { + //DB_DataObject throws error when an empty set would be returned + //That behavior is weird, and not how the rest of StatusNet works. + //So just ignore those errors. + if ($err->getCode() == DB_DATAOBJECT_ERROR_NODATA) { + return; + } if ($err->getCode()) { throw new PEAR_Exception($err->getMessage(), $err->getCode()); } From b407665b983297078f5361db24c60a7d46e0f4ba Mon Sep 17 00:00:00 2001 From: Zachary Copley Date: Tue, 20 Apr 2010 11:29:13 -0700 Subject: [PATCH 032/655] Initial work on API method for updating a group's profile info --- actions/apigroupprofileupdate.php | 370 ++++++++++++++++++++++++++++++ lib/router.php | 6 + 2 files changed, 376 insertions(+) create mode 100644 actions/apigroupprofileupdate.php diff --git a/actions/apigroupprofileupdate.php b/actions/apigroupprofileupdate.php new file mode 100644 index 0000000000..0d3620c265 --- /dev/null +++ b/actions/apigroupprofileupdate.php @@ -0,0 +1,370 @@ +. + * + * @category API + * @package StatusNet + * @author Zach Copley + * @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')) { + exit(1); +} + +require_once INSTALLDIR . '/lib/apiauth.php'; + +class ApiValidationException extends Exception { } + +/** + * API analog to the group edit page + * + * @category API + * @package StatusNet + * @author Zach Copley + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + */ + +class ApiGroupProfileUpdateAction extends ApiAuthAction +{ + + /** + * Take arguments for running + * + * @param array $args $_REQUEST args + * + * @return boolean success flag + * + */ + + function prepare($args) + { + parent::prepare($args); + + $this->nickname = common_canonical_nickname($this->trimmed('nickname')); + $this->fullname = $this->trimmed('fullname'); + $this->homepage = $this->trimmed('homepage'); + $this->description = $this->trimmed('description'); + $this->location = $this->trimmed('location'); + $this->aliasstring = $this->trimmed('aliases'); + + $this->user = $this->auth_user; + $this->group = $this->getTargetGroup($this->arg('id')); + + return true; + } + + /** + * Handle the request + * + * See which request params have been set, and update the profile + * + * @param array $args $_REQUEST data (unused) + * + * @return void + */ + + function handle($args) + { + parent::handle($args); + + if ($_SERVER['REQUEST_METHOD'] != 'POST') { + $this->clientError( + _('This method requires a POST.'), + 400, $this->format + ); + return; + } + + if (!in_array($this->format, array('xml', 'json'))) { + $this->clientError( + _('API method not found.'), + 404, + $this->format + ); + return; + } + + if (empty($this->user)) { + $this->clientError(_('No such user.'), 404, $this->format); + return; + } + + if (empty($this->group)) { + $this->clientError(_('Group not found.'), 404, $this->format); + return false; + } + + if (!$this->user->isAdmin($this->group)) { + $this->clientError(_('You must be an admin to edit the group.'), 403); + return false; + } + + $this->group->query('BEGIN'); + + $orig = clone($this->group); + + try { + + if (!empty($this->nickname)) { + if ($this->validateNickname()) { + $this->group->nickname = $this->nickname; + $this->group->mainpage = common_local_url( + 'showgroup', + array('nickname' => $this->nickname) + ); + } + } + + if (!empty($this->fullname)) { + $this->validateFullname(); + $this->group->fullname = $this->fullname; + } + + if (!empty($this->homepage)) { + $this->validateHomepage(); + $this->group->homepage = $this->hompage; + } + + if (!empty($this->description)) { + $this->validateDescription(); + $this->group->description = $this->decription; + } + + if (!empty($this->location)) { + $this->validateLocation(); + $this->group->location = $this->location; + } + + } catch (ApiValidationException $ave) { + $this->clientError( + $ave->getMessage(), + 403, + $this->format + ); + return; + } + + $result = $this->group->update($orig); + + if (!$result) { + common_log_db_error($this->group, 'UPDATE', __FILE__); + $this->serverError(_('Could not update group.')); + } + + $aliases = null; + + try { + + if (!empty($this->aliasstring)) { + $aliases = $this->parseAliases(); + } + + } catch (ApiValidationException $ave) { + $this->clientError( + $ave->getMessage(), + 403, + $this->format + ); + return; + } + + $result = $this->group->setAliases($aliases); + + if (!$result) { + $this->serverError(_('Could not create aliases.')); + } + + if (!empty($this->nickname) && $this->nickname != $orig->nickname) { + common_log(LOG_INFO, "Saving local group info."); + $local = Local_group::staticGet('group_id', $this->group->id); + $local->setNickname($this->nickname); + } + + $this->group->query('COMMIT'); + + switch($this->format) { + case 'xml': + $this->showSingleXmlGroup($this->group); + break; + case 'json': + $this->showSingleJsonGroup($this->group); + break; + default: + $this->clientError(_('API method not found.'), 404, $this->format); + break; + } + } + + function nicknameExists($nickname) + { + $group = Local_group::staticGet('nickname', $nickname); + + if (!empty($group) && + $group->group_id != $this->group->id) { + return true; + } + + $alias = Group_alias::staticGet('alias', $nickname); + + if (!empty($alias) && + $alias->group_id != $this->group->id) { + return true; + } + + return false; + } + + function validateNickname() + { + if (!Validate::string( + $this->nickname, array( + 'min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT + ) + ) + ) { + throw new ApiValidationException( + _( + 'Nickname must have only lowercase letters ' . + 'and numbers and no spaces.' + ) + ); + } else if ($this->nicknameExists($this->nickname)) { + throw new ApiValidationException( + _('Nickname already in use. Try another one.') + ); + } else if (!User_group::allowedNickname($this->nickname)) { + throw new ApiValidationException( + _('Not a valid nickname.') + ); + } + } + + function validateHomepage() + { + if (!is_null($this->homepage) + && (strlen($this->homepage) > 0) + && !Validate::uri( + $this->homepage, + array('allowed_schemes' => array('http', 'https') + ) + ) + ) { + throw new ApiValidationException( + _('Homepage is not a valid URL.') + ); + } + } + + function validateFullname() + { + if (!is_null($this->fullname) && mb_strlen($this->fullname) > 255) { + throw new ApiValidationException( + _('Full name is too long (max 255 chars).') + ); + } + } + + function validateDescription() + { + if (User_group::descriptionTooLong($this->description)) { + throw new ApiValidationException( + sprintf( + _('description is too long (max %d chars).'), + User_group::maxDescription() + ) + ); + } + } + + function validateLocation() + { + if (!is_null($this->location) && mb_strlen($this->location) > 255) { + throw new ApiValidationException( + _('Location is too long (max 255 chars).') + ); + } + } + + function validateAliases() + { + $aliases = array_map( + 'common_canonical_nickname', + array_unique( + preg_split('/[\s,]+/', + $this->aliasstring + ) + ) + ); + + if (empty($aliases)) { + $aliases = array(); + } + + if (count($aliases) > common_config('group', 'maxaliases')) { + throw new ApiValidationException( + sprintf( + _('Too many aliases! Maximum %d.'), + common_config('group', 'maxaliases') + ) + ); + } + + foreach ($aliases as $alias) { + if (!Validate::string( + $alias, array( + 'min_length' => 1, + 'max_length' => 64, + 'format' => NICKNAME_FMT) + ) + ) { + throw new ApiValidationException( + sprintf( + _('Invalid alias: "%s"'), + $alias + ) + ); + } + + if ($this->nicknameExists($alias)) { + throw new ApiValidationException( + sprintf( + _('Alias "%s" already in use. Try another one.'), + $alias) + ); + } + + // XXX assumes alphanum nicknames + if (strcmp($alias, $nickname) == 0) { + throw new ApiValidationException( + _('Alias can\'t be the same as nickname.') + ); + } + } + + return $aliases; + } + +} \ No newline at end of file diff --git a/lib/router.php b/lib/router.php index a040abb832..faa26c8610 100644 --- a/lib/router.php +++ b/lib/router.php @@ -650,6 +650,12 @@ class Router $m->connect('api/statusnet/groups/create.:format', array('action' => 'ApiGroupCreate', 'format' => '(xml|json)')); + + $m->connect('api/statusnet/groups/update/:id.:format', + array('action' => 'ApiGroupProfileUpdate', + 'id' => '[a-zA-Z0-9]+', + 'format' => '(xml|json)')); + // Tags $m->connect('api/statusnet/tags/timeline/:tag.:format', array('action' => 'ApiTimelineTag', From 06a63b0404aa96efc1118563482c11567b048961 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 7 May 2010 00:52:54 -0700 Subject: [PATCH 033/655] Finish api/statusnet/groups/update --- actions/apigroupprofileupdate.php | 19 ++++++++----------- lib/apiaction.php | 2 ++ 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/actions/apigroupprofileupdate.php b/actions/apigroupprofileupdate.php index 0d3620c265..6ac4b5a4b5 100644 --- a/actions/apigroupprofileupdate.php +++ b/actions/apigroupprofileupdate.php @@ -33,8 +33,6 @@ if (!defined('STATUSNET')) { require_once INSTALLDIR . '/lib/apiauth.php'; -class ApiValidationException extends Exception { } - /** * API analog to the group edit page * @@ -62,6 +60,7 @@ class ApiGroupProfileUpdateAction extends ApiAuthAction parent::prepare($args); $this->nickname = common_canonical_nickname($this->trimmed('nickname')); + $this->fullname = $this->trimmed('fullname'); $this->homepage = $this->trimmed('homepage'); $this->description = $this->trimmed('description'); @@ -172,12 +171,12 @@ class ApiGroupProfileUpdateAction extends ApiAuthAction $this->serverError(_('Could not update group.')); } - $aliases = null; + $aliases = array(); try { - if (!empty($this->aliasstring)) { - $aliases = $this->parseAliases(); + if (!empty($this->aliasstring)) { + $aliases = $this->validateAliases(); } } catch (ApiValidationException $ave) { @@ -195,7 +194,7 @@ class ApiGroupProfileUpdateAction extends ApiAuthAction $this->serverError(_('Could not create aliases.')); } - if (!empty($this->nickname) && $this->nickname != $orig->nickname) { + if (!empty($this->nickname) && ($this->nickname != $orig->nickname)) { common_log(LOG_INFO, "Saving local group info."); $local = Local_group::staticGet('group_id', $this->group->id); $local->setNickname($this->nickname); @@ -260,6 +259,8 @@ class ApiGroupProfileUpdateAction extends ApiAuthAction _('Not a valid nickname.') ); } + + return true; } function validateHomepage() @@ -319,10 +320,6 @@ class ApiGroupProfileUpdateAction extends ApiAuthAction ) ); - if (empty($aliases)) { - $aliases = array(); - } - if (count($aliases) > common_config('group', 'maxaliases')) { throw new ApiValidationException( sprintf( @@ -357,7 +354,7 @@ class ApiGroupProfileUpdateAction extends ApiAuthAction } // XXX assumes alphanum nicknames - if (strcmp($alias, $nickname) == 0) { + if (strcmp($alias, $this->nickname) == 0) { throw new ApiValidationException( _('Alias can\'t be the same as nickname.') ); diff --git a/lib/apiaction.php b/lib/apiaction.php index 42aa08ef7d..d35391d4ea 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -97,6 +97,8 @@ if (!defined('STATUSNET')) { exit(1); } +class ApiValidationException extends Exception { } + /** * Contains most of the Twitter-compatible API output functions. * From da18701394ef717cd68dad11f5a830719ad675e6 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 7 May 2010 16:32:24 -0700 Subject: [PATCH 034/655] Fix for repeats from the API having null source attribution --- actions/apidirectmessagenew.php | 8 -------- actions/apistatusesretweet.php | 2 +- actions/apistatusesupdate.php | 12 ------------ lib/apiaction.php | 9 +++++++++ lib/apiauth.php | 3 +-- 5 files changed, 11 insertions(+), 23 deletions(-) diff --git a/actions/apidirectmessagenew.php b/actions/apidirectmessagenew.php index b9ac92d77b..65d065648f 100644 --- a/actions/apidirectmessagenew.php +++ b/actions/apidirectmessagenew.php @@ -52,7 +52,6 @@ require_once INSTALLDIR . '/lib/apiauth.php'; class ApiDirectMessageNewAction extends ApiAuthAction { - var $source = null; var $other = null; var $content = null; @@ -76,13 +75,6 @@ class ApiDirectMessageNewAction extends ApiAuthAction return; } - $this->source = $this->trimmed('source'); // Not supported by Twitter. - - $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); - if (empty($this->source) || in_array($this->source, $reserved_sources)) { - $source = 'api'; - } - $this->content = $this->trimmed('text'); $this->user = $this->auth_user; diff --git a/actions/apistatusesretweet.php b/actions/apistatusesretweet.php index 128c881e25..9aa3374854 100644 --- a/actions/apistatusesretweet.php +++ b/actions/apistatusesretweet.php @@ -79,7 +79,7 @@ class ApiStatusesRetweetAction extends ApiAuthAction $this->user = $this->auth_user; - if ($this->user->id == $notice->profile_id) { + if ($this->user->id == $this->original->profile_id) { $this->clientError(_('Cannot repeat your own notice.'), 400, $this->format); return false; diff --git a/actions/apistatusesupdate.php b/actions/apistatusesupdate.php index 5f3a447c23..a0a81f3368 100644 --- a/actions/apistatusesupdate.php +++ b/actions/apistatusesupdate.php @@ -155,8 +155,6 @@ class ApiStatusesUpdateAction extends ApiAuthAction var $lat = null; var $lon = null; - static $reserved_sources = array('web', 'omb', 'mail', 'xmpp', 'api'); - /** * Take arguments for running * @@ -171,19 +169,9 @@ class ApiStatusesUpdateAction extends ApiAuthAction parent::prepare($args); $this->status = $this->trimmed('status'); - $this->source = $this->trimmed('source'); $this->lat = $this->trimmed('lat'); $this->lon = $this->trimmed('long'); - // try to set the source attr from OAuth app - if (empty($this->source)) { - $this->source = $this->oauth_source; - } - - if (empty($this->source) || in_array($this->source, self::$reserved_sources)) { - $this->source = 'api'; - } - $this->in_reply_to_status_id = intval($this->trimmed('in_reply_to_status_id')); diff --git a/lib/apiaction.php b/lib/apiaction.php index d35391d4ea..e481a1ef29 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -126,9 +126,12 @@ class ApiAction extends Action var $count = null; var $max_id = null; var $since_id = null; + var $source = null; var $access = self::READ_ONLY; // read (default) or read-write + static $reserved_sources = array('web', 'omb', 'ostatus', 'mail', 'xmpp', 'api'); + /** * Initialization. * @@ -152,6 +155,12 @@ class ApiAction extends Action header('X-StatusNet-Warning: since parameter is disabled; use since_id'); } + $this->source = $this->trimmed('source'); + + if (empty($this->source) || in_array($this->source, self::$reserved_sources)) { + $this->source = 'api'; + } + return true; } diff --git a/lib/apiauth.php b/lib/apiauth.php index 8c39988889..9c68e27713 100644 --- a/lib/apiauth.php +++ b/lib/apiauth.php @@ -72,7 +72,6 @@ class ApiAuthAction extends ApiAction { var $auth_user_nickname = null; var $auth_user_password = null; - var $oauth_source = null; /** * Take arguments for running, looks for an OAuth request, @@ -181,7 +180,7 @@ class ApiAuthAction extends ApiAction // set the source attr - $this->oauth_source = $app->name; + $this->source = $app->name; $appUser = Oauth_application_user::staticGet('token', $access_token); From cef2ded9e79b2d84fddd6e7f5af09c02c50315a8 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Fri, 21 May 2010 10:29:28 -0700 Subject: [PATCH 035/655] Add TweetDeck to notice sources --- db/notice_source.sql | 1 + 1 file changed, 1 insertion(+) diff --git a/db/notice_source.sql b/db/notice_source.sql index f9c5256791..fbcdd6568e 100644 --- a/db/notice_source.sql +++ b/db/notice_source.sql @@ -54,6 +54,7 @@ VALUES ('tr.im','tr.im','http://tr.im/', now()), ('triklepost', 'Tricklepost', 'http://github.com/zcopley/tricklepost/tree/master', now()), ('tweenky','Tweenky','http://beta.tweenky.com/', now()), + ('TweetDeck', 'TweetDeck', 'http://www.tweetdeck.com/', now()), ('twhirl','Twhirl','http://www.twhirl.org/', now()), ('twibble','twibble','http://www.twibble.de/', now()), ('Twidge','Twidge','http://software.complete.org/twidge', now()), From bcca10f5268ac2c2945479dd93d2f302478e02f9 Mon Sep 17 00:00:00 2001 From: Marcel van der Boom Date: Thu, 27 May 2010 19:25:45 +0200 Subject: [PATCH 036/655] Add implementation of API method home_timeline method --- plugins/TwitterBridge/twitteroauthclient.php | 30 ++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index d895d8c73c..6b821ba181 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -217,6 +217,36 @@ class TwitterOAuthClient extends OAuthClient return $statuses; } + /** + * Calls Twitter's /statuses/home_timeline API method + * + * @param int $since_id show statuses after this id + * @param int $max_id show statuses before this id + * @param int $cnt number of statuses to show + * @param int $page page number + * + * @return mixed an array of statuses, similare to friends_timeline, except including retweets + */ + function statusesHomeTimeline($since_id = null, $max_id = null, + $cnt = null, $page = null) + { + + $url = 'https://twitter.com/statuses/home_timeline.json'; + $params = array('since_id' => $since_id, + 'max_id' => $max_id, + 'count' => $cnt, + 'page' => $page); + $qry = http_build_query($params); + + if (!empty($qry)) { + $url .= "?$qry"; + } + + $response = $this->oAuthGet($url); + $statuses = json_decode($response); + return $statuses; + } + /** * Calls Twitter's /statuses/friends API method * From 4211b7f01188b4ab64407e32b380366a048102f4 Mon Sep 17 00:00:00 2001 From: Zach Copley Date: Thu, 27 May 2010 11:21:52 -0700 Subject: [PATCH 037/655] - Implement statusesHomeTimeline() in TwitterBasicAuthClient - Make TwitterStatusFetcher pull home_timeline (includes retweets) instead of friends_timeline --- .../daemons/twitterstatusfetcher.php | 2 +- .../TwitterBridge/twitterbasicauthclient.php | 31 ++++++++++++++++++- plugins/TwitterBridge/twitteroauthclient.php | 2 +- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php index 7c624fdb3b..03a4bd3f34 100755 --- a/plugins/TwitterBridge/daemons/twitterstatusfetcher.php +++ b/plugins/TwitterBridge/daemons/twitterstatusfetcher.php @@ -186,7 +186,7 @@ class TwitterStatusFetcher extends ParallelizingDaemon $timeline = null; try { - $timeline = $client->statusesFriendsTimeline(); + $timeline = $client->statusesHomeTimeline(); } catch (Exception $e) { common_log(LOG_WARNING, $this->name() . ' - Twitter client unable to get friends timeline for user ' . diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index 2c18c94695..cc68b50100 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -2,7 +2,7 @@ /** * StatusNet, the distributed open-source microblogging tool * - * Class for doing OAuth calls against Twitter + * Class for doing HTTP basic auth calls against Twitter * * PHP version 5 * @@ -125,6 +125,35 @@ class TwitterBasicAuthClient return $statuses; } + /** + * Calls Twitter's /statuses/home_timeline API method + * + * @param int $since_id show statuses after this id + * @param int $max_id show statuses before this id + * @param int $cnt number of statuses to show + * @param int $page page number + * + * @return mixed an array of statuses similar to friends timeline but including retweets + */ + function statusesFriendsTimeline($since_id = null, $max_id = null, + $cnt = null, $page = null) + { + $url = 'https://twitter.com/statuses/home_timeline.json'; + $params = array('since_id' => $since_id, + 'max_id' => $max_id, + 'count' => $cnt, + 'page' => $page); + $qry = http_build_query($params); + + if (!empty($qry)) { + $url .= "?$qry"; + } + + $response = $this->httpRequest($url); + $statuses = json_decode($response); + return $statuses; + } + /** * Calls Twitter's /statuses/friends API method * diff --git a/plugins/TwitterBridge/twitteroauthclient.php b/plugins/TwitterBridge/twitteroauthclient.php index 6b821ba181..f6ef786752 100644 --- a/plugins/TwitterBridge/twitteroauthclient.php +++ b/plugins/TwitterBridge/twitteroauthclient.php @@ -225,7 +225,7 @@ class TwitterOAuthClient extends OAuthClient * @param int $cnt number of statuses to show * @param int $page page number * - * @return mixed an array of statuses, similare to friends_timeline, except including retweets + * @return mixed an array of statuses, similar to friends_timeline but including retweets */ function statusesHomeTimeline($since_id = null, $max_id = null, $cnt = null, $page = null) From d2234580357349a6887a2321e69d11de7bb29106 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 31 May 2010 08:49:14 -0700 Subject: [PATCH 038/655] Widgetize the design form Squashed commit of the following: commit a4610db66974866fdeb98184ce7e2be1470fb4d7 Author: Evan Prodromou Date: Mon May 31 08:48:35 2010 -0700 use selfUrl for designform action commit fd9f46ab33caa2c2d0df90d1d596c7b8c6453ce3 Author: Evan Prodromou Date: Mon May 31 08:29:43 2010 -0700 fix design settings syntax commit d1797ef9f90bf038665463424ad962bfe039c9f0 Author: Evan Prodromou Date: Mon Nov 9 23:23:53 2009 -0500 widgetizing design form --- lib/designform.php | 293 +++++++++++++++++++++++++++++++++++++++++ lib/designsettings.php | 173 +----------------------- 2 files changed, 295 insertions(+), 171 deletions(-) create mode 100644 lib/designform.php diff --git a/lib/designform.php b/lib/designform.php new file mode 100644 index 0000000000..b22d77f312 --- /dev/null +++ b/lib/designform.php @@ -0,0 +1,293 @@ +. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @copyright 2009 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); +} + +/** + * Form for choosing a design + * + * Used for choosing a site design, user design, or group design. + * + * @category Form + * @package StatusNet + * @author Evan Prodromou + * @author Sarven Capadisli + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0 + * @link http://status.net/ + * + */ + +class DesignForm extends Form +{ + /** + * Return-to args + */ + + var $design = null; + var $actionurl = null; + + /** + * Constructor + * + * @param HTMLOutputter $out output channel + * @param Design $design initial design + * @param Design $actionurl url of action (for form posting) + */ + + function __construct($out, $design, $actionurl) + { + parent::__construct($out); + + $this->design = $design; + $this->actionurl = $actionurl; + } + + /** + * ID of the form + * + * @return int ID of the form + */ + + function id() + { + return 'design'; + } + + /** + * class of the form + * + * @return string class of the form + */ + + function formClass() + { + return 'form_design'; + } + + /** + * Action of the form + * + * @return string URL of the action + */ + + function action() + { + return $this->actionurl; + } + + /** + * Legend of the Form + * + * @return void + */ + function formLegend() + { + $this->out->element('legend', null, _('Change design')); + } + + /** + * Data elements of the form + * + * @return void + */ + + function formData() + { + $this->out->elementStart('ul', 'form_data'); + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'design_background-image_file'), + _('Upload file')); + $this->out->element('input', array('name' => 'design_background-image_file', + 'type' => 'file', + 'id' => 'design_background-image_file')); + $this->out->element('p', 'form_guide', _('You can upload your personal ' . + 'background image. The maximum file size is 2Mb.')); + $this->out->element('input', array('name' => 'MAX_FILE_SIZE', + 'type' => 'hidden', + 'id' => 'MAX_FILE_SIZE', + 'value' => ImageFile::maxFileSizeInt())); + $this->out->elementEnd('li'); + + if (!empty($design->backgroundimage)) { + + $this->out->elementStart('li', array('id' => + 'design_background-image_onoff')); + + $this->out->element('img', array('src' => + Design::url($design->backgroundimage))); + + $attrs = array('name' => 'design_background-image_onoff', + 'type' => 'radio', + 'id' => 'design_background-image_on', + 'class' => 'radio', + 'value' => 'on'); + + if ($design->disposition & BACKGROUND_ON) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'design_background-image_on', + 'class' => 'radio'), + _('On')); + + $attrs = array('name' => 'design_background-image_onoff', + 'type' => 'radio', + 'id' => 'design_background-image_off', + 'class' => 'radio', + 'value' => 'off'); + + if ($design->disposition & BACKGROUND_OFF) { + $attrs['checked'] = 'checked'; + } + + $this->out->element('input', $attrs); + + $this->out->element('label', array('for' => 'design_background-image_off', + 'class' => 'radio'), + _('Off')); + $this->out->element('p', 'form_guide', _('Turn background image on or off.')); + $this->out->elementEnd('li'); + + $this->out->elementStart('li'); + $this->out->checkbox('design_background-image_repeat', + _('Tile background image'), + ($design->disposition & BACKGROUND_TILE) ? true : false); + $this->out->elementEnd('li'); + } + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + + $this->out->elementStart('fieldset', array('id' => 'settings_design_color')); + $this->out->element('legend', null, _('Change colours')); + $this->out->elementStart('ul', 'form_data'); + + try { + + $bgcolor = new WebColor($design->backgroundcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-1'), _('Background')); + $this->out->element('input', array('name' => 'design_background', + 'type' => 'text', + 'id' => 'swatch-1', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $ccolor = new WebColor($design->contentcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-2'), _('Content')); + $this->out->element('input', array('name' => 'design_content', + 'type' => 'text', + 'id' => 'swatch-2', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $sbcolor = new WebColor($design->sidebarcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-3'), _('Sidebar')); + $this->out->element('input', array('name' => 'design_sidebar', + 'type' => 'text', + 'id' => 'swatch-3', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $tcolor = new WebColor($design->textcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-4'), _('Text')); + $this->out->element('input', array('name' => 'design_text', + 'type' => 'text', + 'id' => 'swatch-4', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + $lcolor = new WebColor($design->linkcolor); + + $this->out->elementStart('li'); + $this->out->element('label', array('for' => 'swatch-5'), _('Links')); + $this->out->element('input', array('name' => 'design_links', + 'type' => 'text', + 'id' => 'swatch-5', + 'class' => 'swatch', + 'maxlength' => '7', + 'size' => '7', + 'value' => '')); + $this->out->elementEnd('li'); + + } catch (WebColorException $e) { + common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id); + } + + $this->out->elementEnd('ul'); + $this->out->elementEnd('fieldset'); + + $this->out->elementStart('fieldset'); + + $this->out->submit('defaults', _('Use defaults'), 'submit form_action-default', + 'defaults', _('Restore default designs')); + + $this->out->element('input', array('id' => 'settings_design_reset', + 'type' => 'reset', + 'value' => 'Reset', + 'class' => 'submit form_action-primary', + 'title' => _('Reset back to default'))); + } + + /** + * Action elements + * + * @return void + */ + + function formActions() + { + $this->out->submit('save', _('Save'), 'submit form_action-secondary', + 'save', _('Save design')); + } +} diff --git a/lib/designsettings.php b/lib/designsettings.php index 4955e92199..98ef8256cd 100644 --- a/lib/designsettings.php +++ b/lib/designsettings.php @@ -87,177 +87,8 @@ class DesignSettingsAction extends AccountSettingsAction function showDesignForm($design) { - - $this->elementStart('form', array('method' => 'post', - 'enctype' => 'multipart/form-data', - 'id' => 'form_settings_design', - 'class' => 'form_settings', - 'action' => $this->submitaction)); - $this->elementStart('fieldset'); - $this->hidden('token', common_session_token()); - - $this->elementStart('fieldset', array('id' => - 'settings_design_background-image')); - $this->element('legend', null, _('Change background image')); - $this->elementStart('ul', 'form_data'); - $this->elementStart('li'); - $this->element('label', array('for' => 'design_background-image_file'), - _('Upload file')); - $this->element('input', array('name' => 'design_background-image_file', - 'type' => 'file', - 'id' => 'design_background-image_file')); - $this->element('p', 'form_guide', _('You can upload your personal ' . - 'background image. The maximum file size is 2MB.')); - $this->element('input', array('name' => 'MAX_FILE_SIZE', - 'type' => 'hidden', - 'id' => 'MAX_FILE_SIZE', - 'value' => ImageFile::maxFileSizeInt())); - $this->elementEnd('li'); - - if (!empty($design->backgroundimage)) { - - $this->elementStart('li', array('id' => - 'design_background-image_onoff')); - - $this->element('img', array('src' => - Design::url($design->backgroundimage))); - - $attrs = array('name' => 'design_background-image_onoff', - 'type' => 'radio', - 'id' => 'design_background-image_on', - 'class' => 'radio', - 'value' => 'on'); - - if ($design->disposition & BACKGROUND_ON) { - $attrs['checked'] = 'checked'; - } - - $this->element('input', $attrs); - - $this->element('label', array('for' => 'design_background-image_on', - 'class' => 'radio'), - _('On')); - - $attrs = array('name' => 'design_background-image_onoff', - 'type' => 'radio', - 'id' => 'design_background-image_off', - 'class' => 'radio', - 'value' => 'off'); - - if ($design->disposition & BACKGROUND_OFF) { - $attrs['checked'] = 'checked'; - } - - $this->element('input', $attrs); - - $this->element('label', array('for' => 'design_background-image_off', - 'class' => 'radio'), - _('Off')); - $this->element('p', 'form_guide', _('Turn background image on or off.')); - $this->elementEnd('li'); - - $this->elementStart('li'); - $this->checkbox('design_background-image_repeat', - _('Tile background image'), - ($design->disposition & BACKGROUND_TILE) ? true : false); - $this->elementEnd('li'); - } - - $this->elementEnd('ul'); - $this->elementEnd('fieldset'); - - $this->elementStart('fieldset', array('id' => 'settings_design_color')); - $this->element('legend', null, _('Change colours')); - $this->elementStart('ul', 'form_data'); - - try { - - $bgcolor = new WebColor($design->backgroundcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-1'), _('Background')); - $this->element('input', array('name' => 'design_background', - 'type' => 'text', - 'id' => 'swatch-1', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $ccolor = new WebColor($design->contentcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-2'), _('Content')); - $this->element('input', array('name' => 'design_content', - 'type' => 'text', - 'id' => 'swatch-2', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $sbcolor = new WebColor($design->sidebarcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-3'), _('Sidebar')); - $this->element('input', array('name' => 'design_sidebar', - 'type' => 'text', - 'id' => 'swatch-3', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $tcolor = new WebColor($design->textcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-4'), _('Text')); - $this->element('input', array('name' => 'design_text', - 'type' => 'text', - 'id' => 'swatch-4', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - $lcolor = new WebColor($design->linkcolor); - - $this->elementStart('li'); - $this->element('label', array('for' => 'swatch-5'), _('Links')); - $this->element('input', array('name' => 'design_links', - 'type' => 'text', - 'id' => 'swatch-5', - 'class' => 'swatch', - 'maxlength' => '7', - 'size' => '7', - 'value' => '')); - $this->elementEnd('li'); - - } catch (WebColorException $e) { - common_log(LOG_ERR, 'Bad color values in design ID: ' .$design->id); - } - - $this->elementEnd('ul'); - $this->elementEnd('fieldset'); - - $this->submit('defaults', _('Use defaults'), 'submit form_action-default', - 'defaults', _('Restore default designs')); - - $this->element('input', array('id' => 'settings_design_reset', - 'type' => 'reset', - 'value' => 'Reset', - 'class' => 'submit form_action-primary', - 'title' => _('Reset back to default'))); - - $this->submit('save', _('Save'), 'submit form_action-secondary', - 'save', _('Save design')); - - $this->elementEnd('fieldset'); - $this->elementEnd('form'); + $form = new DesignForm($this, $design, $this->selfUrl()); + $form->show(); } /** From ed5549bd13e2af84ed3021ab65eaf504280e2bd7 Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Mon, 31 May 2010 09:04:29 -0700 Subject: [PATCH 039/655] move long sets of controls to their own functions in designform --- lib/designform.php | 41 ++++++++++++++++++++++++++--------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/designform.php b/lib/designform.php index b22d77f312..89e7f6ce8b 100644 --- a/lib/designform.php +++ b/lib/designform.php @@ -121,6 +121,29 @@ class DesignForm extends Form */ function formData() + { + $this->backgroundData(); + + $this->out->elementEnd('fieldset'); + + $this->out->elementStart('fieldset', array('id' => 'settings_design_color')); + $this->out->element('legend', null, _('Change colours')); + $this->colourData(); + $this->out->elementEnd('fieldset'); + + $this->out->elementStart('fieldset'); + + $this->out->submit('defaults', _('Use defaults'), 'submit form_action-default', + 'defaults', _('Restore default designs')); + + $this->out->element('input', array('id' => 'settings_design_reset', + 'type' => 'reset', + 'value' => 'Reset', + 'class' => 'submit form_action-primary', + 'title' => _('Reset back to default'))); + } + + function backgroundData() { $this->out->elementStart('ul', 'form_data'); $this->out->elementStart('li'); @@ -187,10 +210,10 @@ class DesignForm extends Form } $this->out->elementEnd('ul'); - $this->out->elementEnd('fieldset'); + } - $this->out->elementStart('fieldset', array('id' => 'settings_design_color')); - $this->out->element('legend', null, _('Change colours')); + function colourData() + { $this->out->elementStart('ul', 'form_data'); try { @@ -265,18 +288,6 @@ class DesignForm extends Form } $this->out->elementEnd('ul'); - $this->out->elementEnd('fieldset'); - - $this->out->elementStart('fieldset'); - - $this->out->submit('defaults', _('Use defaults'), 'submit form_action-default', - 'defaults', _('Restore default designs')); - - $this->out->element('input', array('id' => 'settings_design_reset', - 'type' => 'reset', - 'value' => 'Reset', - 'class' => 'submit form_action-primary', - 'title' => _('Reset back to default'))); } /** From 7cc58b97feb822ab999b7fefa3a50ce53a7838d5 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Thu, 10 Jun 2010 15:23:57 -0700 Subject: [PATCH 040/655] Fix for compile error (misnamed function) in 4211b7f01188b4ab64407e32b380366a048102f4 --- plugins/TwitterBridge/twitterbasicauthclient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/TwitterBridge/twitterbasicauthclient.php b/plugins/TwitterBridge/twitterbasicauthclient.php index cc68b50100..23828ed4a3 100644 --- a/plugins/TwitterBridge/twitterbasicauthclient.php +++ b/plugins/TwitterBridge/twitterbasicauthclient.php @@ -135,7 +135,7 @@ class TwitterBasicAuthClient * * @return mixed an array of statuses similar to friends timeline but including retweets */ - function statusesFriendsTimeline($since_id = null, $max_id = null, + function statusesHomeTimeline($since_id = null, $max_id = null, $cnt = null, $page = null) { $url = 'https://twitter.com/statuses/home_timeline.json'; From 0264f66d76f6a8e5669d305985f96533a156e9ae Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sat, 12 Jun 2010 17:28:43 +0100 Subject: [PATCH 041/655] Initial commit of msn-plugin work --- plugins/Msn/MsnPlugin.php | 173 + plugins/Msn/Queued_Msn.php | 120 + plugins/Msn/extlib/phpmsnclass/msn.class.php | 3755 +++++++++++++++++ plugins/Msn/extlib/phpmsnclass/msnbot.php | 63 + plugins/Msn/extlib/phpmsnclass/sample.php | 40 + .../extlib/phpmsnclass/soap/.svn/all-wcprops | 23 + .../Msn/extlib/phpmsnclass/soap/.svn/entries | 130 + .../text-base/msnab_datatypes.xsd.svn-base | 832 ++++ .../text-base/msnab_servicetypes.xsd.svn-base | 567 +++ .../msnab_sharingservice.wsdl.svn-base | 532 +++ .../phpmsnclass/soap/msnab_datatypes.xsd | 832 ++++ .../phpmsnclass/soap/msnab_servicetypes.xsd | 567 +++ .../soap/msnab_sharingservice.wsdl | 532 +++ plugins/Msn/msnmanager.php | 105 + 14 files changed, 8271 insertions(+) create mode 100644 plugins/Msn/MsnPlugin.php create mode 100644 plugins/Msn/Queued_Msn.php create mode 100644 plugins/Msn/extlib/phpmsnclass/msn.class.php create mode 100755 plugins/Msn/extlib/phpmsnclass/msnbot.php create mode 100644 plugins/Msn/extlib/phpmsnclass/sample.php create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/.svn/all-wcprops create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/.svn/entries create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_datatypes.xsd.svn-base create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_servicetypes.xsd.svn-base create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_sharingservice.wsdl.svn-base create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd create mode 100644 plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl create mode 100644 plugins/Msn/msnmanager.php diff --git a/plugins/Msn/MsnPlugin.php b/plugins/Msn/MsnPlugin.php new file mode 100644 index 0000000000..6737e727ab --- /dev/null +++ b/plugins/Msn/MsnPlugin.php @@ -0,0 +1,173 @@ +. + * + * @category IM + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} +// We bundle the phptoclib library... +set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phptoclib'); + +/** + * Plugin for AIM + * + * @category Plugin + * @package StatusNet + * @author Craig Andrews + * @copyright 2009 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class MsnPlugin extends ImPlugin +{ + public $user = null; + public $password = null; + public $publicFeed = array(); + + public $transport = 'msnim'; + + function getDisplayName() + { + return _m('MSN'); + } + + function normalize($screenname) + { + $screenname = str_replace(" ","", $screenname); + return strtolower($screenname); + } + + function daemon_screenname() + { + return $this->user; + } + + function validate($screenname) + { + if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { + return true; + }else{ + return false; + } + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + function onAutoload($cls) + { + $dir = dirname(__FILE__); + + switch ($cls) + { + case 'Msn': + require_once(INSTALLDIR.'/plugins/Msn/extlib/phpmsnclass/msn.class.php'); + return false; + case 'MsnManager': + include_once $dir . '/'.strtolower($cls).'.php'; + return false; + case 'Fake_Msn': + include_once $dir . '/'. $cls .'.php'; + return false; + default: + return true; + } + } + + function onStartImDaemonIoManagers(&$classes) + { + parent::onStartImDaemonIoManagers(&$classes); + $classes[] = new MsnManager($this); // handles sending/receiving + return true; + } + + function microiduri($screenname) + { + return 'msnim:' . $screenname; + } + + function send_message($screenname, $body) + { + //$this->fake_aim->sendIm($screenname, $body); + //$this->enqueue_outgoing_raw($this->fake_aim->would_be_sent); + $this->enqueue_outgoing_raw(array($screenname, $body)); + return true; + } + + /** + * Accept a queued input message. + * + * @return true if processing completed, false if message should be reprocessed + */ + function receive_raw_message($message) + { + $info=Aim::getMessageInfo($message); + $from = $info['from']; + $user = $this->get_user($from); + $notice_text = $info['message']; + + $this->handle_incoming($from, $notice_text); + + return true; + } + + function initialize(){ + if(!isset($this->user)){ + throw new Exception("must specify a user"); + } + if(!isset($this->password)){ + throw new Exception("must specify a password"); + } + if(!isset($this->nickname)) { + throw new Exception("must specify a nickname"); + } + + $this->fake_msn = new Fake_Msn($this->user,$this->password,4); + return true; + } + + function onPluginVersion(&$versions) + { + $versions[] = array('name' => 'MSN', + 'version' => STATUSNET_VERSION, + 'author' => 'Luke Fitzgerald', + 'homepage' => 'http://status.net/wiki/Plugin:MSN', + 'rawdescription' => + _m('The MSN plugin allows users to send and receive notices over the MSN network.')); + return true; + } +} + diff --git a/plugins/Msn/Queued_Msn.php b/plugins/Msn/Queued_Msn.php new file mode 100644 index 0000000000..bc8e0a1d15 --- /dev/null +++ b/plugins/Msn/Queued_Msn.php @@ -0,0 +1,120 @@ +. + * + * @category Network + * @package StatusNet + * @author Luke Fitzgerald + * @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); +} + +class Queued_XMPP extends MSN { + /** + * Reference to the MsnPlugin object we're hooked up to. + */ + public $plugin; + + /** + * Constructor + * + * @param MsnPlugin $plugin + * @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($plugin, $host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) + { + $this->plugin = $plugin; + + parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); + + // We use $host to connect, but $server to build JIDs if specified. + // This seems to fix an upstream bug where $host was used to build + // $this->basejid, never seen since it isn't actually used in the base + // classes. + if (!$server) { + $server = $this->host; + } + $this->basejid = $this->user . '@' . $server; + + // Normally the fulljid is filled out by the server at resource binding + // time, but we need to do it 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) + { + $this->plugin->enqueue_outgoing_raw($msg); + } + + //@{ + /** + * Stream i/o functions disabled; only do output + */ + public function connect($timeout = 30, $persistent = false, $sendinit = true) + { + throw new Exception("Can't connect to server from fake XMPP."); + } + + public function disconnect() + { + throw new Exception("Can't connect to server from fake XMPP."); + } + + public function process() + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function processUntil($event, $timeout=-1) + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function read() + { + throw new Exception("Can't read stream from fake XMPP."); + } + + public function readyToProcess() + { + throw new Exception("Can't read stream from fake XMPP."); + } + //@} + +} + diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php new file mode 100644 index 0000000000..355d828eb5 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -0,0 +1,3755 @@ +Main Process,1 => sb_control_process,2 => sb_ring_process + private $SwitchBoardSessionUser=false; + private $SwitchBoardMessageQueue=array(); + private $ABAuthHeader; + private $ABService; + private $Contacts; + + public $server = 'messenger.hotmail.com'; + public $port = 1863; + + + public $clientid = ''; + + public $oim_maildata_url = 'https://rsi.hotmail.com/rsi/rsi.asmx'; + public $oim_maildata_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata'; + public $oim_read_url = 'https://rsi.hotmail.com/rsi/rsi.asmx'; + public $oim_read_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage'; + public $oim_del_url = 'https://rsi.hotmail.com/rsi/rsi.asmx'; + public $oim_del_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages'; + + public $membership_url = 'https://contacts.msn.com/abservice/SharingService.asmx'; + public $membership_soap = 'http://www.msn.com/webservices/AddressBook/FindMembership'; + + public $addmember_url = 'https://contacts.msn.com/abservice/SharingService.asmx'; + public $addmember_soap = 'http://www.msn.com/webservices/AddressBook/AddMember'; + + public $addcontact_url = 'https://contacts.msn.com/abservice/abservice.asmx'; + public $addcontact_soap = 'http://www.msn.com/webservices/AddressBook/ABContactAdd'; + + public $delmember_url = 'https://contacts.msn.com/abservice/SharingService.asmx'; + public $delmember_soap = 'http://www.msn.com/webservices/AddressBook/DeleteMember'; + + + public $error = ''; + + public $authed = false; + + public $oim_try = 3; + + public $log_file = ''; + + public $log_path = false; + + public $font_fn = 'Arial'; + public $font_co = '333333'; + public $font_ef = ''; + + + // the message length (include header) is limited (maybe since WLM 8.5 released) + // for WLM: 1664 bytes + // for YIM: 518 bytes + public $max_msn_message_len = 1664; + public $max_yahoo_message_len = 518; + + // Begin added for StatusNet + + private $aContactList = array(); + private $switchBoardSessions = array(); + + /** + * Event Handler Functions + */ + private $myEventHandlers = array(); + + // End added for StatusNet + + private function Array2SoapVar($Array,$ReturnSoapVarObj=true,$TypeName=null,$TypeNameSpace=null) + { + $ArrayString=''; + foreach($Array as $Key => $Val) + { + if($Key{0}==':') continue; + $Attrib=''; + if(is_array($Val[':'])) + { + foreach($Val[':'] as $AttribName => $AttribVal) + $Attrib.=" $AttribName='$AttribVal'"; + } + if($Key{0}=='!') + { + //List Type Define + $Key=substr($Key,1); + foreach($Val as $ListKey => $ListVal) + { + if($ListKey{0}==':') continue; + if(is_array($ListVal)) $ListVal=$this->Array2SoapVar($ListVal,false); + elseif(is_bool($ListVal)) $ListVal=$ListVal?'true':'false'; + $ArrayString.="<$Key$Attrib>$ListVal"; + } + continue; + } + if(is_array($Val)) $Val=$this->Array2SoapVar($Val,false); + elseif(is_bool($Val)) $Val=$Val?'true':'false'; + $ArrayString.="<$Key$Attrib>$Val"; + } + if($ReturnSoapVarObj) return new SoapVar($ArrayString,XSD_ANYXML,$TypeName,$TypeNameSpace); + return $ArrayString; + } + + public function End() + { + $this->log_message("*** someone kill me ***"); + $this->kill_me=true; + } + public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C) + { + $this->user = $Configs['user']; + $this->password = $Configs['password']; + $this->alias = isset($Configs['alias']) ? $Configs['alias'] : ''; + $this->psm = isset($Configs['psm']) ? $Configs['psm'] : ''; + $my_add_function = isset($Configs['add_user_function']) ? $Configs['add_user_function'] : false; + $my_rem_function = isset($Configs['remove_user_function']) ? $Configs['remove_user_function'] : false; + $this->use_ping = isset($Configs['use_ping']) ? $Configs['use_ping'] : false; + $this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30; + $this->backup_file = isset($Configs['backup_file']) ? $Configs['backup_file'] : true; + $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true; + $this->PhotoStickerFile=$Configs['PhotoSticker']; + if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) + { + foreach($this->Emotions as $EmotionFilePath) + $this->MsnObj($EmotionFilePath,$Type=2); + } + $this->debug = isset($Configs['debug']) ? $Configs['debug'] : false; + $this->timeout = $timeout; + // check support + if (!function_exists('curl_init')) throw new Exception("We need curl module!\n"); + if (!function_exists('preg_match')) throw new Exception("We need pcre module!\n"); + if (!function_exists('mhash')) throw new Exception("We need mhash module!\n"); + + if (!function_exists('mcrypt_cbc')) throw new Exception("We need mcrypt module!\n"); + if (!function_exists('bcmod')) throw new Exception("We need bcmath module for $protocol!\n"); + + /* + http://msnpiki.msnfanatic.com/index.php/Client_ID + Client ID for MSN: + normal MSN 8.1 clientid is: + 01110110 01001100 11000000 00101100 + = 0x764CC02C + + we just use following: + * 0x04: Your client can send/receive Ink (GIF format) + * 0x08: Your client can send/recieve Ink (ISF format) + * 0x8000: This means you support Winks receiving (If not set the official Client will warn with 'contact has an older client and is not capable of receiving Winks') + * 0x70000000: This is the value for MSNC7 (WL Msgr 8.1) + = 0x7000800C; + */ + $this->clientid = $client_id; + $this->windows =(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); + $this->ABService=new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl',array('trace' => 1)); + } + + private function get_passport_ticket($url = '') + { + $user = $this->user; + $password = htmlspecialchars($this->password); + + if ($url === '') + $passport_url = $this->passport_url; + else + $passport_url = $url; + + $XML = ' + +
+ + {7108E71A-9926-4FCB-BCC9-9A9D3F32E423} + 4 + 1 + + AQAAAAIAAABsYwQAAAAxMDMz + + + + '.$user.' + '.$password.' + + +
+ + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + http://Passport.NET/tb + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + messengerclear.live.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + messenger.msn.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + contacts.msn.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + messengersecure.live.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + spaces.live.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + storage.msn.com + + + + + + +
'; + + $this->debug_message("*** URL: $passport_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $passport_url); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + // sometimes, rediret to another URL + // MSNP15 + //psf:Redirect + //https://msnia.login.live.com/pp450/RST.srf + //Authentication Failure + if (strpos($data, 'psf:Redirect') === false) { + $this->debug_message("*** Can't get passport ticket! http code = $http_code"); + return false; + } + preg_match("#(.*)#", $data, $matches); + if (count($matches) == 0) { + $this->debug_message("*** redirect, but can't get redirect URL!"); + return false; + } + $redirect_url = $matches[1]; + if ($redirect_url == $passport_url) { + $this->debug_message("*** redirect, but redirect to same URL!"); + return false; + } + $this->debug_message("*** redirect to $redirect_url"); + return $this->get_passport_ticket($redirect_url); + } + + // sometimes, rediret to another URL, also return 200 + // MSNP15 + //psf:Redirect + //https://msnia.login.live.com/pp450/RST.srf + //Authentication Failure + if (strpos($data, 'psf:Redirect') !== false) { + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + $redirect_url = $matches[1]; + if ($redirect_url == $passport_url) { + $this->debug_message("*** redirect, but redirect to same URL!"); + return false; + } + $this->debug_message("*** redirect to $redirect_url"); + return $this->get_passport_ticket($redirect_url); + } + } + + // no Redurect faultcode or URL + // we should get the ticket here + + // we need ticket and secret code + // RST1: messengerclear.live.com + // t=tick&p= + // binary secret + // RST2: messenger.msn.com + // t=tick + // RST3: contacts.msn.com + // t=tick&p= + // RST4: messengersecure.live.com + // t=tick&p= + // RST5: spaces.live.com + // t=tick&p= + // RST6: storage.msn.com + // t=tick&p= + preg_match("#". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "#", + $data, $matches); + + // no ticket found! + if (count($matches) == 0) { + $this->debug_message("*** Can't get passport ticket!"); + return false; + } + + //$this->debug_message(var_export($matches, true)); + // matches[0]: all data + // matches[1]: RST1 (messengerclear.live.com) ticket + // matches[2]: ... + // matches[3]: RST1 (messengerclear.live.com) binary secret + // matches[4]: ... + // matches[5]: RST2 (messenger.msn.com) ticket + // matches[6]: ... + // matches[7]: RST3 (contacts.msn.com) ticket + // matches[8]: ... + // matches[9]: RST4 (messengersecure.live.com) ticket + // matches[10]: ... + // matches[11]: RST5 (spaces.live.com) ticket + // matches[12]: ... + // matches[13]: RST6 (storage.live.com) ticket + // matches[14]: ... + + // so + // ticket => $matches[1] + // secret => $matches[3] + // web_ticket => $matches[5] + // contact_ticket => $matches[7] + // oim_ticket => $matches[9] + // space_ticket => $matches[11] + // storage_ticket => $matches[13] + + // yes, we get ticket + $aTickets = array( + 'ticket' => html_entity_decode($matches[1]), + 'secret' => html_entity_decode($matches[3]), + 'web_ticket' => html_entity_decode($matches[5]), + 'contact_ticket' => html_entity_decode($matches[7]), + 'oim_ticket' => html_entity_decode($matches[9]), + 'space_ticket' => html_entity_decode($matches[11]), + 'storage_ticket' => html_entity_decode($matches[13]) + ); + $this->ticket=$aTickets; + $this->debug_message(var_export($aTickets, true)); + $ABAuthHeaderArray=array( + 'ABAuthHeader'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'ManagedGroupRequest'=>false, + 'TicketToken'=>htmlspecialchars($this->ticket['contact_ticket']), + ) + ); + $this->ABAuthHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook","ABAuthHeader", $this->Array2SoapVar($ABAuthHeaderArray)); + file_put_contents('/tmp/STTicket.txt',htmlspecialchars($this->ticket['storage_ticket'])); + //$this->debug_message("StorageTicket:\n",htmlspecialchars($this->ticket['storage_ticket'])); + return $aTickets; + } + private function UpdateContacts() + { + $ABApplicationHeaderArray=array( + 'ABApplicationHeader'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'ApplicationId'=>'CFE80F9D-180F-4399-82AB-413F33A1FA11', + 'IsMigration'=>false, + 'PartnerScenario'=>'ContactSave' + ) + ); + $ABApplicationHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook",'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); + $ABFindAllArray=array( + 'ABFindAll'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'abId'=>'00000000-0000-0000-0000-000000000000', + 'abView'=>'Full', + 'lastChange'=>'0001-01-01T00:00:00.0000000-08:00', + ) + ); + $ABFindAll=new SoapParam($this->Array2SoapVar($ABFindAllArray),'ABFindAll'); + $this->ABService->__setSoapHeaders(array($ABApplicationHeader,$this->ABAuthHeader)); + $this->Contacts=array(); + try + { + $this->debug_message("*** Update Contacts..."); + $Result=$this->ABService->ABFindAll($ABFindAll); + $this->debug_message("*** Result:\n".print_r($Result,true)."\n".$this->ABService->__getLastResponse()); + foreach($Result->ABFindAllResult->contacts->Contact as $Contact) + $this->Contacts[$Contact->contactInfo->passportName]=$Contact; + } + catch(Exception $e) + { + $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); + } + } + protected function addContact($email, $network, $display = '', $sendADL = false) + { + if ($network != 1) return true; + if(isset($this->Contacts[$email])) return true; + + $ABContactAddArray=array( + 'ABContactAdd'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'abId'=>'00000000-0000-0000-0000-000000000000', + 'contacts'=>array( + 'Contact'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'contactInfo'=>array( + 'contactType'=>'LivePending', + 'passportName'=>$email, + 'isMessengerUser'=>true, + 'MessengerMemberInfo'=>array( + 'DisplayName'=>$email + ) + ) + ) + ), + 'options'=>array( + 'EnableAllowListManagement'=>true + ) + ) + ); + $ABContactAdd=new SoapParam($this->Array2SoapVar($ABContactAddArray),'ABContactAdd'); + try + { + $this->debug_message("*** Add Contacts $email..."); + $this->ABService->ABContactAdd($ABContactAdd); + } + catch(Exception $e) + { + $this->debug_message("*** Add Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); + } + if ($sendADL && !feof($this->NSfp)) { + @list($u_name, $u_domain) = @explode('@', $email); + foreach (array('1', '2') as $l) { + $str = ''; + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + } + $this->UpdateContacts(); + return true; + + + $ABContactAdd=new SoapParam($this->Array2SoapVar($ABContactAddArray),'ABContactAdd'); + + // add contact for WLM + $ticket = htmlspecialchars($this->ticket['contact_ticket']); + $displayName = htmlspecialchars($display); + $user = $email; + + $XML = ' + + + + CFE80F9D-180F-4399-82AB-413F33A1FA11 + false + ContactSave + + + false + '.$ticket.' + + + + + 00000000-0000-0000-0000-000000000000 + + + + LivePending + '.$user.' + true + + '.$displayName.' + + + + + + true + + + +'; + + $header_array = array( + 'SOAPAction: '.$this->addcontact_soap, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + ); + + $this->debug_message("*** URL: $this->addcontact_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->addcontact_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + preg_match('#(.*)(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->log_message("*** can't add contact (network: $network) $email"); + return false; + } + $faultcode = trim($matches[1]); + $faultstring = trim($matches[2]); + $this->log_message("*** can't add contact (network: $network) $email, error code: $faultcode, $faultstring"); + return false; + } + $this->log_message("*** add contact (network: $network) $email"); + if ($sendADL && !feof($this->NSfp)) { + @list($u_name, $u_domain) = @explode('@', $email); + foreach (array('1', '2') as $l) { + $str = ''; + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + } + $this->UpdateContacts(); + return true; + } + + function delMemberFromList($memberID, $email, $network, $list) { + if ($network != 1 && $network != 32) return true; + if ($memberID === false) return true; + $user = $email; + $ticket = htmlspecialchars($this->ticket['contact_ticket']); + if ($network == 1) + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Passport + '.$memberID.' + Accepted + + + + + + +'; + else + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Email + '.$memberID.' + Accepted + + + + + + +'; + + $header_array = array( + 'SOAPAction: '.$this->delmember_soap, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + ); + + $this->debug_message("*** URL: $this->delmember_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->delmember_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + preg_match('#(.*)(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list"); + return false; + } + $faultcode = trim($matches[1]); + $faultstring = trim($matches[2]); + if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) { + $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring"); + return false; + } + $this->log_message("*** delete member (network: $network) $email ($memberID) from $list, not exist"); + return true; + } + $this->log_message("*** delete member (network: $network) $email ($memberID) from $list"); + return true; + } + + function addMemberToList($email, $network, $list) { + if ($network != 1 && $network != 32) return true; + $ticket = htmlspecialchars($this->ticket['contact_ticket']); + $user = $email; + + if ($network == 1) + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Passport + Accepted + '.$user.' + + + + + + +'; + else + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Email + Accepted + '.$user.' + + + MSN.IM.BuddyType + 32:YAHOO + + + + + + + + +'; + $header_array = array( + 'SOAPAction: '.$this->addmember_soap, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + ); + + $this->debug_message("*** URL: $this->addmember_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->addmember_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + preg_match('#(.*)(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->log_message("*** can't add member (network: $network) $email to $list"); + return false; + } + $faultcode = trim($matches[1]); + $faultstring = trim($matches[2]); + if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) { + $this->log_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring"); + return false; + } + $this->log_message("*** add member (network: $network) $email to $list, already exist!"); + return true; + } + $this->log_message("*** add member (network: $network) $email to $list"); + return true; + } + + function getMembershipList($returnData=false) { + $ticket = htmlspecialchars($this->ticket['contact_ticket']); + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + Initial + + + false + '.$ticket.' + + + + + + + Messenger + Invitation + SocialNetwork + Space + Profile + + + + +'; + $header_array = array( + 'SOAPAction: '.$this->membership_soap, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + ); + $this->debug_message("*** URL: $this->membership_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->membership_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + if(($http_code != 200)||(!$returnData)) return array(); + $p = $data; + $aMemberships = array(); + while (1) { + //$this->debug_message("search p = $p"); + $start = strpos($p, ''); + $end = strpos($p, ''); + if ($start === false || $end === false || $start > $end) break; + //$this->debug_message("start = $start, end = $end"); + $end += 13; + $sMembership = substr($p, $start, $end - $start); + $aMemberships[] = $sMembership; + //$this->debug_message("add sMembership = $sMembership"); + $p = substr($p, $end); + } + //$this->debug_message("aMemberships = ".var_export($aMemberships, true)); + + $aContactList = array(); + foreach ($aMemberships as $sMembership) { + //$this->debug_message("sMembership = $sMembership"); + if (isset($matches)) unset($matches); + preg_match('#(.*)#', $sMembership, $matches); + if (count($matches) == 0) continue; + $sMemberRole = $matches[1]; + //$this->debug_message("MemberRole = $sMemberRole"); + if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue; + $p = $sMembership; + if (isset($aMembers)) unset($aMembers); + $aMembers = array(); + while (1) { + //$this->debug_message("search p = $p"); + $start = strpos($p, 'debug_message("add sMember = $sMember"); + $p = substr($p, $end); + } + //$this->debug_message("aMembers = ".var_export($aMembers, true)); + foreach ($aMembers as $sMember) { + //$this->debug_message("sMember = $sMember"); + if (isset($matches)) unset($matches); + preg_match('##', $sMember, $matches); + if (count($matches) == 0) continue; + $sMemberType = $matches[1]; + //$this->debug_message("MemberType = $sMemberType"); + $network = -1; + preg_match('#(.*)#', $sMember, $matches); + if (count($matches) == 0) continue; + $id = $matches[1]; + if ($sMemberType == 'PassportMember') { + if (strpos($sMember, 'Passport') === false) continue; + $network = 1; + preg_match('#(.*)#', $sMember, $matches); + } + else if ($sMemberType == 'EmailMember') { + if (strpos($sMember, 'Email') === false) continue; + // Value is 32: or 32:YAHOO + preg_match('#MSN.IM.BuddyType(.*):(.*)#', $sMember, $matches); + if (count($matches) == 0) continue; + if ($matches[1] != 32) continue; + $network = 32; + preg_match('#(.*)#', $sMember, $matches); + } + if ($network == -1) continue; + if (count($matches) > 0) { + $email = $matches[1]; + @list($u_name, $u_domain) = @explode('@', $email); + if ($u_domain == NULL) continue; + $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id; + $this->log_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); + } + } + } + return $aContactList; + } + + private function connect($user, $password, $redirect_server = '', $redirect_port = 1863) { + $this->id = 1; + if ($redirect_server === '') { + $this->NSfp = @fsockopen($this->server, $this->port, $errno, $errstr, 5); + if (!$this->NSfp) { + $this->error = "Can't connect to $this->server:$this->port, error => $errno, $errstr"; + return false; + } + } + else { + $this->NSfp = @fsockopen($redirect_server, $redirect_port, $errno, $errstr, 5); + if (!$this->NSfp) { + $this->error = "Can't connect to $redirect_server:$redirect_port, error => $errno, $errstr"; + return false; + } + } + + stream_set_timeout($this->NSfp, $this->NSStreamTimeout); + $this->authed = false; + // MSNP9 + // NS: >> VER {id} MSNP9 CVR0 + // MSNP15 + // NS: >>> VER {id} MSNP15 CVR0 + $this->ns_writeln("VER $this->id $this->protocol CVR0"); + + $start_tm = time(); + while (!feof($this->NSfp)) + { + $data = $this->ns_readln(); + // no data? + if ($data === false) { + if ($this->timeout > 0) { + $now_tm = time(); + $used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm; + if ($used_time > $this->timeout) { + // logout now + // NS: >>> OUT + $this->ns_writeln("OUT"); + fclose($this->NSfp); + $this->error = 'Timeout, maybe protocol changed!'; + $this->debug_message("*** $this->error"); + return false; + } + } + continue; + } + $code = substr($data, 0, 3); + $start_tm = time(); + + switch ($code) { + case 'VER': + // MSNP9 + // NS: <<< VER {id} MSNP9 CVR0 + // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 6.0.0602 msmsgs {user} + // MSNP15 + // NS: <<< VER {id} MSNP15 CVR0 + // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs {user} + $this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS $this->buildver msmsgs $user"); + break; + + case 'CVR': + // MSNP9 + // NS: <<< CVR {id} {ver_list} {download_serve} .... + // NS: >>> USR {id} TWN I {user} + // MSNP15 + // NS: <<< CVR {id} {ver_list} {download_serve} .... + // NS: >>> USR {id} SSO I {user} + $this->ns_writeln("USR $this->id $this->login_method I $user"); + break; + + case 'USR': + // already login for passport site, finish the login process now. + // NS: <<< USR {id} OK {user} {verify} 0 + if ($this->authed) return true; + // max. 16 digits for password + if (strlen($password) > 16) + $password = substr($password, 0, 16); + + $this->user = $user; + $this->password = $password; + // NS: <<< USR {id} SSO S {policy} {nonce} + @list(/* USR */, /* id */, /* SSO */, /* S */, $policy, $nonce,) = @explode(' ', $data); + + $this->passport_policy = $policy; + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // logout now + // NS: >>> OUT + $this->ns_writeln("OUT"); + fclose($this->NSfp); + $this->error = 'Passport authenticated fail!'; + $this->debug_message("*** $this->error"); + return false; + } + + $ticket = $aTickets['ticket']; + $secret = $aTickets['secret']; + $this->ticket = $aTickets; + $login_code = $this->generateLoginBLOB($secret, $nonce); + + // NS: >>> USR {id} SSO S {ticket} {login_code} + $this->ns_writeln("USR $this->id $this->login_method S $ticket $login_code"); + $this->authed = true; + break; + + case 'XFR': + // main login server will redirect to anther NS after USR command + // MSNP9 + // NS: <<< XFR {id} NS {server} 0 {server} + // MSNP15 + // NS: <<< XFR {id} NS {server} U D + @list(/* XFR */, /* id */, $Type, $server, /* ... */) = @explode(' ', $data); + if($Type!='NS') break; + @list($ip, $port) = @explode(':', $server); + // this connection will close after XFR + fclose($this->NSfp); + + $this->NSfp = @fsockopen($ip, $port, $errno, $errstr, 5); + if (!$this->NSfp) { + $this->error = "Can't connect to $ip:$port, error => $errno, $errstr"; + $this->debug_message("*** $this->error"); + return false; + } + + stream_set_timeout($this->NSfp, $this->NSStreamTimeout); + // MSNP9 + // NS: >> VER {id} MSNP9 CVR0 + // MSNP15 + // NS: >>> VER {id} MSNP15 CVR0 + $this->ns_writeln("VER $this->id $this->protocol CVR0"); + break; + + case 'GCF': + // return some policy data after 'USR {id} SSO I {user}' command + // NS: <<< GCF 0 {size} + @list(/* GCF */, /* 0 */, $size,) = @explode(' ', $data); + // we don't need the data, just read it and drop + if (is_numeric($size) && $size > 0) + $this->ns_readdata($size); + break; + + default: + // we'll quit if got any error + if (is_numeric($code)) { + // logout now + // NS: >>> OUT + $this->ns_writeln("OUT"); + fclose($this->NSfp); + $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; + $this->debug_message("*** $this->error"); + return false; + } + // unknown response from server, just ignore it + break; + } + } + // never goto here + } + + function derive_key($key, $magic) { + $hash1 = mhash(MHASH_SHA1, $magic, $key); + $hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key); + $hash3 = mhash(MHASH_SHA1, $hash1, $key); + $hash4 = mhash(MHASH_SHA1, $hash3.$magic, $key); + return $hash2.substr($hash4, 0, 4); + } + + function generateLoginBLOB($key, $challenge) { + $key1 = base64_decode($key); + $key2 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY HASH'); + $key3 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY ENCRYPTION'); + + // get hash of challenge using key2 + $hash = mhash(MHASH_SHA1, $challenge, $key2); + + // get 8 bytes random data + $iv = substr(base64_encode(rand(1000,9999).rand(1000,9999)), 2, 8); + + $cipher = mcrypt_cbc(MCRYPT_3DES, $key3, $challenge."\x08\x08\x08\x08\x08\x08\x08\x08", MCRYPT_ENCRYPT, $iv); + + $blob = pack('LLLLLLL', 28, 1, 0x6603, 0x8004, 8, 20, 72); + $blob .= $iv; + $blob .= $hash; + $blob .= $cipher; + + return base64_encode($blob); + } + + function getOIM_maildata() { + preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); + if (count($matches) == 0) { + $this->debug_message('*** no web ticket?'); + return false; + } + $t = htmlspecialchars($matches[1]); + $p = htmlspecialchars($matches[2]); + $XML = ' + + + + '.$t.' +

'.$p.'

+
+
+ + + +
'; + + $header_array = array( + 'SOAPAction: '.$this->oim_maildata_soap, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' + ); + + $this->debug_message("*** URL: $this->oim_maildata_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_maildata_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + $this->debug_message("*** Can't get OIM maildata! http code: $http_code"); + return false; + } + + // See #XML_Data + preg_match('#]*)>(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->debug_message("*** Can't get OIM maildata"); + return ''; + } + return $matches[2]; + } + + function getOIM_message($msgid) { + preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); + if (count($matches) == 0) { + $this->debug_message('*** no web ticket?'); + return false; + } + $t = htmlspecialchars($matches[1]); + $p = htmlspecialchars($matches[2]); + + // read OIM + $XML = ' + + + + '.$t.' +

'.$p.'

+
+
+ + + '.$msgid.' + false + + +
'; + + $header_array = array( + 'SOAPAction: '.$this->oim_read_soap, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' + ); + + $this->debug_message("*** URL: $this->oim_read_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_read_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code"); + return false; + } + + // why can't use preg_match('#(.*)#', $data, $matches)? + // multi-lines? + $start = strpos($data, ''); + $end = strpos($data, ''); + if ($start === false || $end === false || $start > $end) { + $this->debug_message("*** Can't get OIM: $msgid"); + return false; + } + $lines = substr($data, $start + 18, $end - $start); + $aLines = @explode("\n", $lines); + $header = true; + $ignore = false; + $sOIM = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + continue; + } + // stop at empty lines + if ($line === '') break; + $sOIM .= $line; + } + $sMsg = base64_decode($sOIM); + $this->debug_message("*** we get OIM ($msgid): $sMsg"); + + // delete OIM + $XML = ' + + + + '.$t.' +

'.$p.'

+
+
+ + + + '.$msgid.' + + + +
'; + + $header_array = array( + 'SOAPAction: '.$this->oim_del_soap, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' + ); + + $this->debug_message("*** URL: $this->oim_del_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_del_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) + $this->debug_message("*** Can't delete OIM: $msgid, http code = $http_code"); + else + $this->debug_message("*** OIM ($msgid) deleted"); + return $sMsg; + } + private function NSLogout() { + if (is_resource($this->NSfp) && !feof($this->NSfp)) { + // logout now + // NS: >>> OUT + $this->ns_writeln("OUT"); + fclose($this->NSfp); + $this->NSfp = false; + $this->log_message("*** logout now!"); + } + + } + private function NSRetryWait($Wait) { + $this->log_message("*** wait for $Wait seconds"); + for($i=0;$i<$Wait;$i++) { + sleep(1); + if($this->kill_me) return false; + } + return true; + } + public function ProcessSendMessageFileQueue() { + $aFiles = glob(MSN_CLASS_SPOOL_DIR.DIRECTORY_SEPARATOR.'*.msn'); + if (!is_array($aFiles)) return true; + clearstatcache(); + foreach ($aFiles as $filename) { + $fp = fopen($filename, 'rt'); + if (!$fp) continue; + $aTo = array(); + $sMessage = ''; + $buf = trim(fgets($fp)); + if (substr($buf, 0, 3) == 'TO:') { + $aTo = @explode(',', str_replace(array("\r","\n","\t",' '),'',substr($buf, 3))); + while (!feof($fp)) $sMessage.=rtrim(fgets($fp))."\n"; + } + fclose($fp); + if (!is_array($aTo) || count($aTo) == 0 || $sMessage == '') + $this->log_message("!!! message format error? delete $filename"); + else + { + foreach($aTo as $To) + { + @list($user, $domain, $network) = @explode('@', $To); + $MessageList[$network]["$user@$domain"]=$sMessage; + } + } + if($this->backup_file) + { + $backup_dir = MSN_CLASS_SPOOL_DIR.'/backup'; + if (!file_exists($backup_dir)) @mkdir($backup_dir); + $backup_name = $backup_dir.'/'.strftime('%Y%m%d%H%M%S').'_'.posix_getpid().'_'.basename($filename); + if (@rename($filename, $backup_name)) + $this->log_message("*** move file to $backup_name"); + } + else @unlink($filename); + } + foreach ($MessageList as $network => $Messages) + { + switch(trim($network)) + { + case '': + case 1: //MSN + // okay, try to ask a switchboard (SB) for sending message + // NS: >>> XFR {id} SB + // $this->ns_writeln("XFR $this->id SB"); + foreach($Messages as $User => $Message) + $this->MessageQueue[$User][]=$Message; + break; + case 'Offline': //MSN + //Send OIM + //FIXME: 修正Send OIM + foreach($Messages as $To => $Message) + { + $lockkey=''; + for ($i = 0; $i < $this->oim_try; $i++) + { + if(($oim_result = $this->sendOIM($To, $Message, $lockkey))===true) break; + if (is_array($oim_result) && $oim_result['challenge'] !== false) { + // need challenge lockkey + $this->log_message("*** we need a new challenge code for ".$oim_result['challenge']); + $lockkey = $this->getChallenge($oim_result['challenge']); + continue; + } + if ($oim_result === false || $oim_result['auth_policy'] !== false) + { + if ($re_login) + { + $this->log_message("*** can't send OIM, but we already re-login again, so ignore this OIM"); + break; + } + $this->log_message("*** can't send OIM, maybe ticket expired, try to login again"); + // maybe we need to re-login again + if(!$this->get_passport_ticket()) + { + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + break; + } + $this->log_message("**** get new ticket, try it again"); + continue; + } + } + } + break; + default: //Other + foreach($Messages as $To => $Message) { + $Message=$this->getMessage($Message, $network); + $len = strlen($Message); + $this->ns_writeln("UUM $this->id $To $network 1 $len"); + $this->ns_writedata($Message); + $this->log_message("*** sent to $To (network: $network):\n$Message"); + } + } + } + if(isset($this->MessageQueue[$User])&&(!isset($this->MessageQueue[$User]['XFRSent']))) + { + $this->MessageQueue[$User]['XFRSent']=false; + $this->MessageQueue[$User]['ReqTime']=false; + } + return true; + } + public function SignalFunction($signal) + { + switch($signal) + { + case SIGTRAP: + case SIGTERM: + case SIGHUP: + $this->End(); + return; + case SIGCHLD: + $ChildPid=pcntl_wait($status,WUNTRACED); + if($ChildPid>0) + { + $this->log_message("*** Child Process End for ".$this->ChildProcess[$ChildPid]); + unset($this->ChildProcess[$ChildPid]); + } + return; + } + } + + public function Run() + { + $this->log_message("*** startup ***"); + if(!pcntl_signal(SIGCHLD,array($this,'SignalFunction'))) die("Signal SIGCHLD Error\n"); + if(!pcntl_signal(SIGTERM,array($this,'SignalFunction'))) die("Signal SIGTERM Error\n"); + if(!pcntl_signal(SIGTRAP,array($this,'SignalFunction'))) die("Signal SIGTRAP Error\n"); + $process_file = false; + $sent = false; + $aADL = array(); + $aContactList = array(); + while (true) + { + if($this->kill_me) + { + $this->log_message("*** Okay, kill me now!"); + return $this->NSLogout(); + } + if (!is_resource($this->NSfp) || feof($this->NSfp)) + { + $this->log_message("*** try to connect to MSN network"); + if (!$this->connect($this->user, $this->password)) + { + $this->log_message("!!! Can't connect to server: $this->error"); + if(!$this->NSRetryWait($this->retry_wait)) continue; + } + $this->UpdateContacts(); + $this->LastPing=time(); + $this->log_message("*** connected, wait for command"); + $start_tm = time(); + $ping_tm = time(); + stream_set_timeout($this->NSfp, $this->NSStreamTimeout); + $aContactList = $this->getMembershipList(true); + if ($this->update_pending) { + if (is_array($aContactList)) { + $pending = 'Pending'; + foreach ($aContactList as $u_domain => $aUserList) { + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $aData) { + if (isset($aData[$pending])) { + // pending list + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) { + if (isset($aData[$list])) + $cnt++; + else { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { + $aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; + } + } + } + if ($cnt >= 2) { + $id = $aData[$pending]; + // we can delete it from pending now + if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending)) + unset($aContactList[$u_domain][$u_name][$network][$pending]); + } + } + else { + // sync list + foreach (array('Allow', 'Reverse') as $list) { + if (!isset($aData[$list])) { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + $aContactList[$u_domain][$u_name][$network][$list] = false; + } + } + } + } + } + } + } + } + $n = 0; + $sList = ''; + $len = 0; + if (is_array($aContactList)) { + foreach ($aContactList as $u_domain => $aUserList) { + $str = ''; + $len += strlen($str); + if ($len > 7400) { + $aADL[$n] = ''.$sList.''; + $n++; + $sList = ''; + $len = strlen($str); + } + $sList .= $str; + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $status) { + $str = ''; + $len += strlen($str); + // max: 7500, but is 19, + // so we use 7475 + if ($len > 7475) { + $sList .= ''; + $aADL[$n] = ''.$sList.''; + $n++; + $sList = ''.$str; + $len = strlen($sList); + } + else + $sList .= $str; + } + } + $sList .= ''; + } + } + $aADL[$n] = ''.$sList.''; + // NS: >>> BLP {id} BL + $this->ns_writeln("BLP $this->id BL"); + foreach ($aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + // NS: >>> PRP {id} MFN name + if ($this->alias == '') $this->alias = $user; + $aliasname = rawurlencode($this->alias); + $this->ns_writeln("PRP $this->id MFN $aliasname"); + //設定個人大頭貼 + //$MsnObj=$this->PhotoStckObj(); + // NS: >>> CHG {id} {status} {clientid} {msnobj} + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + // NS: >>> UUX {id} length + $str = ''.htmlspecialchars($this->psm).''; + $len = strlen($str); + $this->ns_writeln("UUX $this->id $len"); + $this->ns_writedata($str); + } + $data = $this->ns_readln(); + if($data===false) + { + //If No NS Message Process SendMessageFileQueue + if (time()-$this->LastPing > $this->ping_wait) + { + // NS: >>> PNG + $this->ns_writeln("PNG"); + $this->LastPing = time(); + } + if(count($this->ChildProcess)<$this->MAXChildProcess) + { + $Index=0; + foreach($this->MessageQueue as $User => $Message) + { + if(!trim($User)) continue; + if($Inxdex>=$this->MAXChildProcess-count($this->ChildProcess)) break; + if((!$Message['XFRSent'])||($Message['XFRSent']&&(time()-$this->MessageQueue[$User]['ReqTime']>$this->ReqSBXFRTimeout))) + { + $this->MessageQueue[$User]['XFRSent']=true; + $this->MessageQueue[$User]['ReqTime']=time(); + $this->log_message("*** Request SB for $User"); + $this->ns_writeln("XFR $this->id SB"); + $Index++; + } + } + } + if($this->ProcessSendMessageFileQueue()) continue; + break; + } + switch (substr($data,0,3)) + { + case 'SBS': + // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us + // NS: <<< SBS 0 null + break; + + case 'RFS': + // FIXME: + // NS: <<< RFS ??? + // refresh ADL, so we re-send it again + if (is_array($aADL)) { + foreach ($aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + } + break; + + case 'LST': + // NS: <<< LST {email} {alias} 11 0 + @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data); + @list($u_name, $u_domain) = @explode('@', $email); + if (!isset($aContactList[$u_domain][$u_name][1])) { + $aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; + $this->log_message("*** add to our contact list: $u_name@$u_domain"); + } + break; + + case 'ADL': + // randomly, we get ADL command, someome add us to their contact list for MSNP15 + // NS: <<< ADL 0 {size} + @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) + { + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($aContactList[$u_domain][$u_name][$network])) + $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); + else + { + $re_login = false; + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) + { + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + { + if ($re_login) { + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here"); + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + { + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + } + $aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; + } + $this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); + } + $str = ''; + $len = strlen($str); + } + else + $this->log_message("*** someone add us to their list: $data"); + $this->AddUsToMemberList($u_name.'@'.$u_domain, $network); + } + break; + + case 'RML': + // randomly, we get RML command, someome remove us to their contact list for MSNP15 + // NS: <<< RML 0 {size} + @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) + { + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($aContactList[$u_domain][$u_name][$network])) + { + $aData = $aContactList[$u_domain][$u_name][$network]; + foreach ($aData as $list => $id) + $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); + unset($aContactList[$u_domain][$u_name][$network]); + $this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); + } + else + $this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); + $this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network); + } + else + $this->log_message("*** someone remove us from their list: $data"); + } + break; + + case 'MSG': + // randomly, we get MSG notification from server + // NS: <<< MSG Hotmail Hotmail {size} + @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $maildata = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'Content-Type:', 13) == 0) { + if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && + strpos($line, 'text/x-msmsgsoimnotification') === false) { + // we just need text/x-msmsgsinitialmdatanotification + // or text/x-msmsgsoimnotification + $ignore = true; + break; + } + } + continue; + } + if (strncasecmp($line, 'Mail-Data:', 10) == 0) { + $maildata = trim(substr($line, 10)); + break; + } + } + if ($ignore) { + $this->log_message("*** ingnore MSG for: $line"); + break; + } + if ($maildata == '') { + $this->log_message("*** ingnore MSG not for OIM"); + break; + } + $re_login = false; + if (strcasecmp($maildata, 'too-large') == 0) { + $this->log_message("*** large mail-data, need to get the data via SOAP"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->log_message("*** can't get mail-data via SOAP"); + // maybe we need to re-login again + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + break; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); + break; + } + } + } + // could be a lots of ..., so we can't use preg_match here + $p = $maildata; + $aOIMs = array(); + while (1) { + $start = strpos($p, ''); + $end = strpos($p, ''); + if ($start === false || $end === false || $start > $end) break; + $end += 4; + $sOIM = substr($p, $start, $end - $start); + $aOIMs[] = $sOIM; + $p = substr($p, $end); + } + if (count($aOIMs) == 0) { + $this->log_message("*** ingnore empty OIM"); + break; + } + foreach ($aOIMs as $maildata) { + // T: 11 for MSN, 13 for Yahoo + // S: 6 for MSN, 7 for Yahoo + // RT: the datetime received by server + // RS: already read or not + // SZ: size of message + // E: sender + // I: msgid + // F: always 00000000-0000-0000-0000-000000000009 + // N: sender alias + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without type"); + continue; + } + $oim_type = $matches[1]; + if ($oim_type = 13) + $network = 32; + else + $network = 1; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without sender"); + continue; + } + $oim_sender = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without msgid"); + continue; + } + $oim_msgid = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_size = (count($matches) == 0) ? 0 : $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_time = (count($matches) == 0) ? 0 : $matches[1]; + $this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); + $sMsg = $this->getOIM_message($oim_msgid); + if ($sMsg === false) { + $this->log_message("*** can't get OIM, msgid = $oim_msgid"); + if ($re_login) { + $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + $sMsg = $this->getOIM_message($oim_msgid); + if ($sMsg === false) { + $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + continue; + } + } + $this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); + + $this->ReceivedMessage($oim_sender,$sMsg,$network,true); + } + } + break; + + case 'UBM': + // randomly, we get UBM, this is the message from other network, like Yahoo! + // NS: <<< UBM {email} $network $type {size} + @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $sMsg = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'TypingUser:', 11) == 0) { + $ignore = true; + break; + } + continue; + } + $aSubLines = @explode("\r", $line); + foreach ($aSubLines as $str) { + if ($sMsg !== '') + $sMsg .= "\n"; + $sMsg .= $str; + } + } + if($ignore) + { + $this->log_message("*** ingnore from $from_email: $line"); + break; + } + $this->log_message("*** MSG from $from_email (network: $network): $sMsg"); + $this->ReceivedMessage($from_email,$sMsg,$network,false); + } + break; + + case 'UBX': + // randomly, we get UBX notification from server + // NS: <<< UBX email {network} {size} + @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); + // we don't need the notification data, so just ignore it + if (is_numeric($size) && $size > 0) + $this->ns_readdata($size); + break; + + case 'CHL': + // randomly, we'll get challenge from server + // NS: <<< CHL 0 {code} + @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data); + $fingerprint = $this->getChallenge($chl_code); + // NS: >>> QRY {id} {product_id} 32 + // NS: >>> fingerprint + $this->ns_writeln("QRY $this->id $this->prod_id 32"); + $this->ns_writedata($fingerprint); + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + break; + case 'CHG': + // NS: <<< CHG {id} {status} {code} + // ignore it + // change our status to online first + break; + + case 'XFR': + // sometimes, NS will redirect to another NS + // MSNP9 + // NS: <<< XFR {id} NS {server} 0 {server} + // MSNP15 + // NS: <<< XFR {id} NS {server} U D + // for normal switchboard XFR + // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0 + @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data); + @list($ip, $port) = @explode(':', $server); + if ($server_type != 'SB') { + // maybe exit? + // this connection will close after XFR + $this->NSLogout(); + continue; + } + if(count($this->MessageQueue)) + { + foreach($this->MessageQueue as $User => $Message) + { + //$this->ChildProcess[$ChildPid] + $this->log_message("*** XFR SB $User"); + $pid=pcntl_fork(); + if($pid) + { + //Parrent Process + $this->ChildProcess[$pid]=$User; + break; + } + elseif($pid==-1) + { + $this->log_message("*** Fork Error $User"); + break; + } + else + { + //Child Process + $this->log_message("*** Child Process Start for $User"); + unset($Message['XFRSent']); + unset($Message['ReqTime']); + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); + if ($bSBresult === false) + { + // error for switchboard + $this->log_message("!!! error for sending message to ".$User); + } + die; + } + } + unset($this->MessageQueue[$User]); + } + /* + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); + if ($bSBresult === false) { + // error for switchboard + $this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); + $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; + }*/ + break; + case 'QNG': + // NS: <<< QNG {time} + @list(/* QNG */, $this->ping_wait) = @explode(' ', $data); + if ($this->ping_wait == 0) $this->ping_wait = 50; + //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; + //Mod by Ricky Set Online + break; + + case 'RNG': + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + else + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + // someone is trying to talk to us + // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 + $this->log_message("NS: <<< RNG $data"); + @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); + @list($sb_ip, $sb_port) = @explode(':', $server); + $this->log_message("*** RING from $email, $sb_ip:$sb_port"); + $this->addContact($email,1,$email, true); + $pid=pcntl_fork(); + if($pid) + { + //Parrent Process + $this->ChildProcess[$pid]='RNG'; + break; + } + elseif($pid==-1) + { + $this->log_message("*** Fork Error $User"); + break; + } + else + { + //Child Process + $this->log_message("*** Ring Child Process Start for $User"); + $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email); + die; + } + break; + case 'OUT': + // force logout from NS + // NS: <<< OUT xxx + fclose($this->NSfp); + $this->log_message("*** LOGOUT from NS"); + break; + + default: + $code = substr($data,0,3); + if (is_numeric($code)) { + $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; + $this->debug_message("*** NS: $this->error"); + + return $this->NsLogout(); + } + break; + } + } + return $this->NsLogout(); + } + + /*public function SendMessage($Message, $To) + { + $FileName = MSN_CLASS_SPOOL_DIR.'/'.strftime('%Y%m%d%H%M%S',time()).'_'.posix_getpid().'_sendMessage.msn'; + if(!is_array($To)) + $To=array($To); + $Receiver=''; + foreach($To as $Email) + { + list($name,$host,$network)=explode('@',$Email); + $network=$network==''?1:$network; + if($network==1 && $this->SwitchBoardProcess && $this->SwitchBoardSessionUser=="$name@$host" ) + { + $this->debug_message("*** SendMessage to $Receiver use SB message queue."); + array_push($this->SwitchBoardMessageQueue,$Message); + continue; + } + $Receiver.="$name@$host@$network,"; + } + if($Receiver=='') return; + $Receiver=substr($Receiver,0,-1); + $this->debug_message("*** SendMessage to $Receiver use File queue."); + file_put_contents($FileName,"TO: $Receiver\n$Message\n"); + }*/ + + function getChallenge($code) + { + // MSNP15 + // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges + // Step 1: The MD5 Hash + $md5Hash = md5($code.$this->prod_key); + $aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0")); + for ($i = 0; $i < 4; $i++) { + $aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0")))); + $aMD5[$i] = (0 + base_convert($aMD5[$i], 16, 10)) & 0x7FFFFFFF; + } + + // Step 2: A new string + $chl_id = $code.$this->prod_id; + $chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8)); + + $aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1)); + for ($i = 0; $i < count($aID); $i++) { + $aID[$i] = implode('', array_reverse(@explode("\0", chunk_split($aID[$i], 1, "\0")))); + $aID[$i] = 0 + base_convert(bin2hex($aID[$i]), 16, 10); + } + + // Step 3: The 64 bit key + $magic_num = 0x0E79A9C1; + $str7f = 0x7FFFFFFF; + $high = 0; + $low = 0; + for ($i = 0; $i < count($aID); $i += 2) { + $temp = $aID[$i]; + $temp = bcmod(bcmul($magic_num, $temp), $str7f); + $temp = bcadd($temp, $high); + $temp = bcadd(bcmul($aMD5[0], $temp), $aMD5[1]); + $temp = bcmod($temp, $str7f); + + $high = $aID[$i+1]; + $high = bcmod(bcadd($high, $temp), $str7f); + $high = bcadd(bcmul($aMD5[2], $high), $aMD5[3]); + $high = bcmod($high, $str7f); + + $low = bcadd(bcadd($low, $high), $temp); + } + + $high = bcmod(bcadd($high, $aMD5[1]), $str7f); + $low = bcmod(bcadd($low, $aMD5[3]), $str7f); + + $new_high = bcmul($high & 0xFF, 0x1000000); + $new_high = bcadd($new_high, bcmul($high & 0xFF00, 0x100)); + $new_high = bcadd($new_high, bcdiv($high & 0xFF0000, 0x100)); + $new_high = bcadd($new_high, bcdiv($high & 0xFF000000, 0x1000000)); + // we need integer here + $high = 0+$new_high; + + $new_low = bcmul($low & 0xFF, 0x1000000); + $new_low = bcadd($new_low, bcmul($low & 0xFF00, 0x100)); + $new_low = bcadd($new_low, bcdiv($low & 0xFF0000, 0x100)); + $new_low = bcadd($new_low, bcdiv($low & 0xFF000000, 0x1000000)); + // we need integer here + $low = 0+$new_low; + + // we just use 32 bits integer, don't need the key, just high/low + // $key = bcadd(bcmul($high, 0x100000000), $low); + + // Step 4: Using the key + $md5Hash = md5($code.$this->prod_key); + $aHash = @explode("\0", chunk_split($md5Hash, 8, "\0")); + + $hash = ''; + $hash .= sprintf("%08x", (0 + base_convert($aHash[0], 16, 10)) ^ $high); + $hash .= sprintf("%08x", (0 + base_convert($aHash[1], 16, 10)) ^ $low); + $hash .= sprintf("%08x", (0 + base_convert($aHash[2], 16, 10)) ^ $high); + $hash .= sprintf("%08x", (0 + base_convert($aHash[3], 16, 10)) ^ $low); + + return $hash; + } + + private function getMessage($sMessage, $network = 1) + { + $msg_header = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n"; + $msg_header_len = strlen($msg_header); + if ($network == 1) + $maxlen = $this->max_msn_message_len - $msg_header_len; + else + $maxlen = $this->max_yahoo_message_len - $msg_header_len; + $sMessage=str_replace("\r", '', $sMessage); + $msg=substr($sMessage,0,$maxlen); + return $msg_header.$msg; + } + /** + * + * @param $Action 連線模式 'Active' => 主動傳送訊息,'Passive' => 接收訊息 + * @param $Param + * @return boolean + */ + private function DoSwitchBoard($Action,$Param) + { + $SessionEnd=false; + $Joined=false; + $id=1; + $LastActive=time(); + stream_set_timeout($this->SBFp, $this->SBTimeout); + switch($Action) + { + case 'Active': + $cki_code=$Param['cki']; + $user=$Param['user']; + $this->SwitchBoardMessageQueue=$Param['Msg']; + // SB: >>> USR {id} {user} {cki} + $this->SB_writeln("USR $id $this->user $cki_code"); + $id++; + $this->SwitchBoardSessionUser=$user; + break; + case 'Passive': + $ticket=$Param['ticket']; + $sid=$Param['sid']; + $user=$Param['user']; + // SB: >>> ANS {id} {user} {ticket} {session_id} + $this->SB_writeln("ANS $id $this->user $ticket $sid"); + $id++; + $this->SwitchBoardSessionUser=$user; + break; + default: + return false; + } + while((!feof($this->SBFp))&&(!$SessionEnd)) + { + $data = $this->SB_readln(); + if($this->kill_me) + { + $this->log_message("*** SB Okay, kill me now!"); + break; + } + if($data === false) + { + if(time()-$LastActive > $this->SBIdleTimeout) + { + $this->debug_message("*** SB Idle Timeout!"); + break; + } + if(!$Joined) continue; + foreach($this->SwitchBoardMessageQueue as $Message) + { + if($Message=='') continue; + $aMessage = $this->getMessage($Message); + //CheckEmotion... + $MsnObjDefine=$this->GetMsnObjDefine($aMessage); + if($MsnObjDefine!=='') + { + $SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine"; + $len = strlen($SendString); + $this->SB_writeln("MSG $id N $len"); + $id++; + $this->SB_writedata($SendString); + $this->id++; + } + $len = strlen($aMessage); + $this->SB_writeln("MSG $id N $len"); + $id++; + $this->SB_writedata($aMessage); + } + $this->SwitchBoardMessageQueue=array(); + $LastActive=time(); + continue; + } + $code = substr($data, 0, 3); + switch($code) + { + case 'IRO': + // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid} + @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data); + $this->log_message("*** $email join us"); + $Joined=true; + break; + case 'BYE': + $this->log_message("*** Quit for BYE"); + $SessionEnd=true; + break; + case 'USR': + // SB: <<< USR {id} OK {user} {alias} + // we don't need the data, just ignore it + // request user to join this switchboard + // SB: >>> CAL {id} {user} + $this->SB_writeln("CAL $id $user"); + $id++; + break; + case 'CAL': + // SB: <<< CAL {id} RINGING {?} + // we don't need this, just ignore, and wait for other response + $this->id++; + break; + case 'JOI': + // SB: <<< JOI {user} {alias} {clientid?} + // someone join us + // we don't need the data, just ignore it + // no more user here + $Joined=true; + break; + case 'MSG': + // SB: <<< MSG {email} {alias} {len} + @list(/* MSG */, $from_email, /* alias */, $len, ) = @explode(' ', $data); + $len = trim($len); + $data = $this->SB_readdata($len); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $is_p2p = false; + $sMsg = ''; + foreach ($aLines as $line) + { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'TypingUser:', 11) == 0) { + // typing notification, just ignore + $ignore = true; + break; + } + if (strncasecmp($line, 'Chunk:', 6) == 0) { + // we don't handle any split message, just ignore + $ignore = true; + break; + } + if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) { + // p2p message, ignore it, but we need to send acknowledgement for it... + $is_p2p = true; + $p = strstr($data, "\n\n"); + $sMsg = ''; + if ($p === false) { + $p = strstr($data, "\r\n\r\n"); + if ($p !== false) + $sMsg = substr($p, 4); + } + else + $sMsg = substr($p, 2); + break; + } + if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) { + // ignore all application/x-... message + // for example: + // application/x-ms-ink => ink message + $ignore = true; + break; + } + if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) { + // ignore all text/x-... message + // for example: + // text/x-msnmsgr-datacast => nudge, voice clip.... + // text/x-mms-animemoticon => customized animemotion word + $ignore = true; + break; + } + continue; + } + if ($sMsg !== '') + $sMsg .= "\n"; + $sMsg .= $line; + } + if ($ignore) + { + $this->log_message("*** ingnore from $from_email: $line"); + break; + } + if ($is_p2p) + { + // we will ignore any p2p message after sending acknowledgement + $ignore = true; + $len = strlen($sMsg); + $this->log_message("*** p2p message from $from_email, size $len"); + // header = 48 bytes + // content >= 0 bytes + // footer = 4 bytes + // so it need to >= 52 bytes + /*if ($len < 52) { + $this->log_message("*** p2p: size error, less than 52!"); + break; + }*/ + $aDwords = @unpack("V12dword", $sMsg); + if (!is_array($aDwords)) { + $this->log_message("*** p2p: header unpack error!"); + break; + } + $this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg)); + $hdr_SessionID = $aDwords['dword1']; + $hdr_Identifier = $aDwords['dword2']; + $hdr_DataOffsetLow = $aDwords['dword3']; + $hdr_DataOffsetHigh = $aDwords['dword4']; + $hdr_TotalDataSizeLow = $aDwords['dword5']; + $hdr_TotalDataSizeHigh = $aDwords['dword6']; + $hdr_MessageLength = $aDwords['dword7']; + $hdr_Flag = $aDwords['dword8']; + $hdr_AckID = $aDwords['dword9']; + $hdr_AckUID = $aDwords['dword10']; + $hdr_AckSizeLow = $aDwords['dword11']; + $hdr_AckSizeHigh = $aDwords['dword12']; + $this->debug_message("*** p2p: header SessionID = $hdr_SessionID"); + $this->debug_message("*** p2p: header Inentifier = $hdr_Identifier"); + $this->debug_message("*** p2p: header Data Offset Low = $hdr_DataOffsetLow"); + $this->debug_message("*** p2p: header Data Offset High = $hdr_DataOffsetHigh"); + $this->debug_message("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow"); + $this->debug_message("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh"); + $this->debug_message("*** p2p: header MessageLength = $hdr_MessageLength"); + $this->debug_message("*** p2p: header Flag = $hdr_Flag"); + $this->debug_message("*** p2p: header AckID = $hdr_AckID"); + $this->debug_message("*** p2p: header AckUID = $hdr_AckUID"); + $this->debug_message("*** p2p: header AckSize Low = $hdr_AckSizeLow"); + $this->debug_message("*** p2p: header AckSize High = $hdr_AckSizeHigh"); + if($hdr_Flag==2) { + //This is an ACK from SB ignore.... + $this->debug_message("*** p2p: //This is an ACK from SB ignore....:\n"); + break; + } + $MsgBody=$this->linetoArray(substr($sMsg,48,-4)); + $this->debug_message("*** p2p: body".print_r($MsgBody,true)); + if(($MsgBody['EUF-GUID']=='{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}')&&($PictureFilePath=$this->GetPictureFilePath($MsgBody['Context']))) + { + while(true) + { + if($this->SB_readln()===false) break; + } + $this->debug_message("*** p2p: Inv hdr:\n".$this->dump_binary(substr($sMsg,0,48))); + preg_match('/{([0-9A-F\-]*)}/i',$MsgBody['Via'],$Matches); + $BranchGUID=$Matches[1]; + //it's an invite to send a display picture. + $new_id = ~$hdr_Identifier; + $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + 0, + 2, + $hdr_Identifier, + $hdr_AckID, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh); + $footer = pack("L", 0); + $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer"; + $len = strlen($message); + $this->SB_writeln("MSG $id D $len"); + $id++; + $this->SB_writedata($message); + $this->log_message("*** p2p: send display picture acknowledgement for $hdr_SessionID"); + $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message)); + $this->SB_readln();//Read ACK; + $this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr)); + $new_id-=3; + //Send 200 OK message + $MessageContent="SessionID: ".$MsgBody['SessionID']."\r\n\r\n".pack("C", 0); + $MessagePayload= + "MSNSLP/1.0 200 OK\r\n". + "To: \r\n". + "From: user.">\r\n". + "Via: ".$MsgBody['Via']."\r\n". + "CSeq: ".($MsgBody['CSeq']+1)."\r\n". + "Call-ID: ".$MsgBody['Call-ID']."\r\n". + "Max-Forwards: 0\r\n". + "Content-Type: application/x-msnmsgr-sessionreqbody\r\n". + "Content-Length: ".strlen($MessageContent)."\r\n\r\n". + $MessageContent; + $hdr_TotalDataSizeLow=strlen($MessagePayload); + $hdr_TotalDataSizeHigh=0; + $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + strlen($MessagePayload), + 0, + rand(), + 0, + 0,0); + + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer"; + $this->SB_writeln("MSG $id D ".strlen($message)); + $id++; + $this->SB_writedata($message); + $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message)); + $this->SB_readln();//Read ACK; + + $this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr)); + //send Data preparation message + //send 4 null bytes as data + $hdr_TotalDataSizeLow=4; + $hdr_TotalDataSizeHigh=0; + $new_id++; + $hdr = pack("LLLLLLLLLLLL", + $MsgBody['SessionID'], + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + $hdr_TotalDataSizeLow, + 0, + rand(), + 0, + 0,0); + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr".pack('L',0)."$footer"; + $this->SB_writeln("MSG $id D ".strlen($message)); + $id++; + $this->SB_writedata($message); + $this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message)); + $this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr)); + $this->SB_readln();//Read ACK; + + //send Data Content.. + $footer=pack('N',1); + $new_id++; + $FileSize=filesize($PictureFilePath); + if($hTitle=fopen($PictureFilePath,'rb')) + { + $Offset=0; + //$new_id++; + while(!feof($hTitle)) + { + $FileContent=fread($hTitle,1024); + $FileContentSize=strlen($FileContent); + $hdr = pack("LLLLLLLLLLLL", + $MsgBody['SessionID'], + $new_id, + $Offset, 0, + $FileSize,0, + $FileContentSize, + 0x20, + rand(), + 0, + 0,0 + ); + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer"; + $this->SB_writeln("MSG $id D ".strlen($message)); + $id++; + $this->SB_writedata($message); + $this->debug_message("*** p2p: dump send Data Content message $Offset / $FileSize :\n".$this->dump_binary($message)); + $this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr)); + //$this->SB_readln();//Read ACK; + $Offset+=$FileContentSize; + } + } + //Send Bye + /* + $MessageContent="\r\n".pack("C", 0); + $MessagePayload= + "BYE MSNMSGR:MSNSLP/1.0\r\n". + "To: \r\n". + "From: user.">\r\n". + "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n". + "CSeq: 0\r\n". + "Call-ID: ".$MsgBody['Call-ID']."\r\n". + "Max-Forwards: 0\r\n". + "Content-Type: application/x-msnmsgr-sessionclosebody\r\n". + "Content-Length: ".strlen($MessageContent)."\r\n\r\n".$MessageContent; + $footer=pack('N',0); + $hdr_TotalDataSizeLow=strlen($MessagePayload); + $hdr_TotalDataSizeHigh=0; + $new_id++; + $hdr = pack("LLLLLLLLLLLL", + 0, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + 0, + 0, + rand(), + 0, + 0,0); + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer"; + $this->SB_writeln("MSG $id D ".strlen($message)); + $id++; + $this->SB_writedata($message); + $this->debug_message("*** p2p: dump send BYE message :\n".$this->dump_binary($message)); + */ + break; + } + //TODO: + //if ($hdr_Flag == 2) { + // just send ACK... + // $this->SB_writeln("ACK $id"); + // break; + //} + if ($hdr_SessionID == 4) { + // ignore? + $this->debug_message("*** p2p: ignore flag 4"); + break; + } + $finished = false; + if ($hdr_TotalDataSizeHigh == 0) { + // only 32 bites size + if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow) + $finished = true; + } + else { + // we won't accept any file transfer + // so I think we won't get any message size need to use 64 bits + // 64 bits size here, can't count directly... + $totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10); + $dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10); + $messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10); + $now_size = bcadd($dataoffset, $messagelength); + if (bccomp($now_size, $totalsize) >= 0) + $finished = true; + } + if (!$finished) { + // ignore not finished split packet + $this->debug_message("*** p2p: ignore split packet, not finished"); + break; + } + //$new_id = ~$hdr_Identifier; + /* + $new_id++; + $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + 0, + 2, + $hdr_Identifier, + $hdr_AckID, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh); + $footer = pack("L", 0); + $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer"; + $len = strlen($message); + $this->SB_writeln("MSG $id D $len"); + $id++; + $this->SB_writedata($message); + $this->log_message("*** p2p: send acknowledgement for $hdr_SessionID"); + $this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer)); + */ + break; + } + $this->log_message("*** MSG from $from_email: $sMsg"); + $this->ReceivedMessage($from_email,$sMsg,$network,false); + break; + case '217': + $this->log_message("*** User $user is offline. Try OIM."); + foreach($this->SwitchBoardMessageQueue as $Message) + $this->SendMessage($Message,"$user@Offline"); + $SessionEnd=true; + break; + default: + if (is_numeric($code)) + { + $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; + $this->debug_message("*** SB: $this->error"); + $SessionEnd=true; + } + break; + } + $LastActive = time(); + } + if (feof($this->SBFp)) + { + // lost connection? error? try OIM later + @fclose($this->SBFp); + return false; + } + $this->SB_writeln("OUT"); + @fclose($this->SBFp); + return true; + } + private function switchboard_control($ip, $port, $cki_code, $user, $Messages) + { + $this->SwitchBoardProcess=1; + $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); + $this->SBFp = @fsockopen($ip, $port, $errno, $errstr, 5); + if (!$this->SBFp) + { + $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); + return false; + } + return $this->DoSwitchBoard('Active',array('cki'=>$cki_code, 'user'=>$user,'Msg'=>$Messages)); + } + private function switchboard_ring($ip, $port, $sid, $ticket,$user) + { + $this->SwitchBoardProcess=2; + $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); + $this->SBFp = @fsockopen($ip, $port, $errno, $errstr, 5); + if (!$this->SBFp) + { + $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); + return false; + } + return $this->DoSwitchBoard('Passive',array('sid'=>$sid,'user'=>$user,'ticket'=>$ticket)); + } + + private function sendOIM($to, $sMessage, $lockkey) + { + $XML = ' + + + + + + + http://messenger.msn.com + 1 + + + + text + MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 +X-OIM-Message-Type: OfflineMessage +X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7} +X-OIM-Sequence-Num: 1 + +'.chunk_split(base64_encode($sMessage)).' + + +'; + + $header_array = array( + 'SOAPAction: '.$this->oim_send_soap, + 'Content-Type: text/xml', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' + ); + + $this->debug_message("*** URL: $this->oim_send_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_send_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code == 200) { + $this->debug_message("*** OIM sent for $to"); + return true; + } + + $challenge = false; + $auth_policy = false; + // the lockkey is invalid, authenticated fail, we need challenge it again + // 364763969 + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + // yes, we get new LockKeyChallenge + $challenge = $matches[2]; + $this->debug_message("*** OIM need new challenge ($challenge) for $to"); + } + // auth policy error + // MBI_SSL + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + $auth_policy = $matches[2]; + $this->debug_message("*** OIM need new auth policy ($auth_policy) for $to"); + } + if ($auth_policy === false && $challenge === false) { + //q0:AuthenticationFailed + preg_match("#(.*)#", $data, $matches); + if (count($matches) == 0) { + // no error, we assume the OIM is sent + $this->debug_message("*** OIM sent for $to"); + return true; + } + $err_code = $matches[2]; + //Exception of type 'System.Web.Services.Protocols.SoapException' was thrown. + preg_match("#(.*)#", $data, $matches); + if (count($matches) > 0) + $err_msg = $matches[1]; + else + $err_msg = ''; + $this->debug_message("*** OIM failed for $to"); + $this->debug_message("*** OIM Error code: $err_code"); + $this->debug_message("*** OIM Error Message: $err_msg"); + return false; + } + return array('challenge' => $challenge, 'auth_policy' => $auth_policy); + } + + // read data for specified size + private function ns_readdata($size) { + $data = ''; + $count = 0; + while (!feof($this->NSfp)) { + $buf = @fread($this->NSfp, $size - $count); + $data .= $buf; + $count += strlen($buf); + if ($count >= $size) break; + } + $this->debug_message("NS: data ($size/$count) <<<\n$data"); + return $data; + } + + // read one line + private function ns_readln() { + $data = @fgets($this->NSfp, 4096); + if ($data !== false) { + $data = trim($data); + $this->debug_message("NS: <<< $data"); + } + return $data; + } + + // write to server, append \r\n, also increase id + private function ns_writeln($data) { + @fwrite($this->NSfp, $data."\r\n"); + $this->debug_message("NS: >>> $data"); + $this->id++; + return; + } + + // write data to server + private function ns_writedata($data) { + @fwrite($this->NSfp, $data); + $this->debug_message("NS: >>> $data"); + return; + } + + // read data for specified size for SB + private function sb_readdata($size) { + $data = ''; + $count = 0; + while (!feof($this->SBFp)) { + $buf = @fread($this->SBFp, $size - $count); + $data .= $buf; + $count += strlen($buf); + if ($count >= $size) break; + } + $this->debug_message("SB: data ($size/$count) <<<\n$data"); + return $data; + } + + // read one line for SB + private function sb_readln() { + $data = @fgets($this->SBFp, 4096); + if ($data !== false) { + $data = trim($data); + $this->debug_message("SB: <<< $data"); + } + return $data; + } + + // write to server for SB, append \r\n, also increase id + // switchboard server only accept \r\n, it will lost connection if just \n only + private function sb_writeln($data) { + @fwrite($this->SBFp, $data."\r\n"); + $this->debug_message("SB: >>> $data"); + $this->id++; + return; + } + + // write data to server + private function sb_writedata($data) { + @fwrite($this->SBFp, $data); + $this->debug_message("SB: >>> $data"); + return; + } + + // show debug information + function debug_message($str) { + if (!$this->debug) return; + if($this->debug===STDOUT) echo $str."\n"; + /*$fname=MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msn_'.strftime('%Y%m%d').'.debug'; + $fp = fopen($fname, 'at'); + if ($fp) { + fputs($fp, strftime('%m/%d/%y %H:%M:%S').' ['.posix_getpid().'] '.$str."\n"); + fclose($fp); + return; + }*/ + // still show debug information, if we can't open log_file + echo $str."\n"; + return; + } + + function dump_binary($str) { + $buf = ''; + $a_str = ''; + $h_str = ''; + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + if (($i % 16) == 0) { + if ($buf !== '') { + $buf .= "$h_str $a_str\n"; + } + $buf .= sprintf("%04X:", $i); + $a_str = ''; + $h_str = ''; + } + $ch = ord($str[$i]); + if ($ch < 32) + $a_str .= '.'; + else + $a_str .= chr($ch); + $h_str .= sprintf(" %02X", $ch); + } + if ($h_str !== '') + $buf .= "$h_str $a_str\n"; + return $buf; + } + + // write log + function log_message($str) { + /*$fname = MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msn_'.strftime('%Y%m%d').'.log'; + $fp = fopen($fname, 'at'); + if ($fp) { + fputs($fp, strftime('%m/%d/%y %H:%M:%S').' ['.posix_getpid().'] '.$str."\n"); + fclose($fp); + }*/ + $this->debug_message($str); + return; + } + /** + * + * @param $FilePath 圖檔路徑 + * @param $Type 檔案類型 3=>大頭貼,2表情圖案 + * @return array + */ + private function MsnObj($FilePath,$Type=3) + { + if(!($FileSize=filesize($FilePath))) return ''; + $Location=md5($FilePath); + $Friendly=md5($FilePath.$Type); + if(isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location]; + $sha1d=base64_encode(sha1(file_get_contents($FilePath),true)); + $sha1c=base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d",true)); + $this->MsnObjArray[$Location]=$FilePath; + $MsnObj=''; + $this->MsnObjMap[$Location]=$MsnObj; + $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n"); + return $MsnObj; + } + private function linetoArray($lines) { + $lines=str_replace("\r",'',$lines); + $lines=explode("\n",$lines); + foreach($lines as $line) { + if(!isset($line{3})) continue; + list($Key,$Val)=explode(':',$line); + $Data[trim($Key)]=trim($Val); + } + return $Data; + } + private function GetPictureFilePath($Context) + { + $MsnObj=base64_decode($Context); + if(preg_match('/location="(.*?)"/i',$MsnObj,$Match)) + $location=$Match[1]; + $this->debug_message("*** p2p: PictureFile[$location] ::All".print_r($this->MsnObjArray,true)."\n"); + if($location&&(isset($this->MsnObjArray[$location]))) + return $this->MsnObjArray[$location]; + return false; + } + private function GetMsnObjDefine($Message) + { + $DefineString=''; + if(is_array($this->Emotions)) + foreach($this->Emotions as $Pattern => $FilePath) + { + if(strpos($Message,$Pattern)!==false) + $DefineString.="$Pattern\t".$this->MsnObj($FilePath,2)."\t"; + } + return $DefineString; + } + /** + * Receive Message Overload Function + * @param $Sender + * @param $Message + * @param $Network 1 => msn , 32 =>yahoo + * @param $IsOIM + * @return unknown_type + */ + protected function ReceivedMessage($Sender,$Message,$Network,$IsOIM=false){} + /** + * Remove Us From Member List Overload Function + * @param $User + * @param $Message + * @param $Network 1 => msn , 32 =>yahoo + * @return unknown_type + */ + protected function RemoveUsFromMemberList($User,$Network){} + /** + * Add Us to Member List Overload Function + * @param $User + * @param $Message + * @param $Network 1 => msn , 32 =>yahoo + * @return unknown_type + */ + protected function AddUsToMemberList($User,$Network){} + + public function signon() { + $this->log_message("*** try to connect to MSN network"); + while(!$this->connect($this->user, $this->password)) + { + $this->log_message("!!! Can't connect to server: $this->error"); + if(!$this->NSRetryWait($this->retry_wait)) return; + } + $this->UpdateContacts(); + $this->LastPing=time(); + $this->log_message("*** connected, wait for command"); + $start_tm = time(); + $ping_tm = time(); + stream_set_timeout($this->NSfp, $this->NSStreamTimeout); + $this->aContactList = $this->getMembershipList(true); + if ($this->update_pending) { + if (is_array($this->aContactList)) { + $pending = 'Pending'; + foreach ($this->aContactList as $u_domain => $aUserList) { + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $aData) { + if (isset($aData[$pending])) { + // pending list + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) { + if (isset($aData[$list])) + $cnt++; + else { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; + } + } + } + if ($cnt >= 2) { + $id = $aData[$pending]; + // we can delete it from pending now + if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending)) + unset($this->aContactList[$u_domain][$u_name][$network][$pending]); + } + } + else { + // sync list + foreach (array('Allow', 'Reverse') as $list) { + if (!isset($aData[$list])) { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + } + } + } + } + } + } + } + } + $n = 0; + $sList = ''; + $len = 0; + if (is_array($this->aContactList)) { + foreach ($this->aContactList as $u_domain => $aUserList) { + $str = ''; + $len += strlen($str); + if ($len > 7400) { + $aADL[$n] = ''.$sList.''; + $n++; + $sList = ''; + $len = strlen($str); + } + $sList .= $str; + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $status) { + $str = ''; + $len += strlen($str); + // max: 7500, but is 19, + // so we use 7475 + if ($len > 7475) { + $sList .= ''; + $aADL[$n] = ''.$sList.''; + $n++; + $sList = ''.$str; + $len = strlen($sList); + } + else + $sList .= $str; + } + } + $sList .= ''; + } + } + $aADL[$n] = ''.$sList.''; + // NS: >>> BLP {id} BL + $this->ns_writeln("BLP $this->id BL"); + foreach ($aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + // NS: >>> PRP {id} MFN name + if ($this->alias == '') $this->alias = $user; + $aliasname = rawurlencode($this->alias); + $this->ns_writeln("PRP $this->id MFN $aliasname"); + //設定個人大頭貼 + //$MsnObj=$this->PhotoStckObj(); + // NS: >>> CHG {id} {status} {clientid} {msnobj} + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + // NS: >>> UUX {id} length + $str = ''.htmlspecialchars($this->psm).''; + $len = strlen($str); + $this->ns_writeln("UUX $this->id $len"); + $this->ns_writedata($str); + } + + public function NSreceive() { + $this->log_message("*** startup ***"); + + $aADL = array(); + + // Sign in again if not signed in or socket failed + if (!is_resource($this->NSfp) || feof($this->NSfp)) { + $this->signon(); + } + + $data = $this->ns_readln(); + /*if($data===false) + { + //If No NS Message Process SendMessageFileQueue + if (time()-$this->LastPing > $this->ping_wait) + { + // NS: >>> PNG + $this->ns_writeln("PNG"); + $this->LastPing = time(); + } + if(count($this->ChildProcess)<$this->MAXChildProcess) + { + $Index=0; + foreach($this->MessageQueue as $User => $Message) + { + if(!trim($User)) continue; + if($Inxdex>=$this->MAXChildProcess-count($this->ChildProcess)) break; + if((!$Message['XFRSent'])||($Message['XFRSent']&&(time()-$this->MessageQueue[$User]['ReqTime']>$this->ReqSBXFRTimeout))) + { + $this->MessageQueue[$User]['XFRSent']=true; + $this->MessageQueue[$User]['ReqTime']=time(); + $this->log_message("*** Request SB for $User"); + $this->ns_writeln("XFR $this->id SB"); + $Index++; + } + } + } + if($this->ProcessSendMessageFileQueue()) continue; + break; + }*/ + switch (substr($data,0,3)) + { + case 'SBS': + // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us + // NS: <<< SBS 0 null + break; + + case 'RFS': + // FIXME: + // NS: <<< RFS ??? + // refresh ADL, so we re-send it again + if (is_array($aADL)) { + foreach ($aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + } + break; + + case 'LST': + // NS: <<< LST {email} {alias} 11 0 + @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data); + @list($u_name, $u_domain) = @explode('@', $email); + if (!isset($this->aContactList[$u_domain][$u_name][1])) { + $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; + $this->log_message("*** add to our contact list: $u_name@$u_domain"); + } + break; + + case 'ADL': + // randomly, we get ADL command, someome add us to their contact list for MSNP15 + // NS: <<< ADL 0 {size} + @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) + { + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($this->aContactList[$u_domain][$u_name][$network])) + $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); + else + { + $re_login = false; + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) + { + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + { + if ($re_login) { + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here"); + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + { + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + } + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; + } + $this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); + } + $str = ''; + $len = strlen($str); + } + else + $this->log_message("*** someone add us to their list: $data"); + $this->AddUsToMemberList($u_name.'@'.$u_domain, $network); + } + break; + + case 'RML': + // randomly, we get RML command, someome remove us to their contact list for MSNP15 + // NS: <<< RML 0 {size} + @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) + { + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($this->aContactList[$u_domain][$u_name][$network])) + { + $aData = $this->aContactList[$u_domain][$u_name][$network]; + foreach ($aData as $list => $id) + $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); + unset($this->aContactList[$u_domain][$u_name][$network]); + $this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); + } + else + $this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); + $this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network); + } + else + $this->log_message("*** someone remove us from their list: $data"); + } + break; + + case 'MSG': + // randomly, we get MSG notification from server + // NS: <<< MSG Hotmail Hotmail {size} + @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $maildata = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'Content-Type:', 13) == 0) { + if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && + strpos($line, 'text/x-msmsgsoimnotification') === false) { + // we just need text/x-msmsgsinitialmdatanotification + // or text/x-msmsgsoimnotification + $ignore = true; + break; + } + } + continue; + } + if (strncasecmp($line, 'Mail-Data:', 10) == 0) { + $maildata = trim(substr($line, 10)); + break; + } + } + if ($ignore) { + $this->log_message("*** ingnore MSG for: $line"); + break; + } + if ($maildata == '') { + $this->log_message("*** ingnore MSG not for OIM"); + break; + } + $re_login = false; + if (strcasecmp($maildata, 'too-large') == 0) { + $this->log_message("*** large mail-data, need to get the data via SOAP"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->log_message("*** can't get mail-data via SOAP"); + // maybe we need to re-login again + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + break; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); + break; + } + } + } + // could be a lots of ..., so we can't use preg_match here + $p = $maildata; + $aOIMs = array(); + while (1) { + $start = strpos($p, ''); + $end = strpos($p, ''); + if ($start === false || $end === false || $start > $end) break; + $end += 4; + $sOIM = substr($p, $start, $end - $start); + $aOIMs[] = $sOIM; + $p = substr($p, $end); + } + if (count($aOIMs) == 0) { + $this->log_message("*** ingnore empty OIM"); + break; + } + foreach ($aOIMs as $maildata) { + // T: 11 for MSN, 13 for Yahoo + // S: 6 for MSN, 7 for Yahoo + // RT: the datetime received by server + // RS: already read or not + // SZ: size of message + // E: sender + // I: msgid + // F: always 00000000-0000-0000-0000-000000000009 + // N: sender alias + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without type"); + continue; + } + $oim_type = $matches[1]; + if ($oim_type = 13) + $network = 32; + else + $network = 1; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without sender"); + continue; + } + $oim_sender = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without msgid"); + continue; + } + $oim_msgid = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_size = (count($matches) == 0) ? 0 : $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_time = (count($matches) == 0) ? 0 : $matches[1]; + $this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); + $sMsg = $this->getOIM_message($oim_msgid); + if ($sMsg === false) { + $this->log_message("*** can't get OIM, msgid = $oim_msgid"); + if ($re_login) { + $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + $sMsg = $this->getOIM_message($oim_msgid); + if ($sMsg === false) { + $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + continue; + } + } + $this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); + + //$this->ReceivedMessage($oim_sender,$sMsg,$network,true); + $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true)); + } + } + break; + + case 'UBM': + // randomly, we get UBM, this is the message from other network, like Yahoo! + // NS: <<< UBM {email} $network $type {size} + @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $sMsg = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'TypingUser:', 11) == 0) { + $ignore = true; + break; + } + continue; + } + $aSubLines = @explode("\r", $line); + foreach ($aSubLines as $str) { + if ($sMsg !== '') + $sMsg .= "\n"; + $sMsg .= $str; + } + } + if($ignore) + { + $this->log_message("*** ingnore from $from_email: $line"); + break; + } + $this->log_message("*** MSG from $from_email (network: $network): $sMsg"); + //$this->ReceivedMessage($from_email,$sMsg,$network,false); + $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); + } + break; + + case 'UBX': + // randomly, we get UBX notification from server + // NS: <<< UBX email {network} {size} + @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); + // we don't need the notification data, so just ignore it + if (is_numeric($size) && $size > 0) + $this->ns_readdata($size); + break; + + case 'CHL': + // randomly, we'll get challenge from server + // NS: <<< CHL 0 {code} + @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data); + $fingerprint = $this->getChallenge($chl_code); + // NS: >>> QRY {id} {product_id} 32 + // NS: >>> fingerprint + $this->ns_writeln("QRY $this->id $this->prod_id 32"); + $this->ns_writedata($fingerprint); + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + break; + case 'CHG': + // NS: <<< CHG {id} {status} {code} + // ignore it + // change our status to online first + break; + + case 'XFR': + // sometimes, NS will redirect to another NS + // MSNP9 + // NS: <<< XFR {id} NS {server} 0 {server} + // MSNP15 + // NS: <<< XFR {id} NS {server} U D + // for normal switchboard XFR + // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0 + @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data); + @list($ip, $port) = @explode(':', $server); + if ($server_type != 'SB') { + // maybe exit? + // this connection will close after XFR + $this->NSLogout(); + continue; + } + if(count($this->MessageQueue)) + { + foreach($this->MessageQueue as $User => $Message) + { + //$this->ChildProcess[$ChildPid] + $this->log_message("*** XFR SB $User"); + $pid=pcntl_fork(); + if($pid) + { + //Parrent Process + $this->ChildProcess[$pid]=$User; + break; + } + elseif($pid==-1) + { + $this->log_message("*** Fork Error $User"); + break; + } + else + { + //Child Process + $this->log_message("*** Child Process Start for $User"); + unset($Message['XFRSent']); + unset($Message['ReqTime']); + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); + if ($bSBresult === false) + { + // error for switchboard + $this->log_message("!!! error for sending message to ".$User); + } + die; + } + } + unset($this->MessageQueue[$User]); + } + /* + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); + if ($bSBresult === false) { + // error for switchboard + $this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); + $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; + }*/ + break; + case 'QNG': + // NS: <<< QNG {time} + @list(/* QNG */, $this->ping_wait) = @explode(' ', $data); + if ($this->ping_wait == 0) $this->ping_wait = 50; + //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; + //Mod by Ricky Set Online + break; + + case 'RNG': + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + else + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + // someone is trying to talk to us + // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 + $this->log_message("NS: <<< RNG $data"); + @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); + @list($sb_ip, $sb_port) = @explode(':', $server); + $this->log_message("*** RING from $email, $sb_ip:$sb_port"); + $this->addContact($email,1,$email, true); + $pid=pcntl_fork(); + if($pid) + { + //Parrent Process + $this->ChildProcess[$pid]='RNG'; + break; + } + elseif($pid==-1) + { + $this->log_message("*** Fork Error $User"); + break; + } + else + { + //Child Process + $this->log_message("*** Ring Child Process Start for $User"); + $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email); + die; + } + break; + case 'OUT': + // force logout from NS + // NS: <<< OUT xxx + $this->log_message("*** LOGOUT from NS"); + return $this->NsLogout(); + + default: + $code = substr($data,0,3); + if (is_numeric($code)) { + $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; + $this->debug_message("*** NS: $this->error"); + + return $this->NsLogout(); + } + break; + } + } + + public function SendMessage($Message, $To) { + if(!is_array($To)) + $To=array($To); + $Receiver=''; + foreach($To as $Email) + { + list($name,$host,$network)=explode('@',$Email); + $network=$network==''?1:$network; + if($network==1 && isset($this->switchBoardSessions[$Email]) ) { + $this->debug_message("*** SendMessage to $Receiver use SB message queue."); + array_push($this->SwitchBoardMessageQueue,$Message); + continue; + } + $Receiver.="$name@$host@$network,"; + } + if($Receiver=='') return; + $Receiver=substr($Receiver,0,-1); + $this->debug_message("*** SendMessage to $Receiver use File queue."); + file_put_contents($FileName,"TO: $Receiver\n$Message\n"); + } + + public function getNSSocket() { + return $this->NSfp; + } + + public function getSBSocket() { + return $this->SBfp; + } + + public function getSockets() { + return array($this->NSfp, $this->SBfp); + } + + /** + * Calls User Handler + * + * Calls registered handler for a specific event. + * + * @param String $event Command (event) name (Rvous etc) + * @param String $data Raw message from server + * @see registerHandler + * @return void + */ + private function callHandler($event, $data) { + if (isset($this->myEventHandlers[$event])) { + call_user_func($this->myEventHandlers[$event], $data); + } else { + $this->noHandler($data); + } + } + + /** + * Registers a user handler + * + * Handler List + * IMIn + * + * @param String $event Event name + * @param String $handler User function to call + * @see callHandler + * @return boolean Returns true if successful + */ + public function registerHandler($event, $handler) { + if (is_callable($handler)) { + $this->myEventHandlers[$event] = $handler; + return true; + } else { + return false; + } + } +} diff --git a/plugins/Msn/extlib/phpmsnclass/msnbot.php b/plugins/Msn/extlib/phpmsnclass/msnbot.php new file mode 100755 index 0000000000..7a9f66ca2e --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/msnbot.php @@ -0,0 +1,63 @@ +#!/usr/bin/php +End(); + return; + } +} + +// network: +// 1: WLM/MSN +// 2: LCS +// 4: Mobile Phones +// 32: Yahoo! +function getNetworkName($network) +{ + switch ($network) + { + case 1: + return 'WLM/MSN'; + case 2: + return 'LCS'; + case 4: + return 'Mobile Phones'; + case 32: + return 'Yahoo!'; + } + return "Unknown ($network)"; +} + + +require_once('config.php'); +include_once('msn.class.php'); + +$msn = new MSN(array( + 'user' => 'xxx@hotmail.com', + 'password' => 'mypassword', + 'alias' => 'myalias', + 'psm' => 'psm', +// 'PhotoSticker' => 'msntitle.jpg', + 'debug'=> true, +/* 'Emotions' => array( + 'aaa' => 'emotion.gif' + ),*/ +)); + +$fp=fopen(MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msnbot.pid', 'wt'); +if($fp) +{ + fputs($fp,posix_getpid()); + fclose($fp); +} +declare(ticks = 1); +$msn->Run(); +$msn->log_message("done!"); +@unlink(dirname($_SERVER['argv'][0]).DIRECTORY_SEPARATOR.'log'.DIRECTORY_SEPARATOR.'msnbot.pid'); diff --git a/plugins/Msn/extlib/phpmsnclass/sample.php b/plugins/Msn/extlib/phpmsnclass/sample.php new file mode 100644 index 0000000000..32539c56c2 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/sample.php @@ -0,0 +1,40 @@ +#!/usr/bin/php -Cq + 'statusnetbot@inflatablegoldfish.com', 'password' => 'statusnetplugin', 'alias' => 'statusnetbot', 'psm' => '', 'debug' => true)); + +if ($msn->Run()) { + echo "Error for connect to MSN network\n"; + echo "$msn->error\n"; + exit; +} + +//$msn->sendMessage('Now: '.strftime('%D %T')."\nTesting\nSecond Line\n\n\n\nand Empty Line", + // array( + // 'darkip@inflatablegoldfish.com' + // ) + // ); +echo "Done!\n"; +exit; + +?> + diff --git a/plugins/Msn/extlib/phpmsnclass/soap/.svn/all-wcprops b/plugins/Msn/extlib/phpmsnclass/soap/.svn/all-wcprops new file mode 100644 index 0000000000..0e73537c01 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/.svn/all-wcprops @@ -0,0 +1,23 @@ +K 25 +svn:wc:ra_dav:version-url +V 41 +/svn/!svn/ver/39/trunk/phpmsnclassv2/soap +END +msnab_servicetypes.xsd +K 25 +svn:wc:ra_dav:version-url +V 64 +/svn/!svn/ver/39/trunk/phpmsnclassv2/soap/msnab_servicetypes.xsd +END +msnab_sharingservice.wsdl +K 25 +svn:wc:ra_dav:version-url +V 67 +/svn/!svn/ver/39/trunk/phpmsnclassv2/soap/msnab_sharingservice.wsdl +END +msnab_datatypes.xsd +K 25 +svn:wc:ra_dav:version-url +V 61 +/svn/!svn/ver/39/trunk/phpmsnclassv2/soap/msnab_datatypes.xsd +END diff --git a/plugins/Msn/extlib/phpmsnclass/soap/.svn/entries b/plugins/Msn/extlib/phpmsnclass/soap/.svn/entries new file mode 100644 index 0000000000..062f5cb1de --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/.svn/entries @@ -0,0 +1,130 @@ +10 + +dir +46 +http://phpmsnclass.googlecode.com/svn/trunk/phpmsnclassv2/soap +http://phpmsnclass.googlecode.com/svn + + + +2009-07-27T06:16:08.380493Z +39 +ricky@ez2.us + + + + + + + + + + + + + + +d71849f3-712d-0410-a681-1795f7bea18a + +msnab_servicetypes.xsd +file + + + + +2010-06-08T18:29:30.506015Z +096c0222d82879fa2b4bd47fa45f4aaf +2009-07-27T06:16:08.380493Z +39 +ricky@ez2.us + + + + + + + + + + + + + + + + + + + + + +27903 + +msnab_sharingservice.wsdl +file + + + + +2010-06-08T18:29:30.506015Z +40f2d65d6cf6245c064defb02bd62705 +2009-07-27T06:16:08.380493Z +39 +ricky@ez2.us + + + + + + + + + + + + + + + + + + + + + +27625 + +msnab_datatypes.xsd +file + + + + +2010-06-08T18:29:30.506015Z +6a376c90de444594c1c75970586f99f8 +2009-07-27T06:16:08.380493Z +39 +ricky@ez2.us + + + + + + + + + + + + + + + + + + + + + +42170 + diff --git a/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_datatypes.xsd.svn-base b/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_datatypes.xsd.svn-base new file mode 100644 index 0000000000..46fc23f911 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_datatypes.xsd.svn-base @@ -0,0 +1,832 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A space (ASCII #32) separated list of properties that + have changed as part of an update request. The property + names don't always match the name of the associated + element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether the contact has a Windows Live + Space or not. + + + + + + + + + + + + + Seen is YYYY/MM/DD format. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A space (ASCII #32) separated list of properties that + have changed as part of an update request. The property + names don't always match the name of the associated + element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_servicetypes.xsd.svn-base b/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_servicetypes.xsd.svn-base new file mode 100644 index 0000000000..3fa9798b62 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_servicetypes.xsd.svn-base @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_sharingservice.wsdl.svn-base b/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_sharingservice.wsdl.svn-base new file mode 100644 index 0000000000..7ec87f90c9 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/.svn/text-base/msnab_sharingservice.wsdl.svn-base @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd b/plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd new file mode 100644 index 0000000000..46fc23f911 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/msnab_datatypes.xsd @@ -0,0 +1,832 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A space (ASCII #32) separated list of properties that + have changed as part of an update request. The property + names don't always match the name of the associated + element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Indicates whether the contact has a Windows Live + Space or not. + + + + + + + + + + + + + Seen is YYYY/MM/DD format. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A space (ASCII #32) separated list of properties that + have changed as part of an update request. The property + names don't always match the name of the associated + element. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd b/plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd new file mode 100644 index 0000000000..3fa9798b62 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/msnab_servicetypes.xsd @@ -0,0 +1,567 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl b/plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl new file mode 100644 index 0000000000..7ec87f90c9 --- /dev/null +++ b/plugins/Msn/extlib/phpmsnclass/soap/msnab_sharingservice.wsdl @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php new file mode 100644 index 0000000000..72de11cb10 --- /dev/null +++ b/plugins/Msn/msnmanager.php @@ -0,0 +1,105 @@ +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * AIM background connection manager for AIM-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 handled. + * + * 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 MsnManager extends ImManager +{ + + public $conn = null; + /** + * Initialize connection to server. + * @return boolean true on success + */ + public function start($master) + { + if(parent::start($master)) + { + $this->connect(); + return true; + }else{ + return false; + } + } + + public function getSockets() + { + $this->connect(); + if($this->conn){ + return $this->conn->getSockets(); + }else{ + return array(); + } + } + + /** + * Process AIM events that have come in over the wire. + * @param resource $socket + */ + public function handleInput($socket) + { + common_log(LOG_DEBUG, "Servicing the MSN queue."); + $this->stats('msn_process'); + $this->conn->receive(); + } + + function connect() + { + if (!$this->conn) { + $this->conn=new MSN(array( + 'user' => $this->plugin->user, + 'password' => $this->plugin->password, + 'alias' => $this->plugin->nickname, + 'psm' => 'Send me a message to post a notice', + 'debug' => true + ) + ); + $this->conn->registerHandler("IMIn", array($this, 'handle_msn_message')); + $this->conn->signon(); + } + return $this->conn; + } + + function handle_msn_message($data) + { + $this->plugin->enqueue_incoming_raw($data); + return true; + } + + function send_raw_message($data) + { + $this->connect(); + if (!$this->conn) { + return false; + } + $this->conn->sflapSend($data[0],$data[1],$data[2],$data[3]); + return true; + } +} From d97b5982144571b70cee4c833dfa8262ba13a2f1 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sat, 12 Jun 2010 17:34:25 +0100 Subject: [PATCH 042/655] Removed phpmsnclass sample --- plugins/Msn/extlib/phpmsnclass/sample.php | 40 ----------------------- 1 file changed, 40 deletions(-) delete mode 100644 plugins/Msn/extlib/phpmsnclass/sample.php diff --git a/plugins/Msn/extlib/phpmsnclass/sample.php b/plugins/Msn/extlib/phpmsnclass/sample.php deleted file mode 100644 index 32539c56c2..0000000000 --- a/plugins/Msn/extlib/phpmsnclass/sample.php +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/php -Cq - 'statusnetbot@inflatablegoldfish.com', 'password' => 'statusnetplugin', 'alias' => 'statusnetbot', 'psm' => '', 'debug' => true)); - -if ($msn->Run()) { - echo "Error for connect to MSN network\n"; - echo "$msn->error\n"; - exit; -} - -//$msn->sendMessage('Now: '.strftime('%D %T')."\nTesting\nSecond Line\n\n\n\nand Empty Line", - // array( - // 'darkip@inflatablegoldfish.com' - // ) - // ); -echo "Done!\n"; -exit; - -?> - From 89808a86d53cbb6581b2c549e6015626ec1f0242 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sat, 12 Jun 2010 19:49:28 +0100 Subject: [PATCH 043/655] More work on adapting the phpmsnclass to work with the IM architecture (far from finished still) --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 114 ++++++++++++++----- plugins/Msn/msnmanager.php | 38 ++++++- 2 files changed, 122 insertions(+), 30 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 355d828eb5..c387bbeae9 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -3203,13 +3203,6 @@ X-OIM-Sequence-Num: 1 $data = $this->ns_readln(); /*if($data===false) { - //If No NS Message Process SendMessageFileQueue - if (time()-$this->LastPing > $this->ping_wait) - { - // NS: >>> PNG - $this->ns_writeln("PNG"); - $this->LastPing = time(); - } if(count($this->ChildProcess)<$this->MAXChildProcess) { $Index=0; @@ -3626,8 +3619,8 @@ X-OIM-Sequence-Num: 1 break; case 'QNG': // NS: <<< QNG {time} - @list(/* QNG */, $this->ping_wait) = @explode(' ', $data); - if ($this->ping_wait == 0) $this->ping_wait = 50; + //@list(/* QNG */, $this->ping_wait) = @explode(' ', $data); + //if ($this->ping_wait == 0) $this->ping_wait = 50; //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; //Mod by Ricky Set Online break; @@ -3682,31 +3675,100 @@ X-OIM-Sequence-Num: 1 } } - public function SendMessage($Message, $To) { - if(!is_array($To)) - $To=array($To); - $Receiver=''; - foreach($To as $Email) + public function sendMessageViaSB($message, $to) { + $socket = $this->switchBoardSessions[$to]['socket']; + $lastActive = $this->switchBoardSessions[$to]['lastActive']; + $joined = $this->switchBoardSessions[$to]['joined']; + + //TODO Probably not needed (we're not running in a loop anymore) + /*if($this->kill_me) { - list($name,$host,$network)=explode('@',$Email); - $network=$network==''?1:$network; - if($network==1 && isset($this->switchBoardSessions[$Email]) ) { - $this->debug_message("*** SendMessage to $Receiver use SB message queue."); - array_push($this->SwitchBoardMessageQueue,$Message); - continue; - } - $Receiver.="$name@$host@$network,"; + $this->log_message("*** SB Okay, kill me now!"); + endSBSession($socket); + }*/ + + if(!$Joined) { + // If our participant has not joined the session yet we can't message them! + //TODO Check the behaviour of the queue runner when we return false + return false; } - if($Receiver=='') return; - $Receiver=substr($Receiver,0,-1); - $this->debug_message("*** SendMessage to $Receiver use File queue."); - file_put_contents($FileName,"TO: $Receiver\n$Message\n"); + + $aMessage = $this->getMessage($Message); + //CheckEmotion... + $MsnObjDefine=$this->GetMsnObjDefine($aMessage); + if($MsnObjDefine !== '') + { + $SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine"; + $len = strlen($SendString); + $this->SB_writeln("MSG $id N $len"); + $id++; + $this->SB_writedata($SendString); + $this->id++; + } + $len = strlen($aMessage); + $this->SB_writeln("MSG $id N $len"); + + // Increment the trID + $this->switchBoardSessions[$to]['id']++; + + $this->SB_writedata($aMessage); + + if (feof($this->SBFp)) + { + // lost connection? error? try OIM later + @fclose($this->SBFp); + //TODO introduce callback to add offline message to queue? + return false; + } + $this->SB_writeln("OUT"); + @fclose($this->SBFp); + return true; + } + + //TODO Not sure if this is needed? + private function endSBSession($socket) { + if (feof($this->SBFp)) + { + // lost connection? error? try OIM later + @fclose($this->SBFp); + return false; + } + $this->SB_writeln("OUT"); + @fclose($this->SBFp); + return true; + } + + public function sendMessage($message, $to) { + if($message != '') { + list($name,$host,$network)=explode('@',$to); + $network=$network==''?1:$network; + + if($network === 1 && isset($this->switchBoardSessions[$to])) { + $recipient = $name . $host; + $this->debug_message("*** Sending Message to $recipient using existing SB session"); + $this->sendMessageViaSB($message, $recipient); + } else { + $this->debug_message("*** Not MSN network or no existing SB session"); + + } + } + } + + /** + * Sends a ping command + * + * Should be called about every 50 seconds + */ + public function send_ping() { + // NS: >>> PNG + $this->ns_writeln("PNG"); } public function getNSSocket() { return $this->NSfp; } + // TODO Allow for multiple SB session sockets public function getSBSocket() { return $this->SBfp; } diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index 72de11cb10..aae6906d6c 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -32,8 +32,12 @@ if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } class MsnManager extends ImManager { - public $conn = null; + + protected $lastping = null; + + const PING_INTERVAL = 50; + /** * Initialize connection to server. * @return boolean true on success @@ -58,9 +62,21 @@ class MsnManager extends ImManager return array(); } } - + /** - * Process AIM events that have come in over the wire. + * Idle processing for io manager's execution loop. + * Send keepalive pings to server. + */ + public function idle($timeout=0) + { + $now = time(); + if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { + $this->send_ping(); + } + } + + /** + * Process MSN events that have come in over the wire. * @param resource $socket */ public function handleInput($socket) @@ -83,10 +99,24 @@ class MsnManager extends ImManager ); $this->conn->registerHandler("IMIn", array($this, 'handle_msn_message')); $this->conn->signon(); + $this->lastping = time(); } return $this->conn; } - + + function send_ping() { + $this->connect(); + if (!$this->conn) { + return false; + } + + $now = time(); + + $this->conn->send_ping(); + $this->lastping = $now; + return true; + } + function handle_msn_message($data) { $this->plugin->enqueue_incoming_raw($data); From 52cfc0866c4dd54f8628d47c56abd105e1f79d18 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sat, 12 Jun 2010 21:19:08 +0100 Subject: [PATCH 044/655] Merged in changes to phpmsnclass --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 986 +++++++++---------- 1 file changed, 443 insertions(+), 543 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index c387bbeae9..030cc5dc04 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -60,6 +60,7 @@ class MSN { private $ABAuthHeader; private $ABService; private $Contacts; + private $IgnoreList; public $server = 'messenger.hotmail.com'; public $port = 1863; @@ -107,18 +108,6 @@ class MSN { // for YIM: 518 bytes public $max_msn_message_len = 1664; public $max_yahoo_message_len = 518; - - // Begin added for StatusNet - - private $aContactList = array(); - private $switchBoardSessions = array(); - - /** - * Event Handler Functions - */ - private $myEventHandlers = array(); - - // End added for StatusNet private function Array2SoapVar($Array,$ReturnSoapVarObj=true,$TypeName=null,$TypeNameSpace=null) { @@ -158,6 +147,15 @@ class MSN { $this->log_message("*** someone kill me ***"); $this->kill_me=true; } + private function IsIgnoreMail($Email) + { + if($this->IgnoreList==false) return false; + foreach($this->IgnoreList as $Pattern) + { + if(preg_match($Pattern,$Email)) return true; + } + return false; + } public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C) { $this->user = $Configs['user']; @@ -171,6 +169,7 @@ class MSN { $this->backup_file = isset($Configs['backup_file']) ? $Configs['backup_file'] : true; $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true; $this->PhotoStickerFile=$Configs['PhotoSticker']; + $this->IgnoreList=isset($Configs['IgnoreList'])?$Configs['IgnoreList']:false; if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) { foreach($this->Emotions as $EmotionFilePath) @@ -532,99 +531,6 @@ class MSN { } $this->UpdateContacts(); return true; - - - $ABContactAdd=new SoapParam($this->Array2SoapVar($ABContactAddArray),'ABContactAdd'); - - // add contact for WLM - $ticket = htmlspecialchars($this->ticket['contact_ticket']); - $displayName = htmlspecialchars($display); - $user = $email; - - $XML = ' - - - - CFE80F9D-180F-4399-82AB-413F33A1FA11 - false - ContactSave - - - false - '.$ticket.' - - - - - 00000000-0000-0000-0000-000000000000 - - - - LivePending - '.$user.' - true - - '.$displayName.' - - - - - - true - - - -'; - - $header_array = array( - 'SOAPAction: '.$this->addcontact_soap, - 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' - ); - - $this->debug_message("*** URL: $this->addcontact_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->addcontact_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); - - if ($http_code != 200) { - preg_match('#(.*)(.*)#', $data, $matches); - if (count($matches) == 0) { - $this->log_message("*** can't add contact (network: $network) $email"); - return false; - } - $faultcode = trim($matches[1]); - $faultstring = trim($matches[2]); - $this->log_message("*** can't add contact (network: $network) $email, error code: $faultcode, $faultstring"); - return false; - } - $this->log_message("*** add contact (network: $network) $email"); - if ($sendADL && !feof($this->NSfp)) { - @list($u_name, $u_domain) = @explode('@', $email); - foreach (array('1', '2') as $l) { - $str = ''; - $len = strlen($str); - // NS: >>> ADL {id} {size} - $this->ns_writeln("ADL $this->id $len"); - $this->ns_writedata($str); - } - } - $this->UpdateContacts(); - return true; } function delMemberFromList($memberID, $email, $network, $list) { @@ -934,7 +840,7 @@ class MSN { $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); $this->debug_message("*** Get Result:\n$data"); - if(($http_code != 200)||(!$returnData)) return array(); + if($http_code != 200) return array(); $p = $data; $aMemberships = array(); while (1) { @@ -1562,7 +1468,7 @@ class MSN { $start_tm = time(); $ping_tm = time(); stream_set_timeout($this->NSfp, $this->NSStreamTimeout); - $aContactList = $this->getMembershipList(true); + $aContactList = $this->getMembershipList(); if ($this->update_pending) { if (is_array($aContactList)) { $pending = 'Pending'; @@ -2102,6 +2008,11 @@ class MSN { $this->log_message("NS: <<< RNG $data"); @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); @list($sb_ip, $sb_port) = @explode(':', $server); + if($this->IsIgnoreMail($email)) + { + $this->log_message("*** Ignore RNG from $email"); + break; + } $this->log_message("*** RING from $email, $sb_ip:$sb_port"); $this->addContact($email,1,$email, true); $pid=pcntl_fork(); @@ -2132,7 +2043,7 @@ class MSN { break; default: - $code = substr($data,0,3); + $code = substr($data,0,3); if (is_numeric($code)) { $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; $this->debug_message("*** NS: $this->error"); @@ -2329,7 +2240,7 @@ class MSN { $this->SB_writedata($aMessage); } $this->SwitchBoardMessageQueue=array(); - $LastActive=time(); + if(!$this->IsIgnoreMail($user)) $LastActive = time(); continue; } $code = substr($data, 0, 3); @@ -2725,7 +2636,7 @@ class MSN { } break; } - $LastActive = time(); + if(!$this->IsIgnoreMail($user)) $LastActive = time(); } if (feof($this->SBFp)) { @@ -3089,7 +3000,7 @@ X-OIM-Sequence-Num: 1 $start_tm = time(); $ping_tm = time(); stream_set_timeout($this->NSfp, $this->NSStreamTimeout); - $this->aContactList = $this->getMembershipList(true); + $this->aContactList = $this->getMembershipList(); if ($this->update_pending) { if (is_array($this->aContactList)) { $pending = 'Pending'; @@ -3182,7 +3093,7 @@ X-OIM-Sequence-Num: 1 // NS: >>> CHG {id} {status} {clientid} {msnobj} $this->ns_writeln("CHG $this->id NLN $this->clientid"); if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); // NS: >>> UUX {id} length $str = ''.htmlspecialchars($this->psm).''; $len = strlen($str); @@ -3201,477 +3112,465 @@ X-OIM-Sequence-Num: 1 } $data = $this->ns_readln(); - /*if($data===false) - { - if(count($this->ChildProcess)<$this->MAXChildProcess) + if($data === false) { + // There was no data / an error when reading from the socket so reconnect + $this->signon(); + } else { + switch (substr($data,0,3)) { - $Index=0; - foreach($this->MessageQueue as $User => $Message) - { - if(!trim($User)) continue; - if($Inxdex>=$this->MAXChildProcess-count($this->ChildProcess)) break; - if((!$Message['XFRSent'])||($Message['XFRSent']&&(time()-$this->MessageQueue[$User]['ReqTime']>$this->ReqSBXFRTimeout))) - { - $this->MessageQueue[$User]['XFRSent']=true; - $this->MessageQueue[$User]['ReqTime']=time(); - $this->log_message("*** Request SB for $User"); - $this->ns_writeln("XFR $this->id SB"); - $Index++; + case 'SBS': + // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us + // NS: <<< SBS 0 null + break; + + case 'RFS': + // FIXME: + // NS: <<< RFS ??? + // refresh ADL, so we re-send it again + if (is_array($aADL)) { + foreach ($aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } } - } - } - if($this->ProcessSendMessageFileQueue()) continue; - break; - }*/ - switch (substr($data,0,3)) - { - case 'SBS': - // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us - // NS: <<< SBS 0 null - break; - - case 'RFS': - // FIXME: - // NS: <<< RFS ??? - // refresh ADL, so we re-send it again - if (is_array($aADL)) { - foreach ($aADL as $str) { - $len = strlen($str); - // NS: >>> ADL {id} {size} - $this->ns_writeln("ADL $this->id $len"); - $this->ns_writedata($str); + break; + + case 'LST': + // NS: <<< LST {email} {alias} 11 0 + @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data); + @list($u_name, $u_domain) = @explode('@', $email); + if (!isset($this->aContactList[$u_domain][$u_name][1])) { + $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; + $this->log_message("*** add to our contact list: $u_name@$u_domain"); } - } - break; - - case 'LST': - // NS: <<< LST {email} {alias} 11 0 - @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data); - @list($u_name, $u_domain) = @explode('@', $email); - if (!isset($this->aContactList[$u_domain][$u_name][1])) { - $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; - $this->log_message("*** add to our contact list: $u_name@$u_domain"); - } - break; - - case 'ADL': - // randomly, we get ADL command, someome add us to their contact list for MSNP15 - // NS: <<< ADL 0 {size} - @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { - $data = $this->ns_readdata($size); - preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) + break; + + case 'ADL': + // randomly, we get ADL command, someome add us to their contact list for MSNP15 + // NS: <<< ADL 0 {size} + @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { - $u_domain = $matches[1]; - $u_name = $matches[2]; - $network = $matches[4]; - if (isset($this->aContactList[$u_domain][$u_name][$network])) - $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); - else + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) { - $re_login = false; - $cnt = 0; - foreach (array('Allow', 'Reverse') as $list) + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($this->aContactList[$u_domain][$u_name][$network])) + $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); + else { - if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + $re_login = false; + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) { - if ($re_login) { - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); - continue; - } - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here"); - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); - continue; - } - $re_login = true; - $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); - continue; + if ($re_login) { + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here"); + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + { + $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + continue; + } } + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; } - $this->aContactList[$u_domain][$u_name][$network][$list] = false; - $cnt++; + $this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); } - $this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); - } - $str = ''; - $len = strlen($str); - } - else - $this->log_message("*** someone add us to their list: $data"); - $this->AddUsToMemberList($u_name.'@'.$u_domain, $network); - } - break; - - case 'RML': - // randomly, we get RML command, someome remove us to their contact list for MSNP15 - // NS: <<< RML 0 {size} - @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { - $data = $this->ns_readdata($size); - preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) - { - $u_domain = $matches[1]; - $u_name = $matches[2]; - $network = $matches[4]; - if (isset($this->aContactList[$u_domain][$u_name][$network])) - { - $aData = $this->aContactList[$u_domain][$u_name][$network]; - foreach ($aData as $list => $id) - $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); - unset($this->aContactList[$u_domain][$u_name][$network]); - $this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); + $str = ''; + $len = strlen($str); } else - $this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); - $this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network); + $this->log_message("*** someone add us to their list: $data"); + $this->AddUsToMemberList($u_name.'@'.$u_domain, $network); } - else - $this->log_message("*** someone remove us from their list: $data"); - } - break; - - case 'MSG': - // randomly, we get MSG notification from server - // NS: <<< MSG Hotmail Hotmail {size} - @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) { - $data = $this->ns_readdata($size); - $aLines = @explode("\n", $data); - $header = true; - $ignore = false; - $maildata = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; + break; + + case 'RML': + // randomly, we get RML command, someome remove us to their contact list for MSNP15 + // NS: <<< RML 0 {size} + @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) + { + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($this->aContactList[$u_domain][$u_name][$network])) + { + $aData = $this->aContactList[$u_domain][$u_name][$network]; + foreach ($aData as $list => $id) + $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); + unset($this->aContactList[$u_domain][$u_name][$network]); + $this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); + } + else + $this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); + $this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network); + } + else + $this->log_message("*** someone remove us from their list: $data"); + } + break; + + case 'MSG': + // randomly, we get MSG notification from server + // NS: <<< MSG Hotmail Hotmail {size} + @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $maildata = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'Content-Type:', 13) == 0) { + if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && + strpos($line, 'text/x-msmsgsoimnotification') === false) { + // we just need text/x-msmsgsinitialmdatanotification + // or text/x-msmsgsoimnotification + $ignore = true; + break; + } + } continue; } - if (strncasecmp($line, 'Content-Type:', 13) == 0) { - if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && - strpos($line, 'text/x-msmsgsoimnotification') === false) { - // we just need text/x-msmsgsinitialmdatanotification - // or text/x-msmsgsoimnotification - $ignore = true; + if (strncasecmp($line, 'Mail-Data:', 10) == 0) { + $maildata = trim(substr($line, 10)); + break; + } + } + if ($ignore) { + $this->log_message("*** ingnore MSG for: $line"); + break; + } + if ($maildata == '') { + $this->log_message("*** ingnore MSG not for OIM"); + break; + } + $re_login = false; + if (strcasecmp($maildata, 'too-large') == 0) { + $this->log_message("*** large mail-data, need to get the data via SOAP"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->log_message("*** can't get mail-data via SOAP"); + // maybe we need to re-login again + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + break; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); break; } } - continue; } - if (strncasecmp($line, 'Mail-Data:', 10) == 0) { - $maildata = trim(substr($line, 10)); + // could be a lots of ..., so we can't use preg_match here + $p = $maildata; + $aOIMs = array(); + while (1) { + $start = strpos($p, ''); + $end = strpos($p, ''); + if ($start === false || $end === false || $start > $end) break; + $end += 4; + $sOIM = substr($p, $start, $end - $start); + $aOIMs[] = $sOIM; + $p = substr($p, $end); + } + if (count($aOIMs) == 0) { + $this->log_message("*** ingnore empty OIM"); break; } - } - if ($ignore) { - $this->log_message("*** ingnore MSG for: $line"); - break; - } - if ($maildata == '') { - $this->log_message("*** ingnore MSG not for OIM"); - break; - } - $re_login = false; - if (strcasecmp($maildata, 'too-large') == 0) { - $this->log_message("*** large mail-data, need to get the data via SOAP"); - $maildata = $this->getOIM_maildata(); - if ($maildata === false) { - $this->log_message("*** can't get mail-data via SOAP"); - // maybe we need to re-login again - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); - break; - } - $re_login = true; - $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); - $maildata = $this->getOIM_maildata(); - if ($maildata === false) { - $this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); - break; - } - } - } - // could be a lots of ..., so we can't use preg_match here - $p = $maildata; - $aOIMs = array(); - while (1) { - $start = strpos($p, ''); - $end = strpos($p, ''); - if ($start === false || $end === false || $start > $end) break; - $end += 4; - $sOIM = substr($p, $start, $end - $start); - $aOIMs[] = $sOIM; - $p = substr($p, $end); - } - if (count($aOIMs) == 0) { - $this->log_message("*** ingnore empty OIM"); - break; - } - foreach ($aOIMs as $maildata) { - // T: 11 for MSN, 13 for Yahoo - // S: 6 for MSN, 7 for Yahoo - // RT: the datetime received by server - // RS: already read or not - // SZ: size of message - // E: sender - // I: msgid - // F: always 00000000-0000-0000-0000-000000000009 - // N: sender alias - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without type"); - continue; - } - $oim_type = $matches[1]; - if ($oim_type = 13) - $network = 32; - else - $network = 1; - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without sender"); - continue; - } - $oim_sender = $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without msgid"); - continue; - } - $oim_msgid = $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - $oim_size = (count($matches) == 0) ? 0 : $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - $oim_time = (count($matches) == 0) ? 0 : $matches[1]; - $this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); - $sMsg = $this->getOIM_message($oim_msgid); - if ($sMsg === false) { - $this->log_message("*** can't get OIM, msgid = $oim_msgid"); - if ($re_login) { - $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + foreach ($aOIMs as $maildata) { + // T: 11 for MSN, 13 for Yahoo + // S: 6 for MSN, 7 for Yahoo + // RT: the datetime received by server + // RS: already read or not + // SZ: size of message + // E: sender + // I: msgid + // F: always 00000000-0000-0000-0000-000000000009 + // N: sender alias + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without type"); continue; } - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + $oim_type = $matches[1]; + if ($oim_type = 13) + $network = 32; + else + $network = 1; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without sender"); continue; } - $re_login = true; - $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); + $oim_sender = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->log_message("*** ingnore OIM maildata without msgid"); + continue; + } + $oim_msgid = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_size = (count($matches) == 0) ? 0 : $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_time = (count($matches) == 0) ? 0 : $matches[1]; + $this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); $sMsg = $this->getOIM_message($oim_msgid); if ($sMsg === false) { - $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + $this->log_message("*** can't get OIM, msgid = $oim_msgid"); + if ($re_login) { + $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->log_message("**** get new ticket, try it again"); + $sMsg = $this->getOIM_message($oim_msgid); + if ($sMsg === false) { + $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + continue; + } + } + $this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); + + //$this->ReceivedMessage($oim_sender,$sMsg,$network,true); + $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true)); + } + } + break; + + case 'UBM': + // randomly, we get UBM, this is the message from other network, like Yahoo! + // NS: <<< UBM {email} $network $type {size} + @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) + { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $sMsg = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'TypingUser:', 11) == 0) { + $ignore = true; + break; + } continue; } + $aSubLines = @explode("\r", $line); + foreach ($aSubLines as $str) { + if ($sMsg !== '') + $sMsg .= "\n"; + $sMsg .= $str; + } } - $this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); - - //$this->ReceivedMessage($oim_sender,$sMsg,$network,true); - $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true)); + if($ignore) + { + $this->log_message("*** ingnore from $from_email: $line"); + break; + } + $this->log_message("*** MSG from $from_email (network: $network): $sMsg"); + //$this->ReceivedMessage($from_email,$sMsg,$network,false); + $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); } - } - break; - - case 'UBM': - // randomly, we get UBM, this is the message from other network, like Yahoo! - // NS: <<< UBM {email} $network $type {size} - @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { - $data = $this->ns_readdata($size); - $aLines = @explode("\n", $data); - $header = true; - $ignore = false; - $sMsg = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } - if (strncasecmp($line, 'TypingUser:', 11) == 0) { - $ignore = true; + break; + + case 'UBX': + // randomly, we get UBX notification from server + // NS: <<< UBX email {network} {size} + @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); + // we don't need the notification data, so just ignore it + if (is_numeric($size) && $size > 0) + $this->ns_readdata($size); + break; + + case 'CHL': + // randomly, we'll get challenge from server + // NS: <<< CHL 0 {code} + @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data); + $fingerprint = $this->getChallenge($chl_code); + // NS: >>> QRY {id} {product_id} 32 + // NS: >>> fingerprint + $this->ns_writeln("QRY $this->id $this->prod_id 32"); + $this->ns_writedata($fingerprint); + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + break; + case 'CHG': + // NS: <<< CHG {id} {status} {code} + // ignore it + // change our status to online first + break; + + case 'XFR': + // sometimes, NS will redirect to another NS + // MSNP9 + // NS: <<< XFR {id} NS {server} 0 {server} + // MSNP15 + // NS: <<< XFR {id} NS {server} U D + // for normal switchboard XFR + // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0 + @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data); + @list($ip, $port) = @explode(':', $server); + if ($server_type != 'SB') { + // maybe exit? + // this connection will close after XFR + $this->NSLogout(); + continue; + } + if(count($this->MessageQueue)) + { + foreach($this->MessageQueue as $User => $Message) + { + //$this->ChildProcess[$ChildPid] + $this->log_message("*** XFR SB $User"); + $pid=pcntl_fork(); + if($pid) + { + //Parrent Process + $this->ChildProcess[$pid]=$User; break; } - continue; - } - $aSubLines = @explode("\r", $line); - foreach ($aSubLines as $str) { - if ($sMsg !== '') - $sMsg .= "\n"; - $sMsg .= $str; + elseif($pid==-1) + { + $this->log_message("*** Fork Error $User"); + break; + } + else + { + //Child Process + $this->log_message("*** Child Process Start for $User"); + unset($Message['XFRSent']); + unset($Message['ReqTime']); + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); + if ($bSBresult === false) + { + // error for switchboard + $this->log_message("!!! error for sending message to ".$User); + } + die; + } } + unset($this->MessageQueue[$User]); } - if($ignore) + /* + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); + if ($bSBresult === false) { + // error for switchboard + $this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); + $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; + }*/ + break; + case 'QNG': + // NS: <<< QNG {time} + //@list(/* QNG */, $this->ping_wait) = @explode(' ', $data); + //if ($this->ping_wait == 0) $this->ping_wait = 50; + //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; + //Mod by Ricky Set Online + break; + + case 'RNG': + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + else + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + // someone is trying to talk to us + // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 + $this->log_message("NS: <<< RNG $data"); + @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); + @list($sb_ip, $sb_port) = @explode(':', $server); + if($this->IsIgnoreMail($email)) { - $this->log_message("*** ingnore from $from_email: $line"); + $this->log_message("*** Ignore RNG from $email"); break; } - $this->log_message("*** MSG from $from_email (network: $network): $sMsg"); - //$this->ReceivedMessage($from_email,$sMsg,$network,false); - $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); - } - break; - - case 'UBX': - // randomly, we get UBX notification from server - // NS: <<< UBX email {network} {size} - @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); - // we don't need the notification data, so just ignore it - if (is_numeric($size) && $size > 0) - $this->ns_readdata($size); - break; - - case 'CHL': - // randomly, we'll get challenge from server - // NS: <<< CHL 0 {code} - @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data); - $fingerprint = $this->getChallenge($chl_code); - // NS: >>> QRY {id} {product_id} 32 - // NS: >>> fingerprint - $this->ns_writeln("QRY $this->id $this->prod_id 32"); - $this->ns_writedata($fingerprint); - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - break; - case 'CHG': - // NS: <<< CHG {id} {status} {code} - // ignore it - // change our status to online first - break; - - case 'XFR': - // sometimes, NS will redirect to another NS - // MSNP9 - // NS: <<< XFR {id} NS {server} 0 {server} - // MSNP15 - // NS: <<< XFR {id} NS {server} U D - // for normal switchboard XFR - // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0 - @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data); - @list($ip, $port) = @explode(':', $server); - if ($server_type != 'SB') { - // maybe exit? - // this connection will close after XFR - $this->NSLogout(); - continue; - } - if(count($this->MessageQueue)) - { - foreach($this->MessageQueue as $User => $Message) + $this->log_message("*** RING from $email, $sb_ip:$sb_port"); + $this->addContact($email,1,$email, true); + $pid=pcntl_fork(); + if($pid) { - //$this->ChildProcess[$ChildPid] - $this->log_message("*** XFR SB $User"); - $pid=pcntl_fork(); - if($pid) - { - //Parrent Process - $this->ChildProcess[$pid]=$User; - break; - } - elseif($pid==-1) - { - $this->log_message("*** Fork Error $User"); - break; - } - else - { - //Child Process - $this->log_message("*** Child Process Start for $User"); - unset($Message['XFRSent']); - unset($Message['ReqTime']); - $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); - if ($bSBresult === false) - { - // error for switchboard - $this->log_message("!!! error for sending message to ".$User); - } - die; - } + //Parrent Process + $this->ChildProcess[$pid]='RNG'; + break; + } + elseif($pid==-1) + { + $this->log_message("*** Fork Error $User"); + break; + } + else + { + //Child Process + $this->log_message("*** Ring Child Process Start for $User"); + $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email); + die; } - unset($this->MessageQueue[$User]); - } - /* - $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); - if ($bSBresult === false) { - // error for switchboard - $this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); - $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; - }*/ - break; - case 'QNG': - // NS: <<< QNG {time} - //@list(/* QNG */, $this->ping_wait) = @explode(' ', $data); - //if ($this->ping_wait == 0) $this->ping_wait = 50; - //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; - //Mod by Ricky Set Online - break; - - case 'RNG': - if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - else - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - // someone is trying to talk to us - // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 - $this->log_message("NS: <<< RNG $data"); - @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); - @list($sb_ip, $sb_port) = @explode(':', $server); - $this->log_message("*** RING from $email, $sb_ip:$sb_port"); - $this->addContact($email,1,$email, true); - $pid=pcntl_fork(); - if($pid) - { - //Parrent Process - $this->ChildProcess[$pid]='RNG'; break; - } - elseif($pid==-1) - { - $this->log_message("*** Fork Error $User"); - break; - } - else - { - //Child Process - $this->log_message("*** Ring Child Process Start for $User"); - $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email); - die; - } - break; - case 'OUT': - // force logout from NS - // NS: <<< OUT xxx - $this->log_message("*** LOGOUT from NS"); - return $this->NsLogout(); - - default: - $code = substr($data,0,3); - if (is_numeric($code)) { - $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; - $this->debug_message("*** NS: $this->error"); - + case 'OUT': + // force logout from NS + // NS: <<< OUT xxx + $this->log_message("*** LOGOUT from NS"); return $this->NsLogout(); - } - break; + + default: + $code = substr($data,0,3); + if (is_numeric($code)) { + $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; + $this->debug_message("*** NS: $this->error"); + + return $this->NsLogout(); + } + break; + } } } @@ -3680,7 +3579,7 @@ X-OIM-Sequence-Num: 1 $lastActive = $this->switchBoardSessions[$to]['lastActive']; $joined = $this->switchBoardSessions[$to]['joined']; - //TODO Probably not needed (we're not running in a loop anymore) + //FIXME Probably not needed (we're not running in a loop anymore) /*if($this->kill_me) { $this->log_message("*** SB Okay, kill me now!"); @@ -3725,7 +3624,7 @@ X-OIM-Sequence-Num: 1 return true; } - //TODO Not sure if this is needed? + //FIXME Not sure if this is needed? private function endSBSession($socket) { if (feof($this->SBFp)) { @@ -3746,12 +3645,13 @@ X-OIM-Sequence-Num: 1 if($network === 1 && isset($this->switchBoardSessions[$to])) { $recipient = $name . $host; $this->debug_message("*** Sending Message to $recipient using existing SB session"); - $this->sendMessageViaSB($message, $recipient); + return $this->sendMessageViaSB($message, $recipient); } else { $this->debug_message("*** Not MSN network or no existing SB session"); - + //TODO implement creation of SB session etc } } + return true; } /** From 4007bce9aa7e1364d5aa90de65a9715f6e11d12f Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sat, 12 Jun 2010 21:21:09 +0100 Subject: [PATCH 045/655] Added in missing properties --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 030cc5dc04..b230e346f7 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -108,6 +108,18 @@ class MSN { // for YIM: 518 bytes public $max_msn_message_len = 1664; public $max_yahoo_message_len = 518; + + // Begin added for StatusNet + + private $aContactList = array(); + private $switchBoardSessions = array(); + + /** + * Event Handler Functions + */ + private $myEventHandlers = array(); + + // End added for StatusNet private function Array2SoapVar($Array,$ReturnSoapVarObj=true,$TypeName=null,$TypeNameSpace=null) { From dc66503f33929c2218a8f05151075329fac5005a Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sun, 13 Jun 2010 01:54:09 +0100 Subject: [PATCH 046/655] Added callback for pong (to update time till next ping required) --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 24 +++++++++++--------- plugins/Msn/msnmanager.php | 13 +++++++++-- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index b230e346f7..65525fe556 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -1573,7 +1573,7 @@ class MSN { // NS: >>> CHG {id} {status} {clientid} {msnobj} $this->ns_writeln("CHG $this->id NLN $this->clientid"); if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); // NS: >>> UUX {id} length $str = ''.htmlspecialchars($this->psm).''; $len = strlen($str); @@ -1935,7 +1935,7 @@ class MSN { $this->ns_writedata($fingerprint); $this->ns_writeln("CHG $this->id NLN $this->clientid"); if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); break; case 'CHG': // NS: <<< CHG {id} {status} {code} @@ -2012,9 +2012,9 @@ class MSN { case 'RNG': if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); else - $this->ns_writeln("CHG $this->id NLN $this->clientid"); + $this->ns_writeln("CHG $this->id NLN $this->clientid"); // someone is trying to talk to us // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 $this->log_message("NS: <<< RNG $data"); @@ -2022,8 +2022,8 @@ class MSN { @list($sb_ip, $sb_port) = @explode(':', $server); if($this->IsIgnoreMail($email)) { - $this->log_message("*** Ignore RNG from $email"); - break; + $this->log_message("*** Ignore RNG from $email"); + break; } $this->log_message("*** RING from $email, $sb_ip:$sb_port"); $this->addContact($email,1,$email, true); @@ -3455,7 +3455,7 @@ X-OIM-Sequence-Num: 1 $this->ns_writedata($fingerprint); $this->ns_writeln("CHG $this->id NLN $this->clientid"); if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); break; case 'CHG': // NS: <<< CHG {id} {status} {code} @@ -3524,17 +3524,19 @@ X-OIM-Sequence-Num: 1 break; case 'QNG': // NS: <<< QNG {time} - //@list(/* QNG */, $this->ping_wait) = @explode(' ', $data); + @list(/* QNG */, $ping_wait) = @explode(' ', $data); //if ($this->ping_wait == 0) $this->ping_wait = 50; //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; //Mod by Ricky Set Online + + $this->callHandler('Pong', $ping_wait); break; case 'RNG': if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); else - $this->ns_writeln("CHG $this->id NLN $this->clientid"); + $this->ns_writeln("CHG $this->id NLN $this->clientid"); // someone is trying to talk to us // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 $this->log_message("NS: <<< RNG $data"); @@ -3711,7 +3713,7 @@ X-OIM-Sequence-Num: 1 * Registers a user handler * * Handler List - * IMIn + * IMIn, Pong * * @param String $event Event name * @param String $handler User function to call diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index aae6906d6c..99ac219157 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -36,7 +36,7 @@ class MsnManager extends ImManager protected $lastping = null; - const PING_INTERVAL = 50; + private $pingInterval; /** * Initialize connection to server. @@ -70,7 +70,7 @@ class MsnManager extends ImManager public function idle($timeout=0) { $now = time(); - if (empty($this->lastping) || $now - $this->lastping > self::PING_INTERVAL) { + if (empty($this->lastping) || $now - $this->lastping > $pingInterval) { $this->send_ping(); } } @@ -98,6 +98,7 @@ class MsnManager extends ImManager ) ); $this->conn->registerHandler("IMIn", array($this, 'handle_msn_message')); + $this->conn->registerHandler('Pong', array($this, 'update_ping_time')); $this->conn->signon(); $this->lastping = time(); } @@ -117,6 +118,14 @@ class MsnManager extends ImManager return true; } + /** + * Update the time till the next ping + * @param $data Time till next ping + */ + function update_ping_time($data) { + $pingInterval = $data; + } + function handle_msn_message($data) { $this->plugin->enqueue_incoming_raw($data); From 0083e58db304704be71c6c2fe9dba5484b7ae492 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sun, 13 Jun 2010 03:42:21 +0100 Subject: [PATCH 047/655] - Corrected PhotoSticker bug in phpmsnclass - Update time till next ping when a command is sent --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 17 +++++++---------- plugins/Msn/msnmanager.php | 4 ++++ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 65525fe556..ef4f45b441 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -180,7 +180,7 @@ class MSN { $this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30; $this->backup_file = isset($Configs['backup_file']) ? $Configs['backup_file'] : true; $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true; - $this->PhotoStickerFile=$Configs['PhotoSticker']; + $this->PhotoStickerFile=isset($Configs['PhotoSticker']) ? $Configs['PhotoSticker'] : false; $this->IgnoreList=isset($Configs['IgnoreList'])?$Configs['IgnoreList']:false; if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) { @@ -3626,15 +3626,8 @@ X-OIM-Sequence-Num: 1 $this->SB_writedata($aMessage); - if (feof($this->SBFp)) - { - // lost connection? error? try OIM later - @fclose($this->SBFp); - //TODO introduce callback to add offline message to queue? - return false; - } - $this->SB_writeln("OUT"); - @fclose($this->SBFp); + // Don't close the SB session, we might as well leave it open + return true; } @@ -3651,6 +3644,10 @@ X-OIM-Sequence-Num: 1 return true; } + private function getSBSession($to) { + + } + public function sendMessage($message, $to) { if($message != '') { list($name,$host,$network)=explode('@',$to); diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index 99ac219157..1ef496f56f 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -139,6 +139,10 @@ class MsnManager extends ImManager return false; } $this->conn->sflapSend($data[0],$data[1],$data[2],$data[3]); + + // Sending a command updates the time till next ping + $this->lastping = time(); + $this->pingInterval = 50; return true; } } From f3c1e9da9a0784dc3e071ad8610f701197ab0c84 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sun, 13 Jun 2010 04:14:29 +0100 Subject: [PATCH 048/655] Added some more event handlers and corrected aADL scope --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 28 ++++++++++---------- plugins/Msn/msnmanager.php | 10 +++++++ 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index ef4f45b441..36b47f8e9c 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -112,6 +112,7 @@ class MSN { // Begin added for StatusNet private $aContactList = array(); + private $aADL = array(); private $switchBoardSessions = array(); /** @@ -2172,9 +2173,9 @@ class MSN { $msg_header = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n"; $msg_header_len = strlen($msg_header); if ($network == 1) - $maxlen = $this->max_msn_message_len - $msg_header_len; + $maxlen = $this->max_msn_message_len - $msg_header_len; else - $maxlen = $this->max_yahoo_message_len - $msg_header_len; + $maxlen = $this->max_yahoo_message_len - $msg_header_len; $sMessage=str_replace("\r", '', $sMessage); $msg=substr($sMessage,0,$maxlen); return $msg_header.$msg; @@ -3004,7 +3005,8 @@ X-OIM-Sequence-Num: 1 while(!$this->connect($this->user, $this->password)) { $this->log_message("!!! Can't connect to server: $this->error"); - if(!$this->NSRetryWait($this->retry_wait)) return; + $this->callHandler('ConnectFailed', NULL); + $this->NSRetryWait($this->retry_wait); } $this->UpdateContacts(); $this->LastPing=time(); @@ -3061,7 +3063,7 @@ X-OIM-Sequence-Num: 1 $str = ''; $len += strlen($str); if ($len > 7400) { - $aADL[$n] = ''.$sList.''; + $this->aADL[$n] = ''.$sList.''; $n++; $sList = ''; $len = strlen($str); @@ -3075,7 +3077,7 @@ X-OIM-Sequence-Num: 1 // so we use 7475 if ($len > 7475) { $sList .= ''; - $aADL[$n] = ''.$sList.''; + $this->aADL[$n] = ''.$sList.''; $n++; $sList = ''.$str; $len = strlen($sList); @@ -3087,10 +3089,10 @@ X-OIM-Sequence-Num: 1 $sList .= ''; } } - $aADL[$n] = ''.$sList.''; + $this->aADL[$n] = ''.$sList.''; // NS: >>> BLP {id} BL $this->ns_writeln("BLP $this->id BL"); - foreach ($aADL as $str) { + foreach ($this->aADL as $str) { $len = strlen($str); // NS: >>> ADL {id} {size} $this->ns_writeln("ADL $this->id $len"); @@ -3116,16 +3118,16 @@ X-OIM-Sequence-Num: 1 public function NSreceive() { $this->log_message("*** startup ***"); - $aADL = array(); - // Sign in again if not signed in or socket failed if (!is_resource($this->NSfp) || feof($this->NSfp)) { + $this->callHandler('Reconnect', NULL); $this->signon(); } $data = $this->ns_readln(); if($data === false) { // There was no data / an error when reading from the socket so reconnect + $this->callHandler('Reconnect', NULL); $this->signon(); } else { switch (substr($data,0,3)) @@ -3139,8 +3141,8 @@ X-OIM-Sequence-Num: 1 // FIXME: // NS: <<< RFS ??? // refresh ADL, so we re-send it again - if (is_array($aADL)) { - foreach ($aADL as $str) { + if (is_array($this->aADL)) { + foreach ($this->aADL as $str) { $len = strlen($str); // NS: >>> ADL {id} {size} $this->ns_writeln("ADL $this->id $len"); @@ -3701,8 +3703,6 @@ X-OIM-Sequence-Num: 1 private function callHandler($event, $data) { if (isset($this->myEventHandlers[$event])) { call_user_func($this->myEventHandlers[$event], $data); - } else { - $this->noHandler($data); } } @@ -3710,7 +3710,7 @@ X-OIM-Sequence-Num: 1 * Registers a user handler * * Handler List - * IMIn, Pong + * IMIn, Pong, ConnectFailed, Reconnect * * @param String $event Event name * @param String $handler User function to call diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index 1ef496f56f..354ed0f3ef 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -99,6 +99,8 @@ class MsnManager extends ImManager ); $this->conn->registerHandler("IMIn", array($this, 'handle_msn_message')); $this->conn->registerHandler('Pong', array($this, 'update_ping_time')); + $this->conn->registerHandler('ConnectFailed', array($this, 'handle_connect_failed')); + $this->conn->registerHandler('Reconnect', array($this, 'handle_reconnect')); $this->conn->signon(); $this->lastping = time(); } @@ -131,6 +133,14 @@ class MsnManager extends ImManager $this->plugin->enqueue_incoming_raw($data); return true; } + + function handle_connect_failed($data) { + common_log(LOG_NOTICE, 'MSN connect failed, retrying'); + } + + function handle_reconnect($data) { + common_log(LOG_NOTICE, 'MSN reconnecting'); + } function send_raw_message($data) { From 3d6bb5a5974d002dfbf067374783adf89c296d43 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Mon, 14 Jun 2010 03:47:44 +0100 Subject: [PATCH 049/655] More work on adapting phpmsnclass --- plugins/Msn/MsnPlugin.php | 1 - plugins/Msn/Queued_Msn.php | 120 -- plugins/Msn/extlib/phpmsnclass/msn.class.php | 1567 +++++++++--------- plugins/Msn/msnmanager.php | 2 +- 4 files changed, 827 insertions(+), 863 deletions(-) delete mode 100644 plugins/Msn/Queued_Msn.php diff --git a/plugins/Msn/MsnPlugin.php b/plugins/Msn/MsnPlugin.php index 6737e727ab..5566b54302 100644 --- a/plugins/Msn/MsnPlugin.php +++ b/plugins/Msn/MsnPlugin.php @@ -170,4 +170,3 @@ class MsnPlugin extends ImPlugin return true; } } - diff --git a/plugins/Msn/Queued_Msn.php b/plugins/Msn/Queued_Msn.php deleted file mode 100644 index bc8e0a1d15..0000000000 --- a/plugins/Msn/Queued_Msn.php +++ /dev/null @@ -1,120 +0,0 @@ -. - * - * @category Network - * @package StatusNet - * @author Luke Fitzgerald - * @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); -} - -class Queued_XMPP extends MSN { - /** - * Reference to the MsnPlugin object we're hooked up to. - */ - public $plugin; - - /** - * Constructor - * - * @param MsnPlugin $plugin - * @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($plugin, $host, $port, $user, $password, $resource, $server = null, $printlog = false, $loglevel = null) - { - $this->plugin = $plugin; - - parent::__construct($host, $port, $user, $password, $resource, $server, $printlog, $loglevel); - - // We use $host to connect, but $server to build JIDs if specified. - // This seems to fix an upstream bug where $host was used to build - // $this->basejid, never seen since it isn't actually used in the base - // classes. - if (!$server) { - $server = $this->host; - } - $this->basejid = $this->user . '@' . $server; - - // Normally the fulljid is filled out by the server at resource binding - // time, but we need to do it 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) - { - $this->plugin->enqueue_outgoing_raw($msg); - } - - //@{ - /** - * Stream i/o functions disabled; only do output - */ - public function connect($timeout = 30, $persistent = false, $sendinit = true) - { - throw new Exception("Can't connect to server from fake XMPP."); - } - - public function disconnect() - { - throw new Exception("Can't connect to server from fake XMPP."); - } - - public function process() - { - throw new Exception("Can't read stream from fake XMPP."); - } - - public function processUntil($event, $timeout=-1) - { - throw new Exception("Can't read stream from fake XMPP."); - } - - public function read() - { - throw new Exception("Can't read stream from fake XMPP."); - } - - public function readyToProcess() - { - throw new Exception("Can't read stream from fake XMPP."); - } - //@} - -} - diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 36b47f8e9c..317acd0d53 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -25,14 +25,11 @@ class MSN { private $login_method = 'SSO'; private $oim_send_url = 'https://ows.messenger.msn.com/OimWS/oim.asmx'; private $oim_send_soap = 'http://messenger.live.com/ws/2006/09/oim/Store2'; - private $windows; - private $kill_me = false; private $id; private $ticket; private $user = ''; private $password = ''; private $NSfp=false; - private $SBfp; private $passport_policy = ''; private $alias; private $psm; @@ -46,11 +43,10 @@ class MSN { private $ChildProcess=array(); private $MAXChildProcess=3; private $ReqSBXFRTimeout=60; - private $SBTimeout=2; private $LastPing; private $ping_wait=50; private $SBIdleTimeout=10; - private $SBStreamTimeout=10; + private $SBStreamTimeout=2; private $NSStreamTimeout=2; private $MsnObjArray=array(); private $MsnObjMap=array(); @@ -113,7 +109,10 @@ class MSN { private $aContactList = array(); private $aADL = array(); + private $re_login; private $switchBoardSessions = array(); + private $switchBoardSockets = array(); + private $waitingForXFR = array(); /** * Event Handler Functions @@ -121,6 +120,50 @@ class MSN { private $myEventHandlers = array(); // End added for StatusNet + + public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C) + { + $this->user = $Configs['user']; + $this->password = $Configs['password']; + $this->alias = isset($Configs['alias']) ? $Configs['alias'] : ''; + $this->psm = isset($Configs['psm']) ? $Configs['psm'] : ''; + $this->use_ping = isset($Configs['use_ping']) ? $Configs['use_ping'] : false; + $this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30; + $this->backup_file = isset($Configs['backup_file']) ? $Configs['backup_file'] : true; + $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true; + $this->PhotoStickerFile=isset($Configs['PhotoSticker']) ? $Configs['PhotoSticker'] : false; + if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) + { + foreach($this->Emotions as $EmotionFilePath) + $this->MsnObj($EmotionFilePath,$Type=2); + } + $this->debug = isset($Configs['debug']) ? $Configs['debug'] : false; + $this->timeout = $timeout; + // check support + if (!function_exists('curl_init')) throw new Exception("We need curl module!\n"); + if (!function_exists('preg_match')) throw new Exception("We need pcre module!\n"); + if (!function_exists('mhash')) throw new Exception("We need mhash module!\n"); + + if (!function_exists('mcrypt_cbc')) throw new Exception("We need mcrypt module!\n"); + if (!function_exists('bcmod')) throw new Exception("We need bcmath module for $protocol!\n"); + + /* + http://msnpiki.msnfanatic.com/index.php/Client_ID + Client ID for MSN: + normal MSN 8.1 clientid is: + 01110110 01001100 11000000 00101100 + = 0x764CC02C + + we just use following: + * 0x04: Your client can send/receive Ink (GIF format) + * 0x08: Your client can send/recieve Ink (ISF format) + * 0x8000: This means you support Winks receiving (If not set the official Client will warn with 'contact has an older client and is not capable of receiving Winks') + * 0x70000000: This is the value for MSNC7 (WL Msgr 8.1) + = 0x7000800C; + */ + $this->clientid = $client_id; + $this->ABService=new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl',array('trace' => 1)); + } private function Array2SoapVar($Array,$ReturnSoapVarObj=true,$TypeName=null,$TypeNameSpace=null) { @@ -154,78 +197,16 @@ class MSN { if($ReturnSoapVarObj) return new SoapVar($ArrayString,XSD_ANYXML,$TypeName,$TypeNameSpace); return $ArrayString; } - - public function End() - { - $this->log_message("*** someone kill me ***"); - $this->kill_me=true; - } - private function IsIgnoreMail($Email) - { - if($this->IgnoreList==false) return false; - foreach($this->IgnoreList as $Pattern) - { - if(preg_match($Pattern,$Email)) return true; - } - return false; - } - public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C) - { - $this->user = $Configs['user']; - $this->password = $Configs['password']; - $this->alias = isset($Configs['alias']) ? $Configs['alias'] : ''; - $this->psm = isset($Configs['psm']) ? $Configs['psm'] : ''; - $my_add_function = isset($Configs['add_user_function']) ? $Configs['add_user_function'] : false; - $my_rem_function = isset($Configs['remove_user_function']) ? $Configs['remove_user_function'] : false; - $this->use_ping = isset($Configs['use_ping']) ? $Configs['use_ping'] : false; - $this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30; - $this->backup_file = isset($Configs['backup_file']) ? $Configs['backup_file'] : true; - $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true; - $this->PhotoStickerFile=isset($Configs['PhotoSticker']) ? $Configs['PhotoSticker'] : false; - $this->IgnoreList=isset($Configs['IgnoreList'])?$Configs['IgnoreList']:false; - if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) - { - foreach($this->Emotions as $EmotionFilePath) - $this->MsnObj($EmotionFilePath,$Type=2); - } - $this->debug = isset($Configs['debug']) ? $Configs['debug'] : false; - $this->timeout = $timeout; - // check support - if (!function_exists('curl_init')) throw new Exception("We need curl module!\n"); - if (!function_exists('preg_match')) throw new Exception("We need pcre module!\n"); - if (!function_exists('mhash')) throw new Exception("We need mhash module!\n"); - - if (!function_exists('mcrypt_cbc')) throw new Exception("We need mcrypt module!\n"); - if (!function_exists('bcmod')) throw new Exception("We need bcmath module for $protocol!\n"); - - /* - http://msnpiki.msnfanatic.com/index.php/Client_ID - Client ID for MSN: - normal MSN 8.1 clientid is: - 01110110 01001100 11000000 00101100 - = 0x764CC02C - - we just use following: - * 0x04: Your client can send/receive Ink (GIF format) - * 0x08: Your client can send/recieve Ink (ISF format) - * 0x8000: This means you support Winks receiving (If not set the official Client will warn with 'contact has an older client and is not capable of receiving Winks') - * 0x70000000: This is the value for MSNC7 (WL Msgr 8.1) - = 0x7000800C; - */ - $this->clientid = $client_id; - $this->windows =(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN'); - $this->ABService=new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl',array('trace' => 1)); - } - + private function get_passport_ticket($url = '') { $user = $this->user; $password = htmlspecialchars($this->password); if ($url === '') - $passport_url = $this->passport_url; + $passport_url = $this->passport_url; else - $passport_url = $url; + $passport_url = $url; $XML = ' ticket=$aTickets; $this->debug_message(var_export($aTickets, true)); $ABAuthHeaderArray=array( - 'ABAuthHeader'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'ManagedGroupRequest'=>false, - 'TicketToken'=>htmlspecialchars($this->ticket['contact_ticket']), - ) + 'ABAuthHeader'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'ManagedGroupRequest'=>false, + 'TicketToken'=>htmlspecialchars($this->ticket['contact_ticket']), + ) ); $this->ABAuthHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook","ABAuthHeader", $this->Array2SoapVar($ABAuthHeaderArray)); - file_put_contents('/tmp/STTicket.txt',htmlspecialchars($this->ticket['storage_ticket'])); - //$this->debug_message("StorageTicket:\n",htmlspecialchars($this->ticket['storage_ticket'])); return $aTickets; } + private function UpdateContacts() { $ABApplicationHeaderArray=array( - 'ABApplicationHeader'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'ApplicationId'=>'CFE80F9D-180F-4399-82AB-413F33A1FA11', - 'IsMigration'=>false, - 'PartnerScenario'=>'ContactSave' - ) - ); - $ABApplicationHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook",'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); - $ABFindAllArray=array( - 'ABFindAll'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'abId'=>'00000000-0000-0000-0000-000000000000', - 'abView'=>'Full', - 'lastChange'=>'0001-01-01T00:00:00.0000000-08:00', - ) - ); - $ABFindAll=new SoapParam($this->Array2SoapVar($ABFindAllArray),'ABFindAll'); - $this->ABService->__setSoapHeaders(array($ABApplicationHeader,$this->ABAuthHeader)); - $this->Contacts=array(); - try - { - $this->debug_message("*** Update Contacts..."); - $Result=$this->ABService->ABFindAll($ABFindAll); - $this->debug_message("*** Result:\n".print_r($Result,true)."\n".$this->ABService->__getLastResponse()); - foreach($Result->ABFindAllResult->contacts->Contact as $Contact) - $this->Contacts[$Contact->contactInfo->passportName]=$Contact; - } - catch(Exception $e) - { - $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); - } + 'ABApplicationHeader'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'ApplicationId'=>'CFE80F9D-180F-4399-82AB-413F33A1FA11', + 'IsMigration'=>false, + 'PartnerScenario'=>'ContactSave' + ) + ); + + $ABApplicationHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook",'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); + $ABFindAllArray=array( + 'ABFindAll'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'abId'=>'00000000-0000-0000-0000-000000000000', + 'abView'=>'Full', + 'lastChange'=>'0001-01-01T00:00:00.0000000-08:00', + ) + ); + $ABFindAll=new SoapParam($this->Array2SoapVar($ABFindAllArray),'ABFindAll'); + $this->ABService->__setSoapHeaders(array($ABApplicationHeader,$this->ABAuthHeader)); + $this->Contacts=array(); + try + { + $this->debug_message("*** Update Contacts..."); + $Result=$this->ABService->ABFindAll($ABFindAll); + $this->debug_message("*** Result:\n".print_r($Result,true)."\n".$this->ABService->__getLastResponse()); + foreach($Result->ABFindAllResult->contacts->Contact as $Contact) + $this->Contacts[$Contact->contactInfo->passportName]=$Contact; + } + catch(Exception $e) + { + $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); + return false; + } + return true; } - protected function addContact($email, $network, $display = '', $sendADL = false) + + private function addContact($email, $network, $display = '', $sendADL = false) { if ($network != 1) return true; if(isset($this->Contacts[$email])) return true; $ABContactAddArray=array( - 'ABContactAdd'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'abId'=>'00000000-0000-0000-0000-000000000000', - 'contacts'=>array( - 'Contact'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'contactInfo'=>array( - 'contactType'=>'LivePending', - 'passportName'=>$email, - 'isMessengerUser'=>true, - 'MessengerMemberInfo'=>array( - 'DisplayName'=>$email - ) - ) - ) - ), - 'options'=>array( - 'EnableAllowListManagement'=>true - ) - ) + 'ABContactAdd'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'abId'=>'00000000-0000-0000-0000-000000000000', + 'contacts'=>array( + 'Contact'=>array( + ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'contactInfo'=>array( + 'contactType'=>'LivePending', + 'passportName'=>$email, + 'isMessengerUser'=>true, + 'MessengerMemberInfo'=>array( + 'DisplayName'=>$email + ) + ) + ) + ), + 'options'=>array( + 'EnableAllowListManagement'=>true + ) + ) ); $ABContactAdd=new SoapParam($this->Array2SoapVar($ABContactAddArray),'ABContactAdd'); try @@ -531,6 +515,7 @@ class MSN { catch(Exception $e) { $this->debug_message("*** Add Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); + return false; } if ($sendADL && !feof($this->NSfp)) { @list($u_name, $u_domain) = @explode('@', $email); @@ -552,7 +537,7 @@ class MSN { $user = $email; $ticket = htmlspecialchars($this->ticket['contact_ticket']); if ($network == 1) - $XML = ' + $XML = ' '; else - $XML = ' + $XML = ' delmember_soap, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' - ); + ); - $this->debug_message("*** URL: $this->delmember_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->delmember_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + $this->debug_message("*** URL: $this->delmember_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->delmember_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); - if ($http_code != 200) { - preg_match('#(.*)(.*)#', $data, $matches); - if (count($matches) == 0) { - $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list"); - return false; - } - $faultcode = trim($matches[1]); - $faultstring = trim($matches[2]); - if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) { - $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring"); - return false; - } - $this->log_message("*** delete member (network: $network) $email ($memberID) from $list, not exist"); - return true; + if ($http_code != 200) { + preg_match('#(.*)(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list"); + return false; } - $this->log_message("*** delete member (network: $network) $email ($memberID) from $list"); + $faultcode = trim($matches[1]); + $faultstring = trim($matches[2]); + if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) { + $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring"); + return false; + } + $this->log_message("*** delete member (network: $network) $email ($memberID) from $list, not exist"); return true; + } + $this->log_message("*** delete member (network: $network) $email ($memberID) from $list"); + return true; } function addMemberToList($email, $network, $list) { @@ -677,7 +662,7 @@ class MSN { $user = $email; if ($network == 1) - $XML = ' + $XML = ' '; else - $XML = ' + $XML = ' addmember_soap, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' - ); + ); - $this->debug_message("*** URL: $this->addmember_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->addmember_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + $this->debug_message("*** URL: $this->addmember_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->addmember_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); - if ($http_code != 200) { - preg_match('#(.*)(.*)#', $data, $matches); - if (count($matches) == 0) { - $this->log_message("*** can't add member (network: $network) $email to $list"); - return false; - } - $faultcode = trim($matches[1]); - $faultstring = trim($matches[2]); - if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) { - $this->log_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring"); - return false; - } - $this->log_message("*** add member (network: $network) $email to $list, already exist!"); - return true; + if ($http_code != 200) { + preg_match('#(.*)(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->log_message("*** can't add member (network: $network) $email to $list"); + return false; } - $this->log_message("*** add member (network: $network) $email to $list"); + $faultcode = trim($matches[1]); + $faultstring = trim($matches[2]); + if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) { + $this->log_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring"); + return false; + } + $this->log_message("*** add member (network: $network) $email to $list, already exist!"); return true; + } + $this->log_message("*** add member (network: $network) $email to $list"); + return true; } function getMembershipList($returnData=false) { @@ -837,102 +822,109 @@ class MSN { 'SOAPAction: '.$this->membership_soap, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' - ); - $this->debug_message("*** URL: $this->membership_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->membership_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); - if($http_code != 200) return array(); - $p = $data; - $aMemberships = array(); + ); + $this->debug_message("*** URL: $this->membership_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->membership_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + if($http_code != 200) return false; + $p = $data; + $aMemberships = array(); + while (1) { + //$this->debug_message("search p = $p"); + $start = strpos($p, ''); + $end = strpos($p, ''); + if ($start === false || $end === false || $start > $end) break; + //$this->debug_message("start = $start, end = $end"); + $end += 13; + $sMembership = substr($p, $start, $end - $start); + $aMemberships[] = $sMembership; + //$this->debug_message("add sMembership = $sMembership"); + $p = substr($p, $end); + } + //$this->debug_message("aMemberships = ".var_export($aMemberships, true)); + + $aContactList = array(); + foreach ($aMemberships as $sMembership) { + //$this->debug_message("sMembership = $sMembership"); + if (isset($matches)) unset($matches); + preg_match('#(.*)#', $sMembership, $matches); + if (count($matches) == 0) continue; + $sMemberRole = $matches[1]; + //$this->debug_message("MemberRole = $sMemberRole"); + if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue; + $p = $sMembership; + if (isset($aMembers)) unset($aMembers); + $aMembers = array(); while (1) { //$this->debug_message("search p = $p"); - $start = strpos($p, ''); - $end = strpos($p, ''); + $start = strpos($p, 'debug_message("add sMembership = $sMembership"); + $end += 9; + $sMember = substr($p, $start, $end - $start); + $aMembers[] = $sMember; + //$this->debug_message("add sMember = $sMember"); $p = substr($p, $end); } - //$this->debug_message("aMemberships = ".var_export($aMemberships, true)); - - $aContactList = array(); - foreach ($aMemberships as $sMembership) { - //$this->debug_message("sMembership = $sMembership"); + //$this->debug_message("aMembers = ".var_export($aMembers, true)); + foreach ($aMembers as $sMember) { + //$this->debug_message("sMember = $sMember"); if (isset($matches)) unset($matches); - preg_match('#(.*)#', $sMembership, $matches); + preg_match('##', $sMember, $matches); if (count($matches) == 0) continue; - $sMemberRole = $matches[1]; - //$this->debug_message("MemberRole = $sMemberRole"); - if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue; - $p = $sMembership; - if (isset($aMembers)) unset($aMembers); - $aMembers = array(); - while (1) { - //$this->debug_message("search p = $p"); - $start = strpos($p, 'debug_message("add sMember = $sMember"); - $p = substr($p, $end); + $sMemberType = $matches[1]; + //$this->debug_message("MemberType = $sMemberType"); + $network = -1; + preg_match('#(.*)#', $sMember, $matches); + if (count($matches) == 0) continue; + $id = $matches[1]; + if ($sMemberType == 'PassportMember') { + if (strpos($sMember, 'Passport') === false) continue; + $network = 1; + preg_match('#(.*)#', $sMember, $matches); } - //$this->debug_message("aMembers = ".var_export($aMembers, true)); - foreach ($aMembers as $sMember) { - //$this->debug_message("sMember = $sMember"); - if (isset($matches)) unset($matches); - preg_match('##', $sMember, $matches); + else if ($sMemberType == 'EmailMember') { + if (strpos($sMember, 'Email') === false) continue; + // Value is 32: or 32:YAHOO + preg_match('#MSN.IM.BuddyType(.*):(.*)#', $sMember, $matches); if (count($matches) == 0) continue; - $sMemberType = $matches[1]; - //$this->debug_message("MemberType = $sMemberType"); - $network = -1; - preg_match('#(.*)#', $sMember, $matches); - if (count($matches) == 0) continue; - $id = $matches[1]; - if ($sMemberType == 'PassportMember') { - if (strpos($sMember, 'Passport') === false) continue; - $network = 1; - preg_match('#(.*)#', $sMember, $matches); - } - else if ($sMemberType == 'EmailMember') { - if (strpos($sMember, 'Email') === false) continue; - // Value is 32: or 32:YAHOO - preg_match('#MSN.IM.BuddyType(.*):(.*)#', $sMember, $matches); - if (count($matches) == 0) continue; - if ($matches[1] != 32) continue; - $network = 32; - preg_match('#(.*)#', $sMember, $matches); - } - if ($network == -1) continue; - if (count($matches) > 0) { - $email = $matches[1]; - @list($u_name, $u_domain) = @explode('@', $email); - if ($u_domain == NULL) continue; - $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id; - $this->log_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); - } + if ($matches[1] != 32) continue; + $network = 32; + preg_match('#(.*)#', $sMember, $matches); + } + if ($network == -1) continue; + if (count($matches) > 0) { + $email = $matches[1]; + @list($u_name, $u_domain) = @explode('@', $email); + if ($u_domain == NULL) continue; + $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id; + $this->log_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); } } - return $aContactList; + } + return $aContactList; } + /** + * Connect to the NS server + * @param $user Username + * @param $password Password + * @param $redirect_server Redirect server + * @param $redirect_port Redirect port + */ private function connect($user, $password, $redirect_server = '', $redirect_port = 1863) { $this->id = 1; if ($redirect_server === '') { @@ -1092,6 +1084,133 @@ class MSN { // never goto here } + /** + * Sign onto the NS server and retrieve the address book + */ + public function signon() { + $this->log_message("*** try to connect to MSN network"); + while(!$this->connect($this->user, $this->password)) + { + $this->signonFailed("!!! Can't connect to server: $this->error"); + } + if(!$this->UpdateContacts()) { + $this->signonFailed('!!! Could not update contacts'); + return $this->signon(); + } + $this->LastPing=time(); + $this->log_message("*** connected, wait for command"); + $start_tm = time(); + $ping_tm = time(); + if(($this->aContactList = $this->getMembershipList()) === false) { + $this->signonFailed('!!! Could not get Membership List'); + return $this->signon(); + } + if ($this->update_pending) { + if (is_array($this->aContactList)) { + $pending = 'Pending'; + foreach ($this->aContactList as $u_domain => $aUserList) { + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $aData) { + if (isset($aData[$pending])) { + // pending list + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) { + if (isset($aData[$list])) + $cnt++; + else { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; + } + } + } + if ($cnt >= 2) { + $id = $aData[$pending]; + // we can delete it from pending now + if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending)) + unset($this->aContactList[$u_domain][$u_name][$network][$pending]); + } + } + else { + // sync list + foreach (array('Allow', 'Reverse') as $list) { + if (!isset($aData[$list])) { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + } + } + } + } + } + } + } + } + $n = 0; + $sList = ''; + $len = 0; + if (is_array($this->aContactList)) { + foreach ($this->aContactList as $u_domain => $aUserList) { + $str = ''; + $len += strlen($str); + if ($len > 7400) { + $this->aADL[$n] = ''.$sList.''; + $n++; + $sList = ''; + $len = strlen($str); + } + $sList .= $str; + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $status) { + $str = ''; + $len += strlen($str); + // max: 7500, but is 19, + // so we use 7475 + if ($len > 7475) { + $sList .= ''; + $this->aADL[$n] = ''.$sList.''; + $n++; + $sList = ''.$str; + $len = strlen($sList); + } + else + $sList .= $str; + } + } + $sList .= ''; + } + } + $this->aADL[$n] = ''.$sList.''; + // NS: >>> BLP {id} BL + $this->ns_writeln("BLP $this->id BL"); + foreach ($this->aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + // NS: >>> PRP {id} MFN name + if ($this->alias == '') $this->alias = $user; + $aliasname = rawurlencode($this->alias); + $this->ns_writeln("PRP $this->id MFN $aliasname"); + //設定個人大頭貼 + //$MsnObj=$this->PhotoStckObj(); + // NS: >>> CHG {id} {status} {clientid} {msnobj} + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + // NS: >>> UUX {id} length + $str = ''.htmlspecialchars($this->psm).''; + $len = strlen($str); + $this->ns_writeln("UUX $this->id $len"); + $this->ns_writedata($str); + } + + private function signonFailed($message) { + $this->log_message($message); + $this->callHandler('ConnectFailed', NULL); + $this->NSRetryWait($this->retry_wait); + } + function derive_key($key, $magic) { $hash1 = mhash(MHASH_SHA1, $magic, $key); $hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key); @@ -1148,36 +1267,36 @@ class MSN { 'SOAPAction: '.$this->oim_maildata_soap, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); + ); - $this->debug_message("*** URL: $this->oim_maildata_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_maildata_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + $this->debug_message("*** URL: $this->oim_maildata_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_maildata_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); - if ($http_code != 200) { - $this->debug_message("*** Can't get OIM maildata! http code: $http_code"); - return false; - } + if ($http_code != 200) { + $this->debug_message("*** Can't get OIM maildata! http code: $http_code"); + return false; + } - // See #XML_Data - preg_match('#]*)>(.*)#', $data, $matches); - if (count($matches) == 0) { - $this->debug_message("*** Can't get OIM maildata"); - return ''; - } - return $matches[2]; + // See #XML_Data + preg_match('#]*)>(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->debug_message("*** Can't get OIM maildata"); + return ''; + } + return $matches[2]; } function getOIM_message($msgid) { @@ -1212,60 +1331,60 @@ class MSN { 'SOAPAction: '.$this->oim_read_soap, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); + ); - $this->debug_message("*** URL: $this->oim_read_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_read_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + $this->debug_message("*** URL: $this->oim_read_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_read_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); - if ($http_code != 200) { - $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code"); - return false; - } + if ($http_code != 200) { + $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code"); + return false; + } - // why can't use preg_match('#(.*)#', $data, $matches)? - // multi-lines? - $start = strpos($data, ''); - $end = strpos($data, ''); - if ($start === false || $end === false || $start > $end) { - $this->debug_message("*** Can't get OIM: $msgid"); - return false; - } - $lines = substr($data, $start + 18, $end - $start); - $aLines = @explode("\n", $lines); - $header = true; - $ignore = false; - $sOIM = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } + // why can't use preg_match('#(.*)#', $data, $matches)? + // multi-lines? + $start = strpos($data, ''); + $end = strpos($data, ''); + if ($start === false || $end === false || $start > $end) { + $this->debug_message("*** Can't get OIM: $msgid"); + return false; + } + $lines = substr($data, $start + 18, $end - $start); + $aLines = @explode("\n", $lines); + $header = true; + $ignore = false; + $sOIM = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; continue; } - // stop at empty lines - if ($line === '') break; - $sOIM .= $line; + continue; } - $sMsg = base64_decode($sOIM); - $this->debug_message("*** we get OIM ($msgid): $sMsg"); + // stop at empty lines + if ($line === '') break; + $sOIM .= $line; + } + $sMsg = base64_decode($sOIM); + $this->debug_message("*** we get OIM ($msgid): $sMsg"); - // delete OIM - $XML = ' + // delete OIM + $XML = ' @@ -1284,33 +1403,33 @@ class MSN { '; - $header_array = array( + $header_array = array( 'SOAPAction: '.$this->oim_del_soap, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); + ); - $this->debug_message("*** URL: $this->oim_del_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_del_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + $this->debug_message("*** URL: $this->oim_del_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_del_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); - if ($http_code != 200) + if ($http_code != 200) $this->debug_message("*** Can't delete OIM: $msgid, http code = $http_code"); - else + else $this->debug_message("*** OIM ($msgid) deleted"); - return $sMsg; + return $sMsg; } private function NSLogout() { if (is_resource($this->NSfp) && !feof($this->NSfp)) { @@ -1395,7 +1514,7 @@ class MSN { } if ($oim_result === false || $oim_result['auth_policy'] !== false) { - if ($re_login) + if ($this->re_login) { $this->log_message("*** can't send OIM, but we already re-login again, so ignore this OIM"); break; @@ -1480,7 +1599,6 @@ class MSN { $this->log_message("*** connected, wait for command"); $start_tm = time(); $ping_tm = time(); - stream_set_timeout($this->NSfp, $this->NSStreamTimeout); $aContactList = $this->getMembershipList(); if ($this->update_pending) { if (is_array($aContactList)) { @@ -1659,13 +1777,13 @@ class MSN { $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); else { - $re_login = false; + $this->re_login = false; $cnt = 0; foreach (array('Allow', 'Reverse') as $list) { if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - if ($re_login) { + if ($this->re_login) { $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); continue; } @@ -1676,7 +1794,7 @@ class MSN { $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); continue; } - $re_login = true; + $this->re_login = true; $this->ticket = $aTickets; $this->log_message("**** get new ticket, try it again"); if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) @@ -1770,7 +1888,7 @@ class MSN { $this->log_message("*** ingnore MSG not for OIM"); break; } - $re_login = false; + $this->re_login = false; if (strcasecmp($maildata, 'too-large') == 0) { $this->log_message("*** large mail-data, need to get the data via SOAP"); $maildata = $this->getOIM_maildata(); @@ -1783,7 +1901,7 @@ class MSN { $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); break; } - $re_login = true; + $this->re_login = true; $this->ticket = $aTickets; $this->log_message("**** get new ticket, try it again"); $maildata = $this->getOIM_maildata(); @@ -1849,7 +1967,7 @@ class MSN { $sMsg = $this->getOIM_message($oim_msgid); if ($sMsg === false) { $this->log_message("*** can't get OIM, msgid = $oim_msgid"); - if ($re_login) { + if ($this->re_login) { $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); continue; } @@ -1859,7 +1977,7 @@ class MSN { $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); continue; } - $re_login = true; + $this->re_login = true; $this->ticket = $aTickets; $this->log_message("**** get new ticket, try it again"); $sMsg = $this->getOIM_message($oim_msgid); @@ -2069,30 +2187,6 @@ class MSN { return $this->NsLogout(); } - /*public function SendMessage($Message, $To) - { - $FileName = MSN_CLASS_SPOOL_DIR.'/'.strftime('%Y%m%d%H%M%S',time()).'_'.posix_getpid().'_sendMessage.msn'; - if(!is_array($To)) - $To=array($To); - $Receiver=''; - foreach($To as $Email) - { - list($name,$host,$network)=explode('@',$Email); - $network=$network==''?1:$network; - if($network==1 && $this->SwitchBoardProcess && $this->SwitchBoardSessionUser=="$name@$host" ) - { - $this->debug_message("*** SendMessage to $Receiver use SB message queue."); - array_push($this->SwitchBoardMessageQueue,$Message); - continue; - } - $Receiver.="$name@$host@$network,"; - } - if($Receiver=='') return; - $Receiver=substr($Receiver,0,-1); - $this->debug_message("*** SendMessage to $Receiver use File queue."); - file_put_contents($FileName,"TO: $Receiver\n$Message\n"); - }*/ - function getChallenge($code) { // MSNP15 @@ -2190,9 +2284,9 @@ class MSN { { $SessionEnd=false; $Joined=false; - $id=1; + $this->id=1; $LastActive=time(); - stream_set_timeout($this->SBFp, $this->SBTimeout); + stream_set_timeout($this->SBfp, $this->SBStreamTimeout); switch($Action) { case 'Active': @@ -2200,8 +2294,7 @@ class MSN { $user=$Param['user']; $this->SwitchBoardMessageQueue=$Param['Msg']; // SB: >>> USR {id} {user} {cki} - $this->SB_writeln("USR $id $this->user $cki_code"); - $id++; + $this->SB_writeln("USR $this->id $this->user $cki_code"); $this->SwitchBoardSessionUser=$user; break; case 'Passive': @@ -2209,14 +2302,13 @@ class MSN { $sid=$Param['sid']; $user=$Param['user']; // SB: >>> ANS {id} {user} {ticket} {session_id} - $this->SB_writeln("ANS $id $this->user $ticket $sid"); - $id++; + $this->SB_writeln("ANS $this->id $this->user $ticket $sid"); $this->SwitchBoardSessionUser=$user; break; default: return false; } - while((!feof($this->SBFp))&&(!$SessionEnd)) + while((!feof($this->SBfp))&&(!$SessionEnd)) { $data = $this->SB_readln(); if($this->kill_me) @@ -2242,14 +2334,12 @@ class MSN { { $SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine"; $len = strlen($SendString); - $this->SB_writeln("MSG $id N $len"); - $id++; + $this->SB_writeln("MSG $this->id N $len"); $this->SB_writedata($SendString); $this->id++; } $len = strlen($aMessage); - $this->SB_writeln("MSG $id N $len"); - $id++; + $this->SB_writeln("MSG $this->id N $len"); $this->SB_writedata($aMessage); } $this->SwitchBoardMessageQueue=array(); @@ -2274,8 +2364,7 @@ class MSN { // we don't need the data, just ignore it // request user to join this switchboard // SB: >>> CAL {id} {user} - $this->SB_writeln("CAL $id $user"); - $id++; + $this->SB_writeln("CAL $this->id $user"); break; case 'CAL': // SB: <<< CAL {id} RINGING {?} @@ -2431,8 +2520,7 @@ class MSN { $footer = pack("L", 0); $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer"; $len = strlen($message); - $this->SB_writeln("MSG $id D $len"); - $id++; + $this->SB_writeln("MSG $this->id D $len"); $this->SB_writedata($message); $this->log_message("*** p2p: send display picture acknowledgement for $hdr_SessionID"); $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message)); @@ -2468,8 +2556,7 @@ class MSN { "MIME-Version: 1.0\r\n". "Content-Type: application/x-msnmsgrp2p\r\n". "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer"; - $this->SB_writeln("MSG $id D ".strlen($message)); - $id++; + $this->SB_writeln("MSG $this->id D ".strlen($message)); $this->SB_writedata($message); $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message)); $this->SB_readln();//Read ACK; @@ -2494,8 +2581,7 @@ class MSN { "MIME-Version: 1.0\r\n". "Content-Type: application/x-msnmsgrp2p\r\n". "P2P-Dest: $from_email\r\n\r\n$hdr".pack('L',0)."$footer"; - $this->SB_writeln("MSG $id D ".strlen($message)); - $id++; + $this->SB_writeln("MSG $this->id D ".strlen($message)); $this->SB_writedata($message); $this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message)); $this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr)); @@ -2528,8 +2614,7 @@ class MSN { "MIME-Version: 1.0\r\n". "Content-Type: application/x-msnmsgrp2p\r\n". "P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer"; - $this->SB_writeln("MSG $id D ".strlen($message)); - $id++; + $this->SB_writeln("MSG $this->id D ".strlen($message)); $this->SB_writedata($message); $this->debug_message("*** p2p: dump send Data Content message $Offset / $FileSize :\n".$this->dump_binary($message)); $this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr)); @@ -2651,22 +2736,22 @@ class MSN { } if(!$this->IsIgnoreMail($user)) $LastActive = time(); } - if (feof($this->SBFp)) + if (feof($this->SBfp)) { // lost connection? error? try OIM later - @fclose($this->SBFp); + @fclose($this->SBfp); return false; } $this->SB_writeln("OUT"); - @fclose($this->SBFp); + @fclose($this->SBfp); return true; } - private function switchboard_control($ip, $port, $cki_code, $user, $Messages) + /*private function switchboard_control($ip, $port, $cki_code, $user, $Messages) { $this->SwitchBoardProcess=1; $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); - $this->SBFp = @fsockopen($ip, $port, $errno, $errstr, 5); - if (!$this->SBFp) + $this->SBfp = @fsockopen($ip, $port, $errno, $errstr, 5); + if (!$this->SBfp) { $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); return false; @@ -2677,119 +2762,14 @@ class MSN { { $this->SwitchBoardProcess=2; $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); - $this->SBFp = @fsockopen($ip, $port, $errno, $errstr, 5); - if (!$this->SBFp) + $this->SBfp = @fsockopen($ip, $port, $errno, $errstr, 5); + if (!$this->SBfp) { $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); return false; } return $this->DoSwitchBoard('Passive',array('sid'=>$sid,'user'=>$user,'ticket'=>$ticket)); - } - - private function sendOIM($to, $sMessage, $lockkey) - { - $XML = ' - - - - - - - http://messenger.msn.com - 1 - - - - text - MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: base64 -X-OIM-Message-Type: OfflineMessage -X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7} -X-OIM-Sequence-Num: 1 - -'.chunk_split(base64_encode($sMessage)).' - - -'; - - $header_array = array( - 'SOAPAction: '.$this->oim_send_soap, - 'Content-Type: text/xml', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); - - $this->debug_message("*** URL: $this->oim_send_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_send_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); - - if ($http_code == 200) { - $this->debug_message("*** OIM sent for $to"); - return true; - } - - $challenge = false; - $auth_policy = false; - // the lockkey is invalid, authenticated fail, we need challenge it again - // 364763969 - preg_match("#(.*)#", $data, $matches); - if (count($matches) != 0) { - // yes, we get new LockKeyChallenge - $challenge = $matches[2]; - $this->debug_message("*** OIM need new challenge ($challenge) for $to"); - } - // auth policy error - // MBI_SSL - preg_match("#(.*)#", $data, $matches); - if (count($matches) != 0) { - $auth_policy = $matches[2]; - $this->debug_message("*** OIM need new auth policy ($auth_policy) for $to"); - } - if ($auth_policy === false && $challenge === false) { - //q0:AuthenticationFailed - preg_match("#(.*)#", $data, $matches); - if (count($matches) == 0) { - // no error, we assume the OIM is sent - $this->debug_message("*** OIM sent for $to"); - return true; - } - $err_code = $matches[2]; - //Exception of type 'System.Web.Services.Protocols.SoapException' was thrown. - preg_match("#(.*)#", $data, $matches); - if (count($matches) > 0) - $err_msg = $matches[1]; - else - $err_msg = ''; - $this->debug_message("*** OIM failed for $to"); - $this->debug_message("*** OIM Error code: $err_code"); - $this->debug_message("*** OIM Error Message: $err_msg"); - return false; - } - return array('challenge' => $challenge, 'auth_policy' => $auth_policy); - } + }*/ // read data for specified size private function ns_readdata($size) { @@ -2831,11 +2811,11 @@ X-OIM-Sequence-Num: 1 } // read data for specified size for SB - private function sb_readdata($size) { + private function sb_readdata($socket, $size) { $data = ''; $count = 0; - while (!feof($this->SBFp)) { - $buf = @fread($this->SBFp, $size - $count); + while (!feof($this->SBfp)) { + $buf = @fread($this->SBfp, $size - $count); $data .= $buf; $count += strlen($buf); if ($count >= $size) break; @@ -2845,8 +2825,8 @@ X-OIM-Sequence-Num: 1 } // read one line for SB - private function sb_readln() { - $data = @fgets($this->SBFp, 4096); + private function sb_readln($socket) { + $data = @fgets($socket, 4096); if ($data !== false) { $data = trim($data); $this->debug_message("SB: <<< $data"); @@ -2856,16 +2836,16 @@ X-OIM-Sequence-Num: 1 // write to server for SB, append \r\n, also increase id // switchboard server only accept \r\n, it will lost connection if just \n only - private function sb_writeln($data) { - @fwrite($this->SBFp, $data."\r\n"); + private function sb_writeln($socket, &$id, $data) { + @fwrite($socket, $data."\r\n"); $this->debug_message("SB: >>> $data"); - $this->id++; + $id++; return; } // write data to server - private function sb_writedata($data) { - @fwrite($this->SBFp, $data); + private function sb_writedata($socket, $data) { + @fwrite($socket, $data); $this->debug_message("SB: >>> $data"); return; } @@ -2943,6 +2923,7 @@ X-OIM-Sequence-Num: 1 $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n"); return $MsnObj; } + private function linetoArray($lines) { $lines=str_replace("\r",'',$lines); $lines=explode("\n",$lines); @@ -2953,6 +2934,7 @@ X-OIM-Sequence-Num: 1 } return $Data; } + private function GetPictureFilePath($Context) { $MsnObj=base64_decode($Context); @@ -2963,6 +2945,7 @@ X-OIM-Sequence-Num: 1 return $this->MsnObjArray[$location]; return false; } + private function GetMsnObjDefine($Message) { $DefineString=''; @@ -2974,154 +2957,16 @@ X-OIM-Sequence-Num: 1 } return $DefineString; } - /** - * Receive Message Overload Function - * @param $Sender - * @param $Message - * @param $Network 1 => msn , 32 =>yahoo - * @param $IsOIM - * @return unknown_type - */ - protected function ReceivedMessage($Sender,$Message,$Network,$IsOIM=false){} - /** - * Remove Us From Member List Overload Function - * @param $User - * @param $Message - * @param $Network 1 => msn , 32 =>yahoo - * @return unknown_type - */ - protected function RemoveUsFromMemberList($User,$Network){} - /** - * Add Us to Member List Overload Function - * @param $User - * @param $Message - * @param $Network 1 => msn , 32 =>yahoo - * @return unknown_type - */ - protected function AddUsToMemberList($User,$Network){} - public function signon() { - $this->log_message("*** try to connect to MSN network"); - while(!$this->connect($this->user, $this->password)) - { - $this->log_message("!!! Can't connect to server: $this->error"); - $this->callHandler('ConnectFailed', NULL); - $this->NSRetryWait($this->retry_wait); - } - $this->UpdateContacts(); - $this->LastPing=time(); - $this->log_message("*** connected, wait for command"); - $start_tm = time(); - $ping_tm = time(); - stream_set_timeout($this->NSfp, $this->NSStreamTimeout); - $this->aContactList = $this->getMembershipList(); - if ($this->update_pending) { - if (is_array($this->aContactList)) { - $pending = 'Pending'; - foreach ($this->aContactList as $u_domain => $aUserList) { - foreach ($aUserList as $u_name => $aNetworks) { - foreach ($aNetworks as $network => $aData) { - if (isset($aData[$pending])) { - // pending list - $cnt = 0; - foreach (array('Allow', 'Reverse') as $list) { - if (isset($aData[$list])) - $cnt++; - else { - if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - $this->aContactList[$u_domain][$u_name][$network][$list] = false; - $cnt++; - } - } - } - if ($cnt >= 2) { - $id = $aData[$pending]; - // we can delete it from pending now - if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending)) - unset($this->aContactList[$u_domain][$u_name][$network][$pending]); - } - } - else { - // sync list - foreach (array('Allow', 'Reverse') as $list) { - if (!isset($aData[$list])) { - if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) - $this->aContactList[$u_domain][$u_name][$network][$list] = false; - } - } - } - } - } - } - } - } - $n = 0; - $sList = ''; - $len = 0; - if (is_array($this->aContactList)) { - foreach ($this->aContactList as $u_domain => $aUserList) { - $str = ''; - $len += strlen($str); - if ($len > 7400) { - $this->aADL[$n] = ''.$sList.''; - $n++; - $sList = ''; - $len = strlen($str); - } - $sList .= $str; - foreach ($aUserList as $u_name => $aNetworks) { - foreach ($aNetworks as $network => $status) { - $str = ''; - $len += strlen($str); - // max: 7500, but is 19, - // so we use 7475 - if ($len > 7475) { - $sList .= ''; - $this->aADL[$n] = ''.$sList.''; - $n++; - $sList = ''.$str; - $len = strlen($sList); - } - else - $sList .= $str; - } - } - $sList .= ''; - } - } - $this->aADL[$n] = ''.$sList.''; - // NS: >>> BLP {id} BL - $this->ns_writeln("BLP $this->id BL"); - foreach ($this->aADL as $str) { - $len = strlen($str); - // NS: >>> ADL {id} {size} - $this->ns_writeln("ADL $this->id $len"); - $this->ns_writedata($str); - } - // NS: >>> PRP {id} MFN name - if ($this->alias == '') $this->alias = $user; - $aliasname = rawurlencode($this->alias); - $this->ns_writeln("PRP $this->id MFN $aliasname"); - //設定個人大頭貼 - //$MsnObj=$this->PhotoStckObj(); - // NS: >>> CHG {id} {status} {clientid} {msnobj} - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - // NS: >>> UUX {id} length - $str = ''.htmlspecialchars($this->psm).''; - $len = strlen($str); - $this->ns_writeln("UUX $this->id $len"); - $this->ns_writedata($str); - } - - public function NSreceive() { - $this->log_message("*** startup ***"); - + /** + * Read and handle incoming command from NS + */ + public function nsReceive() { // Sign in again if not signed in or socket failed if (!is_resource($this->NSfp) || feof($this->NSfp)) { $this->callHandler('Reconnect', NULL); $this->signon(); + return; } $data = $this->ns_readln(); @@ -3178,13 +3023,13 @@ X-OIM-Sequence-Num: 1 $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); else { - $re_login = false; + $this->re_login = false; $cnt = 0; foreach (array('Allow', 'Reverse') as $list) { if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - if ($re_login) { + if ($this->re_login) { $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); continue; } @@ -3195,7 +3040,7 @@ X-OIM-Sequence-Num: 1 $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); continue; } - $re_login = true; + $this->re_login = true; $this->ticket = $aTickets; $this->log_message("**** get new ticket, try it again"); if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) @@ -3289,7 +3134,7 @@ X-OIM-Sequence-Num: 1 $this->log_message("*** ingnore MSG not for OIM"); break; } - $re_login = false; + $this->re_login = false; if (strcasecmp($maildata, 'too-large') == 0) { $this->log_message("*** large mail-data, need to get the data via SOAP"); $maildata = $this->getOIM_maildata(); @@ -3302,7 +3147,7 @@ X-OIM-Sequence-Num: 1 $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); break; } - $re_login = true; + $this->re_login = true; $this->ticket = $aTickets; $this->log_message("**** get new ticket, try it again"); $maildata = $this->getOIM_maildata(); @@ -3368,7 +3213,7 @@ X-OIM-Sequence-Num: 1 $sMsg = $this->getOIM_message($oim_msgid); if ($sMsg === false) { $this->log_message("*** can't get OIM, msgid = $oim_msgid"); - if ($re_login) { + if ($this->re_login) { $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); continue; } @@ -3378,7 +3223,7 @@ X-OIM-Sequence-Num: 1 $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); continue; } - $re_login = true; + $this->re_login = true; $this->ticket = $aTickets; $this->log_message("**** get new ticket, try it again"); $sMsg = $this->getOIM_message($oim_msgid); @@ -3590,23 +3435,87 @@ X-OIM-Sequence-Num: 1 } } - public function sendMessageViaSB($message, $to) { + /** + * Read and handle incoming command/message from + * a switchboard session socket + */ + public function sbReceive() { + + } + + /** + * Send a request for a switchboard session + * @param $to Target email for switchboard session + */ + private function reqSBSession($to) { + $this->log_message("*** Request SB for $to"); + $this->ns_writeln("XFR $this->id SB"); + + // Add to the queue of those waiting for a switchboard session reponse + $this->switchBoardSessions[$to] = array('socket' => NULL, 'id' => 1, 'lastActive' => NULL, 'joined' => false, 'XFRReqTime' => time()); + $this->waitingForXFR[] = &$this->switchBoardSessions[$to]; + } + + /** + * Following an XFR or RNG, connect to the switchboard session + * @param $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case or RNG) + * @param $ip IP of Switchboard + * @param $port Port of Switchboard + * @param $to User on other end of Switchboard + * @param $param Array of parameters - 'cki', 'ticket', 'sid' + * @return Whether successful + */ + private function connectToSBSession($mode, $ip, $port, $to, $param) { + $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); + + $this->switchBoardSessions[$to]['socket'] = @fsockopen($ip, $port, $errno, $errstr, 5); $socket = $this->switchBoardSessions[$to]['socket']; - $lastActive = $this->switchBoardSessions[$to]['lastActive']; - $joined = $this->switchBoardSessions[$to]['joined']; - - //FIXME Probably not needed (we're not running in a loop anymore) - /*if($this->kill_me) - { - $this->log_message("*** SB Okay, kill me now!"); - endSBSession($socket); - }*/ - - if(!$Joined) { - // If our participant has not joined the session yet we can't message them! - //TODO Check the behaviour of the queue runner when we return false + if(!$socket) { + $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); return false; } + $this->switchBoardSockets[$socket] = $socket; + + stream_set_timeout($socket, $this->SBStreamTimeout); + + $id = &$this->switchBoardSessions[$to]['id']; + + if($mode == 'Active') { + $cki_code = $param['cki']; + + // SB: >>> USR {id} {user} {cki} + $this->sb_writeln($socket, $id, "USR $id $this->user $cki_code"); + } else { + // Passive + $ticket = $param['ticket']; + $sid = $param['sid']; + + // SB: >>> ANS {id} {user} {ticket} {session_id} + $this->sb_writeln($socket, $id, "ANS $id $this->user $ticket $sid"); + } + + $this->switchBoardSessions[$to]['lastActive'] = time(); + } + + /** + * Send a message via an existing SB session + * @param $message Message + * @param $to Recipient for message + * @return Whether successful + */ + private function sendMessageViaSB($message, $to) { + if(socketcheck($this->switchBoardSessions[$to]['socket'])) { + $this->reqSBSession($to); + return false; + } + + if(!$this->switchBoardSessions[$to]['joined']) { + // If our participant has not joined the session yet we can't message them! + return false; + } + + $id = &$this->switchBoardSessions[$to]['id']; + $socket = $this->switchBoardSessions[$to]['socket']; $aMessage = $this->getMessage($Message); //CheckEmotion... @@ -3615,79 +3524,255 @@ X-OIM-Sequence-Num: 1 { $SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine"; $len = strlen($SendString); - $this->SB_writeln("MSG $id N $len"); - $id++; - $this->SB_writedata($SendString); - $this->id++; + // TODO handle failure during write to socket + $this->sb_writeln($socket, $id, "MSG $id N $len"); + $this->sb_writedata($socket, $SendString); } $len = strlen($aMessage); - $this->SB_writeln("MSG $id N $len"); - - // Increment the trID - $this->switchBoardSessions[$to]['id']++; - - $this->SB_writedata($aMessage); + // TODO handle failure during write to socket + $this->sb_writeln($socket, $id, "MSG $id N $len"); + $this->sb_writedata($socket, $aMessage); // Don't close the SB session, we might as well leave it open return true; } - //FIXME Not sure if this is needed? - private function endSBSession($socket) { - if (feof($this->SBFp)) - { - // lost connection? error? try OIM later - @fclose($this->SBFp); + /** + * + * @param $to + * @param $sMessage + * @param $lockkey + */ + private function sendOIM($to, $sMessage, $lockkey) { + $XML = ' + + + + + + + http://messenger.msn.com + 1 + + + + text + MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 +X-OIM-Message-Type: OfflineMessage +X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7} +X-OIM-Sequence-Num: 1 + +'.chunk_split(base64_encode($sMessage)).' + + +'; + + $header_array = array( + 'SOAPAction: '.$this->oim_send_soap, + 'Content-Type: text/xml', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' + ); + + $this->debug_message("*** URL: $this->oim_send_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $this->oim_send_url); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code == 200) { + $this->debug_message("*** OIM sent for $to"); + return true; + } + + $challenge = false; + $auth_policy = false; + // the lockkey is invalid, authenticated fail, we need challenge it again + // 364763969 + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + // yes, we get new LockKeyChallenge + $challenge = $matches[2]; + $this->debug_message("*** OIM need new challenge ($challenge) for $to"); + } + // auth policy error + // MBI_SSL + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + $auth_policy = $matches[2]; + $this->debug_message("*** OIM need new auth policy ($auth_policy) for $to"); + } + if ($auth_policy === false && $challenge === false) { + //q0:AuthenticationFailed + preg_match("#(.*)#", $data, $matches); + if (count($matches) == 0) { + // no error, we assume the OIM is sent + $this->debug_message("*** OIM sent for $to"); + return true; + } + $err_code = $matches[2]; + //Exception of type 'System.Web.Services.Protocols.SoapException' was thrown. + preg_match("#(.*)#", $data, $matches); + if (count($matches) > 0) + $err_msg = $matches[1]; + else + $err_msg = ''; + $this->debug_message("*** OIM failed for $to"); + $this->debug_message("*** OIM Error code: $err_code"); + $this->debug_message("*** OIM Error Message: $err_msg"); return false; } - $this->SB_writeln("OUT"); - @fclose($this->SBFp); - return true; + return array('challenge' => $challenge, 'auth_policy' => $auth_policy); } - private function getSBSession($to) { - + /** + * Send a message to a user on another network + * @param $message Message + * @param $to Intended recipient + * @param $network Network + */ + private function sendOtherNetworkMessage($message, $to, $network) { + $message=$this->getMessage($nessage, $network); + $len = strlen($message); + $this->ns_writeln("UUM $this->id $to $network 1 $len"); + $this->ns_writedata($Message); + $this->log_message("*** sent to $to (network: $network):\n$Message"); } + /** + * Send a message + * @param $message Message + * @param $to To address in form user@host.com@network + * where network is 1 for MSN, 32 for Yahoo + * and 'Offline' for offline messages + */ public function sendMessage($message, $to) { if($message != '') { list($name,$host,$network)=explode('@',$to); $network=$network==''?1:$network; - if($network === 1 && isset($this->switchBoardSessions[$to])) { + if($network === 1 && $this->switchBoardSessions[$to]['socket'] != NULL && time()-$this->switchBoardSessions[$to]['lastActive'] < $this->SBIdleTimeout) { $recipient = $name . $host; $this->debug_message("*** Sending Message to $recipient using existing SB session"); return $this->sendMessageViaSB($message, $recipient); + } elseif($network == 'Offline') { + //Send OIM + //FIXME: 修正Send OIM + $lockkey=''; + for ($i = 0; $i < $this->oim_try; $i++) + { + if(($oim_result = $this->sendOIM($To, $Message, $lockkey))===true) break; + if (is_array($oim_result) && $oim_result['challenge'] !== false) { + // need challenge lockkey + $this->log_message("*** we need a new challenge code for ".$oim_result['challenge']); + $lockkey = $this->getChallenge($oim_result['challenge']); + continue; + } + if ($oim_result === false || $oim_result['auth_policy'] !== false) + { + if ($this->re_login) + { + $this->log_message("*** can't send OIM, but we already re-login again, so ignore this OIM"); + break; + } + $this->log_message("*** can't send OIM, maybe ticket expired, try to login again"); + // maybe we need to re-login again + if(!$this->get_passport_ticket()) + { + $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + break; + } + $this->log_message("**** get new ticket, try it again"); + continue; + } + } } else { $this->debug_message("*** Not MSN network or no existing SB session"); - //TODO implement creation of SB session etc + $this->reqSBSession($to); + return false; } } return true; } + //FIXME Not sure if this is needed? + private function endSBSession($socket) { + if (feof($socket)) + { + // lost connection? error? try OIM later + @fclose($socket); + return false; + } + $fake = 0; + $this->sb_writeln($socket, $fake, "OUT"); + @fclose($socket); + return true; + } + /** * Sends a ping command * * Should be called about every 50 seconds */ - public function send_ping() { + public function sendPing() { // NS: >>> PNG $this->ns_writeln("PNG"); } + /** + * Get the NS socket + */ public function getNSSocket() { return $this->NSfp; } - // TODO Allow for multiple SB session sockets - public function getSBSocket() { - return $this->SBfp; + /** + * Get the Switchboard sockets currently in use + */ + public function getSBSockets() { + return $this->switchBoardSockets; } + /** + * Get all the sockets currently in use + */ public function getSockets() { - return array($this->NSfp, $this->SBfp); + return array_merge($this->NSfp, $this->switchBoardSockets); + } + + /** + * Checks socket for end of file + * + * @access public + * @param Resource $socket Socket to check + * @return boolean true if end of file (socket) + */ + private static function socketcheck($socket){ + $info = stream_get_meta_data($socket); + return $info['eof']; } /** diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index 354ed0f3ef..b0540c46e5 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -115,7 +115,7 @@ class MsnManager extends ImManager $now = time(); - $this->conn->send_ping(); + $this->conn->sendPing(); $this->lastping = $now; return true; } From d1c9908282052228be9822846c9dcac1f430cbfe Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Mon, 14 Jun 2010 04:26:41 +0100 Subject: [PATCH 050/655] Added better error handling to signon method --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 209 ++++++++++--------- 1 file changed, 109 insertions(+), 100 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 317acd0d53..73f02f6d73 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -139,6 +139,7 @@ class MSN { } $this->debug = isset($Configs['debug']) ? $Configs['debug'] : false; $this->timeout = $timeout; + // check support if (!function_exists('curl_init')) throw new Exception("We need curl module!\n"); if (!function_exists('preg_match')) throw new Exception("We need pcre module!\n"); @@ -1089,123 +1090,131 @@ class MSN { */ public function signon() { $this->log_message("*** try to connect to MSN network"); - while(!$this->connect($this->user, $this->password)) - { - $this->signonFailed("!!! Can't connect to server: $this->error"); - } - if(!$this->UpdateContacts()) { - $this->signonFailed('!!! Could not update contacts'); - return $this->signon(); - } - $this->LastPing=time(); - $this->log_message("*** connected, wait for command"); - $start_tm = time(); - $ping_tm = time(); - if(($this->aContactList = $this->getMembershipList()) === false) { - $this->signonFailed('!!! Could not get Membership List'); - return $this->signon(); - } - if ($this->update_pending) { - if (is_array($this->aContactList)) { - $pending = 'Pending'; - foreach ($this->aContactList as $u_domain => $aUserList) { - foreach ($aUserList as $u_name => $aNetworks) { - foreach ($aNetworks as $network => $aData) { - if (isset($aData[$pending])) { - // pending list - $cnt = 0; - foreach (array('Allow', 'Reverse') as $list) { - if (isset($aData[$list])) - $cnt++; - else { - if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - $this->aContactList[$u_domain][$u_name][$network][$list] = false; + + while(true) { + while(!$this->connect($this->user, $this->password)) + { + $this->signonFailure("!!! Can't connect to server: $this->error"); + } + if($this->UpdateContacts() === false) { + $this->signonFailure('!!! Update Contacts failed'); + continue; + } + $this->LastPing=time(); + $this->log_message("*** connected, wait for command"); + $start_tm = time(); + $ping_tm = time(); + if(($this->aContactList = $this->getMembershipList()) === false) { + continue; + } + if ($this->update_pending) { + if (is_array($this->aContactList)) { + $pending = 'Pending'; + foreach ($this->aContactList as $u_domain => $aUserList) { + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $aData) { + if (isset($aData[$pending])) { + // pending list + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) { + if (isset($aData[$list])) $cnt++; + else { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; + } + } + } + if ($cnt >= 2) { + $id = $aData[$pending]; + // we can delete it from pending now + if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending)) + unset($this->aContactList[$u_domain][$u_name][$network][$pending]); + } + } + else { + // sync list + foreach (array('Allow', 'Reverse') as $list) { + if (!isset($aData[$list])) { + if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) + $this->aContactList[$u_domain][$u_name][$network][$list] = false; } } } - if ($cnt >= 2) { - $id = $aData[$pending]; - // we can delete it from pending now - if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending)) - unset($this->aContactList[$u_domain][$u_name][$network][$pending]); - } - } - else { - // sync list - foreach (array('Allow', 'Reverse') as $list) { - if (!isset($aData[$list])) { - if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) - $this->aContactList[$u_domain][$u_name][$network][$list] = false; - } - } } } } } } - } - $n = 0; - $sList = ''; - $len = 0; - if (is_array($this->aContactList)) { - foreach ($this->aContactList as $u_domain => $aUserList) { - $str = ''; - $len += strlen($str); - if ($len > 7400) { - $this->aADL[$n] = ''.$sList.''; - $n++; - $sList = ''; - $len = strlen($str); - } - $sList .= $str; - foreach ($aUserList as $u_name => $aNetworks) { - foreach ($aNetworks as $network => $status) { - $str = ''; - $len += strlen($str); - // max: 7500, but is 19, - // so we use 7475 - if ($len > 7475) { - $sList .= ''; - $this->aADL[$n] = ''.$sList.''; - $n++; - $sList = ''.$str; - $len = strlen($sList); - } - else - $sList .= $str; + $n = 0; + $sList = ''; + $len = 0; + if (is_array($this->aContactList)) { + foreach ($this->aContactList as $u_domain => $aUserList) { + $str = ''; + $len += strlen($str); + if ($len > 7400) { + $this->aADL[$n] = ''.$sList.''; + $n++; + $sList = ''; + $len = strlen($str); } + $sList .= $str; + foreach ($aUserList as $u_name => $aNetworks) { + foreach ($aNetworks as $network => $status) { + $str = ''; + $len += strlen($str); + // max: 7500, but is 19, + // so we use 7475 + if ($len > 7475) { + $sList .= ''; + $this->aADL[$n] = ''.$sList.''; + $n++; + $sList = ''.$str; + $len = strlen($sList); + } + else + $sList .= $str; + } + } + $sList .= ''; } - $sList .= ''; } - } - $this->aADL[$n] = ''.$sList.''; - // NS: >>> BLP {id} BL - $this->ns_writeln("BLP $this->id BL"); - foreach ($this->aADL as $str) { + $this->aADL[$n] = ''.$sList.''; + // NS: >>> BLP {id} BL + $this->ns_writeln("BLP $this->id BL"); + foreach ($this->aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + // NS: >>> PRP {id} MFN name + if ($this->alias == '') $this->alias = $user; + $aliasname = rawurlencode($this->alias); + $this->ns_writeln("PRP $this->id MFN $aliasname"); + //設定個人大頭貼 + //$MsnObj=$this->PhotoStckObj(); + // NS: >>> CHG {id} {status} {clientid} {msnobj} + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if($this->PhotoStickerFile!==false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + // NS: >>> UUX {id} length + $str = ''.htmlspecialchars($this->psm).''; $len = strlen($str); - // NS: >>> ADL {id} {size} - $this->ns_writeln("ADL $this->id $len"); + $this->ns_writeln("UUX $this->id $len"); $this->ns_writedata($str); + break; } - // NS: >>> PRP {id} MFN name - if ($this->alias == '') $this->alias = $user; - $aliasname = rawurlencode($this->alias); - $this->ns_writeln("PRP $this->id MFN $aliasname"); - //設定個人大頭貼 - //$MsnObj=$this->PhotoStckObj(); - // NS: >>> CHG {id} {status} {clientid} {msnobj} - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - // NS: >>> UUX {id} length - $str = ''.htmlspecialchars($this->psm).''; - $len = strlen($str); - $this->ns_writeln("UUX $this->id $len"); - $this->ns_writedata($str); } - private function signonFailed($message) { + /** + * Called if there is an error during signon + * + * @param $message Error message to log + */ + private function signonFailure($message) { $this->log_message($message); $this->callHandler('ConnectFailed', NULL); $this->NSRetryWait($this->retry_wait); From 2ef01c5b7468c269bcb5f1abc837687cc5644b21 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Mon, 14 Jun 2010 04:54:03 +0100 Subject: [PATCH 051/655] Removed Run method as all code has been moved into new methods --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 73f02f6d73..16ce72bde7 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -1097,7 +1097,7 @@ class MSN { $this->signonFailure("!!! Can't connect to server: $this->error"); } if($this->UpdateContacts() === false) { - $this->signonFailure('!!! Update Contacts failed'); + $this->signonFailure('!!! Update contacts failed'); continue; } $this->LastPing=time(); @@ -1105,6 +1105,7 @@ class MSN { $start_tm = time(); $ping_tm = time(); if(($this->aContactList = $this->getMembershipList()) === false) { + $this->signonFailure('!!! Get Membership list failed'); continue; } if ($this->update_pending) { From 0a4738a8060714fa7dc6dbd7af7f7f4d6b6c0c70 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Mon, 14 Jun 2010 19:53:43 +0100 Subject: [PATCH 052/655] Lots more work on adapting library. Added more commenting and fixed some stuff on the integration side. --- plugins/Msn/MsnPlugin.php | 362 ++--- plugins/Msn/extlib/phpmsnclass/msn.class.php | 1371 +++++------------- plugins/Msn/msnmanager.php | 341 +++-- 3 files changed, 716 insertions(+), 1358 deletions(-) diff --git a/plugins/Msn/MsnPlugin.php b/plugins/Msn/MsnPlugin.php index 5566b54302..9a9e703986 100644 --- a/plugins/Msn/MsnPlugin.php +++ b/plugins/Msn/MsnPlugin.php @@ -1,172 +1,190 @@ -. - * - * @category IM - * @package StatusNet - * @author Craig Andrews - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -if (!defined('STATUSNET')) { - // This check helps protect against security problems; - // your code file can't be executed directly from the web. - exit(1); -} -// We bundle the phptoclib library... -set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phptoclib'); - -/** - * Plugin for AIM - * - * @category Plugin - * @package StatusNet - * @author Craig Andrews - * @copyright 2009 StatusNet, Inc. - * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 - * @link http://status.net/ - */ - -class MsnPlugin extends ImPlugin -{ - public $user = null; - public $password = null; - public $publicFeed = array(); - - public $transport = 'msnim'; - - function getDisplayName() - { - return _m('MSN'); - } - - function normalize($screenname) - { - $screenname = str_replace(" ","", $screenname); - return strtolower($screenname); - } - - function daemon_screenname() - { - return $this->user; - } - - function validate($screenname) - { - if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { - return true; - }else{ - return false; - } - } - - /** - * Load related modules when needed - * - * @param string $cls Name of the class to be loaded - * - * @return boolean hook value; true means continue processing, false means stop. - */ - function onAutoload($cls) - { - $dir = dirname(__FILE__); - - switch ($cls) - { - case 'Msn': - require_once(INSTALLDIR.'/plugins/Msn/extlib/phpmsnclass/msn.class.php'); - return false; - case 'MsnManager': - include_once $dir . '/'.strtolower($cls).'.php'; - return false; - case 'Fake_Msn': - include_once $dir . '/'. $cls .'.php'; - return false; - default: - return true; - } - } - - function onStartImDaemonIoManagers(&$classes) - { - parent::onStartImDaemonIoManagers(&$classes); - $classes[] = new MsnManager($this); // handles sending/receiving - return true; - } - - function microiduri($screenname) - { - return 'msnim:' . $screenname; - } - - function send_message($screenname, $body) - { - //$this->fake_aim->sendIm($screenname, $body); - //$this->enqueue_outgoing_raw($this->fake_aim->would_be_sent); - $this->enqueue_outgoing_raw(array($screenname, $body)); - return true; - } - - /** - * Accept a queued input message. - * - * @return true if processing completed, false if message should be reprocessed - */ - function receive_raw_message($message) - { - $info=Aim::getMessageInfo($message); - $from = $info['from']; - $user = $this->get_user($from); - $notice_text = $info['message']; - - $this->handle_incoming($from, $notice_text); - - return true; - } - - function initialize(){ - if(!isset($this->user)){ - throw new Exception("must specify a user"); - } - if(!isset($this->password)){ - throw new Exception("must specify a password"); - } - if(!isset($this->nickname)) { - throw new Exception("must specify a nickname"); - } - - $this->fake_msn = new Fake_Msn($this->user,$this->password,4); - return true; - } - - function onPluginVersion(&$versions) - { - $versions[] = array('name' => 'MSN', - 'version' => STATUSNET_VERSION, - 'author' => 'Luke Fitzgerald', - 'homepage' => 'http://status.net/wiki/Plugin:MSN', - 'rawdescription' => - _m('The MSN plugin allows users to send and receive notices over the MSN network.')); - return true; - } -} +. + * + * @category IM + * @package StatusNet + * @author Luke Fitzgerald + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +if (!defined('STATUSNET')) { + // This check helps protect against security problems; + // your code file can't be executed directly from the web. + exit(1); +} +// We bundle the phpmsnclass library... +set_include_path(get_include_path() . PATH_SEPARATOR . dirname(__FILE__) . '/extlib/phpmsnclass'); + +/** + * Plugin for MSN + * + * @category Plugin + * @package StatusNet + * @author Luke Fitzgerald + * @copyright 2010 StatusNet, Inc. + * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 + * @link http://status.net/ + */ + +class MsnPlugin extends ImPlugin { + public $user = null; + public $password = null; + public $nickname = null; + public $transport = 'msnim'; + + /** + * Get the internationalized/translated display name of this IM service + * + * @return string Name of service + */ + function getDisplayName() { + return _m('MSN'); + } + + /** + * Normalize a screenname for comparison + * + * @param string $screenname screenname to normalize + * @return string an equivalent screenname in normalized form + */ + function normalize($screenname) { + $screenname = str_replace(" ","", $screenname); + return strtolower($screenname); + } + + /** + * Get the screenname of the daemon that sends and receives messages + * + * @return string Screenname + */ + function daemon_screenname() { + return $this->user; + } + + /** + * Validate (ensure the validity of) a screenname + * + * @param string $screenname screenname to validate + * + * @return boolean + */ + function validate($screenname) { + //TODO Correct this for MSN screennames + if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { + return true; + }else{ + return false; + } + } + + /** + * Load related modules when needed + * + * @param string $cls Name of the class to be loaded + * + * @return boolean hook value; true means continue processing, false means stop. + */ + public function onAutoload($cls) { + $dir = dirname(__FILE__); + + switch ($cls) { + case 'Msn': + require_once(INSTALLDIR.'/plugins/Msn/extlib/phpmsnclass/msn.class.php'); + return false; + case 'MsnManager': + include_once $dir . '/'.strtolower($cls).'.php'; + return false; + default: + return true; + } + } + + public function onStartImDaemonIoManagers(&$classes) { + parent::onStartImDaemonIoManagers(&$classes); + $classes[] = new MsnManager($this); // handles sending/receiving + return true; + } + + /** + * Get a microid URI for the given screenname + * + * @param string $screenname + * @return string microid URI + */ + public function microiduri($screenname) { + return 'msnim:' . $screenname; + } + + /** + * Send a message to a given screenname + * + * @param string $screenname Screenname to send to + * @param string $body Text to send + * @return boolean success value + */ + public function send_message($screenname, $body) { + $this->enqueue_outgoing_raw(array('to' => $screenname, 'message' => $body)); + return true; + } + + /** + * Accept a queued input message. + * + * @param array $data Data + * @return true if processing completed, false if message should be reprocessed + */ + public function receive_raw_message($data) { + $this->handle_incoming($data['sender'], $data['message']); + return true; + } + + /** + * Initialize plugin + * + * @return void + */ + public function initialize() { + if (!isset($this->user)) { + throw new Exception("Must specify a user"); + } + if (!isset($this->password)) { + throw new Exception("Must specify a password"); + } + if (!isset($this->nickname)) { + throw new Exception("Must specify a nickname"); + } + + return true; + } + + function onPluginVersion(&$versions) { + $versions[] = array('name' => 'MSN', + 'version' => STATUSNET_VERSION, + 'author' => 'Luke Fitzgerald', + 'homepage' => 'http://status.net/wiki/Plugin:MSN', + 'rawdescription' => + _m('The MSN plugin allows users to send and receive notices over the MSN network.')); + return true; + } +} diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 16ce72bde7..9b087ac792 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -33,33 +33,24 @@ class MSN { private $passport_policy = ''; private $alias; private $psm; - private $use_ping; private $retry_wait; - private $backup_file; private $update_pending; private $PhotoStickerFile=false; private $Emotions=false; - private $MessageQueue=array(); - private $ChildProcess=array(); - private $MAXChildProcess=3; private $ReqSBXFRTimeout=60; private $LastPing; private $ping_wait=50; private $SBIdleTimeout=10; private $SBStreamTimeout=2; - private $NSStreamTimeout=2; private $MsnObjArray=array(); private $MsnObjMap=array(); - private $SwitchBoardProcess=false; // false=>Main Process,1 => sb_control_process,2 => sb_ring_process - private $SwitchBoardSessionUser=false; - private $SwitchBoardMessageQueue=array(); private $ABAuthHeader; private $ABService; private $Contacts; private $IgnoreList; - public $server = 'messenger.hotmail.com'; - public $port = 1863; + private $server = 'messenger.hotmail.com'; + private $port = 1863; public $clientid = ''; @@ -104,49 +95,61 @@ class MSN { // for YIM: 518 bytes public $max_msn_message_len = 1664; public $max_yahoo_message_len = 518; - + // Begin added for StatusNet - + private $aContactList = array(); private $aADL = array(); - private $re_login; private $switchBoardSessions = array(); private $switchBoardSockets = array(); private $waitingForXFR = array(); - + /** * Event Handler Functions */ private $myEventHandlers = array(); - + // End added for StatusNet - + + /** + * Constructor method + * + * @param array $Configs Array of configuration options + * 'user' - Username + * 'password' - Password + * 'alias' - Bot nickname + * 'psm' - Bot personal status message + * 'retry_wait' - Time to wait before trying to reconnect + * 'update_pending' - Whether to update pending contacts + * 'PhotoSticker' - Photo file to use (?) + * 'debug' - Enable/Disable debugging mode + * @param integer $timeout Connection timeout + * @param integer $client_id Client id (hexadecimal) + * @return MSN + */ public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C) { $this->user = $Configs['user']; $this->password = $Configs['password']; $this->alias = isset($Configs['alias']) ? $Configs['alias'] : ''; $this->psm = isset($Configs['psm']) ? $Configs['psm'] : ''; - $this->use_ping = isset($Configs['use_ping']) ? $Configs['use_ping'] : false; $this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30; - $this->backup_file = isset($Configs['backup_file']) ? $Configs['backup_file'] : true; $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true; $this->PhotoStickerFile=isset($Configs['PhotoSticker']) ? $Configs['PhotoSticker'] : false; - if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) - { + + if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) { foreach($this->Emotions as $EmotionFilePath) $this->MsnObj($EmotionFilePath,$Type=2); - } + } $this->debug = isset($Configs['debug']) ? $Configs['debug'] : false; $this->timeout = $timeout; - - // check support - if (!function_exists('curl_init')) throw new Exception("We need curl module!\n"); - if (!function_exists('preg_match')) throw new Exception("We need pcre module!\n"); - if (!function_exists('mhash')) throw new Exception("We need mhash module!\n"); - if (!function_exists('mcrypt_cbc')) throw new Exception("We need mcrypt module!\n"); - if (!function_exists('bcmod')) throw new Exception("We need bcmath module for $protocol!\n"); + // Check support + if (!function_exists('curl_init')) throw new Exception("curl module not found!\n"); + if (!function_exists('preg_match')) throw new Exception("pcre module not found!\n"); + if (!function_exists('mhash')) throw new Exception("mhash module not found!\n"); + if (!function_exists('mcrypt_cbc')) throw new Exception("mcrypt module not found!\n"); + if (!function_exists('bcmod')) throw new Exception("bcmath module not found!\n"); /* http://msnpiki.msnfanatic.com/index.php/Client_ID @@ -198,7 +201,13 @@ class MSN { if($ReturnSoapVarObj) return new SoapVar($ArrayString,XSD_ANYXML,$TypeName,$TypeNameSpace); return $ArrayString; } - + + /** + * Get Passport ticket + * + * @param string $url URL string (Optional) + * @return mixed Array of tickets or false on failure + */ private function get_passport_ticket($url = '') { $user = $this->user; @@ -427,7 +436,7 @@ class MSN { 'oim_ticket' => html_entity_decode($matches[9]), 'space_ticket' => html_entity_decode($matches[11]), 'storage_ticket' => html_entity_decode($matches[13]) - ); + ); $this->ticket=$aTickets; $this->debug_message(var_export($aTickets, true)); $ABAuthHeaderArray=array( @@ -440,7 +449,7 @@ class MSN { $this->ABAuthHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook","ABAuthHeader", $this->Array2SoapVar($ABAuthHeaderArray)); return $aTickets; } - + private function UpdateContacts() { $ABApplicationHeaderArray=array( @@ -451,7 +460,7 @@ class MSN { 'PartnerScenario'=>'ContactSave' ) ); - + $ABApplicationHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook",'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); $ABFindAllArray=array( 'ABFindAll'=>array( @@ -479,7 +488,7 @@ class MSN { } return true; } - + private function addContact($email, $network, $display = '', $sendADL = false) { if ($network != 1) return true; @@ -532,7 +541,8 @@ class MSN { return true; } - function delMemberFromList($memberID, $email, $network, $list) { + function delMemberFromList($memberID, $email, $network, $list) + { if ($network != 1 && $network != 32) return true; if ($memberID === false) return true; $user = $email; @@ -641,23 +651,24 @@ class MSN { if ($http_code != 200) { preg_match('#(.*)(.*)#', $data, $matches); if (count($matches) == 0) { - $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list"); + $this->debug_message("*** can't delete member (network: $network) $email ($memberID) to $list"); return false; } $faultcode = trim($matches[1]); $faultstring = trim($matches[2]); if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) { - $this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring"); + $this->debug_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring"); return false; } - $this->log_message("*** delete member (network: $network) $email ($memberID) from $list, not exist"); + $this->debug_message("*** delete member (network: $network) $email ($memberID) from $list, not exist"); return true; } - $this->log_message("*** delete member (network: $network) $email ($memberID) from $list"); + $this->debug_message("*** delete member (network: $network) $email ($memberID) from $list"); return true; } - function addMemberToList($email, $network, $list) { + function addMemberToList($email, $network, $list) + { if ($network != 1 && $network != 32) return true; $ticket = htmlspecialchars($this->ticket['contact_ticket']); $user = $email; @@ -771,23 +782,24 @@ class MSN { if ($http_code != 200) { preg_match('#(.*)(.*)#', $data, $matches); if (count($matches) == 0) { - $this->log_message("*** can't add member (network: $network) $email to $list"); + $this->debug_message("*** can't add member (network: $network) $email to $list"); return false; } $faultcode = trim($matches[1]); $faultstring = trim($matches[2]); if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) { - $this->log_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring"); + $this->debug_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring"); return false; } - $this->log_message("*** add member (network: $network) $email to $list, already exist!"); + $this->debug_message("*** add member (network: $network) $email to $list, already exist!"); return true; } - $this->log_message("*** add member (network: $network) $email to $list"); + $this->debug_message("*** add member (network: $network) $email to $list"); return true; } - function getMembershipList($returnData=false) { + function getMembershipList($returnData=false) + { $ticket = htmlspecialchars($this->ticket['contact_ticket']); $XML = ' log_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); + $this->debug_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); } } } @@ -921,10 +933,11 @@ class MSN { /** * Connect to the NS server - * @param $user Username - * @param $password Password - * @param $redirect_server Redirect server - * @param $redirect_port Redirect port + * @param String $user Username + * @param String $password Password + * @param String $redirect_server Redirect server + * @param Integer $redirect_port Redirect port + * @return Boolean Returns true if successful */ private function connect($user, $password, $redirect_server = '', $redirect_port = 1863) { $this->id = 1; @@ -943,7 +956,7 @@ class MSN { } } - stream_set_timeout($this->NSfp, $this->NSStreamTimeout); + stream_set_timeout($this->NSfp, $this->timeout); $this->authed = false; // MSNP9 // NS: >> VER {id} MSNP9 CVR0 @@ -952,26 +965,20 @@ class MSN { $this->ns_writeln("VER $this->id $this->protocol CVR0"); $start_tm = time(); - while (!feof($this->NSfp)) + while (!self::socketcheck($this->NSfp)) { $data = $this->ns_readln(); // no data? if ($data === false) { - if ($this->timeout > 0) { - $now_tm = time(); - $used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm; - if ($used_time > $this->timeout) { - // logout now - // NS: >>> OUT - $this->ns_writeln("OUT"); - fclose($this->NSfp); - $this->error = 'Timeout, maybe protocol changed!'; - $this->debug_message("*** $this->error"); - return false; - } - } - continue; + // logout now + // NS: >>> OUT + $this->ns_writeln("OUT"); + @fclose($this->NSfp); + $this->error = 'Timeout, maybe protocol changed!'; + $this->debug_message("*** $this->error"); + return false; } + $code = substr($data, 0, 3); $start_tm = time(); @@ -1015,7 +1022,7 @@ class MSN { // logout now // NS: >>> OUT $this->ns_writeln("OUT"); - fclose($this->NSfp); + @fclose($this->NSfp); $this->error = 'Passport authenticated fail!'; $this->debug_message("*** $this->error"); return false; @@ -1041,7 +1048,7 @@ class MSN { if($Type!='NS') break; @list($ip, $port) = @explode(':', $server); // this connection will close after XFR - fclose($this->NSfp); + @fclose($this->NSfp); $this->NSfp = @fsockopen($ip, $port, $errno, $errstr, 5); if (!$this->NSfp) { @@ -1050,7 +1057,7 @@ class MSN { return false; } - stream_set_timeout($this->NSfp, $this->NSStreamTimeout); + stream_set_timeout($this->NSfp, $this->timeout); // MSNP9 // NS: >> VER {id} MSNP9 CVR0 // MSNP15 @@ -1073,7 +1080,7 @@ class MSN { // logout now // NS: >>> OUT $this->ns_writeln("OUT"); - fclose($this->NSfp); + @fclose($this->NSfp); $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; $this->debug_message("*** $this->error"); return false; @@ -1087,13 +1094,14 @@ class MSN { /** * Sign onto the NS server and retrieve the address book + * + * @return void */ public function signon() { - $this->log_message("*** try to connect to MSN network"); - + $this->debug_message("*** try to connect to MSN network"); + while(true) { - while(!$this->connect($this->user, $this->password)) - { + while(!$this->connect($this->user, $this->password)) { $this->signonFailure("!!! Can't connect to server: $this->error"); } if($this->UpdateContacts() === false) { @@ -1101,11 +1109,10 @@ class MSN { continue; } $this->LastPing=time(); - $this->log_message("*** connected, wait for command"); $start_tm = time(); $ping_tm = time(); if(($this->aContactList = $this->getMembershipList()) === false) { - $this->signonFailure('!!! Get Membership list failed'); + $this->signonFailure('!!! Get membership list failed'); continue; } if ($this->update_pending) { @@ -1206,21 +1213,26 @@ class MSN { $len = strlen($str); $this->ns_writeln("UUX $this->id $len"); $this->ns_writedata($str); - break; + if(!socketcheck($this->NSfp)) { + $this->debug_message("*** connected, wait for command"); + break; + } else { + $this->NSRetryWait($this->retry_wait); + } } } - + /** * Called if there is an error during signon - * - * @param $message Error message to log + * + * @param string $message Error message to log */ private function signonFailure($message) { - $this->log_message($message); - $this->callHandler('ConnectFailed', NULL); + $this->debug_message($message); + $this->callHandler('ConnectFailed'); $this->NSRetryWait($this->retry_wait); } - + function derive_key($key, $magic) { $hash1 = mhash(MHASH_SHA1, $magic, $key); $hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key); @@ -1441,6 +1453,7 @@ class MSN { $this->debug_message("*** OIM ($msgid) deleted"); return $sMsg; } + private function NSLogout() { if (is_resource($this->NSfp) && !feof($this->NSfp)) { // logout now @@ -1448,753 +1461,14 @@ class MSN { $this->ns_writeln("OUT"); fclose($this->NSfp); $this->NSfp = false; - $this->log_message("*** logout now!"); + $this->debug_message("*** logout now!"); } } - private function NSRetryWait($Wait) { - $this->log_message("*** wait for $Wait seconds"); - for($i=0;$i<$Wait;$i++) { - sleep(1); - if($this->kill_me) return false; - } - return true; - } - public function ProcessSendMessageFileQueue() { - $aFiles = glob(MSN_CLASS_SPOOL_DIR.DIRECTORY_SEPARATOR.'*.msn'); - if (!is_array($aFiles)) return true; - clearstatcache(); - foreach ($aFiles as $filename) { - $fp = fopen($filename, 'rt'); - if (!$fp) continue; - $aTo = array(); - $sMessage = ''; - $buf = trim(fgets($fp)); - if (substr($buf, 0, 3) == 'TO:') { - $aTo = @explode(',', str_replace(array("\r","\n","\t",' '),'',substr($buf, 3))); - while (!feof($fp)) $sMessage.=rtrim(fgets($fp))."\n"; - } - fclose($fp); - if (!is_array($aTo) || count($aTo) == 0 || $sMessage == '') - $this->log_message("!!! message format error? delete $filename"); - else - { - foreach($aTo as $To) - { - @list($user, $domain, $network) = @explode('@', $To); - $MessageList[$network]["$user@$domain"]=$sMessage; - } - } - if($this->backup_file) - { - $backup_dir = MSN_CLASS_SPOOL_DIR.'/backup'; - if (!file_exists($backup_dir)) @mkdir($backup_dir); - $backup_name = $backup_dir.'/'.strftime('%Y%m%d%H%M%S').'_'.posix_getpid().'_'.basename($filename); - if (@rename($filename, $backup_name)) - $this->log_message("*** move file to $backup_name"); - } - else @unlink($filename); - } - foreach ($MessageList as $network => $Messages) - { - switch(trim($network)) - { - case '': - case 1: //MSN - // okay, try to ask a switchboard (SB) for sending message - // NS: >>> XFR {id} SB - // $this->ns_writeln("XFR $this->id SB"); - foreach($Messages as $User => $Message) - $this->MessageQueue[$User][]=$Message; - break; - case 'Offline': //MSN - //Send OIM - //FIXME: 修正Send OIM - foreach($Messages as $To => $Message) - { - $lockkey=''; - for ($i = 0; $i < $this->oim_try; $i++) - { - if(($oim_result = $this->sendOIM($To, $Message, $lockkey))===true) break; - if (is_array($oim_result) && $oim_result['challenge'] !== false) { - // need challenge lockkey - $this->log_message("*** we need a new challenge code for ".$oim_result['challenge']); - $lockkey = $this->getChallenge($oim_result['challenge']); - continue; - } - if ($oim_result === false || $oim_result['auth_policy'] !== false) - { - if ($this->re_login) - { - $this->log_message("*** can't send OIM, but we already re-login again, so ignore this OIM"); - break; - } - $this->log_message("*** can't send OIM, maybe ticket expired, try to login again"); - // maybe we need to re-login again - if(!$this->get_passport_ticket()) - { - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); - break; - } - $this->log_message("**** get new ticket, try it again"); - continue; - } - } - } - break; - default: //Other - foreach($Messages as $To => $Message) { - $Message=$this->getMessage($Message, $network); - $len = strlen($Message); - $this->ns_writeln("UUM $this->id $To $network 1 $len"); - $this->ns_writedata($Message); - $this->log_message("*** sent to $To (network: $network):\n$Message"); - } - } - } - if(isset($this->MessageQueue[$User])&&(!isset($this->MessageQueue[$User]['XFRSent']))) - { - $this->MessageQueue[$User]['XFRSent']=false; - $this->MessageQueue[$User]['ReqTime']=false; - } - return true; - } - public function SignalFunction($signal) - { - switch($signal) - { - case SIGTRAP: - case SIGTERM: - case SIGHUP: - $this->End(); - return; - case SIGCHLD: - $ChildPid=pcntl_wait($status,WUNTRACED); - if($ChildPid>0) - { - $this->log_message("*** Child Process End for ".$this->ChildProcess[$ChildPid]); - unset($this->ChildProcess[$ChildPid]); - } - return; - } - } - public function Run() - { - $this->log_message("*** startup ***"); - if(!pcntl_signal(SIGCHLD,array($this,'SignalFunction'))) die("Signal SIGCHLD Error\n"); - if(!pcntl_signal(SIGTERM,array($this,'SignalFunction'))) die("Signal SIGTERM Error\n"); - if(!pcntl_signal(SIGTRAP,array($this,'SignalFunction'))) die("Signal SIGTRAP Error\n"); - $process_file = false; - $sent = false; - $aADL = array(); - $aContactList = array(); - while (true) - { - if($this->kill_me) - { - $this->log_message("*** Okay, kill me now!"); - return $this->NSLogout(); - } - if (!is_resource($this->NSfp) || feof($this->NSfp)) - { - $this->log_message("*** try to connect to MSN network"); - if (!$this->connect($this->user, $this->password)) - { - $this->log_message("!!! Can't connect to server: $this->error"); - if(!$this->NSRetryWait($this->retry_wait)) continue; - } - $this->UpdateContacts(); - $this->LastPing=time(); - $this->log_message("*** connected, wait for command"); - $start_tm = time(); - $ping_tm = time(); - $aContactList = $this->getMembershipList(); - if ($this->update_pending) { - if (is_array($aContactList)) { - $pending = 'Pending'; - foreach ($aContactList as $u_domain => $aUserList) { - foreach ($aUserList as $u_name => $aNetworks) { - foreach ($aNetworks as $network => $aData) { - if (isset($aData[$pending])) { - // pending list - $cnt = 0; - foreach (array('Allow', 'Reverse') as $list) { - if (isset($aData[$list])) - $cnt++; - else { - if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - $aContactList[$u_domain][$u_name][$network][$list] = false; - $cnt++; - } - } - } - if ($cnt >= 2) { - $id = $aData[$pending]; - // we can delete it from pending now - if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending)) - unset($aContactList[$u_domain][$u_name][$network][$pending]); - } - } - else { - // sync list - foreach (array('Allow', 'Reverse') as $list) { - if (!isset($aData[$list])) { - if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) - $aContactList[$u_domain][$u_name][$network][$list] = false; - } - } - } - } - } - } - } - } - $n = 0; - $sList = ''; - $len = 0; - if (is_array($aContactList)) { - foreach ($aContactList as $u_domain => $aUserList) { - $str = ''; - $len += strlen($str); - if ($len > 7400) { - $aADL[$n] = ''.$sList.''; - $n++; - $sList = ''; - $len = strlen($str); - } - $sList .= $str; - foreach ($aUserList as $u_name => $aNetworks) { - foreach ($aNetworks as $network => $status) { - $str = ''; - $len += strlen($str); - // max: 7500, but is 19, - // so we use 7475 - if ($len > 7475) { - $sList .= ''; - $aADL[$n] = ''.$sList.''; - $n++; - $sList = ''.$str; - $len = strlen($sList); - } - else - $sList .= $str; - } - } - $sList .= ''; - } - } - $aADL[$n] = ''.$sList.''; - // NS: >>> BLP {id} BL - $this->ns_writeln("BLP $this->id BL"); - foreach ($aADL as $str) { - $len = strlen($str); - // NS: >>> ADL {id} {size} - $this->ns_writeln("ADL $this->id $len"); - $this->ns_writedata($str); - } - // NS: >>> PRP {id} MFN name - if ($this->alias == '') $this->alias = $user; - $aliasname = rawurlencode($this->alias); - $this->ns_writeln("PRP $this->id MFN $aliasname"); - //設定個人大頭貼 - //$MsnObj=$this->PhotoStckObj(); - // NS: >>> CHG {id} {status} {clientid} {msnobj} - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - // NS: >>> UUX {id} length - $str = ''.htmlspecialchars($this->psm).''; - $len = strlen($str); - $this->ns_writeln("UUX $this->id $len"); - $this->ns_writedata($str); - } - $data = $this->ns_readln(); - if($data===false) - { - //If No NS Message Process SendMessageFileQueue - if (time()-$this->LastPing > $this->ping_wait) - { - // NS: >>> PNG - $this->ns_writeln("PNG"); - $this->LastPing = time(); - } - if(count($this->ChildProcess)<$this->MAXChildProcess) - { - $Index=0; - foreach($this->MessageQueue as $User => $Message) - { - if(!trim($User)) continue; - if($Inxdex>=$this->MAXChildProcess-count($this->ChildProcess)) break; - if((!$Message['XFRSent'])||($Message['XFRSent']&&(time()-$this->MessageQueue[$User]['ReqTime']>$this->ReqSBXFRTimeout))) - { - $this->MessageQueue[$User]['XFRSent']=true; - $this->MessageQueue[$User]['ReqTime']=time(); - $this->log_message("*** Request SB for $User"); - $this->ns_writeln("XFR $this->id SB"); - $Index++; - } - } - } - if($this->ProcessSendMessageFileQueue()) continue; - break; - } - switch (substr($data,0,3)) - { - case 'SBS': - // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us - // NS: <<< SBS 0 null - break; - - case 'RFS': - // FIXME: - // NS: <<< RFS ??? - // refresh ADL, so we re-send it again - if (is_array($aADL)) { - foreach ($aADL as $str) { - $len = strlen($str); - // NS: >>> ADL {id} {size} - $this->ns_writeln("ADL $this->id $len"); - $this->ns_writedata($str); - } - } - break; - - case 'LST': - // NS: <<< LST {email} {alias} 11 0 - @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data); - @list($u_name, $u_domain) = @explode('@', $email); - if (!isset($aContactList[$u_domain][$u_name][1])) { - $aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; - $this->log_message("*** add to our contact list: $u_name@$u_domain"); - } - break; - - case 'ADL': - // randomly, we get ADL command, someome add us to their contact list for MSNP15 - // NS: <<< ADL 0 {size} - @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { - $data = $this->ns_readdata($size); - preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) - { - $u_domain = $matches[1]; - $u_name = $matches[2]; - $network = $matches[4]; - if (isset($aContactList[$u_domain][$u_name][$network])) - $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); - else - { - $this->re_login = false; - $cnt = 0; - foreach (array('Allow', 'Reverse') as $list) - { - if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) - { - if ($this->re_login) { - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); - continue; - } - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here"); - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); - continue; - } - $this->re_login = true; - $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); - if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) - { - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); - continue; - } - } - $aContactList[$u_domain][$u_name][$network][$list] = false; - $cnt++; - } - $this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); - } - $str = ''; - $len = strlen($str); - } - else - $this->log_message("*** someone add us to their list: $data"); - $this->AddUsToMemberList($u_name.'@'.$u_domain, $network); - } - break; - - case 'RML': - // randomly, we get RML command, someome remove us to their contact list for MSNP15 - // NS: <<< RML 0 {size} - @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { - $data = $this->ns_readdata($size); - preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) - { - $u_domain = $matches[1]; - $u_name = $matches[2]; - $network = $matches[4]; - if (isset($aContactList[$u_domain][$u_name][$network])) - { - $aData = $aContactList[$u_domain][$u_name][$network]; - foreach ($aData as $list => $id) - $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); - unset($aContactList[$u_domain][$u_name][$network]); - $this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); - } - else - $this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); - $this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network); - } - else - $this->log_message("*** someone remove us from their list: $data"); - } - break; - - case 'MSG': - // randomly, we get MSG notification from server - // NS: <<< MSG Hotmail Hotmail {size} - @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) { - $data = $this->ns_readdata($size); - $aLines = @explode("\n", $data); - $header = true; - $ignore = false; - $maildata = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } - if (strncasecmp($line, 'Content-Type:', 13) == 0) { - if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && - strpos($line, 'text/x-msmsgsoimnotification') === false) { - // we just need text/x-msmsgsinitialmdatanotification - // or text/x-msmsgsoimnotification - $ignore = true; - break; - } - } - continue; - } - if (strncasecmp($line, 'Mail-Data:', 10) == 0) { - $maildata = trim(substr($line, 10)); - break; - } - } - if ($ignore) { - $this->log_message("*** ingnore MSG for: $line"); - break; - } - if ($maildata == '') { - $this->log_message("*** ingnore MSG not for OIM"); - break; - } - $this->re_login = false; - if (strcasecmp($maildata, 'too-large') == 0) { - $this->log_message("*** large mail-data, need to get the data via SOAP"); - $maildata = $this->getOIM_maildata(); - if ($maildata === false) { - $this->log_message("*** can't get mail-data via SOAP"); - // maybe we need to re-login again - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); - break; - } - $this->re_login = true; - $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); - $maildata = $this->getOIM_maildata(); - if ($maildata === false) { - $this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); - break; - } - } - } - // could be a lots of ..., so we can't use preg_match here - $p = $maildata; - $aOIMs = array(); - while (1) { - $start = strpos($p, ''); - $end = strpos($p, ''); - if ($start === false || $end === false || $start > $end) break; - $end += 4; - $sOIM = substr($p, $start, $end - $start); - $aOIMs[] = $sOIM; - $p = substr($p, $end); - } - if (count($aOIMs) == 0) { - $this->log_message("*** ingnore empty OIM"); - break; - } - foreach ($aOIMs as $maildata) { - // T: 11 for MSN, 13 for Yahoo - // S: 6 for MSN, 7 for Yahoo - // RT: the datetime received by server - // RS: already read or not - // SZ: size of message - // E: sender - // I: msgid - // F: always 00000000-0000-0000-0000-000000000009 - // N: sender alias - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without type"); - continue; - } - $oim_type = $matches[1]; - if ($oim_type = 13) - $network = 32; - else - $network = 1; - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without sender"); - continue; - } - $oim_sender = $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without msgid"); - continue; - } - $oim_msgid = $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - $oim_size = (count($matches) == 0) ? 0 : $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - $oim_time = (count($matches) == 0) ? 0 : $matches[1]; - $this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); - $sMsg = $this->getOIM_message($oim_msgid); - if ($sMsg === false) { - $this->log_message("*** can't get OIM, msgid = $oim_msgid"); - if ($this->re_login) { - $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); - continue; - } - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); - continue; - } - $this->re_login = true; - $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); - $sMsg = $this->getOIM_message($oim_msgid); - if ($sMsg === false) { - $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); - continue; - } - } - $this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); - - $this->ReceivedMessage($oim_sender,$sMsg,$network,true); - } - } - break; - - case 'UBM': - // randomly, we get UBM, this is the message from other network, like Yahoo! - // NS: <<< UBM {email} $network $type {size} - @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { - $data = $this->ns_readdata($size); - $aLines = @explode("\n", $data); - $header = true; - $ignore = false; - $sMsg = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } - if (strncasecmp($line, 'TypingUser:', 11) == 0) { - $ignore = true; - break; - } - continue; - } - $aSubLines = @explode("\r", $line); - foreach ($aSubLines as $str) { - if ($sMsg !== '') - $sMsg .= "\n"; - $sMsg .= $str; - } - } - if($ignore) - { - $this->log_message("*** ingnore from $from_email: $line"); - break; - } - $this->log_message("*** MSG from $from_email (network: $network): $sMsg"); - $this->ReceivedMessage($from_email,$sMsg,$network,false); - } - break; - - case 'UBX': - // randomly, we get UBX notification from server - // NS: <<< UBX email {network} {size} - @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); - // we don't need the notification data, so just ignore it - if (is_numeric($size) && $size > 0) - $this->ns_readdata($size); - break; - - case 'CHL': - // randomly, we'll get challenge from server - // NS: <<< CHL 0 {code} - @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data); - $fingerprint = $this->getChallenge($chl_code); - // NS: >>> QRY {id} {product_id} 32 - // NS: >>> fingerprint - $this->ns_writeln("QRY $this->id $this->prod_id 32"); - $this->ns_writedata($fingerprint); - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - break; - case 'CHG': - // NS: <<< CHG {id} {status} {code} - // ignore it - // change our status to online first - break; - - case 'XFR': - // sometimes, NS will redirect to another NS - // MSNP9 - // NS: <<< XFR {id} NS {server} 0 {server} - // MSNP15 - // NS: <<< XFR {id} NS {server} U D - // for normal switchboard XFR - // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0 - @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data); - @list($ip, $port) = @explode(':', $server); - if ($server_type != 'SB') { - // maybe exit? - // this connection will close after XFR - $this->NSLogout(); - continue; - } - if(count($this->MessageQueue)) - { - foreach($this->MessageQueue as $User => $Message) - { - //$this->ChildProcess[$ChildPid] - $this->log_message("*** XFR SB $User"); - $pid=pcntl_fork(); - if($pid) - { - //Parrent Process - $this->ChildProcess[$pid]=$User; - break; - } - elseif($pid==-1) - { - $this->log_message("*** Fork Error $User"); - break; - } - else - { - //Child Process - $this->log_message("*** Child Process Start for $User"); - unset($Message['XFRSent']); - unset($Message['ReqTime']); - $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); - if ($bSBresult === false) - { - // error for switchboard - $this->log_message("!!! error for sending message to ".$User); - } - die; - } - } - unset($this->MessageQueue[$User]); - } - /* - $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); - if ($bSBresult === false) { - // error for switchboard - $this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); - $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; - }*/ - break; - case 'QNG': - // NS: <<< QNG {time} - @list(/* QNG */, $this->ping_wait) = @explode(' ', $data); - if ($this->ping_wait == 0) $this->ping_wait = 50; - //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; - //Mod by Ricky Set Online - break; - - case 'RNG': - if($this->PhotoStickerFile!==false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - else - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - // someone is trying to talk to us - // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 - $this->log_message("NS: <<< RNG $data"); - @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); - @list($sb_ip, $sb_port) = @explode(':', $server); - if($this->IsIgnoreMail($email)) - { - $this->log_message("*** Ignore RNG from $email"); - break; - } - $this->log_message("*** RING from $email, $sb_ip:$sb_port"); - $this->addContact($email,1,$email, true); - $pid=pcntl_fork(); - if($pid) - { - //Parrent Process - $this->ChildProcess[$pid]='RNG'; - break; - } - elseif($pid==-1) - { - $this->log_message("*** Fork Error $User"); - break; - } - else - { - //Child Process - $this->log_message("*** Ring Child Process Start for $User"); - $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email); - die; - } - break; - case 'OUT': - // force logout from NS - // NS: <<< OUT xxx - fclose($this->NSfp); - $this->log_message("*** LOGOUT from NS"); - break; - - default: - $code = substr($data,0,3); - if (is_numeric($code)) { - $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; - $this->debug_message("*** NS: $this->error"); - - return $this->NsLogout(); - } - break; - } - } - return $this->NsLogout(); + private function NSRetryWait($wait) { + $this->debug_message("*** wait for $Wait seconds"); + sleep($wait); } function getChallenge($code) @@ -2323,7 +1597,7 @@ class MSN { $data = $this->SB_readln(); if($this->kill_me) { - $this->log_message("*** SB Okay, kill me now!"); + $this->debug_message("*** SB Okay, kill me now!"); break; } if($data === false) @@ -2362,11 +1636,11 @@ class MSN { case 'IRO': // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid} @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data); - $this->log_message("*** $email join us"); + $this->debug_message("*** $email join us"); $Joined=true; break; case 'BYE': - $this->log_message("*** Quit for BYE"); + $this->debug_message("*** Quit for BYE"); $SessionEnd=true; break; case 'USR': @@ -2453,7 +1727,7 @@ class MSN { } if ($ignore) { - $this->log_message("*** ingnore from $from_email: $line"); + $this->debug_message("*** ingnore from $from_email: $line"); break; } if ($is_p2p) @@ -2461,18 +1735,18 @@ class MSN { // we will ignore any p2p message after sending acknowledgement $ignore = true; $len = strlen($sMsg); - $this->log_message("*** p2p message from $from_email, size $len"); + $this->debug_message("*** p2p message from $from_email, size $len"); // header = 48 bytes // content >= 0 bytes // footer = 4 bytes // so it need to >= 52 bytes /*if ($len < 52) { - $this->log_message("*** p2p: size error, less than 52!"); + $this->debug_message("*** p2p: size error, less than 52!"); break; }*/ $aDwords = @unpack("V12dword", $sMsg); if (!is_array($aDwords)) { - $this->log_message("*** p2p: header unpack error!"); + $this->debug_message("*** p2p: header unpack error!"); break; } $this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg)); @@ -2532,9 +1806,9 @@ class MSN { $len = strlen($message); $this->SB_writeln("MSG $this->id D $len"); $this->SB_writedata($message); - $this->log_message("*** p2p: send display picture acknowledgement for $hdr_SessionID"); - $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message)); - $this->SB_readln();//Read ACK; + $this->debug_message("*** p2p: send display picture acknowledgement for $hdr_SessionID"); + $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message)); + $this->SB_readln();//Read ACK; $this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr)); $new_id-=3; //Send 200 OK message @@ -2570,7 +1844,7 @@ class MSN { $this->SB_writedata($message); $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message)); $this->SB_readln();//Read ACK; - + $this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr)); //send Data preparation message //send 4 null bytes as data @@ -2639,7 +1913,7 @@ class MSN { "BYE MSNMSGR:MSNSLP/1.0\r\n". "To: \r\n". "From: user.">\r\n". - "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n". + "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n". "CSeq: 0\r\n". "Call-ID: ".$MsgBody['Call-ID']."\r\n". "Max-Forwards: 0\r\n". @@ -2649,7 +1923,7 @@ class MSN { $hdr_TotalDataSizeLow=strlen($MessagePayload); $hdr_TotalDataSizeHigh=0; $new_id++; - $hdr = pack("LLLLLLLLLLLL", + $hdr = pack("LLLLLLLLLLLL", 0, $new_id, 0, 0, @@ -2721,16 +1995,16 @@ class MSN { $this->SB_writeln("MSG $id D $len"); $id++; $this->SB_writedata($message); - $this->log_message("*** p2p: send acknowledgement for $hdr_SessionID"); + $this->debug_message("*** p2p: send acknowledgement for $hdr_SessionID"); $this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer)); */ break; } - $this->log_message("*** MSG from $from_email: $sMsg"); + $this->debug_message("*** MSG from $from_email: $sMsg"); $this->ReceivedMessage($from_email,$sMsg,$network,false); break; case '217': - $this->log_message("*** User $user is offline. Try OIM."); + $this->debug_message("*** User $user is offline. Try OIM."); foreach($this->SwitchBoardMessageQueue as $Message) $this->SendMessage($Message,"$user@Offline"); $SessionEnd=true; @@ -2902,21 +2176,10 @@ class MSN { return $buf; } - // write log - function log_message($str) { - /*$fname = MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msn_'.strftime('%Y%m%d').'.log'; - $fp = fopen($fname, 'at'); - if ($fp) { - fputs($fp, strftime('%m/%d/%y %H:%M:%S').' ['.posix_getpid().'] '.$str."\n"); - fclose($fp); - }*/ - $this->debug_message($str); - return; - } /** * * @param $FilePath 圖檔路徑 - * @param $Type 檔案類型 3=>大頭貼,2表情圖案 + * @param $Type 檔案類型 3=>大頭貼,2表情圖案 * @return array */ private function MsnObj($FilePath,$Type=3) @@ -2933,7 +2196,7 @@ class MSN { $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n"); return $MsnObj; } - + private function linetoArray($lines) { $lines=str_replace("\r",'',$lines); $lines=explode("\n",$lines); @@ -2944,7 +2207,7 @@ class MSN { } return $Data; } - + private function GetPictureFilePath($Context) { $MsnObj=base64_decode($Context); @@ -2955,7 +2218,7 @@ class MSN { return $this->MsnObjArray[$location]; return false; } - + private function GetMsnObjDefine($Message) { $DefineString=''; @@ -2967,23 +2230,26 @@ class MSN { } return $DefineString; } - + /** * Read and handle incoming command from NS */ - public function nsReceive() { + private function nsReceive() { // Sign in again if not signed in or socket failed - if (!is_resource($this->NSfp) || feof($this->NSfp)) { - $this->callHandler('Reconnect', NULL); + if (!is_resource($this->NSfp) || self::socketcheck($this->NSfp)) { + $this->callHandler('Reconnect'); + $this->NSRetryWait($this->retry_wait); $this->signon(); return; } - + $data = $this->ns_readln(); if($data === false) { // There was no data / an error when reading from the socket so reconnect - $this->callHandler('Reconnect', NULL); + $this->callHandler('Reconnect'); + $this->NSRetryWait($this->retry_wait); $this->signon(); + return; } else { switch (substr($data,0,3)) { @@ -2991,7 +2257,7 @@ class MSN { // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us // NS: <<< SBS 0 null break; - + case 'RFS': // FIXME: // NS: <<< RFS ??? @@ -3005,17 +2271,17 @@ class MSN { } } break; - + case 'LST': // NS: <<< LST {email} {alias} 11 0 @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data); @list($u_name, $u_domain) = @explode('@', $email); if (!isset($this->aContactList[$u_domain][$u_name][1])) { $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; - $this->log_message("*** add to our contact list: $u_name@$u_domain"); + $this->debug_message("*** add to our contact list: $u_name@$u_domain"); } break; - + case 'ADL': // randomly, we get ADL command, someome add us to their contact list for MSNP15 // NS: <<< ADL 0 {size} @@ -3030,49 +2296,48 @@ class MSN { $u_name = $matches[2]; $network = $matches[4]; if (isset($this->aContactList[$u_domain][$u_name][$network])) - $this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); - else - { - $this->re_login = false; + $this->debug_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); + else { + $re_login = false; $cnt = 0; foreach (array('Allow', 'Reverse') as $list) { if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - if ($this->re_login) { - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + if ($re_login) { + $this->debug_message("*** can't add $u_name@$u_domain (network: $network) to $list"); continue; } $aTickets = $this->get_passport_ticket(); if (!$aTickets || !is_array($aTickets)) { // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here"); - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + $this->debug_message("*** can't re-login, something wrong here"); + $this->debug_message("*** can't add $u_name@$u_domain (network: $network) to $list"); continue; } - $this->re_login = true; + $re_login = true; $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); + $this->debug_message("**** get new ticket, try it again"); if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - $this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + $this->debug_message("*** can't add $u_name@$u_domain (network: $network) to $list"); continue; } } $this->aContactList[$u_domain][$u_name][$network][$list] = false; $cnt++; } - $this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); + $this->debug_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); } $str = ''; $len = strlen($str); } else - $this->log_message("*** someone add us to their list: $data"); + $this->debug_message("*** someone add us to their list: $data"); $this->AddUsToMemberList($u_name.'@'.$u_domain, $network); } break; - + case 'RML': // randomly, we get RML command, someome remove us to their contact list for MSNP15 // NS: <<< RML 0 {size} @@ -3092,17 +2357,17 @@ class MSN { foreach ($aData as $list => $id) $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); unset($this->aContactList[$u_domain][$u_name][$network]); - $this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); + $this->debug_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); } else - $this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); + $this->debug_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); $this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network); } else - $this->log_message("*** someone remove us from their list: $data"); + $this->debug_message("*** someone remove us from their list: $data"); } break; - + case 'MSG': // randomly, we get MSG notification from server // NS: <<< MSG Hotmail Hotmail {size} @@ -3137,32 +2402,32 @@ class MSN { } } if ($ignore) { - $this->log_message("*** ingnore MSG for: $line"); + $this->debug_message("*** ingnore MSG for: $line"); break; } if ($maildata == '') { - $this->log_message("*** ingnore MSG not for OIM"); + $this->debug_message("*** ingnore MSG not for OIM"); break; } - $this->re_login = false; + $re_login = false; if (strcasecmp($maildata, 'too-large') == 0) { - $this->log_message("*** large mail-data, need to get the data via SOAP"); + $this->debug_message("*** large mail-data, need to get the data via SOAP"); $maildata = $this->getOIM_maildata(); if ($maildata === false) { - $this->log_message("*** can't get mail-data via SOAP"); + $this->debug_message("*** can't get mail-data via SOAP"); // maybe we need to re-login again $aTickets = $this->get_passport_ticket(); if (!$aTickets || !is_array($aTickets)) { // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + $this->debug_message("*** can't re-login, something wrong here, ignore this OIM"); break; } - $this->re_login = true; + $re_login = true; $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); + $this->debug_message("*** get new ticket, try it again"); $maildata = $this->getOIM_maildata(); if ($maildata === false) { - $this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); + $this->debug_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); break; } } @@ -3180,7 +2445,7 @@ class MSN { $p = substr($p, $end); } if (count($aOIMs) == 0) { - $this->log_message("*** ingnore empty OIM"); + $this->debug_message("*** ingnore empty OIM"); break; } foreach ($aOIMs as $maildata) { @@ -3195,7 +2460,7 @@ class MSN { // N: sender alias preg_match('#(.*)#', $maildata, $matches); if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without type"); + $this->debug_message("*** ingnore OIM maildata without type"); continue; } $oim_type = $matches[1]; @@ -3205,13 +2470,13 @@ class MSN { $network = 1; preg_match('#(.*)#', $maildata, $matches); if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without sender"); + $this->debug_message("*** ingnore OIM maildata without sender"); continue; } $oim_sender = $matches[1]; preg_match('#(.*)#', $maildata, $matches); if (count($matches) == 0) { - $this->log_message("*** ingnore OIM maildata without msgid"); + $this->debug_message("*** ingnore OIM maildata without msgid"); continue; } $oim_msgid = $matches[1]; @@ -3219,37 +2484,37 @@ class MSN { $oim_size = (count($matches) == 0) ? 0 : $matches[1]; preg_match('#(.*)#', $maildata, $matches); $oim_time = (count($matches) == 0) ? 0 : $matches[1]; - $this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); + $this->debug_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); $sMsg = $this->getOIM_message($oim_msgid); if ($sMsg === false) { - $this->log_message("*** can't get OIM, msgid = $oim_msgid"); - if ($this->re_login) { - $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + $this->debug_message("*** can't get OIM, msgid = $oim_msgid"); + if ($re_login) { + $this->debug_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); continue; } $aTickets = $this->get_passport_ticket(); if (!$aTickets || !is_array($aTickets)) { // failed to login? ignore it - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); + $this->debug_message("*** can't re-login, something wrong here, ignore this OIM"); continue; } - $this->re_login = true; + $re_login = true; $this->ticket = $aTickets; - $this->log_message("**** get new ticket, try it again"); + $this->debug_message("*** get new ticket, try it again"); $sMsg = $this->getOIM_message($oim_msgid); if ($sMsg === false) { - $this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + $this->debug_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); continue; } } - $this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); - + $this->debug_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); + //$this->ReceivedMessage($oim_sender,$sMsg,$network,true); $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true)); } } break; - + case 'UBM': // randomly, we get UBM, this is the message from other network, like Yahoo! // NS: <<< UBM {email} $network $type {size} @@ -3283,15 +2548,15 @@ class MSN { } if($ignore) { - $this->log_message("*** ingnore from $from_email: $line"); + $this->debug_message("*** ingnore from $from_email: $line"); break; } - $this->log_message("*** MSG from $from_email (network: $network): $sMsg"); + $this->debug_message("*** MSG from $from_email (network: $network): $sMsg"); //$this->ReceivedMessage($from_email,$sMsg,$network,false); $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); } break; - + case 'UBX': // randomly, we get UBX notification from server // NS: <<< UBX email {network} {size} @@ -3300,7 +2565,7 @@ class MSN { if (is_numeric($size) && $size > 0) $this->ns_readdata($size); break; - + case 'CHL': // randomly, we'll get challenge from server // NS: <<< CHL 0 {code} @@ -3310,7 +2575,7 @@ class MSN { // NS: >>> fingerprint $this->ns_writeln("QRY $this->id $this->prod_id 32"); $this->ns_writedata($fingerprint); - $this->ns_writeln("CHG $this->id NLN $this->clientid"); + $this->ns_writeln("CHG $this->id NLN $this->clientid"); if($this->PhotoStickerFile!==false) $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); break; @@ -3319,7 +2584,7 @@ class MSN { // ignore it // change our status to online first break; - + case 'XFR': // sometimes, NS will redirect to another NS // MSNP9 @@ -3341,7 +2606,7 @@ class MSN { foreach($this->MessageQueue as $User => $Message) { //$this->ChildProcess[$ChildPid] - $this->log_message("*** XFR SB $User"); + $this->debug_message("*** XFR SB $User"); $pid=pcntl_fork(); if($pid) { @@ -3351,20 +2616,20 @@ class MSN { } elseif($pid==-1) { - $this->log_message("*** Fork Error $User"); + $this->debug_message("*** Fork Error $User"); break; } else { //Child Process - $this->log_message("*** Child Process Start for $User"); + $this->debug_message("*** Child Process Start for $User"); unset($Message['XFRSent']); unset($Message['ReqTime']); $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); if ($bSBresult === false) { // error for switchboard - $this->log_message("!!! error for sending message to ".$User); + $this->debug_message("!!! error for sending message to ".$User); } die; } @@ -3375,20 +2640,16 @@ class MSN { $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); if ($bSBresult === false) { // error for switchboard - $this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); + $this->debug_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; }*/ break; case 'QNG': // NS: <<< QNG {time} @list(/* QNG */, $ping_wait) = @explode(' ', $data); - //if ($this->ping_wait == 0) $this->ping_wait = 50; - //if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping; - //Mod by Ricky Set Online - $this->callHandler('Pong', $ping_wait); break; - + case 'RNG': if($this->PhotoStickerFile!==false) $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); @@ -3396,15 +2657,15 @@ class MSN { $this->ns_writeln("CHG $this->id NLN $this->clientid"); // someone is trying to talk to us // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 - $this->log_message("NS: <<< RNG $data"); + $this->debug_message("NS: <<< RNG $data"); @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); @list($sb_ip, $sb_port) = @explode(':', $server); if($this->IsIgnoreMail($email)) { - $this->log_message("*** Ignore RNG from $email"); + $this->debug_message("*** Ignore RNG from $email"); break; } - $this->log_message("*** RING from $email, $sb_ip:$sb_port"); + $this->debug_message("*** RING from $email, $sb_ip:$sb_port"); $this->addContact($email,1,$email, true); $pid=pcntl_fork(); if($pid) @@ -3415,13 +2676,13 @@ class MSN { } elseif($pid==-1) { - $this->log_message("*** Fork Error $User"); + $this->debug_message("*** Fork Error $User"); break; } else { //Child Process - $this->log_message("*** Ring Child Process Start for $User"); + $this->debug_message("*** Ring Child Process Start for $User"); $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email); die; } @@ -3429,104 +2690,131 @@ class MSN { case 'OUT': // force logout from NS // NS: <<< OUT xxx - $this->log_message("*** LOGOUT from NS"); + $this->debug_message("*** LOGOUT from NS"); return $this->NsLogout(); - + default: $code = substr($data,0,3); if (is_numeric($code)) { $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; $this->debug_message("*** NS: $this->error"); - + return $this->NsLogout(); } break; } } } - + /** * Read and handle incoming command/message from * a switchboard session socket */ - public function sbReceive() { - + private function sbReceive() { + + } + + /** + * Checks for new data and calls appropriate methods + * + * This method is usually called in an infinite loop to keep checking for new data + * + * @return void + */ + public function receive() { + //First, get an array of sockets that have data that is ready to be read + $ready = array(); + $ready = $this->getSockets(); + $numrdy = stream_select($ready, $w = NULL, $x = NULL,NULL); + + //Now that we've waited for something, go through the $ready + //array and read appropriately + + for($i = 0;$iNSfp) { + $this->nsReceive(); + } else { + $this->sbReceive($socket); + } + } } /** * Send a request for a switchboard session - * @param $to Target email for switchboard session + * @param String $to Target email for switchboard session */ private function reqSBSession($to) { - $this->log_message("*** Request SB for $to"); + $this->debug_message("*** Request SB for $to"); $this->ns_writeln("XFR $this->id SB"); - + // Add to the queue of those waiting for a switchboard session reponse $this->switchBoardSessions[$to] = array('socket' => NULL, 'id' => 1, 'lastActive' => NULL, 'joined' => false, 'XFRReqTime' => time()); $this->waitingForXFR[] = &$this->switchBoardSessions[$to]; } - + /** * Following an XFR or RNG, connect to the switchboard session - * @param $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case or RNG) - * @param $ip IP of Switchboard - * @param $port Port of Switchboard - * @param $to User on other end of Switchboard - * @param $param Array of parameters - 'cki', 'ticket', 'sid' - * @return Whether successful + * + * @param string $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case or RNG) + * @param string $ip IP of Switchboard + * @param integer $port Port of Switchboard + * @param string $to User on other end of Switchboard + * @param array $param Array of parameters - 'cki', 'ticket', 'sid' + * @return boolean true if successful */ private function connectToSBSession($mode, $ip, $port, $to, $param) { $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); - + $this->switchBoardSessions[$to]['socket'] = @fsockopen($ip, $port, $errno, $errstr, 5); $socket = $this->switchBoardSessions[$to]['socket']; if(!$socket) { $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); return false; } - $this->switchBoardSockets[$socket] = $socket; - + $this->switchBoardSockets[(int) $socket] = $socket; + stream_set_timeout($socket, $this->SBStreamTimeout); - + $id = &$this->switchBoardSessions[$to]['id']; - + if($mode == 'Active') { $cki_code = $param['cki']; - + // SB: >>> USR {id} {user} {cki} $this->sb_writeln($socket, $id, "USR $id $this->user $cki_code"); } else { // Passive $ticket = $param['ticket']; $sid = $param['sid']; - + // SB: >>> ANS {id} {user} {ticket} {session_id} $this->sb_writeln($socket, $id, "ANS $id $this->user $ticket $sid"); } - + $this->switchBoardSessions[$to]['lastActive'] = time(); } - + /** * Send a message via an existing SB session - * @param $message Message - * @param $to Recipient for message - * @return Whether successful + * + * @param string $to Recipient for message + * @param string $message Message + * @return boolean true on success */ - private function sendMessageViaSB($message, $to) { + private function sendMessageViaSB($to, $message) { if(socketcheck($this->switchBoardSessions[$to]['socket'])) { $this->reqSBSession($to); return false; } - + if(!$this->switchBoardSessions[$to]['joined']) { // If our participant has not joined the session yet we can't message them! return false; } - + $id = &$this->switchBoardSessions[$to]['id']; $socket = $this->switchBoardSessions[$to]['socket']; - + $aMessage = $this->getMessage($Message); //CheckEmotion... $MsnObjDefine=$this->GetMsnObjDefine($aMessage); @@ -3542,17 +2830,17 @@ class MSN { // TODO handle failure during write to socket $this->sb_writeln($socket, $id, "MSG $id N $len"); $this->sb_writedata($socket, $aMessage); - + // Don't close the SB session, we might as well leave it open - + return true; } - + /** - * - * @param $to - * @param $sMessage - * @param $lockkey + * Send offline message + * @param string $to Intended recipient + * @param string $sMessage Message + * @param string $lockkey Lock key */ private function sendOIM($to, $sMessage, $lockkey) { $XML = ' @@ -3657,65 +2945,74 @@ X-OIM-Sequence-Num: 1 } return array('challenge' => $challenge, 'auth_policy' => $auth_policy); } - + /** * Send a message to a user on another network + * * @param $message Message * @param $to Intended recipient * @param $network Network + * @return void */ private function sendOtherNetworkMessage($message, $to, $network) { - $message=$this->getMessage($nessage, $network); + $message = $this->getMessage($message, $network); $len = strlen($message); $this->ns_writeln("UUM $this->id $to $network 1 $len"); $this->ns_writedata($Message); - $this->log_message("*** sent to $to (network: $network):\n$Message"); + $this->debug_message("*** Sent to $to (network: $network):\n$Message"); } - + /** * Send a message - * @param $message Message - * @param $to To address in form user@host.com@network - * where network is 1 for MSN, 32 for Yahoo - * and 'Offline' for offline messages + * + * @param string $to To address in form user@host.com(@network) + * where network is 1 for MSN, 32 for Yahoo + * and 'Offline' for offline messages + * @param string $message Message */ - public function sendMessage($message, $to) { + public function sendMessage($to, $message) { if($message != '') { - list($name,$host,$network)=explode('@',$to); - $network=$network==''?1:$network; - - if($network === 1 && $this->switchBoardSessions[$to]['socket'] != NULL && time()-$this->switchBoardSessions[$to]['lastActive'] < $this->SBIdleTimeout) { + list($name, $host, $network) = explode('@', $to); + $network = $network == '' ? 1 : $network; + + if ($network === 1 && $this->switchBoardSessions[$to]['socket'] !== NULL) { $recipient = $name . $host; - $this->debug_message("*** Sending Message to $recipient using existing SB session"); - return $this->sendMessageViaSB($message, $recipient); - } elseif($network == 'Offline') { + $this->debug_message("*** Attempting to send message to $recipient using existing SB session"); + + if ($this->sendMessageViaSB($message, $recipient)) { + $this->debug_message('*** Message sent successfully'); + return true; + } else { + $this->debug_message('*** Message sending failed, requesting new SB session'); + $this->reqSBSession($to); + return false; + } + } elseif ($network == 'Offline') { //Send OIM //FIXME: 修正Send OIM - $lockkey=''; - for ($i = 0; $i < $this->oim_try; $i++) - { - if(($oim_result = $this->sendOIM($To, $Message, $lockkey))===true) break; + $lockkey = ''; + $re_login = false; + for ($i = 0; $i < $this->oim_try; $i++) { + if (($oim_result = $this->sendOIM($to, $message, $lockkey)) === true) break; if (is_array($oim_result) && $oim_result['challenge'] !== false) { // need challenge lockkey - $this->log_message("*** we need a new challenge code for ".$oim_result['challenge']); + $this->debug_message("*** Need challenge code for ".$oim_result['challenge']); $lockkey = $this->getChallenge($oim_result['challenge']); continue; } - if ($oim_result === false || $oim_result['auth_policy'] !== false) - { - if ($this->re_login) - { - $this->log_message("*** can't send OIM, but we already re-login again, so ignore this OIM"); - break; + if ($oim_result === false || $oim_result['auth_policy'] !== false) { + if ($re_login) { + $this->debug_message("*** Can't send OIM, but we already re-logged-in again, so ignore this OIM"); + return true; } - $this->log_message("*** can't send OIM, maybe ticket expired, try to login again"); - // maybe we need to re-login again - if(!$this->get_passport_ticket()) - { - $this->log_message("*** can't re-login, something wrong here, ignore this OIM"); - break; + $this->debug_message("*** Can't send OIM, maybe ticket expired, trying to login again"); + + // Maybe we need to re-login again + if (!$this->get_passport_ticket()) { + $this->debug_message("*** Can't re-login, something went wrong here, ignore this OIM"); + return false; } - $this->log_message("**** get new ticket, try it again"); + $this->debug_message("*** Getting new ticket and trying again"); continue; } } @@ -3727,11 +3024,10 @@ X-OIM-Sequence-Num: 1 } return true; } - + //FIXME Not sure if this is needed? private function endSBSession($socket) { - if (feof($socket)) - { + if (feof($socket)) { // lost connection? error? try OIM later @fclose($socket); return false; @@ -3741,76 +3037,95 @@ X-OIM-Sequence-Num: 1 @fclose($socket); return true; } - + /** * Sends a ping command - * + * * Should be called about every 50 seconds + * + * @return void */ public function sendPing() { // NS: >>> PNG $this->ns_writeln("PNG"); } - + + /** + * Methods to return sockets / check socket status + */ + /** * Get the NS socket + * + * @return resource NS socket */ public function getNSSocket() { return $this->NSfp; } - + /** * Get the Switchboard sockets currently in use + * + * @return array Array of Switchboard sockets */ public function getSBSockets() { return $this->switchBoardSockets; } - + /** * Get all the sockets currently in use + * + * @return array Array of socket resources */ public function getSockets() { return array_merge($this->NSfp, $this->switchBoardSockets); } - - /** + + /** * Checks socket for end of file * - * @access public - * @param Resource $socket Socket to check - * @return boolean true if end of file (socket) + * @param resource $socket Socket to check + * @return boolean true if end of file (socket) */ private static function socketcheck($socket){ $info = stream_get_meta_data($socket); return $info['eof']; } - + + /** + * Methods to add / call callbacks + */ + /** * Calls User Handler * * Calls registered handler for a specific event. - * - * @param String $event Command (event) name (Rvous etc) - * @param String $data Raw message from server + * + * @param string $event Command (event) name (Rvous etc) + * @param array $data Data * @see registerHandler * @return void */ - private function callHandler($event, $data) { + private function callHandler($event, $data = NULL) { if (isset($this->myEventHandlers[$event])) { - call_user_func($this->myEventHandlers[$event], $data); + if ($data !== NULL) { + call_user_func($this->myEventHandlers[$event], $data); + } else { + call_user_func($this->myEventHandlers[$event]); + } } } - - /** + + /** * Registers a user handler - * + * * Handler List * IMIn, Pong, ConnectFailed, Reconnect * - * @param String $event Event name - * @param String $handler User function to call + * @param string $event Event name + * @param string $handler User function to call * @see callHandler - * @return boolean Returns true if successful + * @return boolean true if successful */ public function registerHandler($event, $handler) { if (is_callable($handler)) { diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index b0540c46e5..8f436bdff8 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -1,158 +1,183 @@ -. - */ - -if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } - -/** - * AIM background connection manager for AIM-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 handled. - * - * 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 MsnManager extends ImManager -{ - public $conn = null; - - protected $lastping = null; - - private $pingInterval; - - /** - * Initialize connection to server. - * @return boolean true on success - */ - public function start($master) - { - if(parent::start($master)) - { - $this->connect(); - return true; - }else{ - return false; - } - } - - public function getSockets() - { - $this->connect(); - if($this->conn){ - return $this->conn->getSockets(); - }else{ - return array(); - } - } - - /** - * Idle processing for io manager's execution loop. - * Send keepalive pings to server. - */ - public function idle($timeout=0) - { - $now = time(); - if (empty($this->lastping) || $now - $this->lastping > $pingInterval) { - $this->send_ping(); - } - } - - /** - * Process MSN events that have come in over the wire. - * @param resource $socket - */ - public function handleInput($socket) - { - common_log(LOG_DEBUG, "Servicing the MSN queue."); - $this->stats('msn_process'); - $this->conn->receive(); - } - - function connect() - { - if (!$this->conn) { - $this->conn=new MSN(array( - 'user' => $this->plugin->user, - 'password' => $this->plugin->password, - 'alias' => $this->plugin->nickname, - 'psm' => 'Send me a message to post a notice', - 'debug' => true - ) - ); - $this->conn->registerHandler("IMIn", array($this, 'handle_msn_message')); - $this->conn->registerHandler('Pong', array($this, 'update_ping_time')); - $this->conn->registerHandler('ConnectFailed', array($this, 'handle_connect_failed')); - $this->conn->registerHandler('Reconnect', array($this, 'handle_reconnect')); - $this->conn->signon(); - $this->lastping = time(); - } - return $this->conn; - } - - function send_ping() { - $this->connect(); - if (!$this->conn) { - return false; - } - - $now = time(); - - $this->conn->sendPing(); - $this->lastping = $now; - return true; - } - - /** - * Update the time till the next ping - * @param $data Time till next ping - */ - function update_ping_time($data) { - $pingInterval = $data; - } - - function handle_msn_message($data) - { - $this->plugin->enqueue_incoming_raw($data); - return true; - } - - function handle_connect_failed($data) { - common_log(LOG_NOTICE, 'MSN connect failed, retrying'); - } - - function handle_reconnect($data) { - common_log(LOG_NOTICE, 'MSN reconnecting'); - } - - function send_raw_message($data) - { - $this->connect(); - if (!$this->conn) { - return false; - } - $this->conn->sflapSend($data[0],$data[1],$data[2],$data[3]); - - // Sending a command updates the time till next ping - $this->lastping = time(); - $this->pingInterval = 50; - return true; - } -} +. + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +/** + * MSN background connection manager for MSN-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 handled. + * + * In a multi-site queuedaemon.php run, one connection will be instantiated + * for each site being handled by the current process that has MSN enabled. + */ + +class MsnManager extends ImManager { + public $conn = null; + private $lastping = null; + private $pingInterval; + + /** + * Initialise connection to server. + * + * @return boolean true on success + */ + public function start($master) { + if (parent::start($master)) { + $this->connect(); + return true; + } else { + return false; + } + } + + /** + * Return any open sockets that the run loop should listen + * for input on. + * + * @return array Array of socket resources + */ + public function getSockets() { + $this->connect(); + if ($this->conn) { + return $this->conn->getSockets(); + } else { + return array(); + } + } + + /** + * Idle processing for io manager's execution loop. + * Send keepalive pings to server. + * + * @return void + */ + public function idle($timeout = 0) { + if (empty($this->lastping) || time() - $this->lastping > $this->pingInterval) { + $this->send_ping(); + } + } + + /** + * Process MSN events that have come in over the wire. + * + * @param resource $socket Socket ready + * @return void + */ + public function handleInput($socket) { + common_log(LOG_DEBUG, 'Servicing the MSN queue.'); + $this->stats('msn_process'); + $this->conn->receive(); + } + + /** + * Initiate connection + * + * @return void + */ + function connect() { + if (!$this->conn) { + $this->conn = new MSN(array('user' => $this->plugin->user, + 'password' => $this->plugin->password, + 'alias' => $this->plugin->nickname, + 'psm' => 'Send me a message to post a notice', + 'debug' => true)); + $this->conn->registerHandler("IMIn", array($this, 'handle_msn_message')); + $this->conn->registerHandler('Pong', array($this, 'update_ping_time')); + $this->conn->registerHandler('ConnectFailed', array($this, 'handle_connect_failed')); + $this->conn->registerHandler('Reconnect', array($this, 'handle_reconnect')); + $this->conn->signon(); + $this->lastping = time(); + } + return $this->conn; + } + + /** + * Called by the idle process to send a ping + * when necessary + * + * @return void + */ + private function send_ping() { + $this->connect(); + if (!$this->conn) { + return false; + } + + $this->conn->sendPing(); + $this->lastping = time(); + return true; + } + + /** + * Update the time till the next ping + * @param $data Time till next ping + */ + private function update_ping_time($data) { + $pingInterval = $data; + } + + /** + * Called via a callback when a message is received + * + * Passes it back to the queuing system + * + * @param array $data Data + */ + private function handle_msn_message($data) { + $this->plugin->enqueue_incoming_raw($data); + return true; + } + + /** + * Called by callback to log failure during connect + * + * @param void $data Not used (there to keep callback happy) + */ + function handle_connect_failed($data) { + common_log(LOG_NOTICE, 'MSN connect failed, retrying'); + } + + /** + * Called by callback to log reconnection + * + * @param void $data Not used (there to keep callback happy) + */ + function handle_reconnect($data) { + common_log(LOG_NOTICE, 'MSN reconnecting'); + } + + function send_raw_message($data) { + $this->connect(); + if (!$this->conn) { + return false; + } + + if (!$this->conn->sendMessage($data['to'], $data['message'])) { + return false; + } + + // Sending a command updates the time till next ping + $this->lastping = time(); + $this->pingInterval = 50; + return true; + } +} From 27e8cfd360323cdfed1562c87740464d8bac502b Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Tue, 15 Jun 2010 20:51:04 +0100 Subject: [PATCH 053/655] Adaptation of library almost complete. Bot now signs in correctly when launched using startdaemons.sh --- plugins/Msn/MsnPlugin.php | 23 +- plugins/Msn/extlib/phpmsnclass/msn.class.php | 1760 +++++++++--------- plugins/Msn/msnmanager.php | 15 +- 3 files changed, 871 insertions(+), 927 deletions(-) diff --git a/plugins/Msn/MsnPlugin.php b/plugins/Msn/MsnPlugin.php index 9a9e703986..8452f15220 100644 --- a/plugins/Msn/MsnPlugin.php +++ b/plugins/Msn/MsnPlugin.php @@ -91,11 +91,8 @@ class MsnPlugin extends ImPlugin { */ function validate($screenname) { //TODO Correct this for MSN screennames - if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { - return true; - }else{ - return false; - } + //if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { + return true; } /** @@ -109,7 +106,7 @@ class MsnPlugin extends ImPlugin { $dir = dirname(__FILE__); switch ($cls) { - case 'Msn': + case 'MSN': require_once(INSTALLDIR.'/plugins/Msn/extlib/phpmsnclass/msn.class.php'); return false; case 'MsnManager': @@ -179,12 +176,14 @@ class MsnPlugin extends ImPlugin { } function onPluginVersion(&$versions) { - $versions[] = array('name' => 'MSN', - 'version' => STATUSNET_VERSION, - 'author' => 'Luke Fitzgerald', - 'homepage' => 'http://status.net/wiki/Plugin:MSN', - 'rawdescription' => - _m('The MSN plugin allows users to send and receive notices over the MSN network.')); + $versions[] = array( + 'name' => 'MSN', + 'version' => STATUSNET_VERSION, + 'author' => 'Luke Fitzgerald', + 'homepage' => 'http://status.net/wiki/Plugin:MSN', + 'rawdescription' => + _m('The MSN plugin allows users to send and receive notices over the MSN network.') + ); return true; } } diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 9b087ac792..6146bd1c5a 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -37,7 +37,7 @@ class MSN { private $update_pending; private $PhotoStickerFile=false; private $Emotions=false; - private $ReqSBXFRTimeout=60; + private $XFRReqTimeout=60; private $LastPing; private $ping_wait=50; private $SBIdleTimeout=10; @@ -47,7 +47,6 @@ class MSN { private $ABAuthHeader; private $ABService; private $Contacts; - private $IgnoreList; private $server = 'messenger.hotmail.com'; private $port = 1863; @@ -81,10 +80,6 @@ class MSN { public $oim_try = 3; - public $log_file = ''; - - public $log_path = false; - public $font_fn = 'Arial'; public $font_co = '333333'; public $font_ef = ''; @@ -100,8 +95,27 @@ class MSN { private $aContactList = array(); private $aADL = array(); + + /** + * Holds session information indexed by screenname if + * session has no socket or socket if socket present + * + * @var array + */ private $switchBoardSessions = array(); - private $switchBoardSockets = array(); + + /** + * Holds sockets indexed by screenname + * + * @var array + */ + private $switchBoardSessionLookup = array(); + + /** + * Holds references to sessions waiting for XFR + * + * @var array + */ private $waitingForXFR = array(); /** @@ -127,8 +141,7 @@ class MSN { * @param integer $client_id Client id (hexadecimal) * @return MSN */ - public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C) - { + public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C) { $this->user = $Configs['user']; $this->password = $Configs['password']; $this->alias = isset($Configs['alias']) ? $Configs['alias'] : ''; @@ -137,7 +150,7 @@ class MSN { $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true; $this->PhotoStickerFile=isset($Configs['PhotoSticker']) ? $Configs['PhotoSticker'] : false; - if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) { + if ($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) { foreach($this->Emotions as $EmotionFilePath) $this->MsnObj($EmotionFilePath,$Type=2); } @@ -169,36 +182,31 @@ class MSN { $this->ABService=new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl',array('trace' => 1)); } - private function Array2SoapVar($Array,$ReturnSoapVarObj=true,$TypeName=null,$TypeNameSpace=null) - { - $ArrayString=''; - foreach($Array as $Key => $Val) - { - if($Key{0}==':') continue; - $Attrib=''; - if(is_array($Val[':'])) - { - foreach($Val[':'] as $AttribName => $AttribVal) - $Attrib.=" $AttribName='$AttribVal'"; + private function Array2SoapVar($Array, $ReturnSoapVarObj = true, $TypeName = null, $TypeNameSpace = null) { + $ArrayString = ''; + foreach($Array as $Key => $Val) { + if ($Key{0} == ':') continue; + $Attrib = ''; + if (is_array($Val[':'])) { + foreach ($Val[':'] as $AttribName => $AttribVal) + $Attrib .= " $AttribName='$AttribVal'"; } - if($Key{0}=='!') - { + if ($Key{0} == '!') { //List Type Define - $Key=substr($Key,1); - foreach($Val as $ListKey => $ListVal) - { - if($ListKey{0}==':') continue; - if(is_array($ListVal)) $ListVal=$this->Array2SoapVar($ListVal,false); - elseif(is_bool($ListVal)) $ListVal=$ListVal?'true':'false'; - $ArrayString.="<$Key$Attrib>$ListVal"; + $Key = substr($Key,1); + foreach ($Val as $ListKey => $ListVal) { + if ($ListKey{0} == ':') continue; + if (is_array($ListVal)) $ListVal = $this->Array2SoapVar($ListVal, false); + elseif (is_bool($ListVal)) $ListVal = $ListVal ? 'true' : 'false'; + $ArrayString .= "<$Key$Attrib>$ListVal"; } continue; } - if(is_array($Val)) $Val=$this->Array2SoapVar($Val,false); - elseif(is_bool($Val)) $Val=$Val?'true':'false'; - $ArrayString.="<$Key$Attrib>$Val"; + if (is_array($Val)) $Val = $this->Array2SoapVar($Val, false); + elseif (is_bool($Val)) $Val = $Val ? 'true' : 'false'; + $ArrayString .= "<$Key$Attrib>$Val"; } - if($ReturnSoapVarObj) return new SoapVar($ArrayString,XSD_ANYXML,$TypeName,$TypeNameSpace); + if ($ReturnSoapVarObj) return new SoapVar($ArrayString, XSD_ANYXML, $TypeName, $TypeNameSpace); return $ArrayString; } @@ -208,8 +216,7 @@ class MSN { * @param string $url URL string (Optional) * @return mixed Array of tickets or false on failure */ - private function get_passport_ticket($url = '') - { + private function get_passport_ticket($url = '') { $user = $this->user; $password = htmlspecialchars($this->password); @@ -310,8 +317,8 @@ class MSN { '; - $this->debug_message("*** URL: $passport_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); + //$this->debug_message("*** URL: $passport_url"); + //$this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $passport_url); if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); @@ -323,33 +330,33 @@ class MSN { $data = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + //$this->debug_message("*** Get Result:\n$data"); if ($http_code != 200) { - // sometimes, rediret to another URL + // sometimes, redirect to another URL // MSNP15 //psf:Redirect //https://msnia.login.live.com/pp450/RST.srf //Authentication Failure if (strpos($data, 'psf:Redirect') === false) { - $this->debug_message("*** Can't get passport ticket! http code = $http_code"); + $this->debug_message("*** Could not get passport ticket! http code = $http_code"); return false; } preg_match("#(.*)#", $data, $matches); if (count($matches) == 0) { - $this->debug_message("*** redirect, but can't get redirect URL!"); + $this->debug_message('*** Redirected, but could not get redirect URL!'); return false; } $redirect_url = $matches[1]; if ($redirect_url == $passport_url) { - $this->debug_message("*** redirect, but redirect to same URL!"); + $this->debug_message('*** Redirected, but to same URL!'); return false; } - $this->debug_message("*** redirect to $redirect_url"); + $this->debug_message("*** Redirected to $redirect_url"); return $this->get_passport_ticket($redirect_url); } - // sometimes, rediret to another URL, also return 200 + // sometimes, redirect to another URL, also return 200 // MSNP15 //psf:Redirect //https://msnia.login.live.com/pp450/RST.srf @@ -359,10 +366,10 @@ class MSN { if (count($matches) != 0) { $redirect_url = $matches[1]; if ($redirect_url == $passport_url) { - $this->debug_message("*** redirect, but redirect to same URL!"); + $this->debug_message('*** Redirected, but to same URL!'); return false; } - $this->debug_message("*** redirect to $redirect_url"); + $this->debug_message("*** Redirected to $redirect_url"); return $this->get_passport_ticket($redirect_url); } } @@ -397,7 +404,7 @@ class MSN { // no ticket found! if (count($matches) == 0) { - $this->debug_message("*** Can't get passport ticket!"); + $this->debug_message('*** Could not get passport ticket!'); return false; } @@ -437,94 +444,100 @@ class MSN { 'space_ticket' => html_entity_decode($matches[11]), 'storage_ticket' => html_entity_decode($matches[13]) ); - $this->ticket=$aTickets; - $this->debug_message(var_export($aTickets, true)); - $ABAuthHeaderArray=array( - 'ABAuthHeader'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'ManagedGroupRequest'=>false, - 'TicketToken'=>htmlspecialchars($this->ticket['contact_ticket']), + $this->ticket = $aTickets; + //$this->debug_message(var_export($aTickets, true)); + $ABAuthHeaderArray = array( + 'ABAuthHeader' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'ManagedGroupRequest' => false, + 'TicketToken' => htmlspecialchars($this->ticket['contact_ticket']), ) ); - $this->ABAuthHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook","ABAuthHeader", $this->Array2SoapVar($ABAuthHeaderArray)); + $this->ABAuthHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABAuthHeader', $this->Array2SoapVar($ABAuthHeaderArray)); return $aTickets; } - private function UpdateContacts() - { - $ABApplicationHeaderArray=array( - 'ABApplicationHeader'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'ApplicationId'=>'CFE80F9D-180F-4399-82AB-413F33A1FA11', - 'IsMigration'=>false, - 'PartnerScenario'=>'ContactSave' + /** + * Fetch contact list + * + * @return boolean true on success + */ + private function UpdateContacts() { + $ABApplicationHeaderArray = array( + 'ABApplicationHeader' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'ApplicationId' => 'CFE80F9D-180F-4399-82AB-413F33A1FA11', + 'IsMigration' => false, + 'PartnerScenario' => 'ContactSave' ) ); - $ABApplicationHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook",'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); - $ABFindAllArray=array( - 'ABFindAll'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'abId'=>'00000000-0000-0000-0000-000000000000', - 'abView'=>'Full', - 'lastChange'=>'0001-01-01T00:00:00.0000000-08:00', + $ABApplicationHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); + $ABFindAllArray = array( + 'ABFindAll' => array( + ':' => array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'abId' => '00000000-0000-0000-0000-000000000000', + 'abView' => 'Full', + 'lastChange' => '0001-01-01T00:00:00.0000000-08:00', ) ); - $ABFindAll=new SoapParam($this->Array2SoapVar($ABFindAllArray),'ABFindAll'); - $this->ABService->__setSoapHeaders(array($ABApplicationHeader,$this->ABAuthHeader)); - $this->Contacts=array(); - try - { - $this->debug_message("*** Update Contacts..."); - $Result=$this->ABService->ABFindAll($ABFindAll); - $this->debug_message("*** Result:\n".print_r($Result,true)."\n".$this->ABService->__getLastResponse()); + $ABFindAll = new SoapParam($this->Array2SoapVar($ABFindAllArray), 'ABFindAll'); + $this->ABService->__setSoapHeaders(array($ABApplicationHeader, $this->ABAuthHeader)); + $this->Contacts = array(); + try { + $this->debug_message('*** Updating Contacts...'); + $Result = $this->ABService->ABFindAll($ABFindAll); + $this->debug_message("*** Result:\n".print_r($Result, true)."\n".$this->ABService->__getLastResponse()); foreach($Result->ABFindAllResult->contacts->Contact as $Contact) - $this->Contacts[$Contact->contactInfo->passportName]=$Contact; - } - catch(Exception $e) - { + $this->Contacts[$Contact->contactInfo->passportName] = $Contact; + } catch(Exception $e) { $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); return false; } return true; } - private function addContact($email, $network, $display = '', $sendADL = false) - { + /** + * Add contact + * + * @param string $email + * @param integer $network + * @param string $display + * @param boolean $sendADL + * @return boolean true on success + */ + private function addContact($email, $network, $display = '', $sendADL = false) { if ($network != 1) return true; - if(isset($this->Contacts[$email])) return true; + if (isset($this->Contacts[$email])) return true; - $ABContactAddArray=array( - 'ABContactAdd'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'abId'=>'00000000-0000-0000-0000-000000000000', - 'contacts'=>array( - 'Contact'=>array( - ':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'contactInfo'=>array( - 'contactType'=>'LivePending', - 'passportName'=>$email, - 'isMessengerUser'=>true, - 'MessengerMemberInfo'=>array( - 'DisplayName'=>$email + $ABContactAddArray = array( + 'ABContactAdd' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'abId' => '00000000-0000-0000-0000-000000000000', + 'contacts' => array( + 'Contact' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'contactInfo' => array( + 'contactType' => 'LivePending', + 'passportName' => $email, + 'isMessengerUser' => true, + 'MessengerMemberInfo' => array( + 'DisplayName' => $email ) ) ) ), - 'options'=>array( - 'EnableAllowListManagement'=>true + 'options' => array( + 'EnableAllowListManagement' => true ) ) ); - $ABContactAdd=new SoapParam($this->Array2SoapVar($ABContactAddArray),'ABContactAdd'); - try - { - $this->debug_message("*** Add Contacts $email..."); + $ABContactAdd = new SoapParam($this->Array2SoapVar($ABContactAddArray), 'ABContactAdd'); + try { + $this->debug_message("*** Adding Contact $email..."); $this->ABService->ABContactAdd($ABContactAdd); - } - catch(Exception $e) - { - $this->debug_message("*** Add Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); + } catch(Exception $e) { + $this->debug_message("*** Add Contact Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); return false; } if ($sendADL && !feof($this->NSfp)) { @@ -533,6 +546,7 @@ class MSN { $str = ''; $len = strlen($str); // NS: >>> ADL {id} {size} + //TODO introduce error checking $this->ns_writeln("ADL $this->id $len"); $this->ns_writedata($str); } @@ -541,8 +555,15 @@ class MSN { return true; } - function delMemberFromList($memberID, $email, $network, $list) - { + /** + * Remove contact from list + * + * @param integer $memberID + * @param string $email + * @param integer $network + * @param string $list + */ + function delMemberFromList($memberID, $email, $network, $list) { if ($network != 1 && $network != 32) return true; if ($memberID === false) return true; $user = $email; @@ -632,8 +653,8 @@ class MSN { 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' ); - $this->debug_message("*** URL: $this->delmember_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); + //$this->debug_message("*** URL: $this->delmember_url"); + //$this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $this->delmember_url); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); @@ -646,29 +667,35 @@ class MSN { $data = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + //$this->debug_message("*** Get Result:\n$data"); if ($http_code != 200) { preg_match('#(.*)(.*)#', $data, $matches); if (count($matches) == 0) { - $this->debug_message("*** can't delete member (network: $network) $email ($memberID) to $list"); + $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list"); return false; } $faultcode = trim($matches[1]); $faultstring = trim($matches[2]); if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) { - $this->debug_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring"); + $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, error code: $faultcode, $faultstring"); return false; } - $this->debug_message("*** delete member (network: $network) $email ($memberID) from $list, not exist"); + $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, not present in list"); return true; } - $this->debug_message("*** delete member (network: $network) $email ($memberID) from $list"); + $this->debug_message("*** Member successfully deleted (network: $network) $email ($memberID) from $list list"); return true; } - function addMemberToList($email, $network, $list) - { + /** + * Add contact to list + * + * @param string $email + * @param integer $network + * @param string $list + */ + function addMemberToList($email, $network, $list) { if ($network != 1 && $network != 32) return true; $ticket = htmlspecialchars($this->ticket['contact_ticket']); $user = $email; @@ -763,8 +790,8 @@ class MSN { 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' ); - $this->debug_message("*** URL: $this->addmember_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); + //$this->debug_message("*** URL: $this->addmember_url"); + //$this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $this->addmember_url); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); @@ -777,29 +804,33 @@ class MSN { $data = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + //$this->debug_message("*** Get Result:\n$data"); if ($http_code != 200) { preg_match('#(.*)(.*)#', $data, $matches); if (count($matches) == 0) { - $this->debug_message("*** can't add member (network: $network) $email to $list"); + $this->debug_message("*** Could not add member (network: $network) $email to $list list"); return false; } $faultcode = trim($matches[1]); $faultstring = trim($matches[2]); if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) { - $this->debug_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring"); + $this->debug_message("*** Could not add member (network: $network) $email to $list list, error code: $faultcode, $faultstring"); return false; } - $this->debug_message("*** add member (network: $network) $email to $list, already exist!"); + $this->debug_message("*** Could not add member (network: $network) $email to $list list, already present"); return true; } - $this->debug_message("*** add member (network: $network) $email to $list"); + $this->debug_message("*** Member successfully added (network: $network) $email to $list list"); return true; } - function getMembershipList($returnData=false) - { + /** + * Get membership lists + * + * @param mixed $returnData Membership list or false on failure + */ + function getMembershipList($returnData = false) { $ticket = htmlspecialchars($this->ticket['contact_ticket']); $XML = ' debug_message("*** URL: $this->membership_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); + //$this->debug_message("*** URL: $this->membership_url"); + //$this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $this->membership_url); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); @@ -850,8 +881,8 @@ class MSN { $data = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); - if($http_code != 200) return false; + //$this->debug_message("*** Get Result:\n$data"); + if ($http_code != 200) return false; $p = $data; $aMemberships = array(); while (1) { @@ -924,7 +955,7 @@ class MSN { @list($u_name, $u_domain) = @explode('@', $email); if ($u_domain == NULL) continue; $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id; - $this->debug_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); + $this->debug_message("*** Adding new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); } } } @@ -933,6 +964,7 @@ class MSN { /** * Connect to the NS server + * * @param String $user Username * @param String $password Password * @param String $redirect_server Redirect server @@ -942,21 +974,19 @@ class MSN { private function connect($user, $password, $redirect_server = '', $redirect_port = 1863) { $this->id = 1; if ($redirect_server === '') { - $this->NSfp = @fsockopen($this->server, $this->port, $errno, $errstr, 5); + $this->NSfp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout); if (!$this->NSfp) { - $this->error = "Can't connect to $this->server:$this->port, error => $errno, $errstr"; + $this->error = "!!! Could not connect to $this->server:$this->port, error => $errno, $errstr"; return false; } } else { - $this->NSfp = @fsockopen($redirect_server, $redirect_port, $errno, $errstr, 5); + $this->NSfp = @fsockopen($redirect_server, $redirect_port, $errno, $errstr, $this->timeout); if (!$this->NSfp) { - $this->error = "Can't connect to $redirect_server:$redirect_port, error => $errno, $errstr"; + $this->error = "!!! Could not connect to $redirect_server:$redirect_port, error => $errno, $errstr"; return false; } } - - stream_set_timeout($this->NSfp, $this->timeout); $this->authed = false; // MSNP9 // NS: >> VER {id} MSNP9 CVR0 @@ -965,8 +995,7 @@ class MSN { $this->ns_writeln("VER $this->id $this->protocol CVR0"); $start_tm = time(); - while (!self::socketcheck($this->NSfp)) - { + while (!self::socketcheck($this->NSfp)) { $data = $this->ns_readln(); // no data? if ($data === false) { @@ -975,7 +1004,6 @@ class MSN { $this->ns_writeln("OUT"); @fclose($this->NSfp); $this->error = 'Timeout, maybe protocol changed!'; - $this->debug_message("*** $this->error"); return false; } @@ -1024,7 +1052,6 @@ class MSN { $this->ns_writeln("OUT"); @fclose($this->NSfp); $this->error = 'Passport authenticated fail!'; - $this->debug_message("*** $this->error"); return false; } @@ -1045,19 +1072,17 @@ class MSN { // MSNP15 // NS: <<< XFR {id} NS {server} U D @list(/* XFR */, /* id */, $Type, $server, /* ... */) = @explode(' ', $data); - if($Type!='NS') break; + if ($Type!='NS') break; @list($ip, $port) = @explode(':', $server); // this connection will close after XFR @fclose($this->NSfp); - $this->NSfp = @fsockopen($ip, $port, $errno, $errstr, 5); + $this->NSfp = @fsockopen($ip, $port, $errno, $errstr, $this->timeout); if (!$this->NSfp) { $this->error = "Can't connect to $ip:$port, error => $errno, $errstr"; - $this->debug_message("*** $this->error"); return false; } - stream_set_timeout($this->NSfp, $this->timeout); // MSNP9 // NS: >> VER {id} MSNP9 CVR0 // MSNP15 @@ -1071,7 +1096,7 @@ class MSN { @list(/* GCF */, /* 0 */, $size,) = @explode(' ', $data); // we don't need the data, just read it and drop if (is_numeric($size) && $size > 0) - $this->ns_readdata($size); + $this->ns_readdata($size); break; default: @@ -1082,7 +1107,6 @@ class MSN { $this->ns_writeln("OUT"); @fclose($this->NSfp); $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; - $this->debug_message("*** $this->error"); return false; } // unknown response from server, just ignore it @@ -1098,23 +1122,26 @@ class MSN { * @return void */ public function signon() { - $this->debug_message("*** try to connect to MSN network"); + /* FIXME Don't implement the signon as a loop or we could hang + * the queue handler! */ + $this->debug_message('*** Trying to connect to MSN network'); - while(true) { - while(!$this->connect($this->user, $this->password)) { - $this->signonFailure("!!! Can't connect to server: $this->error"); - } - if($this->UpdateContacts() === false) { - $this->signonFailure('!!! Update contacts failed'); + while (true) { + // Connect + if (!$this->connect($this->user, $this->password)) { + $this->signonFailure("!!! Could not connect to server: $this->error"); continue; } - $this->LastPing=time(); - $start_tm = time(); - $ping_tm = time(); - if(($this->aContactList = $this->getMembershipList()) === false) { + + // Update contacts + if ($this->UpdateContacts() === false) continue; + + // Get membership lists + if (($this->aContactList = $this->getMembershipList()) === false) { $this->signonFailure('!!! Get membership list failed'); continue; } + if ($this->update_pending) { if (is_array($this->aContactList)) { $pending = 'Pending'; @@ -1206,15 +1233,15 @@ class MSN { //$MsnObj=$this->PhotoStckObj(); // NS: >>> CHG {id} {status} {clientid} {msnobj} $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if($this->PhotoStickerFile!==false) + if ($this->PhotoStickerFile !== false) $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); // NS: >>> UUX {id} length $str = ''.htmlspecialchars($this->psm).''; $len = strlen($str); $this->ns_writeln("UUX $this->id $len"); $this->ns_writedata($str); - if(!socketcheck($this->NSfp)) { - $this->debug_message("*** connected, wait for command"); + if (!self::socketcheck($this->NSfp)) { + $this->debug_message('*** Connected, waiting for commands'); break; } else { $this->NSRetryWait($this->retry_wait); @@ -1262,10 +1289,15 @@ class MSN { return base64_encode($blob); } + /** + * Get OIM mail data + * + * @return string mail data or false on failure + */ function getOIM_maildata() { preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); if (count($matches) == 0) { - $this->debug_message('*** no web ticket?'); + $this->debug_message('*** No web ticket?'); return false; } $t = htmlspecialchars($matches[1]); @@ -1291,8 +1323,8 @@ class MSN { 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' ); - $this->debug_message("*** URL: $this->oim_maildata_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); + //$this->debug_message("*** URL: $this->oim_maildata_url"); + //$this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $this->oim_maildata_url); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); @@ -1305,26 +1337,32 @@ class MSN { $data = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + //$this->debug_message("*** Get Result:\n$data"); if ($http_code != 200) { - $this->debug_message("*** Can't get OIM maildata! http code: $http_code"); + $this->debug_message("*** Could not get OIM maildata! http code: $http_code"); return false; } // See #XML_Data preg_match('#]*)>(.*)#', $data, $matches); if (count($matches) == 0) { - $this->debug_message("*** Can't get OIM maildata"); - return ''; + $this->debug_message('*** Could not get OIM maildata'); + return false; } return $matches[2]; } + /** + * Fetch OIM message with given id + * + * @param string $msgid + * @return string Message or false on failure + */ function getOIM_message($msgid) { preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); if (count($matches) == 0) { - $this->debug_message('*** no web ticket?'); + $this->debug_message('*** No web ticket?'); return false; } $t = htmlspecialchars($matches[1]); @@ -1355,8 +1393,8 @@ class MSN { 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' ); - $this->debug_message("*** URL: $this->oim_read_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); + //$this->debug_message("*** URL: $this->oim_read_url"); + //$this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $this->oim_read_url); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); @@ -1369,7 +1407,7 @@ class MSN { $data = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + //$this->debug_message("*** Get Result:\n$data"); if ($http_code != 200) { $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code"); @@ -1403,7 +1441,7 @@ class MSN { $sOIM .= $line; } $sMsg = base64_decode($sOIM); - $this->debug_message("*** we get OIM ($msgid): $sMsg"); + //$this->debug_message("*** we get OIM ($msgid): $sMsg"); // delete OIM $XML = ' @@ -1431,8 +1469,8 @@ class MSN { 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' ); - $this->debug_message("*** URL: $this->oim_del_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); + //$this->debug_message("*** URL: $this->oim_del_url"); + //$this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $this->oim_del_url); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); @@ -1445,15 +1483,20 @@ class MSN { $data = curl_exec($curl); $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); + //$this->debug_message("*** Get Result:\n$data"); if ($http_code != 200) - $this->debug_message("*** Can't delete OIM: $msgid, http code = $http_code"); + $this->debug_message("*** Could not delete OIM: $msgid, http code = $http_code"); else $this->debug_message("*** OIM ($msgid) deleted"); return $sMsg; } + /** + * Log out and close the NS connection + * + * @return void + */ private function NSLogout() { if (is_resource($this->NSfp) && !feof($this->NSfp)) { // logout now @@ -1461,18 +1504,27 @@ class MSN { $this->ns_writeln("OUT"); fclose($this->NSfp); $this->NSfp = false; - $this->debug_message("*** logout now!"); + $this->debug_message("*** Logged out"); } - } + /** + * Sleep for the given number of seconds + * + * @param integer $wait Number of seconds to sleep for + */ private function NSRetryWait($wait) { - $this->debug_message("*** wait for $Wait seconds"); + $this->debug_message("*** Sleeping for $wait seconds before retrying"); sleep($wait); } - function getChallenge($code) - { + /** + * Generate challenge response + * + * @param string $code + * @return string challenge response code + */ + function getChallenge($code) { // MSNP15 // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges // Step 1: The MD5 Hash @@ -1546,514 +1598,23 @@ class MSN { return $hash; } - private function getMessage($sMessage, $network = 1) - { + /** + * Generate the data to send a message + * + * @param string $sMessage Message + * @param integer $network Network + */ + private function getMessage($sMessage, $network = 1) { $msg_header = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n"; $msg_header_len = strlen($msg_header); if ($network == 1) $maxlen = $this->max_msn_message_len - $msg_header_len; else $maxlen = $this->max_yahoo_message_len - $msg_header_len; - $sMessage=str_replace("\r", '', $sMessage); - $msg=substr($sMessage,0,$maxlen); + $sMessage = str_replace("\r", '', $sMessage); + $msg = substr($sMessage, 0, $maxlen); return $msg_header.$msg; } - /** - * - * @param $Action 連線模式 'Active' => 主動傳送訊息,'Passive' => 接收訊息 - * @param $Param - * @return boolean - */ - private function DoSwitchBoard($Action,$Param) - { - $SessionEnd=false; - $Joined=false; - $this->id=1; - $LastActive=time(); - stream_set_timeout($this->SBfp, $this->SBStreamTimeout); - switch($Action) - { - case 'Active': - $cki_code=$Param['cki']; - $user=$Param['user']; - $this->SwitchBoardMessageQueue=$Param['Msg']; - // SB: >>> USR {id} {user} {cki} - $this->SB_writeln("USR $this->id $this->user $cki_code"); - $this->SwitchBoardSessionUser=$user; - break; - case 'Passive': - $ticket=$Param['ticket']; - $sid=$Param['sid']; - $user=$Param['user']; - // SB: >>> ANS {id} {user} {ticket} {session_id} - $this->SB_writeln("ANS $this->id $this->user $ticket $sid"); - $this->SwitchBoardSessionUser=$user; - break; - default: - return false; - } - while((!feof($this->SBfp))&&(!$SessionEnd)) - { - $data = $this->SB_readln(); - if($this->kill_me) - { - $this->debug_message("*** SB Okay, kill me now!"); - break; - } - if($data === false) - { - if(time()-$LastActive > $this->SBIdleTimeout) - { - $this->debug_message("*** SB Idle Timeout!"); - break; - } - if(!$Joined) continue; - foreach($this->SwitchBoardMessageQueue as $Message) - { - if($Message=='') continue; - $aMessage = $this->getMessage($Message); - //CheckEmotion... - $MsnObjDefine=$this->GetMsnObjDefine($aMessage); - if($MsnObjDefine!=='') - { - $SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine"; - $len = strlen($SendString); - $this->SB_writeln("MSG $this->id N $len"); - $this->SB_writedata($SendString); - $this->id++; - } - $len = strlen($aMessage); - $this->SB_writeln("MSG $this->id N $len"); - $this->SB_writedata($aMessage); - } - $this->SwitchBoardMessageQueue=array(); - if(!$this->IsIgnoreMail($user)) $LastActive = time(); - continue; - } - $code = substr($data, 0, 3); - switch($code) - { - case 'IRO': - // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid} - @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data); - $this->debug_message("*** $email join us"); - $Joined=true; - break; - case 'BYE': - $this->debug_message("*** Quit for BYE"); - $SessionEnd=true; - break; - case 'USR': - // SB: <<< USR {id} OK {user} {alias} - // we don't need the data, just ignore it - // request user to join this switchboard - // SB: >>> CAL {id} {user} - $this->SB_writeln("CAL $this->id $user"); - break; - case 'CAL': - // SB: <<< CAL {id} RINGING {?} - // we don't need this, just ignore, and wait for other response - $this->id++; - break; - case 'JOI': - // SB: <<< JOI {user} {alias} {clientid?} - // someone join us - // we don't need the data, just ignore it - // no more user here - $Joined=true; - break; - case 'MSG': - // SB: <<< MSG {email} {alias} {len} - @list(/* MSG */, $from_email, /* alias */, $len, ) = @explode(' ', $data); - $len = trim($len); - $data = $this->SB_readdata($len); - $aLines = @explode("\n", $data); - $header = true; - $ignore = false; - $is_p2p = false; - $sMsg = ''; - foreach ($aLines as $line) - { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } - if (strncasecmp($line, 'TypingUser:', 11) == 0) { - // typing notification, just ignore - $ignore = true; - break; - } - if (strncasecmp($line, 'Chunk:', 6) == 0) { - // we don't handle any split message, just ignore - $ignore = true; - break; - } - if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) { - // p2p message, ignore it, but we need to send acknowledgement for it... - $is_p2p = true; - $p = strstr($data, "\n\n"); - $sMsg = ''; - if ($p === false) { - $p = strstr($data, "\r\n\r\n"); - if ($p !== false) - $sMsg = substr($p, 4); - } - else - $sMsg = substr($p, 2); - break; - } - if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) { - // ignore all application/x-... message - // for example: - // application/x-ms-ink => ink message - $ignore = true; - break; - } - if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) { - // ignore all text/x-... message - // for example: - // text/x-msnmsgr-datacast => nudge, voice clip.... - // text/x-mms-animemoticon => customized animemotion word - $ignore = true; - break; - } - continue; - } - if ($sMsg !== '') - $sMsg .= "\n"; - $sMsg .= $line; - } - if ($ignore) - { - $this->debug_message("*** ingnore from $from_email: $line"); - break; - } - if ($is_p2p) - { - // we will ignore any p2p message after sending acknowledgement - $ignore = true; - $len = strlen($sMsg); - $this->debug_message("*** p2p message from $from_email, size $len"); - // header = 48 bytes - // content >= 0 bytes - // footer = 4 bytes - // so it need to >= 52 bytes - /*if ($len < 52) { - $this->debug_message("*** p2p: size error, less than 52!"); - break; - }*/ - $aDwords = @unpack("V12dword", $sMsg); - if (!is_array($aDwords)) { - $this->debug_message("*** p2p: header unpack error!"); - break; - } - $this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg)); - $hdr_SessionID = $aDwords['dword1']; - $hdr_Identifier = $aDwords['dword2']; - $hdr_DataOffsetLow = $aDwords['dword3']; - $hdr_DataOffsetHigh = $aDwords['dword4']; - $hdr_TotalDataSizeLow = $aDwords['dword5']; - $hdr_TotalDataSizeHigh = $aDwords['dword6']; - $hdr_MessageLength = $aDwords['dword7']; - $hdr_Flag = $aDwords['dword8']; - $hdr_AckID = $aDwords['dword9']; - $hdr_AckUID = $aDwords['dword10']; - $hdr_AckSizeLow = $aDwords['dword11']; - $hdr_AckSizeHigh = $aDwords['dword12']; - $this->debug_message("*** p2p: header SessionID = $hdr_SessionID"); - $this->debug_message("*** p2p: header Inentifier = $hdr_Identifier"); - $this->debug_message("*** p2p: header Data Offset Low = $hdr_DataOffsetLow"); - $this->debug_message("*** p2p: header Data Offset High = $hdr_DataOffsetHigh"); - $this->debug_message("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow"); - $this->debug_message("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh"); - $this->debug_message("*** p2p: header MessageLength = $hdr_MessageLength"); - $this->debug_message("*** p2p: header Flag = $hdr_Flag"); - $this->debug_message("*** p2p: header AckID = $hdr_AckID"); - $this->debug_message("*** p2p: header AckUID = $hdr_AckUID"); - $this->debug_message("*** p2p: header AckSize Low = $hdr_AckSizeLow"); - $this->debug_message("*** p2p: header AckSize High = $hdr_AckSizeHigh"); - if($hdr_Flag==2) { - //This is an ACK from SB ignore.... - $this->debug_message("*** p2p: //This is an ACK from SB ignore....:\n"); - break; - } - $MsgBody=$this->linetoArray(substr($sMsg,48,-4)); - $this->debug_message("*** p2p: body".print_r($MsgBody,true)); - if(($MsgBody['EUF-GUID']=='{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}')&&($PictureFilePath=$this->GetPictureFilePath($MsgBody['Context']))) - { - while(true) - { - if($this->SB_readln()===false) break; - } - $this->debug_message("*** p2p: Inv hdr:\n".$this->dump_binary(substr($sMsg,0,48))); - preg_match('/{([0-9A-F\-]*)}/i',$MsgBody['Via'],$Matches); - $BranchGUID=$Matches[1]; - //it's an invite to send a display picture. - $new_id = ~$hdr_Identifier; - $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID, - $new_id, - 0, 0, - $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, - 0, - 2, - $hdr_Identifier, - $hdr_AckID, - $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh); - $footer = pack("L", 0); - $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer"; - $len = strlen($message); - $this->SB_writeln("MSG $this->id D $len"); - $this->SB_writedata($message); - $this->debug_message("*** p2p: send display picture acknowledgement for $hdr_SessionID"); - $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message)); - $this->SB_readln();//Read ACK; - $this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr)); - $new_id-=3; - //Send 200 OK message - $MessageContent="SessionID: ".$MsgBody['SessionID']."\r\n\r\n".pack("C", 0); - $MessagePayload= - "MSNSLP/1.0 200 OK\r\n". - "To: \r\n". - "From: user.">\r\n". - "Via: ".$MsgBody['Via']."\r\n". - "CSeq: ".($MsgBody['CSeq']+1)."\r\n". - "Call-ID: ".$MsgBody['Call-ID']."\r\n". - "Max-Forwards: 0\r\n". - "Content-Type: application/x-msnmsgr-sessionreqbody\r\n". - "Content-Length: ".strlen($MessageContent)."\r\n\r\n". - $MessageContent; - $hdr_TotalDataSizeLow=strlen($MessagePayload); - $hdr_TotalDataSizeHigh=0; - $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID, - $new_id, - 0, 0, - $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, - strlen($MessagePayload), - 0, - rand(), - 0, - 0,0); - - $message = - "MIME-Version: 1.0\r\n". - "Content-Type: application/x-msnmsgrp2p\r\n". - "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer"; - $this->SB_writeln("MSG $this->id D ".strlen($message)); - $this->SB_writedata($message); - $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message)); - $this->SB_readln();//Read ACK; - - $this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr)); - //send Data preparation message - //send 4 null bytes as data - $hdr_TotalDataSizeLow=4; - $hdr_TotalDataSizeHigh=0; - $new_id++; - $hdr = pack("LLLLLLLLLLLL", - $MsgBody['SessionID'], - $new_id, - 0, 0, - $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, - $hdr_TotalDataSizeLow, - 0, - rand(), - 0, - 0,0); - $message = - "MIME-Version: 1.0\r\n". - "Content-Type: application/x-msnmsgrp2p\r\n". - "P2P-Dest: $from_email\r\n\r\n$hdr".pack('L',0)."$footer"; - $this->SB_writeln("MSG $this->id D ".strlen($message)); - $this->SB_writedata($message); - $this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message)); - $this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr)); - $this->SB_readln();//Read ACK; - - //send Data Content.. - $footer=pack('N',1); - $new_id++; - $FileSize=filesize($PictureFilePath); - if($hTitle=fopen($PictureFilePath,'rb')) - { - $Offset=0; - //$new_id++; - while(!feof($hTitle)) - { - $FileContent=fread($hTitle,1024); - $FileContentSize=strlen($FileContent); - $hdr = pack("LLLLLLLLLLLL", - $MsgBody['SessionID'], - $new_id, - $Offset, 0, - $FileSize,0, - $FileContentSize, - 0x20, - rand(), - 0, - 0,0 - ); - $message = - "MIME-Version: 1.0\r\n". - "Content-Type: application/x-msnmsgrp2p\r\n". - "P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer"; - $this->SB_writeln("MSG $this->id D ".strlen($message)); - $this->SB_writedata($message); - $this->debug_message("*** p2p: dump send Data Content message $Offset / $FileSize :\n".$this->dump_binary($message)); - $this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr)); - //$this->SB_readln();//Read ACK; - $Offset+=$FileContentSize; - } - } - //Send Bye - /* - $MessageContent="\r\n".pack("C", 0); - $MessagePayload= - "BYE MSNMSGR:MSNSLP/1.0\r\n". - "To: \r\n". - "From: user.">\r\n". - "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n". - "CSeq: 0\r\n". - "Call-ID: ".$MsgBody['Call-ID']."\r\n". - "Max-Forwards: 0\r\n". - "Content-Type: application/x-msnmsgr-sessionclosebody\r\n". - "Content-Length: ".strlen($MessageContent)."\r\n\r\n".$MessageContent; - $footer=pack('N',0); - $hdr_TotalDataSizeLow=strlen($MessagePayload); - $hdr_TotalDataSizeHigh=0; - $new_id++; - $hdr = pack("LLLLLLLLLLLL", - 0, - $new_id, - 0, 0, - $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, - 0, - 0, - rand(), - 0, - 0,0); - $message = - "MIME-Version: 1.0\r\n". - "Content-Type: application/x-msnmsgrp2p\r\n". - "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer"; - $this->SB_writeln("MSG $id D ".strlen($message)); - $id++; - $this->SB_writedata($message); - $this->debug_message("*** p2p: dump send BYE message :\n".$this->dump_binary($message)); - */ - break; - } - //TODO: - //if ($hdr_Flag == 2) { - // just send ACK... - // $this->SB_writeln("ACK $id"); - // break; - //} - if ($hdr_SessionID == 4) { - // ignore? - $this->debug_message("*** p2p: ignore flag 4"); - break; - } - $finished = false; - if ($hdr_TotalDataSizeHigh == 0) { - // only 32 bites size - if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow) - $finished = true; - } - else { - // we won't accept any file transfer - // so I think we won't get any message size need to use 64 bits - // 64 bits size here, can't count directly... - $totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10); - $dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10); - $messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10); - $now_size = bcadd($dataoffset, $messagelength); - if (bccomp($now_size, $totalsize) >= 0) - $finished = true; - } - if (!$finished) { - // ignore not finished split packet - $this->debug_message("*** p2p: ignore split packet, not finished"); - break; - } - //$new_id = ~$hdr_Identifier; - /* - $new_id++; - $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID, - $new_id, - 0, 0, - $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, - 0, - 2, - $hdr_Identifier, - $hdr_AckID, - $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh); - $footer = pack("L", 0); - $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer"; - $len = strlen($message); - $this->SB_writeln("MSG $id D $len"); - $id++; - $this->SB_writedata($message); - $this->debug_message("*** p2p: send acknowledgement for $hdr_SessionID"); - $this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer)); - */ - break; - } - $this->debug_message("*** MSG from $from_email: $sMsg"); - $this->ReceivedMessage($from_email,$sMsg,$network,false); - break; - case '217': - $this->debug_message("*** User $user is offline. Try OIM."); - foreach($this->SwitchBoardMessageQueue as $Message) - $this->SendMessage($Message,"$user@Offline"); - $SessionEnd=true; - break; - default: - if (is_numeric($code)) - { - $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; - $this->debug_message("*** SB: $this->error"); - $SessionEnd=true; - } - break; - } - if(!$this->IsIgnoreMail($user)) $LastActive = time(); - } - if (feof($this->SBfp)) - { - // lost connection? error? try OIM later - @fclose($this->SBfp); - return false; - } - $this->SB_writeln("OUT"); - @fclose($this->SBfp); - return true; - } - /*private function switchboard_control($ip, $port, $cki_code, $user, $Messages) - { - $this->SwitchBoardProcess=1; - $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); - $this->SBfp = @fsockopen($ip, $port, $errno, $errstr, 5); - if (!$this->SBfp) - { - $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); - return false; - } - return $this->DoSwitchBoard('Active',array('cki'=>$cki_code, 'user'=>$user,'Msg'=>$Messages)); - } - private function switchboard_ring($ip, $port, $sid, $ticket,$user) - { - $this->SwitchBoardProcess=2; - $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); - $this->SBfp = @fsockopen($ip, $port, $errno, $errstr, 5); - if (!$this->SBfp) - { - $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); - return false; - } - return $this->DoSwitchBoard('Passive',array('sid'=>$sid,'user'=>$user,'ticket'=>$ticket)); - }*/ // read data for specified size private function ns_readdata($size) { @@ -2098,8 +1659,8 @@ class MSN { private function sb_readdata($socket, $size) { $data = ''; $count = 0; - while (!feof($this->SBfp)) { - $buf = @fread($this->SBfp, $size - $count); + while (!feof($socket)) { + $buf = @fread($socket, $size - $count); $data .= $buf; $count += strlen($buf); if ($count >= $size) break; @@ -2137,7 +1698,7 @@ class MSN { // show debug information function debug_message($str) { if (!$this->debug) return; - if($this->debug===STDOUT) echo $str."\n"; + if ($this->debug===STDOUT) echo $str."\n"; /*$fname=MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msn_'.strftime('%Y%m%d').'.debug'; $fp = fopen($fname, 'at'); if ($fp) { @@ -2184,55 +1745,54 @@ class MSN { */ private function MsnObj($FilePath,$Type=3) { - if(!($FileSize=filesize($FilePath))) return ''; - $Location=md5($FilePath); - $Friendly=md5($FilePath.$Type); - if(isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location]; - $sha1d=base64_encode(sha1(file_get_contents($FilePath),true)); - $sha1c=base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d",true)); - $this->MsnObjArray[$Location]=$FilePath; - $MsnObj=''; - $this->MsnObjMap[$Location]=$MsnObj; + if (!($FileSize=filesize($FilePath))) return ''; + $Location = md5($FilePath); + $Friendly = md5($FilePath.$Type); + if (isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location]; + $sha1d = base64_encode(sha1(file_get_contents($FilePath), true)); + $sha1c = base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d",true)); + $this->MsnObjArray[$Location] = $FilePath; + $MsnObj = ''; + $this->MsnObjMap[$Location] = $MsnObj; $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n"); return $MsnObj; } private function linetoArray($lines) { - $lines=str_replace("\r",'',$lines); - $lines=explode("\n",$lines); - foreach($lines as $line) { - if(!isset($line{3})) continue; - list($Key,$Val)=explode(':',$line); - $Data[trim($Key)]=trim($Val); + $lines = str_replace("\r", '', $lines); + $lines = explode("\n", $lines); + foreach ($lines as $line) { + if (!isset($line{3})) continue; + list($Key,$Val) = explode(':', $line); + $Data[trim($Key)] = trim($Val); } return $Data; } - private function GetPictureFilePath($Context) - { - $MsnObj=base64_decode($Context); - if(preg_match('/location="(.*?)"/i',$MsnObj,$Match)) - $location=$Match[1]; + private function GetPictureFilePath($Context) { + $MsnObj = base64_decode($Context); + if (preg_match('/location="(.*?)"/i', $MsnObj, $Match)) + $location = $Match[1]; $this->debug_message("*** p2p: PictureFile[$location] ::All".print_r($this->MsnObjArray,true)."\n"); - if($location&&(isset($this->MsnObjArray[$location]))) - return $this->MsnObjArray[$location]; + if ($location && isset($this->MsnObjArray[$location])) + return $this->MsnObjArray[$location]; return false; } - private function GetMsnObjDefine($Message) - { - $DefineString=''; - if(is_array($this->Emotions)) - foreach($this->Emotions as $Pattern => $FilePath) - { - if(strpos($Message,$Pattern)!==false) - $DefineString.="$Pattern\t".$this->MsnObj($FilePath,2)."\t"; - } + private function GetMsnObjDefine($Message) { + $DefineString = ''; + if (is_array($this->Emotions)) + foreach ($this->Emotions as $Pattern => $FilePath) { + if (strpos($Message, $Pattern)!==false) + $DefineString .= "$Pattern\t".$this->MsnObj($FilePath, 2)."\t"; + } return $DefineString; } /** * Read and handle incoming command from NS + * + * @return void */ private function nsReceive() { // Sign in again if not signed in or socket failed @@ -2244,15 +1804,14 @@ class MSN { } $data = $this->ns_readln(); - if($data === false) { + if ($data === false) { // There was no data / an error when reading from the socket so reconnect $this->callHandler('Reconnect'); $this->NSRetryWait($this->retry_wait); $this->signon(); return; } else { - switch (substr($data,0,3)) - { + switch (substr($data, 0, 3)) { case 'SBS': // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us // NS: <<< SBS 0 null @@ -2274,67 +1833,63 @@ class MSN { case 'LST': // NS: <<< LST {email} {alias} 11 0 - @list(/* LST */, $email, /* alias */, ) = @explode(' ', $data); + @list(/* LST */, $email, /* alias */,) = @explode(' ', $data); @list($u_name, $u_domain) = @explode('@', $email); if (!isset($this->aContactList[$u_domain][$u_name][1])) { $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; - $this->debug_message("*** add to our contact list: $u_name@$u_domain"); + $this->debug_message("*** Added to contact list: $u_name@$u_domain"); } break; case 'ADL': - // randomly, we get ADL command, someome add us to their contact list for MSNP15 + // randomly, we get ADL command, someone add us to their contact list for MSNP15 // NS: <<< ADL 0 {size} @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { + if (is_numeric($size) && $size > 0) { $data = $this->ns_readdata($size); preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) - { + if (is_array($matches) && count($matches) > 0) { $u_domain = $matches[1]; $u_name = $matches[2]; $network = $matches[4]; if (isset($this->aContactList[$u_domain][$u_name][$network])) - $this->debug_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain"); + $this->debug_message("*** Someone (network: $network) added us to their list (but already in our list): $u_name@$u_domain"); else { $re_login = false; $cnt = 0; - foreach (array('Allow', 'Reverse') as $list) - { - if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) - { + foreach (array('Allow', 'Reverse') as $list) { + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { if ($re_login) { - $this->debug_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); continue; } $aTickets = $this->get_passport_ticket(); if (!$aTickets || !is_array($aTickets)) { // failed to login? ignore it - $this->debug_message("*** can't re-login, something wrong here"); - $this->debug_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + $this->debug_message("*** Could not re-login, something wrong here"); + $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); continue; } $re_login = true; $this->ticket = $aTickets; - $this->debug_message("**** get new ticket, try it again"); - if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) - { - $this->debug_message("*** can't add $u_name@$u_domain (network: $network) to $list"); + $this->debug_message("**** Got new ticket, trying again"); + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { + $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); continue; } } $this->aContactList[$u_domain][$u_name][$network][$list] = false; $cnt++; } - $this->debug_message("*** someone (network: $network) add us to their list: $u_name@$u_domain"); + $this->debug_message("*** Someone (network: $network) added us to their list: $u_name@$u_domain"); } $str = ''; $len = strlen($str); + + $this->callHandler('AddedToList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network)); } else - $this->debug_message("*** someone add us to their list: $data"); - $this->AddUsToMemberList($u_name.'@'.$u_domain, $network); + $this->debug_message("*** Someone added us to their list: $data"); } break; @@ -2342,29 +1897,29 @@ class MSN { // randomly, we get RML command, someome remove us to their contact list for MSNP15 // NS: <<< RML 0 {size} @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { + if (is_numeric($size) && $size > 0) { $data = $this->ns_readdata($size); preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) - { + if (is_array($matches) && count($matches) > 0) { $u_domain = $matches[1]; $u_name = $matches[2]; $network = $matches[4]; - if (isset($this->aContactList[$u_domain][$u_name][$network])) - { + if (isset($this->aContactList[$u_domain][$u_name][$network])) { $aData = $this->aContactList[$u_domain][$u_name][$network]; + foreach ($aData as $list => $id) - $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); + $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); + unset($this->aContactList[$u_domain][$u_name][$network]); - $this->debug_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain"); + $this->debug_message("*** Someone (network: $network) removed us from their list: $u_name@$u_domain"); } else - $this->debug_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain"); - $this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network); + $this->debug_message("*** Someone (network: $network) removed us from their list (but not in our list): $u_name@$u_domain"); + + $this->callHandler('RemovedFromList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network)); } else - $this->debug_message("*** someone remove us from their list: $data"); + $this->debug_message("*** Someone removed us from their list: $data"); } break; @@ -2386,8 +1941,7 @@ class MSN { continue; } if (strncasecmp($line, 'Content-Type:', 13) == 0) { - if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && - strpos($line, 'text/x-msmsgsoimnotification') === false) { + if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && strpos($line, 'text/x-msmsgsoimnotification') === false) { // we just need text/x-msmsgsinitialmdatanotification // or text/x-msmsgsoimnotification $ignore = true; @@ -2402,32 +1956,33 @@ class MSN { } } if ($ignore) { - $this->debug_message("*** ingnore MSG for: $line"); + $this->debug_message("*** Ignoring MSG for: $line"); break; } if ($maildata == '') { - $this->debug_message("*** ingnore MSG not for OIM"); + $this->debug_message("*** Ignoring MSG not for OIM"); break; } $re_login = false; if (strcasecmp($maildata, 'too-large') == 0) { - $this->debug_message("*** large mail-data, need to get the data via SOAP"); + $this->debug_message("*** Large mail-data, need to get the data via SOAP"); $maildata = $this->getOIM_maildata(); if ($maildata === false) { - $this->debug_message("*** can't get mail-data via SOAP"); + $this->debug_message("*** Could not get mail-data via SOAP"); + // maybe we need to re-login again $aTickets = $this->get_passport_ticket(); if (!$aTickets || !is_array($aTickets)) { // failed to login? ignore it - $this->debug_message("*** can't re-login, something wrong here, ignore this OIM"); + $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM"); break; } $re_login = true; $this->ticket = $aTickets; - $this->debug_message("*** get new ticket, try it again"); + $this->debug_message("*** Got new ticket, trying again"); $maildata = $this->getOIM_maildata(); if ($maildata === false) { - $this->debug_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM"); + $this->debug_message("*** Could not get mail-data via SOAP, and re-login already attempted, ignoring this OIM"); break; } } @@ -2445,7 +2000,7 @@ class MSN { $p = substr($p, $end); } if (count($aOIMs) == 0) { - $this->debug_message("*** ingnore empty OIM"); + $this->debug_message("*** Ignoring empty OIM"); break; } foreach ($aOIMs as $maildata) { @@ -2460,23 +2015,23 @@ class MSN { // N: sender alias preg_match('#(.*)#', $maildata, $matches); if (count($matches) == 0) { - $this->debug_message("*** ingnore OIM maildata without type"); + $this->debug_message("*** Ignoring OIM maildata without type"); continue; } $oim_type = $matches[1]; if ($oim_type = 13) - $network = 32; + $network = 32; else - $network = 1; + $network = 1; preg_match('#(.*)#', $maildata, $matches); if (count($matches) == 0) { - $this->debug_message("*** ingnore OIM maildata without sender"); + $this->debug_message("*** Ignoring OIM maildata without sender"); continue; } $oim_sender = $matches[1]; preg_match('#(.*)#', $maildata, $matches); if (count($matches) == 0) { - $this->debug_message("*** ingnore OIM maildata without msgid"); + $this->debug_message("*** Ignoring OIM maildata without msgid"); continue; } $oim_msgid = $matches[1]; @@ -2484,18 +2039,18 @@ class MSN { $oim_size = (count($matches) == 0) ? 0 : $matches[1]; preg_match('#(.*)#', $maildata, $matches); $oim_time = (count($matches) == 0) ? 0 : $matches[1]; - $this->debug_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); + $this->debug_message("*** OIM received from $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); $sMsg = $this->getOIM_message($oim_msgid); if ($sMsg === false) { - $this->debug_message("*** can't get OIM, msgid = $oim_msgid"); + $this->debug_message("*** Could not get OIM, msgid = $oim_msgid"); if ($re_login) { - $this->debug_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM"); continue; } $aTickets = $this->get_passport_ticket(); if (!$aTickets || !is_array($aTickets)) { // failed to login? ignore it - $this->debug_message("*** can't re-login, something wrong here, ignore this OIM"); + $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM"); continue; } $re_login = true; @@ -2503,13 +2058,11 @@ class MSN { $this->debug_message("*** get new ticket, try it again"); $sMsg = $this->getOIM_message($oim_msgid); if ($sMsg === false) { - $this->debug_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM"); + $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM"); continue; } } $this->debug_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); - - //$this->ReceivedMessage($oim_sender,$sMsg,$network,true); $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true)); } } @@ -2519,8 +2072,7 @@ class MSN { // randomly, we get UBM, this is the message from other network, like Yahoo! // NS: <<< UBM {email} $network $type {size} @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) - { + if (is_numeric($size) && $size > 0) { $data = $this->ns_readdata($size); $aLines = @explode("\n", $data); $header = true; @@ -2546,13 +2098,11 @@ class MSN { $sMsg .= $str; } } - if($ignore) - { - $this->debug_message("*** ingnore from $from_email: $line"); + if ($ignore) { + $this->debug_message("*** Ignoring message from $from_email: $line"); break; } $this->debug_message("*** MSG from $from_email (network: $network): $sMsg"); - //$this->ReceivedMessage($from_email,$sMsg,$network,false); $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); } break; @@ -2563,7 +2113,7 @@ class MSN { @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); // we don't need the notification data, so just ignore it if (is_numeric($size) && $size > 0) - $this->ns_readdata($size); + $this->ns_readdata($size); break; case 'CHL': @@ -2576,7 +2126,7 @@ class MSN { $this->ns_writeln("QRY $this->id $this->prod_id 32"); $this->ns_writedata($fingerprint); $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if($this->PhotoStickerFile!==false) + if ($this->PhotoStickerFile !== false) $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); break; case 'CHG': @@ -2601,41 +2151,10 @@ class MSN { $this->NSLogout(); continue; } - if(count($this->MessageQueue)) - { - foreach($this->MessageQueue as $User => $Message) - { - //$this->ChildProcess[$ChildPid] - $this->debug_message("*** XFR SB $User"); - $pid=pcntl_fork(); - if($pid) - { - //Parrent Process - $this->ChildProcess[$pid]=$User; - break; - } - elseif($pid==-1) - { - $this->debug_message("*** Fork Error $User"); - break; - } - else - { - //Child Process - $this->debug_message("*** Child Process Start for $User"); - unset($Message['XFRSent']); - unset($Message['ReqTime']); + + $this->debug_message("NS: <<< XFR SB"); + $user = array_shift($this->waitingForXFR); $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); - if ($bSBresult === false) - { - // error for switchboard - $this->debug_message("!!! error for sending message to ".$User); - } - die; - } - } - unset($this->MessageQueue[$User]); - } /* $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); if ($bSBresult === false) { @@ -2651,7 +2170,7 @@ class MSN { break; case 'RNG': - if($this->PhotoStickerFile!==false) + if ($this->PhotoStickerFile !== false) $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); else $this->ns_writeln("CHG $this->id NLN $this->clientid"); @@ -2660,32 +2179,9 @@ class MSN { $this->debug_message("NS: <<< RNG $data"); @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); @list($sb_ip, $sb_port) = @explode(':', $server); - if($this->IsIgnoreMail($email)) - { - $this->debug_message("*** Ignore RNG from $email"); - break; - } $this->debug_message("*** RING from $email, $sb_ip:$sb_port"); - $this->addContact($email,1,$email, true); - $pid=pcntl_fork(); - if($pid) - { - //Parrent Process - $this->ChildProcess[$pid]='RNG'; - break; - } - elseif($pid==-1) - { - $this->debug_message("*** Fork Error $User"); - break; - } - else - { - //Child Process - $this->debug_message("*** Ring Child Process Start for $User"); - $this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email); - die; - } + $this->addContact($email, 1, $email, true); + $this->connectToSBSession('Passive', $sb_ip, $sb_port, $email, array('sid' => $sid, 'ticket' => $ticket)); break; case 'OUT': // force logout from NS @@ -2710,8 +2206,428 @@ class MSN { * Read and handle incoming command/message from * a switchboard session socket */ - private function sbReceive() { + private function sbReceive($socket) { + $intsocket = (int) $socket; + $session = &$this->switchBoardSessions[$intsocket]; + if (feof($socket)) { + // Unset session lookup value + unset($this->switchBoardSessionLookup[$session['to']]); + + // Unset session itself + unset($this->switchBoardSessions[$intsocket]); + return; + } + + $id = &$session['id']; + + $data = $this->sb_readln($socket); + $code = substr($data, 0, 3); + switch($code) { + case 'IRO': + // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid} + @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data); + $this->debug_message("*** $email joined session"); + $session['joined'] = true; + break; + case 'BYE': + $this->debug_message("*** Quit for BYE"); + $this->endSBSession(); + break; + case 'USR': + // SB: <<< USR {id} OK {user} {alias} + // we don't need the data, just ignore it + // request user to join this switchboard + // SB: >>> CAL {id} {user} + $this->sb_writeln($socket, $id, "CAL $this->id $user"); + break; + case 'CAL': + // SB: <<< CAL {id} RINGING {?} + // we don't need this, just ignore, and wait for other response + $session['id']++; + break; + case 'JOI': + // SB: <<< JOI {user} {alias} {clientid?} + // someone join us + // we don't need the data, just ignore it + // no more user here + $session['joined'] = true; + break; + case 'MSG': + // SB: <<< MSG {email} {alias} {len} + @list(/* MSG */, $from_email, /* alias */, $len, ) = @explode(' ', $data); + $len = trim($len); + $data = $this->sb_readdata($socket, $len); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $is_p2p = false; + $sMsg = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'TypingUser:', 11) == 0) { + // typing notification, just ignore + $ignore = true; + break; + } + if (strncasecmp($line, 'Chunk:', 6) == 0) { + // we don't handle any split message, just ignore + $ignore = true; + break; + } + if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) { + // p2p message, ignore it, but we need to send acknowledgement for it... + $is_p2p = true; + $p = strstr($data, "\n\n"); + $sMsg = ''; + if ($p === false) { + $p = strstr($data, "\r\n\r\n"); + if ($p !== false) + $sMsg = substr($p, 4); + } + else + $sMsg = substr($p, 2); + break; + } + if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) { + // ignore all application/x-... message + // for example: + // application/x-ms-ink => ink message + $ignore = true; + break; + } + if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) { + // ignore all text/x-... message + // for example: + // text/x-msnmsgr-datacast => nudge, voice clip.... + // text/x-mms-animemoticon => customized animemotion word + $ignore = true; + break; + } + continue; + } + if ($sMsg !== '') + $sMsg .= "\n"; + $sMsg .= $line; + } + if ($ignore) { + $this->debug_message("*** Ignoring SB data from $from_email: $line"); + break; + } + if ($is_p2p) { + // we will ignore any p2p message after sending acknowledgement + $ignore = true; + $len = strlen($sMsg); + $this->debug_message("*** p2p message from $from_email, size $len"); + // header = 48 bytes + // content >= 0 bytes + // footer = 4 bytes + // so it need to >= 52 bytes + /*if ($len < 52) { + $this->debug_message("*** p2p: size error, less than 52!"); + break; + }*/ + $aDwords = @unpack("V12dword", $sMsg); + if (!is_array($aDwords)) { + $this->debug_message("*** p2p: header unpack error!"); + break; + } + $this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg)); + $hdr_SessionID = $aDwords['dword1']; + $hdr_Identifier = $aDwords['dword2']; + $hdr_DataOffsetLow = $aDwords['dword3']; + $hdr_DataOffsetHigh = $aDwords['dword4']; + $hdr_TotalDataSizeLow = $aDwords['dword5']; + $hdr_TotalDataSizeHigh = $aDwords['dword6']; + $hdr_MessageLength = $aDwords['dword7']; + $hdr_Flag = $aDwords['dword8']; + $hdr_AckID = $aDwords['dword9']; + $hdr_AckUID = $aDwords['dword10']; + $hdr_AckSizeLow = $aDwords['dword11']; + $hdr_AckSizeHigh = $aDwords['dword12']; + $this->debug_message("*** p2p: header SessionID = $hdr_SessionID"); + $this->debug_message("*** p2p: header Inentifier = $hdr_Identifier"); + $this->debug_message("*** p2p: header Data Offset Low = $hdr_DataOffsetLow"); + $this->debug_message("*** p2p: header Data Offset High = $hdr_DataOffsetHigh"); + $this->debug_message("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow"); + $this->debug_message("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh"); + $this->debug_message("*** p2p: header MessageLength = $hdr_MessageLength"); + $this->debug_message("*** p2p: header Flag = $hdr_Flag"); + $this->debug_message("*** p2p: header AckID = $hdr_AckID"); + $this->debug_message("*** p2p: header AckUID = $hdr_AckUID"); + $this->debug_message("*** p2p: header AckSize Low = $hdr_AckSizeLow"); + $this->debug_message("*** p2p: header AckSize High = $hdr_AckSizeHigh"); + if ($hdr_Flag == 2) { + //This is an ACK from SB ignore.... + $this->debug_message("*** p2p: //This is an ACK from SB ignore....:\n"); + break; + } + $MsgBody = $this->linetoArray(substr($sMsg, 48, -4)); + $this->debug_message("*** p2p: body".print_r($MsgBody, true)); + if (($MsgBody['EUF-GUID']=='{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}')&&($PictureFilePath=$this->GetPictureFilePath($MsgBody['Context']))) { + while (true) { + if ($this->sb_readln($socket) === false) break; + } + $this->debug_message("*** p2p: Inv hdr:\n".$this->dump_binary(substr($sMsg, 0, 48))); + preg_match('/{([0-9A-F\-]*)}/i', $MsgBody['Via'], $Matches); + $BranchGUID = $Matches[1]; + //it's an invite to send a display picture. + $new_id = ~$hdr_Identifier; + $hdr = pack( + "LLLLLLLLLLLL", $hdr_SessionID, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + 0, + 2, + $hdr_Identifier, + $hdr_AckID, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh + ); + $footer = pack("L", 0); + $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer"; + $len = strlen($message); + $this->sb_writeln($socket, $id, "MSG $this->id D $len"); + $this->sb_writedata($socket, $message); + $this->debug_message("*** p2p: send display picture acknowledgement for $hdr_SessionID"); + $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message)); + $this->sb_readln($socket); // Read ACK; + $this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr)); + $new_id -= 3; + //Send 200 OK message + $MessageContent="SessionID: ".$MsgBody['SessionID']."\r\n\r\n".pack("C", 0); + $MessagePayload= + "MSNSLP/1.0 200 OK\r\n". + "To: \r\n". + "From: user.">\r\n". + "Via: ".$MsgBody['Via']."\r\n". + "CSeq: ".($MsgBody['CSeq']+1)."\r\n". + "Call-ID: ".$MsgBody['Call-ID']."\r\n". + "Max-Forwards: 0\r\n". + "Content-Type: application/x-msnmsgr-sessionreqbody\r\n". + "Content-Length: ".strlen($MessageContent)."\r\n\r\n". + $MessageContent; + $hdr_TotalDataSizeLow=strlen($MessagePayload); + $hdr_TotalDataSizeHigh=0; + $hdr = pack( + "LLLLLLLLLLLL", $hdr_SessionID, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + strlen($MessagePayload), + 0, + rand(), + 0, + 0, 0 + ); + + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer"; + $this->sb_writeln($socket, $id, "MSG $this->id D ".strlen($message)); + $this->sb_writedata($socket, $message); + $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message)); + $this->sb_readln($socket); // Read ACK; + + $this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr)); + // send data preparation message + // send 4 null bytes as data + $hdr_TotalDataSizeLow = 4; + $hdr_TotalDataSizeHigh = 0 ; + $new_id++; + $hdr = pack( + "LLLLLLLLLLLL", + $MsgBody['SessionID'], + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + $hdr_TotalDataSizeLow, + 0, + rand(), + 0, + 0, 0 + ); + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr".pack('L', 0)."$footer"; + $this->sb_writeln($socket, $id, "MSG $this->id D ".strlen($message)); + $this->sb_writedata($socket, $message); + $this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message)); + $this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr)); + $this->sb_readln($socket); // Read ACK; + + // send Data Content.. + $footer=pack('N',1); + $new_id++; + $FileSize=filesize($PictureFilePath); + if ($hTitle=fopen($PictureFilePath,'rb')) { + $Offset = 0; + //$new_id++; + while (!feof($hTitle)) { + $FileContent = fread($hTitle, 1024); + $FileContentSize = strlen($FileContent); + $hdr = pack( + "LLLLLLLLLLLL", + $MsgBody['SessionID'], + $new_id, + $Offset, 0, + $FileSize, 0, + $FileContentSize, + 0x20, + rand(), + 0, + 0, 0 + ); + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer"; + $this->sb_writeln($socket, $id, "MSG $this->id D ".strlen($message)); + $this->sb_writedata($socket, $message); + $this->debug_message("*** p2p: dump send Data Content message $Offset / $FileSize :\n".$this->dump_binary($message)); + $this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr)); + //$this->SB_readln($socket);//Read ACK; + $Offset += $FileContentSize; + } + } + //Send Bye + /* + $MessageContent="\r\n".pack("C", 0); + $MessagePayload= + "BYE MSNMSGR:MSNSLP/1.0\r\n". + "To: \r\n". + "From: user.">\r\n". + "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n". + "CSeq: 0\r\n". + "Call-ID: ".$MsgBody['Call-ID']."\r\n". + "Max-Forwards: 0\r\n". + "Content-Type: application/x-msnmsgr-sessionclosebody\r\n". + "Content-Length: ".strlen($MessageContent)."\r\n\r\n".$MessageContent; + $footer=pack('N',0); + $hdr_TotalDataSizeLow=strlen($MessagePayload); + $hdr_TotalDataSizeHigh=0; + $new_id++; + $hdr = pack("LLLLLLLLLLLL", + 0, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + 0, + 0, + rand(), + 0, + 0,0); + $message = + "MIME-Version: 1.0\r\n". + "Content-Type: application/x-msnmsgrp2p\r\n". + "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer"; + $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message)); + $id++; + $this->sb_writedata($socket, $message); + $this->debug_message("*** p2p: dump send BYE message :\n".$this->dump_binary($message)); + */ + break; + } + //TODO: + //if ($hdr_Flag == 2) { + // just send ACK... + // $this->sb_writeln($socket, $id, "ACK $id"); + // break; + //} + if ($hdr_SessionID == 4) { + // ignore? + $this->debug_message("*** p2p: ignore flag 4"); + break; + } + $finished = false; + if ($hdr_TotalDataSizeHigh == 0) { + // only 32 bites size + if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow) + $finished = true; + } + else { + // we won't accept any file transfer + // so I think we won't get any message size need to use 64 bits + // 64 bits size here, can't count directly... + $totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10); + $dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10); + $messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10); + $now_size = bcadd($dataoffset, $messagelength); + if (bccomp($now_size, $totalsize) >= 0) + $finished = true; + } + if (!$finished) { + // ignore not finished split packet + $this->debug_message("*** p2p: ignore split packet, not finished"); + break; + } + //$new_id = ~$hdr_Identifier; + /* + $new_id++; + $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID, + $new_id, + 0, 0, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh, + 0, + 2, + $hdr_Identifier, + $hdr_AckID, + $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh); + $footer = pack("L", 0); + $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer"; + $len = strlen($message); + $this->sb_writeln($socket, $id, "MSG $id D $len"); + $id++; + $this->sb_writedata($socket, $message); + $this->debug_message("*** p2p: send acknowledgement for $hdr_SessionID"); + $this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer)); + */ + break; + } + $this->debug_message("*** MSG from $from_email: $sMsg"); + $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); + break; + case '217': + $this->debug_message("*** User $user is offline. Trying OIM."); + $session['offline'] = true; + break; + default: + if (is_numeric($code)) { + $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; + $this->debug_message("*** SB: $this->error"); + $sessionEnd=true; + } + break; + } + } + + /** + * Called when we want to end a switchboard session + * or a switchboard session ends + * + * @param resource $socket Socket + * @param boolean $killsession Whether to delete the session + * @return void + */ + private function endSBSession($socket, $killsession = false) { + if (!self::socketcheck($socket)) { + $this->sb_writeln($socket, $fake = 0, 'OUT'); + } + @fclose($socket); + + // Unset session lookup value + $intsocket = (int) $socket; + unset($this->switchBoardSessionLookup[$this->switchBoardSessions[$intsocket]['to']]); + + // Unset session itself + unset($this->switchBoardSessions[$intsocket]); } /** @@ -2722,16 +2638,16 @@ class MSN { * @return void */ public function receive() { - //First, get an array of sockets that have data that is ready to be read + // First, get an array of sockets that have data that is ready to be read $ready = array(); $ready = $this->getSockets(); - $numrdy = stream_select($ready, $w = NULL, $x = NULL,NULL); + $numrdy = stream_select($ready, $w = NULL, $x = NULL, NULL); - //Now that we've waited for something, go through the $ready - //array and read appropriately + // Now that we've waited for something, go through the $ready + // array and read appropriately - for($i = 0;$iNSfp) { + foreach ($ready as $socket) { + if ($socket == $this->NSfp) { $this->nsReceive(); } else { $this->sbReceive($socket); @@ -2741,21 +2657,29 @@ class MSN { /** * Send a request for a switchboard session - * @param String $to Target email for switchboard session + * + * @param string $to Target email for switchboard session */ private function reqSBSession($to) { $this->debug_message("*** Request SB for $to"); $this->ns_writeln("XFR $this->id SB"); // Add to the queue of those waiting for a switchboard session reponse - $this->switchBoardSessions[$to] = array('socket' => NULL, 'id' => 1, 'lastActive' => NULL, 'joined' => false, 'XFRReqTime' => time()); + $this->switchBoardSessions[$to] = array( + 'to' => $to, + 'socket' => NULL, + 'id' => 1, + 'joined' => false, + 'offline' => false, + 'XFRReqTime' => time() + ); $this->waitingForXFR[] = &$this->switchBoardSessions[$to]; } /** * Following an XFR or RNG, connect to the switchboard session * - * @param string $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case or RNG) + * @param string $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case of RNG) * @param string $ip IP of Switchboard * @param integer $port Port of Switchboard * @param string $to User on other end of Switchboard @@ -2763,21 +2687,35 @@ class MSN { * @return boolean true if successful */ private function connectToSBSession($mode, $ip, $port, $to, $param) { - $this->debug_message("*** SB: try to connect to switchboard server $ip:$port"); + $this->debug_message("*** SB: Trying to connect to switchboard server $ip:$port"); - $this->switchBoardSessions[$to]['socket'] = @fsockopen($ip, $port, $errno, $errstr, 5); - $socket = $this->switchBoardSessions[$to]['socket']; - if(!$socket) { + $socket = @fsockopen($ip, $port, $errno, $errstr, $this->timeout); + if (!$socket) { $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr"); return false; } - $this->switchBoardSockets[(int) $socket] = $socket; - stream_set_timeout($socket, $this->SBStreamTimeout); + // Store the socket in the lookup array + $this->switchBoardSessionLookup[$to] = $socket; - $id = &$this->switchBoardSessions[$to]['id']; + // Store the socket in the sessions array + $intsocket = (int) $socket; + $this->switchBoardSessions[$to] = array( + 'to' => $to, + 'socket' => $socket, + 'id' => 1, + 'joined' => false, + 'offline' => false, + 'XFRReqTime' => time() + ); - if($mode == 'Active') { + // Change the index of the session to the socket + $this->switchBoardSessions[$intsocket] = $this->switchBoardSessions[$to]; + unset($this->switchBoardSessions[$to]); + + $id = &$this->switchBoardSessions[$intsocket]['id']; + + if ($mode == 'Active') { $cki_code = $param['cki']; // SB: >>> USR {id} {user} {cki} @@ -2790,8 +2728,6 @@ class MSN { // SB: >>> ANS {id} {user} {ticket} {session_id} $this->sb_writeln($socket, $id, "ANS $id $this->user $ticket $sid"); } - - $this->switchBoardSessions[$to]['lastActive'] = time(); } /** @@ -2802,24 +2738,23 @@ class MSN { * @return boolean true on success */ private function sendMessageViaSB($to, $message) { - if(socketcheck($this->switchBoardSessions[$to]['socket'])) { - $this->reqSBSession($to); + $socket = $this->switchBoardSessionLookup[$to]; + if (self::socketcheck($socket)) { return false; } - if(!$this->switchBoardSessions[$to]['joined']) { + if (!$this->switchBoardSessions[$to]['joined']) { // If our participant has not joined the session yet we can't message them! return false; } - $id = &$this->switchBoardSessions[$to]['id']; - $socket = $this->switchBoardSessions[$to]['socket']; + $intsocket = (int) $socket; + $id = &$this->switchBoardSessions[$intsocket]['id']; $aMessage = $this->getMessage($Message); //CheckEmotion... $MsnObjDefine=$this->GetMsnObjDefine($aMessage); - if($MsnObjDefine !== '') - { + if ($MsnObjDefine !== '') { $SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine"; $len = strlen($SendString); // TODO handle failure during write to socket @@ -2838,9 +2773,11 @@ class MSN { /** * Send offline message + * * @param string $to Intended recipient * @param string $sMessage Message * @param string $lockkey Lock key + * @return mixed true on success or error data */ private function sendOIM($to, $sMessage, $lockkey) { $XML = ' @@ -2949,17 +2886,19 @@ X-OIM-Sequence-Num: 1 /** * Send a message to a user on another network * - * @param $message Message - * @param $to Intended recipient - * @param $network Network + * @param string $to Intended recipient + * @param string $message Message + * @param integer $network Network * @return void */ - private function sendOtherNetworkMessage($message, $to, $network) { + private function sendOtherNetworkMessage($to, $message, $network) { $message = $this->getMessage($message, $network); $len = strlen($message); + // TODO Introduce error checking for message sending $this->ns_writeln("UUM $this->id $to $network 1 $len"); $this->ns_writedata($Message); $this->debug_message("*** Sent to $to (network: $network):\n$Message"); + return true; } /** @@ -2971,21 +2910,35 @@ X-OIM-Sequence-Num: 1 * @param string $message Message */ public function sendMessage($to, $message) { - if($message != '') { + if ($message != '') { list($name, $host, $network) = explode('@', $to); $network = $network == '' ? 1 : $network; + $recipient = $name.$host; - if ($network === 1 && $this->switchBoardSessions[$to]['socket'] !== NULL) { - $recipient = $name . $host; - $this->debug_message("*** Attempting to send message to $recipient using existing SB session"); - - if ($this->sendMessageViaSB($message, $recipient)) { - $this->debug_message('*** Message sent successfully'); - return true; - } else { - $this->debug_message('*** Message sending failed, requesting new SB session'); - $this->reqSBSession($to); + if ($network === 1) { + if (!isset($this->switchBoardSessionLookup[$recipient]) && (!isset($this->switchBoardSessions[$recipient]) + || time() - $this->switchBoardSessions[$recipient]['XFRReqTime'] > $this->XFRReqTimeout)) { + $this->debug_message("*** No existing SB session or request has timed out"); + $this->reqSBSession($recipient); return false; + } else { + $socket = $this->switchBoardSessionLookup[$to]; + if ($this->switchBoardSessions[(int) $socket]['offline']) { + $this->debug_message("*** Contact ($recipient) offline, sending OIM"); + $this->endSBSession($socket); + return $this->sendMessage($recipient.'@Offline', $message); + } else { + $this->debug_message("*** Attempting to send message to $recipient using existing SB session"); + + if ($this->sendMessageViaSB($recipient, $message)) { + $this->debug_message('*** Message sent successfully'); + return true; + } else { + $this->debug_message('*** Message sending failed, requesting new SB session'); + $this->reqSBSession($to); + return false; + } + } } } elseif ($network == 'Offline') { //Send OIM @@ -2993,7 +2946,7 @@ X-OIM-Sequence-Num: 1 $lockkey = ''; $re_login = false; for ($i = 0; $i < $this->oim_try; $i++) { - if (($oim_result = $this->sendOIM($to, $message, $lockkey)) === true) break; + if (($oim_result = $this->sendOIM($recipient, $message, $lockkey)) === true) break; if (is_array($oim_result) && $oim_result['challenge'] !== false) { // need challenge lockkey $this->debug_message("*** Need challenge code for ".$oim_result['challenge']); @@ -3002,14 +2955,14 @@ X-OIM-Sequence-Num: 1 } if ($oim_result === false || $oim_result['auth_policy'] !== false) { if ($re_login) { - $this->debug_message("*** Can't send OIM, but we already re-logged-in again, so ignore this OIM"); - return true; + $this->debug_message("*** Can't send OIM, but we already re-logged-in again, so returning false"); + return false; } $this->debug_message("*** Can't send OIM, maybe ticket expired, trying to login again"); // Maybe we need to re-login again if (!$this->get_passport_ticket()) { - $this->debug_message("*** Can't re-login, something went wrong here, ignore this OIM"); + $this->debug_message("*** Can't re-login, something went wrong here, returning false"); return false; } $this->debug_message("*** Getting new ticket and trying again"); @@ -3017,27 +2970,13 @@ X-OIM-Sequence-Num: 1 } } } else { - $this->debug_message("*** Not MSN network or no existing SB session"); - $this->reqSBSession($to); - return false; + // Other network + return $this->sendOtherNetworkMessage($recipient, $message, $network); } } return true; } - //FIXME Not sure if this is needed? - private function endSBSession($socket) { - if (feof($socket)) { - // lost connection? error? try OIM later - @fclose($socket); - return false; - } - $fake = 0; - $this->sb_writeln($socket, $fake, "OUT"); - @fclose($socket); - return true; - } - /** * Sends a ping command * @@ -3069,7 +3008,7 @@ X-OIM-Sequence-Num: 1 * @return array Array of Switchboard sockets */ public function getSBSockets() { - return $this->switchBoardSockets; + return $this->switchBoardSessionLookup; } /** @@ -3078,7 +3017,7 @@ X-OIM-Sequence-Num: 1 * @return array Array of socket resources */ public function getSockets() { - return array_merge($this->NSfp, $this->switchBoardSockets); + return array_merge(array($this->NSfp), $this->switchBoardSessionLookup); } /** @@ -3120,7 +3059,8 @@ X-OIM-Sequence-Num: 1 * Registers a user handler * * Handler List - * IMIn, Pong, ConnectFailed, Reconnect + * IMIn, Pong, ConnectFailed, Reconnect, + * AddedToList, RemovedFromList * * @param string $event Event name * @param string $handler User function to call diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index 8f436bdff8..5b04995c18 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -95,11 +95,15 @@ class MsnManager extends ImManager { */ function connect() { if (!$this->conn) { - $this->conn = new MSN(array('user' => $this->plugin->user, - 'password' => $this->plugin->password, - 'alias' => $this->plugin->nickname, - 'psm' => 'Send me a message to post a notice', - 'debug' => true)); + $this->conn = new MSN( + array( + 'user' => $this->plugin->user, + 'password' => $this->plugin->password, + 'alias' => $this->plugin->nickname, + 'psm' => 'Send me a message to post a notice', + 'debug' => true + ) + ); $this->conn->registerHandler("IMIn", array($this, 'handle_msn_message')); $this->conn->registerHandler('Pong', array($this, 'update_ping_time')); $this->conn->registerHandler('ConnectFailed', array($this, 'handle_connect_failed')); @@ -124,6 +128,7 @@ class MsnManager extends ImManager { $this->conn->sendPing(); $this->lastping = time(); + $this->pingInterval = 50; return true; } From 4ee2c12507b046e048ff023b94872cbbe9bde9a4 Mon Sep 17 00:00:00 2001 From: Craig Andrews Date: Tue, 15 Jun 2010 17:04:15 -0400 Subject: [PATCH 054/655] Use presence of IM plugins to decide if "IM" menu option should be shown in Connect --- lib/connectsettingsaction.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/connectsettingsaction.php b/lib/connectsettingsaction.php index b9c14799e0..c3a88be552 100644 --- a/lib/connectsettingsaction.php +++ b/lib/connectsettingsaction.php @@ -105,7 +105,7 @@ class ConnectSettingsNav extends Widget # action => array('prompt', 'title') $menu = array(); - if (common_config('xmpp', 'enabled')) { + if (Event::handle('GetImTransports', array(&$transports))) { $menu['imsettings'] = array(_('IM'), _('Updates by instant messenger (IM)')); From d41298950b9c2d05067d71f6b2ab3315c6330489 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Wed, 16 Jun 2010 00:04:59 +0100 Subject: [PATCH 055/655] Added validate regexp and a few more comments --- plugins/Msn/MsnPlugin.php | 29 ++++++++++++++++++----------- plugins/Msn/msnmanager.php | 3 +++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/plugins/Msn/MsnPlugin.php b/plugins/Msn/MsnPlugin.php index 8452f15220..f00333d728 100644 --- a/plugins/Msn/MsnPlugin.php +++ b/plugins/Msn/MsnPlugin.php @@ -58,7 +58,7 @@ class MsnPlugin extends ImPlugin { * * @return string Name of service */ - function getDisplayName() { + public function getDisplayName() { return _m('MSN'); } @@ -68,7 +68,7 @@ class MsnPlugin extends ImPlugin { * @param string $screenname screenname to normalize * @return string an equivalent screenname in normalized form */ - function normalize($screenname) { + public function normalize($screenname) { $screenname = str_replace(" ","", $screenname); return strtolower($screenname); } @@ -78,7 +78,7 @@ class MsnPlugin extends ImPlugin { * * @return string Screenname */ - function daemon_screenname() { + public function daemon_screenname() { return $this->user; } @@ -86,20 +86,21 @@ class MsnPlugin extends ImPlugin { * Validate (ensure the validity of) a screenname * * @param string $screenname screenname to validate - * * @return boolean */ - function validate($screenname) { - //TODO Correct this for MSN screennames - //if(preg_match('/^[a-z]\w{2,15}$/i', $screenname)) { - return true; + public function validate($screenname) { + // RFC 2822 (simplified) regexp + if(preg_match('/[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&\'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/i', $screenname)) { + return true; + } else { + return false; + } } /** * Load related modules when needed * * @param string $cls Name of the class to be loaded - * * @return boolean hook value; true means continue processing, false means stop. */ public function onAutoload($cls) { @@ -159,7 +160,7 @@ class MsnPlugin extends ImPlugin { /** * Initialize plugin * - * @return void + * @return boolean */ public function initialize() { if (!isset($this->user)) { @@ -175,7 +176,13 @@ class MsnPlugin extends ImPlugin { return true; } - function onPluginVersion(&$versions) { + /** + * Get plugin information + * + * @param array $versions array to insert information into + * @return void + */ + public function onPluginVersion(&$versions) { $versions[] = array( 'name' => 'MSN', 'version' => STATUSNET_VERSION, diff --git a/plugins/Msn/msnmanager.php b/plugins/Msn/msnmanager.php index 5b04995c18..66152f0d2a 100644 --- a/plugins/Msn/msnmanager.php +++ b/plugins/Msn/msnmanager.php @@ -146,6 +146,7 @@ class MsnManager extends ImManager { * Passes it back to the queuing system * * @param array $data Data + * @return void */ private function handle_msn_message($data) { $this->plugin->enqueue_incoming_raw($data); @@ -156,6 +157,7 @@ class MsnManager extends ImManager { * Called by callback to log failure during connect * * @param void $data Not used (there to keep callback happy) + * @return void */ function handle_connect_failed($data) { common_log(LOG_NOTICE, 'MSN connect failed, retrying'); @@ -165,6 +167,7 @@ class MsnManager extends ImManager { * Called by callback to log reconnection * * @param void $data Not used (there to keep callback happy) + * @return void */ function handle_reconnect($data) { common_log(LOG_NOTICE, 'MSN reconnecting'); From 2d883eed893f4c7178030c032b518444b43eeabe Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Wed, 16 Jun 2010 01:22:52 +0100 Subject: [PATCH 056/655] Reordered methods and changed properties to constants as needed --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 3086 +++++++++--------- 1 file changed, 1572 insertions(+), 1514 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 6146bd1c5a..1e8d7e0f1f 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -1,49 +1,66 @@ user = $Configs['user']; $this->password = $Configs['password']; $this->alias = isset($Configs['alias']) ? $Configs['alias'] : ''; @@ -179,789 +175,13 @@ class MSN { = 0x7000800C; */ $this->clientid = $client_id; - $this->ABService=new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl',array('trace' => 1)); - } - - private function Array2SoapVar($Array, $ReturnSoapVarObj = true, $TypeName = null, $TypeNameSpace = null) { - $ArrayString = ''; - foreach($Array as $Key => $Val) { - if ($Key{0} == ':') continue; - $Attrib = ''; - if (is_array($Val[':'])) { - foreach ($Val[':'] as $AttribName => $AttribVal) - $Attrib .= " $AttribName='$AttribVal'"; - } - if ($Key{0} == '!') { - //List Type Define - $Key = substr($Key,1); - foreach ($Val as $ListKey => $ListVal) { - if ($ListKey{0} == ':') continue; - if (is_array($ListVal)) $ListVal = $this->Array2SoapVar($ListVal, false); - elseif (is_bool($ListVal)) $ListVal = $ListVal ? 'true' : 'false'; - $ArrayString .= "<$Key$Attrib>$ListVal"; - } - continue; - } - if (is_array($Val)) $Val = $this->Array2SoapVar($Val, false); - elseif (is_bool($Val)) $Val = $Val ? 'true' : 'false'; - $ArrayString .= "<$Key$Attrib>$Val"; - } - if ($ReturnSoapVarObj) return new SoapVar($ArrayString, XSD_ANYXML, $TypeName, $TypeNameSpace); - return $ArrayString; + $this->ABService = new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl', array('trace' => 1)); } /** - * Get Passport ticket - * - * @param string $url URL string (Optional) - * @return mixed Array of tickets or false on failure - */ - private function get_passport_ticket($url = '') { - $user = $this->user; - $password = htmlspecialchars($this->password); - - if ($url === '') - $passport_url = $this->passport_url; - else - $passport_url = $url; - - $XML = ' - -
- - {7108E71A-9926-4FCB-BCC9-9A9D3F32E423} - 4 - 1 - - AQAAAAIAAABsYwQAAAAxMDMz - - - - '.$user.' - '.$password.' - - -
- - - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - http://Passport.NET/tb - - - - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - messengerclear.live.com - - - - - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - messenger.msn.com - - - - - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - contacts.msn.com - - - - - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - messengersecure.live.com - - - - - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - spaces.live.com - - - - - - http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue - - - storage.msn.com - - - - - - -
'; - - //$this->debug_message("*** URL: $passport_url"); - //$this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $passport_url); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - //$this->debug_message("*** Get Result:\n$data"); - - if ($http_code != 200) { - // sometimes, redirect to another URL - // MSNP15 - //psf:Redirect - //https://msnia.login.live.com/pp450/RST.srf - //Authentication Failure - if (strpos($data, 'psf:Redirect') === false) { - $this->debug_message("*** Could not get passport ticket! http code = $http_code"); - return false; - } - preg_match("#(.*)#", $data, $matches); - if (count($matches) == 0) { - $this->debug_message('*** Redirected, but could not get redirect URL!'); - return false; - } - $redirect_url = $matches[1]; - if ($redirect_url == $passport_url) { - $this->debug_message('*** Redirected, but to same URL!'); - return false; - } - $this->debug_message("*** Redirected to $redirect_url"); - return $this->get_passport_ticket($redirect_url); - } - - // sometimes, redirect to another URL, also return 200 - // MSNP15 - //psf:Redirect - //https://msnia.login.live.com/pp450/RST.srf - //Authentication Failure - if (strpos($data, 'psf:Redirect') !== false) { - preg_match("#(.*)#", $data, $matches); - if (count($matches) != 0) { - $redirect_url = $matches[1]; - if ($redirect_url == $passport_url) { - $this->debug_message('*** Redirected, but to same URL!'); - return false; - } - $this->debug_message("*** Redirected to $redirect_url"); - return $this->get_passport_ticket($redirect_url); - } - } - - // no Redurect faultcode or URL - // we should get the ticket here - - // we need ticket and secret code - // RST1: messengerclear.live.com - // t=tick&p= - // binary secret - // RST2: messenger.msn.com - // t=tick - // RST3: contacts.msn.com - // t=tick&p= - // RST4: messengersecure.live.com - // t=tick&p= - // RST5: spaces.live.com - // t=tick&p= - // RST6: storage.msn.com - // t=tick&p= - preg_match("#". - "(.*)(.*)". - "(.*)(.*)". - "(.*)(.*)". - "(.*)(.*)". - "(.*)(.*)". - "(.*)(.*)". - "(.*)(.*)". - "#", - $data, $matches); - - // no ticket found! - if (count($matches) == 0) { - $this->debug_message('*** Could not get passport ticket!'); - return false; - } - - //$this->debug_message(var_export($matches, true)); - // matches[0]: all data - // matches[1]: RST1 (messengerclear.live.com) ticket - // matches[2]: ... - // matches[3]: RST1 (messengerclear.live.com) binary secret - // matches[4]: ... - // matches[5]: RST2 (messenger.msn.com) ticket - // matches[6]: ... - // matches[7]: RST3 (contacts.msn.com) ticket - // matches[8]: ... - // matches[9]: RST4 (messengersecure.live.com) ticket - // matches[10]: ... - // matches[11]: RST5 (spaces.live.com) ticket - // matches[12]: ... - // matches[13]: RST6 (storage.live.com) ticket - // matches[14]: ... - - // so - // ticket => $matches[1] - // secret => $matches[3] - // web_ticket => $matches[5] - // contact_ticket => $matches[7] - // oim_ticket => $matches[9] - // space_ticket => $matches[11] - // storage_ticket => $matches[13] - - // yes, we get ticket - $aTickets = array( - 'ticket' => html_entity_decode($matches[1]), - 'secret' => html_entity_decode($matches[3]), - 'web_ticket' => html_entity_decode($matches[5]), - 'contact_ticket' => html_entity_decode($matches[7]), - 'oim_ticket' => html_entity_decode($matches[9]), - 'space_ticket' => html_entity_decode($matches[11]), - 'storage_ticket' => html_entity_decode($matches[13]) - ); - $this->ticket = $aTickets; - //$this->debug_message(var_export($aTickets, true)); - $ABAuthHeaderArray = array( - 'ABAuthHeader' => array( - ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), - 'ManagedGroupRequest' => false, - 'TicketToken' => htmlspecialchars($this->ticket['contact_ticket']), - ) - ); - $this->ABAuthHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABAuthHeader', $this->Array2SoapVar($ABAuthHeaderArray)); - return $aTickets; - } - - /** - * Fetch contact list - * - * @return boolean true on success - */ - private function UpdateContacts() { - $ABApplicationHeaderArray = array( - 'ABApplicationHeader' => array( - ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), - 'ApplicationId' => 'CFE80F9D-180F-4399-82AB-413F33A1FA11', - 'IsMigration' => false, - 'PartnerScenario' => 'ContactSave' - ) - ); - - $ABApplicationHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); - $ABFindAllArray = array( - 'ABFindAll' => array( - ':' => array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), - 'abId' => '00000000-0000-0000-0000-000000000000', - 'abView' => 'Full', - 'lastChange' => '0001-01-01T00:00:00.0000000-08:00', - ) - ); - $ABFindAll = new SoapParam($this->Array2SoapVar($ABFindAllArray), 'ABFindAll'); - $this->ABService->__setSoapHeaders(array($ABApplicationHeader, $this->ABAuthHeader)); - $this->Contacts = array(); - try { - $this->debug_message('*** Updating Contacts...'); - $Result = $this->ABService->ABFindAll($ABFindAll); - $this->debug_message("*** Result:\n".print_r($Result, true)."\n".$this->ABService->__getLastResponse()); - foreach($Result->ABFindAllResult->contacts->Contact as $Contact) - $this->Contacts[$Contact->contactInfo->passportName] = $Contact; - } catch(Exception $e) { - $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); - return false; - } - return true; - } - - /** - * Add contact - * - * @param string $email - * @param integer $network - * @param string $display - * @param boolean $sendADL - * @return boolean true on success - */ - private function addContact($email, $network, $display = '', $sendADL = false) { - if ($network != 1) return true; - if (isset($this->Contacts[$email])) return true; - - $ABContactAddArray = array( - 'ABContactAdd' => array( - ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), - 'abId' => '00000000-0000-0000-0000-000000000000', - 'contacts' => array( - 'Contact' => array( - ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), - 'contactInfo' => array( - 'contactType' => 'LivePending', - 'passportName' => $email, - 'isMessengerUser' => true, - 'MessengerMemberInfo' => array( - 'DisplayName' => $email - ) - ) - ) - ), - 'options' => array( - 'EnableAllowListManagement' => true - ) - ) - ); - $ABContactAdd = new SoapParam($this->Array2SoapVar($ABContactAddArray), 'ABContactAdd'); - try { - $this->debug_message("*** Adding Contact $email..."); - $this->ABService->ABContactAdd($ABContactAdd); - } catch(Exception $e) { - $this->debug_message("*** Add Contact Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); - return false; - } - if ($sendADL && !feof($this->NSfp)) { - @list($u_name, $u_domain) = @explode('@', $email); - foreach (array('1', '2') as $l) { - $str = ''; - $len = strlen($str); - // NS: >>> ADL {id} {size} - //TODO introduce error checking - $this->ns_writeln("ADL $this->id $len"); - $this->ns_writedata($str); - } - } - $this->UpdateContacts(); - return true; - } - - /** - * Remove contact from list - * - * @param integer $memberID - * @param string $email - * @param integer $network - * @param string $list - */ - function delMemberFromList($memberID, $email, $network, $list) { - if ($network != 1 && $network != 32) return true; - if ($memberID === false) return true; - $user = $email; - $ticket = htmlspecialchars($this->ticket['contact_ticket']); - if ($network == 1) - $XML = ' - - - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - ContactMsgrAPI - - - false - '.$ticket.' - - - - - - 0 - Messenger - - - - - '.$list.' - - - Passport - '.$memberID.' - Accepted - - - - - - -'; - else - $XML = ' - - - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - ContactMsgrAPI - - - false - '.$ticket.' - - - - - - 0 - Messenger - - - - - '.$list.' - - - Email - '.$memberID.' - Accepted - - - - - - -'; - - $header_array = array( - 'SOAPAction: '.$this->delmember_soap, - 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' - ); - - //$this->debug_message("*** URL: $this->delmember_url"); - //$this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->delmember_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - //$this->debug_message("*** Get Result:\n$data"); - - if ($http_code != 200) { - preg_match('#(.*)(.*)#', $data, $matches); - if (count($matches) == 0) { - $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list"); - return false; - } - $faultcode = trim($matches[1]); - $faultstring = trim($matches[2]); - if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) { - $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, error code: $faultcode, $faultstring"); - return false; - } - $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, not present in list"); - return true; - } - $this->debug_message("*** Member successfully deleted (network: $network) $email ($memberID) from $list list"); - return true; - } - - /** - * Add contact to list - * - * @param string $email - * @param integer $network - * @param string $list - */ - function addMemberToList($email, $network, $list) { - if ($network != 1 && $network != 32) return true; - $ticket = htmlspecialchars($this->ticket['contact_ticket']); - $user = $email; - - if ($network == 1) - $XML = ' - - - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - ContactMsgrAPI - - - false - '.$ticket.' - - - - - - 0 - Messenger - - - - - '.$list.' - - - Passport - Accepted - '.$user.' - - - - - - -'; - else - $XML = ' - - - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - ContactMsgrAPI - - - false - '.$ticket.' - - - - - - 0 - Messenger - - - - - '.$list.' - - - Email - Accepted - '.$user.' - - - MSN.IM.BuddyType - 32:YAHOO - - - - - - - - -'; - $header_array = array( - 'SOAPAction: '.$this->addmember_soap, - 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' - ); - - //$this->debug_message("*** URL: $this->addmember_url"); - //$this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->addmember_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - //$this->debug_message("*** Get Result:\n$data"); - - if ($http_code != 200) { - preg_match('#(.*)(.*)#', $data, $matches); - if (count($matches) == 0) { - $this->debug_message("*** Could not add member (network: $network) $email to $list list"); - return false; - } - $faultcode = trim($matches[1]); - $faultstring = trim($matches[2]); - if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) { - $this->debug_message("*** Could not add member (network: $network) $email to $list list, error code: $faultcode, $faultstring"); - return false; - } - $this->debug_message("*** Could not add member (network: $network) $email to $list list, already present"); - return true; - } - $this->debug_message("*** Member successfully added (network: $network) $email to $list list"); - return true; - } - - /** - * Get membership lists - * - * @param mixed $returnData Membership list or false on failure - */ - function getMembershipList($returnData = false) { - $ticket = htmlspecialchars($this->ticket['contact_ticket']); - $XML = ' - - - - 996CDE1E-AA53-4477-B943-2BE802EA6166 - false - Initial - - - false - '.$ticket.' - - - - - - - Messenger - Invitation - SocialNetwork - Space - Profile - - - - -'; - $header_array = array( - 'SOAPAction: '.$this->membership_soap, - 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' - ); - //$this->debug_message("*** URL: $this->membership_url"); - //$this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->membership_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - //$this->debug_message("*** Get Result:\n$data"); - if ($http_code != 200) return false; - $p = $data; - $aMemberships = array(); - while (1) { - //$this->debug_message("search p = $p"); - $start = strpos($p, ''); - $end = strpos($p, ''); - if ($start === false || $end === false || $start > $end) break; - //$this->debug_message("start = $start, end = $end"); - $end += 13; - $sMembership = substr($p, $start, $end - $start); - $aMemberships[] = $sMembership; - //$this->debug_message("add sMembership = $sMembership"); - $p = substr($p, $end); - } - //$this->debug_message("aMemberships = ".var_export($aMemberships, true)); - - $aContactList = array(); - foreach ($aMemberships as $sMembership) { - //$this->debug_message("sMembership = $sMembership"); - if (isset($matches)) unset($matches); - preg_match('#(.*)#', $sMembership, $matches); - if (count($matches) == 0) continue; - $sMemberRole = $matches[1]; - //$this->debug_message("MemberRole = $sMemberRole"); - if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue; - $p = $sMembership; - if (isset($aMembers)) unset($aMembers); - $aMembers = array(); - while (1) { - //$this->debug_message("search p = $p"); - $start = strpos($p, 'debug_message("add sMember = $sMember"); - $p = substr($p, $end); - } - //$this->debug_message("aMembers = ".var_export($aMembers, true)); - foreach ($aMembers as $sMember) { - //$this->debug_message("sMember = $sMember"); - if (isset($matches)) unset($matches); - preg_match('##', $sMember, $matches); - if (count($matches) == 0) continue; - $sMemberType = $matches[1]; - //$this->debug_message("MemberType = $sMemberType"); - $network = -1; - preg_match('#(.*)#', $sMember, $matches); - if (count($matches) == 0) continue; - $id = $matches[1]; - if ($sMemberType == 'PassportMember') { - if (strpos($sMember, 'Passport') === false) continue; - $network = 1; - preg_match('#(.*)#', $sMember, $matches); - } - else if ($sMemberType == 'EmailMember') { - if (strpos($sMember, 'Email') === false) continue; - // Value is 32: or 32:YAHOO - preg_match('#MSN.IM.BuddyType(.*):(.*)#', $sMember, $matches); - if (count($matches) == 0) continue; - if ($matches[1] != 32) continue; - $network = 32; - preg_match('#(.*)#', $sMember, $matches); - } - if ($network == -1) continue; - if (count($matches) > 0) { - $email = $matches[1]; - @list($u_name, $u_domain) = @explode('@', $email); - if ($u_domain == NULL) continue; - $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id; - $this->debug_message("*** Adding new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); - } - } - } - return $aContactList; - } - + * Signon methods + */ + /** * Connect to the NS server * @@ -992,10 +212,10 @@ class MSN { // NS: >> VER {id} MSNP9 CVR0 // MSNP15 // NS: >>> VER {id} MSNP15 CVR0 - $this->ns_writeln("VER $this->id $this->protocol CVR0"); + $this->ns_writeln("VER $this->id ".PROTOCOL.' CVR0'); $start_tm = time(); - while (!self::socketcheck($this->NSfp)) { + while (!socketcheck($this->NSfp)) { $data = $this->ns_readln(); // no data? if ($data === false) { @@ -1018,7 +238,7 @@ class MSN { // MSNP15 // NS: <<< VER {id} MSNP15 CVR0 // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs {user} - $this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS $this->buildver msmsgs $user"); + $this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS ".BUILDVER." msmsgs $user"); break; case 'CVR': @@ -1028,7 +248,7 @@ class MSN { // MSNP15 // NS: <<< CVR {id} {ver_list} {download_serve} .... // NS: >>> USR {id} SSO I {user} - $this->ns_writeln("USR $this->id $this->login_method I $user"); + $this->ns_writeln("USR $this->id ".LOGIN_METHOD." I $user"); break; case 'USR': @@ -1061,7 +281,7 @@ class MSN { $login_code = $this->generateLoginBLOB($secret, $nonce); // NS: >>> USR {id} SSO S {ticket} {login_code} - $this->ns_writeln("USR $this->id $this->login_method S $ticket $login_code"); + $this->ns_writeln("USR $this->id ".LOGIN_METHOD." S $ticket $login_code"); $this->authed = true; break; @@ -1087,7 +307,7 @@ class MSN { // NS: >> VER {id} MSNP9 CVR0 // MSNP15 // NS: >>> VER {id} MSNP15 CVR0 - $this->ns_writeln("VER $this->id $this->protocol CVR0"); + $this->ns_writeln("VER $this->id ".PROTOCOL.' CVR0'); break; case 'GCF': @@ -1240,7 +460,7 @@ class MSN { $len = strlen($str); $this->ns_writeln("UUX $this->id $len"); $this->ns_writedata($str); - if (!self::socketcheck($this->NSfp)) { + if (!socketcheck($this->NSfp)) { $this->debug_message('*** Connected, waiting for commands'); break; } else { @@ -1253,6 +473,7 @@ class MSN { * Called if there is an error during signon * * @param string $message Error message to log + * @return void */ private function signonFailure($message) { $this->debug_message($message); @@ -1260,244 +481,12 @@ class MSN { $this->NSRetryWait($this->retry_wait); } - function derive_key($key, $magic) { - $hash1 = mhash(MHASH_SHA1, $magic, $key); - $hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key); - $hash3 = mhash(MHASH_SHA1, $hash1, $key); - $hash4 = mhash(MHASH_SHA1, $hash3.$magic, $key); - return $hash2.substr($hash4, 0, 4); - } - - function generateLoginBLOB($key, $challenge) { - $key1 = base64_decode($key); - $key2 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY HASH'); - $key3 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY ENCRYPTION'); - - // get hash of challenge using key2 - $hash = mhash(MHASH_SHA1, $challenge, $key2); - - // get 8 bytes random data - $iv = substr(base64_encode(rand(1000,9999).rand(1000,9999)), 2, 8); - - $cipher = mcrypt_cbc(MCRYPT_3DES, $key3, $challenge."\x08\x08\x08\x08\x08\x08\x08\x08", MCRYPT_ENCRYPT, $iv); - - $blob = pack('LLLLLLL', 28, 1, 0x6603, 0x8004, 8, 20, 72); - $blob .= $iv; - $blob .= $hash; - $blob .= $cipher; - - return base64_encode($blob); - } - - /** - * Get OIM mail data - * - * @return string mail data or false on failure - */ - function getOIM_maildata() { - preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); - if (count($matches) == 0) { - $this->debug_message('*** No web ticket?'); - return false; - } - $t = htmlspecialchars($matches[1]); - $p = htmlspecialchars($matches[2]); - $XML = ' - - - - '.$t.' -

'.$p.'

-
-
- - - -
'; - - $header_array = array( - 'SOAPAction: '.$this->oim_maildata_soap, - 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); - - //$this->debug_message("*** URL: $this->oim_maildata_url"); - //$this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_maildata_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - //$this->debug_message("*** Get Result:\n$data"); - - if ($http_code != 200) { - $this->debug_message("*** Could not get OIM maildata! http code: $http_code"); - return false; - } - - // See #XML_Data - preg_match('#]*)>(.*)#', $data, $matches); - if (count($matches) == 0) { - $this->debug_message('*** Could not get OIM maildata'); - return false; - } - return $matches[2]; - } - - /** - * Fetch OIM message with given id - * - * @param string $msgid - * @return string Message or false on failure - */ - function getOIM_message($msgid) { - preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); - if (count($matches) == 0) { - $this->debug_message('*** No web ticket?'); - return false; - } - $t = htmlspecialchars($matches[1]); - $p = htmlspecialchars($matches[2]); - - // read OIM - $XML = ' - - - - '.$t.' -

'.$p.'

-
-
- - - '.$msgid.' - false - - -
'; - - $header_array = array( - 'SOAPAction: '.$this->oim_read_soap, - 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); - - //$this->debug_message("*** URL: $this->oim_read_url"); - //$this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_read_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - //$this->debug_message("*** Get Result:\n$data"); - - if ($http_code != 200) { - $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code"); - return false; - } - - // why can't use preg_match('#(.*)#', $data, $matches)? - // multi-lines? - $start = strpos($data, ''); - $end = strpos($data, ''); - if ($start === false || $end === false || $start > $end) { - $this->debug_message("*** Can't get OIM: $msgid"); - return false; - } - $lines = substr($data, $start + 18, $end - $start); - $aLines = @explode("\n", $lines); - $header = true; - $ignore = false; - $sOIM = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } - continue; - } - // stop at empty lines - if ($line === '') break; - $sOIM .= $line; - } - $sMsg = base64_decode($sOIM); - //$this->debug_message("*** we get OIM ($msgid): $sMsg"); - - // delete OIM - $XML = ' - - - - '.$t.' -

'.$p.'

-
-
- - - - '.$msgid.' - - - -
'; - - $header_array = array( - 'SOAPAction: '.$this->oim_del_soap, - 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); - - //$this->debug_message("*** URL: $this->oim_del_url"); - //$this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_del_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - //$this->debug_message("*** Get Result:\n$data"); - - if ($http_code != 200) - $this->debug_message("*** Could not delete OIM: $msgid, http code = $http_code"); - else - $this->debug_message("*** OIM ($msgid) deleted"); - return $sMsg; - } - /** * Log out and close the NS connection * * @return void */ - private function NSLogout() { + private function nsLogout() { if (is_resource($this->NSfp) && !feof($this->NSfp)) { // logout now // NS: >>> OUT @@ -1509,286 +498,9 @@ class MSN { } /** - * Sleep for the given number of seconds - * - * @param integer $wait Number of seconds to sleep for - */ - private function NSRetryWait($wait) { - $this->debug_message("*** Sleeping for $wait seconds before retrying"); - sleep($wait); - } - - /** - * Generate challenge response - * - * @param string $code - * @return string challenge response code - */ - function getChallenge($code) { - // MSNP15 - // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges - // Step 1: The MD5 Hash - $md5Hash = md5($code.$this->prod_key); - $aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0")); - for ($i = 0; $i < 4; $i++) { - $aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0")))); - $aMD5[$i] = (0 + base_convert($aMD5[$i], 16, 10)) & 0x7FFFFFFF; - } - - // Step 2: A new string - $chl_id = $code.$this->prod_id; - $chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8)); - - $aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1)); - for ($i = 0; $i < count($aID); $i++) { - $aID[$i] = implode('', array_reverse(@explode("\0", chunk_split($aID[$i], 1, "\0")))); - $aID[$i] = 0 + base_convert(bin2hex($aID[$i]), 16, 10); - } - - // Step 3: The 64 bit key - $magic_num = 0x0E79A9C1; - $str7f = 0x7FFFFFFF; - $high = 0; - $low = 0; - for ($i = 0; $i < count($aID); $i += 2) { - $temp = $aID[$i]; - $temp = bcmod(bcmul($magic_num, $temp), $str7f); - $temp = bcadd($temp, $high); - $temp = bcadd(bcmul($aMD5[0], $temp), $aMD5[1]); - $temp = bcmod($temp, $str7f); - - $high = $aID[$i+1]; - $high = bcmod(bcadd($high, $temp), $str7f); - $high = bcadd(bcmul($aMD5[2], $high), $aMD5[3]); - $high = bcmod($high, $str7f); - - $low = bcadd(bcadd($low, $high), $temp); - } - - $high = bcmod(bcadd($high, $aMD5[1]), $str7f); - $low = bcmod(bcadd($low, $aMD5[3]), $str7f); - - $new_high = bcmul($high & 0xFF, 0x1000000); - $new_high = bcadd($new_high, bcmul($high & 0xFF00, 0x100)); - $new_high = bcadd($new_high, bcdiv($high & 0xFF0000, 0x100)); - $new_high = bcadd($new_high, bcdiv($high & 0xFF000000, 0x1000000)); - // we need integer here - $high = 0+$new_high; - - $new_low = bcmul($low & 0xFF, 0x1000000); - $new_low = bcadd($new_low, bcmul($low & 0xFF00, 0x100)); - $new_low = bcadd($new_low, bcdiv($low & 0xFF0000, 0x100)); - $new_low = bcadd($new_low, bcdiv($low & 0xFF000000, 0x1000000)); - // we need integer here - $low = 0+$new_low; - - // we just use 32 bits integer, don't need the key, just high/low - // $key = bcadd(bcmul($high, 0x100000000), $low); - - // Step 4: Using the key - $md5Hash = md5($code.$this->prod_key); - $aHash = @explode("\0", chunk_split($md5Hash, 8, "\0")); - - $hash = ''; - $hash .= sprintf("%08x", (0 + base_convert($aHash[0], 16, 10)) ^ $high); - $hash .= sprintf("%08x", (0 + base_convert($aHash[1], 16, 10)) ^ $low); - $hash .= sprintf("%08x", (0 + base_convert($aHash[2], 16, 10)) ^ $high); - $hash .= sprintf("%08x", (0 + base_convert($aHash[3], 16, 10)) ^ $low); - - return $hash; - } - - /** - * Generate the data to send a message - * - * @param string $sMessage Message - * @param integer $network Network - */ - private function getMessage($sMessage, $network = 1) { - $msg_header = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n"; - $msg_header_len = strlen($msg_header); - if ($network == 1) - $maxlen = $this->max_msn_message_len - $msg_header_len; - else - $maxlen = $this->max_yahoo_message_len - $msg_header_len; - $sMessage = str_replace("\r", '', $sMessage); - $msg = substr($sMessage, 0, $maxlen); - return $msg_header.$msg; - } - - // read data for specified size - private function ns_readdata($size) { - $data = ''; - $count = 0; - while (!feof($this->NSfp)) { - $buf = @fread($this->NSfp, $size - $count); - $data .= $buf; - $count += strlen($buf); - if ($count >= $size) break; - } - $this->debug_message("NS: data ($size/$count) <<<\n$data"); - return $data; - } - - // read one line - private function ns_readln() { - $data = @fgets($this->NSfp, 4096); - if ($data !== false) { - $data = trim($data); - $this->debug_message("NS: <<< $data"); - } - return $data; - } - - // write to server, append \r\n, also increase id - private function ns_writeln($data) { - @fwrite($this->NSfp, $data."\r\n"); - $this->debug_message("NS: >>> $data"); - $this->id++; - return; - } - - // write data to server - private function ns_writedata($data) { - @fwrite($this->NSfp, $data); - $this->debug_message("NS: >>> $data"); - return; - } - - // read data for specified size for SB - private function sb_readdata($socket, $size) { - $data = ''; - $count = 0; - while (!feof($socket)) { - $buf = @fread($socket, $size - $count); - $data .= $buf; - $count += strlen($buf); - if ($count >= $size) break; - } - $this->debug_message("SB: data ($size/$count) <<<\n$data"); - return $data; - } - - // read one line for SB - private function sb_readln($socket) { - $data = @fgets($socket, 4096); - if ($data !== false) { - $data = trim($data); - $this->debug_message("SB: <<< $data"); - } - return $data; - } - - // write to server for SB, append \r\n, also increase id - // switchboard server only accept \r\n, it will lost connection if just \n only - private function sb_writeln($socket, &$id, $data) { - @fwrite($socket, $data."\r\n"); - $this->debug_message("SB: >>> $data"); - $id++; - return; - } - - // write data to server - private function sb_writedata($socket, $data) { - @fwrite($socket, $data); - $this->debug_message("SB: >>> $data"); - return; - } - - // show debug information - function debug_message($str) { - if (!$this->debug) return; - if ($this->debug===STDOUT) echo $str."\n"; - /*$fname=MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msn_'.strftime('%Y%m%d').'.debug'; - $fp = fopen($fname, 'at'); - if ($fp) { - fputs($fp, strftime('%m/%d/%y %H:%M:%S').' ['.posix_getpid().'] '.$str."\n"); - fclose($fp); - return; - }*/ - // still show debug information, if we can't open log_file - echo $str."\n"; - return; - } - - function dump_binary($str) { - $buf = ''; - $a_str = ''; - $h_str = ''; - $len = strlen($str); - for ($i = 0; $i < $len; $i++) { - if (($i % 16) == 0) { - if ($buf !== '') { - $buf .= "$h_str $a_str\n"; - } - $buf .= sprintf("%04X:", $i); - $a_str = ''; - $h_str = ''; - } - $ch = ord($str[$i]); - if ($ch < 32) - $a_str .= '.'; - else - $a_str .= chr($ch); - $h_str .= sprintf(" %02X", $ch); - } - if ($h_str !== '') - $buf .= "$h_str $a_str\n"; - return $buf; - } - - /** - * - * @param $FilePath 圖檔路徑 - * @param $Type 檔案類型 3=>大頭貼,2表情圖案 - * @return array + * NS and SB command handling methods */ - private function MsnObj($FilePath,$Type=3) - { - if (!($FileSize=filesize($FilePath))) return ''; - $Location = md5($FilePath); - $Friendly = md5($FilePath.$Type); - if (isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location]; - $sha1d = base64_encode(sha1(file_get_contents($FilePath), true)); - $sha1c = base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d",true)); - $this->MsnObjArray[$Location] = $FilePath; - $MsnObj = ''; - $this->MsnObjMap[$Location] = $MsnObj; - $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n"); - return $MsnObj; - } - - private function linetoArray($lines) { - $lines = str_replace("\r", '', $lines); - $lines = explode("\n", $lines); - foreach ($lines as $line) { - if (!isset($line{3})) continue; - list($Key,$Val) = explode(':', $line); - $Data[trim($Key)] = trim($Val); - } - return $Data; - } - - private function GetPictureFilePath($Context) { - $MsnObj = base64_decode($Context); - if (preg_match('/location="(.*?)"/i', $MsnObj, $Match)) - $location = $Match[1]; - $this->debug_message("*** p2p: PictureFile[$location] ::All".print_r($this->MsnObjArray,true)."\n"); - if ($location && isset($this->MsnObjArray[$location])) - return $this->MsnObjArray[$location]; - return false; - } - - private function GetMsnObjDefine($Message) { - $DefineString = ''; - if (is_array($this->Emotions)) - foreach ($this->Emotions as $Pattern => $FilePath) { - if (strpos($Message, $Pattern)!==false) - $DefineString .= "$Pattern\t".$this->MsnObj($FilePath, 2)."\t"; - } - return $DefineString; - } - + /** * Read and handle incoming command from NS * @@ -1796,7 +508,7 @@ class MSN { */ private function nsReceive() { // Sign in again if not signed in or socket failed - if (!is_resource($this->NSfp) || self::socketcheck($this->NSfp)) { + if (!is_resource($this->NSfp) || socketcheck($this->NSfp)) { $this->callHandler('Reconnect'); $this->NSRetryWait($this->retry_wait); $this->signon(); @@ -2123,7 +835,7 @@ class MSN { $fingerprint = $this->getChallenge($chl_code); // NS: >>> QRY {id} {product_id} 32 // NS: >>> fingerprint - $this->ns_writeln("QRY $this->id $this->prod_id 32"); + $this->ns_writeln("QRY $this->id ".PROD_ID.' 32'); $this->ns_writedata($fingerprint); $this->ns_writeln("CHG $this->id NLN $this->clientid"); if ($this->PhotoStickerFile !== false) @@ -2148,7 +860,7 @@ class MSN { if ($server_type != 'SB') { // maybe exit? // this connection will close after XFR - $this->NSLogout(); + $this->nsLogout(); continue; } @@ -2187,7 +899,7 @@ class MSN { // force logout from NS // NS: <<< OUT xxx $this->debug_message("*** LOGOUT from NS"); - return $this->NsLogout(); + return $this->nsLogout(); default: $code = substr($data,0,3); @@ -2195,7 +907,7 @@ class MSN { $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; $this->debug_message("*** NS: $this->error"); - return $this->NsLogout(); + return $this->nsLogout(); } break; } @@ -2608,28 +1320,6 @@ class MSN { } } - /** - * Called when we want to end a switchboard session - * or a switchboard session ends - * - * @param resource $socket Socket - * @param boolean $killsession Whether to delete the session - * @return void - */ - private function endSBSession($socket, $killsession = false) { - if (!self::socketcheck($socket)) { - $this->sb_writeln($socket, $fake = 0, 'OUT'); - } - @fclose($socket); - - // Unset session lookup value - $intsocket = (int) $socket; - unset($this->switchBoardSessionLookup[$this->switchBoardSessions[$intsocket]['to']]); - - // Unset session itself - unset($this->switchBoardSessions[$intsocket]); - } - /** * Checks for new data and calls appropriate methods * @@ -2655,6 +1345,10 @@ class MSN { } } + /** + * Switchboard related methods + */ + /** * Send a request for a switchboard session * @@ -2729,6 +1423,28 @@ class MSN { $this->sb_writeln($socket, $id, "ANS $id $this->user $ticket $sid"); } } + + /** + * Called when we want to end a switchboard session + * or a switchboard session ends + * + * @param resource $socket Socket + * @param boolean $killsession Whether to delete the session + * @return void + */ + private function endSBSession($socket, $killsession = false) { + if (!socketcheck($socket)) { + $this->sb_writeln($socket, $fake = 0, 'OUT'); + } + @fclose($socket); + + // Unset session lookup value + $intsocket = (int) $socket; + unset($this->switchBoardSessionLookup[$this->switchBoardSessions[$intsocket]['to']]); + + // Unset session itself + unset($this->switchBoardSessions[$intsocket]); + } /** * Send a message via an existing SB session @@ -2739,7 +1455,7 @@ class MSN { */ private function sendMessageViaSB($to, $message) { $socket = $this->switchBoardSessionLookup[$to]; - if (self::socketcheck($socket)) { + if (socketcheck($socket)) { return false; } @@ -2771,118 +1487,6 @@ class MSN { return true; } - /** - * Send offline message - * - * @param string $to Intended recipient - * @param string $sMessage Message - * @param string $lockkey Lock key - * @return mixed true on success or error data - */ - private function sendOIM($to, $sMessage, $lockkey) { - $XML = ' - - - - - - - http://messenger.msn.com - 1 - - - - text - MIME-Version: 1.0 -Content-Type: text/plain; charset=UTF-8 -Content-Transfer-Encoding: base64 -X-OIM-Message-Type: OfflineMessage -X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7} -X-OIM-Sequence-Num: 1 - -'.chunk_split(base64_encode($sMessage)).' - - -'; - - $header_array = array( - 'SOAPAction: '.$this->oim_send_soap, - 'Content-Type: text/xml', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')' - ); - - $this->debug_message("*** URL: $this->oim_send_url"); - $this->debug_message("*** Sending SOAP:\n$XML"); - $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, $this->oim_send_url); - curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); - curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); - curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); - if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); - curl_setopt($curl, CURLOPT_POST, 1); - curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); - $data = curl_exec($curl); - $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); - curl_close($curl); - $this->debug_message("*** Get Result:\n$data"); - - if ($http_code == 200) { - $this->debug_message("*** OIM sent for $to"); - return true; - } - - $challenge = false; - $auth_policy = false; - // the lockkey is invalid, authenticated fail, we need challenge it again - // 364763969 - preg_match("#(.*)#", $data, $matches); - if (count($matches) != 0) { - // yes, we get new LockKeyChallenge - $challenge = $matches[2]; - $this->debug_message("*** OIM need new challenge ($challenge) for $to"); - } - // auth policy error - // MBI_SSL - preg_match("#(.*)#", $data, $matches); - if (count($matches) != 0) { - $auth_policy = $matches[2]; - $this->debug_message("*** OIM need new auth policy ($auth_policy) for $to"); - } - if ($auth_policy === false && $challenge === false) { - //q0:AuthenticationFailed - preg_match("#(.*)#", $data, $matches); - if (count($matches) == 0) { - // no error, we assume the OIM is sent - $this->debug_message("*** OIM sent for $to"); - return true; - } - $err_code = $matches[2]; - //Exception of type 'System.Web.Services.Protocols.SoapException' was thrown. - preg_match("#(.*)#", $data, $matches); - if (count($matches) > 0) - $err_msg = $matches[1]; - else - $err_msg = ''; - $this->debug_message("*** OIM failed for $to"); - $this->debug_message("*** OIM Error code: $err_code"); - $this->debug_message("*** OIM Error Message: $err_msg"); - return false; - } - return array('challenge' => $challenge, 'auth_policy' => $auth_policy); - } - /** * Send a message to a user on another network * @@ -2978,37 +1582,1001 @@ X-OIM-Sequence-Num: 1 } /** - * Sends a ping command + * OIM methods + */ + + /** + * Get OIM mail data + * + * @return string mail data or false on failure + */ + function getOIM_maildata() { + preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); + if (count($matches) == 0) { + $this->debug_message('*** No web ticket?'); + return false; + } + $t = htmlspecialchars($matches[1]); + $p = htmlspecialchars($matches[2]); + $XML = ' + + + + '.$t.' +

'.$p.'

+
+
+ + + +
'; + + $header_array = array( + 'SOAPAction: '.OIM_MAILDATA_SOAP, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + ); + + $this->debug_message('*** URL: '.OIM_MAILDATA_URL); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, OIM_MAILDATA_URL); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + $this->debug_message("*** Could not get OIM maildata! http code: $http_code"); + return false; + } + + // See #XML_Data + preg_match('#]*)>(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->debug_message('*** Could not get OIM maildata'); + return false; + } + return $matches[2]; + } + + /** + * Fetch OIM message with given id + * + * @param string $msgid + * @return string Message or false on failure + */ + function getOIM_message($msgid) { + preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches); + if (count($matches) == 0) { + $this->debug_message('*** No web ticket?'); + return false; + } + $t = htmlspecialchars($matches[1]); + $p = htmlspecialchars($matches[2]); + + // read OIM + $XML = ' + + + + '.$t.' +

'.$p.'

+
+
+ + + '.$msgid.' + false + + +
'; + + $header_array = array( + 'SOAPAction: '.OIM_READ_SOAP, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + ); + + $this->debug_message('*** URL: '.OIM_READ_URL); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, OIM_READ_URL); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code"); + return false; + } + + // why can't use preg_match('#(.*)#', $data, $matches)? + // multi-lines? + $start = strpos($data, ''); + $end = strpos($data, ''); + if ($start === false || $end === false || $start > $end) { + $this->debug_message("*** Can't get OIM: $msgid"); + return false; + } + $lines = substr($data, $start + 18, $end - $start); + $aLines = @explode("\n", $lines); + $header = true; + $ignore = false; + $sOIM = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + continue; + } + // stop at empty lines + if ($line === '') break; + $sOIM .= $line; + } + $sMsg = base64_decode($sOIM); + //$this->debug_message("*** we get OIM ($msgid): $sMsg"); + + // delete OIM + $XML = ' + + + + '.$t.' +

'.$p.'

+
+
+ + + + '.$msgid.' + + + +
'; + + $header_array = array( + 'SOAPAction: '.OIM_DEL_SOAP, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + ); + + $this->debug_message('*** URL: '.OIM_DEL_URL); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, OIM_DEL_URL); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) + $this->debug_message("*** Could not delete OIM: $msgid, http code = $http_code"); + else + $this->debug_message("*** OIM ($msgid) deleted"); + return $sMsg; + } + + /** + * Send offline message * - * Should be called about every 50 seconds + * @param string $to Intended recipient + * @param string $sMessage Message + * @param string $lockkey Lock key + * @return mixed true on success or error data + */ + private function sendOIM($to, $sMessage, $lockkey) { + $XML = ' + + + + + + + http://messenger.msn.com + 1 + + + + text + MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: base64 +X-OIM-Message-Type: OfflineMessage +X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7} +X-OIM-Sequence-Num: 1 + +'.chunk_split(base64_encode($sMessage)).' + + +'; + + $header_array = array( + 'SOAPAction: '.OIM_SEND_SOAP, + 'Content-Type: text/xml', + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + ); + + $this->debug_message('*** URL: '.OIM_SEND_URL); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, OIM_SEND_URL); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code == 200) { + $this->debug_message("*** OIM sent for $to"); + return true; + } + + $challenge = false; + $auth_policy = false; + // the lockkey is invalid, authenticated fail, we need challenge it again + // 364763969 + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + // yes, we get new LockKeyChallenge + $challenge = $matches[2]; + $this->debug_message("*** OIM need new challenge ($challenge) for $to"); + } + // auth policy error + // MBI_SSL + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + $auth_policy = $matches[2]; + $this->debug_message("*** OIM need new auth policy ($auth_policy) for $to"); + } + if ($auth_policy === false && $challenge === false) { + //q0:AuthenticationFailed + preg_match("#(.*)#", $data, $matches); + if (count($matches) == 0) { + // no error, we assume the OIM is sent + $this->debug_message("*** OIM sent for $to"); + return true; + } + $err_code = $matches[2]; + //Exception of type 'System.Web.Services.Protocols.SoapException' was thrown. + preg_match("#(.*)#", $data, $matches); + if (count($matches) > 0) + $err_msg = $matches[1]; + else + $err_msg = ''; + $this->debug_message("*** OIM failed for $to"); + $this->debug_message("*** OIM Error code: $err_code"); + $this->debug_message("*** OIM Error Message: $err_msg"); + return false; + } + return array('challenge' => $challenge, 'auth_policy' => $auth_policy); + } + + /** + * Contact / Membership list methods + */ + + /** + * Fetch contact list + * + * @return boolean true on success + */ + private function UpdateContacts() { + $ABApplicationHeaderArray = array( + 'ABApplicationHeader' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'ApplicationId' => 'CFE80F9D-180F-4399-82AB-413F33A1FA11', + 'IsMigration' => false, + 'PartnerScenario' => 'ContactSave' + ) + ); + + $ABApplicationHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray)); + $ABFindAllArray = array( + 'ABFindAll' => array( + ':' => array('xmlns'=>'http://www.msn.com/webservices/AddressBook'), + 'abId' => '00000000-0000-0000-0000-000000000000', + 'abView' => 'Full', + 'lastChange' => '0001-01-01T00:00:00.0000000-08:00', + ) + ); + $ABFindAll = new SoapParam($this->Array2SoapVar($ABFindAllArray), 'ABFindAll'); + $this->ABService->__setSoapHeaders(array($ABApplicationHeader, $this->ABAuthHeader)); + $this->Contacts = array(); + try { + $this->debug_message('*** Updating Contacts...'); + $Result = $this->ABService->ABFindAll($ABFindAll); + $this->debug_message("*** Result:\n".print_r($Result, true)."\n".$this->ABService->__getLastResponse()); + foreach($Result->ABFindAllResult->contacts->Contact as $Contact) + $this->Contacts[$Contact->contactInfo->passportName] = $Contact; + } catch(Exception $e) { + $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); + return false; + } + return true; + } + + /** + * Add contact + * + * @param string $email + * @param integer $network + * @param string $display + * @param boolean $sendADL + * @return boolean true on success + */ + private function addContact($email, $network, $display = '', $sendADL = false) { + if ($network != 1) return true; + if (isset($this->Contacts[$email])) return true; + + $ABContactAddArray = array( + 'ABContactAdd' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'abId' => '00000000-0000-0000-0000-000000000000', + 'contacts' => array( + 'Contact' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'contactInfo' => array( + 'contactType' => 'LivePending', + 'passportName' => $email, + 'isMessengerUser' => true, + 'MessengerMemberInfo' => array( + 'DisplayName' => $email + ) + ) + ) + ), + 'options' => array( + 'EnableAllowListManagement' => true + ) + ) + ); + $ABContactAdd = new SoapParam($this->Array2SoapVar($ABContactAddArray), 'ABContactAdd'); + try { + $this->debug_message("*** Adding Contact $email..."); + $this->ABService->ABContactAdd($ABContactAdd); + } catch(Exception $e) { + $this->debug_message("*** Add Contact Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage()); + return false; + } + if ($sendADL && !feof($this->NSfp)) { + @list($u_name, $u_domain) = @explode('@', $email); + foreach (array('1', '2') as $l) { + $str = ''; + $len = strlen($str); + // NS: >>> ADL {id} {size} + //TODO introduce error checking + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); + } + } + $this->UpdateContacts(); + return true; + } + + /** + * Remove contact from list + * + * @param integer $memberID + * @param string $email + * @param integer $network + * @param string $list + */ + function delMemberFromList($memberID, $email, $network, $list) { + if ($network != 1 && $network != 32) return true; + if ($memberID === false) return true; + $user = $email; + $ticket = htmlspecialchars($this->ticket['contact_ticket']); + if ($network == 1) + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Passport + '.$memberID.' + Accepted + + + + + + +'; + else + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Email + '.$memberID.' + Accepted + + + + + + +'; + + $header_array = array( + 'SOAPAction: '.DELMEMBER_SOAP, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + ); + + $this->debug_message('*** URL: '.DELMEMBER_URL); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, DELMEMBER_URL); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + preg_match('#(.*)(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list"); + return false; + } + $faultcode = trim($matches[1]); + $faultstring = trim($matches[2]); + if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) { + $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, error code: $faultcode, $faultstring"); + return false; + } + $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, not present in list"); + return true; + } + $this->debug_message("*** Member successfully deleted (network: $network) $email ($memberID) from $list list"); + return true; + } + + /** + * Add contact to list + * + * @param string $email + * @param integer $network + * @param string $list + */ + function addMemberToList($email, $network, $list) { + if ($network != 1 && $network != 32) return true; + $ticket = htmlspecialchars($this->ticket['contact_ticket']); + $user = $email; + + if ($network == 1) + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Passport + Accepted + '.$user.' + + + + + + +'; + else + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + ContactMsgrAPI + + + false + '.$ticket.' + + + + + + 0 + Messenger + + + + + '.$list.' + + + Email + Accepted + '.$user.' + + + MSN.IM.BuddyType + 32:YAHOO + + + + + + + + +'; + $header_array = array( + 'SOAPAction: '.ADDMEMBER_SOAP, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + ); + + $this->debug_message('*** URL: '.ADDMEMBER_URL); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, ADDMEMBER_URL); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + preg_match('#(.*)(.*)#', $data, $matches); + if (count($matches) == 0) { + $this->debug_message("*** Could not add member (network: $network) $email to $list list"); + return false; + } + $faultcode = trim($matches[1]); + $faultstring = trim($matches[2]); + if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) { + $this->debug_message("*** Could not add member (network: $network) $email to $list list, error code: $faultcode, $faultstring"); + return false; + } + $this->debug_message("*** Could not add member (network: $network) $email to $list list, already present"); + return true; + } + $this->debug_message("*** Member successfully added (network: $network) $email to $list list"); + return true; + } + + /** + * Get membership lists + * + * @param mixed $returnData Membership list or false on failure + */ + function getMembershipList($returnData = false) { + $ticket = htmlspecialchars($this->ticket['contact_ticket']); + $XML = ' + + + + 996CDE1E-AA53-4477-B943-2BE802EA6166 + false + Initial + + + false + '.$ticket.' + + + + + + + Messenger + Invitation + SocialNetwork + Space + Profile + + + + +'; + $header_array = array( + 'SOAPAction: '.MEMBERSHIP_SOAP, + 'Content-Type: text/xml; charset=utf-8', + 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' + ); + $this->debug_message('*** URL: '.MEMBERSHIP_URL); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, MEMBERSHIP_URL); + curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) return false; + $p = $data; + $aMemberships = array(); + while (1) { + //$this->debug_message("search p = $p"); + $start = strpos($p, ''); + $end = strpos($p, ''); + if ($start === false || $end === false || $start > $end) break; + //$this->debug_message("start = $start, end = $end"); + $end += 13; + $sMembership = substr($p, $start, $end - $start); + $aMemberships[] = $sMembership; + //$this->debug_message("add sMembership = $sMembership"); + $p = substr($p, $end); + } + //$this->debug_message("aMemberships = ".var_export($aMemberships, true)); + + $aContactList = array(); + foreach ($aMemberships as $sMembership) { + //$this->debug_message("sMembership = $sMembership"); + if (isset($matches)) unset($matches); + preg_match('#(.*)#', $sMembership, $matches); + if (count($matches) == 0) continue; + $sMemberRole = $matches[1]; + //$this->debug_message("MemberRole = $sMemberRole"); + if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue; + $p = $sMembership; + if (isset($aMembers)) unset($aMembers); + $aMembers = array(); + while (1) { + //$this->debug_message("search p = $p"); + $start = strpos($p, 'debug_message("add sMember = $sMember"); + $p = substr($p, $end); + } + //$this->debug_message("aMembers = ".var_export($aMembers, true)); + foreach ($aMembers as $sMember) { + //$this->debug_message("sMember = $sMember"); + if (isset($matches)) unset($matches); + preg_match('##', $sMember, $matches); + if (count($matches) == 0) continue; + $sMemberType = $matches[1]; + //$this->debug_message("MemberType = $sMemberType"); + $network = -1; + preg_match('#(.*)#', $sMember, $matches); + if (count($matches) == 0) continue; + $id = $matches[1]; + if ($sMemberType == 'PassportMember') { + if (strpos($sMember, 'Passport') === false) continue; + $network = 1; + preg_match('#(.*)#', $sMember, $matches); + } + else if ($sMemberType == 'EmailMember') { + if (strpos($sMember, 'Email') === false) continue; + // Value is 32: or 32:YAHOO + preg_match('#MSN.IM.BuddyType(.*):(.*)#', $sMember, $matches); + if (count($matches) == 0) continue; + if ($matches[1] != 32) continue; + $network = 32; + preg_match('#(.*)#', $sMember, $matches); + } + if ($network == -1) continue; + if (count($matches) > 0) { + $email = $matches[1]; + @list($u_name, $u_domain) = @explode('@', $email); + if ($u_domain == NULL) continue; + $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id; + $this->debug_message("*** Adding new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)"); + } + } + } + return $aContactList; + } + + /** + * MsnObj related methods + */ + + /** * + * @param $FilePath 圖檔路徑 + * @param $Type 檔案類型 3=>大頭貼,2表情圖案 + * @return array + */ + private function MsnObj($FilePath, $Type = 3) { + if (!($FileSize=filesize($FilePath))) return ''; + $Location = md5($FilePath); + $Friendly = md5($FilePath.$Type); + if (isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location]; + $sha1d = base64_encode(sha1(file_get_contents($FilePath), true)); + $sha1c = base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d", true)); + $this->MsnObjArray[$Location] = $FilePath; + $MsnObj = ''; + $this->MsnObjMap[$Location] = $MsnObj; + $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n"); + return $MsnObj; + } + + private function GetPictureFilePath($Context) { + $MsnObj = base64_decode($Context); + if (preg_match('/location="(.*?)"/i', $MsnObj, $Match)) + $location = $Match[1]; + $this->debug_message("*** p2p: PictureFile[$location] ::All".print_r($this->MsnObjArray,true)."\n"); + if ($location && isset($this->MsnObjArray[$location])) + return $this->MsnObjArray[$location]; + return false; + } + + private function GetMsnObjDefine($Message) { + $DefineString = ''; + if (is_array($this->Emotions)) + foreach ($this->Emotions as $Pattern => $FilePath) { + if (strpos($Message, $Pattern) !== false) + $DefineString .= "$Pattern\t".$this->MsnObj($FilePath, 2)."\t"; + } + return $DefineString; + } + + /** + * Socket methods + */ + + /** + * Read data of specified size from NS socket + * + * @param integer $size Size to read + * @return string Data read + */ + private function ns_readdata($size) { + $data = ''; + $count = 0; + while (!feof($this->NSfp)) { + $buf = @fread($this->NSfp, $size - $count); + $data .= $buf; + $count += strlen($buf); + if ($count >= $size) break; + } + $this->debug_message("NS: data ($size/$count) <<<\n$data"); + return $data; + } + + /** + * Read line from the NS socket + * + * @return string Data read + */ + private function ns_readln() { + $data = @fgets($this->NSfp, 4096); + if ($data !== false) { + $data = trim($data); + $this->debug_message("NS: <<< $data"); + } + return $data; + } + + /** + * Write line to NS socket + * + * Also increments id + * + * @param string $data Line to write to socket * @return void */ - public function sendPing() { - // NS: >>> PNG - $this->ns_writeln("PNG"); + private function ns_writeln($data) { + @fwrite($this->NSfp, $data."\r\n"); + $this->debug_message("NS: >>> $data"); + $this->id++; } /** - * Methods to return sockets / check socket status - */ - - /** - * Get the NS socket - * - * @return resource NS socket + * Write data to NS socket + * + * @param string $data Data to write to socket + * @return void */ - public function getNSSocket() { - return $this->NSfp; + private function ns_writedata($data) { + @fwrite($this->NSfp, $data); + $this->debug_message("NS: >>> $data"); } /** - * Get the Switchboard sockets currently in use - * - * @return array Array of Switchboard sockets + * Read data of specified size from given SB socket + * + * @param resource $socket SB socket + * @param integer $size Size to read + * @return string Data read */ - public function getSBSockets() { - return $this->switchBoardSessionLookup; + private function sb_readdata($socket, $size) { + $data = ''; + $count = 0; + while (!feof($socket)) { + $buf = @fread($socket, $size - $count); + $data .= $buf; + $count += strlen($buf); + if ($count >= $size) break; + } + $this->debug_message("SB: data ($size/$count) <<<\n$data"); + return $data; + } + + /** + * Read line from given SB socket + * + * @param resource $socket SB Socket + * @return string Line read + */ + private function sb_readln($socket) { + $data = @fgets($socket, 4096); + if ($data !== false) { + $data = trim($data); + $this->debug_message("SB: <<< $data"); + } + return $data; + } + + /** + * Write line to given SB socket + * + * Also increments id + * + * @param resource $socket SB socket + * @param integer $id Reference to SB id + * @param string $data Line to write + * @return void + */ + private function sb_writeln($socket, &$id, $data) { + @fwrite($socket, $data."\r\n"); + $this->debug_message("SB: >>> $data"); + $id++; + } + + /** + * Write data to given SB socket + * + * @param resource $socket SB socket + * @param $data Data to write to socket + * @return void + */ + private function sb_writedata($socket, $data) { + @fwrite($socket, $data); + $this->debug_message("SB: >>> $data"); } /** @@ -3030,7 +2598,451 @@ X-OIM-Sequence-Num: 1 $info = stream_get_meta_data($socket); return $info['eof']; } + + /** + * Key generation methods + */ + + private function derive_key($key, $magic) { + $hash1 = mhash(MHASH_SHA1, $magic, $key); + $hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key); + $hash3 = mhash(MHASH_SHA1, $hash1, $key); + $hash4 = mhash(MHASH_SHA1, $hash3.$magic, $key); + return $hash2.substr($hash4, 0, 4); + } + private function generateLoginBLOB($key, $challenge) { + $key1 = base64_decode($key); + $key2 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY HASH'); + $key3 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY ENCRYPTION'); + + // get hash of challenge using key2 + $hash = mhash(MHASH_SHA1, $challenge, $key2); + + // get 8 bytes random data + $iv = substr(base64_encode(rand(1000,9999).rand(1000,9999)), 2, 8); + + $cipher = mcrypt_cbc(MCRYPT_3DES, $key3, $challenge."\x08\x08\x08\x08\x08\x08\x08\x08", MCRYPT_ENCRYPT, $iv); + + $blob = pack('LLLLLLL', 28, 1, 0x6603, 0x8004, 8, 20, 72); + $blob .= $iv; + $blob .= $hash; + $blob .= $cipher; + + return base64_encode($blob); + } + + /** + * Generate challenge response + * + * @param string $code + * @return string challenge response code + */ + private function getChallenge($code) { + // MSNP15 + // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges + // Step 1: The MD5 Hash + $md5Hash = md5($code.PROD_KEY); + $aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0")); + for ($i = 0; $i < 4; $i++) { + $aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0")))); + $aMD5[$i] = (0 + base_convert($aMD5[$i], 16, 10)) & 0x7FFFFFFF; + } + + // Step 2: A new string + $chl_id = $code.PROD_ID; + $chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8)); + + $aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1)); + for ($i = 0; $i < count($aID); $i++) { + $aID[$i] = implode('', array_reverse(@explode("\0", chunk_split($aID[$i], 1, "\0")))); + $aID[$i] = 0 + base_convert(bin2hex($aID[$i]), 16, 10); + } + + // Step 3: The 64 bit key + $magic_num = 0x0E79A9C1; + $str7f = 0x7FFFFFFF; + $high = 0; + $low = 0; + for ($i = 0; $i < count($aID); $i += 2) { + $temp = $aID[$i]; + $temp = bcmod(bcmul($magic_num, $temp), $str7f); + $temp = bcadd($temp, $high); + $temp = bcadd(bcmul($aMD5[0], $temp), $aMD5[1]); + $temp = bcmod($temp, $str7f); + + $high = $aID[$i+1]; + $high = bcmod(bcadd($high, $temp), $str7f); + $high = bcadd(bcmul($aMD5[2], $high), $aMD5[3]); + $high = bcmod($high, $str7f); + + $low = bcadd(bcadd($low, $high), $temp); + } + + $high = bcmod(bcadd($high, $aMD5[1]), $str7f); + $low = bcmod(bcadd($low, $aMD5[3]), $str7f); + + $new_high = bcmul($high & 0xFF, 0x1000000); + $new_high = bcadd($new_high, bcmul($high & 0xFF00, 0x100)); + $new_high = bcadd($new_high, bcdiv($high & 0xFF0000, 0x100)); + $new_high = bcadd($new_high, bcdiv($high & 0xFF000000, 0x1000000)); + // we need integer here + $high = 0+$new_high; + + $new_low = bcmul($low & 0xFF, 0x1000000); + $new_low = bcadd($new_low, bcmul($low & 0xFF00, 0x100)); + $new_low = bcadd($new_low, bcdiv($low & 0xFF0000, 0x100)); + $new_low = bcadd($new_low, bcdiv($low & 0xFF000000, 0x1000000)); + // we need integer here + $low = 0+$new_low; + + // we just use 32 bits integer, don't need the key, just high/low + // $key = bcadd(bcmul($high, 0x100000000), $low); + + // Step 4: Using the key + $md5Hash = md5($code.PROD_KEY); + $aHash = @explode("\0", chunk_split($md5Hash, 8, "\0")); + + $hash = ''; + $hash .= sprintf("%08x", (0 + base_convert($aHash[0], 16, 10)) ^ $high); + $hash .= sprintf("%08x", (0 + base_convert($aHash[1], 16, 10)) ^ $low); + $hash .= sprintf("%08x", (0 + base_convert($aHash[2], 16, 10)) ^ $high); + $hash .= sprintf("%08x", (0 + base_convert($aHash[3], 16, 10)) ^ $low); + + return $hash; + } + + /** + * Utility methods + */ + + private function Array2SoapVar($Array, $ReturnSoapVarObj = true, $TypeName = null, $TypeNameSpace = null) { + $ArrayString = ''; + foreach($Array as $Key => $Val) { + if ($Key{0} == ':') continue; + $Attrib = ''; + if (is_array($Val[':'])) { + foreach ($Val[':'] as $AttribName => $AttribVal) + $Attrib .= " $AttribName = '$AttribVal'"; + } + if ($Key{0} == '!') { + //List Type Define + $Key = substr($Key,1); + foreach ($Val as $ListKey => $ListVal) { + if ($ListKey{0} == ':') continue; + if (is_array($ListVal)) $ListVal = $this->Array2SoapVar($ListVal, false); + elseif (is_bool($ListVal)) $ListVal = $ListVal ? 'true' : 'false'; + $ArrayString .= "<$Key$Attrib>$ListVal"; + } + continue; + } + if (is_array($Val)) $Val = $this->Array2SoapVar($Val, false); + elseif (is_bool($Val)) $Val = $Val ? 'true' : 'false'; + $ArrayString .= "<$Key$Attrib>$Val"; + } + if ($ReturnSoapVarObj) return new SoapVar($ArrayString, XSD_ANYXML, $TypeName, $TypeNameSpace); + return $ArrayString; + } + + private function linetoArray($lines) { + $lines = str_replace("\r", '', $lines); + $lines = explode("\n", $lines); + foreach ($lines as $line) { + if (!isset($line{3})) continue; + list($Key, $Val) = explode(':', $line); + $Data[trim($Key)] = trim($Val); + } + return $Data; + } + + /** + * Get Passport ticket + * + * @param string $url URL string (Optional) + * @return mixed Array of tickets or false on failure + */ + private function get_passport_ticket($url = '') { + $user = $this->user; + $password = htmlspecialchars($this->password); + + if ($url === '') + $passport_url = PASSPORT_URL; + else + $passport_url = $url; + + $XML = ' + +
+ + {7108E71A-9926-4FCB-BCC9-9A9D3F32E423} + 4 + 1 + + AQAAAAIAAABsYwQAAAAxMDMz + + + + '.$user.' + '.$password.' + + +
+ + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + http://Passport.NET/tb + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + messengerclear.live.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + messenger.msn.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + contacts.msn.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + messengersecure.live.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + spaces.live.com + + + + + + http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue + + + storage.msn.com + + + + + + +
'; + + $this->debug_message("*** URL: $passport_url"); + $this->debug_message("*** Sending SOAP:\n$XML"); + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $passport_url); + if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0); + curl_setopt($curl, CURLOPT_POST, 1); + curl_setopt($curl, CURLOPT_POSTFIELDS, $XML); + $data = curl_exec($curl); + $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); + curl_close($curl); + $this->debug_message("*** Get Result:\n$data"); + + if ($http_code != 200) { + // sometimes, redirect to another URL + // MSNP15 + //psf:Redirect + //https://msnia.login.live.com/pp450/RST.srf + //Authentication Failure + if (strpos($data, 'psf:Redirect') === false) { + $this->debug_message("*** Could not get passport ticket! http code = $http_code"); + return false; + } + preg_match("#(.*)#", $data, $matches); + if (count($matches) == 0) { + $this->debug_message('*** Redirected, but could not get redirect URL!'); + return false; + } + $redirect_url = $matches[1]; + if ($redirect_url == $passport_url) { + $this->debug_message('*** Redirected, but to same URL!'); + return false; + } + $this->debug_message("*** Redirected to $redirect_url"); + return $this->get_passport_ticket($redirect_url); + } + + // sometimes, redirect to another URL, also return 200 + // MSNP15 + //psf:Redirect + //https://msnia.login.live.com/pp450/RST.srf + //Authentication Failure + if (strpos($data, 'psf:Redirect') !== false) { + preg_match("#(.*)#", $data, $matches); + if (count($matches) != 0) { + $redirect_url = $matches[1]; + if ($redirect_url == $passport_url) { + $this->debug_message('*** Redirected, but to same URL!'); + return false; + } + $this->debug_message("*** Redirected to $redirect_url"); + return $this->get_passport_ticket($redirect_url); + } + } + + // no Redurect faultcode or URL + // we should get the ticket here + + // we need ticket and secret code + // RST1: messengerclear.live.com + // t=tick&p= + // binary secret + // RST2: messenger.msn.com + // t=tick + // RST3: contacts.msn.com + // t=tick&p= + // RST4: messengersecure.live.com + // t=tick&p= + // RST5: spaces.live.com + // t=tick&p= + // RST6: storage.msn.com + // t=tick&p= + preg_match("#". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "(.*)(.*)". + "#", + $data, $matches); + + // no ticket found! + if (count($matches) == 0) { + $this->debug_message('*** Could not get passport ticket!'); + return false; + } + + //$this->debug_message(var_export($matches, true)); + // matches[0]: all data + // matches[1]: RST1 (messengerclear.live.com) ticket + // matches[2]: ... + // matches[3]: RST1 (messengerclear.live.com) binary secret + // matches[4]: ... + // matches[5]: RST2 (messenger.msn.com) ticket + // matches[6]: ... + // matches[7]: RST3 (contacts.msn.com) ticket + // matches[8]: ... + // matches[9]: RST4 (messengersecure.live.com) ticket + // matches[10]: ... + // matches[11]: RST5 (spaces.live.com) ticket + // matches[12]: ... + // matches[13]: RST6 (storage.live.com) ticket + // matches[14]: ... + + // so + // ticket => $matches[1] + // secret => $matches[3] + // web_ticket => $matches[5] + // contact_ticket => $matches[7] + // oim_ticket => $matches[9] + // space_ticket => $matches[11] + // storage_ticket => $matches[13] + + // yes, we get ticket + $aTickets = array( + 'ticket' => html_entity_decode($matches[1]), + 'secret' => html_entity_decode($matches[3]), + 'web_ticket' => html_entity_decode($matches[5]), + 'contact_ticket' => html_entity_decode($matches[7]), + 'oim_ticket' => html_entity_decode($matches[9]), + 'space_ticket' => html_entity_decode($matches[11]), + 'storage_ticket' => html_entity_decode($matches[13]) + ); + $this->ticket = $aTickets; + //$this->debug_message(var_export($aTickets, true)); + $ABAuthHeaderArray = array( + 'ABAuthHeader' => array( + ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'), + 'ManagedGroupRequest' => false, + 'TicketToken' => htmlspecialchars($this->ticket['contact_ticket']), + ) + ); + $this->ABAuthHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABAuthHeader', $this->Array2SoapVar($ABAuthHeaderArray)); + return $aTickets; + } + + /** + * Generate the data to send a message + * + * @param string $sMessage Message + * @param integer $network Network + * @return string Message data + */ + private function getMessage($sMessage, $network = 1) { + $msg_header = "MIME-Version: 1.0\r\nContent-Type: text/plain; charset=UTF-8\r\nX-MMS-IM-Format: FN=$this->font_fn; EF=$this->font_ef; CO=$this->font_co; CS=0; PF=22\r\n\r\n"; + $msg_header_len = strlen($msg_header); + if ($network == 1) + $maxlen = $this->max_msn_message_len - $msg_header_len; + else + $maxlen = $this->max_yahoo_message_len - $msg_header_len; + $sMessage = str_replace("\r", '', $sMessage); + $msg = substr($sMessage, 0, $maxlen); + return $msg_header.$msg; + } + + /** + * Sleep for the given number of seconds + * + * @param integer $wait Number of seconds to sleep for + */ + private function NSRetryWait($wait) { + $this->debug_message("*** Sleeping for $wait seconds before retrying"); + sleep($wait); + } + + /** + * Sends a ping command + * + * Should be called about every 50 seconds + * + * @return void + */ + public function sendPing() { + // NS: >>> PNG + $this->ns_writeln("PNG"); + } + /** * Methods to add / call callbacks */ @@ -3075,4 +3087,50 @@ X-OIM-Sequence-Num: 1 return false; } } + + /** + * Debugging methods + */ + + /** + * Print message if debugging is enabled + * + * @param string $str Message to print + */ + private function debug_message($str) { + if (!$this->debug) return; + echo $str."\n"; + } + + /** + * Dump binary data + * + * @param string $str Data string + * @return Binary data + */ + private function dump_binary($str) { + $buf = ''; + $a_str = ''; + $h_str = ''; + $len = strlen($str); + for ($i = 0; $i < $len; $i++) { + if (($i % 16) == 0) { + if ($buf !== '') { + $buf .= "$h_str $a_str\n"; + } + $buf .= sprintf("%04X:", $i); + $a_str = ''; + $h_str = ''; + } + $ch = ord($str[$i]); + if ($ch < 32) + $a_str .= '.'; + else + $a_str .= chr($ch); + $h_str .= sprintf(" %02X", $ch); + } + if ($h_str !== '') + $buf .= "$h_str $a_str\n"; + return $buf; + } } From d52f6d5aeaa820e44beadf397dd5c0f15e0d33e7 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Wed, 16 Jun 2010 01:24:28 +0100 Subject: [PATCH 057/655] Removed unnecessary else statement --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 726 +++++++++---------- 1 file changed, 363 insertions(+), 363 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 1e8d7e0f1f..af9a45e49d 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -522,395 +522,395 @@ class MSN { $this->NSRetryWait($this->retry_wait); $this->signon(); return; - } else { - switch (substr($data, 0, 3)) { - case 'SBS': - // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us - // NS: <<< SBS 0 null - break; + } + + switch (substr($data, 0, 3)) { + case 'SBS': + // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us + // NS: <<< SBS 0 null + break; - case 'RFS': - // FIXME: - // NS: <<< RFS ??? - // refresh ADL, so we re-send it again - if (is_array($this->aADL)) { - foreach ($this->aADL as $str) { - $len = strlen($str); - // NS: >>> ADL {id} {size} - $this->ns_writeln("ADL $this->id $len"); - $this->ns_writedata($str); - } + case 'RFS': + // FIXME: + // NS: <<< RFS ??? + // refresh ADL, so we re-send it again + if (is_array($this->aADL)) { + foreach ($this->aADL as $str) { + $len = strlen($str); + // NS: >>> ADL {id} {size} + $this->ns_writeln("ADL $this->id $len"); + $this->ns_writedata($str); } - break; + } + break; - case 'LST': - // NS: <<< LST {email} {alias} 11 0 - @list(/* LST */, $email, /* alias */,) = @explode(' ', $data); - @list($u_name, $u_domain) = @explode('@', $email); - if (!isset($this->aContactList[$u_domain][$u_name][1])) { - $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; - $this->debug_message("*** Added to contact list: $u_name@$u_domain"); - } - break; + case 'LST': + // NS: <<< LST {email} {alias} 11 0 + @list(/* LST */, $email, /* alias */,) = @explode(' ', $data); + @list($u_name, $u_domain) = @explode('@', $email); + if (!isset($this->aContactList[$u_domain][$u_name][1])) { + $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow'; + $this->debug_message("*** Added to contact list: $u_name@$u_domain"); + } + break; - case 'ADL': - // randomly, we get ADL command, someone add us to their contact list for MSNP15 - // NS: <<< ADL 0 {size} - @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) { - $data = $this->ns_readdata($size); - preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) { - $u_domain = $matches[1]; - $u_name = $matches[2]; - $network = $matches[4]; - if (isset($this->aContactList[$u_domain][$u_name][$network])) - $this->debug_message("*** Someone (network: $network) added us to their list (but already in our list): $u_name@$u_domain"); - else { - $re_login = false; - $cnt = 0; - foreach (array('Allow', 'Reverse') as $list) { + case 'ADL': + // randomly, we get ADL command, someone add us to their contact list for MSNP15 + // NS: <<< ADL 0 {size} + @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) { + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($this->aContactList[$u_domain][$u_name][$network])) + $this->debug_message("*** Someone (network: $network) added us to their list (but already in our list): $u_name@$u_domain"); + else { + $re_login = false; + $cnt = 0; + foreach (array('Allow', 'Reverse') as $list) { + if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { + if ($re_login) { + $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->debug_message("*** Could not re-login, something wrong here"); + $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->debug_message("**** Got new ticket, trying again"); if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - if ($re_login) { - $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); - continue; - } - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->debug_message("*** Could not re-login, something wrong here"); - $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); - continue; - } - $re_login = true; - $this->ticket = $aTickets; - $this->debug_message("**** Got new ticket, trying again"); - if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) { - $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); - continue; - } - } - $this->aContactList[$u_domain][$u_name][$network][$list] = false; - $cnt++; - } - $this->debug_message("*** Someone (network: $network) added us to their list: $u_name@$u_domain"); - } - $str = ''; - $len = strlen($str); - - $this->callHandler('AddedToList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network)); - } - else - $this->debug_message("*** Someone added us to their list: $data"); - } - break; - - case 'RML': - // randomly, we get RML command, someome remove us to their contact list for MSNP15 - // NS: <<< RML 0 {size} - @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) { - $data = $this->ns_readdata($size); - preg_match('##', $data, $matches); - if (is_array($matches) && count($matches) > 0) { - $u_domain = $matches[1]; - $u_name = $matches[2]; - $network = $matches[4]; - if (isset($this->aContactList[$u_domain][$u_name][$network])) { - $aData = $this->aContactList[$u_domain][$u_name][$network]; - - foreach ($aData as $list => $id) - $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); - - unset($this->aContactList[$u_domain][$u_name][$network]); - $this->debug_message("*** Someone (network: $network) removed us from their list: $u_name@$u_domain"); - } - else - $this->debug_message("*** Someone (network: $network) removed us from their list (but not in our list): $u_name@$u_domain"); - - $this->callHandler('RemovedFromList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network)); - } - else - $this->debug_message("*** Someone removed us from their list: $data"); - } - break; - - case 'MSG': - // randomly, we get MSG notification from server - // NS: <<< MSG Hotmail Hotmail {size} - @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) { - $data = $this->ns_readdata($size); - $aLines = @explode("\n", $data); - $header = true; - $ignore = false; - $maildata = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } - if (strncasecmp($line, 'Content-Type:', 13) == 0) { - if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && strpos($line, 'text/x-msmsgsoimnotification') === false) { - // we just need text/x-msmsgsinitialmdatanotification - // or text/x-msmsgsoimnotification - $ignore = true; - break; + $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list"); + continue; } } - continue; - } - if (strncasecmp($line, 'Mail-Data:', 10) == 0) { - $maildata = trim(substr($line, 10)); - break; + $this->aContactList[$u_domain][$u_name][$network][$list] = false; + $cnt++; } + $this->debug_message("*** Someone (network: $network) added us to their list: $u_name@$u_domain"); } - if ($ignore) { - $this->debug_message("*** Ignoring MSG for: $line"); - break; - } - if ($maildata == '') { - $this->debug_message("*** Ignoring MSG not for OIM"); - break; - } - $re_login = false; - if (strcasecmp($maildata, 'too-large') == 0) { - $this->debug_message("*** Large mail-data, need to get the data via SOAP"); - $maildata = $this->getOIM_maildata(); - if ($maildata === false) { - $this->debug_message("*** Could not get mail-data via SOAP"); + $str = ''; + $len = strlen($str); - // maybe we need to re-login again - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM"); - break; - } - $re_login = true; - $this->ticket = $aTickets; - $this->debug_message("*** Got new ticket, trying again"); - $maildata = $this->getOIM_maildata(); - if ($maildata === false) { - $this->debug_message("*** Could not get mail-data via SOAP, and re-login already attempted, ignoring this OIM"); - break; - } - } - } - // could be a lots of ..., so we can't use preg_match here - $p = $maildata; - $aOIMs = array(); - while (1) { - $start = strpos($p, ''); - $end = strpos($p, ''); - if ($start === false || $end === false || $start > $end) break; - $end += 4; - $sOIM = substr($p, $start, $end - $start); - $aOIMs[] = $sOIM; - $p = substr($p, $end); - } - if (count($aOIMs) == 0) { - $this->debug_message("*** Ignoring empty OIM"); - break; - } - foreach ($aOIMs as $maildata) { - // T: 11 for MSN, 13 for Yahoo - // S: 6 for MSN, 7 for Yahoo - // RT: the datetime received by server - // RS: already read or not - // SZ: size of message - // E: sender - // I: msgid - // F: always 00000000-0000-0000-0000-000000000009 - // N: sender alias - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->debug_message("*** Ignoring OIM maildata without type"); - continue; - } - $oim_type = $matches[1]; - if ($oim_type = 13) - $network = 32; - else - $network = 1; - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->debug_message("*** Ignoring OIM maildata without sender"); - continue; - } - $oim_sender = $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - if (count($matches) == 0) { - $this->debug_message("*** Ignoring OIM maildata without msgid"); - continue; - } - $oim_msgid = $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - $oim_size = (count($matches) == 0) ? 0 : $matches[1]; - preg_match('#(.*)#', $maildata, $matches); - $oim_time = (count($matches) == 0) ? 0 : $matches[1]; - $this->debug_message("*** OIM received from $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); - $sMsg = $this->getOIM_message($oim_msgid); - if ($sMsg === false) { - $this->debug_message("*** Could not get OIM, msgid = $oim_msgid"); - if ($re_login) { - $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM"); - continue; - } - $aTickets = $this->get_passport_ticket(); - if (!$aTickets || !is_array($aTickets)) { - // failed to login? ignore it - $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM"); - continue; - } - $re_login = true; - $this->ticket = $aTickets; - $this->debug_message("*** get new ticket, try it again"); - $sMsg = $this->getOIM_message($oim_msgid); - if ($sMsg === false) { - $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM"); - continue; - } - } - $this->debug_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); - $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true)); - } + $this->callHandler('AddedToList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network)); } - break; + else + $this->debug_message("*** Someone added us to their list: $data"); + } + break; - case 'UBM': - // randomly, we get UBM, this is the message from other network, like Yahoo! - // NS: <<< UBM {email} $network $type {size} - @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); - if (is_numeric($size) && $size > 0) { - $data = $this->ns_readdata($size); - $aLines = @explode("\n", $data); - $header = true; - $ignore = false; - $sMsg = ''; - foreach ($aLines as $line) { - $line = rtrim($line); - if ($header) { - if ($line === '') { - $header = false; - continue; - } - if (strncasecmp($line, 'TypingUser:', 11) == 0) { + case 'RML': + // randomly, we get RML command, someome remove us to their contact list for MSNP15 + // NS: <<< RML 0 {size} + @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { + $data = $this->ns_readdata($size); + preg_match('##', $data, $matches); + if (is_array($matches) && count($matches) > 0) { + $u_domain = $matches[1]; + $u_name = $matches[2]; + $network = $matches[4]; + if (isset($this->aContactList[$u_domain][$u_name][$network])) { + $aData = $this->aContactList[$u_domain][$u_name][$network]; + + foreach ($aData as $list => $id) + $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list); + + unset($this->aContactList[$u_domain][$u_name][$network]); + $this->debug_message("*** Someone (network: $network) removed us from their list: $u_name@$u_domain"); + } + else + $this->debug_message("*** Someone (network: $network) removed us from their list (but not in our list): $u_name@$u_domain"); + + $this->callHandler('RemovedFromList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network)); + } + else + $this->debug_message("*** Someone removed us from their list: $data"); + } + break; + + case 'MSG': + // randomly, we get MSG notification from server + // NS: <<< MSG Hotmail Hotmail {size} + @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $maildata = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'Content-Type:', 13) == 0) { + if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && strpos($line, 'text/x-msmsgsoimnotification') === false) { + // we just need text/x-msmsgsinitialmdatanotification + // or text/x-msmsgsoimnotification $ignore = true; break; } - continue; - } - $aSubLines = @explode("\r", $line); - foreach ($aSubLines as $str) { - if ($sMsg !== '') - $sMsg .= "\n"; - $sMsg .= $str; } + continue; } - if ($ignore) { - $this->debug_message("*** Ignoring message from $from_email: $line"); + if (strncasecmp($line, 'Mail-Data:', 10) == 0) { + $maildata = trim(substr($line, 10)); break; } - $this->debug_message("*** MSG from $from_email (network: $network): $sMsg"); - $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); } - break; + if ($ignore) { + $this->debug_message("*** Ignoring MSG for: $line"); + break; + } + if ($maildata == '') { + $this->debug_message("*** Ignoring MSG not for OIM"); + break; + } + $re_login = false; + if (strcasecmp($maildata, 'too-large') == 0) { + $this->debug_message("*** Large mail-data, need to get the data via SOAP"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->debug_message("*** Could not get mail-data via SOAP"); - case 'UBX': - // randomly, we get UBX notification from server - // NS: <<< UBX email {network} {size} - @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); - // we don't need the notification data, so just ignore it - if (is_numeric($size) && $size > 0) - $this->ns_readdata($size); - break; + // maybe we need to re-login again + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM"); + break; + } + $re_login = true; + $this->ticket = $aTickets; + $this->debug_message("*** Got new ticket, trying again"); + $maildata = $this->getOIM_maildata(); + if ($maildata === false) { + $this->debug_message("*** Could not get mail-data via SOAP, and re-login already attempted, ignoring this OIM"); + break; + } + } + } + // could be a lots of ..., so we can't use preg_match here + $p = $maildata; + $aOIMs = array(); + while (1) { + $start = strpos($p, ''); + $end = strpos($p, ''); + if ($start === false || $end === false || $start > $end) break; + $end += 4; + $sOIM = substr($p, $start, $end - $start); + $aOIMs[] = $sOIM; + $p = substr($p, $end); + } + if (count($aOIMs) == 0) { + $this->debug_message("*** Ignoring empty OIM"); + break; + } + foreach ($aOIMs as $maildata) { + // T: 11 for MSN, 13 for Yahoo + // S: 6 for MSN, 7 for Yahoo + // RT: the datetime received by server + // RS: already read or not + // SZ: size of message + // E: sender + // I: msgid + // F: always 00000000-0000-0000-0000-000000000009 + // N: sender alias + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->debug_message("*** Ignoring OIM maildata without type"); + continue; + } + $oim_type = $matches[1]; + if ($oim_type = 13) + $network = 32; + else + $network = 1; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->debug_message("*** Ignoring OIM maildata without sender"); + continue; + } + $oim_sender = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + if (count($matches) == 0) { + $this->debug_message("*** Ignoring OIM maildata without msgid"); + continue; + } + $oim_msgid = $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_size = (count($matches) == 0) ? 0 : $matches[1]; + preg_match('#(.*)#', $maildata, $matches); + $oim_time = (count($matches) == 0) ? 0 : $matches[1]; + $this->debug_message("*** OIM received from $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size"); + $sMsg = $this->getOIM_message($oim_msgid); + if ($sMsg === false) { + $this->debug_message("*** Could not get OIM, msgid = $oim_msgid"); + if ($re_login) { + $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM"); + continue; + } + $aTickets = $this->get_passport_ticket(); + if (!$aTickets || !is_array($aTickets)) { + // failed to login? ignore it + $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM"); + continue; + } + $re_login = true; + $this->ticket = $aTickets; + $this->debug_message("*** get new ticket, try it again"); + $sMsg = $this->getOIM_message($oim_msgid); + if ($sMsg === false) { + $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM"); + continue; + } + } + $this->debug_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg"); + $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true)); + } + } + break; - case 'CHL': - // randomly, we'll get challenge from server - // NS: <<< CHL 0 {code} - @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data); - $fingerprint = $this->getChallenge($chl_code); - // NS: >>> QRY {id} {product_id} 32 - // NS: >>> fingerprint - $this->ns_writeln("QRY $this->id ".PROD_ID.' 32'); - $this->ns_writedata($fingerprint); + case 'UBM': + // randomly, we get UBM, this is the message from other network, like Yahoo! + // NS: <<< UBM {email} $network $type {size} + @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data); + if (is_numeric($size) && $size > 0) { + $data = $this->ns_readdata($size); + $aLines = @explode("\n", $data); + $header = true; + $ignore = false; + $sMsg = ''; + foreach ($aLines as $line) { + $line = rtrim($line); + if ($header) { + if ($line === '') { + $header = false; + continue; + } + if (strncasecmp($line, 'TypingUser:', 11) == 0) { + $ignore = true; + break; + } + continue; + } + $aSubLines = @explode("\r", $line); + foreach ($aSubLines as $str) { + if ($sMsg !== '') + $sMsg .= "\n"; + $sMsg .= $str; + } + } + if ($ignore) { + $this->debug_message("*** Ignoring message from $from_email: $line"); + break; + } + $this->debug_message("*** MSG from $from_email (network: $network): $sMsg"); + $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false)); + } + break; + + case 'UBX': + // randomly, we get UBX notification from server + // NS: <<< UBX email {network} {size} + @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data); + // we don't need the notification data, so just ignore it + if (is_numeric($size) && $size > 0) + $this->ns_readdata($size); + break; + + case 'CHL': + // randomly, we'll get challenge from server + // NS: <<< CHL 0 {code} + @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data); + $fingerprint = $this->getChallenge($chl_code); + // NS: >>> QRY {id} {product_id} 32 + // NS: >>> fingerprint + $this->ns_writeln("QRY $this->id ".PROD_ID.' 32'); + $this->ns_writedata($fingerprint); + $this->ns_writeln("CHG $this->id NLN $this->clientid"); + if ($this->PhotoStickerFile !== false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + break; + case 'CHG': + // NS: <<< CHG {id} {status} {code} + // ignore it + // change our status to online first + break; + + case 'XFR': + // sometimes, NS will redirect to another NS + // MSNP9 + // NS: <<< XFR {id} NS {server} 0 {server} + // MSNP15 + // NS: <<< XFR {id} NS {server} U D + // for normal switchboard XFR + // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0 + @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data); + @list($ip, $port) = @explode(':', $server); + if ($server_type != 'SB') { + // maybe exit? + // this connection will close after XFR + $this->nsLogout(); + continue; + } + + $this->debug_message("NS: <<< XFR SB"); + $user = array_shift($this->waitingForXFR); + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); + /* + $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); + if ($bSBresult === false) { + // error for switchboard + $this->debug_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); + $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; + }*/ + break; + case 'QNG': + // NS: <<< QNG {time} + @list(/* QNG */, $ping_wait) = @explode(' ', $data); + $this->callHandler('Pong', $ping_wait); + break; + + case 'RNG': + if ($this->PhotoStickerFile !== false) + $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); + else $this->ns_writeln("CHG $this->id NLN $this->clientid"); - if ($this->PhotoStickerFile !== false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - break; - case 'CHG': - // NS: <<< CHG {id} {status} {code} - // ignore it - // change our status to online first - break; + // someone is trying to talk to us + // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 + $this->debug_message("NS: <<< RNG $data"); + @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); + @list($sb_ip, $sb_port) = @explode(':', $server); + $this->debug_message("*** RING from $email, $sb_ip:$sb_port"); + $this->addContact($email, 1, $email, true); + $this->connectToSBSession('Passive', $sb_ip, $sb_port, $email, array('sid' => $sid, 'ticket' => $ticket)); + break; + case 'OUT': + // force logout from NS + // NS: <<< OUT xxx + $this->debug_message("*** LOGOUT from NS"); + return $this->nsLogout(); - case 'XFR': - // sometimes, NS will redirect to another NS - // MSNP9 - // NS: <<< XFR {id} NS {server} 0 {server} - // MSNP15 - // NS: <<< XFR {id} NS {server} U D - // for normal switchboard XFR - // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0 - @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data); - @list($ip, $port) = @explode(':', $server); - if ($server_type != 'SB') { - // maybe exit? - // this connection will close after XFR - $this->nsLogout(); - continue; - } + default: + $code = substr($data,0,3); + if (is_numeric($code)) { + $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; + $this->debug_message("*** NS: $this->error"); - $this->debug_message("NS: <<< XFR SB"); - $user = array_shift($this->waitingForXFR); - $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message); - /* - $bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage); - if ($bSBresult === false) { - // error for switchboard - $this->debug_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]); - $aOfflineUsers[] = $aMSNUsers[$nCurrentUser]; - }*/ - break; - case 'QNG': - // NS: <<< QNG {time} - @list(/* QNG */, $ping_wait) = @explode(' ', $data); - $this->callHandler('Pong', $ping_wait); - break; - - case 'RNG': - if ($this->PhotoStickerFile !== false) - $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile))); - else - $this->ns_writeln("CHG $this->id NLN $this->clientid"); - // someone is trying to talk to us - // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0 - $this->debug_message("NS: <<< RNG $data"); - @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data); - @list($sb_ip, $sb_port) = @explode(':', $server); - $this->debug_message("*** RING from $email, $sb_ip:$sb_port"); - $this->addContact($email, 1, $email, true); - $this->connectToSBSession('Passive', $sb_ip, $sb_port, $email, array('sid' => $sid, 'ticket' => $ticket)); - break; - case 'OUT': - // force logout from NS - // NS: <<< OUT xxx - $this->debug_message("*** LOGOUT from NS"); return $this->nsLogout(); - - default: - $code = substr($data,0,3); - if (is_numeric($code)) { - $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List"; - $this->debug_message("*** NS: $this->error"); - - return $this->nsLogout(); - } - break; - } + } + break; } } From 956b24f05d93f05afe1c5071a5d9798c849efecc Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Wed, 16 Jun 2010 01:30:44 +0100 Subject: [PATCH 058/655] Access constants and static methods properly ;) --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 86 ++++++++++---------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index af9a45e49d..5c1a83940b 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -212,10 +212,10 @@ class MSN { // NS: >> VER {id} MSNP9 CVR0 // MSNP15 // NS: >>> VER {id} MSNP15 CVR0 - $this->ns_writeln("VER $this->id ".PROTOCOL.' CVR0'); + $this->ns_writeln("VER $this->id ".self::PROTOCOL.' CVR0'); $start_tm = time(); - while (!socketcheck($this->NSfp)) { + while (!self::socketcheck($this->NSfp)) { $data = $this->ns_readln(); // no data? if ($data === false) { @@ -238,7 +238,7 @@ class MSN { // MSNP15 // NS: <<< VER {id} MSNP15 CVR0 // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs {user} - $this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS ".BUILDVER." msmsgs $user"); + $this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS ".self::BUILDVER." msmsgs $user"); break; case 'CVR': @@ -248,7 +248,7 @@ class MSN { // MSNP15 // NS: <<< CVR {id} {ver_list} {download_serve} .... // NS: >>> USR {id} SSO I {user} - $this->ns_writeln("USR $this->id ".LOGIN_METHOD." I $user"); + $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." I $user"); break; case 'USR': @@ -281,7 +281,7 @@ class MSN { $login_code = $this->generateLoginBLOB($secret, $nonce); // NS: >>> USR {id} SSO S {ticket} {login_code} - $this->ns_writeln("USR $this->id ".LOGIN_METHOD." S $ticket $login_code"); + $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." S $ticket $login_code"); $this->authed = true; break; @@ -307,7 +307,7 @@ class MSN { // NS: >> VER {id} MSNP9 CVR0 // MSNP15 // NS: >>> VER {id} MSNP15 CVR0 - $this->ns_writeln("VER $this->id ".PROTOCOL.' CVR0'); + $this->ns_writeln("VER $this->id ".self::PROTOCOL.' CVR0'); break; case 'GCF': @@ -460,7 +460,7 @@ class MSN { $len = strlen($str); $this->ns_writeln("UUX $this->id $len"); $this->ns_writedata($str); - if (!socketcheck($this->NSfp)) { + if (!self::socketcheck($this->NSfp)) { $this->debug_message('*** Connected, waiting for commands'); break; } else { @@ -508,7 +508,7 @@ class MSN { */ private function nsReceive() { // Sign in again if not signed in or socket failed - if (!is_resource($this->NSfp) || socketcheck($this->NSfp)) { + if (!is_resource($this->NSfp) || self::socketcheck($this->NSfp)) { $this->callHandler('Reconnect'); $this->NSRetryWait($this->retry_wait); $this->signon(); @@ -836,7 +836,7 @@ class MSN { $fingerprint = $this->getChallenge($chl_code); // NS: >>> QRY {id} {product_id} 32 // NS: >>> fingerprint - $this->ns_writeln("QRY $this->id ".PROD_ID.' 32'); + $this->ns_writeln("QRY $this->id ".self::PROD_ID.' 32'); $this->ns_writedata($fingerprint); $this->ns_writeln("CHG $this->id NLN $this->clientid"); if ($this->PhotoStickerFile !== false) @@ -1433,7 +1433,7 @@ class MSN { * @return void */ private function endSBSession($socket, $killsession = false) { - if (!socketcheck($socket)) { + if (!self::socketcheck($socket)) { $this->sb_writeln($socket, $fake = 0, 'OUT'); } @fclose($socket); @@ -1455,7 +1455,7 @@ class MSN { */ private function sendMessageViaSB($to, $message) { $socket = $this->switchBoardSessionLookup[$to]; - if (socketcheck($socket)) { + if (self::socketcheck($socket)) { return false; } @@ -1614,15 +1614,15 @@ class MSN {
'; $header_array = array( - 'SOAPAction: '.OIM_MAILDATA_SOAP, + 'SOAPAction: '.self::OIM_MAILDATA_SOAP, 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')' ); - $this->debug_message('*** URL: '.OIM_MAILDATA_URL); + $this->debug_message('*** URL: '.self::OIM_MAILDATA_URL); $this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, OIM_MAILDATA_URL); + curl_setopt($curl, CURLOPT_URL, self::OIM_MAILDATA_URL); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); @@ -1684,15 +1684,15 @@ class MSN { '; $header_array = array( - 'SOAPAction: '.OIM_READ_SOAP, + 'SOAPAction: '.self::OIM_READ_SOAP, 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')' ); - $this->debug_message('*** URL: '.OIM_READ_URL); + $this->debug_message('*** URL: '.self::OIM_READ_URL); $this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, OIM_READ_URL); + curl_setopt($curl, CURLOPT_URL, self::OIM_READ_URL); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); @@ -1760,15 +1760,15 @@ class MSN { '; $header_array = array( - 'SOAPAction: '.OIM_DEL_SOAP, + 'SOAPAction: '.self::OIM_DEL_SOAP, 'Content-Type: text/xml; charset=utf-8', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')' ); - $this->debug_message('*** URL: '.OIM_DEL_URL); + $this->debug_message('*** URL: '.self::OIM_DEL_URL); $this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, OIM_DEL_URL); + curl_setopt($curl, CURLOPT_URL, self::OIM_DEL_URL); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); @@ -1807,11 +1807,11 @@ class MSN { xml:lang="zh-TW" proxy="MSNMSGR" xmlns="http://messenger.msn.com/ws/2004/09/oim/" - msnpVer="'.PROTOCOL.'" - buildVer="'.BUILDVER.'"/> + msnpVer="'.self::PROTOCOL.'" + buildVer="'.self::BUILDVER.'"/> @@ -1834,15 +1834,15 @@ X-OIM-Sequence-Num: 1 '; $header_array = array( - 'SOAPAction: '.OIM_SEND_SOAP, + 'SOAPAction: '.self::OIM_SEND_SOAP, 'Content-Type: text/xml', - 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.BUILDVER.')' + 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')' ); - $this->debug_message('*** URL: '.OIM_SEND_URL); + $this->debug_message('*** URL: '.self::OIM_SEND_URL); $this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, OIM_SEND_URL); + curl_setopt($curl, CURLOPT_URL, self::OIM_SEND_URL); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); @@ -2095,15 +2095,15 @@ X-OIM-Sequence-Num: 1 '; $header_array = array( - 'SOAPAction: '.DELMEMBER_SOAP, + 'SOAPAction: '.self::DELMEMBER_SOAP, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' ); - $this->debug_message('*** URL: '.DELMEMBER_URL); + $this->debug_message('*** URL: '.self::DELMEMBER_URL); $this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, DELMEMBER_URL); + curl_setopt($curl, CURLOPT_URL, self::DELMEMBER_URL); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); @@ -2232,15 +2232,15 @@ X-OIM-Sequence-Num: 1 '; $header_array = array( - 'SOAPAction: '.ADDMEMBER_SOAP, + 'SOAPAction: '.self::ADDMEMBER_SOAP, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' ); - $this->debug_message('*** URL: '.ADDMEMBER_URL); + $this->debug_message('*** URL: '.self::ADDMEMBER_URL); $this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, ADDMEMBER_URL); + curl_setopt($curl, CURLOPT_URL, self::ADDMEMBER_URL); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); @@ -2310,14 +2310,14 @@ X-OIM-Sequence-Num: 1 '; $header_array = array( - 'SOAPAction: '.MEMBERSHIP_SOAP, + 'SOAPAction: '.self::MEMBERSHIP_SOAP, 'Content-Type: text/xml; charset=utf-8', 'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)' ); - $this->debug_message('*** URL: '.MEMBERSHIP_URL); + $this->debug_message('*** URL: '.self::MEMBERSHIP_URL); $this->debug_message("*** Sending SOAP:\n$XML"); $curl = curl_init(); - curl_setopt($curl, CURLOPT_URL, MEMBERSHIP_URL); + curl_setopt($curl, CURLOPT_URL, self::MEMBERSHIP_URL); curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1); @@ -2642,7 +2642,7 @@ X-OIM-Sequence-Num: 1 // MSNP15 // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges // Step 1: The MD5 Hash - $md5Hash = md5($code.PROD_KEY); + $md5Hash = md5($code.self::PROD_KEY); $aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0")); for ($i = 0; $i < 4; $i++) { $aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0")))); @@ -2650,7 +2650,7 @@ X-OIM-Sequence-Num: 1 } // Step 2: A new string - $chl_id = $code.PROD_ID; + $chl_id = $code.self::PROD_ID; $chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8)); $aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1)); @@ -2700,7 +2700,7 @@ X-OIM-Sequence-Num: 1 // $key = bcadd(bcmul($high, 0x100000000), $low); // Step 4: Using the key - $md5Hash = md5($code.PROD_KEY); + $md5Hash = md5($code.self::PROD_KEY); $aHash = @explode("\0", chunk_split($md5Hash, 8, "\0")); $hash = ''; @@ -2766,7 +2766,7 @@ X-OIM-Sequence-Num: 1 $password = htmlspecialchars($this->password); if ($url === '') - $passport_url = PASSPORT_URL; + $passport_url = self::PASSPORT_URL; else $passport_url = $url; From 62a7f102757d51eab83134621763deeb313847ce Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Wed, 16 Jun 2010 01:43:55 +0100 Subject: [PATCH 059/655] $killsession parameter not needed - we'll kill the session later anyway --- plugins/Msn/extlib/phpmsnclass/msn.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Msn/extlib/phpmsnclass/msn.class.php b/plugins/Msn/extlib/phpmsnclass/msn.class.php index 5c1a83940b..74a7e3eec1 100644 --- a/plugins/Msn/extlib/phpmsnclass/msn.class.php +++ b/plugins/Msn/extlib/phpmsnclass/msn.class.php @@ -1432,7 +1432,7 @@ class MSN { * @param boolean $killsession Whether to delete the session * @return void */ - private function endSBSession($socket, $killsession = false) { + private function endSBSession($socket) { if (!self::socketcheck($socket)) { $this->sb_writeln($socket, $fake = 0, 'OUT'); } From 0b2bbd20aa015f5d9d48c4264f56e13324346b4a Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Wed, 16 Jun 2010 01:55:39 +0100 Subject: [PATCH 060/655] Added Phergie PHP IRC library --- plugins/Irc/extlib/phergie/.gitignore | 2 + plugins/Irc/extlib/phergie/LICENSE | 27 + .../Irc/extlib/phergie/Phergie/Autoload.php | 84 ++ plugins/Irc/extlib/phergie/Phergie/Bot.php | 390 +++++++ plugins/Irc/extlib/phergie/Phergie/Config.php | 170 ++++ .../phergie/Phergie/Config/Exception.php | 44 + .../Irc/extlib/phergie/Phergie/Connection.php | 359 +++++++ .../phergie/Phergie/Connection/Exception.php | 44 + .../phergie/Phergie/Connection/Handler.php | 130 +++ .../phergie/Phergie/Driver/Abstract.php | 301 ++++++ .../phergie/Phergie/Driver/Exception.php | 49 + .../extlib/phergie/Phergie/Driver/Streams.php | 696 +++++++++++++ .../extlib/phergie/Phergie/Event/Abstract.php | 62 ++ .../extlib/phergie/Phergie/Event/Command.php | 62 ++ .../phergie/Phergie/Event/Exception.php | 38 + .../extlib/phergie/Phergie/Event/Handler.php | 174 ++++ .../extlib/phergie/Phergie/Event/Request.php | 450 +++++++++ .../extlib/phergie/Phergie/Event/Response.php | 953 ++++++++++++++++++ .../Irc/extlib/phergie/Phergie/Exception.php | 33 + .../Irc/extlib/phergie/Phergie/Hostmask.php | 217 ++++ .../phergie/Phergie/Hostmask/Exception.php | 37 + .../phergie/Phergie/Plugin/Abstract.php | 582 +++++++++++ .../Irc/extlib/phergie/Phergie/Plugin/Acl.php | 92 ++ .../extlib/phergie/Phergie/Plugin/AltNick.php | 95 ++ .../phergie/Phergie/Plugin/AudioScrobbler.php | 191 ++++ .../phergie/Phergie/Plugin/AutoJoin.php | 69 ++ .../phergie/Phergie/Plugin/BeerScore.php | 156 +++ .../extlib/phergie/Phergie/Plugin/Cache.php | 106 ++ .../extlib/phergie/Phergie/Plugin/Command.php | 134 +++ .../extlib/phergie/Phergie/Plugin/Ctcp.php | 91 ++ .../extlib/phergie/Phergie/Plugin/Daddy.php | 60 ++ .../phergie/Phergie/Plugin/Exception.php | 113 +++ .../extlib/phergie/Phergie/Plugin/Google.php | 375 +++++++ .../extlib/phergie/Phergie/Plugin/Handler.php | 412 ++++++++ .../extlib/phergie/Phergie/Plugin/Help.php | 250 +++++ .../extlib/phergie/Phergie/Plugin/Http.php | 275 +++++ .../phergie/Phergie/Plugin/Http/Response.php | 228 +++++ .../phergie/Phergie/Plugin/Invisible.php | 44 + .../extlib/phergie/Phergie/Plugin/Join.php | 56 + .../extlib/phergie/Phergie/Plugin/Part.php | 55 + .../Irc/extlib/phergie/Phergie/Plugin/Php.php | 88 ++ .../phergie/Phergie/Plugin/Php/Source.php | 46 + .../Phergie/Plugin/Php/Source/Local.php | 203 ++++ .../extlib/phergie/Phergie/Plugin/Ping.php | 162 +++ .../extlib/phergie/Phergie/Plugin/Pong.php | 44 + .../phergie/Phergie/Plugin/Prioritize.php | 99 ++ .../extlib/phergie/Phergie/Plugin/Puppet.php | 89 ++ .../extlib/phergie/Phergie/Plugin/Quit.php | 60 ++ .../extlib/phergie/Phergie/Plugin/Remind.php | 360 +++++++ .../phergie/Phergie/Plugin/TerryChay.php | 109 ++ .../Phergie/Plugin/TheFuckingWeather.php | 150 +++ .../extlib/phergie/Phergie/Plugin/Time.php | 72 ++ .../extlib/phergie/Phergie/Plugin/Twitter.php | 223 ++++ .../Phergie/Plugin/Twitter/laconica.class.php | 41 + .../Phergie/Plugin/Twitter/twitter.class.php | 287 ++++++ .../Irc/extlib/phergie/Phergie/Plugin/Url.php | 739 ++++++++++++++ .../Phergie/Plugin/Url/Shorten/Abstract.php | 41 + .../Phergie/Plugin/Url/Shorten/Trim.php | 44 + .../phergie/Phergie/Plugin/UserInfo.php | 413 ++++++++ .../phergie/Phergie/Process/Abstract.php | 130 +++ .../extlib/phergie/Phergie/Process/Async.php | 161 +++ .../phergie/Phergie/Process/Exception.php | 33 + .../phergie/Phergie/Process/Standard.php | 61 ++ .../phergie/Phergie/Tools/LogViewer/INSTALL | 24 + .../phergie/Phergie/Tools/LogViewer/index.php | 368 +++++++ .../Irc/extlib/phergie/Phergie/Tools/README | 6 + .../extlib/phergie/Phergie/Ui/Abstract.php | 116 +++ .../Irc/extlib/phergie/Phergie/Ui/Console.php | 223 ++++ .../Irc/extlib/phergie/PhergiePackageTask.php | 110 ++ plugins/Irc/extlib/phergie/README | 11 + plugins/Irc/extlib/phergie/Settings.php.dist | 97 ++ .../Tests/Phergie/Plugin/HandlerTest.php | 461 +++++++++ .../phergie/Tests/Phergie/Plugin/Mock.php | 49 + .../phergie/Tests/Phergie/Plugin/PingTest.php | 175 ++++ .../phergie/Tests/Phergie/Plugin/PongTest.php | 74 ++ .../Tests/Phergie/Plugin/TerryChayTest.php | 99 ++ .../phergie/Tests/Phergie/Plugin/TestCase.php | 207 ++++ .../TestNonInstantiablePluginFromFile.php | 43 + .../Irc/extlib/phergie/Tests/TestHelper.php | 26 + plugins/Irc/extlib/phergie/Tests/phpunit.xml | 26 + plugins/Irc/extlib/phergie/build.xml | 298 ++++++ plugins/Irc/extlib/phergie/phergie.bat | 14 + plugins/Irc/extlib/phergie/phergie.php | 54 + 83 files changed, 13842 insertions(+) create mode 100644 plugins/Irc/extlib/phergie/.gitignore create mode 100644 plugins/Irc/extlib/phergie/LICENSE create mode 100755 plugins/Irc/extlib/phergie/Phergie/Autoload.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Bot.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Config.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Config/Exception.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Connection.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Event/Command.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Event/Exception.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Event/Handler.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Event/Request.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Event/Response.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Exception.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Hostmask.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Hostmask/Exception.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Abstract.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Acl.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/AltNick.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/AudioScrobbler.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/AutoJoin.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/BeerScore.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Cache.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Command.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Ctcp.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Daddy.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Exception.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Google.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Handler.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Help.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Http.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Http/Response.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Invisible.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Join.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Part.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Php.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Php/Source/Local.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Ping.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Pong.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Prioritize.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Puppet.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Plugin/Quit.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Remind.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/TerryChay.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/TheFuckingWeather.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Time.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/laconica.class.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Twitter/twitter.class.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Url.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Abstract.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/Url/Shorten/Trim.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Plugin/UserInfo.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Process/Abstract.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Process/Async.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Process/Exception.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Process/Standard.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/INSTALL create mode 100755 plugins/Irc/extlib/phergie/Phergie/Tools/LogViewer/index.php create mode 100755 plugins/Irc/extlib/phergie/Phergie/Tools/README create mode 100644 plugins/Irc/extlib/phergie/Phergie/Ui/Abstract.php create mode 100644 plugins/Irc/extlib/phergie/Phergie/Ui/Console.php create mode 100644 plugins/Irc/extlib/phergie/PhergiePackageTask.php create mode 100644 plugins/Irc/extlib/phergie/README create mode 100755 plugins/Irc/extlib/phergie/Settings.php.dist create mode 100644 plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/HandlerTest.php create mode 100755 plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/Mock.php create mode 100644 plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PingTest.php create mode 100644 plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/PongTest.php create mode 100644 plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TerryChayTest.php create mode 100644 plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestCase.php create mode 100755 plugins/Irc/extlib/phergie/Tests/Phergie/Plugin/TestNonInstantiablePluginFromFile.php create mode 100644 plugins/Irc/extlib/phergie/Tests/TestHelper.php create mode 100644 plugins/Irc/extlib/phergie/Tests/phpunit.xml create mode 100644 plugins/Irc/extlib/phergie/build.xml create mode 100644 plugins/Irc/extlib/phergie/phergie.bat create mode 100755 plugins/Irc/extlib/phergie/phergie.php diff --git a/plugins/Irc/extlib/phergie/.gitignore b/plugins/Irc/extlib/phergie/.gitignore new file mode 100644 index 0000000000..553fe8e258 --- /dev/null +++ b/plugins/Irc/extlib/phergie/.gitignore @@ -0,0 +1,2 @@ +Settings.php +*.db diff --git a/plugins/Irc/extlib/phergie/LICENSE b/plugins/Irc/extlib/phergie/LICENSE new file mode 100644 index 0000000000..d7d23420ac --- /dev/null +++ b/plugins/Irc/extlib/phergie/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2010, Phergie Development Team +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. + +Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation +and/or other materials provided with the distribution. + +Neither the name of the Phergie Development Team nor the names of its +contributors may be used to endorse or promote products derived from this +software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/plugins/Irc/extlib/phergie/Phergie/Autoload.php b/plugins/Irc/extlib/phergie/Phergie/Autoload.php new file mode 100755 index 0000000000..b03fe2ae10 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Autoload.php @@ -0,0 +1,84 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Autoloader for Phergie classes. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Autoload +{ + /** + * Constructor to add the base Phergie path to the include_path. + * + * @return void + */ + public function __construct() + { + $path = dirname(__FILE__); + $includePath = get_include_path(); + $includePathList = explode(PATH_SEPARATOR, $includePath); + if (!in_array($path, $includePathList)) { + self::addPath($path); + } + } + + /** + * Autoload callback for loading class files. + * + * @param string $class Class to load + * + * @return void + */ + public function load($class) + { + if (substr($class, 0, 8) == 'Phergie_') { + $class = substr($class, 8); + } + include str_replace('_', DIRECTORY_SEPARATOR, $class) . '.php'; + } + + /** + * Registers an instance of this class as an autoloader. + * + * @return void + */ + public static function registerAutoloader() + { + spl_autoload_register(array(new self, 'load')); + } + + /** + * Add a path to the include path. + * + * @param string $path Path to add + * + * @return void + */ + public static function addPath($path) + { + set_include_path($path . PATH_SEPARATOR . get_include_path()); + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Bot.php b/plugins/Irc/extlib/phergie/Phergie/Bot.php new file mode 100755 index 0000000000..153bd55905 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Bot.php @@ -0,0 +1,390 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Composite class for other components to represent the bot. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Bot +{ + /** + * Current version of Phergie + */ + const VERSION = '2.0.1'; + + /** + * Current driver instance + * + * @var Phergie_Driver_Abstract + */ + protected $driver; + + /** + * Current configuration instance + * + * @var Phergie_Config + */ + protected $config; + + /** + * Current connection handler instance + * + * @var Phergie_Connection_Handler + */ + protected $connections; + + /** + * Current plugin handler instance + * + * @var Phergie_Plugin_Handler + */ + protected $plugins; + + /** + * Current event handler instance + * + * @var Phergie_Event_Handler + */ + protected $events; + + /** + * Current end-user interface instance + * + * @var Phergie_Ui_Abstract + */ + protected $ui; + + /** + * Current processor instance + * + * @var Phergie_Process_Abstract + */ + protected $processor; + + /** + * Returns a driver instance, creating one of the default class if + * none has been set. + * + * @return Phergie_Driver_Abstract + */ + public function getDriver() + { + if (empty($this->driver)) { + // Check if a driver has been defined in the configuration to use + // as the default + $config = $this->getConfig(); + if (isset($config['driver'])) { + $class = 'Phergie_Driver_' . ucfirst($config['driver']); + } else { + // Otherwise default to the Streams driver. + $class = 'Phergie_Driver_Streams'; + } + + $this->driver = new $class; + } + return $this->driver; + } + + /** + * Sets the driver instance to use. + * + * @param Phergie_Driver_Abstract $driver Driver instance + * + * @return Phergie_Bot Provides a fluent interface + */ + public function setDriver(Phergie_Driver_Abstract $driver) + { + $this->driver = $driver; + return $this; + } + + /** + * Sets the configuration to use. + * + * @param Phergie_Config $config Configuration instance + * + * @return Phergie_Runner_Abstract Provides a fluent interface + */ + public function setConfig(Phergie_Config $config) + { + $this->config = $config; + return $this; + } + + /** + * Returns the entire configuration in use or the value of a specific + * configuration setting. + * + * @param string $index Optional index of a specific configuration + * setting for which the corresponding value should be returned + * @param mixed $default Value to return if no match is found for $index + * + * @return mixed Value corresponding to $index or the entire + * configuration if $index is not specified + */ + public function getConfig($index = null, $default = null) + { + if (empty($this->config)) { + $this->config = new Phergie_Config; + $this->config->read('Settings.php'); + } + if ($index !== null) { + if (isset($this->config[$index])) { + return $this->config[$index]; + } else { + return $default; + } + } + return $this->config; + } + + /** + * Returns a plugin handler instance, creating it if it does not already + * exist and using a default class if none has been set. + * + * @return Phergie_Plugin_Handler + */ + public function getPluginHandler() + { + if (empty($this->plugins)) { + $this->plugins = new Phergie_Plugin_Handler( + $this->getConfig(), + $this->getEventHandler() + ); + } + return $this->plugins; + } + + /** + * Sets the plugin handler instance to use. + * + * @param Phergie_Plugin_Handler $handler Plugin handler instance + * + * @return Phergie_Bot Provides a fluent interface + */ + public function setPluginHandler(Phergie_Plugin_Handler $handler) + { + $this->plugins = $handler; + return $this; + } + + /** + * Returns an event handler instance, creating it if it does not already + * exist and using a default class if none has been set. + * + * @return Phergie_Event_Handler + */ + public function getEventHandler() + { + if (empty($this->events)) { + $this->events = new Phergie_Event_Handler; + } + return $this->events; + } + + /** + * Sets the event handler instance to use. + * + * @param Phergie_Event_Handler $handler Event handler instance + * + * @return Phergie_Bot Provides a fluent interface + */ + public function setEventHandler(Phergie_Event_Handler $handler) + { + $this->events = $handler; + return $this; + } + + /** + * Returns a connection handler instance, creating it if it does not + * already exist and using a default class if none has been set. + * + * @return Phergie_Connection_Handler + */ + public function getConnectionHandler() + { + if (empty($this->connections)) { + $this->connections = new Phergie_Connection_Handler; + } + return $this->connections; + } + + /** + * Sets the connection handler instance to use. + * + * @param Phergie_Connection_Handler $handler Connection handler instance + * + * @return Phergie_Bot Provides a fluent interface + */ + public function setConnectionHandler(Phergie_Connection_Handler $handler) + { + $this->connections = $handler; + return $this; + } + + /** + * Returns an end-user interface instance, creating it if it does not + * already exist and using a default class if none has been set. + * + * @return Phergie_Ui_Abstract + */ + public function getUi() + { + if (empty($this->ui)) { + $this->ui = new Phergie_Ui_Console; + } + return $this->ui; + } + + /** + * Sets the end-user interface instance to use. + * + * @param Phergie_Ui_Abstract $ui End-user interface instance + * + * @return Phergie_Bot Provides a fluent interface + */ + public function setUi(Phergie_Ui_Abstract $ui) + { + $this->ui = $ui; + return $this; + } + + /** + * Returns a processer instance, creating one if none exists. + * + * @return Phergie_Process_Abstract + */ + public function getProcessor() + { + if (empty($this->processor)) { + $class = 'Phergie_Process_Standard'; + + $type = $this->getConfig('processor'); + if (!empty($type)) { + $class = 'Phergie_Process_' . ucfirst($type); + } + + $this->processor = new $class( + $this, + $this->getConfig('processor.options', array()) + ); + } + return $this->processor; + } + + /** + * Sets the processer instance to use. + * + * @param Phergie_Process_Abstract $processor Processer instance + * + * @return Phergie_Bot Provides a fluent interface + */ + public function setProcessor(Phergie_Process_Abstract $processor) + { + $this->processor = $processor; + return $this; + } + + /** + * Loads plugins into the plugin handler. + * + * @return void + */ + protected function loadPlugins() + { + $config = $this->getConfig(); + $plugins = $this->getPluginHandler(); + $ui = $this->getUi(); + + $plugins->setAutoload($config['plugins.autoload']); + foreach ($config['plugins'] as $name) { + try { + $plugin = $plugins->addPlugin($name); + $ui->onPluginLoad($name); + } catch (Phergie_Plugin_Exception $e) { + $ui->onPluginFailure($name, $e->getMessage()); + if (!empty($plugin)) { + $plugins->removePlugin($plugin); + } + } + } + } + + /** + * Configures and establishes connections to IRC servers. + * + * @return void + */ + protected function loadConnections() + { + $config = $this->getConfig(); + $driver = $this->getDriver(); + $connections = $this->getConnectionHandler(); + $plugins = $this->getPluginHandler(); + $ui = $this->getUi(); + + foreach ($config['connections'] as $data) { + $connection = new Phergie_Connection($data); + $connections->addConnection($connection); + + $ui->onConnect($data['host']); + $driver->setConnection($connection)->doConnect(); + $plugins->setConnection($connection); + $plugins->onConnect(); + } + } + + /** + * Establishes server connections and initiates an execution loop to + * continuously receive and process events. + * + * @return Phergie_Bot Provides a fluent interface + */ + public function run() + { + set_time_limit(0); + + $timezone = $this->getConfig('timezone', 'UTC'); + date_default_timezone_set($timezone); + + $ui = $this->getUi(); + $ui->setEnabled($this->getConfig('ui.enabled')); + + $this->loadPlugins(); + $this->loadConnections(); + + $processor = $this->getProcessor(); + + $connections = $this->getConnectionHandler(); + while (count($connections)) { + $processor->handleEvents(); + } + + $ui->onShutdown(); + + return $this; + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Config.php b/plugins/Irc/extlib/phergie/Phergie/Config.php new file mode 100755 index 0000000000..f011db2365 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Config.php @@ -0,0 +1,170 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Reads from and writes to PHP configuration files and provides access to + * the settings they contain. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Config implements ArrayAccess +{ + /** + * Mapping of configuration file paths to an array of names of settings + * they contain + * + * @var array + */ + protected $files = array(); + + /** + * Mapping of setting names to their current corresponding values + * + * @var array + */ + protected $settings = array(); + + /** + * Includes a specified PHP configuration file and incorporates its + * return value (which should be an associative array) into the current + * configuration settings. + * + * @param string $file Path to the file to read + * + * @return Phergie_Config Provides a fluent interface + * @throws Phergie_Config_Exception + */ + public function read($file) + { + if (!(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' + && file_exists($file)) + && !is_executable($file) + ) { + throw new Phergie_Config_Exception( + 'Path "' . $file . '" does not reference an executable file', + Phergie_Config_Exception::ERR_FILE_NOT_EXECUTABLE + ); + } + + $settings = include $file; + if (!is_array($settings)) { + throw new Phergie_Config_Exception( + 'File "' . $file . '" does not return an array', + Phergie_Config_Exception::ERR_ARRAY_NOT_RETURNED + ); + } + + $this->files[$file] = array_keys($settings); + $this->settings += $settings; + + return $this; + } + + /** + * Writes the values of the current configuration settings back to their + * originating files. + * + * @return Phergie_Config Provides a fluent interface + */ + public function write() + { + foreach ($this->files as $file => &$settings) { + $values = array(); + foreach ($settings as $setting) { + $values[$setting] = $this->settings[$setting]; + } + $source = 'settings[$offset]); + } + + /** + * Returns the value of a configuration setting. + * + * @param string $offset Configuration setting name + * + * @return mixed Configuration setting value or NULL if it is not + * assigned a value + * @see ArrayAccess::offsetGet() + */ + public function offsetGet($offset) + { + if (isset($this->settings[$offset])) { + $value = &$this->settings[$offset]; + } else { + $value = null; + } + + return $value; + } + + /** + * Sets the value of a configuration setting. + * + * @param string $offset Configuration setting name + * @param mixed $value New setting value + * + * @return void + * @see ArrayAccess::offsetSet() + */ + public function offsetSet($offset, $value) + { + $this->settings[$offset] = $value; + } + + /** + * Removes the value set for a configuration setting. + * + * @param string $offset Configuration setting name + * + * @return void + * @see ArrayAccess::offsetUnset() + */ + public function offsetUnset($offset) + { + unset($this->settings[$offset]); + + foreach ($this->files as $file => $settings) { + $key = array_search($offset, $settings); + if ($key !== false) { + unset($this->files[$file][$key]); + } + } + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Config/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Config/Exception.php new file mode 100644 index 0000000000..fb646c10c1 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Config/Exception.php @@ -0,0 +1,44 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Exception related to configuration. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Config_Exception extends Phergie_Exception +{ + /** + * Error indicating that an attempt was made to read a configuration + * file that could not be executed + */ + const ERR_FILE_NOT_EXECUTABLE = 1; + + /** + * Error indicating that a read configuration file does not return an + * array + */ + const ERR_ARRAY_NOT_RETURNED = 2; +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection.php b/plugins/Irc/extlib/phergie/Phergie/Connection.php new file mode 100755 index 0000000000..80f91e8da5 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Connection.php @@ -0,0 +1,359 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Data structure for connection metadata. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Connection +{ + /** + * Host to which the client will connect + * + * @var string + */ + protected $host; + + /** + * Port on which the client will connect, defaults to the standard IRC + * port + * + * @var int + */ + protected $port; + + /** + * Transport for the connection, defaults to tcp but can be set to ssl + * or variations thereof to connect over SSL + * + * @var string + */ + protected $transport; + + /** + * Nick that the client will use + * + * @var string + */ + protected $nick; + + /** + * Username that the client will use + * + * @var string + */ + protected $username; + + /** + * Realname that the client will use + * + * @var string + */ + protected $realname; + + /** + * Password that the client will use + * + * @var string + */ + protected $password; + + /** + * Hostmask for the connection + * + * @var Phergie_Hostmask + */ + protected $hostmask; + + /** + * Constructor to initialize instance properties. + * + * @param array $options Optional associative array of property values + * to initialize + * + * @return void + */ + public function __construct(array $options = array()) + { + $this->transport = 'tcp'; + + $this->setOptions($options); + } + + /** + * Emits an error related to a required connection setting does not have + * value set for it. + * + * @param string $setting Name of the setting + * + * @return void + */ + protected function checkSetting($setting) + { + if (empty($this->$setting)) { + throw new Phergie_Connection_Exception( + 'Required connection setting "' . $setting . '" missing', + Phergie_Connection_Exception::ERR_REQUIRED_SETTING_MISSING + ); + } + } + + /** + * Returns a hostmask that uniquely identifies the connection. + * + * @return string + */ + public function getHostmask() + { + if (empty($this->hostmask)) { + $this->hostmask = new Phergie_Hostmask( + $this->nick, + $this->username, + $this->host + ); + } + + return $this->hostmask; + } + + /** + * Sets the host to which the client will connect. + * + * @param string $host Hostname + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setHost($host) + { + if (empty($this->host)) { + $this->host = (string) $host; + } + + return $this; + } + + /** + * Returns the host to which the client will connect if it is set or + * emits an error if it is not set. + * + * @return string + */ + public function getHost() + { + $this->checkSetting('host'); + + return $this->host; + } + + /** + * Sets the port on which the client will connect. + * + * @param int $port Port + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setPort($port) + { + if (empty($this->port)) { + $this->port = (int) $port; + } + + return $this; + } + + /** + * Returns the port on which the client will connect. + * + * @return int + */ + public function getPort() + { + if (empty($this->port)) { + $this->port = 6667; + } + + return $this->port; + } + + /** + * Sets the transport for the connection to use. + * + * @param string $transport Transport (ex: tcp, ssl, etc.) + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setTransport($transport) + { + $this->transport = (string) $transport; + + if (!in_array($this->transport, stream_get_transports())) { + throw new Phergie_Connection_Exception( + 'Transport ' . $this->transport . ' is not supported', + Phergie_Connection_Exception::TRANSPORT_NOT_SUPPORTED + ); + } + + return $this; + } + + /** + * Returns the transport in use by the connection. + * + * @return string Transport (ex: tcp, ssl, etc.) + */ + public function getTransport() + { + return $this->transport; + } + + /** + * Sets the nick that the client will use. + * + * @param string $nick Nickname + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setNick($nick) + { + if (empty($this->nick)) { + $this->nick = (string) $nick; + } + + return $this; + } + + /** + * Returns the nick that the client will use. + * + * @return string + */ + public function getNick() + { + $this->checkSetting('nick'); + + return $this->nick; + } + + /** + * Sets the username that the client will use. + * + * @param string $username Username + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setUsername($username) + { + if (empty($this->username)) { + $this->username = (string) $username; + } + + return $this; + } + + /** + * Returns the username that the client will use. + * + * @return string + */ + public function getUsername() + { + $this->checkSetting('username'); + + return $this->username; + } + + /** + * Sets the realname that the client will use. + * + * @param string $realname Real name + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setRealname($realname) + { + if (empty($this->realname)) { + $this->realname = (string) $realname; + } + + return $this; + } + + /** + * Returns the realname that the client will use. + * + * @return string + */ + public function getRealname() + { + $this->checkSetting('realname'); + + return $this->realname; + } + + /** + * Sets the password that the client will use. + * + * @param string $password Password + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setPassword($password) + { + if (empty($this->password)) { + $this->password = (string) $password; + } + + return $this; + } + + /** + * Returns the password that the client will use. + * + * @return string + */ + public function getPassword() + { + return $this->password; + } + + /** + * Sets multiple connection settings using an array. + * + * @param array $options Associative array of setting names mapped to + * corresponding values + * + * @return Phergie_Connection Provides a fluent interface + */ + public function setOptions(array $options) + { + foreach ($options as $option => $value) { + $method = 'set' . ucfirst($option); + if (method_exists($this, $method)) { + $this->$method($value); + } + } + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php new file mode 100644 index 0000000000..a750e1d860 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Connection/Exception.php @@ -0,0 +1,44 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Exception related to a connection to an IRC server. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Connection_Exception extends Phergie_Exception +{ + /** + * Error indicating that an operation was attempted requiring a value + * for a specific configuration setting, but none was set + */ + const ERR_REQUIRED_SETTING_MISSING = 1; + + /** + * Error indicating that a connection is configured to use a transport, + * but that transport is not supported by the current PHP installation + */ + const ERR_TRANSPORT_NOT_SUPPORTED = 2; +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php b/plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php new file mode 100644 index 0000000000..e9aeddcd3e --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Connection/Handler.php @@ -0,0 +1,130 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Handles connections initiated by the bot. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Connection_Handler implements Countable, IteratorAggregate +{ + /** + * Map of connections indexed by hostmask + * + * @var array + */ + protected $connections; + + /** + * Constructor to initialize storage for connections. + * + * @return void + */ + public function __construct() + { + $this->connections = array(); + } + + /** + * Adds a connection to the connection list. + * + * @param Phergie_Connection $connection Connection to add + * + * @return Phergie_Connection_Handler Provides a fluent interface + */ + public function addConnection(Phergie_Connection $connection) + { + $this->connections[(string) $connection->getHostmask()] = $connection; + return $this; + } + + /** + * Removes a connection from the connection list. + * + * @param Phergie_Connection|string $connection Instance or hostmask for + * the connection to remove + * + * @return Phergie_Connection_Handler Provides a fluent interface + */ + public function removeConnection($connection) + { + if ($connection instanceof Phergie_Connection) { + $hostmask = (string) $connection->getHostmask(); + } elseif (is_string($connection) + && isset($this->connections[$connection])) { + $hostmask = $connection; + } else { + return $this; + } + unset($this->connections[$hostmask]); + return $this; + } + + /** + * Returns the number of connections in the list. + * + * @return int Number of connections + */ + public function count() + { + return count($this->connections); + } + + /** + * Returns an iterator for the connection list. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->connections); + } + + /** + * Returns a list of specified connection objects. + * + * @param array|string $keys One or more hostmasks identifying the + * connections to return + * + * @return array List of Phergie_Connection objects corresponding to the + * specified hostmask(s) + */ + public function getConnections($keys) + { + $connections = array(); + + if (!is_array($keys)) { + $keys = array($keys); + } + + foreach ($keys as $key) { + if (isset($this->connections[$key])) { + $connections[] = $this->connections[$key]; + } + } + + return $connections; + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php new file mode 100755 index 0000000000..62736620d4 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Abstract.php @@ -0,0 +1,301 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Base class for drivers which handle issuing client commands to the IRC + * server and converting responses into usable data objects. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +abstract class Phergie_Driver_Abstract +{ + /** + * Currently active connection + * + * @var Phergie_Connection + */ + protected $connection; + + /** + * Sets the currently active connection. + * + * @param Phergie_Connection $connection Active connection + * + * @return Phergie_Driver_Abstract Provides a fluent interface + */ + public function setConnection(Phergie_Connection $connection) + { + $this->connection = $connection; + + return $this; + } + + /** + * Returns the currently active connection. + * + * @return Phergie_Connection + * @throws Phergie_Driver_Exception + */ + public function getConnection() + { + if (empty($this->connection)) { + throw new Phergie_Driver_Exception( + 'Operation requires an active connection, but none is set', + Phergie_Driver_Exception::ERR_NO_ACTIVE_CONNECTION + ); + } + + return $this->connection; + } + + /** + * Returns an event if one has been received from the server. + * + * @return Phergie_Event_Interface|null Event instance if an event has + * been received, NULL otherwise + */ + public abstract function getEvent(); + + /** + * Initiates a connection with the server. + * + * @return void + */ + public abstract function doConnect(); + + /** + * Terminates the connection with the server. + * + * @param string $reason Reason for connection termination (optional) + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_6 + */ + public abstract function doQuit($reason = null); + + /** + * Joins a channel. + * + * @param string $channels Comma-delimited list of channels to join + * @param string $keys Optional comma-delimited list of channel keys + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_1 + */ + public abstract function doJoin($channels, $keys = null); + + /** + * Leaves a channel. + * + * @param string $channels Comma-delimited list of channels to leave + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_2 + */ + public abstract function doPart($channels); + + /** + * Invites a user to an invite-only channel. + * + * @param string $nick Nick of the user to invite + * @param string $channel Name of the channel + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_7 + */ + public abstract function doInvite($nick, $channel); + + /** + * Obtains a list of nicks of users in specified channels. + * + * @param string $channels Comma-delimited list of one or more channels + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_5 + */ + public abstract function doNames($channels); + + /** + * Obtains a list of channel names and topics. + * + * @param string $channels Comma-delimited list of one or more channels + * to which the response should be restricted + * (optional) + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_6 + */ + public abstract function doList($channels = null); + + /** + * Retrieves or changes a channel topic. + * + * @param string $channel Name of the channel + * @param string $topic New topic to assign (optional) + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_4 + */ + public abstract function doTopic($channel, $topic = null); + + /** + * Retrieves or changes a channel or user mode. + * + * @param string $target Channel name or user nick + * @param string $mode New mode to assign (optional) + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_3 + */ + public abstract function doMode($target, $mode = null); + + /** + * Changes the client nick. + * + * @param string $nick New nick to assign + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_1_2 + */ + public abstract function doNick($nick); + + /** + * Retrieves information about a nick. + * + * @param string $nick Nick + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_5_2 + */ + public abstract function doWhois($nick); + + /** + * Sends a message to a nick or channel. + * + * @param string $target Channel name or user nick + * @param string $text Text of the message to send + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_1 + */ + public abstract function doPrivmsg($target, $text); + + /** + * Sends a notice to a nick or channel. + * + * @param string $target Channel name or user nick + * @param string $text Text of the notice to send + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_4_2 + */ + public abstract function doNotice($target, $text); + + /** + * Kicks a user from a channel. + * + * @param string $nick Nick of the user + * @param string $channel Channel name + * @param string $reason Reason for the kick (optional) + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_2_8 + */ + public abstract function doKick($nick, $channel, $reason = null); + + /** + * Responds to a server test of client responsiveness. + * + * @param string $daemon Daemon from which the original request originates + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html#c4_6_3 + */ + public abstract function doPong($daemon); + + /** + * Sends a CTCP ACTION (/me) command to a nick or channel. + * + * @param string $target Channel name or user nick + * @param string $text Text of the action to perform + * + * @return void + * @link http://www.invlogic.com/irc/ctcp.html#4.4 + */ + public abstract function doAction($target, $text); + + /** + * Sends a CTCP PING request to a user. + * + * @param string $nick User nick + * @param string $hash Hash to use in the handshake + * + * @return void + * @link http://www.invlogic.com/irc/ctcp.html#4.2 + */ + public abstract function doPing($nick, $hash); + + /** + * Sends a CTCP VERSION request or response to a user. + * + * @param string $nick User nick + * @param string $version Version string to send for a response + * + * @return void + * @link http://www.invlogic.com/irc/ctcp.html#4.1 + */ + public abstract function doVersion($nick, $version = null); + + /** + * Sends a CTCP TIME request to a user. + * + * @param string $nick User nick + * @param string $time Time string to send for a response + * + * @return void + * @link http://www.invlogic.com/irc/ctcp.html#4.6 + */ + public abstract function doTime($nick, $time = null); + + /** + * Sends a CTCP FINGER request to a user. + * + * @param string $nick User nick + * @param string $finger Finger string to send for a response + * + * @return void + * @link http://www.irchelp.org/irchelp/rfc/ctcpspec.html + */ + public abstract function doFinger($nick, $finger = null); + + /** + * Sends a raw command to the server. + * + * @param string $command Command string to send + * + * @return void + */ + public abstract function doRaw($command); +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php new file mode 100755 index 0000000000..c405522292 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Exception.php @@ -0,0 +1,49 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Exception related to driver operations. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Driver_Exception extends Phergie_Exception +{ + /** + * Error indicating that an operation was requested requiring an active + * connection before one had been set + */ + const ERR_NO_ACTIVE_CONNECTION = 1; + + /** + * Error indicating that an operation was requested requiring an active + * connection where one had been set but not initiated + */ + const ERR_NO_INITIATED_CONNECTION = 2; + + /** + * Error indicating that an attempt to initiate a connection failed + */ + const ERR_CONNECTION_ATTEMPT_FAILED = 3; +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php b/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php new file mode 100755 index 0000000000..8fe53aaa2f --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Driver/Streams.php @@ -0,0 +1,696 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Driver that uses the sockets wrapper of the streams extension for + * communicating with the server and handles formatting and parsing of + * events using PHP. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Driver_Streams extends Phergie_Driver_Abstract +{ + /** + * Socket handlers + * + * @var array + */ + protected $sockets = array(); + + /** + * Reference to the currently active socket handler + * + * @var resource + */ + protected $socket; + + /** + * Amount of time in seconds to wait to receive an event each time the + * socket is polled + * + * @var float + */ + protected $timeout = 0.1; + + /** + * Handles construction of command strings and their transmission to the + * server. + * + * @param string $command Command to send + * @param string|array $args Optional string or array of sequential + * arguments + * + * @return string Command string that was sent + * @throws Phergie_Driver_Exception + */ + protected function send($command, $args = '') + { + // Require an open socket connection to continue + if (empty($this->socket)) { + throw new Phergie_Driver_Exception( + 'doConnect() must be called first', + Phergie_Driver_Exception::ERR_NO_INITIATED_CONNECTION + ); + } + + // Add the command + $buffer = strtoupper($command); + + // Add arguments + if (!empty($args)) { + + // Apply formatting if arguments are passed in as an array + if (is_array($args)) { + $end = count($args) - 1; + $args[$end] = ':' . $args[$end]; + $args = implode(' ', $args); + } + + $buffer .= ' ' . $args; + } + + // Transmit the command over the socket connection + fwrite($this->socket, $buffer . "\r\n"); + + // Return the command string that was transmitted + return $buffer; + } + + /** + * Overrides the parent class to set the currently active socket handler + * when the active connection is changed. + * + * @param Phergie_Connection $connection Active connection + * + * @return Phergie_Driver_Streams Provides a fluent interface + */ + public function setConnection(Phergie_Connection $connection) + { + // Set the active socket handler + $hostmask = (string) $connection->getHostmask(); + if (!empty($this->sockets[$hostmask])) { + $this->socket = $this->sockets[$hostmask]; + } + + // Set the active connection + return parent::setConnection($connection); + } + + /** + * Returns a list of hostmasks corresponding to sockets with data to read. + * + * @param int $sec Length of time to wait for new data (seconds) + * @param int $usec Length of time to wait for new data (microseconds) + * + * @return array List of hostmasks or an empty array if none were found + * to have data to read + */ + public function getActiveReadSockets($sec = 0, $usec = 200000) + { + $read = $this->sockets; + $write = null; + $error = null; + $active = array(); + + if (count($this->sockets) > 0) { + $number = stream_select($read, $write, $error, $sec, $usec); + if ($number > 0) { + foreach ($read as $item) { + $active[] = array_search($item, $this->sockets); + } + } + } + + return $active; + } + + /** + * Sets the amount of time to wait for a new event each time the socket + * is polled. + * + * @param float $timeout Amount of time in seconds + * + * @return Phergie_Driver_Streams Provides a fluent interface + */ + public function setTimeout($timeout) + { + $timeout = (float) $timeout; + if ($timeout) { + $this->timeout = $timeout; + } + return $this; + } + + /** + * Returns the amount of time to wait for a new event each time the + * socket is polled. + * + * @return float Amount of time in seconds + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Supporting method to parse event argument strings where the last + * argument may contain a colon. + * + * @param string $args Argument string to parse + * @param int $count Optional maximum number of arguments + * + * @return array Array of argument values + */ + protected function parseArguments($args, $count = -1) + { + return preg_split('/ :?/S', $args, $count); + } + + /** + * Listens for an event on the current connection. + * + * @return Phergie_Event_Interface|null Event instance if an event was + * received, NULL otherwise + */ + public function getEvent() + { + // Check for a new event on the current connection + $buffer = fgets($this->socket, 512); + + // If no new event was found, return NULL + if (empty($buffer)) { + return null; + } + + // Strip the trailing newline from the buffer + $buffer = rtrim($buffer); + + // If the event is from the server... + if (substr($buffer, 0, 1) != ':') { + + // Parse the command and arguments + list($cmd, $args) = array_pad(explode(' ', $buffer, 2), 2, null); + + } else { + // If the event could be from the server or a user... + + // Parse the server hostname or user hostmask, command, and arguments + list($prefix, $cmd, $args) + = array_pad(explode(' ', ltrim($buffer, ':'), 3), 3, null); + if (strpos($prefix, '@') !== false) { + $hostmask = Phergie_Hostmask::fromString($prefix); + } + } + + // Parse the event arguments depending on the event type + $cmd = strtolower($cmd); + switch ($cmd) { + case 'names': + case 'nick': + case 'quit': + case 'ping': + case 'join': + case 'error': + $args = array(ltrim($args, ':')); + break; + + case 'privmsg': + case 'notice': + $ctcp = substr(strstr($args, ':'), 1); + if (substr($ctcp, 0, 1) === "\x01" && substr($ctcp, -1) === "\x01") { + $ctcp = substr($ctcp, 1, -1); + $reply = ($cmd == 'notice'); + list($cmd, $args) = array_pad(explode(' ', $ctcp, 2), 2, null); + $cmd = strtolower($cmd); + switch ($cmd) { + case 'version': + case 'time': + case 'finger': + if ($reply) { + $args = $ctcp; + } + break; + case 'ping': + if ($reply) { + $cmd .= 'Response'; + } else { + $cmd = 'ctcpPing'; + } + break; + case 'action': + $args = array($this->getConnection()->getNick(), $args); + break; + + default: + $cmd = 'ctcp'; + if ($reply) { + $cmd .= 'Response'; + } + $args = array($this->getConnection()->getNick(), $ctcp); + break; + } + } else { + $args = $this->parseArguments($args, 2); + } + break; + + case 'oper': + case 'topic': + case 'mode': + $args = $this->parseArguments($args); + break; + + case 'part': + case 'kill': + case 'invite': + $args = $this->parseArguments($args, 2); + break; + + case 'kick': + $args = $this->parseArguments($args, 3); + break; + + // Remove the target from responses + default: + $args = substr($args, strpos($args, ' ') + 1); + break; + } + + // Create, populate, and return an event object + if (ctype_digit($cmd)) { + $event = new Phergie_Event_Response; + $event + ->setCode($cmd) + ->setDescription($args); + } else { + $event = new Phergie_Event_Request; + $event + ->setType($cmd) + ->setArguments($args); + if (isset($hostmask)) { + $event->setHostmask($hostmask); + } + } + $event->setRawData($buffer); + return $event; + } + + /** + * Initiates a connection with the server. + * + * @return void + */ + public function doConnect() + { + // Listen for input indefinitely + set_time_limit(0); + + // Get connection information + $connection = $this->getConnection(); + $hostname = $connection->getHost(); + $port = $connection->getPort(); + $password = $connection->getPassword(); + $username = $connection->getUsername(); + $nick = $connection->getNick(); + $realname = $connection->getRealname(); + $transport = $connection->getTransport(); + + // Establish and configure the socket connection + $remote = $transport . '://' . $hostname . ':' . $port; + $this->socket = @stream_socket_client($remote, $errno, $errstr); + if (!$this->socket) { + throw new Phergie_Driver_Exception( + 'Unable to connect: socket error ' . $errno . ' ' . $errstr, + Phergie_Driver_Exception::ERR_CONNECTION_ATTEMPT_FAILED + ); + } + + $seconds = (int) $this->timeout; + $microseconds = ($this->timeout - $seconds) * 1000000; + stream_set_timeout($this->socket, $seconds, $microseconds); + + // Send the password if one is specified + if (!empty($password)) { + $this->send('PASS', $password); + } + + // Send user information + $this->send( + 'USER', + array( + $username, + $hostname, + $hostname, + $realname + ) + ); + + $this->send('NICK', $nick); + + // Add the socket handler to the internal array for socket handlers + $this->sockets[(string) $connection->getHostmask()] = $this->socket; + } + + /** + * Terminates the connection with the server. + * + * @param string $reason Reason for connection termination (optional) + * + * @return void + */ + public function doQuit($reason = null) + { + // Send a QUIT command to the server + $this->send('QUIT', $reason); + + // Terminate the socket connection + fclose($this->socket); + + // Remove the socket from the internal socket list + unset($this->sockets[(string) $this->getConnection()->getHostmask()]); + } + + /** + * Joins a channel. + * + * @param string $channels Comma-delimited list of channels to join + * @param string $keys Optional comma-delimited list of channel keys + * + * @return void + */ + public function doJoin($channels, $keys = null) + { + $args = array($channels); + + if (!empty($keys)) { + $args[] = $keys; + } + + $this->send('JOIN', $args); + } + + /** + * Leaves a channel. + * + * @param string $channels Comma-delimited list of channels to leave + * + * @return void + */ + public function doPart($channels) + { + $this->send('PART', $channels); + } + + /** + * Invites a user to an invite-only channel. + * + * @param string $nick Nick of the user to invite + * @param string $channel Name of the channel + * + * @return void + */ + public function doInvite($nick, $channel) + { + $this->send('INVITE', array($nick, $channel)); + } + + /** + * Obtains a list of nicks of usrs in currently joined channels. + * + * @param string $channels Comma-delimited list of one or more channels + * + * @return void + */ + public function doNames($channels) + { + $this->send('NAMES', $channels); + } + + /** + * Obtains a list of channel names and topics. + * + * @param string $channels Comma-delimited list of one or more channels + * to which the response should be restricted + * (optional) + * + * @return void + */ + public function doList($channels = null) + { + $this->send('LIST', $channels); + } + + /** + * Retrieves or changes a channel topic. + * + * @param string $channel Name of the channel + * @param string $topic New topic to assign (optional) + * + * @return void + */ + public function doTopic($channel, $topic = null) + { + $args = array($channel); + + if (!empty($topic)) { + $args[] = $topic; + } + + $this->send('TOPIC', $args); + } + + /** + * Retrieves or changes a channel or user mode. + * + * @param string $target Channel name or user nick + * @param string $mode New mode to assign (optional) + * + * @return void + */ + public function doMode($target, $mode = null) + { + $args = array($target); + + if (!empty($mode)) { + $args[] = $mode; + } + + $this->send('MODE', $args); + } + + /** + * Changes the client nick. + * + * @param string $nick New nick to assign + * + * @return void + */ + public function doNick($nick) + { + $this->send('NICK', $nick); + } + + /** + * Retrieves information about a nick. + * + * @param string $nick Nick + * + * @return void + */ + public function doWhois($nick) + { + $this->send('WHOIS', $nick); + } + + /** + * Sends a message to a nick or channel. + * + * @param string $target Channel name or user nick + * @param string $text Text of the message to send + * + * @return void + */ + public function doPrivmsg($target, $text) + { + $this->send('PRIVMSG', array($target, $text)); + } + + /** + * Sends a notice to a nick or channel. + * + * @param string $target Channel name or user nick + * @param string $text Text of the notice to send + * + * @return void + */ + public function doNotice($target, $text) + { + $this->send('NOTICE', array($target, $text)); + } + + /** + * Kicks a user from a channel. + * + * @param string $nick Nick of the user + * @param string $channel Channel name + * @param string $reason Reason for the kick (optional) + * + * @return void + */ + public function doKick($nick, $channel, $reason = null) + { + $args = array($nick, $channel); + + if (!empty($reason)) { + $args[] = $response; + } + + $this->send('KICK', $args); + } + + /** + * Responds to a server test of client responsiveness. + * + * @param string $daemon Daemon from which the original request originates + * + * @return void + */ + public function doPong($daemon) + { + $this->send('PONG', $daemon); + } + + /** + * Sends a CTCP ACTION (/me) command to a nick or channel. + * + * @param string $target Channel name or user nick + * @param string $text Text of the action to perform + * + * @return void + */ + public function doAction($target, $text) + { + $buffer = rtrim('ACTION ' . $text); + + $this->doPrivmsg($target, chr(1) . $buffer . chr(1)); + } + + /** + * Sends a CTCP response to a user. + * + * @param string $nick User nick + * @param string $command Command to send + * @param string|array $args String or array of sequential arguments + * (optional) + * + * @return void + */ + protected function doCtcp($nick, $command, $args = null) + { + if (is_array($args)) { + $args = implode(' ', $args); + } + + $buffer = rtrim(strtoupper($command) . ' ' . $args); + + $this->doNotice($nick, chr(1) . $buffer . chr(1)); + } + + /** + * Sends a CTCP PING request or response (they are identical) to a user. + * + * @param string $nick User nick + * @param string $hash Hash to use in the handshake + * + * @return void + */ + public function doPing($nick, $hash) + { + $this->doCtcp($nick, 'PING', $hash); + } + + /** + * Sends a CTCP VERSION request or response to a user. + * + * @param string $nick User nick + * @param string $version Version string to send for a response + * + * @return void + */ + public function doVersion($nick, $version = null) + { + if ($version) { + $this->doCtcp($nick, 'VERSION', $version); + } else { + $this->doCtcp($nick, 'VERSION'); + } + } + + /** + * Sends a CTCP TIME request to a user. + * + * @param string $nick User nick + * @param string $time Time string to send for a response + * + * @return void + */ + public function doTime($nick, $time = null) + { + if ($time) { + $this->doCtcp($nick, 'TIME', $time); + } else { + $this->doCtcp($nick, 'TIME'); + } + } + + /** + * Sends a CTCP FINGER request to a user. + * + * @param string $nick User nick + * @param string $finger Finger string to send for a response + * + * @return void + */ + public function doFinger($nick, $finger = null) + { + if ($finger) { + $this->doCtcp($nick, 'FINGER', $finger); + } else { + $this->doCtcp($nick, 'FINGER'); + } + } + + /** + * Sends a raw command to the server. + * + * @param string $command Command string to send + * + * @return void + */ + public function doRaw($command) + { + $this->send('RAW', $command); + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php b/plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php new file mode 100644 index 0000000000..54b035dc03 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Abstract.php @@ -0,0 +1,62 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Base class for events. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +abstract class Phergie_Event_Abstract +{ + /** + * Event type, used for determining the callback to execute in response + * + * @var string + */ + protected $type; + + /** + * Returns the event type. + * + * @return string + */ + public function getType() + { + return $this->type; + } + + /** + * Sets the event type. + * + * @param string $type Event type + * + * @return Phergie_Event_Abstract Implements a fluent interface + */ + public function setType($type) + { + $this->type = (string) $type; + return $this; + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Command.php b/plugins/Irc/extlib/phergie/Phergie/Event/Command.php new file mode 100644 index 0000000000..5940636ba7 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Command.php @@ -0,0 +1,62 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Event originating from a plugin for the bot. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Event_Command extends Phergie_Event_Request +{ + /** + * Reference to the plugin instance that created the event + * + * @var Phergie_Plugin_Abstract + */ + protected $plugin; + + /** + * Stores a reference to the plugin instance that created the event. + * + * @param Phergie_Plugin_Abstract $plugin Plugin instance + * + * @return Phergie_Event_Command Provides a fluent interface + */ + public function setPlugin(Phergie_Plugin_Abstract $plugin) + { + $this->plugin = $plugin; + return $this; + } + + /** + * Returns a reference to the plugin instance that created the event. + * + * @return Phergie_Plugin_Abstract + */ + public function getPlugin() + { + return $this->plugin; + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Exception.php b/plugins/Irc/extlib/phergie/Phergie/Event/Exception.php new file mode 100644 index 0000000000..6b094a810c --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Exception.php @@ -0,0 +1,38 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Exception related to outgoing events. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Event_Exception extends Phergie_Exception +{ + /** + * Error indicating that an attempt was made to create an event of an + * unknown type + */ + const ERR_UNKNOWN_EVENT_TYPE = 1; +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Handler.php b/plugins/Irc/extlib/phergie/Phergie/Event/Handler.php new file mode 100644 index 0000000000..7df1fca35a --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Handler.php @@ -0,0 +1,174 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Handles events initiated by plugins. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ +class Phergie_Event_Handler implements IteratorAggregate, Countable +{ + /** + * Current queue of events + * + * @var array + */ + protected $events; + + /** + * Constructor to initialize the event queue. + * + * @return void + */ + public function __construct() + { + $this->events = array(); + } + + /** + * Adds an event to the queue. + * + * @param Phergie_Plugin_Abstract $plugin Plugin originating the event + * @param string $type Event type, corresponding to a + * Phergie_Event_Command::TYPE_* constant + * @param array $args Optional event arguments + * + * @return Phergie_Event_Handler Provides a fluent interface + */ + public function addEvent(Phergie_Plugin_Abstract $plugin, $type, + array $args = array() + ) { + if (!defined('Phergie_Event_Command::TYPE_' . strtoupper($type))) { + throw new Phergie_Event_Exception( + 'Unknown event type "' . $type . '"', + Phergie_Event_Exception::ERR_UNKNOWN_EVENT_TYPE + ); + } + + $event = new Phergie_Event_Command; + $event + ->setPlugin($plugin) + ->setType($type) + ->setArguments($args); + + $this->events[] = $event; + + return $this; + } + + /** + * Returns the current event queue. + * + * @return array Enumerated array of Phergie_Event_Command objects + */ + public function getEvents() + { + return $this->events; + } + + /** + * Clears the event queue. + * + * @return Phergie_Event_Handler Provides a fluent interface + */ + public function clearEvents() + { + $this->events = array(); + return $this; + } + + /** + * Replaces the current event queue with a given queue of events. + * + * @param array $events Ordered list of objects of the class + * Phergie_Event_Command + * + * @return Phergie_Event_Handler Provides a fluent interface + */ + public function replaceEvents(array $events) + { + $this->events = $events; + return $this; + } + + /** + * Returns whether an event of the given type exists in the queue. + * + * @param string $type Event type from Phergie_Event_Request::TYPE_* + * constants + * + * @return bool TRUE if an event of the specified type exists in the + * queue, FALSE otherwise + */ + public function hasEventOfType($type) + { + foreach ($this->events as $event) { + if ($event->getType() == $type) { + return true; + } + } + return false; + } + + /** + * Returns a list of events of a specified type. + * + * @param string $type Event type from Phergie_Event_Request::TYPE_* + * constants + * + * @return array Array containing event instances of the specified type + * or an empty array if no such events were found + */ + public function getEventsOfType($type) + { + $events = array(); + foreach ($this->events as $event) { + if ($event->getType() == $type) { + $events[] = $event; + } + } + return $events; + } + + /** + * Returns an iterator for the current event queue. + * + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->events); + } + + /** + * Returns the number of events in the event queue + * + * @return int number of queued events + */ + public function count() + { + return count($this->events); + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Request.php b/plugins/Irc/extlib/phergie/Phergie/Event/Request.php new file mode 100755 index 0000000000..a559d9dbe5 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Request.php @@ -0,0 +1,450 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Autonomous event originating from a user or the server. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + * @link http://www.irchelp.org/irchelp/rfc/chapter4.html + */ +class Phergie_Event_Request + extends Phergie_Event_Abstract + implements ArrayAccess +{ + /** + * Nick message event type + */ + const TYPE_NICK = 'nick'; + + /** + * Whois message event type + */ + const TYPE_WHOIS = 'whois'; + + /** + * Quit command event type + */ + const TYPE_QUIT = 'quit'; + + /** + * Join message event type + */ + const TYPE_JOIN = 'join'; + + /** + * Kick message event type + */ + const TYPE_KICK = 'kick'; + + /** + * Part message event type + */ + const TYPE_PART = 'part'; + + /** + * Invite message event type + */ + const TYPE_INVITE = 'invite'; + + /** + * Mode message event type + */ + const TYPE_MODE = 'mode'; + + /** + * Topic message event type + */ + const TYPE_TOPIC = 'topic'; + + /** + * Private message command event type + */ + const TYPE_PRIVMSG = 'privmsg'; + + /** + * Notice message event type + */ + const TYPE_NOTICE = 'notice'; + + /** + * Pong message event type + */ + const TYPE_PONG = 'pong'; + + /** + * CTCP ACTION command event type + */ + const TYPE_ACTION = 'action'; + + /** + * CTCP PING command event type + */ + const TYPE_PING = 'ping'; + + /** + * CTCP TIME command event type + */ + const TYPE_TIME = 'time'; + + /** + * CTCP VERSION command event type + */ + const TYPE_VERSION = 'version'; + + /** + * RAW message event type + */ + const TYPE_RAW = 'raw'; + + /** + * Mapping of event types to their named parameters + * + * @var array + */ + protected static $map = array( + + self::TYPE_QUIT => array( + 'message' => 0 + ), + + self::TYPE_JOIN => array( + 'channel' => 0 + ), + + self::TYPE_KICK => array( + 'channel' => 0, + 'user' => 1, + 'comment' => 2 + ), + + self::TYPE_PART => array( + 'channel' => 0, + 'message' => 1 + ), + + self::TYPE_INVITE => array( + 'nickname' => 0, + 'channel' => 1 + ), + + self::TYPE_MODE => array( + 'target' => 0, + 'mode' => 1, + 'limit' => 2, + 'user' => 3, + 'banmask' => 4 + ), + + self::TYPE_TOPIC => array( + 'channel' => 0, + 'topic' => 1 + ), + + self::TYPE_PRIVMSG => array( + 'receiver' => 0, + 'text' => 1 + ), + + self::TYPE_NOTICE => array( + 'nickname' => 0, + 'text' => 1 + ), + + self::TYPE_ACTION => array( + 'target' => 0, + 'action' => 1 + ), + + self::TYPE_RAW => array( + 'message' => 0 + ) + + ); + + /** + * Hostmask representing the originating user, if applicable + * + * @var Phergie_Hostmask + */ + protected $hostmask; + + /** + * Arguments included with the message + * + * @var array + */ + protected $arguments; + + /** + * Raw data sent by the server + * + * @var string + */ + protected $rawData; + + /** + * Sets the hostmask representing the originating user. + * + * @param Phergie_Hostmask $hostmask User hostmask + * + * @return Phergie_Event_Request Provides a fluent interface + */ + public function setHostmask(Phergie_Hostmask $hostmask) + { + $this->hostmask = $hostmask; + return $this; + } + + /** + * Returns the hostmask representing the originating user. + * + * @return Phergie_Event_Request|null Hostmask or NULL if none was set + */ + public function getHostmask() + { + return $this->hostmask; + } + + /** + * Sets the arguments for the request. + * + * @param array $arguments Request arguments + * + * @return Phergie_Event_Request Provides a fluent interface + */ + public function setArguments($arguments) + { + $this->arguments = $arguments; + return $this; + } + + /** + * Returns the arguments for the request. + * + * @return array + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * Resolves an argument specification to an integer position. + * + * @param mixed $argument Integer position (starting from 0) or the + * equivalent string name of the argument from self::$map + * + * @return int|null Integer position of the argument or NULL if no + * corresponding argument was found + */ + protected function resolveArgument($argument) + { + if (isset($this->arguments[$argument])) { + return $argument; + } else { + $argument = strtolower($argument); + if (isset(self::$map[$this->type][$argument]) + && isset($this->arguments[self::$map[$this->type][$argument]]) + ) { + return self::$map[$this->type][$argument]; + } + } + return null; + } + + /** + * Returns a single specified argument for the request. + * + * @param mixed $argument Integer position (starting from 0) or the + * equivalent string name of the argument from self::$map + * + * @return string|null Argument value or NULL if none is set + */ + public function getArgument($argument) + { + $argument = $this->resolveArgument($argument); + if ($argument !== null) { + return $this->arguments[$argument]; + } + return null; + } + + /** + * Sets the raw buffer for the event. + * + * @param string $buffer Raw event buffer + * + * @return Phergie_Event_Request Provides a fluent interface + */ + public function setRawData($buffer) + { + $this->rawData = $buffer; + return $this; + } + + /** + * Returns the raw buffer sent from the server for the event. + * + * @return string + */ + public function getRawData() + { + return $this->rawData; + } + + /** + * Returns the nick of the user who originated the event. + * + * @return string + */ + public function getNick() + { + return $this->hostmask->getNick(); + } + + /** + * Returns the channel name if the event occurred in a channel or the + * user nick if the event was a private message directed at the bot by a + * user. + * + * @return string + */ + public function getSource() + { + if (substr($this->arguments[0], 0, 1) == '#') { + return $this->arguments[0]; + } + return $this->hostmask->getNick(); + } + + /** + * Returns whether or not the event occurred within a channel. + * + * @return TRUE if the event is in a channel, FALSE otherwise + */ + public function isInChannel() + { + return (substr($this->getSource(), 0, 1) == '#'); + } + + /** + * Returns whether or not the event originated from a user. + * + * @return TRUE if the event is from a user, FALSE otherwise + */ + public function isFromUser() + { + if (empty($this->hostmask)) { + return false; + } + $username = $this->hostmask->getUsername(); + return !empty($username); + } + + /** + * Returns whether or not the event originated from the server. + * + * @return TRUE if the event is from the server, FALSE otherwise + */ + public function isFromServer() + { + $username = $this->hostmask->getUsername(); + return empty($username); + } + + /** + * Provides access to named parameters via virtual "getter" methods. + * + * @param string $name Name of the method called + * @param array $arguments Arguments passed to the method (should always + * be empty) + * + * @return mixed Method return value + */ + public function __call($name, array $arguments) + { + if (!count($arguments) && substr($name, 0, 3) == 'get') { + return $this->getArgument(substr($name, 3)); + } + } + + /** + * Checks to see if an event argument is assigned a value. + * + * @param string|int $offset Argument name or position beginning from 0 + * + * @return bool TRUE if the argument has a value, FALSE otherwise + * @see ArrayAccess::offsetExists() + */ + public function offsetExists($offset) + { + return ($this->resolveArgument($offset) !== null); + } + + /** + * Returns the value of an event argument. + * + * @param string|int $offset Argument name or position beginning from 0 + * + * @return string|null Argument value or NULL if none is set + * @see ArrayAccess::offsetGet() + */ + public function offsetGet($offset) + { + return $this->getArgument($offset); + } + + /** + * Sets the value of an event argument. + * + * @param string|int $offset Argument name or position beginning from 0 + * @param string $value New argument value + * + * @return void + * @see ArrayAccess::offsetSet() + */ + public function offsetSet($offset, $value) + { + $offset = $this->resolveArgument($offset); + if ($offset !== null) { + $this->arguments[$offset] = $value; + } + } + + /** + * Removes the value set for an event argument. + * + * @param string|int $offset Argument name or position beginning from 0 + * + * @return void + * @see ArrayAccess::offsetUnset() + */ + public function offsetUnset($offset) + { + if ($offset = $this->resolveArgument($offset)) { + unset($this->arguments[$offset]); + } + } +} diff --git a/plugins/Irc/extlib/phergie/Phergie/Event/Response.php b/plugins/Irc/extlib/phergie/Phergie/Event/Response.php new file mode 100755 index 0000000000..097e2535e8 --- /dev/null +++ b/plugins/Irc/extlib/phergie/Phergie/Event/Response.php @@ -0,0 +1,953 @@ + + * @copyright 2008-2010 Phergie Development Team (http://phergie.org) + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + */ + +/** + * Event originating from the server in response to an event sent by the + * current client. + * + * @category Phergie + * @package Phergie + * @author Phergie Development Team + * @license http://phergie.org/license New BSD License + * @link http://pear.phergie.org/package/Phergie + * @link http://www.irchelp.org/irchelp/rfc/chapter6.html + */ +class Phergie_Event_Response extends Phergie_Event_Abstract +{ + /** + * No such nick/channel + * + * Used to indicate the nickname parameter supplied to a command is currently + * unused. + */ + const ERR_NOSUCHNICK = '401'; + + /** + * No such server + * + * Used to indicate the server name given currently doesn't exist. + */ + const ERR_NOSUCHSERVER = '402'; + + /** + * No such channel + * + * Used to indicate the given channel name is invalid. + */ + const ERR_NOSUCHCHANNEL = '403'; + + /** + * Cannot send to channel + * + * Sent to a user who is either (a) not on a channel which is mode +n or (b) not + * a chanop (or mode +v) on a channel which has mode +m set and is trying to send + * a PRIVMSG message to that channel. + */ + const ERR_CANNOTSENDTOCHAN = '404'; + + /** + * You have joined too many channels + * + * Sent to a user when they have joined the maximum number of allowed channels + * and they try to join another channel. + */ + const ERR_TOOMANYCHANNELS = '405'; + + /** + * There was no such nickname + * + * Returned by WHOWAS to indicate there is no history information for that + * nickname. + */ + const ERR_WASNOSUCHNICK = '406'; + + /** + * Duplicate recipients. No message delivered + * + * Returned to a client which is attempting to send PRIVMSG/NOTICE using the + * user@host destination format and for a user@host which has several + * occurrences. + */ + const ERR_TOOMANYTARGETS = '407'; + + /** + * No origin specified + * + * PING or PONG message missing the originator parameter which is required since + * these commands must work without valid prefixes. + */ + const ERR_NOORIGIN = '409'; + + /** + * No recipient given () + */ + const ERR_NORECIPIENT = '411'; + + /** + * No text to send + */ + const ERR_NOTEXTTOSEND = '412'; + + /** + * No toplevel domain specified + */ + const ERR_NOTOPLEVEL = '413'; + + /** + * Wildcard in toplevel domain + * + * 412 - 414 are returned by PRIVMSG to indicate that the message wasn't + * delivered for some reason. ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that + * are returned when an invalid use of "PRIVMSG $" or "PRIVMSG #" + * is attempted. + */ + const ERR_WILDTOPLEVEL = '414'; + + /** + * Unknown command + * + * Returned to a registered client to indicate that the command sent is unknown + * by the server. + */ + const ERR_UNKNOWNCOMMAND = '421'; + + /** + * MOTD File is missing + * + * Server's MOTD file could not be opened by the server. + */ + const ERR_NOMOTD = '422'; + + /** + * No administrative info available + * + * Returned by a server in response to an ADMIN message when there is an error in + * finding the appropriate information. + */ + const ERR_NOADMININFO = '423'; + + /** + * File error doing on + * + * Generic error message used to report a failed file operation during the + * processing of a message. + */ + const ERR_FILEERROR = '424'; + + /** + * No nickname given + * + * Returned when a nickname parameter expected for a command and isn't found. + */ + const ERR_NONICKNAMEGIVEN = '431'; + + /** + * Erroneus nickname + * + * Returned after receiving a NICK message which contains characters which do not + * fall in the defined set. See section x.x.x for details on valid nicknames. + */ + const ERR_ERRONEUSNICKNAME = '432'; + + /** + * Nickname is already in use + * + * Returned when a NICK message is processed that results in an attempt to change + * to a currently existing nickname. + */ + const ERR_NICKNAMEINUSE = '433'; + + /** + * Nickname collision KILL + * + * Returned by a server to a client when it detects a nickname collision + * (registered of a NICK that already exists by another server). + */ + const ERR_NICKCOLLISION = '436'; + + /** + * They aren't on that channel + * + * Returned by the server to indicate that the target user of the command is not + * on the given channel. + */ + const ERR_USERNOTINCHANNEL = '441'; + + /** + * You're not on that channel + * + * Returned by the server whenever a client tries to perform a channel effecting + * command for which the client isn't a member. + */ + const ERR_NOTONCHANNEL = '442'; + + /** + * is already on channel + * + * Returned when a client tries to invite a user to a channel they are already + * on. + */ + const ERR_USERONCHANNEL = '443'; + + /** + * User not logged in + * + * Returned by the summon after a SUMMON command for a user was unable to be + * performed since they were not logged in. + */ + const ERR_NOLOGIN = '444'; + + /** + * SUMMON has been disabled + * + * Returned as a response to the SUMMON command. Must be returned by any server + * which does not implement it. + */ + const ERR_SUMMONDISABLED = '445'; + + /** + * USERS has been disabled + * + * Returned as a response to the USERS command. Must be returned by any server + * which does not implement it. + */ + const ERR_USERSDISABLED = '446'; + + /** + * You have not registered + * + * Returned by the server to indicate that the client must be registered before + * the server will allow it to be parsed in detail. + */ + const ERR_NOTREGISTERED = '451'; + + /** + * Not enough parameters + * + * Returned by the server by numerous commands to indicate to the client that it + * didn't supply enough parameters. + */ + const ERR_NEEDMOREPARAMS = '461'; + + /** + * You may not reregister + * + * Returned by the server to any link which tries to change part of the + * registered details (such as password or user details from second USER + * message). + */ + const ERR_ALREADYREGISTRED = '462'; + + /** + * Your host isn't among the privileged + * + * Returned to a client which attempts to register with a server which does not + * been setup to allow connections from the host the attempted connection is + * tried. + */ + const ERR_NOPERMFORHOST = '463'; + + /** + * Password incorrect + * + * Returned to indicate a failed attempt at registering a connection for which a + * password was required and was either not given or incorrect. + */ + const ERR_PASSWDMISMATCH = '464'; + + /** + * You are banned from this server + * + * Returned after an attempt to connect and register yourself with a server which + * has been setup to explicitly deny connections to you. + */ + const ERR_YOUREBANNEDCREEP = '465'; + + /** + * Channel key already set + */ + const ERR_KEYSET = '467'; + + /** + * Cannot join channel (+l) + */ + const ERR_CHANNELISFULL = '471'; + + /** + * is unknown mode char to me + */ + const ERR_UNKNOWNMODE = '472'; + + /** + * Cannot join channel (+i) + */ + const ERR_INVITEONLYCHAN = '473'; + + /** + * Cannot join channel (+b) + */ + const ERR_BANNEDFROMCHAN = '474'; + + /** + * Cannot join channel (+k) + */ + const ERR_BADCHANNELKEY = '475'; + + /** + * Permission Denied- You're not an IRC operator + * + * Any command requiring operator privileges to operate must return this error to + * indicate the attempt was unsuccessful. + */ + const ERR_NOPRIVILEGES = '481'; + + /** + * You're not channel operator + * + * Any command requiring 'chanop' privileges (such as MODE messages) must return + * this error if the client making the attempt is not a chanop on the specified + * channel. + */ + const ERR_CHANOPRIVSNEEDED = '482'; + + /** + * You cant kill a server! + * + * Any attempts to use the KILL command on a server are to be refused and this + * error returned directly to the client. + */ + const ERR_CANTKILLSERVER = '483'; + + /** + * No O-lines for your host + * + * If a client sends an OPER message and the server has not been configured to + * allow connections from the client's host as an operator, this error must be + * returned. + */ + const ERR_NOOPERHOST = '491'; + + /** + * Unknown MODE flag + * + * Returned by the server to indicate that a MODE message was sent with a + * nickname parameter and that the a mode flag sent was not recognized. + */ + const ERR_UMODEUNKNOWNFLAG = '501'; + + /** + * Cant change mode for other users + * + * Error sent to any user trying to view or change the user mode for a user other + * than themselves. + */ + const ERR_USERSDONTMATCH = '502'; + + /** + * Dummy reply number. Not used. + */ + const RPL_NONE = '300'; + + /** + * [{}] + * + * Reply format used by USERHOST to list replies to the query list. The reply + * string is composed as follows = ['*'] '=' <'+'|'-'> + * The '*' indicates whether the client has registered as an Operator. The '-' or + * '+' characters represent whether the client has set an AWAY message or not + * respectively. + */ + const RPL_USERHOST = '302'; + + /** + * [ {}] + * + * Reply format used by ISON to list replies to the query list. + */ + const RPL_ISON = '303'; + + /** + * + */ + const RPL_AWAY = '301'; + + /** + * You are no longer marked as being away + */ + const RPL_UNAWAY = '305'; + + /** + * You have been marked as being away + * + * These replies are used with the AWAY command (if allowed). RPL_AWAY is sent to + * any client sending a PRIVMSG to a client which is away. RPL_AWAY is only sent + * by the server to which the client is connected. Replies RPL_UNAWAY and + * RPL_NOWAWAY are sent when the client removes and sets an AWAY message. + */ + const RPL_NOWAWAY = '306'; + + /** + * * + */ + const RPL_WHOISUSER = '311'; + + /** + * + */ + const RPL_WHOISSERVER = '312'; + + /** + * is an IRC operator + */ + const RPL_WHOISOPERATOR = '313'; + + /** + * seconds idle + */ + const RPL_WHOISIDLE = '317'; + + /** + * End of /WHOIS list + */ + const RPL_ENDOFWHOIS = '318'; + + /** + * {[@|+]} + * + * Replies 311 - 313, 317 - 319 are all replies generated in response to a WHOIS + * message. Given that there are enough parameters present, the answering server + * must either formulate a reply out of the above numerics (if the query nick is + * found) or return an error reply. The '*' in RPL_WHOISUSER is there as the + * literal character and not as a wild card. For each reply set, only + * RPL_WHOISCHANNELS may appear more than once (for long lists of channel names). + * The '@' and '+' characters next to the channel name indicate whether a client + * is a channel operator or has been granted permission to speak on a moderated + * channel. The RPL_ENDOFWHOIS reply is used to mark the end of processing a + * WHOIS message. + */ + const RPL_WHOISCHANNELS = '319'; + + /** + * * + */ + const RPL_WHOWASUSER = '314'; + + /** + * End of WHOWAS + * + * When replying to a WHOWAS message, a server must use the replies + * RPL_WHOWASUSER, RPL_WHOISSERVER or ERR_WASNOSUCHNICK for each nickname in the + * presented list. At the end of all reply batches, there must be RPL_ENDOFWHOWAS + * (even if there was only one reply and it was an error). + */ + const RPL_ENDOFWHOWAS = '369'; + + /** + * Channel Users Name + */ + const RPL_LISTSTART = '321'; + + /** + * <# visible> + */ + const RPL_LIST = '322'; + + /** + * End of /LIST + * + * Replies RPL_LISTSTART, RPL_LIST, RPL_LISTEND mark the start, actual replies + * with data and end of the server's response to a LIST command. If there are no + * channels available to return, only the start and end reply must be sent. + */ + const RPL_LISTEND = '323'; + + /** + * + */ + const RPL_CHANNELMODEIS = '324'; + + /** + * No topic is set + */ + const RPL_NOTOPIC = '331'; + + /** + * + * + * When sending a TOPIC message to determine the channel topic, one of two + * replies is sent. If the topic is set, RPL_TOPIC is sent back else RPL_NOTOPIC. + */ + const RPL_TOPIC = '332'; + + /** + * + * + * Returned by the server to indicate that the attempted INVITE message was + * successful and is being passed onto the end client. + */ + const RPL_INVITING = '341'; + + /** + * Summoning user to IRC + * + * Returned by a server answering a SUMMON message to indicate that it is + * summoning that user. + */ + const RPL_SUMMONING = '342'; + + /** + * . + * + * Reply by the server showing its version details. The is the version + * of the software being used (including any patchlevel revisions) and the + * is used to indicate if the server is running in "debug mode". The + * "comments" field may contain any comments about the version or further version + * details. + */ + const RPL_VERSION = '351'; + + /** + * [*][@|+] + */ + const RPL_WHOREPLY = '352'; + + /** + * End of /WHO list + * + * The RPL_WHOREPLY and RPL_ENDOFWHO pair are used to answer a WHO message. The + * RPL_WHOREPLY is only sent if there is an appropriate match to the WHO query. + * If there is a list of parameters supplied with a WHO message, a RPL_ENDOFWHO + * must be sent after processing each list item with being the item. + */ + const RPL_ENDOFWHO = '315'; + + /** + * [[@|+] [[@|+] [...]]] + */ + const RPL_NAMREPLY = '353'; + + /** + * End of /NAMES list + * + * To reply to a NAMES message, a reply pair consisting of RPL_NAMREPLY and + * RPL_ENDOFNAMES is sent by the server back to the client. If there is no + * channel found as in the query, then only RPL_ENDOFNAMES is returned. The + * exception to this is when a NAMES message is sent with no parameters and all + * visible channels and contents are sent back in a series of RPL_NAMEREPLY + * messages with a RPL_ENDOFNAMES to mark the end. + */ + const RPL_ENDOFNAMES = '366'; + + /** + * + */ + const RPL_LINKS = '364'; + + /** + * End of /LINKS list + * + * In replying to the LINKS message, a server must send replies back using the + * RPL_LINKS numeric and mark the end of the list using an RPL_ENDOFLINKS reply.v + */ + const RPL_ENDOFLINKS = '365'; + + /** + * + */ + const RPL_BANLIST = '367'; + + /** + * End of channel ban list + * + * When listing the active 'bans' for a given channel, a server is required to + * send the list back using the RPL_BANLIST and RPL_ENDOFBANLIST messages. A + * separate RPL_BANLIST is sent for each active banid. After the banids have been + * listed (or if none present) a RPL_ENDOFBANLIST must be sent. + */ + const RPL_ENDOFBANLIST = '368'; + + /** + * + */ + const RPL_INFO = '371'; + + /** + * End of /INFO list + * + * A server responding to an INFO message is required to send all its 'info' in a + * series of RPL_INFO messages with a RPL_ENDOFINFO reply to indicate the end of + * the replies. + */ + const RPL_ENDOFINFO = '374'; + + /** + * - Message of the day - + */ + const RPL_MOTDSTART = '375'; + + /** + * - + */ + const RPL_MOTD = '372'; + + /** + * End of /MOTD command + * + * When responding to the MOTD message and the MOTD file is found, the file is + * displayed line by line, with each line no longer than 80 characters, using + * RPL_MOTD format replies. These should be surrounded by a RPL_MOTDSTART (before + * the RPL_MOTDs) and an RPL_ENDOFMOTD (after). + */ + const RPL_ENDOFMOTD = '376'; + + /** + * You are now an IRC operator + * + * RPL_YOUREOPER is sent back to a client which has just successfully issued an + * OPER message and gained operator status. + */ + const RPL_YOUREOPER = '381'; + + /** + * Rehashing + * + * If the REHASH option is used and an operator sends a REHASH message, an + * RPL_REHASHING is sent back to the operator. + */ + const RPL_REHASHING = '382'; + + /** + * + * + * When replying to the TIME message, a server must send the reply using the + * RPL_TIME format above. The string showing the time need only contain the + * correct day and time there. There is no further requirement for the time + * string. + */ + const RPL_TIME = '391'; + + /** + * UserID Terminal Host + */ + const RPL_USERSSTART = '392'; + + /** + * %-8s %-9s %-8s + */ + const RPL_USERS = '393'; + + /** + * End of users + */ + const RPL_ENDOFUSERS = '394'; + + /** + * Nobody logged in + * + * If the USERS message is handled by a server, the replies RPL_USERSTART, + * RPL_USERS, RPL_ENDOFUSERS and RPL_NOUSERS are used. RPL_USERSSTART must be + * sent first, following by either a sequence of RPL_USERS or a single + * RPL_NOUSER. Following this is RPL_ENDOFUSERS. + */ + const RPL_NOUSERS = '395'; + + /** + * Link + */ + const RPL_TRACELINK = '200'; + + /** + * Try. + */ + const RPL_TRACECONNECTING = '201'; + + /** + * H.S. + */ + const RPL_TRACEHANDSHAKE = '202'; + + /** + * ???? [] + */ + const RPL_TRACEUNKNOWN = '203'; + + /** + * Oper + */ + const RPL_TRACEOPERATOR = '204'; + + /** + * User + */ + const RPL_TRACEUSER = '205'; + + /** + * Serv S C @ + */ + const RPL_TRACESERVER = '206'; + + /** + * 0 + */ + const RPL_TRACENEWTYPE = '208'; + + /** + * File + * + * The RPL_TRACE* are all returned by the server in response to the TRACE + * message. How many are returned is dependent on the the TRACE message and + * whether it was sent by an operator or not. There is no predefined order for + * which occurs first. Replies RPL_TRACEUNKNOWN, RPL_TRACECONNECTING and + * RPL_TRACEHANDSHAKE are all used for connections which have not been fully + * established and are either unknown, still attempting to connect or in the + * process of completing the 'server handshake'. RPL_TRACELINK is sent by any + * server which handles a TRACE message and has to pass it on to another server. + * The list of RPL_TRACELINKs sent in response to a TRACE command traversing the + * IRC network should reflect the actual connectivity of the servers themselves + * along that path. RPL_TRACENEWTYPE is to be used for any connection which does + * not fit in the other categories but is being displayed anyway. + */ + const RPL_TRACELOG = '261'; + + /** + *