+ case 'CVR':\r
+ // MSNP9\r
+ // NS: <<< CVR {id} {ver_list} {download_serve} ....\r
+ // NS: >>> USR {id} TWN I {user}\r
+ // MSNP15\r
+ // NS: <<< CVR {id} {ver_list} {download_serve} ....\r
+ // NS: >>> USR {id} SSO I {user}\r
+ $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." I $user");\r
+ break;\r
+\r
+ case 'USR':\r
+ // already login for passport site, finish the login process now.\r
+ // NS: <<< USR {id} OK {user} {verify} 0\r
+ if ($this->authed) return true;\r
+ // max. 16 digits for password\r
+ if (strlen($password) > 16)\r
+ $password = substr($password, 0, 16);\r
+\r
+ $this->user = $user;\r
+ $this->password = $password;\r
+ // NS: <<< USR {id} SSO S {policy} {nonce}\r
+ @list(/* USR */, /* id */, /* SSO */, /* S */, $policy, $nonce) = @explode(' ', $data);\r
+\r
+ $this->passport_policy = $policy;\r
+ $aTickets = $this->get_passport_ticket();\r
+ if (!$aTickets || !is_array($aTickets)) {\r
+ // logout now\r
+ // NS: >>> OUT\r
+ $this->ns_writeln("OUT");\r
+ @fclose($this->NSfp);\r
+ $this->error = 'Passport authentication failed!';\r
+ return false;\r
+ }\r
+\r
+ $ticket = $aTickets['ticket'];\r
+ $secret = $aTickets['secret'];\r
+ $this->ticket = $aTickets;\r
+ $login_code = $this->generateLoginBLOB($secret, $nonce);\r
+\r
+ // NS: >>> USR {id} SSO S {ticket} {login_code}\r
+ $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." S $ticket $login_code");\r
+ $this->authed = true;\r
+ break;\r
+\r
+ case 'XFR':\r
+ // main login server will redirect to anther NS after USR command\r
+ // MSNP9\r
+ // NS: <<< XFR {id} NS {server} 0 {server}\r
+ // MSNP15\r
+ // NS: <<< XFR {id} NS {server} U D\r
+ @list(/* XFR */, /* id */, $Type, $server) = @explode(' ', $data);\r
+ if ($Type!='NS') break;\r
+ @list($ip, $port) = @explode(':', $server);\r
+ // this connection will close after XFR\r
+ @fclose($this->NSfp);\r
+\r
+ $this->NSfp = @fsockopen($ip, $port, $errno, $errstr, $this->timeout);\r
+ if (!$this->NSfp) {\r
+ $this->error = "Can't connect to $ip:$port, error => $errno, $errstr";\r
+ return false;\r
+ }\r
+\r
+ // MSNP9\r
+ // NS: >> VER {id} MSNP9 CVR0\r
+ // MSNP15\r
+ // NS: >>> VER {id} MSNP15 CVR0\r
+ $this->ns_writeln("VER $this->id ".self::PROTOCOL.' CVR0');\r
+ break;\r
+\r
+ case 'GCF':\r
+ // return some policy data after 'USR {id} SSO I {user}' command\r
+ // NS: <<< GCF 0 {size}\r
+ @list(/* GCF */, /* 0 */, $size) = @explode(' ', $data);\r
+ // we don't need the data, just read it and drop\r
+ if (is_numeric($size) && $size > 0)\r
+ $this->ns_readdata($size);\r
+ break;\r
+\r
+ default:\r
+ // we'll quit if got any error\r
+ if (is_numeric($code)) {\r
+ // logout now\r
+ // NS: >>> OUT\r
+ $this->ns_writeln("OUT");\r
+ @fclose($this->NSfp);\r
+ $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";\r
+ return false;\r
+ }\r
+ // unknown response from server, just ignore it\r
+ break;\r
+ }\r
+ }\r
+ // never goto here\r
+ }\r
+\r
+ /**\r
+ * Sign onto the NS server and retrieve the address book\r
+ *\r
+ * @return void\r
+ */\r
+ public function signon() {\r
+ /* FIXME Don't implement the signon as a loop or we could hang\r
+ * the queue handler! */\r
+ $this->debug_message('*** Trying to connect to MSN network');\r
+\r
+ // Remove any remaining switchboard sessions\r
+ $this->switchBoardSessions = array();\r
+ $this->switchBoardSessionLookup = array();\r
+\r
+ while (true) {\r
+ // Connect\r
+ if (!$this->connect($this->user, $this->password)) {\r
+ $this->signonFailure("!!! Could not connect to server: $this->error");\r
+ continue;\r
+ }\r
+\r
+ // Update contacts\r
+ if ($this->UpdateContacts() === false) {\r
+ $this->signonFailure('');\r
+ continue;\r
+ }\r
+\r
+ // Get membership lists\r
+ if (($this->aContactList = $this->getMembershipList()) === false) {\r
+ $this->signonFailure('!!! Get membership list failed');\r
+ continue;\r
+ }\r
+\r
+ if ($this->update_pending) {\r
+ if (is_array($this->aContactList)) {\r
+ $pending = 'Pending';\r
+ foreach ($this->aContactList as $u_domain => $aUserList) {\r
+ foreach ($aUserList as $u_name => $aNetworks) {\r
+ foreach ($aNetworks as $network => $aData) {\r
+ if (isset($aData[$pending])) {\r
+ // pending list\r
+ $cnt = 0;\r
+ foreach (array('Allow', 'Reverse') as $list) {\r
+ if (isset($aData[$list]))\r
+ $cnt++;\r
+ else {\r
+ if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {\r
+ $this->aContactList[$u_domain][$u_name][$network][$list] = false;\r
+ $cnt++;\r
+ }\r
+ }\r
+ }\r
+ if ($cnt >= 2) {\r
+ $id = $aData[$pending];\r
+ // we can delete it from pending now\r
+ if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending))\r
+ unset($this->aContactList[$u_domain][$u_name][$network][$pending]);\r
+ }\r
+ }\r
+ else {\r
+ // sync list\r
+ foreach (array('Allow', 'Reverse') as $list) {\r
+ if (!isset($aData[$list])) {\r
+ if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list))\r
+ $this->aContactList[$u_domain][$u_name][$network][$list] = false;\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ }\r
+ $n = 0;\r
+ $sList = '';\r
+ $len = 0;\r
+ if (is_array($this->aContactList)) {\r
+ foreach ($this->aContactList as $u_domain => $aUserList) {\r
+ $str = '<d n="'.$u_domain.'">';\r
+ $len += strlen($str);\r
+ if ($len > 7400) {\r
+ $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';\r
+ $n++;\r
+ $sList = '';\r
+ $len = strlen($str);\r
+ }\r
+ $sList .= $str;\r
+ foreach ($aUserList as $u_name => $aNetworks) {\r
+ foreach ($aNetworks as $network => $status) {\r
+ $str = '<c n="'.$u_name.'" l="3" t="'.$network.'" />';\r
+ $len += strlen($str);\r
+ // max: 7500, but <ml l="1"></d></ml> is 19,\r
+ // so we use 7475\r
+ if ($len > 7475) {\r
+ $sList .= '</d>';\r
+ $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';\r
+ $n++;\r
+ $sList = '<d n="'.$u_domain.'">'.$str;\r
+ $len = strlen($sList);\r
+ }\r
+ else\r
+ $sList .= $str;\r
+ }\r
+ }\r
+ $sList .= '</d>';\r
+ }\r
+ }\r
+ $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';\r
+ // NS: >>> BLP {id} BL\r
+ $this->ns_writeln("BLP $this->id BL");\r
+ foreach ($this->aADL as $str) {\r
+ $len = strlen($str);\r
+ // NS: >>> ADL {id} {size}\r
+ $this->ns_writeln("ADL $this->id $len");\r
+ $this->ns_writedata($str);\r
+ }\r
+ // NS: >>> PRP {id} MFN name\r
+ if ($this->alias == '') $this->alias = $user;\r
+ $aliasname = rawurlencode($this->alias);\r
+ $this->ns_writeln("PRP $this->id MFN $aliasname");\r
+ //設定個人大頭貼\r
+ //$MsnObj=$this->PhotoStckObj();\r
+ // NS: >>> CHG {id} {status} {clientid} {msnobj}\r
+ $this->ns_writeln("CHG $this->id NLN $this->clientid");\r
+ if ($this->PhotoStickerFile !== false)\r
+ $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));\r
+ // NS: >>> UUX {id} length\r
+ $str = '<Data><PSM>'.htmlspecialchars($this->psm).'</PSM><CurrentMedia></CurrentMedia><MachineGuid></MachineGuid></Data>';\r
+ $len = strlen($str);\r
+ $this->ns_writeln("UUX $this->id $len");\r
+ $this->ns_writedata($str);\r
+ if (!self::socketcheck($this->NSfp)) {\r
+ $this->debug_message('*** Connected, waiting for commands');\r
+ break;\r
+ } else {\r
+ $this->NSRetryWait($this->retry_wait);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Called if there is an error during signon\r
+ *\r
+ * @param string $message Error message to log\r
+ * @return void\r
+ */\r
+ private function signonFailure($message) {\r
+ if(!empty($message)) {\r
+ $this->debug_message($message);\r
+ }\r
+ $this->callHandler('ConnectFailed', $message);\r
+ $this->NSRetryWait($this->retry_wait);\r
+ }\r
+\r
+ /**\r
+ * Log out and close the NS connection\r
+ *\r
+ * @return void\r
+ */\r
+ private function nsLogout() {\r
+ if (is_resource($this->NSfp) && !feof($this->NSfp)) {\r
+ // logout now\r
+ // NS: >>> OUT\r
+ $this->ns_writeln("OUT");\r
+ fclose($this->NSfp);\r
+ $this->NSfp = false;\r
+ $this->debug_message("*** Logged out");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * NS and SB command handling methods\r
+ */\r
+\r
+ /**\r
+ * Read and handle incoming command from NS\r
+ *\r
+ * @return void\r
+ */\r
+ private function nsReceive() {\r
+ // Sign in again if not signed in or socket failed\r
+ if (!is_resource($this->NSfp) || self::socketcheck($this->NSfp)) {\r
+ $this->callHandler('Reconnect');\r
+ $this->NSRetryWait($this->retry_wait);\r
+ $this->signon();\r
+ return;\r
+ }\r
+\r
+ $data = $this->ns_readln();\r
+ if ($data === false) {\r
+ // There was no data / an error when reading from the socket so reconnect\r
+ $this->callHandler('Reconnect');\r
+ $this->NSRetryWait($this->retry_wait);\r
+ $this->signon();\r
+ return;\r
+ }\r
+\r
+ switch (substr($data, 0, 3)) {\r
+ case 'SBS':\r
+ // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us\r
+ // NS: <<< SBS 0 null\r
+ break;\r
+\r
+ case 'RFS':\r
+ // FIXME:\r
+ // NS: <<< RFS ???\r
+ // refresh ADL, so we re-send it again\r
+ if (is_array($this->aADL)) {\r
+ foreach ($this->aADL as $str) {\r
+ $len = strlen($str);\r
+ // NS: >>> ADL {id} {size}\r
+ $this->ns_writeln("ADL $this->id $len");\r
+ $this->ns_writedata($str);\r
+ }\r
+ }\r
+ break;\r
+\r
+ case 'LST':\r
+ // NS: <<< LST {email} {alias} 11 0\r
+ @list(/* LST */, $email) = @explode(' ', $data);\r
+ @list($u_name, $u_domain) = @explode('@', $email);\r
+ if (!isset($this->aContactList[$u_domain][$u_name][1])) {\r
+ $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow';\r
+ $this->debug_message("*** Added to contact list: $u_name@$u_domain");\r
+ }\r
+ break;\r
+\r
+ case 'ADL':\r
+ // randomly, we get ADL command, someone add us to their contact list for MSNP15\r
+ // NS: <<< ADL 0 {size}\r
+ @list(/* ADL */, /* 0 */, $size) = @explode(' ', $data);\r
+ if (is_numeric($size) && $size > 0) {\r
+ $data = $this->ns_readdata($size);\r
+ preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);\r
+ if (is_array($matches) && count($matches) > 0) {\r
+ $u_domain = $matches[1];\r
+ $u_name = $matches[2];\r
+ $network = $matches[4];\r
+ if (isset($this->aContactList[$u_domain][$u_name][$network]))\r
+ $this->debug_message("*** Someone (network: $network) added us to their list (but already in our list): $u_name@$u_domain");\r
+ else {\r
+ $re_login = false;\r
+ $cnt = 0;\r
+ foreach (array('Allow', 'Reverse') as $list) {\r
+ if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {\r
+ if ($re_login) {\r
+ $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");\r
+ continue;\r
+ }\r
+ $aTickets = $this->get_passport_ticket();\r
+ if (!$aTickets || !is_array($aTickets)) {\r
+ // failed to login? ignore it\r
+ $this->debug_message("*** Could not re-login, something wrong here");\r
+ $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");\r
+ continue;\r
+ }\r
+ $re_login = true;\r
+ $this->ticket = $aTickets;\r
+ $this->debug_message("**** Got new ticket, trying again");\r
+ if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {\r
+ $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");\r
+ continue;\r
+ }\r
+ }\r
+ $this->aContactList[$u_domain][$u_name][$network][$list] = false;\r
+ $cnt++;\r
+ }\r
+ $this->debug_message("*** Someone (network: $network) added us to their list: $u_name@$u_domain");\r
+ }\r
+ $str = '<ml l="1"><d n="'.$u_domain.'"><c n="'.$u_name.'" l="3" t="'.$network.'" /></d></ml>';\r
+ $len = strlen($str);\r
+\r
+ $this->callHandler('AddedToList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network));\r
+ }\r
+ else\r
+ $this->debug_message("*** Someone added us to their list: $data");\r
+ }\r
+ break;\r
+\r
+ case 'RML':\r
+ // randomly, we get RML command, someome remove us to their contact list for MSNP15\r
+ // NS: <<< RML 0 {size}\r
+ @list(/* RML */, /* 0 */, $size) = @explode(' ', $data);\r
+ if (is_numeric($size) && $size > 0) {\r
+ $data = $this->ns_readdata($size);\r
+ preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);\r
+ if (is_array($matches) && count($matches) > 0) {\r
+ $u_domain = $matches[1];\r
+ $u_name = $matches[2];\r
+ $network = $matches[4];\r
+ if (isset($this->aContactList[$u_domain][$u_name][$network])) {\r
+ $aData = $this->aContactList[$u_domain][$u_name][$network];\r
+\r
+ foreach ($aData as $list => $id)\r
+ $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list);\r
+\r
+ unset($this->aContactList[$u_domain][$u_name][$network]);\r
+ $this->debug_message("*** Someone (network: $network) removed us from their list: $u_name@$u_domain");\r
+ }\r
+ else\r
+ $this->debug_message("*** Someone (network: $network) removed us from their list (but not in our list): $u_name@$u_domain");\r
+\r
+ $this->callHandler('RemovedFromList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network));\r
+ }\r
+ else\r
+ $this->debug_message("*** Someone removed us from their list: $data");\r
+ }\r
+ break;\r
+\r
+ case 'MSG':\r
+ // randomly, we get MSG notification from server\r
+ // NS: <<< MSG Hotmail Hotmail {size}\r
+ @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size) = @explode(' ', $data);\r
+ if (is_numeric($size) && $size > 0) {\r
+ $data = $this->ns_readdata($size);\r
+ $aLines = @explode("\n", $data);\r
+ $header = true;\r
+ $ignore = false;\r
+ $maildata = '';\r
+ foreach ($aLines as $line) {\r
+ $line = rtrim($line);\r
+ if ($header) {\r
+ if ($line === '') {\r
+ $header = false;\r
+ continue;\r
+ }\r
+ if (strncasecmp($line, 'Content-Type:', 13) == 0) {\r
+ if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && strpos($line, 'text/x-msmsgsoimnotification') === false) {\r
+ // we just need text/x-msmsgsinitialmdatanotification\r
+ // or text/x-msmsgsoimnotification\r
+ $ignore = true;\r
+ break;\r
+ }\r
+ }\r
+ continue;\r
+ }\r
+ if (strncasecmp($line, 'Mail-Data:', 10) == 0) {\r
+ $maildata = trim(substr($line, 10));\r
+ break;\r
+ }\r
+ }\r
+ if ($ignore) {\r
+ $this->debug_message("*** Ignoring MSG for: $line");\r
+ break;\r
+ }\r
+ if ($maildata == '') {\r
+ $this->debug_message("*** Ignoring MSG not for OIM");\r
+ break;\r
+ }\r
+ $re_login = false;\r
+ if (strcasecmp($maildata, 'too-large') == 0) {\r
+ $this->debug_message("*** Large mail-data, need to get the data via SOAP");\r
+ $maildata = $this->getOIM_maildata();\r
+ if ($maildata === false) {\r
+ $this->debug_message("*** Could not get mail-data via SOAP");\r
+\r
+ // maybe we need to re-login again\r
+ $aTickets = $this->get_passport_ticket();\r
+ if (!$aTickets || !is_array($aTickets)) {\r
+ // failed to login? ignore it\r
+ $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM");\r
+ break;\r
+ }\r
+ $re_login = true;\r
+ $this->ticket = $aTickets;\r
+ $this->debug_message("*** Got new ticket, trying again");\r
+ $maildata = $this->getOIM_maildata();\r
+ if ($maildata === false) {\r
+ $this->debug_message("*** Could not get mail-data via SOAP, and re-login already attempted, ignoring this OIM");\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ // could be a lots of <M>...</M>, so we can't use preg_match here\r
+ $p = $maildata;\r
+ $aOIMs = array();\r
+ while (1) {\r
+ $start = strpos($p, '<M>');\r
+ $end = strpos($p, '</M>');\r
+ if ($start === false || $end === false || $start > $end) break;\r
+ $end += 4;\r
+ $sOIM = substr($p, $start, $end - $start);\r
+ $aOIMs[] = $sOIM;\r
+ $p = substr($p, $end);\r
+ }\r
+ if (count($aOIMs) == 0) {\r
+ $this->debug_message("*** Ignoring empty OIM");\r
+ break;\r
+ }\r
+ foreach ($aOIMs as $maildata) {\r
+ // T: 11 for MSN, 13 for Yahoo\r
+ // S: 6 for MSN, 7 for Yahoo\r
+ // RT: the datetime received by server\r
+ // RS: already read or not\r
+ // SZ: size of message\r
+ // E: sender\r
+ // I: msgid\r
+ // F: always 00000000-0000-0000-0000-000000000009\r
+ // N: sender alias\r
+ preg_match('#<T>(.*)</T>#', $maildata, $matches);\r
+ if (count($matches) == 0) {\r
+ $this->debug_message("*** Ignoring OIM maildata without <T>type</T>");\r
+ continue;\r
+ }\r
+ $oim_type = $matches[1];\r
+ if ($oim_type = 13)\r
+ $network = 32;\r
+ else\r
+ $network = 1;\r
+ preg_match('#<E>(.*)</E>#', $maildata, $matches);\r
+ if (count($matches) == 0) {\r
+ $this->debug_message("*** Ignoring OIM maildata without <E>sender</E>");\r
+ continue;\r
+ }\r
+ $oim_sender = $matches[1];\r
+ preg_match('#<I>(.*)</I>#', $maildata, $matches);\r
+ if (count($matches) == 0) {\r
+ $this->debug_message("*** Ignoring OIM maildata without <I>msgid</I>");\r
+ continue;\r
+ }\r
+ $oim_msgid = $matches[1];\r
+ preg_match('#<SZ>(.*)</SZ>#', $maildata, $matches);\r
+ $oim_size = (count($matches) == 0) ? 0 : $matches[1];\r
+ preg_match('#<RT>(.*)</RT>#', $maildata, $matches);\r
+ $oim_time = (count($matches) == 0) ? 0 : $matches[1];\r
+ $this->debug_message("*** OIM received from $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size");\r
+ $sMsg = $this->getOIM_message($oim_msgid);\r
+ if ($sMsg === false) {\r
+ $this->debug_message("*** Could not get OIM, msgid = $oim_msgid");\r
+ if ($re_login) {\r
+ $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM");\r
+ continue;\r
+ }\r
+ $aTickets = $this->get_passport_ticket();\r
+ if (!$aTickets || !is_array($aTickets)) {\r
+ // failed to login? ignore it\r
+ $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM");\r
+ continue;\r
+ }\r
+ $re_login = true;\r
+ $this->ticket = $aTickets;\r
+ $this->debug_message("*** get new ticket, try it again");\r
+ $sMsg = $this->getOIM_message($oim_msgid);\r
+ if ($sMsg === false) {\r
+ $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM");\r
+ continue;\r
+ }\r
+ }\r
+ $this->debug_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg");\r
+ $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true));\r
+ }\r
+ }\r
+ break;\r
+\r
+ case 'UBM':\r
+ // randomly, we get UBM, this is the message from other network, like Yahoo!\r
+ // NS: <<< UBM {email} $network $type {size}\r
+ @list(/* UBM */, $from_email, $network, $type, $size) = @explode(' ', $data);\r
+ if (is_numeric($size) && $size > 0) {\r
+ $data = $this->ns_readdata($size);\r
+ $aLines = @explode("\n", $data);\r
+ $header = true;\r
+ $ignore = false;\r
+ $sMsg = '';\r
+ foreach ($aLines as $line) {\r
+ $line = rtrim($line);\r
+ if ($header) {\r
+ if ($line === '') {\r
+ $header = false;\r
+ continue;\r
+ }\r
+ if (strncasecmp($line, 'TypingUser:', 11) == 0) {\r
+ $ignore = true;\r
+ break;\r
+ }\r
+ continue;\r
+ }\r
+ $aSubLines = @explode("\r", $line);\r
+ foreach ($aSubLines as $str) {\r
+ if ($sMsg !== '')\r
+ $sMsg .= "\n";\r
+ $sMsg .= $str;\r
+ }\r
+ }\r
+ if ($ignore) {\r
+ $this->debug_message("*** Ignoring message from $from_email: $line");\r
+ break;\r
+ }\r
+ $this->debug_message("*** MSG from $from_email (network: $network): $sMsg");\r
+ $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false));\r
+ }\r
+ break;\r
+\r
+ case 'UBX':\r
+ // randomly, we get UBX notification from server\r
+ // NS: <<< UBX email {network} {size}\r
+ @list(/* UBX */, /* email */, /* network */, $size) = @explode(' ', $data);\r
+ // we don't need the notification data, so just ignore it\r
+ if (is_numeric($size) && $size > 0)\r
+ $this->ns_readdata($size);\r
+ break;\r
+\r
+ case 'CHL':\r
+ // randomly, we'll get challenge from server\r
+ // NS: <<< CHL 0 {code}\r
+ @list(/* CHL */, /* 0 */, $chl_code) = @explode(' ', $data);\r
+ $fingerprint = $this->getChallenge($chl_code);\r
+ // NS: >>> QRY {id} {product_id} 32\r
+ // NS: >>> fingerprint\r
+ $this->ns_writeln("QRY $this->id ".self::PROD_ID.' 32');\r
+ $this->ns_writedata($fingerprint);\r
+ $this->ns_writeln("CHG $this->id NLN $this->clientid");\r
+ if ($this->PhotoStickerFile !== false)\r
+ $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));\r
+ break;\r
+ case 'CHG':\r
+ // NS: <<< CHG {id} {status} {code}\r
+ // ignore it\r
+ // change our status to online first\r
+ break;\r
+\r
+ case 'XFR':\r
+ // sometimes, NS will redirect to another NS\r
+ // MSNP9\r
+ // NS: <<< XFR {id} NS {server} 0 {server}\r
+ // MSNP15\r
+ // NS: <<< XFR {id} NS {server} U D\r
+ // for normal switchboard XFR\r
+ // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0\r
+ @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code) = @explode(' ', $data);\r
+ @list($ip, $port) = @explode(':', $server);\r
+ if ($server_type != 'SB') {\r
+ // maybe exit?\r
+ // this connection will close after XFR\r
+ $this->nsLogout();\r
+ continue;\r
+ }\r
+\r
+ $this->debug_message("NS: <<< XFR SB");\r
+ $session = array_shift($this->waitingForXFR);\r
+ $this->connectToSBSession('Active', $ip, $port, $session['to'], array('cki' => $cki_code));\r
+ break;\r
+ case 'QNG':\r
+ // NS: <<< QNG {time}\r
+ @list(/* QNG */, $ping_wait) = @explode(' ', $data);\r
+ $this->callHandler('Pong', $ping_wait);\r
+ break;\r
+\r
+ case 'RNG':\r
+ if ($this->PhotoStickerFile !== false)\r
+ $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));\r
+ else\r
+ $this->ns_writeln("CHG $this->id NLN $this->clientid");\r
+ // someone is trying to talk to us\r
+ // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0\r
+ $this->debug_message("NS: <<< RNG $data");\r
+ @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name) = @explode(' ', $data);\r
+ @list($sb_ip, $sb_port) = @explode(':', $server);\r
+ $this->debug_message("*** RING from $email, $sb_ip:$sb_port");\r
+ $this->addContact($email, 1, $email, true);\r
+ $this->connectToSBSession('Passive', $sb_ip, $sb_port, $email, array('sid' => $sid, 'ticket' => $ticket));\r
+ break;\r
+\r
+ case 'NLN':\r
+ // NS: <<< NLN {status} {email} {networkid} {nickname} {clientid} {dpobj}\r
+ @list(/* NLN */, $status, $email, $network, $nickname) = @explode(' ', $data);\r
+ $this->callHandler('StatusChange', array('screenname' => $email, 'status' => $status, 'network' => $network, 'nickname' => $nickname));\r
+ break;\r
+\r
+ case 'OUT':\r
+ // force logout from NS\r
+ // NS: <<< OUT xxx\r
+ $this->debug_message("*** LOGOUT from NS");\r
+ return $this->nsLogout();\r
+\r
+ default:\r
+ $code = substr($data,0,3);\r
+ if (is_numeric($code)) {\r
+ $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";\r
+ $this->debug_message("*** NS: $this->error");\r
+\r
+ return $this->nsLogout();\r
+ }\r
+ break;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Read and handle incoming command/message from\r
+ * a switchboard session socket\r
+ */\r
+ private function sbReceive($socket) {\r
+ $intsocket = (int) $socket;\r
+ $session = &$this->switchBoardSessions[$intsocket];\r
+\r
+ if (feof($socket)) {\r
+ // Unset session lookup value\r
+ unset($this->switchBoardSessionLookup[$session['to']]);\r
+\r
+ // Unset session itself\r
+ unset($this->switchBoardSessions[$intsocket]);\r
+ return;\r
+ }\r
+\r
+ $id = &$session['id'];\r
+\r
+ $data = $this->sb_readln($socket);\r
+ $code = substr($data, 0, 3);\r
+ switch($code) {\r
+ case 'IRO':\r
+ // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid}\r
+ @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data);\r
+ $this->debug_message("*** $email joined session");\r
+ if ($email == $session['to']) {\r
+ $session['joined'] = true;\r
+ $this->callHandler('SessionReady', array('to' => $email));\r
+ }\r
+ break;\r
+ case 'BYE':\r
+ $this->debug_message("*** Quit for BYE");\r
+ $this->endSBSession($socket);\r
+ break;\r
+ case 'USR':\r
+ // SB: <<< USR {id} OK {user} {alias}\r
+ // we don't need the data, just ignore it\r
+ // request user to join this switchboard\r
+ // SB: >>> CAL {id} {user}\r
+ $this->sb_writeln($socket, $id, "CAL $id ".$session['to']);\r
+ break;\r
+ case 'CAL':\r
+ // SB: <<< CAL {id} RINGING {?}\r
+ // we don't need this, just ignore, and wait for other response\r
+ $session['id']++;\r
+ break;\r
+ case 'JOI':\r
+ // SB: <<< JOI {user} {alias} {clientid?}\r
+ // someone join us\r
+ @list(/* JOI */, $email) = @explode(' ', $data);\r
+ if ($email == $session['to']) {\r
+ $session['joined'] = true;\r
+ $this->callHandler('SessionReady', array('to' => $email));\r
+ }\r
+ break;\r
+ case 'MSG':\r
+ // SB: <<< MSG {email} {alias} {len}\r
+ @list(/* MSG */, $from_email, /* alias */, $len) = @explode(' ', $data);\r
+ $len = trim($len);\r
+ $data = $this->sb_readdata($socket, $len);\r
+ $aLines = @explode("\n", $data);\r
+ $header = true;\r
+ $ignore = false;\r
+ $is_p2p = false;\r
+ $sMsg = '';\r
+ foreach ($aLines as $line) {\r
+ $line = rtrim($line);\r
+ if ($header) {\r
+ if ($line === '') {\r
+ $header = false;\r
+ continue;\r
+ }\r
+ if (strncasecmp($line, 'TypingUser:', 11) == 0) {\r
+ // typing notification, just ignore\r
+ $ignore = true;\r
+ break;\r
+ }\r
+ if (strncasecmp($line, 'Chunk:', 6) == 0) {\r
+ // we don't handle any split message, just ignore\r
+ $ignore = true;\r
+ break;\r
+ }\r
+ if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) {\r
+ // p2p message, ignore it, but we need to send acknowledgement for it...\r
+ $is_p2p = true;\r
+ $p = strstr($data, "\n\n");\r
+ $sMsg = '';\r
+ if ($p === false) {\r
+ $p = strstr($data, "\r\n\r\n");\r
+ if ($p !== false)\r
+ $sMsg = substr($p, 4);\r
+ }\r
+ else\r
+ $sMsg = substr($p, 2);\r
+ break;\r
+ }\r
+ if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) {\r
+ // ignore all application/x-... message\r
+ // for example:\r
+ // application/x-ms-ink => ink message\r
+ $ignore = true;\r
+ break;\r
+ }\r
+ if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) {\r
+ // ignore all text/x-... message\r
+ // for example:\r
+ // text/x-msnmsgr-datacast => nudge, voice clip....\r
+ // text/x-mms-animemoticon => customized animemotion word\r
+ $ignore = true;\r
+ break;\r
+ }\r
+ continue;\r
+ }\r
+ if ($sMsg !== '')\r
+ $sMsg .= "\n";\r
+ $sMsg .= $line;\r
+ }\r
+ if ($ignore) {\r
+ $this->debug_message("*** Ignoring SB data from $from_email: $line");\r
+ break;\r
+ }\r
+ if ($is_p2p) {\r
+ // we will ignore any p2p message after sending acknowledgement\r
+ $ignore = true;\r
+ $len = strlen($sMsg);\r
+ $this->debug_message("*** p2p message from $from_email, size $len");\r
+ // header = 48 bytes\r
+ // content >= 0 bytes\r
+ // footer = 4 bytes\r
+ // so it need to >= 52 bytes\r
+ /*if ($len < 52) {\r
+ $this->debug_message("*** p2p: size error, less than 52!");\r
+ break;\r
+ }*/\r
+ $aDwords = @unpack("V12dword", $sMsg);\r
+ if (!is_array($aDwords)) {\r
+ $this->debug_message("*** p2p: header unpack error!");\r
+ break;\r
+ }\r
+ $this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg));\r
+ $hdr_SessionID = $aDwords['dword1'];\r
+ $hdr_Identifier = $aDwords['dword2'];\r
+ $hdr_DataOffsetLow = $aDwords['dword3'];\r
+ $hdr_DataOffsetHigh = $aDwords['dword4'];\r
+ $hdr_TotalDataSizeLow = $aDwords['dword5'];\r
+ $hdr_TotalDataSizeHigh = $aDwords['dword6'];\r
+ $hdr_MessageLength = $aDwords['dword7'];\r
+ $hdr_Flag = $aDwords['dword8'];\r
+ $hdr_AckID = $aDwords['dword9'];\r
+ $hdr_AckUID = $aDwords['dword10'];\r
+ $hdr_AckSizeLow = $aDwords['dword11'];\r
+ $hdr_AckSizeHigh = $aDwords['dword12'];\r
+ $this->debug_message("*** p2p: header SessionID = $hdr_SessionID");\r
+ $this->debug_message("*** p2p: header Inentifier = $hdr_Identifier");\r
+ $this->debug_message("*** p2p: header Data Offset Low = $hdr_DataOffsetLow");\r
+ $this->debug_message("*** p2p: header Data Offset High = $hdr_DataOffsetHigh");\r
+ $this->debug_message("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow");\r
+ $this->debug_message("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh");\r
+ $this->debug_message("*** p2p: header MessageLength = $hdr_MessageLength");\r
+ $this->debug_message("*** p2p: header Flag = $hdr_Flag");\r
+ $this->debug_message("*** p2p: header AckID = $hdr_AckID");\r
+ $this->debug_message("*** p2p: header AckUID = $hdr_AckUID");\r
+ $this->debug_message("*** p2p: header AckSize Low = $hdr_AckSizeLow");\r
+ $this->debug_message("*** p2p: header AckSize High = $hdr_AckSizeHigh");\r
+ if ($hdr_Flag == 2) {\r
+ //This is an ACK from SB ignore....\r
+ $this->debug_message("*** p2p: //This is an ACK from SB ignore....:\n");\r
+ break;\r
+ }\r
+ $MsgBody = $this->linetoArray(substr($sMsg, 48, -4));\r
+ $this->debug_message("*** p2p: body".print_r($MsgBody, true));\r
+ if (($MsgBody['EUF-GUID']=='{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}')&&($PictureFilePath=$this->GetPictureFilePath($MsgBody['Context']))) {\r
+ while (true) {\r
+ if ($this->sb_readln($socket) === false) break;\r
+ }\r
+ $this->debug_message("*** p2p: Inv hdr:\n".$this->dump_binary(substr($sMsg, 0, 48)));\r
+ preg_match('/{([0-9A-F\-]*)}/i', $MsgBody['Via'], $Matches);\r
+ $BranchGUID = $Matches[1];\r
+ //it's an invite to send a display picture.\r
+ $new_id = ~$hdr_Identifier;\r
+ $hdr = pack(\r
+ "LLLLLLLLLLLL", $hdr_SessionID,\r
+ $new_id,\r
+ 0, 0,\r
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
+ 0,\r
+ 2,\r
+ $hdr_Identifier,\r
+ $hdr_AckID,\r
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh\r
+ );\r
+ $footer = pack("L", 0);\r
+ $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";\r
+ $len = strlen($message);\r
+ $this->sb_writeln($socket, $id, "MSG $id D $len");\r
+ $this->sb_writedata($socket, $message);\r
+ $this->debug_message("*** p2p: send display picture acknowledgement for $hdr_SessionID");\r
+ $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message));\r
+ $this->sb_readln($socket); // Read ACK;\r
+ $this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr));\r
+ $new_id -= 3;\r
+ //Send 200 OK message\r
+ $MessageContent="SessionID: ".$MsgBody['SessionID']."\r\n\r\n".pack("C", 0);\r
+ $MessagePayload=\r
+ "MSNSLP/1.0 200 OK\r\n".\r
+ "To: <msnmsgr:".$from_email.">\r\n".\r
+ "From: <msnmsgr:".$this->user.">\r\n".\r
+ "Via: ".$MsgBody['Via']."\r\n".\r
+ "CSeq: ".($MsgBody['CSeq']+1)."\r\n".\r
+ "Call-ID: ".$MsgBody['Call-ID']."\r\n".\r
+ "Max-Forwards: 0\r\n".\r
+ "Content-Type: application/x-msnmsgr-sessionreqbody\r\n".\r
+ "Content-Length: ".strlen($MessageContent)."\r\n\r\n".\r
+ $MessageContent;\r
+ $hdr_TotalDataSizeLow=strlen($MessagePayload);\r
+ $hdr_TotalDataSizeHigh=0;\r
+ $hdr = pack(\r
+ "LLLLLLLLLLLL", $hdr_SessionID,\r
+ $new_id,\r
+ 0, 0,\r
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
+ strlen($MessagePayload),\r
+ 0,\r
+ rand(),\r
+ 0,\r
+ 0, 0\r
+ );\r
+\r
+ $message =\r
+ "MIME-Version: 1.0\r\n".\r
+ "Content-Type: application/x-msnmsgrp2p\r\n".\r
+ "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";\r
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
+ $this->sb_writedata($socket, $message);\r
+ $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message));\r
+ $this->sb_readln($socket); // Read ACK;\r
+\r
+ $this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr));\r
+ // send data preparation message\r
+ // send 4 null bytes as data\r
+ $hdr_TotalDataSizeLow = 4;\r
+ $hdr_TotalDataSizeHigh = 0 ;\r
+ $new_id++;\r
+ $hdr = pack(\r
+ "LLLLLLLLLLLL",\r
+ $MsgBody['SessionID'],\r
+ $new_id,\r
+ 0, 0,\r
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
+ $hdr_TotalDataSizeLow,\r
+ 0,\r
+ rand(),\r
+ 0,\r
+ 0, 0\r
+ );\r
+ $message =\r
+ "MIME-Version: 1.0\r\n".\r
+ "Content-Type: application/x-msnmsgrp2p\r\n".\r
+ "P2P-Dest: $from_email\r\n\r\n$hdr".pack('L', 0)."$footer";\r
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
+ $this->sb_writedata($socket, $message);\r
+ $this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message));\r
+ $this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr));\r
+ $this->sb_readln($socket); // Read ACK;\r
+\r
+ // send Data Content..\r
+ $footer=pack('N',1);\r
+ $new_id++;\r
+ $FileSize=filesize($PictureFilePath);\r
+ if ($hTitle=fopen($PictureFilePath,'rb')) {\r
+ $Offset = 0;\r
+ //$new_id++;\r
+ while (!feof($hTitle)) {\r
+ $FileContent = fread($hTitle, 1024);\r
+ $FileContentSize = strlen($FileContent);\r
+ $hdr = pack(\r
+ "LLLLLLLLLLLL",\r
+ $MsgBody['SessionID'],\r
+ $new_id,\r
+ $Offset, 0,\r
+ $FileSize, 0,\r
+ $FileContentSize,\r
+ 0x20,\r
+ rand(),\r
+ 0,\r
+ 0, 0\r
+ );\r
+ $message =\r
+ "MIME-Version: 1.0\r\n".\r
+ "Content-Type: application/x-msnmsgrp2p\r\n".\r
+ "P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer";\r
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
+ $this->sb_writedata($socket, $message);\r
+ $this->debug_message("*** p2p: dump send Data Content message $Offset / $FileSize :\n".$this->dump_binary($message));\r
+ $this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr));\r
+ //$this->SB_readln($socket);//Read ACK;\r
+ $Offset += $FileContentSize;\r
+ }\r
+ }\r
+ //Send Bye\r
+ /*\r
+ $MessageContent="\r\n".pack("C", 0);\r
+ $MessagePayload=\r
+ "BYE MSNMSGR:MSNSLP/1.0\r\n".\r
+ "To: <msnmsgr:$from_email>\r\n".\r
+ "From: <msnmsgr:".$this->user.">\r\n".\r
+ "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n".\r
+ "CSeq: 0\r\n".\r
+ "Call-ID: ".$MsgBody['Call-ID']."\r\n".\r
+ "Max-Forwards: 0\r\n".\r
+ "Content-Type: application/x-msnmsgr-sessionclosebody\r\n".\r
+ "Content-Length: ".strlen($MessageContent)."\r\n\r\n".$MessageContent;\r
+ $footer=pack('N',0);\r
+ $hdr_TotalDataSizeLow=strlen($MessagePayload);\r
+ $hdr_TotalDataSizeHigh=0;\r
+ $new_id++;\r
+ $hdr = pack("LLLLLLLLLLLL",\r
+ 0,\r
+ $new_id,\r
+ 0, 0,\r
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
+ 0,\r
+ 0,\r
+ rand(),\r
+ 0,\r
+ 0,0);\r
+ $message =\r
+ "MIME-Version: 1.0\r\n".\r
+ "Content-Type: application/x-msnmsgrp2p\r\n".\r
+ "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";\r
+ $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
+ $id++;\r
+ $this->sb_writedata($socket, $message);\r
+ $this->debug_message("*** p2p: dump send BYE message :\n".$this->dump_binary($message));\r
+ */\r
+ break;\r
+ }\r
+ //TODO:\r
+ //if ($hdr_Flag == 2) {\r
+ // just send ACK...\r
+ // $this->sb_writeln($socket, $id, "ACK $id");\r
+ // break;\r
+ //}\r
+ if ($hdr_SessionID == 4) {\r
+ // ignore?\r
+ $this->debug_message("*** p2p: ignore flag 4");\r
+ break;\r
+ }\r
+ $finished = false;\r
+ if ($hdr_TotalDataSizeHigh == 0) {\r
+ // only 32 bites size\r
+ if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow)\r
+ $finished = true;\r
+ }\r
+ else {\r
+ // we won't accept any file transfer\r
+ // so I think we won't get any message size need to use 64 bits\r
+ // 64 bits size here, can't count directly...\r
+ $totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10);\r
+ $dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10);\r
+ $messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10);\r
+ $now_size = bcadd($dataoffset, $messagelength);\r
+ if (bccomp($now_size, $totalsize) >= 0)\r
+ $finished = true;\r
+ }\r
+ if (!$finished) {\r
+ // ignore not finished split packet\r
+ $this->debug_message("*** p2p: ignore split packet, not finished");\r
+ break;\r
+ }\r
+ //$new_id = ~$hdr_Identifier;\r
+ /*\r
+ $new_id++;\r
+ $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID,\r
+ $new_id,\r
+ 0, 0,\r
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
+ 0,\r
+ 2,\r
+ $hdr_Identifier,\r
+ $hdr_AckID,\r
+ $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh);\r
+ $footer = pack("L", 0);\r
+ $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";\r
+ $len = strlen($message);\r
+ $this->sb_writeln($socket, $id, "MSG $id D $len");\r
+ $id++;\r
+ $this->sb_writedata($socket, $message);\r
+ $this->debug_message("*** p2p: send acknowledgement for $hdr_SessionID");\r
+ $this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer));\r
+ */\r
+ break;\r
+ }\r
+ $this->debug_message("*** MSG from $from_email: $sMsg");\r
+ $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => 1, 'offline' => false));\r
+ break;\r
+ case '217':\r
+ $this->debug_message('*** User '.$session['to'].' is offline. Trying OIM.');\r
+ $session['offline'] = true;\r
+ break;\r
+ default:\r
+ if (is_numeric($code)) {\r
+ $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";\r
+ $this->debug_message("*** SB: $this->error");\r
+ }\r
+ break;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Checks for new data and calls appropriate methods\r
+ *\r
+ * This method is usually called in an infinite loop to keep checking for new data\r
+ *\r
+ * @return void\r
+ */\r
+ public function receive() {\r
+ // First, get an array of sockets that have data that is ready to be read\r
+ $ready = array();\r
+ $ready = $this->getSockets();\r
+ $numrdy = stream_select($ready, $w = NULL, $x = NULL, NULL);\r
+\r
+ // Now that we've waited for something, go through the $ready\r
+ // array and read appropriately\r
+\r
+ foreach ($ready as $socket) {\r
+ if ($socket == $this->NSfp) {\r
+ $this->nsReceive();\r
+ } else {\r
+ $this->sbReceive($socket);\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Switchboard related methods\r
+ */\r
+\r
+ /**\r
+ * Send a request for a switchboard session\r
+ *\r
+ * @param string $to Target email for switchboard session\r
+ */\r
+ private function reqSBSession($to) {\r
+ $this->debug_message("*** Request SB for $to");\r
+ $this->ns_writeln("XFR $this->id SB");\r
+\r
+ // Add to the queue of those waiting for a switchboard session reponse\r
+ $this->switchBoardSessions[$to] = array(\r
+ 'to' => $to,\r
+ 'socket' => NULL,\r
+ 'id' => 1,\r
+ 'joined' => false,\r
+ 'offline' => false,\r
+ 'XFRReqTime' => time()\r
+ );\r
+ $this->waitingForXFR[$to] = &$this->switchBoardSessions[$to];\r
+ }\r
+\r
+ /**\r
+ * Following an XFR or RNG, connect to the switchboard session\r
+ *\r
+ * @param string $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case of RNG)\r
+ * @param string $ip IP of Switchboard\r
+ * @param integer $port Port of Switchboard\r
+ * @param string $to User on other end of Switchboard\r
+ * @param array $param Array of parameters - 'cki', 'ticket', 'sid'\r
+ * @return boolean true if successful\r
+ */\r
+ private function connectToSBSession($mode, $ip, $port, $to, $param) {\r
+ $this->debug_message("*** SB: Trying to connect to switchboard server $ip:$port");\r
+\r
+ $socket = @fsockopen($ip, $port, $errno, $errstr, $this->timeout);\r
+ if (!$socket) {\r
+ $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr");\r
+ return false;\r
+ }\r
+\r
+ // Store the socket in the lookup array\r
+ $this->switchBoardSessionLookup[$to] = $socket;\r
+\r
+ // Store the socket in the sessions array\r
+ $this->switchBoardSessions[$to] = array(\r
+ 'to' => $to,\r
+ 'socket' => $socket,\r
+ 'id' => 1,\r
+ 'joined' => false,\r
+ 'offline' => false,\r
+ 'XFRReqTime' => time()\r
+ );\r
+\r
+ // Change the index of the session to the socket\r
+ $intsocket = (int) $socket;\r
+ $this->switchBoardSessions[$intsocket] = $this->switchBoardSessions[$to];\r
+ unset($this->switchBoardSessions[$to]);\r
+\r
+ $id = &$this->switchBoardSessions[$intsocket]['id'];\r
+\r
+ if ($mode == 'Active') {\r
+ $cki_code = $param['cki'];\r
+\r
+ // SB: >>> USR {id} {user} {cki}\r
+ $this->sb_writeln($socket, $id, "USR $id $this->user $cki_code");\r
+ } else {\r
+ // Passive\r
+ $ticket = $param['ticket'];\r
+ $sid = $param['sid'];\r
+\r
+ // SB: >>> ANS {id} {user} {ticket} {session_id}\r
+ $this->sb_writeln($socket, $id, "ANS $id $this->user $ticket $sid");\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Called when we want to end a switchboard session\r
+ * or a switchboard session ends\r
+ *\r
+ * @param resource $socket Socket\r
+ * @param boolean $killsession Whether to delete the session\r
+ * @return void\r
+ */\r
+ private function endSBSession($socket) {\r
+ if (!self::socketcheck($socket)) {\r
+ $this->sb_writeln($socket, $fake = 0, 'OUT');\r
+ }\r
+ @fclose($socket);\r
+\r
+ // Unset session lookup value\r
+ $intsocket = (int) $socket;\r
+ unset($this->switchBoardSessionLookup[$this->switchBoardSessions[$intsocket]['to']]);\r
+\r
+ // Unset session itself\r
+ unset($this->switchBoardSessions[$intsocket]);\r
+ }\r
+\r
+ /**\r
+ * Send a message via an existing SB session\r
+ *\r
+ * @param string $to Recipient for message\r
+ * @param string $message Message\r
+ * @return boolean true on success\r
+ */\r
+ private function sendMessageViaSB($to, $message) {\r
+ $socket = $this->switchBoardSessionLookup[$to];\r
+ if (self::socketcheck($socket)) {\r
+ return false;\r
+ }\r
+\r
+ $id = &$this->switchBoardSessions[(int) $socket]['id'];\r
+\r
+ $aMessage = $this->getMessage($message);\r
+ // CheckEmotion...\r
+ $MsnObjDefine = $this->GetMsnObjDefine($aMessage);\r
+ if ($MsnObjDefine !== '') {\r
+ $SendString = "MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine";\r
+ $len = strlen($SendString);\r
+\r
+ if ($this->sb_writeln($socket, $id, "MSG $id N $len") === false ||\r
+ $this->sb_writedata($socket, $SendString) === false) {\r
+ $this->endSBSession($socket);\r
+ return false;\r
+ }\r
+ }\r
+ $len = strlen($aMessage);\r
+\r
+ if ($this->sb_writeln($socket, $id, "MSG $id N $len") === false ||\r
+ $this->sb_writedata($socket, $aMessage) === false) {\r
+ $this->endSBSession($socket);\r
+ return false;\r
+ }\r
+\r
+ // Don't close the SB session, we might as well leave it open\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Send a message to a user on another network\r
+ *\r
+ * @param string $to Intended recipient\r
+ * @param string $message Message\r
+ * @param integer $network Network\r
+ * @return void\r
+ */\r
+ private function sendOtherNetworkMessage($to, $message, $network) {\r
+ $message = $this->getMessage($message, $network);\r
+ $len = strlen($message);\r
+ if ($this->ns_writeln("UUM $this->id $to $network 1 $len") === false ||\r
+ $this->ns_writedata($Message) === false) {\r
+ return false;\r
+ }\r
+ $this->debug_message("*** Sent to $to (network: $network):\n$Message");\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * Send a message\r
+ *\r
+ * @param string $to To address in form user@host.com(@network)\r
+ * where network is 1 for MSN, 32 for Yahoo\r
+ * and 'Offline' for offline messages\r
+ * @param string $message Message\r
+ * @param boolean &$waitForSession Boolean passed by reference,\r
+ * if set to true on return, message\r
+ * did not fail to send but is\r
+ * waiting for a valid session\r
+ *\r
+ * @return boolean true on success\r
+ */\r
+ public function sendMessage($to, $message, &$waitForSession) {\r
+ if ($message != '') {\r
+ $toParts = explode('@', $to);\r
+ if(count($toParts) < 3) {\r
+ list($name, $host) = $toParts;\r
+ $network = 1;\r
+ } else {\r
+ list($name, $host, $network) = $toParts;\r
+ }\r
+\r
+ $recipient = $name.'@'.$host;\r
+\r
+ if ($network === 1) {\r
+ if (!isset($this->switchBoardSessionLookup[$recipient])) {\r
+ if (!isset($this->switchBoardSessions[$recipient]) || time() - $this->switchBoardSessions[$recipient]['XFRReqTime'] > $this->XFRReqTimeout) {\r
+ $this->debug_message("*** No existing SB session or request has timed out");\r
+ $this->reqSBSession($recipient);\r
+ }\r
+\r
+ $waitForSession = true;\r
+ return false;\r
+ } else {\r
+ $socket = $this->switchBoardSessionLookup[$recipient];\r
+ $intsocket = (int) $socket;\r
+ if ($this->switchBoardSessions[$intsocket]['offline']) {\r
+ $this->debug_message("*** Contact ($recipient) offline, sending OIM");\r
+ $this->endSBSession($socket);\r
+ $waitForSession = false;\r
+ return $this->sendMessage($recipient.'@Offline', $message);\r
+ } else {\r
+ if ($this->switchBoardSessions[$intsocket]['joined'] !== true) {\r
+ $this->debug_message("*** Recipient has not joined session, returning false");\r
+ $waitForSession = true;\r
+ return false;\r
+ }\r
+\r
+ $this->debug_message("*** Attempting to send message to $recipient using existing SB session");\r
+\r
+ if ($this->sendMessageViaSB($recipient, $message)) {\r
+ $this->debug_message('*** Message sent successfully');\r
+ return true;\r
+ }\r
+\r
+ $waitForSession = false;\r
+ return false;\r
+ }\r
+ }\r
+ } elseif ($network == 'Offline') {\r
+ //Send OIM\r
+ //FIXME: 修正Send OIM\r
+ $lockkey = '';\r
+ $re_login = false;\r
+ for ($i = 0; $i < $this->oim_try; $i++) {\r
+ if (($oim_result = $this->sendOIM($recipient, $message, $lockkey)) === true) break;\r
+ if (is_array($oim_result) && $oim_result['challenge'] !== false) {\r
+ // need challenge lockkey\r
+ $this->debug_message("*** Need challenge code for ".$oim_result['challenge']);\r
+ $lockkey = $this->getChallenge($oim_result['challenge']);\r
+ continue;\r
+ }\r
+ if ($oim_result === false || $oim_result['auth_policy'] !== false) {\r
+ if ($re_login) {\r
+ $this->debug_message("*** Can't send OIM, but we already re-logged-in again, so returning false");\r
+ return false;\r
+ }\r
+ $this->debug_message("*** Can't send OIM, maybe ticket expired, trying to login again");\r
+\r
+ // Maybe we need to re-login again\r
+ if (!$this->get_passport_ticket()) {\r
+ $this->debug_message("*** Can't re-login, something went wrong here, returning false");\r
+ return false;\r
+ }\r
+ $this->debug_message("*** Getting new ticket and trying again");\r
+ continue;\r
+ }\r
+ }\r
+ return true;\r
+ } else {\r
+ // Other network\r
+ return $this->sendOtherNetworkMessage($recipient, $message, $network);\r
+ }\r
+ }\r
+ return true;\r
+ }\r
+\r
+ /**\r
+ * OIM methods\r
+ */\r
+\r
+ /**\r
+ * Get OIM mail data\r
+ *\r
+ * @return string mail data or false on failure\r
+ */\r
+ function getOIM_maildata() {\r
+ preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);\r
+ if (count($matches) == 0) {\r
+ $this->debug_message('*** No web ticket?');\r
+ return false;\r
+ }\r
+ $t = htmlspecialchars($matches[1]);\r
+ $p = htmlspecialchars($matches[2]);\r
+ $XML = '<?xml version="1.0" encoding="utf-8"?>\r
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\r
+<soap:Header>\r
+ <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
+ <t>'.$t.'</t>\r
+ <p>'.$p.'</p>\r
+ </PassportCookie>\r
+</soap:Header>\r
+<soap:Body>\r
+ <GetMetadata xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi" />\r
+</soap:Body>\r
+</soap:Envelope>';\r
+\r
+ $header_array = array(\r
+ 'SOAPAction: '.self::OIM_MAILDATA_SOAP,\r
+ 'Content-Type: text/xml; charset=utf-8',\r
+ 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'\r
+ );\r
+\r
+ $this->debug_message('*** URL: '.self::OIM_MAILDATA_URL);\r
+ $this->debug_message("*** Sending SOAP:\n$XML");\r
+ $curl = curl_init();\r
+ curl_setopt($curl, CURLOPT_URL, self::OIM_MAILDATA_URL);\r
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
+ curl_setopt($curl, CURLOPT_POST, 1);\r
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
+ $data = curl_exec($curl);\r
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
+ curl_close($curl);\r
+ $this->debug_message("*** Get Result:\n$data");\r
+\r
+ if ($http_code != 200) {\r
+ $this->debug_message("*** Could not get OIM maildata! http code: $http_code");\r
+ return false;\r
+ }\r
+\r
+ // <GetMetadataResponse xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">See #XML_Data</GetMetadataResponse>\r
+ preg_match('#<GetMetadataResponse([^>]*)>(.*)</GetMetadataResponse>#', $data, $matches);\r
+ if (count($matches) == 0) {\r
+ $this->debug_message('*** Could not get OIM maildata');\r
+ return false;\r
+ }\r
+ return $matches[2];\r
+ }\r
+\r
+ /**\r
+ * Fetch OIM message with given id\r
+ *\r
+ * @param string $msgid\r
+ * @return string Message or false on failure\r
+ */\r
+ function getOIM_message($msgid) {\r
+ preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);\r
+ if (count($matches) == 0) {\r
+ $this->debug_message('*** No web ticket?');\r
+ return false;\r
+ }\r
+ $t = htmlspecialchars($matches[1]);\r
+ $p = htmlspecialchars($matches[2]);\r
+\r
+ // read OIM\r
+ $XML = '<?xml version="1.0" encoding="utf-8"?>\r
+<soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
+ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\r
+<soap:Header>\r
+ <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
+ <t>'.$t.'</t>\r
+ <p>'.$p.'</p>\r
+ </PassportCookie>\r
+</soap:Header>\r
+<soap:Body>\r
+ <GetMessage xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
+ <messageId>'.$msgid.'</messageId>\r
+ <alsoMarkAsRead>false</alsoMarkAsRead>\r
+ </GetMessage>\r
+</soap:Body>\r
+</soap:Envelope>';\r
+\r
+ $header_array = array(\r
+ 'SOAPAction: '.self::OIM_READ_SOAP,\r
+ 'Content-Type: text/xml; charset=utf-8',\r
+ 'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'\r
+ );\r
+\r
+ $this->debug_message('*** URL: '.self::OIM_READ_URL);\r
+ $this->debug_message("*** Sending SOAP:\n$XML");\r
+ $curl = curl_init();\r
+ curl_setopt($curl, CURLOPT_URL, self::OIM_READ_URL);\r
+ curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
+ curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
+ curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
+ curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
+ if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
+ curl_setopt($curl, CURLOPT_POST, 1);\r
+ curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
+ $data = curl_exec($curl);\r
+ $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
+ curl_close($curl);\r
+ $this->debug_message("*** Get Result:\n$data");\r