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