ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/Net-XMPP2/lib/Net/XMPP2/Connection.pm
Revision: 1.7
Committed: Tue Feb 6 22:52:45 2007 UTC (19 years, 7 months ago) by elmex
Branch: MAIN
Changes since 1.6: +7 -1 lines
Log Message:
implemented firts parts of roster handling.
added jid handling functions.

File Contents

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