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