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__ |