Main Process,1 => sb_control_process,2 => sb_ring_process
private $SwitchBoardSessionUser=false;
private $SwitchBoardMessageQueue=array();
private $ABAuthHeader;
private $ABService;
private $Contacts;
private $IgnoreList;
public $server = 'messenger.hotmail.com';
public $port = 1863;
public $clientid = '';
public $oim_maildata_url = 'https://rsi.hotmail.com/rsi/rsi.asmx';
public $oim_maildata_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMetadata';
public $oim_read_url = 'https://rsi.hotmail.com/rsi/rsi.asmx';
public $oim_read_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/GetMessage';
public $oim_del_url = 'https://rsi.hotmail.com/rsi/rsi.asmx';
public $oim_del_soap = 'http://www.hotmail.msn.com/ws/2004/09/oim/rsi/DeleteMessages';
public $membership_url = 'https://contacts.msn.com/abservice/SharingService.asmx';
public $membership_soap = 'http://www.msn.com/webservices/AddressBook/FindMembership';
public $addmember_url = 'https://contacts.msn.com/abservice/SharingService.asmx';
public $addmember_soap = 'http://www.msn.com/webservices/AddressBook/AddMember';
public $addcontact_url = 'https://contacts.msn.com/abservice/abservice.asmx';
public $addcontact_soap = 'http://www.msn.com/webservices/AddressBook/ABContactAdd';
public $delmember_url = 'https://contacts.msn.com/abservice/SharingService.asmx';
public $delmember_soap = 'http://www.msn.com/webservices/AddressBook/DeleteMember';
public $error = '';
public $authed = false;
public $oim_try = 3;
public $log_file = '';
public $log_path = false;
public $font_fn = 'Arial';
public $font_co = '333333';
public $font_ef = '';
// the message length (include header) is limited (maybe since WLM 8.5 released)
// for WLM: 1664 bytes
// for YIM: 518 bytes
public $max_msn_message_len = 1664;
public $max_yahoo_message_len = 518;
// Begin added for StatusNet
private $aContactList = array();
private $switchBoardSessions = array();
/**
* Event Handler Functions
*/
private $myEventHandlers = array();
// End added for StatusNet
private function Array2SoapVar($Array,$ReturnSoapVarObj=true,$TypeName=null,$TypeNameSpace=null)
{
$ArrayString='';
foreach($Array as $Key => $Val)
{
if($Key{0}==':') continue;
$Attrib='';
if(is_array($Val[':']))
{
foreach($Val[':'] as $AttribName => $AttribVal)
$Attrib.=" $AttribName='$AttribVal'";
}
if($Key{0}=='!')
{
//List Type Define
$Key=substr($Key,1);
foreach($Val as $ListKey => $ListVal)
{
if($ListKey{0}==':') continue;
if(is_array($ListVal)) $ListVal=$this->Array2SoapVar($ListVal,false);
elseif(is_bool($ListVal)) $ListVal=$ListVal?'true':'false';
$ArrayString.="<$Key$Attrib>$ListVal$Key>";
}
continue;
}
if(is_array($Val)) $Val=$this->Array2SoapVar($Val,false);
elseif(is_bool($Val)) $Val=$Val?'true':'false';
$ArrayString.="<$Key$Attrib>$Val$Key>";
}
if($ReturnSoapVarObj) return new SoapVar($ArrayString,XSD_ANYXML,$TypeName,$TypeNameSpace);
return $ArrayString;
}
public function End()
{
$this->log_message("*** someone kill me ***");
$this->kill_me=true;
}
private function IsIgnoreMail($Email)
{
if($this->IgnoreList==false) return false;
foreach($this->IgnoreList as $Pattern)
{
if(preg_match($Pattern,$Email)) return true;
}
return false;
}
public function __construct ($Configs=array(), $timeout = 15, $client_id = 0x7000800C)
{
$this->user = $Configs['user'];
$this->password = $Configs['password'];
$this->alias = isset($Configs['alias']) ? $Configs['alias'] : '';
$this->psm = isset($Configs['psm']) ? $Configs['psm'] : '';
$my_add_function = isset($Configs['add_user_function']) ? $Configs['add_user_function'] : false;
$my_rem_function = isset($Configs['remove_user_function']) ? $Configs['remove_user_function'] : false;
$this->use_ping = isset($Configs['use_ping']) ? $Configs['use_ping'] : false;
$this->retry_wait = isset($Configs['retry_wait']) ? $Configs['retry_wait'] : 30;
$this->backup_file = isset($Configs['backup_file']) ? $Configs['backup_file'] : true;
$this->update_pending = isset($Configs['update_pending']) ? $Configs['update_pending'] : true;
$this->PhotoStickerFile=$Configs['PhotoSticker'];
$this->IgnoreList=isset($Configs['IgnoreList'])?$Configs['IgnoreList']:false;
if($this->Emotions = isset($Configs['Emotions']) ? $Configs['Emotions']:false)
{
foreach($this->Emotions as $EmotionFilePath)
$this->MsnObj($EmotionFilePath,$Type=2);
}
$this->debug = isset($Configs['debug']) ? $Configs['debug'] : false;
$this->timeout = $timeout;
// check support
if (!function_exists('curl_init')) throw new Exception("We need curl module!\n");
if (!function_exists('preg_match')) throw new Exception("We need pcre module!\n");
if (!function_exists('mhash')) throw new Exception("We need mhash module!\n");
if (!function_exists('mcrypt_cbc')) throw new Exception("We need mcrypt module!\n");
if (!function_exists('bcmod')) throw new Exception("We need bcmath module for $protocol!\n");
/*
http://msnpiki.msnfanatic.com/index.php/Client_ID
Client ID for MSN:
normal MSN 8.1 clientid is:
01110110 01001100 11000000 00101100
= 0x764CC02C
we just use following:
* 0x04: Your client can send/receive Ink (GIF format)
* 0x08: Your client can send/recieve Ink (ISF format)
* 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')
* 0x70000000: This is the value for MSNC7 (WL Msgr 8.1)
= 0x7000800C;
*/
$this->clientid = $client_id;
$this->windows =(strtoupper(substr(PHP_OS, 0, 3)) === 'WIN');
$this->ABService=new SoapClient(realpath(dirname(__FILE__)).'/soap/msnab_sharingservice.wsdl',array('trace' => 1));
}
private function get_passport_ticket($url = '')
{
$user = $this->user;
$password = htmlspecialchars($this->password);
if ($url === '')
$passport_url = $this->passport_url;
else
$passport_url = $url;
$XML = '
{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}
4
1
AQAAAAIAAABsYwQAAAAxMDMz
'.$user.'
'.$password.'
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
http://Passport.NET/tb
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
messengerclear.live.com
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
messenger.msn.com
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
contacts.msn.com
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
messengersecure.live.com
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
spaces.live.com
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
storage.msn.com
';
$this->debug_message("*** URL: $passport_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $passport_url);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if ($http_code != 200) {
// sometimes, rediret to another URL
// MSNP15
//psf:Redirect
//https://msnia.login.live.com/pp450/RST.srf
//Authentication Failure
if (strpos($data, 'psf:Redirect') === false) {
$this->debug_message("*** Can't get passport ticket! http code = $http_code");
return false;
}
preg_match("#(.*)#", $data, $matches);
if (count($matches) == 0) {
$this->debug_message("*** redirect, but can't get redirect URL!");
return false;
}
$redirect_url = $matches[1];
if ($redirect_url == $passport_url) {
$this->debug_message("*** redirect, but redirect to same URL!");
return false;
}
$this->debug_message("*** redirect to $redirect_url");
return $this->get_passport_ticket($redirect_url);
}
// sometimes, rediret to another URL, also return 200
// MSNP15
//psf:Redirect
//https://msnia.login.live.com/pp450/RST.srf
//Authentication Failure
if (strpos($data, 'psf:Redirect') !== false) {
preg_match("#(.*)#", $data, $matches);
if (count($matches) != 0) {
$redirect_url = $matches[1];
if ($redirect_url == $passport_url) {
$this->debug_message("*** redirect, but redirect to same URL!");
return false;
}
$this->debug_message("*** redirect to $redirect_url");
return $this->get_passport_ticket($redirect_url);
}
}
// no Redurect faultcode or URL
// we should get the ticket here
// we need ticket and secret code
// RST1: messengerclear.live.com
// t=tick&p=
// binary secret
// RST2: messenger.msn.com
// t=tick
// RST3: contacts.msn.com
// t=tick&p=
// RST4: messengersecure.live.com
// t=tick&p=
// RST5: spaces.live.com
// t=tick&p=
// RST6: storage.msn.com
// t=tick&p=
preg_match("#".
"(.*)(.*)".
"(.*)(.*)".
"(.*)(.*)".
"(.*)(.*)".
"(.*)(.*)".
"(.*)(.*)".
"(.*)(.*)".
"#",
$data, $matches);
// no ticket found!
if (count($matches) == 0) {
$this->debug_message("*** Can't get passport ticket!");
return false;
}
//$this->debug_message(var_export($matches, true));
// matches[0]: all data
// matches[1]: RST1 (messengerclear.live.com) ticket
// matches[2]: ...
// matches[3]: RST1 (messengerclear.live.com) binary secret
// matches[4]: ...
// matches[5]: RST2 (messenger.msn.com) ticket
// matches[6]: ...
// matches[7]: RST3 (contacts.msn.com) ticket
// matches[8]: ...
// matches[9]: RST4 (messengersecure.live.com) ticket
// matches[10]: ...
// matches[11]: RST5 (spaces.live.com) ticket
// matches[12]: ...
// matches[13]: RST6 (storage.live.com) ticket
// matches[14]: ...
// so
// ticket => $matches[1]
// secret => $matches[3]
// web_ticket => $matches[5]
// contact_ticket => $matches[7]
// oim_ticket => $matches[9]
// space_ticket => $matches[11]
// storage_ticket => $matches[13]
// yes, we get ticket
$aTickets = array(
'ticket' => html_entity_decode($matches[1]),
'secret' => html_entity_decode($matches[3]),
'web_ticket' => html_entity_decode($matches[5]),
'contact_ticket' => html_entity_decode($matches[7]),
'oim_ticket' => html_entity_decode($matches[9]),
'space_ticket' => html_entity_decode($matches[11]),
'storage_ticket' => html_entity_decode($matches[13])
);
$this->ticket=$aTickets;
$this->debug_message(var_export($aTickets, true));
$ABAuthHeaderArray=array(
'ABAuthHeader'=>array(
':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'),
'ManagedGroupRequest'=>false,
'TicketToken'=>htmlspecialchars($this->ticket['contact_ticket']),
)
);
$this->ABAuthHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook","ABAuthHeader", $this->Array2SoapVar($ABAuthHeaderArray));
file_put_contents('/tmp/STTicket.txt',htmlspecialchars($this->ticket['storage_ticket']));
//$this->debug_message("StorageTicket:\n",htmlspecialchars($this->ticket['storage_ticket']));
return $aTickets;
}
private function UpdateContacts()
{
$ABApplicationHeaderArray=array(
'ABApplicationHeader'=>array(
':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'),
'ApplicationId'=>'CFE80F9D-180F-4399-82AB-413F33A1FA11',
'IsMigration'=>false,
'PartnerScenario'=>'ContactSave'
)
);
$ABApplicationHeader=new SoapHeader("http://www.msn.com/webservices/AddressBook",'ABApplicationHeader', $this->Array2SoapVar($ABApplicationHeaderArray));
$ABFindAllArray=array(
'ABFindAll'=>array(
':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'),
'abId'=>'00000000-0000-0000-0000-000000000000',
'abView'=>'Full',
'lastChange'=>'0001-01-01T00:00:00.0000000-08:00',
)
);
$ABFindAll=new SoapParam($this->Array2SoapVar($ABFindAllArray),'ABFindAll');
$this->ABService->__setSoapHeaders(array($ABApplicationHeader,$this->ABAuthHeader));
$this->Contacts=array();
try
{
$this->debug_message("*** Update Contacts...");
$Result=$this->ABService->ABFindAll($ABFindAll);
$this->debug_message("*** Result:\n".print_r($Result,true)."\n".$this->ABService->__getLastResponse());
foreach($Result->ABFindAllResult->contacts->Contact as $Contact)
$this->Contacts[$Contact->contactInfo->passportName]=$Contact;
}
catch(Exception $e)
{
$this->debug_message("*** Update Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage());
}
}
protected function addContact($email, $network, $display = '', $sendADL = false)
{
if ($network != 1) return true;
if(isset($this->Contacts[$email])) return true;
$ABContactAddArray=array(
'ABContactAdd'=>array(
':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'),
'abId'=>'00000000-0000-0000-0000-000000000000',
'contacts'=>array(
'Contact'=>array(
':'=>array('xmlns'=>'http://www.msn.com/webservices/AddressBook'),
'contactInfo'=>array(
'contactType'=>'LivePending',
'passportName'=>$email,
'isMessengerUser'=>true,
'MessengerMemberInfo'=>array(
'DisplayName'=>$email
)
)
)
),
'options'=>array(
'EnableAllowListManagement'=>true
)
)
);
$ABContactAdd=new SoapParam($this->Array2SoapVar($ABContactAddArray),'ABContactAdd');
try
{
$this->debug_message("*** Add Contacts $email...");
$this->ABService->ABContactAdd($ABContactAdd);
}
catch(Exception $e)
{
$this->debug_message("*** Add Contacts Error \nRequest:".$this->ABService->__getLastRequest()."\nError:".$e->getMessage());
}
if ($sendADL && !feof($this->NSfp)) {
@list($u_name, $u_domain) = @explode('@', $email);
foreach (array('1', '2') as $l) {
$str = '';
$len = strlen($str);
// NS: >>> ADL {id} {size}
$this->ns_writeln("ADL $this->id $len");
$this->ns_writedata($str);
}
}
$this->UpdateContacts();
return true;
}
function delMemberFromList($memberID, $email, $network, $list) {
if ($network != 1 && $network != 32) return true;
if ($memberID === false) return true;
$user = $email;
$ticket = htmlspecialchars($this->ticket['contact_ticket']);
if ($network == 1)
$XML = '
996CDE1E-AA53-4477-B943-2BE802EA6166
false
ContactMsgrAPI
false
'.$ticket.'
0
Messenger
'.$list.'
Passport
'.$memberID.'
Accepted
';
else
$XML = '
996CDE1E-AA53-4477-B943-2BE802EA6166
false
ContactMsgrAPI
false
'.$ticket.'
0
Messenger
'.$list.'
Email
'.$memberID.'
Accepted
';
$header_array = array(
'SOAPAction: '.$this->delmember_soap,
'Content-Type: text/xml; charset=utf-8',
'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
);
$this->debug_message("*** URL: $this->delmember_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->delmember_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if ($http_code != 200) {
preg_match('#(.*)(.*)#', $data, $matches);
if (count($matches) == 0) {
$this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list");
return false;
}
$faultcode = trim($matches[1]);
$faultstring = trim($matches[2]);
if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member does not exist') === false) {
$this->log_message("*** can't delete member (network: $network) $email ($memberID) to $list, error code: $faultcode, $faultstring");
return false;
}
$this->log_message("*** delete member (network: $network) $email ($memberID) from $list, not exist");
return true;
}
$this->log_message("*** delete member (network: $network) $email ($memberID) from $list");
return true;
}
function addMemberToList($email, $network, $list) {
if ($network != 1 && $network != 32) return true;
$ticket = htmlspecialchars($this->ticket['contact_ticket']);
$user = $email;
if ($network == 1)
$XML = '
996CDE1E-AA53-4477-B943-2BE802EA6166
false
ContactMsgrAPI
false
'.$ticket.'
0
Messenger
'.$list.'
Passport
Accepted
'.$user.'
';
else
$XML = '
996CDE1E-AA53-4477-B943-2BE802EA6166
false
ContactMsgrAPI
false
'.$ticket.'
0
Messenger
'.$list.'
Email
Accepted
'.$user.'
MSN.IM.BuddyType
32:YAHOO
';
$header_array = array(
'SOAPAction: '.$this->addmember_soap,
'Content-Type: text/xml; charset=utf-8',
'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
);
$this->debug_message("*** URL: $this->addmember_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->addmember_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if ($http_code != 200) {
preg_match('#(.*)(.*)#', $data, $matches);
if (count($matches) == 0) {
$this->log_message("*** can't add member (network: $network) $email to $list");
return false;
}
$faultcode = trim($matches[1]);
$faultstring = trim($matches[2]);
if (strcasecmp($faultcode, 'soap:Client') || stripos($faultstring, 'Member already exists') === false) {
$this->log_message("*** can't add member (network: $network) $email to $list, error code: $faultcode, $faultstring");
return false;
}
$this->log_message("*** add member (network: $network) $email to $list, already exist!");
return true;
}
$this->log_message("*** add member (network: $network) $email to $list");
return true;
}
function getMembershipList($returnData=false) {
$ticket = htmlspecialchars($this->ticket['contact_ticket']);
$XML = '
996CDE1E-AA53-4477-B943-2BE802EA6166
false
Initial
false
'.$ticket.'
Messenger
Invitation
SocialNetwork
Space
Profile
';
$header_array = array(
'SOAPAction: '.$this->membership_soap,
'Content-Type: text/xml; charset=utf-8',
'User-Agent: MSN Explorer/9.0 (MSN 8.0; TmstmpExt)'
);
$this->debug_message("*** URL: $this->membership_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->membership_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if($http_code != 200) return array();
$p = $data;
$aMemberships = array();
while (1) {
//$this->debug_message("search p = $p");
$start = strpos($p, '');
$end = strpos($p, '');
if ($start === false || $end === false || $start > $end) break;
//$this->debug_message("start = $start, end = $end");
$end += 13;
$sMembership = substr($p, $start, $end - $start);
$aMemberships[] = $sMembership;
//$this->debug_message("add sMembership = $sMembership");
$p = substr($p, $end);
}
//$this->debug_message("aMemberships = ".var_export($aMemberships, true));
$aContactList = array();
foreach ($aMemberships as $sMembership) {
//$this->debug_message("sMembership = $sMembership");
if (isset($matches)) unset($matches);
preg_match('#(.*)#', $sMembership, $matches);
if (count($matches) == 0) continue;
$sMemberRole = $matches[1];
//$this->debug_message("MemberRole = $sMemberRole");
if ($sMemberRole != 'Allow' && $sMemberRole != 'Reverse' && $sMemberRole != 'Pending') continue;
$p = $sMembership;
if (isset($aMembers)) unset($aMembers);
$aMembers = array();
while (1) {
//$this->debug_message("search p = $p");
$start = strpos($p, 'debug_message("add sMember = $sMember");
$p = substr($p, $end);
}
//$this->debug_message("aMembers = ".var_export($aMembers, true));
foreach ($aMembers as $sMember) {
//$this->debug_message("sMember = $sMember");
if (isset($matches)) unset($matches);
preg_match('##', $sMember, $matches);
if (count($matches) == 0) continue;
$sMemberType = $matches[1];
//$this->debug_message("MemberType = $sMemberType");
$network = -1;
preg_match('#(.*)#', $sMember, $matches);
if (count($matches) == 0) continue;
$id = $matches[1];
if ($sMemberType == 'PassportMember') {
if (strpos($sMember, 'Passport') === false) continue;
$network = 1;
preg_match('#(.*)#', $sMember, $matches);
}
else if ($sMemberType == 'EmailMember') {
if (strpos($sMember, 'Email') === false) continue;
// Value is 32: or 32:YAHOO
preg_match('#MSN.IM.BuddyType(.*):(.*)#', $sMember, $matches);
if (count($matches) == 0) continue;
if ($matches[1] != 32) continue;
$network = 32;
preg_match('#(.*)#', $sMember, $matches);
}
if ($network == -1) continue;
if (count($matches) > 0) {
$email = $matches[1];
@list($u_name, $u_domain) = @explode('@', $email);
if ($u_domain == NULL) continue;
$aContactList[$u_domain][$u_name][$network][$sMemberRole] = $id;
$this->log_message("*** add new contact (network: $network, status: $sMemberRole): $u_name@$u_domain ($id)");
}
}
}
return $aContactList;
}
private function connect($user, $password, $redirect_server = '', $redirect_port = 1863) {
$this->id = 1;
if ($redirect_server === '') {
$this->NSfp = @fsockopen($this->server, $this->port, $errno, $errstr, 5);
if (!$this->NSfp) {
$this->error = "Can't connect to $this->server:$this->port, error => $errno, $errstr";
return false;
}
}
else {
$this->NSfp = @fsockopen($redirect_server, $redirect_port, $errno, $errstr, 5);
if (!$this->NSfp) {
$this->error = "Can't connect to $redirect_server:$redirect_port, error => $errno, $errstr";
return false;
}
}
stream_set_timeout($this->NSfp, $this->NSStreamTimeout);
$this->authed = false;
// MSNP9
// NS: >> VER {id} MSNP9 CVR0
// MSNP15
// NS: >>> VER {id} MSNP15 CVR0
$this->ns_writeln("VER $this->id $this->protocol CVR0");
$start_tm = time();
while (!feof($this->NSfp))
{
$data = $this->ns_readln();
// no data?
if ($data === false) {
if ($this->timeout > 0) {
$now_tm = time();
$used_time = ($now_tm >= $start_tm) ? $now_tm - $start_tm : $now_tm;
if ($used_time > $this->timeout) {
// logout now
// NS: >>> OUT
$this->ns_writeln("OUT");
fclose($this->NSfp);
$this->error = 'Timeout, maybe protocol changed!';
$this->debug_message("*** $this->error");
return false;
}
}
continue;
}
$code = substr($data, 0, 3);
$start_tm = time();
switch ($code) {
case 'VER':
// MSNP9
// NS: <<< VER {id} MSNP9 CVR0
// NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 6.0.0602 msmsgs {user}
// MSNP15
// NS: <<< VER {id} MSNP15 CVR0
// NS: >>> CVR {id} 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs {user}
$this->ns_writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS $this->buildver msmsgs $user");
break;
case 'CVR':
// MSNP9
// NS: <<< CVR {id} {ver_list} {download_serve} ....
// NS: >>> USR {id} TWN I {user}
// MSNP15
// NS: <<< CVR {id} {ver_list} {download_serve} ....
// NS: >>> USR {id} SSO I {user}
$this->ns_writeln("USR $this->id $this->login_method I $user");
break;
case 'USR':
// already login for passport site, finish the login process now.
// NS: <<< USR {id} OK {user} {verify} 0
if ($this->authed) return true;
// max. 16 digits for password
if (strlen($password) > 16)
$password = substr($password, 0, 16);
$this->user = $user;
$this->password = $password;
// NS: <<< USR {id} SSO S {policy} {nonce}
@list(/* USR */, /* id */, /* SSO */, /* S */, $policy, $nonce,) = @explode(' ', $data);
$this->passport_policy = $policy;
$aTickets = $this->get_passport_ticket();
if (!$aTickets || !is_array($aTickets)) {
// logout now
// NS: >>> OUT
$this->ns_writeln("OUT");
fclose($this->NSfp);
$this->error = 'Passport authenticated fail!';
$this->debug_message("*** $this->error");
return false;
}
$ticket = $aTickets['ticket'];
$secret = $aTickets['secret'];
$this->ticket = $aTickets;
$login_code = $this->generateLoginBLOB($secret, $nonce);
// NS: >>> USR {id} SSO S {ticket} {login_code}
$this->ns_writeln("USR $this->id $this->login_method S $ticket $login_code");
$this->authed = true;
break;
case 'XFR':
// main login server will redirect to anther NS after USR command
// MSNP9
// NS: <<< XFR {id} NS {server} 0 {server}
// MSNP15
// NS: <<< XFR {id} NS {server} U D
@list(/* XFR */, /* id */, $Type, $server, /* ... */) = @explode(' ', $data);
if($Type!='NS') break;
@list($ip, $port) = @explode(':', $server);
// this connection will close after XFR
fclose($this->NSfp);
$this->NSfp = @fsockopen($ip, $port, $errno, $errstr, 5);
if (!$this->NSfp) {
$this->error = "Can't connect to $ip:$port, error => $errno, $errstr";
$this->debug_message("*** $this->error");
return false;
}
stream_set_timeout($this->NSfp, $this->NSStreamTimeout);
// MSNP9
// NS: >> VER {id} MSNP9 CVR0
// MSNP15
// NS: >>> VER {id} MSNP15 CVR0
$this->ns_writeln("VER $this->id $this->protocol CVR0");
break;
case 'GCF':
// return some policy data after 'USR {id} SSO I {user}' command
// NS: <<< GCF 0 {size}
@list(/* GCF */, /* 0 */, $size,) = @explode(' ', $data);
// we don't need the data, just read it and drop
if (is_numeric($size) && $size > 0)
$this->ns_readdata($size);
break;
default:
// we'll quit if got any error
if (is_numeric($code)) {
// logout now
// NS: >>> OUT
$this->ns_writeln("OUT");
fclose($this->NSfp);
$this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
$this->debug_message("*** $this->error");
return false;
}
// unknown response from server, just ignore it
break;
}
}
// never goto here
}
function derive_key($key, $magic) {
$hash1 = mhash(MHASH_SHA1, $magic, $key);
$hash2 = mhash(MHASH_SHA1, $hash1.$magic, $key);
$hash3 = mhash(MHASH_SHA1, $hash1, $key);
$hash4 = mhash(MHASH_SHA1, $hash3.$magic, $key);
return $hash2.substr($hash4, 0, 4);
}
function generateLoginBLOB($key, $challenge) {
$key1 = base64_decode($key);
$key2 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY HASH');
$key3 = $this->derive_key($key1, 'WS-SecureConversationSESSION KEY ENCRYPTION');
// get hash of challenge using key2
$hash = mhash(MHASH_SHA1, $challenge, $key2);
// get 8 bytes random data
$iv = substr(base64_encode(rand(1000,9999).rand(1000,9999)), 2, 8);
$cipher = mcrypt_cbc(MCRYPT_3DES, $key3, $challenge."\x08\x08\x08\x08\x08\x08\x08\x08", MCRYPT_ENCRYPT, $iv);
$blob = pack('LLLLLLL', 28, 1, 0x6603, 0x8004, 8, 20, 72);
$blob .= $iv;
$blob .= $hash;
$blob .= $cipher;
return base64_encode($blob);
}
function getOIM_maildata() {
preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);
if (count($matches) == 0) {
$this->debug_message('*** no web ticket?');
return false;
}
$t = htmlspecialchars($matches[1]);
$p = htmlspecialchars($matches[2]);
$XML = '
'.$t.'
'.$p.'
';
$header_array = array(
'SOAPAction: '.$this->oim_maildata_soap,
'Content-Type: text/xml; charset=utf-8',
'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
);
$this->debug_message("*** URL: $this->oim_maildata_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->oim_maildata_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if ($http_code != 200) {
$this->debug_message("*** Can't get OIM maildata! http code: $http_code");
return false;
}
// See #XML_Data
preg_match('#]*)>(.*)#', $data, $matches);
if (count($matches) == 0) {
$this->debug_message("*** Can't get OIM maildata");
return '';
}
return $matches[2];
}
function getOIM_message($msgid) {
preg_match('#t=(.*)&p=(.*)#', $this->ticket['web_ticket'], $matches);
if (count($matches) == 0) {
$this->debug_message('*** no web ticket?');
return false;
}
$t = htmlspecialchars($matches[1]);
$p = htmlspecialchars($matches[2]);
// read OIM
$XML = '
'.$t.'
'.$p.'
'.$msgid.'
false
';
$header_array = array(
'SOAPAction: '.$this->oim_read_soap,
'Content-Type: text/xml; charset=utf-8',
'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
);
$this->debug_message("*** URL: $this->oim_read_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->oim_read_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if ($http_code != 200) {
$this->debug_message("*** Can't get OIM: $msgid, http code = $http_code");
return false;
}
// why can't use preg_match('#(.*)#', $data, $matches)?
// multi-lines?
$start = strpos($data, '');
$end = strpos($data, '');
if ($start === false || $end === false || $start > $end) {
$this->debug_message("*** Can't get OIM: $msgid");
return false;
}
$lines = substr($data, $start + 18, $end - $start);
$aLines = @explode("\n", $lines);
$header = true;
$ignore = false;
$sOIM = '';
foreach ($aLines as $line) {
$line = rtrim($line);
if ($header) {
if ($line === '') {
$header = false;
continue;
}
continue;
}
// stop at empty lines
if ($line === '') break;
$sOIM .= $line;
}
$sMsg = base64_decode($sOIM);
$this->debug_message("*** we get OIM ($msgid): $sMsg");
// delete OIM
$XML = '
'.$t.'
'.$p.'
'.$msgid.'
';
$header_array = array(
'SOAPAction: '.$this->oim_del_soap,
'Content-Type: text/xml; charset=utf-8',
'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
);
$this->debug_message("*** URL: $this->oim_del_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->oim_del_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if ($http_code != 200)
$this->debug_message("*** Can't delete OIM: $msgid, http code = $http_code");
else
$this->debug_message("*** OIM ($msgid) deleted");
return $sMsg;
}
private function NSLogout() {
if (is_resource($this->NSfp) && !feof($this->NSfp)) {
// logout now
// NS: >>> OUT
$this->ns_writeln("OUT");
fclose($this->NSfp);
$this->NSfp = false;
$this->log_message("*** logout now!");
}
}
private function NSRetryWait($Wait) {
$this->log_message("*** wait for $Wait seconds");
for($i=0;$i<$Wait;$i++) {
sleep(1);
if($this->kill_me) return false;
}
return true;
}
public function ProcessSendMessageFileQueue() {
$aFiles = glob(MSN_CLASS_SPOOL_DIR.DIRECTORY_SEPARATOR.'*.msn');
if (!is_array($aFiles)) return true;
clearstatcache();
foreach ($aFiles as $filename) {
$fp = fopen($filename, 'rt');
if (!$fp) continue;
$aTo = array();
$sMessage = '';
$buf = trim(fgets($fp));
if (substr($buf, 0, 3) == 'TO:') {
$aTo = @explode(',', str_replace(array("\r","\n","\t",' '),'',substr($buf, 3)));
while (!feof($fp)) $sMessage.=rtrim(fgets($fp))."\n";
}
fclose($fp);
if (!is_array($aTo) || count($aTo) == 0 || $sMessage == '')
$this->log_message("!!! message format error? delete $filename");
else
{
foreach($aTo as $To)
{
@list($user, $domain, $network) = @explode('@', $To);
$MessageList[$network]["$user@$domain"]=$sMessage;
}
}
if($this->backup_file)
{
$backup_dir = MSN_CLASS_SPOOL_DIR.'/backup';
if (!file_exists($backup_dir)) @mkdir($backup_dir);
$backup_name = $backup_dir.'/'.strftime('%Y%m%d%H%M%S').'_'.posix_getpid().'_'.basename($filename);
if (@rename($filename, $backup_name))
$this->log_message("*** move file to $backup_name");
}
else @unlink($filename);
}
foreach ($MessageList as $network => $Messages)
{
switch(trim($network))
{
case '':
case 1: //MSN
// okay, try to ask a switchboard (SB) for sending message
// NS: >>> XFR {id} SB
// $this->ns_writeln("XFR $this->id SB");
foreach($Messages as $User => $Message)
$this->MessageQueue[$User][]=$Message;
break;
case 'Offline': //MSN
//Send OIM
//FIXME: 修正Send OIM
foreach($Messages as $To => $Message)
{
$lockkey='';
for ($i = 0; $i < $this->oim_try; $i++)
{
if(($oim_result = $this->sendOIM($To, $Message, $lockkey))===true) break;
if (is_array($oim_result) && $oim_result['challenge'] !== false) {
// need challenge lockkey
$this->log_message("*** we need a new challenge code for ".$oim_result['challenge']);
$lockkey = $this->getChallenge($oim_result['challenge']);
continue;
}
if ($oim_result === false || $oim_result['auth_policy'] !== false)
{
if ($re_login)
{
$this->log_message("*** can't send OIM, but we already re-login again, so ignore this OIM");
break;
}
$this->log_message("*** can't send OIM, maybe ticket expired, try to login again");
// maybe we need to re-login again
if(!$this->get_passport_ticket())
{
$this->log_message("*** can't re-login, something wrong here, ignore this OIM");
break;
}
$this->log_message("**** get new ticket, try it again");
continue;
}
}
}
break;
default: //Other
foreach($Messages as $To => $Message) {
$Message=$this->getMessage($Message, $network);
$len = strlen($Message);
$this->ns_writeln("UUM $this->id $To $network 1 $len");
$this->ns_writedata($Message);
$this->log_message("*** sent to $To (network: $network):\n$Message");
}
}
}
if(isset($this->MessageQueue[$User])&&(!isset($this->MessageQueue[$User]['XFRSent'])))
{
$this->MessageQueue[$User]['XFRSent']=false;
$this->MessageQueue[$User]['ReqTime']=false;
}
return true;
}
public function SignalFunction($signal)
{
switch($signal)
{
case SIGTRAP:
case SIGTERM:
case SIGHUP:
$this->End();
return;
case SIGCHLD:
$ChildPid=pcntl_wait($status,WUNTRACED);
if($ChildPid>0)
{
$this->log_message("*** Child Process End for ".$this->ChildProcess[$ChildPid]);
unset($this->ChildProcess[$ChildPid]);
}
return;
}
}
public function Run()
{
$this->log_message("*** startup ***");
if(!pcntl_signal(SIGCHLD,array($this,'SignalFunction'))) die("Signal SIGCHLD Error\n");
if(!pcntl_signal(SIGTERM,array($this,'SignalFunction'))) die("Signal SIGTERM Error\n");
if(!pcntl_signal(SIGTRAP,array($this,'SignalFunction'))) die("Signal SIGTRAP Error\n");
$process_file = false;
$sent = false;
$aADL = array();
$aContactList = array();
while (true)
{
if($this->kill_me)
{
$this->log_message("*** Okay, kill me now!");
return $this->NSLogout();
}
if (!is_resource($this->NSfp) || feof($this->NSfp))
{
$this->log_message("*** try to connect to MSN network");
if (!$this->connect($this->user, $this->password))
{
$this->log_message("!!! Can't connect to server: $this->error");
if(!$this->NSRetryWait($this->retry_wait)) continue;
}
$this->UpdateContacts();
$this->LastPing=time();
$this->log_message("*** connected, wait for command");
$start_tm = time();
$ping_tm = time();
stream_set_timeout($this->NSfp, $this->NSStreamTimeout);
$aContactList = $this->getMembershipList();
if ($this->update_pending) {
if (is_array($aContactList)) {
$pending = 'Pending';
foreach ($aContactList as $u_domain => $aUserList) {
foreach ($aUserList as $u_name => $aNetworks) {
foreach ($aNetworks as $network => $aData) {
if (isset($aData[$pending])) {
// pending list
$cnt = 0;
foreach (array('Allow', 'Reverse') as $list) {
if (isset($aData[$list]))
$cnt++;
else {
if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
$aContactList[$u_domain][$u_name][$network][$list] = false;
$cnt++;
}
}
}
if ($cnt >= 2) {
$id = $aData[$pending];
// we can delete it from pending now
if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending))
unset($aContactList[$u_domain][$u_name][$network][$pending]);
}
}
else {
// sync list
foreach (array('Allow', 'Reverse') as $list) {
if (!isset($aData[$list])) {
if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
$aContactList[$u_domain][$u_name][$network][$list] = false;
}
}
}
}
}
}
}
}
$n = 0;
$sList = '';
$len = 0;
if (is_array($aContactList)) {
foreach ($aContactList as $u_domain => $aUserList) {
$str = '';
$len += strlen($str);
if ($len > 7400) {
$aADL[$n] = ''.$sList.'';
$n++;
$sList = '';
$len = strlen($str);
}
$sList .= $str;
foreach ($aUserList as $u_name => $aNetworks) {
foreach ($aNetworks as $network => $status) {
$str = '';
$len += strlen($str);
// max: 7500, but is 19,
// so we use 7475
if ($len > 7475) {
$sList .= '';
$aADL[$n] = ''.$sList.'';
$n++;
$sList = ''.$str;
$len = strlen($sList);
}
else
$sList .= $str;
}
}
$sList .= '';
}
}
$aADL[$n] = ''.$sList.'';
// NS: >>> BLP {id} BL
$this->ns_writeln("BLP $this->id BL");
foreach ($aADL as $str) {
$len = strlen($str);
// NS: >>> ADL {id} {size}
$this->ns_writeln("ADL $this->id $len");
$this->ns_writedata($str);
}
// NS: >>> PRP {id} MFN name
if ($this->alias == '') $this->alias = $user;
$aliasname = rawurlencode($this->alias);
$this->ns_writeln("PRP $this->id MFN $aliasname");
//設定個人大頭貼
//$MsnObj=$this->PhotoStckObj();
// NS: >>> CHG {id} {status} {clientid} {msnobj}
$this->ns_writeln("CHG $this->id NLN $this->clientid");
if($this->PhotoStickerFile!==false)
$this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
// NS: >>> UUX {id} length
$str = ''.htmlspecialchars($this->psm).'';
$len = strlen($str);
$this->ns_writeln("UUX $this->id $len");
$this->ns_writedata($str);
}
$data = $this->ns_readln();
if($data===false)
{
//If No NS Message Process SendMessageFileQueue
if (time()-$this->LastPing > $this->ping_wait)
{
// NS: >>> PNG
$this->ns_writeln("PNG");
$this->LastPing = time();
}
if(count($this->ChildProcess)<$this->MAXChildProcess)
{
$Index=0;
foreach($this->MessageQueue as $User => $Message)
{
if(!trim($User)) continue;
if($Inxdex>=$this->MAXChildProcess-count($this->ChildProcess)) break;
if((!$Message['XFRSent'])||($Message['XFRSent']&&(time()-$this->MessageQueue[$User]['ReqTime']>$this->ReqSBXFRTimeout)))
{
$this->MessageQueue[$User]['XFRSent']=true;
$this->MessageQueue[$User]['ReqTime']=time();
$this->log_message("*** Request SB for $User");
$this->ns_writeln("XFR $this->id SB");
$Index++;
}
}
}
if($this->ProcessSendMessageFileQueue()) continue;
break;
}
switch (substr($data,0,3))
{
case 'SBS':
// after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us
// NS: <<< SBS 0 null
break;
case 'RFS':
// FIXME:
// NS: <<< RFS ???
// refresh ADL, so we re-send it again
if (is_array($aADL)) {
foreach ($aADL as $str) {
$len = strlen($str);
// NS: >>> ADL {id} {size}
$this->ns_writeln("ADL $this->id $len");
$this->ns_writedata($str);
}
}
break;
case 'LST':
// NS: <<< LST {email} {alias} 11 0
@list(/* LST */, $email, /* alias */, ) = @explode(' ', $data);
@list($u_name, $u_domain) = @explode('@', $email);
if (!isset($aContactList[$u_domain][$u_name][1])) {
$aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow';
$this->log_message("*** add to our contact list: $u_name@$u_domain");
}
break;
case 'ADL':
// randomly, we get ADL command, someome add us to their contact list for MSNP15
// NS: <<< ADL 0 {size}
@list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0)
{
$data = $this->ns_readdata($size);
preg_match('##', $data, $matches);
if (is_array($matches) && count($matches) > 0)
{
$u_domain = $matches[1];
$u_name = $matches[2];
$network = $matches[4];
if (isset($aContactList[$u_domain][$u_name][$network]))
$this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain");
else
{
$re_login = false;
$cnt = 0;
foreach (array('Allow', 'Reverse') as $list)
{
if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
{
if ($re_login) {
$this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
continue;
}
$aTickets = $this->get_passport_ticket();
if (!$aTickets || !is_array($aTickets)) {
// failed to login? ignore it
$this->log_message("*** can't re-login, something wrong here");
$this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
continue;
}
$re_login = true;
$this->ticket = $aTickets;
$this->log_message("**** get new ticket, try it again");
if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
{
$this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
continue;
}
}
$aContactList[$u_domain][$u_name][$network][$list] = false;
$cnt++;
}
$this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain");
}
$str = '';
$len = strlen($str);
}
else
$this->log_message("*** someone add us to their list: $data");
$this->AddUsToMemberList($u_name.'@'.$u_domain, $network);
}
break;
case 'RML':
// randomly, we get RML command, someome remove us to their contact list for MSNP15
// NS: <<< RML 0 {size}
@list(/* RML */, /* 0 */, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0)
{
$data = $this->ns_readdata($size);
preg_match('##', $data, $matches);
if (is_array($matches) && count($matches) > 0)
{
$u_domain = $matches[1];
$u_name = $matches[2];
$network = $matches[4];
if (isset($aContactList[$u_domain][$u_name][$network]))
{
$aData = $aContactList[$u_domain][$u_name][$network];
foreach ($aData as $list => $id)
$this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list);
unset($aContactList[$u_domain][$u_name][$network]);
$this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain");
}
else
$this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain");
$this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network);
}
else
$this->log_message("*** someone remove us from their list: $data");
}
break;
case 'MSG':
// randomly, we get MSG notification from server
// NS: <<< MSG Hotmail Hotmail {size}
@list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0) {
$data = $this->ns_readdata($size);
$aLines = @explode("\n", $data);
$header = true;
$ignore = false;
$maildata = '';
foreach ($aLines as $line) {
$line = rtrim($line);
if ($header) {
if ($line === '') {
$header = false;
continue;
}
if (strncasecmp($line, 'Content-Type:', 13) == 0) {
if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false &&
strpos($line, 'text/x-msmsgsoimnotification') === false) {
// we just need text/x-msmsgsinitialmdatanotification
// or text/x-msmsgsoimnotification
$ignore = true;
break;
}
}
continue;
}
if (strncasecmp($line, 'Mail-Data:', 10) == 0) {
$maildata = trim(substr($line, 10));
break;
}
}
if ($ignore) {
$this->log_message("*** ingnore MSG for: $line");
break;
}
if ($maildata == '') {
$this->log_message("*** ingnore MSG not for OIM");
break;
}
$re_login = false;
if (strcasecmp($maildata, 'too-large') == 0) {
$this->log_message("*** large mail-data, need to get the data via SOAP");
$maildata = $this->getOIM_maildata();
if ($maildata === false) {
$this->log_message("*** can't get mail-data via SOAP");
// maybe we need to re-login again
$aTickets = $this->get_passport_ticket();
if (!$aTickets || !is_array($aTickets)) {
// failed to login? ignore it
$this->log_message("*** can't re-login, something wrong here, ignore this OIM");
break;
}
$re_login = true;
$this->ticket = $aTickets;
$this->log_message("**** get new ticket, try it again");
$maildata = $this->getOIM_maildata();
if ($maildata === false) {
$this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM");
break;
}
}
}
// could be a lots of ..., so we can't use preg_match here
$p = $maildata;
$aOIMs = array();
while (1) {
$start = strpos($p, '');
$end = strpos($p, '');
if ($start === false || $end === false || $start > $end) break;
$end += 4;
$sOIM = substr($p, $start, $end - $start);
$aOIMs[] = $sOIM;
$p = substr($p, $end);
}
if (count($aOIMs) == 0) {
$this->log_message("*** ingnore empty OIM");
break;
}
foreach ($aOIMs as $maildata) {
// T: 11 for MSN, 13 for Yahoo
// S: 6 for MSN, 7 for Yahoo
// RT: the datetime received by server
// RS: already read or not
// SZ: size of message
// E: sender
// I: msgid
// F: always 00000000-0000-0000-0000-000000000009
// N: sender alias
preg_match('#(.*)#', $maildata, $matches);
if (count($matches) == 0) {
$this->log_message("*** ingnore OIM maildata without type");
continue;
}
$oim_type = $matches[1];
if ($oim_type = 13)
$network = 32;
else
$network = 1;
preg_match('#(.*)#', $maildata, $matches);
if (count($matches) == 0) {
$this->log_message("*** ingnore OIM maildata without sender");
continue;
}
$oim_sender = $matches[1];
preg_match('#(.*)#', $maildata, $matches);
if (count($matches) == 0) {
$this->log_message("*** ingnore OIM maildata without msgid");
continue;
}
$oim_msgid = $matches[1];
preg_match('#(.*)#', $maildata, $matches);
$oim_size = (count($matches) == 0) ? 0 : $matches[1];
preg_match('##', $maildata, $matches);
$oim_time = (count($matches) == 0) ? 0 : $matches[1];
$this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size");
$sMsg = $this->getOIM_message($oim_msgid);
if ($sMsg === false) {
$this->log_message("*** can't get OIM, msgid = $oim_msgid");
if ($re_login) {
$this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM");
continue;
}
$aTickets = $this->get_passport_ticket();
if (!$aTickets || !is_array($aTickets)) {
// failed to login? ignore it
$this->log_message("*** can't re-login, something wrong here, ignore this OIM");
continue;
}
$re_login = true;
$this->ticket = $aTickets;
$this->log_message("**** get new ticket, try it again");
$sMsg = $this->getOIM_message($oim_msgid);
if ($sMsg === false) {
$this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM");
continue;
}
}
$this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg");
$this->ReceivedMessage($oim_sender,$sMsg,$network,true);
}
}
break;
case 'UBM':
// randomly, we get UBM, this is the message from other network, like Yahoo!
// NS: <<< UBM {email} $network $type {size}
@list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0)
{
$data = $this->ns_readdata($size);
$aLines = @explode("\n", $data);
$header = true;
$ignore = false;
$sMsg = '';
foreach ($aLines as $line) {
$line = rtrim($line);
if ($header) {
if ($line === '') {
$header = false;
continue;
}
if (strncasecmp($line, 'TypingUser:', 11) == 0) {
$ignore = true;
break;
}
continue;
}
$aSubLines = @explode("\r", $line);
foreach ($aSubLines as $str) {
if ($sMsg !== '')
$sMsg .= "\n";
$sMsg .= $str;
}
}
if($ignore)
{
$this->log_message("*** ingnore from $from_email: $line");
break;
}
$this->log_message("*** MSG from $from_email (network: $network): $sMsg");
$this->ReceivedMessage($from_email,$sMsg,$network,false);
}
break;
case 'UBX':
// randomly, we get UBX notification from server
// NS: <<< UBX email {network} {size}
@list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data);
// we don't need the notification data, so just ignore it
if (is_numeric($size) && $size > 0)
$this->ns_readdata($size);
break;
case 'CHL':
// randomly, we'll get challenge from server
// NS: <<< CHL 0 {code}
@list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data);
$fingerprint = $this->getChallenge($chl_code);
// NS: >>> QRY {id} {product_id} 32
// NS: >>> fingerprint
$this->ns_writeln("QRY $this->id $this->prod_id 32");
$this->ns_writedata($fingerprint);
$this->ns_writeln("CHG $this->id NLN $this->clientid");
if($this->PhotoStickerFile!==false)
$this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
break;
case 'CHG':
// NS: <<< CHG {id} {status} {code}
// ignore it
// change our status to online first
break;
case 'XFR':
// sometimes, NS will redirect to another NS
// MSNP9
// NS: <<< XFR {id} NS {server} 0 {server}
// MSNP15
// NS: <<< XFR {id} NS {server} U D
// for normal switchboard XFR
// NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0
@list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data);
@list($ip, $port) = @explode(':', $server);
if ($server_type != 'SB') {
// maybe exit?
// this connection will close after XFR
$this->NSLogout();
continue;
}
if(count($this->MessageQueue))
{
foreach($this->MessageQueue as $User => $Message)
{
//$this->ChildProcess[$ChildPid]
$this->log_message("*** XFR SB $User");
$pid=pcntl_fork();
if($pid)
{
//Parrent Process
$this->ChildProcess[$pid]=$User;
break;
}
elseif($pid==-1)
{
$this->log_message("*** Fork Error $User");
break;
}
else
{
//Child Process
$this->log_message("*** Child Process Start for $User");
unset($Message['XFRSent']);
unset($Message['ReqTime']);
$bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message);
if ($bSBresult === false)
{
// error for switchboard
$this->log_message("!!! error for sending message to ".$User);
}
die;
}
}
unset($this->MessageQueue[$User]);
}
/*
$bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage);
if ($bSBresult === false) {
// error for switchboard
$this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]);
$aOfflineUsers[] = $aMSNUsers[$nCurrentUser];
}*/
break;
case 'QNG':
// NS: <<< QNG {time}
@list(/* QNG */, $this->ping_wait) = @explode(' ', $data);
if ($this->ping_wait == 0) $this->ping_wait = 50;
//if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping;
//Mod by Ricky Set Online
break;
case 'RNG':
if($this->PhotoStickerFile!==false)
$this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
else
$this->ns_writeln("CHG $this->id NLN $this->clientid");
// someone is trying to talk to us
// NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0
$this->log_message("NS: <<< RNG $data");
@list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data);
@list($sb_ip, $sb_port) = @explode(':', $server);
if($this->IsIgnoreMail($email))
{
$this->log_message("*** Ignore RNG from $email");
break;
}
$this->log_message("*** RING from $email, $sb_ip:$sb_port");
$this->addContact($email,1,$email, true);
$pid=pcntl_fork();
if($pid)
{
//Parrent Process
$this->ChildProcess[$pid]='RNG';
break;
}
elseif($pid==-1)
{
$this->log_message("*** Fork Error $User");
break;
}
else
{
//Child Process
$this->log_message("*** Ring Child Process Start for $User");
$this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email);
die;
}
break;
case 'OUT':
// force logout from NS
// NS: <<< OUT xxx
fclose($this->NSfp);
$this->log_message("*** LOGOUT from NS");
break;
default:
$code = substr($data,0,3);
if (is_numeric($code)) {
$this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
$this->debug_message("*** NS: $this->error");
return $this->NsLogout();
}
break;
}
}
return $this->NsLogout();
}
/*public function SendMessage($Message, $To)
{
$FileName = MSN_CLASS_SPOOL_DIR.'/'.strftime('%Y%m%d%H%M%S',time()).'_'.posix_getpid().'_sendMessage.msn';
if(!is_array($To))
$To=array($To);
$Receiver='';
foreach($To as $Email)
{
list($name,$host,$network)=explode('@',$Email);
$network=$network==''?1:$network;
if($network==1 && $this->SwitchBoardProcess && $this->SwitchBoardSessionUser=="$name@$host" )
{
$this->debug_message("*** SendMessage to $Receiver use SB message queue.");
array_push($this->SwitchBoardMessageQueue,$Message);
continue;
}
$Receiver.="$name@$host@$network,";
}
if($Receiver=='') return;
$Receiver=substr($Receiver,0,-1);
$this->debug_message("*** SendMessage to $Receiver use File queue.");
file_put_contents($FileName,"TO: $Receiver\n$Message\n");
}*/
function getChallenge($code)
{
// MSNP15
// http://msnpiki.msnfanatic.com/index.php/MSNP11:Challenges
// Step 1: The MD5 Hash
$md5Hash = md5($code.$this->prod_key);
$aMD5 = @explode("\0", chunk_split($md5Hash, 8, "\0"));
for ($i = 0; $i < 4; $i++) {
$aMD5[$i] = implode('', array_reverse(@explode("\0", chunk_split($aMD5[$i], 2, "\0"))));
$aMD5[$i] = (0 + base_convert($aMD5[$i], 16, 10)) & 0x7FFFFFFF;
}
// Step 2: A new string
$chl_id = $code.$this->prod_id;
$chl_id .= str_repeat('0', 8 - (strlen($chl_id) % 8));
$aID = @explode("\0", substr(chunk_split($chl_id, 4, "\0"), 0, -1));
for ($i = 0; $i < count($aID); $i++) {
$aID[$i] = implode('', array_reverse(@explode("\0", chunk_split($aID[$i], 1, "\0"))));
$aID[$i] = 0 + base_convert(bin2hex($aID[$i]), 16, 10);
}
// Step 3: The 64 bit key
$magic_num = 0x0E79A9C1;
$str7f = 0x7FFFFFFF;
$high = 0;
$low = 0;
for ($i = 0; $i < count($aID); $i += 2) {
$temp = $aID[$i];
$temp = bcmod(bcmul($magic_num, $temp), $str7f);
$temp = bcadd($temp, $high);
$temp = bcadd(bcmul($aMD5[0], $temp), $aMD5[1]);
$temp = bcmod($temp, $str7f);
$high = $aID[$i+1];
$high = bcmod(bcadd($high, $temp), $str7f);
$high = bcadd(bcmul($aMD5[2], $high), $aMD5[3]);
$high = bcmod($high, $str7f);
$low = bcadd(bcadd($low, $high), $temp);
}
$high = bcmod(bcadd($high, $aMD5[1]), $str7f);
$low = bcmod(bcadd($low, $aMD5[3]), $str7f);
$new_high = bcmul($high & 0xFF, 0x1000000);
$new_high = bcadd($new_high, bcmul($high & 0xFF00, 0x100));
$new_high = bcadd($new_high, bcdiv($high & 0xFF0000, 0x100));
$new_high = bcadd($new_high, bcdiv($high & 0xFF000000, 0x1000000));
// we need integer here
$high = 0+$new_high;
$new_low = bcmul($low & 0xFF, 0x1000000);
$new_low = bcadd($new_low, bcmul($low & 0xFF00, 0x100));
$new_low = bcadd($new_low, bcdiv($low & 0xFF0000, 0x100));
$new_low = bcadd($new_low, bcdiv($low & 0xFF000000, 0x1000000));
// we need integer here
$low = 0+$new_low;
// we just use 32 bits integer, don't need the key, just high/low
// $key = bcadd(bcmul($high, 0x100000000), $low);
// Step 4: Using the key
$md5Hash = md5($code.$this->prod_key);
$aHash = @explode("\0", chunk_split($md5Hash, 8, "\0"));
$hash = '';
$hash .= sprintf("%08x", (0 + base_convert($aHash[0], 16, 10)) ^ $high);
$hash .= sprintf("%08x", (0 + base_convert($aHash[1], 16, 10)) ^ $low);
$hash .= sprintf("%08x", (0 + base_convert($aHash[2], 16, 10)) ^ $high);
$hash .= sprintf("%08x", (0 + base_convert($aHash[3], 16, 10)) ^ $low);
return $hash;
}
private function getMessage($sMessage, $network = 1)
{
$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";
$msg_header_len = strlen($msg_header);
if ($network == 1)
$maxlen = $this->max_msn_message_len - $msg_header_len;
else
$maxlen = $this->max_yahoo_message_len - $msg_header_len;
$sMessage=str_replace("\r", '', $sMessage);
$msg=substr($sMessage,0,$maxlen);
return $msg_header.$msg;
}
/**
*
* @param $Action 連線模式 'Active' => 主動傳送訊息,'Passive' => 接收訊息
* @param $Param
* @return boolean
*/
private function DoSwitchBoard($Action,$Param)
{
$SessionEnd=false;
$Joined=false;
$id=1;
$LastActive=time();
stream_set_timeout($this->SBFp, $this->SBTimeout);
switch($Action)
{
case 'Active':
$cki_code=$Param['cki'];
$user=$Param['user'];
$this->SwitchBoardMessageQueue=$Param['Msg'];
// SB: >>> USR {id} {user} {cki}
$this->SB_writeln("USR $id $this->user $cki_code");
$id++;
$this->SwitchBoardSessionUser=$user;
break;
case 'Passive':
$ticket=$Param['ticket'];
$sid=$Param['sid'];
$user=$Param['user'];
// SB: >>> ANS {id} {user} {ticket} {session_id}
$this->SB_writeln("ANS $id $this->user $ticket $sid");
$id++;
$this->SwitchBoardSessionUser=$user;
break;
default:
return false;
}
while((!feof($this->SBFp))&&(!$SessionEnd))
{
$data = $this->SB_readln();
if($this->kill_me)
{
$this->log_message("*** SB Okay, kill me now!");
break;
}
if($data === false)
{
if(time()-$LastActive > $this->SBIdleTimeout)
{
$this->debug_message("*** SB Idle Timeout!");
break;
}
if(!$Joined) continue;
foreach($this->SwitchBoardMessageQueue as $Message)
{
if($Message=='') continue;
$aMessage = $this->getMessage($Message);
//CheckEmotion...
$MsnObjDefine=$this->GetMsnObjDefine($aMessage);
if($MsnObjDefine!=='')
{
$SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine";
$len = strlen($SendString);
$this->SB_writeln("MSG $id N $len");
$id++;
$this->SB_writedata($SendString);
$this->id++;
}
$len = strlen($aMessage);
$this->SB_writeln("MSG $id N $len");
$id++;
$this->SB_writedata($aMessage);
}
$this->SwitchBoardMessageQueue=array();
if(!$this->IsIgnoreMail($user)) $LastActive = time();
continue;
}
$code = substr($data, 0, 3);
switch($code)
{
case 'IRO':
// SB: <<< IRO {id} {rooster} {roostercount} {email} {alias} {clientid}
@list(/* IRO */, /* id */, $cur_num, $total, $email, $alias, $clientid) = @explode(' ', $data);
$this->log_message("*** $email join us");
$Joined=true;
break;
case 'BYE':
$this->log_message("*** Quit for BYE");
$SessionEnd=true;
break;
case 'USR':
// SB: <<< USR {id} OK {user} {alias}
// we don't need the data, just ignore it
// request user to join this switchboard
// SB: >>> CAL {id} {user}
$this->SB_writeln("CAL $id $user");
$id++;
break;
case 'CAL':
// SB: <<< CAL {id} RINGING {?}
// we don't need this, just ignore, and wait for other response
$this->id++;
break;
case 'JOI':
// SB: <<< JOI {user} {alias} {clientid?}
// someone join us
// we don't need the data, just ignore it
// no more user here
$Joined=true;
break;
case 'MSG':
// SB: <<< MSG {email} {alias} {len}
@list(/* MSG */, $from_email, /* alias */, $len, ) = @explode(' ', $data);
$len = trim($len);
$data = $this->SB_readdata($len);
$aLines = @explode("\n", $data);
$header = true;
$ignore = false;
$is_p2p = false;
$sMsg = '';
foreach ($aLines as $line)
{
$line = rtrim($line);
if ($header) {
if ($line === '') {
$header = false;
continue;
}
if (strncasecmp($line, 'TypingUser:', 11) == 0) {
// typing notification, just ignore
$ignore = true;
break;
}
if (strncasecmp($line, 'Chunk:', 6) == 0) {
// we don't handle any split message, just ignore
$ignore = true;
break;
}
if (strncasecmp($line, 'Content-Type: application/x-msnmsgrp2p', 38) == 0) {
// p2p message, ignore it, but we need to send acknowledgement for it...
$is_p2p = true;
$p = strstr($data, "\n\n");
$sMsg = '';
if ($p === false) {
$p = strstr($data, "\r\n\r\n");
if ($p !== false)
$sMsg = substr($p, 4);
}
else
$sMsg = substr($p, 2);
break;
}
if (strncasecmp($line, 'Content-Type: application/x-', 28) == 0) {
// ignore all application/x-... message
// for example:
// application/x-ms-ink => ink message
$ignore = true;
break;
}
if (strncasecmp($line, 'Content-Type: text/x-', 21) == 0) {
// ignore all text/x-... message
// for example:
// text/x-msnmsgr-datacast => nudge, voice clip....
// text/x-mms-animemoticon => customized animemotion word
$ignore = true;
break;
}
continue;
}
if ($sMsg !== '')
$sMsg .= "\n";
$sMsg .= $line;
}
if ($ignore)
{
$this->log_message("*** ingnore from $from_email: $line");
break;
}
if ($is_p2p)
{
// we will ignore any p2p message after sending acknowledgement
$ignore = true;
$len = strlen($sMsg);
$this->log_message("*** p2p message from $from_email, size $len");
// header = 48 bytes
// content >= 0 bytes
// footer = 4 bytes
// so it need to >= 52 bytes
/*if ($len < 52) {
$this->log_message("*** p2p: size error, less than 52!");
break;
}*/
$aDwords = @unpack("V12dword", $sMsg);
if (!is_array($aDwords)) {
$this->log_message("*** p2p: header unpack error!");
break;
}
$this->debug_message("*** p2p: dump received message:\n".$this->dump_binary($sMsg));
$hdr_SessionID = $aDwords['dword1'];
$hdr_Identifier = $aDwords['dword2'];
$hdr_DataOffsetLow = $aDwords['dword3'];
$hdr_DataOffsetHigh = $aDwords['dword4'];
$hdr_TotalDataSizeLow = $aDwords['dword5'];
$hdr_TotalDataSizeHigh = $aDwords['dword6'];
$hdr_MessageLength = $aDwords['dword7'];
$hdr_Flag = $aDwords['dword8'];
$hdr_AckID = $aDwords['dword9'];
$hdr_AckUID = $aDwords['dword10'];
$hdr_AckSizeLow = $aDwords['dword11'];
$hdr_AckSizeHigh = $aDwords['dword12'];
$this->debug_message("*** p2p: header SessionID = $hdr_SessionID");
$this->debug_message("*** p2p: header Inentifier = $hdr_Identifier");
$this->debug_message("*** p2p: header Data Offset Low = $hdr_DataOffsetLow");
$this->debug_message("*** p2p: header Data Offset High = $hdr_DataOffsetHigh");
$this->debug_message("*** p2p: header Total Data Size Low = $hdr_TotalDataSizeLow");
$this->debug_message("*** p2p: header Total Data Size High = $hdr_TotalDataSizeHigh");
$this->debug_message("*** p2p: header MessageLength = $hdr_MessageLength");
$this->debug_message("*** p2p: header Flag = $hdr_Flag");
$this->debug_message("*** p2p: header AckID = $hdr_AckID");
$this->debug_message("*** p2p: header AckUID = $hdr_AckUID");
$this->debug_message("*** p2p: header AckSize Low = $hdr_AckSizeLow");
$this->debug_message("*** p2p: header AckSize High = $hdr_AckSizeHigh");
if($hdr_Flag==2) {
//This is an ACK from SB ignore....
$this->debug_message("*** p2p: //This is an ACK from SB ignore....:\n");
break;
}
$MsgBody=$this->linetoArray(substr($sMsg,48,-4));
$this->debug_message("*** p2p: body".print_r($MsgBody,true));
if(($MsgBody['EUF-GUID']=='{A4268EEC-FEC5-49E5-95C3-F126696BDBF6}')&&($PictureFilePath=$this->GetPictureFilePath($MsgBody['Context'])))
{
while(true)
{
if($this->SB_readln()===false) break;
}
$this->debug_message("*** p2p: Inv hdr:\n".$this->dump_binary(substr($sMsg,0,48)));
preg_match('/{([0-9A-F\-]*)}/i',$MsgBody['Via'],$Matches);
$BranchGUID=$Matches[1];
//it's an invite to send a display picture.
$new_id = ~$hdr_Identifier;
$hdr = pack("LLLLLLLLLLLL", $hdr_SessionID,
$new_id,
0, 0,
$hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
0,
2,
$hdr_Identifier,
$hdr_AckID,
$hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh);
$footer = pack("L", 0);
$message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";
$len = strlen($message);
$this->SB_writeln("MSG $id D $len");
$id++;
$this->SB_writedata($message);
$this->log_message("*** p2p: send display picture acknowledgement for $hdr_SessionID");
$this->debug_message("*** p2p: Invite ACK message:\n".$this->dump_binary($message));
$this->SB_readln();//Read ACK;
$this->debug_message("*** p2p: Invite ACK Hdr:\n".$this->dump_binary($hdr));
$new_id-=3;
//Send 200 OK message
$MessageContent="SessionID: ".$MsgBody['SessionID']."\r\n\r\n".pack("C", 0);
$MessagePayload=
"MSNSLP/1.0 200 OK\r\n".
"To: \r\n".
"From: user.">\r\n".
"Via: ".$MsgBody['Via']."\r\n".
"CSeq: ".($MsgBody['CSeq']+1)."\r\n".
"Call-ID: ".$MsgBody['Call-ID']."\r\n".
"Max-Forwards: 0\r\n".
"Content-Type: application/x-msnmsgr-sessionreqbody\r\n".
"Content-Length: ".strlen($MessageContent)."\r\n\r\n".
$MessageContent;
$hdr_TotalDataSizeLow=strlen($MessagePayload);
$hdr_TotalDataSizeHigh=0;
$hdr = pack("LLLLLLLLLLLL", $hdr_SessionID,
$new_id,
0, 0,
$hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
strlen($MessagePayload),
0,
rand(),
0,
0,0);
$message =
"MIME-Version: 1.0\r\n".
"Content-Type: application/x-msnmsgrp2p\r\n".
"P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";
$this->SB_writeln("MSG $id D ".strlen($message));
$id++;
$this->SB_writedata($message);
$this->debug_message("*** p2p: dump 200 ok message:\n".$this->dump_binary($message));
$this->SB_readln();//Read ACK;
$this->debug_message("*** p2p: 200 ok:\n".$this->dump_binary($hdr));
//send Data preparation message
//send 4 null bytes as data
$hdr_TotalDataSizeLow=4;
$hdr_TotalDataSizeHigh=0;
$new_id++;
$hdr = pack("LLLLLLLLLLLL",
$MsgBody['SessionID'],
$new_id,
0, 0,
$hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
$hdr_TotalDataSizeLow,
0,
rand(),
0,
0,0);
$message =
"MIME-Version: 1.0\r\n".
"Content-Type: application/x-msnmsgrp2p\r\n".
"P2P-Dest: $from_email\r\n\r\n$hdr".pack('L',0)."$footer";
$this->SB_writeln("MSG $id D ".strlen($message));
$id++;
$this->SB_writedata($message);
$this->debug_message("*** p2p: dump send Data preparation message:\n".$this->dump_binary($message));
$this->debug_message("*** p2p: Data Prepare Hdr:\n".$this->dump_binary($hdr));
$this->SB_readln();//Read ACK;
//send Data Content..
$footer=pack('N',1);
$new_id++;
$FileSize=filesize($PictureFilePath);
if($hTitle=fopen($PictureFilePath,'rb'))
{
$Offset=0;
//$new_id++;
while(!feof($hTitle))
{
$FileContent=fread($hTitle,1024);
$FileContentSize=strlen($FileContent);
$hdr = pack("LLLLLLLLLLLL",
$MsgBody['SessionID'],
$new_id,
$Offset, 0,
$FileSize,0,
$FileContentSize,
0x20,
rand(),
0,
0,0
);
$message =
"MIME-Version: 1.0\r\n".
"Content-Type: application/x-msnmsgrp2p\r\n".
"P2P-Dest: $from_email\r\n\r\n$hdr$FileContent$footer";
$this->SB_writeln("MSG $id D ".strlen($message));
$id++;
$this->SB_writedata($message);
$this->debug_message("*** p2p: dump send Data Content message $Offset / $FileSize :\n".$this->dump_binary($message));
$this->debug_message("*** p2p: Data Content Hdr:\n".$this->dump_binary($hdr));
//$this->SB_readln();//Read ACK;
$Offset+=$FileContentSize;
}
}
//Send Bye
/*
$MessageContent="\r\n".pack("C", 0);
$MessagePayload=
"BYE MSNMSGR:MSNSLP/1.0\r\n".
"To: \r\n".
"From: user.">\r\n".
"Via: MSNSLP/1.0/TLP ;branch={".$BranchGUID."}\r\n".
"CSeq: 0\r\n".
"Call-ID: ".$MsgBody['Call-ID']."\r\n".
"Max-Forwards: 0\r\n".
"Content-Type: application/x-msnmsgr-sessionclosebody\r\n".
"Content-Length: ".strlen($MessageContent)."\r\n\r\n".$MessageContent;
$footer=pack('N',0);
$hdr_TotalDataSizeLow=strlen($MessagePayload);
$hdr_TotalDataSizeHigh=0;
$new_id++;
$hdr = pack("LLLLLLLLLLLL",
0,
$new_id,
0, 0,
$hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
0,
0,
rand(),
0,
0,0);
$message =
"MIME-Version: 1.0\r\n".
"Content-Type: application/x-msnmsgrp2p\r\n".
"P2P-Dest: $from_email\r\n\r\n$hdr$MessagePayload$footer";
$this->SB_writeln("MSG $id D ".strlen($message));
$id++;
$this->SB_writedata($message);
$this->debug_message("*** p2p: dump send BYE message :\n".$this->dump_binary($message));
*/
break;
}
//TODO:
//if ($hdr_Flag == 2) {
// just send ACK...
// $this->SB_writeln("ACK $id");
// break;
//}
if ($hdr_SessionID == 4) {
// ignore?
$this->debug_message("*** p2p: ignore flag 4");
break;
}
$finished = false;
if ($hdr_TotalDataSizeHigh == 0) {
// only 32 bites size
if (($hdr_MessageLength + $hdr_DataOffsetLow) == $hdr_TotalDataSizeLow)
$finished = true;
}
else {
// we won't accept any file transfer
// so I think we won't get any message size need to use 64 bits
// 64 bits size here, can't count directly...
$totalsize = base_convert(sprintf("%X%08X", $hdr_TotalDataSizeHigh, $hdr_TotalDataSizeLow), 16, 10);
$dataoffset = base_convert(sprintf("%X%08X", $hdr_DataOffsetHigh, $hdr_DataOffsetLow), 16, 10);
$messagelength = base_convert(sprintf("%X", $hdr_MessageLength), 16, 10);
$now_size = bcadd($dataoffset, $messagelength);
if (bccomp($now_size, $totalsize) >= 0)
$finished = true;
}
if (!$finished) {
// ignore not finished split packet
$this->debug_message("*** p2p: ignore split packet, not finished");
break;
}
//$new_id = ~$hdr_Identifier;
/*
$new_id++;
$hdr = pack("LLLLLLLLLLLL", $hdr_SessionID,
$new_id,
0, 0,
$hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh,
0,
2,
$hdr_Identifier,
$hdr_AckID,
$hdr_TotalDataSizeLow, $hdr_TotalDataSizeHigh);
$footer = pack("L", 0);
$message = "MIME-Version: 1.0\r\nContent-Type: application/x-msnmsgrp2p\r\nP2P-Dest: $from_email\r\n\r\n$hdr$footer";
$len = strlen($message);
$this->SB_writeln("MSG $id D $len");
$id++;
$this->SB_writedata($message);
$this->log_message("*** p2p: send acknowledgement for $hdr_SessionID");
$this->debug_message("*** p2p: dump sent message:\n".$this->dump_binary($hdr.$footer));
*/
break;
}
$this->log_message("*** MSG from $from_email: $sMsg");
$this->ReceivedMessage($from_email,$sMsg,$network,false);
break;
case '217':
$this->log_message("*** User $user is offline. Try OIM.");
foreach($this->SwitchBoardMessageQueue as $Message)
$this->SendMessage($Message,"$user@Offline");
$SessionEnd=true;
break;
default:
if (is_numeric($code))
{
$this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
$this->debug_message("*** SB: $this->error");
$SessionEnd=true;
}
break;
}
if(!$this->IsIgnoreMail($user)) $LastActive = time();
}
if (feof($this->SBFp))
{
// lost connection? error? try OIM later
@fclose($this->SBFp);
return false;
}
$this->SB_writeln("OUT");
@fclose($this->SBFp);
return true;
}
private function switchboard_control($ip, $port, $cki_code, $user, $Messages)
{
$this->SwitchBoardProcess=1;
$this->debug_message("*** SB: try to connect to switchboard server $ip:$port");
$this->SBFp = @fsockopen($ip, $port, $errno, $errstr, 5);
if (!$this->SBFp)
{
$this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr");
return false;
}
return $this->DoSwitchBoard('Active',array('cki'=>$cki_code, 'user'=>$user,'Msg'=>$Messages));
}
private function switchboard_ring($ip, $port, $sid, $ticket,$user)
{
$this->SwitchBoardProcess=2;
$this->debug_message("*** SB: try to connect to switchboard server $ip:$port");
$this->SBFp = @fsockopen($ip, $port, $errno, $errstr, 5);
if (!$this->SBFp)
{
$this->debug_message("*** SB: Can't connect to $ip:$port, error => $errno, $errstr");
return false;
}
return $this->DoSwitchBoard('Passive',array('sid'=>$sid,'user'=>$user,'ticket'=>$ticket));
}
private function sendOIM($to, $sMessage, $lockkey)
{
$XML = '
http://messenger.msn.com
1
text
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: base64
X-OIM-Message-Type: OfflineMessage
X-OIM-Run-Id: {DAB68CFA-38C9-449B-945E-38AFA51E50A7}
X-OIM-Sequence-Num: 1
'.chunk_split(base64_encode($sMessage)).'
';
$header_array = array(
'SOAPAction: '.$this->oim_send_soap,
'Content-Type: text/xml',
'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger '.$this->buildver.')'
);
$this->debug_message("*** URL: $this->oim_send_url");
$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $this->oim_send_url);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header_array);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
if ($this->debug) curl_setopt($curl, CURLOPT_HEADER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $XML);
$data = curl_exec($curl);
$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);
curl_close($curl);
$this->debug_message("*** Get Result:\n$data");
if ($http_code == 200) {
$this->debug_message("*** OIM sent for $to");
return true;
}
$challenge = false;
$auth_policy = false;
// the lockkey is invalid, authenticated fail, we need challenge it again
// 364763969
preg_match("#(.*)#", $data, $matches);
if (count($matches) != 0) {
// yes, we get new LockKeyChallenge
$challenge = $matches[2];
$this->debug_message("*** OIM need new challenge ($challenge) for $to");
}
// auth policy error
// MBI_SSL
preg_match("#(.*)#", $data, $matches);
if (count($matches) != 0) {
$auth_policy = $matches[2];
$this->debug_message("*** OIM need new auth policy ($auth_policy) for $to");
}
if ($auth_policy === false && $challenge === false) {
//q0:AuthenticationFailed
preg_match("#(.*)#", $data, $matches);
if (count($matches) == 0) {
// no error, we assume the OIM is sent
$this->debug_message("*** OIM sent for $to");
return true;
}
$err_code = $matches[2];
//Exception of type 'System.Web.Services.Protocols.SoapException' was thrown.
preg_match("#(.*)#", $data, $matches);
if (count($matches) > 0)
$err_msg = $matches[1];
else
$err_msg = '';
$this->debug_message("*** OIM failed for $to");
$this->debug_message("*** OIM Error code: $err_code");
$this->debug_message("*** OIM Error Message: $err_msg");
return false;
}
return array('challenge' => $challenge, 'auth_policy' => $auth_policy);
}
// read data for specified size
private function ns_readdata($size) {
$data = '';
$count = 0;
while (!feof($this->NSfp)) {
$buf = @fread($this->NSfp, $size - $count);
$data .= $buf;
$count += strlen($buf);
if ($count >= $size) break;
}
$this->debug_message("NS: data ($size/$count) <<<\n$data");
return $data;
}
// read one line
private function ns_readln() {
$data = @fgets($this->NSfp, 4096);
if ($data !== false) {
$data = trim($data);
$this->debug_message("NS: <<< $data");
}
return $data;
}
// write to server, append \r\n, also increase id
private function ns_writeln($data) {
@fwrite($this->NSfp, $data."\r\n");
$this->debug_message("NS: >>> $data");
$this->id++;
return;
}
// write data to server
private function ns_writedata($data) {
@fwrite($this->NSfp, $data);
$this->debug_message("NS: >>> $data");
return;
}
// read data for specified size for SB
private function sb_readdata($size) {
$data = '';
$count = 0;
while (!feof($this->SBFp)) {
$buf = @fread($this->SBFp, $size - $count);
$data .= $buf;
$count += strlen($buf);
if ($count >= $size) break;
}
$this->debug_message("SB: data ($size/$count) <<<\n$data");
return $data;
}
// read one line for SB
private function sb_readln() {
$data = @fgets($this->SBFp, 4096);
if ($data !== false) {
$data = trim($data);
$this->debug_message("SB: <<< $data");
}
return $data;
}
// write to server for SB, append \r\n, also increase id
// switchboard server only accept \r\n, it will lost connection if just \n only
private function sb_writeln($data) {
@fwrite($this->SBFp, $data."\r\n");
$this->debug_message("SB: >>> $data");
$this->id++;
return;
}
// write data to server
private function sb_writedata($data) {
@fwrite($this->SBFp, $data);
$this->debug_message("SB: >>> $data");
return;
}
// show debug information
function debug_message($str) {
if (!$this->debug) return;
if($this->debug===STDOUT) echo $str."\n";
/*$fname=MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msn_'.strftime('%Y%m%d').'.debug';
$fp = fopen($fname, 'at');
if ($fp) {
fputs($fp, strftime('%m/%d/%y %H:%M:%S').' ['.posix_getpid().'] '.$str."\n");
fclose($fp);
return;
}*/
// still show debug information, if we can't open log_file
echo $str."\n";
return;
}
function dump_binary($str) {
$buf = '';
$a_str = '';
$h_str = '';
$len = strlen($str);
for ($i = 0; $i < $len; $i++) {
if (($i % 16) == 0) {
if ($buf !== '') {
$buf .= "$h_str $a_str\n";
}
$buf .= sprintf("%04X:", $i);
$a_str = '';
$h_str = '';
}
$ch = ord($str[$i]);
if ($ch < 32)
$a_str .= '.';
else
$a_str .= chr($ch);
$h_str .= sprintf(" %02X", $ch);
}
if ($h_str !== '')
$buf .= "$h_str $a_str\n";
return $buf;
}
// write log
function log_message($str) {
/*$fname = MSN_CLASS_LOG_DIR.DIRECTORY_SEPARATOR.'msn_'.strftime('%Y%m%d').'.log';
$fp = fopen($fname, 'at');
if ($fp) {
fputs($fp, strftime('%m/%d/%y %H:%M:%S').' ['.posix_getpid().'] '.$str."\n");
fclose($fp);
}*/
$this->debug_message($str);
return;
}
/**
*
* @param $FilePath 圖檔路徑
* @param $Type 檔案類型 3=>大頭貼,2表情圖案
* @return array
*/
private function MsnObj($FilePath,$Type=3)
{
if(!($FileSize=filesize($FilePath))) return '';
$Location=md5($FilePath);
$Friendly=md5($FilePath.$Type);
if(isset($this->MsnObjMap[$Location])) return $this->MsnObjMap[$Location];
$sha1d=base64_encode(sha1(file_get_contents($FilePath),true));
$sha1c=base64_encode(sha1("Creator".$this->user."Size$FileSize"."Type$Type"."Location$Location"."Friendly".$Friendly."SHA1D$sha1d",true));
$this->MsnObjArray[$Location]=$FilePath;
$MsnObj='';
$this->MsnObjMap[$Location]=$MsnObj;
$this->debug_message("*** p2p: addMsnObj $FilePath::$MsnObj\n");
return $MsnObj;
}
private function linetoArray($lines) {
$lines=str_replace("\r",'',$lines);
$lines=explode("\n",$lines);
foreach($lines as $line) {
if(!isset($line{3})) continue;
list($Key,$Val)=explode(':',$line);
$Data[trim($Key)]=trim($Val);
}
return $Data;
}
private function GetPictureFilePath($Context)
{
$MsnObj=base64_decode($Context);
if(preg_match('/location="(.*?)"/i',$MsnObj,$Match))
$location=$Match[1];
$this->debug_message("*** p2p: PictureFile[$location] ::All".print_r($this->MsnObjArray,true)."\n");
if($location&&(isset($this->MsnObjArray[$location])))
return $this->MsnObjArray[$location];
return false;
}
private function GetMsnObjDefine($Message)
{
$DefineString='';
if(is_array($this->Emotions))
foreach($this->Emotions as $Pattern => $FilePath)
{
if(strpos($Message,$Pattern)!==false)
$DefineString.="$Pattern\t".$this->MsnObj($FilePath,2)."\t";
}
return $DefineString;
}
/**
* Receive Message Overload Function
* @param $Sender
* @param $Message
* @param $Network 1 => msn , 32 =>yahoo
* @param $IsOIM
* @return unknown_type
*/
protected function ReceivedMessage($Sender,$Message,$Network,$IsOIM=false){}
/**
* Remove Us From Member List Overload Function
* @param $User
* @param $Message
* @param $Network 1 => msn , 32 =>yahoo
* @return unknown_type
*/
protected function RemoveUsFromMemberList($User,$Network){}
/**
* Add Us to Member List Overload Function
* @param $User
* @param $Message
* @param $Network 1 => msn , 32 =>yahoo
* @return unknown_type
*/
protected function AddUsToMemberList($User,$Network){}
public function signon() {
$this->log_message("*** try to connect to MSN network");
while(!$this->connect($this->user, $this->password))
{
$this->log_message("!!! Can't connect to server: $this->error");
if(!$this->NSRetryWait($this->retry_wait)) return;
}
$this->UpdateContacts();
$this->LastPing=time();
$this->log_message("*** connected, wait for command");
$start_tm = time();
$ping_tm = time();
stream_set_timeout($this->NSfp, $this->NSStreamTimeout);
$this->aContactList = $this->getMembershipList();
if ($this->update_pending) {
if (is_array($this->aContactList)) {
$pending = 'Pending';
foreach ($this->aContactList as $u_domain => $aUserList) {
foreach ($aUserList as $u_name => $aNetworks) {
foreach ($aNetworks as $network => $aData) {
if (isset($aData[$pending])) {
// pending list
$cnt = 0;
foreach (array('Allow', 'Reverse') as $list) {
if (isset($aData[$list]))
$cnt++;
else {
if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list)) {
$this->aContactList[$u_domain][$u_name][$network][$list] = false;
$cnt++;
}
}
}
if ($cnt >= 2) {
$id = $aData[$pending];
// we can delete it from pending now
if ($this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $pending))
unset($this->aContactList[$u_domain][$u_name][$network][$pending]);
}
}
else {
// sync list
foreach (array('Allow', 'Reverse') as $list) {
if (!isset($aData[$list])) {
if ($this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
$this->aContactList[$u_domain][$u_name][$network][$list] = false;
}
}
}
}
}
}
}
}
$n = 0;
$sList = '';
$len = 0;
if (is_array($this->aContactList)) {
foreach ($this->aContactList as $u_domain => $aUserList) {
$str = '';
$len += strlen($str);
if ($len > 7400) {
$aADL[$n] = ''.$sList.'';
$n++;
$sList = '';
$len = strlen($str);
}
$sList .= $str;
foreach ($aUserList as $u_name => $aNetworks) {
foreach ($aNetworks as $network => $status) {
$str = '';
$len += strlen($str);
// max: 7500, but is 19,
// so we use 7475
if ($len > 7475) {
$sList .= '';
$aADL[$n] = ''.$sList.'';
$n++;
$sList = ''.$str;
$len = strlen($sList);
}
else
$sList .= $str;
}
}
$sList .= '';
}
}
$aADL[$n] = ''.$sList.'';
// NS: >>> BLP {id} BL
$this->ns_writeln("BLP $this->id BL");
foreach ($aADL as $str) {
$len = strlen($str);
// NS: >>> ADL {id} {size}
$this->ns_writeln("ADL $this->id $len");
$this->ns_writedata($str);
}
// NS: >>> PRP {id} MFN name
if ($this->alias == '') $this->alias = $user;
$aliasname = rawurlencode($this->alias);
$this->ns_writeln("PRP $this->id MFN $aliasname");
//設定個人大頭貼
//$MsnObj=$this->PhotoStckObj();
// NS: >>> CHG {id} {status} {clientid} {msnobj}
$this->ns_writeln("CHG $this->id NLN $this->clientid");
if($this->PhotoStickerFile!==false)
$this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
// NS: >>> UUX {id} length
$str = ''.htmlspecialchars($this->psm).'';
$len = strlen($str);
$this->ns_writeln("UUX $this->id $len");
$this->ns_writedata($str);
}
public function NSreceive() {
$this->log_message("*** startup ***");
$aADL = array();
// Sign in again if not signed in or socket failed
if (!is_resource($this->NSfp) || feof($this->NSfp)) {
$this->signon();
}
$data = $this->ns_readln();
if($data === false) {
// There was no data / an error when reading from the socket so reconnect
$this->signon();
} else {
switch (substr($data,0,3))
{
case 'SBS':
// after 'USR {id} OK {user} {verify} 0' response, the server will send SBS and profile to us
// NS: <<< SBS 0 null
break;
case 'RFS':
// FIXME:
// NS: <<< RFS ???
// refresh ADL, so we re-send it again
if (is_array($aADL)) {
foreach ($aADL as $str) {
$len = strlen($str);
// NS: >>> ADL {id} {size}
$this->ns_writeln("ADL $this->id $len");
$this->ns_writedata($str);
}
}
break;
case 'LST':
// NS: <<< LST {email} {alias} 11 0
@list(/* LST */, $email, /* alias */, ) = @explode(' ', $data);
@list($u_name, $u_domain) = @explode('@', $email);
if (!isset($this->aContactList[$u_domain][$u_name][1])) {
$this->aContactList[$u_domain][$u_name][1]['Allow'] = 'Allow';
$this->log_message("*** add to our contact list: $u_name@$u_domain");
}
break;
case 'ADL':
// randomly, we get ADL command, someome add us to their contact list for MSNP15
// NS: <<< ADL 0 {size}
@list(/* ADL */, /* 0 */, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0)
{
$data = $this->ns_readdata($size);
preg_match('##', $data, $matches);
if (is_array($matches) && count($matches) > 0)
{
$u_domain = $matches[1];
$u_name = $matches[2];
$network = $matches[4];
if (isset($this->aContactList[$u_domain][$u_name][$network]))
$this->log_message("*** someone (network: $network) add us to their list (but already in our list): $u_name@$u_domain");
else
{
$re_login = false;
$cnt = 0;
foreach (array('Allow', 'Reverse') as $list)
{
if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
{
if ($re_login) {
$this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
continue;
}
$aTickets = $this->get_passport_ticket();
if (!$aTickets || !is_array($aTickets)) {
// failed to login? ignore it
$this->log_message("*** can't re-login, something wrong here");
$this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
continue;
}
$re_login = true;
$this->ticket = $aTickets;
$this->log_message("**** get new ticket, try it again");
if (!$this->addMemberToList($u_name.'@'.$u_domain, $network, $list))
{
$this->log_message("*** can't add $u_name@$u_domain (network: $network) to $list");
continue;
}
}
$this->aContactList[$u_domain][$u_name][$network][$list] = false;
$cnt++;
}
$this->log_message("*** someone (network: $network) add us to their list: $u_name@$u_domain");
}
$str = '';
$len = strlen($str);
}
else
$this->log_message("*** someone add us to their list: $data");
$this->AddUsToMemberList($u_name.'@'.$u_domain, $network);
}
break;
case 'RML':
// randomly, we get RML command, someome remove us to their contact list for MSNP15
// NS: <<< RML 0 {size}
@list(/* RML */, /* 0 */, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0)
{
$data = $this->ns_readdata($size);
preg_match('##', $data, $matches);
if (is_array($matches) && count($matches) > 0)
{
$u_domain = $matches[1];
$u_name = $matches[2];
$network = $matches[4];
if (isset($this->aContactList[$u_domain][$u_name][$network]))
{
$aData = $this->aContactList[$u_domain][$u_name][$network];
foreach ($aData as $list => $id)
$this->delMemberFromList($id, $u_name.'@'.$u_domain, $network, $list);
unset($this->aContactList[$u_domain][$u_name][$network]);
$this->log_message("*** someone (network: $network) remove us from their list: $u_name@$u_domain");
}
else
$this->log_message("*** someone (network: $network) remove us from their list (but not in our list): $u_name@$u_domain");
$this->RemoveUsFromMemberList($u_name.'@'.$u_domain, $network);
}
else
$this->log_message("*** someone remove us from their list: $data");
}
break;
case 'MSG':
// randomly, we get MSG notification from server
// NS: <<< MSG Hotmail Hotmail {size}
@list(/* MSG */, /* Hotmail */, /* Hotmail */, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0) {
$data = $this->ns_readdata($size);
$aLines = @explode("\n", $data);
$header = true;
$ignore = false;
$maildata = '';
foreach ($aLines as $line) {
$line = rtrim($line);
if ($header) {
if ($line === '') {
$header = false;
continue;
}
if (strncasecmp($line, 'Content-Type:', 13) == 0) {
if (strpos($line, 'text/x-msmsgsinitialmdatanotification') === false &&
strpos($line, 'text/x-msmsgsoimnotification') === false) {
// we just need text/x-msmsgsinitialmdatanotification
// or text/x-msmsgsoimnotification
$ignore = true;
break;
}
}
continue;
}
if (strncasecmp($line, 'Mail-Data:', 10) == 0) {
$maildata = trim(substr($line, 10));
break;
}
}
if ($ignore) {
$this->log_message("*** ingnore MSG for: $line");
break;
}
if ($maildata == '') {
$this->log_message("*** ingnore MSG not for OIM");
break;
}
$re_login = false;
if (strcasecmp($maildata, 'too-large') == 0) {
$this->log_message("*** large mail-data, need to get the data via SOAP");
$maildata = $this->getOIM_maildata();
if ($maildata === false) {
$this->log_message("*** can't get mail-data via SOAP");
// maybe we need to re-login again
$aTickets = $this->get_passport_ticket();
if (!$aTickets || !is_array($aTickets)) {
// failed to login? ignore it
$this->log_message("*** can't re-login, something wrong here, ignore this OIM");
break;
}
$re_login = true;
$this->ticket = $aTickets;
$this->log_message("**** get new ticket, try it again");
$maildata = $this->getOIM_maildata();
if ($maildata === false) {
$this->log_message("*** can't get mail-data via SOAP, and we already re-login again, so ignore this OIM");
break;
}
}
}
// could be a lots of ..., so we can't use preg_match here
$p = $maildata;
$aOIMs = array();
while (1) {
$start = strpos($p, '');
$end = strpos($p, '');
if ($start === false || $end === false || $start > $end) break;
$end += 4;
$sOIM = substr($p, $start, $end - $start);
$aOIMs[] = $sOIM;
$p = substr($p, $end);
}
if (count($aOIMs) == 0) {
$this->log_message("*** ingnore empty OIM");
break;
}
foreach ($aOIMs as $maildata) {
// T: 11 for MSN, 13 for Yahoo
// S: 6 for MSN, 7 for Yahoo
// RT: the datetime received by server
// RS: already read or not
// SZ: size of message
// E: sender
// I: msgid
// F: always 00000000-0000-0000-0000-000000000009
// N: sender alias
preg_match('#(.*)#', $maildata, $matches);
if (count($matches) == 0) {
$this->log_message("*** ingnore OIM maildata without type");
continue;
}
$oim_type = $matches[1];
if ($oim_type = 13)
$network = 32;
else
$network = 1;
preg_match('#(.*)#', $maildata, $matches);
if (count($matches) == 0) {
$this->log_message("*** ingnore OIM maildata without sender");
continue;
}
$oim_sender = $matches[1];
preg_match('#(.*)#', $maildata, $matches);
if (count($matches) == 0) {
$this->log_message("*** ingnore OIM maildata without msgid");
continue;
}
$oim_msgid = $matches[1];
preg_match('#(.*)#', $maildata, $matches);
$oim_size = (count($matches) == 0) ? 0 : $matches[1];
preg_match('##', $maildata, $matches);
$oim_time = (count($matches) == 0) ? 0 : $matches[1];
$this->log_message("*** You've OIM sent by $oim_sender, Time: $oim_time, MSGID: $oim_msgid, size: $oim_size");
$sMsg = $this->getOIM_message($oim_msgid);
if ($sMsg === false) {
$this->log_message("*** can't get OIM, msgid = $oim_msgid");
if ($re_login) {
$this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM");
continue;
}
$aTickets = $this->get_passport_ticket();
if (!$aTickets || !is_array($aTickets)) {
// failed to login? ignore it
$this->log_message("*** can't re-login, something wrong here, ignore this OIM");
continue;
}
$re_login = true;
$this->ticket = $aTickets;
$this->log_message("**** get new ticket, try it again");
$sMsg = $this->getOIM_message($oim_msgid);
if ($sMsg === false) {
$this->log_message("*** can't get OIM via SOAP, and we already re-login again, so ignore this OIM");
continue;
}
}
$this->log_message("*** MSG (Offline) from $oim_sender (network: $network): $sMsg");
//$this->ReceivedMessage($oim_sender,$sMsg,$network,true);
$this->callHandler('IMin', array('sender' => $oim_sender, 'message' => $sMsg, 'network' => $network, 'offline' => true));
}
}
break;
case 'UBM':
// randomly, we get UBM, this is the message from other network, like Yahoo!
// NS: <<< UBM {email} $network $type {size}
@list(/* UBM */, $from_email, $network, $type, $size,) = @explode(' ', $data);
if (is_numeric($size) && $size > 0)
{
$data = $this->ns_readdata($size);
$aLines = @explode("\n", $data);
$header = true;
$ignore = false;
$sMsg = '';
foreach ($aLines as $line) {
$line = rtrim($line);
if ($header) {
if ($line === '') {
$header = false;
continue;
}
if (strncasecmp($line, 'TypingUser:', 11) == 0) {
$ignore = true;
break;
}
continue;
}
$aSubLines = @explode("\r", $line);
foreach ($aSubLines as $str) {
if ($sMsg !== '')
$sMsg .= "\n";
$sMsg .= $str;
}
}
if($ignore)
{
$this->log_message("*** ingnore from $from_email: $line");
break;
}
$this->log_message("*** MSG from $from_email (network: $network): $sMsg");
//$this->ReceivedMessage($from_email,$sMsg,$network,false);
$this->callHandler('IMin', array('sender' => $from_email, 'message' => $sMsg, 'network' => $network, 'offline' => false));
}
break;
case 'UBX':
// randomly, we get UBX notification from server
// NS: <<< UBX email {network} {size}
@list(/* UBX */, /* email */, /* network */, $size,) = @explode(' ', $data);
// we don't need the notification data, so just ignore it
if (is_numeric($size) && $size > 0)
$this->ns_readdata($size);
break;
case 'CHL':
// randomly, we'll get challenge from server
// NS: <<< CHL 0 {code}
@list(/* CHL */, /* 0 */, $chl_code,) = @explode(' ', $data);
$fingerprint = $this->getChallenge($chl_code);
// NS: >>> QRY {id} {product_id} 32
// NS: >>> fingerprint
$this->ns_writeln("QRY $this->id $this->prod_id 32");
$this->ns_writedata($fingerprint);
$this->ns_writeln("CHG $this->id NLN $this->clientid");
if($this->PhotoStickerFile!==false)
$this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
break;
case 'CHG':
// NS: <<< CHG {id} {status} {code}
// ignore it
// change our status to online first
break;
case 'XFR':
// sometimes, NS will redirect to another NS
// MSNP9
// NS: <<< XFR {id} NS {server} 0 {server}
// MSNP15
// NS: <<< XFR {id} NS {server} U D
// for normal switchboard XFR
// NS: <<< XFR {id} SB {server} CKI {cki} U messenger.msn.com 0
@list(/* XFR */, /* {id} */, $server_type, $server, /* CKI */, $cki_code, /* ... */) = @explode(' ', $data);
@list($ip, $port) = @explode(':', $server);
if ($server_type != 'SB') {
// maybe exit?
// this connection will close after XFR
$this->NSLogout();
continue;
}
if(count($this->MessageQueue))
{
foreach($this->MessageQueue as $User => $Message)
{
//$this->ChildProcess[$ChildPid]
$this->log_message("*** XFR SB $User");
$pid=pcntl_fork();
if($pid)
{
//Parrent Process
$this->ChildProcess[$pid]=$User;
break;
}
elseif($pid==-1)
{
$this->log_message("*** Fork Error $User");
break;
}
else
{
//Child Process
$this->log_message("*** Child Process Start for $User");
unset($Message['XFRSent']);
unset($Message['ReqTime']);
$bSBresult = $this->switchboard_control($ip, $port, $cki_code, $User, $Message);
if ($bSBresult === false)
{
// error for switchboard
$this->log_message("!!! error for sending message to ".$User);
}
die;
}
}
unset($this->MessageQueue[$User]);
}
/*
$bSBresult = $this->switchboard_control($ip, $port, $cki_code, $aMSNUsers[$nCurrentUser], $sMessage);
if ($bSBresult === false) {
// error for switchboard
$this->log_message("!!! error for sending message to ".$aMSNUsers[$nCurrentUser]);
$aOfflineUsers[] = $aMSNUsers[$nCurrentUser];
}*/
break;
case 'QNG':
// NS: <<< QNG {time}
@list(/* QNG */, $ping_wait) = @explode(' ', $data);
//if ($this->ping_wait == 0) $this->ping_wait = 50;
//if (is_int($use_ping) && $use_ping > 0) $ping_wait = $use_ping;
//Mod by Ricky Set Online
$this->callHandler('Pong', $ping_wait);
break;
case 'RNG':
if($this->PhotoStickerFile!==false)
$this->ns_writeln("CHG $this->id NLN $this->clientid ".rawurlencode($this->MsnObj($this->PhotoStickerFile)));
else
$this->ns_writeln("CHG $this->id NLN $this->clientid");
// someone is trying to talk to us
// NS: <<< RNG {session_id} {server} {auth_type} {ticket} {email} {alias} U {client} 0
$this->log_message("NS: <<< RNG $data");
@list(/* RNG */, $sid, $server, /* auth_type */, $ticket, $email, $name, ) = @explode(' ', $data);
@list($sb_ip, $sb_port) = @explode(':', $server);
if($this->IsIgnoreMail($email))
{
$this->log_message("*** Ignore RNG from $email");
break;
}
$this->log_message("*** RING from $email, $sb_ip:$sb_port");
$this->addContact($email,1,$email, true);
$pid=pcntl_fork();
if($pid)
{
//Parrent Process
$this->ChildProcess[$pid]='RNG';
break;
}
elseif($pid==-1)
{
$this->log_message("*** Fork Error $User");
break;
}
else
{
//Child Process
$this->log_message("*** Ring Child Process Start for $User");
$this->switchboard_ring($sb_ip, $sb_port, $sid, $ticket,$email);
die;
}
break;
case 'OUT':
// force logout from NS
// NS: <<< OUT xxx
$this->log_message("*** LOGOUT from NS");
return $this->NsLogout();
default:
$code = substr($data,0,3);
if (is_numeric($code)) {
$this->error = "Error code: $code, please check the detail information from: http://msnpiki.msnfanatic.com/index.php/Reference:Error_List";
$this->debug_message("*** NS: $this->error");
return $this->NsLogout();
}
break;
}
}
}
public function sendMessageViaSB($message, $to) {
$socket = $this->switchBoardSessions[$to]['socket'];
$lastActive = $this->switchBoardSessions[$to]['lastActive'];
$joined = $this->switchBoardSessions[$to]['joined'];
//FIXME Probably not needed (we're not running in a loop anymore)
/*if($this->kill_me)
{
$this->log_message("*** SB Okay, kill me now!");
endSBSession($socket);
}*/
if(!$Joined) {
// If our participant has not joined the session yet we can't message them!
//TODO Check the behaviour of the queue runner when we return false
return false;
}
$aMessage = $this->getMessage($Message);
//CheckEmotion...
$MsnObjDefine=$this->GetMsnObjDefine($aMessage);
if($MsnObjDefine !== '')
{
$SendString="MIME-Version: 1.0\r\nContent-Type: text/x-mms-emoticon\r\n\r\n$MsnObjDefine";
$len = strlen($SendString);
$this->SB_writeln("MSG $id N $len");
$id++;
$this->SB_writedata($SendString);
$this->id++;
}
$len = strlen($aMessage);
$this->SB_writeln("MSG $id N $len");
// Increment the trID
$this->switchBoardSessions[$to]['id']++;
$this->SB_writedata($aMessage);
if (feof($this->SBFp))
{
// lost connection? error? try OIM later
@fclose($this->SBFp);
//TODO introduce callback to add offline message to queue?
return false;
}
$this->SB_writeln("OUT");
@fclose($this->SBFp);
return true;
}
//FIXME Not sure if this is needed?
private function endSBSession($socket) {
if (feof($this->SBFp))
{
// lost connection? error? try OIM later
@fclose($this->SBFp);
return false;
}
$this->SB_writeln("OUT");
@fclose($this->SBFp);
return true;
}
public function sendMessage($message, $to) {
if($message != '') {
list($name,$host,$network)=explode('@',$to);
$network=$network==''?1:$network;
if($network === 1 && isset($this->switchBoardSessions[$to])) {
$recipient = $name . $host;
$this->debug_message("*** Sending Message to $recipient using existing SB session");
return $this->sendMessageViaSB($message, $recipient);
} else {
$this->debug_message("*** Not MSN network or no existing SB session");
//TODO implement creation of SB session etc
}
}
return true;
}
/**
* Sends a ping command
*
* Should be called about every 50 seconds
*/
public function send_ping() {
// NS: >>> PNG
$this->ns_writeln("PNG");
}
public function getNSSocket() {
return $this->NSfp;
}
// TODO Allow for multiple SB session sockets
public function getSBSocket() {
return $this->SBfp;
}
public function getSockets() {
return array($this->NSfp, $this->SBfp);
}
/**
* Calls User Handler
*
* Calls registered handler for a specific event.
*
* @param String $event Command (event) name (Rvous etc)
* @param String $data Raw message from server
* @see registerHandler
* @return void
*/
private function callHandler($event, $data) {
if (isset($this->myEventHandlers[$event])) {
call_user_func($this->myEventHandlers[$event], $data);
} else {
$this->noHandler($data);
}
}
/**
* Registers a user handler
*
* Handler List
* IMIn, Pong
*
* @param String $event Event name
* @param String $handler User function to call
* @see callHandler
* @return boolean Returns true if successful
*/
public function registerHandler($event, $handler) {
if (is_callable($handler)) {
$this->myEventHandlers[$event] = $handler;
return true;
} else {
return false;
}
}
}