| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Net::OICQ; |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# $Id: OICQ.pm,v 1.19 2007/06/16 12:35:08 tans Exp $ |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
# Copyright (c) 2002 - 2007 Shufeng Tan. All rights reserved. |
|
6
|
|
|
|
|
|
|
# |
|
7
|
|
|
|
|
|
|
# This package is free software and is provided "as is" without express |
|
8
|
|
|
|
|
|
|
# or implied warranty. It may be used, redistributed and/or modified |
|
9
|
|
|
|
|
|
|
# under the terms of the Perl Artistic License (see |
|
10
|
|
|
|
|
|
|
# http://www.perl.com/perl/misc/Artistic.html) |
|
11
|
|
|
|
|
|
|
|
|
12
|
1
|
|
|
1
|
|
23282
|
use 5.008; |
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
122
|
|
|
13
|
1
|
|
|
1
|
|
6
|
use strict; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
34
|
|
|
14
|
1
|
|
|
1
|
|
6
|
use warnings; |
|
|
1
|
|
|
|
|
5
|
|
|
|
1
|
|
|
|
|
30
|
|
|
15
|
1
|
|
|
1
|
|
1015
|
use bytes; |
|
|
1
|
|
|
|
|
9
|
|
|
|
1
|
|
|
|
|
4
|
|
|
16
|
1
|
|
|
1
|
|
24
|
use Carp; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
70
|
|
|
17
|
1
|
|
|
1
|
|
842
|
use FileHandle; |
|
|
1
|
|
|
|
|
12878
|
|
|
|
1
|
|
|
|
|
9
|
|
|
18
|
1
|
|
|
1
|
|
1494
|
use IO::Socket::INET; |
|
|
1
|
|
|
|
|
1229866
|
|
|
|
1
|
|
|
|
|
12
|
|
|
19
|
1
|
|
|
1
|
|
761
|
use Digest::MD5; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
52
|
|
|
20
|
1
|
|
|
1
|
|
1331
|
use Encode; |
|
|
1
|
|
|
|
|
485220
|
|
|
|
1
|
|
|
|
|
152
|
|
|
21
|
|
|
|
|
|
|
|
|
22
|
1
|
|
|
1
|
|
765
|
use Crypt::OICQ qw(encrypt decrypt); |
|
|
1
|
|
|
|
|
4562
|
|
|
|
1
|
|
|
|
|
80
|
|
|
23
|
1
|
|
|
1
|
|
561
|
use Net::OICQ::ClientEvent; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
82
|
|
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
our $VERSION = '1.6'; |
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
#################### Begin OICQ protocol data ###################### |
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
our $SERVER_DOMAIN = pack("H*", "74656e63656e742e636f6d"); # ;-) |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# An OICQ session may use UDP or TCP. |
|
32
|
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# The first two bytes of a TCP packet are a short integer in network |
|
34
|
|
|
|
|
|
|
# order (pack 'n'), which stores the data length including the leading |
|
35
|
|
|
|
|
|
|
# two bytes. Other than these two bytes, the format of TCP packets is |
|
36
|
|
|
|
|
|
|
# identical to that of UDP packets. The following description is |
|
37
|
|
|
|
|
|
|
# for UDP packets only. |
|
38
|
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# A QQ data segment always begins with ASCII STX and ends with ASCII ETX |
|
40
|
|
|
|
|
|
|
|
|
41
|
1
|
|
|
1
|
|
6
|
use constant STX => "\x02"; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
68
|
|
|
42
|
1
|
|
|
1
|
|
6
|
use constant ETX => "\x03"; |
|
|
1
|
|
|
|
|
1
|
|
|
|
1
|
|
|
|
|
1971
|
|
|
43
|
|
|
|
|
|
|
|
|
44
|
|
|
|
|
|
|
# Bytes 0x01-0x02 seem to be client version |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
# These two bytes used to be fixed at 0x01 0x00 for packets from servers |
|
47
|
|
|
|
|
|
|
# but they may use the same value as client, as of July 2006 |
|
48
|
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
# 0x06 0x2e for packets from GB client version 2000c build 630 |
|
50
|
|
|
|
|
|
|
# 0x07 0x2e for packets from En client version 2000c build 305 |
|
51
|
|
|
|
|
|
|
# 0x08 0x01 for packets from En client version 2000c build 630 |
|
52
|
|
|
|
|
|
|
# 0x09 0x09 for packets from GB client version 2000c build 1230b |
|
53
|
|
|
|
|
|
|
# 0x0b 0x37 for packets from QQ 2003iii 0304 |
|
54
|
|
|
|
|
|
|
# 0x0e 0x2d for packets from GB client version 2005 sp1 V05.0.201.110 |
|
55
|
|
|
|
|
|
|
# 0x0f 0x5f for packets from GB client V06.0.200.410 |
|
56
|
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
our $CLIENT_VER = "\x0f\x5f"; #"\x0e\x2d"; |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
# Bytes 0x03-0x04 indicate command |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
our %CmdCode = ( |
|
62
|
|
|
|
|
|
|
logout => "\0\x01", |
|
63
|
|
|
|
|
|
|
keep_alive => "\0\x02", |
|
64
|
|
|
|
|
|
|
update_info => "\0\x04", |
|
65
|
|
|
|
|
|
|
search_users => "\0\x05", |
|
66
|
|
|
|
|
|
|
get_user_info => "\0\x06", |
|
67
|
|
|
|
|
|
|
add_contact_1 => "\0\x09", |
|
68
|
|
|
|
|
|
|
del_contact => "\0\x0a", |
|
69
|
|
|
|
|
|
|
add_contact_2 => "\0\x0b", |
|
70
|
|
|
|
|
|
|
set_mode => "\0\x0d", |
|
71
|
|
|
|
|
|
|
ack_service_msg => "\0\x12", |
|
72
|
|
|
|
|
|
|
send_msg => "\0\x16", |
|
73
|
|
|
|
|
|
|
recv_msg => "\0\x17", |
|
74
|
|
|
|
|
|
|
unknown_001a => "\0\x1a", |
|
75
|
|
|
|
|
|
|
forbid_contact => "\0\x1c", |
|
76
|
|
|
|
|
|
|
req_file_key => "\0\x1d", # provided by alexe |
|
77
|
|
|
|
|
|
|
cell_phone_1 => "\0\x21", # provided by alexe |
|
78
|
|
|
|
|
|
|
login => "\0\x22", |
|
79
|
|
|
|
|
|
|
get_friends_list => "\0\x26", |
|
80
|
|
|
|
|
|
|
get_online_friends => "\0\x27", |
|
81
|
|
|
|
|
|
|
cell_phone_2 => "\0\x29", # provided by alexe |
|
82
|
|
|
|
|
|
|
do_group => "\0\x30", # provided by alexe |
|
83
|
|
|
|
|
|
|
#login_request => "\0\x62", # obsolete |
|
84
|
|
|
|
|
|
|
recv_service_msg => "\0\x80", |
|
85
|
|
|
|
|
|
|
recv_friend_status => "\0\x81", |
|
86
|
|
|
|
|
|
|
login_request_1 => "\0\x91", |
|
87
|
|
|
|
|
|
|
login_request_2 => "\0\xba", |
|
88
|
|
|
|
|
|
|
); |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
our %Cmd; |
|
91
|
|
|
|
|
|
|
foreach my $cmd (keys %CmdCode) { $Cmd{$CmdCode{$cmd}} = $cmd } |
|
92
|
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
our %GrpCmdCode = ( |
|
94
|
|
|
|
|
|
|
get_info => "\x04", |
|
95
|
|
|
|
|
|
|
search => "\x06", |
|
96
|
|
|
|
|
|
|
online_members => "\x0b", |
|
97
|
|
|
|
|
|
|
member_info => "\x0c", |
|
98
|
|
|
|
|
|
|
grp_cmd_0x0f => "\x0f", |
|
99
|
|
|
|
|
|
|
grp_cmd_0x19 => "\x19", |
|
100
|
|
|
|
|
|
|
send_msg => "\x1a", |
|
101
|
|
|
|
|
|
|
grp_cmd_0x36 => "\x36", |
|
102
|
|
|
|
|
|
|
); |
|
103
|
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
our %GrpCmd; |
|
105
|
|
|
|
|
|
|
foreach my $cmd (keys %GrpCmdCode) { $GrpCmd{$GrpCmdCode{$cmd}} = $cmd } |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# Bytes 0x05-0x06 form a packet sequence number, a 16-bit integer |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
# Login modes |
|
110
|
|
|
|
|
|
|
our %ConnectMode = ( |
|
111
|
|
|
|
|
|
|
Normal => "\x0a", |
|
112
|
|
|
|
|
|
|
Away => "\x1e", |
|
113
|
|
|
|
|
|
|
Invisible => "\x28" |
|
114
|
|
|
|
|
|
|
); |
|
115
|
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# System message code for 0x80 cmd |
|
117
|
|
|
|
|
|
|
our %ServiceMsgCode = ( |
|
118
|
|
|
|
|
|
|
'01' => 'User', |
|
119
|
|
|
|
|
|
|
'02' => 'ContactRequest', |
|
120
|
|
|
|
|
|
|
'06' => 'Broadcast' |
|
121
|
|
|
|
|
|
|
); |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# Separators |
|
124
|
|
|
|
|
|
|
our $FS = "\x1e"; # Field separator |
|
125
|
|
|
|
|
|
|
our $RS = "\x1f"; # Record separator |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
our @InfoHeader = qw( |
|
128
|
|
|
|
|
|
|
UserID Nickname Country Province PostCode Street Phone Age Sex Realname |
|
129
|
|
|
|
|
|
|
Email PagerCode PagerProvider PagerStationNum PagerNum PagerType |
|
130
|
|
|
|
|
|
|
Occupation Homepage Authorization unkn19 unkn20 Avatar |
|
131
|
|
|
|
|
|
|
MobilePhone MobileType Aboutme City unkn26 unkn27 unkn28 PublishMobile |
|
132
|
|
|
|
|
|
|
PublishContact School Horoscope Shengxiao BloodType unkn35 unkn36 |
|
133
|
|
|
|
|
|
|
); |
|
134
|
|
|
|
|
|
|
|
|
135
|
|
|
|
|
|
|
our %Emoticon = ( |
|
136
|
|
|
|
|
|
|
"\x41" => '¾ªÑÈ', "\x42" => 'Ʋ×ì', "\x43" => 'É«', "\x44" => '·¢´ô', "\x45" => 'µÃÒâ', |
|
137
|
|
|
|
|
|
|
"\x46" => 'Á÷Àá', "\x47" => 'º¦Ðß', "\x48" => '±Õ×ì', "\x49" => '˯', "\x4a" => '´ó¿Þ', |
|
138
|
|
|
|
|
|
|
"\x4b" => 'ÞÏÞÎ', "\x4c" => '·¢Å', "\x4d" => 'µ÷Ƥ', "\x4e" => 'ßÚÑÀ', "\x4f" => '΢Ц', |
|
139
|
|
|
|
|
|
|
"\x73" => 'Äѹý', "\x74" => '¿á', "\x75" => '·Çµä', "\x76" => '×¥¿ñ', "\x77" => 'ÍÂ', |
|
140
|
|
|
|
|
|
|
"\x8a" => '', "\x8b" => '', "\x8c" => '', "\x8d" => '', "\x8e" => '', |
|
141
|
|
|
|
|
|
|
"\x8f" => '', "\x78" => '', "\x79" => '', "\x7a" => '', "\x7b" => '', |
|
142
|
|
|
|
|
|
|
"\x90" => '', "\x91" => '', "\x92" => '', "\x93" => '', "\x94" => '', |
|
143
|
|
|
|
|
|
|
"\x95" => '', "\x96" => '', "\x97" => '', "\x98" => '', "\x99" => '', |
|
144
|
|
|
|
|
|
|
"\x59" => '', "\x5a" => '', "\x5c" => '', "\x58" => '', "\x57" => '', |
|
145
|
|
|
|
|
|
|
"\x55" => '', "\x7c" => '', "\x7d" => '', "\x7e" => '', "\x7f" => '', |
|
146
|
|
|
|
|
|
|
"\x9a" => '', "\x9b" => '', "\x60" => '', "\x67" => '', "\x9c" => '', |
|
147
|
|
|
|
|
|
|
"\x9d" => '', "\x9e" => '', "\x5e" => '', "\x9f" => '', "\x89" => '', |
|
148
|
|
|
|
|
|
|
"\x80" => '', "\x81" => '', "\x82" => '', "\x62" => '', "\x63" => '', |
|
149
|
|
|
|
|
|
|
"\x64" => '', "\x65" => '', "\x66" => '', "\x83" => '', "\x68" => '', |
|
150
|
|
|
|
|
|
|
"\x84" => '', "\x85" => '', "\x86" => '', "\x87" => '', "\x6b" => '', |
|
151
|
|
|
|
|
|
|
"\x6e" => '', "\x6f" => '', "\x70" => '', "\x88" => '', "\xa0" => '', |
|
152
|
|
|
|
|
|
|
"\x50" => '', "\x51" => '', "\x52" => '', "\x53" => '', "\x54" => '', |
|
153
|
|
|
|
|
|
|
"\x56" => '', "\x5b" => '', "\x5d" => '', "\x5f" => '', "\x61" => '', |
|
154
|
|
|
|
|
|
|
"\x69" => 'ÏÂÓê', "\x6a" => '¶àÔÆ', "\x6c" => 'Ñ©ÈË', "\x6d" => 'ÐÇÐÇ', "\x71" => 'Å®', |
|
155
|
|
|
|
|
|
|
"\x72" => 'ÄÐ' |
|
156
|
|
|
|
|
|
|
); |
|
157
|
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
# Some constants for constructing client packets |
|
159
|
|
|
|
|
|
|
my $PacketHead = STX . $CLIENT_VER; |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
my $ProxyConnect = "CONNECT %s HTTP/1.1\r\nAccept: */*\r\nContent-Type: text/html\r\nProxy-Connection: Keep-Alive\r\nContent-length: 0\r\n\r\n"; |
|
162
|
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
#################### End OICQ protocol data ######################## |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Constructor |
|
166
|
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
sub new { |
|
168
|
0
|
|
|
0
|
0
|
|
my ($class) = @_; |
|
169
|
0
|
0
|
|
|
|
|
my $homedir = exists($ENV{HOME}) ? $ENV{HOME} : |
|
|
|
0
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
(exists($ENV{HOMEPATH}) ? $ENV{HOMEPATH} : '.'); |
|
171
|
0
|
|
|
|
|
|
my $dir = "$homedir/.oicq"; |
|
172
|
0
|
0
|
|
|
|
|
if (-e $dir) { |
|
173
|
0
|
0
|
|
|
|
|
-d $dir or croak "$dir exists but is not a directory"; |
|
174
|
|
|
|
|
|
|
} else { |
|
175
|
0
|
0
|
|
|
|
|
mkdir($dir) or croak "Failed to mkdir $dir: $!"; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
0
|
|
|
|
|
|
my $self = { |
|
178
|
|
|
|
|
|
|
Dir => $dir, |
|
179
|
|
|
|
|
|
|
LastSvrAck => 0, |
|
180
|
|
|
|
|
|
|
Font => 'Tahoma', |
|
181
|
|
|
|
|
|
|
FontSize => 12, |
|
182
|
|
|
|
|
|
|
FontColor => '00a000', |
|
183
|
|
|
|
|
|
|
Debug => 0 # 1 - trace packets, 2 - desect packets |
|
184
|
|
|
|
|
|
|
}; |
|
185
|
0
|
|
|
|
|
|
my $logfile = "$dir/oicq.log"; |
|
186
|
0
|
|
|
|
|
|
my $log = new FileHandle ">>$logfile"; |
|
187
|
0
|
0
|
|
|
|
|
defined($log) or croak "Failed to open >>$logfile"; |
|
188
|
0
|
|
|
|
|
|
$log->autoflush; |
|
189
|
0
|
|
|
|
|
|
$self->{LogFile} = $logfile; |
|
190
|
0
|
|
|
|
|
|
$self->{Log} = $log; |
|
191
|
0
|
|
|
|
|
|
return bless($self, $class); |
|
192
|
|
|
|
|
|
|
} |
|
193
|
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
# Methods that do not require connection to a server |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
sub set_user { |
|
197
|
0
|
|
|
0
|
0
|
|
my ($self, $id, $pw) = @_; |
|
198
|
|
|
|
|
|
|
|
|
199
|
0
|
|
|
|
|
|
$self->{Id} = $id; |
|
200
|
0
|
|
|
|
|
|
$self->{Passwd} = $pw; |
|
201
|
0
|
|
|
|
|
|
$self->{_Id} = pack('N', $id); |
|
202
|
0
|
|
|
|
|
|
$self->{PWKey} = Digest::MD5::md5(Digest::MD5::md5($pw)); |
|
203
|
0
|
|
|
|
|
|
$self->{EventQueue} = []; |
|
204
|
0
|
|
|
|
|
|
$self->{EventQueueSize} = 50; |
|
205
|
0
|
|
|
|
|
|
$self->{SearchCount} = 0; |
|
206
|
0
|
|
|
|
|
|
$self->{LogChat} = 1; |
|
207
|
0
|
|
|
|
|
|
$self->{Info} = {}; # use id as hash key |
|
208
|
0
|
|
|
|
|
|
$self->{Away} = 0; |
|
209
|
0
|
|
|
|
|
|
$self->{LastAutoReply} = {}; # use id as hash key |
|
210
|
0
|
|
|
|
|
|
$self->{AutoAwayTime} = ""; |
|
211
|
|
|
|
|
|
|
|
|
212
|
0
|
|
|
|
|
|
my $userdir = "$self->{Dir}/$id"; |
|
213
|
0
|
0
|
|
|
|
|
-e $userdir or mkdir($userdir); |
|
214
|
0
|
0
|
|
|
|
|
if (-d $userdir) { |
|
215
|
0
|
|
|
|
|
|
foreach ($self->get_saved_ids) { $self->get_nickname($_) }; |
|
|
0
|
|
|
|
|
|
|
|
216
|
0
|
|
|
|
|
|
my $logfile = "$userdir/user.log"; |
|
217
|
0
|
|
|
|
|
|
my $log = new FileHandle(">>$logfile"); |
|
218
|
0
|
0
|
|
|
|
|
if (defined $log) { |
|
219
|
0
|
0
|
|
|
|
|
$self->log_t("Switch log to $logfile") if $self->{Debug}; |
|
220
|
0
|
|
|
|
|
|
$self->{Log} = undef; |
|
221
|
0
|
|
|
|
|
|
$self->{LogFile} = $logfile; |
|
222
|
0
|
|
|
|
|
|
$self->{Log} = $log; |
|
223
|
0
|
|
|
|
|
|
$log->autoflush; |
|
224
|
|
|
|
|
|
|
} else { |
|
225
|
0
|
|
|
|
|
|
$self->log_t("Failed to open >>$logfile"); |
|
226
|
|
|
|
|
|
|
} |
|
227
|
|
|
|
|
|
|
} else { |
|
228
|
0
|
|
|
|
|
|
$self->log_t("Failed to mkdir $userdir"); |
|
229
|
|
|
|
|
|
|
} |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Methods for building OICQ packets |
|
233
|
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
sub finalize_packet { |
|
235
|
1
|
|
|
1
|
|
9
|
use bytes; |
|
|
1
|
|
|
|
|
2
|
|
|
|
1
|
|
|
|
|
4
|
|
|
236
|
0
|
|
|
0
|
0
|
|
my ($self, $packet) = @_; |
|
237
|
0
|
0
|
|
|
|
|
return($packet) if $self->{UDP}; |
|
238
|
0
|
|
|
|
|
|
return(pack('n', length($packet) + 2) . $packet); |
|
239
|
|
|
|
|
|
|
} |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
# A TCP packet from server may contain multiple QQ data segment, sometimes with |
|
242
|
|
|
|
|
|
|
# null segments in the beginning, the end, or between commands. |
|
243
|
|
|
|
|
|
|
# get_data method returns a list of valid QQ data segments, each of |
|
244
|
|
|
|
|
|
|
# which generates a server event. |
|
245
|
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
sub get_data { |
|
247
|
0
|
|
|
0
|
0
|
|
my ($self, $packet) = @_; |
|
248
|
0
|
0
|
|
|
|
|
return () unless $packet; |
|
249
|
|
|
|
|
|
|
# do nothing to UDP packets |
|
250
|
0
|
0
|
|
|
|
|
return ($packet) if $self->{UDP}; |
|
251
|
0
|
|
|
|
|
|
my $len = length($packet); |
|
252
|
0
|
0
|
|
|
|
|
if ($len < 10) { # 2 leading bytes + 7 bytes of header + 1 byte of tail(0x03) |
|
253
|
0
|
0
|
|
|
|
|
$self->log_t("Discard short segment:\n", unpack("H*", $packet)) if $self->{Debug} > 8; |
|
254
|
0
|
|
|
|
|
|
return (); |
|
255
|
|
|
|
|
|
|
} |
|
256
|
0
|
|
|
|
|
|
my $len1 = unpack('n', substr($packet, 0, 2)); |
|
257
|
0
|
0
|
|
|
|
|
return () if $len1 == 0; # TCP QQ packets must declare length in the beginning |
|
258
|
0
|
0
|
|
|
|
|
if ($len1 <= $len) { |
|
259
|
0
|
0
|
0
|
|
|
|
if (substr($packet, 2, 1) eq STX and substr($packet, $len1-1, 1) eq ETX) { |
|
260
|
0
|
|
|
|
|
|
return(substr($packet, 2, $len1 - 2), get_data($self, substr($packet, $len1))); |
|
261
|
|
|
|
|
|
|
} |
|
262
|
0
|
0
|
|
|
|
|
$self->log_t("$len1 bytes discarded:\n", unpack("H*", substr($packet, 0, $len1))) if $self->{Debug} > 8; |
|
263
|
0
|
0
|
|
|
|
|
return get_data($self, substr($packet, $len1)) if $len > $len1; |
|
264
|
0
|
|
|
|
|
|
return (); |
|
265
|
|
|
|
|
|
|
} |
|
266
|
0
|
0
|
|
|
|
|
$self->log_t("Fragmented packet:\n", unpack("H*", $packet)) if $self->{Debug} > 8; |
|
267
|
0
|
|
|
|
|
|
return (); |
|
268
|
|
|
|
|
|
|
} |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
# sub build_packet has been merged into sub send2svr |
|
271
|
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
sub rand_str { |
|
273
|
0
|
|
|
0
|
0
|
|
my $len = pop; |
|
274
|
0
|
|
|
|
|
|
join('', map(pack("C", rand(0xff)), 1..$len)); |
|
275
|
|
|
|
|
|
|
} |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
sub build_login_request_packet { |
|
278
|
0
|
|
|
0
|
0
|
|
my ($self, $step) = @_; |
|
279
|
0
|
0
|
|
|
|
|
die "Invalid login request step: $step\n" unless $CmdCode{"login_request_$step"}; |
|
280
|
0
|
|
|
|
|
|
my $randkey = rand_str(16); |
|
281
|
|
|
|
|
|
|
# Need to save it for decrypting server responses |
|
282
|
0
|
|
|
|
|
|
$self->{"RandKey$step"} = $randkey; |
|
283
|
0
|
0
|
|
|
|
|
my $data = $step == 1 ? "\0"x15 : "\1\0\5\0\0\0\0"; |
|
284
|
0
|
|
|
|
|
|
my $seq = pack('n', rand(0xff)); |
|
285
|
0
|
|
|
|
|
|
$self->{Seq} = unpack('n', $seq); |
|
286
|
0
|
|
|
|
|
|
my $packet = $PacketHead . $CmdCode{"login_request_$step"} . $seq . $self->{_Id} . |
|
287
|
|
|
|
|
|
|
$randkey . encrypt(undef, $data, $randkey) . ETX; |
|
288
|
0
|
|
|
|
|
|
$self->finalize_packet($packet); |
|
289
|
|
|
|
|
|
|
} |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
sub build_login_packet { |
|
292
|
0
|
|
|
0
|
0
|
|
my ($self, $server_response) = @_; |
|
293
|
|
|
|
|
|
|
|
|
294
|
0
|
|
|
|
|
|
my $randkey = rand_str(16); |
|
295
|
0
|
|
|
|
|
|
$self->{RandKey} = $randkey; |
|
296
|
|
|
|
|
|
|
# No change in seq number |
|
297
|
0
|
|
|
|
|
|
my $data = encrypt(undef, "", $self->{PWKey}) . "\0"x19 . |
|
298
|
|
|
|
|
|
|
#pack('H*', '09f9cce1f7e8502203cd7731deabfcda') . |
|
299
|
|
|
|
|
|
|
pack('H*', '41d118ac147858f1d0814d7d7d7bd91f') . |
|
300
|
|
|
|
|
|
|
#pack('H*', '01') . |
|
301
|
|
|
|
|
|
|
pack('C', 0xc4) . #rand(0xff)) . |
|
302
|
|
|
|
|
|
|
$ConnectMode{$self->{ConnectMode}} . "\0"x25 . |
|
303
|
|
|
|
|
|
|
#pack('H*', '2447087cb1d3404cbda9037f36689e39') . |
|
304
|
|
|
|
|
|
|
pack('H*', 'd7e27d1ab27e6346a70c4c0c3bd53256') . |
|
305
|
|
|
|
|
|
|
#substr($server_response, 8, -1) . |
|
306
|
|
|
|
|
|
|
substr($server_response, 5) . |
|
307
|
|
|
|
|
|
|
#pack('H*', '0140011032a09700104fac17133afc7e8cfd1bd97d2613adc2') . |
|
308
|
|
|
|
|
|
|
pack('H*', '01400175fda7bc00106b12f591b1d70bed46bbc3c23c663038') . |
|
309
|
|
|
|
|
|
|
"\0"x5 . "\x06" . "\0"x19 . |
|
310
|
|
|
|
|
|
|
pack('H*', '0299c281ae0010bb2673dcc29868b74cbc3f08cce01ea1') . |
|
311
|
|
|
|
|
|
|
#(pack('H*', '00')x297); |
|
312
|
|
|
|
|
|
|
"\0"x249; |
|
313
|
0
|
|
|
|
|
|
my $packet = $PacketHead . $CmdCode{'login'} . pack('n', $self->{Seq}) . |
|
314
|
|
|
|
|
|
|
$self->{_Id} . $randkey . encrypt(undef, $data, $randkey) . ETX; |
|
315
|
0
|
|
|
|
|
|
$self->finalize_packet($packet); |
|
316
|
|
|
|
|
|
|
} |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
sub build_logout_packet { |
|
319
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
320
|
0
|
|
|
|
|
|
my $packet = $PacketHead . $CmdCode{'logout'} . ("\xff" x 2) . $self->{_Id} . |
|
321
|
|
|
|
|
|
|
encrypt(undef, $self->{PWKey}, $self->{Key}) . ETX; |
|
322
|
0
|
|
|
|
|
|
$self->finalize_packet($packet); |
|
323
|
|
|
|
|
|
|
} |
|
324
|
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# Methods for logging and output |
|
326
|
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
sub log { |
|
328
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
329
|
0
|
|
|
|
|
|
my $log = $self->{Log}; |
|
330
|
0
|
|
|
|
|
|
my $mesg = "@_"; |
|
331
|
|
|
|
|
|
|
#Encode::from_to($mesg, 'euc-cn', 'utf8'); |
|
332
|
0
|
|
|
|
|
|
print $log $mesg; |
|
333
|
|
|
|
|
|
|
} |
|
334
|
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
sub logf { |
|
336
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
337
|
0
|
|
|
|
|
|
my $log = $self->{Log}; |
|
338
|
0
|
|
|
|
|
|
my $mesg = "@_"; |
|
339
|
|
|
|
|
|
|
#Encode::from_to($mesg, 'euc-cn', 'utf8'); |
|
340
|
0
|
|
|
|
|
|
printf $log $mesg; |
|
341
|
|
|
|
|
|
|
} |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
sub log_t { |
|
344
|
0
|
|
|
0
|
0
|
|
my ($self, @msg) = @_; |
|
345
|
0
|
|
|
|
|
|
my $log = $self->{Log}; |
|
346
|
0
|
|
|
|
|
|
my $mesg = "@msg\n"; |
|
347
|
|
|
|
|
|
|
#Encode::from_to($mesg, 'euc-cn', 'utf8'); |
|
348
|
0
|
|
|
|
|
|
print $log substr(localtime, 4, 16), $mesg; |
|
349
|
|
|
|
|
|
|
} |
|
350
|
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
sub hexdump { |
|
352
|
0
|
|
|
0
|
0
|
|
my $str = pop; |
|
353
|
0
|
0
|
|
|
|
|
return unless defined $str; |
|
354
|
0
|
|
|
|
|
|
my $res = ""; |
|
355
|
0
|
|
|
|
|
|
my $len = length($str); |
|
356
|
0
|
|
|
|
|
|
for (my $i = 0; $i < $len; $i += 16) { |
|
357
|
0
|
|
|
|
|
|
my $s = substr($str, $i, 16); |
|
358
|
0
|
|
|
|
|
|
my $hex = unpack('H*', $s); |
|
359
|
|
|
|
|
|
|
#$s =~ s/[\x00-\x1f\x80-\x8f]/./g; # 0x00-0x1f will screw up terminal |
|
360
|
0
|
|
|
|
|
|
$hex =~ s/(\w\w)/$1 /g; |
|
361
|
0
|
|
|
|
|
|
$res .= $hex . "\n"; # sprintf("%-48s %s\n", $hex, $s); |
|
362
|
|
|
|
|
|
|
} |
|
363
|
0
|
|
|
|
|
|
$str =~ s/[\x00-\x1f]/./g; |
|
364
|
0
|
|
|
|
|
|
return $res . $str . "\n"; |
|
365
|
|
|
|
|
|
|
} |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
sub dump_substr { |
|
368
|
0
|
|
|
0
|
0
|
|
my ($self, $data, $tmpl, $prefix, $begin, $len) = @_; |
|
369
|
0
|
|
|
|
|
|
my ($str, $end); |
|
370
|
0
|
0
|
|
|
|
|
if (defined($len)) { |
|
371
|
0
|
|
|
|
|
|
$str = substr($data, $begin, $len); |
|
372
|
0
|
0
|
|
|
|
|
$end = ($begin+$len < length($data)) ? $begin+$len-1 : length($data)-1; |
|
373
|
|
|
|
|
|
|
} else { |
|
374
|
0
|
|
|
|
|
|
$str = substr($data, $begin); |
|
375
|
0
|
|
|
|
|
|
$end = length($data)-1; |
|
376
|
|
|
|
|
|
|
} |
|
377
|
0
|
|
|
|
|
|
$self->logf("0x%02x-0x%02x %s: ", $begin, $end, $prefix); |
|
378
|
0
|
0
|
|
|
|
|
if ($tmpl =~ /\w/) { |
|
379
|
0
|
0
|
|
|
|
|
if ($tmpl eq 'H*') { |
|
380
|
0
|
|
|
|
|
|
$self->log("\n", $self->hexdump($str)); |
|
381
|
|
|
|
|
|
|
} else { |
|
382
|
0
|
|
|
|
|
|
$self->log(unpack($tmpl, $str), "\n"); |
|
383
|
|
|
|
|
|
|
} |
|
384
|
|
|
|
|
|
|
} else { |
|
385
|
0
|
|
|
|
|
|
$self->log("$str\n"); |
|
386
|
|
|
|
|
|
|
} |
|
387
|
|
|
|
|
|
|
} |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
sub desect { |
|
390
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
391
|
0
|
0
|
|
|
|
|
return unless $self->{Debug} > 1; |
|
392
|
0
|
|
|
|
|
|
my $data = shift; |
|
393
|
0
|
|
|
|
|
|
foreach my $arg (@_) { |
|
394
|
0
|
|
|
|
|
|
$self->dump_substr($data, @{$arg}); |
|
|
0
|
|
|
|
|
|
|
|
395
|
|
|
|
|
|
|
} |
|
396
|
0
|
|
|
|
|
|
return; |
|
397
|
|
|
|
|
|
|
} |
|
398
|
|
|
|
|
|
|
|
|
399
|
|
|
|
|
|
|
sub show_address { |
|
400
|
0
|
|
|
0
|
0
|
|
my ($self, $data) = @_; |
|
401
|
0
|
|
|
|
|
|
my $ip = join('.', map(ord($_), split('', substr($data, 0, 4)))); |
|
402
|
0
|
0
|
|
|
|
|
return $ip unless length($data) >= 6; |
|
403
|
0
|
|
|
|
|
|
my $port = unpack('n', substr($data, 4, 2)); |
|
404
|
0
|
|
|
|
|
|
return "$ip:$port"; |
|
405
|
|
|
|
|
|
|
} |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
sub remove_saved_id { |
|
408
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
409
|
0
|
|
|
|
|
|
my $file = "$self->{Dir}/$self->{Id}/$id.dat"; |
|
410
|
0
|
0
|
|
|
|
|
if (-e $file) { |
|
411
|
0
|
|
|
|
|
|
unlink($file); |
|
412
|
0
|
0
|
|
|
|
|
return 0 if -e $file; |
|
413
|
0
|
|
|
|
|
|
return 1; |
|
414
|
|
|
|
|
|
|
} else { |
|
415
|
0
|
|
|
|
|
|
return 0; |
|
416
|
|
|
|
|
|
|
} |
|
417
|
|
|
|
|
|
|
} |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
sub get_saved_ids { |
|
420
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
421
|
0
|
|
|
|
|
|
my $dir = "$self->{Dir}/$self->{Id}"; |
|
422
|
0
|
|
|
|
|
|
my @ids = (); |
|
423
|
0
|
0
|
|
|
|
|
if (opendir(DIR, $dir)) { |
|
424
|
0
|
|
|
|
|
|
while(my $f = readdir(DIR)) { |
|
425
|
0
|
0
|
|
|
|
|
next unless $f =~ /^(\d+)\.dat$/; |
|
426
|
0
|
|
|
|
|
|
push @ids, $1; |
|
427
|
|
|
|
|
|
|
} |
|
428
|
0
|
|
|
|
|
|
closedir(DIR); |
|
429
|
|
|
|
|
|
|
} |
|
430
|
0
|
|
|
|
|
|
return @ids; |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
sub get_face { |
|
434
|
0
|
|
|
0
|
0
|
|
my $num = pop; |
|
435
|
0
|
0
|
|
|
|
|
return $num unless $num =~ /^\d+$/; |
|
436
|
0
|
|
|
|
|
|
sprintf('%d-%d', 1 + $num/3, 1 + $num % 3); |
|
437
|
|
|
|
|
|
|
} |
|
438
|
|
|
|
|
|
|
|
|
439
|
|
|
|
|
|
|
sub toggle_autoreply { |
|
440
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
441
|
0
|
0
|
|
|
|
|
if ($self->{Away}) { |
|
442
|
0
|
|
|
|
|
|
$self->{Away} = 0; |
|
443
|
0
|
|
|
|
|
|
return "off"; |
|
444
|
|
|
|
|
|
|
} else { |
|
445
|
0
|
|
|
|
|
|
$self->{Away} = 1; |
|
446
|
0
|
|
|
|
|
|
return "on"; |
|
447
|
|
|
|
|
|
|
} |
|
448
|
|
|
|
|
|
|
} |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
# Nickname can be updated by get_friends_list or get_user_info |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
sub get_nickname { |
|
453
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
454
|
0
|
0
|
|
|
|
|
if (defined $self->{Info}->{$id}) { |
|
455
|
0
|
0
|
|
|
|
|
if (defined $self->{Info}->{$id}->{Nickname}) { |
|
456
|
0
|
|
|
|
|
|
return $self->{Info}->{$id}->{Nickname}; |
|
457
|
|
|
|
|
|
|
} |
|
458
|
|
|
|
|
|
|
} else { |
|
459
|
0
|
|
|
|
|
|
$self->{Info}->{$id} = {}; |
|
460
|
|
|
|
|
|
|
} |
|
461
|
0
|
|
|
|
|
|
my $infofile = "$self->{Dir}/$self->{Id}/$id.dat"; |
|
462
|
0
|
|
|
|
|
|
my $nick = ""; |
|
463
|
0
|
0
|
|
|
|
|
if (open(INFO, $infofile)) { |
|
464
|
0
|
|
|
|
|
|
while(my $line = ) { |
|
465
|
0
|
0
|
|
|
|
|
if ($line =~ /^Nickname +=> *'(.*)'/) { |
|
466
|
0
|
|
|
|
|
|
$nick = $1; |
|
467
|
0
|
|
|
|
|
|
last; |
|
468
|
|
|
|
|
|
|
} |
|
469
|
|
|
|
|
|
|
} |
|
470
|
0
|
|
|
|
|
|
close(INFO); |
|
471
|
|
|
|
|
|
|
} |
|
472
|
0
|
|
|
|
|
|
$self->{Info}->{$id}->{Nickname} = $nick; |
|
473
|
0
|
|
|
|
|
|
return $nick; |
|
474
|
|
|
|
|
|
|
} |
|
475
|
|
|
|
|
|
|
|
|
476
|
|
|
|
|
|
|
sub get_servers { |
|
477
|
0
|
|
|
0
|
0
|
|
my @servers; |
|
478
|
0
|
0
|
0
|
|
|
|
if (exists $ENV{OICQ_SVR} and $ENV{OICQ_SVR} =~ /\w+/) { |
|
479
|
0
|
|
|
|
|
|
my $svr = $ENV{OICQ_SVR}; |
|
480
|
0
|
|
|
|
|
|
$svr =~ s/^\W+//; |
|
481
|
0
|
|
|
|
|
|
$svr =~ s/\W+$//; |
|
482
|
0
|
|
|
|
|
|
@servers = split(/[^\w\.]+/, $svr); |
|
483
|
0
|
0
|
|
|
|
|
return @servers if @servers; |
|
484
|
|
|
|
|
|
|
} |
|
485
|
|
|
|
|
|
|
|
|
486
|
0
|
|
|
|
|
|
my $type = pop; |
|
487
|
0
|
0
|
|
|
|
|
if ($type =~ /udp/i) { |
|
488
|
0
|
|
|
|
|
|
map {'sz'. $_ . '.' . $SERVER_DOMAIN} (2 .. 9, ''); |
|
|
0
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
} else { |
|
490
|
0
|
|
|
|
|
|
map {'tcpconn' . $_ . '.' . $SERVER_DOMAIN} (6, 5, 4, 3, 2, ''); |
|
|
0
|
|
|
|
|
|
|
|
491
|
|
|
|
|
|
|
} |
|
492
|
|
|
|
|
|
|
} |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
sub tcp_connect { |
|
495
|
0
|
|
|
0
|
0
|
|
my ($self, $server, $proxy) = @_; |
|
496
|
0
|
|
|
|
|
|
my ($svr_ip, $svr_port); |
|
497
|
0
|
0
|
|
|
|
|
if ($server =~ /^(\S+):(\d+)$/) { |
|
498
|
0
|
|
|
|
|
|
($svr_ip, $svr_port) = ($1, $2); |
|
499
|
|
|
|
|
|
|
} else { |
|
500
|
0
|
|
|
|
|
|
$svr_ip = $server; |
|
501
|
0
|
|
|
|
|
|
$svr_port = 443; |
|
502
|
|
|
|
|
|
|
} |
|
503
|
0
|
|
|
|
|
|
my $socket; |
|
504
|
0
|
0
|
|
|
|
|
$proxy = $ENV{OICQ_PROXY} unless defined $proxy; |
|
505
|
0
|
0
|
|
|
|
|
if ($proxy) { |
|
506
|
0
|
|
|
|
|
|
my ($proxy_ip, $proxy_port); |
|
507
|
0
|
0
|
|
|
|
|
if ($proxy =~ /:/) { |
|
508
|
0
|
|
|
|
|
|
($proxy_ip, $proxy_port) = split(/:/, $proxy); |
|
509
|
|
|
|
|
|
|
} else { |
|
510
|
0
|
|
|
|
|
|
$proxy_ip = $proxy; |
|
511
|
0
|
|
|
|
|
|
$proxy_port = 80; |
|
512
|
|
|
|
|
|
|
} |
|
513
|
0
|
|
|
|
|
|
$socket = IO::Socket::INET->new( |
|
514
|
|
|
|
|
|
|
Proto => 'tcp', PeerAddr => $proxy_ip, PeerPort => $proxy_port |
|
515
|
|
|
|
|
|
|
); |
|
516
|
0
|
0
|
|
|
|
|
unless(defined $socket) { |
|
517
|
0
|
|
|
|
|
|
$self->mesg("socket error: $@"); |
|
518
|
0
|
|
|
|
|
|
return; |
|
519
|
|
|
|
|
|
|
} |
|
520
|
0
|
|
|
|
|
|
$self->{Socket} = $socket; |
|
521
|
0
|
|
|
|
|
|
$socket->send(sprintf $ProxyConnect, "$svr_ip:$svr_port"); |
|
522
|
0
|
|
|
|
|
|
my $resp = $self->timed_recv(0x4000, 10); |
|
523
|
0
|
0
|
0
|
|
|
|
if (defined $resp && $resp =~ m|HTTP/.+ 200 Connection established|) { |
|
524
|
0
|
|
|
|
|
|
$self->mesg("via proxy $proxy_ip:$proxy_port "); |
|
525
|
0
|
|
|
|
|
|
$self->{Proxy} = "$proxy_ip:$proxy_port"; |
|
526
|
0
|
|
|
|
|
|
$self->{SvrIP} = $svr_ip; |
|
527
|
0
|
|
|
|
|
|
$self->{SvrPort} = $svr_port; |
|
528
|
0
|
|
|
|
|
|
$self->{Socket} = $socket; |
|
529
|
0
|
|
|
|
|
|
$self->{UDP} = 0; |
|
530
|
0
|
|
|
|
|
|
return $socket; |
|
531
|
|
|
|
|
|
|
} |
|
532
|
0
|
0
|
|
|
|
|
$resp = "" unless defined $resp; |
|
533
|
0
|
|
|
|
|
|
$self->mesg("failed to connect to proxy $proxy_ip:$proxy_port\n$resp\n"); |
|
534
|
0
|
|
|
|
|
|
return; |
|
535
|
|
|
|
|
|
|
} else { |
|
536
|
0
|
|
|
|
|
|
$socket = IO::Socket::INET->new( |
|
537
|
|
|
|
|
|
|
Proto => 'tcp', PeerAddr => $svr_ip, PeerPort => $svr_port |
|
538
|
|
|
|
|
|
|
); |
|
539
|
0
|
0
|
|
|
|
|
unless(defined $socket) { |
|
540
|
0
|
|
|
|
|
|
$self->mesg("socket error: $@"); |
|
541
|
0
|
|
|
|
|
|
return; |
|
542
|
|
|
|
|
|
|
} |
|
543
|
0
|
|
|
|
|
|
$self->{SvrIP} = $svr_ip; |
|
544
|
0
|
|
|
|
|
|
$self->{SvrPort} = $svr_port; |
|
545
|
0
|
|
|
|
|
|
$self->{Socket} = $socket; |
|
546
|
0
|
|
|
|
|
|
$self->{UDP} = 0; |
|
547
|
0
|
|
|
|
|
|
return $socket; |
|
548
|
|
|
|
|
|
|
} |
|
549
|
|
|
|
|
|
|
} |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
sub timed_recv { |
|
552
|
0
|
|
|
0
|
0
|
|
my ($self, $length, $timeout) = @_; |
|
553
|
0
|
|
|
|
|
|
my $socket = $self->{Socket}; |
|
554
|
0
|
|
|
|
|
|
my $timeout_msg = "tImEoUt\n"; |
|
555
|
0
|
|
|
|
|
|
my $res; |
|
556
|
0
|
|
|
0
|
|
|
local $SIG{ALRM} = sub { die $timeout_msg }; |
|
|
0
|
|
|
|
|
|
|
|
557
|
0
|
|
|
|
|
|
alarm($timeout); |
|
558
|
0
|
|
|
|
|
|
eval { $socket->recv($res, $length, 0); alarm(0) }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
559
|
0
|
0
|
|
|
|
|
if ($@ eq $timeout_msg) { |
|
560
|
0
|
|
|
|
|
|
return; |
|
561
|
|
|
|
|
|
|
} |
|
562
|
0
|
|
|
|
|
|
return $res; |
|
563
|
|
|
|
|
|
|
} |
|
564
|
|
|
|
|
|
|
|
|
565
|
|
|
|
|
|
|
sub udp_connect { |
|
566
|
0
|
|
|
0
|
0
|
|
my ($self, $server) = @_; |
|
567
|
0
|
0
|
|
|
|
|
croak "Server IP not provided\n" unless defined($server); |
|
568
|
0
|
|
|
|
|
|
my $port = 8000; |
|
569
|
|
|
|
|
|
|
|
|
570
|
0
|
|
|
|
|
|
my $socket = IO::Socket::INET->new( |
|
571
|
|
|
|
|
|
|
Proto => 'udp', PeerAddr => $server, PeerPort => $port |
|
572
|
|
|
|
|
|
|
); |
|
573
|
0
|
0
|
|
|
|
|
unless(defined $socket) { |
|
574
|
0
|
|
|
|
|
|
$self->mesg("socket error: $@"); |
|
575
|
0
|
|
|
|
|
|
return; |
|
576
|
|
|
|
|
|
|
} |
|
577
|
0
|
|
|
|
|
|
$self->{SvrIP} = $server; |
|
578
|
0
|
|
|
|
|
|
$self->{SvrPort} = $port; |
|
579
|
0
|
|
|
|
|
|
$self->{Socket} = $socket; |
|
580
|
0
|
|
|
|
|
|
$self->{UDP} = 1; |
|
581
|
0
|
|
|
|
|
|
return $socket; |
|
582
|
|
|
|
|
|
|
} |
|
583
|
|
|
|
|
|
|
|
|
584
|
|
|
|
|
|
|
sub connect { |
|
585
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
586
|
0
|
|
|
|
|
|
my $proto = shift; |
|
587
|
0
|
0
|
|
|
|
|
($proto eq 'udp') ? $self->udp_connect(@_) : $self->tcp_connect(@_); |
|
588
|
|
|
|
|
|
|
} |
|
589
|
|
|
|
|
|
|
|
|
590
|
|
|
|
|
|
|
sub login { |
|
591
|
0
|
|
|
0
|
0
|
|
my ($self, $id, $pw, $mode, $proto, $proxy) = @_; |
|
592
|
0
|
|
|
|
|
|
$self->set_user($id, $pw); |
|
593
|
0
|
|
|
|
|
|
$self->{Key} = ""; |
|
594
|
0
|
|
|
|
|
|
$| = 1; |
|
595
|
|
|
|
|
|
|
|
|
596
|
0
|
0
|
0
|
|
|
|
if (defined $mode && exists $ConnectMode{$mode}) { |
|
597
|
0
|
|
|
|
|
|
$self->log_t("login as $id in $mode mode"); |
|
598
|
0
|
|
|
|
|
|
$self->{ConnectMode} = $mode; |
|
599
|
|
|
|
|
|
|
} else { |
|
600
|
0
|
|
|
|
|
|
$self->log_t("login as $id, default to invisible mode"); |
|
601
|
0
|
|
|
|
|
|
$self->{ConnectMode} = 'Invisible'; |
|
602
|
|
|
|
|
|
|
} |
|
603
|
|
|
|
|
|
|
# Default to tcp connection |
|
604
|
0
|
0
|
0
|
|
|
|
$proto = 'tcp' unless defined($proto) && $proto eq 'udp'; |
|
605
|
0
|
|
|
|
|
|
my @servers = $self->get_servers($proto); |
|
606
|
0
|
|
|
|
|
|
my $login_packet; |
|
607
|
0
|
|
|
|
|
|
SVR: foreach my $svr (@servers) { |
|
608
|
0
|
|
|
|
|
|
$self->mesg("Connecting to $proto server $svr..."); |
|
609
|
0
|
|
|
|
|
|
my $socket = $self->connect($proto, $svr, $proxy); |
|
610
|
0
|
0
|
|
|
|
|
next SVR unless defined $socket; |
|
611
|
0
|
0
|
|
|
|
|
$self->mesg("socket created...") if $self->{Debug}; |
|
612
|
|
|
|
|
|
|
|
|
613
|
0
|
0
|
|
|
|
|
unless ($login_packet) { |
|
614
|
0
|
|
|
|
|
|
my $token = $self->get_login_token($svr, $proto, $proxy); |
|
615
|
0
|
0
|
|
|
|
|
next SVR unless $token; |
|
616
|
0
|
|
|
|
|
|
$login_packet = $self->build_login_packet($token); |
|
617
|
|
|
|
|
|
|
} |
|
618
|
0
|
|
|
|
|
|
my $plain = $self->decrypt_login_response($login_packet); |
|
619
|
0
|
0
|
|
|
|
|
unless(defined $plain) { |
|
620
|
0
|
|
|
|
|
|
$login_packet = undef; |
|
621
|
0
|
|
|
|
|
|
next SVR; |
|
622
|
|
|
|
|
|
|
} |
|
623
|
0
|
0
|
|
|
|
|
$self->mesg("decrypted login resp: ", unpack("H*", $plain), "\n") if $self->{Debug}; |
|
624
|
0
|
|
|
|
|
|
my $login = ord($plain); |
|
625
|
0
|
0
|
0
|
|
|
|
if ($login == 0) { # login successfull |
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
626
|
0
|
|
|
|
|
|
$self->{Key} = substr($plain, 1, 0x10); |
|
627
|
0
|
|
|
|
|
|
$self->{Addr} = $self->show_address(substr($plain, 0x15, 6)); |
|
628
|
0
|
|
|
|
|
|
$self->{LoginTime} = unpack('N', substr($plain, 0x21, 4)); |
|
629
|
0
|
|
|
|
|
|
$self->{Addr2} = $self->show_address(substr($plain, 0x7b, 4)); |
|
630
|
0
|
|
|
|
|
|
$self->{LoginTime2} = unpack('N', substr($plain, 0x7f, 4)); |
|
631
|
0
|
|
|
|
|
|
$self->mesg("ok.\n"); |
|
632
|
0
|
|
|
|
|
|
last SVR; |
|
633
|
|
|
|
|
|
|
} elsif ($login == 1) { # redirect to another server |
|
634
|
0
|
|
|
|
|
|
$svr = $self->show_address(substr($plain, 5, 6)); |
|
635
|
0
|
|
|
|
|
|
($self->{SvrIP}, $self->{SvrPort}) = split(/:/, $svr); |
|
636
|
0
|
|
|
|
|
|
$self->{Socket} = undef; |
|
637
|
0
|
|
|
|
|
|
$self->log_t("redirected to server $svr"); |
|
638
|
0
|
|
|
|
|
|
$self->mesg(" redirected.\n"); |
|
639
|
0
|
|
|
|
|
|
redo SVR; |
|
640
|
|
|
|
|
|
|
} elsif ($login == 9 or $login == 5) { # wrong password |
|
641
|
0
|
|
|
|
|
|
$self->mesg("$plain\nError code $login\n"); |
|
642
|
0
|
|
|
|
|
|
last SVR; |
|
643
|
|
|
|
|
|
|
} elsif ($login == 10) { # redirect to another server |
|
644
|
0
|
|
|
|
|
|
$svr = $self->show_address(substr($plain, -4)); |
|
645
|
0
|
|
|
|
|
|
$self->mesg("redirected to server $svr (code $login).\n"); |
|
646
|
0
|
|
|
|
|
|
$self->{SvrIP} = $svr; |
|
647
|
0
|
|
|
|
|
|
$self->{Socket} = undef; |
|
648
|
0
|
|
|
|
|
|
$socket = undef; |
|
649
|
0
|
|
|
|
|
|
redo SVR; |
|
650
|
|
|
|
|
|
|
} else { |
|
651
|
0
|
|
|
|
|
|
my $h = unpack("H*", $plain); |
|
652
|
0
|
|
|
|
|
|
$self->mesg("failed with error code $login\n$h\n"); |
|
653
|
0
|
|
|
|
|
|
last SVR; |
|
654
|
|
|
|
|
|
|
} |
|
655
|
|
|
|
|
|
|
} |
|
656
|
|
|
|
|
|
|
|
|
657
|
0
|
0
|
|
|
|
|
return 0 unless $self->{Key}; |
|
658
|
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
# Make sure we logout when control-C is pressed |
|
660
|
0
|
|
|
0
|
|
|
$SIG{INT} = sub { $self->logout; exit 1 }; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
661
|
|
|
|
|
|
|
# Prepare LogoutPacket for logout |
|
662
|
0
|
|
|
|
|
|
$self->{LogoutPacket} = $self->build_logout_packet; |
|
663
|
0
|
|
|
|
|
|
$self->{LastKeepaliveTime} = time; |
|
664
|
|
|
|
|
|
|
|
|
665
|
0
|
|
|
|
|
|
return 1; |
|
666
|
|
|
|
|
|
|
} |
|
667
|
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
sub get_login_token { |
|
669
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
670
|
0
|
|
|
|
|
|
my $socket = $self->{Socket}; |
|
671
|
0
|
0
|
|
|
|
|
return unless defined $socket; |
|
672
|
0
|
0
|
|
|
|
|
$self->mesg("socket created...") if $self->{Debug}; |
|
673
|
0
|
|
|
|
|
|
my ($login_req, $resp); |
|
674
|
0
|
|
|
|
|
|
foreach my $step (2) { |
|
675
|
0
|
|
|
|
|
|
$login_req = $self->build_login_request_packet($step); |
|
676
|
0
|
|
|
|
|
|
$socket->send($login_req); |
|
677
|
0
|
0
|
|
|
|
|
$self->mesg("waiting for token $step...") if $self->{Debug}; |
|
678
|
0
|
|
|
|
|
|
$resp = $self->timed_recv(1024, 5); |
|
679
|
0
|
0
|
|
|
|
|
if (defined $resp) { |
|
680
|
0
|
0
|
|
|
|
|
$self->mesg("received...") if $self->{Debug}; |
|
681
|
|
|
|
|
|
|
} else { |
|
682
|
0
|
|
|
|
|
|
$self->mesg("timed out.\n"); |
|
683
|
0
|
|
|
|
|
|
return; |
|
684
|
|
|
|
|
|
|
} |
|
685
|
|
|
|
|
|
|
} |
|
686
|
|
|
|
|
|
|
#foreach (1 .. 8) { |
|
687
|
|
|
|
|
|
|
# $socket->send($login_req); |
|
688
|
|
|
|
|
|
|
#} |
|
689
|
0
|
|
|
|
|
|
my $token; |
|
690
|
0
|
|
|
|
|
|
foreach my $r ($self->get_data($resp)) { |
|
691
|
0
|
0
|
|
|
|
|
next unless substr($r, 3, 2) eq $CmdCode{login_request_2}; |
|
692
|
0
|
|
|
|
|
|
eval { $token = decrypt(undef, substr($r, 7, -1), $self->{RandKey2}) }; |
|
|
0
|
|
|
|
|
|
|
|
693
|
0
|
0
|
|
|
|
|
$self->mesg("token:", unpack("H*", $token)) if $self->{Debug}; |
|
694
|
0
|
0
|
|
|
|
|
return($token) if $token; |
|
695
|
|
|
|
|
|
|
} |
|
696
|
|
|
|
|
|
|
|
|
697
|
0
|
|
|
|
|
|
$self->mesg("unexpected server response to login request:\n", |
|
698
|
|
|
|
|
|
|
unpack('H*', $resp), "\n$resp\n"); |
|
699
|
0
|
|
|
|
|
|
return; |
|
700
|
|
|
|
|
|
|
} |
|
701
|
|
|
|
|
|
|
|
|
702
|
|
|
|
|
|
|
sub decrypt_login_response { |
|
703
|
0
|
|
|
0
|
0
|
|
my ($self, $login_packet) = @_; |
|
704
|
0
|
|
|
|
|
|
$self->{Socket}->send($login_packet); |
|
705
|
0
|
|
|
|
|
|
$self->mesg("login packet sent ..."); |
|
706
|
0
|
|
|
|
|
|
my $data; |
|
707
|
0
|
|
|
|
|
|
RECV: while (1) { |
|
708
|
0
|
|
|
|
|
|
my $resp = $self->timed_recv(4096, 5); |
|
709
|
0
|
0
|
|
|
|
|
unless($resp) { |
|
710
|
0
|
|
|
|
|
|
$self->mesg(" no response.\n"); |
|
711
|
0
|
|
|
|
|
|
return; |
|
712
|
|
|
|
|
|
|
} |
|
713
|
0
|
|
|
|
|
|
foreach my $d ($self->get_data($resp)) { |
|
714
|
0
|
0
|
|
|
|
|
$self->mesg("\nServer response:", unpack("H*", $d), "\n") if $self->{Debug}; |
|
715
|
0
|
0
|
|
|
|
|
if (substr($d, 3, 2) eq "\x00\x22") { |
|
716
|
0
|
|
|
|
|
|
$data = $d; |
|
717
|
0
|
|
|
|
|
|
last RECV; |
|
718
|
|
|
|
|
|
|
} |
|
719
|
|
|
|
|
|
|
} |
|
720
|
|
|
|
|
|
|
} |
|
721
|
0
|
|
|
|
|
|
$self->{LastSvrAck} = time; |
|
722
|
|
|
|
|
|
|
#my ($data) = $self->get_data($resp); |
|
723
|
|
|
|
|
|
|
#return unless defined $data; |
|
724
|
0
|
|
|
|
|
|
my $crypt = substr($data, 7, -1); |
|
725
|
0
|
|
|
|
|
|
my $plain; |
|
726
|
0
|
0
|
|
|
|
|
$self->mesg("received ", length($crypt), " bytes...") if $self->{Debug}; |
|
727
|
0
|
0
|
|
|
|
|
my @keys = length($crypt) == 32 ? qw(RandKey PWKey) : qw(PWKey RandKey); |
|
728
|
0
|
|
|
|
|
|
foreach my $key (@keys) { |
|
729
|
0
|
|
|
|
|
|
eval { $plain = decrypt(undef, $crypt, $self->{$key}) }; |
|
|
0
|
|
|
|
|
|
|
|
730
|
0
|
0
|
|
|
|
|
if (defined $plain) { |
|
731
|
0
|
0
|
|
|
|
|
$self->mesg("decrypted with $key\n") if $self->{Debug}; |
|
732
|
0
|
|
|
|
|
|
return $plain; |
|
733
|
|
|
|
|
|
|
} |
|
734
|
0
|
0
|
0
|
|
|
|
$self->mesg("Failed to decrypt login response: $@") if $@ && $self->{Debug}; |
|
735
|
|
|
|
|
|
|
} |
|
736
|
0
|
|
|
|
|
|
return undef; |
|
737
|
|
|
|
|
|
|
} |
|
738
|
|
|
|
|
|
|
|
|
739
|
|
|
|
|
|
|
sub mesg { |
|
740
|
0
|
|
|
0
|
0
|
|
my ($self, @mesg) = @_; |
|
741
|
0
|
|
|
|
|
|
my $mesg = "@mesg"; |
|
742
|
0
|
0
|
0
|
|
|
|
if (exists($ENV{LANG}) and $ENV{LANG} =~ /UTF-8/) { |
|
743
|
0
|
|
|
|
|
|
Encode::from_to($mesg, 'euc-cn', 'utf8'); |
|
744
|
|
|
|
|
|
|
} |
|
745
|
0
|
|
|
|
|
|
print $mesg; |
|
746
|
|
|
|
|
|
|
} |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
# send2svr may take command Seq num as an optional argument |
|
749
|
|
|
|
|
|
|
# it returns a Net::OICQ::ClientEvent object if the packet is sent |
|
750
|
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
sub send2svr { |
|
752
|
0
|
|
|
0
|
0
|
|
my ($self, $cmd, $data, $seq) = @_; |
|
753
|
0
|
0
|
|
|
|
|
croak "send2svr error: bad command: $cmd" unless exists $CmdCode{$cmd}; |
|
754
|
0
|
0
|
|
|
|
|
unless(defined $seq) { |
|
755
|
0
|
|
|
|
|
|
$seq = pack('n', ++$self->{Seq}); |
|
756
|
|
|
|
|
|
|
} |
|
757
|
0
|
|
|
|
|
|
my $header = $PacketHead . $CmdCode{$cmd} . $seq . $self->{_Id}; |
|
758
|
0
|
|
|
|
|
|
my $crypt = encrypt(undef, $data, $self->{Key}); |
|
759
|
0
|
|
|
|
|
|
my $packet = $self->finalize_packet("$header$crypt" . ETX); |
|
760
|
0
|
0
|
|
|
|
|
if ($self->{Socket}->send($packet)) { |
|
761
|
0
|
|
|
|
|
|
return(new Net::OICQ::ClientEvent($header, $data, $self)); |
|
762
|
|
|
|
|
|
|
} |
|
763
|
0
|
|
|
|
|
|
return undef; |
|
764
|
|
|
|
|
|
|
} |
|
765
|
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
# get_friends_list provided by Chen Peng |
|
767
|
|
|
|
|
|
|
|
|
768
|
|
|
|
|
|
|
sub get_friends_list { |
|
769
|
0
|
|
|
0
|
0
|
|
my ($self, $flag) = @_; |
|
770
|
0
|
0
|
|
|
|
|
defined $flag or $flag = pack('H4', '0000'); |
|
771
|
0
|
|
|
|
|
|
$self->send2svr('get_friends_list', $flag); |
|
772
|
|
|
|
|
|
|
} |
|
773
|
|
|
|
|
|
|
|
|
774
|
|
|
|
|
|
|
sub get_online_friends { |
|
775
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
776
|
0
|
|
|
|
|
|
$self->send2svr('get_online_friends', pack('H*', '0200000000')); |
|
777
|
|
|
|
|
|
|
} |
|
778
|
|
|
|
|
|
|
|
|
779
|
|
|
|
|
|
|
sub set_mode { |
|
780
|
0
|
|
|
0
|
0
|
|
my ($self, $mode_code) = @_; |
|
781
|
0
|
|
|
|
|
|
$self->send2svr('set_mode', $mode_code); |
|
782
|
|
|
|
|
|
|
} |
|
783
|
|
|
|
|
|
|
|
|
784
|
|
|
|
|
|
|
sub get_user_info { |
|
785
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
786
|
0
|
|
|
|
|
|
$self->send2svr('get_user_info', $id); |
|
787
|
|
|
|
|
|
|
} |
|
788
|
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
sub update_info { |
|
790
|
0
|
|
|
0
|
0
|
|
my ($self, $hashref) = @_; |
|
791
|
0
|
|
|
|
|
|
my $info = $self->{MyInfo}; |
|
792
|
0
|
0
|
0
|
|
|
|
return unless defined $hashref and defined $info; |
|
793
|
0
|
|
|
|
|
|
my %new_info; |
|
794
|
|
|
|
|
|
|
# Use all upper-case letters for keys |
|
795
|
0
|
|
|
|
|
|
foreach my $k (keys %$hashref) { |
|
796
|
0
|
|
|
|
|
|
$new_info{uc($k)} = $hashref->{$k}; |
|
797
|
|
|
|
|
|
|
} |
|
798
|
0
|
|
|
|
|
|
my @update; |
|
799
|
0
|
|
|
|
|
|
for (my $i = 1; $i < $#InfoHeader; $i++) { |
|
800
|
0
|
|
|
|
|
|
my $attr = uc($InfoHeader[$i]); |
|
801
|
0
|
0
|
|
|
|
|
push(@update, defined($new_info{$attr}) ? $new_info{$attr} : $info->[$i]); |
|
802
|
|
|
|
|
|
|
} |
|
803
|
0
|
|
|
|
|
|
$self->send2svr('update_info', join($RS, "", "", @update)); |
|
804
|
|
|
|
|
|
|
} |
|
805
|
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
sub set_passwd { |
|
807
|
0
|
|
|
0
|
0
|
|
my ($self, $newpw) = @_; |
|
808
|
0
|
0
|
|
|
|
|
return unless defined $self->{MyInfo}; |
|
809
|
0
|
|
|
|
|
|
my @info = @{$self->{MyInfo}}; |
|
|
0
|
|
|
|
|
|
|
|
810
|
0
|
|
|
|
|
|
pop @info; shift @info; |
|
|
0
|
|
|
|
|
|
|
|
811
|
0
|
|
|
|
|
|
$self->send2svr('update_info', join($RS, $self->{Passwd}, $newpw, @info)); |
|
812
|
|
|
|
|
|
|
} |
|
813
|
|
|
|
|
|
|
|
|
814
|
|
|
|
|
|
|
sub accept_contact { |
|
815
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
816
|
0
|
|
|
|
|
|
$self->send2svr('add_contact_2', $id.$RS."0"); |
|
817
|
|
|
|
|
|
|
} |
|
818
|
|
|
|
|
|
|
|
|
819
|
|
|
|
|
|
|
sub reject_contact { |
|
820
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
821
|
0
|
|
|
|
|
|
$self->send2svr('add_contact_2', $id.$RS."1"); |
|
822
|
|
|
|
|
|
|
} |
|
823
|
|
|
|
|
|
|
|
|
824
|
|
|
|
|
|
|
sub add_contact { |
|
825
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
826
|
0
|
|
|
|
|
|
$self->send2svr('add_contact_1', "$id"); |
|
827
|
|
|
|
|
|
|
} |
|
828
|
|
|
|
|
|
|
|
|
829
|
|
|
|
|
|
|
sub add_contact_2 { |
|
830
|
0
|
|
|
0
|
0
|
|
my ($self, $id, $msg) = @_; |
|
831
|
0
|
|
|
|
|
|
$self->send2svr('add_contact_2', "$id$RS"."2$RS$msg"); |
|
832
|
|
|
|
|
|
|
} |
|
833
|
|
|
|
|
|
|
|
|
834
|
|
|
|
|
|
|
sub del_contact { |
|
835
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
836
|
0
|
|
|
|
|
|
$self->send2svr('del_contact', "$id"); |
|
837
|
|
|
|
|
|
|
} |
|
838
|
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
sub forbid_contact { |
|
840
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
841
|
0
|
|
|
|
|
|
$self->send2svr('forbid_contact', "$id"); |
|
842
|
|
|
|
|
|
|
} |
|
843
|
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
sub msg_tail { |
|
845
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
846
|
0
|
|
|
|
|
|
my $font_name = $self->{Font}; |
|
847
|
|
|
|
|
|
|
# Let's have fun with font size and color |
|
848
|
0
|
|
|
|
|
|
my $font_size = $self->{FontSize}; |
|
849
|
0
|
|
|
|
|
|
my $font_color = $self->{FontColor}; |
|
850
|
0
|
0
|
|
|
|
|
if ($font_size =~ /^\d+$/) { |
|
851
|
0
|
|
|
|
|
|
$font_size = chr($font_size); |
|
852
|
|
|
|
|
|
|
} else { |
|
853
|
0
|
|
|
|
|
|
$font_size = chr(8+rand(14)); |
|
854
|
|
|
|
|
|
|
} |
|
855
|
0
|
0
|
|
|
|
|
if ($font_color =~ /^[\da-f]{6}$/) { |
|
856
|
0
|
|
|
|
|
|
$font_color = pack("H*", $font_color); |
|
857
|
|
|
|
|
|
|
} else { |
|
858
|
0
|
|
|
|
|
|
$font_color = chr(rand(0xff)).chr(rand(0xff)).chr(rand(0xff)); |
|
859
|
|
|
|
|
|
|
} |
|
860
|
0
|
|
|
|
|
|
my $msg_tail = " \0$font_size$font_color\0\x86\x02$font_name"; |
|
861
|
|
|
|
|
|
|
# Don't know what would happen if font_name is very looooong. Don't care either. |
|
862
|
0
|
|
|
|
|
|
return $msg_tail . chr(length($msg_tail)); |
|
863
|
|
|
|
|
|
|
} |
|
864
|
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
# send_msg is also used for auto-reply |
|
866
|
|
|
|
|
|
|
# I don't think this is a bug, it is a feature. |
|
867
|
|
|
|
|
|
|
sub send_msg { |
|
868
|
0
|
|
|
0
|
0
|
|
my ($self, $dstid, $msg) = @_; |
|
869
|
1
|
|
|
1
|
|
8227
|
use bytes; |
|
|
1
|
|
|
|
|
3
|
|
|
|
1
|
|
|
|
|
9
|
|
|
870
|
0
|
|
|
|
|
|
my $nickname = $self->get_nickname($dstid); |
|
871
|
0
|
0
|
0
|
|
|
|
if ($dstid =~ /^20/ and $nickname eq "\xc8\xba") { |
|
872
|
|
|
|
|
|
|
# Group message |
|
873
|
0
|
|
|
|
|
|
return $self->send_group_msg($dstid, $msg); |
|
874
|
|
|
|
|
|
|
} |
|
875
|
0
|
0
|
|
|
|
|
$self->log_t("Sent message to $dstid:\n", $msg) if $self->{LogChat}; |
|
876
|
0
|
|
|
|
|
|
my $dstid_ = pack('N', $dstid); |
|
877
|
0
|
|
|
|
|
|
my $head = $self->{_Id} . $dstid_ . $CLIENT_VER . $self->{_Id} . $dstid_ . |
|
878
|
|
|
|
|
|
|
Digest::MD5::md5($dstid_ . $self->{Key}) . "\0\x0b"; |
|
879
|
0
|
|
|
|
|
|
my @trunks = $self->split_gb_msg($msg); |
|
880
|
0
|
|
|
|
|
|
my $last_trunk = pop(@trunks); |
|
881
|
0
|
|
|
|
|
|
my $msg_seq = 0x57 + rand(0xa8); |
|
882
|
0
|
|
|
|
|
|
my $time = pack('N', time); |
|
883
|
0
|
|
|
|
|
|
foreach my $trunk (@trunks) { |
|
884
|
0
|
|
|
|
|
|
my $data = $head . pack('n', ++$msg_seq) . $time . |
|
885
|
|
|
|
|
|
|
"\0\x3f\0\0\0\1\1\0" . chr(rand(0xfd)) . "\0\1" . $trunk; |
|
886
|
0
|
|
|
|
|
|
$self->send2svr('send_msg', $data); |
|
887
|
0
|
|
|
|
|
|
sleep(1); |
|
888
|
|
|
|
|
|
|
} |
|
889
|
0
|
|
|
|
|
|
my $data = $head . pack('n', ++$msg_seq) . $time . |
|
890
|
|
|
|
|
|
|
"\0\x3f\0\0\0\1\1\0" . chr(rand(0xfd)) . "\0\1" . |
|
891
|
|
|
|
|
|
|
$last_trunk . $self->msg_tail; |
|
892
|
0
|
|
|
|
|
|
$self->send2svr('send_msg', $data); |
|
893
|
|
|
|
|
|
|
} |
|
894
|
|
|
|
|
|
|
|
|
895
|
|
|
|
|
|
|
# Server will not send message longer than 601 bytes |
|
896
|
|
|
|
|
|
|
|
|
897
|
|
|
|
|
|
|
sub split_gb_msg { |
|
898
|
0
|
|
|
0
|
0
|
|
my ($self, $msg) = @_; |
|
899
|
0
|
|
|
|
|
|
my $len = length($msg); |
|
900
|
0
|
|
|
|
|
|
my $max_len = 601; |
|
901
|
0
|
0
|
|
|
|
|
return ($msg) if $len <= $max_len; |
|
902
|
0
|
|
|
|
|
|
my $msg0 = substr($msg, 0, $max_len); |
|
903
|
|
|
|
|
|
|
# here is my idea of splitting a long messages while avoiding breaking up |
|
904
|
|
|
|
|
|
|
# any GB character |
|
905
|
|
|
|
|
|
|
# First, count the non GB characters in the first 601 characters |
|
906
|
0
|
|
|
|
|
|
my $non_gb_count = $msg0 =~ tr/\x00-\xa0/\x00-\xa0/; |
|
907
|
0
|
0
|
|
|
|
|
if ($non_gb_count % 2) { |
|
908
|
|
|
|
|
|
|
# if there are an odd number of non GB characters, |
|
909
|
|
|
|
|
|
|
# it's ok to break at position 601 |
|
910
|
0
|
|
|
|
|
|
return ($msg0, $self->split_gb_msg(substr($msg, $max_len))); |
|
911
|
|
|
|
|
|
|
} else { |
|
912
|
0
|
|
|
|
|
|
$max_len--; |
|
913
|
0
|
|
|
|
|
|
return (substr($msg, 0, $max_len), $self->split_gb_msg(substr($msg, $max_len))); |
|
914
|
|
|
|
|
|
|
} |
|
915
|
|
|
|
|
|
|
} |
|
916
|
|
|
|
|
|
|
|
|
917
|
|
|
|
|
|
|
sub ack_msg { |
|
918
|
0
|
|
|
0
|
0
|
|
my ($self, $seq, $plain) = @_; |
|
919
|
0
|
|
|
|
|
|
$plain = substr($plain, 0, 16); |
|
920
|
0
|
|
|
|
|
|
my $event = $self->send2svr('recv_msg', $plain, $seq); |
|
921
|
0
|
0
|
|
|
|
|
if ($self->{UDP}) { |
|
922
|
0
|
|
|
|
|
|
foreach (1..2) { |
|
923
|
0
|
|
|
|
|
|
$self->send2svr('recv_msg', $plain, $seq); |
|
924
|
|
|
|
|
|
|
} |
|
925
|
|
|
|
|
|
|
} |
|
926
|
0
|
|
|
|
|
|
return $event; |
|
927
|
|
|
|
|
|
|
} |
|
928
|
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
sub ack_service_msg { |
|
930
|
0
|
|
|
0
|
0
|
|
my ($self, $code, $srcid, $seq) = @_; |
|
931
|
0
|
|
|
|
|
|
$self->send2svr('ack_service_msg', "$code$FS$srcid$FS$seq"); |
|
932
|
|
|
|
|
|
|
} |
|
933
|
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
sub keepalive { |
|
935
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
|
936
|
0
|
|
|
|
|
|
$self->{LastKeepaliveTime} = time; |
|
937
|
0
|
|
|
|
|
|
$self->send2svr('keep_alive', $self->{Id}); |
|
938
|
|
|
|
|
|
|
} |
|
939
|
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
sub search_user { |
|
941
|
0
|
|
|
0
|
0
|
|
my ($self, $id) = @_; |
|
942
|
0
|
|
|
|
|
|
$self->send2svr('search_users', join($RS, '0', $id, '-','-','0')); |
|
943
|
|
|
|
|
|
|
} |
|
944
|
|
|
|
|
|
|
|
|
945
|
|
|
|
|
|
|
sub list_online_users { |
|
946
|
0
|
|
|
0
|
0
|
|
my ($self, $num) = @_; |
|
947
|
0
|
0
|
|
|
|
|
defined $num or $num = 1; |
|
948
|
0
|
|
|
|
|
|
my $begin = $self->{SearchCount}; |
|
949
|
0
|
|
|
|
|
|
$self->{SearchCount} += $num; |
|
950
|
0
|
|
|
|
|
|
my $end = $self->{SearchCount} -1; |
|
951
|
0
|
|
|
|
|
|
foreach my $p ($begin .. $end) { |
|
952
|
0
|
|
|
|
|
|
$self->send2svr('search_users', "1".$RS."$p"); |
|
953
|
|
|
|
|
|
|
} |
|
954
|
|
|
|
|
|
|
} |
|
955
|
|
|
|
|
|
|
|
|
956
|
|
|
|
|
|
|
sub request_file_key { |
|
957
|
0
|
|
|
0
|
0
|
|
my ($self, $hex_code) = @_; |
|
958
|
0
|
|
|
|
|
|
$self->send2svr('req_file_key', pack("H*", $hex_code)); |
|
959
|
|
|
|
|
|
|
} |
|
960
|
|
|
|
|
|
|
|
|
961
|
|
|
|
|
|
|
sub do_group { |
|
962
|
0
|
|
|
0
|
0
|
|
my ($self, $group_cmd, $group_id, $what) = @_; |
|
963
|
0
|
|
|
|
|
|
my $data = $GrpCmdCode{$group_cmd}; |
|
964
|
0
|
0
|
|
|
|
|
$data .= pack('H2', '01') if $group_cmd eq 'search'; |
|
965
|
0
|
|
|
|
|
|
$data .= pack('N', $group_id) . $what; |
|
966
|
0
|
|
|
|
|
|
$self->send2svr('do_group', $data); |
|
967
|
|
|
|
|
|
|
} |
|
968
|
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
# Group functions are provided by alexe |
|
970
|
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
sub send_group_msg { |
|
972
|
0
|
|
|
0
|
0
|
|
my ($self, $group_id, @msg) = @_; |
|
973
|
0
|
|
|
|
|
|
my $mesg = "@msg"; |
|
974
|
0
|
0
|
|
|
|
|
$self->log_t("Sent message to Group $group_id:\n", $mesg) if $self->{LogChat}; |
|
975
|
0
|
|
|
|
|
|
my $group_int_id = $self->group_int_id($group_id); |
|
976
|
0
|
|
|
|
|
|
my @trunks = $self->split_gb_msg($mesg); |
|
977
|
0
|
|
|
|
|
|
my $last_trunk = pop(@trunks); |
|
978
|
0
|
|
|
|
|
|
foreach my $trunk (@trunks) { |
|
979
|
0
|
|
|
|
|
|
my $data = "\0\1\1\0\x39\xe8\0\0\0\0$trunk"; |
|
980
|
0
|
|
|
|
|
|
$data = pack('n', length($data)) . $data; |
|
981
|
0
|
|
|
|
|
|
$self->do_group('send_msg', $group_int_id, $data); |
|
982
|
0
|
|
|
|
|
|
sleep(1); |
|
983
|
|
|
|
|
|
|
} |
|
984
|
0
|
|
|
|
|
|
my $data = "\0\1\1\0\x39\xe8\0\0\0\0$last_trunk" . $self->msg_tail; |
|
985
|
0
|
|
|
|
|
|
$data = pack('n', length($data)) . $data; |
|
986
|
0
|
|
|
|
|
|
$self->do_group('send_msg', $group_int_id, $data); |
|
987
|
|
|
|
|
|
|
} |
|
988
|
|
|
|
|
|
|
|
|
989
|
|
|
|
|
|
|
sub get_group_info { |
|
990
|
0
|
|
|
0
|
0
|
|
my ($self, $group_id) = @_; |
|
991
|
0
|
|
|
|
|
|
$self->do_group('get_info', $self->group_int_id($group_id), ""); |
|
992
|
|
|
|
|
|
|
} |
|
993
|
|
|
|
|
|
|
|
|
994
|
|
|
|
|
|
|
sub search_group { |
|
995
|
0
|
|
|
0
|
0
|
|
my($self, $group_id) = @_; |
|
996
|
0
|
|
|
|
|
|
$self->do_group('search', $group_id, ""); |
|
997
|
|
|
|
|
|
|
} |
|
998
|
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
sub group_online_members { |
|
1000
|
0
|
|
|
0
|
0
|
|
my ($self, $group_id) = @_; |
|
1001
|
0
|
|
|
|
|
|
$self->do_group('online_members', $self->group_int_id($group_id), ""); |
|
1002
|
|
|
|
|
|
|
} |
|
1003
|
|
|
|
|
|
|
|
|
1004
|
|
|
|
|
|
|
sub group_int_id { |
|
1005
|
0
|
|
|
0
|
0
|
|
my ($self, $group_id) = @_; |
|
1006
|
0
|
0
|
|
|
|
|
$group_id += 202000000 if $group_id < 202000000; |
|
1007
|
0
|
|
|
|
|
|
return $group_id; |
|
1008
|
|
|
|
|
|
|
} |
|
1009
|
|
|
|
|
|
|
|
|
1010
|
|
|
|
|
|
|
sub logout { |
|
1011
|
0
|
|
|
0
|
0
|
|
my $self = shift; |
|
1012
|
0
|
0
|
0
|
|
|
|
defined($self->{LogoutPacket}) && $self->{LogoutPacket} || return; |
|
1013
|
0
|
|
|
|
|
|
my $packet = $self->{LogoutPacket}; |
|
1014
|
0
|
|
|
|
|
|
foreach (1..3) { |
|
1015
|
0
|
|
|
|
|
|
$self->{Socket}->send($packet); |
|
1016
|
|
|
|
|
|
|
} |
|
1017
|
|
|
|
|
|
|
} |
|
1018
|
|
|
|
|
|
|
|
|
1019
|
|
|
|
|
|
|
1; |
|
1020
|
|
|
|
|
|
|
|
|
1021
|
|
|
|
|
|
|
__END__ |