Файл: CMS/core/classes/WebIcqPro.class.php
Строк: 2170
<?php
include(dirname(__FILE__) . '/rtf.class.php');
/**
* http://wip.asminog.com/forum/ - disscus this package here.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
* See http://www.gnu.org/copyleft/lesser.html
*
* @author Sergey Akudovich
* @package WebIcqPro
*
*/
class WebIcqPro_LV {
const VERSION = '1.4.8b';
/**
* Last error message
*
* @var string
*/
public $error;
protected function packLV($value, $length = 'n')
{
return pack($length, strlen($value)).$value;
}
protected function unpackLV(&$data, $length = 2)
{
if (strlen($data)>=$length)
{
$result = unpack('nsize', $data);
$result['data'] = substr($data, $length , $result['size']);
$data = substr($data, $result['size']+$length);
return $result['data'];
}
$this->error = 'unpackLV brocken LV';
return false;
}
}
/**
* Layer for TLV (Type-Length-Value) data format
* @access private
*/
class WebIcqPro_TLV extends WebIcqPro_LV {
/**
* Pack data to TLV
*
* Use this method to create TLVs. available formats 'c', 'n', 'N', 'a*', 'H*'
*
* @access private
* @param integer $type
* @param string $value
* @param string $format
* @return string binary
*/
protected function packTLV($type, $value='', $format = 'a*')
{
if (in_array($format, array('c', 'n', 'N', 'a*', 'H*')))
{
$len = strlen($value);
switch ($format) {
case 'c':
$len = 1;
break;
case 'n':
$len = 2;
break;
case 'N':
$len = 4;
break;
case 'H*':
$len = strlen($value)/2;
break;
}
return pack('nn'.$format, $type, $len, $value);
}
$this->error = 'Warning: packTLV unknown data format: '.$format;
return false;
}
/**
* Unpack data from TLV
*
* Use this method to extract TLV binary data
*
* @access private
* @param string $data
* @return string
*/
protected function unpackTLV(& $data)
{
if (strlen($data)>=4)
{
$result = unpack('ntype/nsize', $data);
$result['data'] = substr($data, 4 , $result['size']);
$data = substr($data, $result['size']+4);
return $result;
}
$this->error = 'Error: unpackTLV brocken TLV';
return false;
}
/**
* Unpuck data from set of TLVs
*
* Use this method to extract a set of TLVs binary data to an associated array where array indexes are TLV type and value TLV data
*
* @access private
* @param string $data
* @return array
*/
protected function splitTLVsToArray($data, $fixqip = false)
{
$tlv = array();
while (strlen($data) > 0)
{
$tlv_data = $this->unpackTLV($data);
if ($tlv_data)
{
$tlv[$tlv_data['type']] = $tlv_data['data'];
}
elseif($fixqip)
{
if(isset($tlv[0x0101]))
{
$tlv[0x0101] = substr($tlv[0x0101], 0 , -1);
}
return $tlv;
}
else
{
return false;
}
}
return $tlv;
}
}
/**
* Layer for SNAC data format
* @access private
*/
class WebIcqPro_SNAC extends WebIcqPro_TLV {
/**
* Request counter
*
* This variable store request id for SNAC
*
* @access private
* @var integer
*/
private $request_id;
/**
* Capability for message
*
* This variable store current method of capability for ibcm
*
* @access private
* @var string
*/
private $ibcm_capabilities;
/**
* Message type
*
* This variable store current type of ibcm
*
* @access private
* @var string
*/
private $ibcm_type;
/**
* Message Encoding
*
* This variable store current encoding of message
*
* @access private
* @var string
*/
private $ibcm_encoding;
/**
* Client type.
*
* This variable used to store current client name.
*
* @access private
* @var string
*/
protected $agent;
/**
* Array of known SNACs
*
* Full list of supported SNACs. All added handlers should be added here.
*
* @access private
* @var array
*/
protected $snac_names = array(
0x01 => array( // Generic service controls
0x02 => 'ClientReady',
0x03 => 'ServerFamilies',
0x06 => 'ClientRates',
0x07 => 'ServerRates',
0x08 => 'ClientRatesAck',
0x0E => 'ClientRequestSelfInfo',
0x0F => 'ServerResponseSelfInfo',
0x11 => 'ClientIdleTime',
0x17 => 'ClientFamiliesVersions',
0x18 => 'ServerFamiliesVersions',
0x1E => 'ClientStatus',
'version' => 0x04
),
0x02 => array( // Location services
0x02 => 'ClientLocationRights',
0x03 => 'ServerLocationRights',
0x04 => 'ClientLocationInfo',
'version' => 0x01
),
0x03 => array( // Buddy List management service
0x01 => 'OscarError',
0x02 => 'ClientBuddylistRights',
0x03 => 'ServerBuddylistRights',
0x04 => 'ClientBuddylistAdd',
0x05 => 'ClientBuddylistDelete',
0x0B => 'ServerUserOnline',
0x0C => 'ServerUserOffline',
'version' => 0x01
),
0x04 => array( // ICBM (messages) service
0x01 => 'OscarError',
0x02 => 'ClientSetIBCMParams',
0x04 => 'ClientIBCMRights',
0x05 => 'ServerIBCMRights',
0x06 => 'ClientIBCM',
0x07 => 'ServerIBCM',
0x0B => 'ClientIBCMAck',
0x0C => 'ServerIBCMAck',
'version' => 0x01
),
0x09 => array( // Privacy management service
0x02 => 'ClientPrivicyRights',
0x03 => 'ServerPrivicyRights',
'version' => 0x01
),
0x13 => array( // Server Side Information (SSI) service
0x01 => 'ServerSSIError',
0x02 => 'ClientSSIRights',
0x03 => 'ServerSSIRights',
0x04 => 'ClientSSI',
0x05 => 'ClientSSICheckout',
0x06 => 'ServerSSI',
0x07 => 'ClientSSIActivate',
0x08 => 'ClientSSIAdd',
0x0A => 'ClientSSIDelete',
0x0E => 'ServerSSIAck',
0x0F => 'ServerSSIModificationDate',
0x11 => 'ClientSSIEditStart',
0x12 => 'ClientSSIEditEnd',
0x18 => 'ClientSSIAuthRequest',
0x19 => 'ServerSSIAuthRequest',
0x1a => 'ClientSSIAuthResponse',
0x1b => 'ServerSSIAuthResponse',
0x1c => 'ServerSSIYouAdded',
'version' => 0x04
),
0x15 => array( // ICQ specific extensions service
0x02 => 'ClientMetaData',
0x03 => 'ServerMetaData',
'version' => 0x01
),
0x17 => array( // Authorization/registration service
0x02 => 'ClientMd5Login',
0x03 => 'ServerMd5LoginReply',
0x06 => 'ClientMd5Request',
0x07 => 'ServerMd5Response',
'version' => 0x01
)
);
public $debug = false;
protected $protocol_version = 11;
protected $capability_flag = '03000000';
private $rates;
protected $rates_groups;
private $login_errors = array(
0x0001 => 'Invalid nick or password',
0x0002 => 'Service temporarily unavailable',
0x0003 => 'All other errors',
0x0004 => 'Incorrect nick or password, re-enter',
0x0005 => 'Mismatch nick or password, re-enter',
0x0006 => 'Internal client error (bad input to authorizer)',
0x0007 => 'Invalid account',
0x0008 => 'Deleted account',
0x0009 => 'Expired account',
0x000A => 'No access to database',
0x000B => 'No access to resolver',
0x000C => 'Invalid database fields',
0x000D => 'Bad database status',
0x000E => 'Bad resolver status',
0x000F => 'Internal error',
0x0010 => 'Service temporarily offline',
0x0011 => 'Suspended account',
0x0012 => 'DB send error',
0x0013 => 'DB link error',
0x0014 => 'Reservation map error',
0x0015 => 'Reservation link error',
0x0016 => 'The users num connected from this IP has reached the maximum',
0x0017 => 'The users num connected from this IP has reached the maximum (reservation)',
0x0018 => 'Rate limit exceeded (reservation). Please try to reconnect in a few minutes',
0x0019 => 'User too heavily warned',
0x001A => 'Reservation timeout',
0x001B => 'You are using an older version of ICQ. Upgrade required',
0x001C => 'You are using an older version of ICQ. Upgrade recommended',
0x001D => 'Rate limit exceeded. Please try to reconnect in a few minutes',
0x001E => 'Can`t register on the ICQ network. Reconnect in a few minutes',
0x0020 => 'Invalid SecurID',
0x0022 => 'Account suspended because of your age (age < 13)'
);
private $oscar_errors = array(
0x01 => 'Invalid SNAC header.',
0x02 => 'Server rate limit exceeded',
0x03 => 'Client rate limit exceeded',
0x04 => 'Recipient is not logged in',
0x05 => 'Requested service unavailable',
0x06 => 'Requested service not defined',
0x07 => 'You sent obsolete SNAC',
0x08 => 'Not supported by server',
0x09 => 'Not supported by client',
0x0A => 'Refused by client',
0x0B => 'Reply too big',
0x0C => 'Responses lost',
0x0D => 'Request denied',
0x0E => 'Incorrect SNAC format',
0x0F => 'Insufficient rights',
0x10 => 'In local permit/deny (recipient blocked)',
0x11 => 'Sender too evil',
0x12 => 'Receiver too evil',
0x13 => 'User temporarily unavailable',
0x14 => 'No match',
0x15 => 'List overflow',
0x16 => 'Request ambiguous',
0x17 => 'Server queue full',
0x18 => 'Not while on AOL',
);
private $oscar_buddy_errors = array(
0x0000 => 'No errors (success)',
0x0002 => 'Item you want to modify not found in list',
0x0003 => 'Item you want to add allready exists',
0x000A => 'Error adding item (invalid id, allready in list, invalid data)',
0x000C => 'Can't add item. Limit for this type of items exceeded',
0x000D => 'Trying to add ICQ contact to an AIM list',
0x000E => 'Can't add this contact because it requires authorization'
);
protected $substatuses = array(
'STATUS_WEBAWARE' => 0x0001,
'STATUS_SHOWIP' => 0x0002,
'STATUS_BIRTHDAY' => 0x0008,
'STATUS_WEBFRONT' => 0x0020,
'STATUS_DCDISABLED' => 0x0100,
'STATUS_DCAUTH' => 0x1000,
'STATUS_DCCONT' => 0x2000
);
protected $statuses = array(
'STATUS_ONLINE' => 0x0000,
'STATUS_AWAY' => 0x0001,
'STATUS_DND' => 0x0002,
'STATUS_DND2' => 0x0013,
'STATUS_NA' => 0x0004,
'STATUS_NA2' => 0x0005,
'STATUS_OCCUPIED' => 0x0010,
'STATUS_OCCUPIED2' => 0x0011,
'STATUS_FREE4CHAT' => 0x0020,
'STATUS_INVISIBLE' => 0x0100,
'STATUS_EVIL' => 0x3000,
'STATUS_DEPRESSION' => 0x4000,
'STATUS_ATHOME' => 0x5000,
'STATUS_ATWORK' => 0x6000,
'STATUS_LUNCH' => 0x2001,
'STATUS_OFFLINE' => 0xFFFF
);
protected $status_message = '';
protected $capabilities = array(
'0138ca7b769a491588f213fc00979ea8',
'67361515612d4c078f3dbde6408ea041',
'1a093c6cd7fd4ec59d51a6474e34f5a0',
'b2ec8f167c6f451bbd79dc58497888b9',
'094613494C7F11D18222444553540000',
'0946134E4C7F11D18222444553540000',
'094613434C7F11D18222444553540000',
'563FC8090B6F41BD9F79422609DFA2F3'
// '094600004C7F11D18222444553540000', // Avatar
//'0946134D4C7F11D18222444553540000', // Setting this lets AIM users receive messages from ICQ users, and ICQ users receive messages from AIM users. It also lets ICQ users show up in buddy lists for AIM users, and AIM users show up in buddy lists for ICQ users. And ICQ privacy/invisibility acts like AIM privacy, in that if you add a user to your deny list, you will not be able to see them as online (previous you could still see them, but they couldn't see you.
//'094613444C7F11D18222444553540000', // Something called "route finder". Currently used only by ICQ2K clients.
//'094613494C7F11D18222444553540000', // Client supports channel 2 extended, TLV(0x2711) based messages. Currently used only by ICQ clients. ICQ clients and clones use this GUID as message format sign.
//'1A093C6CD7FD4EC59D51A6474E34F5A0', // Xtraz
// '0946134E4C7F11D18222444553540000', // Client supports UTF-8 messages. This capability currently used by AIM service and AIM clients.
// '97B12751243C4334AD22D6ABF73F1492', // Client supports RTF messages. This capability currently used by ICQ service and ICQ clients.
// '563FC8090B6f41BD9F79422609DFA2F3', // Typing Notifications
);
protected $user_agent_capability = array(
'miranda' => '4D6972616E64614D0004000200030700',
'sim' => '53494D20636C69656E74202000090402',
'trillian' => '97B12751243C4334AD22D6ABF73F1409',
'licq' => '4c69637120636c69656e742030303030',
'kopete' => '4b6f7065746520494351202030303030',
'micq' => '6d49435120A920522e4b2e2030303030',
'andrq' => '265251696e7369646530303030303030',
'randq' => '522651696e7369646530303030303030',
'mchat' => '6d436861742069637120303030303030',
'jimm' => '4a696d6d203030303030303030303030',
'macicq' => 'dd16f20284e611d490db00104b9b4b7d',
'icqlite' => '178C2D9BDAA545BB8DDBF3BDBD53A10A',
'icq' => '178c2d9bdaa545bb8ddbf3bdbd53a10a',
// 'qip' => '563FC8090B6F41514950203230303561',
// 'qippda' => '563FC8090B6F41514950202020202021',
// 'qipmobile' => '563FC8090B6F41514950202020202022',
// 'anastasia' => '44E5BFCEB096E547BD65EFD6A37E3602',
// 'icq2001' => '2e7a6475fadf4dc8886fea3595fdb6df',
// 'icq2002' => '10cf40d14c7f11d18222444553540000',
// 'IcqJs7 => '6963716A202020202020202020202020',
// 'IcqJs7sec => '6963716A2053656375726520494D2020',
// 'TrilCrypt => 'f2e7c7f4fead4dfbb23536798bdf0000',
// 'SimOld => '97b12751243c4334ad22d6abf73f1400',
// 'Im2 => '74EDC33644DF485B8B1C671A1F86099F',
// 'Is2001 => '2e7a6475fadf4dc8886fea3595fdb6df',
// 'Is2002 => '10cf40d14c7f11d18222444553540000',
// 'Comm20012 => 'a0e93f374c7f11d18222444553540000',
// 'StrIcq => 'a0e93f374fe9d311bcd20004ac96dd96',
// 'AimIcon => '094613464c7f11d18222444553540000',
// 'AimDirect => '094613454c7f11d18222444553540000',
// 'AimChat => '748F2420628711D18222444553540000',
// 'Uim => 'A7E40A96B3A0479AB845C9E467C56B1F',
// 'Rambler => '7E11B778A3534926A80244735208C42A',
// 'Abv => '00E7E0DFA9D04Fe19162C8909A132A1B',
// 'Netvigator => '4C6B90A33D2D480E89D62E4B2C10D99F',
// 'tZers => 'b2ec8f167c6f451bbd79dc58497888b9',
// 'HtmlMsgs => '0138ca7b769a491588f213fc00979ea8',
// 'SimpLite => '53494D5053494D5053494D5053494D50',
// 'SimpPro => '53494D505F50524F53494D505F50524F',
'webicqpro' => '57656249637150726f00010405000062'
);
private $message_capabilities = array(
'TLV2711' => '094613494c7f11d18222444553540000',
'REVERSE_REQ' => '094613444c7f11d18222444553540000',
'OSCAR_FT' => '094613434c7f11d18222444553540000',
'OSCAR_FT' => '094613434c7f11d18222444553540000',
'MESSAGE' => '00000000000000000000000000000000',
'PLUGIN' => ARRAY(
'MESSAGE' => 'be6b73050fc2104fa6de4db1e3564b0e',
'STATUSMSGEXT' => '811a18bc0e6c1847a5916f18dcc76f1a',
'FILE' => 'f02d12d93091d3118dd700104b06462e',
'WEBURL' => '371c5872e987d411a4c100d0b759b1d9',
'CONTACTS' => '2a0e7d467676d411bce60004ac961ea6',
'GREETING_CARD' => '01e53b482ae4d111b679006097e1e294',
'CHAT' => 'bff720b2378ed411bd280004ac96d905',
'SMS_MESSAGE' => '0e28f60011e7d311bcf30004ac969dc2',
'XTRAZ_SCRIPT' => '3b60b3efd82a6c45a4e09c5a5e67e865'
)
);
/**
* Set of - X Statuses.
* @thanks �_� ���� (http://intrigue.ru/forum/index.php?action=profile;u=190)
*
* @var array
*/
protected $x_statuses = array(
"journal" => '0072d9084ad143dd91996f026966026f',
"angry" => '01d8d7eeac3b492aa58dd3d877e66b92',
"ppc" => '101117c9a3b040f981ac49e159fbd5d4',
"cinema" => '107a9a1812324da4b6cd0879db780f09',
"phone" => '1292e5501b644f66b206b29af378e48d',
"browsing" => '12d07e3ef885489e8e97a72a6551e58d',
"mobile" => '160c60bbdd4443f39140050f00e6c009',
"wc" => '16f5b76fa9d240358cc5c084703c98fa',
"coffee" => '1b78ae31fa0b4d3893d1997eeeafb218',
"sick" => '1f7a4071bf3b4e60bc324c5787b04cf1',
"picnic" => '2ce0e4e57c6443709c3a7a1ce878a7dc',
"thinking" => '3fb0bd36af3b4a609eefcf190f6a5a7e',
"smoking" => '3fb0bd36af3b4a609eefcf190f6a5a7f',
"business" => '488e14898aca4a0882aa77ce7a165208',
"duck" => '5a581ea1e580430ca06f612298b7e4c7',
"studying" => '609d52f8a29a49a6b2a02524c5e9d260',
"?" => '631436ff3f8a40d0a5cb7b66e051b364',
"typing" => '634f6bd8add24aa1aab9115bc26d05a1',
"shopping" => '63627337a03f49ff80e5f709cde0a4ee',
"music" => '61bee0dd8bdd475d8dee5f4baacf19a7',
"zzz" => '6443c6af22604517b58cd7df8e290352',
"fun" => '6f4930984f7c4affa27634a03bceaea7',
"sleeping" => '785e8c4840d34c65886f04cf3f3f43df',
"tv" => '80537de2a4674a76b3546dfd075f5ec6',
"tired" => '83c9b78e77e74378b2c5fb6cfcc35bec',
"beer" => '8c50dbae81ed4786acca16cc3213c7b7',
"surfing" => 'a6ed557e6bf744d4a5d4d2e7d95ce81f',
"pro7" => 'b70867f538254327a1ffcf4cc1939797',
"working" => 'ba74db3e9e24434b87b62f6b8dfee50f',
"love2" => 'cd5643a2c94c4724b52cdc0124a1d0cd',
"gaming" => 'd4a611d08f014ec09223c5b6bec6ccf0',
"google" => 'd4e2b0ba334e4fa598d0117dbf4d3cc8',
"love" => 'ddcf0ea971954048a9c6413206d6f280',
"party" => 'e601e41c33734bd1bc06811d6c323d81',
"sex" => 'e601e41c33734bd1bc06811d6c323d82',
"meeting" => 'f18ab52edc57491d99dc6444502457af',
"eating" => 'f8e8d7b282c4414290f810c6ce0a89a6',
);
protected $contact_list = array();
protected $contact_list_groups = array('all_childs_ids' => array(0));
protected $encodings = array(
'ASCII' => 0x00,
'UNICODE' => 0x02,
'LATIN_1' => 0x03
);
/**
* Set default values
*
* @return WebIcqPro_SNAC
*/
protected function __construct()
{
$this->request_id = 0;
$this->setMessageType();
$this->setMessageCapabilities();
$this->setUserAgent();
}
private function beautifyBinaryLog($data)
{
$return = chunk_split(chunk_split(strtoupper(bin2hex($data)), 2, ' '), 24, ' ');
$return = str_split($return, 50);
$search = array("r", "n", "t");
$replace = array(".", ".", ".");
foreach ($return as $key => $line)
{
$return[$key] = str_pad($line, 50) . str_replace($search, $replace, substr($data, 0+16*$key, 16));
}
return implode("rn", $return);
}
private function dump($str, $file = 'dump')
{
if ($this->debug && $file) {
$f = fopen($file, 'a');
fwrite($f, $str);
fclose($f);
}
else if (!$file)
{
echo $str;
}
}
public function log($msg, $data='', $tolog = false)
{
$msg .= "n".trim($this->beautifyBinaryLog($data))."nn";
if ($tolog)
{
$this->dump($msg, false);
}
else
{
$this->dump($msg);
}
}
private function parseSnac($snac)
{
if (strlen($snac) > 10)
{
$return = unpack('ntype/nsubtype/nflag/Nrequest_id', $snac);
$return['data'] = substr($snac, 10);
return $return;
}
$this->error = 'Error: Broken SNAC can`t parse';
return false;
}
protected function analizeSnac($snac)
{
$snac = $this->parseSnac($snac);
if ($snac)
{
if (isset($this->snac_names[$snac['type']][$snac['subtype']]))
{
if (method_exists($this, $this->snac_names[$snac['type']][$snac['subtype']]))
{
$snac['callback'] = $this->snac_names[$snac['type']][$snac['subtype']];
}
}
}
return $snac;
}
/**
* Return SNAC header
*
* @param ineger $type
* @param integer $subtype
* @param integer $flag
* @return string binary
*/
private function __header($type, $subtype, $flag = 0)
{
return pack('nnnN', $type, $subtype, $flag, ++$this->request_id);
}
/**
* Pack ready CNAC
*
* @return string binary
*/
protected function ClientReady($args)
{
return $this->__header(0x01, 0x02).pack('n*',
0x0022, 0x0001, 0x0110, 0x164F,
0x0001, 0x0004, 0x0110, 0x164F,
0x0013, 0x0004, 0x0110, 0x164F,
0x0002, 0x0001, 0x0110, 0x164F,
0x0003, 0x0001, 0x0110, 0x164F,
0x0015, 0x0001, 0x0110, 0x164F,
0x0004, 0x0001, 0x0110, 0x164F,
0x0006, 0x0001, 0x0110, 0x164F,
0x0009, 0x0001, 0x0110, 0x164F,
0x000A, 0x0001, 0x0110, 0x164F,
0x000B, 0x0001, 0x0110, 0x164F);
}
protected function ServerFamilies($data)
{
$families = unpack('n*', $data);
foreach ($this->snac_names as $family => $value)
{
if (!in_array($family, $families))
{
unset($this->snac_names[$family]);
}
}
return true;
}
protected function ClientRates($args)
{
return $this->__header(0x01, 0x06);
}
protected function ServerRates($data)
{
$classes = unpack('n', $data);
$data = substr($data, 2);
for ($i = 0; $i < $classes; $i++)
{
if (strlen($data)>=35)
{
$rate = unpack('nrate/Nwindow/Nclear/Nalert/Nlimit/Ndisconect/Ncurrent/Nmax/Ntime/cstate', $data);
$this->rates[array_shift($rate)] = $rate;
$data = substr($data, 35);
}
else
{
$this->error = 'Notice: Can`t get rates from server';
return false;
}
}
while (strlen($data) >= 4)
{
$group = unpack('nclass/nsise');
$data = substr($data, 4);
if (strlen($data) >= 4*$group['size'])
{
$this->rates_groups[$group['class']] = unpack(substr(str_repeat('N/', $group['size']), -1), $data);
$data = substr($data, 4*$group['size']);
}
else
{
$this->error = 'Notice: Can`t get rates groups from server';
return false;
}
}
if (strlen($data))
{
$this->error = 'Notice: Can`t get rates/groups from server';
return false;
}
return true;
}
protected function ClientRatesAck($args)
{
if (is_array($this->rates_groups) && count($this->rates_groups))
{
$snac = $this->__header(0x01, 0x08);
foreach ($this->rates_groups as $group => $falilies)
{
$snac .= pack('n', $group);
}
return $snac;
}
$this->error = 'Error: Can`t create SNAC rates groups empty';
return false;
}
protected function ClientRequestSelfInfo($args)
{
return $this->__header(0x01, 0x0E);
}
/**
* Requested online info response
*
* @todo implement this functionality
* @param string $data
* @return boolean
*/
protected function ServerResponseSelfInfo($data)
{
return true;
}
protected function ClientIdleTime($args)
{
return $this->__header(0x01, 0x11).pack('N', 1);
}
protected function ClientFamiliesVersions($args)
{
$snac = $this->__header(0x01, 0x17);
foreach ($this->snac_names as $fname => $family)
{
$snac .= pack('n2', $fname, $family['version']);
}
return $snac;
}
protected function ServerFamiliesVersions($data)
{
$families = unpack('n*', $data);
while (count($families)>1)
{
$falily = array_shift($families);
$version = array_shift($families);
$versions[$falily] = $version;
}
foreach ($this->snac_names as $fname => $family)
{
if (!isset($versions[$fname])) // todo: need to do something with families lower versions // || $versions[$fname] > $family['version']
{
unset($this->snac_names[$fname]);
}
}
return true;
}
protected function ClientStatus($args)
{
extract($args);
$snac = $this->__header(0x01, 0x1E);
$snac .= $this->packTLV(0x06, ($this->substatuses[$substatus]<<16) + $this->statuses[$status], 'N');
$snac .= $this->packTLV(0x0C, pack('NNcnNNNNNNn', 0x00, 0x00, 0x00, $this->protocol_version, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00));
return $snac;
}
protected function ClientLocationRights($args)
{
return $this->__header(0x02, 0x02);
}
/**
* @todo
*/
protected function ServerLocationRights($data)
{
return true;
}
protected function ClientLocationInfo($args)
{
return $this->__header(0x02, 0x04).$this->packTLV(0x05, implode($this->capabilities).$this->user_agent_capability[$this->agent], 'H*');
}
protected function OscarError($data)
{
$error = unpack('ncode', $data);
if (isset($this->oscar_errors[$error['code']])) {
$error['error'] = $this->oscar_errors[$error['code']];
}
return $this->createResponse("error", $error);
}
protected function ClientBuddylistRights($args)
{
return $this->__header(0x03, 0x02);
}
/**
* @todo
*/
protected function ServerBuddylistRights($data)
{
return true;
}
/**
* Add contact to list
*
* @deprecated
* @param array $args
* @return string binary
*/
protected function ClientBuddyListAdd($args)
{
extract($args);
$data = '';
if(is_array($uins)) {
foreach ($uins as $id) {
$data .= pack('c', strlen($id)).$id;
$this->contact_list[$id] = array();
}
}
else {
$data .= pack('c', strlen($uins)).$uins;
$this->contact_list[$uins] = array();
}
return $this->__header(0x03, 0x04).$data;
}
/**
* Delete contact from list
*
* @deprecated
* @param array $args
* @return string binary
*/
protected function ClientBuddyListDelete($args)
{
extract($args);
$data = '';
if(is_array($uins)) {
foreach ($uins as $uin) {
$data .= pack('c', strlen($uin)).$uin;
unset($this->contact_list[$uin]);
}
}
else {
$data .= pack('c', strlen($uins)).$uins;
unset($this->contact_list[$uins]);
}
return $this->__header(0x03, 0x05).$data;
}
protected function ServerUserOnline($data)
{
$response = $this->createResponse('useronline');
$info = unpack('clength', $data);
$uin = substr($data, 1, $info['length']);
$response['uin'] = $uin;
$data = substr($data, ($info['length']+1));
if (!isset($this->contact_list[$uin]))
{
return false;
// We dont need it!!!
//$this->contact_list[$uin] = array();
}
else
{
if(isset($this->contact_list[$uin]['status']))
{
$response['old_status'] = $this->contact_list[$uin]['status'];
}
unset($this->contact_list[$uin]['ip']);
unset($this->contact_list[$uin]['online_time']);
unset($this->contact_list[$uin]['signon_time']);
unset($this->contact_list[$uin]['member_since']);
unset($this->contact_list[$uin]['substatus']);
unset($this->contact_list[$uin]['status']);
}
$info = unpack('nwarning_level/nsize', $data);
$data = substr($data, 4);
for ($i = 0; $i < $info['size']; $i++)
{
$tlv = $this->unpackTLV($data);
switch ($tlv['type']) {
case 0x000A:
$this->contact_list[$uin]['ip'] = long2ip($tlv['data']);
break;
case 0x000F:
$this->contact_list[$uin]['online_time'] = $tlv['data'];
break;
case 0x0003:
$this->contact_list[$uin]['signon_time'] = $tlv['data'];
break;
case 0x0005:
$this->contact_list[$uin]['member_since'] = $tlv['data'];
break;
case 0x0006:
$status = unpack('nsubstatus/nstatus', $tlv['data']);
$this->contact_list[$uin]['substatus'] = array_search($status['substatus'], $this->substatuses);
$this->contact_list[$uin]['status'] = array_search($status['status'], $this->statuses);
$response['status'] = $this->contact_list[$uin]['status'];
break;
case 0x0001: // todo: user class
case 0x000C: // todo: dc info
case 0x000D: // todo: user capabilities
case 0x0011: // todo: time updated
case 0x0019: // todo: new style capabilities
case 0x001D: // todo: user icon id & hash
break;
}
}
return $response;
}
protected function ServerUserOffline($data)
{
return $this->ServerUserOnline($data);
}
protected function ClientIBCMRights()
{
return $this->__header(0x04, 0x04);
}
/**
* @todo
*/
protected function ServerIBCMRights($data)
{
$this->writeFlap('ClientSetIBCMParams', array());
return true;
}
protected function ClientSetIBCMParams($args)
{
extract($args);
if(!isset($channel)) $channel = 0x00;
return $this->__header(0x04, 0x02).pack('nNnnnN', $channel, 0x03, 0x1f40, 0x03e7, 0X03e7, 0x00);
}
/**
* Pack message to SNAC
*
* @param string $uin
* @param string $message
* @return string binary
*/
protected function ClientIBCM($args)
{
extract($args);
$uin_size = strlen($uin);
$message_size = strlen($message);
$cookie = microtime(true);
$snack = $this->__header(0x04, 0x06).pack('dnca*', $cookie, $this->ibcm_type, $uin_size, $uin);
switch ($this->ibcm_type)
{
case 0x01:
$tlv_data = pack('c2nc3n3a*', 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, ($message_size+4), $this->ibcm_encoding, 0x00, $message);
$snack .= $this->packTLV(0x02, $tlv_data).$this->packTLV(0x03).$this->packTLV(0x06);
break;
case 0x02:
$tlv_data = pack('ndH*n5n2v2d2nVn3dn3cvnva*H*', 0x00, $cookie, $this->ibcm_capabilities, 0x0A, 0x02, 0x01, 0x0F, 0x00, 0x2711, ($message_size+62), 0x1B, $this->protocol_version, 0x00, 0x00, 0x00, 0x03, $this->request_id, 0x0E, $this->request_id, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, ($message_size+1), $message, '0000000000FFFFFF00');
$snack .= $this->packTLV(0x05, $tlv_data).$this->packTLV(0x03);
break;
default:
$this->error = 'Warning: Snac ClientIBCM unknown ibcm_type';
$snack = false;
break;
}
return array('return' => $cookie, 'data' => $snack);
}
/**
* Unpack message from server
*
* @param string $data
* @return array
*/
protected function ServerIBCM($data)
{
if (strlen($data))
{
$msg = unpack('dcookie/nchannel/cnamesize', $data);
$data = substr($data, 11);
$return['from'] = substr($data, 0, $msg['namesize']);
$return['channel'] = $msg['channel'];
$return['cookie'] = $msg['cookie'];
$data = substr($data, $msg['namesize']);
$msg = unpack('nwarnlevel/nTLVnumber', $data);
$data = substr($data, 4);
$tlvs = $this->splitTLVsToArray($data);
foreach ($tlvs as $type => $data)
{
switch ($type)
{
case 2:
if ($return['channel'] == 1)
{
$subtlvs = $this->splitTLVsToArray($data, true);
if (isset($subtlvs[0x0101]))
{
$return['encoding'] = unpack('nnumset/nsubset', substr($subtlvs[0x0101], 0, 4));
$return['encoding']['numset'] = in_array($return['encoding']['numset'], $this->encodings) ? array_search($return['encoding']['numset'], $this->encodings): $return['encoding']['numset'];
$return['message'] = substr($subtlvs[0x0101], 4);
$return = $this->createResponse('message', $return);
}
}
break;
case 5:
if ($return['channel'] == 2)
{
if (strlen($data) < 26 )
{
// empty message
return false;
}
$msg = unpack('ntype/dcookie', $data);
$return['type'] = $msg['type'];
$return['cookie'] = $msg['cookie'];
$return['capability'] = bin2hex(substr($data, 10, 16));
$data = substr($data, 26);
if ($return['capability'] != $this->message_capabilities['TLV2711'])
{
// todo: handel other
echo "Not supported message capability:".$return['capability']."rn";
$this->log('>>', $data, true);
return $return;
}
$subtlvs = $this->splitTLVsToArray($data);
foreach ($subtlvs as $type => $data)
{
switch ($type)
{
case 0x00004:
$return['external_ip'] = long2ip($data);
case 0x00005:
$return['listening_port'] = $data;
break;
case 0x2711:
if (strlen($data) < 33)
{
// empty message
echo "Empty?rn";
$this->log('>>', $data, true);
return $return;
}
$meta = unpack('vuId/vversion', substr($data, 0, 4));
$return['version'] = $meta['version'];
$return['capability2'] = bin2hex(substr($data, 4, 16));
$meta = unpack('vuId/vcookie2', substr($data, 29, 4));
$return['cookie2'] = $meta['cookie2'];
$data = substr($data, 33);
switch ($return['capability2']) {
// MESSAGE
case $this->message_capabilities['MESSAGE']:
if (strlen($data) < 20)
{
echo "Empty message?rn";
$this->log('>>', $data, true);
return $return;
}
//empty zerous
$meta = unpack('Ctype/Cflag/vstatus/vpriority/vsize', substr($data, 12, 8));
$data = substr($data, 20);
switch ($meta['type'])
{
// Chat request message
case 0x02:
$return['debugmsg'] = 'deprecated?';
$return = $this->createResponse('chatrequest', $return);
break;
// File request / file ok message
case 0x03:
if ($return['type'] == 0 || $return['type'] = 1)
{
$return = $this->createResponse('filerequest', $return);
}
else if ($return['type'] == 2)
{
$return = $this->createResponse('fileresponse', $return);
}
else
{
//strange file message
$return['debugmsg'] = 'strange file message';
return $return;
}
break;
// Plugin message described by text string
case 0x1A:
return $this->parseServerGreeting($return, $meta, $data);
break;
default:
return $this->parseMessageTypes($return, $meta, $data);
break;
}
break;
}
break;
}
}
}
break;
case 6:
$status = unpack('nstatus/nsubstatus', $data);
$return['status'] = $status['status'];
$return['substatus'] = $status['substatus'];
break;
}
}
$this->writeFlap('ClientIBCMAck', $return);
return $return;
}
return false;
}
private function parseMessageTypes($return, $meta, $data)
{
if ($return['type'] == 2)
{
//todo: hendle message ack
return $return;
}
//todo: process 0xFE formatted
switch ($meta['type']) {
// Plain text (simple) message
case 0x01:
//todo: DC special check
$meta['rtf'] = false;
if (strlen($data) > $meta['size']+ 36)
{
//rtf message
if (strpos($data, '{97B12751-243C-4334-AD22-D6ABF73F1492}') !== false)
{
$meta['rtf'] = true;
}
//utf-8 message
if (strpos($data, '{0946134E-4C7F-11D1-8222-444553540000}') !== false)
{
$return['encoding'] = array('numset' => 'UTF-8', 'subset' => 0);
}
}
$return['message'] = substr($data, 0, $meta['size']);
if ($meta['rtf'])
{
$return['rtf'] = $return['message'];
$return['message'] = RTF::Text($return['message']);
}
$return = $this->createResponse('message', $return);
break;
// URL message (0xFE formatted)
case 0x04:
$this->log('URL', $data);
$return['message'] = substr($data, 0, $meta['size']);
$return = $this->createResponse('urlmessage', $return);
break;
// Authorization request message (0xFE formatted)
case 0x06:
// Authorization denied message (0xFE formatted)
case 0x07:
// Authorization given message (empty)
case 0x08:
// Message from OSCAR server (0xFE formatted)
case 0x09:
// Web pager message (0xFE formatted)
case 0x0D:
// Email express message (0xFE formatted)
case 0x0E:
// Contact list message (0xFE formatted)
case 0x13:
$this->log($meta['type'].'>>', $data, true);
//$return['message'] = substr($data, 0, $meta['size']);
$return = array_merge($return, $meta);
break;
// "You-were-added" message (0xFE formatted)
case 0x0C:
$return = $this->createResponse('youadded', $return);
break;
// Auto away message
case 0xE8:
$return['message'] = substr($data, 0, $meta['size']);
$return = $this->createResponse('autoaway', $return);
break;
// Auto occupied message
case 0xE9:
$return['message'] = substr($data, 0, $meta['size']);
$return = $this->createResponse('autooccupied', $return);
break;
// Auto not available message
case 0xEA:
$return['message'] = substr($data, 0, $meta['size']);
$return = $this->createResponse('autona', $return);
break;
// Auto do not disturb message
case 0xEB:
$return['message'] = substr($data, 0, $meta['size']);
$return = $this->createResponse('autodnd', $return);
break;
// Auto free for chat message
case 0xEC:
$return['message'] = substr($data, 0, $meta['size']);
$return = $this->createResponse('autofreeforchat', $return);
break;
}
return $return;
}
private function parseServerGreeting($return, $meta, $data)
{
if (strlen($data) < 24)
{
$return['debugmsg'] = 'to short to indificate plugin type';
$this->log('SG', $data, true);
return $return;
}
// $plugin = unpack()
//
//
// $return['capability'] = bin2hex(substr($data, 10, 16));
return false;
}
protected function ClientIBCMAck($args)
{
if (is_array($args))
{
extract($args);
$uin_size = strlen($from);
$channel = isset($channel) ? $channel : 0x00;
$cookie = isset($cookie) ? $cookie : 0x00;
$reason = isset($reason) ? $reason : (in_array($channel, array(0x01, 0x02)) ? 0x03 : 0x01);
$message = (isset($message) && $message != '') ? '' : $this->status_message;
$snack = $this->__header(0x04, 0x0B).pack('dnca*n', $cookie, $channel, $uin_size, $from, $reason);
switch ($channel)
{
case 0x02:
$data = pack('vH*nH*cn', $this->protocol_version, '00000000000000000000000000000000', 0x00, $this->capability_flag, 0x00, 0x07);
$message = pack('nH*va*H*', 0x07, '000000000000000000000000E80300000000', strlen($message)+1, $message, '0000000000FFFFFF00');
$data .= pack('v', strlen($message)).$message;
$snack .= pack('v', 0x1B).$data;
break;
default:
$message_size = strlen($message);
$snack .= pack('c2nc3n3a*', 0x05, 0x01, 0x01, 0x01, 0x01, 0x01, ($message_size+4), 0x03, 0x00, $message);
break;
}
return $snack;
}
else
{
// todo: get answer from server.
}
}
protected function ServerIBCMAck($data)
{
if (strlen($data) > 11) {
$msg = unpack('did/nchannel/cuin', $data);
$msg['uin'] = substr($data, 11, $msg['uin']);
return $this->createResponse('accepted', $msg);
}
$this->error = 'ServerIBCMAck: too short';
return false;
}
protected function ClientPrivicyRights()
{
return $this->__header(0x09, 0x02);
}
/**
* @todo
*/
protected function ServerPrivicyRights($data)
{
return true;
}
protected function ServerSSIError($data)
{
$response = unpack('ncode', substr($data, 0, 2));
if (isset($this->oscar_errors[$response['code']])) {
$response['error'] = $this->oscar_errors[$response['code']];
}
return $this->createResponse('error', $response);
}
protected function ClientSSIRights($args)
{
return $this->__header(0x13, 0x02);
}
/**
* @todo
*/
protected function ServerSSIRights($data)
{
return true;
}
protected function ClientSSI($args)
{
return $this->__header(0x13, 0x04);
}
protected function ServerSSI($data, $flag = 0)
{
$this->error = "";
$coockie = substr($data, 0, 8);
$data = substr($data, 8);
$header = unpack('cversion/nlength', $data);
$data = substr($data, 3);
for ($i=0; $i < $header['length']; $i++) {
if(strlen($data) >= 10) {
$itemname = $this->unpackLV($data);
$props = unpack('ngroupID/nitemID/ntype/nsize', $data);
$data = substr($data, 8);
if(strlen($data) >= $props['size']) {
$props['tlvs'] = array();
if($props['size'] > 0) {
$props['tlvs'] = $this->splitTLVsToArray(substr($data, 0, $props['size']));
$data = substr($data, $props['size']);
}
switch ($props['type']) {
case 0x0000: // Buddy record (name: uin for ICQ and screenname for AIM)
$this->contact_list[$itemname] = $this->convertContact($props);
break;
case 0x0001: // Group record
$this->contact_list_groups[$itemname] = $this->convertGroup($props);
$this->contact_list_groups['all_childs_ids'] = array_merge($this->contact_list_groups['all_childs_ids'], $this->contact_list_groups[$itemname]['childs']);
case 0x0002: // Permit record ("Allow" list in AIM, and "Visible" list in ICQ)
case 0x0003: // Deny record ("Block" list in AIM, and "Invisible" list in ICQ)
case 0x0004: // Permit/deny settings or/and bitmask of the AIM classes
case 0x0005: // Presence info (if others can see your idle status, etc)
case 0x0009: // Unknown. ICQ2k shortcut bar items ?
case 0x000E: // Ignore list record.
case 0x000F: // Last update date (name: "LastUpdateDate").
case 0x0010: // Non-ICQ contact (to send SMS). Name: 1#EXT, 2#EXT, etc
case 0x0013: // Item that contain roster import time (name: "Import time")
case 0x0014: // Own icon (avatar) info. Name is an avatar id number as text
break;
}
}
else {
$this->error .= 'SSI extra data parsing error item #'.($i+1).' Name: '.$itemname."rn";
}
}
else {
$this->error .= 'SSI parsing error item #'.($i+1)."rn";
return false;
}
}
if ($flag == 0 || $flag == 32768) {
$this->writeFlap('ClientSSIActivate');
return $this->createResponse("contactlist");
}
return true;
}
private function convertContact($item)
{
$contact = array();
if(is_array($item['tlvs']))
{
foreach ($item['tlvs'] as $key => $value) {
switch ($key) {
case 0x0066:
$contact['authorization'] = $value;
break;
case 0x0131:
$contact['name'] = $value;
break;
case 0x0137:
$contact['mail'] = $value;
break;
case 0x013A:
$contact['sms'] = $value;
break;
case 0x013C:
$contact['comment'] = $value;
break;
case 0x0145:
$contact['time'] = $value;
break;
default:
$contact[$key] = $value;
break;
}
}
}
$contact['id'] = $item['itemID'];
$contact['group'] = $item['groupID'];
return $contact;
}
private function convertGroup($item)
{
$group = array('childs' => array());
if(is_array($item['tlvs']))
{
foreach ($item['tlvs'] as $key => $value) {
switch ($key) {
case 0x00C8:
while (strlen($value) > 1)
{
$id = unpack('n', substr($value, 0, 2));
$group['childs'][] = $id[1];
$value = substr($value, 2);
}
break;
}
}
}
$group['id'] = $item['itemID'];
$group['group'] = $item['groupID'];
return $group;
}
/**
* Check SSI state (time / number of items)
*
* @todo implement this functionality
* @param string $data
* @return boolean
*/
protected function ClientSSICheckout($args)
{
extract($args);
if(!isset($time))
{
$time = '47877097';
}
return $this->__header(0x13, 0x05).pack('Nn', $time, 0x00);
}
protected function ClientSSIActivate($args)
{
return $this->__header(0x13, 0x07);
}
protected function ClientSSIAdd($args)
{
extract($args);
$uins = isset($uins) ? $uins : array();
$data = '';
foreach ($uins as $uin => $name) {
if(!isset($this->contact_list[$uin]))
{
$data .= $this->packContact($uin, $name, array('id'=>$this->reservItemId($group), 'group' => $this->getGroupId($group)), true);
}
}
if (isset($group) && isset($parent))
{
$data .= $this->packGroup($group, $parent);
}
if (strlen($data) > 0) {
return $this->__header(0x13, 0x08).$data;
}
$this->error = "SSIAdd error: Nothing to add.";
return false;
}
private function reservItemId($group)
{
if (isset($this->contact_list_groups[$group]))
{
$id = 0;
while ($id++ < 65535)
{
if (!in_array($id, $this->contact_list_groups['all_childs_ids']))
{
$this->contact_list_groups['all_childs_ids'][] = $id;
return $id;
}
}
return $id;
}
}
private function getGroupId($name)
{
if (isset($this->contact_list_groups[$name]))
{
return $this->contact_list_groups[$name]['group'];
}
$this->error = 'getGroupId: No such group '.$name;
return false;
}
protected function ClientSSIDelete($args)
{
extract($args);
$uins = isset($uins) ? $uins : array();
$data = '';
foreach ($uins as $uin) {
if(isset($this->contact_list[$uin]))
{
$data .= $this->packContact($uin, false, $this->contact_list[$uin]);
}
}
if (isset($group) && isset($parent))
{
$data .= $this->packGroup($group, $parent);
}
if (strlen($data) > 0) {
return $this->__header(0x13, 0x0A).$data;
}
$this->error = "SSIDelete error: Nothing to delete.";
return false;
}
private function packContact($uin, $name = false, $contact = array(), $authorize = false)
{
//todo: groups
$tlv = $name ? $this->packTLV(0x0131, $name) : '';
if ($authorize) {
$tlv .= $this->packTLV(0x66);
}
$group = isset($contact['group']) ? $contact['group'] : 0;
$id = isset($contact['id']) ? $contact['id'] : 0;
return $this->packLV($uin).pack('nnn', $group, $id, 0x00).$this->packLV($tlv);
}
private function packGroup($name, $parent = "")
{
$group = $this->getGroupId($parent);
$id = $this->getGroupId($name) ? $this->getGroupId($name) : $this->reservItemId($parent);
return $this->packLV($name).pack('nnnn', $id, $group, 0x01, 0x00);
}
/**
* @todo
*/
protected function ServerSSIModificationDate($data)
{
return true;
}
/**
* @todo errors to is
*/
protected function ServerSSIAck($data)
{
$errors = array('errors' => array());
while (strlen($data) > 1) {
$error = unpack('ncode', $data);
if (isset($this->oscar_buddy_errors[$error['code']])) {
$error['error'] = $this->oscar_buddy_errors[$error['code']];
}
$data = substr($data, 2);
$errors['errors'][] = $error;
}
return $errors;
}
protected function ClientSSIEditStart()
{
return $this->__header(0x13, 0x11);
}
protected function ClientSSIEditEnd()
{
$this->contact_list = array();
$this->contact_list_groups = array('all_childs_ids' => array(0));
$this->writeFlap('ClientSSICheckout');
return $this->__header(0x13, 0x12);
}
protected function ClientSSIAuthRequest($args)
{
extract($args);
$reason = isset($reason) ? $reason : "";
return $this->__header(0x13, 0x18).$this->packLV($uin, 'c').$this->packLV($reason).pack('n', 0x0);
}
protected function ServerSSIAuthRequest($data)
{
$response = $this->createResponse('authrequest');
$response['from'] = $this->unpackLV($data, 1);
$response['reason'] = $this->unpackLV($data);
return $response;
}
protected function ClientSSIAuthResponse($args)
{
extract($args);
$reason = isset($reason) ? $reason : "";
$allow = isset($allow) ? $allow : false;
return $this->__header(0x13, 0x1a).$this->packLV($uin, 'c').pack('c', $allow).$this->packLV($reason);
}
protected function ServerSSIAuthResponse($data)
{
$response = $this->createResponse('authresponse');
$response['from'] = $this->unpackLV($data, 1);
$granted = unpack('c', substr($data, 0, 1));
$response['granted'] = $granted[1];
$data = substr($data, 1);
$response['reason'] = $this->unpackLV($data);
return $response;
}
protected function ServerSSIYouAdded($data)
{
$response = $this->createResponse('youadded');
$response['from'] = $this->unpackLV($data, 1);
return $response;
}
private function createResponse($type = "message", $response = array())
{
$response['type'] = $type;
return $response;
}
protected function ClientMetaData($args)
{
static $sequence = 1;
extract($args);
$ret = $this->__header(0x15, 0x02);
switch ($type)
{
case 'offline': // 003C
$ret .= $this->packTLV(0x01, pack('vVvv', 0x08, $uin, 0x3C, $sequence));
break;
case 'delete_offline': // 003E
$ret .= $this->packTLV(0x01, pack('vVvv', 0x08, $uin, 0x3E, $sequence));
break;
default: // todo: 07D0
$pack = pack('VvvvV', $uin, 0x07D0, $sequence, 0x04BA, $uinsearch); //CLI_SHORTINFO_REQUEST
$ret .= $this->packTLV(0x01, pack('v', strlen($pack)).$pack);
break;
}
return array('return' => $sequence++, 'data' => $ret);
}
protected function ServerMetaData($data)
{
if (strlen($data))
{
$data = substr($data, 4);
if (strlen($data) > 0)
{
$msg = unpack('vsize/Vmyuin/vtype', $data);
$msg['data'] = substr($data, 10);
switch ($msg['type'])
{
case 0x41: // Offline message
$msg = unpack('Vfrom/vyear/Cmonth/Cday/Chour/Cminute/Cmsgtype/Cflag/nlength/a*message', $msg['data']);
$this->createResponse("offlinemessage", $msg);
break;
case 0x42: // End of offline messages
$this->writeFlap('ClientMetaData', array('uin' => $msg['myuin'], 'type' => 'delete_offline'));
$msg = true;
break;
case 0x07DA: // SRV_META_INFO_REPLY
$msg = array_merge($msg, unpack('vid', substr($data, 8, 10)));
$msg1 = unpack('vsubtype', $msg['data']);
$msg['data'] = substr($msg['data'], 2);
switch ($msg1['subtype']) {
case 0x0104: //Short info
$msg1 = unpack('csuccess', $msg['data']);
$msg['data'] = substr($msg['data'], 1);
$msg['type'] = 'shortinfo';
if($msg1['success'] == 0x0A)
{
$size = unpack('v', $msg['data']);
$msg['nick'] = substr($msg['data'], 2, $size[1]-1);
$msg['data'] = substr($msg['data'], $size[1]+2);
$size = unpack('v', $msg['data']);
$msg['firstname'] = substr($msg['data'], 2, $size[1]-1);
$msg['data'] = substr($msg['data'], $size[1]+2);
$size = unpack('v', $msg['data']);
$msg['lastname'] = substr($msg['data'], 2, $size[1]-1);
$msg['data'] = substr($msg['data'], $size[1]+2);
$size = unpack('v', $msg['data']);
$msg['email'] = substr($msg['data'], 2, $size[1]-1);
$msg['data'] = substr($msg['data'], $size[1]+2);
// $this->log("Short info data:", $msg['data']);
$msg1 = unpack('cauthorization/cunknown/cgender', $msg['data']);
unset($msg['data']);
$msg = array_merge($msg, $msg1);
$this->createResponse("shortinfo", $msg);
}
break;
default:
return false;
break;
}
break;
default:
return false;
break;
}
}
return $msg;
}
return false;
}
protected function ClientMd5Request($args)
{
extract($args);
return $this->__header(0x17, 0x06).$this->packTLV(0x01, $uin);//.$this->packTLV(0x4B).$this->packTLV(0x5A);
}
protected function ServerMd5Response($data)
{
return substr($data, 2);
}
protected function ClientMd5Login($args)
{
extract($args);
$password = pack('H*', md5($password));
$password = pack('H*', md5($authkey.$password.'AOL Instant Messenger (SM)'));
return $this->__header(0x17, 0x02).
$this->packTLV(0x01, $uin).
$this->packTLV(0x25, $password).
$this->packTLV(0x4C, '').
$this->packTLV(0x03, 'ICQ Client').
$this->packTLV(0x16, 0x010A, 'n').
$this->packTLV(0x17, 0x0006, 'n').
$this->packTLV(0x18, 0x00, 'n').
$this->packTLV(0x19, 0x00, 'n').
$this->packTLV(0x1A, 0x1B67, 'n').
$this->packTLV(0x14, 0x00007535, 'N').
$this->packTLV(0x0F, 'ru').
$this->packTLV(0x0E, 'ru');//.
//$this->packTLV(0x94, 0x00, 'c');
}
protected function ServerMd5LoginReply($data)
{
$tlv = $this->splitTLVsToArray($data);
if (isset($tlv[0x05]) && isset($tlv[0x06]))
{
return array($tlv[0x05], $tlv[0x06]);
}
$this->error = 'Error: can`t parse server answer';
if (isset($tlv[0x08]))
{
$error_no = unpack('n',$tlv[0x08]);
if ($error_no && isset($this->login_errors[$error_no[1]])) {
$this->error = $this->login_errors[$error_no[1]];
}
}
return false;
}
/**
* Set message capability
*
* @param string $value
* @return boolean
*/
protected function setMessageCapabilities($value = 'utf-8')
{
switch (strtolower($value))
{
case 'rtf':
$this->ibcm_capabilities = '97B12751243C4334AD22D6ABF73F1492';
break;
case 'utf-8':
$this->ibcm_capabilities = '094613494C7F11D18222444553540000';
break;
default:
$this->error = 'Warning: MessageCapabilities: "'.$value.'" unknown';
return false;
}
return true;
}
protected function setUserAgent($value = 'webicqpro')
{
$value = strtolower($value);
if (isset($this->user_agent_capability[$value]))
{
$this->agent = $value;
return true;
}
$this->error = 'Warning: UserAgent: "'.$value.'" is not valid user agent or has unknown capability';
return false;
}
/**
* Set message type
*
* @param string $value
* @return boolean
*/
protected function setMessageType($value = 'plain_text')
{
switch (strtolower($value))
{
case 'plain_text':
$this->ibcm_type = 0x01;
break;
case 'rtf':
$this->ibcm_type = 0x02;
break;
case 'old_style':
$this->ibcm_type = 0x04;
break;
default:
$this->error = 'Warning: MessageType: "'.$value.'" unknown';
return false;
}
return true;
}
protected function setEncoding($value = 'LATIN_1')
{
if(isset($this->encodings[$value])) {
$this->ibcm_encoding = $this->encodings[$value];
return true;
}
$this->error = 'Warning: Encoding not supported';
return false;
}
}
/**
* Layer for FLAP data format
* @access private
*/
class WebIcqPro_FLAP extends WebIcqPro_SNAC{
protected $channel;
private $sequence;
private $body;
private $info = array();
protected function __construct()
{
parent::__construct();
$this->sequence = rand(0x0000, 0x8000);
}
private function getSequence()
{
if (++$this->sequence > 0x8000)
{
$this->sequence = 0x00;
}
return $this->sequence;
}
protected function packFlap($body)
{
return pack('ccnn', 0x2A, $this->channel, $this->getSequence(), strlen($body)).$body;
}
protected function helloFlap($flap = false, $extra = '')
{
if ($flap)
{
if (isset($flap['data']) && strlen($flap['data']) == 4)
{
return unpack('N', $flap['data']);
}
}
else
{
return $this->packFlap(pack('N', 0x01).$extra);
}
return false;
}
}
/**
* Class for simple work with socets
* @access private
*/
class WebIcqPro_Socet extends WebIcqPro_FLAP
{
protected $socet = false;
private $server_url;
private $server_port;
private $timeout_second;
private $timeout_msecond;
protected function __construct()
{
parent::__construct();
$this->setServerUrl();
$this->setServerPort();
$this->setTimeout(6,0);
}
protected function socetOpen()
{
$this->socet = fsockopen($this->server_url, $this->server_port, $erorno, $errormsg, ($this->timeout_second+$this->timeout_msecond/1000));
if ($this->socet)
{
return true;
}
$this->error = 'Error: Cant establish connection to: '.$this->server_url.':'.$this->server_port."n".$errormsg;
return false;
}
protected function socetClose()
{
@fclose($this->socet);
$this->socet = false;
}
protected function socetWrite($data)
{
if ($this->socet)
{
stream_set_timeout($this->socet, $this->timeout_second, $this->timeout_msecond);
if (!fwrite($this->socet, $data)) {
$this->socet = false;
$this->error = 'Error: Server close connection';
return false;
}
return true;
}
$this->error = 'Error: Not connected';
return false;
}
protected function writeFlap($name, $args = array())
{
if (method_exists($this, $name))
{
$response = $this->$name($args);
if (is_array($response) && isset($response['return']) && isset($response['data']))
{
$flap = $this->packFlap($response['data']);
$this->log(">> ".$name, $flap);
if ($this->socetWrite($flap))
{
return $response['return'];
}
return false;
}
elseif ($response)
{
$flap = $this->packFlap($response);
$this->log(">> ".$name, $flap);
return $this->socetWrite($flap);
}
}
return false;
}
private function socetRead($size)
{
if ($this->socet)
{
stream_set_timeout($this->socet, $this->timeout_second, $this->timeout_msecond);
$data = @fread($this->socet, $size);
$socet_status = stream_get_meta_data($this->socet);
if ($data && !$socet_status['timed_out'])
{
return $data;
}
if ($socet_status['eof'])
{
$this->socet = false;
$this->error = 'Error: Server close connection';
}
}
return false;
}
protected function readFlap($name = false)
{
$data = $this->socetRead(6);
if ($data)
{
$flap = unpack('ccommand/cchanel/nsequence/nsize', $data);
if ($flap['chanel'] == 4)
{
$this->error = 'Notice: Server close connection';
$this->socetClose();
return false;
}
$flap['data'] = $this->socetRead($flap['size']);
if ($flap['data'])
{
if ($name)
{
$snac = $this->analizeSnac($flap['data']);
if (isset($snac['callback']) && $snac['callback'] == $name)
{
$this->log("<< ".$snac['callback'].'('.dechex($snac['type']).', '.dechex($snac['subtype']).')', $data.$flap['data']);
return $this->$name($snac['data'], $snac['flag']);
}
elseif(isset($snac['callback']))
{
$this->log("<< ".$snac['callback'].'('.dechex($snac['type']).', '.dechex($snac['subtype']).')', $data.$flap['data']);
$this->$snac['callback']($snac['data'], $snac['flag']);
$this->error = 'Warning: Wrong server response "'.$name.'" expected but SNAC('.dechex($snac['type']).', '.dechex($snac['subtype']).') received';
}
return false;
}
return $flap;
}
}
return false;
}
protected function readSocket()
{
$data = $this->socetRead(6);
if ($data)
{
$flap = unpack('ccommand/cchanel/nsequence/nsize', $data);
if ($flap['chanel'] == 4)
{
$this->error = 'Notice: Server close connection';
$this->socetClose();
return false;
}
$flap['data'] = $this->socetRead($flap['size']);
if ($flap['data'])
{
$snac = $this->analizeSnac($flap['data']);
if (isset($snac['callback']))
{
$this->log("<< ".$snac['callback'].' ('.dechex($snac['type']).'x'.dechex($snac['subtype']).')', $data.$flap['data']);
return $this->$snac['callback']($snac['data'], $snac['flag']);
}
else
{
$this->log('<< Unknown SNAC: '.dechex($snac['type']).'x'.dechex($snac['subtype']), $data.$flap['data']);
}
}
}
return false;
}
protected function setTimeout($second = 1, $msecond = 0)
{
$this->timeout_second = $second;
$this->timeout_msecond = $msecond;
}
protected function setServerUrl($value = 'login.icq.com')
{
$this->server_url = $value;
return true;
}
protected function setServerPort($value = 5190)
{
$this->server_port = $value;
return true;
}
protected function setServer($value = 'login.icq.com:5190')
{
$server = explode(':', $value);
if (count($server) == 2 && is_numeric($server[1]))
{
$this->server_url = $server[0];
$this->server_port = $server[1];
return true;
}
$this->error = 'Error: Wrong server address format';
return false;
}
}
/**
* Set of tools for simple and clear work with ICQ(OSCAR) protocol.
*
*/
class WebIcqPro extends WebIcqPro_Socet {
private $uin;
/**
* Constructor for WebIcqPro class
* @access internal
*/
public function __construct()
{
parent::__construct();
}
/**
* Establish connection to ICQ server
*
* Method initiate login sequence with MD5 based authorization.
*
* @todo full client request/responses support
*
* @param string $uin
* @param string $pass
* @return boolean
*/
public function connect($uin, $pass)
{
$this->uin = str_replace('-', '', $uin);
if ($this->socet)
{
$this->error = 'Error: Connection already opened';
return false;
}
if ($this->socetOpen())
{
$flap = $this->readFlap();
$this->channel = 0x01;
if ($this->helloFlap($flap))
{
$this->socetWrite($this->helloFlap());
$this->channel = 0x02;
$this->writeFlap('ClientMd5Request', array('uin' => $this->uin));
$authkey = $this->readFlap('ServerMd5Response');
if ($authkey)
{
$this->writeFlap('ClientMd5Login', array('uin' => $this->uin, 'password' => $pass, 'authkey' => $authkey));
$reconect = $this->readFlap('ServerMd5LoginReply');
$this->disconnect();
if ($reconect)
{
$this->setServer(array_shift($reconect));
$cookie = array_shift($reconect);
if ($this->socetOpen())
{
$flap = $this->readFlap();
$this->channel = 0x01;
if ($this->helloFlap($flap))
{
$this->socetWrite($this->helloFlap(false, $this->packTLV(0x06, $cookie)));
$this->channel = 2;
$this->readFlap('ServerFamilies');
$this->writeFlap('ClientFamiliesVersions');
$this->readFlap('ServerFamiliesVersions');
$this->writeFlap('ClientRates');
$this->readFlap();
if (is_array($this->rates_groups) && count($this->rates_groups))
{
$this->writeFlap('ClientRatesAck');
}
$this->writeFlap('ClientSSIRights');
$this->writeFlap('ClientSSICheckout');
$this->writeFlap('ClientLocationRights');
$this->writeFlap('ClientBuddylistRights');
$this->writeFlap('ClientIBCMRights');
$this->writeFlap('ClientPrivicyRights');
// $this->ClientLimitations();
$this->writeFlap('ClientLocationInfo');
$this->writeFlap('ClientReady');
return true;
}
}
}
}
else
{
$this->readFlap('ServerMd5LoginReply');
}
}
}
return false;
}
/**
* Method activate Status Notifications
*
* This method make possible to read Status Notifications for contacts in your contact list.
*
* @deprecated Generate a traffic, but handler is not implemented yet!
* @todo make it useful. Let's class reads status notifications!
*
* @return boolean
*/
public function activateStatusNotifications()
{
return $this->writeFlap('ClientSSI');
}
/**
* Method set status for client
*
* Parameters are case insensitive. Try to set different substatuses for privicy and etc. purposes.
* You can set any icq status from the list:
* - STATUS_ONLINE
* - STATUS_AWAY
* - STATUS_DND
* - STATUS_NA
* - STATUS_OCCUPIED
* - STATUS_FREE4CHAT
* - STATUS_INVISIBLE
*
* Also possible to set substutuses:
* - STATUS_WEBAWARE
* - STATUS_SHOWIP
* - STATUS_BIRTHDAY
* - STATUS_WEBFRONT
* - STATUS_DCDISABLED
* - STATUS_DCAUTH
* - STATUS_DCCONT
*
* @param string $status
* @param string $substatus
* @return boolean
*/
public function setStatus($status = 'STATUS_ONLINE', $substatus = 'STATUS_DCCONT', $message = '')
{
$this->status_message = $message;
if (isset($this->statuses[$status]))
{
if (isset($this->substatuses[$substatus]))
{
return $this->writeFlap('ClientStatus', array('status' => $status, 'substatus' => $substatus,)) && $this->writeFlap('ClientIdleTime');
}
$this->error = 'setStatus: unknown substatus '.$substatus;
return false;
}
$this->error = 'setStatus: unknown status '.$status;
return false;
}
/**
* Method set X status for client
*
*
* @param string $status
* @return boolean
*/
public function setXStatus($status = '')
{
if (isset($this->x_statuses[$status]))
{
$this->capabilities['xSatus'] = $this->x_statuses[$status];
} elseif ($status == '') {
unset($this->capabilities['xSatus']);
} else {
$this->error = 'setXStatus: unknown status '.$status;
return false;
}
$this->writeFlap('ClientIdleTime');
return $this->writeFlap('ClientLocationInfo');
}
/**
* Method return all available X Statuses
*
* @return array
*/
public function getXStatuses()
{
return array_keys($this->x_statuses);
}
/**
* Method indicate the connection status.
*
*
*
* @return boolean
*/
public function isConnected()
{
return $this->socet;
}
/**
* Close connection
*
*/
public function disconnect()
{
$this->channel = 0x04;
$this->socetWrite($this->packFlap(''));
$this->socetClose();
}
/**
* Activate the posibility to read offline messages.
*
* After activation offline messages will be accessable like simple messages.
*
* @see readMessage
* @param string $uin
* @return boolean
*/
public function activateOfflineMessages()
{
return $this->writeFlap('ClientMetaData', array('uin' => $this->uin, 'type' => 'offline'));
}
/**
* Get contact short info.
*
*
* @see readMessage
* @param string $uin
* @return boolean
*/
public function getShortInfo($uin)
{
$uin = str_replace('-', '', $uin);
return $this->writeFlap('ClientMetaData', array('uin' => $this->uin, 'uinsearch' => $uin, 'type' => 'shortinfo'));
}
/**
* Read message from the server.
*
* Return an associated array with different set of values of false if nathisn to read.
* Available sets of data:
* - from - sender UIN
* - message - the message
* - ... - can be ather information like status and etc.
*
* @return array
*/
public function readMessage()
{
return $this->readSocket();
}
/**
* Send message to the server.
*
* Try to send message to the $uin.
*
* @todo check the delivery
* @param string $uin
* @param string $message
* @return boolean
*/
public function sendMessage($uin, $message)
{
$uin = str_replace('-', '', $uin);
$this->writeFlap('ClientIdleTime');
return $this->writeFlap('ClientIBCM', array('uin' => $uin, 'message' => $message));
}
/**
* Sets additional options
*
* Do not change this options if you dont know what it mean.
*
* Usage: $icq->setOption('UserAgent', 'miranda');
*
* Options available:
* MessageType - rtf, utf, old style(offline message)
* MessageCapabilities - utf, rtf
* UserAgent - miranda, sim, trillian, licq, kopete, micq, andrq, randq, mchat, jimm, macicq, icqlite
* Server - login.icq.com:5190
* ServerPort - 5190, 80, 8080
* Timeout - seconds before stop reading from socets.
*
*
* @param string $name
* @param mixed $value
* @return boolean
*/
public function setOption($name, $value)
{
$method = 'set'.$name;
if (method_exists($this, $method))
{
return $this->$method($value);
}
$this->error = 'Warning: setOption name: "'.$name.'" unknown';
return false;
}
/**
* Return list of contacts
*
* @return array
*/
public function getContactList()
{
return $this->contact_list;
}
/**
* Return list of contacts groups
*
* @return array
*/
public function getContactListGroups()
{
$groups = array_keys($this->contact_list_groups);
unset($groups['all_childs_ids']);
return $groups;
}
/**
* Add uins to list of contacts. First argument is group name. Ather uins to add.
* Also posible to add with custom name:
*
* addContact("Buddies", array('uin' => UIN_TO_ADD, 'name' => "Custom name"), ...)
*
* @todo errors handling
*
* @param mixed list of uins
* @return boolean
*/
public function addContact()
{
$uin = array();
$args = func_get_args();
$group = array_shift($args);
foreach ($args as $id) {
if(is_array($id))
{
$uin[str_replace('-', '', $id['uin'])] = $id['name'];
}
else
{
$uin[str_replace('-', '', $id)] = false;
}
}
if(count($uin) > 0) {
$this->writeFlap('ClientSSIEditStart');
$this->writeFlap('ClientSSIAdd', array('uins' => $uin, 'group' => $group));
$this->writeFlap('ClientSSIEditEnd');
return true;
}
return false;
}
/**
* Delete uins from list of contacts.
* deleteContact(UIN_TO_DELETE, ...)
*
* @todo errors handling
* @param mixed list of uins
* @return boolean
*/
public function deleteContact()
{
$uin = array();
foreach (func_get_args() as $id) {
$uin[] = str_replace('-', '', $id);
}
if(count($uin) > 0) {
$this->writeFlap('ClientSSIEditStart');
$this->writeFlap('ClientSSIDelete', array('uins' => $uin));
$this->writeFlap('ClientSSIEditEnd');
return true;
}
return true;
}
public function addContactGroup($name, $parent = "")
{
$this->writeFlap('ClientSSIEditStart');
$this->writeFlap('ClientSSIAdd', array('group' => $name, 'parent' => $parent));
$this->writeFlap('ClientSSIEditEnd');
return true;
}
public function deleteContactGroup($name, $parent)
{
$this->writeFlap('ClientSSIEditStart');
$this->writeFlap('ClientSSIDelete', array('group' => $name, 'parent' => $parent));
$this->writeFlap('ClientSSIEditEnd');
return true;
}
public function getAuthorization($uin, $reason='')
{
return $this->writeFlap('ClientSSIAuthRequest', array('uin' => $uin, 'reason' => $reason));
}
public function setAuthorization($uin, $granted=true, $reason='')
{
return $this->writeFlap('ClientSSIAuthResponse', array('uin' => $uin, 'reason' => $reason, 'allow' => $granted));
}
}
?>