File Coverage

blib/lib/Net/OSCAR/Connection/Direct.pm
Criterion Covered Total %
statement 28 295 9.4
branch 0 128 0.0
condition 0 64 0.0
subroutine 10 16 62.5
pod 0 5 0.0
total 38 508 7.4


line stmt bran cond sub pod time code
1             =pod
2              
3             Net::OSCAR::Connection::Direct -- OSCAR direct connections
4              
5             =cut
6              
7             package Net::OSCAR::Connection::Direct;
8             BEGIN {
9 1     1   1860 $Net::OSCAR::Connection::Direct::VERSION = '1.928';
10             }
11              
12             $REVISION = '$Revision$';
13              
14 1     1   6 use strict;
  1         2  
  1         33  
15 1     1   5 use Carp;
  1         2  
  1         79  
16              
17 1     1   5 use vars qw(@ISA $REVISION);
  1         2  
  1         50  
18 1     1   4 use Socket;
  1         3  
  1         753  
19 1     1   11 use Symbol;
  1         3  
  1         64  
20 1     1   6 use Net::OSCAR::Common qw(:all);
  1         2  
  1         433  
21 1     1   7 use Net::OSCAR::Constants;
  1         3  
  1         210  
22 1     1   7 use Net::OSCAR::Utility;
  1         1  
  1         138  
23 1     1   6 use Net::OSCAR::XML;
  1         3  
  1         3133  
