source: perl/modules/AIM/lib/Net/OSCAR/Connection/Direct.pm @ 3dcccba

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