分类:
2009-12-30 18:44:32
function getChallenge($code){
// MSNP15
//
// 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;
$aMessage = array();
$aStr = @explode("\n", $sMessage);
$cur_len = 0;
$msg = '';
$add_crlf = false;
foreach ($aStr as $str) {
$str = str_replace("\r", '', $str);
$len = strlen($str);
while ($len > $maxlen) {
if ($cur_len > 0) {
// already has header/msg
$aMessage[] = $msg_header.$msg;
$cur_len = 0;
$msg = '';
$add_crlf = false;
}
$aMessage[] = $msg_header.substr($str, 0, $maxlen);
$str = substr($str, $maxlen);
$len = strlen($str);
}
if (($cur_len + $len) > $maxlen) {
$aMessage[] = $msg_header.$msg;
$cur_len = 0;
$msg = '';
$add_crlf = false;
}
if ($msg !== '' || $add_crlf) {
$msg .= "\r\n";
$cur_len += 2;
}
$add_crlf = true;
$msg .= $str;
$cur_len += $len;
}
if ($cur_len != 0)
$aMessage[] = $msg_header.$msg;
return $aMessage;
}
private function switchboard_control($ip, $port, $cki_code, $sTo, $sMessage)
{
$this->debug_message("*** SB: try to connect to switchboard server $ip:$port");
$this->sb = @fsockopen($ip, $port, $errno, $errstr, 5);
if (!$this->sb) {
$this->error = "SB: Can't connect to $ip:$port, error => $errno, $errstr";
$this->debug_message("*** $this->error");
return false;
}
$user = $sTo;
stream_set_timeout($this->sb, $this->stream_timeout);
// SB: >>> USR {id} {user} {cki}
$this->sb_writeln("USR $this->id $this->user $cki_code");
$sent = false;
$start_tm = time();
$got_error = false;
$offline = false;
while (!feof($this->sb)) {
if ($sent || $offline) break;
$data = $this->sb_readln();
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) {
$this->error = 'Timeout, maybe protocol changed!';
$this->debug_message("*** $this->error");
break;
}
}
continue;
}
$code = substr($data, 0, 3);
$start_tm = time();
switch($code) {
case 'USR':
$this->sb_writeln("CAL $this->id $user");
break;
case 'CAL':
break;
case '217':
$this->debug_message("*** SB: $user offline! skip to send message!");
$offline = true;
break;
case 'JOI':
$aMessage = $this->getMessage($sMessage);
foreach ($aMessage as $message) {
$len = strlen($message);
$this->sb_writeln("MSG 20 N $len");
$this->sb_writedata($message);
}
$sent = true;
break;
default:
if (is_numeric($code)) {
$this->error = "Error code: $code, please check the detail information from: ";
$this->debug_message("*** SB: $this->error");
$got_error = true;
}
break;
}
}
if (feof($this->sb)) {
// lost connection? error? try OIM later
@fclose($this->sb);
return false;
}
$this->sb_writeln("OUT");
@fclose($this->sb);
if ($offline || $got_error) return false;
return true;
}
private function sendOIM($to, $sMessage, $lockkey) {
$XML = '
xmlns:xsd=""
xmlns:soap="">
.$this->user.'"
friendlyName="=?utf-8?B?'.base64_encode($this->user).'?="
xml:lang="zh-TW"
proxy="MSNMSGR"
xmlns=""
msnpVer="MSNP15"
buildVer="8.1.0178"/>
.$to.'" xmlns=""/>
.htmlspecialchars($this->oim_ticket).'"
appid="'.$this->prod_id.'"
lockkey="'.$lockkey.'"
xmlns=""/>
1
text
>http://messenger.msn.com/ws/2004/09/oim/">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)).'
Content>
soap:Body>
soap:Envelope>';
$header_array = array(
'SOAPAction: http://messenger.live.com/ws/2006/09/oim/Store2',
'Content-Type: text/xml',
'User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; Messenger 8.1.0178)'
);
//$this->debug_message("*** URL: $this->oim_send_url");
//$this->debug_message("*** Sending SOAP:\n$XML");
$curl = curl_init();
curl_setopt($curl, CURLOPT_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];
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 readdata($size)
{
$data = '';
$count = 0;
while (!feof($this->fp)) {
$buf = @fread($this->fp, $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 readln()
{
$data = @fgets($this->fp, 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 writeln($data)
{
@fwrite($this->fp, $data."\r\n");
//$this->debug_message("NS: >>> $data");
$this->id++;
return;
}
// write data to server
private function writedata($data) {
@fwrite($this->fp, $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->sb)) {
$buf = @fread($this->sb, $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->sb, 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->sb, $data."\r\n");
//$this->debug_message("SB: >>> $data");
$this->id++;
return;
}
// write data to server
private function sb_writedata($data) {
@fwrite($this->sb, $data);
return;
}
// show debug information
private function debug_message($str) {
if ($this->debug) echo $str."\n";
}
}