24             @ISA = qw(Net::OSCAR::Connection);
25              
26             sub process_one($;$$$) {
27 0     0 0   my($self, $read, $write, $error) = @_;
28 0           my $snac;
29              
30 0 0         if($error) {
31 0           $self->{sockerr} = 1;
32 0           $self->disconnect();
33              
34 0 0 0       if($self->{rv}->{ft_state} eq "connecting" or $self->{rv}->{ft_state} eq "connected") {
35 0           $self->log_print(OSCAR_DBG_INFO, "Couldn't connect to rendezvous peer; revising rendezvous.");
36 0           $self->{session}->rendezvous_revise($self->{rv}->{cookie});
37             }
38              
39 0           return;
40             }
41              
42             #$self->log_printf(OSCAR_DBG_DEBUG,
43             # "Called process_one on direct connection: st=%s, fts=%s, dir=%s, acp=%s, r=$read, w=$write, e=$error",
44             # $self->{state}, $self->{rv}->{ft_state}, $self->{rv}->{direction}, $self->{rv}->{accepted}
45             #);
46 0 0 0       if($read and $self->{rv}->{ft_state} eq "listening") {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0          
    0          
47 0           my $newsock = gensym();
48              
49 0 0         if(accept($newsock, $self->{socket})) {
50 0           $self->log_print(OSCAR_DBG_DEBUG, "Accepted incoming connection.");
51 0           $self->{session}->callback_connection_changed($self, "deleted");
52 0           close($self->{socket});
53 0           $self->{socket} = $newsock;
54 0           $self->set_blocking(0);
55              
56 0 0         if($self->{rv}->{direction} eq "send") {
57 0           $self->{state} = "write";
58             } else {
59 0           $self->{state} = "read";
60             }
61              
62 0           $self->{rv}->{ft_state} = "connected";
63 0           $self->{session}->callback_connection_changed($self, $self->{state});
64              
65 0           return 1;
66             } else {
67 0           $self->log_print(OSCAR_DBG_WARN, "Failed to accept incoming connection: $!");
68 0           return 0;
69             }
70             } elsif($write and $self->{rv}->{ft_state} eq "proxy_connect") {
71 0           $self->log_print(OSCAR_DBG_DEBUG, "Connected to proxy.");
72 0           $self->{connected} = 1;
73 0           my $ret;
74              
75 0 0         if($self->{sent_proxy_init}++) {
76 0           my $packet = protoparse($self->{session}, "direct_connect_proxy_init")->pack(
77             msg_type => 2,
78             screenname => $self->{rv}->{peer},
79             cookie => $self->{rv}->{cookie},
80             capability => OSCAR_CAPS()->{filexfer}->{value}
81             );
82 0           $ret = $self->write(pack("n", length($packet)) . $packet);
83             } else {
84 0           $ret = $self->write();
85             }
86              
87 0 0         return $ret unless $ret;
88              
89 0           delete $self->{sent_proxy_init};
90 0           $self->{rv}->{ft_state} = "proxy_ack";
91 0           $self->{state} = "read";
92 0           $self->{session}->callback_connection_changed($self, "read");
93             } elsif($read and $self->{rv}->{ft_state} eq "proxy_ack") {
94 0           my $ret = $self->get_proxy_header("direct_connect_proxy_reply");
95 0 0         return $ret unless $ret;
96              
97 0 0 0       if($ret->{magic} != 1098 or $ret->{msg_type} != 3) {
98 0           $self->{sockerr} = 1;
99 0           $self->disconnect();
100              
101 0           $self->log_print(OSCAR_DBG_INFO, "Bad response from proxy; revising rendezvous.");
102 0           $self->{session}->rendezvous_revise($self->{rv}->{cookie});
103              
104 0           return undef;
105             } else {
106 0           $self->{rv}->{ft_state} = "proxy_connected";
107 0           $self->{state} = "read";
108 0           $self->{session}->callback_connection_changed($self, "read");
109              
110 0 0         my %protodata = (
111             status => "accept",
112             cookie => $self->{rv}->{cookie},
113             capability => OSCAR_CAPS()->{$self->{rv}->{type}} ? OSCAR_CAPS()->{$self->{rv}->{type}}->{value} : $self->{rv}->{type},
114             client_1_ip => $ret->{ip},
115             port => $ret->{port}
116             );
117 0           $self->{session}->send_message($self->{rv}->{sender}, 2, protoparse($self->{session}, "rendezvous_IM")->pack(%protodata));
118             }
119             } elsif($read and $self->{rv}->{ft_state} eq "proxy_connect") {
120 0           my $ret = $self->get_proxy_header();
121 0 0         return $ret unless $ret;
122              
123 0 0 0       if($ret->{magic} != 1098 or $ret->{msg_type} != 5) {
124 0           $self->{sockerr} = 1;
125 0           $self->disconnect();
126              
127 0           $self->log_print(OSCAR_DBG_INFO, "Bad response from proxy; revising rendezvous.");
128 0           $self->{session}->rendezvous_revise($self->{rv}->{cookie});
129              
130 0           return undef;
131             } else {
132 0           $self->log_print(OSCAR_DBG_DEBUG, "Rendezvous peer connected to proxy.");
133 0           $self->{rv}->{ft_state} = "connected";
134 0 0         if($self->{rv}->{direction} eq "send") {
135 0           $self->{state} = "write";
136             } else {
137 0           $self->{state} = "read";
138             }
139              
140 0           $self->{session}->callback_connection_changed($self, $self->{state});
141             }
142             } elsif($write and $self->{rv}->{ft_state} eq "connecting") {
143 0           $self->log_print(OSCAR_DBG_DEBUG, "Connected.");
144 0           $self->{connected} = 1;
145              
146 0           my %protodata;
147 0           $protodata{status} = "accept";
148 0           $protodata{cookie} = $self->{rv}->{cookie};
149 0 0         $protodata{capability} = OSCAR_CAPS()->{$self->{rv}->{type}} ? OSCAR_CAPS()->{$self->{rv}->{type}}->{value} : $self->{rv}->{type};
150 0           $self->{session}->send_message($self->{rv}->{sender}, 2, protoparse($self->{session}, "rendezvous_IM")->pack(%protodata));
151              
152 0           $self->{rv}->{ft_state} = "connected";
153 0           $self->{rv}->{accepted} = 1;
154 0 0         if($self->{rv}->{direction} eq "receive") {
155 0           $self->{state} = "read";
156 0           $self->{session}->callback_connection_changed($self, $self->{state});
157             }
158             } elsif($write and $self->{rv}->{ft_state} eq "connected") {
159 0 0         if($self->{rv}->{direction} eq "send") {
160 0 0         return 1 unless $self->{rv}->{accepted};
161             }
162              
163 0           $self->log_print(OSCAR_DBG_DEBUG, "Sending OFT header (SYN).");
164 0           my $ret;
165 0 0         if($self->{sent_oft_header}) {
166 0           $self->log_print(OSCAR_DBG_DEBUG, "Flushing buffer");
167 0           $ret = $self->write(); # Flush buffer
168             } else {
169 0           $self->log_print(OSCAR_DBG_DEBUG, "Sending initial header");
170 0           $self->{sent_oft_header} = 1;
171 0 0 0       if($self->{rv}->{direction} eq "send" and !$self->{got_files}) {
172 0           $self->{checksum} = $self->checksum($self->{rv}->{data}->[0]);
173 0           $self->{byte_count} = $self->{rv}->{total_size};
174 0           $self->{bytes_left} = length($self->{rv}->{data}->[0]);
175 0           $self->{filename} = $self->{rv}->{filenames}->[0];
176             }
177 0           $ret = $self->send_oft_header();
178             }
179 0 0         return $ret unless $ret;
180              
181 0 0         if($self->{rv}->{direction} eq "receive") {
182 0 0 0       if($self->{rv}->{file_count} == 1 or ($self->{sent_oft_header} and $self->{sent_oft_header} >= 2)) {
      0        
183 0           $self->{rv}->{ft_state} = "data";
184             } else {
185 0           $self->log_print(OSCAR_DBG_DEBUG, "Sending second header.");
186 0           $self->{sent_oft_header} = 2;
187 0           $ret = $self->send_oft_header();
188 0 0         return $ret unless $ret;
189 0           $self->{rv}->{ft_state} = "data";
190             }
191             }
192              
193 0           delete $self->{sent_oft_header};
194 0           $self->{state} = "read";
195 0           $self->{session}->callback_connection_changed($self, "read");
196             } elsif($read and $self->{rv}->{ft_state} eq "connected") {
197 0           $self->log_print(OSCAR_DBG_DEBUG, "Getting OFT header");
198 0           my $ret = $self->get_oft_header();
199 0 0         return $ret unless $ret;
200              
201 0 0         if($self->{rv}->{direction} eq "send") {
    0          
202 0           $self->{rv}->{ft_state} = "data";
203             } elsif($self->{got_files}) {
204 0           $self->{sent_oft_header} = 2;
205 0           $self->log_print(OSCAR_DBG_DEBUG, "Sending second header.");
206 0           $ret = $self->send_oft_header();
207 0 0         if($ret) {
208 0           delete $self->{sent_oft_header};
209 0           $self->{rv}->{ft_date} = "data";
210 0           $self->{state} = "read";
211 0           $self->{session}->callback_connection_changed($self, "read");
212 0           return;
213             }
214             }
215              
216 0           $self->{state} = "write";
217 0           $self->{session}->callback_connection_changed($self, "write");
218             } elsif($self->{rv}->{ft_state} eq "data") {
219 0           my $ret;
220              
221 0 0 0       if($write and $self->{rv}->{direction} eq "send") {
    0 0        
222 0           $self->log_print(OSCAR_DBG_DEBUG, "Sending data");
223 0 0         if($self->{sent_data}++) {
224 0           $ret = $self->write();
225             } else {
226 0           $ret = $self->write($self->{rv}->{data}->[0]);
227             }
228              
229 0 0         if($ret) {
230 0           $self->log_print(OSCAR_DBG_DEBUG, "Done sending data");
231              
232 0           shift @{$self->{rv}->{data}};
  0            
233 0           shift @{$self->{rv}->{filenames}};
  0            
234 0           $self->{sent_data} = 0;
235              
236 0           $self->{rv}->{ft_state} = "fin";
237 0           $self->{state} = "read";
238 0           $self->{session}->callback_connection_changed($self, "read");
239             } else {
240 0           return $ret;
241             }
242             } elsif($read and $self->{rv}->{direction} eq "receive") {
243 0           $self->log_printf(OSCAR_DBG_DEBUG, "Receiving %d bytes of data", $self->{read_size});
244 0 0         if($self->{got_data}++) {
245 0           $self->log_print(OSCAR_DBG_DEBUG, "Getting more data");
246 0           $ret = $self->read();
247             } else {
248 0           $self->log_print(OSCAR_DBG_DEBUG, "Doing initial read");
249 0           $ret = $self->read($self->{read_size});
250             }
251              
252 0 0         if($ret) {
253 0           $self->log_printf(OSCAR_DBG_DEBUG, "Got complete file, %d bytes.", length($ret));
254              
255 0   0       $self->{rv}->{data} ||= [];
256 0           push @{$self->{rv}->{data}}, $ret;
  0            
257 0           shift @{$self->{rv}->{filenames}};
  0            
258 0           $self->{bytes_recv} = length($ret);
259 0           $self->{got_data} = 0;
260 0           $self->{received_checksum} = $self->checksum($ret);
261              
262 0 0         if($self->{received_checksum} != $self->{checksum}) {
263 0           $self->log_printf(OSCAR_DBG_WARN, "Checksum mismatch: %lu/%lu", $self->{checksum}, $self->{received_checksum});
264 0           $self->log_print(OSCAR_DBG_WARN, "Data: ", hexdump($ret));
265 0           $self->{sockerr} = 1;
266 0           $self->disconnect();
267 0           return undef;
268             } else {
269 0           $self->log_print(OSCAR_DBG_WARN, "Data: ", hexdump($ret));
270             }
271              
272 0           $self->{rv}->{ft_state} = "fin";
273 0           $self->{state} = "write";
274 0           $self->{session}->callback_connection_changed($self, "write");
275             } else {
276 0           return $ret;
277             }
278             }
279             } elsif($self->{rv}->{ft_state} eq "fin") {
280 0 0 0       if($read and $self->{rv}->{direction} eq "send") {
    0 0        
281 0           $self->log_print(OSCAR_DBG_DEBUG, "Getting OFT fin header");
282 0           my $ret = $self->get_oft_header();
283 0 0         return $ret unless $ret;
284              
285 0 0         if(@{$self->{rv}->{data}}) {
  0            
286 0           $self->{rv}->{ft_state} = "connected";
287 0           $self->{state} = "write";
288 0           $self->{session}->callback_connection_changed($self, "write");
289             } else {
290 0           $self->disconnect();
291             }
292              
293 0           return 1;
294             } elsif($write and $self->{rv}->{direction} eq "receive") {
295 0           $self->log_print(OSCAR_DBG_DEBUG, "Sending OFT fin header");
296 0           my $ret = $self->send_oft_header();
297 0 0         return $ret unless $ret;
298              
299 0 0         if(++$self->{got_files} < $self->{rv}->{file_count}) {
300 0           $self->{rv}->{ft_state} = "connected";
301 0           $self->{state} = "read";
302 0           $self->{session}->callback_connection_changed($self, "read");
303             } else {
304 0           $self->disconnect();
305             }
306 0           return 1;
307             }
308             }
309             }
310              
311             sub send_oft_header($) {
312 0     0 0   my $self = shift;
313              
314 0           my $total_size = 0;
315 0           $total_size += length($_) foreach @{$self->{rv}->{data}};
  0            
316              
317 0           my $type;
318             my $cookie;
319 0 0 0       if($self->{rv}->{ft_state} eq "connected" and ($self->{sent_oft_header} and $self->{sent_oft_header} != 2)) {
      0        
320 0 0         if($self->{rv}->{direction} eq "send") {
321 0           $type = 0x101;
322 0           $cookie = chr(0) x 8;
323             } else {
324 0           $type = 0x202;
325 0           $cookie = $self->{rv}->{cookie};
326             }
327             } else {
328 0           $type = 0x204;
329 0           $cookie = $self->{rv}->{cookie};
330             }
331              
332 0           my %protodata = (
333             type => $type,
334             cookie => $cookie,
335             file_count => $self->{rv}->{file_count},
336 0           files_left => scalar(@{$self->{rv}->{data}}),
337             byte_count => $self->{byte_count},
338             bytes_left => $self->{bytes_left},
339             mtime => time(),
340             ctime => 0,
341             bytes_received => $self->{bytes_recv},
342             checksum => $self->{checksum},
343             received_checksum => $self->{received_checksum},
344             filename => $self->{filename}
345             );
346 0           $self->write(protoparse($self->{session}, "file_transfer_header")->pack(%protodata));
347             }
348              
349             sub get_oft_header($) {
350 0     0 0   my $self = shift;
351              
352 0           my $header = $self->read(6);
353 0 0         return $header unless $header;
354 0           my($magic, $length) = unpack("a4 n", $header);
355              
356 0 0         if($magic ne "OFT2") {
357 0           $self->log_print(OSCAR_DBG_WARN, "Got unexpected data while reading file transfer header!");
358 0           $self->{sockerr} = 1;
359 0           $self->disconnect();
360 0           return undef;
361             }
362              
363 0           my $data = $self->read($length - 6);
364 0 0         return $data unless $data;
365            
366 0           my %protodata = protoparse($self->{session}, "file_transfer_header")->unpack($header . $data);
367 0 0         if($self->{rv}->{direction} eq "receive") {
368 0 0 0       if(
369             $protodata{file_count} != $self->{rv}->{file_count} or
370             $protodata{byte_count} != $self->{rv}->{total_size}
371             ) {
372 0           $self->log_print(OSCAR_DBG_WARN, "Rendezvous header data doesn't match initial proposal!");
373 0           $self->{sockerr} = 1;
374 0           $self->disconnect();
375 0           return undef;
376             } else {
377 0           $self->{read_size} = $protodata{bytes_left};
378 0           $self->{checksum} = $protodata{checksum};
379 0           $self->{byte_count} = $protodata{byte_count};
380 0           $self->{bytes_left} = $protodata{bytes_left};
381 0           $self->{filename} = $protodata{filename};
382             }
383             } else {
384 0 0         if($protodata{cookie} ne $self->{rv}->{cookie}) {
385 0           $self->log_print(OSCAR_DBG_WARN, "Rendezvous header cookie doesn't match initial proposal!");
386 0           $self->{sockerr} = 1;
387 0           $self->disconnect();
388 0           return undef;
389             }
390             }
391              
392 0           $self->log_print(OSCAR_DBG_DEBUG, "Got OFT header.");
393 0           return 1;
394             }
395              
396             # Adopted from Gaim's implementation
397             sub checksum($$) {
398 0     0 0   my($self, $part) = @_;
399 0           my $check = sprintf("%lu", (0xFFFF0000 >> 16) & 0xFFFF);
400              
401 0           for(my $i = 0; $i < length($part); $i++) {
402 0           my $oldcheck = $check;
403              
404 0           my $byte = ord(substr($part, $i, 1));
405 0 0         my $val = ($i & 1) ? $byte : ($byte << 8);
406 0           $check -= $val;
407 0           $check = sprintf("%lu", $check);
408              
409 0 0         if($check > $oldcheck) {
410 0           $check--;
411 0           $check = sprintf("%lu", $check);
412             }
413             }
414              
415 0           $check = (($check & 0x0000FFFF) + ($check >> 16));
416 0           $check = (($check & 0x0000FFFF) + ($check >> 16));
417 0           $check = $check << 16;
418              
419 0           return sprintf("%lu", $check);
420             }
421              
422             sub get_proxy_header($;$) {
423 0     0 0   my ($self, $protobit) = @_;
424 0           my $socket = $self->{socket};
425 0           my ($buffer, $len);
426 0           my $nchars;
427 0   0       $protobit ||= "direct_connect_proxy_hdr";
428              
429 0 0         if(!$self->{buff_gotproxy}) {
430 0           my $header = $self->read(2);
431 0 0         if(!defined($header)) {
    0          
432 0           return undef;
433             } elsif($header eq "") {
434 0           return "";
435             }
436              
437 0           $self->{buff_gotproxy} = 2;
438 0           ($self->{proxy_size}) = unpack("n", $header);
439             }
440              
441 0 0         if($self->{proxy_size} > 0) {
442 0           my $data = $self->read($self->{proxy_size}, 2);
443 0 0         if(!defined($data)) {
    0          
444 0           return undef;
445             } elsif($data eq "") {
446 0           return "";
447             }
448              
449 0     0     $self->log_print_cond(OSCAR_DBG_PACKETS, sub { "Got ", hexdump($data) });
  0            
450 0           delete $self->{buff_gotproxy};
451 0           return {protoparse($self->{session}, $protobit)->unpack($data)};
452             } else {
453 0           delete $self->{buff_gotproxy};
454 0           return "";
455             }
456             }
457              
458             1;