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