From 93e76f3b83c2eba4932d467e1693e9c5dd13b9ee Mon Sep 17 00:00:00 2001 From: Evan Prodromou Date: Sat, 24 Apr 2010 12:14:12 -0400 Subject: [PATCH 001/666] use statusnet_ as namespace prefix for JSON --- lib/apiaction.php | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/apiaction.php b/lib/apiaction.php index 6ee0a94d94..bb4884b45d 100644 --- a/lib/apiaction.php +++ b/lib/apiaction.php @@ -225,7 +225,7 @@ class ApiAction extends Action // StatusNet-specific - $twitter_user['statusnet:profile_url'] = $profile->profileurl; + $twitter_user['statusnet_profile_url'] = $profile->profileurl; return $twitter_user; } @@ -314,7 +314,7 @@ class ApiAction extends Action // StatusNet-specific - $twitter_status['statusnet:html'] = $notice->rendered; + $twitter_status['statusnet_html'] = $notice->rendered; return $twitter_status; } @@ -508,7 +508,11 @@ class ApiAction extends Action $this->showTwitterXmlStatus($value, 'retweeted_status'); break; default: - $this->element($element, null, $value); + if (strncmp($element, 'statusnet_', 10) == 0) { + $this->element('statusnet:'.substr($element, 10), null, $value); + } else { + $this->element($element, null, $value); + } } } $this->elementEnd($tag); @@ -533,6 +537,8 @@ class ApiAction extends Action foreach($twitter_user as $element => $value) { if ($element == 'status') { $this->showTwitterXmlStatus($twitter_user['status']); + } else if (strncmp($element, 'statusnet_', 10) == 0) { + $this->element('statusnet:'.substr($element, 10), null, $value); } else { $this->element($element, null, $value); } From eed0facc87781e25e59337f1371489742a945764 Mon Sep 17 00:00:00 2001 From: Brenda Wallace Date: Thu, 27 May 2010 03:00:58 +0000 Subject: [PATCH 002/666] added user_location_prefs to upgrade script --- db/08to09_pg.sql | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/db/08to09_pg.sql b/db/08to09_pg.sql index b7a0eb8e8c..cc1edc5ec0 100644 --- a/db/08to09_pg.sql +++ b/db/08to09_pg.sql @@ -101,3 +101,13 @@ alter table queue_item rename to queue_item_old; alter table queue_item_new rename to queue_item; +create table user_location_prefs ( + user_id integer not null /*comment 'user who has the preference'*/ references "user" (id), + share_location int default 1 /* comment 'Whether to share location data'*/, + created timestamp not null /*comment 'date this record was created'*/, + modified timestamp /* comment 'date this record was modified'*/, + + primary key (user_id) +); + + From af4fd327429fcc01769b33ece458a77a37b2463f Mon Sep 17 00:00:00 2001 From: Brenda Wallace Date: Thu, 27 May 2010 03:06:42 +0000 Subject: [PATCH 003/666] added the inbox table to postgres upgrade script --- db/08to09_pg.sql | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/db/08to09_pg.sql b/db/08to09_pg.sql index cc1edc5ec0..c2fabf63ba 100644 --- a/db/08to09_pg.sql +++ b/db/08to09_pg.sql @@ -110,4 +110,12 @@ create table user_location_prefs ( primary key (user_id) ); +create table inbox ( + + user_id integer not null /* comment 'user receiving the notice' */ references "user" (id), + notice_ids bytea /* comment 'packed list of notice ids' */, + + primary key (user_id) + +); From 0264f66d76f6a8e5669d305985f96533a156e9ae Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Sat, 12 Jun 2010 17:28:43 +0100 Subject: [PATCH 004/666] 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 005/666] 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 006/666] 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 007/666] 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 008/666] 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 009/666] 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 010/666] - 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 011/666] 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 012/666] 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 013/666] 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 014/666] 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 015/666] 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 016/666] 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 d41298950b9c2d05067d71f6b2ab3315c6330489 Mon Sep 17 00:00:00 2001 From: Luke Fitzgerald Date: Wed, 16 Jun 2010 00:04:59 +0100 Subject: [PATCH 017/666] 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 018/666] 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 019/666] 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 020/666] 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 021/666] $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 022/666] 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'; + + /** + *