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