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