分类:
2009-12-30 18:41:30
/**
* 基于 版
* 去除大段注释,以及无用代码
* 仅使用发消息及获取好友列表功能
* 2008-12-15 李勇
*/
class MSN{
//dispatch服务器地址及端口
private $server = 'messenger.hotmail.com';
private $port = 1863;
private $passport_url = '';
private $prod_key = 'PK}_A_0N_K%O?A9S';
private $prod_id = 'PROD0114ES4Z%Q5W';
private $clientid = '0x7000800C';
private $id;
private $fp = false;
private $error = '';
private $authed = false;
private $user = '';
private $password = '';
private $passport_policy = '';
private $oim_try = 3;
private $oim_ticket = '';
private $contact_ticket = '';
private $debug = true;
private $timeout = 15;
private $stream_timeout = 2;
private $sb;
private $font_fn = 'Arial';
private $font_co = '333333';
private $font_ef = '';
private $max_msn_message_len = 1664;
private $max_yahoo_message_len = 518;
/**
* 构造函数,需要以下PHP扩展的支持curl,prece,mhash,mcrypt,bcmath
*
* @return MSN
*/
public function MSN(){
if (!function_exists('curl_init')) die("We need curl module!\n");
if (!function_exists('preg_match')) die("We need pcre module!\n");
if (!function_exists('mhash'))die("We need mhash module !\n");
if (!function_exists('mcrypt_cbc')) die("We need mcrypt module !\n");
if (!function_exists('bcmod'))die("We need bcmath module !\n");
return;
}
/**
* 登录指定MSN账号
*
* @param string $user
* @param string $password
* @return bool
* @uses
* var
* id
* fp
* =>error
* =>authed
* timeout
* =>passport_policy
* =>oim_ticket
* =>contact_ticket
* =>user
* =>password
* function
* readln
* writeln
* readdata
* debug_message
* get_passport_ticket
* generateLoginBLOB
*/
public function connect($user, $password){
$this->id = 1;
$server='messenger.hotmail.com';
$port=1863;
$errno=0;
$errstr='';
$stream_timeout = 2;
$this->fp = @fsockopen($server, $port, $errno, $errstr, 5);
if (!$this->fp) {
$this->error = "Can't connect to $server:$port, error => $errno, $errstr";
return false;
}
stream_set_timeout($this->fp, $stream_timeout);
$this->authed = false;
$this->writeln("VER $this->id MSNP15 CVR0");
$start_tm = time();
while (!feof($this->fp)) {
$data = $this->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->writeln("OUT");
fclose($this->fp);
$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':
$this->writeln("CVR $this->id 0x0409 winnt 5.1 i386 MSMSGS 8.1.0178 msmsgs $user");
break;
case 'CVR':
$this->writeln("USR $this->id SSO I $user");
break;
case 'USR':
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->writeln("OUT");
fclose($this->fp);
$this->error = 'Passport authenticated fail!';
$this->debug_message("*** $this->error");
return false;
}
$this->oim_ticket = $aTickets['oim_ticket'];
$this->contact_ticket = $aTickets['contact_ticket'];
// NS: >>> USR {id} SSO S {ticket} {login_code}
$this->writeln("USR $this->id SSO S ".$aTickets['ticket']." ".$this->generateLoginBLOB($aTickets['secret'], $nonce));
$this->authed = true;
break;
case 'XFR':
@list(/* XFR */, /* id */, /* NS */, $server, /* ... */) = @explode(' ', $data);
@list($ip, $port) = @explode(':', $server);
// this connection will close after XFR
fclose($this->fp);
$errno=0;
$errstr='';
$this->fp = @fsockopen($ip, $port, $errno, $errstr, 5);
if (!$this->fp) {
$this->error = "Can't connect to $ip:$port, error => $errno, $errstr";
$this->debug_message("*** $this->error");
return false;
}
stream_set_timeout($this->fp, $this->stream_timeout);
$this->writeln("VER $this->id MSNP15 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->readdata($size);
break;
default:
// we'll quit if got any error
if (is_numeric($code)) {
// logout now
// NS: >>> OUT
$this->writeln("OUT");
fclose($this->fp);
$this->error = "Error code: $code, please check the detail information from: ";
$this->debug_message("*** $this->error");
return false;
}
// unknown response from server, just ignore it
break;
}
}
// never goto here
return false;
}
/**
* 获取通行证的令牌
*
* @param string $url
* @return string
* @uses
* var
* user<=
* password<=
* passport_url<=
* passport_policy<=
* function
*/
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 = '
xmlns:wsse=""
xmlns:saml="urn:oasis:names:tc:SAML:1.0:assertion"
xmlns:wsp=""
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
xmlns:wsa=""
xmlns:wssc=""
xmlns:wst="">
{7108E71A-9926-4FCB-BCC9-9A9D3F32E423}
4
1
AQAAAAIAAABsYwQAAAAxMDMz
' .$user.'
' .$password.'
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
http://schemas.xmlsoap.org/ws/2004/04/security/trust/Issue
messengerclear.live.com
.$this->passport_policy.'">
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
';
$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);
if ($http_code != 200) {
// sometimes, redirect to another URL
// MSNP15
//psf:Redirect
//
//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
//
//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);
}
}
preg_match("#".
"(.*) (.*)".
"(.*) (.*)".
"(.*) (.*)".
"(.*) (.*)".
"(.*) (.*)".
"(.*) (.*)".
"(.*) (.*)".
"#",
$data, $matches);
// no ticket found!
if (count($matches) == 0) {
$this->debug_message("*** Can't get passport ticket!");
return false;
}
// yes, we get ticket
$aTickets = array(
'ticket' => html_entity_decode($matches[1]),
'secret' => html_entity_decode($matches[3]),
'contact_ticket' => html_entity_decode($matches[7]),
'oim_ticket' => html_entity_decode($matches[9]),
);
return $aTickets;
}