ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.2
Committed: Tue Jan 23 22:36:11 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.1: +253 -31 lines
Log Message:
implemented TLS!

File Contents

# Content
1 package Net::XMPP2::Connection;
2 use warnings;
3 use strict;
4 use AnyEvent;
5 use IO::Socket::INET;
6 use Net::XMPP2::Parser;
7 use Net::XMPP2::Writer;
8 use Net::XMPP2::Util;
9 use Net::XMPP2::Namespaces qw/xmpp_ns/;
10 use Net::DNS;
11 use Net::SSLeay;
12
13 BEGIN {
14 Net::SSLeay::load_error_strings ();
15 Net::SSLeay::SSLeay_add_ssl_algorithms ();
16 Net::SSLeay::randomize ();
17 }
18
19 our @ISA = qw/Net::XMPP2::SimpleConnection/;
20
21 =head1 NAME
22
23 Net::XMPP2::Connection - A XML stream that implements the XMPP RFC 3920.
24
25 =head1 SYNOPSIS
26
27 use Net::XMPP2::Connection;
28
29 my $con =
30 Net::XMPP2::Connection->new (
31 username => "abc",
32 domain => "jabber.org",
33 resource => "Net::XMPP2"
34 );
35
36 $con->connect or die "Couldn't connect to jabber.org: $!";
37 $con->init;
38 $con->reg_cb (stream_ready => sub { print "XMPP stream ready!\n" });
39
40 =head1 DESCRIPTION
41
42 This module represents a XMPP stream as described in RFC 3920. You can issue the basic
43 XMPP XML stanzas with methods like C<send_iq>, C<send_message> and C<send_presence>.
44
45 And receive events with the C<reg_cb> event framework from the connection.
46
47 If you need instant messaging stuff please take a look at C<Net::XMPP2::IM::Connection>.
48
49 =head1 METHODS
50
51 =head2 new (%args)
52
53 Following arguments can be passed in C<%args>:
54
55 =over 4
56
57 =item language => $tag
58
59 This should be the language of the human readable contents that
60 will be transmitted over the stream. The default will be 'en'.
61
62 Please look in RFC 3066 how C<$tag> should look like.
63
64 =item resource => $resource
65
66 If this argument is given C<$resource> will be passed as desired
67 resource on resource binding.
68
69 Note: You have to take care that the stringprep profile for
70 resources can be applied at: C<$resource>. Otherwise the server
71 might signal an error. See L<Net::XMPP2::Util> for utility functions
72 to check this.
73
74 =item domain => $domain
75
76 This is the destination host we are going to connect to.
77 As the connection won't be automatically connected use C<connect>
78 to initiate the connect.
79
80 Note: A SRV RR lookup will be performed to discover the real hostname
81 and port to connect to. See also C<connect>.
82
83 =item port => $port
84
85 This is optional, the default port is 5222.
86
87 Note: A SRV RR lookup will be performed to discover the real hostname
88 and port to connect to. See also C<connect>.
89
90 =item username => $username
91
92 This is your C<$username> (the userpart in the JID);
93
94 Note: You have to take care that the stringprep profile for
95 nodes can be applied at: C<$username>. Otherwise the server
96 might signal an error. See L<Net::XMPP2::Util> for utility functions
97 to check this.
98
99 =item password => $password
100
101 This is the password for the C<username> above.
102
103 =back
104
105 =cut
106
107 sub new {
108 my $this = shift;
109 my $class = ref($this) || $this;
110 my $self = { language => 'en', @_ };
111 bless $self, $class;
112
113 $self->{parser} = new Net::XMPP2::Parser;
114 $self->{writer} = Net::XMPP2::Writer->new (
115 write_cb => sub { $self->write_data ($_[0]) }
116 );
117
118 $self->{parser}->set_stanza_cb (sub {
119 $self->handle_stanza (@_);
120 });
121
122 $self->{iq_id} = 1;
123
124 $self->{disconnect_cb} = sub {
125 my ($host, $port, $message) = @_;
126 $self->event (disconnect => $host, $port, $message);
127 };
128
129 return $self;
130 }
131
132 =head2 connect ($no_srv_rr)
133
134 Try to connect to the domain and port passed in C<new>.
135
136 A SRV RR lookup will be performed on the domain to discover
137 the host and port to use. If you don't want this set C<$no_srv_rr>
138 to a true value. C<$no_srv_rr> is false by default.
139
140 As the SRV RR lookup might return multiple host and you fail to
141 connect to one you might just call this function again to try a
142 different host.
143
144 If C<connect> was successful and we connected a true value is returned.
145 If the connect was unsuccessful undef is returned and C<$!> will be set
146 to the error that occured while connecting.
147
148 If you want to know whether further connection attempts might be more
149 successful (as SRV RR lookup may return multiple hosts) call C<may_try_connect>
150 (see also C<may_try_connect>).
151
152 Note that an internal list will be kept of tried hosts. Use
153 C<reset_connect_tries> to reset the internal list of tried hosts.
154
155 =cut
156
157 sub connect {
158 my ($self, $no_srv_rr) = @_;
159
160 my ($host, $port) = ($self->{domain}, $self->{port} || 5222);
161
162 unless ($no_srv_rr) {
163 my $res = Net::DNS::Resolver->new;
164 my $p = $res->query ('_xmpp-client._tcp.'.$host, 'SRV');
165 if ($p) {
166 my @srvs = grep { $_->type eq 'SRV' } $p->answer;
167 if (@srvs) {
168 @srvs = sort { $a->priority <=> $b->priority } @srvs;
169 @srvs = sort { $b->weight <=> $a->weight } @srvs; # TODO
170 $port = $srvs[0]->port;
171 $host = $srvs[0]->target;
172 }
173 }
174 }
175
176 if ($self->SUPER::connect ($host, $port)) {
177 $self->event (connect => $host, $port);
178 return 1;
179 } else {
180 return undef;
181 }
182 }
183
184 =head2 may_try_connect
185
186 Returns the number of left alternatives of hosts to connect to for the
187 domain passed to C<new>.
188
189 An internal list of tried hosts will be managed by C<connect> and those
190 hosts will be ignored by a SRV RR lookup (which will be done if you
191 call this function).
192
193 Use C<reset_connect_tries> to reset the internal list of tried hosts.
194
195 =cut
196
197 sub may_try_connect {
198 # TODO
199 }
200
201 =head2 reset_connect_tries
202
203 This function resets the internal list of tried hosts for C<connect>.
204 See also C<connect>.
205
206 =cut
207
208 sub reset_connect_tries {
209 # TODO
210 }
211
212 sub handle_data {
213 my ($self, $buf) = @_;
214 $self->event (debug_recv => $$buf);
215 $self->{parser}->feed (substr $$buf, 0, (length $$buf), '');
216 }
217
218 sub write_data {
219 my ($self, $data) = @_;
220 $self->event (debug_send => $data);
221 $self->SUPER::write_data ($data);
222 }
223
224 =item reg_cb ($eventname1, $cb1, [$eventname2, $cb2, ...])
225
226 This method registers a callback C<$cb1> for the event with the
227 name C<$eventname1>. You can also pass multiple of these eventname => callback
228 pairs.
229
230 To see a documentation of emitted events please take a look at the EVENTS section
231 below.
232
233 =cut
234
235 sub reg_cb {
236 my ($self, %regs) = @_;
237
238 for my $cmd (keys %regs) {
239 my $cb = $regs{$cmd};
240 push @{$self->{events}->{$cmd}}, $cb;
241 }
242
243 1;
244 }
245
246 sub event {
247 my ($self, $ev, @arg) = @_;
248
249 my $nxt = [];
250
251 for (@{$self->{events}->{lc $ev}}) {
252 $_->($self, @arg) and push @$nxt, $_;
253 }
254
255 $self->{events}->{lc $ev} = $nxt;
256 }
257
258 sub handle_stanza {
259 my ($self, $p, $node) = @_;
260
261 if ($node->eq (stream => 'features')) {
262 $self->event (stream_features => $node);
263 $self->handle_stream_features ($node);
264 } elsif ($node->eq (tls => 'proceed')) {
265 $self->enable_ssl;
266 $self->{parser}->init;
267 $self->{writer}->init;
268 $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
269
270 } elsif ($node->eq (sasl => 'challenge')) {
271 $self->handle_sasl_challenge ($node);
272 } elsif ($node->eq (sasl => 'success')) {
273 $self->handle_sasl_success ($node);
274 } elsif ($node->eq (client => 'iq')) {
275 $self->handle_iq ($node);
276 } elsif ($node->eq (stream => 'error')) {
277 $self->handle_error ($node);
278 } else {
279 warn "Didn't understood stanza: '" . $node->name . "'";
280 }
281 }
282
283 =head2 init ($domain)
284
285 Initiate the XML stream.
286
287 =cut
288
289 sub init {
290 my ($self) = @_;
291 $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
292 }
293
294 =head2 send_iq ($type, $create_cb, $result_cb, %attrs)
295
296 This method sends an IQ XMPP request.
297
298 Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
299 about the meaning of C<$type>, C<$create_cb> and C<%attrs>.
300
301 C<$result_cb> will be called when a result was received. The first argument
302 to C<$result_cb> will be a Net::XMPP2::Parser instance and the second
303 will be a Net::XMPP2::Node instance containing the IQ result stanza contents.
304
305 If the IQ resulted in a stanza error the second argument to C<$result_cb> will
306 be C<undef> (if the error type was not 'continue') and the third argument will
307 be a Net::XMPP2::Node containg the IQ error stanza. And the fourth argument
308 will be a array reference with following contents:
309
310 =over 4
311
312 =item index 0: error type
313
314 This will be one of: 'cancel', 'continue', 'modify', 'auth' and 'wait'.
315
316 =item index 1: error condition element
317
318 This might be undefined if other XMPP speakers don't play nice i guess.
319
320 =item index 2: error text
321
322 This will be the human readable form of the error which is maybe undef if
323 not supplied.
324
325 =item index 3: error code
326
327 If the error element had an 'code' attribute it will be put here,
328 the RFC says that this is for backward compatibility :)
329
330 =back
331
332 =cut
333
334 sub send_iq {
335 my ($self, $type, $create_cb, $result_cb, %attrs) = @_;
336 my $id = $self->{iq_id}++;
337 $self->{iqs}->{$id} = $result_cb;
338 $self->{writer}->send_iq ($id, $type, $create_cb, %attrs);
339 }
340
341 sub handle_iq {
342 my ($self, $node) = @_;
343
344 if ($node->attr ('type') eq 'result') {
345 if (my $cb = $self->{iqs}->{$node->attr ('id')}) {
346 $cb->($node);
347 }
348 } elsif ($node->attr ('type') eq 'error') {
349 if (my $cb = $self->{iqs}->{$node->attr ('id')}) {
350
351 my $error = $self->filter_error_stanza ($node);
352 $cb->(($error->[0] eq 'continue' ? $node : undef), $node, $error);
353 }
354 }
355 }
356
357 sub filter_error_stanza {
358 my ($self, $node) = @_;
359 my $p = $self->{parser};
360 my @error;
361 my ($err) = $node->find_all ([qw/client error/]);
362 $error[0] = $err->attr ('type');
363 $error[3] = $err->attr ('code');
364 if ($err) {
365 if (my ($txt) = $err->find_all ([qw/stanzas text/])) {
366 $error[2] = $txt->text;
367 }
368 for my $er (
369 qw/bad-request conflict feature-not-implemented forbidden
370 gone internal-server-error item-not-found jid-malformed
371 not-acceptable not-allowed not-authorized payment-required
372 recipient-unavailable redirect registration-required
373 remote-server-not-found remote-server-timeout resource-constraint
374 service-unavailable subscription-required undefined-condition
375 unexpected-request/)
376 {
377 if (my ($el) = $err->find_all ([stanzas => $er])) {
378 $error[1] = $el;
379 last;
380 }
381 }
382 } else {
383 warn "no error element found in error stanza!";
384 }
385 return \@error
386 }
387
388 sub handle_stream_features {
389 my ($self, $node) = @_;
390 my @mechs = $node->find_all ([qw/sasl mechanisms/], [qw/sasl mechanism/]);
391 my @bind = $node->find_all ([qw/bind bind/]);
392 my @tls = $node->find_all ([qw/tls starttls/]);
393
394 if (not ($self->{ssl_enabled}) and @tls) {
395 $self->{writer}->send_starttls;
396
397 } elsif (not ($self->{authenticated}) and @mechs) {
398 $self->{writer}->send_sasl_auth (
399 (join ' ', map { $_->text } @mechs),
400 $self->{username}, $self->{domain}, $self->{password}
401 );
402
403 } elsif (@bind) {
404 $self->do_rebind ($self->{resource});
405 }
406 }
407
408 sub handle_sasl_challenge {
409 my ($self, $node) = @_;
410 $self->{writer}->send_sasl_response ($node->text);
411 }
412
413 sub handle_sasl_success {
414 my ($self, $node) = @_;
415 $self->{authenticated} = 1;
416 $self->{parser}->init;
417 $self->{writer}->init;
418 $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
419 }
420
421 sub handle_error {
422 my ($self, $node) = @_;
423 my @txt = $node->find_all ([qw/stream text/]);
424 my $error;
425 for my $er (
426 qw/bad-format bad-namespace-prefix conflict connection-timeout host-gone
427 host-unknown improper-addressing internal-server-error invalid-from
428 invalid-id invalid-namespace invalid-xml not-authorized policy-violation
429 remote-connection-failed resource-constraint restricted-xml
430 see-other-host system-shutdown undefined-condition unsupported-stanza-type
431 unsupported-version xml-not-well-formed/)
432 {
433 for ($node->nodes) {
434 if ($node->eq (streams => $er)) {
435 $error = $_->name;
436 last
437 }
438 }
439 }
440 unless ($error) {
441 warn "got undefined error stanza, trying to find any undefined error...";
442 for ($node->nodes) {
443 if ($node->eq_ns ('streams')) {
444 $error = $node->name;
445 }
446 }
447 }
448 $self->event (stream_error => $error, (@txt ? $txt[0]->text : ''));
449 $self->{writer}->send_end_of_stream;
450 }
451
452 =head2 do_rebind ($resource)
453
454 In case you got a C<bind_error> event and want to retry
455 binding you can call this function to set a new C<$resource>
456 and retry binding.
457
458 If it fails again you can call this again. Becareful not to
459 end up in a loop!
460
461 If binding was successful the C<stream_ready> event will be generated.
462
463 =cut
464
465 sub do_rebind {
466 my ($self, $resource) = @_;
467 $self->{resource} = $resource;
468 $self->send_iq (
469 set =>
470 sub {
471 my ($w) = @_;
472 if ($self->{resource}) {
473 $w->startTag ([xmpp_ns ('bind'), 'bind']);
474 $w->startTag ([xmpp_ns ('bind'), 'resource']);
475 $w->characters ($self->{resource});
476 $w->endTag;
477 $w->endTag;
478 } else {
479 $w->emptyTag ([xmpp_ns ('bind'), 'bind'])
480 }
481 },
482 sub {
483 my ($ret_iq, $err_iq, $err) = @_;
484
485 if ($err) {
486 my ($res) = $err_iq->find_all ([qw/bind bind/], [qw/bind resource/]);
487 $self->event (bind_error => $err->[0], ($res ? $res : $self->{resource}));
488
489 } else {
490 my @jid = $ret_iq->find_all ([qw/bind bind/], [qw/bind jid/]);
491 my $jid = $jid[0]->text;
492 unless ($jid) { die "Got empty JID tag from server!\n" }
493 $self->{jid} = $jid;
494
495 $self->event (stream_ready => $jid);
496 }
497 }
498 );
499 }
500
501 =head2 jid
502
503 After the stream has been bound to a resource the JID can be retrieved via this
504 method.
505
506 =cut
507
508 sub jid { $_[0]->{jid} }
509
510 =head1 EVENTS
511
512 These events can be registered on with C<reg_cb>:
513
514 =over 4
515
516 =item stream_features => $node
517
518 This
519
520 =item stream_ready => $jid
521
522 This event is sent if the XML stream has been established (and
523 resources have been bound) and is ready for transmitting regular stanzas.
524
525 C<$jid> is the bound jabber id.
526
527 =item bind_error => $error_name, $resource
528
529 This event is generated when the stream was unable to bind to
530 any or the in C<new> specified resource. C<$error_name>
531 may be 'bad-request', 'not-allowed' or 'conflict'.
532
533 Node: this is untested, i couldn't get the server to send a bind error
534 to test this.
535
536 =item connect => $host, $port
537
538 This event is generated when a successful connect was performed to
539 the domain passed to C<new>.
540
541 Note: C<$host> and C<$port> might be different from the domain you passed to
542 C<new> if C<connect> performed a SRV RR lookup.
543
544 If this connection is lost a C<disconnect> will be generated with the same
545 C<$host> and C<$port>.
546
547 =item disconnect => $host, $port, $message
548
549 This event is generated when the connection was lost or another error
550 occured while writing or reading from it.
551
552 C<$message> is a humand readable error message for the failure.
553 C<$host> and C<$port> were the host and port we were connected to.
554
555 Note: C<$host> and C<$port> might be different from the domain you passed to
556 C<new> if C<connect> performed a SRV RR lookup.
557
558 =back
559
560 =head1 AUTHOR
561
562 Robin Redeker, C<< <elmex at ta-sa.org> >>
563
564 =head1 BUGS
565
566 Please report any bugs or feature requests to
567 C<bug-net-xmpp2 at rt.cpan.org>, or through the web interface at
568 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-XMPP2>.
569 I will be notified, and then you'll automatically be notified of progress on
570 your bug as I make changes.
571
572 =head1 SUPPORT
573
574 You can find documentation for this module with the perldoc command.
575
576 perldoc Net::XMPP2
577
578 You can also look for information at:
579
580 =over 4
581
582 =item * AnnoCPAN: Annotated CPAN documentation
583
584 L<http://annocpan.org/dist/Net-XMPP2>
585
586 =item * CPAN Ratings
587
588 L<http://cpanratings.perl.org/d/Net-XMPP2>
589
590 =item * RT: CPAN's request tracker
591
592 L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-XMPP2>
593
594 =item * Search CPAN
595
596 L<http://search.cpan.org/dist/Net-XMPP2>
597
598 =back
599
600 =head1 ACKNOWLEDGEMENTS
601
602 =head1 COPYRIGHT & LICENSE
603
604 Copyright 2007 Robin Redeker, all rights reserved.
605
606 This program is free software; you can redistribute it and/or modify it
607 under the same terms as Perl itself.
608
609 =cut
610
611 package Net::XMPP2::SimpleConnection;
612 use IO::Socket::INET;
613 use Errno;
614 use Fcntl;
615 use Encode;
616
617 sub new {
618 my $this = shift;
619 my $class = ref($this) || $this;
620 my $self = { disconnect_cb => sub {}, @_ };
621 bless $self, $class;
622 return $self;
623 }
624
625 sub set_block {
626 my ($self) = @_;
627 my $flags = 0;
628 fcntl($self->{socket}, F_GETFL, $flags)
629 or die "Couldn't get flags for HANDLE : $!\n";
630 $flags &= ~O_NONBLOCK;
631 fcntl($self->{socket}, F_SETFL, $flags)
632 or die "Couldn't set flags for HANDLE: $!\n";
633 }
634
635 sub set_noblock {
636 my ($self) = @_;
637 my $flags = 0;
638 fcntl($self->{socket}, F_GETFL, $flags)
639 or die "Couldn't get flags for HANDLE : $!\n";
640 $flags |= O_NONBLOCK;
641 fcntl($self->{socket}, F_SETFL, $flags)
642 or die "Couldn't set flags for HANDLE: $!\n";
643 }
644
645 sub connect {
646 my ($self, $host, $port) = @_;
647
648 $self->{socket}
649 and return 1;
650
651 my $sock = IO::Socket::INET->new (
652 PeerAddr => $host,
653 PeerPort => $port,
654 Proto => 'tcp',
655 Blocking => 1
656 );
657 return undef unless $sock;;
658
659 $self->{socket} = $sock;
660 $self->{host} = $host;
661 $self->{port} = $port;
662
663 $self->set_noblock;
664
665 binmode $sock, ":utf8";
666
667 $self->{r} =
668 AnyEvent->io (poll => 'r', fh => $sock, cb => sub {
669 my $l = sysread $sock, my $data, 1024;
670
671 unless ($l) {
672 return if $! == Errno::EGAIN();
673 if (defined $l) {
674 $self->{disconnect_cb}->($self->{host}, $self->{port}, "EOF from server '$self->{host}:$self->{port}'");
675 $self->end_sockets;
676 return;
677
678 } else {
679 $self->{disconnect_cb}->($self->{host}, $self->{port}, "Error while reading from server '$self->{host}:$port': $!");
680 $self->end_sockets;
681 return;
682 }
683 }
684
685 $self->{read_buffer} .= $data;
686 $self->handle_data (\$self->{read_buffer});
687 });
688 return 1;
689 }
690
691 sub end_sockets {
692 my ($self) = @_;
693 delete $self->{r};
694 delete $self->{w};
695 delete $self->{ssl};
696 delete $self->{socket};
697 delete $self->{ssl_enabled};
698 }
699
700 sub dumpbio {
701 my ($self) = @_;
702
703 print "er: ".Net::SSLeay::BIO_should_retry (Net::SSLeay::get_rbio ($self->{ssl}));
704 print " ew: ".Net::SSLeay::BIO_should_retry (Net::SSLeay::get_wbio ($self->{ssl}));
705 print " rr: ".Net::SSLeay::BIO_should_read (Net::SSLeay::get_rbio ($self->{ssl}));
706 print " rw: ".Net::SSLeay::BIO_should_read (Net::SSLeay::get_wbio ($self->{ssl}));
707 print " wr: ".Net::SSLeay::BIO_should_write (Net::SSLeay::get_rbio ($self->{ssl}));
708 print " ww: ".Net::SSLeay::BIO_should_write (Net::SSLeay::get_wbio ($self->{ssl}))."\n";
709
710 my $e = Net::SSLeay::BIO_should_retry (Net::SSLeay::get_wbio ($self->{ssl}))
711 | Net::SSLeay::BIO_should_retry (Net::SSLeay::get_wbio ($self->{ssl}));
712 my $w = Net::SSLeay::BIO_should_read (Net::SSLeay::get_wbio ($self->{ssl}))
713 | Net::SSLeay::BIO_should_write (Net::SSLeay::get_wbio ($self->{ssl}));
714 my $r = Net::SSLeay::BIO_should_read (Net::SSLeay::get_rbio ($self->{ssl}))
715 | Net::SSLeay::BIO_should_write (Net::SSLeay::get_rbio ($self->{ssl}));
716
717 print "TEST:$e $w $r\n";
718 # delete $self->{r};
719 # delete $self->{w};
720 # if ($w) { $self->make_ssl_write_watcher }
721 # if ($r) { $self->make_ssl_read_watcher }
722 # unless ($e) {
723 # $self->make_ssl_read_watcher;
724 # $self->make_ssl_write_watcher;
725 # }
726 }
727
728 sub try_ssl_write {
729 my ($self) = @_;
730 unless ($self->{ssl_out_buffer}) {
731 delete $self->{w};
732 return;
733 }
734
735 my $l = Net::SSLeay::write_nb ($self->{ssl},
736 $self->{ssl_out_buffer}, length ($self->{ssl_out_buffer}));
737
738 if ($l <= 0) {
739 if ($l == 0) {
740 $self->{disconnect_cb}->($self->{host}, $self->{port},
741 "unexpected EOF from server (ssl) '$self->{host}:$self->{port}'");
742 $self->end_sockets;
743 return;
744
745 } else {
746 my $err2 = Net::SSLeay::get_error $self->{ssl}, $l;
747 #d# warn "write err[$err2]\n"; $self->dumpbio;
748 if ($err2 == 2 || $err2 == 3) {
749 delete $self->{w};
750 $self->make_ssl_write_watcher ($err2 == 2 ? 'r' : 'w');
751 return;
752 }
753
754 if ($! != Errno::EGAIN()
755 or my $err = Net::SSLeay::ERR_get_error) {
756
757 $self->{disconnect_cb}->($self->{host}, $self->{port},
758 sprintf (
759 "Error while writing from server '$self->{host}:$self->{port}': (%d|%s|%s)",
760 $err2, (Net::SSLeay::ERR_error_string $err), "$!")
761 );
762 $self->end_sockets;
763 return;
764 }
765 }
766 $self->make_ssl_read_watcher;
767 return;
768 } else { warn "wrote: $l\n" }
769
770 if ($l == length $self->{ssl_out_buffer}) {
771 delete $self->{w};
772 }
773
774 $self->{ssl_out_buffer} = substr $self->{ssl_out_buffer}, $l;
775 }
776
777 sub try_ssl_read {
778 my ($self) = @_;
779 my $l = Net::SSLeay::read_nb ($self->{ssl}, $self->{ssl_read_data});
780
781 if ($l <= 0) {
782 if ($l == 0) {
783 $self->{disconnect_cb}->($self->{host}, $self->{port},
784 "unexpected EOF from server (ssl) '$self->{host}:$self->{port}'");
785 $self->end_sockets;
786 return;
787
788 } else {
789 my $err2 = Net::SSLeay::get_error $self->{ssl}, $l;
790 #d# warn "read err[$err2]\n"; $self->dumpbio;
791 if ($err2 == 2 || $err2 == 3) {
792 delete $self->{r};
793 $self->make_ssl_read_watcher ($err2 == 2 ? 'r' : 'w');
794 return;
795 }
796
797 if ($! != Errno::EGAIN()
798 or my $err = Net::SSLeay::ERR_get_error) {
799
800 $self->{disconnect_cb}->($self->{host}, $self->{port},
801 sprintf (
802 "Error while reading from server '$self->{host}:$self->{port}':"
803 ."(%d|%s|%s)",
804 $err2, (Net::SSLeay::ERR_error_string $err), "$!")
805 );
806 $self->end_sockets;
807 return;
808 }
809 }
810 }
811
812 $self->{read_buffer} .= decode_utf8 ($self->{ssl_read_data});
813 $self->handle_data (\$self->{read_buffer});
814
815 }
816
817 sub make_ssl_read_watcher {
818 my ($self, $poll) = @_;
819 return if $self->{r};
820
821 $poll ||= 'r';
822 $self->{r} =
823 AnyEvent->io (
824 poll => $poll, fh => $self->{socket}, cb => sub {
825 warn "read cb [$poll]\n";
826 $self->try_ssl_read;
827 }
828 );
829 }
830
831 sub make_ssl_write_watcher {
832 my ($self, $poll) = @_;
833 return if $self->{w};
834
835 $poll ||= 'w';
836 $self->{w} =
837 AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
838 warn "write cb [$poll]\n";
839 $self->try_ssl_write;
840 });
841 }
842
843 sub write_data {
844 my ($self, $data) = @_;
845 return unless $self->{r};
846
847 my $cl = $self->{socket};
848 $self->{write_buffer} .= $data;
849
850 unless ($self->{w}) {
851 if (not $self->{ssl_enabled}) {
852 $self->{w} =
853 AnyEvent->io (poll => 'w', fh => $cl, cb => sub {
854 if (my $data = $self->{write_buffer}) {
855 my $len = syswrite $cl, $data;
856 unless ($len) {
857 return if $! == Errno::EGAIN();
858 if (not defined $len) {
859 warn "error when writing data on $self->{host}:$self->{port}: $!";
860 return;
861 } else {
862 delete $self->{w};
863 }
864 }
865
866 if ($len == length $self->{write_buffer}) {
867 delete $self->{w};
868 }
869
870 $self->{write_buffer} = substr $self->{write_buffer}, $len;
871 }
872 });
873
874 } else {
875 unless ($self->{ssl_out_buffer}) {
876 $self->{ssl_out_buffer} = encode_utf8 ($self->{write_buffer});
877 $self->{write_buffer} = "";
878 $self->make_ssl_write_watcher;
879 }
880 }
881 }
882 }
883
884 sub enable_ssl {
885 my ($self) = @_;
886
887 $Net::SSLeay::ssl_version = 10; # Insist on TLSv1
888
889 $self->{ssl_enabled} = 1;
890
891 warn "START TLS!\n";
892
893 $self->{r} = undef;
894 $self->{w} = undef;
895
896 $self->{ctx} = Net::SSLeay::CTX_new ();
897 Net::SSLeay::CTX_set_mode($self->{ctx}, 1);
898 $self->{ssl} = Net::SSLeay::new ($self->{ctx});
899
900 Net::SSLeay::set_fd ($self->{ssl}, fileno $self->{socket});
901 #d# warn "CONNECT\n";
902 Net::SSLeay::connect $self->{ssl};
903 #d# warn "CONNECT END\n";
904 binmode $self->{socket}, ":bytes";
905
906 $self->{ssl_read_data} = "";
907
908 $self->make_ssl_read_watcher;
909 }
910
911 1; # End of Net::XMPP2