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