package Net::ICQ2000;
use strict;
no strict 'refs';
use vars qw(
$VERSION
%_New_Connection_Nefotiation_Codes
%_TLV_OUT %_TLV_IN %_TLV_Length_O %_TLV_Length_I %_Srv_Codes
%_Srv_Decoder %_Cmd_Codes %_Cmd_Encoder
%_Status_Codes %_r_Status_Codes
);
use Time::Local;
use IO::Socket;
use IO::Select;
use Carp;
# use bytes;
$VERSION = '0.2.1';
sub new {
my($Package, $UIN, $Password, $AutoConnect, $ServerAddress, $ServerPort) = @_;
$ServerAddress or $ServerAddress = "login.icq.com";
$ServerPort or $ServerPort = "5190";
my $Me = {
_UIN => $UIN,
_Password => $Password,
_Server => $ServerAddress,
_ServerPort => $ServerPort,
_Socket => undef,
_Select => undef,
_Seq_Num => int(rand(0xFFFF)),
_Incoming_Queue => [],
_Outgoing_Queue => [],
_Connection_Cookie => 0,
_Hooks => {},
_Connected => 0,
_LoggedIn => 0,
_FLAP_Bytes_Left => 0,
_FLAP_In_progress => undef,
_Mem => 1,
_Auto_Login => 1, #one means minimum, two means full ICQ logon, 0 means none/developer deals with it..
_Auto_Login_Contact_List => [],
_Auto_Login_Visible_List => [],
_Sent_Requests => {},
_Status => "Online",
_Debug => 0
};
bless($Me, $Package);
$Me->Connect() if $AutoConnect;
return $Me;
}
sub Connect {
my($Me) = @_;
return if $Me->{_Connected};
$Me->{_UIN} or croak("Attempted to connect without UIN!");
$Me->{_Password} or croak("Attempted to connect without Password!");
$Me->{_Socket} = IO::Socket::INET->new( Proto => "tcp",
PeerAddr => $Me->{_Server},
PeerPort => $Me->{_ServerPort}) or croak("socket error: $@");
$Me->{_Select} = IO::Select->new($Me->{_Socket});
$Me->{_Connected} = 1;
}
sub Disconnect {
my($Me) = @_;
$Me->{_Connected} or return;
close($Me->{_Socket});
$Me->{_Select} = undef;
$Me->{_Connected} = 0;
$Me->{_Incoming_Queue} = [];
$Me->{_Outgoing_Queue} = [];
}
sub Set_Login_Details {
my($Me, $UIN, $Pass) = @_;
return if $Me->{_Connected};
$Me->{_UIN} = $UIN if $UIN;
$Me->{_Password} = $Pass if $Pass;
}
sub Execute_Once {
my ($Me) = @_;
$Me->{_Connected} or return;
$Me->Check_Incoming;
$Me->Deal_With_FLAPs;
$Me->Send_Outgoing;
}
sub Send_Command {
my ($Me, $Command, $Details) = @_;
(exists $_Cmd_Codes{$Command}) or return;
&{$_Cmd_Encoder{$_Cmd_Codes{$Command}}}($Me, $Details) if (exists $_Cmd_Encoder{$_Cmd_Codes{$Command}});
}
sub Add_Hook {
my($Me, $HookType, $HookFunction) = @_;
$_Srv_Codes{$HookType} or croak("Bad Hook type!\n");
$Me->{_Hooks}{$_Srv_Codes{$HookType}} = $HookFunction;
}
%_Status_Codes = (
'Online' => 0x00020000,
'Free_For_Chat' => 0x00020020,
'Away' => 0x00020001,
'Not_Avalible' => 0x00020005,
'Occupied' => 0x00020011,
'Do_Not_Disturb' => 0x00020013,
'Invisible' => 0x00120100
);
%_r_Status_Codes = (
'0000' => 'Online',
'0020' => 'Free for Chat',
'0001' => 'Away',
'0004' => 'N/A',
'0005' => 'N/A',
'0010' => 'Occupied',
'0011' => 'Occupied',
'0013' => 'Do Not Disturb',
'0100' => 'Invisible'
);
%_Cmd_Encoder = (
#Cmd_GSC_Client_Ready
'1:2' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(1, 2, 0, 0, 2);
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 3));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0110));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 2));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0101));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 3));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0110));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x15));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0110));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 4));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0110));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 6));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0110));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 9));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0110));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0a));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0110));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x028a));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
#turn off the auto login, to save processor time..
$Me->{_Auto_Login} = 0;
},
#Cmd_GSC_Reqest_Rate_Info
'1:6' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(1, 6, 0, 0, 6);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_GSC_Rate_Info_Ack
'1:8' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(1, 8, 0, 0, 8);
#another junk filled responce (AOL must like using up network resources..)
push(@{$Responce->{Data_Load}}, (0,1,0,2,0,3,0,4,0,5));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_GSC_LoggedIn_User_Info
'1:14' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(1, 14, 0, 0, 14);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_GSC_ICQInform
'1:23' => sub {
my($Me, $event) = @_;
my($Responce);
#Never changes..
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(1, 0x17, 0, 0, 0x17);
push(@{$Responce->{Data_Load}}, (0,1,0,3,0,2,0,1,0,3,0,1,0,21,0,1,0,4,0,1,0,6,0,1,0,9,0,1,0,10,0,1));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_GSC_Set_Status
'1:30' => sub {
my($Me, $event) = @_;
my($Responce, $Responce2);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(1, 30, 0, 0, 30);
push(@{$Responce->{Data_Load}}, _Write_TLV(2, 'Status', $_Status_Codes{$event->{Status}}));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
#send the "Made Change/update command" (really I don't know whta this is for..)
@{$Responce2->{Data_Load}} = &_Make_SNAC_Header(1, 17, 0, 0, 17);
push(@{$Responce2->{Data_Load}}, _int_to_bytes(4, 0));
push(@{$Me->{_Outgoing_Queue}}, $Responce2);
},
#Cmd_LS_LoggedIn_User_Rights
'2:2' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(2, 2, 0, 0, 2);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_LS_Set_User_Info
'2:4' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(2, 4, 0, 0, 4);
#if this is setting our details, shouldn't we set something? maybe later.. : )
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 5));
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 32));
foreach ("09","46","13","49","4c","7f","11","d1","82","22","44","45","53","54","00","00","09","46","13","44","4c","7f","11","d1","82","22","44","45","53","54","00","00"){
push(@{$Responce->{Data_Load}}, ord);
}
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_BLM_Rights_Info
'3:2' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(3, 2, 0, 0, 2);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_CTL_UploadList
'3:4' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(3, 4, 0, 0, 4);
#don't send the command unless we have a list to send..
return if ($#{$event->{ContactList}} == -1);
foreach (@{$event->{ContactList}}){
push(@{$Responce->{Data_Load}}, _int_to_bytes(1, length($_)));
push(@{$Responce->{Data_Load}}, _str_to_bytes($_));
}
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_Mes_Add_ICBM_Param
'4:2' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(4, 2, 0, 0, 2);
push(@{$Responce->{Data_Load}}, (0,0,0,0,0,3,0x1f,0x40,3,0xe7,3,0xef,0,0,0,0));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_Mes_Param_Info
'4:4' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(4, 4, 0, 0, 4);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_BOS_Get_Rights
'9:2' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(9, 2, 0, 0, 2);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_BOS_Add_VisibleList
'9:5' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(9, 5, 0, 0, 5);
foreach (@{$event->{VisibleList}}){
push(@{$Responce->{Data_Load}}, _int_to_bytes(1, length($_)));
push(@{$Responce->{Data_Load}}, _str_to_bytes($_));
}
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_BOS_Add_InVisibleList
'9:7' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(9, 7, 0, 0, 7);
foreach (@{$event->{InVisibleList}}){
push(@{$Responce->{Data_Load}}, _int_to_bytes(1, length($_)));
push(@{$Responce->{Data_Load}}, _str_to_bytes($_));
}
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_Authorize
'19:26' => sub {
my($Me, $event) = @_;
my($Responce, @TempPacket);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(19, 26, 0, 0, 26);
my$uin = $event->{uin};
push(@TempPacket, _uin_to_buin($uin));
push(@TempPacket, _int_to_bytes(1,0x01));
push(@TempPacket, _int_to_bytes(4,0x00000000));
push(@{$Responce->{Data_Load}}, @TempPacket);
# $Me->{_Mem}++;
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_Add_ContactList
'19:20' => sub {
my($Me, $event) = @_;
my($Responce);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(19, 20, 0, 0, 20);
#don't send the command unless we have a list to send..
return if ($#{$event->{ContactList}} == -1);
foreach (@{$event->{ContactList}}){
push(@{$Responce->{Data_Load}}, _int_to_bytes(1, length($_)));
push(@{$Responce->{Data_Load}}, _str_to_bytes($_));
}
push(@{$Responce->{Data_Load}}, _int_to_bytes(4,0x00000000));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_Send_Message
'4:6' => sub {
my($Me, $event) = @_;
my($Responce, @TempPacket);
if($event->{MessageType} eq 'text')
{
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(4, 6, 0, 1, 6);
my$uin = $event->{uin};
my$msg = $event->{text};
my$len = length($msg) + 4;
push(@TempPacket, _int_to_bytes(4,0x52995d00));
push(@TempPacket, _int_to_bytes(4,0x69230000));
push(@TempPacket, _int_to_bytes(2,0x0001));
push(@TempPacket, _uin_to_buin($uin));
push(@TempPacket, _int_to_bytes(2,0x0002)); # TLV
push(@TempPacket, _int_to_bytes(2,$len + 9)); # TLV
push(@TempPacket, _int_to_bytes(3,0x050100));
push(@TempPacket, _int_to_bytes(4,0x01010101));
push(@TempPacket, _int_to_bytes(2,$len));
push(@TempPacket, _int_to_bytes(2,0));
push(@TempPacket, _int_to_bytes(2,0xffff));
push(@TempPacket, _str_to_bytes($msg));
push(@TempPacket, _int_to_bytes(2,0x0006));
push(@TempPacket, _int_to_bytes(2,0x0000));
push(@{$Responce->{Data_Load}}, @TempPacket);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
}
},
#Cmd_Add_List
'19:8' => sub {
my($Me, $event) = @_;
my($Responce, @TempPacket);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(19, 8, 0, 0, 8);
push(@{$Responce->{Data_Load}}, _int_to_bytes(1, 0x00));
# $Me->{_Mem}++;
push(@{$Me->{_Outgoing_Queue}}, $Responce);
push(@TempPacket, _int_to_endian_bytes(4, 41316677));#encode the ICQ num..
push(@TempPacket, _int_to_bytes(2, 0x7fd1));
push(@TempPacket, _int_to_bytes(2, 0x7fd1));
push(@TempPacket, _int_to_bytes(3, 0x0));
push(@TempPacket, _int_to_bytes(1, 0x4));
push(@TempPacket, _int_to_bytes(4, 0x01310000));
push(@{$Responce->{Data_Load}}, @TempPacket);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
},
#Cmd_Srv_Message
'21:2' => sub {
my($Me, $event) = @_;
my($Responce, @TempPacket);
@{$Responce->{Data_Load}} = &_Make_SNAC_Header(0x15, 2, 0, 0, ($Me->{_Mem}*65536+0x02)); #strainge request ID..
$Me->{_Mem}++;
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 0x0001));
#Argh, Finally figured this bit out!!!
#this next four packets is the length in little endian and normal!! so work
#out the packet length first...
push(@TempPacket, _int_to_endian_bytes(4, $Me->{_UIN}));#encode the ICQ num..
if ($event->{MessageType} eq "")
{
push(@TempPacket, _int_to_bytes(2, 0x3c00));
push(@TempPacket, _int_to_bytes(1, $Me->{_Mem}));
push(@TempPacket, _int_to_bytes(1, 0));
}
elsif ($event->{MessageType} eq "ack_offline"){
push(@TempPacket, _int_to_bytes(2, 0x3e00));
push(@TempPacket, _int_to_bytes(1, $Me->{_Mem}));
push(@TempPacket, _int_to_bytes(1, 0));
}
elsif ($event->{MessageType} eq "key"){
print "sending key [$event->{Key}]\n";
$Me->{_Sent_Requests}{ (($Me->{_Mem}-1)*65536+0x02) } = $event->{Key};
push(@TempPacket, _int_to_bytes(2, 0xd007));
push(@TempPacket, _int_to_bytes(1, $Me->{_Mem}));
push(@TempPacket, _int_to_bytes(3, 0x9808));
my $Key = "".$event->{Key}."";
push(@TempPacket, _int_to_endian_bytes(2, length($Key)+1));
push(@TempPacket, _str_to_bytes($Key));
push(@TempPacket, _int_to_bytes(1, 0));
}
elsif ($event->{MessageType} eq "SMS"){
push(@TempPacket, _int_to_bytes(2, 0xd007));
push(@TempPacket, _int_to_bytes(1, $Me->{_Mem}));
push(@TempPacket, _int_to_bytes(4, 0x00821400));
push(@TempPacket, _int_to_bytes(4, 0x01001600));
push(@TempPacket, _int_to_bytes(17, 0));
my $TimeString = gmtime();
if ($TimeString =~ /(\w+) (\w+) (\d+) (\d+:\d+:\d+) (\d+)/){
$TimeString = $1.", ".$3." ".$2." ".$5." ".$4." GMT";
}
else {
print "Unable to encode time...\n";
return;
}
my $SMSMessage = "".$event->{SMS_Dest_Number}."".$event->{text}."";
$SMSMessage .= "1252".$Me->{_UIN}."Robbot";
$SMSMessage .= "Yes";
my $SMSLength = length($SMSMessage)+1;
push(@TempPacket, _int_to_bytes(2, $SMSLength));
push(@TempPacket, _str_to_bytes($SMSMessage));
push(@TempPacket, _int_to_bytes(1, 0)); #null end..
}
#NOW work out that length thingy (what a crappy place for it!!!)
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, $#TempPacket + 3));
push(@{$Responce->{Data_Load}}, _int_to_endian_bytes(2, $#TempPacket + 1));
push(@{$Responce->{Data_Load}}, @TempPacket);
push(@{$Me->{_Outgoing_Queue}}, $Responce);
}
);
%_Srv_Decoder = (
#Srv_GSC_Ready
'1:3' => sub {
my ($Me, $event) = @_;
#nothing intresting to get from SNAC..
if ($Me->{_Auto_Login}){
$Me->Send_Command("Cmd_GSC_ICQInform");
}
return;
},
#Srv_GSC_Rate_Info
"1:7" => sub {
my ($Me, $event) = @_;
#my ($Refined);
if ($Me->{_Auto_Login} > 1){
#ack the rate info..
$Me->Send_Command("Cmd_GSC_Rate_Info_Ack");
#also send some other requests..
$Me->Send_Command("Cmd_GSC_LoggedIn_User_Info");
$Me->Send_Command("Cmd_LS_LoggedIn_User_Rights");
$Me->Send_Command("Cmd_BLM_Rights_Info");
$Me->Send_Command("Cmd_Mes_Param_Info");
$Me->Send_Command("Cmd_BOS_Get_Rights");
}
#Loads of data, but I have no idea what to do with it..
#(tells us all the posible commands?..)
return ($event);
},
#Srv_GSC_User_Info
'1:15' => sub {
my ($Me, $event) = @_;
my ($Refined, $i, $DataLength);
#$event->{Data_Load}
$i = 10;
$DataLength = ${$event->{Data_Load}}[$i];
$i++;
$Refined->{Online_User} = _bytes_to_str($event->{Data_Load}, $i, $DataLength);
$i += $DataLength;
$Refined->{Warning_Lev} = _bytes_to_int ($event->{Data_Load}, $i, 2);
$i += 4;
($Refined, $i) = &_Read_TLV($event->{Data_Load}, 2, $i, $Refined);
return ($Refined);
},
#Srv_GSC_MOTD
'1:19' => sub {
my ($Me, $event) = @_;
my ($Refined, $i);
$i = 12;
($Refined, $i) = &_Read_TLV($event->{Data_Load}, 2, $i, $Refined);
return ($Refined);
},
#Srv_GSC_ICQClientConfirm
'1:24' => sub {
my ($Me, $event) = @_;
my ($Refined);
#$event->{Data_Load}
if ($Me->{_Auto_Login}){
if ($Me->{_Auto_Login} == 1){
my ($details);
$details->{Status} = $Me->{_Status};
$Me->Send_Command("Cmd_CTL_UploadList", {ContactList=> $Me->{_Auto_Login_Contact_List}});
if($Me->{_Status} eq 'Invisible')
{
$Me->Send_Command("Cmd_BOS_Add_VisibleList", {VisibleList=> $Me->{_Auto_Login_Visible_List}});
}
$Me->Send_Command("Cmd_GSC_Set_Status", $details);
$Me->Send_Command("Cmd_GSC_Client_Ready");
$Me->Send_Command("Cmd_Srv_Message");
$Me->{_LoggedIn} = 1;
}
else {
$Me->Send_Command("Cmd_GSC_Reqest_Rate_Info");
}
}
return ($Refined);
},
#Srv_LS_Rights_Response
'2:3' => sub {
my ($Me, $event) = @_;
my ($Refined);
#no idea what to do with this data..
#$event->{Data_Load}
return ($Refined);
},
#Srv_BLM_Rights_Response
'3:3' => sub {
my ($Me, $event) = @_;
my ($Refined);
#no idea what to do with this data..
#$event->{Data_Load}
return ($Refined);
},
#Srv_BLM_Contact_Online
'3:11' => sub {
my ($Me, $event) = @_;
my ($Refined, $DataLength, $i);
$i = 10;
$DataLength = ${$event->{Data_Load}}[$i];$i++;
$Refined->{UIN} = _bytes_to_str($event->{Data_Load}, $i, $DataLength);$i += $DataLength + 4;
($Refined, $i) = _Read_TLV($event->{Data_Load}, 2, $i, $Refined, _bytes_to_int($event->{Data_Load}, $i-4, 4));
return ($Refined);
},
#Srv_BLM_Contact_Offline
'3:12' => sub {
my ($Me, $event) = @_;
my ($Refined, $DataLength, $i);
$i = 10;
$DataLength = ${$event->{Data_Load}}[$i];$i++;
$Refined->{UIN} = _bytes_to_str($event->{Data_Load}, $i, $DataLength);$i += $DataLength + 4;
($Refined, $i) = _Read_TLV($event->{Data_Load}, 2, $i, $Refined, _bytes_to_int($event->{Data_Load}, $i-4, 4));
return ($Refined);
},
#Srv_Mes_Rights_Response
'4:5' => sub {
my ($Me, $event) = @_;
my ($Refined);
#no idea what to do with this data..
#$event->{Data_Load}
return ($Refined);
},
#Srv_Mes_Received
'4:7' => sub {
my ($Me, $event) = @_;
my ($Refined, $i, $DataLength, $DataType);
print "Incomming..\n" if $Me->{_Debug};
_print_packet($event->{Data_Load}, ()) if $Me->{_Debug};
$i = 19;
$Refined->{SenderType} = $event->{Data_Load}->[$i];$i++;
$DataLength = ${$event->{Data_Load}}[$i];$i++;
$Refined->{Sender} = _bytes_to_str($event->{Data_Load}, $i, $DataLength);$i += $DataLength + 4;
($Refined, $i) = _Read_TLV($event->{Data_Load}, 2, $i, $Refined, _bytes_to_int($event->{Data_Load}, $i-4, 4));
if ($Refined->{Encoded_Message}){
#this is a weird ass message, so decode it..
my @Encoded_Message = split(/ /, $Refined->{Encoded_Message});
undef $Refined->{Encoded_Message};
$Refined->{TaggedDataString} = _bytes_to_str(\@Encoded_Message, 0x32, _endian_bytes_to_int(\@Encoded_Message, 0x2f, 2));
_Decode_Tagged_Text($Refined->{TaggedDataString}, $Refined);
return ($Refined);
}
$Refined->{Message_Encoding} = _bytes_to_int($event->{Data_Load}, $i, 2); $i+=2;
# print "==> $Refined->{Message_Encoding}\n";
if ($Refined->{Message_Encoding} == 2){
#normal text message..
$Refined->{MessageType} = "Normal";
$DataLength = _bytes_to_int ($event->{Data_Load}, $i, 2);
$i += 15;
$DataLength -= 13;
$Refined->{text} = _bytes_to_str($event->{Data_Load}, $i, $DataLength);
}elsif ($Refined->{Message_Encoding} == 5){
$DataLength = _bytes_to_int($event->{Data_Load}, $i, 2);
$i+=2;
$i+=4;
my $type = ord(_bytes_to_str($event->{Data_Load}, $i, 1));
$i++;
# my $data = _bytes_to_str($event->{Data_Load}, $i, $DataLength-4);
# my @bytes = _str_to_bytes($data);
# print ">> [$type] @bytes <<\n";
#print "$bytes[2]\n";
if($type == 12) # Auth request!
{
$i++;
my $data = _bytes_to_str($event->{Data_Load}, $i, $DataLength-4);
my($nick,$fname,$lname,$email,$xxx,$reason);
($nick,$fname,$lname,$email,$xxx,$reason) = split /\xfe/,$data;
$Refined->{nick} = $nick;
$Refined->{first_name} = $fname;
$Refined->{last_name} = $lname;
$Refined->{email} = $email;
$Refined->{reason} = $reason;
$Refined->{MessageType} = "auth_request";
}
elsif($type == 4) # URL
{
$Refined->{MessageType} = "URL";
$DataLength = _bytes_to_int ($event->{Data_Load}, $i, 2);
my $data = _bytes_to_str($event->{Data_Load}, $i+2, $DataLength);
($Refined->{Description},$Refined->{URL}) = split /\xfe/,$data;
}
elsif($type == 26) # Contact request
{
my $data = _bytes_to_str($event->{Data_Load}, $i, $DataLength-4); #$DataLength-4);
my @bytes = _str_to_bytes($data);
$i = $bytes[2]-1;
my $reason = '';
foreach (@bytes[$i+1..$i+$bytes[$i]])
{
$reason .= chr;
}
$Refined->{MessageType} = "contacts_request";
$Refined->{Reason} = $reason;
}
elsif($type == 19) # Auth request!
{
$i+=2;
my $data = _bytes_to_str($event->{Data_Load}, $i, $DataLength-4);
my ($contactcount,@contacts) = split /\xfe/,$data;
# print "Got $contactcount contacts!\n";
# $j = 0;
# while($ <= $#contacts)
# {
# print $contacts[$i]. " " . $contacts[$i+1] . "\n";
# $i+=2;
# }
$Refined->{MessageType} = "contacts";
$Refined->{Count} = $contactcount;
$Refined->{Contacts} = \@contacts;
}
}
return ($Refined);
},
#Srv_BOS_Rights
'9:3' => sub {
my ($Me, $event) = @_;
my ($Refined);
if ($Me->{_Auto_Login} > 1){
$Me->Send_Command("Cmd_Mes_Add_ICBM_Param");
$Me->Send_Command("Cmd_LS_Set_User_Info");
$Me->Send_Command("Cmd_CTL_UploadList", {ContactList=> $Me->{_Auto_Login_Contact_List}});
$Me->Send_Command("Cmd_GSC_Set_Status", {Status => $Me->{_Status}});
$Me->Send_Command("Cmd_GSC_Client_Ready");
#now send all the Ad requests (hey, this is how the client does it.. : /
$Me->Send_Command("Cmd_Srv_Message");
$Me->Send_Command("Cmd_Srv_Message", {MessageType => "key", Key => "DataFilesIP"});
$Me->Send_Command("Cmd_Srv_Message", {MessageType => "key", Key => "BannersIP"});
$Me->Send_Command("Cmd_Srv_Message", {MessageType => "key", Key => "ChannelsIP"});
}
#$event->{Data_Load}
return ($Refined);
},
#Srv_Srv_Message
'21:3' => sub {
my ($Me, $event) = @_;
my ($Refined, $i);
################
##### NOTE #####
################
#This Srv responce seems to be the one that AOL desided to hack ALL ICQ functions that
# they couldn't fit under the normal AIM protocol. This means that this family
# seems to ave a lot of sub sub sub families, and hence is a bastard to decode,
# and then when u think u've got it, one call out of 900000 screws up in the decoding
# so if anyone has some good insights into this family please let me know!!!!
print "Incomming..\n" if $Me->{_Debug};
print "[".$event->{Channel_ID}."][".$event->{Sequence_ID}."][".$event->{Data_Size}."][".$event->{Family_ID}."][".$event->{Sub_ID}."]\n" if $Me->{_Debug};
_print_packet($event->{Data_Load}, ()) if $Me->{_Debug};
$Refined->{Flags} = _bytes_to_int($event->{Data_Load}, 4, 2);
$Refined->{Ref} = _bytes_to_int($event->{Data_Load}, 6, 4);
if (exists $Me->{_Sent_Requests}{$Refined->{Ref}}){
$Refined->{Responce_Type} = $Me->{_Sent_Requests}{$Refined->{Ref}};
undef $Me->{_Sent_Requests}{$Refined->{Ref}};
}
#first ten is SNAC header, then a 00 01 (normally..) then the message's size in
#Normal then endian format (don't have any idea why, but it is..) but skip all that..
$i = 16;
$Refined->{Our_UIN} = _endian_bytes_to_int($event->{Data_Load}, $i, 4);$i += 4;
#the first of the sub sub types..
$Refined->{MessageType} = _endian_bytes_to_int($event->{Data_Load}, $i, 2);$i += 2;
if ($Refined->{MessageType} == 65){
# normally offline messages..
if (_endian_bytes_to_int($event->{Data_Load}, $i, 2) == 2){
#90% sure it's an offline message..
$i += 2;
$Refined->{Sender} = _endian_bytes_to_int($event->{Data_Load}, $i, 4);$i += 4;
#note, the time given is in GMT, not local, so make it local..(DIE AOL!!!)
$Refined->{Sent_Time} = localtime(timegm(0,
_endian_bytes_to_int($event->{Data_Load}, $i+5, 1),
_endian_bytes_to_int($event->{Data_Load}, $i+4, 1),
_endian_bytes_to_int($event->{Data_Load}, $i+3, 1),
_endian_bytes_to_int($event->{Data_Load}, $i+2, 1)-1,
_endian_bytes_to_int($event->{Data_Load}, $i, 2)));
$i += 6;
$Refined->{Message_Encoding} = _endian_bytes_to_int($event->{Data_Load}, $i, 1);
$Refined->{Message_Flags} = _endian_bytes_to_int($event->{Data_Load}, $i, 1);
$i+=2;
my $DataLength=0;
if ($Refined->{Message_Encoding} == 1){
#normal text message..
$Refined->{MessageType} = "Normal";
$DataLength = _bytes_to_int ($event->{Data_Load}, $i, 2);
$Refined->{text} = _bytes_to_str($event->{Data_Load}, $i+2, $DataLength);
}elsif ($Refined->{Message_Encoding} == 4)
{
$Refined->{MessageType} = "URL";
$DataLength = _bytes_to_int ($event->{Data_Load}, $i, 2);
my $data = _bytes_to_str($event->{Data_Load}, $i+2, $DataLength);
($Refined->{Description},$Refined->{URL}) = split /\xfe/,$data;
}elsif ($Refined->{Message_Encoding} == 5)
{
my $DataLength = _bytes_to_int($event->{Data_Load}, $i, 2);
$i+=2;
$i+=4;
my $type = ord(_bytes_to_str($event->{Data_Load}, $i, 1));
$i++;
if($type == 12) # Auth request!
{
$i++;
my $data = _bytes_to_str($event->{Data_Load}, $i, $DataLength-4);
my($nick,$fname,$lname,$email,$xxx,$reason);
($nick,$fname,$lname,$email,$xxx,$reason) = split /\xfe/,$data;
$Refined->{nick} = $nick;
$Refined->{first_name} = $fname;
$Refined->{last_name} = $lname;
$Refined->{email} = $email;
$Refined->{reason} = $reason;
$Refined->{MessageType} = "auth_request";
}
elsif($type == 19) # contacts
{
$i+=2;
my $data = _bytes_to_str($event->{Data_Load}, $i, $DataLength-4);
my ($contactcount,@contacts) = split /\xfe/,$data;
$Refined->{MessageType} = "contacts";
$Refined->{Count} = $contactcount;
$Refined->{Contacts} = \@contacts;
}
}elsif ($Refined->{Message_Encoding} == 26)
{
$DataLength = _bytes_to_int ($event->{Data_Load}, $i, 2);
my $data = _bytes_to_str($event->{Data_Load}, $i+2, $DataLength);
my @bytes = _str_to_bytes($data);
$i = $bytes[1]-2;
my $reason = '';
foreach (@bytes[$i+1..$i+$bytes[$i]])
{
$reason .= chr;
}
$Refined->{MessageType} = "contacts_request";
$Refined->{Reason} = $reason;
}
}
else {
print "Argh, something Screwed up!!!";
return;
}
}
elsif ($Refined->{MessageType} == 66){
# End of offline messages
$Me->Send_Command("Cmd_Srv_Message", {MessageType => "ack_offline"});
$Refined->{MessageType} = "ack_offline";
}
elsif ($Refined->{MessageType} == 2010){
#Server messages stored in "html" style tags..
$i += 2;
if (_bytes_to_int($event->{Data_Load}, $i, 2) == 41480) {
#short gap.. (this is a VERY bad way of doing this.. should fix..)
$i += 3;
}
else {
#don't know what these 11(?) bytes do..
$i += 11;
}
$Refined->{TaggedDataString} = _bytes_to_str($event->{Data_Load}, $i+2, _bytes_to_int($event->{Data_Load}, $i, 2));
_Decode_Tagged_Text($Refined->{TaggedDataString}, $Refined);
}
return ($Refined);
}
);
%_Cmd_Codes = (
Cmd_GSC_Client_Ready => "1:2",
Cmd_GSC_Reqest_Rate_Info => "1:6",
Cmd_GSC_LoggedIn_User_Info => "1:14",
Cmd_GSC_ICQInform => "1:23",
Cmd_GSC_Set_Status => "1:30",
Cmd_LS_LoggedIn_User_Rights => "2:2",
Cmd_LS_Set_User_Info => "2:4",
Cmd_BLM_Rights_Info => "3:2",
Cmd_CTL_UploadList => "3:4",
Cmd_Mes_Add_ICBM_Param => "4:2",
Cmd_Mes_Param_Info => "4:4",
Cmd_BOS_Get_Rights => "9:2",
Cmd_BOS_Add_VisibleList => "9:5",
Cmd_BOS_Add_InVisibleList => "9:7",
Cmd_Srv_Message => "21:2",
Cmd_Send_Message => "4:6",
Cmd_Add_ContactList => "19:20",
Cmd_Authorize => "19:26"
);
%_Srv_Codes = (
Srv_GSC_Error => "1:1",
Srv_GSC_Ready => "1:3",
Srv_GSC_Redirect => "1:5",
Srv_GSC_Rate_Info => "1:7",
Srv_GSC_Rate_Change => "1:10",
Srv_GSC_User_Info => "1:15",
Srv_GSC_MOTD => "1:19",
Srv_GSC_ICQClientConfirm=> "1:24",
Srv_LS_Rights_Response => "2:3",
Srv_BLM_Rights_Response => "3:3",
Srv_BLM_Contact_Online => "3:11",
Srv_BLM_Contact_Offline => "3:12",
Srv_Mes_Rights_Response => "4:5",
Srv_Mes_Received => "4:7",
Srv_BOS_Rights => "9:3",
Srv_Srv_Message => "21:3"
);
%_New_Connection_Nefotiation_Codes = (
1 => sub {
my ($Me, $event) = @_;
my($Responce);
print "Sending Connection reply..\n";
if ($Me->{_Connection_Cookie}){
print "Sending Cookie\n";
#Second time connected, so send the cookie..
$Responce->{Channel_ID} = 1;
@{$Responce->{Data_Load}} = _int_to_bytes(4, 1);
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'Connection_Cookie', $Me->{_Connection_Cookie}));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
#wipe the now used cookie (eat? :)
$Me->{_Connection_Cookie} = 0;
return;
}
#send our login details..
$Responce->{Channel_ID} = 1;
@{$Responce->{Data_Load}} = _int_to_bytes(2, 0);
push(@{$Responce->{Data_Load}}, _int_to_bytes(2, 1));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'UIN', $Me->{_UIN}));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'Password', &_Password_Encrypt($Me->{_Password})));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'ClientProfile', "ICQ Inc. - Product of ICQ (TM).2000b.4.63.1.3279.85"));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'ClientType', 266));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'ClientVersionMajor', 4));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'ClientVersionMinor', 63));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'ClientICQNumber', 1));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'ClientBuildMajor', 3279));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'ClientBuildMinor', 85));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'Language', "en"));
push(@{$Responce->{Data_Load}}, _Write_TLV(1, 'CountryCode', "us"));
push(@{$Me->{_Outgoing_Queue}}, $Responce);
}
);
sub _Disconnection_Nefotiation {
my ($Me, $event) = @_;
my ($Details, $i);
print "Incomming..\n" if $Me->{_Debug};
_print_packet($event->{Data_Load}, ()) if $Me->{_Debug};
($Details, $i) = &_Read_TLV($event->{Data_Load}, 4);
croak("Server got our UIN wrong!![".$Details->{UIN}."]") if ($Details->{UIN} != $Me->{_UIN});
$Me->{_Connection_Cookie} = $Details->{Connection_Cookie} if ($Details->{Connection_Cookie});
if ($Details->{Server_And_Port}){
#we've been told to disconnect, and reconnect...
print "Disconnecting as instructed..\n";
$Me->Disconnect();
#change the server we are going to access...
($Me->{_Server}, $Me->{_ServerPort}) = split (/:/, $Details->{Server_And_Port});
print "Changing to server [".$Me->{_Server}."][".$Me->{_ServerPort}."]\n" if ($Me->{_Debug});
$Me->Connect();
}
elsif ($Details->{Password_Error}){
#run the PasswordError hook 9,9
if (exists $Me->{_Hooks}{$_->{9}{9}} ) {
&{$Me->{_Hooks}{9}{9}}($Me, $_)
}
}
elsif ($Details->{Dual_User_Online}){
#run the DualUserError hook 9,10
if (exists $Me->{_Hooks}{$_->{9}{10}} ) {
&{$Me->{_Hooks}{9}{10}}($Me, $_)
}
}
}
sub Check_Incoming {
my ($Me) = @_;
my($RawPacket, @Packet);
while (IO::Select->select($Me->{_Select}, undef, undef, .00001)) {
$Me->{_Socket}->recv($RawPacket, 10000);
if (!$RawPacket) {
$Me->Disconnect;
return;
}
@Packet = split(//, $RawPacket);
foreach (@Packet){
$_ = ord;
}
my $PLength = @Packet;
#decode the packet into FLAPs
for(my $i =0; $i < $PLength; $i++){
if ($Me->{_FLAP_Bytes_Left} > 0){
push (@{$Me->{_FLAP_In_progress}{Data_Load}}, $Packet[$i]);
$Me->{_FLAP_Bytes_Left}--;
if ($Me->{_FLAP_Bytes_Left} <= 0){
#end the FLAP, and move it to the Queue..
push(@{$Me->{_Incoming_Queue}}, $Me->{_FLAP_In_progress});
$Me->{_FLAP_In_progress} = undef;
$Me->{_FLAP_Bytes_Left} = 0;
}
next;
}
#it's a new FLAP..
$Packet[$i] == 42 or croak("Recieved Data Missaligned!");
$Me->{_FLAP_In_progress}{Channel_ID} = _bytes_to_int(\@Packet, $i+1, 1);
$Me->{_FLAP_In_progress}{Sequence_ID} = _bytes_to_int(\@Packet, $i+2, 2);
$Me->{_FLAP_In_progress}{Data_Size} = $Me->{_FLAP_Bytes_Left} = _bytes_to_int(\@Packet, $i+4, 2);
$Me->{_FLAP_In_progress}{Family_ID} = _bytes_to_int(\@Packet, $i+6, 2);
$Me->{_FLAP_In_progress}{Sub_ID} = _bytes_to_int(\@Packet, $i+8, 2);
$i +=5;
}
}
}
sub Deal_With_FLAPs {
my($Me) = @_;
foreach (@{$Me->{_Incoming_Queue}}){
if ($_->{Channel_ID} == 1){
#login system message, deal with it..
if ( exists $_New_Connection_Nefotiation_Codes{$_->{Sub_ID}} ) {
print "Found Connection Event, Dealing with it,,\n";
&{$_New_Connection_Nefotiation_Codes{$_->{Sub_ID}}}($Me, $_);
}
}
elsif ($_->{Channel_ID} == 2){
#This is a non critical FLAP. so decode, and pass to a hook if there is one..
my $ID = $_->{Family_ID}.":".$_->{Sub_ID};
# print "\n==> $ID\n";
if (exists $Me->{_Hooks}{$ID} ) {
#decode the Sub_ID etc..
print "can't run sub!![$ID]\n" if ( !(exists $_Srv_Decoder{$ID}) );
my ($Refined);
$Refined = &{$_Srv_Decoder{$ID}}($Me, $_) if ( exists $_Srv_Decoder{$ID} );
#run the Hook..
&{$Me->{_Hooks}{$ID}}($Me, $Refined);
}
elsif ($Me->{_Auto_Login}){
&{$_Srv_Decoder{$ID}}($Me, $_) if ( exists $_Srv_Decoder{$ID} );
}
elsif ($Me->{_Debug}){
print "Incomming..\n" if $Me->{_Debug};
print "[".$_->{Channel_ID}."][".$_->{Sequence_ID}."][".$_->{Data_Size}."][".$_->{Family_ID}."][".$_->{Sub_ID}."]\n";
_print_packet($_->{Data_Load}, ());
}
}
elsif ($_->{Channel_ID} == 4){
print "Found DisConnection Event, Dealing with it,,\n";
&_Disconnection_Nefotiation($Me, $_);
}
else {
#this is an error type message..
}
}
$Me->{_Incoming_Queue} = [];
}
sub Send_Outgoing {
my($Me) = @_;
my($Chan, $Data_Size, @Header, $Raw_Data);
foreach (@{$Me->{_Outgoing_Queue}}){
if ($_->{Channel_ID}){$Chan = $_->{Channel_ID};}else {$Chan = 2;}
$Data_Size = @{$_->{Data_Load}};
@Header = (42, $Chan);
$Me->{_Seq_Num}++;
$Me->{_Seq_Num} = 0 if $Me->{_Seq_Num} > 65535;
push(@Header, _int_to_bytes(2, $Me->{_Seq_Num}));
push(@Header, _int_to_bytes(2, $Data_Size));
foreach (@Header){
$Raw_Data .= chr($_);
}
foreach (@{$_->{Data_Load}}){
$Raw_Data .= chr($_);
}
print "Outgoing..\n" if $Me->{_Debug};
_print_packet(\@Header, \@{$_->{Data_Load}}) if $Me->{_Debug};
}
#send them all off..
if ($Raw_Data) {
$Me->{_Socket}->send($Raw_Data);
}
$Me->{_Outgoing_Queue} = [];
}
#########################
### Private functions ###
#########################
#These functions should only ever be run from within the ICQ object..
# _bytes_to_int(array_ref, start, bytes)
#
# Converts the byte array referenced by , starting at offset
# and running for values, into an integer, and returns it.
# The bytes in the array must be in little-endian order.
#
# _bytes_to_int([0x34, 0x12, 0xAA, 0xBB], 0, 2) == 0x1234
# _bytes_to_int([0x34, 0x12, 0xAA, 0xBB], 2, 1) == 0xAA
sub _endian_bytes_to_int {
my ($array, $start, $bytes) = @_;
my ($ret);
$ret = 0;
for (my $i = $start+$bytes-1; $i >= $start; $i--) {
$ret <<= 8;
$ret |= ($array->[$i] or 0);
}
return $ret;
}
sub _bytes_to_int {
my ($array, $start, $bytes) = @_;
my ($ret);
$ret = 0;
for (my $i = $start; $i < $start+$bytes; $i++) {
$ret <<= 8;
$ret |= ($array->[$i] or 0);
}
return $ret;
}
# _int_to_endian_bytes(bytes, val)
#
# Converts into an array of bytes and returns it.
# If is too big, only the least significant bytes are
# returned. The array is in little-endian order.
#
# _int_to_bytes(2, 0x1234) == (0x34, 0x12)
# _int_to_bytes(2, 0x12345) == (0x45, 0x23)
sub _int_to_endian_bytes {
my ($bytes, $val) = @_;
my (@ret);
for (my $i=0; $i<$bytes; $i++) {
push @ret, ($val >> ($i*8) & 0xFF);
}
return @ret;
}
# _int_to_bytes(bytes, val)
#
# Converts into an array of bytes and returns it.
# If is too big, only the least significant bytes are
# returned. The array is not little-endian order.
#
# _int_to_bytes(2, 0x1234) == (0x12, 0x34)
# _int_to_bytes(2, 0x12345) == (0x12, 0x34)
sub _int_to_bytes {
my ($bytes, $val) = @_;
my (@ret);
for (my $i=0; $i<$bytes; $i++) {
unshift @ret, ($val >> ($i*8) & 0xFF);
}
return @ret;
}
# _str_to_bytes(str, add_zero)
#
# Converts into an array of bytes and returns it.
#
# _str_to_bytes('foo') == ('f', 'o', 'o')
sub _str_to_bytes {
my ($string) = @_;
my (@ret);
# the ?: keeps split() from complaining about undefined values
foreach (split(//, defined($string) ? $string : '')) {
push @ret, ord($_);
}
return @ret;
}
# _uin_to_buin(str, add_zero)
#
# Converts into an array of bytes and returns it.
#
# _str_to_bytes('foo') == ('f', 'o', 'o')
sub _uin_to_buin {
my ($uin) = @_;
my (@ret);
push @ret, length($uin);
# the ?: keeps split() from complaining about undefined values
foreach (split(//, defined($uin) ? $uin : '')) {
push @ret, ord($_);
}
return @ret;
}
# _bytes_to_str(array_ref, start, bytes)
#
# Converts the byte array referenced by , starting at offset
# and running for values, into a string, and returns it.
#
# _bytes_to_str([0x12, 'f', 'o', 'o', '!'], 1, 3) == 'foo'
sub _bytes_to_str {
# thanks to Dimitar Peikov for the fix
my ($array, $start, $bytes) = @_;
my ($ret);
$ret = '';
for (my $i = $start; $i < $start+$bytes; $i++) {
$ret .= $array->[$i] ? chr($array->[$i]) : '';
}
return $ret;
}
# print_packet(Header_packet_ref, Body_packet_ref)
#
# Dumps the ICQ packet contained in the byte array referenced by
# to STDOUT.
sub _print_packet {
my ($Header, $packet) = @_;
my ($Counter, $TLine);
foreach (@$Header) {
$Counter++;
print sprintf("%02X ", $_);
if ($_ >= 32){
$TLine .= chr($_);
}
else {
$TLine .= ".";
}
if ($Counter % 16 == 0){
print " ".$TLine."\n";
$TLine = '';
}
}
while ($Counter > 16){$Counter -=16}
if (16 - $Counter > 1 && $Counter > 0){
foreach (1..(16 - $Counter)){
print " ";
}
print " ".$TLine."\n";
}
$TLine ='';
$Counter =0;
foreach (@$packet) {
$Counter++;
print sprintf("%02X ", $_);
if ($_ >= 32){
$TLine .= chr($_);
}
else {
$TLine .= ".";
}
if ($Counter % 16 == 0){
print " ".$TLine."\n";
$TLine = '';
}
}
while ($Counter > 16){$Counter -=16}
if (16 - $Counter > 1 && $Counter > 0){
foreach (1..(16 - $Counter)){
print " ";
}
print " ".$TLine."\n";
}
print "\n";
}
# _Password_Encrypt(Password_String)
# Encrypts the password for sending to the server using a simple XOR "encryption" method
sub _Password_Encrypt {
my ($Password) = @_;
my ($FinishedString);
my @Pass = split (//, $Password);
foreach (@Pass){
$_ = ord($_);
}
my @encoding_table = (
0xf3, 0x26, 0x81, 0xc4,
0x39, 0x86, 0xdb, 0x92,
0x71, 0xa3, 0xb9, 0xe6,
0x53, 0x7a, 0x95, 0x7c);
for (my $i = 0; $i < length($Password); $i++){
$FinishedString .= chr($Pass[$i] ^ $encoding_table[$i]);
}
return ($FinishedString);
}
# _Make_SNAC_Header(Comand_Family, Sub_Family, FlagA, FlagB, RequestID)
#makes the SNAC header which has to be at the top of every command..
sub _Make_SNAC_Header {
my($Family, $Sub_Family, $FlagA, $FlagB, $RequestID) = @_;
my (@Header);
@Header = _int_to_bytes(2, $Family);
push(@Header, _int_to_bytes(2, $Sub_Family));
push(@Header, _int_to_bytes(1, $FlagA));
push(@Header, _int_to_bytes(1, $FlagB));
push(@Header, _int_to_bytes(4, $RequestID));
return @Header;
}
#this function takes a tagged string (like the server sends..) and breaks it up into
# it's parts...
sub _Decode_Tagged_Text {
my($String, $Details) = @_;
my($Key, $Data, $i);
my @Entries = split (/, $String);
if ($Entries[4]){
chop $Entries[1];
$Details->{MessageType} = $Entries[1];
$i = 2;
}
else {
$i = 1;
}
while ($Entries[$i] && $Entries[$i+1]){
($Key, $Data) = split(/>/, $Entries[$i]);
$Details->{$Key} = $Data;
$i += 2;
}
return ($Details);
}
#####################
### TLV functions ###
#####################
# TLV (Type, Length, Value) is the way much of the data is sent an recieved
# The Data below contains the definitions of the Types, their lengths, and what kind
# of data is to be expected (eg strings or ints etc..)
# Also has the _Write_TLV and _Read_TLV functions..
#definitions for the TLVs types being sent from the server..
#The first digit (2 or 4) denotes the FLAP's Chan
%_TLV_IN = (
2 => { User_Class => 0x01,#!?????
Signup_Date => 0x02,#! doesn't really work for ICQ, set to date of login, 1 sec before normal login date..
SignOn_Date => 0x03,#!
Unknown00 => 0x04,#! ??
Encoded_Message => 0x05,#!
Online_Status => 0x06,#!
Ip_Address => 0x0a,#! in 4 byte format..
Web_Address => 0x0b,#!
Unknown02 => 0x0c,#! (long like 25 bytes..)
Unknown03 => 0x0d,#! ???
Time_Online => 0x0f #!
},
4 => { UIN => 0x01,#!
HTML_Address => 0x04,#!
Server_And_Port => 0x05,#!
Connection_Cookie => 0x06,#!
Error_Code => 0x08,#!
Dual_User_Online => 0x09,
},
);
#definitions for the TLVs types being sent from us to the server..
#The first digit (1 or 2) denotes the FLAP's Chan
%_TLV_OUT = (
1 => { UIN => 0x01,#!
Password => 0x02,#!
ClientProfile => 0x03,#!
User_Info => 0x05,
Connection_Cookie => 0x06,#!
CountryCode => 0x0e,#!
Language => 0x0f,#!
ClientBuildMinor => 0x14,#!
ClientType => 0x16,#!
ClientVersionMajor => 0x17,#!
ClientVersionMinor => 0x18,#!
ClientICQNumber => 0x19,#!
ClientBuildMajor => 0x1a #!
},
2 => { Status => 0x06,#!
Unknown00 => 0x08,#!????
Unknown01 => 0x0c,#!????
Unknown00 => 0x08,#!????
}
);
#if the TLV is a number, we define the number of bytes to use..(note all numbers are their decimal value, not hex)
# 1000 denotes a "raw" data input, and is encoded differently..
%_TLV_Length_O = (
1 => { 6 =>1000,
20 =>4,
22 =>2,
23 =>2,
24 =>2,
25 =>2,
26 =>2
},
2 => { 6 =>4,
8 =>2,
},
);
#This defines the type of data we expect comming in, the codes are as follows..
# 0 or no entry = String
# 1 = Int
# 2 = Raw (obtains the data still as a string of numbers seperated by spaces)
# 3 = IP
%_TLV_Length_I = (
2 => { 1 =>1,
2 =>1,
3 =>1,
4 =>1,
5 =>2,
6 =>1,
10 =>3,
15 =>1,
},
4 => { 8 =>1,
6 =>2,
},
);
# _Write_TLV(Message_Channel, Type_Value, Info_To_Encode)
#
# This creates an packet array ready for sending to the server, containing the given data
sub _Write_TLV {
my($Chan, $Value, $Infomation) = @_;
my(@Data);
$Value = $_TLV_OUT{$Chan}{$Value} if (exists $_TLV_OUT{$Chan}{$Value});
@Data = _int_to_bytes(2, $Value);
if ($_TLV_Length_O{$Chan}{$Value} == 1000){
#get it as an array!
my @Cookie = split(/ /, $Infomation);
my $CLength = @Cookie;
push(@Data, _int_to_bytes(2, $CLength));
push(@Data, @Cookie);
}
elsif (exists $_TLV_Length_O{$Chan}{$Value}){
#their a number, and need a set byte size..
push(@Data, _int_to_bytes(2, $_TLV_Length_O{$Chan}{$Value}));
push(@Data, _int_to_bytes($_TLV_Length_O{$Chan}{$Value}, $Infomation));
}
else {
push(@Data, _int_to_bytes(2, length($Infomation)));
push(@Data, _str_to_bytes($Infomation));
}
return (@Data);
}
# _Read_TLV(Array_to_Read, Message_Channel, Starting_offset_in_array, Array_for_results, Max_number_of_TLVs)
#
# This reads through an packet array picking out and decoding all the TLVs it can find,
# till it reaches the end of the array, or else reaches the Max_Num value (counted in TLVs not bytes..)
# It returns an Hash containing the found types/values and the final of set.
sub _Read_TLV {
my($Array, $Chan, $Start, $Details, $Max) = @_;
my($i, $ArrayLength, $DataType, $DataLength, $DataTypeName);
$ArrayLength = @$Array;
$Start or $Start = 0;
$Max or $Max = 100000;
for ($i = $Start; $i <$ArrayLength;){
#only get up to the max number of TVLs
$Max or last;
$Max--;
#read in the Data Type/length..
$DataType = _bytes_to_int ($Array, $i, 2);
$DataLength = _bytes_to_int ($Array, $i+2, 2);
$i += 4;
#find the name of this data type..
$DataTypeName = $DataType;
foreach (keys %{$_TLV_IN{$Chan}}){
$DataTypeName = $_ if ($_TLV_IN{$Chan}{$_} == $DataType);
}
if ($_TLV_Length_I{$Chan}{$DataType} == 2){
#get it as an array!
for (my $p=0; $p < $DataLength; $p++){
$Details->{$DataTypeName} .= $Array->[$i+$p]." ";
}
chop $Details->{$DataTypeName};
}
elsif ($_TLV_Length_I{$Chan}{$DataType} == 3){
#get it as IP address
if ($DataLength != 4){
print "Argh, This an't an IP!!!\n";
}
else {
$Details->{$DataTypeName} = _bytes_to_int ($Array, $i, 1)."."._bytes_to_int ($Array, $i+1, 1)."."._bytes_to_int ($Array, $i+2, 1)."."._bytes_to_int ($Array, $i+3, 1);
}
}
elsif ($_TLV_Length_I{$Chan}{$DataType} == 1){
#we're getting a number...
$Details->{$DataTypeName} = _bytes_to_int ($Array, $i, $DataLength);
}
else {
$Details->{$DataTypeName} = _bytes_to_str ($Array, $i, $DataLength);
}
$i +=$DataLength;
}
return ($Details, $i);
}
1;
__END__
# Below is the stub of documentation for your module. You better edit it!
=head1 NAME
Net::ICQ2000 - ICQv7 protocol realisation
=head1 SYNOPSIS
use Net::ICQ2000 ;)
=head1 DESCRIPTION
This module is designed to give perl scripts access to the ICQ network and
the functions provided by it, like SMS
=head1 AUTHOR
Written by Robin Fisher UIN 24340914
Some parts/ideas were borrowes from Jeremy Muhlich, Luke Petre and anyone else
who contributed to Net::ICQ.
Slightly modified by Alexander Timoshenko
=head1 SEE ALSO
perl(1).
=cut