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