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