ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-XMPP2/lib/Net/XMPP2/SimpleConnection.pm
Revision: 1.7
Committed: Mon Jul 2 13:15:00 2007 UTC (19 years, 2 months ago) by elmex
Branch: MAIN
Changes since 1.6: +18 -1 lines
Log Message:
added some error handling for EOF ... err... disconnects from SSL

File Contents

# User Rev Content
1 elmex 1.1 package Net::XMPP2::SimpleConnection;
2 elmex 1.7 use strict;
3    
4 elmex 1.1 use IO::Socket::INET;
5     use Errno;
6     use Fcntl;
7     use Encode;
8     use Net::SSLeay;
9 elmex 1.5 use IO::Handle;
10 elmex 1.1
11     BEGIN {
12     Net::SSLeay::load_error_strings ();
13     Net::SSLeay::SSLeay_add_ssl_algorithms ();
14     Net::SSLeay::randomize ();
15     }
16    
17     =head1 NAME
18    
19     Net::XMPP2::SimpleConnection - A low level TCP/TLS connection
20    
21     =head1 SYNOPSIS
22    
23     package foo;
24     use Net::XMPP2::SimpleConnection;
25    
26     our @ISA = qw/Net::XMPP2::SimpleConnection/;
27    
28     =head1 DESCRIPTION
29    
30     This module only implements the basic low level socket and SSL handling stuff.
31     It is used by L<Net::XMPP2::Connection>.
32    
33     =cut
34    
35     sub new {
36     my $this = shift;
37     my $class = ref($this) || $this;
38     my $self = { disconnect_cb => sub {}, @_ };
39     bless $self, $class;
40     return $self;
41     }
42    
43     sub set_block {
44     my ($self) = @_;
45     my $flags = 0;
46     fcntl($self->{socket}, F_GETFL, $flags)
47     or die "Couldn't get flags for HANDLE : $!\n";
48     $flags &= ~O_NONBLOCK;
49     fcntl($self->{socket}, F_SETFL, $flags)
50     or die "Couldn't set flags for HANDLE: $!\n";
51     }
52    
53     sub set_noblock {
54     my ($self) = @_;
55     my $flags = 0;
56     fcntl($self->{socket}, F_GETFL, $flags)
57     or die "Couldn't get flags for HANDLE : $!\n";
58     $flags |= O_NONBLOCK;
59     fcntl($self->{socket}, F_SETFL, $flags)
60     or die "Couldn't set flags for HANDLE: $!\n";
61     }
62    
63     sub connect {
64     my ($self, $host, $port) = @_;
65    
66     $self->{socket}
67     and return 1;
68    
69     my $sock = IO::Socket::INET->new (
70     PeerAddr => $host,
71     PeerPort => $port,
72     Proto => 'tcp',
73     Blocking => 1
74     );
75     return undef unless $sock;;
76    
77     $self->{socket} = $sock;
78     $self->{host} = $host;
79     $self->{port} = $port;
80    
81     $self->set_noblock;
82    
83 elmex 1.4 binmode $sock, ":raw";
84 elmex 1.1
85     $self->{r} =
86     AnyEvent->io (poll => 'r', fh => $sock, cb => sub {
87     my $l = sysread $sock, my $data, 1024;
88    
89     if ($l) {
90 elmex 1.4 $self->{read_buffer} .= decode_utf8 $data;
91 elmex 1.1 $self->handle_data (\$self->{read_buffer});
92    
93     } else {
94     return if $! == Errno::EAGAIN;
95     if (defined $l) {
96 elmex 1.5 $self->disconnect ("EOF from server '$self->{host}:$self->{port}'");
97 elmex 1.1 return;
98    
99     } else {
100 elmex 1.5 $self->disconnect ("Error while reading from server '$self->{host}:$port': $!");
101 elmex 1.1 return;
102     }
103     }
104     });
105     return 1;
106     }
107    
108     sub end_sockets {
109     my ($self) = @_;
110     delete $self->{r};
111     delete $self->{w};
112     if (delete $self->{ssl_enabled}) {
113     Net::SSLeay::free ($self->{ssl});
114     delete $self->{ssl};
115     Net::SSLeay::CTX_free ($self->{ctx});
116     delete $self->{ctx};
117     }
118 elmex 1.5 close ($self->{socket});
119     delete $self->{socket};
120 elmex 1.1 }
121    
122     sub try_ssl_write {
123     my ($self) = @_;
124    
125 elmex 1.2 my $l = Net::SSLeay::write ($self->{ssl}, $self->{write_buffer});
126 elmex 1.1
127     if ($l <= 0) {
128     if ($l == 0) {
129 elmex 1.5 $self->disconnect ("unexpected EOF from server (ssl) '$self->{host}:$self->{port}'");
130 elmex 1.1 return;
131    
132     } else {
133     my $err2 = Net::SSLeay::get_error $self->{ssl}, $l;
134     if ($err2 == 2 || $err2 == 3) {
135     delete $self->{w};
136     $self->make_ssl_write_watcher ($err2 == 2 ? 'r' : 'w');
137     return;
138     }
139    
140     if ($! != Errno::EAGAIN
141     or my $err = Net::SSLeay::ERR_get_error) {
142    
143 elmex 1.5 $self->disconnect (
144 elmex 1.1 sprintf (
145     "Error while writing from server '$self->{host}:$self->{port}': (%d|%s|%s)",
146     $err2, (Net::SSLeay::ERR_error_string $err), "$!")
147     );
148     return;
149     }
150     }
151     } else {
152 elmex 1.2 $self->debug_wrote_data (substr $self->{write_buffer}, 0, $l);
153     $self->{write_buffer} = substr $self->{write_buffer}, $l;
154     if (length ($self->{write_buffer}) <= 0) {
155     delete $self->{w};
156     }
157 elmex 1.1 }
158     }
159    
160     sub try_ssl_read {
161     my ($self) = @_;
162 elmex 1.2 my $r = Net::SSLeay::read ($self->{ssl});
163    
164     if (defined $r) {
165 elmex 1.7 if ($r eq '') {
166     if (my $err = Net::SSLeay::ERR_get_error) {
167     $self->disconnect (
168     sprintf (
169     "Error while reading from server '$self->{host}:$self->{port}':"
170     ."(%s|%s)",
171     (Net::SSLeay::ERR_error_string $err), "$!")
172     );
173     return;
174     }
175     # is this right? $r = '' => EOF? sucky Net::SSLeay... arg...
176     $self->disconnect ("EOF from server '$self->{host}:$self->{port}'.");
177     return;
178     }
179    
180 elmex 1.2 $self->{read_buffer} .= decode_utf8 ($r);
181     $self->handle_data (\$self->{read_buffer});
182     } else {
183 elmex 1.7 my $err2 = Net::SSLeay::get_error $self->{ssl}, $r;
184 elmex 1.2 if ($err2 == 2 || $err2 == 3) {
185 elmex 1.6 #d# warn "READ RETRY $err2\n";
186 elmex 1.2 delete $self->{r};
187     $self->make_ssl_read_watcher ($err2 == 2 ? 'r' : 'w');
188     return;
189     }
190    
191     if ($! != Errno::EAGAIN
192     or my $err = Net::SSLeay::ERR_get_error) {
193 elmex 1.1
194 elmex 1.5 $self->disconnect (
195 elmex 1.2 sprintf (
196     "Error while reading from server '$self->{host}:$self->{port}':"
197     ."(%d|%s|%s)",
198     $err2, (Net::SSLeay::ERR_error_string $err), "$!")
199     );
200 elmex 1.1 return;
201     }
202     }
203     }
204    
205     sub write_data {
206     my ($self, $data) = @_;
207     #return unless $self->{r};
208    
209     my $cl = $self->{socket};
210 elmex 1.4 $self->{write_buffer} .= encode_utf8 ($data);
211 elmex 1.1
212     unless ($self->{w}) {
213 elmex 1.2 $self->{w} =
214     AnyEvent->io (poll => 'w', fh => $cl, cb => sub {
215     if (not $self->{ssl_enabled}) {
216 elmex 1.1 if (my $data = $self->{write_buffer}) {
217     my $len = syswrite $cl, $data;
218     unless ($len) {
219     return if $! == Errno::EAGAIN;
220     if (not defined $len) {
221     warn "error when writing data on $self->{host}:$self->{port}: $!";
222     return;
223     } else {
224     delete $self->{w};
225     }
226     }
227    
228     if ($len == length $self->{write_buffer}) {
229     delete $self->{w};
230     }
231    
232     $self->debug_wrote_data (substr $self->{write_buffer}, 0, $len);
233     $self->{write_buffer} = substr $self->{write_buffer}, $len;
234     }
235 elmex 1.2 } else {
236     $self->try_ssl_write;
237     }
238     });
239     }
240     }
241    
242     sub make_ssl_read_watcher {
243     my ($self, $poll) = @_;
244     return if $self->{r};
245 elmex 1.1
246 elmex 1.2 $poll ||= 'r';
247     $self->{r} =
248     AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
249     $self->try_ssl_read;
250     });
251     }
252    
253     sub make_ssl_write_watcher {
254     my ($self, $poll) = @_;
255     return if $self->{w};
256    
257     $poll ||= 'w';
258     $self->{w} =
259     AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
260     $self->try_ssl_write;
261     });
262 elmex 1.1 }
263    
264     sub enable_ssl {
265     my ($self) = @_;
266    
267     $Net::SSLeay::ssl_version = 10; # Insist on TLSv1
268    
269     $self->{ssl_enabled} = 1;
270    
271 elmex 1.3 #d# warn "START TLS!\n";
272 elmex 1.1
273     $self->{r} = undef;
274     $self->{w} = undef;
275    
276     $self->{ctx} = Net::SSLeay::CTX_new ();
277 elmex 1.3
278 elmex 1.2 # enable SSL_MODE_ENABLE_PARTIAL_WRITE and SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER
279     Net::SSLeay::CTX_set_mode($self->{ctx}, 1 | 2);
280 elmex 1.3
281 elmex 1.1 $self->{ssl} = Net::SSLeay::new ($self->{ctx});
282    
283     Net::SSLeay::set_fd ($self->{ssl}, fileno $self->{socket});
284     #d# warn "CONNECT\n";
285     Net::SSLeay::connect $self->{ssl};
286     #d# warn "CONNECT END\n";
287     binmode $self->{socket}, ":bytes";
288    
289     $self->{ssl_read_data} = "";
290     $self->make_ssl_read_watcher;
291     }
292    
293 elmex 1.5 sub disconnect {
294     my ($self, $msg) = @_;
295     $self->end_sockets;
296     $self->{disconnect_cb}->($self->{host}, $self->{port}, $msg);
297     }
298    
299 elmex 1.1 1;