]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Msn/extlib/phpmsnclass/msn.class.php
5c79b3126161414ebef8d645c00bb9c76d400c69
[quix0rs-gnu-social.git] / plugins / Msn / extlib / phpmsnclass / msn.class.php
1 <?php\r
2 /*\r
3 \r
4 phpmsnclass ver 2.0s\r
5 Luke Fitzgerald <lw.fitzgerald@googlemail.com>\r
6 \r
7 Based on MSN class ver 2.0 by Tommy Wu, Ricky Su\r
8 License: GPL\r
9 \r
10 Documentation on the MSN protocol can be found at: http://msnpiki.msnfanatic.com/index.php/Main_Page\r
11 \r
12 This class uses MSNP15.\r
13 \r
14 In addition to PHP5, the additional php modules required are:\r
15 curl pcre mhash mcrypt bcmath\r
16 \r
17 */\r
18 \r
19 class MSN {\r
20     const PROTOCOL = 'MSNP15';\r
21     const PASSPORT_URL = 'https://login.live.com/RST.srf';\r
22     const BUILDVER = '8.1.0178';\r
23     const PROD_KEY = 'PK}_A_0N_K%O?A9S';\r
24     const PROD_ID = 'PROD0114ES4Z%Q5W';\r
25     const LOGIN_METHOD = 'SSO';\r
26     \r
27     const OIM_SEND_URL = 'https://ows.messenger.msn.com/OimWS/oim.asmx';\r
28     const OIM_SEND_SOAP = 'http://messenger.live.com/ws/2006/09/oim/Store2';\r
29     \r
30     const OIM_MAILDATA_URL = 'https://rsi.hotmail.com/rsi/rsi.asmx';\r
31     const OIM_MAILDATA_SOAP = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata';\r
32     const OIM_READ_URL = 'https://rsi.hotmail.com/rsi/rsi.asmx';\r
33     const OIM_READ_SOAP = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage';\r
34     const OIM_DEL_URL = 'https://rsi.hotmail.com/rsi/rsi.asmx';\r
35     const OIM_DEL_SOAP = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages';\r
36 \r
37     const MEMBERSHIP_URL = 'https://contacts.msn.com/abservice/SharingService.asmx';\r
38     const MEMBERSHIP_SOAP = 'http://www.msn.com/webservices/AddressBook/FindMembership';\r
39 \r
40     const ADDMEMBER_URL = 'https://contacts.msn.com/abservice/SharingService.asmx';\r
41     const ADDMEMBER_SOAP = 'http://www.msn.com/webservices/AddressBook/AddMember';\r
42 \r
43     const DELMEMBER_URL = 'https://contacts.msn.com/abservice/SharingService.asmx';\r
44     const DELMEMBER_SOAP = 'http://www.msn.com/webservices/AddressBook/DeleteMember';\r
45     \r
46     // the message length (include header) is limited (maybe since WLM 8.5 released)\r
47     // for WLM: 1664 bytes\r
48     // for YIM: 518 bytes\r
49     const MAX_MSN_MESSAGE_LEN = 1664;\r
50     const MAX_YAHOO_MESSAGE_LEN = 518;\r
51     \r
52     private $debug;\r
53     private $timeout;\r
54     \r
55     private $id;\r
56     private $ticket;\r
57     private $user = '';\r
58     private $password = '';\r
59     private $NSfp = false;\r
60     private $passport_policy = '';\r
61     private $alias;\r
62     private $psm;\r
63     private $retry_wait;\r
64     private $update_pending;\r
65     private $PhotoStickerFile = false;\r
66     private $Emotions = false;\r
67     private $XFRReqTimeout = 60;\r
68     private $SBStreamTimeout = 2;\r
69     private $MsnObjArray = array();\r
70     private $MsnObjMap = array();\r
71     private $ABAuthHeader;\r
72     private $ABService;\r
73     private $Contacts;\r
74 \r
75     private $server = 'messenger.hotmail.com';\r
76     private $port = 1863;\r
77 \r
78     private $clientid = '';\r
79     \r
80     private $error = '';\r
81 \r
82     private $authed = false;\r
83 \r
84     private $oim_try = 3;\r
85 \r
86     private $font_fn = 'Arial';\r
87     private $font_co = '333333';\r
88     private $font_ef = '';\r
89 \r
90     // Begin added for StatusNet\r
91 \r
92     private $aContactList = array();\r
93     private $aADL = array();\r
94 \r
95     /**\r
96     * Holds session information indexed by screenname if\r
97     * session has no socket or socket if socket present\r
98     *\r
99     * @var array\r
100     */\r
101     private $switchBoardSessions = array();\r
102 \r
103     /**\r
104     * Holds sockets indexed by screenname\r
105     *\r
106     * @var array\r
107     */\r
108     private $switchBoardSessionLookup = array();\r
109 \r
110     /**\r
111     * Holds references to sessions waiting for XFR\r
112     *\r
113     * @var array\r
114     */\r
115     private $waitingForXFR = array();\r
116 \r
117     /**\r
118     * Event Handler Functions\r
119     */\r
120     private $myEventHandlers = array();\r
121 \r
122     // End added for StatusNet\r
123 \r
124     /**\r
125     * Constructor method\r
126     *\r
127     * @param array $Configs Array of configuration options\r
128     *                       'user'           - Username\r
129     *                       'password'       - Password\r
130     *                       'alias'          - Bot nickname\r
131     *                       'psm'            - Bot personal status message\r
132     *                       'retry_wait'     - Time to wait before trying to reconnect\r
133     *                       'update_pending' - Whether to update pending contacts\r
134     *                       'PhotoSticker'   - Photo file to use (?)\r
135     *                       'debug'          - Enable/Disable debugging mode\r
136     * @param integer $timeout Connection timeout\r
137     * @param integer $client_id Client id (hexadecimal)\r
138     * @return MSN\r
139     */\r
140     public function __construct ($Configs = array(), $timeout = 15, $client_id = 0x7000800C) {\r
141         $this->user = $Configs['user'];\r
142         $this->password = $Configs['password'];\r
143         $this->alias = isset($Configs['alias']) ? $Configs['alias'] : '';\r
144         $this->psm = isset($Configs['psm']) ? $Configs['psm'] : '';\r
145         $this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30;\r
146         $this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true;\r
147         $this->PhotoStickerFile=isset($Configs['PhotoSticker']) ? $Configs['PhotoSticker'] : false;\r
148 \r
149         if ($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false) {\r
150             foreach($this->Emotions as $EmotionFilePath)\r
151                 $this->MsnObj($EmotionFilePath,$Type=2);\r
152         }\r
153         $this->debug = isset($Configs['debug']) ? $Configs['debug'] : false;\r
154         $this->timeout = $timeout;\r
155 \r
156         // Check support\r
157         if (!function_exists('curl_init')) throw new Exception("curl module not found!\n");\r
158         if (!function_exists('preg_match')) throw new Exception("pcre module not found!\n");\r
159         if (!function_exists('mhash')) throw new Exception("mhash module not found!\n");\r
160         if (!function_exists('mcrypt_cbc')) throw new Exception("mcrypt module not found!\n");\r
161         if (!function_exists('bcmod')) throw new Exception("bcmath module not found!\n");\r
162 \r
163         /*\r
164          http://msnpiki.msnfanatic.com/index.php/Client_ID\r
165          Client ID for MSN:\r
166          normal MSN 8.1 clientid is:\r
167          01110110 01001100 11000000 00101100\r
168          = 0x764CC02C\r
169 \r
170          we just use following:\r
171          * 0x04: Your client can send/receive Ink (GIF format)\r
172          * 0x08: Your client can send/recieve Ink (ISF format)\r
173          * 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')\r
174          * 0x70000000: This is the value for MSNC7 (WL Msgr 8.1)\r
175          = 0x7000800C;\r
176          */\r
177         $this->clientid = $client_id;\r
178         $this->ABService = new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl', array('trace' => 1));\r
179     }\r
180 \r
181     /**\r
182      * Signon methods\r
183      */\r
184     \r
185     /**\r
186      * Connect to the NS server\r
187      *\r
188      * @param String $user Username\r
189      * @param String $password Password\r
190      * @param String $redirect_server Redirect server\r
191      * @param Integer $redirect_port Redirect port\r
192      * @return Boolean Returns true if successful\r
193      */\r
194     private function connect($user, $password, $redirect_server = '', $redirect_port = 1863) {\r
195         $this->id = 1;\r
196         if ($redirect_server === '') {\r
197             $this->NSfp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);\r
198             if (!$this->NSfp) {\r
199                 $this->error = "!!! Could not connect to $this->server:$this->port, error => $errno, $errstr";\r
200                 return false;\r
201             }\r
202         }\r
203         else {\r
204             $this->NSfp = @fsockopen($redirect_server, $redirect_port, $errno, $errstr, $this->timeout);\r
205             if (!$this->NSfp) {\r
206                 $this->error = "!!! Could not connect to $redirect_server:$redirect_port, error => $errno, $errstr";\r
207                 return false;\r
208             }\r
209         }\r
210         $this->authed = false;\r
211         // MSNP9\r
212         // NS: >> VER {id} MSNP9 CVR0\r
213         // MSNP15\r
214         // NS: >>> VER {id} MSNP15 CVR0\r
215         $this->ns_writeln("VER $this->id ".self::PROTOCOL.' CVR0');\r
216 \r
217         $start_tm = time();\r
218         while (!self::socketcheck($this->NSfp)) {\r
219             $data = $this->ns_readln();\r
220             // no data?\r
221             if ($data === false) {\r
222                 // logout now\r
223                 // NS: >>> OUT\r
224                 $this->ns_writeln("OUT");\r
225                 @fclose($this->NSfp);\r
226                 $this->error = 'Timeout, maybe protocol changed!';\r
227                 return false;\r
228             }\r
229 \r
230             $code = substr($data, 0, 3);\r
231             $start_tm = time();\r
232 \r
233             switch ($code) {\r
234                 case 'VER':\r
235                     // MSNP9\r
236                     // NS: <<< VER {id} MSNP9 CVR0\r
237                     // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 6.0.0602 msmsgs {user}\r
238                     // MSNP15\r
239                     // NS: <<< VER {id} MSNP15 CVR0\r
240                     // NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs {user}\r
241                     $this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS ".self::BUILDVER." msmsgs $user");\r
242                     break;\r
243 \r
244                 case 'CVR':\r
245                     // MSNP9\r
246                     // NS: <<< CVR {id} {ver_list} {download_serve} ....\r
247                     // NS: >>> USR {id} TWN I {user}\r
248                     // MSNP15\r
249                     // NS: <<< CVR {id} {ver_list} {download_serve} ....\r
250                     // NS: >>> USR {id} SSO I {user}\r
251                     $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." I $user");\r
252                     break;\r
253 \r
254                 case 'USR':\r
255                     // already login for passport site, finish the login process now.\r
256                     // NS: <<< USR {id} OK {user} {verify} 0\r
257                     if ($this->authed) return true;\r
258                     // max. 16 digits for password\r
259                     if (strlen($password) > 16)\r
260                     $password = substr($password, 0, 16);\r
261 \r
262                     $this->user = $user;\r
263                     $this->password = $password;\r
264                     // NS: <<< USR {id} SSO S {policy} {nonce}\r
265                     @list(/* USR */, /* id */, /* SSO */, /* S */, $policy, $nonce,) = @explode(' ', $data);\r
266 \r
267                     $this->passport_policy = $policy;\r
268                     $aTickets = $this->get_passport_ticket();\r
269                     if (!$aTickets || !is_array($aTickets)) {\r
270                         // logout now\r
271                         // NS: >>> OUT\r
272                         $this->ns_writeln("OUT");\r
273                         @fclose($this->NSfp);\r
274                         $this->error = 'Passport authenticated fail!';\r
275                         return false;\r
276                     }\r
277 \r
278                     $ticket = $aTickets['ticket'];\r
279                     $secret = $aTickets['secret'];\r
280                     $this->ticket = $aTickets;\r
281                     $login_code = $this->generateLoginBLOB($secret, $nonce);\r
282 \r
283                     // NS: >>> USR {id} SSO S {ticket} {login_code}\r
284                     $this->ns_writeln("USR $this->id ".self::LOGIN_METHOD." S $ticket $login_code");\r
285                     $this->authed = true;\r
286                     break;\r
287 \r
288                 case 'XFR':\r
289                     // main login server will redirect to anther NS after USR command\r
290                     // MSNP9\r
291                     // NS: <<< XFR {id} NS {server} 0 {server}\r
292                     // MSNP15\r
293                     // NS: <<< XFR {id} NS {server} U D\r
294                     @list(/* XFR */, /* id */, $Type, $server, /* ... */) = @explode(' ', $data);\r
295                     if ($Type!='NS') break;\r
296                     @list($ip, $port) = @explode(':', $server);\r
297                     // this connection will close after XFR\r
298                     @fclose($this->NSfp);\r
299 \r
300                     $this->NSfp = @fsockopen($ip, $port, $errno, $errstr, $this->timeout);\r
301                     if (!$this->NSfp) {\r
302                         $this->error = "Can't connect to $ip:$port, error => $errno, $errstr";\r
303                         return false;\r
304                     }\r
305 \r
306                     // MSNP9\r
307                     // NS: >> VER {id} MSNP9 CVR0\r
308                     // MSNP15\r
309                     // NS: >>> VER {id} MSNP15 CVR0\r
310                     $this->ns_writeln("VER $this->id ".self::PROTOCOL.' CVR0');\r
311                     break;\r
312 \r
313                 case 'GCF':\r
314                     // return some policy data after 'USR {id} SSO I {user}' command\r
315                     // NS: <<< GCF 0 {size}\r
316                     @list(/* GCF */, /* 0 */, $size,) = @explode(' ', $data);\r
317                     // we don't need the data, just read it and drop\r
318                     if (is_numeric($size) && $size > 0)\r
319                         $this->ns_readdata($size);\r
320                     break;\r
321 \r
322                 default:\r
323                     // we'll quit if got any error\r
324                     if (is_numeric($code)) {\r
325                         // logout now\r
326                         // NS: >>> OUT\r
327                         $this->ns_writeln("OUT");\r
328                         @fclose($this->NSfp);\r
329                         $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";\r
330                         return false;\r
331                     }\r
332                     // unknown response from server, just ignore it\r
333                     break;\r
334             }\r
335         }\r
336         // never goto here\r
337     }\r
338 \r
339     /**\r
340      * Sign onto the NS server and retrieve the address book\r
341      *\r
342      * @return void\r
343      */\r
344     public function signon() {\r
345         /* FIXME Don't implement the signon as a loop or we could hang\r
346         *        the queue handler! */\r
347         $this->debug_message('*** Trying to connect to MSN network');\r
348         \r
349         // Remove any remaining switchboard sessions\r
350         $this->switchBoardSessions = array();\r
351         $this->switchBoardSessionLookup = array();\r
352 \r
353         while (true) {\r
354             // Connect\r
355             if (!$this->connect($this->user, $this->password)) {\r
356                 $this->signonFailure("!!! Could not connect to server: $this->error");\r
357                 continue;\r
358             }\r
359 \r
360             // Update contacts\r
361             if ($this->UpdateContacts() === false) continue;\r
362 \r
363             // Get membership lists\r
364             if (($this->aContactList = $this->getMembershipList()) === false) {\r
365                 $this->signonFailure('!!! Get membership list failed');\r
366                 continue;\r
367             }\r
368 \r
369             if ($this->update_pending) {\r
370                 if (is_array($this->aContactList)) {\r
371                     $pending = 'Pending';\r
372                     foreach ($this->aContactList as $u_domain => $aUserList) {\r
373                         foreach ($aUserList as $u_name => $aNetworks) {\r
374                             foreach ($aNetworks as $network => $aData) {\r
375                                 if (isset($aData[$pending])) {\r
376                                     // pending list\r
377                                     $cnt = 0;\r
378                                     foreach (array('Allow', 'Reverse') as $list) {\r
379                                         if (isset($aData[$list]))\r
380                                             $cnt++;\r
381                                         else {\r
382                                             if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {\r
383                                                 $this->aContactList[$u_domain][$u_name][$network][$list] = false;\r
384                                                 $cnt++;\r
385                                             }\r
386                                         }\r
387                                     }\r
388                                     if ($cnt >= 2) {\r
389                                         $id = $aData[$pending];\r
390                                         // we can delete it from pending now\r
391                                         if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending))\r
392                                             unset($this->aContactList[$u_domain][$u_name][$network][$pending]);\r
393                                     }\r
394                                 }\r
395                                 else {\r
396                                     // sync list\r
397                                     foreach (array('Allow', 'Reverse') as $list) {\r
398                                         if (!isset($aData[$list])) {\r
399                                             if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list))\r
400                                                 $this->aContactList[$u_domain][$u_name][$network][$list] = false;\r
401                                         }\r
402                                     }\r
403                                 }\r
404                             }\r
405                         }\r
406                     }\r
407                 }\r
408             }\r
409             $n = 0;\r
410             $sList = '';\r
411             $len = 0;\r
412             if (is_array($this->aContactList)) {\r
413                 foreach ($this->aContactList as $u_domain => $aUserList) {\r
414                     $str = '<d n="'.$u_domain.'">';\r
415                     $len += strlen($str);\r
416                     if ($len > 7400) {\r
417                         $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';\r
418                         $n++;\r
419                         $sList = '';\r
420                         $len = strlen($str);\r
421                     }\r
422                     $sList .= $str;\r
423                     foreach ($aUserList as $u_name => $aNetworks) {\r
424                         foreach ($aNetworks as $network => $status) {\r
425                             $str = '<c n="'.$u_name.'" l="3" t="'.$network.'" />';\r
426                             $len += strlen($str);\r
427                             // max: 7500, but <ml l="1"></d></ml> is 19,\r
428                             // so we use 7475\r
429                             if ($len > 7475) {\r
430                                 $sList .= '</d>';\r
431                                 $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';\r
432                                 $n++;\r
433                                 $sList = '<d n="'.$u_domain.'">'.$str;\r
434                                 $len = strlen($sList);\r
435                             }\r
436                             else\r
437                                 $sList .= $str;\r
438                         }\r
439                     }\r
440                     $sList .= '</d>';\r
441                 }\r
442             }\r
443             $this->aADL[$n] = '<ml l="1">'.$sList.'</ml>';\r
444             // NS: >>> BLP {id} BL\r
445             $this->ns_writeln("BLP $this->id BL");\r
446             foreach ($this->aADL as $str) {\r
447                 $len = strlen($str);\r
448                 // NS: >>> ADL {id} {size}\r
449                 $this->ns_writeln("ADL $this->id $len");\r
450                 $this->ns_writedata($str);\r
451             }\r
452             // NS: >>> PRP {id} MFN name\r
453             if ($this->alias == '') $this->alias = $user;\r
454             $aliasname = rawurlencode($this->alias);\r
455             $this->ns_writeln("PRP $this->id MFN $aliasname");\r
456             //設定個人大頭貼\r
457             //$MsnObj=$this->PhotoStckObj();\r
458             // NS: >>> CHG {id} {status} {clientid} {msnobj}\r
459             $this->ns_writeln("CHG $this->id NLN $this->clientid");\r
460             if ($this->PhotoStickerFile !== false)\r
461                 $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));\r
462             // NS: >>> UUX {id} length\r
463             $str = '<Data><PSM>'.htmlspecialchars($this->psm).'</PSM><CurrentMedia></CurrentMedia><MachineGuid></MachineGuid></Data>';\r
464             $len = strlen($str);\r
465             $this->ns_writeln("UUX $this->id $len");\r
466             $this->ns_writedata($str);\r
467             if (!self::socketcheck($this->NSfp)) {\r
468                 $this->debug_message('*** Connected, waiting for commands');\r
469                 break;\r
470             } else {\r
471                 $this->NSRetryWait($this->retry_wait);\r
472             }\r
473         }\r
474     }\r
475 \r
476     /**\r
477     * Called if there is an error during signon\r
478     *\r
479     * @param string $message Error message to log\r
480     * @return void\r
481     */\r
482     private function signonFailure($message) {\r
483         $this->debug_message($message);\r
484         $this->callHandler('ConnectFailed');\r
485         $this->NSRetryWait($this->retry_wait);\r
486     }\r
487 \r
488     /**\r
489     * Log out and close the NS connection\r
490     *\r
491     * @return void\r
492     */\r
493     private function nsLogout() {\r
494         if (is_resource($this->NSfp) && !feof($this->NSfp)) {\r
495             // logout now\r
496             // NS: >>> OUT\r
497             $this->ns_writeln("OUT");\r
498             fclose($this->NSfp);\r
499             $this->NSfp = false;\r
500             $this->debug_message("*** Logged out");\r
501         }\r
502     }\r
503 \r
504     /**\r
505      * NS and SB command handling methods\r
506      */\r
507     \r
508     /**\r
509      * Read and handle incoming command from NS\r
510      *\r
511      * @return void\r
512      */\r
513     private function nsReceive() {\r
514         // Sign in again if not signed in or socket failed\r
515         if (!is_resource($this->NSfp) || self::socketcheck($this->NSfp)) {\r
516             $this->callHandler('Reconnect');\r
517             $this->NSRetryWait($this->retry_wait);\r
518             $this->signon();\r
519             return;\r
520         }\r
521 \r
522         $data = $this->ns_readln();\r
523         if ($data === false) {\r
524             // There was no data / an error when reading from the socket so reconnect\r
525             $this->callHandler('Reconnect');\r
526             $this->NSRetryWait($this->retry_wait);\r
527             $this->signon();\r
528             return;\r
529         }\r
530         \r
531         switch (substr($data, 0, 3)) {\r
532             case 'SBS':\r
533                 // after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us\r
534                 // NS: <<< SBS 0 null\r
535                 break;\r
536 \r
537             case 'RFS':\r
538                 // FIXME:\r
539                 // NS: <<< RFS ???\r
540                 // refresh ADL, so we re-send it again\r
541                 if (is_array($this->aADL)) {\r
542                     foreach ($this->aADL as $str) {\r
543                         $len = strlen($str);\r
544                         // NS: >>> ADL {id} {size}\r
545                         $this->ns_writeln("ADL $this->id $len");\r
546                         $this->ns_writedata($str);\r
547                     }\r
548                 }\r
549                 break;\r
550 \r
551             case 'LST':\r
552                 // NS: <<< LST {email} {alias} 11 0\r
553                 @list(/* LST */, $email, /* alias */,) = @explode(' ', $data);\r
554                 @list($u_name, $u_domain) = @explode('@', $email);\r
555                 if (!isset($this->aContactList[$u_domain][$u_name][1])) {\r
556                     $this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow';\r
557                     $this->debug_message("*** Added to contact list: $u_name@$u_domain");\r
558                 }\r
559                 break;\r
560 \r
561             case 'ADL':\r
562                 // randomly, we get ADL command, someone add us to their contact list for MSNP15\r
563                 // NS: <<< ADL 0 {size}\r
564                 @list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data);\r
565                 if (is_numeric($size) && $size > 0) {\r
566                     $data = $this->ns_readdata($size);\r
567                     preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);\r
568                     if (is_array($matches) && count($matches) > 0) {\r
569                         $u_domain = $matches[1];\r
570                         $u_name = $matches[2];\r
571                         $network = $matches[4];\r
572                         if (isset($this->aContactList[$u_domain][$u_name][$network]))\r
573                             $this->debug_message("*** Someone (network: $network) added us to their list (but already in our list): $u_name@$u_domain");\r
574                         else {\r
575                             $re_login = false;\r
576                             $cnt = 0;\r
577                             foreach (array('Allow', 'Reverse') as $list) {\r
578                                 if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {\r
579                                     if ($re_login) {\r
580                                         $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");\r
581                                         continue;\r
582                                     }\r
583                                     $aTickets = $this->get_passport_ticket();\r
584                                     if (!$aTickets || !is_array($aTickets)) {\r
585                                         // failed to login? ignore it\r
586                                         $this->debug_message("*** Could not re-login, something wrong here");\r
587                                         $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");\r
588                                         continue;\r
589                                     }\r
590                                     $re_login = true;\r
591                                     $this->ticket = $aTickets;\r
592                                     $this->debug_message("**** Got new ticket, trying again");\r
593                                     if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {\r
594                                         $this->debug_message("*** Could not add $u_name@$u_domain (network: $network) to $list list");\r
595                                         continue;\r
596                                     }\r
597                                 }\r
598                                 $this->aContactList[$u_domain][$u_name][$network][$list] = false;\r
599                                 $cnt++;\r
600                             }\r
601                             $this->debug_message("*** Someone (network: $network) added us to their list: $u_name@$u_domain");\r
602                         }\r
603                         $str = '<ml l="1"><d n="'.$u_domain.'"><c n="'.$u_name.'" l="3" t="'.$network.'" /></d></ml>';\r
604                         $len = strlen($str);\r
605 \r
606                         $this->callHandler('AddedToList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network));\r
607                     }\r
608                     else\r
609                         $this->debug_message("*** Someone added us to their list: $data");\r
610                 }\r
611                 break;\r
612 \r
613             case 'RML':\r
614                 // randomly, we get RML command, someome remove us to their contact list for MSNP15\r
615                 // NS: <<< RML 0 {size}\r
616                 @list(/* RML */, /* 0 */, $size,) = @explode(' ', $data);\r
617                 if (is_numeric($size) && $size > 0) {\r
618                     $data = $this->ns_readdata($size);\r
619                     preg_match('#<ml><d n="([^"]+)"><c n="([^"]+)"(.*) t="(\d*)"(.*) /></d></ml>#', $data, $matches);\r
620                     if (is_array($matches) && count($matches) > 0) {\r
621                         $u_domain = $matches[1];\r
622                         $u_name = $matches[2];\r
623                         $network = $matches[4];\r
624                         if (isset($this->aContactList[$u_domain][$u_name][$network])) {\r
625                             $aData = $this->aContactList[$u_domain][$u_name][$network];\r
626 \r
627                             foreach ($aData as $list => $id)\r
628                                 $this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list);\r
629 \r
630                             unset($this->aContactList[$u_domain][$u_name][$network]);\r
631                             $this->debug_message("*** Someone (network: $network) removed us from their list: $u_name@$u_domain");\r
632                         }\r
633                         else\r
634                             $this->debug_message("*** Someone (network: $network) removed us from their list (but not in our list): $u_name@$u_domain");\r
635 \r
636                         $this->callHandler('RemovedFromList', array('screenname' => $u_name.'@'.$u_domain, 'network' => $network));\r
637                     }\r
638                     else\r
639                         $this->debug_message("*** Someone removed us from their list: $data");\r
640                 }\r
641                 break;\r
642 \r
643             case 'MSG':\r
644                 // randomly, we get MSG notification from server\r
645                 // NS: <<< MSG Hotmail Hotmail {size}\r
646                 @list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data);\r
647                 if (is_numeric($size) && $size > 0) {\r
648                     $data = $this->ns_readdata($size);\r
649                     $aLines = @explode("\n", $data);\r
650                     $header = true;\r
651                     $ignore = false;\r
652                     $maildata = '';\r
653                     foreach ($aLines as $line) {\r
654                         $line = rtrim($line);\r
655                         if ($header) {\r
656                             if ($line === '') {\r
657                                 $header = false;\r
658                                 continue;\r
659                             }\r
660                             if (strncasecmp($line, 'Content-Type:', 13) == 0) {\r
661                                 if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false && strpos($line, 'text/x-msmsgsoimnotification') === false) {\r
662                                     // we just need text/x-msmsgsinitialmdatanotification\r
663                                     // or text/x-msmsgsoimnotification\r
664                                     $ignore = true;\r
665                                     break;\r
666                                 }\r
667                             }\r
668                             continue;\r
669                         }\r
670                         if (strncasecmp($line, 'Mail-Data:', 10) == 0) {\r
671                             $maildata = trim(substr($line, 10));\r
672                             break;\r
673                         }\r
674                     }\r
675                     if ($ignore) {\r
676                         $this->debug_message("*** Ignoring MSG for: $line");\r
677                         break;\r
678                     }\r
679                     if ($maildata == '') {\r
680                         $this->debug_message("*** Ignoring MSG not for OIM");\r
681                         break;\r
682                     }\r
683                     $re_login = false;\r
684                     if (strcasecmp($maildata, 'too-large') == 0) {\r
685                         $this->debug_message("*** Large mail-data, need to get the data via SOAP");\r
686                         $maildata = $this->getOIM_maildata();\r
687                         if ($maildata === false) {\r
688                             $this->debug_message("*** Could not get mail-data via SOAP");\r
689 \r
690                             // maybe we need to re-login again\r
691                             $aTickets = $this->get_passport_ticket();\r
692                             if (!$aTickets || !is_array($aTickets)) {\r
693                                 // failed to login? ignore it\r
694                                 $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM");\r
695                                 break;\r
696                             }\r
697                             $re_login = true;\r
698                             $this->ticket = $aTickets;\r
699                             $this->debug_message("*** Got new ticket, trying again");\r
700                             $maildata = $this->getOIM_maildata();\r
701                             if ($maildata === false) {\r
702                                 $this->debug_message("*** Could not get mail-data via SOAP, and re-login already attempted, ignoring this OIM");\r
703                                 break;\r
704                             }\r
705                         }\r
706                     }\r
707                     // could be a lots of <M>...</M>, so we can't use preg_match here\r
708                     $p = $maildata;\r
709                     $aOIMs = array();\r
710                     while (1) {\r
711                         $start = strpos($p, '<M>');\r
712                         $end = strpos($p, '</M>');\r
713                         if ($start === false || $end === false || $start > $end) break;\r
714                         $end += 4;\r
715                         $sOIM = substr($p, $start, $end - $start);\r
716                         $aOIMs[] = $sOIM;\r
717                         $p = substr($p, $end);\r
718                     }\r
719                     if (count($aOIMs) == 0) {\r
720                         $this->debug_message("*** Ignoring empty OIM");\r
721                         break;\r
722                     }\r
723                     foreach ($aOIMs as $maildata) {\r
724                         // T: 11 for MSN, 13 for Yahoo\r
725                         // S: 6 for MSN, 7 for Yahoo\r
726                         // RT: the datetime received by server\r
727                         // RS: already read or not\r
728                         // SZ: size of message\r
729                         // E: sender\r
730                         // I: msgid\r
731                         // F: always 00000000-0000-0000-0000-000000000009\r
732                         // N: sender alias\r
733                         preg_match('#<T>(.*)</T>#', $maildata, $matches);\r
734                         if (count($matches) == 0) {\r
735                             $this->debug_message("*** Ignoring OIM maildata without <T>type</T>");\r
736                             continue;\r
737                         }\r
738                         $oim_type = $matches[1];\r
739                         if ($oim_type = 13)\r
740                             $network = 32;\r
741                         else\r
742                             $network = 1;\r
743                         preg_match('#<E>(.*)</E>#', $maildata, $matches);\r
744                         if (count($matches) == 0) {\r
745                             $this->debug_message("*** Ignoring OIM maildata without <E>sender</E>");\r
746                             continue;\r
747                         }\r
748                         $oim_sender = $matches[1];\r
749                         preg_match('#<I>(.*)</I>#', $maildata, $matches);\r
750                         if (count($matches) == 0) {\r
751                             $this->debug_message("*** Ignoring OIM maildata without <I>msgid</I>");\r
752                             continue;\r
753                         }\r
754                         $oim_msgid = $matches[1];\r
755                         preg_match('#<SZ>(.*)</SZ>#', $maildata, $matches);\r
756                         $oim_size = (count($matches) == 0) ? 0 : $matches[1];\r
757                         preg_match('#<RT>(.*)</RT>#', $maildata, $matches);\r
758                         $oim_time = (count($matches) == 0) ? 0 : $matches[1];\r
759                         $this->debug_message("*** OIM received from $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size");\r
760                         $sMsg = $this->getOIM_message($oim_msgid);\r
761                         if ($sMsg === false) {\r
762                             $this->debug_message("*** Could not get OIM, msgid = $oim_msgid");\r
763                             if ($re_login) {\r
764                                 $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM");\r
765                                 continue;\r
766                             }\r
767                             $aTickets = $this->get_passport_ticket();\r
768                             if (!$aTickets || !is_array($aTickets)) {\r
769                                 // failed to login? ignore it\r
770                                 $this->debug_message("*** Could not re-login, something wrong here, ignoring this OIM");\r
771                                 continue;\r
772                             }\r
773                             $re_login = true;\r
774                             $this->ticket = $aTickets;\r
775                             $this->debug_message("*** get new ticket, try it again");\r
776                             $sMsg = $this->getOIM_message($oim_msgid);\r
777                             if ($sMsg === false) {\r
778                                 $this->debug_message("*** Could not get OIM via SOAP, and re-login already attempted, ignoring this OIM");\r
779                                 continue;\r
780                             }\r
781                         }\r
782                         $this->debug_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg");\r
783                         $this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true));\r
784                     }\r
785                 }\r
786                 break;\r
787 \r
788             case 'UBM':\r
789                 // randomly, we get UBM, this is the message from other network, like Yahoo!\r
790                 // NS: <<< UBM {email} $network $type {size}\r
791                 @list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data);\r
792                 if (is_numeric($size) && $size > 0) {\r
793                     $data = $this->ns_readdata($size);\r
794                     $aLines = @explode("\n", $data);\r
795                     $header = true;\r
796                     $ignore = false;\r
797                     $sMsg = '';\r
798                     foreach ($aLines as $line) {\r
799                         $line = rtrim($line);\r
800                         if ($header) {\r
801                             if ($line === '') {\r
802                                 $header = false;\r
803                                 continue;\r
804                             }\r
805                             if (strncasecmp($line, 'TypingUser:', 11) == 0) {\r
806                                 $ignore = true;\r
807                                 break;\r
808                             }\r
809                             continue;\r
810                         }\r
811                         $aSubLines = @explode("\r", $line);\r
812                         foreach ($aSubLines as $str) {\r
813                             if ($sMsg !== '')\r
814                             $sMsg .= "\n";\r
815                             $sMsg .= $str;\r
816                         }\r
817                     }\r
818                     if ($ignore) {\r
819                         $this->debug_message("*** Ignoring message from $from_email: $line");\r
820                         break;\r
821                     }\r
822                     $this->debug_message("*** MSG from $from_email (network: $network): $sMsg");\r
823                     $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false));\r
824                 }\r
825                 break;\r
826 \r
827             case 'UBX':\r
828                 // randomly, we get UBX notification from server\r
829                 // NS: <<< UBX email {network} {size}\r
830                 @list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data);\r
831                 // we don't need the notification data, so just ignore it\r
832                 if (is_numeric($size) && $size > 0)\r
833                     $this->ns_readdata($size);\r
834                 break;\r
835 \r
836             case 'CHL':\r
837                 // randomly, we'll get challenge from server\r
838                 // NS: <<< CHL 0 {code}\r
839                 @list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data);\r
840                 $fingerprint = $this->getChallenge($chl_code);\r
841                 // NS: >>> QRY {id} {product_id} 32\r
842                 // NS: >>> fingerprint\r
843                 $this->ns_writeln("QRY $this->id ".self::PROD_ID.' 32');\r
844                 $this->ns_writedata($fingerprint);\r
845                 $this->ns_writeln("CHG $this->id NLN $this->clientid");\r
846                 if ($this->PhotoStickerFile !== false)\r
847                     $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));\r
848                 break;\r
849             case 'CHG':\r
850                 // NS: <<< CHG {id} {status} {code}\r
851                 // ignore it\r
852                 // change our status to online first\r
853                 break;\r
854 \r
855             case 'XFR':\r
856                 // sometimes, NS will redirect to another NS\r
857                 // MSNP9\r
858                 // NS: <<< XFR {id} NS {server} 0 {server}\r
859                 // MSNP15\r
860                 // NS: <<< XFR {id} NS {server} U D\r
861                 // for normal switchboard XFR\r
862                 // NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0\r
863                 @list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data);\r
864                 @list($ip, $port) = @explode(':', $server);\r
865                 if ($server_type != 'SB') {\r
866                     // maybe exit?\r
867                     // this connection will close after XFR\r
868                     $this->nsLogout();\r
869                     continue;\r
870                 }\r
871 \r
872                 $this->debug_message("NS: <<< XFR SB");\r
873                 $session = array_shift($this->waitingForXFR);\r
874                 $this->connectToSBSession('Active', $ip, $port, $session['to'], array('cki' => $cki_code));\r
875                 break;\r
876             case 'QNG':\r
877                 // NS: <<< QNG {time}\r
878                 @list(/* QNG */, $ping_wait) = @explode(' ', $data);\r
879                 $this->callHandler('Pong', $ping_wait);\r
880                 break;\r
881 \r
882             case 'RNG':\r
883                 if ($this->PhotoStickerFile !== false)\r
884                     $this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));\r
885                 else\r
886                     $this->ns_writeln("CHG $this->id NLN $this->clientid");\r
887                 // someone is trying to talk to us\r
888                 // NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0\r
889                 $this->debug_message("NS: <<< RNG $data");\r
890                 @list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name,) = @explode(' ', $data);\r
891                 @list($sb_ip, $sb_port) = @explode(':', $server);\r
892                 $this->debug_message("*** RING from $email, $sb_ip:$sb_port");\r
893                 $this->addContact($email, 1, $email, true);\r
894                 $this->connectToSBSession('Passive', $sb_ip, $sb_port, $email, array('sid' => $sid, 'ticket' => $ticket));\r
895                 break;\r
896             \r
897             case 'NLN':\r
898                 // NS: <<< NLN {status} {email} {networkid} {nickname} {clientid} {dpobj}\r
899                 // NS: <<< NLN NLN darkip@inflatablegoldfish.com 1 Luke 2685403136 0\r
900                 @list(/* NLN */, $status, $email, $network, $nickname, /* clientid */, /* dbobj */,) = @explode(' ', $data);\r
901                 $this->callHandler('StatusChange', array('screenname' => $email, 'status' => $status, 'network' => $network, 'nickname' => $nickname));\r
902                 break;\r
903             \r
904             case 'OUT':\r
905                 // force logout from NS\r
906                 // NS: <<< OUT xxx\r
907                 $this->debug_message("*** LOGOUT from NS");\r
908                 return $this->nsLogout();\r
909 \r
910             default:\r
911                 $code = substr($data,0,3);\r
912                 if (is_numeric($code)) {\r
913                     $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";\r
914                     $this->debug_message("*** NS: $this->error");\r
915 \r
916                     return $this->nsLogout();\r
917                 }\r
918                 break;\r
919         }\r
920     }\r
921 \r
922     /**\r
923      * Read and handle incoming command/message from\r
924      * a switchboard session socket\r
925      */\r
926     private function sbReceive($socket) {\r
927         $intsocket = (int) $socket;\r
928         $session = &$this->switchBoardSessions[$intsocket];\r
929 \r
930         if (feof($socket)) {\r
931             // Unset session lookup value\r
932             unset($this->switchBoardSessionLookup[$session['to']]);\r
933 \r
934             // Unset session itself\r
935             unset($this->switchBoardSessions[$intsocket]);\r
936             return;\r
937         }\r
938 \r
939         $id = &$session['id'];\r
940 \r
941         $data = $this->sb_readln($socket);\r
942         $code = substr($data, 0, 3);\r
943         switch($code) {\r
944             case 'IRO':\r
945                 // SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid}\r
946                 @list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data);\r
947                 $this->debug_message("*** $email joined session");\r
948                 $session['joined'] = true;\r
949                 break;\r
950             case 'BYE':\r
951                 $this->debug_message("*** Quit for BYE");\r
952                 $this->endSBSession();\r
953                 break;\r
954             case 'USR':\r
955                 // SB: <<< USR {id} OK {user} {alias}\r
956                 // we don't need the data, just ignore it\r
957                 // request user to join this switchboard\r
958                 // SB: >>> CAL {id} {user}\r
959                 $this->sb_writeln($socket, $id, "CAL $id ".$session['to']);\r
960                 break;\r
961             case 'CAL':\r
962                 // SB: <<< CAL {id} RINGING {?}\r
963                 // we don't need this, just ignore, and wait for other response\r
964                 $session['id']++;\r
965                 break;\r
966             case 'JOI':\r
967                 // SB: <<< JOI {user} {alias} {clientid?}\r
968                 // someone join us\r
969                 // we don't need the data, just ignore it\r
970                 // no more user here\r
971                 $session['joined'] = true;\r
972                 break;\r
973             case 'MSG':\r
974                 // SB: <<< MSG {email} {alias} {len}\r
975                 @list(/* MSG */, $from_email, /* alias */, $len, ) = @explode(' ', $data);\r
976                 $len = trim($len);\r
977                 $data = $this->sb_readdata($socket, $len);\r
978                 $aLines = @explode("\n", $data);\r
979                 $header = true;\r
980                 $ignore = false;\r
981                 $is_p2p = false;\r
982                 $sMsg = '';\r
983                 foreach ($aLines as $line) {\r
984                     $line = rtrim($line);\r
985                     if ($header) {\r
986                         if ($line === '') {\r
987                             $header = false;\r
988                             continue;\r
989                         }\r
990                         if (strncasecmp($line, 'TypingUser:', 11) == 0) {\r
991                             // typing notification, just ignore\r
992                             $ignore = true;\r
993                             break;\r
994                         }\r
995                         if (strncasecmp($line, 'Chunk:', 6) == 0) {\r
996                             // we don't handle any split message, just ignore\r
997                             $ignore = true;\r
998                             break;\r
999                         }\r
1000                         if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) {\r
1001                             // p2p message, ignore it, but we need to send acknowledgement for it...\r
1002                             $is_p2p = true;\r
1003                             $p = strstr($data, "\n\n");\r
1004                             $sMsg = '';\r
1005                             if ($p === false) {\r
1006                                 $p = strstr($data, "\r\n\r\n");\r
1007                                 if ($p !== false)\r
1008                                 $sMsg = substr($p, 4);\r
1009                             }\r
1010                             else\r
1011                             $sMsg = substr($p, 2);\r
1012                             break;\r
1013                         }\r
1014                         if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) {\r
1015                             // ignore all application/x-... message\r
1016                             // for example:\r
1017                             //      application/x-ms-ink        => ink message\r
1018                             $ignore = true;\r
1019                             break;\r
1020                         }\r
1021                         if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) {\r
1022                             // ignore all text/x-... message\r
1023                             // for example:\r
1024                             //      text/x-msnmsgr-datacast         => nudge, voice clip....\r
1025                             //      text/x-mms-animemoticon         => customized animemotion word\r
1026                             $ignore = true;\r
1027                             break;\r
1028                         }\r
1029                         continue;\r
1030                     }\r
1031                     if ($sMsg !== '')\r
1032                         $sMsg .= "\n";\r
1033                     $sMsg .= $line;\r
1034                 }\r
1035                 if ($ignore) {\r
1036                     $this->debug_message("*** Ignoring SB data from $from_email: $line");\r
1037                     break;\r
1038                 }\r
1039                 if ($is_p2p) {\r
1040                     // we will ignore any p2p message after sending acknowledgement\r
1041                     $ignore = true;\r
1042                     $len = strlen($sMsg);\r
1043                     $this->debug_message("*** p2p message from $from_email, size $len");\r
1044                     // header = 48 bytes\r
1045                     // content >= 0 bytes\r
1046                     // footer = 4 bytes\r
1047                     // so it need to >= 52 bytes\r
1048                     /*if ($len < 52) {\r
1049                         $this->debug_message("*** p2p: size error, less than 52!");\r
1050                         break;\r
1051                     }*/\r
1052                     $aDwords = @unpack("V12dword", $sMsg);\r
1053                     if (!is_array($aDwords)) {\r
1054                         $this->debug_message("*** p2p: header unpack error!");\r
1055                         break;\r
1056                     }\r
1057                     $this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg));\r
1058                     $hdr_SessionID = $aDwords['dword1'];\r
1059                     $hdr_Identifier = $aDwords['dword2'];\r
1060                     $hdr_DataOffsetLow = $aDwords['dword3'];\r
1061                     $hdr_DataOffsetHigh = $aDwords['dword4'];\r
1062                     $hdr_TotalDataSizeLow = $aDwords['dword5'];\r
1063                     $hdr_TotalDataSizeHigh = $aDwords['dword6'];\r
1064                     $hdr_MessageLength = $aDwords['dword7'];\r
1065                     $hdr_Flag = $aDwords['dword8'];\r
1066                     $hdr_AckID = $aDwords['dword9'];\r
1067                     $hdr_AckUID = $aDwords['dword10'];\r
1068                     $hdr_AckSizeLow = $aDwords['dword11'];\r
1069                     $hdr_AckSizeHigh = $aDwords['dword12'];\r
1070                     $this->debug_message("*** p2p: header SessionID = $hdr_SessionID");\r
1071                     $this->debug_message("*** p2p: header Inentifier = $hdr_Identifier");\r
1072                     $this->debug_message("*** p2p: header Data Offset Low = $hdr_DataOffsetLow");\r
1073                     $this->debug_message("*** p2p: header Data Offset High = $hdr_DataOffsetHigh");\r
1074                     $this->debug_message("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow");\r
1075                     $this->debug_message("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh");\r
1076                     $this->debug_message("*** p2p: header MessageLength = $hdr_MessageLength");\r
1077                     $this->debug_message("*** p2p: header Flag = $hdr_Flag");\r
1078                     $this->debug_message("*** p2p: header AckID = $hdr_AckID");\r
1079                     $this->debug_message("*** p2p: header AckUID = $hdr_AckUID");\r
1080                     $this->debug_message("*** p2p: header AckSize Low = $hdr_AckSizeLow");\r
1081                     $this->debug_message("*** p2p: header AckSize High = $hdr_AckSizeHigh");\r
1082                     if ($hdr_Flag == 2) {\r
1083                         //This is an ACK from SB ignore....\r
1084                         $this->debug_message("*** p2p: //This is an ACK from SB ignore....:\n");\r
1085                         break;\r
1086                     }\r
1087                     $MsgBody = $this->linetoArray(substr($sMsg, 48, -4));\r
1088                     $this->debug_message("*** p2p: body".print_r($MsgBody, true));\r
1089                     if (($MsgBody['EUF-GUID']=='{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}')&&($PictureFilePath=$this->GetPictureFilePath($MsgBody['Context']))) {\r
1090                         while (true) {\r
1091                             if ($this->sb_readln($socket) === false) break;\r
1092                         }\r
1093                         $this->debug_message("*** p2p: Inv hdr:\n".$this->dump_binary(substr($sMsg, 0, 48)));\r
1094                         preg_match('/{([0-9A-F\-]*)}/i', $MsgBody['Via'], $Matches);\r
1095                         $BranchGUID = $Matches[1];\r
1096                         //it's an invite to send a display picture.\r
1097                         $new_id = ~$hdr_Identifier;\r
1098                         $hdr = pack(\r
1099                             "LLLLLLLLLLLL", $hdr_SessionID,\r
1100                             $new_id,\r
1101                             0, 0,\r
1102                             $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
1103                             0,\r
1104                             2,\r
1105                             $hdr_Identifier,\r
1106                             $hdr_AckID,\r
1107                             $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh\r
1108                         );\r
1109                         $footer = pack("L", 0);\r
1110                         $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";\r
1111                         $len = strlen($message);\r
1112                         $this->sb_writeln($socket, $id, "MSG $id D $len");\r
1113                         $this->sb_writedata($socket, $message);\r
1114                         $this->debug_message("*** p2p: send display picture acknowledgement for $hdr_SessionID");\r
1115                         $this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message));\r
1116                         $this->sb_readln($socket); // Read ACK;\r
1117                         $this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr));\r
1118                         $new_id -= 3;\r
1119                         //Send 200 OK message\r
1120                         $MessageContent="SessionID: ".$MsgBody['SessionID']."\r\n\r\n".pack("C", 0);\r
1121                         $MessagePayload=\r
1122                             "MSNSLP/1.0 200 OK\r\n".\r
1123                             "To: <msnmsgr:".$from_email.">\r\n".\r
1124                             "From: <msnmsgr:".$this->user.">\r\n".\r
1125                             "Via: ".$MsgBody['Via']."\r\n".\r
1126                             "CSeq: ".($MsgBody['CSeq']+1)."\r\n".\r
1127                             "Call-ID: ".$MsgBody['Call-ID']."\r\n".\r
1128                             "Max-Forwards: 0\r\n".\r
1129                             "Content-Type: application/x-msnmsgr-sessionreqbody\r\n".\r
1130                             "Content-Length: ".strlen($MessageContent)."\r\n\r\n".\r
1131                         $MessageContent;\r
1132                         $hdr_TotalDataSizeLow=strlen($MessagePayload);\r
1133                         $hdr_TotalDataSizeHigh=0;\r
1134                         $hdr = pack(\r
1135                             "LLLLLLLLLLLL", $hdr_SessionID,\r
1136                             $new_id,\r
1137                             0, 0,\r
1138                             $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
1139                             strlen($MessagePayload),\r
1140                             0,\r
1141                             rand(),\r
1142                             0,\r
1143                             0, 0\r
1144                         );\r
1145 \r
1146                         $message =\r
1147                             "MIME-Version: 1.0\r\n".\r
1148                             "Content-Type: application/x-msnmsgrp2p\r\n".\r
1149                             "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";\r
1150                         $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
1151                         $this->sb_writedata($socket, $message);\r
1152                         $this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message));\r
1153                         $this->sb_readln($socket); // Read ACK;\r
1154 \r
1155                         $this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr));\r
1156                         // send data preparation message\r
1157                         // send 4 null bytes as data\r
1158                         $hdr_TotalDataSizeLow = 4;\r
1159                         $hdr_TotalDataSizeHigh = 0 ;\r
1160                         $new_id++;\r
1161                         $hdr = pack(\r
1162                             "LLLLLLLLLLLL",\r
1163                             $MsgBody['SessionID'],\r
1164                             $new_id,\r
1165                             0, 0,\r
1166                             $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
1167                             $hdr_TotalDataSizeLow,\r
1168                             0,\r
1169                             rand(),\r
1170                             0,\r
1171                             0, 0\r
1172                         );\r
1173                         $message =\r
1174                             "MIME-Version: 1.0\r\n".\r
1175                             "Content-Type: application/x-msnmsgrp2p\r\n".\r
1176                             "P2P-Dest: $from_email\r\n\r\n$hdr".pack('L', 0)."$footer";\r
1177                         $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
1178                         $this->sb_writedata($socket, $message);\r
1179                         $this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message));\r
1180                         $this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr));\r
1181                         $this->sb_readln($socket); // Read ACK;\r
1182 \r
1183                         // send Data Content..\r
1184                         $footer=pack('N',1);\r
1185                         $new_id++;\r
1186                         $FileSize=filesize($PictureFilePath);\r
1187                         if ($hTitle=fopen($PictureFilePath,'rb')) {\r
1188                             $Offset = 0;\r
1189                             //$new_id++;\r
1190                             while (!feof($hTitle)) {\r
1191                                 $FileContent = fread($hTitle, 1024);\r
1192                                 $FileContentSize = strlen($FileContent);\r
1193                                 $hdr = pack(\r
1194                                     "LLLLLLLLLLLL",\r
1195                                     $MsgBody['SessionID'],\r
1196                                     $new_id,\r
1197                                     $Offset, 0,\r
1198                                     $FileSize, 0,\r
1199                                     $FileContentSize,\r
1200                                     0x20,\r
1201                                     rand(),\r
1202                                     0,\r
1203                                     0, 0\r
1204                                 );\r
1205                                 $message =\r
1206                                     "MIME-Version: 1.0\r\n".\r
1207                                     "Content-Type: application/x-msnmsgrp2p\r\n".\r
1208                                     "P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer";\r
1209                                 $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
1210                                 $this->sb_writedata($socket, $message);\r
1211                                 $this->debug_message("*** p2p: dump send Data Content message  $Offset / $FileSize :\n".$this->dump_binary($message));\r
1212                                 $this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr));\r
1213                                 //$this->SB_readln($socket);//Read ACK;\r
1214                                 $Offset += $FileContentSize;\r
1215                             }\r
1216                         }\r
1217                         //Send Bye\r
1218                         /*\r
1219                         $MessageContent="\r\n".pack("C", 0);\r
1220                         $MessagePayload=\r
1221                             "BYE MSNMSGR:MSNSLP/1.0\r\n".\r
1222                             "To: <msnmsgr:$from_email>\r\n".\r
1223                             "From: <msnmsgr:".$this->user.">\r\n".\r
1224                             "Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n".\r
1225                             "CSeq: 0\r\n".\r
1226                             "Call-ID: ".$MsgBody['Call-ID']."\r\n".\r
1227                             "Max-Forwards: 0\r\n".\r
1228                             "Content-Type: application/x-msnmsgr-sessionclosebody\r\n".\r
1229                             "Content-Length: ".strlen($MessageContent)."\r\n\r\n".$MessageContent;\r
1230                         $footer=pack('N',0);\r
1231                         $hdr_TotalDataSizeLow=strlen($MessagePayload);\r
1232                         $hdr_TotalDataSizeHigh=0;\r
1233                         $new_id++;\r
1234                         $hdr = pack("LLLLLLLLLLLL",\r
1235                         0,\r
1236                         $new_id,\r
1237                         0, 0,\r
1238                         $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
1239                         0,\r
1240                         0,\r
1241                         rand(),\r
1242                         0,\r
1243                         0,0);\r
1244                         $message =\r
1245                                     "MIME-Version: 1.0\r\n".\r
1246                                     "Content-Type: application/x-msnmsgrp2p\r\n".\r
1247                                     "P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";\r
1248                         $this->sb_writeln($socket, $id, "MSG $id D ".strlen($message));\r
1249                         $id++;\r
1250                         $this->sb_writedata($socket, $message);\r
1251                         $this->debug_message("*** p2p: dump send BYE message :\n".$this->dump_binary($message));\r
1252                         */\r
1253                         break;\r
1254                     }\r
1255                     //TODO:\r
1256                     //if ($hdr_Flag == 2) {\r
1257                     // just send ACK...\r
1258                     //    $this->sb_writeln($socket, $id, "ACK $id");\r
1259                     //    break;\r
1260                     //}\r
1261                     if ($hdr_SessionID == 4) {\r
1262                         // ignore?\r
1263                         $this->debug_message("*** p2p: ignore flag 4");\r
1264                         break;\r
1265                     }\r
1266                     $finished = false;\r
1267                     if ($hdr_TotalDataSizeHigh == 0) {\r
1268                         // only 32 bites size\r
1269                         if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow)\r
1270                         $finished = true;\r
1271                     }\r
1272                     else {\r
1273                         // we won't accept any file transfer\r
1274                         // so I think we won't get any message size need to use 64 bits\r
1275                         // 64 bits size here, can't count directly...\r
1276                         $totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10);\r
1277                         $dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10);\r
1278                         $messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10);\r
1279                         $now_size = bcadd($dataoffset, $messagelength);\r
1280                         if (bccomp($now_size, $totalsize) >= 0)\r
1281                         $finished = true;\r
1282                     }\r
1283                     if (!$finished) {\r
1284                         // ignore not finished split packet\r
1285                         $this->debug_message("*** p2p: ignore split packet, not finished");\r
1286                         break;\r
1287                     }\r
1288                     //$new_id = ~$hdr_Identifier;\r
1289                     /*\r
1290                      $new_id++;\r
1291                      $hdr = pack("LLLLLLLLLLLL", $hdr_SessionID,\r
1292                      $new_id,\r
1293                      0, 0,\r
1294                      $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,\r
1295                      0,\r
1296                      2,\r
1297                      $hdr_Identifier,\r
1298                      $hdr_AckID,\r
1299                      $hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh);\r
1300                      $footer = pack("L", 0);\r
1301                      $message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";\r
1302                      $len = strlen($message);\r
1303                      $this->sb_writeln($socket, $id, "MSG $id D $len");\r
1304                      $id++;\r
1305                      $this->sb_writedata($socket, $message);\r
1306                      $this->debug_message("*** p2p: send acknowledgement for $hdr_SessionID");\r
1307                      $this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer));\r
1308                      */\r
1309                     break;\r
1310                 }\r
1311                 $this->debug_message("*** MSG from $from_email: $sMsg");\r
1312                 $this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false));\r
1313                 break;\r
1314             case '217':\r
1315                 $this->debug_message('*** User '.$session['to'].' is offline. Trying OIM.');\r
1316                 $session['offline'] = true;\r
1317                 break;\r
1318             default:\r
1319                 if (is_numeric($code)) {\r
1320                     $this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";\r
1321                     $this->debug_message("*** SB: $this->error");\r
1322                     $sessionEnd=true;\r
1323                 }\r
1324                 break;\r
1325         }\r
1326     }\r
1327 \r
1328     /**\r
1329      * Checks for new data and calls appropriate methods\r
1330      *\r
1331      * This method is usually called in an infinite loop to keep checking for new data\r
1332      *\r
1333      * @return void\r
1334      */\r
1335     public function receive() {\r
1336         // First, get an array of sockets that have data that is ready to be read\r
1337         $ready = array();\r
1338         $ready = $this->getSockets();\r
1339         $numrdy = stream_select($ready, $w = NULL, $x = NULL, NULL);\r
1340 \r
1341         // Now that we've waited for something, go through the $ready\r
1342         // array and read appropriately\r
1343 \r
1344         foreach ($ready as $socket) {\r
1345             if ($socket == $this->NSfp) {\r
1346                 $this->nsReceive();\r
1347             } else {\r
1348                 $this->sbReceive($socket);\r
1349             }\r
1350         }\r
1351     }\r
1352 \r
1353     /**\r
1354      * Switchboard related methods\r
1355      */\r
1356     \r
1357     /**\r
1358      * Send a request for a switchboard session\r
1359      *\r
1360      * @param string $to Target email for switchboard session\r
1361      */\r
1362     private function reqSBSession($to) {\r
1363         $this->debug_message("*** Request SB for $to");\r
1364         $this->ns_writeln("XFR $this->id SB");\r
1365 \r
1366         // Add to the queue of those waiting for a switchboard session reponse\r
1367         $this->switchBoardSessions[$to] = array(\r
1368             'to' => $to,\r
1369             'socket' => NULL,\r
1370             'id' => 1,\r
1371             'joined' => false,\r
1372             'offline' => false,\r
1373             'XFRReqTime' => time()\r
1374         );\r
1375         $this->waitingForXFR[$to] = &$this->switchBoardSessions[$to];\r
1376     }\r
1377 \r
1378     /**\r
1379      * Following an XFR or RNG, connect to the switchboard session\r
1380      *\r
1381      * @param string $mode Mode, either 'Active' (in the case of XFR) or 'Passive' (in the case of RNG)\r
1382      * @param string $ip IP of Switchboard\r
1383      * @param integer $port Port of Switchboard\r
1384      * @param string $to User on other end of Switchboard\r
1385      * @param array $param Array of parameters - 'cki', 'ticket', 'sid'\r
1386      * @return boolean true if successful\r
1387      */\r
1388     private function connectToSBSession($mode, $ip, $port, $to, $param) {\r
1389         $this->debug_message("*** SB: Trying to connect to switchboard server $ip:$port");\r
1390 \r
1391         $socket = @fsockopen($ip, $port, $errno, $errstr, $this->timeout);\r
1392         if (!$socket) {\r
1393             $this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr");\r
1394             return false;\r
1395         }\r
1396 \r
1397         // Store the socket in the lookup array\r
1398         $this->switchBoardSessionLookup[$to] = $socket;\r
1399 \r
1400         // Store the socket in the sessions array\r
1401         $this->switchBoardSessions[$to] = array(\r
1402             'to' => $to,\r
1403             'socket' => $socket,\r
1404             'id' => 1,\r
1405             'joined' => false,\r
1406             'offline' => false,\r
1407             'XFRReqTime' => time()\r
1408         );\r
1409         \r
1410         // Change the index of the session to the socket\r
1411         $intsocket = (int) $socket;\r
1412         $this->switchBoardSessions[$intsocket] = $this->switchBoardSessions[$to];\r
1413         unset($this->switchBoardSessions[$to]);\r
1414         \r
1415         $id = &$this->switchBoardSessions[$intsocket]['id'];\r
1416 \r
1417         if ($mode == 'Active') {\r
1418             $cki_code = $param['cki'];\r
1419 \r
1420             // SB: >>> USR {id} {user} {cki}\r
1421             $this->sb_writeln($socket, $id, "USR $id $this->user $cki_code");\r
1422         } else {\r
1423             // Passive\r
1424             $ticket = $param['ticket'];\r
1425             $sid = $param['sid'];\r
1426 \r
1427             // SB: >>> ANS {id} {user} {ticket} {session_id}\r
1428             $this->sb_writeln($socket, $id, "ANS $id $this->user $ticket $sid");\r
1429         }\r
1430     }\r
1431     \r
1432     /**\r
1433     * Called when we want to end a switchboard session\r
1434     * or a switchboard session ends\r
1435     *\r
1436     * @param resource $socket Socket\r
1437     * @param boolean $killsession Whether to delete the session\r
1438     * @return void\r
1439     */\r
1440     private function endSBSession($socket) {\r
1441         if (!self::socketcheck($socket)) {\r
1442             $this->sb_writeln($socket, $fake = 0, 'OUT');\r
1443         }\r
1444         @fclose($socket);\r
1445 \r
1446         // Unset session lookup value\r
1447         $intsocket = (int) $socket;\r
1448         unset($this->switchBoardSessionLookup[$this->switchBoardSessions[$intsocket]['to']]);\r
1449 \r
1450         // Unset session itself\r
1451         unset($this->switchBoardSessions[$intsocket]);\r
1452     }\r
1453 \r
1454     /**\r
1455      * Send a message via an existing SB session\r
1456      *\r
1457      * @param string $to Recipient for message\r
1458      * @param string $message Message\r
1459      * @return boolean true on success\r
1460      */\r
1461     private function sendMessageViaSB($to, $message) {\r
1462         $socket = $this->switchBoardSessionLookup[$to];\r
1463         if (self::socketcheck($socket)) {\r
1464             return false;\r
1465         }\r
1466         \r
1467         $intsocket = (int) $socket;\r
1468         if (!$this->switchBoardSessions[$intsocket]['joined']) {\r
1469             // If our participant has not joined the session yet we can't message them!\r
1470             return false;\r
1471         }\r
1472 \r
1473         $id = &$this->switchBoardSessions[$intsocket]['id'];\r
1474 \r
1475         $aMessage = $this->getMessage($message);\r
1476         // CheckEmotion...\r
1477         $MsnObjDefine = $this->GetMsnObjDefine($aMessage);\r
1478         if ($MsnObjDefine !== '') {\r
1479             $SendString = "MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine";\r
1480             $len = strlen($SendString);\r
1481 \r
1482             if ($this->sb_writeln($socket, $id, "MSG $id N $len") === false ||\r
1483                 $this->sb_writedata($socket, $SendString) === false) {\r
1484                     return false;\r
1485                 }\r
1486         }\r
1487         $len = strlen($aMessage);\r
1488 \r
1489         if ($this->sb_writeln($socket, $id, "MSG $id N $len") === false ||\r
1490             $this->sb_writedata($socket, $aMessage) === false) {\r
1491                 return false;\r
1492             }\r
1493 \r
1494         // Don't close the SB session, we might as well leave it open\r
1495         return true;\r
1496     }\r
1497 \r
1498     /**\r
1499      * Send a message to a user on another network\r
1500      *\r
1501      * @param string $to Intended recipient\r
1502      * @param string $message Message\r
1503      * @param integer $network Network\r
1504      * @return void\r
1505      */\r
1506     private function sendOtherNetworkMessage($to, $message, $network) {\r
1507         $message = $this->getMessage($message, $network);\r
1508         $len = strlen($message);\r
1509         if ($this->ns_writeln("UUM $this->id $to $network 1 $len") === false ||\r
1510             $this->ns_writedata($Message) === false) {\r
1511             return false;\r
1512         }\r
1513         $this->debug_message("*** Sent to $to (network: $network):\n$Message");\r
1514         return true;\r
1515     }\r
1516 \r
1517     /**\r
1518      * Send a message\r
1519      *\r
1520      * @param string $to To address in form user@host.com(@network)\r
1521      *                   where network is 1 for MSN, 32 for Yahoo\r
1522      *                   and 'Offline' for offline messages\r
1523      * @param string $message Message\r
1524      */\r
1525     public function sendMessage($to, $message) {\r
1526         if ($message != '') {\r
1527             list($name, $host, $network) = explode('@', $to);\r
1528             $network = $network == '' ? 1 : $network;\r
1529             $recipient = $name.'@'.$host;\r
1530 \r
1531             if ($network === 1) {\r
1532                 if (!isset($this->switchBoardSessionLookup[$recipient])) {\r
1533                         if (!isset($this->switchBoardSessions[$recipient]) || time() - $this->switchBoardSessions[$recipient]['XFRReqTime'] > $this->XFRReqTimeout) {\r
1534                                 $this->debug_message("*** No existing SB session or request has timed out");\r
1535                             $this->reqSBSession($recipient);\r
1536                         }\r
1537                         return false;\r
1538                 } else {\r
1539                     $socket = $this->switchBoardSessionLookup[$recipient];\r
1540                     if ($this->switchBoardSessions[(int) $socket]['offline']) {\r
1541                         $this->debug_message("*** Contact ($recipient) offline, sending OIM");\r
1542                         $this->endSBSession($socket);\r
1543                         return $this->sendMessage($recipient.'@Offline', $message);\r
1544                     } else {\r
1545                         $this->debug_message("*** Attempting to send message to $recipient using existing SB session");\r
1546 \r
1547                         if ($this->sendMessageViaSB($recipient, $message)) {\r
1548                             $this->debug_message('*** Message sent successfully');\r
1549                             return true;\r
1550                         } else {\r
1551                             $this->debug_message('*** Message sending failed, requesting new SB session');\r
1552                             $this->reqSBSession($recipient);\r
1553                             return false;\r
1554                         }\r
1555                     }\r
1556                 }\r
1557             } elseif ($network == 'Offline') {\r
1558                 //Send OIM\r
1559                 //FIXME: ä¿®æ­£Send OIM\r
1560                 $lockkey = '';\r
1561                 $re_login = false;\r
1562                 for ($i = 0; $i < $this->oim_try; $i++) {\r
1563                     if (($oim_result = $this->sendOIM($recipient, $message, $lockkey)) === true) break;\r
1564                     if (is_array($oim_result) && $oim_result['challenge'] !== false) {\r
1565                         // need challenge lockkey\r
1566                         $this->debug_message("*** Need challenge code for ".$oim_result['challenge']);\r
1567                         $lockkey = $this->getChallenge($oim_result['challenge']);\r
1568                         continue;\r
1569                     }\r
1570                     if ($oim_result === false || $oim_result['auth_policy'] !== false) {\r
1571                         if ($re_login) {\r
1572                             $this->debug_message("*** Can't send OIM, but we already re-logged-in again, so returning false");\r
1573                             return false;\r
1574                         }\r
1575                         $this->debug_message("*** Can't send OIM, maybe ticket expired, trying to login again");\r
1576 \r
1577                         // Maybe we need to re-login again\r
1578                         if (!$this->get_passport_ticket()) {\r
1579                             $this->debug_message("*** Can't re-login, something went wrong here, returning false");\r
1580                             return false;\r
1581                         }\r
1582                         $this->debug_message("*** Getting new ticket and trying again");\r
1583                         continue;\r
1584                     }\r
1585                 }\r
1586                 return true;\r
1587             } else {\r
1588                 // Other network\r
1589                 return $this->sendOtherNetworkMessage($recipient, $message, $network);\r
1590             }\r
1591         }\r
1592         return true;\r
1593     }\r
1594 \r
1595     /**\r
1596      * OIM methods\r
1597      */\r
1598     \r
1599     /**\r
1600     * Get OIM mail data\r
1601     *\r
1602     * @return string mail data or false on failure\r
1603     */\r
1604     function getOIM_maildata() {\r
1605         preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);\r
1606         if (count($matches) == 0) {\r
1607             $this->debug_message('*** No web ticket?');\r
1608             return false;\r
1609         }\r
1610         $t = htmlspecialchars($matches[1]);\r
1611         $p = htmlspecialchars($matches[2]);\r
1612         $XML = '<?xml version="1.0" encoding="utf-8"?>\r
1613 <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
1614                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
1615                xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\r
1616 <soap:Header>\r
1617   <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
1618     <t>'.$t.'</t>\r
1619     <p>'.$p.'</p>\r
1620   </PassportCookie>\r
1621 </soap:Header>\r
1622 <soap:Body>\r
1623   <GetMetadata xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi" />\r
1624 </soap:Body>\r
1625 </soap:Envelope>';\r
1626 \r
1627         $header_array = array(\r
1628             'SOAPAction: '.self::OIM_MAILDATA_SOAP,\r
1629             'Content-Type: text/xml; charset=utf-8',\r
1630             'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'\r
1631         );\r
1632 \r
1633         $this->debug_message('*** URL: '.self::OIM_MAILDATA_URL);\r
1634         $this->debug_message("*** Sending SOAP:\n$XML");\r
1635         $curl = curl_init();\r
1636         curl_setopt($curl, CURLOPT_URL, self::OIM_MAILDATA_URL);\r
1637         curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
1638         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
1639         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
1640         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
1641         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
1642         curl_setopt($curl, CURLOPT_POST, 1);\r
1643         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
1644         $data = curl_exec($curl);\r
1645         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
1646         curl_close($curl);\r
1647         $this->debug_message("*** Get Result:\n$data");\r
1648 \r
1649         if ($http_code != 200) {\r
1650             $this->debug_message("*** Could not get OIM maildata! http code: $http_code");\r
1651             return false;\r
1652         }\r
1653 \r
1654         // <GetMetadataResponse xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">See #XML_Data</GetMetadataResponse>\r
1655         preg_match('#<GetMetadataResponse([^>]*)>(.*)</GetMetadataResponse>#', $data, $matches);\r
1656         if (count($matches) == 0) {\r
1657             $this->debug_message('*** Could not get OIM maildata');\r
1658             return false;\r
1659         }\r
1660         return $matches[2];\r
1661     }\r
1662 \r
1663     /**\r
1664     * Fetch OIM message with given id\r
1665     *\r
1666     * @param string $msgid\r
1667     * @return string Message or false on failure\r
1668     */\r
1669     function getOIM_message($msgid) {\r
1670         preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);\r
1671         if (count($matches) == 0) {\r
1672             $this->debug_message('*** No web ticket?');\r
1673             return false;\r
1674         }\r
1675         $t = htmlspecialchars($matches[1]);\r
1676         $p = htmlspecialchars($matches[2]);\r
1677 \r
1678         // read OIM\r
1679         $XML = '<?xml version="1.0" encoding="utf-8"?>\r
1680 <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
1681                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
1682                xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\r
1683 <soap:Header>\r
1684   <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
1685     <t>'.$t.'</t>\r
1686     <p>'.$p.'</p>\r
1687   </PassportCookie>\r
1688 </soap:Header>\r
1689 <soap:Body>\r
1690   <GetMessage xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
1691     <messageId>'.$msgid.'</messageId>\r
1692     <alsoMarkAsRead>false</alsoMarkAsRead>\r
1693   </GetMessage>\r
1694 </soap:Body>\r
1695 </soap:Envelope>';\r
1696 \r
1697         $header_array = array(\r
1698             'SOAPAction: '.self::OIM_READ_SOAP,\r
1699             'Content-Type: text/xml; charset=utf-8',\r
1700             'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'\r
1701         );\r
1702 \r
1703         $this->debug_message('*** URL: '.self::OIM_READ_URL);\r
1704         $this->debug_message("*** Sending SOAP:\n$XML");\r
1705         $curl = curl_init();\r
1706         curl_setopt($curl, CURLOPT_URL, self::OIM_READ_URL);\r
1707         curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
1708         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
1709         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
1710         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
1711         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
1712         curl_setopt($curl, CURLOPT_POST, 1);\r
1713         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
1714         $data = curl_exec($curl);\r
1715         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
1716         curl_close($curl);\r
1717         $this->debug_message("*** Get Result:\n$data");\r
1718 \r
1719         if ($http_code != 200) {\r
1720             $this->debug_message("*** Can't get OIM: $msgid, http code = $http_code");\r
1721             return false;\r
1722         }\r
1723 \r
1724         // why can't use preg_match('#<GetMessageResult>(.*)</GetMessageResult>#', $data, $matches)?\r
1725         // multi-lines?\r
1726         $start = strpos($data, '<GetMessageResult>');\r
1727         $end = strpos($data, '</GetMessageResult>');\r
1728         if ($start === false || $end === false || $start > $end) {\r
1729             $this->debug_message("*** Can't get OIM: $msgid");\r
1730             return false;\r
1731         }\r
1732         $lines = substr($data, $start + 18, $end - $start);\r
1733         $aLines = @explode("\n", $lines);\r
1734         $header = true;\r
1735         $ignore = false;\r
1736         $sOIM = '';\r
1737         foreach ($aLines as $line) {\r
1738             $line = rtrim($line);\r
1739             if ($header) {\r
1740                 if ($line === '') {\r
1741                     $header = false;\r
1742                     continue;\r
1743                 }\r
1744                 continue;\r
1745             }\r
1746             // stop at empty lines\r
1747             if ($line === '') break;\r
1748             $sOIM .= $line;\r
1749         }\r
1750         $sMsg = base64_decode($sOIM);\r
1751         //$this->debug_message("*** we get OIM ($msgid): $sMsg");\r
1752 \r
1753         // delete OIM\r
1754         $XML = '<?xml version="1.0" encoding="utf-8"?>\r
1755 <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
1756                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
1757                xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\r
1758 <soap:Header>\r
1759   <PassportCookie xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
1760     <t>'.$t.'</t>\r
1761     <p>'.$p.'</p>\r
1762   </PassportCookie>\r
1763 </soap:Header>\r
1764 <soap:Body>\r
1765   <DeleteMessages xmlns="http://www.hotmail.msn.com/ws/2004/09/oim/rsi">\r
1766     <messageIds>\r
1767       <messageId>'.$msgid.'</messageId>\r
1768     </messageIds>\r
1769   </DeleteMessages>\r
1770 </soap:Body>\r
1771 </soap:Envelope>';\r
1772 \r
1773         $header_array = array(\r
1774             'SOAPAction: '.self::OIM_DEL_SOAP,\r
1775             'Content-Type: text/xml; charset=utf-8',\r
1776             'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'\r
1777         );\r
1778 \r
1779         $this->debug_message('*** URL: '.self::OIM_DEL_URL);\r
1780         $this->debug_message("*** Sending SOAP:\n$XML");\r
1781         $curl = curl_init();\r
1782         curl_setopt($curl, CURLOPT_URL, self::OIM_DEL_URL);\r
1783         curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
1784         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
1785         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
1786         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
1787         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
1788         curl_setopt($curl, CURLOPT_POST, 1);\r
1789         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
1790         $data = curl_exec($curl);\r
1791         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
1792         curl_close($curl);\r
1793         $this->debug_message("*** Get Result:\n$data");\r
1794 \r
1795         if ($http_code != 200)\r
1796             $this->debug_message("*** Could not delete OIM: $msgid, http code = $http_code");\r
1797         else\r
1798             $this->debug_message("*** OIM ($msgid) deleted");\r
1799         return $sMsg;\r
1800     }\r
1801     \r
1802     /**\r
1803      * Send offline message\r
1804      *\r
1805      * @param string $to Intended recipient\r
1806      * @param string $sMessage Message\r
1807      * @param string $lockkey Lock key\r
1808      * @return mixed true on success or error data\r
1809      */\r
1810     private function sendOIM($to, $sMessage, $lockkey) {\r
1811         $XML = '<?xml version="1.0" encoding="utf-8"?>\r
1812 <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
1813                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
1814                xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">\r
1815 <soap:Header>\r
1816   <From memberName="'.$this->user.'"\r
1817         friendlyName="=?utf-8?B?'.base64_encode($this->user).'?="\r
1818         xml:lang="zh-TW"\r
1819         proxy="MSNMSGR"\r
1820         xmlns="http://messenger.msn.com/ws/2004/09/oim/"\r
1821         msnpVer="'.self::PROTOCOL.'"\r
1822         buildVer="'.self::BUILDVER.'"/>\r
1823   <To memberName="'.$to.'" xmlns="http://messenger.msn.com/ws/2004/09/oim/"/>\r
1824   <Ticket passport="'.htmlspecialchars($this->ticket['oim_ticket']).'"\r
1825           appid="'.self::PROD_ID.'"\r
1826           lockkey="'.$lockkey.'"\r
1827           xmlns="http://messenger.msn.com/ws/2004/09/oim/"/>\r
1828   <Sequence xmlns="http://schemas.xmlsoap.org/ws/2003/03/rm">\r
1829     <Identifier xmlns="http://schemas.xmlsoap.org/ws/2002/07/utility">http://messenger.msn.com</Identifier>\r
1830     <MessageNumber>1</MessageNumber>\r
1831   </Sequence>\r
1832 </soap:Header>\r
1833 <soap:Body>\r
1834   <MessageType xmlns="http://messenger.msn.com/ws/2004/09/oim/">text</MessageType>\r
1835   <Content xmlns="http://messenger.msn.com/ws/2004/09/oim/">MIME-Version: 1.0\r
1836 Content-Type: text/plain; charset=UTF-8\r
1837 Content-Transfer-Encoding: base64\r
1838 X-OIM-Message-Type: OfflineMessage\r
1839 X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7}\r
1840 X-OIM-Sequence-Num: 1\r
1841 \r
1842 '.chunk_split(base64_encode($sMessage)).'\r
1843   </Content>\r
1844 </soap:Body>\r
1845 </soap:Envelope>';\r
1846 \r
1847         $header_array = array(\r
1848             'SOAPAction: '.self::OIM_SEND_SOAP,\r
1849             'Content-Type: text/xml',\r
1850             'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.self::BUILDVER.')'\r
1851         );\r
1852 \r
1853         $this->debug_message('*** URL: '.self::OIM_SEND_URL);\r
1854         $this->debug_message("*** Sending SOAP:\n$XML");\r
1855         $curl = curl_init();\r
1856         curl_setopt($curl, CURLOPT_URL, self::OIM_SEND_URL);\r
1857         curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
1858         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
1859         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
1860         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
1861         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
1862         curl_setopt($curl, CURLOPT_POST, 1);\r
1863         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
1864         $data = curl_exec($curl);\r
1865         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
1866         curl_close($curl);\r
1867         $this->debug_message("*** Get Result:\n$data");\r
1868 \r
1869         if ($http_code == 200) {\r
1870             $this->debug_message("*** OIM sent for $to");\r
1871             return true;\r
1872         }\r
1873 \r
1874         $challenge = false;\r
1875         $auth_policy = false;\r
1876         // the lockkey is invalid, authenticated fail, we need challenge it again\r
1877         // <LockKeyChallenge xmlns="http://messenger.msn.com/ws/2004/09/oim/">364763969</LockKeyChallenge>\r
1878         preg_match("#<LockKeyChallenge (.*)>(.*)</LockKeyChallenge>#", $data, $matches);\r
1879         if (count($matches) != 0) {\r
1880             // yes, we get new LockKeyChallenge\r
1881             $challenge = $matches[2];\r
1882             $this->debug_message("*** OIM need new challenge ($challenge) for $to");\r
1883         }\r
1884         // auth policy error\r
1885         // <RequiredAuthPolicy xmlns="http://messenger.msn.com/ws/2004/09/oim/">MBI_SSL</RequiredAuthPolicy>\r
1886         preg_match("#<RequiredAuthPolicy (.*)>(.*)</RequiredAuthPolicy>#", $data, $matches);\r
1887         if (count($matches) != 0) {\r
1888             $auth_policy = $matches[2];\r
1889             $this->debug_message("*** OIM need new auth policy ($auth_policy) for $to");\r
1890         }\r
1891         if ($auth_policy === false && $challenge === false) {\r
1892             //<faultcode xmlns:q0="http://messenger.msn.com/ws/2004/09/oim/">q0:AuthenticationFailed</faultcode>\r
1893             preg_match("#<faultcode (.*)>(.*)</faultcode>#", $data, $matches);\r
1894             if (count($matches) == 0) {\r
1895                 // no error, we assume the OIM is sent\r
1896                 $this->debug_message("*** OIM sent for $to");\r
1897                 return true;\r
1898             }\r
1899             $err_code = $matches[2];\r
1900             //<faultstring>Exception of type 'System.Web.Services.Protocols.SoapException' was thrown.</faultstring>\r
1901             preg_match("#<faultstring>(.*)</faultstring>#", $data, $matches);\r
1902             if (count($matches) > 0)\r
1903                 $err_msg = $matches[1];\r
1904             else\r
1905                 $err_msg = '';\r
1906             $this->debug_message("*** OIM failed for $to");\r
1907             $this->debug_message("*** OIM Error code: $err_code");\r
1908             $this->debug_message("*** OIM Error Message: $err_msg");\r
1909             return false;\r
1910         }\r
1911         return array('challenge' => $challenge, 'auth_policy' => $auth_policy);\r
1912     }\r
1913     \r
1914     /**\r
1915      * Contact / Membership list methods\r
1916      */\r
1917     \r
1918     /**\r
1919     * Fetch contact list\r
1920     *\r
1921     * @return boolean true on success\r
1922     */\r
1923     private function UpdateContacts() {\r
1924         $ABApplicationHeaderArray = array(\r
1925             'ABApplicationHeader' => array(\r
1926                 ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),\r
1927                 'ApplicationId' => 'CFE80F9D-180F-4399-82AB-413F33A1FA11',\r
1928                 'IsMigration' => false,\r
1929                 'PartnerScenario' => 'ContactSave'\r
1930              )\r
1931         );\r
1932 \r
1933         $ABApplicationHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray));\r
1934         $ABFindAllArray = array(\r
1935             'ABFindAll' => array(\r
1936                 ':' => array('xmlns'=>'http://www.msn.com/webservices/AddressBook'),\r
1937                 'abId' => '00000000-0000-0000-0000-000000000000',\r
1938                 'abView' => 'Full',\r
1939                 'lastChange' => '0001-01-01T00:00:00.0000000-08:00',\r
1940             )\r
1941         );\r
1942         $ABFindAll = new SoapParam($this->Array2SoapVar($ABFindAllArray), 'ABFindAll');\r
1943         $this->ABService->__setSoapHeaders(array($ABApplicationHeader, $this->ABAuthHeader));\r
1944         $this->Contacts = array();\r
1945         try {\r
1946             $this->debug_message('*** Updating Contacts...');\r
1947             $Result = $this->ABService->ABFindAll($ABFindAll);\r
1948             $this->debug_message("*** Result:\n".print_r($Result, true)."\n".$this->ABService->__getLastResponse());\r
1949             foreach($Result->ABFindAllResult->contacts->Contact as $Contact)\r
1950                 $this->Contacts[$Contact->contactInfo->passportName] = $Contact;\r
1951         } catch(Exception $e) {\r
1952             $this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage());\r
1953             return false;\r
1954         }\r
1955         return true;\r
1956     }\r
1957 \r
1958     /**\r
1959     * Add contact\r
1960     *\r
1961     * @param string $email\r
1962     * @param integer $network\r
1963     * @param string $display\r
1964     * @param boolean $sendADL\r
1965     * @return boolean true on success\r
1966     */\r
1967     private function addContact($email, $network, $display = '', $sendADL = false) {\r
1968         if ($network != 1) return true;\r
1969         if (isset($this->Contacts[$email])) return true;\r
1970 \r
1971         $ABContactAddArray = array(\r
1972             'ABContactAdd' => array(\r
1973                 ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),\r
1974                 'abId' => '00000000-0000-0000-0000-000000000000',\r
1975                 'contacts' => array(\r
1976                     'Contact' => array(\r
1977                         ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),\r
1978                         'contactInfo' => array(\r
1979                             'contactType' => 'LivePending',\r
1980                             'passportName' => $email,\r
1981                             'isMessengerUser' => true,\r
1982                             'MessengerMemberInfo' => array(\r
1983                                 'DisplayName' => $email\r
1984                             )\r
1985                         )\r
1986                     )\r
1987                 ),\r
1988                 'options' => array(\r
1989                     'EnableAllowListManagement' => true\r
1990                 )\r
1991             )\r
1992         );\r
1993         $ABContactAdd = new SoapParam($this->Array2SoapVar($ABContactAddArray), 'ABContactAdd');\r
1994         try {\r
1995             $this->debug_message("*** Adding Contact $email...");\r
1996             $this->ABService->ABContactAdd($ABContactAdd);\r
1997         } catch(Exception $e) {\r
1998             $this->debug_message("*** Add Contact Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage());\r
1999             return false;\r
2000         }\r
2001         if ($sendADL && !feof($this->NSfp)) {\r
2002             @list($u_name, $u_domain) = @explode('@', $email);\r
2003             foreach (array('1', '2') as $l) {\r
2004                 $str = '<ml l="1"><d n="'.$u_domain.'"><c n="'.$u_name.'" l="'.$l.'" t="'.$network.'" /></d></ml>';\r
2005                 $len = strlen($str);\r
2006                 // NS: >>> ADL {id} {size}\r
2007                 $this->ns_writeln("ADL $this->id $len");\r
2008                 $this->ns_writedata($str);\r
2009             }\r
2010         }\r
2011         $this->UpdateContacts();\r
2012         return true;\r
2013     }\r
2014 \r
2015     /**\r
2016     * Remove contact from list\r
2017     *\r
2018     * @param integer $memberID\r
2019     * @param string $email\r
2020     * @param integer $network\r
2021     * @param string $list\r
2022     */\r
2023     function delMemberFromList($memberID, $email, $network, $list) {\r
2024         if ($network != 1 && $network != 32) return true;\r
2025         if ($memberID === false) return true;\r
2026         $user = $email;\r
2027         $ticket = htmlspecialchars($this->ticket['contact_ticket']);\r
2028         if ($network == 1)\r
2029             $XML = '<?xml version="1.0" encoding="utf-8"?>\r
2030 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"\r
2031                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
2032                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
2033                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">\r
2034 <soap:Header>\r
2035     <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2036         <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>\r
2037         <IsMigration>false</IsMigration>\r
2038         <PartnerScenario>ContactMsgrAPI</PartnerScenario>\r
2039     </ABApplicationHeader>\r
2040     <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2041         <ManagedGroupRequest>false</ManagedGroupRequest>\r
2042         <TicketToken>'.$ticket.'</TicketToken>\r
2043     </ABAuthHeader>\r
2044 </soap:Header>\r
2045 <soap:Body>\r
2046     <DeleteMember xmlns="http://www.msn.com/webservices/AddressBook">\r
2047         <serviceHandle>\r
2048             <Id>0</Id>\r
2049             <Type>Messenger</Type>\r
2050             <ForeignId></ForeignId>\r
2051         </serviceHandle>\r
2052         <memberships>\r
2053             <Membership>\r
2054                 <MemberRole>'.$list.'</MemberRole>\r
2055                 <Members>\r
2056                     <Member xsi:type="PassportMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\r
2057                         <Type>Passport</Type>\r
2058                         <MembershipId>'.$memberID.'</MembershipId>\r
2059                         <State>Accepted</State>\r
2060                     </Member>\r
2061                 </Members>\r
2062             </Membership>\r
2063         </memberships>\r
2064     </DeleteMember>\r
2065 </soap:Body>\r
2066 </soap:Envelope>';\r
2067         else\r
2068             $XML = '<?xml version="1.0" encoding="utf-8"?>\r
2069 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"\r
2070                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
2071                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
2072                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">\r
2073 <soap:Header>\r
2074     <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2075         <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>\r
2076         <IsMigration>false</IsMigration>\r
2077         <PartnerScenario>ContactMsgrAPI</PartnerScenario>\r
2078     </ABApplicationHeader>\r
2079     <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2080         <ManagedGroupRequest>false</ManagedGroupRequest>\r
2081         <TicketToken>'.$ticket.'</TicketToken>\r
2082     </ABAuthHeader>\r
2083 </soap:Header>\r
2084 <soap:Body>\r
2085     <DeleteMember xmlns="http://www.msn.com/webservices/AddressBook">\r
2086         <serviceHandle>\r
2087             <Id>0</Id>\r
2088             <Type>Messenger</Type>\r
2089             <ForeignId></ForeignId>\r
2090         </serviceHandle>\r
2091         <memberships>\r
2092             <Membership>\r
2093                 <MemberRole>'.$list.'</MemberRole>\r
2094                 <Members>\r
2095                     <Member xsi:type="EmailMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\r
2096                         <Type>Email</Type>\r
2097                         <MembershipId>'.$memberID.'</MembershipId>\r
2098                         <State>Accepted</State>\r
2099                     </Member>\r
2100                 </Members>\r
2101             </Membership>\r
2102         </memberships>\r
2103     </DeleteMember>\r
2104 </soap:Body>\r
2105 </soap:Envelope>';\r
2106 \r
2107         $header_array = array(\r
2108             'SOAPAction: '.self::DELMEMBER_SOAP,\r
2109             'Content-Type: text/xml; charset=utf-8',\r
2110             'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'\r
2111         );\r
2112 \r
2113         $this->debug_message('*** URL: '.self::DELMEMBER_URL);\r
2114         $this->debug_message("*** Sending SOAP:\n$XML");\r
2115         $curl = curl_init();\r
2116         curl_setopt($curl, CURLOPT_URL, self::DELMEMBER_URL);\r
2117         curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
2118         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
2119         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
2120         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
2121         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
2122         curl_setopt($curl, CURLOPT_POST, 1);\r
2123         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
2124         $data = curl_exec($curl);\r
2125         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
2126         curl_close($curl);\r
2127         $this->debug_message("*** Get Result:\n$data");\r
2128 \r
2129         if ($http_code != 200) {\r
2130             preg_match('#<faultcode>(.*)</faultcode><faultstring>(.*)</faultstring>#', $data, $matches);\r
2131             if (count($matches) == 0) {\r
2132                 $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list");\r
2133                 return false;\r
2134             }\r
2135             $faultcode = trim($matches[1]);\r
2136             $faultstring = trim($matches[2]);\r
2137             if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) {\r
2138                 $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, error code: $faultcode, $faultstring");\r
2139                 return false;\r
2140             }\r
2141             $this->debug_message("*** Could not delete member (network: $network) $email ($memberID) from $list list, not present in list");\r
2142             return true;\r
2143         }\r
2144         $this->debug_message("*** Member successfully deleted (network: $network) $email ($memberID) from $list list");\r
2145         return true;\r
2146     }\r
2147 \r
2148     /**\r
2149     * Add contact to list\r
2150     *\r
2151     * @param string $email\r
2152     * @param integer $network\r
2153     * @param string $list\r
2154     */\r
2155     function addMemberToList($email, $network, $list) {\r
2156         if ($network != 1 && $network != 32) return true;\r
2157         $ticket = htmlspecialchars($this->ticket['contact_ticket']);\r
2158         $user = $email;\r
2159 \r
2160         if ($network == 1)\r
2161             $XML = '<?xml version="1.0" encoding="utf-8"?>\r
2162 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"\r
2163                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
2164                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
2165                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">\r
2166 <soap:Header>\r
2167     <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2168         <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>\r
2169         <IsMigration>false</IsMigration>\r
2170         <PartnerScenario>ContactMsgrAPI</PartnerScenario>\r
2171     </ABApplicationHeader>\r
2172     <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2173         <ManagedGroupRequest>false</ManagedGroupRequest>\r
2174         <TicketToken>'.$ticket.'</TicketToken>\r
2175     </ABAuthHeader>\r
2176 </soap:Header>\r
2177 <soap:Body>\r
2178     <AddMember xmlns="http://www.msn.com/webservices/AddressBook">\r
2179         <serviceHandle>\r
2180             <Id>0</Id>\r
2181             <Type>Messenger</Type>\r
2182             <ForeignId></ForeignId>\r
2183         </serviceHandle>\r
2184         <memberships>\r
2185             <Membership>\r
2186                 <MemberRole>'.$list.'</MemberRole>\r
2187                 <Members>\r
2188                     <Member xsi:type="PassportMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\r
2189                         <Type>Passport</Type>\r
2190                         <State>Accepted</State>\r
2191                         <PassportName>'.$user.'</PassportName>\r
2192                     </Member>\r
2193                 </Members>\r
2194             </Membership>\r
2195         </memberships>\r
2196     </AddMember>\r
2197 </soap:Body>\r
2198 </soap:Envelope>';\r
2199         else\r
2200             $XML = '<?xml version="1.0" encoding="utf-8"?>\r
2201 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"\r
2202                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
2203                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
2204                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">\r
2205 <soap:Header>\r
2206     <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2207         <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>\r
2208         <IsMigration>false</IsMigration>\r
2209         <PartnerScenario>ContactMsgrAPI</PartnerScenario>\r
2210     </ABApplicationHeader>\r
2211     <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2212         <ManagedGroupRequest>false</ManagedGroupRequest>\r
2213         <TicketToken>'.$ticket.'</TicketToken>\r
2214     </ABAuthHeader>\r
2215 </soap:Header>\r
2216 <soap:Body>\r
2217     <AddMember xmlns="http://www.msn.com/webservices/AddressBook">\r
2218         <serviceHandle>\r
2219             <Id>0</Id>\r
2220             <Type>Messenger</Type>\r
2221             <ForeignId></ForeignId>\r
2222         </serviceHandle>\r
2223         <memberships>\r
2224             <Membership>\r
2225                 <MemberRole>'.$list.'</MemberRole>\r
2226                 <Members>\r
2227                     <Member xsi:type="EmailMember" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\r
2228                         <Type>Email</Type>\r
2229                         <State>Accepted</State>\r
2230                         <Email>'.$user.'</Email>\r
2231                         <Annotations>\r
2232                             <Annotation>\r
2233                                 <Name>MSN.IM.BuddyType</Name>\r
2234                                 <Value>32:YAHOO</Value>\r
2235                             </Annotation>\r
2236                         </Annotations>\r
2237                     </Member>\r
2238                 </Members>\r
2239             </Membership>\r
2240         </memberships>\r
2241     </AddMember>\r
2242 </soap:Body>\r
2243 </soap:Envelope>';\r
2244         $header_array = array(\r
2245             'SOAPAction: '.self::ADDMEMBER_SOAP,\r
2246             'Content-Type: text/xml; charset=utf-8',\r
2247             'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'\r
2248         );\r
2249 \r
2250         $this->debug_message('*** URL: '.self::ADDMEMBER_URL);\r
2251         $this->debug_message("*** Sending SOAP:\n$XML");\r
2252         $curl = curl_init();\r
2253         curl_setopt($curl, CURLOPT_URL, self::ADDMEMBER_URL);\r
2254         curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
2255         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
2256         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
2257         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
2258         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
2259         curl_setopt($curl, CURLOPT_POST, 1);\r
2260         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
2261         $data = curl_exec($curl);\r
2262         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
2263         curl_close($curl);\r
2264         $this->debug_message("*** Get Result:\n$data");\r
2265 \r
2266         if ($http_code != 200) {\r
2267             preg_match('#<faultcode>(.*)</faultcode><faultstring>(.*)</faultstring>#', $data, $matches);\r
2268             if (count($matches) == 0) {\r
2269                 $this->debug_message("*** Could not add member (network: $network) $email to $list list");\r
2270                 return false;\r
2271             }\r
2272             $faultcode = trim($matches[1]);\r
2273             $faultstring = trim($matches[2]);\r
2274             if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) {\r
2275                 $this->debug_message("*** Could not add member (network: $network) $email to $list list, error code: $faultcode, $faultstring");\r
2276                 return false;\r
2277             }\r
2278             $this->debug_message("*** Could not add member (network: $network) $email to $list list, already present");\r
2279             return true;\r
2280         }\r
2281         $this->debug_message("*** Member successfully added (network: $network) $email to $list list");\r
2282         return true;\r
2283     }\r
2284 \r
2285     /**\r
2286     * Get membership lists\r
2287     *\r
2288     * @param mixed $returnData Membership list or false on failure\r
2289     */\r
2290     function getMembershipList($returnData = false) {\r
2291         $ticket = htmlspecialchars($this->ticket['contact_ticket']);\r
2292         $XML = '<?xml version="1.0" encoding="utf-8"?>\r
2293 <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"\r
2294                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\r
2295                xmlns:xsd="http://www.w3.org/2001/XMLSchema"\r
2296                xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">\r
2297 <soap:Header>\r
2298     <ABApplicationHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2299         <ApplicationId>996CDE1E-AA53-4477-B943-2BE802EA6166</ApplicationId>\r
2300         <IsMigration>false</IsMigration>\r
2301         <PartnerScenario>Initial</PartnerScenario>\r
2302     </ABApplicationHeader>\r
2303     <ABAuthHeader xmlns="http://www.msn.com/webservices/AddressBook">\r
2304         <ManagedGroupRequest>false</ManagedGroupRequest>\r
2305         <TicketToken>'.$ticket.'</TicketToken>\r
2306     </ABAuthHeader>\r
2307 </soap:Header>\r
2308 <soap:Body>\r
2309     <FindMembership xmlns="http://www.msn.com/webservices/AddressBook">\r
2310         <serviceFilter>\r
2311             <Types>\r
2312                 <ServiceType>Messenger</ServiceType>\r
2313                 <ServiceType>Invitation</ServiceType>\r
2314                 <ServiceType>SocialNetwork</ServiceType>\r
2315                 <ServiceType>Space</ServiceType>\r
2316                 <ServiceType>Profile</ServiceType>\r
2317             </Types>\r
2318         </serviceFilter>\r
2319     </FindMembership>\r
2320 </soap:Body>\r
2321 </soap:Envelope>';\r
2322         $header_array = array(\r
2323             'SOAPAction: '.self::MEMBERSHIP_SOAP,\r
2324             'Content-Type: text/xml; charset=utf-8',\r
2325             'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'\r
2326         );\r
2327         $this->debug_message('*** URL: '.self::MEMBERSHIP_URL);\r
2328         $this->debug_message("*** Sending SOAP:\n$XML");\r
2329         $curl = curl_init();\r
2330         curl_setopt($curl, CURLOPT_URL, self::MEMBERSHIP_URL);\r
2331         curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);\r
2332         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
2333         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
2334         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
2335         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
2336         curl_setopt($curl, CURLOPT_POST, 1);\r
2337         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
2338         $data = curl_exec($curl);\r
2339         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
2340         curl_close($curl);\r
2341         $this->debug_message("*** Get Result:\n$data");\r
2342         \r
2343         if ($http_code != 200) return false;\r
2344         $p = $data;\r
2345         $aMemberships = array();\r
2346         while (1) {\r
2347             //$this->debug_message("search p = $p");\r
2348             $start = strpos($p, '<Membership>');\r
2349             $end = strpos($p, '</Membership>');\r
2350             if ($start === false || $end === false || $start > $end) break;\r
2351             //$this->debug_message("start = $start, end = $end");\r
2352             $end += 13;\r
2353             $sMembership = substr($p, $start, $end - $start);\r
2354             $aMemberships[] = $sMembership;\r
2355             //$this->debug_message("add sMembership = $sMembership");\r
2356             $p = substr($p, $end);\r
2357         }\r
2358         //$this->debug_message("aMemberships = ".var_export($aMemberships, true));\r
2359 \r
2360         $aContactList = array();\r
2361         foreach ($aMemberships as $sMembership) {\r
2362             //$this->debug_message("sMembership = $sMembership");\r
2363             if (isset($matches)) unset($matches);\r
2364             preg_match('#<MemberRole>(.*)</MemberRole>#', $sMembership, $matches);\r
2365             if (count($matches) == 0) continue;\r
2366             $sMemberRole = $matches[1];\r
2367             //$this->debug_message("MemberRole = $sMemberRole");\r
2368             if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue;\r
2369             $p = $sMembership;\r
2370             if (isset($aMembers)) unset($aMembers);\r
2371             $aMembers = array();\r
2372             while (1) {\r
2373                 //$this->debug_message("search p = $p");\r
2374                 $start = strpos($p, '<Member xsi:type="');\r
2375                 $end = strpos($p, '</Member>');\r
2376                 if ($start === false || $end === false || $start > $end) break;\r
2377                 //$this->debug_message("start = $start, end = $end");\r
2378                 $end += 9;\r
2379                 $sMember = substr($p, $start, $end - $start);\r
2380                 $aMembers[] = $sMember;\r
2381                 //$this->debug_message("add sMember = $sMember");\r
2382                 $p = substr($p, $end);\r
2383             }\r
2384             //$this->debug_message("aMembers = ".var_export($aMembers, true));\r
2385             foreach ($aMembers as $sMember) {\r
2386                 //$this->debug_message("sMember = $sMember");\r
2387                 if (isset($matches)) unset($matches);\r
2388                 preg_match('#<Member xsi\:type="([^"]*)">#', $sMember, $matches);\r
2389                 if (count($matches) == 0) continue;\r
2390                 $sMemberType = $matches[1];\r
2391                 //$this->debug_message("MemberType = $sMemberType");\r
2392                 $network = -1;\r
2393                 preg_match('#<MembershipId>(.*)</MembershipId>#', $sMember, $matches);\r
2394                 if (count($matches) == 0) continue;\r
2395                 $id = $matches[1];\r
2396                 if ($sMemberType == 'PassportMember') {\r
2397                     if (strpos($sMember, '<Type>Passport</Type>') === false) continue;\r
2398                     $network = 1;\r
2399                     preg_match('#<PassportName>(.*)</PassportName>#', $sMember, $matches);\r
2400                 }\r
2401                 else if ($sMemberType == 'EmailMember') {\r
2402                     if (strpos($sMember, '<Type>Email</Type>') === false) continue;\r
2403                     // Value is 32: or 32:YAHOO\r
2404                     preg_match('#<Annotation><Name>MSN.IM.BuddyType</Name><Value>(.*):(.*)</Value></Annotation>#', $sMember, $matches);\r
2405                     if (count($matches) == 0) continue;\r
2406                     if ($matches[1] != 32) continue;\r
2407                     $network = 32;\r
2408                     preg_match('#<Email>(.*)</Email>#', $sMember, $matches);\r
2409                 }\r
2410                 if ($network == -1) continue;\r
2411                 if (count($matches) > 0) {\r
2412                     $email = $matches[1];\r
2413                     @list($u_name, $u_domain) = @explode('@', $email);\r
2414                     if ($u_domain == NULL) continue;\r
2415                     $aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id;\r
2416                     $this->debug_message("*** Adding new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)");\r
2417                 }\r
2418             }\r
2419         }\r
2420         return $aContactList;\r
2421     }\r
2422     \r
2423     /**\r
2424      * MsnObj related methods\r
2425      */\r
2426     \r
2427     /**\r
2428      *\r
2429      * @param $FilePath åœ–檔路徑\r
2430      * @param $Type     æª”案類型 3=>大頭貼,2表情圖案\r
2431      * @return array\r
2432      */\r
2433     private function MsnObj($FilePath, $Type = 3) {\r
2434         if (!($FileSize=filesize($FilePath))) return '';\r
2435         $Location = md5($FilePath);\r
2436         $Friendly = md5($FilePath.$Type);\r
2437         if (isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location];\r
2438         $sha1d = base64_encode(sha1(file_get_contents($FilePath), true));\r
2439         $sha1c = base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d", true));\r
2440         $this->MsnObjArray[$Location] = $FilePath;\r
2441         $MsnObj = '<msnobj Creator="'.$this->user.'" Size="'.$FileSize.'" Type="'.$Type.'" Location="'.$Location.'" Friendly="'.$Friendly.'" SHA1D="'.$sha1d.'" SHA1C="'.$sha1c.'"/>';\r
2442         $this->MsnObjMap[$Location] = $MsnObj;\r
2443         $this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n");\r
2444         return $MsnObj;\r
2445     }\r
2446 \r
2447     private function GetPictureFilePath($Context) {\r
2448         $MsnObj = base64_decode($Context);\r
2449         if (preg_match('/location="(.*?)"/i', $MsnObj, $Match))\r
2450             $location = $Match[1];\r
2451         $this->debug_message("*** p2p: PictureFile[$location] ::All".print_r($this->MsnObjArray,true)."\n");\r
2452         if ($location && isset($this->MsnObjArray[$location]))\r
2453             return $this->MsnObjArray[$location];\r
2454         return false;\r
2455     }\r
2456 \r
2457     private function GetMsnObjDefine($Message) {\r
2458         $DefineString = '';\r
2459         if (is_array($this->Emotions))\r
2460             foreach ($this->Emotions as $Pattern => $FilePath) {\r
2461                 if (strpos($Message, $Pattern) !== false)\r
2462                 $DefineString .= "$Pattern\t".$this->MsnObj($FilePath, 2)."\t";\r
2463             }\r
2464         return $DefineString;\r
2465     }\r
2466     \r
2467     /**\r
2468      * Socket methods\r
2469      */\r
2470     \r
2471     /**\r
2472      * Read data of specified size from NS socket\r
2473      * \r
2474      * @param integer $size Size to read\r
2475      * @return string Data read\r
2476      */\r
2477     private function ns_readdata($size) {\r
2478         $data = '';\r
2479         $count = 0;\r
2480         while (!feof($this->NSfp)) {\r
2481             $buf = @fread($this->NSfp, $size - $count);\r
2482             $data .= $buf;\r
2483             $count += strlen($buf);\r
2484             if ($count >= $size) break;\r
2485         }\r
2486         $this->debug_message("NS: data ($size/$count) <<<\n$data");\r
2487         return $data;\r
2488     }\r
2489 \r
2490     /**\r
2491      * Read line from the NS socket\r
2492      * \r
2493      * @return string Data read\r
2494      */\r
2495     private function ns_readln() {\r
2496         $data = @fgets($this->NSfp, 4096);\r
2497         if ($data !== false) {\r
2498             $data = trim($data);\r
2499             $this->debug_message("NS: <<< $data");\r
2500         }\r
2501         return $data;\r
2502     }\r
2503 \r
2504     /**\r
2505      * Write line to NS socket\r
2506      * \r
2507      * Also increments id\r
2508      * \r
2509      * @param string $data Line to write to socket\r
2510      * @return mixed Bytes written or false on failure\r
2511      */\r
2512     private function ns_writeln($data) {\r
2513         $result = @fwrite($this->NSfp, $data."\r\n");\r
2514         if ($result !== false) {\r
2515             $this->debug_message("NS: >>> $data");\r
2516             $this->id++;\r
2517         }\r
2518         return $result;\r
2519     }\r
2520 \r
2521     /**\r
2522      * Write data to NS socket\r
2523      * \r
2524      * @param string $data Data to write to socket\r
2525      * @return mixed Bytes written or false on failure\r
2526      */\r
2527     private function ns_writedata($data) {\r
2528         $result = @fwrite($this->NSfp, $data);\r
2529         if ($result !== false) {\r
2530             $this->debug_message("NS: >>> $data");\r
2531         }\r
2532         return $result;\r
2533     }\r
2534 \r
2535     /**\r
2536      * Read data of specified size from given SB socket\r
2537      * \r
2538      * @param resource $socket SB socket\r
2539      * @param integer $size Size to read\r
2540      * @return string Data read\r
2541      */\r
2542     private function sb_readdata($socket, $size) {\r
2543         $data = '';\r
2544         $count = 0;\r
2545         while (!feof($socket)) {\r
2546             $buf = @fread($socket, $size - $count);\r
2547             $data .= $buf;\r
2548             $count += strlen($buf);\r
2549             if ($count >= $size) break;\r
2550         }\r
2551         $this->debug_message("SB: data ($size/$count) <<<\n$data");\r
2552         return $data;\r
2553     }\r
2554 \r
2555     /**\r
2556      * Read line from given SB socket\r
2557      * \r
2558      * @param resource $socket SB Socket\r
2559      * @return string Line read\r
2560      */\r
2561     private function sb_readln($socket) {\r
2562         $data = @fgets($socket, 4096);\r
2563         if ($data !== false) {\r
2564             $data = trim($data);\r
2565             $this->debug_message("SB: <<< $data");\r
2566         }\r
2567         return $data;\r
2568     }\r
2569 \r
2570     /**\r
2571      * Write line to given SB socket\r
2572      * \r
2573      * Also increments id\r
2574      * \r
2575      * @param resource $socket SB socket\r
2576      * @param integer $id Reference to SB id\r
2577      * @param string $data Line to write\r
2578      * @return mixed Bytes written or false on error\r
2579      */\r
2580     private function sb_writeln($socket, &$id, $data) {\r
2581         $result = @fwrite($socket, $data."\r\n");\r
2582         if ($result !== false) {\r
2583             $this->debug_message("SB: >>> $data");\r
2584             $id++;\r
2585         }\r
2586         return $result;\r
2587     }\r
2588 \r
2589     /**\r
2590      * Write data to given SB socket\r
2591      * \r
2592      * @param resource $socket SB socket\r
2593      * @param $data Data to write to socket\r
2594      * @return mixed Bytes written or false on error\r
2595      */\r
2596     private function sb_writedata($socket, $data) {\r
2597         $result = @fwrite($socket, $data);\r
2598         if ($result !== false) {\r
2599             $this->debug_message("SB: >>> $data");\r
2600         }\r
2601         return $result;\r
2602     }\r
2603 \r
2604     /**\r
2605      * Get all the sockets currently in use\r
2606      *\r
2607      * @return array Array of socket resources\r
2608      */\r
2609     public function getSockets() {\r
2610         return array_merge(array($this->NSfp), $this->switchBoardSessionLookup);\r
2611     }\r
2612 \r
2613     /**\r
2614      * Checks socket for end of file\r
2615      *\r
2616      * @param resource $socket Socket to check\r
2617      * @return boolean true if end of file (socket)\r
2618      */\r
2619     private static function socketcheck($socket){\r
2620         $info = stream_get_meta_data($socket);\r
2621         return $info['eof'];\r
2622     }\r
2623     \r
2624     /**\r
2625      * Key generation methods\r
2626      */\r
2627     \r
2628     private function derive_key($key, $magic) {\r
2629         $hash1 = mhash(MHASH_SHA1, $magic, $key);\r
2630         $hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key);\r
2631         $hash3 = mhash(MHASH_SHA1, $hash1, $key);\r
2632         $hash4 = mhash(MHASH_SHA1, $hash3.$magic, $key);\r
2633         return $hash2.substr($hash4, 0, 4);\r
2634     }\r
2635 \r
2636     private function generateLoginBLOB($key, $challenge) {\r
2637         $key1 = base64_decode($key);\r
2638         $key2 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY HASH');\r
2639         $key3 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY ENCRYPTION');\r
2640 \r
2641         // get hash of challenge using key2\r
2642         $hash = mhash(MHASH_SHA1, $challenge, $key2);\r
2643 \r
2644         // get 8 bytes random data\r
2645         $iv = substr(base64_encode(rand(1000,9999).rand(1000,9999)), 2, 8);\r
2646 \r
2647         $cipher = mcrypt_cbc(MCRYPT_3DES, $key3, $challenge."\x08\x08\x08\x08\x08\x08\x08\x08", MCRYPT_ENCRYPT, $iv);\r
2648 \r
2649         $blob = pack('LLLLLLL', 28, 1, 0x6603, 0x8004, 8, 20, 72);\r
2650         $blob .= $iv;\r
2651         $blob .= $hash;\r
2652         $blob .= $cipher;\r
2653 \r
2654         return base64_encode($blob);\r
2655     }\r
2656     \r
2657     /**\r
2658     * Generate challenge response\r
2659     *\r
2660     * @param string $code\r
2661     * @return string challenge response code\r
2662     */\r
2663     private function getChallenge($code) {\r
2664         // MSNP15\r
2665         // http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges\r
2666         // Step 1: The MD5 Hash\r
2667         $md5Hash = md5($code.self::PROD_KEY);\r
2668         $aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0"));\r
2669         for ($i = 0; $i < 4; $i++) {\r
2670             $aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0"))));\r
2671             $aMD5[$i] = (0 + base_convert($aMD5[$i], 16, 10)) & 0x7FFFFFFF;\r
2672         }\r
2673 \r
2674         // Step 2: A new string\r
2675         $chl_id = $code.self::PROD_ID;\r
2676         $chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8));\r
2677 \r
2678         $aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1));\r
2679         for ($i = 0; $i < count($aID); $i++) {\r
2680             $aID[$i] = implode('', array_reverse(@explode("\0", chunk_split($aID[$i], 1, "\0"))));\r
2681             $aID[$i] = 0 + base_convert(bin2hex($aID[$i]), 16, 10);\r
2682         }\r
2683 \r
2684         // Step 3: The 64 bit key\r
2685         $magic_num = 0x0E79A9C1;\r
2686         $str7f = 0x7FFFFFFF;\r
2687         $high = 0;\r
2688         $low = 0;\r
2689         for ($i = 0; $i < count($aID); $i += 2) {\r
2690             $temp = $aID[$i];\r
2691             $temp = bcmod(bcmul($magic_num, $temp), $str7f);\r
2692             $temp = bcadd($temp, $high);\r
2693             $temp = bcadd(bcmul($aMD5[0], $temp), $aMD5[1]);\r
2694             $temp = bcmod($temp, $str7f);\r
2695 \r
2696             $high = $aID[$i+1];\r
2697             $high = bcmod(bcadd($high, $temp), $str7f);\r
2698             $high = bcadd(bcmul($aMD5[2], $high), $aMD5[3]);\r
2699             $high = bcmod($high, $str7f);\r
2700 \r
2701             $low = bcadd(bcadd($low, $high), $temp);\r
2702         }\r
2703 \r
2704         $high = bcmod(bcadd($high, $aMD5[1]), $str7f);\r
2705         $low = bcmod(bcadd($low, $aMD5[3]), $str7f);\r
2706 \r
2707         $new_high = bcmul($high & 0xFF, 0x1000000);\r
2708         $new_high = bcadd($new_high, bcmul($high & 0xFF00, 0x100));\r
2709         $new_high = bcadd($new_high, bcdiv($high & 0xFF0000, 0x100));\r
2710         $new_high = bcadd($new_high, bcdiv($high & 0xFF000000, 0x1000000));\r
2711         // we need integer here\r
2712         $high = 0+$new_high;\r
2713 \r
2714         $new_low = bcmul($low & 0xFF, 0x1000000);\r
2715         $new_low = bcadd($new_low, bcmul($low & 0xFF00, 0x100));\r
2716         $new_low = bcadd($new_low, bcdiv($low & 0xFF0000, 0x100));\r
2717         $new_low = bcadd($new_low, bcdiv($low & 0xFF000000, 0x1000000));\r
2718         // we need integer here\r
2719         $low = 0+$new_low;\r
2720 \r
2721         // we just use 32 bits integer, don't need the key, just high/low\r
2722         // $key = bcadd(bcmul($high, 0x100000000), $low);\r
2723 \r
2724         // Step 4: Using the key\r
2725         $md5Hash = md5($code.self::PROD_KEY);\r
2726         $aHash = @explode("\0", chunk_split($md5Hash, 8, "\0"));\r
2727 \r
2728         $hash = '';\r
2729         $hash .= sprintf("%08x", (0 + base_convert($aHash[0], 16, 10)) ^ $high);\r
2730         $hash .= sprintf("%08x", (0 + base_convert($aHash[1], 16, 10)) ^ $low);\r
2731         $hash .= sprintf("%08x", (0 + base_convert($aHash[2], 16, 10)) ^ $high);\r
2732         $hash .= sprintf("%08x", (0 + base_convert($aHash[3], 16, 10)) ^ $low);\r
2733 \r
2734         return $hash;\r
2735     }\r
2736     \r
2737     /**\r
2738      * Utility methods\r
2739      */\r
2740     \r
2741     private function Array2SoapVar($Array, $ReturnSoapVarObj = true, $TypeName = null, $TypeNameSpace = null) {\r
2742         $ArrayString = '';\r
2743         foreach($Array as $Key => $Val) {\r
2744             if ($Key{0} == ':') continue;\r
2745             $Attrib = '';\r
2746             if (is_array($Val[':'])) {\r
2747                 foreach ($Val[':'] as $AttribName => $AttribVal)\r
2748                 $Attrib .= " $AttribName = '$AttribVal'";\r
2749             }\r
2750             if ($Key{0} == '!') {\r
2751                 //List Type Define\r
2752                 $Key = substr($Key,1);\r
2753                 foreach ($Val as $ListKey => $ListVal) {\r
2754                     if ($ListKey{0} == ':') continue;\r
2755                     if (is_array($ListVal)) $ListVal = $this->Array2SoapVar($ListVal, false);\r
2756                     elseif (is_bool($ListVal)) $ListVal = $ListVal ? 'true' : 'false';\r
2757                     $ArrayString .= "<$Key$Attrib>$ListVal</$Key>";\r
2758                 }\r
2759                 continue;\r
2760             }\r
2761             if (is_array($Val)) $Val = $this->Array2SoapVar($Val, false);\r
2762             elseif (is_bool($Val)) $Val = $Val ? 'true' : 'false';\r
2763             $ArrayString .= "<$Key$Attrib>$Val</$Key>";\r
2764         }\r
2765         if ($ReturnSoapVarObj) return new SoapVar($ArrayString, XSD_ANYXML, $TypeName, $TypeNameSpace);\r
2766         return $ArrayString;\r
2767     }\r
2768     \r
2769     private function linetoArray($lines) {\r
2770         $lines = str_replace("\r", '', $lines);\r
2771         $lines = explode("\n", $lines);\r
2772         foreach ($lines as $line) {\r
2773             if (!isset($line{3})) continue;\r
2774             list($Key, $Val) = explode(':', $line);\r
2775             $Data[trim($Key)] = trim($Val);\r
2776         }\r
2777         return $Data;\r
2778     }\r
2779     \r
2780     /**\r
2781     * Get Passport ticket\r
2782     *\r
2783     * @param string $url URL string (Optional)\r
2784     * @return mixed Array of tickets or false on failure\r
2785     */\r
2786     private function get_passport_ticket($url = '') {\r
2787         $user = $this->user;\r
2788         $password = htmlspecialchars($this->password);\r
2789 \r
2790         if ($url === '')\r
2791             $passport_url = self::PASSPORT_URL;\r
2792         else\r
2793             $passport_url = $url;\r
2794 \r
2795         $XML = '<?xml version="1.0" encoding="UTF-8"?>\r
2796 <Envelope xmlns="http://schemas.xmlsoap.org/soap/envelope/"\r
2797           xmlns:wsse="http://schemas.xmlsoap.org/ws/2003/06/secext"\r
2798           xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"\r
2799           xmlns:wsp="http://schemas.xmlsoap.org/ws/2002/12/policy"\r
2800           xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"\r
2801           xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/03/addressing"\r
2802           xmlns:wssc="http://schemas.xmlsoap.org/ws/2004/04/sc"\r
2803           xmlns:wst="http://schemas.xmlsoap.org/ws/2004/04/trust">\r
2804 <Header>\r
2805   <ps:AuthInfo xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="PPAuthInfo">\r
2806     <ps:HostingApp>{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}</ps:HostingApp>\r
2807     <ps:BinaryVersion>4</ps:BinaryVersion>\r
2808     <ps:UIVersion>1</ps:UIVersion>\r
2809     <ps:Cookies></ps:Cookies>\r
2810     <ps:RequestParams>AQAAAAIAAABsYwQAAAAxMDMz</ps:RequestParams>\r
2811   </ps:AuthInfo>\r
2812   <wsse:Security>\r
2813     <wsse:UsernameToken Id="user">\r
2814       <wsse:Username>'.$user.'</wsse:Username>\r
2815       <wsse:Password>'.$password.'</wsse:Password>\r
2816     </wsse:UsernameToken>\r
2817   </wsse:Security>\r
2818 </Header>\r
2819 <Body>\r
2820   <ps:RequestMultipleSecurityTokens xmlns:ps="http://schemas.microsoft.com/Passport/SoapServices/PPCRL" Id="RSTS">\r
2821     <wst:RequestSecurityToken Id="RST0">\r
2822       <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\r
2823       <wsp:AppliesTo>\r
2824         <wsa:EndpointReference>\r
2825           <wsa:Address>http://Passport.NET/tb</wsa:Address>\r
2826         </wsa:EndpointReference>\r
2827       </wsp:AppliesTo>\r
2828     </wst:RequestSecurityToken>\r
2829     <wst:RequestSecurityToken Id="RST1">\r
2830       <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\r
2831       <wsp:AppliesTo>\r
2832         <wsa:EndpointReference>\r
2833           <wsa:Address>messengerclear.live.com</wsa:Address>\r
2834         </wsa:EndpointReference>\r
2835       </wsp:AppliesTo>\r
2836       <wsse:PolicyReference URI="'.$this->passport_policy.'"></wsse:PolicyReference>\r
2837     </wst:RequestSecurityToken>\r
2838     <wst:RequestSecurityToken Id="RST2">\r
2839       <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\r
2840       <wsp:AppliesTo>\r
2841         <wsa:EndpointReference>\r
2842           <wsa:Address>messenger.msn.com</wsa:Address>\r
2843         </wsa:EndpointReference>\r
2844       </wsp:AppliesTo>\r
2845       <wsse:PolicyReference URI="?id=507"></wsse:PolicyReference>\r
2846     </wst:RequestSecurityToken>\r
2847     <wst:RequestSecurityToken Id="RST3">\r
2848       <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\r
2849       <wsp:AppliesTo>\r
2850         <wsa:EndpointReference>\r
2851           <wsa:Address>contacts.msn.com</wsa:Address>\r
2852         </wsa:EndpointReference>\r
2853       </wsp:AppliesTo>\r
2854       <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>\r
2855     </wst:RequestSecurityToken>\r
2856     <wst:RequestSecurityToken Id="RST4">\r
2857       <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\r
2858       <wsp:AppliesTo>\r
2859         <wsa:EndpointReference>\r
2860           <wsa:Address>messengersecure.live.com</wsa:Address>\r
2861         </wsa:EndpointReference>\r
2862       </wsp:AppliesTo>\r
2863       <wsse:PolicyReference URI="MBI_SSL"></wsse:PolicyReference>\r
2864     </wst:RequestSecurityToken>\r
2865     <wst:RequestSecurityToken Id="RST5">\r
2866       <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\r
2867       <wsp:AppliesTo>\r
2868         <wsa:EndpointReference>\r
2869           <wsa:Address>spaces.live.com</wsa:Address>\r
2870         </wsa:EndpointReference>\r
2871       </wsp:AppliesTo>\r
2872       <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>\r
2873     </wst:RequestSecurityToken>\r
2874     <wst:RequestSecurityToken Id="RST6">\r
2875       <wst:RequestType>http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue</wst:RequestType>\r
2876       <wsp:AppliesTo>\r
2877         <wsa:EndpointReference>\r
2878           <wsa:Address>storage.msn.com</wsa:Address>\r
2879         </wsa:EndpointReference>\r
2880       </wsp:AppliesTo>\r
2881       <wsse:PolicyReference URI="MBI"></wsse:PolicyReference>\r
2882     </wst:RequestSecurityToken>\r
2883   </ps:RequestMultipleSecurityTokens>\r
2884 </Body>\r
2885 </Envelope>';\r
2886 \r
2887         $this->debug_message("*** URL: $passport_url");\r
2888         $this->debug_message("*** Sending SOAP:\n$XML");\r
2889         $curl = curl_init();\r
2890         curl_setopt($curl, CURLOPT_URL, $passport_url);\r
2891         if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);\r
2892         curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);\r
2893         curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);\r
2894         curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);\r
2895         curl_setopt($curl, CURLOPT_POST, 1);\r
2896         curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);\r
2897         $data = curl_exec($curl);\r
2898         $http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);\r
2899         curl_close($curl);\r
2900         $this->debug_message("*** Get Result:\n$data");\r
2901 \r
2902         if ($http_code != 200) {\r
2903             // sometimes, redirect to another URL\r
2904             // MSNP15\r
2905             //<faultcode>psf:Redirect</faultcode>\r
2906             //<psf:redirectUrl>https://msnia.login.live.com/pp450/RST.srf</psf:redirectUrl>\r
2907             //<faultstring>Authentication Failure</faultstring>\r
2908             if (strpos($data, '<faultcode>psf:Redirect</faultcode>') === false) {\r
2909                 $this->debug_message("*** Could not get passport ticket! http code = $http_code");\r
2910                 return false;\r
2911             }\r
2912             preg_match("#<psf\:redirectUrl>(.*)</psf\:redirectUrl>#", $data, $matches);\r
2913             if (count($matches) == 0) {\r
2914                 $this->debug_message('*** Redirected, but could not get redirect URL!');\r
2915                 return false;\r
2916             }\r
2917             $redirect_url = $matches[1];\r
2918             if ($redirect_url == $passport_url) {\r
2919                 $this->debug_message('*** Redirected, but to same URL!');\r
2920                 return false;\r
2921             }\r
2922             $this->debug_message("*** Redirected to $redirect_url");\r
2923             return $this->get_passport_ticket($redirect_url);\r
2924         }\r
2925 \r
2926         // sometimes, redirect to another URL, also return 200\r
2927         // MSNP15\r
2928         //<faultcode>psf:Redirect</faultcode>\r
2929         //<psf:redirectUrl>https://msnia.login.live.com/pp450/RST.srf</psf:redirectUrl>\r
2930         //<faultstring>Authentication Failure</faultstring>\r
2931         if (strpos($data, '<faultcode>psf:Redirect</faultcode>') !== false) {\r
2932             preg_match("#<psf\:redirectUrl>(.*)</psf\:redirectUrl>#", $data, $matches);\r
2933             if (count($matches) != 0) {\r
2934                 $redirect_url = $matches[1];\r
2935                 if ($redirect_url == $passport_url) {\r
2936                     $this->debug_message('*** Redirected, but to same URL!');\r
2937                     return false;\r
2938                 }\r
2939                 $this->debug_message("*** Redirected to $redirect_url");\r
2940                 return $this->get_passport_ticket($redirect_url);\r
2941             }\r
2942         }\r
2943 \r
2944         // no Redurect faultcode or URL\r
2945         // we should get the ticket here\r
2946 \r
2947         // we need ticket and secret code\r
2948         // RST1: messengerclear.live.com\r
2949         // <wsse:BinarySecurityToken Id="Compact1">t=tick&p=</wsse:BinarySecurityToken>\r
2950         // <wst:BinarySecret>binary secret</wst:BinarySecret>\r
2951         // RST2: messenger.msn.com\r
2952         // <wsse:BinarySecurityToken Id="PPToken2">t=tick</wsse:BinarySecurityToken>\r
2953         // RST3: contacts.msn.com\r
2954         // <wsse:BinarySecurityToken Id="Compact3">t=tick&p=</wsse:BinarySecurityToken>\r
2955         // RST4: messengersecure.live.com\r
2956         // <wsse:BinarySecurityToken Id="Compact4">t=tick&p=</wsse:BinarySecurityToken>\r
2957         // RST5: spaces.live.com\r
2958         // <wsse:BinarySecurityToken Id="Compact5">t=tick&p=</wsse:BinarySecurityToken>\r
2959         // RST6: storage.msn.com\r
2960         // <wsse:BinarySecurityToken Id="Compact6">t=tick&p=</wsse:BinarySecurityToken>\r
2961         preg_match("#".\r
2962             "<wsse\:BinarySecurityToken Id=\"Compact1\">(.*)</wsse\:BinarySecurityToken>(.*)".\r
2963             "<wst\:BinarySecret>(.*)</wst\:BinarySecret>(.*)".\r
2964             "<wsse\:BinarySecurityToken Id=\"PPToken2\">(.*)</wsse\:BinarySecurityToken>(.*)".\r
2965             "<wsse\:BinarySecurityToken Id=\"Compact3\">(.*)</wsse\:BinarySecurityToken>(.*)".\r
2966             "<wsse\:BinarySecurityToken Id=\"Compact4\">(.*)</wsse\:BinarySecurityToken>(.*)".\r
2967             "<wsse\:BinarySecurityToken Id=\"Compact5\">(.*)</wsse\:BinarySecurityToken>(.*)".\r
2968             "<wsse\:BinarySecurityToken Id=\"Compact6\">(.*)</wsse\:BinarySecurityToken>(.*)".\r
2969             "#",\r
2970         $data, $matches);\r
2971 \r
2972         // no ticket found!\r
2973         if (count($matches) == 0) {\r
2974             $this->debug_message('*** Could not get passport ticket!');\r
2975             return false;\r
2976         }\r
2977 \r
2978         //$this->debug_message(var_export($matches, true));\r
2979         // matches[0]: all data\r
2980         // matches[1]: RST1 (messengerclear.live.com) ticket\r
2981         // matches[2]: ...\r
2982         // matches[3]: RST1 (messengerclear.live.com) binary secret\r
2983         // matches[4]: ...\r
2984         // matches[5]: RST2 (messenger.msn.com) ticket\r
2985         // matches[6]: ...\r
2986         // matches[7]: RST3 (contacts.msn.com) ticket\r
2987         // matches[8]: ...\r
2988         // matches[9]: RST4 (messengersecure.live.com) ticket\r
2989         // matches[10]: ...\r
2990         // matches[11]: RST5 (spaces.live.com) ticket\r
2991         // matches[12]: ...\r
2992         // matches[13]: RST6 (storage.live.com) ticket\r
2993         // matches[14]: ...\r
2994 \r
2995         // so\r
2996         // ticket => $matches[1]\r
2997         // secret => $matches[3]\r
2998         // web_ticket => $matches[5]\r
2999         // contact_ticket => $matches[7]\r
3000         // oim_ticket => $matches[9]\r
3001         // space_ticket => $matches[11]\r
3002         // storage_ticket => $matches[13]\r
3003 \r
3004         // yes, we get ticket\r
3005         $aTickets = array(\r
3006             'ticket' => html_entity_decode($matches[1]),\r
3007             'secret' => html_entity_decode($matches[3]),\r
3008             'web_ticket' => html_entity_decode($matches[5]),\r
3009             'contact_ticket' => html_entity_decode($matches[7]),\r
3010             'oim_ticket' => html_entity_decode($matches[9]),\r
3011             'space_ticket' => html_entity_decode($matches[11]),\r
3012             'storage_ticket' => html_entity_decode($matches[13])\r
3013         );\r
3014         $this->ticket = $aTickets;\r
3015         //$this->debug_message(var_export($aTickets, true));\r
3016         $ABAuthHeaderArray = array(\r
3017             'ABAuthHeader' => array(\r
3018                 ':' => array('xmlns' => 'http://www.msn.com/webservices/AddressBook'),\r
3019                 'ManagedGroupRequest' => false,\r
3020                 'TicketToken' => htmlspecialchars($this->ticket['contact_ticket']),\r
3021             )\r
3022         );\r
3023         $this->ABAuthHeader = new SoapHeader('http://www.msn.com/webservices/AddressBook', 'ABAuthHeader', $this->Array2SoapVar($ABAuthHeaderArray));\r
3024         return $aTickets;\r
3025     }\r
3026     \r
3027     /**\r
3028     * Generate the data to send a message\r
3029     *\r
3030     * @param string $sMessage Message\r
3031     * @param integer $network Network\r
3032     * @return string Message data\r
3033     */\r
3034     private function getMessage($sMessage, $network = 1) {\r
3035         $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";\r
3036         $msg_header_len = strlen($msg_header);\r
3037         if ($network == 1)\r
3038             $maxlen = self::MAX_MSN_MESSAGE_LEN - $msg_header_len;\r
3039         else\r
3040             $maxlen = self::MAX_YAHOO_MESSAGE_LEN - $msg_header_len;\r
3041         $sMessage = str_replace("\r", '', $sMessage);\r
3042         $msg = substr($sMessage, 0, $maxlen);\r
3043         return $msg_header.$msg;\r
3044     }\r
3045     \r
3046     /**\r
3047     * Sleep for the given number of seconds\r
3048     *\r
3049     * @param integer $wait Number of seconds to sleep for\r
3050     */\r
3051     private function NSRetryWait($wait) {\r
3052         $this->debug_message("*** Sleeping for $wait seconds before retrying");\r
3053         sleep($wait);\r
3054     }\r
3055     \r
3056     /**\r
3057      * Sends a ping command\r
3058      *\r
3059      * Should be called about every 50 seconds\r
3060      *\r
3061      * @return void\r
3062      */\r
3063     public function sendPing() {\r
3064         // NS: >>> PNG\r
3065         $this->ns_writeln("PNG");\r
3066     }\r
3067     \r
3068     /**\r
3069     * Methods to add / call callbacks\r
3070     */\r
3071 \r
3072     /**\r
3073      * Calls User Handler\r
3074      *\r
3075      * Calls registered handler for a specific event.\r
3076      *\r
3077      * @param string $event Command (event) name (Rvous etc)\r
3078      * @param array $data Data\r
3079      * @see registerHandler\r
3080      * @return void\r
3081      */\r
3082     private function callHandler($event, $data = NULL) {\r
3083         if (isset($this->myEventHandlers[$event])) {\r
3084             if ($data !== NULL) {\r
3085                 call_user_func($this->myEventHandlers[$event], $data);\r
3086             } else {\r
3087                 call_user_func($this->myEventHandlers[$event]);\r
3088             }\r
3089         }\r
3090     }\r
3091 \r
3092     /**\r
3093      * Registers a user handler\r
3094      *\r
3095      * Handler List\r
3096      * IMIn, Pong, ConnectFailed, Reconnect,\r
3097      * AddedToList, RemovedFromList, StatusChange\r
3098      *\r
3099      * @param string $event Event name\r
3100      * @param string $handler User function to call\r
3101      * @see callHandler\r
3102      * @return boolean true if successful\r
3103      */\r
3104     public function registerHandler($event, $handler) {\r
3105         if (is_callable($handler)) {\r
3106             $this->myEventHandlers[$event] = $handler;\r
3107             return true;\r
3108         } else {\r
3109             return false;\r
3110         }\r
3111     }\r
3112     \r
3113     /**\r
3114      * Debugging methods\r
3115      */\r
3116     \r
3117     /**\r
3118      * Print message if debugging is enabled\r
3119      * \r
3120      * @param string $str Message to print\r
3121      */\r
3122     private function debug_message($str) {\r
3123         if (!$this->debug) return;\r
3124         echo $str."\n";\r
3125     }\r
3126     \r
3127     /**\r
3128      * Dump binary data\r
3129      * \r
3130      * @param string $str Data string\r
3131      * @return Binary data\r
3132      */\r
3133     private function dump_binary($str) {\r
3134         $buf = '';\r
3135         $a_str = '';\r
3136         $h_str = '';\r
3137         $len = strlen($str);\r
3138         for ($i = 0; $i < $len; $i++) {\r
3139             if (($i % 16) == 0) {\r
3140                 if ($buf !== '') {\r
3141                     $buf .= "$h_str $a_str\n";\r
3142                 }\r
3143                 $buf .= sprintf("%04X:", $i);\r
3144                 $a_str = '';\r
3145                 $h_str = '';\r
3146             }\r
3147             $ch = ord($str[$i]);\r
3148             if ($ch < 32)\r
3149             $a_str .= '.';\r
3150             else\r
3151             $a_str .= chr($ch);\r
3152             $h_str .= sprintf(" %02X", $ch);\r
3153         }\r
3154         if ($h_str !== '')\r
3155         $buf .= "$h_str $a_str\n";\r
3156         return $buf;\r
3157     }\r
3158 }\r