ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.3
Committed: Tue Jan 23 22:57:22 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.2: +9 -11 lines
Log Message:
more testing, fixed typo :-)

File Contents

# User Rev Content
1 elmex 1.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 elmex 1.2 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 elmex 1.1
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 elmex 1.2 } 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 elmex 1.1 } 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 elmex 1.2 my @tls = $node->find_all ([qw/tls starttls/]);
393 elmex 1.1
394 elmex 1.2 if (not ($self->{ssl_enabled}) and @tls) {
395     $self->{writer}->send_starttls;
396    
397     } elsif (not ($self->{authenticated}) and @mechs) {
398 elmex 1.1 $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 elmex 1.2 use Errno;
614 elmex 1.1 use Fcntl;
615 elmex 1.2 use Encode;
616 elmex 1.1
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 elmex 1.2 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 elmex 1.1 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 elmex 1.2 $self->set_noblock;
664 elmex 1.1
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 elmex 1.3 return if $! == Errno::EAGAIN;
673 elmex 1.1 if (defined $l) {
674 elmex 1.2 $self->{disconnect_cb}->($self->{host}, $self->{port}, "EOF from server '$self->{host}:$self->{port}'");
675     $self->end_sockets;
676 elmex 1.1 return;
677    
678     } else {
679 elmex 1.2 $self->{disconnect_cb}->($self->{host}, $self->{port}, "Error while reading from server '$self->{host}:$port': $!");
680     $self->end_sockets;
681 elmex 1.1 return;
682     }
683     }
684 elmex 1.2
685     $self->{read_buffer} .= $data;
686     $self->handle_data (\$self->{read_buffer});
687 elmex 1.1 });
688     return 1;
689     }
690    
691 elmex 1.2 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 elmex 1.3 if ($! != Errno::EAGAIN
755 elmex 1.2 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 elmex 1.3 if ($! != Errno::EAGAIN
798 elmex 1.2 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 elmex 1.3 AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
824     #d# warn "read cb [$poll]\n";
825     $self->try_ssl_read;
826     });
827 elmex 1.2 }
828    
829     sub make_ssl_write_watcher {
830     my ($self, $poll) = @_;
831     return if $self->{w};
832    
833     $poll ||= 'w';
834     $self->{w} =
835     AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
836 elmex 1.3 #d# warn "write cb [$poll]\n";
837 elmex 1.2 $self->try_ssl_write;
838     });
839     }
840    
841 elmex 1.1 sub write_data {
842     my ($self, $data) = @_;
843     return unless $self->{r};
844    
845     my $cl = $self->{socket};
846     $self->{write_buffer} .= $data;
847    
848     unless ($self->{w}) {
849 elmex 1.2 if (not $self->{ssl_enabled}) {
850     $self->{w} =
851     AnyEvent->io (poll => 'w', fh => $cl, cb => sub {
852     if (my $data = $self->{write_buffer}) {
853     my $len = syswrite $cl, $data;
854     unless ($len) {
855 elmex 1.3 return if $! == Errno::EAGAIN;
856 elmex 1.2 if (not defined $len) {
857     warn "error when writing data on $self->{host}:$self->{port}: $!";
858     return;
859     } else {
860     delete $self->{w};
861     }
862     }
863    
864     if ($len == length $self->{write_buffer}) {
865 elmex 1.1 delete $self->{w};
866     }
867    
868 elmex 1.2 $self->{write_buffer} = substr $self->{write_buffer}, $len;
869 elmex 1.1 }
870 elmex 1.2 });
871 elmex 1.1
872 elmex 1.2 } else {
873     unless ($self->{ssl_out_buffer}) {
874     $self->{ssl_out_buffer} = encode_utf8 ($self->{write_buffer});
875     $self->{write_buffer} = "";
876     $self->make_ssl_write_watcher;
877     }
878     }
879 elmex 1.1 }
880     }
881    
882 elmex 1.2 sub enable_ssl {
883     my ($self) = @_;
884    
885     $Net::SSLeay::ssl_version = 10; # Insist on TLSv1
886    
887     $self->{ssl_enabled} = 1;
888    
889     warn "START TLS!\n";
890    
891     $self->{r} = undef;
892     $self->{w} = undef;
893    
894     $self->{ctx} = Net::SSLeay::CTX_new ();
895     Net::SSLeay::CTX_set_mode($self->{ctx}, 1);
896     $self->{ssl} = Net::SSLeay::new ($self->{ctx});
897    
898     Net::SSLeay::set_fd ($self->{ssl}, fileno $self->{socket});
899     #d# warn "CONNECT\n";
900     Net::SSLeay::connect $self->{ssl};
901     #d# warn "CONNECT END\n";
902     binmode $self->{socket}, ":bytes";
903    
904     $self->{ssl_read_data} = "";
905    
906     $self->make_ssl_read_watcher;
907     }
908    
909 elmex 1.1 1; # End of Net::XMPP2