ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.6
Committed: Sat Feb 3 11:39:55 2007 UTC (19 years, 7 months ago) by elmex
Branch: MAIN
Changes since 1.5: +41 -11 lines
Log Message:
marked low level events with an _xml. changed documentation a bit.

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