ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.4
Committed: Sun Jan 28 21:52:19 2007 UTC (19 years, 8 months ago) by elmex
Branch: MAIN
Changes since 1.3: +172 -18 lines
Log Message:
further detail work and implemented extension enabling and extensions xep-0086
along with xml stanza error generation. implemented also first parts
of Net::XMPP2::IM::Connection.

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 elmex 1.4 my $handled;
252 elmex 1.1 for (@{$self->{events}->{lc $ev}}) {
253     $_->($self, @arg) and push @$nxt, $_;
254     }
255    
256     $self->{events}->{lc $ev} = $nxt;
257     }
258    
259     sub handle_stanza {
260     my ($self, $p, $node) = @_;
261    
262     if ($node->eq (stream => 'features')) {
263     $self->event (stream_features => $node);
264     $self->handle_stream_features ($node);
265 elmex 1.4 $self->{features} = $node;
266 elmex 1.2 } elsif ($node->eq (tls => 'proceed')) {
267     $self->enable_ssl;
268     $self->{parser}->init;
269     $self->{writer}->init;
270     $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
271    
272 elmex 1.1 } elsif ($node->eq (sasl => 'challenge')) {
273     $self->handle_sasl_challenge ($node);
274     } elsif ($node->eq (sasl => 'success')) {
275     $self->handle_sasl_success ($node);
276     } elsif ($node->eq (client => 'iq')) {
277     $self->handle_iq ($node);
278 elmex 1.4 } elsif ($node->eq (client => 'message')) {
279     $self->event (message => $node);
280     } elsif ($node->eq (client => 'presence')) {
281     $self->event (presence => $node);
282 elmex 1.1 } elsif ($node->eq (stream => 'error')) {
283     $self->handle_error ($node);
284     } else {
285     warn "Didn't understood stanza: '" . $node->name . "'";
286     }
287     }
288    
289     =head2 init ($domain)
290    
291     Initiate the XML stream.
292    
293     =cut
294    
295     sub init {
296     my ($self) = @_;
297     $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
298     }
299    
300     =head2 send_iq ($type, $create_cb, $result_cb, %attrs)
301    
302     This method sends an IQ XMPP request.
303    
304     Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
305     about the meaning of C<$type>, C<$create_cb> and C<%attrs>.
306    
307 elmex 1.4 C<$result_cb> will be called when a result was received. The first argument to
308     C<$result_cb> will be a Net::XMPP2::Node instance containing the IQ result
309     stanza contents.
310 elmex 1.1
311     If the IQ resulted in a stanza error the second argument to C<$result_cb> will
312     be C<undef> (if the error type was not 'continue') and the third argument will
313     be a Net::XMPP2::Node containg the IQ error stanza. And the fourth argument
314     will be a array reference with following contents:
315    
316 elmex 1.4 This method returns the newly generated id for this iq request.
317    
318 elmex 1.1 =over 4
319    
320     =item index 0: error type
321    
322     This will be one of: 'cancel', 'continue', 'modify', 'auth' and 'wait'.
323    
324     =item index 1: error condition element
325    
326     This might be undefined if other XMPP speakers don't play nice i guess.
327    
328     =item index 2: error text
329    
330     This will be the human readable form of the error which is maybe undef if
331     not supplied.
332    
333     =item index 3: error code
334    
335     If the error element had an 'code' attribute it will be put here,
336     the RFC says that this is for backward compatibility :)
337    
338     =back
339    
340     =cut
341    
342     sub send_iq {
343     my ($self, $type, $create_cb, $result_cb, %attrs) = @_;
344     my $id = $self->{iq_id}++;
345     $self->{iqs}->{$id} = $result_cb;
346     $self->{writer}->send_iq ($id, $type, $create_cb, %attrs);
347 elmex 1.4 $id
348     }
349    
350     =head2 reply_iq_result ($req_iq_node, $create_cb, %attrs)
351    
352     This method will generate a result reply to the iq request C<Net::XMPP2::Node>
353     in C<$req_iq_node>.
354    
355     Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
356     about the meaning C<$create_cb> and C<%attrs>.
357    
358     The type for this iq reply is 'result'.
359    
360     =cut
361    
362     sub reply_iq_result {
363     my ($self, $iqnode, $create_cb, %attrs) = @_;
364     $self->{writer}->send_iq ($iqnode->attr ('id'), 'result', $create_cb, %attrs);
365     }
366    
367     =head2 reply_iq_error ($req_iq_node, $error_type, $error, %attrs)
368    
369     This method will generate an error reply to the iq request C<Net::XMPP2::Node>
370     in C<$req_iq_node>.
371    
372     C<$error_type> is one of 'cancel', 'continue', 'modify', 'auth' and 'wait'.
373     C<$error> is one of the defined error conditions described in
374     L<Net::XMPP2::Writer::write_error_tag>.
375    
376     Please take a look at the documentation for C<send_iq> in Net::XMPP2::Writer
377     about the meaning C<$create_cb> and C<%attrs>.
378    
379     The type for this iq reply is 'error'.
380    
381     =cut
382    
383     sub reply_iq_error {
384     my ($self, $iqnode, $errtype, $error, %attrs) = @_;
385    
386     $self->{writer}->send_iq (
387     $iqnode->attr ('id'), 'error',
388     sub { $self->{writer}->write_error_tag ($iqnode, $errtype, $error) },
389     %attrs
390     );
391 elmex 1.1 }
392    
393     sub handle_iq {
394     my ($self, $node) = @_;
395    
396 elmex 1.4 my $type = $node->attr ('type');
397    
398     if ($type eq 'result') {
399     if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
400 elmex 1.1 $cb->($node);
401     }
402 elmex 1.4 } elsif ($type eq 'error') {
403     if (my $cb = delete $self->{iqs}->{$node->attr ('id')}) {
404 elmex 1.1
405     my $error = $self->filter_error_stanza ($node);
406     $cb->(($error->[0] eq 'continue' ? $node : undef), $node, $error);
407     }
408 elmex 1.4
409     } else {
410     my $handled = 0;
411     $self->event ("iq_${type}_request" => $node, \$handled);
412    
413     my @from;
414     push @from, (to => $node->attr ('from')) if $node->attr ('from');
415    
416     unless ($handled) {
417     $self->reply_iq_error ($node, undef, 'service-unavailable', @from);
418     }
419 elmex 1.1 }
420     }
421    
422     sub filter_error_stanza {
423     my ($self, $node) = @_;
424     my $p = $self->{parser};
425     my @error;
426     my ($err) = $node->find_all ([qw/client error/]);
427     $error[0] = $err->attr ('type');
428     $error[3] = $err->attr ('code');
429     if ($err) {
430     if (my ($txt) = $err->find_all ([qw/stanzas text/])) {
431     $error[2] = $txt->text;
432     }
433     for my $er (
434     qw/bad-request conflict feature-not-implemented forbidden
435     gone internal-server-error item-not-found jid-malformed
436     not-acceptable not-allowed not-authorized payment-required
437     recipient-unavailable redirect registration-required
438     remote-server-not-found remote-server-timeout resource-constraint
439     service-unavailable subscription-required undefined-condition
440     unexpected-request/)
441     {
442     if (my ($el) = $err->find_all ([stanzas => $er])) {
443     $error[1] = $el;
444     last;
445     }
446     }
447     } else {
448     warn "no error element found in error stanza!";
449     }
450     return \@error
451     }
452    
453     sub handle_stream_features {
454     my ($self, $node) = @_;
455     my @mechs = $node->find_all ([qw/sasl mechanisms/], [qw/sasl mechanism/]);
456     my @bind = $node->find_all ([qw/bind bind/]);
457 elmex 1.2 my @tls = $node->find_all ([qw/tls starttls/]);
458 elmex 1.1
459 elmex 1.2 if (not ($self->{ssl_enabled}) and @tls) {
460     $self->{writer}->send_starttls;
461    
462     } elsif (not ($self->{authenticated}) and @mechs) {
463 elmex 1.1 $self->{writer}->send_sasl_auth (
464     (join ' ', map { $_->text } @mechs),
465     $self->{username}, $self->{domain}, $self->{password}
466     );
467    
468     } elsif (@bind) {
469     $self->do_rebind ($self->{resource});
470     }
471     }
472    
473     sub handle_sasl_challenge {
474     my ($self, $node) = @_;
475     $self->{writer}->send_sasl_response ($node->text);
476     }
477    
478     sub handle_sasl_success {
479     my ($self, $node) = @_;
480     $self->{authenticated} = 1;
481     $self->{parser}->init;
482     $self->{writer}->init;
483     $self->{writer}->send_init_stream ($self->{language}, $self->{domain});
484     }
485    
486     sub handle_error {
487     my ($self, $node) = @_;
488     my @txt = $node->find_all ([qw/stream text/]);
489     my $error;
490     for my $er (
491     qw/bad-format bad-namespace-prefix conflict connection-timeout host-gone
492     host-unknown improper-addressing internal-server-error invalid-from
493     invalid-id invalid-namespace invalid-xml not-authorized policy-violation
494     remote-connection-failed resource-constraint restricted-xml
495     see-other-host system-shutdown undefined-condition unsupported-stanza-type
496     unsupported-version xml-not-well-formed/)
497     {
498     for ($node->nodes) {
499     if ($node->eq (streams => $er)) {
500     $error = $_->name;
501     last
502     }
503     }
504     }
505     unless ($error) {
506     warn "got undefined error stanza, trying to find any undefined error...";
507     for ($node->nodes) {
508     if ($node->eq_ns ('streams')) {
509     $error = $node->name;
510     }
511     }
512     }
513     $self->event (stream_error => $error, (@txt ? $txt[0]->text : ''));
514     $self->{writer}->send_end_of_stream;
515     }
516    
517 elmex 1.4 =head2 send_presence ($type, $create_cb, %attrs)
518    
519     This method sends a presence stanza, for the meanings
520     of C<$type>, C<$create_cb> and C<%attrs> please take a look
521     at the documentation for L<Net::XMPP2::Writer::send_presence>.
522    
523     This methods does attach an id attribute to the message stanza and
524     will return the id that was used (so you can react on possible replies).
525    
526     =cut
527    
528     sub send_presence {
529     my ($self, $type, $create_cb, %attrs) = @_;
530     my $id = $self->{iq_id}++;
531     $self->{writer}->send_presence ($id, $type, $create_cb, %attrs);
532     $id
533     }
534    
535     =head2 send_message ($to, $type, $create_cb, %attrs)
536    
537     This method sends a presence stanza, for the meanings
538     of C<$to>, C<$type>, C<$create_cb> and C<%attrs> please take a look
539     at the documentation for L<Net::XMPP2::Writer::send_message>.
540    
541     This methods does attach an id attribute to the message stanza and
542     will return the id that was used (so you can react on possible replies).
543    
544     =cut
545    
546     sub send_message {
547     my ($self, $to, $type, $create_cb, %attrs) = @_;
548     my $id = $self->{iq_id}++;
549     $self->{writer}->send_message ($id, $to, $type, $create_cb, %attrs);
550     $id
551     }
552    
553 elmex 1.1 =head2 do_rebind ($resource)
554    
555     In case you got a C<bind_error> event and want to retry
556     binding you can call this function to set a new C<$resource>
557     and retry binding.
558    
559     If it fails again you can call this again. Becareful not to
560     end up in a loop!
561    
562     If binding was successful the C<stream_ready> event will be generated.
563    
564     =cut
565    
566     sub do_rebind {
567     my ($self, $resource) = @_;
568     $self->{resource} = $resource;
569     $self->send_iq (
570     set =>
571     sub {
572     my ($w) = @_;
573     if ($self->{resource}) {
574     $w->startTag ([xmpp_ns ('bind'), 'bind']);
575     $w->startTag ([xmpp_ns ('bind'), 'resource']);
576     $w->characters ($self->{resource});
577     $w->endTag;
578     $w->endTag;
579     } else {
580     $w->emptyTag ([xmpp_ns ('bind'), 'bind'])
581     }
582     },
583     sub {
584     my ($ret_iq, $err_iq, $err) = @_;
585    
586     if ($err) {
587     my ($res) = $err_iq->find_all ([qw/bind bind/], [qw/bind resource/]);
588     $self->event (bind_error => $err->[0], ($res ? $res : $self->{resource}));
589    
590     } else {
591     my @jid = $ret_iq->find_all ([qw/bind bind/], [qw/bind jid/]);
592     my $jid = $jid[0]->text;
593     unless ($jid) { die "Got empty JID tag from server!\n" }
594     $self->{jid} = $jid;
595    
596     $self->event (stream_ready => $jid);
597     }
598     }
599     );
600     }
601    
602     =head2 jid
603    
604     After the stream has been bound to a resource the JID can be retrieved via this
605     method.
606    
607     =cut
608    
609     sub jid { $_[0]->{jid} }
610    
611 elmex 1.4 =head2 features
612    
613     Returns the last received <features> tag in form of an L<Net::XMPP2::Node> object.
614    
615     =cut
616    
617     sub features { $_[0]->{features} }
618    
619     #sub enable_extension {
620     # my ($self, @exts) = @_;
621     # for (@exts) {
622     # if (/^xep-(\d+)$/i) {
623     # $self->{ext}->{''.(1*$1)} = 1;
624     # }
625     # }
626     #}
627     #
628     #sub check_extension {
629     # my ($self, $extnum) = @_;
630     # return $self->{ext}->{"$extnum"} || $Net::XMPP2::EXTENSION_ENABLED{"$extnum"};
631     #}
632    
633 elmex 1.1 =head1 EVENTS
634    
635     These events can be registered on with C<reg_cb>:
636    
637     =over 4
638    
639     =item stream_features => $node
640    
641 elmex 1.4 This event is sent when a stream feature (<features>) tag is received. C<$node> is the
642     L<Net::XMPP2::Node> object that represents the <features> tag.
643 elmex 1.1
644     =item stream_ready => $jid
645    
646     This event is sent if the XML stream has been established (and
647     resources have been bound) and is ready for transmitting regular stanzas.
648    
649     C<$jid> is the bound jabber id.
650    
651     =item bind_error => $error_name, $resource
652    
653     This event is generated when the stream was unable to bind to
654     any or the in C<new> specified resource. C<$error_name>
655     may be 'bad-request', 'not-allowed' or 'conflict'.
656    
657     Node: this is untested, i couldn't get the server to send a bind error
658     to test this.
659    
660     =item connect => $host, $port
661    
662     This event is generated when a successful connect was performed to
663     the domain passed to C<new>.
664    
665     Note: C<$host> and C<$port> might be different from the domain you passed to
666     C<new> if C<connect> performed a SRV RR lookup.
667    
668     If this connection is lost a C<disconnect> will be generated with the same
669     C<$host> and C<$port>.
670    
671     =item disconnect => $host, $port, $message
672    
673     This event is generated when the connection was lost or another error
674     occured while writing or reading from it.
675    
676     C<$message> is a humand readable error message for the failure.
677     C<$host> and C<$port> were the host and port we were connected to.
678    
679     Note: C<$host> and C<$port> might be different from the domain you passed to
680     C<new> if C<connect> performed a SRV RR lookup.
681    
682 elmex 1.4 =item presence => $node
683    
684     This event is sent when a presence stanza is received. C<$node> is the
685     L<Net::XMPP2::Node> object that represents the <presence> tag.
686    
687     =item message => $node
688    
689     This event is sent when a message stanza is received. C<$node> is the
690     L<Net::XMPP2::Node> object that represents the <message> tag.
691    
692     =item iq_set_request => $node, $handled_ref
693    
694     =item iq_get_request => $node, $handled_ref
695    
696     These events are sent when an iq request stanza of type 'get' or 'set' is received.
697     C<$type> will either be 'get' or 'set' and C<$node> will be the L<Net::XMPP2::Node>
698     object of the iq tag.
699    
700     If C<$$handled_ref> is true an event handler should not handle this message anymore.
701    
702     If one of the event handlers handled this message the scalar pointed at by
703     the reference in C<$handled_ref> should be set to 1 true value. If C<$$handled_ref>
704     is still false after all event handlers were executed an error iq will be generated.
705    
706 elmex 1.1 =back
707    
708     =head1 AUTHOR
709    
710     Robin Redeker, C<< <elmex at ta-sa.org> >>
711    
712     =head1 BUGS
713    
714     Please report any bugs or feature requests to
715     C<bug-net-xmpp2 at rt.cpan.org>, or through the web interface at
716     L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Net-XMPP2>.
717     I will be notified, and then you'll automatically be notified of progress on
718     your bug as I make changes.
719    
720     =head1 SUPPORT
721    
722     You can find documentation for this module with the perldoc command.
723    
724     perldoc Net::XMPP2
725    
726     You can also look for information at:
727    
728     =over 4
729    
730     =item * AnnoCPAN: Annotated CPAN documentation
731    
732     L<http://annocpan.org/dist/Net-XMPP2>
733    
734     =item * CPAN Ratings
735    
736     L<http://cpanratings.perl.org/d/Net-XMPP2>
737    
738     =item * RT: CPAN's request tracker
739    
740     L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Net-XMPP2>
741    
742     =item * Search CPAN
743    
744     L<http://search.cpan.org/dist/Net-XMPP2>
745    
746     =back
747    
748     =head1 ACKNOWLEDGEMENTS
749    
750     =head1 COPYRIGHT & LICENSE
751    
752     Copyright 2007 Robin Redeker, all rights reserved.
753    
754     This program is free software; you can redistribute it and/or modify it
755     under the same terms as Perl itself.
756    
757     =cut
758    
759     package Net::XMPP2::SimpleConnection;
760     use IO::Socket::INET;
761 elmex 1.2 use Errno;
762 elmex 1.1 use Fcntl;
763 elmex 1.2 use Encode;
764 elmex 1.1
765     sub new {
766     my $this = shift;
767     my $class = ref($this) || $this;
768     my $self = { disconnect_cb => sub {}, @_ };
769     bless $self, $class;
770     return $self;
771     }
772    
773 elmex 1.2 sub set_block {
774     my ($self) = @_;
775     my $flags = 0;
776     fcntl($self->{socket}, F_GETFL, $flags)
777     or die "Couldn't get flags for HANDLE : $!\n";
778     $flags &= ~O_NONBLOCK;
779     fcntl($self->{socket}, F_SETFL, $flags)
780     or die "Couldn't set flags for HANDLE: $!\n";
781     }
782    
783     sub set_noblock {
784     my ($self) = @_;
785     my $flags = 0;
786     fcntl($self->{socket}, F_GETFL, $flags)
787     or die "Couldn't get flags for HANDLE : $!\n";
788     $flags |= O_NONBLOCK;
789     fcntl($self->{socket}, F_SETFL, $flags)
790     or die "Couldn't set flags for HANDLE: $!\n";
791     }
792    
793 elmex 1.1 sub connect {
794     my ($self, $host, $port) = @_;
795    
796     $self->{socket}
797     and return 1;
798    
799     my $sock = IO::Socket::INET->new (
800     PeerAddr => $host,
801     PeerPort => $port,
802     Proto => 'tcp',
803     Blocking => 1
804     );
805     return undef unless $sock;;
806    
807     $self->{socket} = $sock;
808     $self->{host} = $host;
809     $self->{port} = $port;
810    
811 elmex 1.2 $self->set_noblock;
812 elmex 1.1
813     binmode $sock, ":utf8";
814    
815     $self->{r} =
816     AnyEvent->io (poll => 'r', fh => $sock, cb => sub {
817     my $l = sysread $sock, my $data, 1024;
818    
819 elmex 1.4 if ($l) {
820     $self->{read_buffer} .= $data;
821     $self->handle_data (\$self->{read_buffer});
822    
823     } else {
824 elmex 1.3 return if $! == Errno::EAGAIN;
825 elmex 1.1 if (defined $l) {
826 elmex 1.2 $self->{disconnect_cb}->($self->{host}, $self->{port}, "EOF from server '$self->{host}:$self->{port}'");
827     $self->end_sockets;
828 elmex 1.1 return;
829    
830     } else {
831 elmex 1.2 $self->{disconnect_cb}->($self->{host}, $self->{port}, "Error while reading from server '$self->{host}:$port': $!");
832     $self->end_sockets;
833 elmex 1.1 return;
834     }
835     }
836     });
837     return 1;
838     }
839    
840 elmex 1.2 sub end_sockets {
841     my ($self) = @_;
842     delete $self->{r};
843     delete $self->{w};
844     delete $self->{socket};
845 elmex 1.4 if (delete $self->{ssl_enabled}) {
846     Net::SSLeay::free ($self->{ssl});
847     delete $self->{ssl};
848     Net::SSLeay::CTX_free ($self->{ctx});
849     delete $self->{ctx};
850     }
851 elmex 1.2 }
852    
853     sub dumpbio {
854     my ($self) = @_;
855    
856     print "er: ".Net::SSLeay::BIO_should_retry (Net::SSLeay::get_rbio ($self->{ssl}));
857     print " ew: ".Net::SSLeay::BIO_should_retry (Net::SSLeay::get_wbio ($self->{ssl}));
858     print " rr: ".Net::SSLeay::BIO_should_read (Net::SSLeay::get_rbio ($self->{ssl}));
859     print " rw: ".Net::SSLeay::BIO_should_read (Net::SSLeay::get_wbio ($self->{ssl}));
860     print " wr: ".Net::SSLeay::BIO_should_write (Net::SSLeay::get_rbio ($self->{ssl}));
861     print " ww: ".Net::SSLeay::BIO_should_write (Net::SSLeay::get_wbio ($self->{ssl}))."\n";
862    
863     my $e = Net::SSLeay::BIO_should_retry (Net::SSLeay::get_wbio ($self->{ssl}))
864     | Net::SSLeay::BIO_should_retry (Net::SSLeay::get_wbio ($self->{ssl}));
865     my $w = Net::SSLeay::BIO_should_read (Net::SSLeay::get_wbio ($self->{ssl}))
866     | Net::SSLeay::BIO_should_write (Net::SSLeay::get_wbio ($self->{ssl}));
867     my $r = Net::SSLeay::BIO_should_read (Net::SSLeay::get_rbio ($self->{ssl}))
868     | Net::SSLeay::BIO_should_write (Net::SSLeay::get_rbio ($self->{ssl}));
869    
870     print "TEST:$e $w $r\n";
871     # delete $self->{r};
872     # delete $self->{w};
873     # if ($w) { $self->make_ssl_write_watcher }
874     # if ($r) { $self->make_ssl_read_watcher }
875     # unless ($e) {
876     # $self->make_ssl_read_watcher;
877     # $self->make_ssl_write_watcher;
878     # }
879     }
880    
881     sub try_ssl_write {
882     my ($self) = @_;
883     unless ($self->{ssl_out_buffer}) {
884     delete $self->{w};
885     return;
886     }
887    
888     my $l = Net::SSLeay::write_nb ($self->{ssl},
889     $self->{ssl_out_buffer}, length ($self->{ssl_out_buffer}));
890    
891     if ($l <= 0) {
892     if ($l == 0) {
893     $self->{disconnect_cb}->($self->{host}, $self->{port},
894     "unexpected EOF from server (ssl) '$self->{host}:$self->{port}'");
895     $self->end_sockets;
896     return;
897    
898     } else {
899     my $err2 = Net::SSLeay::get_error $self->{ssl}, $l;
900     #d# warn "write err[$err2]\n"; $self->dumpbio;
901     if ($err2 == 2 || $err2 == 3) {
902     delete $self->{w};
903     $self->make_ssl_write_watcher ($err2 == 2 ? 'r' : 'w');
904     return;
905     }
906    
907 elmex 1.3 if ($! != Errno::EAGAIN
908 elmex 1.2 or my $err = Net::SSLeay::ERR_get_error) {
909    
910     $self->{disconnect_cb}->($self->{host}, $self->{port},
911     sprintf (
912     "Error while writing from server '$self->{host}:$self->{port}': (%d|%s|%s)",
913     $err2, (Net::SSLeay::ERR_error_string $err), "$!")
914     );
915     $self->end_sockets;
916     return;
917     }
918     }
919     $self->make_ssl_read_watcher;
920     return;
921 elmex 1.4 } #d# else { warn "wrote: $l\n" }
922 elmex 1.2
923     if ($l == length $self->{ssl_out_buffer}) {
924     delete $self->{w};
925     }
926    
927     $self->{ssl_out_buffer} = substr $self->{ssl_out_buffer}, $l;
928     }
929    
930     sub try_ssl_read {
931     my ($self) = @_;
932     my $l = Net::SSLeay::read_nb ($self->{ssl}, $self->{ssl_read_data});
933    
934     if ($l <= 0) {
935     if ($l == 0) {
936     $self->{disconnect_cb}->($self->{host}, $self->{port},
937     "unexpected EOF from server (ssl) '$self->{host}:$self->{port}'");
938     $self->end_sockets;
939     return;
940    
941     } else {
942     my $err2 = Net::SSLeay::get_error $self->{ssl}, $l;
943     #d# warn "read err[$err2]\n"; $self->dumpbio;
944     if ($err2 == 2 || $err2 == 3) {
945     delete $self->{r};
946     $self->make_ssl_read_watcher ($err2 == 2 ? 'r' : 'w');
947     return;
948     }
949    
950 elmex 1.3 if ($! != Errno::EAGAIN
951 elmex 1.2 or my $err = Net::SSLeay::ERR_get_error) {
952    
953     $self->{disconnect_cb}->($self->{host}, $self->{port},
954     sprintf (
955     "Error while reading from server '$self->{host}:$self->{port}':"
956     ."(%d|%s|%s)",
957     $err2, (Net::SSLeay::ERR_error_string $err), "$!")
958     );
959     $self->end_sockets;
960     return;
961     }
962     }
963 elmex 1.4 } else {
964     $self->{read_buffer} .= decode_utf8 ($self->{ssl_read_data});
965     $self->handle_data (\$self->{read_buffer});
966     $self->{ssl_read_data} = "";
967 elmex 1.2 }
968    
969     }
970    
971     sub make_ssl_read_watcher {
972     my ($self, $poll) = @_;
973     return if $self->{r};
974    
975     $poll ||= 'r';
976     $self->{r} =
977 elmex 1.3 AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
978     #d# warn "read cb [$poll]\n";
979     $self->try_ssl_read;
980     });
981 elmex 1.2 }
982    
983     sub make_ssl_write_watcher {
984     my ($self, $poll) = @_;
985     return if $self->{w};
986    
987     $poll ||= 'w';
988     $self->{w} =
989     AnyEvent->io (poll => $poll, fh => $self->{socket}, cb => sub {
990 elmex 1.3 #d# warn "write cb [$poll]\n";
991 elmex 1.2 $self->try_ssl_write;
992     });
993     }
994    
995 elmex 1.1 sub write_data {
996     my ($self, $data) = @_;
997     return unless $self->{r};
998    
999     my $cl = $self->{socket};
1000     $self->{write_buffer} .= $data;
1001    
1002     unless ($self->{w}) {
1003 elmex 1.2 if (not $self->{ssl_enabled}) {
1004     $self->{w} =
1005     AnyEvent->io (poll => 'w', fh => $cl, cb => sub {
1006     if (my $data = $self->{write_buffer}) {
1007     my $len = syswrite $cl, $data;
1008     unless ($len) {
1009 elmex 1.3 return if $! == Errno::EAGAIN;
1010 elmex 1.2 if (not defined $len) {
1011     warn "error when writing data on $self->{host}:$self->{port}: $!";
1012     return;
1013     } else {
1014     delete $self->{w};
1015     }
1016     }
1017    
1018     if ($len == length $self->{write_buffer}) {
1019 elmex 1.1 delete $self->{w};
1020     }
1021    
1022 elmex 1.2 $self->{write_buffer} = substr $self->{write_buffer}, $len;
1023 elmex 1.1 }
1024 elmex 1.2 });
1025 elmex 1.1
1026 elmex 1.2 } else {
1027     unless ($self->{ssl_out_buffer}) {
1028     $self->{ssl_out_buffer} = encode_utf8 ($self->{write_buffer});
1029     $self->{write_buffer} = "";
1030     $self->make_ssl_write_watcher;
1031     }
1032     }
1033 elmex 1.1 }
1034     }
1035    
1036 elmex 1.2 sub enable_ssl {
1037     my ($self) = @_;
1038    
1039     $Net::SSLeay::ssl_version = 10; # Insist on TLSv1
1040    
1041     $self->{ssl_enabled} = 1;
1042    
1043     warn "START TLS!\n";
1044    
1045     $self->{r} = undef;
1046     $self->{w} = undef;
1047    
1048     $self->{ctx} = Net::SSLeay::CTX_new ();
1049     Net::SSLeay::CTX_set_mode($self->{ctx}, 1);
1050     $self->{ssl} = Net::SSLeay::new ($self->{ctx});
1051    
1052     Net::SSLeay::set_fd ($self->{ssl}, fileno $self->{socket});
1053     #d# warn "CONNECT\n";
1054     Net::SSLeay::connect $self->{ssl};
1055     #d# warn "CONNECT END\n";
1056     binmode $self->{socket}, ":bytes";
1057    
1058     $self->{ssl_read_data} = "";
1059    
1060     $self->make_ssl_read_watcher;
1061     }
1062    
1063 elmex 1.1 1; # End of Net::XMPP2